Bug 1074688 - Part 2 Add Join/Refresh/Leave room functions to the mozLoop API. r=mikedeboer
authorMark Banner <standard8@mozilla.com>
Thu, 06 Nov 2014 20:52:16 +0000
changeset 214468 e5503802d876767bbc3ae79196440663c44c3639
parent 214467 5a26fa6c3ad270c6e78042135f568d07b15821d7
child 214469 ca16d47debf88e9fbe103c16fa3184e5ed218faa
push id51494
push userkwierso@gmail.com
push dateFri, 07 Nov 2014 03:08:20 +0000
treeherdermozilla-inbound@c4b831696f15 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1074688
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 1074688 - Part 2 Add Join/Refresh/Leave room functions to the mozLoop API. r=mikedeboer
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();
 }