Bug 1074688 - Part 2 Add Join/Refresh/Leave room functions to the mozLoop API. r=mikedeboer a=loop-only
authorMark Banner <standard8@mozilla.com>
Thu, 06 Nov 2014 20:52:16 +0000
changeset 233803 259292d137124cf8a03efd2012318372d72f7d71
parent 233802 aee99eeceee6ce85419606684db52b2418c08b12
child 233804 9216940023374187351e3594b0855c98ca8e1d20
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer, loop-only
bugs1074688
milestone35.0a2
Bug 1074688 - Part 2 Add Join/Refresh/Leave room functions to the mozLoop API. r=mikedeboer a=loop-only
browser/components/loop/LoopRooms.jsm
browser/components/loop/test/xpcshell/test_looprooms.js
--- a/browser/components/loop/LoopRooms.jsm
+++ b/browser/components/loop/LoopRooms.jsm
@@ -13,16 +13,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
   const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
   return new EventEmitter();
 });
 
 this.EXPORTED_SYMBOLS = ["LoopRooms", "roomsPushNotification"];
 
+// The maximum number of clients that we support currently.
+const CLIENT_MAX_SIZE = 2;
+
 const roomsPushNotification = function(version, channelID) {
   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.
@@ -266,16 +269,91 @@ let LoopRoomsInternal = {
     MozLoopService.hawkRequest(this.sessionType, url, "DELETE")
       .then(response => {
         this.rooms.delete(roomToken);
         eventEmitter.emit("delete", room);
         callback(null, room);
       }, error => callback(error)).catch(error => callback(error));
   },
 
+  /**
+   * Internal function to handle POSTs to a room.
+   *
+   * @param {String} roomToken  The room token.
+   * @param {Object} postData   The data to post to the room.
+   * @param {Function} callback Function that will be invoked once the operation
+   *                            finished. The first argument passed will be an
+   *                            `Error` object or `null`.
+   */
+  _postToRoom(roomToken, postData, callback) {
+    let url = "/rooms/" + encodeURIComponent(roomToken);
+    MozLoopService.hawkRequest(this.sessionType, url, "POST", postData).then(response => {
+      // Delete doesn't have a body return.
+      var joinData = response.body ? JSON.parse(response.body) : {};
+      callback(null, joinData);
+    }, error => callback(error)).catch(error => callback(error));
+  },
+
+  /**
+   * Joins a room
+   *
+   * @param {String} roomToken  The room token.
+   * @param {Function} callback Function that will be invoked once the operation
+   *                            finished. The first argument passed will be an
+   *                            `Error` object or `null`.
+   */
+  join: function(roomToken, callback) {
+    this._postToRoom(roomToken, {
+      action: "join",
+      displayName: MozLoopService.userProfile.email,
+      clientMaxSize: CLIENT_MAX_SIZE
+    }, callback);
+  },
+
+  /**
+   * Refreshes a room
+   *
+   * @param {String} roomToken    The room token.
+   * @param {String} sessionToken The session token for the session that has been
+   *                              joined
+   * @param {Function} callback   Function that will be invoked once the operation
+   *                              finished. The first argument passed will be an
+   *                              `Error` object or `null`.
+   */
+  refreshMembership: function(roomToken, sessionToken, callback) {
+    this._postToRoom(roomToken, {
+      action: "refresh",
+      sessionToken: sessionToken
+    }, callback);
+  },
+
+  /**
+   * Leaves a room. Although this is an sync function, no data is returned
+   * from the server.
+   *
+   * @param {String} roomToken    The room token.
+   * @param {String} sessionToken The session token for the session that has been
+   *                              joined
+   * @param {Function} callback   Optional. Function that will be invoked once the operation
+   *                              finished. The first argument passed will be an
+   *                              `Error` object or `null`.
+   */
+  leave: function(roomToken, sessionToken, callback) {
+    if (!callback) {
+      callback = function(error) {
+        if (error) {
+          MozLoopService.log.error(error);
+        }
+      };
+    }
+    this._postToRoom(roomToken, {
+      action: "leave",
+      sessionToken: sessionToken
+    }, callback);
+  },
 
   /**
    * Callback used to indicate changes to rooms data on the LoopServer.
    *
    * @param {String} version   Version number assigned to this change set.
    * @param {String} channelID Notification channel identifier.
    */
   onNotification: function(version, channelID) {
@@ -317,16 +395,29 @@ this.LoopRooms = {
   open: function(roomToken) {
     return LoopRoomsInternal.open(roomToken);
   },
 
   delete: function(roomToken, callback) {
     return LoopRoomsInternal.delete(roomToken, callback);
   },
 
+  join: function(roomToken, callback) {
+    return LoopRoomsInternal.join(roomToken, callback);
+  },
+
+  refreshMembership: function(roomToken, sessionToken, callback) {
+    return LoopRoomsInternal.refreshMembership(roomToken, sessionToken,
+      callback);
+  },
+
+  leave: function(roomToken, sessionToken, callback) {
+    return LoopRoomsInternal.leave(roomToken, sessionToken, callback);
+  },
+
   promise: function(method, ...params) {
     return new Promise((resolve, reject) => {
       this[method](...params, (error, result) => {
         if (error) {
           reject(error);
         } else {
           resolve(result);
         }
--- a/browser/components/loop/test/xpcshell/test_looprooms.js
+++ b/browser/components/loop/test/xpcshell/test_looprooms.js
@@ -207,17 +207,26 @@ add_task(function* setup_server() {
     res.write(JSON.stringify(roomDetail));
     res.processAsync();
     res.finish();
   }
 
   // Add a request handler for each room in the list.
   [...kRooms.values()].forEach(function(room) {
     loopServer.registerPathHandler("/rooms/" + encodeURIComponent(room.roomToken), (req, res) => {
-      returnRoomDetails(res, room.roomName);
+      if (req.method == "POST") {
+        let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream);
+        let data = JSON.parse(body);
+        res.setStatusLine(null, 200, "OK");
+        res.write(JSON.stringify(data));
+        res.processAsync();
+        res.finish();
+      } else {
+        returnRoomDetails(res, room.roomName);
+      }
     });
   });
 
   loopServer.registerPathHandler("/rooms/error401", (req, res) => {
     res.setStatusLine(null, 401, "Not Found");
     res.processAsync();
     res.finish();
   });
@@ -316,16 +325,49 @@ add_task(function* test_roomUpdates() {
   gExpectedUpdates.push("_nxD4V4FflQ");
   gExpectedJoins["_nxD4V4FflQ"] = [
     "2a1787a6-4a73-43b5-ae3e-906ec1e763cb",
     "5de6281c-6568-455f-af08-c0b0a973100e"];
   roomsPushNotification("4");
   yield waitForCondition(() => Object.getOwnPropertyNames(gExpectedJoins).length === 0);
 });
 
+// Test if joining a room works as expected.
+add_task(function* test_joinRoom() {
+  // We need these set up for getting the email address.
+  Services.prefs.setCharPref("loop.fxa_oauth.profile", JSON.stringify({
+    email: "fake@invalid.com"
+  }));
+  Services.prefs.setCharPref("loop.fxa_oauth.tokendata", JSON.stringify({
+    token_type: "bearer"
+  }));
+
+  let roomToken = "_nxD4V4FflQ";
+  let joinedData = yield LoopRooms.promise("join", roomToken);
+  Assert.equal(joinedData.action, "join");
+  Assert.equal(joinedData.displayName, "fake@invalid.com");
+});
+
+// Test if refreshing a room works as expected.
+add_task(function* test_refreshMembership() {
+  let roomToken = "_nxD4V4FflQ";
+  let refreshedData = yield LoopRooms.promise("refreshMembership", roomToken,
+    "fakeSessionToken");
+  Assert.equal(refreshedData.action, "refresh");
+  Assert.equal(refreshedData.sessionToken, "fakeSessionToken");
+});
+
+// Test if leaving a room works as expected.
+add_task(function* test_leaveRoom() {
+  let roomToken = "_nxD4V4FflQ";
+  let leaveData = yield LoopRooms.promise("leave", roomToken, "fakeLeaveSessionToken");
+  Assert.equal(leaveData.action, "leave");
+  Assert.equal(leaveData.sessionToken, "fakeLeaveSessionToken");
+});
+
 // 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");
  });
 
 function run_test() {
   setupFakeLoopServer();
@@ -334,16 +376,19 @@ function run_test() {
   LoopRooms.on("update", onRoomUpdated);
   LoopRooms.on("joined", onRoomJoined);
   LoopRooms.on("left", onRoomLeft);
 
   do_register_cleanup(function () {
     // Revert original Chat.open implementation
     Chat.open = openChatOrig;
 
+    Services.prefs.clearUserPref("loop.fxa_oauth.profile");
+    Services.prefs.clearUserPref("loop.fxa_oauth.tokendata");
+
     LoopRooms.off("add", onRoomAdded);
     LoopRooms.off("update", onRoomUpdated);
     LoopRooms.off("joined", onRoomJoined);
     LoopRooms.off("left", onRoomLeft);
   });
 
   run_next_test();
 }