diffusion
Version:
Diffusion JavaScript client
389 lines (345 loc) • 13.6 kB
JavaScript
/*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 : <number>,<br /> strategy : <function><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;