Bug 1093500 - Cleanup Loop registration by pulling push URLs from the push handler. r=pkerr a=loop-only
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 05 Nov 2014 13:58:52 -0800
changeset 235121 139182aee28755bcc827a6b9c66cef7e3e8f2b02
parent 235120 7e49e9a80a0166a8f988d0d3236bf35fbd2f1332
child 235122 aee99eeceee6ce85419606684db52b2418c08b12
push id611
push userraliiev@mozilla.com
push dateMon, 05 Jan 2015 23:23:16 +0000
treeherdermozilla-release@345cd3b9c445 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspkerr, loop-only
bugs1093500
milestone35.0a2
Bug 1093500 - Cleanup Loop registration by pulling push URLs from the push handler. r=pkerr a=loop-only
browser/components/loop/MozLoopAPI.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/content/js/client.js
browser/components/loop/test/desktop-local/client_test.js
browser/components/loop/test/desktop-local/conversation_test.js
browser/components/loop/test/mochitest/browser_fxa_login.js
browser/components/loop/test/mochitest/head.js
browser/components/loop/test/xpcshell/test_looprooms.js
browser/components/loop/test/xpcshell/test_loopservice_busy.js
browser/components/loop/test/xpcshell/test_loopservice_dnd.js
browser/components/loop/test/xpcshell/test_loopservice_notification.js
browser/components/loop/test/xpcshell/test_loopservice_restart.js
browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
browser/components/loop/test/xpcshell/test_loopservice_token_save.js
browser/components/loop/test/xpcshell/test_loopservice_token_send.js
browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -368,27 +368,28 @@ function injectLoopAPI(targetWindow) {
 
     /**
      * Call to ensure that any necessary registrations for the Loop Service
      * have taken place.
      *
      * Callback parameters:
      * - err null on successful registration, non-null otherwise.
      *
+     * @param {LOOP_SESSION_TYPE} sessionType
      * @param {Function} callback Will be called once registration is complete,
      *                            or straight away if registration has already
      *                            happened.
      */
     ensureRegistered: {
       enumerable: true,
       writable: true,
-      value: function(callback) {
+      value: function(sessionType, callback) {
         // We translate from a promise to a callback, as we can't pass promises from
         // Promise.jsm across the priv versus unpriv boundary.
-        MozLoopService.promiseRegisteredWithServers().then(() => {
+        MozLoopService.promiseRegisteredWithServers(sessionType).then(() => {
           callback(null);
         }, err => {
           callback(cloneValueInto(err, targetWindow));
         }).catch(Cu.reportError);
       }
     },
 
     /**
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -101,20 +101,16 @@ function setJSONPref(aName, aValue) {
   Services.prefs.setCharPref(aName, value);
 }
 
 function getJSONPref(aName) {
   let value = Services.prefs.getCharPref(aName);
   return !!value ? JSON.parse(value) : null;
 }
 
-// The current deferred for the registration process. This is set if in progress
-// or the registration was successful. This is null if a registration attempt was
-// unsuccessful.
-let gRegisteredDeferred = null;
 let gHawkClient = null;
 let gLocalizedStrings = null;
 let gFxAEnabled = true;
 let gFxAOAuthClientPromise = null;
 let gFxAOAuthClient = null;
 let gErrors = new Map();
 let gLastWindowId = 0;
 let gConversationWindowData = new Map();
@@ -127,16 +123,23 @@ let gConversationWindowData = new Map();
  * and register with the Loop server.
  */
 let MozLoopServiceInternal = {
   mocks: {
     pushHandler: undefined,
     webSocket: undefined,
   },
 
+  /**
+   * The current deferreds for the registration processes. This is set if in progress
+   * or the registration was successful. This is null if a registration attempt was
+   * unsuccessful.
+   */
+  deferredRegistrations: new Map(),
+
   get pushHandler() this.mocks.pushHandler || MozLoopPushHandler,
 
   // The uri of the Loop server.
   get loopServerUri() Services.prefs.getCharPref("loop.server"),
 
   /**
    * The initial delay for push registration. This ensures we don't start
    * kicking off straight after browser startup, just a few seconds later.
@@ -305,82 +308,99 @@ let MozLoopServiceInternal = {
     this.notifyStatusChanged();
   },
 
   get errors() {
     return gErrors;
   },
 
   /**
-   * Starts registration of Loop with the push server, and then will register
-   * with the Loop server as a GUEST. It will return early if already registered.
+   * Get endpoints with the push server and register for notifications.
+   * For now we register as both a Guest and FxA user and all must succeed.
    *
-   * @returns {Promise} a promise that is resolved with no params on completion, or
-   *          rejected with an error code or string.
+   * @return {Promise} resolves with all push endpoints
+   *                   rejects if any of the push registrations failed
    */
-  promiseRegisteredWithServers: function() {
-    if (gRegisteredDeferred) {
-      return gRegisteredDeferred.promise;
-    }
-
+  promiseRegisteredWithPushServer: function() {
     // Wrap push notification registration call-back in a Promise.
-    let registerForNotification = function(channelID, onNotification) {
+    function registerForNotification(channelID, onNotification) {
+      log.debug("registerForNotification", channelID);
       return new Promise((resolve, reject) => {
-        let onRegistered = (error, pushUrl) => {
+        function onRegistered(error, pushUrl) {
+          log.debug("registerForNotification onRegistered:", error, pushUrl);
           if (error) {
             reject(Error(error));
           } else {
             resolve(pushUrl);
           }
-        };
+        }
+
+        // If we're already registered, resolve with the existing push URL
+        let pushURL = MozLoopServiceInternal.pushHandler.registeredChannels[channelID];
+        if (pushURL) {
+          log.debug("Using the existing push endpoint for channelID:", channelID);
+          resolve(pushURL);
+          return;
+        }
+
         MozLoopServiceInternal.pushHandler.register(channelID, onRegistered, onNotification);
       });
-    };
-
-    gRegisteredDeferred = Promise.defer();
-    // We grab the promise early in case .initialize or its results sets
-    // it back to null on error.
-    let result = gRegisteredDeferred.promise;
+    }
 
     let options = this.mocks.webSocket ? { mockWebSocket: this.mocks.webSocket } : {};
     this.pushHandler.initialize(options);
 
     let callsRegGuest = registerForNotification(MozLoopService.channelIDs.callsGuest,
                                                 LoopCalls.onNotification);
 
     let roomsRegGuest = registerForNotification(MozLoopService.channelIDs.roomsGuest,
                                                 roomsPushNotification);
 
     let callsRegFxA = registerForNotification(MozLoopService.channelIDs.callsFxA,
                                               LoopCalls.onNotification);
 
     let roomsRegFxA = registerForNotification(MozLoopService.channelIDs.roomsFxA,
                                               roomsPushNotification);
 
-    Promise.all([callsRegGuest, roomsRegGuest, callsRegFxA, roomsRegFxA])
-    .then((pushUrls) => {
-      return this.registerWithLoopServer(LOOP_SESSION_TYPE.GUEST,{
-        calls: pushUrls[0],
-        rooms: pushUrls[1],
-      });
+    return Promise.all([callsRegGuest, roomsRegGuest, callsRegFxA, roomsRegFxA]);
+  },
+
+  /**
+   * Starts registration of Loop with the push server, and then will register
+   * with the Loop server. It will return early if already registered.
+   *
+   * @param {LOOP_SESSION_TYPE} sessionType
+   * @returns {Promise} a promise that is resolved with no params on completion, or
+   *          rejected with an error code or string.
+   */
+  promiseRegisteredWithServers: function(sessionType = LOOP_SESSION_TYPE.GUEST) {
+    if (this.deferredRegistrations.has(sessionType)) {
+      log.debug("promiseRegisteredWithServers: registration already completed or in progress:", sessionType);
+      return this.deferredRegistrations.get(sessionType).promise;
+    }
+
+    let result = null;
+    let deferred = Promise.defer();
+    log.debug("assigning to deferredRegistrations for sessionType:", sessionType);
+    this.deferredRegistrations.set(sessionType, deferred);
+
+    // We grab the promise early in case one of the callers below delete it from the map.
+    result = deferred.promise;
+
+    this.promiseRegisteredWithPushServer().then(() => {
+      return this.registerWithLoopServer(sessionType);
     }).then(() => {
-      // storeSessionToken could have rejected and nulled the promise if the token was malformed.
-      if (!gRegisteredDeferred) {
-        return;
-      }
-      gRegisteredDeferred.resolve("registered to guest status");
+      deferred.resolve("registered to status:" + sessionType);
       // No need to clear the promise here, everything was good, so we don't need
       // to re-register.
     }, error => {
-      log.error("Failed to register with Loop server: ", error);
-      // registerWithLoopServer may have already made this null.
-      if (gRegisteredDeferred) {
-        gRegisteredDeferred.reject(error);
-      }
-      gRegisteredDeferred = null;
+      log.error("Failed to register with Loop server with sessionType " + sessionType, error);
+      deferred.reject(error);
+      this.deferredRegistrations.delete(sessionType);
+      log.debug("Cleared deferredRegistration for sessionType:", sessionType);
     });
 
     return result;
   },
 
   /**
    * Performs a hawk based request to the loop server.
    *
@@ -392,16 +412,17 @@ let MozLoopServiceInternal = {
    *                            transmitted with the request.
    * @returns {Promise}
    *        Returns a promise that resolves to the response of the API call,
    *        or is rejected with an error.  If the server response can be parsed
    *        as JSON and contains an 'error' property, the promise will be
    *        rejected with this JSON-parsed response.
    */
   hawkRequest: function(sessionType, path, method, payloadObj) {
+    log.debug("hawkRequest: " + path, sessionType);
     if (!gHawkClient) {
       gHawkClient = new HawkClient(this.loopServerUri);
     }
 
     let sessionToken;
     try {
       sessionToken = Services.prefs.getCharPref(this.getSessionTokenPrefName(sessionType));
     } catch (x) {
@@ -483,96 +504,110 @@ let MozLoopServiceInternal = {
     if (sessionToken) {
       // XXX should do more validation here
       if (sessionToken.length === 64) {
         Services.prefs.setCharPref(this.getSessionTokenPrefName(sessionType), sessionToken);
         log.debug("Stored a hawk session token for sessionType", sessionType);
       } else {
         // XXX Bubble the precise details up to the UI somehow (bug 1013248).
         log.warn("Loop server sent an invalid session token");
-        gRegisteredDeferred.reject("session-token-wrong-size");
-        gRegisteredDeferred = null;
         return false;
       }
     }
     return true;
   },
 
-
   /**
    * Clear the loop session token so we don't use it for Hawk Requests anymore.
    *
    * This should normally be used after unregistering with the server so it can
    * clean up session state first.
    *
    * @param {LOOP_SESSION_TYPE} sessionType The type of session to use for the request.
    *                                        One of the LOOP_SESSION_TYPE members.
    */
   clearSessionToken: function(sessionType) {
     Services.prefs.clearUserPref(this.getSessionTokenPrefName(sessionType));
     log.debug("Cleared hawk session token for sessionType", sessionType);
   },
 
   /**
-   * Registers with the Loop server either as a guest or a FxA user.
+   * Registers with the Loop server either as a guest or a FxA user. This method should only be
+   * called by promiseRegisteredWithServers since it prevents calling this while a registration is
+   * already in progress.
    *
+   * @private
    * @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
-   * @param {String} pushUrls The push url given by the push server.
    * @param {Boolean} [retry=true] Whether to retry if authentication fails.
    * @return {Promise}
    */
-  registerWithLoopServer: function(sessionType, pushUrls, retry = true) {
+  registerWithLoopServer: function(sessionType, retry = true) {
+    log.debug("registerWithLoopServer with sessionType:", sessionType);
+
+    let callsPushURL, roomsPushURL;
+    if (sessionType == LOOP_SESSION_TYPE.FXA) {
+      callsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA];
+      roomsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA];
+    } else if (sessionType == LOOP_SESSION_TYPE.GUEST) {
+      callsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.callsGuest];
+      roomsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsGuest];
+    }
+
+    if (!callsPushURL || !roomsPushURL) {
+      return Promise.reject("Invalid sessionType or missing push URLs for registerWithLoopServer: " + sessionType);
+    }
+
     // create a registration payload with a backwards compatible attribute (simplePushURL)
     // that will register only the calls notification.
     let msg = {
-        simplePushURL: pushUrls.calls,
-        simplePushURLs: pushUrls
+        simplePushURL: callsPushURL,
+        simplePushURLs: {
+          calls: callsPushURL,
+          rooms: roomsPushURL,
+        },
     };
     return this.hawkRequest(sessionType, "/registration", "POST", msg)
       .then((response) => {
-        // If this failed we got an invalid token. storeSessionToken rejects
-        // the gRegisteredDeferred promise for us, so here we just need to
-        // early return.
+        // If this failed we got an invalid token.
         if (!this.storeSessionToken(sessionType, response.headers)) {
-          return;
+          return Promise.reject("session-token-wrong-size");
         }
 
         log.debug("Successfully registered with server for sessionType", sessionType);
         this.clearError("registration");
+        return undefined;
       }, (error) => {
         // There's other errors than invalid auth token, but we should only do the reset
         // as a last resort.
         if (error.code === 401) {
           // Authorization failed, invalid token, we need to try again with a new token.
           if (retry) {
-            return this.registerWithLoopServer(sessionType, pushUrls, false);
+            return this.registerWithLoopServer(sessionType, false);
           }
         }
 
         log.error("Failed to register with the loop server. Error: ", error);
         this.setError("registration", error);
-        gRegisteredDeferred.reject(error);
-        gRegisteredDeferred = null;
         throw error;
       }
     );
   },
 
   /**
    * Unregisters from the Loop server either as a guest or a FxA user.
    *
    * This is normally only wanted for FxA users as we normally want to keep the
    * guest session with the device.
    *
    * NOTE: It is the responsibiliy of the caller the clear the session token
    * after all of the notification classes: calls and rooms, for either
    * Guest or FxA have been unregistered with the LoopServer.
    *
    * @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
-   * @param {String} pushURLs The push URL previously given by the push server.
+   * @param {String} pushURL The push URL previously given by the push server.
    *                         This may not be necessary to unregister in the future.
    * @return {Promise} resolving when the unregistration request finishes
    */
   unregisterFromLoopServer: function(sessionType, pushURL) {
     let prefType = Services.prefs.getPrefType(this.getSessionTokenPrefName(sessionType));
     if (prefType == Services.prefs.PREF_INVALID) {
       return Promise.resolve("already unregistered");
     }
@@ -968,38 +1003,37 @@ this.MozLoopService = {
     error => {
       // If we get a non-object then setError was already called for a different error type.
       if (typeof(error) == "object") {
         MozLoopServiceInternal.setError("initialization", error);
       }
     });
 
     try {
-      yield this.promiseRegisteredWithServers();
+      if (MozLoopServiceInternal.urlExpiryTimeIsInFuture()) {
+        yield this.promiseRegisteredWithServers(LOOP_SESSION_TYPE.GUEST);
+      } else {
+        log.debug("delayedInitialize: URL expiry time isn't in the future so not registering as a guest");
+      }
     } catch (ex) {
-      log.debug("MozLoopService: Failure of initial registration", ex);
+      log.debug("MozLoopService: Failure of guest registration", ex);
       deferredInitialization.reject(ex);
       yield completedPromise;
       return;
     }
 
     if (!MozLoopServiceInternal.fxAOAuthTokenData) {
-      log.debug("MozLoopService: Initialized without an already logged-in account");
-      deferredInitialization.resolve("initialized to guest status");
+      log.debug("delayedInitialize: Initialized without an already logged-in account");
+      deferredInitialization.resolve("initialized without FxA status");
       yield completedPromise;
       return;
     }
 
     log.debug("MozLoopService: Initializing with already logged-in account");
-    let pushURLs = {
-      calls: MozLoopServiceInternal.pushHandler.registeredChannels[this.channelIDs.callsFxA],
-      rooms: MozLoopServiceInternal.pushHandler.registeredChannels[this.channelIDs.roomsFxA]
-    };
-
-    MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURLs).then(() => {
+    MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
       deferredInitialization.resolve("initialized to logged-in status");
     }, error => {
       log.debug("MozLoopService: error logging in using cached auth token");
       MozLoopServiceInternal.setError("login", error);
       deferredInitialization.reject("error logging in using cached auth token");
     });
     yield completedPromise;
   }),
@@ -1107,18 +1141,18 @@ this.MozLoopService = {
                                              this._DNSService.RESOLVE_DISABLE_IPV6,
                                              onLookupComplete,
                                              Services.tm.mainThread);
   },
 
   /**
    * @see MozLoopServiceInternal.promiseRegisteredWithServers
    */
-  promiseRegisteredWithServers: function() {
-    return MozLoopServiceInternal.promiseRegisteredWithServers();
+  promiseRegisteredWithServers: function(sessionType = LOOP_SESSION_TYPE.GUEST) {
+    return MozLoopServiceInternal.promiseRegisteredWithServers(sessionType);
   },
 
   /**
    * Used to note a call url expiry time. If the time is later than the current
    * latest expiry time, then the stored expiry time is increased. For times
    * sooner, this function is a no-op; this ensures we always have the latest
    * expiry time for a url.
    *
@@ -1304,29 +1338,21 @@ this.MozLoopService = {
       return Promise.resolve(MozLoopServiceInternal.fxAOAuthTokenData);
     }
     return MozLoopServiceInternal.promiseFxAOAuthAuthorization().then(response => {
       return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
     }).then(tokenData => {
       MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
       return tokenData;
     }).then(tokenData => {
-      return gRegisteredDeferred.promise.then(Task.async(function*() {
-        let callsUrl = MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA],
-            roomsUrl = MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA];
-        if (callsUrl && roomsUrl) {
-          yield MozLoopServiceInternal.registerWithLoopServer(
-            LOOP_SESSION_TYPE.FXA, {calls: callsUrl, rooms: roomsUrl});
-        } else {
-          throw new Error("No pushUrls for FxA registration");
-        }
+      return MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
         MozLoopServiceInternal.clearError("login");
         MozLoopServiceInternal.clearError("profile");
         return MozLoopServiceInternal.fxAOAuthTokenData;
-      }));
+      });
     }).then(tokenData => {
       let client = new FxAccountsProfileClient({
         serverURL: gFxAOAuthClient.parameters.profile_uri,
         token: tokenData.access_token
       });
       client.fetchProfile().then(result => {
         MozLoopServiceInternal.fxAOAuthProfile = result;
         MozLoopServiceInternal.notifyStatusChanged("login");
@@ -1335,16 +1361,17 @@ this.MozLoopService = {
         this.setError("profile", error);
         MozLoopServiceInternal.fxAOAuthProfile = null;
         MozLoopServiceInternal.notifyStatusChanged();
       });
       return tokenData;
     }).catch(error => {
       MozLoopServiceInternal.fxAOAuthTokenData = null;
       MozLoopServiceInternal.fxAOAuthProfile = null;
+      MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA);
       throw error;
     }).catch((error) => {
       MozLoopServiceInternal.setError("login", error);
       // Re-throw for testing
       throw error;
     });
   },
 
@@ -1366,31 +1393,32 @@ this.MozLoopService = {
       }
       if (roomsPushUrl) {
         yield MozLoopServiceInternal.unregisterFromLoopServer(LOOP_SESSION_TYPE.FXA, roomsPushUrl);
       }
     } catch (error) {
       throw error;
     } finally {
       MozLoopServiceInternal.clearSessionToken(LOOP_SESSION_TYPE.FXA);
-    }
 
-    MozLoopServiceInternal.fxAOAuthTokenData = null;
-    MozLoopServiceInternal.fxAOAuthProfile = null;
+      MozLoopServiceInternal.fxAOAuthTokenData = null;
+      MozLoopServiceInternal.fxAOAuthProfile = null;
+      MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA);
 
-    // Reset the client since the initial promiseFxAOAuthParameters() call is
-    // what creates a new session.
-    gFxAOAuthClient = null;
-    gFxAOAuthClientPromise = null;
+      // Reset the client since the initial promiseFxAOAuthParameters() call is
+      // what creates a new session.
+      gFxAOAuthClient = null;
+      gFxAOAuthClientPromise = null;
 
-    // clearError calls notifyStatusChanged so should be done last when the
-    // state is clean.
-    MozLoopServiceInternal.clearError("registration");
-    MozLoopServiceInternal.clearError("login");
-    MozLoopServiceInternal.clearError("profile");
+      // clearError calls notifyStatusChanged so should be done last when the
+      // state is clean.
+      MozLoopServiceInternal.clearError("registration");
+      MozLoopServiceInternal.clearError("login");
+      MozLoopServiceInternal.clearError("profile");
+    }
   }),
 
   openFxASettings: Task.async(function() {
     try {
       let fxAOAuthClient = yield MozLoopServiceInternal.promiseFxAOAuthClient();
       if (!fxAOAuthClient) {
         log.error("Could not get the OAuth client");
         return;
--- a/browser/components/loop/content/js/client.js
+++ b/browser/components/loop/content/js/client.js
@@ -82,20 +82,21 @@ loop.Client = (function($) {
     },
 
     /**
      * Ensures the client is registered with the push server.
      *
      * Callback parameters:
      * - err null on successful registration, non-null otherwise.
      *
+     * @param {LOOP_SESSION_TYPE} sessionType Guest or FxA
      * @param {Function} cb Callback(err)
      */
-    _ensureRegistered: function(cb) {
-      this.mozLoop.ensureRegistered(function(error) {
+    _ensureRegistered: function(sessionType, cb) {
+      this.mozLoop.ensureRegistered(sessionType, function(error) {
         if (error) {
           console.log("Error registering with Loop server, code: " + error);
           cb(error);
           return;
         } else {
           cb(null);
         }
       });
@@ -105,27 +106,21 @@ loop.Client = (function($) {
      * Internal handler for requesting a call url from the server.
      *
      * Callback parameters:
      * - err null on successful registration, non-null otherwise.
      * - callUrlData an object of the obtained call url data if successful:
      * -- callUrl: The url of the call
      * -- expiresAt: The amount of hours until expiry of the url
      *
+     * @param {LOOP_SESSION_TYPE} sessionType
      * @param  {string} nickname the nickname of the future caller
      * @param  {Function} cb Callback(err, callUrlData)
      */
-    _requestCallUrlInternal: function(nickname, cb) {
-      var sessionType;
-      if (this.mozLoop.userProfile) {
-        sessionType = this.mozLoop.LOOP_SESSION_TYPE.FXA;
-      } else {
-        sessionType = this.mozLoop.LOOP_SESSION_TYPE.GUEST;
-      }
-
+    _requestCallUrlInternal: function(sessionType, nickname, cb) {
       this.mozLoop.hawkRequest(sessionType, "/call-url/", "POST",
                                {callerId: nickname},
         function (error, responseText) {
           if (error) {
             this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
             this._failureHandler(cb, error);
             return;
           }
@@ -154,17 +149,17 @@ loop.Client = (function($) {
      * @param {mozLoop.LOOP_SESSION_TYPE} sessionType The type of session which
      *                                                the url belongs to.
      * @param {function} cb Callback function used for handling an error
      *                      response. XXX The incoming call panel does not
      *                      exist after the block button is clicked therefore
      *                      it does not make sense to display an error.
      **/
     deleteCallUrl: function(token, sessionType, cb) {
-      this._ensureRegistered(function(err) {
+      this._ensureRegistered(sessionType, function(err) {
         if (err) {
           cb(err);
           return;
         }
 
         this._deleteCallUrlInternal(token, sessionType, cb);
       }.bind(this));
     },
@@ -201,23 +196,30 @@ loop.Client = (function($) {
      * -- callUrl: The url of the call
      * -- expiresAt: The amount of hours until expiry of the url
      *
      * @param  {String} simplepushUrl a registered Simple Push URL
      * @param  {string} nickname the nickname of the future caller
      * @param  {Function} cb Callback(err, callUrlData)
      */
     requestCallUrl: function(nickname, cb) {
-      this._ensureRegistered(function(err) {
+      var sessionType;
+      if (this.mozLoop.userProfile) {
+        sessionType = this.mozLoop.LOOP_SESSION_TYPE.FXA;
+      } else {
+        sessionType = this.mozLoop.LOOP_SESSION_TYPE.GUEST;
+      }
+
+      this._ensureRegistered(sessionType, function(err) {
         if (err) {
           cb(err);
           return;
         }
 
-        this._requestCallUrlInternal(nickname, cb);
+        this._requestCallUrlInternal(sessionType, nickname, cb);
       }.bind(this));
     },
 
     /**
      * Sets up an outgoing call, getting the relevant data from the server.
      *
      * Callback parameters:
      * - err null on successful registration, non-null otherwise.
--- a/browser/components/loop/test/desktop-local/client_test.js
+++ b/browser/components/loop/test/desktop-local/client_test.js
@@ -27,17 +27,17 @@ describe("loop.Client", function() {
     sandbox = sinon.sandbox.create();
     callback = sinon.spy();
     fakeToken = "fakeTokenText";
     mozLoop = {
       getLoopCharPref: sandbox.stub()
         .returns(null)
         .withArgs("hawk-session-token")
         .returns(fakeToken),
-      ensureRegistered: sinon.stub().callsArgWith(0, null),
+      ensureRegistered: sinon.stub().callsArgWith(1, null),
       noteCallUrlExpiry: sinon.spy(),
       hawkRequest: sinon.stub(),
       LOOP_SESSION_TYPE: {
         GUEST: 1,
         FXA: 2
       },
       userProfile: null,
       telemetryAdd: sinon.spy()
@@ -57,17 +57,17 @@ describe("loop.Client", function() {
     describe("#deleteCallUrl", function() {
       it("should ensure loop is registered", function() {
         client.deleteCallUrl("fakeToken", mozLoop.LOOP_SESSION_TYPE.FXA, callback);
 
         sinon.assert.calledOnce(mozLoop.ensureRegistered);
       });
 
       it("should send an error when registration fails", function() {
-        mozLoop.ensureRegistered.callsArgWith(0, "offline");
+        mozLoop.ensureRegistered.callsArgWith(1, "offline");
 
         client.deleteCallUrl("fakeToken", mozLoop.LOOP_SESSION_TYPE.FXA, callback);
 
         sinon.assert.calledOnce(callback);
         sinon.assert.calledWithExactly(callback, "offline");
       });
 
       it("should make a delete call to /call-url/{fakeToken}", function() {
@@ -108,17 +108,17 @@ describe("loop.Client", function() {
     describe("#requestCallUrl", function() {
       it("should ensure loop is registered", function() {
         client.requestCallUrl("foo", callback);
 
         sinon.assert.calledOnce(mozLoop.ensureRegistered);
       });
 
       it("should send an error when registration fails", function() {
-        mozLoop.ensureRegistered.callsArgWith(0, "offline");
+        mozLoop.ensureRegistered.callsArgWith(1, "offline");
 
         client.requestCallUrl("foo", callback);
 
         sinon.assert.calledOnce(callback);
         sinon.assert.calledWithExactly(callback, "offline");
       });
 
       it("should post to /call-url/", function() {
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -39,16 +39,20 @@ describe("loop.conversation", function()
         return "en-US";
       },
       setLoopCharPref: sinon.stub(),
       getLoopCharPref: sinon.stub().returns("http://fakeurl"),
       getLoopBoolPref: sinon.stub(),
       calls: {
         clearCallInProgress: sinon.stub()
       },
+      LOOP_SESSION_TYPE: {
+        GUEST: 1,
+        FXA: 2
+      },
       startAlerting: sinon.stub(),
       stopAlerting: sinon.stub(),
       ensureRegistered: sinon.stub(),
       get appVersionInfo() {
         return {
           version: "42",
           channel: "test",
           platform: "test"
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -337,17 +337,17 @@ add_task(function* logoutWithIncorrectPu
   let pushURL = "http://www.example.com/";
   mockPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA] = pushURL;
   mockPushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA] = pushURL;
 
   // Create a fake FxA hawk session token
   const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
   Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
 
-  yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, {calls: pushURL});
+  yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA);
   let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
   ise(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL");
   mockPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA] = "http://www.example.com/invalid";
   let caught = false;
   yield MozLoopService.logOutFromFxA().catch((error) => {
     caught = true;
   });
   ok(caught, "Should have caught an error logging out with a mismatched push URL");
@@ -360,17 +360,17 @@ add_task(function* logoutWithNoPushURL()
   yield resetFxA();
   let pushURL = "http://www.example.com/";
   mockPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA] = pushURL;
 
   // Create a fake FxA hawk session token
   const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
   Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
 
-  yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, {calls: pushURL});
+  yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA);
   let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
   ise(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL");
   mockPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA] = null;
   mockPushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA] = null;
   yield MozLoopService.logOutFromFxA();
   checkLoggedOutState();
   registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
   ise(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL wasn't deleted");
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -115,17 +115,17 @@ function promiseOAuthParamsSetup(baseURL
   });
 }
 
 function* resetFxA() {
   let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
   global.gHawkClient = null;
   global.gFxAOAuthClientPromise = null;
   global.gFxAOAuthClient = null;
-  global.gRegisteredDeferred = null;
+  MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA);
   MozLoopServiceInternal.fxAOAuthProfile = null;
   MozLoopServiceInternal.fxAOAuthTokenData = null;
   const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
   Services.prefs.clearUserPref(fxASessionPref);
   MozLoopService.errors.clear();
   let notified = promiseObserverNotified("loop-status-changed");
   MozLoopServiceInternal.notifyStatusChanged();
   yield notified;
--- a/browser/components/loop/test/xpcshell/test_looprooms.js
+++ b/browser/components/loop/test/xpcshell/test_looprooms.js
@@ -224,16 +224,18 @@ add_task(function* setup_server() {
 
   loopServer.registerPathHandler("/rooms/errorMalformed", (req, res) => {
     res.setStatusLine(null, 200, "OK");
     res.write("{\"some\": \"Syntax Error!\"}}}}}}");
     res.processAsync();
     res.finish();
   });
 
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
   yield MozLoopService.promiseRegisteredWithServers();
 });
 
 // Test if fetching a list of all available rooms works correctly.
 add_task(function* test_getAllRooms() {
   gExpectedAdds.push(...kRooms.values());
   let rooms = yield LoopRooms.promise("getAll");
   Assert.equal(rooms.length, 3);
--- a/browser/components/loop/test/xpcshell/test_loopservice_busy.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_busy.js
@@ -19,16 +19,18 @@ let msgHandler = function(msg) {
       msg.reason === "busy") {
     actionReceived = true;
   }
 };
 
 add_test(function test_busy_2guest_calls() {
   actionReceived = false;
 
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
   MozLoopService.promiseRegisteredWithServers().then(() => {
     let opened = 0;
     let windowId;
     Chat.open = function(contentWindow, origin, title, url) {
       opened++;
       windowId = url.match(/about:loopconversation\#(\d+)$/)[1];
     };
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_dnd.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_dnd.js
@@ -26,16 +26,18 @@ add_test(function test_set_do_not_distur
   do_check_true(Services.prefs.getBoolPref("loop.do_not_disturb"));
 
   run_next_test();
 });
 
 add_test(function test_do_not_disturb_disabled_should_open_chat_window() {
   MozLoopService.doNotDisturb = false;
 
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
   MozLoopService.promiseRegisteredWithServers().then(() => {
     let opened = false;
     Chat.open = function() {
       opened = true;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_notification.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_notification.js
@@ -5,16 +5,18 @@
 XPCOMUtils.defineLazyModuleGetter(this, "Chat",
                                   "resource:///modules/Chat.jsm");
 
 let openChatOrig = Chat.open;
 
 add_test(function test_openChatWindow_on_notification() {
   Services.prefs.setCharPref("loop.seenToS", "unseen");
 
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
   MozLoopService.promiseRegisteredWithServers().then(() => {
     let opened = false;
     Chat.open = function() {
       opened = true;
     };
 
     mockPushHandler.notify(1, MozLoopService.channelIDs.callsGuest);
 
--- a/browser/components/loop/test/xpcshell/test_loopservice_restart.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_restart.js
@@ -38,17 +38,17 @@ add_task(function test_initialize_with_u
   Services.prefs.setIntPref(LOOP_URL_EXPIRY_PREF, Date.now() / 1000 + 10);
   Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
 
   loopServer.registerPathHandler("/registration", (request, response) => {
     response.setStatusLine(null, 200, "OK");
   });
 
   yield MozLoopService.initialize().then((msg) => {
-    Assert.equal(msg, "initialized to guest status", "Initialize should register as a " +
+    Assert.equal(msg, "initialized without FxA status", "Initialize should register as a " +
                                                      "guest when no auth tokens but expired URLs");
   }, (error) => {
     Assert.ok(false, error, "should have resolved the promise that initialize returned");
   });
 });
 
 add_task(function test_initialize_with_invalid_fxa_token() {
   Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
@@ -65,17 +65,17 @@ add_task(function test_initialize_with_i
       message: "Unknown credentials",
     }));
   });
 
   yield MozLoopService.initialize().then(() => {
     Assert.ok(false, "Initializing with an invalid token should reject the promise");
   },
   (error) => {
-    Assert.equal(MozLoopServiceInternal.pushHandler.pushUrl, kEndPointUrl, "Push URL should match");
+    Assert.equal(MozLoopServiceInternal.pushHandler.registrationPushURL, kEndPointUrl, "Push URL should match");
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), "",
                  "FXA pref should be cleared if token was invalid");
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), "",
                  "FXA profile pref should be cleared if token was invalid");
     Assert.ok(MozLoopServiceInternal.errors.has("login"),
               "Initialization error should have been reported to UI");
   });
 });
@@ -99,17 +99,17 @@ add_task(function test_initialize_with_f
   });
 });
 
 function run_test() {
   setupFakeLoopServer();
   // Note, this is just used to speed up the test.
   Services.prefs.setIntPref(LOOP_INITIAL_DELAY_PREF, 0);
   MozLoopServiceInternal.mocks.pushHandler = mockPushHandler;
-  mockPushHandler.pushUrl = kEndPointUrl;
+  mockPushHandler.registrationPushURL = kEndPointUrl;
 
   do_register_cleanup(function() {
     MozLoopServiceInternal.mocks.pushHandler = undefined;
     Services.prefs.clearUserPref(LOOP_INITIAL_DELAY_PREF);
     Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
     Services.prefs.clearUserPref(LOOP_FXA_PROFILE_PREF);
     Services.prefs.clearUserPref(LOOP_URL_EXPIRY_PREF);
   });
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
@@ -41,14 +41,16 @@ add_test(function test_registration_inva
     do_throw("shouldn't be a failure result: " + err);
   });
 });
 
 
 function run_test() {
   setupFakeLoopServer();
 
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
   do_register_cleanup(function() {
     Services.prefs.clearUserPref("loop.hawk-session-token");
   });
 
   run_next_test();
 }
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_save.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_save.js
@@ -29,14 +29,16 @@ add_test(function test_registration_retu
   }, err => {
     do_throw("shouldn't error on a successful request");
   });
 });
 
 function run_test() {
   setupFakeLoopServer();
 
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
   do_register_cleanup(function() {
     Services.prefs.clearUserPref("loop.hawk-session-token");
   });
 
   run_next_test();
 }
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_send.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_send.js
@@ -30,14 +30,16 @@ add_test(function test_registration_uses
     do_throw("shouldn't error on a succesful request");
   });
 });
 
 
 function run_test() {
   setupFakeLoopServer();
 
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
   do_register_cleanup(function() {
     Services.prefs.clearUserPref("loop.hawk-session-token");
   });
 
   run_next_test();
 }
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
@@ -34,14 +34,16 @@ add_test(function test_registration_hand
 
     run_next_test();
   });
 });
 
 function run_test() {
   setupFakeLoopServer();
 
+  mockPushHandler.registrationPushURL = kEndPointUrl;
+
   do_register_cleanup(function() {
     Services.prefs.clearUserPref("loop.hawk-session-token");
   });
 
   run_next_test();
 }