mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1074663: Register with PushServer for updates to rooms, r=MattN
This commit is contained in:
parent
a37c975c1d
commit
2fd6df7067
383
browser/components/loop/LoopCalls.jsm
Normal file
383
browser/components/loop/LoopCalls.jsm
Normal file
@ -0,0 +1,383 @@
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["LoopCalls"];
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService",
|
||||
"resource:///modules/loop/MozLoopService.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LOOP_SESSION_TYPE",
|
||||
"resource:///modules/loop/MozLoopService.jsm");
|
||||
|
||||
/**
|
||||
* Attempts to open a websocket.
|
||||
*
|
||||
* A new websocket interface is used each time. If an onStop callback
|
||||
* was received, calling asyncOpen() on the same interface will
|
||||
* trigger a "alreay open socket" exception even though the channel
|
||||
* is logically closed.
|
||||
*/
|
||||
function CallProgressSocket(progressUrl, callId, token) {
|
||||
if (!progressUrl || !callId || !token) {
|
||||
throw new Error("missing required arguments");
|
||||
}
|
||||
|
||||
this._progressUrl = progressUrl;
|
||||
this._callId = callId;
|
||||
this._token = token;
|
||||
}
|
||||
|
||||
CallProgressSocket.prototype = {
|
||||
/**
|
||||
* Open websocket and run hello exchange.
|
||||
* Sends a hello message to the server.
|
||||
*
|
||||
* @param {function} Callback used after a successful handshake
|
||||
* over the progressUrl.
|
||||
* @param {function} Callback used if an error is encountered
|
||||
*/
|
||||
connect: function(onSuccess, onError) {
|
||||
this._onSuccess = onSuccess;
|
||||
this._onError = onError ||
|
||||
(reason => {MozLoopService.logwarn("LoopCalls::callProgessSocket - ", reason);});
|
||||
|
||||
if (!onSuccess) {
|
||||
this._onError("missing onSuccess argument");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Services.io.offline) {
|
||||
this._onError("IO offline");
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = Services.io.newURI(this._progressUrl, null, null);
|
||||
|
||||
// Allow _websocket to be set for testing.
|
||||
this._websocket = this._websocket ||
|
||||
Cc["@mozilla.org/network/protocol;1?name=" + uri.scheme]
|
||||
.createInstance(Ci.nsIWebSocketChannel);
|
||||
|
||||
this._websocket.asyncOpen(uri, this._progressUrl, this, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener method, handles the start of the websocket stream.
|
||||
* Sends a hello message to the server.
|
||||
*
|
||||
* @param {nsISupports} aContext Not used
|
||||
*/
|
||||
onStart: function() {
|
||||
let helloMsg = {
|
||||
messageType: "hello",
|
||||
callId: this._callId,
|
||||
auth: this._token,
|
||||
};
|
||||
try { // in case websocket has closed before this handler is run
|
||||
this._websocket.sendMsg(JSON.stringify(helloMsg));
|
||||
}
|
||||
catch (error) {
|
||||
this._onError(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener method, called when the websocket is closed.
|
||||
*
|
||||
* @param {nsISupports} aContext Not used
|
||||
* @param {nsresult} aStatusCode Reason for stopping (NS_OK = successful)
|
||||
*/
|
||||
onStop: function(aContext, aStatusCode) {
|
||||
if (!this._handshakeComplete) {
|
||||
this._onError("[" + aStatusCode + "]");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener method, called when the websocket is closed by the server.
|
||||
* If there are errors, onStop may be called without ever calling this
|
||||
* method.
|
||||
*
|
||||
* @param {nsISupports} aContext Not used
|
||||
* @param {integer} aCode the websocket closing handshake close code
|
||||
* @param {String} aReason the websocket closing handshake close reason
|
||||
*/
|
||||
onServerClose: function(aContext, aCode, aReason) {
|
||||
if (!this._handshakeComplete) {
|
||||
this._onError("[" + aCode + "]" + aReason);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener method, called when the websocket receives a message.
|
||||
*
|
||||
* @param {nsISupports} aContext Not used
|
||||
* @param {String} aMsg The message data
|
||||
*/
|
||||
onMessageAvailable: function(aContext, aMsg) {
|
||||
let msg = {};
|
||||
try {
|
||||
msg = JSON.parse(aMsg);
|
||||
}
|
||||
catch (error) {
|
||||
MozLoopService.logerror("LoopCalls: error parsing progress message - ", error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.messageType && msg.messageType === 'hello') {
|
||||
this._handshakeComplete = true;
|
||||
this._onSuccess();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Create a JSON message payload and send on websocket.
|
||||
*
|
||||
* @param {Object} aMsg Message to send.
|
||||
*/
|
||||
_send: function(aMsg) {
|
||||
if (!this._handshakeComplete) {
|
||||
MozLoopService.logwarn("LoopCalls::_send error - handshake not complete");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._websocket.sendMsg(JSON.stringify(aMsg));
|
||||
}
|
||||
catch (error) {
|
||||
this._onError(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies the server that the user has declined the call
|
||||
* with a reason of busy.
|
||||
*/
|
||||
sendBusy: function() {
|
||||
this._send({
|
||||
messageType: "action",
|
||||
event: "terminate",
|
||||
reason: "busy"
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal helper methods and state
|
||||
*
|
||||
* The registration is a two-part process. First we need to connect to
|
||||
* and register with the push server. Then we need to take the result of that
|
||||
* and register with the Loop server.
|
||||
*/
|
||||
let LoopCallsInternal = {
|
||||
callsData: {inUse: false},
|
||||
_mocks: {webSocket: undefined},
|
||||
|
||||
/**
|
||||
* Callback from MozLoopPushHandler - A push notification has been received from
|
||||
* the server.
|
||||
*
|
||||
* @param {String} version The version information from the server.
|
||||
*/
|
||||
onNotification: function(version, channelID) {
|
||||
if (MozLoopService.doNotDisturb) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We set this here as it is assumed that once the user receives an incoming
|
||||
// call, they'll have had enough time to see the terms of service. See
|
||||
// bug 1046039 for background.
|
||||
Services.prefs.setCharPref("loop.seenToS", "seen");
|
||||
|
||||
// Request the information on the new call(s) associated with this version.
|
||||
// The registered FxA session is checked first, then the anonymous session.
|
||||
// Make the call to get the GUEST session regardless of whether the FXA
|
||||
// request fails.
|
||||
|
||||
if (channelID == LoopCalls.channelIDs.FxA && MozLoopService.userProfile) {
|
||||
this._getCalls(LOOP_SESSION_TYPE.FXA, version);
|
||||
} else {
|
||||
this._getCalls(LOOP_SESSION_TYPE.GUEST, version);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Make a hawkRequest to GET/calls?=version for this session type.
|
||||
*
|
||||
* @param {LOOP_SESSION_TYPE} sessionType - type of hawk token used
|
||||
* for the GET operation.
|
||||
* @param {Object} version - LoopPushService notification version
|
||||
*
|
||||
* @returns {Promise}
|
||||
*
|
||||
*/
|
||||
|
||||
_getCalls: function(sessionType, version) {
|
||||
return MozLoopService.hawkRequest(sessionType, "/calls?version=" + version, "GET").then(
|
||||
response => {this._processCalls(response, sessionType);}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Process the calls array returned from a GET/calls?version request.
|
||||
* Only one active call is permitted at this time.
|
||||
*
|
||||
* @param {Object} response - response payload from GET
|
||||
*
|
||||
* @param {LOOP_SESSION_TYPE} sessionType - type of hawk token used
|
||||
* for the GET operation.
|
||||
*
|
||||
*/
|
||||
|
||||
_processCalls: function(response, sessionType) {
|
||||
try {
|
||||
let respData = JSON.parse(response.body);
|
||||
if (respData.calls && Array.isArray(respData.calls)) {
|
||||
respData.calls.forEach((callData) => {
|
||||
if (!this.callsData.inUse) {
|
||||
callData.sessionType = sessionType;
|
||||
this._startCall(callData, "incoming");
|
||||
} else {
|
||||
this._returnBusy(callData);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
MozLoopService.logwarn("Error: missing calls[] in response");
|
||||
}
|
||||
} catch (err) {
|
||||
MozLoopService.logwarn("Error parsing calls info", err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a call, saves the call data, and opens a chat window.
|
||||
*
|
||||
* @param {Object} callData The data associated with the call including an id.
|
||||
* @param {Boolean} conversationType Whether or not the call is "incoming"
|
||||
* or "outgoing"
|
||||
*/
|
||||
_startCall: function(callData, conversationType) {
|
||||
this.callsData.inUse = true;
|
||||
this.callsData.data = callData;
|
||||
MozLoopService.openChatWindow(
|
||||
null,
|
||||
// No title, let the page set that, to avoid flickering.
|
||||
"",
|
||||
"about:loopconversation#" + conversationType + "/" + callData.callId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a direct call to the contact addresses.
|
||||
*
|
||||
* @param {Object} contact The contact to call
|
||||
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
|
||||
* @return true if the call is opened, false if it is not opened (i.e. busy)
|
||||
*/
|
||||
startDirectCall: function(contact, callType) {
|
||||
if (this.callsData.inUse)
|
||||
return false;
|
||||
|
||||
var callData = {
|
||||
contact: contact,
|
||||
callType: callType,
|
||||
callId: Math.floor((Math.random() * 10))
|
||||
};
|
||||
|
||||
this._startCall(callData, "outgoing");
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open call progress websocket and terminate with a reason of busy
|
||||
* the server.
|
||||
*
|
||||
* @param {callData} Must contain the progressURL, callId and websocketToken
|
||||
* returned by the LoopService.
|
||||
*/
|
||||
_returnBusy: function(callData) {
|
||||
let callProgress = new CallProgressSocket(
|
||||
callData.progressURL,
|
||||
callData.callId,
|
||||
callData.websocketToken);
|
||||
callProgress._websocket = this._mocks.webSocket;
|
||||
// This instance of CallProgressSocket should stay alive until the underlying
|
||||
// websocket is closed since it is passed to the websocket as the nsIWebSocketListener.
|
||||
callProgress.connect(() => {callProgress.sendBusy();});
|
||||
}
|
||||
};
|
||||
Object.freeze(LoopCallsInternal);
|
||||
|
||||
/**
|
||||
* Public API
|
||||
*/
|
||||
this.LoopCalls = {
|
||||
// Channel ids that will be registered with the PushServer for notifications
|
||||
channelIDs: {
|
||||
FxA: "25389583-921f-4169-a426-a4673658944b",
|
||||
Guest: "801f754b-686b-43ec-bd83-1419bbf58388",
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback from MozLoopPushHandler - A push notification has been received from
|
||||
* the server.
|
||||
*
|
||||
* @param {String} version The version information from the server.
|
||||
*/
|
||||
onNotification: function(version, channelID) {
|
||||
LoopCallsInternal.onNotification(version, channelID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the callData for a specific loopCallId
|
||||
*
|
||||
* The data was retrieved from the LoopServer via a GET/calls/<version> request
|
||||
* triggered by an incoming message from the LoopPushServer.
|
||||
*
|
||||
* @param {int} loopCallId
|
||||
* @return {callData} The callData or undefined if error.
|
||||
*/
|
||||
getCallData: function(loopCallId) {
|
||||
if (LoopCallsInternal.callsData.data &&
|
||||
LoopCallsInternal.callsData.data.callId == loopCallId) {
|
||||
return LoopCallsInternal.callsData.data;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Releases the callData for a specific loopCallId
|
||||
*
|
||||
* The result of this call will be a free call session slot.
|
||||
*
|
||||
* @param {int} loopCallId
|
||||
*/
|
||||
releaseCallData: function(loopCallId) {
|
||||
if (LoopCallsInternal.callsData.data &&
|
||||
LoopCallsInternal.callsData.data.callId == loopCallId) {
|
||||
LoopCallsInternal.callsData.data = undefined;
|
||||
LoopCallsInternal.callsData.inUse = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a direct call to the contact addresses.
|
||||
*
|
||||
* @param {Object} contact The contact to call
|
||||
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
|
||||
* @return true if the call is opened, false if it is not opened (i.e. busy)
|
||||
*/
|
||||
startDirectCall: function(contact, callType) {
|
||||
LoopCallsInternal.startDirectCall(contact, callType);
|
||||
}
|
||||
};
|
||||
Object.freeze(LoopCalls);
|
25
browser/components/loop/LoopRooms.jsm
Normal file
25
browser/components/loop/LoopRooms.jsm
Normal file
@ -0,0 +1,25 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["LoopRooms"];
|
||||
|
||||
/**
|
||||
* Public Loop Rooms API
|
||||
*/
|
||||
this.LoopRooms = Object.freeze({
|
||||
// Channel ids that will be registered with the PushServer for notifications
|
||||
channelIDs: {
|
||||
FxA: "6add272a-d316-477c-8335-f00f73dfde71",
|
||||
Guest: "19d3f799-a8f3-4328-9822-b7cd02765832",
|
||||
},
|
||||
|
||||
onNotification: function(version, channelID) {
|
||||
return;
|
||||
},
|
||||
});
|
@ -9,6 +9,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/loop/LoopCalls.jsm");
|
||||
Cu.import("resource:///modules/loop/MozLoopService.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
|
||||
@ -47,7 +48,7 @@ const cloneErrorObject = function(error, targetWindow) {
|
||||
if (typeof value != "string" && typeof value != "number") {
|
||||
value = String(value);
|
||||
}
|
||||
|
||||
|
||||
Object.defineProperty(Cu.waiveXrays(obj), prop, {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
@ -204,7 +205,7 @@ function injectLoopAPI(targetWindow) {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: function(loopCallId) {
|
||||
return Cu.cloneInto(MozLoopService.getCallData(loopCallId), targetWindow);
|
||||
return Cu.cloneInto(LoopCalls.getCallData(loopCallId), targetWindow);
|
||||
}
|
||||
},
|
||||
|
||||
@ -219,7 +220,7 @@ function injectLoopAPI(targetWindow) {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: function(loopCallId) {
|
||||
MozLoopService.releaseCallData(loopCallId);
|
||||
LoopCalls.releaseCallData(loopCallId);
|
||||
}
|
||||
},
|
||||
|
||||
@ -653,7 +654,7 @@ function injectLoopAPI(targetWindow) {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: function(contact, callType) {
|
||||
MozLoopService.startDirectCall(contact, callType);
|
||||
LoopCalls.startDirectCall(contact, callType);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["MozLoopPushHandler"];
|
||||
|
||||
@ -22,14 +23,15 @@ XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
let MozLoopPushHandler = {
|
||||
// This is the uri of the push server.
|
||||
pushServerUri: undefined,
|
||||
// This is the channel id we're using for notifications
|
||||
channelID: "8b1081ce-9b35-42b5-b8f5-3ff8cb813a50",
|
||||
// Records containing the registration and notification callbacks indexed by channelID.
|
||||
// Each channel will be registered with the PushServer.
|
||||
channels: {},
|
||||
// This is the UserAgent UUID assigned by the PushServer
|
||||
uaID: undefined,
|
||||
// Stores the push url if we're registered and we have one.
|
||||
pushUrl: undefined,
|
||||
// Set to true once the channelID has been registered with the PushServer.
|
||||
registered: false,
|
||||
// Each successfully registered channelID is used as a key to hold its pushEndpoint URL.
|
||||
registeredChannels: {},
|
||||
|
||||
_channelsToRegister: {},
|
||||
|
||||
_minRetryDelay_ms: (() => {
|
||||
try {
|
||||
@ -50,34 +52,78 @@ let MozLoopPushHandler = {
|
||||
})(),
|
||||
|
||||
/**
|
||||
* Starts a connection to the push socket server. On
|
||||
* Inializes the PushHandler and opens a socket with the PushServer.
|
||||
* It will automatically say hello and register any channels
|
||||
* that are found in the work queue at that point.
|
||||
*
|
||||
* @param {Object} options Set of configuration options. Currently,
|
||||
* the only option is mocketWebSocket which will be
|
||||
* used for testing.
|
||||
*/
|
||||
initialize: function(options = {}) {
|
||||
if (Services.io.offline) {
|
||||
console.warn("MozLoopPushHandler - IO offline");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._initDone) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this._initDone = true;
|
||||
|
||||
if ("mockWebSocket" in options) {
|
||||
this._mockWebSocket = options.mockWebSocket;
|
||||
}
|
||||
|
||||
this._openSocket();
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Start registration of a PushServer notification channel.
|
||||
* connection, it will automatically say hello and register the channel
|
||||
* id with the server.
|
||||
*
|
||||
* Register callback parameters:
|
||||
* onRegistered callback parameters:
|
||||
* - {String|null} err: Encountered error, if any
|
||||
* - {String} url: The push url obtained from the server
|
||||
*
|
||||
* Callback parameters:
|
||||
* onNotification parameters:
|
||||
* - {String} version The version string received from the push server for
|
||||
* the notification.
|
||||
* - {String} channelID The channelID on which the notification was sent.
|
||||
*
|
||||
* @param {Function} registerCallback Callback to be called once we are
|
||||
* @param {String} channelID Channel ID to use in registration.
|
||||
*
|
||||
* @param {Function} onRegistered Callback to be called once we are
|
||||
* registered.
|
||||
* @param {Function} notificationCallback Callback to be called when a
|
||||
* @param {Function} onNotification Callback to be called when a
|
||||
* push notification is received (may be called multiple
|
||||
* times).
|
||||
* @param {Object} mockPushHandler Optional, test-only object, to allow
|
||||
* the websocket to be mocked for tests.
|
||||
*/
|
||||
initialize: function(registerCallback, notificationCallback, mockPushHandler) {
|
||||
if (mockPushHandler) {
|
||||
this._mockPushHandler = mockPushHandler;
|
||||
register: function(channelID, onRegistered, onNotification) {
|
||||
if (!channelID || !onRegistered || !onNotification) {
|
||||
throw new Error("missing required parameter(s):"
|
||||
+ (channelID ? "" : " channelID")
|
||||
+ (onRegistered ? "" : " onRegistered")
|
||||
+ (onNotification ? "" : " onNotification"));
|
||||
}
|
||||
// Only register new channels
|
||||
if (!(channelID in this.channels)) {
|
||||
this.channels[channelID] = {
|
||||
onRegistered: onRegistered,
|
||||
onNotification: onNotification
|
||||
};
|
||||
|
||||
this._registerCallback = registerCallback;
|
||||
this._notificationCallback = notificationCallback;
|
||||
this._openSocket();
|
||||
// If registration is in progress, simply add to the work list.
|
||||
// Else, re-start a registration cycle.
|
||||
if (this._registrationID) {
|
||||
this._channelsToRegister.push(channelID);
|
||||
} else {
|
||||
this._registerChannels();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -91,9 +137,11 @@ let MozLoopPushHandler = {
|
||||
// If a uaID has already been assigned, assume this is a re-connect
|
||||
// and send the uaID in order to re-synch with the
|
||||
// PushServer. If a registration has been completed, send the channelID.
|
||||
let helloMsg = { messageType: "hello",
|
||||
uaid: this.uaID,
|
||||
channelIDs: this.registered ? [this.channelID] :[] };
|
||||
let helloMsg = {
|
||||
messageType: "hello",
|
||||
uaid: this.uaID || "",
|
||||
channelIDs: Object.keys(this.registeredChannels)};
|
||||
|
||||
this._retryOperation(() => this.onStart(), this._maxRetryDelay_ms);
|
||||
try { // in case websocket has closed before this handler is run
|
||||
this._websocket.sendMsg(JSON.stringify(helloMsg));
|
||||
@ -138,10 +186,12 @@ let MozLoopPushHandler = {
|
||||
switch(msg.messageType) {
|
||||
case "hello":
|
||||
this._retryEnd();
|
||||
if (this.uaID !== msg.uaid) {
|
||||
this.uaID = msg.uaid;
|
||||
this._registerChannel();
|
||||
}
|
||||
this._isConnected = true;
|
||||
if (this.uaID !== msg.uaid) {
|
||||
this.uaID = msg.uaid;
|
||||
this.registeredChannels = {};
|
||||
this._registerChannels();
|
||||
}
|
||||
break;
|
||||
|
||||
case "register":
|
||||
@ -150,8 +200,8 @@ let MozLoopPushHandler = {
|
||||
|
||||
case "notification":
|
||||
msg.updates.forEach((update) => {
|
||||
if (update.channelID === this.channelID) {
|
||||
this._notificationCallback(update.version);
|
||||
if (update.channelID in this.registeredChannels) {
|
||||
this.channels[update.channelID].onNotification(update.version, update.channelID);
|
||||
}
|
||||
});
|
||||
break;
|
||||
@ -161,30 +211,40 @@ let MozLoopPushHandler = {
|
||||
/**
|
||||
* Handles the PushServer registration response.
|
||||
*
|
||||
* @param {} msg PushServer to UserAgent registration response (parsed from JSON).
|
||||
* @param {Object} msg PushServer to UserAgent registration response (parsed from JSON).
|
||||
*/
|
||||
_onRegister: function(msg) {
|
||||
let registerNext = () => {
|
||||
this._registrationID = this._channelsToRegister.shift();
|
||||
this._sendRegistration(this._registrationID);
|
||||
}
|
||||
|
||||
switch (msg.status) {
|
||||
case 200:
|
||||
this._retryEnd(); // reset retry mechanism
|
||||
this.registered = true;
|
||||
if (this.pushUrl !== msg.pushEndpoint) {
|
||||
this.pushUrl = msg.pushEndpoint;
|
||||
this._registerCallback(null, this.pushUrl);
|
||||
if (msg.channelID == this._registrationID) {
|
||||
this._retryEnd(); // reset retry mechanism
|
||||
this.registeredChannels[msg.channelID] = msg.pushEndpoint;
|
||||
this.channels[msg.channelID].onRegistered(null, msg.pushEndpoint, msg.channelID);
|
||||
registerNext();
|
||||
}
|
||||
break;
|
||||
|
||||
case 500:
|
||||
// retry the registration request after a suitable delay
|
||||
this._retryOperation(() => this._registerChannel());
|
||||
this._retryOperation(() => this._sendRegistration(msg.channelID));
|
||||
break;
|
||||
|
||||
case 409:
|
||||
this._registerCallback("error: PushServer ChannelID already in use");
|
||||
this.channels[this._registrationID].onRegistered(
|
||||
"error: PushServer ChannelID already in use: " + msg.channelID);
|
||||
registerNext();
|
||||
break;
|
||||
|
||||
default:
|
||||
this._registerCallback("error: PushServer registration failure, status = " + msg.status);
|
||||
let id = this._channelsToRegister.shift();
|
||||
this.channels[this._registrationID].onRegistered(
|
||||
"error: PushServer registration failure, status = " + msg.status);
|
||||
registerNext();
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -198,16 +258,14 @@ let MozLoopPushHandler = {
|
||||
* is logically closed.
|
||||
*/
|
||||
_openSocket: function() {
|
||||
if (this._mockPushHandler) {
|
||||
this._isConnected = false;
|
||||
|
||||
if (this._mockWebSocket) {
|
||||
// For tests, use the mock instance.
|
||||
this._websocket = this._mockPushHandler;
|
||||
} else if (!Services.io.offline) {
|
||||
this._websocket = this._mockWebSocket;
|
||||
} else {
|
||||
this._websocket = Cc["@mozilla.org/network/protocol;1?name=wss"]
|
||||
.createInstance(Ci.nsIWebSocketChannel);
|
||||
} else {
|
||||
this._registerCallback("offline");
|
||||
console.warn("MozLoopPushHandler - IO offline");
|
||||
return;
|
||||
}
|
||||
|
||||
this._websocket.protocol = "push-notification";
|
||||
@ -259,15 +317,39 @@ let MozLoopPushHandler = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles registering a service
|
||||
* Begins registering the channelIDs with the PushServer
|
||||
*/
|
||||
_registerChannel: function() {
|
||||
this.registered = false;
|
||||
try { // in case websocket has closed
|
||||
this._websocket.sendMsg(JSON.stringify({messageType: "register",
|
||||
channelID: this.channelID}));
|
||||
_registerChannels: function() {
|
||||
// Hold off registration operation until handshake is complete.
|
||||
if (!this._isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a registration is pending, do not generate a work list.
|
||||
// Assume registration is in progress.
|
||||
if (!this._registrationID) {
|
||||
// Generate a list of channelIDs that have not yet been registered.
|
||||
this._channelsToRegister = Object.keys(this.channels).filter((id) => {
|
||||
return !(id in this.registeredChannels);
|
||||
});
|
||||
this._registrationID = this._channelsToRegister.shift();
|
||||
this._sendRegistration(this._registrationID);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles registering a service
|
||||
*
|
||||
* @param {string} channelID - identification token to use in registration for this channel.
|
||||
*/
|
||||
_sendRegistration: function(channelID) {
|
||||
if (channelID) {
|
||||
try { // in case websocket has closed
|
||||
this._websocket.sendMsg(JSON.stringify({messageType: "register",
|
||||
channelID: channelID}));
|
||||
}
|
||||
catch (e) {console.warn("MozLoopPushHandler::_registerChannel websocket.sendMsg() failure");}
|
||||
}
|
||||
catch (e) {console.warn("MozLoopPushHandler::_registerChannel websocket.sendMsg() failure");}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -301,4 +383,3 @@ let MozLoopPushHandler = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -31,6 +31,7 @@ Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm", this);
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
|
||||
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE"];
|
||||
@ -64,6 +65,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "LoopContacts",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopStorage",
|
||||
"resource:///modules/loop/LoopStorage.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopCalls",
|
||||
"resource:///modules/loop/LoopCalls.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopRooms",
|
||||
"resource:///modules/loop/LoopRooms.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
|
||||
"resource:///modules/loop/MozLoopPushHandler.jsm");
|
||||
|
||||
@ -108,160 +115,6 @@ let gFxAOAuthClientPromise = null;
|
||||
let gFxAOAuthClient = null;
|
||||
let gErrors = new Map();
|
||||
|
||||
/**
|
||||
* Attempts to open a websocket.
|
||||
*
|
||||
* A new websocket interface is used each time. If an onStop callback
|
||||
* was received, calling asyncOpen() on the same interface will
|
||||
* trigger a "alreay open socket" exception even though the channel
|
||||
* is logically closed.
|
||||
*/
|
||||
function CallProgressSocket(progressUrl, callId, token) {
|
||||
if (!progressUrl || !callId || !token) {
|
||||
throw new Error("missing required arguments");
|
||||
}
|
||||
|
||||
this._progressUrl = progressUrl;
|
||||
this._callId = callId;
|
||||
this._token = token;
|
||||
}
|
||||
|
||||
CallProgressSocket.prototype = {
|
||||
/**
|
||||
* Open websocket and run hello exchange.
|
||||
* Sends a hello message to the server.
|
||||
*
|
||||
* @param {function} Callback used after a successful handshake
|
||||
* over the progressUrl.
|
||||
* @param {function} Callback used if an error is encountered
|
||||
*/
|
||||
connect: function(onSuccess, onError) {
|
||||
this._onSuccess = onSuccess;
|
||||
this._onError = onError ||
|
||||
(reason => {log.warn("MozLoopService::callProgessSocket - ", reason);});
|
||||
|
||||
if (!onSuccess) {
|
||||
this._onError("missing onSuccess argument");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Services.io.offline) {
|
||||
this._onError("IO offline");
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = Services.io.newURI(this._progressUrl, null, null);
|
||||
|
||||
// Allow _websocket to be set for testing.
|
||||
this._websocket = this._websocket ||
|
||||
Cc["@mozilla.org/network/protocol;1?name=" + uri.scheme]
|
||||
.createInstance(Ci.nsIWebSocketChannel);
|
||||
|
||||
this._websocket.asyncOpen(uri, this._progressUrl, this, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener method, handles the start of the websocket stream.
|
||||
* Sends a hello message to the server.
|
||||
*
|
||||
* @param {nsISupports} aContext Not used
|
||||
*/
|
||||
onStart: function() {
|
||||
let helloMsg = {
|
||||
messageType: "hello",
|
||||
callId: this._callId,
|
||||
auth: this._token,
|
||||
};
|
||||
try { // in case websocket has closed before this handler is run
|
||||
this._websocket.sendMsg(JSON.stringify(helloMsg));
|
||||
}
|
||||
catch (error) {
|
||||
this._onError(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener method, called when the websocket is closed.
|
||||
*
|
||||
* @param {nsISupports} aContext Not used
|
||||
* @param {nsresult} aStatusCode Reason for stopping (NS_OK = successful)
|
||||
*/
|
||||
onStop: function(aContext, aStatusCode) {
|
||||
if (!this._handshakeComplete) {
|
||||
this._onError("[" + aStatusCode + "]");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener method, called when the websocket is closed by the server.
|
||||
* If there are errors, onStop may be called without ever calling this
|
||||
* method.
|
||||
*
|
||||
* @param {nsISupports} aContext Not used
|
||||
* @param {integer} aCode the websocket closing handshake close code
|
||||
* @param {String} aReason the websocket closing handshake close reason
|
||||
*/
|
||||
onServerClose: function(aContext, aCode, aReason) {
|
||||
if (!this._handshakeComplete) {
|
||||
this._onError("[" + aCode + "]" + aReason);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener method, called when the websocket receives a message.
|
||||
*
|
||||
* @param {nsISupports} aContext Not used
|
||||
* @param {String} aMsg The message data
|
||||
*/
|
||||
onMessageAvailable: function(aContext, aMsg) {
|
||||
let msg = {};
|
||||
try {
|
||||
msg = JSON.parse(aMsg);
|
||||
}
|
||||
catch (error) {
|
||||
log.error("MozLoopService: error parsing progress message - ", error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.messageType && msg.messageType === 'hello') {
|
||||
this._handshakeComplete = true;
|
||||
this._onSuccess();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Create a JSON message payload and send on websocket.
|
||||
*
|
||||
* @param {Object} aMsg Message to send.
|
||||
*/
|
||||
_send: function(aMsg) {
|
||||
if (!this._handshakeComplete) {
|
||||
log.warn("MozLoopService::_send error - handshake not complete");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this._websocket.sendMsg(JSON.stringify(aMsg));
|
||||
}
|
||||
catch (error) {
|
||||
this._onError(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies the server that the user has declined the call
|
||||
* with a reason of busy.
|
||||
*/
|
||||
sendBusy: function() {
|
||||
this._send({
|
||||
messageType: "action",
|
||||
event: "terminate",
|
||||
reason: "busy"
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal helper methods and state
|
||||
*
|
||||
@ -270,7 +123,6 @@ CallProgressSocket.prototype = {
|
||||
* and register with the Loop server.
|
||||
*/
|
||||
let MozLoopServiceInternal = {
|
||||
callsData: {inUse: false},
|
||||
_mocks: {webSocket: undefined},
|
||||
|
||||
// The uri of the Loop server.
|
||||
@ -454,24 +306,73 @@ let MozLoopServiceInternal = {
|
||||
*
|
||||
* @param {Object} mockPushHandler Optional, test-only mock push handler. Used
|
||||
* to allow mocking of the MozLoopPushHandler.
|
||||
* @param {Object} mockWebSocket Optional, test-only mock webSocket. To be passed
|
||||
* through to MozLoopPushHandler.
|
||||
* @returns {Promise} a promise that is resolved with no params on completion, or
|
||||
* rejected with an error code or string.
|
||||
*/
|
||||
promiseRegisteredWithServers: function(mockPushHandler, mockWebSocket) {
|
||||
this._mocks.webSocket = mockWebSocket;
|
||||
|
||||
if (gRegisteredDeferred) {
|
||||
return gRegisteredDeferred.promise;
|
||||
}
|
||||
|
||||
this._mocks.webSocket = mockWebSocket;
|
||||
this._mocks.pushHandler = mockPushHandler;
|
||||
|
||||
// Wrap push notification registration call-back in a Promise.
|
||||
let registerForNotification = function(channelID, onNotification) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let onRegistered = (error, pushUrl) => {
|
||||
if (error) {
|
||||
reject(Error(error));
|
||||
} else {
|
||||
resolve(pushUrl);
|
||||
}
|
||||
};
|
||||
gPushHandler.register(channelID, onRegistered, onNotification);
|
||||
});
|
||||
};
|
||||
|
||||
gRegisteredDeferred = Promise.defer();
|
||||
// We grab the promise early in case .initialize or its results sets
|
||||
// it back to null on error.
|
||||
let result = gRegisteredDeferred.promise;
|
||||
|
||||
gPushHandler = mockPushHandler || MozLoopPushHandler;
|
||||
gPushHandler.initialize(this.onPushRegistered.bind(this),
|
||||
this.onHandleNotification.bind(this));
|
||||
let options = mockWebSocket ? {mockWebSocket: mockWebSocket} : {};
|
||||
gPushHandler.initialize(options);
|
||||
|
||||
let callsRegGuest = registerForNotification(LoopCalls.channelIDs.Guest,
|
||||
LoopCalls.onNotification);
|
||||
|
||||
let roomsRegGuest = registerForNotification(LoopRooms.channelIDs.Guest,
|
||||
LoopRooms.onNotification);
|
||||
|
||||
let callsRegFxA = registerForNotification(LoopCalls.channelIDs.FxA,
|
||||
LoopCalls.onNotification);
|
||||
|
||||
let roomsRegFxA = registerForNotification(LoopRooms.channelIDs.FxA,
|
||||
LoopRooms.onNotification);
|
||||
Promise.all([callsRegGuest, roomsRegGuest, callsRegFxA, roomsRegFxA])
|
||||
.then((pushUrls) => {
|
||||
return this.registerWithLoopServer(LOOP_SESSION_TYPE.GUEST,
|
||||
{calls: pushUrls[0], rooms: pushUrls[1]}) })
|
||||
.then(() => {
|
||||
// storeSessionToken could have rejected and nulled the promise if the token was malformed.
|
||||
if (!gRegisteredDeferred) {
|
||||
return;
|
||||
}
|
||||
gRegisteredDeferred.resolve("registered to guest status");
|
||||
// No need to clear the promise here, everything was good, so we don't need
|
||||
// to re-register.
|
||||
}, error => {
|
||||
log.error("Failed to register with Loop server: ", error);
|
||||
// registerWithLoopServer may have already made this null.
|
||||
if (gRegisteredDeferred) {
|
||||
gRegisteredDeferred.reject(error);
|
||||
}
|
||||
gRegisteredDeferred = null;
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
@ -606,53 +507,23 @@ let MozLoopServiceInternal = {
|
||||
log.debug("Cleared hawk session token for sessionType", sessionType);
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback from MozLoopPushHandler - The push server has been registered
|
||||
* and has given us a push url.
|
||||
*
|
||||
* @param {String} pushUrl The push url given by the push server.
|
||||
*/
|
||||
onPushRegistered: function(err, pushUrl) {
|
||||
if (err) {
|
||||
gRegisteredDeferred.reject(err);
|
||||
gRegisteredDeferred = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.registerWithLoopServer(LOOP_SESSION_TYPE.GUEST, pushUrl).then(() => {
|
||||
// storeSessionToken could have rejected and nulled the promise if the token was malformed.
|
||||
if (!gRegisteredDeferred) {
|
||||
return;
|
||||
}
|
||||
gRegisteredDeferred.resolve("registered to guest status");
|
||||
// No need to clear the promise here, everything was good, so we don't need
|
||||
// to re-register.
|
||||
}, error => {
|
||||
log.error("Failed to register with Loop server: ", error);
|
||||
// registerWithLoopServer may have already made this null.
|
||||
if (gRegisteredDeferred) {
|
||||
gRegisteredDeferred.reject(error);
|
||||
}
|
||||
gRegisteredDeferred = null;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers with the Loop server either as a guest or a FxA user.
|
||||
*
|
||||
* @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
|
||||
* @param {String} pushUrl The push url given by the push server.
|
||||
* @param {String} pushUrls The push url given by the push server.
|
||||
* @param {Boolean} [retry=true] Whether to retry if authentication fails.
|
||||
* @return {Promise}
|
||||
*/
|
||||
registerWithLoopServer: function(sessionType, pushUrl, retry = true) {
|
||||
return this.hawkRequest(sessionType, "/registration", "POST", { simplePushURL: pushUrl})
|
||||
registerWithLoopServer: function(sessionType, pushUrls, retry = true) {
|
||||
return this.hawkRequest(sessionType, "/registration", "POST", { simplePushURLs: pushUrls})
|
||||
.then((response) => {
|
||||
// If this failed we got an invalid token. storeSessionToken rejects
|
||||
// the gRegisteredDeferred promise for us, so here we just need to
|
||||
// early return.
|
||||
if (!this.storeSessionToken(sessionType, response.headers))
|
||||
if (!this.storeSessionToken(sessionType, response.headers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Successfully registered with server for sessionType", sessionType);
|
||||
this.clearError("registration");
|
||||
@ -662,7 +533,7 @@ let MozLoopServiceInternal = {
|
||||
if (error.code === 401) {
|
||||
// Authorization failed, invalid token, we need to try again with a new token.
|
||||
if (retry) {
|
||||
return this.registerWithLoopServer(sessionType, pushUrl, false);
|
||||
return this.registerWithLoopServer(sessionType, pushUrls, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -681,8 +552,12 @@ let MozLoopServiceInternal = {
|
||||
* This is normally only wanted for FxA users as we normally want to keep the
|
||||
* guest session with the device.
|
||||
*
|
||||
* NOTE: It is the responsibiliy of the caller the clear the session token
|
||||
* after all of the notification classes: calls and rooms, for either
|
||||
* Guest or FxA have been unregistered with the LoopServer.
|
||||
*
|
||||
* @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
|
||||
* @param {String} pushURL The push URL previously given by the push server.
|
||||
* @param {String} pushURLs The push URL previously given by the push server.
|
||||
* This may not be necessary to unregister in the future.
|
||||
* @return {Promise} resolving when the unregistration request finishes
|
||||
*/
|
||||
@ -696,11 +571,8 @@ let MozLoopServiceInternal = {
|
||||
return this.hawkRequest(sessionType, unregisterURL, "DELETE")
|
||||
.then(() => {
|
||||
log.debug("Successfully unregistered from server for sessionType", sessionType);
|
||||
MozLoopServiceInternal.clearSessionToken(sessionType);
|
||||
},
|
||||
error => {
|
||||
// Always clear the registration token regardless of whether the server acknowledges the logout.
|
||||
MozLoopServiceInternal.clearSessionToken(sessionType);
|
||||
if (error.code === 401) {
|
||||
// Authorization failed, invalid token. This is fine since it may mean we already logged out.
|
||||
return;
|
||||
@ -711,167 +583,6 @@ let MozLoopServiceInternal = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback from MozLoopPushHandler - A push notification has been received from
|
||||
* the server.
|
||||
*
|
||||
* @param {String} version The version information from the server.
|
||||
*/
|
||||
onHandleNotification: function(version) {
|
||||
if (this.doNotDisturb) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We set this here as it is assumed that once the user receives an incoming
|
||||
// call, they'll have had enough time to see the terms of service. See
|
||||
// bug 1046039 for background.
|
||||
Services.prefs.setCharPref("loop.seenToS", "seen");
|
||||
|
||||
// Request the information on the new call(s) associated with this version.
|
||||
// The registered FxA session is checked first, then the anonymous session.
|
||||
// Make the call to get the GUEST session regardless of whether the FXA
|
||||
// request fails.
|
||||
|
||||
if (MozLoopService.userProfile) {
|
||||
this._getCalls(LOOP_SESSION_TYPE.FXA, version).catch(() => {});
|
||||
}
|
||||
this._getCalls(LOOP_SESSION_TYPE.GUEST, version).catch(
|
||||
error => {this._hawkRequestError(error);});
|
||||
},
|
||||
|
||||
/**
|
||||
* Make a hawkRequest to GET/calls?=version for this session type.
|
||||
*
|
||||
* @param {LOOP_SESSION_TYPE} sessionType - type of hawk token used
|
||||
* for the GET operation.
|
||||
* @param {Object} version - LoopPushService notification version
|
||||
*
|
||||
* @returns {Promise}
|
||||
*
|
||||
*/
|
||||
|
||||
_getCalls: function(sessionType, version) {
|
||||
return this.hawkRequest(sessionType, "/calls?version=" + version, "GET").then(
|
||||
response => {this._processCalls(response, sessionType);}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Process the calls array returned from a GET/calls?version request.
|
||||
* Only one active call is permitted at this time.
|
||||
*
|
||||
* @param {Object} response - response payload from GET
|
||||
*
|
||||
* @param {LOOP_SESSION_TYPE} sessionType - type of hawk token used
|
||||
* for the GET operation.
|
||||
*
|
||||
*/
|
||||
|
||||
_processCalls: function(response, sessionType) {
|
||||
try {
|
||||
let respData = JSON.parse(response.body);
|
||||
if (respData.calls && Array.isArray(respData.calls)) {
|
||||
respData.calls.forEach((callData) => {
|
||||
if (!this.callsData.inUse) {
|
||||
callData.sessionType = sessionType;
|
||||
this._startCall(callData, "incoming");
|
||||
} else {
|
||||
this._returnBusy(callData);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.warn("Error: missing calls[] in response");
|
||||
}
|
||||
} catch (err) {
|
||||
log.warn("Error parsing calls info", err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a call, saves the call data, and opens a chat window.
|
||||
*
|
||||
* @param {Object} callData The data associated with the call including an id.
|
||||
* @param {String} conversationType Whether or not the call is "incoming"
|
||||
* or "outgoing"
|
||||
*/
|
||||
_startCall: function(callData, conversationType) {
|
||||
const openChat = () => {
|
||||
this.callsData.inUse = true;
|
||||
this.callsData.data = callData;
|
||||
|
||||
this.openChatWindow(
|
||||
null,
|
||||
// No title, let the page set that, to avoid flickering.
|
||||
"",
|
||||
"about:loopconversation#" + conversationType + "/" + callData.callId);
|
||||
};
|
||||
|
||||
if (conversationType == "incoming" && ("callerId" in callData) &&
|
||||
EMAIL_OR_PHONE_RE.test(callData.callerId)) {
|
||||
LoopContacts.search({
|
||||
q: callData.callerId,
|
||||
field: callData.callerId.contains("@") ? "email" : "tel"
|
||||
}, (err, contacts) => {
|
||||
if (err) {
|
||||
// Database error, helas!
|
||||
openChat();
|
||||
return;
|
||||
}
|
||||
|
||||
for (let contact of contacts) {
|
||||
if (contact.blocked) {
|
||||
// Blocked! Send a busy signal back to the caller.
|
||||
this._returnBusy(callData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
openChat();
|
||||
})
|
||||
} else {
|
||||
openChat();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a direct call to the contact addresses.
|
||||
*
|
||||
* @param {Object} contact The contact to call
|
||||
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
|
||||
* @return true if the call is opened, false if it is not opened (i.e. busy)
|
||||
*/
|
||||
startDirectCall: function(contact, callType) {
|
||||
if (this.callsData.inUse)
|
||||
return false;
|
||||
|
||||
var callData = {
|
||||
contact: contact,
|
||||
callType: callType,
|
||||
callId: Math.floor((Math.random() * 10))
|
||||
};
|
||||
|
||||
this._startCall(callData, "outgoing");
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Open call progress websocket and terminate with a reason of busy
|
||||
* the server.
|
||||
*
|
||||
* @param {callData} Must contain the progressURL, callId and websocketToken
|
||||
* returned by the LoopService.
|
||||
*/
|
||||
_returnBusy: function(callData) {
|
||||
let callProgress = new CallProgressSocket(
|
||||
callData.progressURL,
|
||||
callData.callId,
|
||||
callData.websocketToken);
|
||||
callProgress._websocket = this._mocks.webSocket;
|
||||
// This instance of CallProgressSocket should stay alive until the underlying
|
||||
// websocket is closed since it is passed to the websocket as the nsIWebSocketListener.
|
||||
callProgress.connect(() => {callProgress.sendBusy();});
|
||||
},
|
||||
|
||||
/**
|
||||
* A getter to obtain and store the strings for loop. This is structured
|
||||
* for use by l10n.js.
|
||||
@ -1167,8 +878,11 @@ let gInitializeTimerFunc = (deferredInitialization, mockPushHandler, mockWebSock
|
||||
|
||||
log.debug("MozLoopService: Initializing with already logged-in account");
|
||||
let registeredPromise =
|
||||
MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA,
|
||||
gPushHandler.pushUrl);
|
||||
MozLoopServiceInternal.registerWithLoopServer(
|
||||
LOOP_SESSION_TYPE.FXA, {
|
||||
calls: gPushHandler.registeredChannels[LoopCalls.channelIDs.FxA],
|
||||
rooms: gPushHandler.registeredChannels[LoopRooms.channelIDs.FxA]
|
||||
});
|
||||
registeredPromise.then(() => {
|
||||
deferredInitialization.resolve("initialized to logged-in status");
|
||||
}, error => {
|
||||
@ -1238,6 +952,18 @@ this.MozLoopService = {
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Opens the chat window
|
||||
*
|
||||
* @param {Object} contentWindow The window to open the chat window in, may
|
||||
* be null.
|
||||
* @param {String} title The title of the chat window.
|
||||
* @param {String} url The page to load in the chat window.
|
||||
*/
|
||||
openChatWindow: function(contentWindow, title, url) {
|
||||
MozLoopServiceInternal.openChatWindow(contentWindow, title, url);
|
||||
},
|
||||
|
||||
/**
|
||||
* If we're operating the service in "soft start" mode, and this browser
|
||||
* isn't already activated, check whether it's time for it to become active.
|
||||
@ -1457,39 +1183,6 @@ this.MozLoopService = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the callData for a specific loopCallId
|
||||
*
|
||||
* The data was retrieved from the LoopServer via a GET/calls/<version> request
|
||||
* triggered by an incoming message from the LoopPushServer.
|
||||
*
|
||||
* @param {int} loopCallId
|
||||
* @return {callData} The callData or undefined if error.
|
||||
*/
|
||||
getCallData: function(loopCallId) {
|
||||
if (MozLoopServiceInternal.callsData.data &&
|
||||
MozLoopServiceInternal.callsData.data.callId == loopCallId) {
|
||||
return MozLoopServiceInternal.callsData.data;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Releases the callData for a specific loopCallId
|
||||
*
|
||||
* The result of this call will be a free call session slot.
|
||||
*
|
||||
* @param {int} loopCallId
|
||||
*/
|
||||
releaseCallData: function(loopCallId) {
|
||||
if (MozLoopServiceInternal.callsData.data &&
|
||||
MozLoopServiceInternal.callsData.data.callId == loopCallId) {
|
||||
MozLoopServiceInternal.callsData.data = undefined;
|
||||
MozLoopServiceInternal.callsData.inUse = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set any character preference under "loop.".
|
||||
*
|
||||
@ -1565,7 +1258,6 @@ this.MozLoopService = {
|
||||
if (MozLoopServiceInternal.fxAOAuthTokenData) {
|
||||
return Promise.resolve(MozLoopServiceInternal.fxAOAuthTokenData);
|
||||
}
|
||||
|
||||
return MozLoopServiceInternal.promiseFxAOAuthAuthorization().then(response => {
|
||||
return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
|
||||
}).then(tokenData => {
|
||||
@ -1573,10 +1265,13 @@ this.MozLoopService = {
|
||||
return tokenData;
|
||||
}).then(tokenData => {
|
||||
return gRegisteredDeferred.promise.then(Task.async(function*() {
|
||||
if (gPushHandler.pushUrl) {
|
||||
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, gPushHandler.pushUrl);
|
||||
let callsUrl = gPushHandler.registeredChannels[LoopCalls.channelIDs.FxA],
|
||||
roomsUrl = gPushHandler.registeredChannels[LoopRooms.channelIDs.FxA];
|
||||
if (callsUrl && roomsUrl) {
|
||||
yield MozLoopServiceInternal.registerWithLoopServer(
|
||||
LOOP_SESSION_TYPE.FXA, {calls: callsUrl, rooms: roomsUrl});
|
||||
} else {
|
||||
throw new Error("No pushUrl for FxA registration");
|
||||
throw new Error("No pushUrls for FxA registration");
|
||||
}
|
||||
MozLoopServiceInternal.clearError("login");
|
||||
MozLoopServiceInternal.clearError("profile");
|
||||
@ -1617,10 +1312,23 @@ this.MozLoopService = {
|
||||
*/
|
||||
logOutFromFxA: Task.async(function*() {
|
||||
log.debug("logOutFromFxA");
|
||||
if (gPushHandler && gPushHandler.pushUrl) {
|
||||
yield MozLoopServiceInternal.unregisterFromLoopServer(LOOP_SESSION_TYPE.FXA,
|
||||
gPushHandler.pushUrl);
|
||||
} else {
|
||||
let callsPushUrl, roomsPushUrl;
|
||||
if (gPushHandler) {
|
||||
callsPushUrl = gPushHandler.registeredChannels[LoopCalls.channelIDs.FxA];
|
||||
roomsPushUrl = gPushHandler.registeredChannels[LoopRooms.channelIDs.FxA];
|
||||
}
|
||||
try {
|
||||
if (callsPushUrl) {
|
||||
yield MozLoopServiceInternal.unregisterFromLoopServer(
|
||||
LOOP_SESSION_TYPE.FXA, callsPushUrl);
|
||||
}
|
||||
if (roomsPushUrl) {
|
||||
yield MozLoopServiceInternal.unregisterFromLoopServer(
|
||||
LOOP_SESSION_TYPE.FXA, roomsPushUrl);
|
||||
}
|
||||
}
|
||||
catch (error) {throw error}
|
||||
finally {
|
||||
MozLoopServiceInternal.clearSessionToken(LOOP_SESSION_TYPE.FXA);
|
||||
}
|
||||
|
||||
@ -1646,7 +1354,6 @@ this.MozLoopService = {
|
||||
log.error("Could not get the OAuth client");
|
||||
return;
|
||||
}
|
||||
|
||||
let url = new URL("/settings", fxAOAuthClient.parameters.content_uri);
|
||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
win.switchToTabHavingURI(url.toString(), true);
|
||||
@ -1674,15 +1381,4 @@ this.MozLoopService = {
|
||||
return MozLoopServiceInternal.hawkRequest(sessionType, path, method, payloadObj).catch(
|
||||
error => {MozLoopServiceInternal._hawkRequestError(error);});
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts a direct call to the contact addresses.
|
||||
*
|
||||
* @param {Object} contact The contact to call
|
||||
* @param {String} callType The type of call, e.g. "audio-video" or "audio-only"
|
||||
* @return true if the call is opened, false if it is not opened (i.e. busy)
|
||||
*/
|
||||
startDirectCall: function(contact, callType) {
|
||||
MozLoopServiceInternal.startDirectCall(contact, callType);
|
||||
},
|
||||
};
|
||||
|
@ -15,7 +15,9 @@ BROWSER_CHROME_MANIFESTS += [
|
||||
EXTRA_JS_MODULES.loop += [
|
||||
'CardDavImporter.jsm',
|
||||
'GoogleImporter.jsm',
|
||||
'LoopCalls.jsm',
|
||||
'LoopContacts.jsm',
|
||||
'LoopRooms.jsm',
|
||||
'LoopStorage.jsm',
|
||||
'MozLoopAPI.jsm',
|
||||
'MozLoopPushHandler.jsm',
|
||||
|
@ -122,7 +122,7 @@ add_task(function* params_no_hawk_session() {
|
||||
});
|
||||
|
||||
add_task(function* params_nonJSON() {
|
||||
Services.prefs.setCharPref("loop.server", "https://loop.invalid");
|
||||
Services.prefs.setCharPref("loop.server", "https://localhost:3000/invalid");
|
||||
// Reset after changing the server so a new HawkClient is created
|
||||
yield resetFxA();
|
||||
|
||||
@ -248,7 +248,7 @@ add_task(function* basicAuthorizationAndRegistration() {
|
||||
yield promiseOAuthParamsSetup(BASE_URL, params);
|
||||
|
||||
info("registering");
|
||||
mockPushHandler.pushUrl = "https://localhost/pushUrl/guest";
|
||||
mockPushHandler.registrationPushURL = "https://localhost/pushUrl/guest";
|
||||
// Notification observed due to the error being cleared upon successful registration.
|
||||
let statusChangedPromise = promiseObserverNotified("loop-status-changed");
|
||||
yield MozLoopService.register(mockPushHandler);
|
||||
@ -256,7 +256,8 @@ add_task(function* basicAuthorizationAndRegistration() {
|
||||
|
||||
// Normally the same pushUrl would be registered but we change it in the test
|
||||
// to be able to check for success on the second registration.
|
||||
mockPushHandler.pushUrl = "https://localhost/pushUrl/fxa";
|
||||
mockPushHandler.registeredChannels[LoopCalls.channelIDs.FxA] = "https://localhost/pushUrl/fxa-calls";
|
||||
mockPushHandler.registeredChannels[LoopRooms.channelIDs.FxA] = "https://localhost/pushUrl/fxa-rooms";
|
||||
|
||||
statusChangedPromise = promiseObserverNotified("loop-status-changed");
|
||||
yield loadLoopPanel({loopURL: BASE_URL, stayOnline: true});
|
||||
@ -268,6 +269,7 @@ add_task(function* basicAuthorizationAndRegistration() {
|
||||
let loopButton = document.getElementById("loop-call-button");
|
||||
is(loopButton.getAttribute("state"), "", "state of loop button should be empty when not logged in");
|
||||
|
||||
info("Login");
|
||||
let tokenData = yield MozLoopService.logInToFxA();
|
||||
yield promiseObserverNotified("loop-status-changed", "login");
|
||||
ise(tokenData.access_token, "code1_access_token", "Check access_token");
|
||||
@ -280,7 +282,9 @@ add_task(function* basicAuthorizationAndRegistration() {
|
||||
is(loopButton.getAttribute("state"), "active", "state of loop button should be active when logged in");
|
||||
|
||||
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, "https://localhost/pushUrl/fxa",
|
||||
ise(registrationResponse.response.simplePushURLs.calls, "https://localhost/pushUrl/fxa-calls",
|
||||
"Check registered push URL");
|
||||
ise(registrationResponse.response.simplePushURLs.rooms, "https://localhost/pushUrl/fxa-rooms",
|
||||
"Check registered push URL");
|
||||
|
||||
let loopPanel = document.getElementById("loop-notification-panel");
|
||||
@ -329,16 +333,17 @@ add_task(function* loginWithParams401() {
|
||||
add_task(function* logoutWithIncorrectPushURL() {
|
||||
yield resetFxA();
|
||||
let pushURL = "http://www.example.com/";
|
||||
mockPushHandler.pushUrl = pushURL;
|
||||
mockPushHandler.registeredChannels[LoopCalls.channelIDs.FxA] = pushURL;
|
||||
mockPushHandler.registeredChannels[LoopRooms.channelIDs.FxA] = pushURL;
|
||||
|
||||
// Create a fake FxA hawk session token
|
||||
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
|
||||
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
|
||||
|
||||
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURL);
|
||||
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, {calls: pushURL});
|
||||
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL");
|
||||
mockPushHandler.pushUrl = "http://www.example.com/invalid";
|
||||
ise(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL");
|
||||
mockPushHandler.registeredChannels[LoopCalls.channelIDs.FxA] = "http://www.example.com/invalid";
|
||||
let caught = false;
|
||||
yield MozLoopService.logOutFromFxA().catch((error) => {
|
||||
caught = true;
|
||||
@ -346,26 +351,27 @@ add_task(function* logoutWithIncorrectPushURL() {
|
||||
ok(caught, "Should have caught an error logging out with a mismatched push URL");
|
||||
checkLoggedOutState();
|
||||
registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL wasn't deleted");
|
||||
ise(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL wasn't deleted");
|
||||
});
|
||||
|
||||
add_task(function* logoutWithNoPushURL() {
|
||||
yield resetFxA();
|
||||
let pushURL = "http://www.example.com/";
|
||||
mockPushHandler.pushUrl = pushURL;
|
||||
mockPushHandler.registeredChannels[LoopCalls.channelIDs.FxA] = pushURL;
|
||||
|
||||
// Create a fake FxA hawk session token
|
||||
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
|
||||
Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
|
||||
|
||||
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURL);
|
||||
yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, {calls: pushURL});
|
||||
let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL");
|
||||
mockPushHandler.pushUrl = null;
|
||||
ise(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL");
|
||||
mockPushHandler.registeredChannels[LoopCalls.channelIDs.FxA] = null;
|
||||
mockPushHandler.registeredChannels[LoopRooms.channelIDs.FxA] = null;
|
||||
yield MozLoopService.logOutFromFxA();
|
||||
checkLoggedOutState();
|
||||
registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
|
||||
ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL wasn't deleted");
|
||||
ise(registrationResponse.response.simplePushURLs.calls, pushURL, "Check registered push URL wasn't deleted");
|
||||
});
|
||||
|
||||
add_task(function* loginWithRegistration401() {
|
||||
|
@ -6,6 +6,8 @@ const {
|
||||
LOOP_SESSION_TYPE,
|
||||
MozLoopServiceInternal,
|
||||
} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
|
||||
const {LoopCalls} = Cu.import("resource:///modules/loop/LoopCalls.jsm", {});
|
||||
const {LoopRooms} = Cu.import("resource:///modules/loop/LoopRooms.jsm", {});
|
||||
|
||||
// Cache this value only once, at the beginning of a
|
||||
// test run, so that it doesn't pick up the offline=true
|
||||
@ -120,6 +122,7 @@ function* resetFxA() {
|
||||
global.gHawkClient = null;
|
||||
global.gFxAOAuthClientPromise = null;
|
||||
global.gFxAOAuthClient = null;
|
||||
global.gRegisteredDeferred = null;
|
||||
MozLoopServiceInternal.fxAOAuthProfile = null;
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = null;
|
||||
const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
|
||||
@ -196,21 +199,30 @@ let mockPushHandler = {
|
||||
// This sets the registration result to be returned when initialize
|
||||
// is called. By default, it is equivalent to success.
|
||||
registrationResult: null,
|
||||
pushUrl: undefined,
|
||||
registrationPushURL: null,
|
||||
notificationCallback: {},
|
||||
registeredChannels: {},
|
||||
|
||||
/**
|
||||
* MozLoopPushHandler API
|
||||
*/
|
||||
initialize: function(registerCallback, notificationCallback) {
|
||||
registerCallback(this.registrationResult, this.pushUrl);
|
||||
this._notificationCallback = notificationCallback;
|
||||
initialize: function(options = {}) {
|
||||
if ("mockWebSocket" in options) {
|
||||
this._mockWebSocket = options.mockWebSocket;
|
||||
}
|
||||
},
|
||||
|
||||
register: function(channelId, registerCallback, notificationCallback) {
|
||||
this.notificationCallback[channelId] = notificationCallback;
|
||||
this.registeredChannels[channelId] = this.registrationPushURL;
|
||||
setTimeout(registerCallback(this.registrationResult, this.registrationPushURL, channelId), 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test-only API to simplify notifying a push notification result.
|
||||
*/
|
||||
notify: function(version) {
|
||||
this._notificationCallback(version);
|
||||
notify: function(version, chanId) {
|
||||
this.notificationCallback[chanId](version, chanId);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -197,9 +197,11 @@ function registration(request, response) {
|
||||
let body = NetUtil.readInputStreamToString(request.bodyInputStream,
|
||||
request.bodyInputStream.available());
|
||||
let payload = JSON.parse(body);
|
||||
if (payload.simplePushURL == "https://localhost/pushUrl/fxa" &&
|
||||
(!request.hasHeader("Authorization") ||
|
||||
!request.getHeader("Authorization").startsWith("Hawk"))) {
|
||||
if ((payload.simplePushURL == "https://localhost/pushUrl/fxa" ||
|
||||
payload.simplePushURLs.calls == "https://localhost/pushUrl/fxa-calls" ||
|
||||
payload.simplePushURLs.rooms == "https://localhost/pushUrl/fxa-rooms") &&
|
||||
(!request.hasHeader("Authorization") ||
|
||||
!request.getHeader("Authorization").startsWith("Hawk"))) {
|
||||
response.setStatusLine(request.httpVersion, 401, "Missing Hawk");
|
||||
response.write("401 Missing Hawk Authorization header");
|
||||
return;
|
||||
@ -224,11 +226,14 @@ function delete_registration(request, response) {
|
||||
// making the path become a query parameter. This is because we aren't actually
|
||||
// registering endpoints at the root of the hostname e.g. /registration.
|
||||
let url = new URL(request.queryString.replace(/%3F.*/,""), "http://www.example.com");
|
||||
let registration = JSON.parse(getSharedState("/registration"));
|
||||
if (registration.simplePushURL == url.searchParams.get("simplePushURL")) {
|
||||
setSharedState("/registration", "");
|
||||
} else {
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad Request");
|
||||
let state = getSharedState("/registration");
|
||||
if (state != "") { //Already set to empty value on a successful channel unregsitration.
|
||||
let registration = JSON.parse(state);
|
||||
if (registration.simplePushURLs.calls == url.searchParams.get("simplePushURL")) {
|
||||
setSharedState("/registration", "");
|
||||
} else {
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad Request");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ Cu.import("resource://gre/modules/Http.jsm");
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource:///modules/loop/MozLoopService.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource:///modules/loop/LoopCalls.jsm");
|
||||
const { MozLoopServiceInternal } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
|
||||
@ -18,6 +19,7 @@ const kMockWebSocketChannelName = "Mock WebSocket Channel";
|
||||
const kWebSocketChannelContractID = "@mozilla.org/network/protocol;1?name=wss";
|
||||
|
||||
const kServerPushUrl = "http://localhost:3456";
|
||||
const kLoopServerUrl = "http://localhost:3465";
|
||||
const kEndPointUrl = "http://example.com/fake";
|
||||
const kUAID = "f47ac11b-58ca-4372-9567-0e02b2c3d479";
|
||||
|
||||
@ -75,21 +77,30 @@ let mockPushHandler = {
|
||||
// This sets the registration result to be returned when initialize
|
||||
// is called. By default, it is equivalent to success.
|
||||
registrationResult: null,
|
||||
registrationPushURL: undefined,
|
||||
registrationPushURL: null,
|
||||
notificationCallback: {},
|
||||
registeredChannels: {},
|
||||
|
||||
/**
|
||||
* MozLoopPushHandler API
|
||||
*/
|
||||
initialize: function(registerCallback, notificationCallback) {
|
||||
registerCallback(this.registrationResult, this.registrationPushURL);
|
||||
this._notificationCallback = notificationCallback;
|
||||
initialize: function(options = {}) {
|
||||
if ("mockWebSocket" in options) {
|
||||
this._mockWebSocket = options.mockWebSocket;
|
||||
}
|
||||
},
|
||||
|
||||
register: function(channelId, registerCallback, notificationCallback) {
|
||||
this.notificationCallback[channelId] = notificationCallback;
|
||||
this.registeredChannels[channelId] = this.registrationPushURL;
|
||||
registerCallback(this.registrationResult, this.registrationPushURL, channelId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Test-only API to simplify notifying a push notification result.
|
||||
*/
|
||||
notify: function(version) {
|
||||
this._notificationCallback(version);
|
||||
notify: function(version, chanId) {
|
||||
this.notificationCallback[chanId](version, chanId);
|
||||
}
|
||||
};
|
||||
|
||||
@ -98,9 +109,8 @@ let mockPushHandler = {
|
||||
* enables us to check parameters and return messages similar to the push
|
||||
* server.
|
||||
*/
|
||||
let MockWebSocketChannel = function(options) {
|
||||
let _options = options || {};
|
||||
this.defaultMsgHandler = _options.defaultMsgHandler;
|
||||
let MockWebSocketChannel = function(options = {}) {
|
||||
this.defaultMsgHandler = options.defaultMsgHandler;
|
||||
};
|
||||
|
||||
MockWebSocketChannel.prototype = {
|
||||
|
@ -1,24 +1,39 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
{
|
||||
add_test(function test_initalize_offline() {
|
||||
Services.io.offline = true;
|
||||
|
||||
MozLoopPushHandler.initialize(function(err) {
|
||||
Assert.equal(err, "offline", "Should error with 'offline' when offline");
|
||||
|
||||
Services.io.offline = false;
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
let dummyCallback = () => {};
|
||||
let mockWebSocket = new MockWebSocketChannel();
|
||||
|
||||
add_test(function test_initalize_offline() {
|
||||
Services.io.offline = true;
|
||||
do_check_false(MozLoopPushHandler.initialize());
|
||||
Services.io.offline = false;
|
||||
MozLoopPushHandler.initialize({mockWebSocket: mockWebSocket});
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_initalize_missing_chanid() {
|
||||
Assert.throws(() => {MozLoopPushHandler.register(null, dummyCallback, dummyCallback)});
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_initalize_missing_regcallback() {
|
||||
Assert.throws(() => {MozLoopPushHandler.register("chan-1", null, dummyCallback)});
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_initalize_missing_notifycallback() {
|
||||
Assert.throws(() => {MozLoopPushHandler.register("chan-1", dummyCallback, null)});
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_initalize_websocket() {
|
||||
MozLoopPushHandler.initialize(
|
||||
function(err, url) {
|
||||
MozLoopPushHandler.register(
|
||||
"chan-1",
|
||||
function(err, url, id) {
|
||||
Assert.equal(err, null, "Should return null for success");
|
||||
Assert.equal(url, kEndPointUrl, "Should return push server application URL");
|
||||
Assert.equal(id, "chan-1", "Should have channel id = chan-1");
|
||||
Assert.equal(mockWebSocket.uri.prePath, kServerPushUrl,
|
||||
"Should have the url from preferences");
|
||||
Assert.equal(mockWebSocket.origin, kServerPushUrl,
|
||||
@ -27,28 +42,29 @@
|
||||
"Should have the protocol set to push-notifications");
|
||||
mockWebSocket.notify(15);
|
||||
},
|
||||
function(version) {
|
||||
function(version, id) {
|
||||
Assert.equal(version, 15, "Should have version number 15");
|
||||
Assert.equal(id, "chan-1", "Should have channel id = chan-1");
|
||||
run_next_test();
|
||||
},
|
||||
},
|
||||
mockWebSocket);
|
||||
});
|
||||
|
||||
add_test(function test_reconnect_websocket() {
|
||||
MozLoopPushHandler.uaID = undefined;
|
||||
MozLoopPushHandler.pushUrl = undefined; //Do this to force a new registration callback.
|
||||
MozLoopPushHandler.registeredChannels = {}; //Do this to force a new registration callback.
|
||||
mockWebSocket.stop();
|
||||
});
|
||||
|
||||
add_test(function test_reopen_websocket() {
|
||||
MozLoopPushHandler.uaID = undefined;
|
||||
MozLoopPushHandler.pushUrl = undefined; //Do this to force a new registration callback.
|
||||
MozLoopPushHandler.registeredChannels = {}; //Do this to force a new registration callback.
|
||||
mockWebSocket.serverClose();
|
||||
});
|
||||
|
||||
add_test(function test_retry_registration() {
|
||||
MozLoopPushHandler.uaID = undefined;
|
||||
MozLoopPushHandler.pushUrl = undefined; //Do this to force a new registration callback.
|
||||
MozLoopPushHandler.registeredChannels = {}; //Do this to force a new registration callback.
|
||||
mockWebSocket.initRegStatus = 500;
|
||||
mockWebSocket.stop();
|
||||
});
|
||||
@ -57,6 +73,7 @@
|
||||
setupFakeLoopServer();
|
||||
|
||||
Services.prefs.setCharPref("services.push.serverURL", kServerPushUrl);
|
||||
Services.prefs.setCharPref("loop.server", kLoopServerUrl);
|
||||
Services.prefs.setIntPref("loop.retry_delay.start", 10); // 10 ms
|
||||
Services.prefs.setIntPref("loop.retry_delay.limit", 20); // 20 ms
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
* 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/. */
|
||||
|
||||
const { LoopCallsInternal } = Cu.import("resource:///modules/loop/LoopCalls.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
|
||||
"resource:///modules/Chat.jsm");
|
||||
|
||||
@ -10,20 +12,22 @@ let openChatOrig = Chat.open;
|
||||
const firstCallId = 4444333221;
|
||||
const secondCallId = 1001100101;
|
||||
|
||||
function test_send_busy_on_call() {
|
||||
let actionReceived = false;
|
||||
let msgHandler = function(msg) {
|
||||
if (msg.messageType &&
|
||||
msg.messageType === "action" &&
|
||||
msg.event === "terminate" &&
|
||||
msg.reason === "busy") {
|
||||
actionReceived = true;
|
||||
}
|
||||
}
|
||||
|
||||
let msgHandler = function(msg) {
|
||||
if (msg.messageType &&
|
||||
msg.messageType === "action" &&
|
||||
msg.event === "terminate" &&
|
||||
msg.reason === "busy") {
|
||||
actionReceived = true;
|
||||
}
|
||||
};
|
||||
let mockWebSocket = new MockWebSocketChannel({defaultMsgHandler: msgHandler});
|
||||
LoopCallsInternal._mocks.webSocket = mockWebSocket;
|
||||
|
||||
let mockWebSocket = new MockWebSocketChannel({defaultMsgHandler: msgHandler});
|
||||
Services.io.offline = false;
|
||||
Services.io.offline = false;
|
||||
|
||||
add_test(function test_busy_2guest_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register(mockPushHandler, mockWebSocket).then(() => {
|
||||
let opened = 0;
|
||||
@ -31,22 +35,90 @@ function test_send_busy_on_call() {
|
||||
opened++;
|
||||
};
|
||||
|
||||
mockPushHandler.notify(1);
|
||||
mockPushHandler.notify(1, LoopCalls.channelIDs.Guest);
|
||||
|
||||
waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
|
||||
do_check_true(opened === 1, "should open only one chat window");
|
||||
do_check_true(actionReceived, "should respond with busy/reject to second call");
|
||||
MozLoopService.releaseCallData(firstCallId);
|
||||
LoopCalls.releaseCallData(firstCallId);
|
||||
run_next_test();
|
||||
}, () => {
|
||||
do_throw("should have opened a chat window for first call and rejected second call");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add_test(test_send_busy_on_call); //FXA call accepted, Guest call rejected
|
||||
add_test(test_send_busy_on_call); //No FXA call, first Guest call accepted, second rejected
|
||||
add_test(function test_busy_1fxa_1guest_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register(mockPushHandler, mockWebSocket).then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
};
|
||||
|
||||
mockPushHandler.notify(1, LoopCalls.channelIDs.FxA);
|
||||
mockPushHandler.notify(1, LoopCalls.channelIDs.Guest);
|
||||
|
||||
waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
|
||||
do_check_true(opened === 1, "should open only one chat window");
|
||||
do_check_true(actionReceived, "should respond with busy/reject to second call");
|
||||
LoopCalls.releaseCallData(firstCallId);
|
||||
run_next_test();
|
||||
}, () => {
|
||||
do_throw("should have opened a chat window for first call and rejected second call");
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_busy_2fxa_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register(mockPushHandler, mockWebSocket).then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
};
|
||||
|
||||
mockPushHandler.notify(1, LoopCalls.channelIDs.FxA);
|
||||
|
||||
waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
|
||||
do_check_true(opened === 1, "should open only one chat window");
|
||||
do_check_true(actionReceived, "should respond with busy/reject to second call");
|
||||
LoopCalls.releaseCallData(firstCallId);
|
||||
run_next_test();
|
||||
}, () => {
|
||||
do_throw("should have opened a chat window for first call and rejected second call");
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_busy_1guest_1fxa_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register(mockPushHandler, mockWebSocket).then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
};
|
||||
|
||||
mockPushHandler.notify(1, LoopCalls.channelIDs.Guest);
|
||||
mockPushHandler.notify(1, LoopCalls.channelIDs.FxA);
|
||||
|
||||
waitForCondition(() => {return actionReceived && opened > 0}).then(() => {
|
||||
do_check_true(opened === 1, "should open only one chat window");
|
||||
do_check_true(actionReceived, "should respond with busy/reject to second call");
|
||||
LoopCalls.releaseCallData(firstCallId);
|
||||
run_next_test();
|
||||
}, () => {
|
||||
do_throw("should have opened a chat window for first call and rejected second call");
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
@ -64,20 +136,18 @@ function run_test()
|
||||
|
||||
let callsRespCount = 0;
|
||||
let callsResponses = [
|
||||
{calls: [{callId: firstCallId,
|
||||
websocketToken: "0deadbeef0",
|
||||
progressURL: "wss://localhost:5000/websocket"}]},
|
||||
{calls: [{callId: secondCallId,
|
||||
websocketToken: "1deadbeef1",
|
||||
progressURL: "wss://localhost:5000/websocket"}]},
|
||||
|
||||
{calls: []},
|
||||
{calls: [{callId: firstCallId,
|
||||
websocketToken: "0deadbeef0",
|
||||
progressURL: "wss://localhost:5000/websocket"},
|
||||
{callId: secondCallId,
|
||||
websocketToken: "1deadbeef1",
|
||||
progressURL: "wss://localhost:5000/websocket"}]},
|
||||
{calls: [{callId: firstCallId,
|
||||
websocketToken: "0deadbeef0",
|
||||
progressURL: "wss://localhost:5000/websocket"}]},
|
||||
{calls: [{callId: secondCallId,
|
||||
websocketToken: "1deadbeef1",
|
||||
progressURL: "wss://localhost:5000/websocket"}]},
|
||||
];
|
||||
|
||||
loopServer.registerPathHandler("/registration", (request, response) => {
|
||||
@ -107,6 +177,8 @@ function run_test()
|
||||
|
||||
// clear test pref
|
||||
Services.prefs.clearUserPref("loop.seenToS");
|
||||
|
||||
LoopCallsInternal._mocks.webSocket = undefined;
|
||||
});
|
||||
|
||||
run_next_test();
|
||||
|
@ -21,13 +21,13 @@ add_task(function test_startDirectCall_opens_window() {
|
||||
openedUrl = url;
|
||||
};
|
||||
|
||||
MozLoopService.startDirectCall(contact, "audio-video");
|
||||
LoopCalls.startDirectCall(contact, "audio-video");
|
||||
|
||||
do_check_true(!!openedUrl, "should open a chat window");
|
||||
|
||||
// Stop the busy kicking in for following tests.
|
||||
let callId = openedUrl.match(/about:loopconversation\#outgoing\/(.*)/)[1];
|
||||
MozLoopService.releaseCallData(callId);
|
||||
LoopCalls.releaseCallData(callId);
|
||||
});
|
||||
|
||||
add_task(function test_startDirectCall_getCallData() {
|
||||
@ -36,17 +36,17 @@ add_task(function test_startDirectCall_getCallData() {
|
||||
openedUrl = url;
|
||||
};
|
||||
|
||||
MozLoopService.startDirectCall(contact, "audio-video");
|
||||
LoopCalls.startDirectCall(contact, "audio-video");
|
||||
|
||||
let callId = openedUrl.match(/about:loopconversation\#outgoing\/(.*)/)[1];
|
||||
|
||||
let callData = MozLoopService.getCallData(callId);
|
||||
let callData = LoopCalls.getCallData(callId);
|
||||
|
||||
do_check_eq(callData.callType, "audio-video", "should have the correct call type");
|
||||
do_check_eq(callData.contact, contact, "should have the contact details");
|
||||
|
||||
// Stop the busy kicking in for following tests.
|
||||
MozLoopService.releaseCallData(callId);
|
||||
LoopCalls.releaseCallData(callId);
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
|
@ -37,7 +37,7 @@ add_test(function test_do_not_disturb_disabled_should_open_chat_window() {
|
||||
opened = true;
|
||||
};
|
||||
|
||||
mockPushHandler.notify(1);
|
||||
mockPushHandler.notify(1, LoopCalls.channelIDs.Guest);
|
||||
|
||||
waitForCondition(function() opened).then(() => {
|
||||
run_next_test();
|
||||
@ -56,7 +56,7 @@ add_test(function test_do_not_disturb_enabled_shouldnt_open_chat_window() {
|
||||
opened = true;
|
||||
};
|
||||
|
||||
mockPushHandler.notify(1);
|
||||
mockPushHandler.notify(1, LoopCalls.channelIDs.Guest);
|
||||
|
||||
do_timeout(500, function() {
|
||||
do_check_false(opened, "should not open a chat window");
|
||||
|
@ -16,7 +16,7 @@ add_test(function test_openChatWindow_on_notification() {
|
||||
opened = true;
|
||||
};
|
||||
|
||||
mockPushHandler.notify(1);
|
||||
mockPushHandler.notify(1, LoopCalls.channelIDs.Guest);
|
||||
|
||||
waitForCondition(function() opened).then(() => {
|
||||
do_check_true(opened, "should open a chat window");
|
||||
|
@ -10,37 +10,19 @@ Cu.import("resource://services-common/utils.js");
|
||||
* other test files.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests reported failures when we're in offline mode.
|
||||
*/
|
||||
add_test(function test_register_offline() {
|
||||
mockPushHandler.registrationResult = "offline";
|
||||
|
||||
// It should callback with failure if in offline mode
|
||||
Services.io.offline = true;
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
do_throw("should not succeed when offline");
|
||||
}, err => {
|
||||
Assert.equal(err, "offline", "should reject with 'offline' when offline");
|
||||
Services.io.offline = false;
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test that the websocket can be fully registered, and that a Loop server
|
||||
* failure is reported.
|
||||
*/
|
||||
add_test(function test_register_websocket_success_loop_server_fail() {
|
||||
mockPushHandler.registrationResult = null;
|
||||
mockPushHandler.registrationResult = "404";
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
do_throw("should not succeed when loop server registration fails");
|
||||
}, err => {
|
||||
}, (err) => {
|
||||
// 404 is an expected failure indicated by the lack of route being set
|
||||
// up on the Loop server mock. This is added in the next test.
|
||||
Assert.equal(err.errno, 404, "Expected no errors in websocket registration");
|
||||
Assert.equal(err.message, "404", "Expected no errors in websocket registration");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
@ -50,14 +32,18 @@ add_test(function test_register_websocket_success_loop_server_fail() {
|
||||
* Tests that we get a success response when both websocket and Loop server
|
||||
* registration are complete.
|
||||
*/
|
||||
|
||||
add_test(function test_register_success() {
|
||||
mockPushHandler.registrationPushURL = kEndPointUrl;
|
||||
mockPushHandler.registrationResult = null;
|
||||
|
||||
loopServer.registerPathHandler("/registration", (request, response) => {
|
||||
let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
|
||||
let data = JSON.parse(body);
|
||||
Assert.equal(data.simplePushURL, kEndPointUrl,
|
||||
"Should send correct push url");
|
||||
Assert.equal(data.simplePushURLs.calls, kEndPointUrl,
|
||||
"Should send correct calls push url");
|
||||
Assert.equal(data.simplePushURLs.rooms, kEndPointUrl,
|
||||
"Should send correct rooms push url");
|
||||
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.processAsync();
|
||||
|
Loading…
Reference in New Issue
Block a user