@iosio/websocket-client
Version:
Simple wrapper for native client-side WebSocket. Similar to socket.io. Easy to use with any backend
503 lines (403 loc) • 16.7 kB
JavaScript
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(['exports', '@iosio/utils/lib/type_checks', '@iosio/utils/lib/eventer', '@iosio/utils/lib/string_manipulation', '@iosio/utils/lib/number_generation'], factory);
} else if (typeof exports !== "undefined") {
factory(exports, require('@iosio/utils/lib/type_checks'), require('@iosio/utils/lib/eventer'), require('@iosio/utils/lib/string_manipulation'), require('@iosio/utils/lib/number_generation'));
} else {
var mod = {
exports: {}
};
factory(mod.exports, global.type_checks, global.eventer, global.string_manipulation, global.number_generation);
global.index = mod.exports;
}
})(this, function (exports, _type_checks, _eventer, _string_manipulation, _number_generation) {
'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 Socket =
/**
* Creates an instance of Socket.
* @param {Object} config - the initial configuration for the module
* @memberof Socket
*/
function Socket(config) {
var _this = this;
_classCallCheck(this, Socket);
this._onOpen = function () {
if (_this._reconnectTimeout) {
clearTimeout(_this._reconnectTimeout);
}
_this._log('socket connected');
_this._eventer.emit(_this.CONNECT);
};
this._onError = function () {
_this._log('socket error');
_this._eventer.emit(_this.ERROR);
};
this._onClose = function (e) {
// 1000: // CLOSE_NORMAL
_this._log('socket closed. error code: ', e.code);
_this._eventer.emit(_this.DISCONNECT);
// this._socket.onclose = this._socket.onopen = this._socket.onerror = null;
if (_this._auto_reconnect && !_this._deliberateClose) {
_this._attemptReconnect();
}
};
this.close = function () {
_this._deliberateClose = true;
//clean up rest
// this._reconnectionInProgress = false;
if (_this._reconnectTimeout) {
clearInterval(_this._reconnectTimeout);
}
if (_this._isConnected()) {
_this._log('closing socket');
_this._socket && _this._socket.close();
} else {
_this._log('socket is already closed');
}
};
this._attemptReconnect = function () {
var time = _this._auto_reconnect && _this._auto_reconnect.every ? _this._auto_reconnect.every : 2000;
if (_this._reconnectTimeout) {
clearTimeout(_this._reconnectTimeout);
}
_this._log('attempting reconnect in: ' + time + 'ms');
_this._reconnectTimeout = setTimeout(function () {
_this._log('reconnecting');
_this._eventer.emit(_this.RECONNECTING);
!_this._isConnected() && _this.open();
}, time);
};
this._validateReceivedMessage = function (data) {
var parsed = (0, _string_manipulation.tryParse)(data); // returns {ok,data,error}
if (!parsed.ok) {
_this._log('data received from socket does not have valid format. instead received:', parsed.data, true);
return { ok: false };
}
if (!parsed.data.event || typeof parsed.data.event !== 'string') {
_this._log('received messages are required to have an event property of type string', '', true);
return { ok: false };
}
var message = parsed.data;
if (!_this._callbacks[message.event]) {
_this._log('no handler exists from this event received from socket:', message.event, true);
return { ok: false };
}
return { ok: true, message: message };
};
this._onMessage = function (_ref) {
var data = _ref.data;
var validation = _this._validateReceivedMessage(data);
if (!validation.ok) {
return;
}
var message = validation.message;
_this._eventer.emit(message.event, message.data);
_this._isResponse(message.event) && _this._eventer.destroy(message.event);
};
this._isConnected = function () {
return _this._socket ? _this._socket.readyState === _this._WebSocket.OPEN : false;
};
this._isResponse = function (res) {
return (0, _type_checks.isString)(res) && res.search('@response-') > -1;
};
this.open = function () {
_this._deliberateClose = false;
if (_this._reconnectTimeout) {
clearInterval(_this._reconnectTimeout);
}
_this._log('initializing socket');
if (!_this._isConnected()) {
try {
_this._socket = new _this._WebSocket(_this._url, _this._websocket_options);
} catch (e) {
_this._log('error instantiating WebSocket', e, true);
_this._socket = false;
}
if (!_this._socket) {
return;
}
_this._socket.onopen = _this._onOpen;
_this._socket.onerror = _this._onError;
_this._socket.onmessage = _this._onMessage;
_this._socket.onclose = _this._onClose;
} else {
_this._log('socket already open');
}
};
this._isValidOnEventArgs = function (event, cb) {
if (!(0, _type_checks.isString)(event)) {
_this._log('Must provide a string for the event type.', '', true);
return false;
}
if (!(0, _type_checks.isFunction)(cb)) {
_this._log('Must provide a function for a callback. Must be named function if you want to remove its listener', '', true);
return false;
}
return true;
};
this.on = function (event, cb) {
_this._isValidOnEventArgs(event, cb) && _this._eventer.on(event, cb);
};
this.off = function (event, cb) {
if (_this._isValidOnEventArgs(event, cb) && cb.name) {
_this._eventer.off(event, cb);
} else {
_this.log('callback function passed to .off must also be a named function (not anonymous) ');
}
};
this.send = function (event) {
var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var response_id = arguments[2];
if (!_this._isConnected()) {
return;
}
if (!(0, _type_checks.isString)(event)) {
_this._log('.send must provide a string for the event type.', '', true);
return;
}
var is_request = (0, _type_checks.isString)(response_id);
var message = void 0;
if (is_request) {
message = {
event: event,
data: data,
type: 'request',
response_id: response_id
};
if ((0, _type_checks.isFunction)(_this._requestMapper)) {
message = _this._requestMapper(message);
}
} else {
message = {
event: event,
data: data,
type: 'send'
};
if ((0, _type_checks.isFunction)(_this._sendMapper)) {
message = _this._sendMapper(message);
}
}
_this._socket.send(JSON.stringify(message));
};
this._validateRequestArgs = function (event, params_or_cb_if_no_params, cb_if_params) {
var ok = true;
if (!(0, _type_checks.isString)(event)) {
_this._log('/request method: a string event name is required', '', true);
ok = false;
}
if (!(0, _type_checks.isObject)(params_or_cb_if_no_params) && !(0, _type_checks.isFunction)(params_or_cb_if_no_params)) {
_this._log('/request method: a params object or callback function is required on the second parameter', '', true);
ok = false;
}
if ((0, _type_checks.isObject)(params_or_cb_if_no_params) && !(0, _type_checks.isFunction)(cb_if_params)) {
_this._log('/request method: a callback function is required as a third parameter if a param object is passed as the second', '', true);
ok = false;
}
if (!ok) {
return { ok: false };
}
if ((0, _type_checks.isObject)(params_or_cb_if_no_params)) {
return {
ok: true,
params: params_or_cb_if_no_params,
cb: cb_if_params
};
} else if ((0, _type_checks.isFunction)(params_or_cb_if_no_params)) {
return {
ok: true,
params: {},
cb: params_or_cb_if_no_params
};
}
return { ok: false };
};
this.request = function (event, params_or_cb_if_no_params, cb_if_params) {
var _validateRequestArgs = _this._validateRequestArgs(event, params_or_cb_if_no_params, cb_if_params),
ok = _validateRequestArgs.ok,
params = _validateRequestArgs.params,
cb = _validateRequestArgs.cb;
if (!ok) {
_this._log('request arguments are invalid', '', true);
return;
}
var response_id = '@response-' + event + '-' + (0, _number_generation.uniqueID)();
_this._log('requesting');
_this.send(event, params, response_id);
_this.on(response_id, cb, true);
};
this._log = function (msg) {
var arg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
var error = arguments[2];
if (_this._should_console_log) {
error ? console.error('Socket.js: ' + msg, arg) : console.info('Socket.js: ' + msg, arg);
}
};
if (!config) {
console.error('missing required config object for socket. required config:');
return;
}
var websocket = config.websocket,
url = config.url,
websocket_options = config.websocket_options,
auto_reconnect = config.auto_reconnect,
requestMapper = config.requestMapper,
sendMapper = config.sendMapper,
should_console_log = config.should_console_log;
if (!url) {
console.error('missing required url for socket');
}
this._url = url ? url : null;
this._auto_reconnect = auto_reconnect ? auto_reconnect : false;
this._websocket_options = websocket_options;
this._requestMapper = null;
if ((0, _type_checks.isFunction)(requestMapper)) {
this._requestMapper = requestMapper;
}
this._sendMapper = false;
if ((0, _type_checks.isFunction)(sendMapper)) {
this._sendMapper = sendMapper;
}
this._reconnectTimeout = null;
this._deliberateClose = false;
this._should_console_log = should_console_log ? should_console_log : false;
this._WebSocket = websocket ? websocket : WebSocket;
this._socket = null;
this.CONNECT = 'connect';
this.DISCONNECT = 'disconnect';
this.RECONNECTING = 'reconnecting';
this.ERROR = 'error';
this._callbacks = Object.create(null);
this._eventer = (0, _eventer.Eventer)(this._callbacks);
this._reconnectionInProgress = false;
}
/**
* Handles emitting when the socket is successfully connected to a websocket server
* @memberof Socket
* @returns {undefined}
*/
/**
* Handles emitting when there is an error
* @memberof Socket
* @returns {undefined}
*/
/**
* Handles emitting when the socket's connection is closed.
* It will also handle auto connection if the connection is not deliberately closed
* and auto connect is enabled
* @memberof Socket
* @returns {undefined}
*
*
* 1000 - CLOSE_NORMAL
* 1006 - CLOSE_ABNORMAL
* 1007 - unsupported payload
* 1011 - Server error (Internal server error while operating)
* 1012 - Server/service is restarting
* 1014 - Bad gateway
* ...
* https://github.com/Luka967/websocket-close-codes
*
*/
/**
* Will close the websocket connection deliberately
* @memberof Socket
* @returns {undefined}
*/
/**
* Attempts to reconnect on a periodical basis
* @memberof Socket
* @returns {undefined}
*/
/**
* Checks if the received message is in the correct format
* @param {Object} data - the received data object
* @memberof Socket
* @returns {Object} - is the received message valid
*/
/**
* Handles emitting when a message is received
* @param {Object} * - the message received
* @property {Object} data the data received from the message
* @memberof Socket
* @returns {undefined}
*/
/**
* Checks if the websocket is connected
* @memberof Socket
* @returns {Boolean} - is the socket connected
*/
/**
* Checks if the message is a response for a named callback emittion
* @param {*} res - the response
* @returns {Boolean} - is the data a response
*/
/**
* Handles opening a websocket connection to the configured destination
* @memberof Socket
* @returns {undefined}
*/
/**
* Checks if the arguments for an event are valid
* @param {String} event - the name of the event
* @param {Function} cb - the callback
* @memberof Socket
* @returns {Object} - are the arguments valid
*/
/**
* Registers an event to be listened for when the websocket client recieves an event from its connected server
* @param {String} event - the name of the event to listen to
* @param {Function} cb - the action to take when the event is received
* @memberof Socket
* @returns {undefined}
*/
/**
* Unregisters an event listener
* @param {String} event - the event name
* @param {Function} cb - the callback
* @memberof Socket
* @returns {undefined}
*/
/**
* Sends a message to the connected websocket server
* @param {String} event - the event to send
* @param {Object} data - the data to send with the event
* @param {String} response_id - the response id
* @memberof Socket
* @returns {undefined}
*/
/**
* Validates the params of a request
* @param {String} event - the event to validate
* @param {Object|Function} params_or_cb_if_no_params - either the options or the callback
* @param {Function} cb_if_params - the callback if there are options
* @memberof Socket
* @returns {Object} - is the request valid
*/
/**
* Creates a named request to the server, the difference between this and send, is that this function
* will also register an event listener for the next response based on the name of this event
* @param {String} event - the name of the event
* @param {Object|Function} params_or_cb_if_no_params - either params or a callback
* @param {Function} cb_if_params - params if there is a callback
* @memberof Socket
* @returns {undefined}
*/
/**
* Handles logging if logging is enabled
* @param {String} msg - the message to log
* @param {String} arg - any arguments to log with the message
* @param {Boolean} error - is this an error
* @memberof Socket
* @returns {undefined}
*/
;
exports.default = Socket;
});