Bug 1093500 - Cleanup Loop registration by pulling push URLs from the push handler. r=pkerr
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 05 Nov 2014 13:58:52 -0800
changeset 214242 6d819f81dcbd8e4620493e123243d4f38570d708
parent 214241 ff1606d46388c9cf9b2d3b5a21788e96478fbb58
child 214243 4d284c7760bf7646bcbeeda0c765e64e2836ba4c
push id27776
push usercbook@mozilla.com
push dateThu, 06 Nov 2014 12:05:44 +0000
treeherdermozilla-central@41adad987d82 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspkerr
bugs1093500
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1093500 - Cleanup Loop registration by pulling push URLs from the push handler. r=pkerr
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");
     }
@@ -965,38 +1000,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;
   }),
@@ -1108,18 +1142,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.
    *
@@ -1305,29 +1339,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");
@@ -1336,16 +1362,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;
     });
   },
 
@@ -1367,31 +1394,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();
 }