/* 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 */ var loop = loop || {}; loop.shared = loop.shared || {}; loop.shared.models = (function(l10n) { "use strict"; /** * Conversation model. */ 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 sessionId: undefined, // OT session id sessionToken: undefined, // OT session token sessionType: undefined, // Hawk session type apiKey: undefined, // OT api key callId: undefined, // The callId on the server progressURL: undefined, // The websocket url to use for progress websocketToken: undefined, // The token to use for websocket auth, this is // stored as a hex string which is what the server // requires. callType: undefined, // The type of incoming call selected by // other peer ("audio" or "audio-video") selectedCallType: "audio-video", // The selected type for the call that was // initiated ("audio" or "audio-video") callToken: undefined, // Incoming call token. // Used for blocking a call url subscribedStream: false, // Used to indicate that a stream has been // subscribed to publishedStream: false // Used to indicate that a stream has been // published }, /** * SDK object. * @type {OT} */ sdk: undefined, /** * SDK session object. * @type {XXX} */ session: undefined, /** * Constructor. * * Options: * * Required: * - {OT} sdk: OT SDK object. * * @param {Object} attributes Attributes object. * @param {Object} options Options object. */ initialize: function(attributes, options) { options = options || {}; if (!options.sdk) { throw new Error("missing required sdk"); } this.sdk = options.sdk; // Set loop.debug.sdk to true in the browser, or standalone: // localStorage.setItem("debug.sdk", true); if (loop.shared.utils.getBoolPreference("debug.sdk")) { this.sdk.setLogLevel(this.sdk.DEBUG); } }, /** * Indicates an incoming conversation has been accepted. */ accepted: function() { this.trigger("call:accepted"); }, /** * Used to indicate that an outgoing call should start any necessary * set-up. * * @param {String} selectedCallType Call type ("audio" or "audio-video") */ setupOutgoingCall: function(selectedCallType) { if (selectedCallType) { this.set("selectedCallType", selectedCallType); } this.trigger("call:outgoing:setup"); }, /** * Starts an outgoing conversation. * * @param {Object} sessionData The session data received from the * server for the outgoing call. */ outgoing: function(sessionData) { this.setOutgoingSessionData(sessionData); this.trigger("call:outgoing"); }, /** * Checks that the session is ready. * * @return {Boolean} */ isSessionReady: function() { return !!this.get("sessionId"); }, /** * Sets session information. * Session data received by creating an outgoing call. * * @param {Object} sessionData Conversation session information. */ setOutgoingSessionData: function(sessionData) { // Explicit property assignment to prevent later "surprises" this.set({ sessionId: sessionData.sessionId, sessionToken: sessionData.sessionToken, apiKey: sessionData.apiKey, callId: sessionData.callId, progressURL: sessionData.progressURL, websocketToken: sessionData.websocketToken.toString(16) }); }, /** * Sets session information about the incoming call. * * @param {Object} sessionData Conversation session information. */ setIncomingSessionData: function(sessionData) { // Explicit property assignment to prevent later "surprises" this.set({ sessionId: sessionData.sessionId, sessionToken: sessionData.sessionToken, sessionType: sessionData.sessionType, apiKey: sessionData.apiKey, callId: sessionData.callId, progressURL: sessionData.progressURL, websocketToken: sessionData.websocketToken.toString(16), callType: sessionData.callType || "audio-video", callToken: sessionData.callToken }); }, /** * Starts a SDK session and subscribe to call events. */ startSession: function() { if (!this.isSessionReady()) { throw new Error("Can't start session as it's not ready"); } this.session = this.sdk.initSession(this.get("sessionId")); this.listenTo(this.session, "streamCreated", this._streamCreated); this.listenTo(this.session, "connectionDestroyed", this._connectionDestroyed); this.listenTo(this.session, "sessionDisconnected", this._sessionDisconnected); this.listenTo(this.session, "networkDisconnected", this._networkDisconnected); this.session.connect(this.get("apiKey"), this.get("sessionToken"), this._onConnectCompletion.bind(this)); }, /** * Ends current session. */ endSession: function() { this.session.disconnect(); this.set("ongoing", false) .once("session:ended", this.stopListening, this); }, /** * Helper function to determine if video stream is available for the * incoming or outgoing call * * @param {string} callType Incoming or outgoing call */ hasVideoStream: function(callType) { if (callType === "incoming") { return this.get("callType") === "audio-video"; } if (callType === "outgoing") { return this.get("selectedCallType") === "audio-video"; } return undefined; }, /** * Publishes a local stream. * * @param {Publisher} publisher The publisher object to publish * to the session. */ publish: function(publisher) { this.session.publish(publisher); this.set("publishedStream", true); }, /** * Subscribes to a remote stream. * * @param {Stream} stream The remote stream to subscribe to. * @param {DOMElement} element The element to display the stream in. * @param {Object} config The display properties to set on the stream as * documented in: * https://tokbox.com/opentok/libraries/client/js/reference/Session.html#subscribe */ subscribe: function(stream, element, config) { this.session.subscribe(stream, element, config); this.set("subscribedStream", true); }, /** * Returns true if a stream has been published and a stream has been * subscribed to. */ streamsConnected: function() { return this.get("publishedStream") && this.get("subscribedStream"); }, /** * 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; } }, /** * Manages connection status * triggers apropriate event for connection error/success * http://tokbox.com/opentok/tutorials/connect-session/js/ * http://tokbox.com/opentok/tutorials/hello-world/js/ * http://tokbox.com/opentok/libraries/client/js/reference/SessionConnectEvent.html * * @param {error|null} error */ _onConnectCompletion: function(error) { if (error) { this.trigger("session:connection-error", error); this.endSession(); } else { this.trigger("session:connected"); this.set("connected", true); } }, /** * New created streams are available. * http://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html * * @param {StreamEvent} event */ _streamCreated: function(event) { this.set("ongoing", true) .trigger("session:stream-created", event); }, /** * Local user hung up. * http://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html * * @param {SessionDisconnectEvent} event */ _sessionDisconnected: function(event) { this.set("connected", false) .set("ongoing", false) .trigger("session:ended"); }, /** * Peer hung up. Disconnects local session. * http://tokbox.com/opentok/libraries/client/js/reference/ConnectionEvent.html * * @param {ConnectionEvent} event */ _connectionDestroyed: function(event) { this.set("connected", false) .set("ongoing", false) .trigger("session:peer-hungup", { connectionId: event.connection.connectionId }); this.endSession(); }, /** * Network was disconnected. * http://tokbox.com/opentok/libraries/client/js/reference/ConnectionEvent.html * * @param {ConnectionEvent} event */ _networkDisconnected: function(event) { this.set("connected", false) .set("ongoing", false) .trigger("session:network-disconnected"); this.endSession(); }, }); /** * Notification model. */ var NotificationModel = Backbone.Model.extend({ defaults: { details: "", detailsButtonLabel: "", level: "info", message: "" } }); /** * Notification collection */ var NotificationCollection = Backbone.Collection.extend({ model: NotificationModel, /** * Adds a warning notification to the stack and renders it. * * @return {String} message */ warn: function(message) { this.add({level: "warning", message: message}); }, /** * Adds a l10n warning notification to the stack and renders it. * * @param {String} messageId L10n message id */ warnL10n: function(messageId) { this.warn(l10n.get(messageId)); }, /** * Adds an error notification to the stack and renders it. * * @return {String} message */ error: function(message) { this.add({level: "error", message: message}); }, /** * Adds a l10n error notification to the stack and renders it. * * @param {String} messageId L10n message id * @param {Object} [l10nProps] An object with variables to be interpolated * into the translation. All members' values must be * strings or numbers. */ errorL10n: function(messageId, l10nProps) { this.error(l10n.get(messageId, l10nProps)); } }); return { ConversationModel: ConversationModel, NotificationCollection: NotificationCollection, NotificationModel: NotificationModel }; })(navigator.mozL10n || document.mozL10n);