Bug 1089547: simplify LoopRooms implementation, add support for events. r=Standard8

This commit is contained in:
Mike de Boer 2014-10-29 14:28:42 +01:00
Родитель 256835686e
Коммит a2c956ceba
7 изменённых файлов: 381 добавлений и 547 удалений

Просмотреть файл

@ -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) {