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 = ["SignInToWebsiteUX"]; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "IdentityService", michael@0: "resource://gre/modules/identity/Identity.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Logger", michael@0: "resource://gre/modules/identity/LogUtils.jsm"); michael@0: michael@0: function log(...aMessageArgs) { michael@0: Logger.log.apply(Logger, ["SignInToWebsiteUX"].concat(aMessageArgs)); michael@0: } michael@0: michael@0: this.SignInToWebsiteUX = { michael@0: michael@0: init: function SignInToWebsiteUX_init() { michael@0: michael@0: Services.obs.addObserver(this, "identity-request", false); michael@0: Services.obs.addObserver(this, "identity-auth", false); michael@0: Services.obs.addObserver(this, "identity-auth-complete", false); michael@0: Services.obs.addObserver(this, "identity-login-state-changed", false); michael@0: }, michael@0: michael@0: uninit: function SignInToWebsiteUX_uninit() { michael@0: Services.obs.removeObserver(this, "identity-request"); michael@0: Services.obs.removeObserver(this, "identity-auth"); michael@0: Services.obs.removeObserver(this, "identity-auth-complete"); michael@0: Services.obs.removeObserver(this, "identity-login-state-changed"); michael@0: }, michael@0: michael@0: observe: function SignInToWebsiteUX_observe(aSubject, aTopic, aData) { michael@0: log("observe: received", aTopic, "with", aData, "for", aSubject); michael@0: let options = null; michael@0: if (aSubject) { michael@0: options = aSubject.wrappedJSObject; michael@0: } michael@0: switch(aTopic) { michael@0: case "identity-request": michael@0: this.requestLogin(options); michael@0: break; michael@0: case "identity-auth": michael@0: this._openAuthenticationUI(aData, options); michael@0: break; michael@0: case "identity-auth-complete": michael@0: this._closeAuthenticationUI(aData); michael@0: break; michael@0: case "identity-login-state-changed": michael@0: let emailAddress = aData; michael@0: if (emailAddress) { michael@0: this._removeRequestUI(options); michael@0: this._showLoggedInUI(emailAddress, options); michael@0: } else { michael@0: this._removeLoggedInUI(options); michael@0: } michael@0: break; michael@0: default: michael@0: Logger.reportError("SignInToWebsiteUX", "Unknown observer notification:", aTopic); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * The website is requesting login so the user must choose an identity to use. michael@0: */ michael@0: requestLogin: function SignInToWebsiteUX_requestLogin(aOptions) { michael@0: let windowID = aOptions.rpId; michael@0: log("requestLogin", aOptions); michael@0: let [chromeWin, browserEl] = this._getUIForWindowID(windowID); michael@0: michael@0: // message is not shown in the UI but is required michael@0: let message = aOptions.origin; michael@0: let mainAction = { michael@0: label: chromeWin.gNavigatorBundle.getString("identity.next.label"), michael@0: accessKey: chromeWin.gNavigatorBundle.getString("identity.next.accessKey"), michael@0: callback: function() {}, // required michael@0: }; michael@0: let options = { michael@0: identity: { michael@0: origin: aOptions.origin, michael@0: }, michael@0: }; michael@0: let secondaryActions = []; michael@0: michael@0: // add some extra properties to the notification to store some identity-related state michael@0: for (let opt in aOptions) { michael@0: options.identity[opt] = aOptions[opt]; michael@0: } michael@0: log("requestLogin: rpId: ", options.identity.rpId); michael@0: michael@0: chromeWin.PopupNotifications.show(browserEl, "identity-request", message, michael@0: "identity-notification-icon", mainAction, michael@0: [], options); michael@0: }, michael@0: michael@0: /** michael@0: * Get the list of possible identities to login to the given origin. michael@0: */ michael@0: getIdentitiesForSite: function SignInToWebsiteUX_getIdentitiesForSite(aOrigin) { michael@0: return IdentityService.RP.getIdentitiesForSite(aOrigin); michael@0: }, michael@0: michael@0: /** michael@0: * User chose a new or existing identity from the doorhanger after a request() call michael@0: */ michael@0: selectIdentity: function SignInToWebsiteUX_selectIdentity(aRpId, aIdentity) { michael@0: log("selectIdentity: rpId: ", aRpId, " identity: ", aIdentity); michael@0: IdentityService.selectIdentity(aRpId, aIdentity); michael@0: }, michael@0: michael@0: // Private michael@0: michael@0: /** michael@0: * Return the chrome window and for the given outer window ID. michael@0: */ michael@0: _getUIForWindowID: function(aWindowID) { michael@0: let content = Services.wm.getOuterWindowWithId(aWindowID); michael@0: if (content) { michael@0: let browser = content.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell).chromeEventHandler; michael@0: let chromeWin = browser.ownerDocument.defaultView; michael@0: return [chromeWin, browser]; michael@0: } michael@0: michael@0: Logger.reportError("SignInToWebsiteUX", "no content"); michael@0: return [null, null]; michael@0: }, michael@0: michael@0: /** michael@0: * Open UI with a content frame displaying aAuthURI so that the user can authenticate with their michael@0: * IDP. Then tell Identity.jsm the identifier for the window so that it knows that the DOM API michael@0: * calls are for this authentication flow. michael@0: */ michael@0: _openAuthenticationUI: function _openAuthenticationUI(aAuthURI, aContext) { michael@0: // Open a tab/window with aAuthURI with an identifier (aID) attached so that the DOM APIs know this is an auth. window. michael@0: let chromeWin = Services.wm.getMostRecentWindow('navigator:browser'); michael@0: let features = "chrome=false,width=640,height=480,centerscreen,location=yes,resizable=yes,scrollbars=yes,status=yes"; michael@0: log("aAuthURI: ", aAuthURI); michael@0: let authWin = Services.ww.openWindow(chromeWin, "about:blank", "", features, null); michael@0: let windowID = authWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID; michael@0: log("authWin outer id: ", windowID); michael@0: michael@0: let provId = aContext.provId; michael@0: // Tell the ID service about the id before loading the url michael@0: IdentityService.IDP.setAuthenticationFlow(windowID, provId); michael@0: michael@0: authWin.location = aAuthURI; michael@0: }, michael@0: michael@0: _closeAuthenticationUI: function _closeAuthenticationUI(aAuthId) { michael@0: log("_closeAuthenticationUI:", aAuthId); michael@0: let [chromeWin, browserEl] = this._getUIForWindowID(aAuthId); michael@0: if (chromeWin) michael@0: chromeWin.close(); michael@0: else michael@0: Logger.reportError("SignInToWebsite", "Could not close window with ID", aAuthId); michael@0: }, michael@0: michael@0: /** michael@0: * Show a doorhanger indicating the currently logged-in user. michael@0: */ michael@0: _showLoggedInUI: function _showLoggedInUI(aIdentity, aContext) { michael@0: let windowID = aContext.rpId; michael@0: log("_showLoggedInUI for ", windowID); michael@0: let [chromeWin, browserEl] = this._getUIForWindowID(windowID); michael@0: michael@0: let message = chromeWin.gNavigatorBundle.getFormattedString("identity.loggedIn.description", michael@0: [aIdentity]); michael@0: let mainAction = { michael@0: label: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.label"), michael@0: accessKey: chromeWin.gNavigatorBundle.getString("identity.loggedIn.signOut.accessKey"), michael@0: callback: function() { michael@0: log("sign out callback fired"); michael@0: IdentityService.RP.logout(windowID); michael@0: }, michael@0: }; michael@0: let secondaryActions = []; michael@0: let options = { michael@0: dismissed: true, michael@0: }; michael@0: let loggedInNot = chromeWin.PopupNotifications.show(browserEl, "identity-logged-in", message, michael@0: "identity-notification-icon", mainAction, michael@0: secondaryActions, options); michael@0: loggedInNot.rpId = windowID; michael@0: }, michael@0: michael@0: /** michael@0: * Remove the doorhanger indicating the currently logged-in user. michael@0: */ michael@0: _removeLoggedInUI: function _removeLoggedInUI(aContext) { michael@0: let windowID = aContext.rpId; michael@0: log("_removeLoggedInUI for ", windowID); michael@0: if (!windowID) michael@0: throw "_removeLoggedInUI: Invalid RP ID"; michael@0: let [chromeWin, browserEl] = this._getUIForWindowID(windowID); michael@0: michael@0: let loggedInNot = chromeWin.PopupNotifications.getNotification("identity-logged-in", browserEl); michael@0: if (loggedInNot) michael@0: chromeWin.PopupNotifications.remove(loggedInNot); michael@0: }, michael@0: michael@0: /** michael@0: * Remove the doorhanger indicating the currently logged-in user. michael@0: */ michael@0: _removeRequestUI: function _removeRequestUI(aContext) { michael@0: let windowID = aContext.rpId; michael@0: log("_removeRequestUI for ", windowID); michael@0: let [chromeWin, browserEl] = this._getUIForWindowID(windowID); michael@0: michael@0: let requestNot = chromeWin.PopupNotifications.getNotification("identity-request", browserEl); michael@0: if (requestNot) michael@0: chromeWin.PopupNotifications.remove(requestNot); michael@0: }, michael@0: michael@0: };