зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1386825 - Part 6: Remove FxAccountsManager now that MOZ_B2G is removed. r=markh
MozReview-Commit-ID: 8ldrMrPqhsV
This commit is contained in:
Родитель
fb60453284
Коммит
7eae341db9
|
@ -1,644 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Temporary abstraction layer for common Fx Accounts operations.
|
||||
* For now, we will be using this module only from B2G but in the end we might
|
||||
* want this to be merged with FxAccounts.jsm and let other products also use
|
||||
* it.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["FxAccountsManager"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccounts.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "permissionManager",
|
||||
"@mozilla.org/permissionmanager;1",
|
||||
"nsIPermissionManager");
|
||||
|
||||
this.FxAccountsManager = {
|
||||
|
||||
init() {
|
||||
Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION);
|
||||
Services.obs.addObserver(this, ON_FXA_UPDATE_NOTIFICATION);
|
||||
},
|
||||
|
||||
observe(aSubject, aTopic, aData) {
|
||||
// Both topics indicate our cache is invalid
|
||||
this._activeSession = null;
|
||||
|
||||
if (aData == ONVERIFIED_NOTIFICATION) {
|
||||
log.debug("FxAccountsManager: cache cleared, broadcasting: " + aData);
|
||||
Services.obs.notifyObservers(null, aData);
|
||||
}
|
||||
},
|
||||
|
||||
// We don't really need to save fxAccounts instance but this way we allow
|
||||
// to mock FxAccounts from tests.
|
||||
_fxAccounts: fxAccounts,
|
||||
|
||||
// We keep the session details here so consumers don't need to deal with
|
||||
// session tokens and are only required to handle the email.
|
||||
_activeSession: null,
|
||||
|
||||
// Are we refreshing our authentication? If so, allow attempts to sign in
|
||||
// while we are already signed in.
|
||||
_refreshing: false,
|
||||
|
||||
// We only expose the email and the verified status so far.
|
||||
get _user() {
|
||||
if (!this._activeSession || !this._activeSession.email) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
email: this._activeSession.email,
|
||||
verified: this._activeSession.verified,
|
||||
profile: this._activeSession.profile,
|
||||
}
|
||||
},
|
||||
|
||||
_error(aError, aDetails) {
|
||||
log.error(aError);
|
||||
let reason = {
|
||||
error: aError
|
||||
};
|
||||
if (aDetails) {
|
||||
reason.details = aDetails;
|
||||
}
|
||||
return Promise.reject(reason);
|
||||
},
|
||||
|
||||
_getError(aServerResponse) {
|
||||
if (!aServerResponse || !aServerResponse.error || !aServerResponse.error.errno) {
|
||||
return null;
|
||||
}
|
||||
let error = SERVER_ERRNO_TO_ERROR[aServerResponse.error.errno];
|
||||
return error;
|
||||
},
|
||||
|
||||
_serverError(aServerResponse) {
|
||||
let error = this._getError({ error: aServerResponse });
|
||||
return this._error(error ? error : ERROR_SERVER_ERROR, aServerResponse);
|
||||
},
|
||||
|
||||
// As with _fxAccounts, we don't really need this method, but this way we
|
||||
// allow tests to mock FxAccountsClient. By default, we want to return the
|
||||
// client used by the fxAccounts object because deep down they should have
|
||||
// access to the same hawk request object which will enable them to share
|
||||
// local clock skeq data.
|
||||
_getFxAccountsClient() {
|
||||
return this._fxAccounts.getAccountsClient();
|
||||
},
|
||||
|
||||
_signInSignUp(aMethod, aEmail, aPassword, aFetchKeys) {
|
||||
if (Services.io.offline) {
|
||||
return this._error(ERROR_OFFLINE);
|
||||
}
|
||||
|
||||
if (!aEmail) {
|
||||
return this._error(ERROR_INVALID_EMAIL);
|
||||
}
|
||||
|
||||
if (!aPassword) {
|
||||
return this._error(ERROR_INVALID_PASSWORD);
|
||||
}
|
||||
|
||||
// Check that there is no signed in account first.
|
||||
if ((!this._refreshing) && this._activeSession) {
|
||||
return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
|
||||
user: this._user
|
||||
});
|
||||
}
|
||||
|
||||
let client = this._getFxAccountsClient();
|
||||
return this._fxAccounts.getSignedInUser().then(
|
||||
user => {
|
||||
if ((!this._refreshing) && user) {
|
||||
return this._error(ERROR_ALREADY_SIGNED_IN_USER, {
|
||||
user: this._user
|
||||
});
|
||||
}
|
||||
return client[aMethod](aEmail, aPassword, aFetchKeys);
|
||||
}
|
||||
).then(
|
||||
user => {
|
||||
let error = this._getError(user);
|
||||
if (!user || !user.uid || !user.sessionToken || error) {
|
||||
return this._error(error ? error : ERROR_INTERNAL_INVALID_USER, {
|
||||
user
|
||||
});
|
||||
}
|
||||
|
||||
// If the user object includes an email field, it may differ in
|
||||
// capitalization from what we sent down. This is the server's
|
||||
// canonical capitalization and should be used instead.
|
||||
user.email = user.email || aEmail;
|
||||
|
||||
// If we're using server-side sign to refreshAuthentication
|
||||
// we don't need to update local state; also because of two
|
||||
// interacting glitches we need to bypass an event emission.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1031580
|
||||
if (this._refreshing) {
|
||||
return Promise.resolve({user: this._user});
|
||||
}
|
||||
|
||||
return this._fxAccounts.setSignedInUser(user).then(
|
||||
() => {
|
||||
this._activeSession = user;
|
||||
log.debug("User signed in: " + JSON.stringify(this._user) +
|
||||
" - Account created " + (aMethod == "signUp"));
|
||||
|
||||
// There is no way to obtain the key fetch token afterwards
|
||||
// without login out the user and asking her to log in again.
|
||||
// Also, key fetch tokens are designed to be short-lived, so
|
||||
// we need to fetch kB as soon as we have the key fetch token.
|
||||
if (aFetchKeys) {
|
||||
this._fxAccounts.getKeys();
|
||||
}
|
||||
|
||||
return this._fxAccounts.getSignedInUserProfile().catch(error => {
|
||||
// Not fetching the profile is sad but the FxA logs will already
|
||||
// have noise.
|
||||
return null;
|
||||
});
|
||||
}
|
||||
).then(profile => {
|
||||
if (profile) {
|
||||
this._activeSession.profile = profile;
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
accountCreated: aMethod === "signUp",
|
||||
user: this._user
|
||||
});
|
||||
});
|
||||
},
|
||||
reason => { return this._serverError(reason); }
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine whether the incoming error means that the current account
|
||||
* has new server-side state via deletion or password change, and if so,
|
||||
* spawn the appropriate UI (sign in or refresh); otherwise re-reject.
|
||||
*
|
||||
* As of May 2014, the only HTTP call triggered by this._getAssertion()
|
||||
* is to /certificate/sign via:
|
||||
* FxAccounts.getAssertion()
|
||||
* FxAccountsInternal.getCertificateSigned()
|
||||
* FxAccountsClient.signCertificate()
|
||||
* See the latter method for possible (error code, errno) pairs.
|
||||
*/
|
||||
_handleGetAssertionError(reason, aAudience, aPrincipal) {
|
||||
log.debug("FxAccountsManager._handleGetAssertionError()");
|
||||
let errno = (reason ? reason.errno : NaN) || NaN;
|
||||
// If the previously valid email/password pair is no longer valid ...
|
||||
if (errno == ERRNO_INVALID_AUTH_TOKEN) {
|
||||
return this._fxAccounts.accountStatus().then(
|
||||
(exists) => {
|
||||
// ... if the email still maps to an account, the password
|
||||
// must have changed, so ask the user to enter the new one ...
|
||||
if (exists) {
|
||||
return this.getAccount().then(
|
||||
(user) => {
|
||||
return this._refreshAuthentication(aAudience, user.email,
|
||||
aPrincipal,
|
||||
true /* logoutOnFailure */);
|
||||
}
|
||||
);
|
||||
}
|
||||
// ... otherwise, the account was deleted, so ask for Sign In/Up
|
||||
return this._localSignOut().then(
|
||||
() => {
|
||||
return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience,
|
||||
aPrincipal);
|
||||
},
|
||||
(reason) => {
|
||||
// reject primary problem, not signout failure
|
||||
log.error("Signing out in response to server error threw: " +
|
||||
reason);
|
||||
return this._error(reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
return Promise.reject(reason.message ? { error: reason.message } : reason);
|
||||
},
|
||||
|
||||
_getAssertion(aAudience, aPrincipal) {
|
||||
return this._fxAccounts.getAssertion(aAudience).then(
|
||||
(result) => {
|
||||
if (aPrincipal) {
|
||||
this._addPermission(aPrincipal);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
(reason) => {
|
||||
return this._handleGetAssertionError(reason, aAudience, aPrincipal);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* "Refresh authentication" means:
|
||||
* Interactively demonstrate knowledge of the FxA password
|
||||
* for the currently logged-in account.
|
||||
* There are two very different scenarios:
|
||||
* 1) The password has changed on the server. Failure should log
|
||||
* the current account OUT.
|
||||
* 2) The person typing can't prove knowledge of the password used
|
||||
* to log in. Failure should do nothing.
|
||||
*/
|
||||
_refreshAuthentication(aAudience, aEmail, aPrincipal,
|
||||
logoutOnFailure = false) {
|
||||
this._refreshing = true;
|
||||
return this._uiRequest(UI_REQUEST_REFRESH_AUTH,
|
||||
aAudience, aPrincipal, aEmail).then(
|
||||
(assertion) => {
|
||||
this._refreshing = false;
|
||||
return assertion;
|
||||
},
|
||||
(reason) => {
|
||||
this._refreshing = false;
|
||||
if (logoutOnFailure) {
|
||||
return this._signOut().then(
|
||||
() => {
|
||||
return this._error(reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
return this._error(reason);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
_localSignOut() {
|
||||
return this._fxAccounts.signOut(true);
|
||||
},
|
||||
|
||||
_signOut() {
|
||||
if (!this._activeSession) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// We clear the local session cache as soon as we get the onlogout
|
||||
// notification triggered within FxAccounts.signOut, so we save the
|
||||
// session token value to be able to remove the remote server session
|
||||
// in case that we have network connection.
|
||||
let sessionToken = this._activeSession.sessionToken;
|
||||
|
||||
return this._localSignOut().then(
|
||||
() => {
|
||||
// At this point the local session should already be removed.
|
||||
|
||||
// The client can create new sessions up to the limit (100?).
|
||||
// Orphaned tokens on the server will eventually be garbage collected.
|
||||
if (Services.io.offline) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// Otherwise, we try to remove the remote session.
|
||||
let client = this._getFxAccountsClient();
|
||||
return client.signOut(sessionToken).then(
|
||||
result => {
|
||||
let error = this._getError(result);
|
||||
if (error) {
|
||||
return this._error(error, result);
|
||||
}
|
||||
log.debug("Signed out");
|
||||
return Promise.resolve();
|
||||
},
|
||||
reason => {
|
||||
return this._serverError(reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
_uiRequest(aRequest, aAudience, aPrincipal, aParams) {
|
||||
if (Services.io.offline) {
|
||||
return this._error(ERROR_OFFLINE);
|
||||
}
|
||||
let ui = Cc["@mozilla.org/fxaccounts/fxaccounts-ui-glue;1"]
|
||||
.createInstance(Ci.nsIFxAccountsUIGlue);
|
||||
if (!ui[aRequest]) {
|
||||
return this._error(ERROR_UI_REQUEST);
|
||||
}
|
||||
|
||||
if (!aParams || !Array.isArray(aParams)) {
|
||||
aParams = [aParams];
|
||||
}
|
||||
|
||||
return ui[aRequest].apply(this, aParams).then(
|
||||
result => {
|
||||
// Even if we get a successful result from the UI, the account will
|
||||
// most likely be unverified, so we cannot get an assertion.
|
||||
if (result && result.verified) {
|
||||
return this._getAssertion(aAudience, aPrincipal);
|
||||
}
|
||||
|
||||
return this._error(ERROR_UNVERIFIED_ACCOUNT, {
|
||||
user: result
|
||||
});
|
||||
},
|
||||
error => {
|
||||
return this._error(ERROR_UI_ERROR, error);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
_addPermission(aPrincipal) {
|
||||
// This will fail from tests cause we are running them in the child
|
||||
// process until we have chrome tests in b2g. Bug 797164.
|
||||
try {
|
||||
permissionManager.addFromPrincipal(aPrincipal, FXACCOUNTS_PERMISSION,
|
||||
Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
} catch (e) {
|
||||
log.warn("Could not add permission " + e);
|
||||
}
|
||||
},
|
||||
|
||||
// -- API --
|
||||
|
||||
signIn(aEmail, aPassword, aFetchKeys) {
|
||||
return this._signInSignUp("signIn", aEmail, aPassword, aFetchKeys);
|
||||
},
|
||||
|
||||
signUp(aEmail, aPassword, aFetchKeys) {
|
||||
return this._signInSignUp("signUp", aEmail, aPassword, aFetchKeys);
|
||||
},
|
||||
|
||||
signOut() {
|
||||
if (!this._activeSession) {
|
||||
// If there is no cached active session, we try to get it from the
|
||||
// account storage.
|
||||
return this.getAccount().then(
|
||||
result => {
|
||||
if (!result) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._signOut();
|
||||
}
|
||||
);
|
||||
}
|
||||
return this._signOut();
|
||||
},
|
||||
|
||||
resendVerificationEmail() {
|
||||
return this._fxAccounts.resendVerificationEmail().then(
|
||||
(result) => {
|
||||
return result;
|
||||
},
|
||||
(error) => {
|
||||
return this._error(ERROR_SERVER_ERROR, error);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
getAccount() {
|
||||
// We check first if we have session details cached.
|
||||
if (this._activeSession) {
|
||||
// If our cache says that the account is not yet verified,
|
||||
// we kick off verification before returning what we have.
|
||||
if (!this._activeSession.verified) {
|
||||
this.verificationStatus(this._activeSession);
|
||||
}
|
||||
log.debug("Account " + JSON.stringify(this._user));
|
||||
return Promise.resolve(this._user);
|
||||
}
|
||||
|
||||
// If no cached information, we try to get it from the persistent storage.
|
||||
return this._fxAccounts.getSignedInUser().then(
|
||||
user => {
|
||||
if (!user || !user.email) {
|
||||
log.debug("No signed in account");
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
this._activeSession = user;
|
||||
// If we get a stored information of a not yet verified account,
|
||||
// we kick off verification before returning what we have.
|
||||
if (!user.verified) {
|
||||
this.verificationStatus(user);
|
||||
// Trying to get the profile for unverified users will fail, so we
|
||||
// don't even try in that case.
|
||||
log.debug("Account ", this._user);
|
||||
return Promise.resolve(this._user);
|
||||
}
|
||||
|
||||
return this._fxAccounts.getSignedInUserProfile().then(profile => {
|
||||
if (profile) {
|
||||
this._activeSession.profile = profile;
|
||||
}
|
||||
log.debug("Account ", this._user);
|
||||
return Promise.resolve(this._user);
|
||||
}).catch(error => {
|
||||
// FxAccounts logs already inform about the error.
|
||||
log.debug("Account ", this._user);
|
||||
return Promise.resolve(this._user);
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
queryAccount(aEmail) {
|
||||
log.debug("queryAccount " + aEmail);
|
||||
if (Services.io.offline) {
|
||||
return this._error(ERROR_OFFLINE);
|
||||
}
|
||||
|
||||
if (!aEmail) {
|
||||
return this._error(ERROR_INVALID_EMAIL);
|
||||
}
|
||||
|
||||
let client = this._getFxAccountsClient();
|
||||
return client.accountExists(aEmail).then(
|
||||
result => {
|
||||
log.debug("Account " + (result ? "" : "does not ") + "exists");
|
||||
let error = this._getError(result);
|
||||
if (error) {
|
||||
return this._error(error, result);
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
registered: result
|
||||
});
|
||||
},
|
||||
reason => { this._serverError(reason); }
|
||||
);
|
||||
},
|
||||
|
||||
verificationStatus() {
|
||||
log.debug("verificationStatus");
|
||||
if (!this._activeSession || !this._activeSession.sessionToken) {
|
||||
this._error(ERROR_NO_TOKEN_SESSION);
|
||||
}
|
||||
|
||||
// There is no way to unverify an already verified account, so we just
|
||||
// return the account details of a verified account
|
||||
if (this._activeSession.verified) {
|
||||
log.debug("Account already verified");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Services.io.offline) {
|
||||
log.warn("Offline; skipping verification.");
|
||||
return;
|
||||
}
|
||||
|
||||
let client = this._getFxAccountsClient();
|
||||
client.recoveryEmailStatus(this._activeSession.sessionToken).then(
|
||||
data => {
|
||||
let error = this._getError(data);
|
||||
if (error) {
|
||||
this._error(error, data);
|
||||
}
|
||||
// If the verification status has changed, update state.
|
||||
if (this._activeSession.verified != data.verified) {
|
||||
this._activeSession.verified = data.verified;
|
||||
this._fxAccounts.setSignedInUser(this._activeSession);
|
||||
this._fxAccounts.getSignedInUserProfile().then(profile => {
|
||||
if (profile) {
|
||||
this._activeSession.profile = profile;
|
||||
}
|
||||
}).catch(error => {
|
||||
// FxAccounts logs already inform about the error.
|
||||
});
|
||||
}
|
||||
log.debug(JSON.stringify(this._user));
|
||||
},
|
||||
reason => { this._serverError(reason); }
|
||||
);
|
||||
},
|
||||
|
||||
/*
|
||||
* Try to get an assertion for the given audience. Here we implement
|
||||
* the heart of the response to navigator.mozId.request() on device.
|
||||
* (We can also be called via the IAC API, but it's request() that
|
||||
* makes this method complex.) The state machine looks like this,
|
||||
* ignoring simple errors:
|
||||
* If no one is signed in, and we aren't suppressing the UI:
|
||||
* trigger the sign in flow.
|
||||
* else if we were asked to refresh and the grace period is up:
|
||||
* trigger the refresh flow.
|
||||
* else:
|
||||
* request user permission to share an assertion if we don't have it
|
||||
* already and ask the core code for an assertion, which might itself
|
||||
* trigger either the sign in or refresh flows (if our account
|
||||
* changed on the server).
|
||||
*
|
||||
* aOptions can include:
|
||||
* refreshAuthentication - (bool) Force re-auth.
|
||||
* silent - (bool) Prevent any UI interaction.
|
||||
* I.e., try to get an automatic assertion.
|
||||
*/
|
||||
getAssertion(aAudience, aPrincipal, aOptions) {
|
||||
if (!aAudience) {
|
||||
return this._error(ERROR_INVALID_AUDIENCE);
|
||||
}
|
||||
|
||||
let principal = aPrincipal;
|
||||
log.debug("FxAccountsManager.getAssertion() aPrincipal: ",
|
||||
principal.origin, principal.appId,
|
||||
principal.isInIsolatedMozBrowserElement);
|
||||
|
||||
return this.getAccount().then(
|
||||
user => {
|
||||
if (user) {
|
||||
// Three have-user cases to consider. First: are we unverified?
|
||||
if (!user.verified) {
|
||||
return this._error(ERROR_UNVERIFIED_ACCOUNT, {
|
||||
user
|
||||
});
|
||||
}
|
||||
// Second case: do we need to refresh?
|
||||
if (aOptions &&
|
||||
(typeof(aOptions.refreshAuthentication) != "undefined")) {
|
||||
let gracePeriod = aOptions.refreshAuthentication;
|
||||
if (typeof(gracePeriod) !== "number" || isNaN(gracePeriod)) {
|
||||
return this._error(ERROR_INVALID_REFRESH_AUTH_VALUE);
|
||||
}
|
||||
// Forcing refreshAuth to silent is a contradiction in terms,
|
||||
// though it might succeed silently if we didn't reject here.
|
||||
if (aOptions.silent) {
|
||||
return this._error(ERROR_NO_SILENT_REFRESH_AUTH);
|
||||
}
|
||||
let secondsSinceAuth = (Date.now() / 1000) -
|
||||
this._activeSession.authAt;
|
||||
if (secondsSinceAuth > gracePeriod) {
|
||||
return this._refreshAuthentication(aAudience, user.email,
|
||||
principal,
|
||||
false /* logoutOnFailure */);
|
||||
}
|
||||
}
|
||||
// Third case: we are all set *locally*. Probably we just return
|
||||
// the assertion, but the attempt might lead to the server saying
|
||||
// we are deleted or have a new password, which will trigger a flow.
|
||||
// Also we need to check if we have permission to get the assertion,
|
||||
// otherwise we need to show the forceAuth UI to let the user know
|
||||
// that the RP with no fxa permissions is trying to obtain an
|
||||
// assertion. Once the user authenticates herself in the forceAuth UI
|
||||
// the permission will be remembered by default.
|
||||
let permission = permissionManager.testPermissionFromPrincipal(
|
||||
principal,
|
||||
FXACCOUNTS_PERMISSION
|
||||
);
|
||||
if (permission == Ci.nsIPermissionManager.PROMPT_ACTION &&
|
||||
!this._refreshing) {
|
||||
return this._refreshAuthentication(aAudience, user.email,
|
||||
principal,
|
||||
false /* logoutOnFailure */);
|
||||
} else if (permission == Ci.nsIPermissionManager.DENY_ACTION &&
|
||||
!this._refreshing) {
|
||||
return this._error(ERROR_PERMISSION_DENIED);
|
||||
} else if (this._refreshing) {
|
||||
// If we are blocked asking for a password we should not continue
|
||||
// the getAssertion process.
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return this._getAssertion(aAudience, principal);
|
||||
}
|
||||
log.debug("No signed in user");
|
||||
if (aOptions && aOptions.silent) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience, principal);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
getKeys() {
|
||||
if (!Services.prefs.getBoolPref("services.sync.enabled", false)) {
|
||||
return Promise.reject(ERROR_SYNC_DISABLED);
|
||||
}
|
||||
|
||||
return this.getAccount().then(
|
||||
user => {
|
||||
if (!user) {
|
||||
log.debug("No signed in user");
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (!user.verified) {
|
||||
return this._error(ERROR_UNVERIFIED_ACCOUNT, {
|
||||
user
|
||||
});
|
||||
}
|
||||
|
||||
return this._fxAccounts.getKeys();
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
FxAccountsManager.init();
|
Загрузка…
Ссылка в новой задаче