зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1020448 - Add Loop pending call timeout for the link clicker UI. r=Standard8,ui-review=dhenein
This commit is contained in:
Родитель
8bd6dc13b4
Коммит
54c330ca18
|
@ -14,6 +14,7 @@ loop.shared.models = (function() {
|
|||
*/
|
||||
var ConversationModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
connected: false, // Session connected flag
|
||||
ongoing: false, // Ongoing call flag
|
||||
callerId: undefined, // Loop caller id
|
||||
loopToken: undefined, // Loop conversation token
|
||||
|
@ -38,11 +39,29 @@ loop.shared.models = (function() {
|
|||
*/
|
||||
session: undefined,
|
||||
|
||||
/**
|
||||
* Pending call timeout value.
|
||||
* @type {Number}
|
||||
*/
|
||||
pendingCallTimeout: undefined,
|
||||
|
||||
/**
|
||||
* Pending call timer.
|
||||
* @type {Number}
|
||||
*/
|
||||
_pendingCallTimer: undefined,
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Required options:
|
||||
* - {OT} sdk: SDK object.
|
||||
* Options:
|
||||
*
|
||||
* Required:
|
||||
* - {OT} sdk: OT SDK object.
|
||||
*
|
||||
* Optional:
|
||||
* - {Number} pendingCallTimeout: Pending call timeout in milliseconds
|
||||
* (default: 20000).
|
||||
*
|
||||
* @param {Object} attributes Attributes object.
|
||||
* @param {Object} options Options object.
|
||||
|
@ -53,6 +72,10 @@ loop.shared.models = (function() {
|
|||
throw new Error("missing required sdk");
|
||||
}
|
||||
this.sdk = options.sdk;
|
||||
this.pendingCallTimeout = options.pendingCallTimeout || 20000;
|
||||
|
||||
// Ensure that any pending call timer is cleared on disconnect/error
|
||||
this.on("session:ended session:error", this._clearPendingCallTimer, this);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -79,21 +102,38 @@ loop.shared.models = (function() {
|
|||
* @param {Object} options Options object
|
||||
*/
|
||||
initiate: function(options) {
|
||||
options = options || {};
|
||||
|
||||
// Outgoing call has never reached destination, closing - see bug 1020448
|
||||
function handleOutgoingCallTimeout() {
|
||||
/*jshint validthis:true */
|
||||
if (!this.get("ongoing")) {
|
||||
this.trigger("timeout").endSession();
|
||||
}
|
||||
}
|
||||
|
||||
function handleResult(err, sessionData) {
|
||||
/*jshint validthis:true */
|
||||
this._clearPendingCallTimer();
|
||||
|
||||
if (err) {
|
||||
this.trigger("session:error", new Error(
|
||||
"Retrieval of session information failed: HTTP " + err));
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX For incoming calls we might have more than one call queued.
|
||||
// For now, we'll just assume the first call is the right information.
|
||||
// We'll probably really want to be getting this data from the
|
||||
// background worker on the desktop client.
|
||||
// Bug 990714 should fix this.
|
||||
if (!options.outgoing)
|
||||
if (options.outgoing) {
|
||||
// Setup pending call timeout.
|
||||
this._pendingCallTimer = setTimeout(
|
||||
handleOutgoingCallTimeout.bind(this), this.pendingCallTimeout);
|
||||
} else {
|
||||
// XXX For incoming calls we might have more than one call queued.
|
||||
// For now, we'll just assume the first call is the right information.
|
||||
// We'll probably really want to be getting this data from the
|
||||
// background worker on the desktop client.
|
||||
// Bug 990714 should fix this.
|
||||
sessionData = sessionData[0];
|
||||
}
|
||||
|
||||
this.setReady(sessionData);
|
||||
}
|
||||
|
@ -156,8 +196,17 @@ loop.shared.models = (function() {
|
|||
*/
|
||||
endSession: function() {
|
||||
this.session.disconnect();
|
||||
this.once("session:ended", this.stopListening, this);
|
||||
this.set("ongoing", false);
|
||||
this.set("ongoing", false)
|
||||
.once("session:ended", this.stopListening, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears current pending call timer, if any.
|
||||
*/
|
||||
_clearPendingCallTimer: function() {
|
||||
if (this._pendingCallTimer) {
|
||||
clearTimeout(this._pendingCallTimer);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -175,7 +224,7 @@ loop.shared.models = (function() {
|
|||
this.endSession();
|
||||
} else {
|
||||
this.trigger("session:connected");
|
||||
this.set("ongoing", true);
|
||||
this.set("connected", true);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -186,7 +235,8 @@ loop.shared.models = (function() {
|
|||
* @param {StreamEvent} event
|
||||
*/
|
||||
_streamCreated: function(event) {
|
||||
this.trigger("session:stream-created", event);
|
||||
this.set("ongoing", true)
|
||||
.trigger("session:stream-created", event);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -196,8 +246,9 @@ loop.shared.models = (function() {
|
|||
* @param {SessionDisconnectEvent} event
|
||||
*/
|
||||
_sessionDisconnected: function(event) {
|
||||
this.trigger("session:ended");
|
||||
this.set("ongoing", false);
|
||||
this.set("connected", false)
|
||||
.set("ongoing", false)
|
||||
.trigger("session:ended");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -207,9 +258,11 @@ loop.shared.models = (function() {
|
|||
* @param {ConnectionEvent} event
|
||||
*/
|
||||
_connectionDestroyed: function(event) {
|
||||
this.trigger("session:peer-hungup", {
|
||||
connectionId: event.connection.connectionId
|
||||
});
|
||||
this.set("connected", false)
|
||||
.set("ongoing", false)
|
||||
.trigger("session:peer-hungup", {
|
||||
connectionId: event.connection.connectionId
|
||||
});
|
||||
this.endSession();
|
||||
},
|
||||
|
||||
|
@ -220,7 +273,9 @@ loop.shared.models = (function() {
|
|||
* @param {ConnectionEvent} event
|
||||
*/
|
||||
_networkDisconnected: function(event) {
|
||||
this.trigger("session:network-disconnected");
|
||||
this.set("connected", false)
|
||||
.set("ongoing", false)
|
||||
.trigger("session:network-disconnected");
|
||||
this.endSession();
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
LOOP_SERVER_URL := $(shell echo $${LOOP_SERVER_URL-http://localhost:5000})
|
||||
LOOP_PENDING_CALL_TIMEOUT := $(shell echo $${LOOP_PENDING_CALL_TIMEOUT-20000})
|
||||
NODE_LOCAL_BIN=./node_modules/.bin
|
||||
|
||||
install:
|
||||
|
@ -21,4 +22,7 @@ frontend:
|
|||
@echo "Not implemented yet."
|
||||
|
||||
config:
|
||||
@echo "var loop = loop || {};\nloop.config = {serverUrl: '`echo $(LOOP_SERVER_URL)`'};" > content/config.js
|
||||
@echo "var loop = loop || {};" > content/config.js
|
||||
@echo "loop.config = loop.config || {};" >> content/config.js
|
||||
@echo "loop.config.serverUrl = '`echo $(LOOP_SERVER_URL)`';" >> content/config.js
|
||||
@echo "loop.config.pendingCallTimeout = `echo $(LOOP_PENDING_CALL_TIMEOUT)`;" >> content/config.js
|
||||
|
|
|
@ -16,11 +16,15 @@ Configuration
|
|||
|
||||
You will need to generate a configuration file, you can do so with:
|
||||
|
||||
$ make config
|
||||
$ make config
|
||||
|
||||
It will read the configuration from the `LOOP_SERVER_URL` env variable and
|
||||
generate the appropriate configuration file. This setting defines the root url
|
||||
of the loop server, without trailing slash.
|
||||
It will read the configuration from the following env variables and generate the
|
||||
appropriate configuration file:
|
||||
|
||||
- `LOOP_SERVER_URL` defines the root url of the loop server, without trailing
|
||||
slash (default: `http://localhost:5000`).
|
||||
- `LOOP_PENDING_CALL_TIMEOUT` defines the amount of time a pending outgoing call
|
||||
should be considered timed out, in milliseconds (default: `20000`).
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
|
|
@ -119,6 +119,8 @@ loop.webapp = (function($, _, OT) {
|
|||
initialize: function() {
|
||||
// Load default view
|
||||
this.loadView(new HomeView());
|
||||
|
||||
this.listenTo(this._conversation, "timeout", this._onTimeout);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -146,6 +148,10 @@ loop.webapp = (function($, _, OT) {
|
|||
this.navigate(route, {trigger: true});
|
||||
},
|
||||
|
||||
_onTimeout: function() {
|
||||
this._notifier.errorL10n("call_timeout_notification_text");
|
||||
},
|
||||
|
||||
/**
|
||||
* Default entry point.
|
||||
*/
|
||||
|
@ -213,8 +219,11 @@ loop.webapp = (function($, _, OT) {
|
|||
function init() {
|
||||
var helper = new WebappHelper();
|
||||
router = new WebappRouter({
|
||||
conversation: new sharedModels.ConversationModel({}, {sdk: OT}),
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"})
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"}),
|
||||
conversation: new sharedModels.ConversationModel({}, {
|
||||
sdk: OT,
|
||||
pendingCallTimeout: loop.config.pendingCallTimeout
|
||||
})
|
||||
});
|
||||
Backbone.history.start();
|
||||
if (helper.isIOS(navigator.platform)) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[en]
|
||||
call_has_ended=Your call has ended.
|
||||
call_timeout_notification_text=Your call did not go through.
|
||||
missing_conversation_info=Missing conversation information.
|
||||
network_disconnected=The network connection terminated abruptly.
|
||||
peer_ended_conversation=Your peer ended the conversation.
|
||||
|
@ -21,6 +22,7 @@ connection_error_see_console_notification=Call failed; see console for details.
|
|||
|
||||
[fr]
|
||||
call_has_ended=L'appel est terminé.
|
||||
call_timeout_notification_text=Votre appel n'a pas abouti.
|
||||
missing_conversation_info=Informations de communication manquantes.
|
||||
network_disconnected=La connexion réseau semble avoir été interrompue.
|
||||
peer_ended_conversation=Votre correspondant a mis fin à la communication.
|
||||
|
|
|
@ -96,7 +96,10 @@ describe("loop.conversation", function() {
|
|||
var conversation;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new loop.shared.models.ConversationModel({}, {sdk: {}});
|
||||
conversation = new loop.shared.models.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
sandbox.stub(conversation, "initiate");
|
||||
});
|
||||
|
||||
|
@ -285,7 +288,10 @@ describe("loop.conversation", function() {
|
|||
var conversation, view;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new loop.shared.models.ConversationModel({}, {sdk: {}});
|
||||
conversation = new loop.shared.models.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
view = new loop.conversation.IncomingCallView({model: conversation});
|
||||
});
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ describe("loop.shared.models", function() {
|
|||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
sandbox.useFakeTimers();
|
||||
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
||||
requests = [];
|
||||
// https://github.com/cjohansen/Sinon.JS/issues/393
|
||||
|
@ -46,9 +47,16 @@ describe("loop.shared.models", function() {
|
|||
describe("#initialize", function() {
|
||||
it("should require a sdk option", function() {
|
||||
expect(function() {
|
||||
new sharedModels.ConversationModel();
|
||||
new sharedModels.ConversationModel({}, {});
|
||||
}).to.Throw(Error, /missing required sdk/);
|
||||
});
|
||||
|
||||
it("should accept a pendingCallTimeout option", function() {
|
||||
expect(new sharedModels.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
}).pendingCallTimeout).eql(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("constructed", function() {
|
||||
|
@ -56,7 +64,10 @@ describe("loop.shared.models", function() {
|
|||
requestCallInfoStub, requestCallsInfoStub;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {sdk: fakeSDK});
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
conversation.set("loopToken", "fakeToken");
|
||||
fakeBaseServerUrl = "http://fakeBaseServerUrl";
|
||||
fakeClient = {
|
||||
|
@ -68,6 +79,10 @@ describe("loop.shared.models", function() {
|
|||
});
|
||||
|
||||
describe("#initiate", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(conversation, "endSession");
|
||||
});
|
||||
|
||||
it("call requestCallInfo on the client for outgoing calls",
|
||||
function() {
|
||||
conversation.initiate({
|
||||
|
@ -139,6 +154,35 @@ describe("loop.shared.models", function() {
|
|||
outgoing: true
|
||||
});
|
||||
});
|
||||
|
||||
it("should end the session on outgoing call timeout", function() {
|
||||
requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
|
||||
|
||||
conversation.initiate({
|
||||
client: fakeClient,
|
||||
outgoing: true
|
||||
});
|
||||
|
||||
sandbox.clock.tick(1001);
|
||||
|
||||
sinon.assert.calledOnce(conversation.endSession);
|
||||
});
|
||||
|
||||
it("should trigger a `timeout` event on outgoing call timeout",
|
||||
function(done) {
|
||||
requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
|
||||
|
||||
conversation.once("timeout", function() {
|
||||
done();
|
||||
});
|
||||
|
||||
conversation.initiate({
|
||||
client: fakeClient,
|
||||
outgoing: true
|
||||
});
|
||||
|
||||
sandbox.clock.tick(1001);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#setReady", function() {
|
||||
|
@ -161,8 +205,11 @@ describe("loop.shared.models", function() {
|
|||
var model;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(sharedModels.ConversationModel.prototype,
|
||||
"_clearPendingCallTimer");
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
model.startSession();
|
||||
});
|
||||
|
@ -182,16 +229,16 @@ describe("loop.shared.models", function() {
|
|||
sinon.match.func);
|
||||
});
|
||||
|
||||
it("should set ongoing to true when no error is called back",
|
||||
it("should set connected to true when no error is called back",
|
||||
function() {
|
||||
fakeSession.connect = function(key, token, cb) {
|
||||
cb(null);
|
||||
};
|
||||
sinon.stub(model, "set");
|
||||
sandbox.stub(model, "set");
|
||||
|
||||
model.startSession();
|
||||
|
||||
sinon.assert.calledWith(model.set, "ongoing", true);
|
||||
sinon.assert.calledWith(model.set, "connected", true);
|
||||
});
|
||||
|
||||
it("should trigger session:connected when no error is called back",
|
||||
|
@ -215,7 +262,7 @@ describe("loop.shared.models", function() {
|
|||
error: true
|
||||
});
|
||||
};
|
||||
sinon.stub(model, "endSession");
|
||||
sandbox.stub(model, "endSession");
|
||||
|
||||
model.startSession();
|
||||
|
||||
|
@ -239,6 +286,17 @@ describe("loop.shared.models", function() {
|
|||
"session:connection-error", sinon.match.object);
|
||||
});
|
||||
|
||||
it("should set the connected attr to true on connection completed",
|
||||
function() {
|
||||
fakeSession.connect = function(key, token, cb) {
|
||||
cb();
|
||||
};
|
||||
|
||||
model.startSession();
|
||||
|
||||
expect(model.get("connected")).eql(true);
|
||||
});
|
||||
|
||||
it("should trigger a session:ended event on sessionDisconnected",
|
||||
function(done) {
|
||||
model.once("session:ended", function(){ done(); });
|
||||
|
@ -246,16 +304,32 @@ describe("loop.shared.models", function() {
|
|||
fakeSession.trigger("sessionDisconnected", {reason: "ko"});
|
||||
});
|
||||
|
||||
it("should set the ongoing attribute to false on sessionDisconnected",
|
||||
function(done) {
|
||||
model.once("session:ended", function() {
|
||||
expect(model.get("ongoing")).eql(false);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should set the connected attribute to false on sessionDisconnected",
|
||||
function() {
|
||||
fakeSession.trigger("sessionDisconnected", {reason: "ko"});
|
||||
|
||||
expect(model.get("connected")).eql(false);
|
||||
});
|
||||
|
||||
it("should set the ongoing attribute to false on sessionDisconnected",
|
||||
function() {
|
||||
fakeSession.trigger("sessionDisconnected", {reason: "ko"});
|
||||
|
||||
expect(model.get("ongoing")).eql(false);
|
||||
});
|
||||
|
||||
it("should clear a pending timer on session:ended", function() {
|
||||
model.trigger("session:ended");
|
||||
|
||||
sinon.assert.calledOnce(model._clearPendingCallTimer);
|
||||
});
|
||||
|
||||
it("should clear a pending timer on session:error", function() {
|
||||
model.trigger("session:error");
|
||||
|
||||
sinon.assert.calledOnce(model._clearPendingCallTimer);
|
||||
});
|
||||
|
||||
describe("connectionDestroyed event received", function() {
|
||||
var fakeEvent = {reason: "ko", connection: {connectionId: 42}};
|
||||
|
||||
|
@ -304,7 +378,8 @@ describe("loop.shared.models", function() {
|
|||
|
||||
beforeEach(function() {
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
model.startSession();
|
||||
});
|
||||
|
@ -315,6 +390,12 @@ describe("loop.shared.models", function() {
|
|||
sinon.assert.calledOnce(fakeSession.disconnect);
|
||||
});
|
||||
|
||||
it("should set the connected attribute to false", function() {
|
||||
model.endSession();
|
||||
|
||||
expect(model.get("connected")).eql(false);
|
||||
});
|
||||
|
||||
it("should set the ongoing attribute to false", function() {
|
||||
model.endSession();
|
||||
|
||||
|
|
|
@ -102,7 +102,10 @@ describe("loop.shared.router", function() {
|
|||
});
|
||||
conversation = new loop.shared.models.ConversationModel({
|
||||
loopToken: "fakeToken"
|
||||
}, {sdk: {}});
|
||||
}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
});
|
||||
|
||||
describe("#constructor", function() {
|
||||
|
|
|
@ -205,7 +205,8 @@ describe("loop.shared.views", function() {
|
|||
initSession: sandbox.stub().returns(fakeSession)
|
||||
};
|
||||
model = new sharedModels.ConversationModel(fakeSessionData, {
|
||||
sdk: fakeSDK
|
||||
sdk: fakeSDK,
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -23,10 +23,12 @@ describe("loop.webapp", function() {
|
|||
error: sandbox.spy(),
|
||||
errorL10n: sandbox.spy(),
|
||||
};
|
||||
loop.config.pendingCallTimeout = 1000;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
delete loop.config.pendingCallTimeout;
|
||||
});
|
||||
|
||||
describe("#init", function() {
|
||||
|
@ -69,7 +71,10 @@ describe("loop.webapp", function() {
|
|||
var router, conversation;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {sdk: {}});
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
router = new loop.webapp.WebappRouter({
|
||||
conversation: conversation,
|
||||
notifier: notifier
|
||||
|
@ -253,7 +258,9 @@ describe("loop.webapp", function() {
|
|||
var conversation;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {sdk: {}});
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000});
|
||||
});
|
||||
|
||||
describe("#initialize", function() {
|
||||
|
@ -268,7 +275,10 @@ describe("loop.webapp", function() {
|
|||
var conversation, initiate, view, fakeSubmitEvent;
|
||||
|
||||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({}, {sdk: {}});
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
view = new loop.webapp.ConversationFormView({
|
||||
model: conversation,
|
||||
notifier: notifier
|
||||
|
@ -307,7 +317,10 @@ describe("loop.webapp", function() {
|
|||
beforeEach(function() {
|
||||
conversation = new sharedModels.ConversationModel({
|
||||
loopToken: "fake"
|
||||
}, {sdk: {}});
|
||||
}, {
|
||||
sdk: {},
|
||||
pendingCallTimeout: 1000
|
||||
});
|
||||
view = new loop.webapp.ConversationFormView({
|
||||
model: conversation,
|
||||
notifier: notifier
|
||||
|
|
Загрузка…
Ссылка в новой задаче