Bug 1102432: refresh the list of rooms upon account switch or logout. r=Niko a=sylvestre
authorMike de Boer <mdeboer@mozilla.com>
Tue, 16 Dec 2014 16:59:05 +0100
changeset 242574 ee96aeb0cf2382a28b0a27bca53e941258d89c4f
parent 242573 cd79379ebfa4987c491071b52fa6726e5ea8adde
child 242575 877827bc103a271dc3b12b0f5526bd20ff40dd8f
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersNiko, sylvestre
bugs1102432
milestone36.0a2
Bug 1102432: refresh the list of rooms upon account switch or logout. r=Niko a=sylvestre
browser/components/loop/LoopRooms.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/content/shared/js/roomStore.js
browser/components/loop/test/shared/roomStore_test.js
browser/components/loop/test/xpcshell/test_looprooms.js
--- a/browser/components/loop/LoopRooms.jsm
+++ b/browser/components/loop/LoopRooms.jsm
@@ -29,16 +29,18 @@ const roomsPushNotification = function(v
   return LoopRoomsInternal.onNotification(version, channelID);
 };
 
 // Since the LoopRoomsInternal.rooms map as defined below is a local cache of
 // room objects that are retrieved from the server, this is list may become out
 // of date. The Push server may notify us of this event, which will set the global
 // 'dirty' flag to TRUE.
 let gDirty = true;
+// Global variable that keeps track of the currently used account.
+let gCurrentUser = null;
 
 /**
  * Extend a `target` object with the properties defined in `source`.
  *
  * @param {Object} target The target object to receive properties defined in `source`
  * @param {Object} source The source object to copy properties from
  */
 const extend = function(target, source) {
@@ -474,16 +476,36 @@ let LoopRoomsInternal = {
     if ((this.sessionType == LOOP_SESSION_TYPE.GUEST && channelID != channelIDs.roomsGuest) ||
         (this.sessionType == LOOP_SESSION_TYPE.FXA   && channelID != channelIDs.roomsFxA)) {
       return;
     }
 
     gDirty = true;
     this.getAll(version, () => {});
   },
+
+  /**
+   * When a user logs in or out, this method should be invoked to check whether
+   * the rooms cache needs to be refreshed.
+   *
+   * @param {String|null} user The FxA userID or NULL
+   */
+  maybeRefresh: function(user = null) {
+    if (gCurrentUser == user) {
+      return;
+    }
+
+    gCurrentUser = user;
+    if (!gDirty) {
+      gDirty = true;
+      this.rooms.clear();
+      eventEmitter.emit("refresh");
+      this.getAll(null, () => {});
+    }
+  }
 };
 Object.freeze(LoopRoomsInternal);
 
 /**
  * Public Loop Rooms API.
  *
  * LoopRooms implements the EventEmitter interface by exposing three methods -
  * `on`, `once` and `off` - to subscribe to events.
@@ -539,16 +561,20 @@ this.LoopRooms = {
   rename: function(roomToken, newRoomName, callback) {
     return LoopRoomsInternal.rename(roomToken, newRoomName, callback);
   },
 
   getGuestCreatedRoom: function() {
     return LoopRoomsInternal.getGuestCreatedRoom();
   },
 
+  maybeRefresh: function(user) {
+    return LoopRoomsInternal.maybeRefresh(user);
+  },
+
   /**
    * This method is only useful for unit tests to set the rooms cache to contain
    * a list of fake room data that can be asserted in tests.
    *
    * @param {Map} stub Stub cache containing fake rooms data
    */
   stubCache: function(stub) {
     LoopRoomsInternal.rooms.clear();
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -238,17 +238,18 @@ let MozLoopServiceInternal = {
   set doNotDisturb(aFlag) {
     Services.prefs.setBoolPref("loop.do_not_disturb", Boolean(aFlag));
     this.notifyStatusChanged();
   },
 
   notifyStatusChanged: function(aReason = null) {
     log.debug("notifyStatusChanged with reason:", aReason);
     let profile = MozLoopService.userProfile;
-    LoopStorage.switchDatabase(profile ? profile.uid : null);
+    LoopStorage.switchDatabase(profile && profile.uid);
+    LoopRooms.maybeRefresh(profile && profile.uid);
     Services.obs.notifyObservers(null, "loop-status-changed", aReason);
   },
 
   /**
    * Record an error and notify interested UI with the relevant user-facing strings attached.
    *
    * @param {String} errorType a key to identify the type of error. Only one
    *                           error of a type will be saved at a time. This value may be used to
--- a/browser/components/loop/content/shared/js/roomStore.js
+++ b/browser/components/loop/content/shared/js/roomStore.js
@@ -132,16 +132,17 @@ loop.store = loop.store || {};
     /**
      * Registers mozLoop.rooms events.
      */
     startListeningToRoomEvents: function() {
       // Rooms event registration
       this._mozLoop.rooms.on("add", this._onRoomAdded.bind(this));
       this._mozLoop.rooms.on("update", this._onRoomUpdated.bind(this));
       this._mozLoop.rooms.on("delete", this._onRoomRemoved.bind(this));
+      this._mozLoop.rooms.on("refresh", this._onRoomsRefresh.bind(this));
     },
 
     /**
      * Updates active room store state.
      */
     _onActiveRoomStoreChange: function() {
       this.setStoreState({activeRoom: this.activeRoomStore.getStoreState()});
     },
@@ -188,16 +189,27 @@ loop.store = loop.store || {};
       this.dispatchAction(new sharedActions.UpdateRoomList({
         roomList: this._storeState.rooms.filter(function(room) {
           return room.roomToken !== removedRoomData.roomToken;
         })
       }));
     },
 
     /**
+     * Executed when the user switches accounts.
+     *
+     * @param {String} eventName The event name (unused).
+     */
+    _onRoomsRefresh: function(eventName) {
+      this.dispatchAction(new sharedActions.UpdateRoomList({
+        roomList: []
+      }));
+    },
+
+    /**
      * Maps and sorts the raw room list received from the mozLoop API.
      *
      * @param  {Array} rawRoomList Raw room list.
      * @return {Array}
      */
     _processRoomList: function(rawRoomList) {
       if (!rawRoomList) {
         return [];
--- a/browser/components/loop/test/shared/roomStore_test.js
+++ b/browser/components/loop/test/shared/roomStore_test.js
@@ -159,16 +159,24 @@ describe("loop.store.RoomStore", functio
           });
 
           expect(store.getStoreState().rooms).to.have.length.of(2);
           expect(store.getStoreState().rooms.some(function(room) {
             return room.roomToken === "_nxD4V4FflQ";
           })).eql(false);
         });
       });
+
+      describe("refresh", function() {
+        it ("should clear the list of rooms", function() {
+          fakeMozLoop.rooms.trigger("refresh", "refresh");
+
+          expect(store.getStoreState().rooms).to.have.length.of(0);
+        });
+      })
     });
 
     describe("#findNextAvailableRoomNumber", function() {
       var fakeNameTemplate = "RoomWord {{conversationLabel}}";
 
       it("should find next available room number from an empty room list",
         function() {
           store.setStoreState({rooms: []});
--- a/browser/components/loop/test/xpcshell/test_looprooms.js
+++ b/browser/components/loop/test/xpcshell/test_looprooms.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource:///modules/loop/LoopRooms.jsm");
 Cu.import("resource:///modules/Chat.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
 
 let openChatOrig = Chat.open;
 
 const kRooms = new Map([
   ["_nxD4V4FflQ", {
     roomToken: "_nxD4V4FflQ",
     roomName: "First Room Name",
     roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
@@ -122,16 +123,17 @@ const compareRooms = function(room1, roo
 };
 
 // LoopRooms emits various events. Test if they work as expected here.
 let gExpectedAdds = [];
 let gExpectedUpdates = [];
 let gExpectedDeletes = [];
 let gExpectedJoins = {};
 let gExpectedLeaves = {};
+let gExpectedRefresh = false;
 
 const onRoomAdded = function(e, room) {
   let expectedIds = gExpectedAdds.map(room => room.roomToken);
   let idx = expectedIds.indexOf(room.roomToken);
   Assert.ok(idx > -1, "Added room should be expected");
   let expected = gExpectedAdds[idx];
   compareRooms(room, expected);
   gExpectedAdds.splice(idx, 1);
@@ -166,16 +168,21 @@ const onRoomLeft = function(e, room, par
   let idx = participants.indexOf(participant.roomConnectionId);
   Assert.ok(idx > -1, "Participant should be expected to leave");
   participants.splice(idx, 1);
   if (!participants.length) {
     delete gExpectedLeaves[room.roomToken];
   }
 };
 
+const onRefresh = function(e) {
+  Assert.ok(gExpectedRefresh, "A refresh event should've been expected");
+  gExpectedRefresh = false;
+};
+
 const parseQueryString = function(qs) {
   let map = {};
   let parts = qs.split("=");
   for (let i = 0, l = parts.length; i < l; ++i) {
     if (i % 2 === 1) {
       map[parts[i - 1]] = parts[i];
     }
   }
@@ -307,16 +314,44 @@ add_task(function* test_openRoom() {
   // Stop the busy kicking in for following tests. (note: windowId can be 'fakeToken')
   let windowId = openedUrl.match(/about:loopconversation\#(\w+)$/)[1];
   let windowData = MozLoopService.getConversationWindowData(windowId);
 
   Assert.equal(windowData.type, "room", "window data should contain room as the type");
   Assert.equal(windowData.roomToken, "fakeToken", "window data should have the roomToken");
 });
 
+// Test if the rooms cache is refreshed after FxA signin or signout.
+add_task(function* test_refresh() {
+  gExpectedAdds.push(...kRooms.values());
+  gExpectedRefresh = true;
+  // Make the switch.
+  MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" };
+  MozLoopServiceInternal.fxAOAuthProfile = {
+    email: "fake@invalid.com",
+    uid: "fake"
+  };
+
+  yield waitForCondition(() => !gExpectedRefresh);
+  yield waitForCondition(() => gExpectedAdds.length === 0);
+
+  gExpectedAdds.push(...kRooms.values());
+  gExpectedRefresh = true;
+  // Simulate a logout.
+  MozLoopServiceInternal.fxAOAuthTokenData = null;
+  MozLoopServiceInternal.fxAOAuthProfile = null;
+
+  yield waitForCondition(() => !gExpectedRefresh);
+  yield waitForCondition(() => gExpectedAdds.length === 0);
+
+  // Simulating a logout again shouldn't yield a refresh event.
+  MozLoopServiceInternal.fxAOAuthTokenData = null;
+  MozLoopServiceInternal.fxAOAuthProfile = null;
+});
+
 // Test if push updates function as expected.
 add_task(function* test_roomUpdates() {
   gExpectedUpdates.push("_nxD4V4FflQ");
   gExpectedLeaves["_nxD4V4FflQ"] = [
     "2a1787a6-4a73-43b5-ae3e-906ec1e763cb",
     "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
   ];
   roomsPushNotification("1", kChannelGuest);
@@ -428,35 +463,42 @@ add_task(function* test_deleteRoom() {
   Assert.ok(!rooms.some((room) => room.roomToken == roomToken));
 });
 
 // Test if the event emitter implementation doesn't leak and is working as expected.
 add_task(function* () {
   Assert.strictEqual(gExpectedAdds.length, 0, "No room additions should be expected anymore");
   Assert.strictEqual(gExpectedUpdates.length, 0, "No room updates should be expected anymore");
   Assert.strictEqual(gExpectedDeletes.length, 0, "No room deletes should be expected anymore");
+  Assert.strictEqual(Object.getOwnPropertyNames(gExpectedJoins).length, 0,
+                     "No room joins should be expected anymore");
+  Assert.strictEqual(Object.getOwnPropertyNames(gExpectedLeaves).length, 0,
+                     "No room leaves should be expected anymore");
+  Assert.ok(!gExpectedRefresh, "No refreshes should be expected anymore");
  });
 
 function run_test() {
   setupFakeLoopServer();
 
   LoopRooms.on("add", onRoomAdded);
   LoopRooms.on("update", onRoomUpdated);
   LoopRooms.on("delete", onRoomDeleted);
   LoopRooms.on("joined", onRoomJoined);
   LoopRooms.on("left", onRoomLeft);
+  LoopRooms.on("refresh", onRefresh);
 
   do_register_cleanup(function () {
     // Revert original Chat.open implementation
     Chat.open = openChatOrig;
 
     MozLoopServiceInternal.fxAOAuthTokenData = null;
     MozLoopServiceInternal.fxAOAuthProfile = null;
 
     LoopRooms.off("add", onRoomAdded);
     LoopRooms.off("update", onRoomUpdated);
     LoopRooms.off("delete", onRoomDeleted);
     LoopRooms.off("joined", onRoomJoined);
     LoopRooms.off("left", onRoomLeft);
+    LoopRooms.off("refresh", onRefresh);
   });
 
   run_next_test();
 }