New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

opentok-accelerator-core

Package Overview
Dependencies
Maintainers
2
Versions
83
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

opentok-accelerator-core - npm Package Compare versions

Comparing version 1.0.29 to 2.0.0

4

.eslintrc.json

@@ -8,6 +8,8 @@ {

"extends": "airbnb",
"parser": "babel-eslint",
"rules": {
"no-confusing-arrow": 1,
"no-unused-expressions": 1,
"no-prototype-builtins": 1
"no-prototype-builtins": 1,
"max-len": ["error", 120]
},

@@ -14,0 +16,0 @@ "globals": {

@@ -185,3 +185,3 @@ # OpenTok Accelerator Core API Methods

**Returns**: `Promise => <resolve: empty reject: Error >`
**Returns**: `Promise => <resolve: Subscriber, reject: Error >`

@@ -188,0 +188,0 @@

@@ -5,3 +5,16 @@ # Accelerator Core Javascript CHANGELOG

--------------------------------------
#### [2.0.0]
`Core` is now a constructor, meaning that multiple instances may be used simultaneously within a single application. Previously, `core` was initialized in the following manner:
```
const otCore = require('opentok-accelerator-core');
otCore.init(options);
```
In `2.0`, this becomes:
```
const Core = require('opentok-accelerator-core');
const otCore = new Core(options);
```
#### [1.0.0]

@@ -8,0 +21,0 @@

'use strict';
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
Object.defineProperty(exports, "__esModule", {
value: true
});
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/* global OT */
/** Dependencies */
var state = require('./state');
var _require = require('./errors'),

@@ -21,18 +23,5 @@ CoreError = _require.CoreError;

message = _require3.message,
logAnalytics = _require3.logAnalytics,
logAction = _require3.logAction,
logVariation = _require3.logVariation;
/** Module variables */
var session = void 0;
var accPack = void 0;
var callProperties = void 0;
var screenProperties = void 0;
var streamContainers = void 0;
var autoSubscribe = void 0;
var connectionLimit = void 0;
var active = false;
/**

@@ -42,2 +31,4 @@ * Default UI propties

*/
var defaultCallProperties = {

@@ -53,2 +44,11 @@ insertMode: 'append',

var Communication = function Communication(options) {
_classCallCheck(this, Communication);
_initialiseProps.call(this);
this.validateOptions(options);
this.setSession();
this.createEventListeners();
}
/**

@@ -59,6 +59,4 @@ * Trigger an event through the API layer

*/
var triggerEvent = function triggerEvent(event, data) {
return accPack.triggerEvent(event, data);
};
/**

@@ -69,13 +67,4 @@ * Determine whether or not the party is able to join the call based on

*/
var ableToJoin = function ableToJoin() {
if (!connectionLimit) {
return true;
}
// Not using the session here since we're concerned with number of active publishers
var connections = Object.values(state.getStreams()).filter(function (s) {
return s.videoType === 'camera';
});
return connections.length < connectionLimit;
};
/**

@@ -86,15 +75,4 @@ * Create a camera publisher object

*/
var createPublisher = function createPublisher(publisherProperties) {
return new Promise(function (resolve, reject) {
// TODO: Handle adding 'name' option to props
var props = Object.assign({}, callProperties, publisherProperties);
// TODO: Figure out how to handle common vs package-specific options
// ^^^ This may already be available through package options
var container = dom.element(streamContainers('publisher', 'camera'));
var publisher = OT.initPublisher(container, props, function (error) {
error ? reject(error) : resolve(publisher);
});
});
};
/**

@@ -105,74 +83,10 @@ * Publish the local camera stream and update state

*/
var publish = function publish(publisherProperties) {
return new Promise(function (resolve, reject) {
var onPublish = function onPublish(publisher) {
return function (error) {
if (error) {
reject(error);
logAnalytics(logAction.startCall, logVariation.fail);
} else {
logAnalytics(logAction.startCall, logVariation.success);
state.addPublisher('camera', publisher);
resolve(publisher);
}
};
};
var publishToSession = function publishToSession(publisher) {
return session.publish(publisher, onPublish(publisher));
};
var handleError = function handleError(error) {
logAnalytics(logAction.startCall, logVariation.fail);
var errorMessage = error.code === 1010 ? 'Check your network connection' : error.message;
triggerEvent('error', errorMessage);
reject(error);
};
createPublisher(publisherProperties).then(publishToSession).catch(handleError);
});
};
/**
* Subscribe to a stream and update the state
* @param {Object} stream - An OpenTok stream object
* @returns {Promise} <resolve: empty reject: Error >
* @returns {Promise} <resolve: Object, reject: Error >
*/
var subscribe = function subscribe(stream) {
return new Promise(function (resolve, reject) {
var connectionData = void 0;
logAnalytics(logAction.subscribe, logVariation.attempt);
var streamMap = state.getStreamMap();
var streamId = stream.streamId;
if (streamMap[streamId]) {
// Are we already subscribing to the stream?
resolve();
} else {
(function () {
// No videoType indicates SIP https://tokbox.com/developer/guides/sip/
var type = pathOr('sip', 'videoType', stream);
try {
connectionData = JSON.parse(path(['connection', 'data'], stream) || null);
} catch (e) {
connectionData = path(['connection', 'data'], stream);
}
var container = dom.query(streamContainers('subscriber', type, connectionData, streamId));
var options = type === 'camera' || type === 'sip' ? callProperties : screenProperties;
var subscriber = session.subscribe(stream, container, options, function (error) {
if (error) {
logAnalytics(logAction.subscribe, logVariation.fail);
reject(error);
} else {
state.addSubscriber(subscriber);
triggerEvent('subscribeTo' + properCase(type), Object.assign({}, { subscriber: subscriber }, state.all()));
type === 'screen' && triggerEvent('startViewingSharedScreen', subscriber); // Legacy event
logAnalytics(logAction.subscribe, logVariation.success);
resolve();
}
});
})();
}
});
};

@@ -184,40 +98,9 @@ /**

*/
var unsubscribe = function unsubscribe(subscriber) {
return new Promise(function (resolve) {
logAnalytics(logAction.unsubscribe, logVariation.attempt);
var type = path('stream.videoType', subscriber);
state.removeSubscriber(type, subscriber);
session.unsubscribe(subscriber);
logAnalytics(logAction.unsubscribe, logVariation.success);
resolve();
});
};
/**
* Ensure all required options are received
* @param {Object} options
*/
var validateOptions = function validateOptions(options) {
var requiredOptions = ['accPack'];
requiredOptions.forEach(function (option) {
if (!options[option]) {
throw new CoreError(option + ' is a required option.', 'invalidParameters');
}
});
accPack = options.accPack;
streamContainers = options.streamContainers;
callProperties = Object.assign({}, defaultCallProperties, options.callProperties);
connectionLimit = options.connectionLimit || null;
autoSubscribe = options.hasOwnProperty('autoSubscribe') ? options.autoSubscribe : true;
screenProperties = Object.assign({}, defaultCallProperties, { videoSource: 'window' }, options.screenProperties);
};
/**
* Set session in module scope
*/
var setSession = function setSession() {
session = state.getSession();
};
/**

@@ -227,7 +110,4 @@ * Subscribe to new stream unless autoSubscribe is set to false

*/
var onStreamCreated = function onStreamCreated(_ref) {
var stream = _ref.stream;
return active && autoSubscribe && subscribe(stream);
};
/**

@@ -237,10 +117,3 @@ * Update state and trigger corresponding event(s) when stream is destroyed

*/
var onStreamDestroyed = function onStreamDestroyed(_ref2) {
var stream = _ref2.stream;
state.removeStream(stream);
var type = pathOr('sip', 'videoType', stream);
type === 'screen' && triggerEvent('endViewingSharedScreen'); // Legacy event
triggerEvent('unsubscribeFrom' + properCase(type), state.getPubSub());
};

@@ -250,7 +123,4 @@ /**

*/
var createEventListeners = function createEventListeners() {
accPack.on('streamCreated', onStreamCreated);
accPack.on('streamDestroyed', onStreamDestroyed);
};
/**

@@ -261,84 +131,9 @@ * Start publishing the local camera feed and subscribing to streams in the session

*/
var startCall = function startCall(publisherProperties) {
return new Promise(function (resolve, reject) {
// eslint-disable-line consistent-return
logAnalytics(logAction.startCall, logVariation.attempt);
/**
* Determine if we're able to join the session based on an existing connection limit
*/
if (!ableToJoin()) {
var errorMessage = 'Session has reached its connection limit';
triggerEvent('error', errorMessage);
logAnalytics(logAction.startCall, logVariation.fail);
return reject(new CoreError(errorMessage, 'connectionLimit'));
}
/**
* Subscribe to any streams that existed before we start the call from our side.
*/
var subscribeToInitialStreams = function subscribeToInitialStreams(publisher) {
// Get an array of initial subscription promises
var initialSubscriptions = function initialSubscriptions() {
if (autoSubscribe) {
var _ret2 = function () {
var streams = state.getStreams();
return {
v: Object.keys(streams).map(function (id) {
return subscribe(streams[id]);
})
};
}();
if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v;
}
return [Promise.resolve()];
};
// Handle success
var onSubscribeToAll = function onSubscribeToAll() {
var pubSubData = Object.assign({}, state.getPubSub(), { publisher: publisher });
triggerEvent('startCall', pubSubData);
active = true;
resolve(pubSubData);
};
// Handle error
var onError = function onError(reason) {
message('Failed to subscribe to all existing streams: ' + reason);
// We do not reject here in case we still successfully publish to the session
resolve(Object.assign({}, state.getPubSub(), { publisher: publisher }));
};
Promise.all(initialSubscriptions()).then(onSubscribeToAll).catch(onError);
};
publish(publisherProperties).then(subscribeToInitialStreams).catch(reject);
});
};
/**
* Stop publishing and unsubscribe from all streams
*/
var endCall = function endCall() {
logAnalytics(logAction.endCall, logVariation.attempt);
var _state$getPubSub = state.getPubSub(),
publishers = _state$getPubSub.publishers,
subscribers = _state$getPubSub.subscribers;
var unpublish = function unpublish(publisher) {
return session.unpublish(publisher);
};
Object.values(publishers.camera).forEach(unpublish);
Object.values(publishers.screen).forEach(unpublish);
// TODO Promise.all for unsubsribing
Object.values(subscribers.camera).forEach(unsubscribe);
Object.values(subscribers.screen).forEach(unsubscribe);
state.removeAllPublishers();
active = false;
triggerEvent('endCall');
logAnalytics(logAction.endCall, logVariation.success);
};
/**

@@ -349,11 +144,4 @@ * Enable/disable local audio or video

*/
var enableLocalAV = function enableLocalAV(id, source, enable) {
var method = 'publish' + properCase(source);
var _state$getPubSub2 = state.getPubSub(),
publishers = _state$getPubSub2.publishers;
publishers.camera[id][method](enable);
};
/**

@@ -365,37 +153,343 @@ * Enable/disable remote audio or video

*/
var enableRemoteAV = function enableRemoteAV(subscriberId, source, enable) {
var method = 'subscribeTo' + properCase(source);
;
var _state$getPubSub3 = state.getPubSub(),
subscribers = _state$getPubSub3.subscribers;
var _initialiseProps = function _initialiseProps() {
var _this = this;
var subscriber = subscribers.camera[subscriberId] || subscribers.sip[subscriberId];
subscriber[method](enable);
};
Object.defineProperty(this, 'validateOptions', {
enumerable: true,
writable: true,
value: function value(options) {
var requiredOptions = ['core', 'state', 'analytics'];
requiredOptions.forEach(function (option) {
if (!options[option]) {
throw new CoreError(option + ' is a required option.', 'invalidParameters');
}
});
var callProperties = options.callProperties,
screenProperties = options.screenProperties,
autoSubscribe = options.autoSubscribe;
/**
* Initialize the communication component
* @param {Object} options
* @param {Object} options.accPack
* @param {Number} options.connectionLimit
* @param {Function} options.streamContainer
*/
var init = function init(options) {
return new Promise(function (resolve) {
validateOptions(options);
setSession();
createEventListeners();
resolve();
_this.active = false;
_this.core = options.core;
_this.state = options.state;
_this.analytics = options.analytics;
_this.streamContainers = options.streamContainers;
_this.callProperties = Object.assign({}, defaultCallProperties, callProperties);
_this.connectionLimit = options.connectionLimit || null;
_this.autoSubscribe = options.hasOwnProperty('autoSubscribe') ? autoSubscribe : true;
_this.screenProperties = Object.assign({}, defaultCallProperties, { videoSource: 'window' }, screenProperties);
}
});
Object.defineProperty(this, 'triggerEvent', {
enumerable: true,
writable: true,
value: function value(event, data) {
return _this.core.triggerEvent(event, data);
}
});
Object.defineProperty(this, 'ableToJoin', {
enumerable: true,
writable: true,
value: function value() {
var connectionLimit = _this.connectionLimit,
state = _this.state;
if (!connectionLimit) {
return true;
}
// Not using the session here since we're concerned with number of active publishers
var connections = Object.values(state.getStreams()).filter(function (s) {
return s.videoType === 'camera';
});
return connections.length < connectionLimit;
}
});
Object.defineProperty(this, 'createPublisher', {
enumerable: true,
writable: true,
value: function value(publisherProperties) {
var callProperties = _this.callProperties,
streamContainers = _this.streamContainers;
return new Promise(function (resolve, reject) {
// TODO: Handle adding 'name' option to props
var props = Object.assign({}, callProperties, publisherProperties);
// TODO: Figure out how to handle common vs package-specific options
// ^^^ This may already be available through package options
var container = dom.element(streamContainers('publisher', 'camera'));
var publisher = OT.initPublisher(container, props, function (error) {
error ? reject(error) : resolve(publisher);
});
});
}
});
Object.defineProperty(this, 'publish', {
enumerable: true,
writable: true,
value: function value(publisherProperties) {
var analytics = _this.analytics,
state = _this.state,
createPublisher = _this.createPublisher,
session = _this.session,
triggerEvent = _this.triggerEvent;
return new Promise(function (resolve, reject) {
var onPublish = function onPublish(publisher) {
return function (error) {
if (error) {
reject(error);
analytics.log(logAction.startCall, logVariation.fail);
} else {
analytics.log(logAction.startCall, logVariation.success);
state.addPublisher('camera', publisher);
resolve(publisher);
}
};
};
var publishToSession = function publishToSession(publisher) {
return session.publish(publisher, onPublish(publisher));
};
var handleError = function handleError(error) {
analytics.log(logAction.startCall, logVariation.fail);
var errorMessage = error.code === 1010 ? 'Check your network connection' : error.message;
triggerEvent('error', errorMessage);
reject(error);
};
createPublisher(publisherProperties).then(publishToSession).catch(handleError);
});
}
});
Object.defineProperty(this, 'subscribe', {
enumerable: true,
writable: true,
value: function value(stream) {
var analytics = _this.analytics,
state = _this.state,
streamContainers = _this.streamContainers,
session = _this.session,
triggerEvent = _this.triggerEvent,
callProperties = _this.callProperties,
screenProperties = _this.screenProperties;
return new Promise(function (resolve, reject) {
var connectionData = void 0;
analytics.log(logAction.subscribe, logVariation.attempt);
var streamMap = state.getStreamMap();
var streamId = stream.streamId;
// No videoType indicates SIP https://tokbox.com/developer/guides/sip/
var type = pathOr('sip', 'videoType', stream);
if (streamMap[streamId]) {
// Are we already subscribing to the stream?
var _state$all = state.all(),
subscribers = _state$all.subscribers;
resolve(subscribers[type][streamMap[streamId]]);
} else {
try {
connectionData = JSON.parse(path(['connection', 'data'], stream) || null);
} catch (e) {
connectionData = path(['connection', 'data'], stream);
}
var container = dom.query(streamContainers('subscriber', type, connectionData, stream));
var options = type === 'camera' || type === 'sip' ? callProperties : screenProperties;
var subscriber = session.subscribe(stream, container, options, function (error) {
if (error) {
analytics.log(logAction.subscribe, logVariation.fail);
reject(error);
} else {
state.addSubscriber(subscriber);
triggerEvent('subscribeTo' + properCase(type), Object.assign({}, { subscriber: subscriber }, state.all()));
type === 'screen' && triggerEvent('startViewingSharedScreen', subscriber); // Legacy event
analytics.log(logAction.subscribe, logVariation.success);
resolve(subscriber);
}
});
}
});
}
});
Object.defineProperty(this, 'unsubscribe', {
enumerable: true,
writable: true,
value: function value(subscriber) {
var analytics = _this.analytics,
session = _this.session,
state = _this.state;
return new Promise(function (resolve) {
analytics.log(logAction.unsubscribe, logVariation.attempt);
var type = path('stream.videoType', subscriber);
state.removeSubscriber(type, subscriber);
session.unsubscribe(subscriber);
analytics.log(logAction.unsubscribe, logVariation.success);
resolve();
});
}
});
Object.defineProperty(this, 'setSession', {
enumerable: true,
writable: true,
value: function value() {
_this.session = _this.state.getSession();
}
});
Object.defineProperty(this, 'onStreamCreated', {
enumerable: true,
writable: true,
value: function value(_ref) {
var stream = _ref.stream;
return _this.active && _this.autoSubscribe && _this.subscribe(stream);
}
});
Object.defineProperty(this, 'onStreamDestroyed', {
enumerable: true,
writable: true,
value: function value(_ref2) {
var stream = _ref2.stream;
var state = _this.state,
triggerEvent = _this.triggerEvent;
state.removeStream(stream);
var type = pathOr('sip', 'videoType', stream);
type === 'screen' && triggerEvent('endViewingSharedScreen'); // Legacy event
triggerEvent('unsubscribeFrom' + properCase(type), state.getPubSub());
}
});
Object.defineProperty(this, 'createEventListeners', {
enumerable: true,
writable: true,
value: function value() {
var core = _this.core,
onStreamCreated = _this.onStreamCreated,
onStreamDestroyed = _this.onStreamDestroyed;
core.on('streamCreated', onStreamCreated);
core.on('streamDestroyed', onStreamDestroyed);
}
});
Object.defineProperty(this, 'startCall', {
enumerable: true,
writable: true,
value: function value(publisherProperties) {
var analytics = _this.analytics,
state = _this.state,
subscribe = _this.subscribe,
ableToJoin = _this.ableToJoin,
triggerEvent = _this.triggerEvent,
autoSubscribe = _this.autoSubscribe,
publish = _this.publish;
return new Promise(function (resolve, reject) {
// eslint-disable-line consistent-return
analytics.log(logAction.startCall, logVariation.attempt);
/**
* Determine if we're able to join the session based on an existing connection limit
*/
if (!ableToJoin()) {
var errorMessage = 'Session has reached its connection limit';
triggerEvent('error', errorMessage);
analytics.log(logAction.startCall, logVariation.fail);
return reject(new CoreError(errorMessage, 'connectionLimit'));
}
/**
* Subscribe to any streams that existed before we start the call from our side.
*/
var subscribeToInitialStreams = function subscribeToInitialStreams(publisher) {
// Get an array of initial subscription promises
var initialSubscriptions = function initialSubscriptions() {
if (autoSubscribe) {
var streams = state.getStreams();
return Object.keys(streams).map(function (id) {
return subscribe(streams[id]);
});
}
return [Promise.resolve()];
};
// Handle success
var onSubscribeToAll = function onSubscribeToAll() {
var pubSubData = Object.assign({}, state.getPubSub(), { publisher: publisher });
triggerEvent('startCall', pubSubData);
_this.active = true;
resolve(pubSubData);
};
// Handle error
var onError = function onError(reason) {
message('Failed to subscribe to all existing streams: ' + reason);
// We do not reject here in case we still successfully publish to the session
resolve(Object.assign({}, _this.state.getPubSub(), { publisher: publisher }));
};
Promise.all(initialSubscriptions()).then(onSubscribeToAll).catch(onError);
};
publish(publisherProperties).then(subscribeToInitialStreams).catch(reject);
});
}
});
Object.defineProperty(this, 'endCall', {
enumerable: true,
writable: true,
value: function value() {
var analytics = _this.analytics,
state = _this.state,
session = _this.session,
unsubscribe = _this.unsubscribe,
triggerEvent = _this.triggerEvent;
analytics.log(logAction.endCall, logVariation.attempt);
var _state$getPubSub = state.getPubSub(),
publishers = _state$getPubSub.publishers,
subscribers = _state$getPubSub.subscribers;
var unpublish = function unpublish(publisher) {
return session.unpublish(publisher);
};
Object.values(publishers.camera).forEach(unpublish);
Object.values(publishers.screen).forEach(unpublish);
// TODO Promise.all for unsubsribing
Object.values(subscribers.camera).forEach(unsubscribe);
Object.values(subscribers.screen).forEach(unsubscribe);
state.removeAllPublishers();
_this.active = false;
triggerEvent('endCall');
analytics.log(logAction.endCall, logVariation.success);
}
});
Object.defineProperty(this, 'enableLocalAV', {
enumerable: true,
writable: true,
value: function value(id, source, enable) {
var method = 'publish' + properCase(source);
var _state$getPubSub2 = _this.state.getPubSub(),
publishers = _state$getPubSub2.publishers;
publishers.camera[id][method](enable);
}
});
Object.defineProperty(this, 'enableRemoteAV', {
enumerable: true,
writable: true,
value: function value(subscriberId, source, enable) {
var method = 'subscribeTo' + properCase(source);
var _state$getPubSub3 = _this.state.getPubSub(),
subscribers = _state$getPubSub3.subscribers;
var subscriber = subscribers.camera[subscriberId] || subscribers.sip[subscriberId];
subscriber[method](enable);
}
});
};
/** Exports */
module.exports = {
init: init,
startCall: startCall,
endCall: endCall,
subscribe: subscribe,
unsubscribe: unsubscribe,
enableLocalAV: enableLocalAV,
enableRemoteAV: enableRemoteAV
};
exports.default = Communication;

@@ -5,2 +5,4 @@ 'use strict';

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/* global OT */

@@ -11,5 +13,5 @@ /**

var util = require('./util');
var internalState = require('./state');
var State = require('./state').default;
var accPackEvents = require('./events');
var communication = require('./communication');
var Communication = require('./communication').default;
var OpenTokSDK = require('./sdk-wrapper/sdkWrapper');

@@ -22,7 +24,5 @@

message = _require2.message,
initLogAnalytics = _require2.initLogAnalytics,
logAnalytics = _require2.logAnalytics,
Analytics = _require2.Analytics,
logAction = _require2.logAction,
logVariation = _require2.logVariation,
updateLogAnalytics = _require2.updateLogAnalytics;
logVariation = _require2.logVariation;

@@ -40,10 +40,63 @@ /**

/**
* Individual Accelerator Packs
* Ensure that we have the required credentials
* @param {Object} credentials
* @param {String} credentials.apiKey
* @param {String} credentials.sessionId
* @param {String} credentials.token
*/
var textChat = void 0; // eslint-disable-line no-unused-vars
var screenSharing = void 0; // eslint-disable-line no-unused-vars
var annotation = void 0;
var archiving = void 0; // eslint-disable-line no-unused-vars
var validateCredentials = function validateCredentials() {
var credentials = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var required = ['apiKey', 'sessionId', 'token'];
required.forEach(function (credential) {
if (!credentials[credential]) {
throw new CoreError(credential + ' is a required credential', 'invalidParameters');
}
});
};
var AccCore = function AccCore(options) {
_classCallCheck(this, AccCore);
_initialiseProps.call(this);
// Options/credentials validation
if (!options) {
throw new CoreError('Missing options required for initialization', 'invalidParameters');
}
var credentials = options.credentials;
validateCredentials(options.credentials);
// Init analytics
this.analytics = new Analytics(window.location.origin, credentials.sessionId, null, credentials.apiKey);
this.analytics.log(logAction.init, logVariation.attempt);
// Create session, setup state
this.session = OT.initSession(credentials.apiKey, credentials.sessionId);
this.internalState = new State();
this.internalState.setSession(this.session);
this.internalState.setCredentials(credentials);
this.internalState.setOptions(options);
// Individual accelerator packs
this.communication = null;
this.textChat = null;
this.screenSharing = null;
this.annotation = null;
this.archiving = null;
// Create internal event listeners
this.createEventListeners(this.session, options);
this.analytics.log(logAction.init, logVariation.success);
}
// OpenTok SDK Wrapper
// Expose utility methods
/**

@@ -54,18 +107,6 @@ * Get access to an accelerator pack

*/
var getAccPack = function getAccPack(packageName) {
logAnalytics(logAction.getAccPack, logVariation.attempt);
var packages = {
textChat: textChat,
screenSharing: screenSharing,
annotation: annotation,
archiving: archiving
};
logAnalytics(logAction.getAccPack, logVariation.success);
return packages[packageName];
};
/** Eventing */
var eventListeners = {};
/**

@@ -76,11 +117,4 @@ * Register events that can be listened to be other components/modules

*/
var registerEvents = function registerEvents(events) {
var eventList = Array.isArray(events) ? events : [events];
eventList.forEach(function (event) {
if (!eventListeners[event]) {
eventListeners[event] = new Set();
}
});
};
/**

@@ -93,20 +127,4 @@ * Register a callback for a specific event or pass an object with

*/
var on = function on(event, callback) {
// logAnalytics(logAction.on, logVariation.attempt);
if ((typeof event === 'undefined' ? 'undefined' : _typeof(event)) === 'object') {
Object.keys(event).forEach(function (eventName) {
on(eventName, event[eventName]);
});
return;
}
var eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
message(event + ' is not a registered event.');
// logAnalytics(logAction.on, logVariation.fail);
} else {
eventCallbacks.add(callback);
// logAnalytics(logAction.on, logVariation.success);
}
};
/**

@@ -118,20 +136,4 @@ * Remove a callback for a specific event. If no parameters are passed,

*/
var off = function off(event, callback) {
// logAnalytics(logAction.off, logVariation.attempt);
if (!event && !callback) {
Object.keys(eventListeners).forEach(function (eventType) {
eventListeners[eventType].clear();
});
} else {
var eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
// logAnalytics(logAction.off, logVariation.fail);
message(event + ' is not a registered event.');
} else {
eventCallbacks.delete(callback);
// logAnalytics(logAction.off, logVariation.success);
}
}
};
/**

@@ -142,14 +144,4 @@ * Trigger an event and fire all registered callbacks

*/
var triggerEvent = function triggerEvent(event, data) {
var eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
registerEvents(event);
message(event + ' has been registered as a new event.');
} else {
eventCallbacks.forEach(function (callback) {
return callback(data, event);
});
}
};
/**

@@ -159,4 +151,4 @@ * Get the current OpenTok session object

*/
var getSession = internalState.getSession;
/**

@@ -166,4 +158,4 @@ * Returns the current OpenTok session credentials

*/
var getCredentials = internalState.getCredentials;
/**

@@ -173,331 +165,49 @@ * Returns the options used for initialization

*/
var getOptions = internalState.getOptions;
var createEventListeners = function createEventListeners(session, options) {
Object.keys(accPackEvents).forEach(function (type) {
return registerEvents(accPackEvents[type]);
});
/**
* If using screen sharing + annotation in an external window, the screen sharing
* package will take care of calling annotation.start() and annotation.linkCanvas()
*/
var usingAnnotation = path('screenSharing.annotation', options);
var internalAnnotation = usingAnnotation && !path('screenSharing.externalWindow', options);
/**
* Connect to the session
* @returns {Promise} <resolve: -, reject: Error>
*/
/**
* Wrap session events and update internalState when streams are created
* or destroyed
*/
accPackEvents.session.forEach(function (eventName) {
session.on(eventName, function (event) {
if (eventName === 'streamCreated') {
internalState.addStream(event.stream);
}
if (eventName === 'streamDestroyed') {
internalState.removeStream(event.stream);
}
triggerEvent(eventName, event);
});
});
if (usingAnnotation) {
on('subscribeToScreen', function (_ref) {
var subscriber = _ref.subscriber;
/**
* Disconnect from the session
* @returns {Promise} <resolve: -, reject: Error>
*/
annotation.start(getSession()).then(function () {
var absoluteParent = dom.query(path('annotation.absoluteParent.subscriber', options));
var linkOptions = absoluteParent ? { absoluteParent: absoluteParent } : null;
annotation.linkCanvas(subscriber, subscriber.element.parentElement, linkOptions);
});
});
on('unsubscribeFromScreen', function () {
annotation.end();
});
}
/**
* Force a remote connection to leave the session
* @param {Object} connection
* @returns {Promise} <resolve: empty, reject: Error>
*/
on('startScreenSharing', function (publisher) {
internalState.addPublisher('screen', publisher);
triggerEvent('startScreenShare', Object.assign({}, { publisher: publisher }, internalState.getPubSub()));
if (internalAnnotation) {
annotation.start(getSession()).then(function () {
var absoluteParent = dom.query(path('annotation.absoluteParent.publisher', options));
var linkOptions = absoluteParent ? { absoluteParent: absoluteParent } : null;
annotation.linkCanvas(publisher, publisher.element.parentElement, linkOptions);
});
}
});
on('endScreenSharing', function (publisher) {
// delete publishers.screen[publisher.id];
internalState.removePublisher('screen', publisher);
triggerEvent('endScreenShare', internalState.getPubSub());
if (usingAnnotation) {
annotation.end();
}
});
};
var setupExternalAnnotation = function setupExternalAnnotation() {
return annotation.start(getSession(), {
screensharing: true
});
};
var linkAnnotation = function linkAnnotation(pubSub, annotationContainer, externalWindow) {
annotation.linkCanvas(pubSub, annotationContainer, {
externalWindow: externalWindow
});
if (externalWindow) {
(function () {
// Add subscribers to the external window
var streams = internalState.getStreams();
var cameraStreams = Object.keys(streams).reduce(function (acc, streamId) {
var stream = streams[streamId];
return stream.videoType === 'camera' || stream.videoType === 'sip' ? acc.concat(stream) : acc;
}, []);
cameraStreams.forEach(annotation.addSubscriberToExternalWindow);
})();
}
};
var initPackages = function initPackages() {
logAnalytics(logAction.initPackages, logVariation.attempt);
var session = getSession();
var options = getOptions();
/**
* Try to require a package. If 'require' is unavailable, look for
* the package in global scope. A switch ttatement is used because
* webpack and Browserify aren't able to resolve require statements
* that use variable names.
* @param {String} packageName - The name of the npm package
* @param {String} globalName - The name of the package if exposed on global/window
* @returns {Object}
*/
var optionalRequire = function optionalRequire(packageName, globalName) {
var result = void 0;
/* eslint-disable global-require, import/no-extraneous-dependencies, import/no-unresolved */
try {
switch (packageName) {
case 'opentok-text-chat':
result = require('opentok-text-chat');
break;
case 'opentok-screen-sharing':
result = require('opentok-screen-sharing');
break;
case 'opentok-annotation':
result = require('opentok-annotation');
break;
case 'opentok-archiving':
result = require('opentok-archiving');
break;
default:
break;
}
/* eslint-enable global-require */
} catch (error) {
result = window[globalName];
}
if (!result) {
logAnalytics(logAction.initPackages, logVariation.fail);
throw new CoreError('Could not load ' + packageName, 'missingDependency');
}
return result;
};
var availablePackages = {
textChat: function textChat() {
return optionalRequire('opentok-text-chat', 'TextChatAccPack');
},
screenSharing: function screenSharing() {
return optionalRequire('opentok-screen-sharing', 'ScreenSharingAccPack');
},
annotation: function annotation() {
return optionalRequire('opentok-annotation', 'AnnotationAccPack');
},
archiving: function archiving() {
return optionalRequire('opentok-archiving', 'ArchivingAccPack');
}
};
var packages = {};
(path('packages', options) || []).forEach(function (acceleratorPack) {
if (availablePackages[acceleratorPack]) {
// eslint-disable-next-line no-param-reassign
packages[properCase(acceleratorPack)] = availablePackages[acceleratorPack]();
} else {
message(acceleratorPack + ' is not a valid accelerator pack');
}
});
/**
* Get containers for streams, controls, and the chat widget
*/
var getDefaultContainer = function getDefaultContainer(pubSub) {
return document.getElementById(pubSub + 'Container');
};
var getContainerElements = function getContainerElements() {
// Need to use path to check for null values
var controls = pathOr('#videoControls', 'controlsContainer', options);
var chat = pathOr('#chat', 'textChat.container', options);
var stream = pathOr(getDefaultContainer, 'streamContainers', options);
return { stream: stream, controls: controls, chat: chat };
};
/** *** *** *** *** */
/**
* Return options for the specified package
* @param {String} packageName
* @returns {Object}
*/
var packageOptions = function packageOptions(packageName) {
/**
* Methods to expose to accelerator packs
*/
var accPack = {
registerEventListener: on, // Legacy option
on: on,
registerEvents: registerEvents,
triggerEvent: triggerEvent,
setupExternalAnnotation: setupExternalAnnotation,
linkAnnotation: linkAnnotation
};
/**
* If options.controlsContainer/containers.controls is null,
* accelerator packs should not append their controls.
*/
var containers = getContainerElements();
var appendControl = !!containers.controls;
var controlsContainer = containers.controls; // Legacy option
var streamContainers = containers.stream;
var baseOptions = { session: session, accPack: accPack, controlsContainer: controlsContainer, appendControl: appendControl, streamContainers: streamContainers };
switch (packageName) {
/* beautify ignore:start */
case 'communication':
{
return Object.assign({}, baseOptions, options.communication);
}
case 'textChat':
{
var textChatOptions = {
textChatContainer: path('textChat.container', options),
waitingMessage: path('textChat.waitingMessage', options),
sender: { alias: path('textChat.name', options) },
alwaysOpen: path('textChat.alwaysOpen', options)
};
return Object.assign({}, baseOptions, textChatOptions);
}
case 'screenSharing':
{
var screenSharingContainer = { screenSharingContainer: streamContainers };
return Object.assign({}, baseOptions, screenSharingContainer, options.screenSharing);
}
case 'annotation':
{
return Object.assign({}, baseOptions, options.annotation);
}
case 'archiving':
{
return Object.assign({}, baseOptions, options.archiving);
}
default:
return {};
/* beautify ignore:end */
}
};
/** Create instances of each package */
// eslint-disable-next-line global-require,import/no-extraneous-dependencies
communication.init(packageOptions('communication'));
textChat = packages.TextChat ? new packages.TextChat(packageOptions('textChat')) : null;
screenSharing = packages.ScreenSharing ? new packages.ScreenSharing(packageOptions('screenSharing')) : null;
annotation = packages.Annotation ? new packages.Annotation(packageOptions('annotation')) : null;
archiving = packages.Archiving ? new packages.Archiving(packageOptions('archiving')) : null;
logAnalytics(logAction.initPackages, logVariation.success);
};
/**
* Ensures that we have the required credentials
* @param {Object} credentials
* @param {String} credentials.apiKey
* @param {String} credentials.sessionId
* @param {String} credentials.token
* Start publishing video and subscribing to streams
* @returns {Promise}
*/
var validateCredentials = function validateCredentials() {
var credentials = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var required = ['apiKey', 'sessionId', 'token'];
required.forEach(function (credential) {
if (!credentials[credential]) {
throw new CoreError(credential + ' is a required credential', 'invalidParameters');
}
});
};
/**
* Connect to the session
* @returns {Promise} <resolve: -, reject: Error>
* Stop all publishing un unsubscribe from all streams
* @returns {void}
*/
var connect = function connect() {
return new Promise(function (resolve, reject) {
logAnalytics(logAction.connect, logVariation.attempt);
var session = getSession();
var _getCredentials = getCredentials(),
token = _getCredentials.token;
session.connect(token, function (error) {
if (error) {
message(error);
logAnalytics(logAction.connect, logVariation.fail);
return reject(error);
}
var sessionId = session.sessionId,
apiKey = session.apiKey;
updateLogAnalytics(sessionId, path('connection.connectionId', session), apiKey);
logAnalytics(logAction.connect, logVariation.success);
initPackages();
triggerEvent('connected', session);
return resolve({ connections: session.connections.length() });
});
});
};
/**
* Disconnect from the session
* @returns {Promise} <resolve: -, reject: Error>
* Manually subscribe to a stream
* @param {Object} stream - An OpenTok stream
* @returns {Promise} <resolve: Subscriber, reject: Error>
*/
var disconnect = function disconnect() {
logAnalytics(logAction.disconnect, logVariation.attempt);
getSession().disconnect();
internalState.reset();
logAnalytics(logAction.disconnect, logVariation.success);
};
/**
* Force a remote connection to leave the session
* @param {Object} connection
* @returns {Promise} <resolve: empty, reject: Error>
* Manually unsubscribe from a stream
* @param {Object} subscriber - An OpenTok subscriber object
* @returns {Promise} <resolve: void, reject: Error>
*/
var forceDisconnect = function forceDisconnect(connection) {
return new Promise(function (resolve, reject) {
logAnalytics(logAction.forceDisconnect, logVariation.attempt);
getSession().forceDisconnect(connection, function (error) {
if (error) {
logAnalytics(logAction.forceDisconnect, logVariation.fail);
reject(error);
} else {
logAnalytics(logAction.forceDisconnect, logVariation.success);
resolve();
}
});
});
};
/**

@@ -508,17 +218,4 @@ * Force the publisher of a stream to stop publishing the stream

*/
var forceUnpublish = function forceUnpublish(stream) {
return new Promise(function (resolve, reject) {
logAnalytics(logAction.forceUnpublish, logVariation.attempt);
getSession().forceUnpublish(stream, function (error) {
if (error) {
logAnalytics(logAction.forceUnpublish, logVariation.fail);
reject(error);
} else {
logAnalytics(logAction.forceUnpublish, logVariation.success);
resolve();
}
});
});
};
/**

@@ -529,6 +226,4 @@ * Get the local publisher object for a stream

*/
var getPublisherForStream = function getPublisherForStream(stream) {
return getSession().getPublisherForStream(stream);
};
/**

@@ -539,6 +234,4 @@ * Get the local subscriber objects for a stream

*/
var getSubscribersForStream = function getSubscribersForStream(stream) {
return getSession().getSubscribersForStream(stream);
};
/**

@@ -551,20 +244,4 @@ * Send a signal using the OpenTok signaling apiKey

*/
var signal = function signal(type, data, to) {
return new Promise(function (resolve, reject) {
logAnalytics(logAction.signal, logVariation.attempt);
var session = getSession();
var signalObj = Object.assign({}, type ? { type: type } : null, data ? { data: JSON.stringify(data) } : null, to ? { to: to } : null // eslint-disable-line comma-dangle
);
session.signal(signalObj, function (error) {
if (error) {
logAnalytics(logAction.signal, logVariation.fail);
reject(error);
} else {
logAnalytics(logAction.signal, logVariation.success);
resolve();
}
});
});
};
/**

@@ -574,15 +251,4 @@ * Enable or disable local audio

*/
var toggleLocalAudio = function toggleLocalAudio(enable) {
logAnalytics(logAction.toggleLocalAudio, logVariation.attempt);
var _internalState$getPub = internalState.getPubSub(),
publishers = _internalState$getPub.publishers;
var toggleAudio = function toggleAudio(id) {
return communication.enableLocalAV(id, 'audio', enable);
};
Object.keys(publishers.camera).forEach(toggleAudio);
logAnalytics(logAction.toggleLocalAudio, logVariation.success);
};
/**

@@ -592,15 +258,4 @@ * Enable or disable local video

*/
var toggleLocalVideo = function toggleLocalVideo(enable) {
logAnalytics(logAction.toggleLocalVideo, logVariation.attempt);
var _internalState$getPub2 = internalState.getPubSub(),
publishers = _internalState$getPub2.publishers;
var toggleVideo = function toggleVideo(id) {
return communication.enableLocalAV(id, 'video', enable);
};
Object.keys(publishers.camera).forEach(toggleVideo);
logAnalytics(logAction.toggleLocalVideo, logVariation.success);
};
/**

@@ -611,8 +266,4 @@ * Enable or disable remote audio

*/
var toggleRemoteAudio = function toggleRemoteAudio(id, enable) {
logAnalytics(logAction.toggleRemoteAudio, logVariation.attempt);
communication.enableRemoteAV(id, 'audio', enable);
logAnalytics(logAction.toggleRemoteAudio, logVariation.success);
};
/**

@@ -623,67 +274,650 @@ * Enable or disable remote video

*/
var toggleRemoteVideo = function toggleRemoteVideo(id, enable) {
logAnalytics(logAction.toggleRemoteVideo, logVariation.attempt);
communication.enableRemoteAV(id, 'video', enable);
logAnalytics(logAction.toggleRemoteVideo, logVariation.success);
};
;
/**
* Initialize the accelerator pack
* @param {Object} options
* @param {Object} options.credentials
* @param {Array} [options.packages]
* @param {Object} [options.containers]
*/
var init = function init(options) {
if (!options) {
throw new CoreError('Missing options required for initialization', 'invalidParameters');
}
var credentials = options.credentials;
Object.defineProperty(AccCore, 'OpenTokSDK', {
enumerable: true,
writable: true,
value: OpenTokSDK
});
Object.defineProperty(AccCore, 'util', {
enumerable: true,
writable: true,
value: util
});
validateCredentials(options.credentials);
var _initialiseProps = function _initialiseProps() {
var _this = this;
// Init analytics
initLogAnalytics(window.location.origin, credentials.sessionId, null, credentials.apiKey);
logAnalytics(logAction.init, logVariation.attempt);
var session = OT.initSession(credentials.apiKey, credentials.sessionId);
createEventListeners(session, options);
internalState.setSession(session);
internalState.setCredentials(credentials);
internalState.setOptions(options);
logAnalytics(logAction.init, logVariation.success);
};
Object.defineProperty(this, 'getAccPack', {
enumerable: true,
writable: true,
value: function value(packageName) {
var analytics = _this.analytics,
textChat = _this.textChat,
screenSharing = _this.screenSharing,
annotation = _this.annotation,
archiving = _this.archiving;
var opentokCore = {
init: init,
connect: connect,
disconnect: disconnect,
forceDisconnect: forceDisconnect,
forceUnpublish: forceUnpublish,
getAccPack: getAccPack,
getOptions: getOptions,
getSession: getSession,
getPublisherForStream: getPublisherForStream,
getSubscribersForStream: getSubscribersForStream,
on: on,
off: off,
registerEventListener: on,
triggerEvent: triggerEvent,
signal: signal,
state: internalState.all,
startCall: communication.startCall,
endCall: communication.endCall,
OpenTokSDK: OpenTokSDK,
toggleLocalAudio: toggleLocalAudio,
toggleLocalVideo: toggleLocalVideo,
toggleRemoteAudio: toggleRemoteAudio,
toggleRemoteVideo: toggleRemoteVideo,
subscribe: communication.subscribe,
unsubscribe: communication.unsubscribe,
util: util
analytics.log(logAction.getAccPack, logVariation.attempt);
var packages = {
textChat: textChat,
screenSharing: screenSharing,
annotation: annotation,
archiving: archiving
};
analytics.log(logAction.getAccPack, logVariation.success);
return packages[packageName];
}
});
Object.defineProperty(this, 'registerEvents', {
enumerable: true,
writable: true,
value: function value(events) {
var eventListeners = _this.eventListeners;
var eventList = Array.isArray(events) ? events : [events];
eventList.forEach(function (event) {
if (!eventListeners[event]) {
eventListeners[event] = new Set();
}
});
}
});
Object.defineProperty(this, 'on', {
enumerable: true,
writable: true,
value: function value(event, callback) {
var eventListeners = _this.eventListeners,
on = _this.on;
// analytics.log(logAction.on, logVariation.attempt);
if ((typeof event === 'undefined' ? 'undefined' : _typeof(event)) === 'object') {
Object.keys(event).forEach(function (eventName) {
on(eventName, event[eventName]);
});
return;
}
var eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
message(event + ' is not a registered event.');
// analytics.log(logAction.on, logVariation.fail);
} else {
eventCallbacks.add(callback);
// analytics.log(logAction.on, logVariation.success);
}
}
});
Object.defineProperty(this, 'off', {
enumerable: true,
writable: true,
value: function value(event, callback) {
var eventListeners = _this.eventListeners;
// analytics.log(logAction.off, logVariation.attempt);
if (!event && !callback) {
Object.keys(eventListeners).forEach(function (eventType) {
eventListeners[eventType].clear();
});
} else {
var eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
// analytics.log(logAction.off, logVariation.fail);
message(event + ' is not a registered event.');
} else {
eventCallbacks.delete(callback);
// analytics.log(logAction.off, logVariation.success);
}
}
}
});
Object.defineProperty(this, 'triggerEvent', {
enumerable: true,
writable: true,
value: function value(event, data) {
var eventListeners = _this.eventListeners,
registerEvents = _this.registerEvents;
var eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
registerEvents(event);
message(event + ' has been registered as a new event.');
} else {
eventCallbacks.forEach(function (callback) {
return callback(data, event);
});
}
}
});
Object.defineProperty(this, 'getSession', {
enumerable: true,
writable: true,
value: function value() {
return _this.internalState.getSession();
}
});
Object.defineProperty(this, 'getCredentials', {
enumerable: true,
writable: true,
value: function value() {
return _this.internalState.getCredentials();
}
});
Object.defineProperty(this, 'getOptions', {
enumerable: true,
writable: true,
value: function value() {
return _this.internalState.getOptions();
}
});
Object.defineProperty(this, 'createEventListeners', {
enumerable: true,
writable: true,
value: function value(session, options) {
_this.eventListeners = {};
var registerEvents = _this.registerEvents,
internalState = _this.internalState,
triggerEvent = _this.triggerEvent,
on = _this.on,
getSession = _this.getSession;
Object.keys(accPackEvents).forEach(function (type) {
return registerEvents(accPackEvents[type]);
});
/**
* If using screen sharing + annotation in an external window, the screen sharing
* package will take care of calling annotation.start() and annotation.linkCanvas()
*/
var usingAnnotation = path('screenSharing.annotation', options);
var internalAnnotation = usingAnnotation && !path('screenSharing.externalWindow', options);
/**
* Wrap session events and update internalState when streams are created
* or destroyed
*/
accPackEvents.session.forEach(function (eventName) {
session.on(eventName, function (event) {
if (eventName === 'streamCreated') {
internalState.addStream(event.stream);
}
if (eventName === 'streamDestroyed') {
internalState.removeStream(event.stream);
}
triggerEvent(eventName, event);
});
});
if (usingAnnotation) {
on('subscribeToScreen', function (_ref) {
var subscriber = _ref.subscriber;
_this.annotation.start(getSession()).then(function () {
var absoluteParent = dom.query(path('annotation.absoluteParent.subscriber', options));
var linkOptions = absoluteParent ? { absoluteParent: absoluteParent } : null;
_this.annotation.linkCanvas(subscriber, subscriber.element.parentElement, linkOptions);
});
});
on('unsubscribeFromScreen', function () {
_this.annotation.end();
});
}
on('startScreenSharing', function (publisher) {
internalState.addPublisher('screen', publisher);
triggerEvent('startScreenShare', Object.assign({}, { publisher: publisher }, internalState.getPubSub()));
if (internalAnnotation) {
_this.annotation.start(getSession()).then(function () {
var absoluteParent = dom.query(path('annotation.absoluteParent.publisher', options));
var linkOptions = absoluteParent ? { absoluteParent: absoluteParent } : null;
_this.annotation.linkCanvas(publisher, publisher.element.parentElement, linkOptions);
});
}
});
on('endScreenSharing', function (publisher) {
// delete publishers.screen[publisher.id];
internalState.removePublisher('screen', publisher);
triggerEvent('endScreenShare', internalState.getPubSub());
if (usingAnnotation) {
_this.annotation.end();
}
});
}
});
Object.defineProperty(this, 'setupExternalAnnotation', {
enumerable: true,
writable: true,
value: function value() {
return _this.annotation.start(_this.getSession(), { screensharing: true });
}
});
Object.defineProperty(this, 'linkAnnotation', {
enumerable: true,
writable: true,
value: function value(pubSub, annotationContainer, externalWindow) {
var annotation = _this.annotation,
internalState = _this.internalState;
annotation.linkCanvas(pubSub, annotationContainer, {
externalWindow: externalWindow
});
if (externalWindow) {
// Add subscribers to the external window
var streams = internalState.getStreams();
var cameraStreams = Object.keys(streams).reduce(function (acc, streamId) {
var stream = streams[streamId];
return stream.videoType === 'camera' || stream.videoType === 'sip' ? acc.concat(stream) : acc;
}, []);
cameraStreams.forEach(annotation.addSubscriberToExternalWindow);
}
}
});
Object.defineProperty(this, 'initPackages', {
enumerable: true,
writable: true,
value: function value() {
var analytics = _this.analytics,
getSession = _this.getSession,
getOptions = _this.getOptions,
internalState = _this.internalState;
var on = _this.on,
registerEvents = _this.registerEvents,
setupExternalAnnotation = _this.setupExternalAnnotation,
triggerEvent = _this.triggerEvent,
linkAnnotation = _this.linkAnnotation;
analytics.log(logAction.initPackages, logVariation.attempt);
var session = getSession();
var options = getOptions();
/**
* Try to require a package. If 'require' is unavailable, look for
* the package in global scope. A switch ttatement is used because
* webpack and Browserify aren't able to resolve require statements
* that use variable names.
* @param {String} packageName - The name of the npm package
* @param {String} globalName - The name of the package if exposed on global/window
* @returns {Object}
*/
var optionalRequire = function optionalRequire(packageName, globalName) {
var result = void 0;
/* eslint-disable global-require, import/no-extraneous-dependencies, import/no-unresolved */
try {
switch (packageName) {
case 'opentok-text-chat':
result = require('opentok-text-chat');
break;
case 'opentok-screen-sharing':
result = require('opentok-screen-sharing');
break;
case 'opentok-annotation':
result = require('opentok-annotation');
break;
case 'opentok-archiving':
result = require('opentok-archiving');
break;
default:
break;
}
/* eslint-enable global-require */
} catch (error) {
result = window[globalName];
}
if (!result) {
analytics.log(logAction.initPackages, logVariation.fail);
throw new CoreError('Could not load ' + packageName, 'missingDependency');
}
return result;
};
var availablePackages = {
textChat: function textChat() {
return optionalRequire('opentok-text-chat', 'TextChatAccPack');
},
screenSharing: function screenSharing() {
return optionalRequire('opentok-screen-sharing', 'ScreenSharingAccPack');
},
annotation: function annotation() {
return optionalRequire('opentok-annotation', 'AnnotationAccPack');
},
archiving: function archiving() {
return optionalRequire('opentok-archiving', 'ArchivingAccPack');
}
};
var packages = {};
(path('packages', options) || []).forEach(function (acceleratorPack) {
if (availablePackages[acceleratorPack]) {
// eslint-disable-next-line no-param-reassign
packages[properCase(acceleratorPack)] = availablePackages[acceleratorPack]();
} else {
message(acceleratorPack + ' is not a valid accelerator pack');
}
});
/**
* Get containers for streams, controls, and the chat widget
*/
var getDefaultContainer = function getDefaultContainer(pubSub) {
return document.getElementById(pubSub + 'Container');
};
var getContainerElements = function getContainerElements() {
// Need to use path to check for null values
var controls = pathOr('#videoControls', 'controlsContainer', options);
var chat = pathOr('#chat', 'textChat.container', options);
var stream = pathOr(getDefaultContainer, 'streamContainers', options);
return { stream: stream, controls: controls, chat: chat };
};
/** *** *** *** *** */
/**
* Return options for the specified package
* @param {String} packageName
* @returns {Object}
*/
var packageOptions = function packageOptions(packageName) {
/**
* Methods to expose to accelerator packs
*/
var accPack = {
registerEventListener: on, // Legacy option
on: on,
registerEvents: registerEvents,
triggerEvent: triggerEvent,
setupExternalAnnotation: setupExternalAnnotation,
linkAnnotation: linkAnnotation
};
/**
* If options.controlsContainer/containers.controls is null,
* accelerator packs should not append their controls.
*/
var containers = getContainerElements();
var appendControl = !!containers.controls;
var controlsContainer = containers.controls; // Legacy option
var streamContainers = containers.stream;
var baseOptions = {
session: session,
core: accPack,
accPack: accPack,
controlsContainer: controlsContainer,
appendControl: appendControl,
streamContainers: streamContainers
};
switch (packageName) {
/* beautify ignore:start */
case 'communication':
{
return Object.assign({}, baseOptions, { state: internalState, analytics: analytics }, options.communication);
}
case 'textChat':
{
var textChatOptions = {
textChatContainer: path('textChat.container', options),
waitingMessage: path('textChat.waitingMessage', options),
sender: { alias: path('textChat.name', options) },
alwaysOpen: path('textChat.alwaysOpen', options)
};
return Object.assign({}, baseOptions, textChatOptions);
}
case 'screenSharing':
{
var screenSharingContainer = { screenSharingContainer: streamContainers };
return Object.assign({}, baseOptions, screenSharingContainer, options.screenSharing);
}
case 'annotation':
{
return Object.assign({}, baseOptions, options.annotation);
}
case 'archiving':
{
return Object.assign({}, baseOptions, options.archiving);
}
default:
return {};
/* beautify ignore:end */
}
};
/** Create instances of each package */
// eslint-disable-next-line global-require,import/no-extraneous-dependencies
_this.communication = new Communication(packageOptions('communication'));
_this.textChat = packages.TextChat ? new packages.TextChat(packageOptions('textChat')) : null;
_this.screenSharing = packages.ScreenSharing ? new packages.ScreenSharing(packageOptions('screenSharing')) : null;
_this.annotation = packages.Annotation ? new packages.Annotation(packageOptions('annotation')) : null;
_this.archiving = packages.Archiving ? new packages.Archiving(packageOptions('archiving')) : null;
analytics.log(logAction.initPackages, logVariation.success);
}
});
Object.defineProperty(this, 'connect', {
enumerable: true,
writable: true,
value: function value() {
var analytics = _this.analytics,
getSession = _this.getSession,
initPackages = _this.initPackages,
triggerEvent = _this.triggerEvent,
getCredentials = _this.getCredentials;
return new Promise(function (resolve, reject) {
analytics.log(logAction.connect, logVariation.attempt);
var session = getSession();
var _getCredentials = getCredentials(),
token = _getCredentials.token;
session.connect(token, function (error) {
if (error) {
message(error);
analytics.log(logAction.connect, logVariation.fail);
return reject(error);
}
var sessionId = session.sessionId,
apiKey = session.apiKey;
analytics.update(sessionId, path('connection.connectionId', session), apiKey);
analytics.log(logAction.connect, logVariation.success);
initPackages();
triggerEvent('connected', session);
return resolve({ connections: session.connections.length() });
});
});
}
});
Object.defineProperty(this, 'disconnect', {
enumerable: true,
writable: true,
value: function value() {
var analytics = _this.analytics,
getSession = _this.getSession,
internalState = _this.internalState;
analytics.log(logAction.disconnect, logVariation.attempt);
getSession().disconnect();
internalState.reset();
analytics.log(logAction.disconnect, logVariation.success);
}
});
Object.defineProperty(this, 'forceDisconnect', {
enumerable: true,
writable: true,
value: function value(connection) {
var analytics = _this.analytics,
getSession = _this.getSession;
return new Promise(function (resolve, reject) {
analytics.log(logAction.forceDisconnect, logVariation.attempt);
getSession().forceDisconnect(connection, function (error) {
if (error) {
analytics.log(logAction.forceDisconnect, logVariation.fail);
reject(error);
} else {
analytics.log(logAction.forceDisconnect, logVariation.success);
resolve();
}
});
});
}
});
Object.defineProperty(this, 'startCall', {
enumerable: true,
writable: true,
value: function value() {
return _this.communication.startCall();
}
});
Object.defineProperty(this, 'endCall', {
enumerable: true,
writable: true,
value: function value() {
return _this.communication.endCall();
}
});
Object.defineProperty(this, 'subscribe', {
enumerable: true,
writable: true,
value: function value(stream) {
return _this.communication.subscribe(stream);
}
});
Object.defineProperty(this, 'unsubscibe', {
enumerable: true,
writable: true,
value: function value(subscriber) {
return _this.communication.unsubscibe(subscriber);
}
});
Object.defineProperty(this, 'forceUnpublish', {
enumerable: true,
writable: true,
value: function value(stream) {
var analytics = _this.analytics,
getSession = _this.getSession;
return new Promise(function (resolve, reject) {
analytics.log(logAction.forceUnpublish, logVariation.attempt);
getSession().forceUnpublish(stream, function (error) {
if (error) {
analytics.log(logAction.forceUnpublish, logVariation.fail);
reject(error);
} else {
analytics.log(logAction.forceUnpublish, logVariation.success);
resolve();
}
});
});
}
});
Object.defineProperty(this, 'getPublisherForStream', {
enumerable: true,
writable: true,
value: function value(stream) {
return _this.getSession().getPublisherForStream(stream);
}
});
Object.defineProperty(this, 'getSubscribersForStream', {
enumerable: true,
writable: true,
value: function value(stream) {
return _this.getSession().getSubscribersForStream(stream);
}
});
Object.defineProperty(this, 'signal', {
enumerable: true,
writable: true,
value: function value(type, data, to) {
var analytics = _this.analytics,
getSession = _this.getSession;
return new Promise(function (resolve, reject) {
analytics.log(logAction.signal, logVariation.attempt);
var session = getSession();
var signalObj = Object.assign({}, type ? { type: type } : null, data ? { data: JSON.stringify(data) } : null, to ? { to: to } : null // eslint-disable-line comma-dangle
);
session.signal(signalObj, function (error) {
if (error) {
analytics.log(logAction.signal, logVariation.fail);
reject(error);
} else {
analytics.log(logAction.signal, logVariation.success);
resolve();
}
});
});
}
});
Object.defineProperty(this, 'toggleLocalAudio', {
enumerable: true,
writable: true,
value: function value(enable) {
var analytics = _this.analytics,
internalState = _this.internalState,
communication = _this.communication;
analytics.log(logAction.toggleLocalAudio, logVariation.attempt);
var _internalState$getPub = internalState.getPubSub(),
publishers = _internalState$getPub.publishers;
var toggleAudio = function toggleAudio(id) {
return communication.enableLocalAV(id, 'audio', enable);
};
Object.keys(publishers.camera).forEach(toggleAudio);
analytics.log(logAction.toggleLocalAudio, logVariation.success);
}
});
Object.defineProperty(this, 'toggleLocalVideo', {
enumerable: true,
writable: true,
value: function value(enable) {
var analytics = _this.analytics,
internalState = _this.internalState,
communication = _this.communication;
analytics.log(logAction.toggleLocalVideo, logVariation.attempt);
var _internalState$getPub2 = internalState.getPubSub(),
publishers = _internalState$getPub2.publishers;
var toggleVideo = function toggleVideo(id) {
return communication.enableLocalAV(id, 'video', enable);
};
Object.keys(publishers.camera).forEach(toggleVideo);
analytics.log(logAction.toggleLocalVideo, logVariation.success);
}
});
Object.defineProperty(this, 'toggleRemoteAudio', {
enumerable: true,
writable: true,
value: function value(id, enable) {
var analytics = _this.analytics,
communication = _this.communication;
analytics.log(logAction.toggleRemoteAudio, logVariation.attempt);
communication.enableRemoteAV(id, 'audio', enable);
analytics.log(logAction.toggleRemoteAudio, logVariation.success);
}
});
Object.defineProperty(this, 'toggleRemoteVideo', {
enumerable: true,
writable: true,
value: function value(id, enable) {
var analytics = _this.analytics,
communication = _this.communication;
analytics.log(logAction.toggleRemoteVideo, logVariation.attempt);
communication.enableRemoteAV(id, 'video', enable);
analytics.log(logAction.toggleRemoteVideo, logVariation.success);
}
});
};
if (global === window) {
window.otCore = opentokCore;
window.AccCore = AccCore;
}
module.exports = opentokCore;
module.exports = AccCore;
'use strict';
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var OTKAnalytics = require('opentok-solutions-logging');

@@ -12,4 +14,2 @@

var analytics = null;
var logVariation = {

@@ -41,16 +41,9 @@ attempt: 'Attempt',

var updateLogAnalytics = function updateLogAnalytics(sessionId, connectionId, apiKey) {
if (sessionId && connectionId && apiKey) {
var sessionInfo = {
sessionId: sessionId,
connectionId: connectionId,
partnerId: apiKey
};
analytics.addSessionInfo(sessionInfo);
}
};
var Analytics = function Analytics(source, sessionId, connectionId, apikey) {
_classCallCheck(this, Analytics);
var initLogAnalytics = function initLogAnalytics(source, sessionId, connectionId, apikey) {
_initialiseProps.call(this);
var otkanalyticsData = {
clientVersion: 'js-vsol-1.0.29', // x.y.z filled by npm build script
clientVersion: 'js-vsol-2.0.0', // x.y.z filled by npm build script
source: source,

@@ -62,20 +55,40 @@ componentId: 'acceleratorCore',

analytics = new OTKAnalytics(otkanalyticsData);
this.analytics = new OTKAnalytics(otkanalyticsData);
if (connectionId) {
updateLogAnalytics(sessionId, connectionId, apikey);
this.update(sessionId, connectionId, apikey);
}
};
var logAnalytics = function logAnalytics(action, variation) {
analytics.logEvent({ action: action, variation: variation });
var _initialiseProps = function _initialiseProps() {
var _this = this;
Object.defineProperty(this, 'update', {
enumerable: true,
writable: true,
value: function value(sessionId, connectionId, apiKey) {
if (sessionId && connectionId && apiKey) {
var sessionInfo = {
sessionId: sessionId,
connectionId: connectionId,
partnerId: apiKey
};
_this.analytics.addSessionInfo(sessionInfo);
}
}
});
Object.defineProperty(this, 'log', {
enumerable: true,
writable: true,
value: function value(action, variation) {
_this.analytics.logEvent({ action: action, variation: variation });
}
});
};
module.exports = {
message: message,
Analytics: Analytics,
logVariation: logVariation,
logAction: logAction,
logVariation: logVariation,
initLogAnalytics: initLogAnalytics,
updateLogAnalytics: updateLogAnalytics,
logAnalytics: logAnalytics
message: message
};

@@ -153,2 +153,8 @@ 'use strict';

});
this.session.on('sessionConnected sessionReconnected', function () {
return state.setConnected(true);
});
this.session.on('sessionDisconnected', function () {
return state.setConnected(false);
});
}

@@ -155,0 +161,0 @@

@@ -31,8 +31,19 @@ "use strict";

this.credentials = null;
// Session Connection Status
this.connected = false;
}
// Get the current OpenTok session
// Set the current connection state
_createClass(State, [{
key: "setConnected",
value: function setConnected(connected) {
this.connected = connected;
}
// Get the current OpenTok session
}, {
key: "getSession",

@@ -186,5 +197,6 @@ value: function getSession() {

var streams = this.streams,
streamMap = this.streamMap;
streamMap = this.streamMap,
connected = this.connected;
return Object.assign({}, this.getPubSub(), { streams: streams, streamMap: streamMap });
return Object.assign({}, this.getPubSub(), { streams: streams, streamMap: streamMap, connected: connected });
}

@@ -191,0 +203,0 @@ }]);

'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var _require = require('./util'),
pathOr = _require.pathOr;
/**
* Internal variables
*/
// Map publisher ids to publisher objects
var State = function State() {
var _this = this;
_classCallCheck(this, State);
var publishers = {
camera: {},
screen: {}
};
Object.defineProperty(this, 'pubSubCount', {
enumerable: true,
writable: true,
value: function value() {
var publishers = _this.publishers,
subscribers = _this.subscribers;
/* eslint-disable no-param-reassign */
// Map subscriber id to subscriber objects
var subscribers = {
camera: {},
screen: {},
sip: {}
};
var pubs = Object.keys(publishers).reduce(function (acc, source) {
acc[source] = Object.keys(publishers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, total: 0 });
// Map stream ids to stream objects
var streams = {};
var subs = Object.keys(subscribers).reduce(function (acc, source) {
acc[source] = Object.keys(subscribers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, sip: 0, total: 0 });
/* eslint-enable no-param-reassign */
return { publisher: pubs, subscriber: subs };
}
});
Object.defineProperty(this, 'getPubSub', {
enumerable: true,
writable: true,
value: function value() {
var publishers = _this.publishers,
subscribers = _this.subscribers,
pubSubCount = _this.pubSubCount;
// Map stream ids to subscriber/publisher ids
var streamMap = {};
return { publishers: publishers, subscribers: subscribers, meta: pubSubCount() };
}
});
Object.defineProperty(this, 'all', {
enumerable: true,
writable: true,
value: function value() {
var streams = _this.streams,
streamMap = _this.streamMap,
getPubSub = _this.getPubSub;
var session = null;
var credentials = null;
var options = null;
return Object.assign({}, { streams: streams, streamMap: streamMap }, getPubSub());
}
});
Object.defineProperty(this, 'getSession', {
enumerable: true,
writable: true,
value: function value() {
return _this.session;
}
});
Object.defineProperty(this, 'setSession', {
enumerable: true,
writable: true,
value: function value(otSession) {
_this.session = otSession;
}
});
Object.defineProperty(this, 'getCredentials', {
enumerable: true,
writable: true,
value: function value() {
return _this.credentials;
}
});
Object.defineProperty(this, 'setCredentials', {
enumerable: true,
writable: true,
value: function value(otCredentials) {
_this.credentials = otCredentials;
}
});
Object.defineProperty(this, 'getOptions', {
enumerable: true,
writable: true,
value: function value() {
return _this.options;
}
});
Object.defineProperty(this, 'setOptions', {
enumerable: true,
writable: true,
value: function value(otOptions) {
_this.options = otOptions;
}
});
Object.defineProperty(this, 'addStream', {
enumerable: true,
writable: true,
value: function value(stream) {
_this.streams[stream.id] = stream;
}
});
Object.defineProperty(this, 'removeStream', {
enumerable: true,
writable: true,
value: function value(stream) {
var streamMap = _this.streamMap,
subscribers = _this.subscribers,
streams = _this.streams;
var type = pathOr('sip', 'videoType', stream);
var subscriberId = streamMap[stream.id];
delete streamMap[stream.id];
delete subscribers[type][subscriberId];
delete streams[stream.id];
}
});
Object.defineProperty(this, 'getStreams', {
enumerable: true,
writable: true,
value: function value() {
return _this.streams;
}
});
Object.defineProperty(this, 'getStreamMap', {
enumerable: true,
writable: true,
value: function value() {
return _this.streamMap;
}
});
Object.defineProperty(this, 'addPublisher', {
enumerable: true,
writable: true,
value: function value(type, publisher) {
_this.streamMap[publisher.streamId] = publisher.id;
_this.publishers[type][publisher.id] = publisher;
}
});
Object.defineProperty(this, 'removePublisher', {
enumerable: true,
writable: true,
value: function value(type, publisher) {
var streamMap = _this.streamMap,
publishers = _this.publishers;
var id = publisher.id || streamMap[publisher.streamId];
delete publishers[type][id];
delete streamMap[publisher.streamId];
}
});
Object.defineProperty(this, 'removeAllPublishers', {
enumerable: true,
writable: true,
value: function value() {
var publishers = _this.publishers,
removePublisher = _this.removePublisher;
['camera', 'screen'].forEach(function (type) {
Object.values(publishers[type]).forEach(function (publisher) {
removePublisher(type, publisher);
});
});
}
});
Object.defineProperty(this, 'addSubscriber', {
enumerable: true,
writable: true,
value: function value(subscriber) {
var subscribers = _this.subscribers,
streamMap = _this.streamMap;
var streamId = subscriber.stream.id;
var type = pathOr('sip', 'stream.videoType', subscriber);
subscribers[type][subscriber.id] = subscriber;
streamMap[streamId] = subscriber.id;
}
});
Object.defineProperty(this, 'removeSubscriber', {
enumerable: true,
writable: true,
value: function value(type, subscriber) {
var subscribers = _this.subscribers,
streamMap = _this.streamMap;
var id = subscriber.id || streamMap[subscriber.streamId];
delete subscribers[type][id];
delete streamMap[subscriber.streamId];
}
});
Object.defineProperty(this, 'removeAllSubscribers', {
enumerable: true,
writable: true,
value: function value() {
['camera', 'screen', 'sip'].forEach(function (type) {
Object.values(_this.subscribers[type]).forEach(function (subscriber) {
_this.removeSubscriber(type, subscriber);
});
});
}
});
Object.defineProperty(this, 'reset', {
enumerable: true,
writable: true,
value: function value() {
var removeAllPublishers = _this.removeAllPublishers,
removeAllSubscribers = _this.removeAllSubscribers,
streams = _this.streams,
streamMap = _this.streamMap;
removeAllPublishers();
removeAllSubscribers();
[streams, streamMap].forEach(function (streamObj) {
Object.keys(streamObj).forEach(function (streamId) {
delete streamObj[streamId]; // eslint-disable-line no-param-reassign
});
});
}
});
this.publishers = {
camera: {},
screen: {}
};
// Map subscriber id to subscriber objects
this.subscribers = {
camera: {},
screen: {},
sip: {}
};
// Map stream ids to stream objects
this.streams = {};
// Map stream ids to subscriber/publisher ids
this.streamMap = {};
// The OpenTok session
this.session = null;
// OpenTok session credentials
this.credentials = null;
// Core options
this.options = null;
}
/**
* Internal methods
*/
* Internal methods
*/

@@ -54,18 +276,3 @@ /**

*/
var pubSubCount = function pubSubCount() {
/* eslint-disable no-param-reassign */
var pubs = Object.keys(publishers).reduce(function (acc, source) {
acc[source] = Object.keys(publishers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, total: 0 });
var subs = Object.keys(subscribers).reduce(function (acc, source) {
acc[source] = Object.keys(subscribers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, sip: 0, total: 0 });
/* eslint-enable no-param-reassign */
return { publisher: pubs, subscriber: subs };
};

@@ -76,6 +283,4 @@ /**

*/
var getPubSub = function getPubSub() {
return { publishers: publishers, subscribers: subscribers, meta: pubSubCount() };
};
/**

@@ -85,6 +290,4 @@ * Get streams, streamMap, publishers, and subscribers

*/
var all = function all() {
return Object.assign({}, { streams: streams, streamMap: streamMap }, getPubSub());
};
/**

@@ -94,6 +297,4 @@ * Get the current OpenTok session

*/
var getSession = function getSession() {
return session;
};
/**

@@ -103,6 +304,4 @@ * Set the current OpenTok session

*/
var setSession = function setSession(otSession) {
session = otSession;
};
/**

@@ -112,6 +311,4 @@ * Get the current OpenTok credentials

*/
var getCredentials = function getCredentials() {
return credentials;
};
/**

@@ -121,6 +318,4 @@ * Set the current OpenTok credentials

*/
var setCredentials = function setCredentials(otCredentials) {
credentials = otCredentials;
};
/**

@@ -130,6 +325,4 @@ * Get the options defined for core

*/
var getOptions = function getOptions() {
return options;
};
/**

@@ -139,6 +332,4 @@ * Set the options defined for core

*/
var setOptions = function setOptions(otOptions) {
options = otOptions;
};
/**

@@ -148,6 +339,4 @@ * Add a stream to state

*/
var addStream = function addStream(stream) {
streams[stream.id] = stream;
};
/**

@@ -157,10 +346,4 @@ * Remove a stream from state and any associated subscribers

*/
var removeStream = function removeStream(stream) {
var type = pathOr('sip', 'videoType', stream);
var subscriberId = streamMap[stream.id];
delete streamMap[stream.id];
delete subscribers[type][subscriberId];
delete streams[stream.id];
};
/**

@@ -170,6 +353,4 @@ * Get all remote streams

*/
var getStreams = function getStreams() {
return streams;
};
/**

@@ -179,6 +360,4 @@ * Get the map of stream ids to publisher/subscriber ids

*/
var getStreamMap = function getStreamMap() {
return streamMap;
};
/**

@@ -189,7 +368,4 @@ * Add a publisher to state

*/
var addPublisher = function addPublisher(type, publisher) {
streamMap[publisher.streamId] = publisher.id;
publishers[type][publisher.id] = publisher;
};
/**

@@ -200,19 +376,9 @@ * Remove a publisher from state

*/
var removePublisher = function removePublisher(type, publisher) {
var id = publisher.id || streamMap[publisher.streamId];
delete publishers[type][id];
delete streamMap[publisher.streamId];
};
/**
* Remove all publishers from state
*/
var removeAllPublishers = function removeAllPublishers() {
['camera', 'screen'].forEach(function (type) {
Object.values(publishers[type]).forEach(function (publisher) {
removePublisher(type, publisher);
});
});
};
/**

@@ -222,9 +388,4 @@ * Add a subscriber to state

*/
var addSubscriber = function addSubscriber(subscriber) {
var streamId = subscriber.stream.id;
var type = pathOr('sip', 'stream.videoType', subscriber);
subscribers[type][subscriber.id] = subscriber;
streamMap[streamId] = subscriber.id;
};
/**

@@ -235,53 +396,17 @@ * Remove a publisher from state

*/
var removeSubscriber = function removeSubscriber(type, subscriber) {
var id = subscriber.id || streamMap[subscriber.streamId];
delete subscribers[type][id];
delete streamMap[subscriber.streamId];
};
/**
* Remove all subscribers from state
*/
var removeAllSubscribers = function removeAllSubscribers() {
['camera', 'screen', 'sip'].forEach(function (type) {
Object.values(subscribers[type]).forEach(function (subscriber) {
removeSubscriber(type, subscriber);
});
});
};
/**
* Reset state
*/
var reset = function reset() {
removeAllPublishers();
removeAllSubscribers();
[streams, streamMap].forEach(function (streamObj) {
Object.keys(streamObj).forEach(function (streamId) {
delete streamObj[streamId]; // eslint-disable-line no-param-reassign
});
});
};
;
/** Exports */
module.exports = {
all: all,
getSession: getSession,
setSession: setSession,
getCredentials: getCredentials,
setCredentials: setCredentials,
getOptions: getOptions,
setOptions: setOptions,
addStream: addStream,
removeStream: removeStream,
getStreams: getStreams,
getStreamMap: getStreamMap,
addPublisher: addPublisher,
removePublisher: removePublisher,
removeAllPublishers: removeAllPublishers,
addSubscriber: addSubscriber,
removeSubscriber: removeSubscriber,
removeAllSubscribers: removeAllSubscribers,
getPubSub: getPubSub,
reset: reset
};
/** Export */
exports.default = State;
{
"name": "opentok-accelerator-core",
"version": "1.0.29",
"version": "2.0.0",
"description": "Opentok Accelerator Core",

@@ -10,3 +10,3 @@ "repository": "https://github.com/opentok/accelerator-core-js",

"build:browser-polyfill": "(echo \"require('babel-polyfill');\\n\"; cat src/core.js) > src/browser-temp.js",
"build:browser-browserify": "mkdir -p browser && browserify src/browser-temp.js -o browser/opentok-acc-core.js -t [ babelify --presets [ es2015 ] ] --im",
"build:browser-browserify": "mkdir -p browser && browserify src/browser-temp.js -o browser/opentok-acc-core.js -t [ babelify --presets [ es2015 ] --plugins [ transform-class-properties ] ] --im",
"build:browser": "npm run build:browser-polyfill && npm run build:browser-browserify && rm src/browser-temp.js",

@@ -31,2 +31,4 @@ "build:logversion": "replace '(js-vsol-)(x.y.z)' '$1'$npm_package_version dist/* browser/*",

"babel-cli": "*",
"babel-eslint": "^6.1.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-polyfill": "*",

@@ -36,3 +38,3 @@ "babel-preset-es2015": "^6.16.0",

"browserify": "^13.1.1",
"eslint": "^3.14.1",
"eslint": "^3.19.0",
"eslint-config-airbnb": "^14.0.0",

@@ -39,0 +41,0 @@ "eslint-plugin-import": "^2.2.0",

@@ -41,3 +41,3 @@ ![logo](./tokbox-logo.png)

```javascript
const otCore = require('opentok-accelerator-core');
const AccCore = require('opentok-accelerator-core');
```

@@ -77,5 +77,6 @@ ```javascript

* @param {String} type - 'camera' or 'screen'
* @param {*} data - Parsed connection data associated with the stream
* @param {*} data - Parsed stream connection data (subscriber only)
* @param {Object} stream - The new stream (subscriber only)
*/
streamContainers(pubSub, type, data){
streamContainers(pubSub, type, data, stream){
return {

@@ -143,3 +144,3 @@ publisher: {

```javascript
otCore.init(options);
const otCore = new AccCore(options);
```

@@ -256,5 +257,4 @@ Connect to the session:

- Creating a messaging or signaling layer using OpenTok sessions.
- Running multiple sessions simultaneously in the same application instance.
- Managing presence without any audio/video, using OpenTok sessions.
You can also use Accelerator Core and the SDK Wrapper in conjunction with each other. While Core is a singleton, the SDK Wrapper provides a constructor, meaning that multiple instances may be created and used at the same time.
You can also use Accelerator Core and the SDK Wrapper in conjunction with each other since multiple instances of each may be created and used simultaneously.

@@ -7,3 +7,3 @@ /* Let CRA handle linting for sample app */

import otCore from './ot-core/core.js';
import Core from './ot-core/core';
import logo from './logo.svg';

@@ -14,2 +14,3 @@ import config from './config.json';

let otCore;
const otCoreOptions = {

@@ -45,3 +46,3 @@ credentials: {

screenSharing: {
extensionID: 'plocfffmbcclpdifaikiikgplfnepkpo',
extensionID: config.extensionID,
annotation: true,

@@ -129,3 +130,3 @@ externalWindow: false,

componentDidMount() {
otCore.init(otCoreOptions);
otCore = new Core(otCoreOptions);
otCore.connect().then(() => this.setState({ connected: true }));

@@ -148,3 +149,3 @@ const events = [

otCore.startCall()
.then(({ publisher, publishers, subscribers, meta }) => {
.then(({ publishers, subscribers, meta }) => {
this.setState({ publishers, subscribers, meta, active: true });

@@ -155,3 +156,3 @@ }).catch(error => console.log(error));

endCall() {
otCore.endCall()
otCore.endCall();
this.setState({ active: false });

@@ -158,0 +159,0 @@ }

@@ -19,2 +19,4 @@ ![logo](https://raw.githubusercontent.com/opentok/accelerator-core/master/tokbox-logo.png)

const OpenTokSDK = require('opentok-accelerator-core').OpenTokSDK;
// or with ES6 Modules
import { OpenTokSDK } from 'opentok-accelerator-core';

@@ -21,0 +23,0 @@ const credentials = {

/* global OT */
/** Dependencies */
const state = require('./state');
const { CoreError } = require('./errors');
const { dom, path, pathOr, properCase } = require('./util');
const { message, logAnalytics, logAction, logVariation } = require('./logging');
const { message, logAction, logVariation } = require('./logging');
/** Module variables */
let session;
let accPack;
let callProperties;
let screenProperties;
let streamContainers;
let autoSubscribe;
let connectionLimit;
let active = false;
/**

@@ -33,304 +21,299 @@ * Default UI propties

};
class Communication {
constructor(options) {
this.validateOptions(options);
this.setSession();
this.createEventListeners();
}
/**
* Trigger an event through the API layer
* @param {String} event - The name of the event
* @param {*} [data]
*/
const triggerEvent = (event, data) => accPack.triggerEvent(event, data);
/**
* Determine whether or not the party is able to join the call based on
* the specified connection limit, if any.
* @return {Boolean}
*/
const ableToJoin = () => {
if (!connectionLimit) {
return true;
validateOptions = (options) => {
const requiredOptions = ['core', 'state', 'analytics'];
requiredOptions.forEach((option) => {
if (!options[option]) {
throw new CoreError(`${option} is a required option.`, 'invalidParameters');
}
});
const { callProperties, screenProperties, autoSubscribe } = options;
this.active = false;
this.core = options.core;
this.state = options.state;
this.analytics = options.analytics;
this.streamContainers = options.streamContainers;
this.callProperties = Object.assign({}, defaultCallProperties, callProperties);
this.connectionLimit = options.connectionLimit || null;
this.autoSubscribe = options.hasOwnProperty('autoSubscribe') ? autoSubscribe : true;
this.screenProperties = Object.assign({}, defaultCallProperties, { videoSource: 'window' }, screenProperties);
}
// Not using the session here since we're concerned with number of active publishers
const connections = Object.values(state.getStreams()).filter(s => s.videoType === 'camera');
return connections.length < connectionLimit;
};
/**
* Trigger an event through the API layer
* @param {String} event - The name of the event
* @param {*} [data]
*/
triggerEvent = (event, data) => this.core.triggerEvent(event, data);
/**
* Create a camera publisher object
* @param {Object} publisherProperties
* @returns {Promise} <resolve: Object, reject: Error>
*/
const createPublisher = publisherProperties =>
new Promise((resolve, reject) => {
// TODO: Handle adding 'name' option to props
const props = Object.assign({}, callProperties, publisherProperties);
// TODO: Figure out how to handle common vs package-specific options
// ^^^ This may already be available through package options
const container = dom.element(streamContainers('publisher', 'camera'));
const publisher = OT.initPublisher(container, props, (error) => {
error ? reject(error) : resolve(publisher);
/**
* Determine whether or not the party is able to join the call based on
* the specified connection limit, if any.
* @return {Boolean}
*/
ableToJoin = () => {
const { connectionLimit, state } = this;
if (!connectionLimit) {
return true;
}
// Not using the session here since we're concerned with number of active publishers
const connections = Object.values(state.getStreams()).filter(s => s.videoType === 'camera');
return connections.length < connectionLimit;
};
/**
* Create a camera publisher object
* @param {Object} publisherProperties
* @returns {Promise} <resolve: Object, reject: Error>
*/
createPublisher = (publisherProperties) => {
const { callProperties, streamContainers } = this;
return new Promise((resolve, reject) => {
// TODO: Handle adding 'name' option to props
const props = Object.assign({}, callProperties, publisherProperties);
// TODO: Figure out how to handle common vs package-specific options
// ^^^ This may already be available through package options
const container = dom.element(streamContainers('publisher', 'camera'));
const publisher = OT.initPublisher(container, props, (error) => {
error ? reject(error) : resolve(publisher);
});
});
});
}
/**
* Publish the local camera stream and update state
* @param {Object} publisherProperties
* @returns {Promise} <resolve: empty, reject: Error>
*/
publish = (publisherProperties) => {
const { analytics, state, createPublisher, session, triggerEvent } = this;
return new Promise((resolve, reject) => {
const onPublish = publisher => (error) => {
if (error) {
reject(error);
analytics.log(logAction.startCall, logVariation.fail);
} else {
analytics.log(logAction.startCall, logVariation.success);
state.addPublisher('camera', publisher);
resolve(publisher);
}
};
/**
* Publish the local camera stream and update state
* @param {Object} publisherProperties
* @returns {Promise} <resolve: empty, reject: Error>
*/
const publish = publisherProperties =>
new Promise((resolve, reject) => {
const onPublish = publisher => (error) => {
if (error) {
const publishToSession = publisher => session.publish(publisher, onPublish(publisher));
const handleError = (error) => {
analytics.log(logAction.startCall, logVariation.fail);
const errorMessage = error.code === 1010 ? 'Check your network connection' : error.message;
triggerEvent('error', errorMessage);
reject(error);
logAnalytics(logAction.startCall, logVariation.fail);
} else {
logAnalytics(logAction.startCall, logVariation.success);
state.addPublisher('camera', publisher);
resolve(publisher);
}
};
};
const publishToSession = publisher => session.publish(publisher, onPublish(publisher));
createPublisher(publisherProperties)
.then(publishToSession)
.catch(handleError);
});
}
const handleError = (error) => {
logAnalytics(logAction.startCall, logVariation.fail);
const errorMessage = error.code === 1010 ? 'Check your network connection' : error.message;
triggerEvent('error', errorMessage);
reject(error);
};
createPublisher(publisherProperties)
.then(publishToSession)
.catch(handleError);
});
/**
* Subscribe to a stream and update the state
* @param {Object} stream - An OpenTok stream object
* @returns {Promise} <resolve: empty reject: Error >
*/
const subscribe = stream =>
new Promise((resolve, reject) => {
let connectionData;
logAnalytics(logAction.subscribe, logVariation.attempt);
const streamMap = state.getStreamMap();
const { streamId } = stream;
if (streamMap[streamId]) {
// Are we already subscribing to the stream?
resolve();
} else {
/**
* Subscribe to a stream and update the state
* @param {Object} stream - An OpenTok stream object
* @returns {Promise} <resolve: Object, reject: Error >
*/
subscribe = (stream) => {
const { analytics, state, streamContainers, session, triggerEvent, callProperties, screenProperties } = this;
return new Promise((resolve, reject) => {
let connectionData;
analytics.log(logAction.subscribe, logVariation.attempt);
const streamMap = state.getStreamMap();
const { streamId } = stream;
// No videoType indicates SIP https://tokbox.com/developer/guides/sip/
const type = pathOr('sip', 'videoType', stream);
try {
connectionData = JSON.parse(path(['connection', 'data'], stream) || null);
} catch (e) {
connectionData = path(['connection', 'data'], stream);
if (streamMap[streamId]) {
// Are we already subscribing to the stream?
const { subscribers } = state.all();
resolve(subscribers[type][streamMap[streamId]]);
} else {
try {
connectionData = JSON.parse(path(['connection', 'data'], stream) || null);
} catch (e) {
connectionData = path(['connection', 'data'], stream);
}
const container = dom.query(streamContainers('subscriber', type, connectionData, stream));
const options = type === 'camera' || type === 'sip' ? callProperties : screenProperties;
const subscriber = session.subscribe(stream, container, options, (error) => {
if (error) {
analytics.log(logAction.subscribe, logVariation.fail);
reject(error);
} else {
state.addSubscriber(subscriber);
triggerEvent(`subscribeTo${properCase(type)}`, Object.assign({}, { subscriber }, state.all()));
type === 'screen' && triggerEvent('startViewingSharedScreen', subscriber); // Legacy event
analytics.log(logAction.subscribe, logVariation.success);
resolve(subscriber);
}
});
}
const container = dom.query(streamContainers('subscriber', type, connectionData, streamId));
const options = type === 'camera' || type === 'sip' ? callProperties : screenProperties;
const subscriber = session.subscribe(stream, container, options, (error) => {
if (error) {
logAnalytics(logAction.subscribe, logVariation.fail);
reject(error);
} else {
state.addSubscriber(subscriber);
triggerEvent(`subscribeTo${properCase(type)}`, Object.assign({}, { subscriber }, state.all()));
type === 'screen' && triggerEvent('startViewingSharedScreen', subscriber); // Legacy event
logAnalytics(logAction.subscribe, logVariation.success);
resolve();
}
});
}
});
});
}
/**
* Unsubscribe from a stream and update the state
* @param {Object} subscriber - An OpenTok subscriber object
* @returns {Promise} <resolve: empty>
*/
const unsubscribe = subscriber =>
new Promise((resolve) => {
logAnalytics(logAction.unsubscribe, logVariation.attempt);
const type = path('stream.videoType', subscriber);
state.removeSubscriber(type, subscriber);
session.unsubscribe(subscriber);
logAnalytics(logAction.unsubscribe, logVariation.success);
resolve();
});
/**
* Unsubscribe from a stream and update the state
* @param {Object} subscriber - An OpenTok subscriber object
* @returns {Promise} <resolve: empty>
*/
unsubscribe = (subscriber) => {
const { analytics, session, state } = this;
return new Promise((resolve) => {
analytics.log(logAction.unsubscribe, logVariation.attempt);
const type = path('stream.videoType', subscriber);
state.removeSubscriber(type, subscriber);
session.unsubscribe(subscriber);
analytics.log(logAction.unsubscribe, logVariation.success);
resolve();
});
}
/**
* Ensure all required options are received
* @param {Object} options
*/
const validateOptions = (options) => {
const requiredOptions = ['accPack'];
requiredOptions.forEach((option) => {
if (!options[option]) {
throw new CoreError(`${option} is a required option.`, 'invalidParameters');
}
});
/**
* Set session in module scope
*/
setSession = () => {
this.session = this.state.getSession();
}
accPack = options.accPack;
streamContainers = options.streamContainers;
callProperties = Object.assign({}, defaultCallProperties, options.callProperties);
connectionLimit = options.connectionLimit || null;
autoSubscribe = options.hasOwnProperty('autoSubscribe') ? options.autoSubscribe : true;
screenProperties = Object.assign({}, defaultCallProperties, { videoSource: 'window' }, options.screenProperties);
};
/**
* Subscribe to new stream unless autoSubscribe is set to false
* @param {Object} stream
*/
onStreamCreated = ({ stream }) => this.active && this.autoSubscribe && this.subscribe(stream);
/**
* Set session in module scope
*/
const setSession = () => {
session = state.getSession();
};
/**
* Update state and trigger corresponding event(s) when stream is destroyed
* @param {Object} stream
*/
onStreamDestroyed = ({ stream }) => {
const { state, triggerEvent } = this;
state.removeStream(stream);
const type = pathOr('sip', 'videoType', stream);
type === 'screen' && triggerEvent('endViewingSharedScreen'); // Legacy event
triggerEvent(`unsubscribeFrom${properCase(type)}`, state.getPubSub());
}
/**
* Subscribe to new stream unless autoSubscribe is set to false
* @param {Object} stream
*/
const onStreamCreated = ({ stream }) => active && autoSubscribe && subscribe(stream);
/**
* Listen for API-level events
*/
createEventListeners = () => {
const { core, onStreamCreated, onStreamDestroyed } = this;
core.on('streamCreated', onStreamCreated);
core.on('streamDestroyed', onStreamDestroyed);
}
/**
* Update state and trigger corresponding event(s) when stream is destroyed
* @param {Object} stream
*/
const onStreamDestroyed = ({ stream }) => {
state.removeStream(stream);
const type = pathOr('sip', 'videoType', stream);
type === 'screen' && triggerEvent('endViewingSharedScreen'); // Legacy event
triggerEvent(`unsubscribeFrom${properCase(type)}`, state.getPubSub());
};
/**
* Start publishing the local camera feed and subscribing to streams in the session
* @param {Object} publisherProperties
* @returns {Promise} <resolve: Object, reject: Error>
*/
startCall = (publisherProperties) => {
const { analytics, state, subscribe, ableToJoin, triggerEvent, autoSubscribe, publish } = this;
return new Promise((resolve, reject) => { // eslint-disable-line consistent-return
analytics.log(logAction.startCall, logVariation.attempt);
/**
* Listen for API-level events
*/
const createEventListeners = () => {
accPack.on('streamCreated', onStreamCreated);
accPack.on('streamDestroyed', onStreamDestroyed);
};
/**
* Determine if we're able to join the session based on an existing connection limit
*/
if (!ableToJoin()) {
const errorMessage = 'Session has reached its connection limit';
triggerEvent('error', errorMessage);
analytics.log(logAction.startCall, logVariation.fail);
return reject(new CoreError(errorMessage, 'connectionLimit'));
}
/**
* Start publishing the local camera feed and subscribing to streams in the session
* @param {Object} publisherProperties
* @returns {Promise} <resolve: Object, reject: Error>
*/
const startCall = publisherProperties =>
new Promise((resolve, reject) => { // eslint-disable-line consistent-return
logAnalytics(logAction.startCall, logVariation.attempt);
/**
* Subscribe to any streams that existed before we start the call from our side.
*/
const subscribeToInitialStreams = (publisher) => {
// Get an array of initial subscription promises
const initialSubscriptions = () => {
if (autoSubscribe) {
const streams = state.getStreams();
return Object.keys(streams).map(id => subscribe(streams[id]));
}
return [Promise.resolve()];
};
/**
* Determine if we're able to join the session based on an existing connection limit
*/
if (!ableToJoin()) {
const errorMessage = 'Session has reached its connection limit';
triggerEvent('error', errorMessage);
logAnalytics(logAction.startCall, logVariation.fail);
return reject(new CoreError(errorMessage, 'connectionLimit'));
}
// Handle success
const onSubscribeToAll = () => {
const pubSubData = Object.assign({}, state.getPubSub(), { publisher });
triggerEvent('startCall', pubSubData);
this.active = true;
resolve(pubSubData);
};
/**
* Subscribe to any streams that existed before we start the call from our side.
*/
const subscribeToInitialStreams = (publisher) => {
// Get an array of initial subscription promises
const initialSubscriptions = () => {
if (autoSubscribe) {
const streams = state.getStreams();
return Object.keys(streams).map(id => subscribe(streams[id]));
}
return [Promise.resolve()];
};
// Handle error
const onError = (reason) => {
message(`Failed to subscribe to all existing streams: ${reason}`);
// We do not reject here in case we still successfully publish to the session
resolve(Object.assign({}, this.state.getPubSub(), { publisher }));
};
// Handle success
const onSubscribeToAll = () => {
const pubSubData = Object.assign({}, state.getPubSub(), { publisher });
triggerEvent('startCall', pubSubData);
active = true;
resolve(pubSubData);
Promise.all(initialSubscriptions())
.then(onSubscribeToAll)
.catch(onError);
};
// Handle error
const onError = (reason) => {
message(`Failed to subscribe to all existing streams: ${reason}`);
// We do not reject here in case we still successfully publish to the session
resolve(Object.assign({}, state.getPubSub(), { publisher }));
};
publish(publisherProperties)
.then(subscribeToInitialStreams)
.catch(reject);
});
}
Promise.all(initialSubscriptions())
.then(onSubscribeToAll)
.catch(onError);
};
/**
* Stop publishing and unsubscribe from all streams
*/
endCall = () => {
const { analytics, state, session, unsubscribe, triggerEvent } = this;
analytics.log(logAction.endCall, logVariation.attempt);
const { publishers, subscribers } = state.getPubSub();
const unpublish = publisher => session.unpublish(publisher);
Object.values(publishers.camera).forEach(unpublish);
Object.values(publishers.screen).forEach(unpublish);
// TODO Promise.all for unsubsribing
Object.values(subscribers.camera).forEach(unsubscribe);
Object.values(subscribers.screen).forEach(unsubscribe);
state.removeAllPublishers();
this.active = false;
triggerEvent('endCall');
analytics.log(logAction.endCall, logVariation.success);
}
publish(publisherProperties)
.then(subscribeToInitialStreams)
.catch(reject);
});
/**
* Enable/disable local audio or video
* @param {String} source - 'audio' or 'video'
* @param {Boolean} enable
*/
enableLocalAV = (id, source, enable) => {
const method = `publish${properCase(source)}`;
const { publishers } = this.state.getPubSub();
publishers.camera[id][method](enable);
}
/**
* Stop publishing and unsubscribe from all streams
*/
const endCall = () => {
logAnalytics(logAction.endCall, logVariation.attempt);
const { publishers, subscribers } = state.getPubSub();
const unpublish = publisher => session.unpublish(publisher);
Object.values(publishers.camera).forEach(unpublish);
Object.values(publishers.screen).forEach(unpublish);
// TODO Promise.all for unsubsribing
Object.values(subscribers.camera).forEach(unsubscribe);
Object.values(subscribers.screen).forEach(unsubscribe);
state.removeAllPublishers();
active = false;
triggerEvent('endCall');
logAnalytics(logAction.endCall, logVariation.success);
};
/**
* Enable/disable remote audio or video
* @param {String} subscriberId
* @param {String} source - 'audio' or 'video'
* @param {Boolean} enable
*/
enableRemoteAV = (subscriberId, source, enable) => {
const method = `subscribeTo${properCase(source)}`;
const { subscribers } = this.state.getPubSub();
const subscriber = subscribers.camera[subscriberId] || subscribers.sip[subscriberId];
subscriber[method](enable);
}
/**
* Enable/disable local audio or video
* @param {String} source - 'audio' or 'video'
* @param {Boolean} enable
*/
const enableLocalAV = (id, source, enable) => {
const method = `publish${properCase(source)}`;
const { publishers } = state.getPubSub();
publishers.camera[id][method](enable);
};
}
/**
* Enable/disable remote audio or video
* @param {String} subscriberId
* @param {String} source - 'audio' or 'video'
* @param {Boolean} enable
*/
const enableRemoteAV = (subscriberId, source, enable) => {
const method = `subscribeTo${properCase(source)}`;
const { subscribers } = state.getPubSub();
const subscriber = subscribers.camera[subscriberId] || subscribers.sip[subscriberId];
subscriber[method](enable);
};
/**
* Initialize the communication component
* @param {Object} options
* @param {Object} options.accPack
* @param {Number} options.connectionLimit
* @param {Function} options.streamContainer
*/
const init = options =>
new Promise((resolve) => {
validateOptions(options);
setSession();
createEventListeners();
resolve();
});
/** Exports */
module.exports = {
init,
startCall,
endCall,
subscribe,
unsubscribe,
enableLocalAV,
enableRemoteAV,
};
export default Communication;

@@ -6,5 +6,5 @@ /* global OT */

const util = require('./util');
const internalState = require('./state');
const State = require('./state').default;
const accPackEvents = require('./events');
const communication = require('./communication');
const Communication = require('./communication').default;
const OpenTokSDK = require('./sdk-wrapper/sdkWrapper');

@@ -14,9 +14,8 @@ const { CoreError } = require('./errors');

message,
initLogAnalytics,
logAnalytics,
Analytics,
logAction,
logVariation,
updateLogAnalytics,
} = require('./logging');
/**

@@ -28,40 +27,13 @@ * Helper methods

/**
* Individual Accelerator Packs
* Ensure that we have the required credentials
* @param {Object} credentials
* @param {String} credentials.apiKey
* @param {String} credentials.sessionId
* @param {String} credentials.token
*/
let textChat; // eslint-disable-line no-unused-vars
let screenSharing; // eslint-disable-line no-unused-vars
let annotation;
let archiving; // eslint-disable-line no-unused-vars
/**
* Get access to an accelerator pack
* @param {String} packageName - textChat, screenSharing, annotation, or archiving
* @returns {Object} The instance of the accelerator pack
*/
const getAccPack = (packageName) => {
logAnalytics(logAction.getAccPack, logVariation.attempt);
const packages = {
textChat,
screenSharing,
annotation,
archiving,
};
logAnalytics(logAction.getAccPack, logVariation.success);
return packages[packageName];
};
/** Eventing */
const eventListeners = {};
/**
* Register events that can be listened to be other components/modules
* @param {array | string} events - A list of event names. A single event may
* also be passed as a string.
*/
const registerEvents = (events) => {
const eventList = Array.isArray(events) ? events : [events];
eventList.forEach((event) => {
if (!eventListeners[event]) {
eventListeners[event] = new Set();
const validateCredentials = (credentials = []) => {
const required = ['apiKey', 'sessionId', 'token'];
required.forEach((credential) => {
if (!credentials[credential]) {
throw new CoreError(`${credential} is a required credential`, 'invalidParameters');
}

@@ -71,555 +43,610 @@ });

/**
* Register a callback for a specific event or pass an object with
* with event => callback key/value pairs to register listeners for
* multiple events.
* @param {String | Object} event - The name of the event
* @param {Function} callback
*/
const on = (event, callback) => {
// logAnalytics(logAction.on, logVariation.attempt);
if (typeof event === 'object') {
Object.keys(event).forEach((eventName) => {
on(eventName, event[eventName]);
});
return;
}
const eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
message(`${event} is not a registered event.`);
// logAnalytics(logAction.on, logVariation.fail);
} else {
eventCallbacks.add(callback);
// logAnalytics(logAction.on, logVariation.success);
}
};
/**
* Remove a callback for a specific event. If no parameters are passed,
* all event listeners will be removed.
* @param {String} event - The name of the event
* @param {Function} callback
*/
const off = (event, callback) => {
// logAnalytics(logAction.off, logVariation.attempt);
if (!event && !callback) {
Object.keys(eventListeners).forEach((eventType) => {
eventListeners[eventType].clear();
});
} else {
const eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
// logAnalytics(logAction.off, logVariation.fail);
message(`${event} is not a registered event.`);
} else {
eventCallbacks.delete(callback);
// logAnalytics(logAction.off, logVariation.success);
class AccCore {
constructor(options) {
// Options/credentials validation
if (!options) {
throw new CoreError('Missing options required for initialization', 'invalidParameters');
}
}
};
const { credentials } = options;
validateCredentials(options.credentials);
/**
* Trigger an event and fire all registered callbacks
* @param {String} event - The name of the event
* @param {*} data - Data to be passed to callback functions
*/
const triggerEvent = (event, data) => {
const eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
registerEvents(event);
message(`${event} has been registered as a new event.`);
} else {
eventCallbacks.forEach(callback => callback(data, event));
}
};
// Init analytics
this.analytics = new Analytics(window.location.origin, credentials.sessionId, null, credentials.apiKey);
this.analytics.log(logAction.init, logVariation.attempt);
/**
* Get the current OpenTok session object
* @returns {Object}
*/
const getSession = internalState.getSession;
// Create session, setup state
this.session = OT.initSession(credentials.apiKey, credentials.sessionId);
this.internalState = new State();
this.internalState.setSession(this.session);
this.internalState.setCredentials(credentials);
this.internalState.setOptions(options);
// Individual accelerator packs
this.communication = null;
this.textChat = null;
this.screenSharing = null;
this.annotation = null;
this.archiving = null;
/**
* Returns the current OpenTok session credentials
* @returns {Object}
*/
const getCredentials = internalState.getCredentials;
// Create internal event listeners
this.createEventListeners(this.session, options);
/**
* Returns the options used for initialization
* @returns {Object}
*/
const getOptions = internalState.getOptions;
this.analytics.log(logAction.init, logVariation.success);
}
const createEventListeners = (session, options) => {
Object.keys(accPackEvents).forEach(type => registerEvents(accPackEvents[type]));
// OpenTok SDK Wrapper
static OpenTokSDK = OpenTokSDK;
// Expose utility methods
static util = util
/**
* If using screen sharing + annotation in an external window, the screen sharing
* package will take care of calling annotation.start() and annotation.linkCanvas()
* Get access to an accelerator pack
* @param {String} packageName - textChat, screenSharing, annotation, or archiving
* @returns {Object} The instance of the accelerator pack
*/
const usingAnnotation = path('screenSharing.annotation', options);
const internalAnnotation = usingAnnotation && !path('screenSharing.externalWindow', options);
getAccPack = (packageName) => {
const { analytics, textChat, screenSharing, annotation, archiving } = this;
analytics.log(logAction.getAccPack, logVariation.attempt);
const packages = {
textChat,
screenSharing,
annotation,
archiving,
};
analytics.log(logAction.getAccPack, logVariation.success);
return packages[packageName];
}
/** Eventing */
/**
* Wrap session events and update internalState when streams are created
* or destroyed
* Register events that can be listened to be other components/modules
* @param {array | string} events - A list of event names. A single event may
* also be passed as a string.
*/
accPackEvents.session.forEach((eventName) => {
session.on(eventName, (event) => {
if (eventName === 'streamCreated') { internalState.addStream(event.stream); }
if (eventName === 'streamDestroyed') { internalState.removeStream(event.stream); }
triggerEvent(eventName, event);
registerEvents = (events) => {
const { eventListeners } = this;
const eventList = Array.isArray(events) ? events : [events];
eventList.forEach((event) => {
if (!eventListeners[event]) {
eventListeners[event] = new Set();
}
});
});
};
if (usingAnnotation) {
on('subscribeToScreen', ({ subscriber }) => {
annotation.start(getSession())
.then(() => {
const absoluteParent = dom.query(path('annotation.absoluteParent.subscriber', options));
const linkOptions = absoluteParent ? { absoluteParent } : null;
annotation.linkCanvas(subscriber, subscriber.element.parentElement, linkOptions);
});
});
on('unsubscribeFromScreen', () => {
annotation.end();
});
}
on('startScreenSharing', (publisher) => {
internalState.addPublisher('screen', publisher);
triggerEvent('startScreenShare', Object.assign({}, { publisher }, internalState.getPubSub()));
if (internalAnnotation) {
annotation.start(getSession())
.then(() => {
const absoluteParent = dom.query(path('annotation.absoluteParent.publisher', options));
const linkOptions = absoluteParent ? { absoluteParent } : null;
annotation.linkCanvas(publisher, publisher.element.parentElement, linkOptions);
});
/**
* Register a callback for a specific event or pass an object with
* with event => callback key/value pairs to register listeners for
* multiple events.
* @param {String | Object} event - The name of the event
* @param {Function} callback
*/
on = (event, callback) => {
const { eventListeners, on } = this;
// analytics.log(logAction.on, logVariation.attempt);
if (typeof event === 'object') {
Object.keys(event).forEach((eventName) => {
on(eventName, event[eventName]);
});
return;
}
});
on('endScreenSharing', (publisher) => {
// delete publishers.screen[publisher.id];
internalState.removePublisher('screen', publisher);
triggerEvent('endScreenShare', internalState.getPubSub());
if (usingAnnotation) {
annotation.end();
const eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
message(`${event} is not a registered event.`);
// analytics.log(logAction.on, logVariation.fail);
} else {
eventCallbacks.add(callback);
// analytics.log(logAction.on, logVariation.success);
}
});
};
const setupExternalAnnotation = () =>
annotation.start(getSession(), {
screensharing: true,
});
const linkAnnotation = (pubSub, annotationContainer, externalWindow) => {
annotation.linkCanvas(pubSub, annotationContainer, {
externalWindow,
});
if (externalWindow) {
// Add subscribers to the external window
const streams = internalState.getStreams();
const cameraStreams = Object.keys(streams).reduce((acc, streamId) => {
const stream = streams[streamId];
return stream.videoType === 'camera' || stream.videoType === 'sip' ? acc.concat(stream) : acc;
}, []);
cameraStreams.forEach(annotation.addSubscriberToExternalWindow);
}
};
const initPackages = () => {
logAnalytics(logAction.initPackages, logVariation.attempt);
const session = getSession();
const options = getOptions();
/**
* Try to require a package. If 'require' is unavailable, look for
* the package in global scope. A switch ttatement is used because
* webpack and Browserify aren't able to resolve require statements
* that use variable names.
* @param {String} packageName - The name of the npm package
* @param {String} globalName - The name of the package if exposed on global/window
* @returns {Object}
* Remove a callback for a specific event. If no parameters are passed,
* all event listeners will be removed.
* @param {String} event - The name of the event
* @param {Function} callback
*/
const optionalRequire = (packageName, globalName) => {
let result;
/* eslint-disable global-require, import/no-extraneous-dependencies, import/no-unresolved */
try {
switch (packageName) {
case 'opentok-text-chat':
result = require('opentok-text-chat');
break;
case 'opentok-screen-sharing':
result = require('opentok-screen-sharing');
break;
case 'opentok-annotation':
result = require('opentok-annotation');
break;
case 'opentok-archiving':
result = require('opentok-archiving');
break;
default:
break;
off = (event, callback) => {
const { eventListeners } = this;
// analytics.log(logAction.off, logVariation.attempt);
if (!event && !callback) {
Object.keys(eventListeners).forEach((eventType) => {
eventListeners[eventType].clear();
});
} else {
const eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
// analytics.log(logAction.off, logVariation.fail);
message(`${event} is not a registered event.`);
} else {
eventCallbacks.delete(callback);
// analytics.log(logAction.off, logVariation.success);
}
/* eslint-enable global-require */
} catch (error) {
result = window[globalName];
}
if (!result) {
logAnalytics(logAction.initPackages, logVariation.fail);
throw new CoreError(`Could not load ${packageName}`, 'missingDependency');
}
return result;
};
}
const availablePackages = {
textChat() {
return optionalRequire('opentok-text-chat', 'TextChatAccPack');
},
screenSharing() {
return optionalRequire('opentok-screen-sharing', 'ScreenSharingAccPack');
},
annotation() {
return optionalRequire('opentok-annotation', 'AnnotationAccPack');
},
archiving() {
return optionalRequire('opentok-archiving', 'ArchivingAccPack');
},
};
const packages = {};
(path('packages', options) || []).forEach((acceleratorPack) => {
if (availablePackages[acceleratorPack]) { // eslint-disable-next-line no-param-reassign
packages[properCase(acceleratorPack)] = availablePackages[acceleratorPack]();
/**
* Trigger an event and fire all registered callbacks
* @param {String} event - The name of the event
* @param {*} data - Data to be passed to callback functions
*/
triggerEvent = (event, data) => {
const { eventListeners, registerEvents } = this;
const eventCallbacks = eventListeners[event];
if (!eventCallbacks) {
registerEvents(event);
message(`${event} has been registered as a new event.`);
} else {
message(`${acceleratorPack} is not a valid accelerator pack`);
eventCallbacks.forEach(callback => callback(data, event));
}
});
};
/**
* Get containers for streams, controls, and the chat widget
* Get the current OpenTok session object
* @returns {Object}
*/
const getDefaultContainer = pubSub => document.getElementById(`${pubSub}Container`);
const getContainerElements = () => {
// Need to use path to check for null values
const controls = pathOr('#videoControls', 'controlsContainer', options);
const chat = pathOr('#chat', 'textChat.container', options);
const stream = pathOr(getDefaultContainer, 'streamContainers', options);
return { stream, controls, chat };
};
/** *** *** *** *** */
getSession = () => this.internalState.getSession()
/**
* Return options for the specified package
* @param {String} packageName
* Returns the current OpenTok session credentials
* @returns {Object}
*/
const packageOptions = (packageName) => {
getCredentials = () => this.internalState.getCredentials()
/**
* Returns the options used for initialization
* @returns {Object}
*/
getOptions = () => this.internalState.getOptions()
createEventListeners = (session, options) => {
this.eventListeners = {};
const { registerEvents, internalState, triggerEvent, on, getSession } = this;
Object.keys(accPackEvents).forEach(type => registerEvents(accPackEvents[type]));
/**
* Methods to expose to accelerator packs
* If using screen sharing + annotation in an external window, the screen sharing
* package will take care of calling annotation.start() and annotation.linkCanvas()
*/
const accPack = {
registerEventListener: on, // Legacy option
on,
registerEvents,
triggerEvent,
setupExternalAnnotation,
linkAnnotation,
};
const usingAnnotation = path('screenSharing.annotation', options);
const internalAnnotation = usingAnnotation && !path('screenSharing.externalWindow', options);
/**
* If options.controlsContainer/containers.controls is null,
* accelerator packs should not append their controls.
* Wrap session events and update internalState when streams are created
* or destroyed
*/
const containers = getContainerElements();
const appendControl = !!containers.controls;
const controlsContainer = containers.controls; // Legacy option
const streamContainers = containers.stream;
const baseOptions = { session, accPack, controlsContainer, appendControl, streamContainers };
accPackEvents.session.forEach((eventName) => {
session.on(eventName, (event) => {
if (eventName === 'streamCreated') { internalState.addStream(event.stream); }
if (eventName === 'streamDestroyed') { internalState.removeStream(event.stream); }
triggerEvent(eventName, event);
});
});
switch (packageName) {
/* beautify ignore:start */
case 'communication': {
return Object.assign({}, baseOptions, options.communication);
if (usingAnnotation) {
on('subscribeToScreen', ({ subscriber }) => {
this.annotation.start(getSession())
.then(() => {
const absoluteParent = dom.query(path('annotation.absoluteParent.subscriber', options));
const linkOptions = absoluteParent ? { absoluteParent } : null;
this.annotation.linkCanvas(subscriber, subscriber.element.parentElement, linkOptions);
});
});
on('unsubscribeFromScreen', () => {
this.annotation.end();
});
}
on('startScreenSharing', (publisher) => {
internalState.addPublisher('screen', publisher);
triggerEvent('startScreenShare', Object.assign({}, { publisher }, internalState.getPubSub()));
if (internalAnnotation) {
this.annotation.start(getSession())
.then(() => {
const absoluteParent = dom.query(path('annotation.absoluteParent.publisher', options));
const linkOptions = absoluteParent ? { absoluteParent } : null;
this.annotation.linkCanvas(publisher, publisher.element.parentElement, linkOptions);
});
}
case 'textChat': {
const textChatOptions = {
textChatContainer: path('textChat.container', options),
waitingMessage: path('textChat.waitingMessage', options),
sender: { alias: path('textChat.name', options) },
alwaysOpen: path('textChat.alwaysOpen', options),
};
return Object.assign({}, baseOptions, textChatOptions);
});
on('endScreenSharing', (publisher) => {
// delete publishers.screen[publisher.id];
internalState.removePublisher('screen', publisher);
triggerEvent('endScreenShare', internalState.getPubSub());
if (usingAnnotation) {
this.annotation.end();
}
case 'screenSharing': {
const screenSharingContainer = { screenSharingContainer: streamContainers };
return Object.assign({}, baseOptions, screenSharingContainer, options.screenSharing);
}
case 'annotation': {
return Object.assign({}, baseOptions, options.annotation);
}
case 'archiving': {
return Object.assign({}, baseOptions, options.archiving);
}
default:
return {};
/* beautify ignore:end */
}
};
});
}
/** Create instances of each package */
// eslint-disable-next-line global-require,import/no-extraneous-dependencies
communication.init(packageOptions('communication'));
textChat = packages.TextChat ? new packages.TextChat(packageOptions('textChat')) : null;
screenSharing = packages.ScreenSharing ? new packages.ScreenSharing(packageOptions('screenSharing')) : null;
annotation = packages.Annotation ? new packages.Annotation(packageOptions('annotation')) : null;
archiving = packages.Archiving ? new packages.Archiving(packageOptions('archiving')) : null;
setupExternalAnnotation = () => this.annotation.start(this.getSession(), { screensharing: true })
logAnalytics(logAction.initPackages, logVariation.success);
};
linkAnnotation = (pubSub, annotationContainer, externalWindow) => {
const { annotation, internalState } = this;
annotation.linkCanvas(pubSub, annotationContainer, {
externalWindow,
});
/**
* Ensures that we have the required credentials
* @param {Object} credentials
* @param {String} credentials.apiKey
* @param {String} credentials.sessionId
* @param {String} credentials.token
*/
const validateCredentials = (credentials = []) => {
const required = ['apiKey', 'sessionId', 'token'];
required.forEach((credential) => {
if (!credentials[credential]) {
throw new CoreError(`${credential} is a required credential`, 'invalidParameters');
if (externalWindow) {
// Add subscribers to the external window
const streams = internalState.getStreams();
const cameraStreams = Object.keys(streams).reduce((acc, streamId) => {
const stream = streams[streamId];
return stream.videoType === 'camera' || stream.videoType === 'sip' ? acc.concat(stream) : acc;
}, []);
cameraStreams.forEach(annotation.addSubscriberToExternalWindow);
}
});
};
}
/**
* Connect to the session
* @returns {Promise} <resolve: -, reject: Error>
*/
const connect = () =>
new Promise((resolve, reject) => {
logAnalytics(logAction.connect, logVariation.attempt);
initPackages = () => {
const { analytics, getSession, getOptions, internalState } = this;
const { on, registerEvents, setupExternalAnnotation, triggerEvent, linkAnnotation } = this;
analytics.log(logAction.initPackages, logVariation.attempt);
const session = getSession();
const { token } = getCredentials();
session.connect(token, (error) => {
if (error) {
message(error);
logAnalytics(logAction.connect, logVariation.fail);
return reject(error);
const options = getOptions();
/**
* Try to require a package. If 'require' is unavailable, look for
* the package in global scope. A switch ttatement is used because
* webpack and Browserify aren't able to resolve require statements
* that use variable names.
* @param {String} packageName - The name of the npm package
* @param {String} globalName - The name of the package if exposed on global/window
* @returns {Object}
*/
const optionalRequire = (packageName, globalName) => {
let result;
/* eslint-disable global-require, import/no-extraneous-dependencies, import/no-unresolved */
try {
switch (packageName) {
case 'opentok-text-chat':
result = require('opentok-text-chat');
break;
case 'opentok-screen-sharing':
result = require('opentok-screen-sharing');
break;
case 'opentok-annotation':
result = require('opentok-annotation');
break;
case 'opentok-archiving':
result = require('opentok-archiving');
break;
default:
break;
}
/* eslint-enable global-require */
} catch (error) {
result = window[globalName];
}
const { sessionId, apiKey } = session;
updateLogAnalytics(sessionId, path('connection.connectionId', session), apiKey);
logAnalytics(logAction.connect, logVariation.success);
initPackages();
triggerEvent('connected', session);
return resolve({ connections: session.connections.length() });
});
});
if (!result) {
analytics.log(logAction.initPackages, logVariation.fail);
throw new CoreError(`Could not load ${packageName}`, 'missingDependency');
}
return result;
};
/**
* Disconnect from the session
* @returns {Promise} <resolve: -, reject: Error>
*/
const disconnect = () => {
logAnalytics(logAction.disconnect, logVariation.attempt);
getSession().disconnect();
internalState.reset();
logAnalytics(logAction.disconnect, logVariation.success);
};
const availablePackages = {
textChat() {
return optionalRequire('opentok-text-chat', 'TextChatAccPack');
},
screenSharing() {
return optionalRequire('opentok-screen-sharing', 'ScreenSharingAccPack');
},
annotation() {
return optionalRequire('opentok-annotation', 'AnnotationAccPack');
},
archiving() {
return optionalRequire('opentok-archiving', 'ArchivingAccPack');
},
};
/**
* Force a remote connection to leave the session
* @param {Object} connection
* @returns {Promise} <resolve: empty, reject: Error>
*/
const forceDisconnect = connection =>
new Promise((resolve, reject) => {
logAnalytics(logAction.forceDisconnect, logVariation.attempt);
getSession().forceDisconnect(connection, (error) => {
if (error) {
logAnalytics(logAction.forceDisconnect, logVariation.fail);
reject(error);
const packages = {};
(path('packages', options) || []).forEach((acceleratorPack) => {
if (availablePackages[acceleratorPack]) { // eslint-disable-next-line no-param-reassign
packages[properCase(acceleratorPack)] = availablePackages[acceleratorPack]();
} else {
logAnalytics(logAction.forceDisconnect, logVariation.success);
resolve();
message(`${acceleratorPack} is not a valid accelerator pack`);
}
});
});
/**
* Get containers for streams, controls, and the chat widget
*/
const getDefaultContainer = pubSub => document.getElementById(`${pubSub}Container`);
const getContainerElements = () => {
// Need to use path to check for null values
const controls = pathOr('#videoControls', 'controlsContainer', options);
const chat = pathOr('#chat', 'textChat.container', options);
const stream = pathOr(getDefaultContainer, 'streamContainers', options);
return { stream, controls, chat };
};
/** *** *** *** *** */
/**
* Force the publisher of a stream to stop publishing the stream
* @param {Object} stream
* @returns {Promise} <resolve: empty, reject: Error>
*/
const forceUnpublish = stream =>
new Promise((resolve, reject) => {
logAnalytics(logAction.forceUnpublish, logVariation.attempt);
getSession().forceUnpublish(stream, (error) => {
if (error) {
logAnalytics(logAction.forceUnpublish, logVariation.fail);
reject(error);
} else {
logAnalytics(logAction.forceUnpublish, logVariation.success);
resolve();
/**
* Return options for the specified package
* @param {String} packageName
* @returns {Object}
*/
const packageOptions = (packageName) => {
/**
* Methods to expose to accelerator packs
*/
const accPack = {
registerEventListener: on, // Legacy option
on,
registerEvents,
triggerEvent,
setupExternalAnnotation,
linkAnnotation,
};
/**
* If options.controlsContainer/containers.controls is null,
* accelerator packs should not append their controls.
*/
const containers = getContainerElements();
const appendControl = !!containers.controls;
const controlsContainer = containers.controls; // Legacy option
const streamContainers = containers.stream;
const baseOptions = {
session,
core: accPack,
accPack,
controlsContainer,
appendControl,
streamContainers,
};
switch (packageName) {
/* beautify ignore:start */
case 'communication': {
return Object.assign({}, baseOptions, { state: internalState, analytics }, options.communication);
}
case 'textChat': {
const textChatOptions = {
textChatContainer: path('textChat.container', options),
waitingMessage: path('textChat.waitingMessage', options),
sender: { alias: path('textChat.name', options) },
alwaysOpen: path('textChat.alwaysOpen', options),
};
return Object.assign({}, baseOptions, textChatOptions);
}
case 'screenSharing': {
const screenSharingContainer = { screenSharingContainer: streamContainers };
return Object.assign({}, baseOptions, screenSharingContainer, options.screenSharing);
}
case 'annotation': {
return Object.assign({}, baseOptions, options.annotation);
}
case 'archiving': {
return Object.assign({}, baseOptions, options.archiving);
}
default:
return {};
/* beautify ignore:end */
}
};
/** Create instances of each package */
// eslint-disable-next-line global-require,import/no-extraneous-dependencies
this.communication = new Communication(packageOptions('communication'));
this.textChat = packages.TextChat ? new packages.TextChat(packageOptions('textChat')) : null;
this.screenSharing = packages.ScreenSharing ? new packages.ScreenSharing(packageOptions('screenSharing')) : null;
this.annotation = packages.Annotation ? new packages.Annotation(packageOptions('annotation')) : null;
this.archiving = packages.Archiving ? new packages.Archiving(packageOptions('archiving')) : null;
analytics.log(logAction.initPackages, logVariation.success);
}
/**
* Connect to the session
* @returns {Promise} <resolve: -, reject: Error>
*/
connect = () => {
const { analytics, getSession, initPackages, triggerEvent, getCredentials } = this;
return new Promise((resolve, reject) => {
analytics.log(logAction.connect, logVariation.attempt);
const session = getSession();
const { token } = getCredentials();
session.connect(token, (error) => {
if (error) {
message(error);
analytics.log(logAction.connect, logVariation.fail);
return reject(error);
}
const { sessionId, apiKey } = session;
analytics.update(sessionId, path('connection.connectionId', session), apiKey);
analytics.log(logAction.connect, logVariation.success);
initPackages();
triggerEvent('connected', session);
return resolve({ connections: session.connections.length() });
});
});
});
}
/**
* Get the local publisher object for a stream
* @param {Object} stream - An OpenTok stream object
* @returns {Object} - The publisher object
*/
const getPublisherForStream = stream => getSession().getPublisherForStream(stream);
/**
* Disconnect from the session
* @returns {Promise} <resolve: -, reject: Error>
*/
disconnect = () => {
const { analytics, getSession, internalState } = this;
analytics.log(logAction.disconnect, logVariation.attempt);
getSession().disconnect();
internalState.reset();
analytics.log(logAction.disconnect, logVariation.success);
};
/**
* Get the local subscriber objects for a stream
* @param {Object} stream - An OpenTok stream object
* @returns {Array} - An array of subscriber object
*/
const getSubscribersForStream = stream => getSession().getSubscribersForStream(stream);
/**
* Force a remote connection to leave the session
* @param {Object} connection
* @returns {Promise} <resolve: empty, reject: Error>
*/
forceDisconnect = (connection) => {
const { analytics, getSession } = this;
return new Promise((resolve, reject) => {
analytics.log(logAction.forceDisconnect, logVariation.attempt);
getSession().forceDisconnect(connection, (error) => {
if (error) {
analytics.log(logAction.forceDisconnect, logVariation.fail);
reject(error);
} else {
analytics.log(logAction.forceDisconnect, logVariation.success);
resolve();
}
});
});
}
/**
* Start publishing video and subscribing to streams
* @returns {Promise}
*/
startCall = () => this.communication.startCall()
/**
* Send a signal using the OpenTok signaling apiKey
* @param {String} type
* @param {*} [data]
* @param {Object} [to] - An OpenTok connection object
* @returns {Promise} <resolve: empty, reject: Error>
*/
const signal = (type, data, to) =>
new Promise((resolve, reject) => {
logAnalytics(logAction.signal, logVariation.attempt);
const session = getSession();
const signalObj = Object.assign({},
type ? { type } : null,
data ? { data: JSON.stringify(data) } : null,
to ? { to } : null // eslint-disable-line comma-dangle
);
session.signal(signalObj, (error) => {
if (error) {
logAnalytics(logAction.signal, logVariation.fail);
reject(error);
} else {
logAnalytics(logAction.signal, logVariation.success);
resolve();
}
/**
* Stop all publishing un unsubscribe from all streams
* @returns {void}
*/
endCall = () => this.communication.endCall()
/**
* Manually subscribe to a stream
* @param {Object} stream - An OpenTok stream
* @returns {Promise} <resolve: Subscriber, reject: Error>
*/
subscribe = stream => this.communication.subscribe(stream)
/**
* Manually unsubscribe from a stream
* @param {Object} subscriber - An OpenTok subscriber object
* @returns {Promise} <resolve: void, reject: Error>
*/
unsubscibe = subscriber => this.communication.unsubscibe(subscriber)
/**
* Force the publisher of a stream to stop publishing the stream
* @param {Object} stream
* @returns {Promise} <resolve: empty, reject: Error>
*/
forceUnpublish = (stream) => {
const { analytics, getSession } = this;
return new Promise((resolve, reject) => {
analytics.log(logAction.forceUnpublish, logVariation.attempt);
getSession().forceUnpublish(stream, (error) => {
if (error) {
analytics.log(logAction.forceUnpublish, logVariation.fail);
reject(error);
} else {
analytics.log(logAction.forceUnpublish, logVariation.success);
resolve();
}
});
});
});
}
/**
* Enable or disable local audio
* @param {Boolean} enable
*/
const toggleLocalAudio = (enable) => {
logAnalytics(logAction.toggleLocalAudio, logVariation.attempt);
const { publishers } = internalState.getPubSub();
const toggleAudio = id => communication.enableLocalAV(id, 'audio', enable);
Object.keys(publishers.camera).forEach(toggleAudio);
logAnalytics(logAction.toggleLocalAudio, logVariation.success);
};
/**
* Get the local publisher object for a stream
* @param {Object} stream - An OpenTok stream object
* @returns {Object} - The publisher object
*/
getPublisherForStream = stream => this.getSession().getPublisherForStream(stream);
/**
* Enable or disable local video
* @param {Boolean} enable
*/
const toggleLocalVideo = (enable) => {
logAnalytics(logAction.toggleLocalVideo, logVariation.attempt);
const { publishers } = internalState.getPubSub();
const toggleVideo = id => communication.enableLocalAV(id, 'video', enable);
Object.keys(publishers.camera).forEach(toggleVideo);
logAnalytics(logAction.toggleLocalVideo, logVariation.success);
};
/**
* Get the local subscriber objects for a stream
* @param {Object} stream - An OpenTok stream object
* @returns {Array} - An array of subscriber object
*/
getSubscribersForStream = stream => this.getSession().getSubscribersForStream(stream);
/**
* Enable or disable remote audio
* @param {String} id - Subscriber id
* @param {Boolean} enable
*/
const toggleRemoteAudio = (id, enable) => {
logAnalytics(logAction.toggleRemoteAudio, logVariation.attempt);
communication.enableRemoteAV(id, 'audio', enable);
logAnalytics(logAction.toggleRemoteAudio, logVariation.success);
};
/**
* Enable or disable remote video
* @param {String} id - Subscriber id
* @param {Boolean} enable
*/
const toggleRemoteVideo = (id, enable) => {
logAnalytics(logAction.toggleRemoteVideo, logVariation.attempt);
communication.enableRemoteAV(id, 'video', enable);
logAnalytics(logAction.toggleRemoteVideo, logVariation.success);
};
/**
* Send a signal using the OpenTok signaling apiKey
* @param {String} type
* @param {*} [data]
* @param {Object} [to] - An OpenTok connection object
* @returns {Promise} <resolve: empty, reject: Error>
*/
signal = (type, data, to) => {
const { analytics, getSession } = this;
return new Promise((resolve, reject) => {
analytics.log(logAction.signal, logVariation.attempt);
const session = getSession();
const signalObj = Object.assign({},
type ? { type } : null,
data ? { data: JSON.stringify(data) } : null,
to ? { to } : null // eslint-disable-line comma-dangle
);
session.signal(signalObj, (error) => {
if (error) {
analytics.log(logAction.signal, logVariation.fail);
reject(error);
} else {
analytics.log(logAction.signal, logVariation.success);
resolve();
}
});
});
}
/**
* Initialize the accelerator pack
* @param {Object} options
* @param {Object} options.credentials
* @param {Array} [options.packages]
* @param {Object} [options.containers]
*/
const init = (options) => {
if (!options) {
throw new CoreError('Missing options required for initialization', 'invalidParameters');
/**
* Enable or disable local audio
* @param {Boolean} enable
*/
toggleLocalAudio = (enable) => {
const { analytics, internalState, communication } = this;
analytics.log(logAction.toggleLocalAudio, logVariation.attempt);
const { publishers } = internalState.getPubSub();
const toggleAudio = id => communication.enableLocalAV(id, 'audio', enable);
Object.keys(publishers.camera).forEach(toggleAudio);
analytics.log(logAction.toggleLocalAudio, logVariation.success);
};
/**
* Enable or disable local video
* @param {Boolean} enable
*/
toggleLocalVideo = (enable) => {
const { analytics, internalState, communication } = this;
analytics.log(logAction.toggleLocalVideo, logVariation.attempt);
const { publishers } = internalState.getPubSub();
const toggleVideo = id => communication.enableLocalAV(id, 'video', enable);
Object.keys(publishers.camera).forEach(toggleVideo);
analytics.log(logAction.toggleLocalVideo, logVariation.success);
};
/**
* Enable or disable remote audio
* @param {String} id - Subscriber id
* @param {Boolean} enable
*/
toggleRemoteAudio = (id, enable) => {
const { analytics, communication } = this;
analytics.log(logAction.toggleRemoteAudio, logVariation.attempt);
communication.enableRemoteAV(id, 'audio', enable);
analytics.log(logAction.toggleRemoteAudio, logVariation.success);
};
/**
* Enable or disable remote video
* @param {String} id - Subscriber id
* @param {Boolean} enable
*/
toggleRemoteVideo = (id, enable) => {
const { analytics, communication } = this;
analytics.log(logAction.toggleRemoteVideo, logVariation.attempt);
communication.enableRemoteAV(id, 'video', enable);
analytics.log(logAction.toggleRemoteVideo, logVariation.success);
}
const { credentials } = options;
validateCredentials(options.credentials);
// Init analytics
initLogAnalytics(window.location.origin, credentials.sessionId, null, credentials.apiKey);
logAnalytics(logAction.init, logVariation.attempt);
const session = OT.initSession(credentials.apiKey, credentials.sessionId);
createEventListeners(session, options);
internalState.setSession(session);
internalState.setCredentials(credentials);
internalState.setOptions(options);
logAnalytics(logAction.init, logVariation.success);
};
}
const opentokCore = {
init,
connect,
disconnect,
forceDisconnect,
forceUnpublish,
getAccPack,
getOptions,
getSession,
getPublisherForStream,
getSubscribersForStream,
on,
off,
registerEventListener: on,
triggerEvent,
signal,
state: internalState.all,
startCall: communication.startCall,
endCall: communication.endCall,
OpenTokSDK,
toggleLocalAudio,
toggleLocalVideo,
toggleRemoteAudio,
toggleRemoteVideo,
subscribe: communication.subscribe,
unsubscribe: communication.unsubscribe,
util,
};
if (global === window) {
window.otCore = opentokCore;
window.AccCore = AccCore;
}
module.exports = opentokCore;
module.exports = AccCore;

@@ -8,4 +8,2 @@ const OTKAnalytics = require('opentok-solutions-logging');

let analytics = null;
const logVariation = {

@@ -37,40 +35,42 @@ attempt: 'Attempt',

const updateLogAnalytics = (sessionId, connectionId, apiKey) => {
if (sessionId && connectionId && apiKey) {
const sessionInfo = {
sessionId,
connectionId,
partnerId: apiKey,
class Analytics {
constructor(source, sessionId, connectionId, apikey) {
const otkanalyticsData = {
clientVersion: 'js-vsol-x.y.z', // x.y.z filled by npm build script
source,
componentId: 'acceleratorCore',
name: 'coreAccelerator',
partnerId: apikey,
};
analytics.addSessionInfo(sessionInfo);
this.analytics = new OTKAnalytics(otkanalyticsData);
if (connectionId) {
this.update(sessionId, connectionId, apikey);
}
}
};
const initLogAnalytics = (source, sessionId, connectionId, apikey) => {
const otkanalyticsData = {
clientVersion: 'js-vsol-x.y.z', // x.y.z filled by npm build script
source,
componentId: 'acceleratorCore',
name: 'coreAccelerator',
partnerId: apikey,
update = (sessionId, connectionId, apiKey) => {
if (sessionId && connectionId && apiKey) {
const sessionInfo = {
sessionId,
connectionId,
partnerId: apiKey,
};
this.analytics.addSessionInfo(sessionInfo);
}
};
analytics = new OTKAnalytics(otkanalyticsData);
log = (action, variation) => {
this.analytics.logEvent({ action, variation });
};
}
if (connectionId) {
updateLogAnalytics(sessionId, connectionId, apikey);
}
};
const logAnalytics = (action, variation) => {
analytics.logEvent({ action, variation });
};
module.exports = {
Analytics,
logVariation,
logAction,
message,
logAction,
logVariation,
initLogAnalytics,
updateLogAnalytics,
logAnalytics,
};

@@ -121,2 +121,4 @@ /* global OT */

this.session.on('streamDestroyed', ({ stream }) => state.removeStream(stream));
this.session.on('sessionConnected sessionReconnected', () => state.setConnected(true));
this.session.on('sessionDisconnected', () => state.setConnected(false));
}

@@ -123,0 +125,0 @@

@@ -23,4 +23,12 @@ class State {

this.credentials = null;
// Session Connection Status
this.connected = false;
}
// Set the current connection state
setConnected(connected) {
this.connected = connected;
}
// Get the current OpenTok session

@@ -140,4 +148,4 @@ getSession() {

all() {
const { streams, streamMap } = this;
return Object.assign({}, this.getPubSub(), { streams, streamMap });
const { streams, streamMap, connected } = this;
return Object.assign({}, this.getPubSub(), { streams, streamMap, connected });
}

@@ -144,0 +152,0 @@ }

const { pathOr } = require('./util');
/**
* Internal variables
*/
// Map publisher ids to publisher objects
const publishers = {
camera: {},
screen: {},
};
class State {
constructor() {
this.publishers = {
camera: {},
screen: {},
};
// Map subscriber id to subscriber objects
const subscribers = {
camera: {},
screen: {},
sip: {},
};
// Map subscriber id to subscriber objects
this.subscribers = {
camera: {},
screen: {},
sip: {},
};
// Map stream ids to stream objects
const streams = {};
// Map stream ids to stream objects
this.streams = {};
// Map stream ids to subscriber/publisher ids
const streamMap = {};
// Map stream ids to subscriber/publisher ids
this.streamMap = {};
let session = null;
let credentials = null;
let options = null;
// The OpenTok session
this.session = null;
// OpenTok session credentials
this.credentials = null;
/**
// Core options
this.options = null;
}
/**
* Internal methods

@@ -50,204 +52,198 @@ */

*/
const pubSubCount = () => {
/* eslint-disable no-param-reassign */
const pubs = Object.keys(publishers).reduce((acc, source) => {
acc[source] = Object.keys(publishers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, total: 0 });
pubSubCount = () => {
const { publishers, subscribers } = this;
/* eslint-disable no-param-reassign */
const pubs = Object.keys(publishers).reduce((acc, source) => {
acc[source] = Object.keys(publishers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, total: 0 });
const subs = Object.keys(subscribers).reduce((acc, source) => {
acc[source] = Object.keys(subscribers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, sip: 0, total: 0 });
/* eslint-enable no-param-reassign */
return { publisher: pubs, subscriber: subs };
};
const subs = Object.keys(subscribers).reduce((acc, source) => {
acc[source] = Object.keys(subscribers[source]).length;
acc.total += acc[source];
return acc;
}, { camera: 0, screen: 0, sip: 0, total: 0 });
/* eslint-enable no-param-reassign */
return { publisher: pubs, subscriber: subs };
}
/**
* Returns the current publishers and subscribers, along with a count of each
* @returns {Object}
*/
const getPubSub = () => ({ publishers, subscribers, meta: pubSubCount() });
/**
* Returns the current publishers and subscribers, along with a count of each
* @returns {Object}
*/
getPubSub = () => {
const { publishers, subscribers, pubSubCount } = this;
return { publishers, subscribers, meta: pubSubCount() };
}
/**
* Get streams, streamMap, publishers, and subscribers
* @return {Object}
*/
const all = () => Object.assign({}, { streams, streamMap }, getPubSub());
/**
* Get streams, streamMap, publishers, and subscribers
* @return {Object}
*/
all = () => {
const { streams, streamMap, getPubSub } = this;
return Object.assign({}, { streams, streamMap }, getPubSub());
}
/**
* Get the current OpenTok session
* @returns {Object}
*/
const getSession = () => session;
/**
* Get the current OpenTok session
* @returns {Object}
*/
getSession = () => this.session;
/**
* Set the current OpenTok session
* @param {Object} otSession
*/
const setSession = (otSession) => {
session = otSession;
};
/**
* Set the current OpenTok session
* @param {Object} otSession
*/
setSession = (otSession) => {
this.session = otSession;
}
/**
* Get the current OpenTok credentials
* @returns {Object}
*/
const getCredentials = () => credentials;
/**
* Get the current OpenTok credentials
* @returns {Object}
*/
getCredentials = () => this.credentials;
/**
* Set the current OpenTok credentials
* @param {Object} otCredentials
*/
const setCredentials = (otCredentials) => {
credentials = otCredentials;
};
/**
* Set the current OpenTok credentials
* @param {Object} otCredentials
*/
setCredentials = (otCredentials) => {
this.credentials = otCredentials;
}
/**
* Get the options defined for core
* @returns {Object}
*/
const getOptions = () => options;
/**
* Get the options defined for core
* @returns {Object}
*/
getOptions = () => this.options;
/**
* Set the options defined for core
* @param {Object} otOptions
*/
const setOptions = (otOptions) => {
options = otOptions;
};
/**
* Set the options defined for core
* @param {Object} otOptions
*/
setOptions = (otOptions) => {
this.options = otOptions;
}
/**
* Add a stream to state
* @param {Object} stream - An OpenTok stream object
*/
const addStream = (stream) => {
streams[stream.id] = stream;
};
/**
* Add a stream to state
* @param {Object} stream - An OpenTok stream object
*/
addStream = (stream) => {
this.streams[stream.id] = stream;
}
/**
* Remove a stream from state and any associated subscribers
* @param {Object} stream - An OpenTok stream object
*/
const removeStream = (stream) => {
const type = pathOr('sip', 'videoType', stream);
const subscriberId = streamMap[stream.id];
delete streamMap[stream.id];
delete subscribers[type][subscriberId];
delete streams[stream.id];
};
/**
* Remove a stream from state and any associated subscribers
* @param {Object} stream - An OpenTok stream object
*/
removeStream = (stream) => {
const { streamMap, subscribers, streams } = this;
const type = pathOr('sip', 'videoType', stream);
const subscriberId = streamMap[stream.id];
delete streamMap[stream.id];
delete subscribers[type][subscriberId];
delete streams[stream.id];
}
/**
* Get all remote streams
* @returns {Object}
*/
const getStreams = () => streams;
/**
* Get all remote streams
* @returns {Object}
*/
getStreams = () => this.streams;
/**
* Get the map of stream ids to publisher/subscriber ids
* @returns {Object}
*/
const getStreamMap = () => streamMap;
/**
* Get the map of stream ids to publisher/subscriber ids
* @returns {Object}
*/
getStreamMap = () => this.streamMap;
/**
* Add a publisher to state
* @param {String} type - 'camera' or 'screen'
* @param {Object} publisher - The OpenTok publisher object
*/
const addPublisher = (type, publisher) => {
streamMap[publisher.streamId] = publisher.id;
publishers[type][publisher.id] = publisher;
};
/**
* Add a publisher to state
* @param {String} type - 'camera' or 'screen'
* @param {Object} publisher - The OpenTok publisher object
*/
addPublisher = (type, publisher) => {
this.streamMap[publisher.streamId] = publisher.id;
this.publishers[type][publisher.id] = publisher;
}
/**
* Remove a publisher from state
* @param {String} type - 'camera' or 'screen'
* @param {Object} publisher - The OpenTok publisher object
*/
const removePublisher = (type, publisher) => {
const id = publisher.id || streamMap[publisher.streamId];
delete publishers[type][id];
delete streamMap[publisher.streamId];
};
/**
* Remove a publisher from state
* @param {String} type - 'camera' or 'screen'
* @param {Object} publisher - The OpenTok publisher object
*/
removePublisher = (type, publisher) => {
const { streamMap, publishers } = this;
const id = publisher.id || streamMap[publisher.streamId];
delete publishers[type][id];
delete streamMap[publisher.streamId];
}
/**
* Remove all publishers from state
*/
const removeAllPublishers = () => {
['camera', 'screen'].forEach((type) => {
Object.values(publishers[type]).forEach((publisher) => {
removePublisher(type, publisher);
/**
* Remove all publishers from state
*/
removeAllPublishers = () => {
const { publishers, removePublisher } = this;
['camera', 'screen'].forEach((type) => {
Object.values(publishers[type]).forEach((publisher) => {
removePublisher(type, publisher);
});
});
});
};
}
/**
* Add a subscriber to state
* @param {Object} - An OpenTok subscriber object
*/
const addSubscriber = (subscriber) => {
const streamId = subscriber.stream.id;
const type = pathOr('sip', 'stream.videoType', subscriber);
subscribers[type][subscriber.id] = subscriber;
streamMap[streamId] = subscriber.id;
};
/**
* Add a subscriber to state
* @param {Object} - An OpenTok subscriber object
*/
addSubscriber = (subscriber) => {
const { subscribers, streamMap } = this;
const streamId = subscriber.stream.id;
const type = pathOr('sip', 'stream.videoType', subscriber);
subscribers[type][subscriber.id] = subscriber;
streamMap[streamId] = subscriber.id;
}
/**
* Remove a publisher from state
* @param {String} type - 'camera' or 'screen'
* @param {Object} subscriber - The OpenTok subscriber object
*/
const removeSubscriber = (type, subscriber) => {
const id = subscriber.id || streamMap[subscriber.streamId];
delete subscribers[type][id];
delete streamMap[subscriber.streamId];
};
/**
* Remove a publisher from state
* @param {String} type - 'camera' or 'screen'
* @param {Object} subscriber - The OpenTok subscriber object
*/
removeSubscriber = (type, subscriber) => {
const { subscribers, streamMap } = this;
const id = subscriber.id || streamMap[subscriber.streamId];
delete subscribers[type][id];
delete streamMap[subscriber.streamId];
}
/**
* Remove all subscribers from state
*/
const removeAllSubscribers = () => {
['camera', 'screen', 'sip'].forEach((type) => {
Object.values(subscribers[type]).forEach((subscriber) => {
removeSubscriber(type, subscriber);
/**
* Remove all subscribers from state
*/
removeAllSubscribers = () => {
['camera', 'screen', 'sip'].forEach((type) => {
Object.values(this.subscribers[type]).forEach((subscriber) => {
this.removeSubscriber(type, subscriber);
});
});
});
};
}
/**
* Reset state
*/
const reset = () => {
removeAllPublishers();
removeAllSubscribers();
[streams, streamMap].forEach((streamObj) => {
Object.keys(streamObj).forEach((streamId) => {
delete streamObj[streamId]; // eslint-disable-line no-param-reassign
/**
* Reset state
*/
reset = () => {
const { removeAllPublishers, removeAllSubscribers, streams, streamMap } = this;
removeAllPublishers();
removeAllSubscribers();
[streams, streamMap].forEach((streamObj) => {
Object.keys(streamObj).forEach((streamId) => {
delete streamObj[streamId]; // eslint-disable-line no-param-reassign
});
});
});
};
}
}
/** Exports */
module.exports = {
all,
getSession,
setSession,
getCredentials,
setCredentials,
getOptions,
setOptions,
addStream,
removeStream,
getStreams,
getStreamMap,
addPublisher,
removePublisher,
removeAllPublishers,
addSubscriber,
removeSubscriber,
removeAllSubscribers,
getPubSub,
reset,
};
/** Export */
export default State;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc