UNPKG

diffusion

Version:

Diffusion JavaScript client

389 lines (345 loc) 13.6 kB
/*eslint valid-jsdoc: "off"*/ /*global Uint8Array Int8Array Int16Array Int32Array Uint8Array Uint8ClampedArray Uint16Array Uint32Array Float32Array Float64Array */ var DEFAULT_HOST = 'localhost'; var DEFAULT_PORT = 80; var DEFAULT_SECURE_PORT = 443; var DEFAULT_SECURE = true; var DEFAULT_PATH = '/diffusion'; // Browser environment checks if (typeof window !== 'undefined') { if (window.location.hostname) { DEFAULT_HOST = window.location.hostname; } // A blank port indicates it's the default associated with the protocol (80 or 443) if (window.location.protocol === "http:") { DEFAULT_SECURE = false; if (window.location.port) { DEFAULT_PORT = parseInt(window.location.port); } } if (window.location.protocol === "https:") { DEFAULT_SECURE = true; // If we're served over https and have an explicit port, assume that as default if (window.location.port) { DEFAULT_SECURE_PORT = parseInt(window.location.port); } } } var DEFAULT_RECONNECT_TIMEOUT = 60000; var DEFAULT_RECONNECT_STRATEGY = function(start) { setTimeout(start, 5000); }; var DEFAULT_ABORT_STRATEGY = function(start, abort) { abort(); }; var DEFAULT_PRINCIPAL = ""; var DEFAULT_PASSWORD = ""; var DEFAULT_ACTIVITY_MONITOR = true; var DEFAULT_TRANSPORTS = ['WEBSOCKET']; var DEFAULT_MAX_MESSAGE_SIZE = 2147483647; var MIN_MAX_MESSAGE_SIZE = 1024; /** * Provide Session configuration options. * <P> * <h5>Connection:</h5> * There are several option values that can be configured to change how Diffusion establishes a connection. These * options are used to derive a connection URL in the format: {protocol}://{host}:{port}/{path}. The protocol used is * determined by the chosen transports and whether secure connections are enabled. * <P> * <table class="table striped"> * <thead> * <tr> * <th>Option</th> * <th>Default value</th> * <th>Description</th> * </tr> * </thead> * <tbody> * <tr> * <td>host</td> * <td><code>localhost</code></td> * <td>The hostname to connect to.</td> * </tr> * <tr> * <td>port</td> * <td><code>80</code> or <code>443</code></td> * <td>The port to connect to. The default value depends on whether secure connections are enabled, or if the client * is being run in a page served via <code>http</code> or <code>https</code>.</td> * </tr> * <tr> * <td>path</td> * <td><code>/diffusion</code></td> * <td>The URL path to apply after the hostname/port. This allows additional context to be provided, such as might be * used by load balancers.</td> * </tr> * <tr> * <td>secure</td> * <td><code>true</code></td> * <td>Determines if secure transports will be used. If no <code>port</code> option is specified, this will also * determine the port to use.</td> * </tr> * </tbody> * </table> * <P> * <h5>Reconnection:</h5> * Reconnection is enabled by default, and accepts several different option values. * <table class="table striped"> * <thead> * <tr> * <th>Option type</th> * <th>Default value</th> * <th>Description</th> * </tr> * </thead> * <tbody> * <tr> * <td><code>boolean</code></td> * <td><code>true</code></td> * <td>Enables or disables reconnection. If set to <code>true</code>, reconnection will be enabled using the default * timeout value and a periodic back-off strategy.</td> * </tr> * <tr> * <td><code>number</code></td> * <td><code>60000</code></td> * <td>Passing a number will enable reconnection with the default strategy and the reconnection timeout set to the * specified value. The reconnection timeout determines how long, in milliseconds, the client will remain in a * <code>disconnected</code> state before the client is closed.</td> * </tr> * <tr> * <td><code>function</code></td> * <td><code>function(reconnect, abort) {<br/> * setTimeout(reconnect, 5000);<br/> * }</code></td> * <td>A strategy function that will be called when the client enters a <code>disconnected</code> state, and * subsequently if attempts to reconnect fail. Two arguments are provided, <code>reconnect</code> and <code>abort</code> * - these are functions to be called within the strategy. The <code>reconnect</code> argument will initiate a * reconnect attempt. <code>abort</code> may be called to abort reconnection, in which case the client will be closed. * </td> * </tr> * <tr> * <td><code>{<br/> timeout : &lt;number&gt;,<br /> strategy : &lt;function&gt;<br/>}</code></td> * <td><code>{<br/> timeout : 60000,<br /> strategy : function(reconnect, abort) {<br/> * setTimeout(reconnect, 5000);</br> * }<br />}</code></td> * <td>An object containing both the timeout and strategy options as specified above, allowing both to be set together. * </td> * </tr> * </tbody> * </table> * <P> * <h5>Transports:</h5> * The <code>transports</code> property configures how the session should connect. It can be set to either a * <code>string</code>, or an <code>array</code> of strings to provide a transport cascading capability. * <table class="table striped"> * <thead> * <tr> * <th>Transport key</th> * <th>Description</th> * </tr> * </thead> * <tbody> * <tr> * <td><code>ws</code>, <code>WS</code>, <code>WEBSOCKET</code></td> * <td>The WebSocket transport. A single, long-lived WebSocket connection will be used to send and receive data.</td> * </tr> * <tr> * <td><code>xhr</code>, <code>XHR</code>, <code>HTTP_POLLING</code></td> * <td>An XHR-based polling transport. Data will be queued on the client and server, and sent in batches.</td> * </tr> * </tbody> * </table> * The client will use the transports in the order provided, for example: * <code>transports: ['WS', 'XHR']</code> indicates that the client will attempt to connect with the WebSocket * transport, and if the connection fails, the client will attempt to connect with the HTTP Polling transport. When no * <code>transports</code> value is provided the client will default to using the WebSocket transport. Any string values * that do not have an associated transport will be ignored. * <P> * <h5>Properties:</h5> * <p> * Supplied session properties will be provided to the server when a session * is created using this session factory. The supplied properties will be * validated during authentication and may be discarded or changed. * <p> * The specified properties will be added to any existing properties set for * this session factory. If any of the keys have been previously declared * then they will be overwritten with the new values. * <p> * For details of how session properties are used see {@link Session}. * * @typedef {object} Session.Options * @property {String} [host=localhost] - The hostname to connect to * @property {Number|String} [port=443] - The port to connect to. * @property {String} [path=/diffusion] - The request path used for connections * @property {Boolean} [secure=true] - Whether to use secure connections * @property {String} [principal] - The principal name this session should connect with. Used for authentication. * @property {String|Buffer|TypedArray|Array} [credentials] - A password string to authenticate with, * a buffer containing custom credentials in binary format, a typed array i.e. Uint8Array or a regular array of octets. * @property {Boolean|Number|Function|Object} [reconnect=true] - Reconnection options. * @property {String|Array} [transports=["WEBSOCKET"]] - The transports to be used for connection establishment. * @property {Number} [maxMessageSize=2147483647] - The maximum size of messages that may be received from the server. * @property {Object} properties An object of key-value pairs that define the user-defined session properties */ // private constructor /*eslint complexity: ["error", 33]*/ function Options(options) { options = options || {}; // Override options.host and options.port if supplied together // in options.host. if (options.host === undefined) { options.host = DEFAULT_HOST; } else if (options.host.indexOf(':') > -1) { var parts = options.host.split(':'); if (options.port === undefined) { options.port = parseInt(parts[1]); } options.host = parts[0]; } if (options.path === undefined) { options.path = DEFAULT_PATH; } else { if (options.path[0] !== '/') { options.path = '/' + options.path; } // Assert that the path ends with "/diffusion" if (options.path.substring(options.path.length - DEFAULT_PATH.length) !== DEFAULT_PATH) { if (options.path[options.path.length - 1] === '/') { options.path = options.path.substring(0, options.path.length - 1); } options.path = options.path + DEFAULT_PATH; } } if (isNaN(parseInt(options.port, 10))) { // Set to undefined in order to let us derive 'secure' option correctly options.port = undefined; } else { options.port = parseInt(options.port, 10); } if (options.secure === undefined) { if (options.port === undefined) { // Default to secure on secure port. options.secure = DEFAULT_SECURE; } else { // If specified port 80, default to insecure else secure. options.secure = options.port === DEFAULT_SECURE_PORT ? true : false; } } if (options.port === undefined) { // Security specified but not port, choose 443 or 80? options.port = options.secure ? DEFAULT_SECURE_PORT : DEFAULT_PORT; } this.host = options.host; this.port = options.port; this.path = options.path; this.secure = options.secure; if (options.reconnect === undefined || (typeof options.reconnect === 'boolean') && options.reconnect) { this.reconnect = { timeout : DEFAULT_RECONNECT_TIMEOUT, strategy : DEFAULT_RECONNECT_STRATEGY }; } else if (typeof options.reconnect === 'number') { this.reconnect = { timeout : options.reconnect, strategy : DEFAULT_RECONNECT_STRATEGY }; } else if (typeof options.reconnect === 'function') { this.reconnect = { timeout : DEFAULT_RECONNECT_TIMEOUT, strategy : options.reconnect }; } else if (typeof options.reconnect === 'object') { this.reconnect = { timeout : options.reconnect.timeout === undefined ? DEFAULT_RECONNECT_TIMEOUT : options.reconnect.timeout, strategy : options.reconnect.strategy || DEFAULT_RECONNECT_STRATEGY }; } else { this.reconnect = { timeout : 0, strategy : DEFAULT_ABORT_STRATEGY }; } if (options.principal !== undefined) { this.principal = options.principal || DEFAULT_PRINCIPAL; if (typeof options.credentials === 'string') { this.credentials = options.credentials; } else if (isTypedArray(options.credentials)) { this.credentials = Buffer.from(options.credentials.buffer); } else if (Array.isArray(options.credentials)) { options.credentials.forEach(function(element) { if (typeof element !== 'number' || element > 127 || element < -128) { throw new Error('Custom credentials invalid. Element must be octet.'); } }); this.credentials = Buffer.from(options.credentials); } else { this.credentials = DEFAULT_PASSWORD; } } if (typeof options.transports === 'string') { this.transports = [options.transports]; } else if (typeof options.transports === 'object' && options.transports instanceof Array && options.transports.length > 0) { this.transports = options.transports.slice(); } else { this.transports = DEFAULT_TRANSPORTS.slice(); } this.transports = this.transports.slice().map(function(t) { return t.toUpperCase(); }); var mms; if (options.maxMessageSize && options.maxMessageSize > MIN_MAX_MESSAGE_SIZE) { mms = options.maxMessageSize; } else { mms = DEFAULT_MAX_MESSAGE_SIZE; } this.maxMessageSize = mms; this.activityMonitor = (options.activityMonitor !== undefined) ? options.activityMonitor : DEFAULT_ACTIVITY_MONITOR; if (options.properties) { var properties = {}; Object.getOwnPropertyNames(options.properties).forEach(function(key) { if (typeof options.properties[key] === 'string') { properties[key] = options.properties[key]; } }); this.properties = properties; } } function isTypedArray(obj) { return (obj instanceof Int8Array || obj instanceof Int16Array || obj instanceof Int32Array || obj instanceof Uint8Array || obj instanceof Uint8ClampedArray || obj instanceof Uint16Array || obj instanceof Uint32Array || obj instanceof Float32Array || obj instanceof Float64Array); } /** * Create a new Options object from this, with additional values provided. * * @param {Object} options - Object to merge * @returns {Options} the new options */ Options.prototype.with = function(options) { var o = {}; var k; for (k in this) { o[k] = this[k]; } for (k in options) { o[k] = options[k]; } return new Options(o); }; module.exports = Options;