michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["FirefoxAccounts"]; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/identity/LogUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "objectCopy", michael@0: "resource://gre/modules/identity/IdentityUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject", michael@0: "resource://gre/modules/identity/IdentityUtils.jsm"); michael@0: michael@0: // loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO", michael@0: // "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by michael@0: // default. michael@0: const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel"; michael@0: try { michael@0: this.LOG_LEVEL = michael@0: Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING michael@0: && Services.prefs.getCharPref(PREF_LOG_LEVEL); michael@0: } catch (e) { michael@0: this.LOG_LEVEL = Log.Level.Error; michael@0: } michael@0: michael@0: let log = Log.repository.getLogger("Identity.FxAccounts"); michael@0: log.level = LOG_LEVEL; michael@0: log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); michael@0: michael@0: #ifdef MOZ_B2G michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager", michael@0: "resource://gre/modules/FxAccountsManager.jsm", michael@0: "FxAccountsManager"); michael@0: #else michael@0: log.warn("The FxAccountsManager is only functional in B2G at this time."); michael@0: var FxAccountsManager = null; michael@0: #endif michael@0: michael@0: function FxAccountsService() { michael@0: Services.obs.addObserver(this, "quit-application-granted", false); michael@0: michael@0: // Maintain interface parity with Identity.jsm and MinimalIdentity.jsm michael@0: this.RP = this; michael@0: michael@0: this._rpFlows = new Map(); michael@0: michael@0: // Enable us to mock FxAccountsManager service in testing michael@0: this.fxAccountsManager = FxAccountsManager; michael@0: } michael@0: michael@0: FxAccountsService.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]), michael@0: michael@0: observe: function observe(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "quit-application-granted": michael@0: Services.obs.removeObserver(this, "quit-application-granted"); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Register a listener for a given windowID as a result of a call to michael@0: * navigator.id.watch(). michael@0: * michael@0: * @param aCaller michael@0: * (Object) an object that represents the caller document, and michael@0: * is expected to have properties: michael@0: * - id (unique, e.g. uuid) michael@0: * - origin (string) michael@0: * michael@0: * and a bunch of callbacks michael@0: * - doReady() michael@0: * - doLogin() michael@0: * - doLogout() michael@0: * - doError() michael@0: * - doCancel() michael@0: * michael@0: */ michael@0: watch: function watch(aRpCaller) { michael@0: this._rpFlows.set(aRpCaller.id, aRpCaller); michael@0: log.debug("watch: " + aRpCaller.id); michael@0: log.debug("Current rp flows: " + this._rpFlows.size); michael@0: michael@0: // Log the user in, if possible, and then call ready(). michael@0: let runnable = { michael@0: run: () => { michael@0: this.fxAccountsManager.getAssertion(aRpCaller.audience, {silent:true}).then( michael@0: data => { michael@0: if (data) { michael@0: this.doLogin(aRpCaller.id, data); michael@0: } else { michael@0: this.doLogout(aRpCaller.id); michael@0: } michael@0: this.doReady(aRpCaller.id); michael@0: }, michael@0: error => { michael@0: log.error("get silent assertion failed: " + JSON.stringify(error)); michael@0: this.doError(aRpCaller.id, error); michael@0: } michael@0: ); michael@0: } michael@0: }; michael@0: Services.tm.currentThread.dispatch(runnable, michael@0: Ci.nsIThread.DISPATCH_NORMAL); michael@0: }, michael@0: michael@0: /** michael@0: * Delete the flow when the screen is unloaded michael@0: */ michael@0: unwatch: function(aRpCallerId, aTargetMM) { michael@0: log.debug("unwatching: " + aRpCallerId); michael@0: this._rpFlows.delete(aRpCallerId); michael@0: }, michael@0: michael@0: /** michael@0: * Initiate a login with user interaction as a result of a call to michael@0: * navigator.id.request(). michael@0: * michael@0: * @param aRPId michael@0: * (integer) the id of the doc object obtained in .watch() michael@0: * michael@0: * @param aOptions michael@0: * (Object) options including privacyPolicy, termsOfService michael@0: */ michael@0: request: function request(aRPId, aOptions) { michael@0: aOptions = aOptions || {}; michael@0: let rp = this._rpFlows.get(aRPId); michael@0: if (!rp) { michael@0: log.error("request() called before watch()"); michael@0: return; michael@0: } michael@0: michael@0: let options = makeMessageObject(rp); michael@0: objectCopy(aOptions, options); michael@0: michael@0: log.debug("get assertion for " + rp.audience); michael@0: michael@0: this.fxAccountsManager.getAssertion(rp.audience, options).then( michael@0: data => { michael@0: log.debug("got assertion for " + rp.audience + ": " + data); michael@0: this.doLogin(aRPId, data); michael@0: }, michael@0: error => { michael@0: log.error("get assertion failed: " + JSON.stringify(error)); michael@0: this.doError(aRPId, error); michael@0: } michael@0: ); michael@0: }, michael@0: michael@0: /** michael@0: * Invoked when a user wishes to logout of a site (for instance, when clicking michael@0: * on an in-content logout button). michael@0: * michael@0: * @param aRpCallerId michael@0: * (integer) the id of the doc object obtained in .watch() michael@0: * michael@0: */ michael@0: logout: function logout(aRpCallerId) { michael@0: // XXX Bug 945363 - Resolve the SSO story for FXA and implement michael@0: // logout accordingly. michael@0: // michael@0: // For now, it makes no sense to logout from a specific RP in michael@0: // Firefox Accounts, so just directly call the logout callback. michael@0: if (!this._rpFlows.has(aRpCallerId)) { michael@0: log.error("logout() called before watch()"); michael@0: return; michael@0: } michael@0: michael@0: // Call logout() on the next tick michael@0: let runnable = { michael@0: run: () => { michael@0: this.fxAccountsManager.signOut().then(() => { michael@0: this.doLogout(aRpCallerId); michael@0: }); michael@0: } michael@0: }; michael@0: Services.tm.currentThread.dispatch(runnable, michael@0: Ci.nsIThread.DISPATCH_NORMAL); michael@0: }, michael@0: michael@0: childProcessShutdown: function childProcessShutdown(messageManager) { michael@0: for (let [key,] of this._rpFlows) { michael@0: if (this._rpFlows.get(key)._mm === messageManager) { michael@0: this._rpFlows.delete(key); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: doLogin: function doLogin(aRpCallerId, aAssertion) { michael@0: let rp = this._rpFlows.get(aRpCallerId); michael@0: if (!rp) { michael@0: log.warn("doLogin found no rp to go with callerId " + aRpCallerId + "\n"); michael@0: return; michael@0: } michael@0: michael@0: rp.doLogin(aAssertion); michael@0: }, michael@0: michael@0: doLogout: function doLogout(aRpCallerId) { michael@0: let rp = this._rpFlows.get(aRpCallerId); michael@0: if (!rp) { michael@0: log.warn("doLogout found no rp to go with callerId " + aRpCallerId + "\n"); michael@0: return; michael@0: } michael@0: michael@0: rp.doLogout(); michael@0: }, michael@0: michael@0: doReady: function doReady(aRpCallerId) { michael@0: let rp = this._rpFlows.get(aRpCallerId); michael@0: if (!rp) { michael@0: log.warn("doReady found no rp to go with callerId " + aRpCallerId + "\n"); michael@0: return; michael@0: } michael@0: michael@0: rp.doReady(); michael@0: }, michael@0: michael@0: doCancel: function doCancel(aRpCallerId) { michael@0: let rp = this._rpFlows.get(aRpCallerId); michael@0: if (!rp) { michael@0: log.warn("doCancel found no rp to go with callerId " + aRpCallerId + "\n"); michael@0: return; michael@0: } michael@0: michael@0: rp.doCancel(); michael@0: }, michael@0: michael@0: doError: function doError(aRpCallerId, aError) { michael@0: let rp = this._rpFlows.get(aRpCallerId); michael@0: if (!rp) { michael@0: log.warn("doCancel found no rp to go with callerId " + aRpCallerId + "\n"); michael@0: return; michael@0: } michael@0: michael@0: rp.doError(aError); michael@0: } michael@0: }; michael@0: michael@0: this.FirefoxAccounts = new FxAccountsService(); michael@0: