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

websocket-as-promised

Package Overview
Dependencies
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

websocket-as-promised - npm Package Compare versions

Comparing version 0.3.2 to 0.4.0

lib/options.js

194

lib/index.js

@@ -18,13 +18,7 @@ 'use strict';

var Channel = require('chnl');
var Pendings = require('pendings');
var ControlledPromise = require('controlled-promise');
var Requests = require('./requests');
var utils = require('./utils');
var defaultOptions = require('./options');
var DEFAULT_OPTIONS = {
createWebSocket: function createWebSocket(url) {
return new WebSocket(url);
},
idProp: 'id',
timeout: 0
};
// see: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants

@@ -47,6 +41,8 @@ var STATE = {

*
* // todo: use options.js
* @param {String} url WebSocket URL
* @param {Object} [options]
* @param {Function} [options.createWebSocket=url => new Websocket(url)] custom WebSocket creation method
* @param {String} [options.idProp="id"] id property name attached to each message
* @param {Function} [options.createWebSocket=url => new Websocket(url)] custom WebSocket creation function
* @param {Function} [options.packMessage] custom packing message function
* @param {Function} [options.packMessage] custom unpacking message function
* @param {Number} [options.timeout=0] default timeout for requests

@@ -58,9 +54,10 @@ */

this._url = url;
this._options = Object.assign({}, DEFAULT_OPTIONS, utils.removeUndefined(options));
this._opening = new Pendings.Pending();
this._closing = new Pendings.Pending();
this._pendingRequests = new Pendings({ timeout: this._options.timeout });
this._options = utils.mergeDefaults(options, defaultOptions);
this._opening = new ControlledPromise();
this._closing = new ControlledPromise();
this._requests = new Requests();
this._onMessage = new Channel();
this._onClose = new Channel();
this._ws = null;
this._wsSubscription = null;
}

@@ -88,21 +85,25 @@

if (this.isClosing) {
return Promise.reject('Can not open closing WebSocket');
} else {
var _options = this._options,
timeout = _options.timeout,
createWebSocket = _options.createWebSocket;
return Promise.reject(new Error('Can not open closing WebSocket connection'));
}
if (this.isOpened) {
return this._opening.promise;
}
return this._opening.call(function () {
var timeout = _this._options.timeout;
return this._opening.call(function () {
_this._closing.reset();
_this._ws = createWebSocket(_this._url);
_this._addWsListeners();
}, timeout);
}
_this._opening.timeout(timeout, 'Can\'t open WebSocket connection within allowed timeout: ' + timeout + ' ms');
_this._opening.promise.catch(function (e) {
return _this._cleanupForClose(e);
});
_this._createWS();
});
}
/**
* Performs JSON request and waits for response.
* Performs request and waits for response.
*
* @param {Object} data
* @param {Object} [options]
* @param {String} [options.requestId]
* @param {String} [options.requestIdPrefix]
* @param {Number} [options.timeout]

@@ -114,35 +115,21 @@ * @returns {Promise}

key: 'request',
value: function request(data, options) {
value: function request(data) {
var _this2 = this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (!data || (typeof data === 'undefined' ? 'undefined' : _typeof(data)) !== 'object') {
return Promise.reject(new Error('WebSocket request data should be a plain object, got ' + data));
}
var idProp = this._options.idProp;
var fn = function fn(id) {
data[idProp] = id;
_this2.sendJson(data);
};
var id = data[idProp];
var promise = id === undefined ? this._pendingRequests.add(fn, options) : this._pendingRequests.set(id, fn, options);
return promise.catch(handleTimeoutError);
var requestId = options.requestId || utils.generateId(options.requestIdPrefix);
var message = this._options.packMessage(requestId, data);
var timeout = options.timeout !== undefined ? options.timeout : this._options.timeout;
return this._requests.create(requestId, function () {
return _this2.send(message);
}, timeout);
}
/**
* Sends JSON data and does not wait for response.
* Sends any data by WebSocket.
*
* @param {Object} data
*/
}, {
key: 'sendJson',
value: function sendJson(data) {
var dataStr = JSON.stringify(data);
this.send(dataStr);
}
/**
* Sends any WebSocket compatible data.
*
* @param {String|ArrayBuffer|Blob} data

@@ -172,28 +159,44 @@ */

this._opening.reset(new Error('Connection closing by client'));
if (this.isClosed) {
return Promise.resolve(this._closing.value);
}
return this._closing.call(function () {
if (_this3._ws) {
_this3._ws.close();
} else {
// case: close without open
_this3._closing.resolve();
}
}, this._options.timeout);
var timeout = _this3._options.timeout;
_this3._closing.timeout(timeout, 'Can\'t close WebSocket connection within allowed timeout: ' + timeout + ' ms');
_this3._ws.close();
});
}
}, {
key: '_addWsListeners',
value: function _addWsListeners() {
key: '_createWS',
value: function _createWS() {
var _this4 = this;
this._ws = this._options.createWebSocket(this._url);
this._wsSubscription = new Channel.Subscription([{ channel: this._ws, event: 'open', listener: function listener(e) {
return _this4._handleOpen(e);
} }, { channel: this._ws, event: 'message', listener: function listener(e) {
return _this4._handleMessage(e);
} }, { channel: this._ws, event: 'error', listener: function listener(e) {
return _this4._handleError(e);
} }, { channel: this._ws, event: 'close', listener: function listener(e) {
return _this4._handleClose(e);
} }]).on();
}
}, {
key: '_addWSListeners',
value: function _addWSListeners() {
var _this5 = this;
this._ws.addEventListener('open', function (event) {
return _this4._handleOpen(event);
return _this5._handleOpen(event);
});
this._ws.addEventListener('message', function (event) {
return _this4._handleMessage(event);
return _this5._handleMessage(event);
});
this._ws.addEventListener('error', function (event) {
return _this4._handleError(event);
return _this5._handleError(event);
});
this._ws.addEventListener('close', function (event) {
return _this4._handleClose(event);
return _this5._handleClose(event);
});

@@ -208,14 +211,15 @@ }

key: '_handleMessage',
value: function _handleMessage(_ref) {
var data = _ref.data;
var jsonData = void 0;
value: function _handleMessage(event) {
var message = event.data;
var requestId = void 0,
data = void 0;
try {
jsonData = JSON.parse(data);
var id = jsonData && jsonData[this._options.idProp];
this._pendingRequests.tryResolve(id, jsonData);
var result = this._options.unpackMessage(message);
requestId = result.requestId;
data = result.data;
} catch (e) {
// do nothing if can not parse data
// do nothing if can not unpack message
}
this._onMessage.dispatch(jsonData, data);
this._onMessage.dispatchAsync(message, data);
this._requests.resolve(requestId, data);
}

@@ -225,19 +229,31 @@ }, {

value: function _handleError() {
// todo: when this event comes?
// currently no specific handling of this event
}
}, {
key: '_handleClose',
value: function _handleClose(_ref2) {
var reason = _ref2.reason,
code = _ref2.code;
var error = new Error('Connection closed with reason: ' + reason + ' (' + code + ')');
// todo: removeWsListeners
value: function _handleClose(event) {
this._onClose.dispatchAsync(event);
this._closing.resolve(event);
var error = new Error('WebSocket connection closed with reason: ' + event.reason + ' (' + event.code + ')');
if (this._opening.isPending) {
this._opening.reject(error);
}
this._cleanupForClose(error);
}
}, {
key: '_cleanupWS',
value: function _cleanupWS() {
if (this._wsSubscription) {
this._wsSubscription.off();
this._wsSubscription = null;
}
this._ws = null;
this._closing.resolve({ reason: reason, code: code });
this._pendingRequests.rejectAll(error);
this._opening.reset(error);
this._onClose.dispatchAsync({ reason: reason, code: code });
}
}, {
key: '_cleanupForClose',
value: function _cleanupForClose(error) {
this._cleanupWS();
this._requests.rejectAll(error);
}
}, {
key: 'ws',

@@ -334,10 +350,2 @@ get: function get() {

function handleTimeoutError(e) {
// inheritance from built-in classes does not work after babel transpile :(
// does not work: e instanceof Pendings.TimeoutError --> always false
// see: https://stackoverflow.com/questions/42064466/instanceof-using-es6-class-inheritance-chain-doesnt-work
var error = e && e.timeout !== undefined ? new Error('Request rejected by timeout (' + e.timeout + ' ms)') : e;
return Promise.reject(error);
}
module.exports = WebSocketAsPromised;

@@ -7,5 +7,8 @@ 'use strict';

* Utils
* @private
*/
exports.mergeDefaults = function (options, defaultOptions) {
return Object.assign({}, defaultOptions, exports.removeUndefined(options));
};
exports.removeUndefined = function (obj) {

@@ -18,2 +21,6 @@ if (obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object') {

return obj;
};
exports.generateId = function (prefix) {
return '' + (prefix || '') + Date.now() + '-' + Math.random();
};
{
"name": "websocket-as-promised",
"version": "0.3.2",
"version": "0.4.0",
"description": "Promise-based WebSocket wrapper",

@@ -21,12 +21,11 @@ "author": {

"babel": "babel src --out-dir lib",
"test": "mocha test/index.test.js",
"test": "mocha test/specs --require=test/globals.js",
"test-lib": "TEST_LIB=1 npm t",
"ci": "npm run code && npm run babel && npm run test-lib",
"docs": "jsdoc2md --template README_TEMPLATE.md --files src/*.js > README.md",
"release": "npm run code && npm test && npm version $VER && npm publish && git push --follow-tags --no-verify",
"docs": "jsdoc2md --template README_TEMPLATE.md --files src/index.js > README.md",
"release": "npm run code && npm run babel && npm run test-lib && npm version $VER && npm publish && git push --follow-tags --no-verify",
"release-patch": "VER=patch npm run release",
"release-minor": "VER=minor npm run release",
"precommit": "npm run lint-staged && npm t",
"prepush": "npm run code && npm t",
"prepublish": "npm run babel && npm run test-lib"
"prepush": "npm run code && npm t"
},

@@ -44,3 +43,4 @@ "lint-staged": {

"chnl": "^0.4.0",
"pendings": "^0.2.3"
"controlled-promise": "^0.1.2",
"promise.prototype.finally": "^3.0.1"
},

@@ -47,0 +47,0 @@ "devDependencies": {

@@ -79,3 +79,2 @@ # websocket-as-promised

* [.request(data, [options])](#WebSocketAsPromised+request) ⇒ <code>Promise</code>
* [.sendJson(data)](#WebSocketAsPromised+sendJson)
* [.send(data)](#WebSocketAsPromised+send)

@@ -90,3 +89,5 @@ * [.close()](#WebSocketAsPromised+close) ⇒ <code>Promise</code>

// todo: use options.js
| Param | Type | Default | Description |

@@ -96,4 +97,5 @@ | --- | --- | --- | --- |

| [options] | <code>Object</code> | | |
| [options.createWebSocket] | <code>function</code> | <code>url =&gt; new Websocket(url)</code> | custom WebSocket creation method |
| [options.idProp] | <code>String</code> | <code>&quot;id&quot;</code> | id property name attached to each message |
| [options.createWebSocket] | <code>function</code> | <code>url =&gt; new Websocket(url)</code> | custom WebSocket creation function |
| [options.packMessage] | <code>function</code> | | custom packing message function |
| [options.packMessage] | <code>function</code> | | custom unpacking message function |
| [options.timeout] | <code>Number</code> | <code>0</code> | default timeout for requests |

@@ -136,4 +138,4 @@

Listener accepts two arguments:
1. original `event.data`
2. `jsonData` if JSON parse succeeded
1. `jsonData` if JSON parse succeeded
2. original `event.data`

@@ -163,3 +165,3 @@ **Kind**: instance property of [<code>WebSocketAsPromised</code>](#WebSocketAsPromised)

### wsp.request(data, [options]) ⇒ <code>Promise</code>
Performs JSON request and waits for response.
Performs request and waits for response.

@@ -172,19 +174,10 @@ **Kind**: instance method of [<code>WebSocketAsPromised</code>](#WebSocketAsPromised)

| [options] | <code>Object</code> |
| [options.requestId] | <code>String</code> |
| [options.requestIdPrefix] | <code>String</code> |
| [options.timeout] | <code>Number</code> |
<a name="WebSocketAsPromised+sendJson"></a>
### wsp.sendJson(data)
Sends JSON data and does not wait for response.
**Kind**: instance method of [<code>WebSocketAsPromised</code>](#WebSocketAsPromised)
| Param | Type |
| --- | --- |
| data | <code>Object</code> |
<a name="WebSocketAsPromised+send"></a>
### wsp.send(data)
Sends any WebSocket compatible data.
Sends any data by WebSocket.

@@ -191,0 +184,0 @@ **Kind**: instance method of [<code>WebSocketAsPromised</code>](#WebSocketAsPromised)

@@ -10,11 +10,7 @@ /**

const Channel = require('chnl');
const Pendings = require('pendings');
const ControlledPromise = require('controlled-promise');
const Requests = require('./requests');
const utils = require('./utils');
const defaultOptions = require('./options');
const DEFAULT_OPTIONS = {
createWebSocket: url => new WebSocket(url),
idProp: 'id',
timeout: 0,
};
// see: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants

@@ -36,6 +32,8 @@ const STATE = {

*
* // todo: use options.js
* @param {String} url WebSocket URL
* @param {Object} [options]
* @param {Function} [options.createWebSocket=url => new Websocket(url)] custom WebSocket creation method
* @param {String} [options.idProp="id"] id property name attached to each message
* @param {Function} [options.createWebSocket=url => new Websocket(url)] custom WebSocket creation function
* @param {Function} [options.packMessage] custom packing message function
* @param {Function} [options.packMessage] custom unpacking message function
* @param {Number} [options.timeout=0] default timeout for requests

@@ -45,9 +43,10 @@ */

this._url = url;
this._options = Object.assign({}, DEFAULT_OPTIONS, utils.removeUndefined(options));
this._opening = new Pendings.Pending();
this._closing = new Pendings.Pending();
this._pendingRequests = new Pendings({timeout: this._options.timeout});
this._options = utils.mergeDefaults(options, defaultOptions);
this._opening = new ControlledPromise();
this._closing = new ControlledPromise();
this._requests = new Requests();
this._onMessage = new Channel();
this._onClose = new Channel();
this._ws = null;
this._wsSubscription = null;
}

@@ -135,50 +134,38 @@

if (this.isClosing) {
return Promise.reject(`Can not open closing WebSocket`);
} else {
const {timeout, createWebSocket} = this._options;
return this._opening.call(() => {
this._closing.reset();
this._ws = createWebSocket(this._url);
this._addWsListeners();
}, timeout);
return Promise.reject(new Error(`Can not open closing WebSocket connection`));
}
if (this.isOpened) {
return this._opening.promise;
}
return this._opening.call(() => {
const {timeout} = this._options;
this._opening.timeout(timeout, `Can't open WebSocket connection within allowed timeout: ${timeout} ms`);
this._opening.promise.catch(e => this._cleanupForClose(e));
this._createWS();
});
}
/**
* Performs JSON request and waits for response.
* Performs request and waits for response.
*
* @param {Object} data
* @param {Object} [options]
* @param {String} [options.requestId]
* @param {String} [options.requestIdPrefix]
* @param {Number} [options.timeout]
* @returns {Promise}
*/
request(data, options) {
request(data, options = {}) {
if (!data || typeof data !== 'object') {
return Promise.reject(new Error(`WebSocket request data should be a plain object, got ${data}`));
}
const {idProp} = this._options;
const fn = id => {
data[idProp] = id;
this.sendJson(data);
};
const id = data[idProp];
const promise = id === undefined
? this._pendingRequests.add(fn, options)
: this._pendingRequests.set(id, fn, options);
return promise.catch(handleTimeoutError);
const requestId = options.requestId || utils.generateId(options.requestIdPrefix);
const message = this._options.packMessage(requestId, data);
const timeout = options.timeout !== undefined ? options.timeout : this._options.timeout;
return this._requests.create(requestId, () => this.send(message), timeout);
}
/**
* Sends JSON data and does not wait for response.
* Sends any data by WebSocket.
*
* @param {Object} data
*/
sendJson(data) {
const dataStr = JSON.stringify(data);
this.send(dataStr);
}
/**
* Sends any WebSocket compatible data.
*
* @param {String|ArrayBuffer|Blob} data

@@ -200,14 +187,23 @@ */

close() {
this._opening.reset(new Error('Connection closing by client'));
if (this.isClosed) {
return Promise.resolve(this._closing.value);
}
return this._closing.call(() => {
if (this._ws) {
this._ws.close();
} else {
// case: close without open
this._closing.resolve();
}
}, this._options.timeout);
const {timeout} = this._options;
this._closing.timeout(timeout, `Can't close WebSocket connection within allowed timeout: ${timeout} ms`);
this._ws.close();
});
}
_addWsListeners() {
_createWS() {
this._ws = this._options.createWebSocket(this._url);
this._wsSubscription = new Channel.Subscription([
{channel: this._ws, event: 'open', listener: e => this._handleOpen(e)},
{channel: this._ws, event: 'message', listener: e => this._handleMessage(e)},
{channel: this._ws, event: 'error', listener: e => this._handleError(e)},
{channel: this._ws, event: 'close', listener: e => this._handleClose(e)},
]).on();
}
_addWSListeners() {
this._ws.addEventListener('open', event => this._handleOpen(event));

@@ -223,37 +219,44 @@ this._ws.addEventListener('message', event => this._handleMessage(event));

_handleMessage({data}) {
let jsonData;
_handleMessage(event) {
const message = event.data;
let requestId, data;
try {
jsonData = JSON.parse(data);
const id = jsonData && jsonData[this._options.idProp];
this._pendingRequests.tryResolve(id, jsonData);
const result = this._options.unpackMessage(message);
requestId = result.requestId;
data = result.data;
} catch(e) {
// do nothing if can not parse data
// do nothing if can not unpack message
}
this._onMessage.dispatch(jsonData, data);
this._onMessage.dispatchAsync(message, data);
this._requests.resolve(requestId, data);
}
_handleError() {
// todo: when this event comes?
// currently no specific handling of this event
}
_handleClose({reason, code}) {
const error = new Error(`Connection closed with reason: ${reason} (${code})`);
// todo: removeWsListeners
_handleClose(event) {
this._onClose.dispatchAsync(event);
this._closing.resolve(event);
const error = new Error(`WebSocket connection closed with reason: ${event.reason} (${event.code})`);
if (this._opening.isPending) {
this._opening.reject(error);
}
this._cleanupForClose(error);
}
_cleanupWS() {
if (this._wsSubscription) {
this._wsSubscription.off();
this._wsSubscription = null;
}
this._ws = null;
this._closing.resolve({reason, code});
this._pendingRequests.rejectAll(error);
this._opening.reset(error);
this._onClose.dispatchAsync({reason, code});
}
}
function handleTimeoutError(e) {
// inheritance from built-in classes does not work after babel transpile :(
// does not work: e instanceof Pendings.TimeoutError --> always false
// see: https://stackoverflow.com/questions/42064466/instanceof-using-es6-class-inheritance-chain-doesnt-work
const error = e && e.timeout !== undefined ? new Error(`Request rejected by timeout (${e.timeout} ms)`) : e;
return Promise.reject(error);
_cleanupForClose(error) {
this._cleanupWS();
this._requests.rejectAll(error);
}
}
module.exports = WebSocketAsPromised;
/**
* Utils
* @private
*/
exports.mergeDefaults = function (options, defaultOptions) {
return Object.assign({}, defaultOptions, exports.removeUndefined(options));
};
exports.removeUndefined = function (obj) {

@@ -12,1 +15,5 @@ if (obj && typeof obj === 'object') {

};
exports.generateId = function (prefix) {
return `${prefix || ''}${Date.now()}-${Math.random()}`;
};

@@ -56,2 +56,4 @@ // todo: make this a separate project!

connection.sendUTF(JSON.stringify(data));
} else if (data.noResponse) {
// nothing
} else if (data.delay) {

@@ -58,0 +60,0 @@ setTimeout(() => connection.sendUTF(message.utf8Data), data.delay);

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