1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 this.EXPORTED_SYMBOLS = ["FirefoxAccounts"];
9 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
11 Cu.import("resource://gre/modules/Log.jsm");
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
13 Cu.import("resource://gre/modules/Services.jsm");
14 Cu.import("resource://gre/modules/identity/LogUtils.jsm");
16 XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
17 "resource://gre/modules/identity/IdentityUtils.jsm");
19 XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject",
20 "resource://gre/modules/identity/IdentityUtils.jsm");
22 // loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
23 // "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
25 const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
28 Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
29 && Services.prefs.getCharPref(PREF_LOG_LEVEL);
31 this.LOG_LEVEL = Log.Level.Error;
34 let log = Log.repository.getLogger("Identity.FxAccounts");
35 log.level = LOG_LEVEL;
36 log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
39 XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
40 "resource://gre/modules/FxAccountsManager.jsm",
42 Cu.import("resource://gre/modules/FxAccountsCommon.js");
44 log.warn("The FxAccountsManager is only functional in B2G at this time.");
45 var FxAccountsManager = null;
46 var ONVERIFIED_NOTIFICATION = null;
47 var ONLOGIN_NOTIFICATION = null;
48 var ONLOGOUT_NOTIFICATION = null;
51 function FxAccountsService() {
52 Services.obs.addObserver(this, "quit-application-granted", false);
53 if (ONVERIFIED_NOTIFICATION) {
54 Services.obs.addObserver(this, ONVERIFIED_NOTIFICATION, false);
55 Services.obs.addObserver(this, ONLOGIN_NOTIFICATION, false);
56 Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION, false);
59 // Maintain interface parity with Identity.jsm and MinimalIdentity.jsm
62 this._rpFlows = new Map();
64 // Enable us to mock FxAccountsManager service in testing
65 this.fxAccountsManager = FxAccountsManager;
68 FxAccountsService.prototype = {
69 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
71 observe: function observe(aSubject, aTopic, aData) {
74 // Guard against matching null ON*_NOTIFICATION
76 case ONVERIFIED_NOTIFICATION:
77 log.debug("Received " + ONVERIFIED_NOTIFICATION + "; firing request()s");
78 for (let [rpId,] of this._rpFlows) {
82 case ONLOGIN_NOTIFICATION:
83 log.debug("Received " + ONLOGIN_NOTIFICATION + "; doLogin()s fired");
84 for (let [rpId,] of this._rpFlows) {
88 case ONLOGOUT_NOTIFICATION:
89 log.debug("Received " + ONLOGOUT_NOTIFICATION + "; doLogout()s fired");
90 for (let [rpId,] of this._rpFlows) {
94 case "quit-application-granted":
95 Services.obs.removeObserver(this, "quit-application-granted");
96 if (ONVERIFIED_NOTIFICATION) {
97 Services.obs.removeObserver(this, ONVERIFIED_NOTIFICATION);
98 Services.obs.removeObserver(this, ONLOGIN_NOTIFICATION);
99 Services.obs.removeObserver(this, ONLOGOUT_NOTIFICATION);
105 cleanupRPRequest: function(aRp) {
106 aRp.pendingRequest = false;
107 this._rpFlows.set(aRp.id, aRp);
111 * Register a listener for a given windowID as a result of a call to
112 * navigator.id.watch().
115 * (Object) an object that represents the caller document, and
116 * is expected to have properties:
117 * - id (unique, e.g. uuid)
120 * and a bunch of callbacks
128 watch: function watch(aRpCaller) {
129 this._rpFlows.set(aRpCaller.id, aRpCaller);
130 log.debug("watch: " + aRpCaller.id);
131 log.debug("Current rp flows: " + this._rpFlows.size);
133 // Log the user in, if possible, and then call ready().
136 this.fxAccountsManager.getAssertion(aRpCaller.audience,
138 { silent:true }).then(
141 this.doLogin(aRpCaller.id, data);
143 this.doLogout(aRpCaller.id);
145 this.doReady(aRpCaller.id);
148 log.error("get silent assertion failed: " + JSON.stringify(error));
149 this.doError(aRpCaller.id, error);
154 Services.tm.currentThread.dispatch(runnable,
155 Ci.nsIThread.DISPATCH_NORMAL);
159 * Delete the flow when the screen is unloaded
161 unwatch: function(aRpCallerId, aTargetMM) {
162 log.debug("unwatching: " + aRpCallerId);
163 this._rpFlows.delete(aRpCallerId);
167 * Initiate a login with user interaction as a result of a call to
168 * navigator.id.request().
171 * (integer) the id of the doc object obtained in .watch()
174 * (Object) options including privacyPolicy, termsOfService
176 request: function request(aRPId, aOptions) {
177 aOptions = aOptions || {};
178 let rp = this._rpFlows.get(aRPId);
180 log.error("request() called before watch()");
184 // We check if we already have a pending request for this RP and in that
185 // case we just bail out. We don't want duplicated onlogin or oncancel
187 if (rp.pendingRequest) {
188 log.debug("request() already called");
192 // Otherwise, we set the RP flow with the pending request flag.
193 rp.pendingRequest = true;
194 this._rpFlows.set(rp.id, rp);
196 let options = makeMessageObject(rp);
197 objectCopy(aOptions, options);
199 log.debug("get assertion for " + rp.audience);
201 this.fxAccountsManager.getAssertion(rp.audience, rp.principal, options)
204 log.debug("got assertion for " + rp.audience + ": " + data);
205 this.doLogin(aRPId, data);
208 log.debug("get assertion failed: " + JSON.stringify(error));
209 // Cancellation is passed through an error channel; here we reroute.
210 if ((error.error && (error.error.details == "DIALOG_CLOSED_BY_USER")) ||
211 (error.details == "DIALOG_CLOSED_BY_USER")) {
212 return this.doCancel(aRPId);
214 this.doError(aRPId, error);
219 this.cleanupRPRequest(rp);
224 this.cleanupRPRequest(rp);
230 * Invoked when a user wishes to logout of a site (for instance, when clicking
231 * on an in-content logout button).
234 * (integer) the id of the doc object obtained in .watch()
237 logout: function logout(aRpCallerId) {
238 // XXX Bug 945363 - Resolve the SSO story for FXA and implement
239 // logout accordingly.
241 // For now, it makes no sense to logout from a specific RP in
242 // Firefox Accounts, so just directly call the logout callback.
243 if (!this._rpFlows.has(aRpCallerId)) {
244 log.error("logout() called before watch()");
248 // Call logout() on the next tick
251 this.fxAccountsManager.signOut().then(() => {
252 this.doLogout(aRpCallerId);
256 Services.tm.currentThread.dispatch(runnable,
257 Ci.nsIThread.DISPATCH_NORMAL);
260 childProcessShutdown: function childProcessShutdown(messageManager) {
261 for (let [key,] of this._rpFlows) {
262 if (this._rpFlows.get(key)._mm === messageManager) {
263 this._rpFlows.delete(key);
268 doLogin: function doLogin(aRpCallerId, aAssertion) {
269 let rp = this._rpFlows.get(aRpCallerId);
271 log.warn("doLogin found no rp to go with callerId " + aRpCallerId);
275 rp.doLogin(aAssertion);
278 doLogout: function doLogout(aRpCallerId) {
279 let rp = this._rpFlows.get(aRpCallerId);
281 log.warn("doLogout found no rp to go with callerId " + aRpCallerId);
288 doReady: function doReady(aRpCallerId) {
289 let rp = this._rpFlows.get(aRpCallerId);
291 log.warn("doReady found no rp to go with callerId " + aRpCallerId);
298 doCancel: function doCancel(aRpCallerId) {
299 let rp = this._rpFlows.get(aRpCallerId);
301 log.warn("doCancel found no rp to go with callerId " + aRpCallerId);
308 doError: function doError(aRpCallerId, aError) {
309 let rp = this._rpFlows.get(aRpCallerId);
311 log.warn("doError found no rp to go with callerId " + aRpCallerId);
319 this.FirefoxAccounts = new FxAccountsService();