UNPKG

webp2p

Version:

The server-less signaling channel for WebRTC

1,189 lines (1,043 loc) 53.2 kB
var WebSocket = require('ws'), httpRequest = require('./http-request'), urlParser = require('url'), strings = require('./strings'), fs = require('fs'), path = require('path'), ObjectLiterals = require('./object-literals'), IbtRealtimeRestServices = require('./IbtRealtimeRestServices'); exports = module.exports = IbtRealTimeSJNode; /* * Initializes a new instance of the IbtRealTimeSJNode class. */ function IbtRealTimeSJNode() { /*********************************************************** * @attributes ***********************************************************/ var appKey; // application key var authToken; // authentication token var clusterUrl; // cluster URL to connect var connectionTimeout; // connection timeout in milliseconds var messageMaxSize; // message maximum size in bytes var channelMaxSize; // channel maximum size in bytes var messagesBuffer; // buffer to hold the message parts var id; // object identifier var isConnected; // indicates whether the client object is connected var isConnecting; // indicates whether the client object is connecting var alreadyConnectedFirstTime; // indicates whether the client already connected for the first time var stopReconnecting; // indicates whether the user disconnected (stop the reconnecting proccess) var ortc; // represents the object itself var reconnectIntervalId; // id used for the reconnect interval var reconnectTimeoutlId; // id used for the reconnect timeout var sockjs; // socket connected to var url; // URL to connect var userPerms; // user permissions var connectionMetadata; // connection metadata used to identify the client var announcementSubChannel; // announcement subchannel var subscribedChannels; // subscribed/subscribing channels var lastKeepAlive; // holds the time of the last keep alive received var invalidConnection; // indicates whether the connection is valid var reconnectStartedAt; // the time which the reconnect started var eventEmitter; // event emitter to send message /*********************************************************** * @attributes initialization ***********************************************************/ connectionTimeout = 15000; messageMaxSize = 800; channelMaxSize = 100; connectionMetadataMaxSize = 256; messagesBuffer = Object.create(null); subscribedChannels = Object.create(null); isConnected = false; isConnecting = false; alreadyConnectedFirstTime = false; invalidConnection = false; ortc = this; lastKeepAlive = null; userPerms = null; reconnectStartedAt = null; // this.eventEmitter = new process.EventEmitter(); /*********************************************************** * @properties ***********************************************************/ this.getId = function () { return id; }; this.setId = function (newId) { id = newId; }; this.getUrl = function () { return url; }; this.setUrl = function (newUrl) { url = newUrl; clusterUrl = null; }; this.getClusterUrl = function () { return clusterUrl; }; this.setClusterUrl = function (newUrl) { clusterUrl = newUrl; url = null; }; this.getConnectionTimeout = function () { return connectionTimeout; }; this.setConnectionTimeout = function (newTimeout) { connectionTimeout = newTimeout; }; this.getIsConnected = function () { return isConnected && ortc.sockjs != null; }; this.getConnectionMetadata = function () { return connectionMetadata; }; this.setConnectionMetadata = function (newConnectionMetadata) { connectionMetadata = newConnectionMetadata; }; this.getAnnouncementSubChannel = function () { return announcementSubChannel; }; this.setAnnouncementSubChannel = function (newAnnouncementSubChannel) { announcementSubChannel = newAnnouncementSubChannel; }; /*********************************************************** * @events ***********************************************************/ this.onConnected = null; this.onDisconnected = null; this.onSubscribed = null; this.onUnsubscribed = null; this.onException = null; this.onReconnecting = null; this.onReconnected = null; /*********************************************************** * @public methods ***********************************************************/ /* * Connects to the gateway with the application key and authentication token. */ this.connect = function (appKey, authToken) { /* Sanity Checks */ if (isConnected) { delegateExceptionCallback(ortc, 'Already connected'); } else if (!url && !clusterUrl) { delegateExceptionCallback(ortc, 'URL and Cluster URL are null or empty'); } else if (!appKey) { delegateExceptionCallback(ortc, 'Application key is null or empty'); } else if (!authToken) { delegateExceptionCallback(ortc, 'Authentication Token is null or empty'); } else if (url && !ortcIsValidUrl(url)) { delegateExceptionCallback(ortc, 'Invalid URL'); } else if (clusterUrl && !ortcIsValidUrl(clusterUrl)) { delegateExceptionCallback(ortc, 'Invalid Cluster URL'); } else if (!ortcIsValidInput(appKey)) { delegateExceptionCallback(ortc, 'Application Key has invalid characters'); } else if (!ortcIsValidInput(authToken)) { delegateExceptionCallback(ortc, 'Authentication Token has invalid characters'); } else if (!ortcIsValidInput(announcementSubChannel)) { delegateExceptionCallback(ortc, 'Announcement Subchannel has invalid characters'); } else if (connectionMetadata && connectionMetadata.length > connectionMetadataMaxSize) { delegateExceptionCallback(ortc, 'Connection metadata size exceeds the limit of ' + connectionMetadataMaxSize + ' characters'); } else { ortc.appKey = appKey; ortc.authToken = authToken; isConnecting = true; stopReconnecting = false; if (clusterUrl && clusterUrl != null) { clusterUrl = ortcTreatUrl(clusterUrl); clusterConnection(ortc.getConnectionTimeout()); } else { url = ortcTreatUrl(url); ortc.sockjs = createSocketConnection(url); } if (!ortc.reconnectTimeoutlId) { // Timeout to reconnect ortc.reconnectTimeoutlId = setTimeout(function () { if (!isConnected) { reconnectSocket(ortc.getConnectionTimeout()); } }, ortc.getConnectionTimeout()); } } }; /* * Subscribes to the channel so the client object can receive all messages sent to it by other clients. */ this.subscribe = function (channel, subscribeOnReconnected, onMessageCallback) { /* Sanity Checks */ if (!isConnected) { delegateExceptionCallback(ortc, 'Not connected'); } else if (!channel) { delegateExceptionCallback(ortc, 'Channel is null or empty'); } else if (!ortcIsValidInput(channel)) { delegateExceptionCallback(ortc, 'Channel has invalid characters'); } else if (subscribedChannels[channel] && subscribedChannels[channel].isSubscribing) { delegateExceptionCallback(ortc, 'Already subscribing to the channel \'' + channel + '\''); } else if (subscribedChannels[channel] && subscribedChannels[channel].isSubscribed) { delegateExceptionCallback(ortc, 'Already subscribed to the channel \'' + channel + '\''); } else if (channel.length > channelMaxSize) { delegateExceptionCallback(ortc, 'Channel size exceeds the limit of ' + channelMaxSize + ' characters'); } else if (!ortcIsValidBoolean(subscribeOnReconnected)) { delegateExceptionCallback(ortc, 'The argument \'subscribeOnReconnected\' must be a boolean'); } else if (!ortcIsFunction(onMessageCallback)) { delegateExceptionCallback(ortc, 'The argument \'onMessageCallback\' must be a function'); } else { if (ortc.sockjs != null) { var domainChannelCharacterIndex = channel.indexOf(':'); var channelToValidate = channel; var hashPerm = null; if (domainChannelCharacterIndex > 0) { channelToValidate = channel.substring(0, domainChannelCharacterIndex + 1) + '*'; } if (userPerms && userPerms != null) { hashPerm = userPerms[channelToValidate] ? userPerms[channelToValidate] : userPerms[channel]; } if (userPerms && userPerms != null && !hashPerm) { delegateExceptionCallback(ortc, 'No permission found to subscribe to the channel \'' + channel + '\''); } else { if (subscribedChannels[channel]) { subscribedChannels[channel].isSubscribing = true; subscribedChannels[channel].isSubscribed = false; subscribedChannels[channel].subscribeOnReconnected = subscribeOnReconnected; subscribedChannels[channel].onMessageCallback = onMessageCallback; } else { subscribedChannels[channel] = { 'isSubscribing': true, 'isSubscribed': false, 'subscribeOnReconnected': subscribeOnReconnected, 'onMessageCallback': onMessageCallback }; } ortc.sockjs.send(JSON.stringify('subscribe;' + ortc.appKey + ';' + ortc.authToken + ';' + channel + ';' + hashPerm)); } } } }; /* * Unsubscribes from the channel so the client object stops receiving messages sent to it. */ this.unsubscribe = function (channel) { /* Sanity Checks */ if (!isConnected) { delegateExceptionCallback(ortc, 'Not connected'); } else if (!channel) { delegateExceptionCallback(ortc, 'Channel is null or empty'); } else if (!ortcIsValidInput(channel)) { delegateExceptionCallback(ortc, 'Channel has invalid characters'); } else if (!subscribedChannels[channel] || (subscribedChannels[channel] && !subscribedChannels[channel].isSubscribed)) { delegateExceptionCallback(ortc, 'Not subscribed to the channel ' + channel); } else if (channel.length > channelMaxSize) { delegateExceptionCallback(ortc, 'Channel size exceeds the limit of ' + channelMaxSize + ' characters'); } else { if (ortc.sockjs != null) { ortc.sockjs.send(JSON.stringify('unsubscribe;' + ortc.appKey + ';' + channel)); } } }; /* * Sends the message to the channel. */ this.send = function (channel, message) { /* Sanity Checks */ if (!isConnected || ortc.sockjs == null) { delegateExceptionCallback(ortc, 'Not connected'); } else if (!channel) { delegateExceptionCallback(ortc, 'Channel is null or empty'); } else if (!ortcIsValidInput(channel)) { delegateExceptionCallback(ortc, 'Channel has invalid characters'); } else if (!message) { delegateExceptionCallback(ortc, 'Message is null or empty'); } else if (!ortcIsString(message)) { delegateExceptionCallback(ortc, 'Message must be a string'); } else if (channel.length > channelMaxSize) { delegateExceptionCallback(ortc, 'Channel size exceeds the limit of ' + channelMaxSize + ' characters'); } else { var domainChannelCharacterIndex = channel.indexOf(':'); var channelToValidate = channel; var hashPerm = null; if (domainChannelCharacterIndex > 0) { channelToValidate = channel.substring(0, domainChannelCharacterIndex + 1) + '*'; } if (userPerms && userPerms != null) { hashPerm = userPerms[channelToValidate] ? userPerms[channelToValidate] : userPerms[channel]; } if (userPerms && userPerms != null && !hashPerm) { delegateExceptionCallback(ortc, 'No permission found to send to the channel \'' + channel + '\''); } else { // Multi part var messageParts = []; var messageId = generateId(8); var i; var allowedMaxSize = messageMaxSize - channel.length; for (i = 0; i < message.length; i = i + allowedMaxSize) { // Just one part if (message.length <= allowedMaxSize) { messageParts.push(message); break; } if (message.substring(i, i + allowedMaxSize)) { messageParts.push(message.substring(i, i + allowedMaxSize)); } } if (messageParts.length == 1) { ortc.sockjs.send(JSON.stringify('send;' + ortc.appKey + ';' + ortc.authToken + ';' + channel + ';' + hashPerm + ';' + messageId + '_1-1_' + messageParts[0])); } else { for (var j = 1; j <= messageParts.length; j++) { var messageToSend = 'send;' + ortc.appKey + ';' + ortc.authToken + ';' + channel + ';' + hashPerm + ';' + messageId + '_' + j + '-' + messageParts.length + '_' + messageParts[j - 1]; this.sendMultiPartMessage(messageToSend); } } } } }; /* * Sends the message to the channel. */ this.sendProxy = function (applicationKey, privateKey, channel, message) { /* Sanity Checks */ if (!isConnected || ortc.sockjs == null) { delegateExceptionCallback(ortc, 'Not connected'); } else if (!applicationKey) { delegateExceptionCallback(ortc, 'Application key is null or empty'); } else if (!privateKey) { delegateExceptionCallback(ortc, 'Private key is null or empty'); } else if (!channel) { delegateExceptionCallback(ortc, 'Channel is null or empty'); } else if (!ortcIsValidInput(channel)) { delegateExceptionCallback(ortc, 'Channel ' + channel + ' has invalid characters'); } else if (!message) { delegateExceptionCallback(ortc, 'Message is null or empty'); } else if (!ortcIsString(message)) { delegateExceptionCallback(ortc, 'Message must be a string'); } else if (channel.length > channelMaxSize) { delegateExceptionCallback(ortc, 'Channel size exceeds the limit of ' + channelMaxSize + ' characters'); } else { // Multi part var messageParts = []; var messageId = generateId(8); var i; var allowedMaxSize = messageMaxSize - channel.length; for (i = 0; i < message.length; i = i + allowedMaxSize) { // Just one part if (message.length <= allowedMaxSize) { messageParts.push(message); break; } if (message.substring(i, i + allowedMaxSize)) { messageParts.push(message.substring(i, i + allowedMaxSize)); } } if (messageParts.length == 1) { ortc.sockjs.send(JSON.stringify('sendproxy;' + applicationKey + ';' + privateKey + ';' + channel + ';' + messageId + '_1-1_' + messageParts[0])); } else { for (var j = 1; j <= messageParts.length; j++) { var messageToSend = 'sendproxy;' + applicationKey + ';' + privateKey + ';' + channel + ';' + messageId + '_' + j + '-' + messageParts.length + '_' + messageParts[j - 1]; this.sendMultiPartMessage(messageToSend); } } } }; this.sendMultiPartMessage = function(message){ process.nextTick(function(){ ortc.sockjs.send(JSON.stringify(message)); }); } /* * Disconnects from the gateway. */ this.disconnect = function () { clearReconnectInterval(); // Stop the reconnecting process stopReconnecting = true; alreadyConnectedFirstTime = false; // Clear subscribed channels subscribedChannels = Object.create(null); /* Sanity Checks */ if (!isConnected && !invalidConnection) { delegateExceptionCallback(ortc, 'Not connected'); } else { disconnectSocket(); } }; /* * Gets a value indicating whether this client object is subscribed to the channel. */ this.isSubscribed = function (channel) { /* Sanity Checks */ if (!isConnected) { delegateExceptionCallback(ortc, 'Not connected'); } else if (!channel) { delegateExceptionCallback(ortc, 'Channel is null or empty'); } else if (!ortcIsValidInput(channel)) { delegateExceptionCallback(ortc, 'Channel has invalid characters'); } else { if (subscribedChannels[channel] && subscribedChannels[channel].isSubscribed) { return subscribedChannels[channel].isSubscribed; } else { return false; } } }; /** * @function {public} saveAuthentication Saves the channels and its permissions for the supplied application key and authentication token. * @param {string} url The ORTC server URL. * @param {bool} isCluster Indicates whether the ORTC server is in a cluster. * @param {string} authenticationToken The authentication token generated by an application server (for instance: a unique session ID). * @param {bool} authenticationTokenIsPrivate Indicates whether the authentication token is private. * @param {string} applicationKey The application key provided when the ORTC service is purchased. * @param {int} timeToLive The authentication token time to live (TTL), in other words, the allowed activity time (in seconds). * @param {string} privateKey The private key provided when the ORTC service is purchased. * @param {object} permissions The channels and their permissions (w: write/read or r: read or p: presence, case sensitive). * @param {function(error, success)} callback Function called when the authentication finishes. If 'error' is not null then an error occurred. * @returns void * @returns void * * </br> * <b class="codeSample">Code Sample:</b> * * <pre class="brush: js; gutter: false;"> * ortcClient.saveAuthentication('http://developers2.realtime.livehtml.net/server/2.1/', true, 'myAuthenticationToken', 0, 'myApplicationKey', 1400, 'myPrivateKey', { "channel1": "wrp", "channel2": "w" }, function (error, success) { * if (error) { * console.log('Error saving authentication: ' + error); * } else if (success) { * console.log('Successfully authenticated'); * } else { * console.log('Not authenticated'); * } * }); * </pre> */ this.saveAuthentication = function (url, isCluster, authenticationToken, authenticationTokenIsPrivate, applicationKey, timeToLive, privateKey, permissions, callback) { var connectionUrl = url; if (isCluster) { getServerFromCluster({ clusterUri : connectionUrl, connectionTimeout : ortc.getConnectionTimeout(), appKey : applicationKey }, function (error, body) { if (error != null) { delegateExceptionCallback(ortc, 'Error getting server from Cluster'); callback('Error getting server from Cluster',null); } else { connectionUrl = body.substring(body.indexOf('=') + 3, body.length - 2); IbtRealtimeRestServices.saveAuthentication(connectionUrl, authenticationToken, authenticationTokenIsPrivate, applicationKey, timeToLive, privateKey, permissions, callback); } }); } else { IbtRealtimeRestServices.saveAuthentication(connectionUrl, authenticationToken, authenticationTokenIsPrivate, applicationKey, timeToLive, privateKey, permissions, callback); } }; /** * @function {public} presence Gets a json indicating the subscriptions in the specified channel and if active the first 100 unique metadata. * @param {object} Object literal with presence attributes. * @... {String} url Server containing the presence service (optional if connected). * @... {bool} isCluster Specifies if url is cluster (optional if connected). * @... {String} applicationKey Application key with access to presence service (optional if connected). * @... {String} authenticationToken Authentication token with access to presence service (optional if connected). * @... {String} channel Channel with presence data active. * @param {function} Callback with error and result parameters. * @returns void * * </br> * <b class="codeSample">Code Sample:</b> * * <pre class="brush: js; gutter: false;"> * ortcClient.presence({ * applicationKey : 'ORTC_APPLICATION_KEY', * authenticationToken : 'AUTHENTICATION_TOKEN', * isCluster : true, * url : 'http://ortc-developers.realtime.co/server/2.1/', * channel : 'CHANNEL_WITH_ACTIVE_PRESENCE' * }, * function(error,result){ * if(error){ * console.log('Presence error:',error); * }else{ * if(result){ * console.log('Subscriptions',result.subscriptions); * * for(var metadata in result.metadata){ * console.log(metadata,'-',result.metadata[metadata]); * } * }else{ * console.log('Subscriptions empty'); * } * } * }); * </pre> */ this.presence = function (parameters,callback) { try{ var requestUrl = null , isCluster = false , appKey = ortc.appKey , authToken = ortc.authToken; if(parameters.url){ requestUrl = parameters.url; isCluster = parameters.isCluster; appKey = parameters.applicationKey; authToken = parameters.authenticationToken; }else{ if (clusterUrl && clusterUrl != null) { requestUrl = clusterUrl; isCluster = true; } else { requestUrl = url; } } getServerUrl({ requestUrl : requestUrl, isCluster : isCluster, appKey : appKey }, function(error,serverUrl){ if(error){ callback(error,null); }else{ IbtRealtimeRestServices.getPresence({ url : serverUrl, appKey : appKey, authToken : authToken, channel : parameters.channel },callback); } }); }catch(e){ callback('Unable to get presence data ' + e,null); } }; /** * @function {public} enablePresence Enables presence for the specified channel with first 100 unique metadata if true. * @param {object} Object literal with presence attributes. * @... {String} url Server containing the presence service (optional if connected). * @... {bool} isCluster Specifies if url is cluster (optional if connected). * @... {String} applicationKey Application key with access to presence service (optional if connected). * @... {String} privateKey The private key provided when the ORTC service is purchased. * @... {String} channel Channel to activate presence. * @... {bool} metadata Defines if to collect first 100 unique metadata. * @param {function} Callback with error and result parameters. * @returns void * * </br> * <b class="codeSample">Code Sample:</b> * * <pre class="brush: js; gutter: false;"> * ortcClient.enablePresence({ * applicationKey : 'ORTC_APPLICATION_KEY', * channel : 'CHANNEL_TO_ACTIVATE_PRESENCE', * privateKey : 'ORTC_PRIVATE_KEY', * url : 'http://ortc-developers.realtime.co/server/2.1/', * isCluster : true, metadata : true * }, * function(error,result){ * if(error){ * console.log('Presence',error); * }else{ * console.log('Presence enable',result); * }); * </pre> */ this.enablePresence = function (parameters,callback) { try{ var requestUrl = null , isCluster = false , appKey = ortc.appKey; if(parameters.url){ requestUrl = parameters.url; isCluster = parameters.isCluster; appKey = parameters.applicationKey; }else{ if (clusterUrl && clusterUrl != null) { requestUrl = clusterUrl; isCluster = true; } else { requestUrl = url; } } getServerUrl({ requestUrl : requestUrl, isCluster : isCluster, appKey : appKey }, function(error,serverUrl){ if(error){ callback(error,null); }else{ IbtRealtimeRestServices.enablePresence({ url : serverUrl, appKey : appKey, privateKey : parameters.privateKey, channel : parameters.channel, metadata : parameters.metadata == true ? 1 : 0 },callback); } }); }catch(e){ callback('Unable to enable presence ' + e,null); } }; /** * @function {public} disablePresence Disables presence for the specified channel. * @param {object} Object literal with presence attributes. * @... {String} url Server containing the presence service (optional if connected). * @... {bool} isCluster Specifies if url is cluster (optional if connected). * @... {String} applicationKey Application key with access to presence service (optional if connected). * @... {String} privateKey The private key provided when the ORTC service is purchased. * @... {String} channel Channel to disable presence. * @param {function} Callback with error and result parameters. * @returns void * * </br> * <b class="codeSample">Code Sample:</b> * * <pre class="brush: js; gutter: false;"> * ortcClient.disablePresence({ * applicationKey : 'ORTC_APPLICATION_KEY', * channel : 'CHANNEL_TO_ACTIVATE_PRESENCE', * privateKey : 'ORTC_PRIVATE_KEY', * url : 'http://ortc-developers.realtime.co/server/2.1/', * isCluster : true * }, * function(error,result){ * if(error){ * console.log('Presence',error); * }else{ * console.log('Presence enable',result); * }); * </pre> */ this.disablePresence = function (parameters,callback) { try{ var requestUrl = null , isCluster = false , appKey = ortc.appKey; if(parameters.url){ requestUrl = parameters.url; isCluster = parameters.isCluster; appKey = parameters.applicationKey; }else{ if (clusterUrl && clusterUrl != null) { requestUrl = clusterUrl; isCluster = true; } else { requestUrl = url; } } getServerUrl({ requestUrl : requestUrl, isCluster : isCluster, appKey : appKey }, function(error,serverUrl){ if(error){ callback(error,null); }else{ IbtRealtimeRestServices.disablePresence({ url : serverUrl, appKey : appKey, privateKey : parameters.privateKey, channel : parameters.channel },callback); } }); }catch(e){ callback('Unable to disable presence ' + e,null); } }; /*********************************************************** * @private methods ***********************************************************/ var getServerUrl = function(parameters,callback){ if (parameters.requestUrl && parameters.isCluster) { var clusterUrl = ortcTreatUrl(parameters.requestUrl); getServerFromCluster({ clusterUri : clusterUrl, appKey : parameters.appKey }, function (error, body) { if (error != null) { callback('Error getting server from Cluster',null); } else { var resultUrl = body.substring(body.indexOf('=') + 3, body.length - 2); callback(null,resultUrl); } }); } else { var resultUrl = ortcTreatUrl(parameters.requestUrl); callback(null,resultUrl); } }; var clearReconnectInterval = function () { // Clear the reconnecting interval if (ortc.reconnectIntervalId) { clearInterval(ortc.reconnectIntervalId); ortc.reconnectIntervalId = null; } // Clear the reconnecting timeout if (ortc.reconnectTimeoutlId) { clearTimeout(ortc.reconnectTimeoutlId); ortc.reconnectTimeoutlId = null; } }; var ortcIsValidUrl = function (input) { return (/^\s*(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?\s*$/).exec(input) ? true : false; }; var ortcIsValidInput = function (input) { return (/^[\w-:\/\.]*$/).exec(input) ? true : false; }; var ortcIsValidBoolean = function (input) { return (/^(true|false|0|1)$/).exec(input) ? true : false; }; var ortcIsFunction = function (input) { return typeof (input) == 'function' ? true : false; }; var ortcIsString = function (input) { return typeof (input) == 'string' ? true : false; }; var ortcTreatUrl = function (url) { url = url.replace(/\s+/g, ''); if (url.charAt(url.length - 1) == '/') { url = url.substring(0, url.length - 1); } return url; }; /* * Generates an ID. */ var generateId = function (size) { var result = ''; var S4 = function () { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); }; for (var i = 0; i < size / 4; i++) { result += S4(); } return result; }; /* * Disconnects the socket. */ var disconnectSocket = function () { reconnectStartedAt = null; isConnecting = false; if (ortc.sockjs != null) { ortc.sockjs.close(); ortc.sockjs = null; } }; /* * Reconnects the socket. */ var reconnectSocket = function (clusterTimeout) { clearReconnectInterval(); ortc.sockjs = null; if (isConnecting) { delegateExceptionCallback(ortc, 'Unable to connect'); } isConnecting = true; delegateReconnectingCallback(ortc); reconnectStartedAt = new Date().getTime(); if (clusterUrl && clusterUrl != null) { clusterConnection(clusterTimeout); } else { ortc.sockjs = createSocketConnection(url); } if (!ortc.reconnectTimeoutlId) { // Timeout to reconnect ortc.reconnectTimeoutlId = setTimeout(function () { if (!isConnected) { reconnectSocket(ortc.getConnectionTimeout()); } }, ortc.getConnectionTimeout()); } }; /* * Tries a connection through the cluster gateway with the application key and authentication token. */ var clusterConnection = function (clusterTimeout) { if (clusterUrl != null) { getServerFromCluster({ clusterUri : clusterUrl, clusterTimeout : clusterTimeout, appKey : ortc.appKey }, function (error, body) { if (error != null) { delegateExceptionCallback(ortc, 'Error getting server from Cluster'); } else { if (body.indexOf('SOCKET_SERVER') >= 0) { url = body.substring(body.indexOf('=') + 3, body.length - 2); sockjs = createSocketConnection(ortc.getUrl()); } } }); } }; /* * Gets server from the cluster. */ var getServerFromCluster = function (parameters,callback) { var queryString = parameters.appKey ? 'appkey=' + parameters.appKey : ""; var parsedUrl = urlParser.parse(parameters.clusterUri + '?' + queryString); httpRequest.get({ url : parsedUrl.hostname, path : parsedUrl.pathname, parameters : parsedUrl.query, callback : function(error,responseData){ if (error != null) { callback(error, null); } else { callback(null, responseData.content); } } }); }; /* * Creates a socket connection. */ var createSocketConnection = function (connectionUrl) { if (ortc.sockjs == null) { var wsScheme = 'ws'; var wsUrl = connectionUrl; if (connectionUrl.substring(0, 7) == 'http://') { wsUrl = wsUrl.substring(7); } else if (connectionUrl.substring(0, 8) == 'https://') { wsUrl = wsUrl.substring(8); wsScheme = 'wss'; } var connid = strings.random_string(8); var serverId = strings.random_number_string(1000); ortc.sockjs = new WebSocket(wsScheme + '://' + wsUrl + '/broadcast/' + serverId + '/' + connid + '/websocket'); // Connect handler ortc.sockjs.onopen = function () { // Update last keep alive time lastKeepAlive = new Date().getTime(); ortc.sockjs.send(JSON.stringify('validate;' + ortc.appKey + ';' + ortc.authToken + ';' + (announcementSubChannel ? announcementSubChannel : '') + ';' + '' + ';' + (connectionMetadata ? connectionMetadata : ''))); }; // Disconnect handler ortc.sockjs.onclose = function (e) { ortc.sockjs = null; if (isConnected) { isConnected = false; isConnecting = false; delegateDisconnectedCallback(ortc); } if (!stopReconnecting && (!reconnectStartedAt || (reconnectStartedAt + connectionTimeout < new Date().getTime()))) { reconnectSocket(ortc.getConnectionTimeout()); } }; // Error handler (when connecting to an non existent server) ortc.sockjs.onerror = function () { }; // Receive handler ortc.sockjs.onmessage = function (m) { // Update last keep alive time lastKeepAlive = new Date().getTime(); var messageType = m.data[0]; switch (messageType) { case 'o': // open break; case 'a': // message var data = JSON.parse(JSON.parse(m.data.substring(1))[0]); var op = data.op; switch (op) { case 'ortc-validated': if (data.up) { userPerms = data.up; // user permissions } isConnecting = false; isConnected = true; reconnectStartedAt = null; if (alreadyConnectedFirstTime) { var channelsToRemove = Object.create(null); // Subscribe to the previously subscribed channels for (var key in subscribedChannels) { // Subscribe again if (subscribedChannels[key].subscribeOnReconnected == true && (subscribedChannels[key].isSubscribing || subscribedChannels[key].isSubscribed)) { subscribedChannels[key].isSubscribing = true; subscribedChannels[key].isSubscribed = false; var domainChannelCharacterIndex = key.indexOf(':'); var channelToValidate = key; var hashPerm = null; if (domainChannelCharacterIndex > 0) { channelToValidate = key.substring(0, domainChannelCharacterIndex + 1) + '*'; } if (userPerms && userPerms != null) { hashPerm = userPerms[channelToValidate] ? userPerms[channelToValidate] : userPerms[key]; } ortc.sockjs.send(JSON.stringify('subscribe;' + ortc.appKey + ';' + ortc.authToken + ';' + key + ';' + hashPerm)); } else { channelsToRemove[key] = key; } } for (var chnKey in channelsToRemove) { ObjectLiterals.removeEntry(subscribedChannels,chnKey); } // Clean messages buffer (can have lost message parts in memory) messagesBuffer = Object.create(null); delegateReconnectedCallback(ortc); } else { alreadyConnectedFirstTime = true; delegateConnectedCallback(ortc); } if (!ortc.reconnectIntervalId) { // Interval to reconnect ortc.reconnectIntervalId = setInterval(function () { if (lastKeepAlive != null && (lastKeepAlive + 35000 < new Date().getTime())) { // 35 seconds lastKeepAlive = null; // Server went down if (isConnected) { disconnectSocket(); if (isConnected) { isConnected = false; isConnecting = false; delegateDisconnectedCallback(ortc); reconnectSocket(ortc.getConnectionTimeout()); } } } }, ortc.getConnectionTimeout()); } break; case 'ortc-subscribed': var channelSubscribed = data.ch; if (subscribedChannels[channelSubscribed]) { subscribedChannels[channelSubscribed].isSubscribing = false; subscribedChannels[channelSubscribed].isSubscribed = true; } delegateSubscribedCallback(ortc, channelSubscribed) break; case 'ortc-unsubscribed': var channelUnsubscribed = data.ch; if (subscribedChannels[channelUnsubscribed]) { subscribedChannels[channelUnsubscribed].isSubscribing = false; subscribedChannels[channelUnsubscribed].isSubscribed = false; } delegateUnsubscribedCallback(ortc, channelUnsubscribed) break; case 'ortc-error': var data = data.ex ? data.ex : data; var operation = data.op; var error = data.ex; delegateExceptionCallback(ortc, error); switch (operation) { case 'validate': if(error.indexOf("Unable to connect") > -1 || error.indexOf("Server is too busy") > -1){ }else{ invalidConnection = true; // Stop the reconnecting process stopReconnecting = true; alreadyConnectedFirstTime = false; clearReconnectInterval(); } break; case 'subscribe': if (channel && subscribedChannels[channel]) { subscribedChannels[channel].isSubscribing = false; } break; case 'subscribe_maxsize': case 'unsubscribe_maxsize': case 'send_maxsize': if (channel && subscribedChannels[channel]) { subscribedChannels[channel].isSubscribing = false; } // Stop the reconnecting process stopReconnecting = true; alreadyConnectedFirstTime = false; clearReconnectInterval(); break; default: break; } if (stopReconnecting) { delegateDisconnectedCallback(ortc); } break; default: var channel = data.ch; var message = data.m; // Multi part var regexPattern = /^(\w[^_]*)_{1}(\d*)-{1}(\d*)_{1}([\s\S.]*)$/; var match = regexPattern.exec(message); var messageId = null; var messageCurrentPart = 1; var messageTotalPart = 1; var lastPart = false; if (match && match.length > 0) { if (match[1]) { messageId = match[1]; } if (match[2]) { messageCurrentPart = match[2]; } if (match[3]) { messageTotalPart = match[3]; } if (match[4]) { message = match[4]; } } // Is a message part if (messageId) { if (!messagesBuffer[messageId]) { messagesBuffer[messageId] = Object.create(null); } messagesBuffer[messageId][messageCurrentPart] = message; // Last message part if (Object.keys(messagesBuffer[messageId]).length == messageTotalPart) { lastPart = true; } } // Message does not have multipart, like the messages received at announcement channels else { lastPart = true; } if (lastPart) { if (messageId) { message = ''; f