зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1089547: simplify LoopRooms implementation, add support for events. r=Standard8
This commit is contained in:
Родитель
256835686e
Коммит
a2c956ceba
|
@ -5,339 +5,223 @@
|
|||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService",
|
||||
"resource:///modules/loop/MozLoopService.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LOOP_SESSION_TYPE",
|
||||
"resource:///modules/loop/MozLoopService.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
|
||||
"resource:///modules/loop/MozLoopPushHandler.jsm");
|
||||
const {MozLoopService, LOOP_SESSION_TYPE} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"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"];
|
||||
|
||||
let gRoomsListFetched = false;
|
||||
let gRooms = new Map();
|
||||
let gCallbacks = new Map();
|
||||
|
||||
/**
|
||||
* Callback used to indicate changes to rooms data on the LoopServer.
|
||||
*
|
||||
* @param {Object} version Version number assigned to this change set.
|
||||
* @param {Object} channelID Notification channel identifier.
|
||||
*
|
||||
*/
|
||||
const roomsPushNotification = function(version, channelID) {
|
||||
return LoopRoomsInternal.onNotification(version, channelID);
|
||||
};
|
||||
|
||||
let LoopRoomsInternal = {
|
||||
getAll: function(callback) {
|
||||
Task.spawn(function*() {
|
||||
yield MozLoopService.register();
|
||||
|
||||
if (gRoomsListFetched) {
|
||||
callback(null, [...gRooms.values()]);
|
||||
return;
|
||||
}
|
||||
// Fetch the rooms from the server.
|
||||
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
|
||||
LOOP_SESSION_TYPE.GUEST;
|
||||
let rooms = yield this.requestRoomList(sessionType);
|
||||
// Add each room to our in-memory Map using a locally unique
|
||||
// identifier.
|
||||
for (let room of rooms) {
|
||||
let id = MozLoopService.generateLocalID();
|
||||
room.localRoomId = id;
|
||||
// Next, request the detailed information for each room.
|
||||
// If the request fails the room data will not be added to the map.
|
||||
try {
|
||||
let details = yield this.requestRoomDetails(room.roomToken, sessionType);
|
||||
for (let attr in details) {
|
||||
room[attr] = details[attr]
|
||||
}
|
||||
delete room.currSize; //This attribute will be eliminated in the next revision.
|
||||
gRooms.set(id, room);
|
||||
}
|
||||
catch (error) {MozLoopService.log.warn(
|
||||
"failed GETing room details for roomToken = " + room.roomToken + ": ", error)}
|
||||
}
|
||||
callback(null, [...gRooms.values()]);
|
||||
return;
|
||||
}.bind(this)).catch((error) => {MozLoopService.log.error("getAll error:", error);
|
||||
callback(error)});
|
||||
return;
|
||||
},
|
||||
|
||||
getRoomData: function(localRoomId, callback) {
|
||||
if (gRooms.has(localRoomId)) {
|
||||
callback(null, gRooms.get(localRoomId));
|
||||
} else {
|
||||
callback(new Error("Room data not found or not fetched yet for room with ID " + localRoomId));
|
||||
}
|
||||
return;
|
||||
},
|
||||
|
||||
/**
|
||||
* Request list of all rooms associated with this account.
|
||||
*
|
||||
* @param {String} sessionType Indicates which hawkRequest endpoint to use.
|
||||
*
|
||||
* @returns {Promise} room list
|
||||
*/
|
||||
requestRoomList: function(sessionType) {
|
||||
return MozLoopService.hawkRequest(sessionType, "/rooms", "GET")
|
||||
.then(response => {
|
||||
let roomsList = JSON.parse(response.body);
|
||||
if (!Array.isArray(roomsList)) {
|
||||
// Force a reject in the returned promise.
|
||||
// To be caught by the caller using the returned Promise.
|
||||
throw new Error("Missing array of rooms in response.");
|
||||
}
|
||||
return roomsList;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Request information about a specific room from the server.
|
||||
*
|
||||
* @param {Object} token Room identifier returned from the LoopServer.
|
||||
* @param {String} sessionType Indicates which hawkRequest endpoint to use.
|
||||
*
|
||||
* @returns {Promise} room details
|
||||
*/
|
||||
requestRoomDetails: function(token, sessionType) {
|
||||
return MozLoopService.hawkRequest(sessionType, "/rooms/" + token, "GET")
|
||||
.then(response => JSON.parse(response.body));
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback used to indicate changes to rooms data on the LoopServer.
|
||||
*
|
||||
* @param {Object} version Version number assigned to this change set.
|
||||
* @param {Object} channelID Notification channel identifier.
|
||||
*
|
||||
*/
|
||||
onNotification: function(version, channelID) {
|
||||
return;
|
||||
},
|
||||
|
||||
createRoom: function(props, callback) {
|
||||
// Always create a basic room record and launch the window, attaching
|
||||
// the localRoomId. Later errors will be returned via the registered callback.
|
||||
let localRoomId = MozLoopService.generateLocalID((id) => {gRooms.has(id)})
|
||||
let room = {localRoomId : localRoomId};
|
||||
for (let prop in props) {
|
||||
room[prop] = props[prop]
|
||||
}
|
||||
|
||||
gRooms.set(localRoomId, room);
|
||||
this.addCallback(localRoomId, "RoomCreated", callback);
|
||||
MozLoopService.openChatWindow(null, "", "about:loopconversation#room/" + localRoomId);
|
||||
|
||||
if (!"roomName" in props ||
|
||||
!"expiresIn" in props ||
|
||||
!"roomOwner" in props ||
|
||||
!"maxSize" in props) {
|
||||
this.postCallback(localRoomId, "RoomCreated",
|
||||
new Error("missing required room create property"));
|
||||
return localRoomId;
|
||||
}
|
||||
|
||||
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
|
||||
LOOP_SESSION_TYPE.GUEST;
|
||||
|
||||
MozLoopService.hawkRequest(sessionType, "/rooms", "POST", props).then(
|
||||
(response) => {
|
||||
let data = JSON.parse(response.body);
|
||||
for (let attr in data) {
|
||||
room[attr] = data[attr]
|
||||
}
|
||||
delete room.expiresIn; //Do not keep this value - it is a request to the server
|
||||
this.postCallback(localRoomId, "RoomCreated", null, room);
|
||||
},
|
||||
(error) => {
|
||||
this.postCallback(localRoomId, "RoomCreated", error);
|
||||
});
|
||||
|
||||
return localRoomId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send an update to the callbacks registered for a specific localRoomId
|
||||
* for a callback type.
|
||||
*
|
||||
* The result set is always saved. Then each
|
||||
* callback function that has been registered when this function is
|
||||
* called will be called with the result set. Any new callback that
|
||||
* is regsitered via addCallback will receive a copy of the last
|
||||
* saved result set when registered. This allows the posting operation
|
||||
* to complete before the callback is registered in an asynchronous
|
||||
* operation.
|
||||
*
|
||||
* Callbacsk must be of the form:
|
||||
* function (error, success) {...}
|
||||
*
|
||||
* @param {String} localRoomId Local room identifier.
|
||||
* @param {String} callbackName callback type
|
||||
* @param {?Error} error result or null.
|
||||
* @param {?Object} success result if error argument is null.
|
||||
*/
|
||||
postCallback: function(localRoomId, callbackName, error, success) {
|
||||
let roomCallbacks = gCallbacks.get(localRoomId);
|
||||
if (!roomCallbacks) {
|
||||
// No callbacks have been registered or results posted for this room.
|
||||
// Initialize a record for this room and callbackName, saving the
|
||||
// result set.
|
||||
gCallbacks.set(localRoomId, new Map([[
|
||||
callbackName,
|
||||
{ callbackList: [], result: { error: error, success: success } }]]));
|
||||
return;
|
||||
}
|
||||
|
||||
let namedCallback = roomCallbacks.get(callbackName);
|
||||
// A callback of this name has not been registered.
|
||||
if (!namedCallback) {
|
||||
roomCallbacks.set(
|
||||
callbackName,
|
||||
{callbackList: [], result: {error: error, success: success}});
|
||||
return;
|
||||
}
|
||||
|
||||
// Record the latest result set.
|
||||
namedCallback.result = {error: error, success: success};
|
||||
|
||||
// Call each registerd callback passing the new result posted.
|
||||
namedCallback.callbackList.forEach((callback) => {
|
||||
callback(error, success);
|
||||
});
|
||||
},
|
||||
|
||||
addCallback: function(localRoomId, callbackName, callback) {
|
||||
let roomCallbacks = gCallbacks.get(localRoomId);
|
||||
if (!roomCallbacks) {
|
||||
// No callbacks have been registered or results posted for this room.
|
||||
// Initialize a record for this room and callbackName.
|
||||
gCallbacks.set(localRoomId, new Map([[
|
||||
callbackName,
|
||||
{callbackList: [callback]}]]));
|
||||
return;
|
||||
}
|
||||
|
||||
let namedCallback = roomCallbacks.get(callbackName);
|
||||
// A callback of this name has not been registered.
|
||||
if (!namedCallback) {
|
||||
roomCallbacks.set(
|
||||
callbackName,
|
||||
{callbackList: [callback]});
|
||||
return;
|
||||
}
|
||||
|
||||
// Add this callback if not already in the array
|
||||
if (namedCallback.callbackList.indexOf(callback) >= 0) {
|
||||
return;
|
||||
}
|
||||
namedCallback.callbackList.push(callback);
|
||||
|
||||
// If a result has been posted for this callback
|
||||
// send it using this new callback function.
|
||||
let result = namedCallback.result;
|
||||
if (result) {
|
||||
callback(result.error, result.success);
|
||||
}
|
||||
},
|
||||
|
||||
deleteCallback: function(localRoomId, callbackName, callback) {
|
||||
let roomCallbacks = gCallbacks.get(localRoomId);
|
||||
if (!roomCallbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
let namedCallback = roomCallbacks.get(callbackName);
|
||||
if (!namedCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
let i = namedCallback.callbackList.indexOf(callback);
|
||||
if (i >= 0) {
|
||||
namedCallback.callbackList.splice(i, 1);
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
return LoopRoomsInternal.onNotification(version, channelID);
|
||||
};
|
||||
Object.freeze(LoopRoomsInternal);
|
||||
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* The LoopRooms class.
|
||||
* 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) {
|
||||
for (let key of Object.getOwnPropertyNames(source)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
/**
|
||||
* The Rooms class.
|
||||
*
|
||||
* Each method that is a member of this class requires the last argument to be a
|
||||
* callback Function. MozLoopAPI will cause things to break if this invariant is
|
||||
* violated. You'll notice this as well in the documentation for each method.
|
||||
*/
|
||||
this.LoopRooms = {
|
||||
let LoopRoomsInternal = {
|
||||
rooms: new Map(),
|
||||
|
||||
/**
|
||||
* Fetch a list of rooms that the currently registered user is a member of.
|
||||
*
|
||||
* @param {String} [version] If set, we will fetch a list of changed rooms since
|
||||
* `version`. Optional.
|
||||
* @param {Function} callback Function that will be invoked once the operation
|
||||
* finished. The first argument passed will be an
|
||||
* `Error` object or `null`. The second argument will
|
||||
* be the list of rooms, if it was fetched successfully.
|
||||
*/
|
||||
getAll: function(version = null, callback) {
|
||||
if (!callback) {
|
||||
callback = version;
|
||||
version = null;
|
||||
}
|
||||
|
||||
Task.spawn(function* () {
|
||||
yield MozLoopService.register();
|
||||
|
||||
if (!gDirty) {
|
||||
callback(null, [...this.rooms.values()]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch the rooms from the server.
|
||||
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
|
||||
LOOP_SESSION_TYPE.GUEST;
|
||||
let url = "/rooms" + (version ? "?version=" + encodeURIComponent(version) : "");
|
||||
let response = yield MozLoopService.hawkRequest(sessionType, url, "GET");
|
||||
let roomsList = JSON.parse(response.body);
|
||||
if (!Array.isArray(roomsList)) {
|
||||
throw new Error("Missing array of rooms in response.");
|
||||
}
|
||||
|
||||
// Next, request the detailed information for each room. If the request
|
||||
// fails the room data will not be added to the map.
|
||||
for (let room of roomsList) {
|
||||
let eventName = this.rooms.has(room.roomToken) ? "update" : "add";
|
||||
this.rooms.set(room.roomToken, room);
|
||||
yield LoopRooms.promise("get", room.roomToken);
|
||||
}
|
||||
|
||||
// Set the 'dirty' flag back to FALSE, since the list is as fresh as can be now.
|
||||
gDirty = false;
|
||||
callback(null, [...this.rooms.values()]);
|
||||
}.bind(this)).catch(error => {
|
||||
callback(error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Request information about a specific room from the server. It will be
|
||||
* returned from the cache if it's already in it.
|
||||
*
|
||||
* @param {String} roomToken Room identifier
|
||||
* @param {Function} callback Function that will be invoked once the operation
|
||||
* finished. The first argument passed will be an
|
||||
* `Error` object or `null`. The second argument will
|
||||
* be the list of rooms, if it was fetched successfully.
|
||||
*/
|
||||
get: function(roomToken, callback) {
|
||||
let room = this.rooms.has(roomToken) ? this.rooms.get(roomToken) : {};
|
||||
// Check if we need to make a request to the server to collect more room data.
|
||||
if (!room || gDirty || !("participants" in room)) {
|
||||
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
|
||||
LOOP_SESSION_TYPE.GUEST;
|
||||
MozLoopService.hawkRequest(sessionType, "/rooms/" + encodeURIComponent(roomToken), "GET")
|
||||
.then(response => {
|
||||
let eventName = ("roomToken" in room) ? "add" : "update";
|
||||
extend(room, JSON.parse(response.body));
|
||||
// Remove the `currSize` for posterity.
|
||||
if ("currSize" in room) {
|
||||
delete room.currSize;
|
||||
}
|
||||
this.rooms.set(roomToken, room);
|
||||
|
||||
eventEmitter.emit(eventName, room);
|
||||
callback(null, room);
|
||||
}, err => callback(err)).catch(err => callback(err));
|
||||
} else {
|
||||
callback(null, room);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a room.
|
||||
*
|
||||
* @param {Object} room Properties to be sent to the LoopServer
|
||||
* @param {Function} callback Function that will be invoked once the operation
|
||||
* finished. The first argument passed will be an
|
||||
* `Error` object or `null`. The second argument will
|
||||
* be the list of rooms, if it was fetched successfully.
|
||||
* be the room, if it was created successfully.
|
||||
*/
|
||||
getAll: function(callback) {
|
||||
return LoopRoomsInternal.getAll(callback);
|
||||
create: function(room, callback) {
|
||||
if (!("roomName" in room) || !("expiresIn" in room) ||
|
||||
!("roomOwner" in room) || !("maxSize" in room)) {
|
||||
callback(new Error("Missing required property to create a room"));
|
||||
return;
|
||||
}
|
||||
|
||||
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
|
||||
LOOP_SESSION_TYPE.GUEST;
|
||||
|
||||
MozLoopService.hawkRequest(sessionType, "/rooms", "POST", room)
|
||||
.then(response => {
|
||||
let data = JSON.parse(response.body);
|
||||
extend(room, data);
|
||||
// Do not keep this value - it is a request to the server.
|
||||
delete room.expiresIn;
|
||||
this.rooms.set(room.roomToken, room);
|
||||
|
||||
eventEmitter.emit("add", room);
|
||||
callback(null, room);
|
||||
}, error => callback(error)).catch(error => callback(error));
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the current stored version of the data for the indicated room.
|
||||
* Callback used to indicate changes to rooms data on the LoopServer.
|
||||
*
|
||||
* @param {String} localRoomId Local room identifier
|
||||
* @param {Function} callback Function that will be invoked once the operation
|
||||
* finished. The first argument passed will be an
|
||||
* `Error` object or `null`. The second argument will
|
||||
* be the list of rooms, if it was fetched successfully.
|
||||
* @param {String} version Version number assigned to this change set.
|
||||
* @param {String} channelID Notification channel identifier.
|
||||
*/
|
||||
getRoomData: function(localRoomId, callback) {
|
||||
return LoopRoomsInternal.getRoomData(localRoomId, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a room. Will both open a chat window for the new room
|
||||
* and perform an exchange with the LoopServer to create the room.
|
||||
* for a callback type. Callback must be of the form:
|
||||
* function (error, success) {...}
|
||||
*
|
||||
* @param {Object} room properties to be sent to the LoopServer
|
||||
* @param {Function} callback Must be of the form: function (error, success) {...}
|
||||
*
|
||||
* @returns {String} localRoomId assigned to this new room.
|
||||
*/
|
||||
createRoom: function(roomProps, callback) {
|
||||
return LoopRoomsInternal.createRoom(roomProps, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a callback of a specified type with a localRoomId.
|
||||
*
|
||||
* @param {String} localRoomId Local room identifier.
|
||||
* @param {String} callbackName callback type
|
||||
* @param {Function} callback Must be of the form: function (error, success) {...}
|
||||
*/
|
||||
addCallback: function(localRoomId, callbackName, callback) {
|
||||
return LoopRoomsInternal.addCallback(localRoomId, callbackName, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Un-register and delete a callback of a specified type for a localRoomId.
|
||||
*
|
||||
* @param {String} localRoomId Local room identifier.
|
||||
* @param {String} callbackName callback type
|
||||
* @param {Function} callback Previously passed to addCallback().
|
||||
*/
|
||||
deleteCallback: function(localRoomId, callbackName, callback) {
|
||||
return LoopRoomsInternal.deleteCallback(localRoomId, callbackName, callback);
|
||||
onNotification: function(version, channelID) {
|
||||
gDirty = true;
|
||||
this.getAll(version, () => {});
|
||||
},
|
||||
};
|
||||
Object.freeze(LoopRooms);
|
||||
Object.freeze(LoopRoomsInternal);
|
||||
|
||||
/**
|
||||
* Public Loop Rooms API.
|
||||
*
|
||||
* LoopRooms implements the EventEmitter interface by exposing three methods -
|
||||
* `on`, `once` and `off` - to subscribe to events.
|
||||
* At this point the following events may be subscribed to:
|
||||
* - 'add': A new room object was successfully added to the data store.
|
||||
* - 'remove': A room was successfully removed from the data store.
|
||||
* - 'update': A room object was successfully updated with changed
|
||||
* properties in the data store.
|
||||
*
|
||||
* See the internal code for the API documentation.
|
||||
*/
|
||||
this.LoopRooms = {
|
||||
getAll: function(version, callback) {
|
||||
return LoopRoomsInternal.getAll(version, callback);
|
||||
},
|
||||
|
||||
get: function(roomToken, callback) {
|
||||
return LoopRoomsInternal.get(roomToken, callback);
|
||||
},
|
||||
|
||||
create: function(options, callback) {
|
||||
return LoopRoomsInternal.create(options, callback);
|
||||
},
|
||||
|
||||
promise: function(method, ...params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this[method](...params, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
on: (...params) => eventEmitter.on(...params),
|
||||
|
||||
once: (...params) => eventEmitter.once(...params),
|
||||
|
||||
off: (...params) => eventEmitter.off(...params)
|
||||
};
|
||||
Object.freeze(this.LoopRooms);
|
||||
|
|
|
@ -75,6 +75,14 @@ const cloneValueInto = function(value, targetWindow) {
|
|||
return value;
|
||||
}
|
||||
|
||||
// Strip Function properties, since they can not be cloned across boundaries
|
||||
// like this.
|
||||
for (let prop of value) {
|
||||
if (typeof value[prop] == "function") {
|
||||
delete value[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect for an error this way, because the Error object is special.
|
||||
if (value.constructor.name == "Error") {
|
||||
return cloneErrorObject(value, targetWindow);
|
||||
|
@ -176,8 +184,10 @@ function injectLoopAPI(targetWindow) {
|
|||
}
|
||||
|
||||
// We have to clone the error property since it may be an Error object.
|
||||
if (error.hasOwnProperty("toString")) {
|
||||
delete error.toString;
|
||||
}
|
||||
errors[type] = Cu.cloneInto(error, targetWindow);
|
||||
|
||||
}
|
||||
return Cu.cloneInto(errors, targetWindow);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
/* 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");
|
||||
|
||||
const kRooms = new Map([
|
||||
["_nxD4V4FflQ", {
|
||||
roomToken: "_nxD4V4FflQ",
|
||||
roomName: "First Room Name",
|
||||
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
|
||||
maxSize: 2,
|
||||
currSize: 0,
|
||||
ctime: 1405517546
|
||||
}],
|
||||
["QzBbvGmIZWU", {
|
||||
roomToken: "QzBbvGmIZWU",
|
||||
roomName: "Second Room Name",
|
||||
roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU",
|
||||
maxSize: 2,
|
||||
currSize: 0,
|
||||
ctime: 140551741
|
||||
}],
|
||||
["3jKS_Els9IU", {
|
||||
roomToken: "3jKS_Els9IU",
|
||||
roomName: "Third Room Name",
|
||||
roomUrl: "http://localhost:3000/rooms/3jKS_Els9IU",
|
||||
maxSize: 3,
|
||||
clientMaxSize: 2,
|
||||
currSize: 1,
|
||||
ctime: 1405518241
|
||||
}]
|
||||
]);
|
||||
|
||||
let roomDetail = {
|
||||
roomName: "First Room Name",
|
||||
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
|
||||
roomOwner: "Alexis",
|
||||
maxSize: 2,
|
||||
clientMaxSize: 2,
|
||||
creationTime: 1405517546,
|
||||
expiresAt: 1405534180,
|
||||
participants: [{
|
||||
displayName: "Alexis",
|
||||
account: "alexis@example.com",
|
||||
roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb"
|
||||
}, {
|
||||
displayName: "Adam",
|
||||
roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
|
||||
}]
|
||||
};
|
||||
|
||||
const kCreateRoomProps = {
|
||||
roomName: "UX Discussion",
|
||||
expiresIn: 5,
|
||||
roomOwner: "Alexis",
|
||||
maxSize: 2
|
||||
};
|
||||
|
||||
const kCreateRoomData = {
|
||||
roomToken: "_nxD4V4FflQ",
|
||||
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
|
||||
expiresAt: 1405534180
|
||||
};
|
||||
|
||||
add_task(function* setup_server() {
|
||||
loopServer.registerPathHandler("/registration", (req, res) => {
|
||||
res.setStatusLine(null, 200, "OK");
|
||||
res.processAsync();
|
||||
res.finish();
|
||||
});
|
||||
|
||||
loopServer.registerPathHandler("/rooms", (req, res) => {
|
||||
res.setStatusLine(null, 200, "OK");
|
||||
|
||||
if (req.method == "POST") {
|
||||
Assert.ok(req.bodyInputStream, "POST request should have a payload");
|
||||
let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream);
|
||||
let data = JSON.parse(body);
|
||||
Assert.deepEqual(data, kCreateRoomProps);
|
||||
|
||||
res.write(JSON.stringify(kCreateRoomData));
|
||||
} else {
|
||||
res.write(JSON.stringify([...kRooms.values()]));
|
||||
}
|
||||
|
||||
res.processAsync();
|
||||
res.finish();
|
||||
});
|
||||
|
||||
function returnRoomDetails(res, roomName) {
|
||||
roomDetail.roomName = roomName;
|
||||
res.setStatusLine(null, 200, "OK");
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
loopServer.registerPathHandler("/rooms/error401", (req, res) => {
|
||||
res.setStatusLine(null, 401, "Not Found");
|
||||
res.processAsync();
|
||||
res.finish();
|
||||
});
|
||||
|
||||
loopServer.registerPathHandler("/rooms/errorMalformed", (req, res) => {
|
||||
res.setStatusLine(null, 200, "OK");
|
||||
res.write("{\"some\": \"Syntax Error!\"}}}}}}");
|
||||
res.processAsync();
|
||||
res.finish();
|
||||
});
|
||||
});
|
||||
|
||||
const normalizeRoom = function(room) {
|
||||
delete room.currSize;
|
||||
if (!("participants" in room)) {
|
||||
let name = room.roomName;
|
||||
for (let key of Object.getOwnPropertyNames(roomDetail)) {
|
||||
room[key] = roomDetail[key];
|
||||
}
|
||||
room.roomName = name;
|
||||
}
|
||||
return room;
|
||||
};
|
||||
|
||||
const compareRooms = function(room1, room2) {
|
||||
Assert.deepEqual(normalizeRoom(room1), normalizeRoom(room2));
|
||||
};
|
||||
|
||||
add_task(function* test_getAllRooms() {
|
||||
yield MozLoopService.register(mockPushHandler);
|
||||
|
||||
let rooms = yield LoopRooms.promise("getAll");
|
||||
Assert.equal(rooms.length, 3);
|
||||
for (let room of rooms) {
|
||||
compareRooms(kRooms.get(room.roomToken), room);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_getRoom() {
|
||||
yield MozLoopService.register(mockPushHandler);
|
||||
|
||||
let roomToken = "_nxD4V4FflQ";
|
||||
let room = yield LoopRooms.promise("get", roomToken);
|
||||
Assert.deepEqual(room, kRooms.get(roomToken));
|
||||
});
|
||||
|
||||
add_task(function* test_errorStates() {
|
||||
yield Assert.rejects(LoopRooms.promise("get", "error401"), /Not Found/, "Fetching a non-existent room should fail");
|
||||
yield Assert.rejects(LoopRooms.promise("get", "errorMalformed"), /SyntaxError/, "Wrong message format should reject");
|
||||
});
|
||||
|
||||
add_task(function* test_createRoom() {
|
||||
let eventCalled = false;
|
||||
LoopRooms.once("add", (e, room) => {
|
||||
compareRooms(room, kCreateRoomProps);
|
||||
eventCalled = true;
|
||||
});
|
||||
let room = yield LoopRooms.promise("create", kCreateRoomProps);
|
||||
compareRooms(room, kCreateRoomProps);
|
||||
Assert.ok(eventCalled, "Event should have fired");
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
run_next_test();
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
/* 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");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
|
||||
"resource:///modules/Chat.jsm");
|
||||
let hasTheseProps = function(a, b) {
|
||||
for (let prop in a) {
|
||||
if (a[prop] != b[prop]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let openChatOrig = Chat.open;
|
||||
|
||||
add_test(function test_openRoomsWindow() {
|
||||
let roomProps = {roomName: "UX Discussion",
|
||||
expiresIn: 5,
|
||||
roomOwner: "Alexis",
|
||||
maxSize: 2}
|
||||
|
||||
let roomData = {roomToken: "_nxD4V4FflQ",
|
||||
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
|
||||
expiresAt: 1405534180}
|
||||
|
||||
loopServer.registerPathHandler("/rooms", (request, response) => {
|
||||
if (!request.bodyInputStream) {
|
||||
do_throw("empty request body");
|
||||
}
|
||||
let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
|
||||
let data = JSON.parse(body);
|
||||
do_check_true(hasTheseProps(roomProps, data));
|
||||
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify(roomData));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
let opened = false;
|
||||
let created = false;
|
||||
let urlPieces = [];
|
||||
|
||||
Chat.open = function(contentWindow, origin, title, url) {
|
||||
urlPieces = url.split('/');
|
||||
do_check_eq(urlPieces[0], "about:loopconversation#room");
|
||||
opened = true;
|
||||
};
|
||||
|
||||
let returnedID = LoopRooms.createRoom(roomProps, (error, data) => {
|
||||
do_check_false(error);
|
||||
do_check_true(data);
|
||||
do_check_true(hasTheseProps(roomData, data));
|
||||
do_check_eq(data.localRoomId, urlPieces[1]);
|
||||
created = true;
|
||||
});
|
||||
|
||||
waitForCondition(function() created && opened).then(() => {
|
||||
do_check_true(opened, "should open a chat window");
|
||||
do_check_eq(returnedID, urlPieces[1]);
|
||||
|
||||
// Verify that a delayed callback, when attached,
|
||||
// received the same data.
|
||||
LoopRooms.addCallback(
|
||||
urlPieces[1], "RoomCreated",
|
||||
(error, data) => {
|
||||
do_check_false(error);
|
||||
do_check_true(data);
|
||||
do_check_true(hasTheseProps(roomData, data));
|
||||
do_check_eq(data.localRoomId, urlPieces[1]);
|
||||
});
|
||||
|
||||
run_next_test();
|
||||
}, () => {
|
||||
do_throw("should have opened a chat window");
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
setupFakeLoopServer();
|
||||
mockPushHandler.registrationPushURL = kEndPointUrl;
|
||||
|
||||
loopServer.registerPathHandler("/registration", (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
|
||||
do_register_cleanup(function() {
|
||||
// Revert original Chat.open implementation
|
||||
Chat.open = openChatOrig;
|
||||
});
|
||||
|
||||
run_next_test();
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
/* 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");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
|
||||
"resource:///modules/Chat.jsm");
|
||||
let hasTheseProps = function(a, b) {
|
||||
for (let prop in a) {
|
||||
if (a[prop] != b[prop]) {
|
||||
do_print("hasTheseProps fail: prop = " + prop);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let openChatOrig = Chat.open;
|
||||
|
||||
add_test(function test_getAllRooms() {
|
||||
|
||||
let roomList = [
|
||||
{ roomToken: "_nxD4V4FflQ",
|
||||
roomName: "First Room Name",
|
||||
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
|
||||
maxSize: 2,
|
||||
currSize: 0,
|
||||
ctime: 1405517546 },
|
||||
{ roomToken: "QzBbvGmIZWU",
|
||||
roomName: "Second Room Name",
|
||||
roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU",
|
||||
maxSize: 2,
|
||||
currSize: 0,
|
||||
ctime: 140551741 },
|
||||
{ roomToken: "3jKS_Els9IU",
|
||||
roomName: "Third Room Name",
|
||||
roomUrl: "http://localhost:3000/rooms/3jKS_Els9IU",
|
||||
maxSize: 3,
|
||||
clientMaxSize: 2,
|
||||
currSize: 1,
|
||||
ctime: 1405518241 }
|
||||
]
|
||||
|
||||
let roomDetail = {
|
||||
roomName: "First Room Name",
|
||||
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
|
||||
roomOwner: "Alexis",
|
||||
maxSize: 2,
|
||||
clientMaxSize: 2,
|
||||
creationTime: 1405517546,
|
||||
expiresAt: 1405534180,
|
||||
participants: [
|
||||
{ displayName: "Alexis", account: "alexis@example.com", roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb" },
|
||||
{ displayName: "Adam", roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7" }
|
||||
]
|
||||
}
|
||||
|
||||
loopServer.registerPathHandler("/rooms", (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify(roomList));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
|
||||
let returnRoomDetails = function(response, roomName) {
|
||||
roomDetail.roomName = roomName;
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify(roomDetail));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
}
|
||||
|
||||
loopServer.registerPathHandler("/rooms/_nxD4V4FflQ", (request, response) => {
|
||||
returnRoomDetails(response, "First Room Name");
|
||||
});
|
||||
|
||||
loopServer.registerPathHandler("/rooms/QzBbvGmIZWU", (request, response) => {
|
||||
returnRoomDetails(response, "Second Room Name");
|
||||
});
|
||||
|
||||
loopServer.registerPathHandler("/rooms/3jKS_Els9IU", (request, response) => {
|
||||
returnRoomDetails(response, "Third Room Name");
|
||||
});
|
||||
|
||||
MozLoopService.register().then(() => {
|
||||
|
||||
LoopRooms.getAll((error, rooms) => {
|
||||
do_check_false(error);
|
||||
do_check_true(rooms);
|
||||
do_check_eq(rooms.length, 3);
|
||||
do_check_eq(rooms[0].roomName, "First Room Name");
|
||||
do_check_eq(rooms[1].roomName, "Second Room Name");
|
||||
do_check_eq(rooms[2].roomName, "Third Room Name");
|
||||
|
||||
let room = rooms[0];
|
||||
do_check_true(room.localRoomId);
|
||||
do_check_false(room.currSize);
|
||||
delete roomList[0].currSize;
|
||||
do_check_true(hasTheseProps(roomList[0], room));
|
||||
delete roomDetail.roomName;
|
||||
delete room.participants;
|
||||
delete roomDetail.participants;
|
||||
do_check_true(hasTheseProps(roomDetail, room));
|
||||
|
||||
LoopRooms.getRoomData(room.localRoomId, (error, roomData) => {
|
||||
do_check_false(error);
|
||||
do_check_true(hasTheseProps(room, roomData));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
mockPushHandler.registrationPushURL = kEndPointUrl;
|
||||
|
||||
loopServer.registerPathHandler("/registration", (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
|
||||
do_register_cleanup(function() {
|
||||
// Revert original Chat.open implementation
|
||||
Chat.open = openChatOrig;
|
||||
});
|
||||
|
||||
run_next_test();
|
||||
}
|
|
@ -6,6 +6,7 @@ skip-if = toolkit == 'gonk'
|
|||
|
||||
[test_loopapi_hawk_request.js]
|
||||
[test_looppush_initialize.js]
|
||||
[test_looprooms.js]
|
||||
[test_loopservice_directcall.js]
|
||||
[test_loopservice_dnd.js]
|
||||
[test_loopservice_expiry.js]
|
||||
|
@ -21,5 +22,3 @@ skip-if = toolkit == 'gonk'
|
|||
[test_loopservice_token_send.js]
|
||||
[test_loopservice_token_validation.js]
|
||||
[test_loopservice_busy.js]
|
||||
[test_rooms_getdata.js]
|
||||
[test_rooms_create.js]
|
||||
|
|
|
@ -109,6 +109,7 @@ this.HawkClient.prototype = {
|
|||
code: restResponse.status,
|
||||
errno: restResponse.status
|
||||
};
|
||||
errorObj.toString = function() this.code + ": " + this.message;
|
||||
let retryAfter = restResponse.headers && restResponse.headers["retry-after"];
|
||||
retryAfter = retryAfter ? parseInt(retryAfter) : retryAfter;
|
||||
if (retryAfter) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче