зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1000131 - Expired Loop call url notification, r=dmose
--HG-- rename : browser/components/loop/standalone/content/js/webapp.js => browser/components/loop/standalone/content/js/webapp.jsx
This commit is contained in:
Родитель
7682085fa5
Коммит
8d7d439893
|
@ -117,8 +117,7 @@ loop.shared.models = (function() {
|
||||||
this._clearPendingCallTimer();
|
this._clearPendingCallTimer();
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
this.trigger("session:error", new Error(
|
this._handleServerError(err);
|
||||||
"Retrieval of session information failed: HTTP " + err));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +199,31 @@ loop.shared.models = (function() {
|
||||||
.once("session:ended", this.stopListening, this);
|
.once("session:ended", this.stopListening, this);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a loop-server error, which has an optional `errno` property which
|
||||||
|
* is server error identifier.
|
||||||
|
*
|
||||||
|
* Triggers the following events:
|
||||||
|
*
|
||||||
|
* - `session:expired` for expired call urls
|
||||||
|
* - `session:error` for other generic errors
|
||||||
|
*
|
||||||
|
* @param {Error} err Error object.
|
||||||
|
*/
|
||||||
|
_handleServerError: function(err) {
|
||||||
|
switch (err.errno) {
|
||||||
|
// loop-server sends 404 + INVALID_TOKEN (errno 105) whenever a token is
|
||||||
|
// missing OR expired; we treat this information as if the url is always
|
||||||
|
// expired.
|
||||||
|
case 105:
|
||||||
|
this.trigger("session:expired", err);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.trigger("session:error", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears current pending call timer, if any.
|
* Clears current pending call timer, if any.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
* 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/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/* global loop:true, hawk, deriveHawkCredentials */
|
/* global loop:true */
|
||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.StandaloneClient = (function($) {
|
loop.StandaloneClient = (function($) {
|
||||||
|
@ -46,7 +46,7 @@ loop.StandaloneClient = (function($) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (properties.length == 1) {
|
if (properties.length === 1) {
|
||||||
return data[properties[0]];
|
return data[properties[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,26 +62,17 @@ loop.StandaloneClient = (function($) {
|
||||||
* @param errorThrown See jQuery docs
|
* @param errorThrown See jQuery docs
|
||||||
*/
|
*/
|
||||||
_failureHandler: function(cb, jqXHR, textStatus, errorThrown) {
|
_failureHandler: function(cb, jqXHR, textStatus, errorThrown) {
|
||||||
var error = "Unknown error.",
|
var jsonErr = jqXHR && jqXHR.responseJSON || {};
|
||||||
jsonRes = jqXHR && jqXHR.responseJSON || {};
|
var message = "HTTP " + jqXHR.status + " " + errorThrown;
|
||||||
// Received error response format:
|
|
||||||
// { "status": "errors",
|
// Logging the technical error to the console
|
||||||
// "errors": [{
|
console.error("Server error", message, jsonErr);
|
||||||
// "location": "url",
|
|
||||||
// "name": "token",
|
// Create an error with server error `errno` code attached as a property
|
||||||
// "description": "invalid token"
|
var err = new Error(message);
|
||||||
// }]}
|
err.errno = jsonErr.errno;
|
||||||
if (jsonRes.status === "errors" && Array.isArray(jsonRes.errors)) {
|
|
||||||
error = "Details: " + jsonRes.errors.map(function(err) {
|
cb(err);
|
||||||
return Object.keys(err).map(function(field) {
|
|
||||||
return field + ": " + err[field];
|
|
||||||
}).join(", ");
|
|
||||||
}).join("; ");
|
|
||||||
}
|
|
||||||
var message = "HTTP " + jqXHR.status + " " + errorThrown +
|
|
||||||
"; " + error;
|
|
||||||
console.error(message);
|
|
||||||
cb(new Error(message));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
/** @jsx React.DOM */
|
||||||
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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,
|
* 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/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/* global loop:true */
|
/* global loop:true, React */
|
||||||
|
/* jshint newcap:false */
|
||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.webapp = (function($, _, OT) {
|
loop.webapp = (function($, _, OT, webL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
loop.config = loop.config || {};
|
loop.config = loop.config || {};
|
||||||
|
@ -13,7 +16,8 @@ loop.webapp = (function($, _, OT) {
|
||||||
|
|
||||||
var sharedModels = loop.shared.models,
|
var sharedModels = loop.shared.models,
|
||||||
sharedViews = loop.shared.views,
|
sharedViews = loop.shared.views,
|
||||||
baseServerUrl = loop.config.serverUrl;
|
baseServerUrl = loop.config.serverUrl,
|
||||||
|
__ = webL10n.get;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App router.
|
* App router.
|
||||||
|
@ -28,6 +32,20 @@ loop.webapp = (function($, _, OT) {
|
||||||
template: _.template('<p data-l10n-id="welcome"></p>')
|
template: _.template('<p data-l10n-id="welcome"></p>')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expired call URL view.
|
||||||
|
*/
|
||||||
|
var CallUrlExpiredView = React.createClass({displayName: 'CallUrlExpiredView',
|
||||||
|
render: function() {
|
||||||
|
/* jshint ignore:start */
|
||||||
|
return (
|
||||||
|
// XXX proper UX/design should be implemented here (see bug 1000131)
|
||||||
|
React.DOM.div(null, __("call_url_unavailable_notification"))
|
||||||
|
);
|
||||||
|
/* jshint ignore:end */
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conversation launcher view. A ConversationModel is associated and attached
|
* Conversation launcher view. A ConversationModel is associated and attached
|
||||||
* as a `model` property.
|
* as a `model` property.
|
||||||
|
@ -93,7 +111,7 @@ loop.webapp = (function($, _, OT) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.model.initiate({
|
this.model.initiate({
|
||||||
client: new loop.StandaloneClient({
|
client: new loop.StandaloneClient({
|
||||||
baseServerUrl: baseServerUrl,
|
baseServerUrl: baseServerUrl
|
||||||
}),
|
}),
|
||||||
outgoing: true,
|
outgoing: true,
|
||||||
// For now, we assume both audio and video as there is no
|
// For now, we assume both audio and video as there is no
|
||||||
|
@ -112,6 +130,7 @@ loop.webapp = (function($, _, OT) {
|
||||||
"": "home",
|
"": "home",
|
||||||
"unsupportedDevice": "unsupportedDevice",
|
"unsupportedDevice": "unsupportedDevice",
|
||||||
"unsupportedBrowser": "unsupportedBrowser",
|
"unsupportedBrowser": "unsupportedBrowser",
|
||||||
|
"call/expired": "expired",
|
||||||
"call/ongoing/:token": "loadConversation",
|
"call/ongoing/:token": "loadConversation",
|
||||||
"call/:token": "initiate"
|
"call/:token": "initiate"
|
||||||
},
|
},
|
||||||
|
@ -121,6 +140,12 @@ loop.webapp = (function($, _, OT) {
|
||||||
this.loadView(new HomeView());
|
this.loadView(new HomeView());
|
||||||
|
|
||||||
this.listenTo(this._conversation, "timeout", this._onTimeout);
|
this.listenTo(this._conversation, "timeout", this._onTimeout);
|
||||||
|
this.listenTo(this._conversation, "session:expired",
|
||||||
|
this._onSessionExpired);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSessionExpired: function() {
|
||||||
|
this.navigate("/call/expired", {trigger: true});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -167,6 +192,10 @@ loop.webapp = (function($, _, OT) {
|
||||||
this.loadView(new sharedViews.UnsupportedBrowserView());
|
this.loadView(new sharedViews.UnsupportedBrowserView());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
expired: function() {
|
||||||
|
this.loadReactComponent(CallUrlExpiredView());
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads conversation launcher view, setting the received conversation token
|
* Loads conversation launcher view, setting the received conversation token
|
||||||
* to the current conversation model. If a session is currently established,
|
* to the current conversation model. If a session is currently established,
|
||||||
|
@ -235,10 +264,11 @@ loop.webapp = (function($, _, OT) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseServerUrl: baseServerUrl,
|
baseServerUrl: baseServerUrl,
|
||||||
|
CallUrlExpiredView: CallUrlExpiredView,
|
||||||
ConversationFormView: ConversationFormView,
|
ConversationFormView: ConversationFormView,
|
||||||
HomeView: HomeView,
|
HomeView: HomeView,
|
||||||
WebappHelper: WebappHelper,
|
WebappHelper: WebappHelper,
|
||||||
init: init,
|
init: init,
|
||||||
WebappRouter: WebappRouter
|
WebappRouter: WebappRouter
|
||||||
};
|
};
|
||||||
})(jQuery, _, window.OT);
|
})(jQuery, _, window.OT, document.webL10n);
|
||||||
|
|
|
@ -0,0 +1,274 @@
|
||||||
|
/** @jsx React.DOM */
|
||||||
|
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/* global loop:true, React */
|
||||||
|
/* jshint newcap:false */
|
||||||
|
|
||||||
|
var loop = loop || {};
|
||||||
|
loop.webapp = (function($, _, OT, webL10n) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
loop.config = loop.config || {};
|
||||||
|
loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
|
||||||
|
|
||||||
|
var sharedModels = loop.shared.models,
|
||||||
|
sharedViews = loop.shared.views,
|
||||||
|
baseServerUrl = loop.config.serverUrl,
|
||||||
|
__ = webL10n.get;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App router.
|
||||||
|
* @type {loop.webapp.WebappRouter}
|
||||||
|
*/
|
||||||
|
var router;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Homepage view.
|
||||||
|
*/
|
||||||
|
var HomeView = sharedViews.BaseView.extend({
|
||||||
|
template: _.template('<p data-l10n-id="welcome"></p>')
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expired call URL view.
|
||||||
|
*/
|
||||||
|
var CallUrlExpiredView = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
/* jshint ignore:start */
|
||||||
|
return (
|
||||||
|
// XXX proper UX/design should be implemented here (see bug 1000131)
|
||||||
|
<div>{__("call_url_unavailable_notification")}</div>
|
||||||
|
);
|
||||||
|
/* jshint ignore:end */
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conversation launcher view. A ConversationModel is associated and attached
|
||||||
|
* as a `model` property.
|
||||||
|
*/
|
||||||
|
var ConversationFormView = sharedViews.BaseView.extend({
|
||||||
|
template: _.template([
|
||||||
|
'<form>',
|
||||||
|
' <p>',
|
||||||
|
' <button class="btn btn-success" data-l10n-id="start_call"></button>',
|
||||||
|
' </p>',
|
||||||
|
'</form>'
|
||||||
|
].join("")),
|
||||||
|
|
||||||
|
events: {
|
||||||
|
"submit": "initiate"
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* Required options:
|
||||||
|
* - {loop.shared.model.ConversationModel} model Conversation model.
|
||||||
|
* - {loop.shared.views.NotificationListView} notifier Notifier component.
|
||||||
|
*
|
||||||
|
* @param {Object} options Options object.
|
||||||
|
*/
|
||||||
|
initialize: function(options) {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if (!options.model) {
|
||||||
|
throw new Error("missing required model");
|
||||||
|
}
|
||||||
|
this.model = options.model;
|
||||||
|
|
||||||
|
if (!options.notifier) {
|
||||||
|
throw new Error("missing required notifier");
|
||||||
|
}
|
||||||
|
this.notifier = options.notifier;
|
||||||
|
|
||||||
|
this.listenTo(this.model, "session:error", this._onSessionError);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSessionError: function(error) {
|
||||||
|
console.error(error);
|
||||||
|
this.notifier.errorL10n("unable_retrieve_call_info");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables this form to prevent multiple submissions.
|
||||||
|
*
|
||||||
|
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=991126
|
||||||
|
*/
|
||||||
|
disableForm: function() {
|
||||||
|
this.$("button").attr("disabled", "disabled");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates the call.
|
||||||
|
*
|
||||||
|
* @param {SubmitEvent} event
|
||||||
|
*/
|
||||||
|
initiate: function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.model.initiate({
|
||||||
|
client: new loop.StandaloneClient({
|
||||||
|
baseServerUrl: baseServerUrl
|
||||||
|
}),
|
||||||
|
outgoing: true,
|
||||||
|
// For now, we assume both audio and video as there is no
|
||||||
|
// other option to select.
|
||||||
|
callType: "audio-video"
|
||||||
|
});
|
||||||
|
this.disableForm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webapp Router.
|
||||||
|
*/
|
||||||
|
var WebappRouter = loop.shared.router.BaseConversationRouter.extend({
|
||||||
|
routes: {
|
||||||
|
"": "home",
|
||||||
|
"unsupportedDevice": "unsupportedDevice",
|
||||||
|
"unsupportedBrowser": "unsupportedBrowser",
|
||||||
|
"call/expired": "expired",
|
||||||
|
"call/ongoing/:token": "loadConversation",
|
||||||
|
"call/:token": "initiate"
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
// Load default view
|
||||||
|
this.loadView(new HomeView());
|
||||||
|
|
||||||
|
this.listenTo(this._conversation, "timeout", this._onTimeout);
|
||||||
|
this.listenTo(this._conversation, "session:expired",
|
||||||
|
this._onSessionExpired);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSessionExpired: function() {
|
||||||
|
this.navigate("/call/expired", {trigger: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override {loop.shared.router.BaseConversationRouter.startCall}
|
||||||
|
*/
|
||||||
|
startCall: function() {
|
||||||
|
if (!this._conversation.get("loopToken")) {
|
||||||
|
this._notifier.errorL10n("missing_conversation_info");
|
||||||
|
this.navigate("home", {trigger: true});
|
||||||
|
} else {
|
||||||
|
this.navigate("call/ongoing/" + this._conversation.get("loopToken"), {
|
||||||
|
trigger: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override {loop.shared.router.BaseConversationRouter.endCall}
|
||||||
|
*/
|
||||||
|
endCall: function() {
|
||||||
|
var route = "home";
|
||||||
|
if (this._conversation.get("loopToken")) {
|
||||||
|
route = "call/" + this._conversation.get("loopToken");
|
||||||
|
}
|
||||||
|
this.navigate(route, {trigger: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onTimeout: function() {
|
||||||
|
this._notifier.errorL10n("call_timeout_notification_text");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default entry point.
|
||||||
|
*/
|
||||||
|
home: function() {
|
||||||
|
this.loadView(new HomeView());
|
||||||
|
},
|
||||||
|
|
||||||
|
unsupportedDevice: function() {
|
||||||
|
this.loadView(new sharedViews.UnsupportedDeviceView());
|
||||||
|
},
|
||||||
|
|
||||||
|
unsupportedBrowser: function() {
|
||||||
|
this.loadView(new sharedViews.UnsupportedBrowserView());
|
||||||
|
},
|
||||||
|
|
||||||
|
expired: function() {
|
||||||
|
this.loadReactComponent(CallUrlExpiredView());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads conversation launcher view, setting the received conversation token
|
||||||
|
* to the current conversation model. If a session is currently established,
|
||||||
|
* terminates it first.
|
||||||
|
*
|
||||||
|
* @param {String} loopToken Loop conversation token.
|
||||||
|
*/
|
||||||
|
initiate: function(loopToken) {
|
||||||
|
// Check if a session is ongoing; if so, terminate it
|
||||||
|
if (this._conversation.get("ongoing")) {
|
||||||
|
this._conversation.endSession();
|
||||||
|
}
|
||||||
|
this._conversation.set("loopToken", loopToken);
|
||||||
|
this.loadView(new ConversationFormView({
|
||||||
|
model: this._conversation,
|
||||||
|
notifier: this._notifier
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads conversation establishment view.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
loadConversation: function(loopToken) {
|
||||||
|
if (!this._conversation.isSessionReady()) {
|
||||||
|
// User has loaded this url directly, actually setup the call.
|
||||||
|
return this.navigate("call/" + loopToken, {trigger: true});
|
||||||
|
}
|
||||||
|
this.loadReactComponent(sharedViews.ConversationView({
|
||||||
|
sdk: OT,
|
||||||
|
model: this._conversation
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local helpers.
|
||||||
|
*/
|
||||||
|
function WebappHelper() {
|
||||||
|
this._iOSRegex = /^(iPad|iPhone|iPod)/;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebappHelper.prototype.isIOS = function isIOS(platform) {
|
||||||
|
return this._iOSRegex.test(platform);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App initialization.
|
||||||
|
*/
|
||||||
|
function init() {
|
||||||
|
var helper = new WebappHelper();
|
||||||
|
router = new WebappRouter({
|
||||||
|
notifier: new sharedViews.NotificationListView({el: "#messages"}),
|
||||||
|
conversation: new sharedModels.ConversationModel({}, {
|
||||||
|
sdk: OT,
|
||||||
|
pendingCallTimeout: loop.config.pendingCallTimeout
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Backbone.history.start();
|
||||||
|
if (helper.isIOS(navigator.platform)) {
|
||||||
|
router.navigate("unsupportedDevice", {trigger: true});
|
||||||
|
} else if (!OT.checkSystemRequirements()) {
|
||||||
|
router.navigate("unsupportedBrowser", {trigger: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseServerUrl: baseServerUrl,
|
||||||
|
CallUrlExpiredView: CallUrlExpiredView,
|
||||||
|
ConversationFormView: ConversationFormView,
|
||||||
|
HomeView: HomeView,
|
||||||
|
WebappHelper: WebappHelper,
|
||||||
|
init: init,
|
||||||
|
WebappRouter: WebappRouter
|
||||||
|
};
|
||||||
|
})(jQuery, _, window.OT, document.webL10n);
|
|
@ -19,6 +19,7 @@ incompatible_device=Incompatible device
|
||||||
sorry_device_unsupported=Sorry, Loop does not currently support your device.
|
sorry_device_unsupported=Sorry, Loop does not currently support your device.
|
||||||
use_firefox_windows_mac_linux=Please open this page using the latest Firefox on Windows, Android, Mac or Linux.
|
use_firefox_windows_mac_linux=Please open this page using the latest Firefox on Windows, Android, Mac or Linux.
|
||||||
connection_error_see_console_notification=Call failed; see console for details.
|
connection_error_see_console_notification=Call failed; see console for details.
|
||||||
|
call_url_unavailable_notification=This URL is unavailable.
|
||||||
|
|
||||||
[fr]
|
[fr]
|
||||||
call_has_ended=L'appel est terminé.
|
call_has_ended=L'appel est terminé.
|
||||||
|
@ -40,3 +41,4 @@ use_latest_firefox.innerHTML=Pour utiliser Loop, merci d'utiliser la dernière v
|
||||||
incompatible_device=Plateforme non supportée
|
incompatible_device=Plateforme non supportée
|
||||||
sorry_device_unsupported=Désolé, Loop ne fonctionne actuellement pas sur votre appareil.
|
sorry_device_unsupported=Désolé, Loop ne fonctionne actuellement pas sur votre appareil.
|
||||||
use_firefox_windows_mac_linux=Merci d'ouvrir cette page avec une version récente de Firefox pour Windows, Android, Mac ou Linux.
|
use_firefox_windows_mac_linux=Merci d'ouvrir cette page avec une version récente de Firefox pour Windows, Android, Mac ou Linux.
|
||||||
|
call_url_unavailable_notification=Cette URL n'est pas disponible.
|
||||||
|
|
|
@ -142,18 +142,42 @@ describe("loop.shared.models", function() {
|
||||||
sinon.assert.calledWith(conversation.setReady, fakeSessionData);
|
sinon.assert.calledWith(conversation.setReady, fakeSessionData);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should trigger a `session:error` on failure", function(done) {
|
it("should trigger a `session:error` event errno is undefined",
|
||||||
requestCallInfoStub.callsArgWith(2,
|
function(done) {
|
||||||
new Error("failed: HTTP 400 Bad Request; fake"));
|
var errMsg = "HTTP 500 Server Error; fake";
|
||||||
|
var err = new Error(errMsg);
|
||||||
|
requestCallInfoStub.callsArgWith(2, err);
|
||||||
|
|
||||||
conversation.on("session:error", function(err) {
|
conversation.on("session:error", function(err) {
|
||||||
expect(err.message).to.match(/failed: HTTP 400 Bad Request; fake/);
|
expect(err.message).eql(errMsg);
|
||||||
done();
|
done();
|
||||||
}).initiate({
|
}).initiate({ client: fakeClient, outgoing: true });
|
||||||
client: fakeClient,
|
});
|
||||||
outgoing: true
|
|
||||||
|
it("should trigger a `session:error` event when errno is not 105",
|
||||||
|
function(done) {
|
||||||
|
var errMsg = "HTTP 400 Bad Request; fake";
|
||||||
|
var err = new Error(errMsg);
|
||||||
|
err.errno = 101;
|
||||||
|
requestCallInfoStub.callsArgWith(2, err);
|
||||||
|
|
||||||
|
conversation.on("session:error", function(err) {
|
||||||
|
expect(err.message).eql(errMsg);
|
||||||
|
done();
|
||||||
|
}).initiate({ client: fakeClient, outgoing: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trigger a `session:expired` event when errno is 105",
|
||||||
|
function(done) {
|
||||||
|
var err = new Error("HTTP 404 Not Found; fake");
|
||||||
|
err.errno = 105;
|
||||||
|
requestCallInfoStub.callsArgWith(2, err);
|
||||||
|
|
||||||
|
conversation.on("session:expired", function(err2) {
|
||||||
|
expect(err2).eql(err);
|
||||||
|
done();
|
||||||
|
}).initiate({ client: fakeClient, outgoing: true });
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it("should end the session on outgoing call timeout", function() {
|
it("should end the session on outgoing call timeout", function() {
|
||||||
requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
|
requestCallInfoStub.callsArgWith(2, null, fakeSessionData);
|
||||||
|
|
|
@ -15,15 +15,6 @@ describe("loop.StandaloneClient", function() {
|
||||||
callback,
|
callback,
|
||||||
fakeToken;
|
fakeToken;
|
||||||
|
|
||||||
var fakeErrorRes = JSON.stringify({
|
|
||||||
status: "errors",
|
|
||||||
errors: [{
|
|
||||||
location: "url",
|
|
||||||
name: "token",
|
|
||||||
description: "invalid token"
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = sinon.sandbox.create();
|
sandbox = sinon.sandbox.create();
|
||||||
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
||||||
|
@ -50,12 +41,19 @@ describe("loop.StandaloneClient", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("requestCallInfo", function() {
|
describe("requestCallInfo", function() {
|
||||||
var client;
|
var client, fakeServerErrorDescription;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
client = new loop.StandaloneClient(
|
client = new loop.StandaloneClient(
|
||||||
{baseServerUrl: "http://fake.api"}
|
{baseServerUrl: "http://fake.api"}
|
||||||
);
|
);
|
||||||
|
fakeServerErrorDescription = {
|
||||||
|
code: 401,
|
||||||
|
errno: 101,
|
||||||
|
error: "error",
|
||||||
|
message: "invalid token",
|
||||||
|
info: "error info"
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should prevent launching a conversation when token is missing",
|
it("should prevent launching a conversation when token is missing",
|
||||||
|
@ -91,13 +89,26 @@ describe("loop.StandaloneClient", function() {
|
||||||
it("should send an error when the request fails", function() {
|
it("should send an error when the request fails", function() {
|
||||||
client.requestCallInfo("fake", "audio", callback);
|
client.requestCallInfo("fake", "audio", callback);
|
||||||
|
|
||||||
requests[0].respond(400, {"Content-Type": "application/json"},
|
requests[0].respond(401, {"Content-Type": "application/json"},
|
||||||
fakeErrorRes);
|
JSON.stringify(fakeServerErrorDescription));
|
||||||
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
|
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
|
||||||
return /400.*invalid token/.test(err.message);
|
return /HTTP 401 Unauthorized/.test(err.message);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should attach the server error description object to the error " +
|
||||||
|
"passed to the callback",
|
||||||
|
function() {
|
||||||
|
client.requestCallInfo("fake", "audio", callback);
|
||||||
|
|
||||||
|
requests[0].respond(401, {"Content-Type": "application/json"},
|
||||||
|
JSON.stringify(fakeServerErrorDescription));
|
||||||
|
|
||||||
|
sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
|
||||||
|
return err.errno === fakeServerErrorDescription.errno;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
it("should send an error if the data is not valid", function() {
|
it("should send an error if the data is not valid", function() {
|
||||||
client.requestCallInfo("fake", "audio", callback);
|
client.requestCallInfo("fake", "audio", callback);
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,19 @@ describe("loop.webapp", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#expired", function() {
|
||||||
|
it("should load the CallUrlExpiredView view", function() {
|
||||||
|
router.expired();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(router.loadReactComponent);
|
||||||
|
sinon.assert.calledWith(router.loadReactComponent,
|
||||||
|
sinon.match(function(value) {
|
||||||
|
return React.addons.TestUtils.isComponentOfType(
|
||||||
|
value, loop.webapp.CallUrlExpiredView);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("#initiate", function() {
|
describe("#initiate", function() {
|
||||||
it("should set the token on the conversation model", function() {
|
it("should set the token on the conversation model", function() {
|
||||||
router.initiate("fakeToken");
|
router.initiate("fakeToken");
|
||||||
|
@ -251,6 +264,14 @@ describe("loop.webapp", function() {
|
||||||
sinon.assert.calledOnce(router.navigate);
|
sinon.assert.calledOnce(router.navigate);
|
||||||
sinon.assert.calledWithMatch(router.navigate, "call/fakeToken");
|
sinon.assert.calledWithMatch(router.navigate, "call/fakeToken");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should navigate to call/expired when a session:expired event is " +
|
||||||
|
"received", function() {
|
||||||
|
conversation.trigger("session:expired");
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(router.navigate);
|
||||||
|
sinon.assert.calledWith(router.navigate, "/call/expired");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче