simple-websocket
Advanced tools
Comparing version 7.2.0 to 8.0.0
406
index.js
/* global WebSocket, DOMException */ | ||
module.exports = Socket | ||
const debug = require('debug')('simple-websocket') | ||
const randombytes = require('randombytes') | ||
const stream = require('readable-stream') | ||
const ws = require('ws') // websockets in node - will be empty object in browser | ||
var debug = require('debug')('simple-websocket') | ||
var inherits = require('inherits') | ||
var randombytes = require('randombytes') | ||
var stream = require('readable-stream') | ||
var ws = require('ws') // websockets in node - will be empty object in browser | ||
const _WebSocket = typeof ws !== 'function' ? WebSocket : ws | ||
var _WebSocket = typeof ws !== 'function' ? WebSocket : ws | ||
const MAX_BUFFERED_AMOUNT = 64 * 1024 | ||
var MAX_BUFFERED_AMOUNT = 64 * 1024 | ||
inherits(Socket, stream.Duplex) | ||
/** | ||
@@ -23,252 +18,243 @@ * WebSocket. Same API as node core `net.Socket`. Duplex stream. | ||
*/ | ||
function Socket (opts) { | ||
var self = this | ||
if (!(self instanceof Socket)) return new Socket(opts) | ||
if (!opts) opts = {} | ||
class Socket extends stream.Duplex { | ||
constructor (opts = {}) { | ||
// Support simple usage: `new Socket(url)` | ||
if (typeof opts === 'string') { | ||
opts = { url: opts } | ||
} | ||
// Support simple usage: `new Socket(url)` | ||
if (typeof opts === 'string') { | ||
opts = { url: opts } | ||
} | ||
opts = Object.assign({ | ||
allowHalfOpen: false | ||
}, opts) | ||
if (opts.url == null && opts.socket == null) { | ||
throw new Error('Missing required `url` or `socket` option') | ||
} | ||
if (opts.url != null && opts.socket != null) { | ||
throw new Error('Must specify either `url` or `socket` option, not both') | ||
} | ||
super(opts) | ||
self._id = randombytes(4).toString('hex').slice(0, 7) | ||
self._debug('new websocket: %o', opts) | ||
if (opts.url == null && opts.socket == null) { | ||
throw new Error('Missing required `url` or `socket` option') | ||
} | ||
if (opts.url != null && opts.socket != null) { | ||
throw new Error('Must specify either `url` or `socket` option, not both') | ||
} | ||
opts = Object.assign({ | ||
allowHalfOpen: false | ||
}, opts) | ||
this._id = randombytes(4).toString('hex').slice(0, 7) | ||
this._debug('new websocket: %o', opts) | ||
stream.Duplex.call(self, opts) | ||
this.connected = false | ||
this.destroyed = false | ||
self.connected = false | ||
self.destroyed = false | ||
this._chunk = null | ||
this._cb = null | ||
this._interval = null | ||
self._chunk = null | ||
self._cb = null | ||
self._interval = null | ||
if (opts.socket) { | ||
self.url = opts.socket.url | ||
self._ws = opts.socket | ||
} else { | ||
self.url = opts.url | ||
try { | ||
if (typeof ws === 'function') { | ||
// `ws` package accepts options | ||
self._ws = new _WebSocket(opts.url, opts) | ||
} else { | ||
self._ws = new _WebSocket(opts.url) | ||
if (opts.socket) { | ||
this.url = opts.socket.url | ||
this._ws = opts.socket | ||
} else { | ||
this.url = opts.url | ||
try { | ||
if (typeof ws === 'function') { | ||
// `ws` package accepts options | ||
this._ws = new _WebSocket(opts.url, opts) | ||
} else { | ||
this._ws = new _WebSocket(opts.url) | ||
} | ||
} catch (err) { | ||
process.nextTick(() => this.destroy(err)) | ||
return | ||
} | ||
} catch (err) { | ||
process.nextTick(function () { | ||
self.destroy(err) | ||
}) | ||
return | ||
} | ||
this._ws.binaryType = 'arraybuffer' | ||
this._ws.onopen = () => { | ||
this._onOpen() | ||
} | ||
this._ws.onmessage = event => { | ||
this._onMessage(event) | ||
} | ||
this._ws.onclose = () => { | ||
this._onClose() | ||
} | ||
this._ws.onerror = () => { | ||
this.destroy(new Error('connection error to ' + this.url)) | ||
} | ||
this._onFinishBound = () => { | ||
this._onFinish() | ||
} | ||
this.once('finish', this._onFinishBound) | ||
} | ||
self._ws.binaryType = 'arraybuffer' | ||
self._ws.onopen = function () { | ||
self._onOpen() | ||
/** | ||
* Send text/binary data to the WebSocket server. | ||
* @param {TypedArrayView|ArrayBuffer|Buffer|string|Blob|Object} chunk | ||
*/ | ||
send (chunk) { | ||
this._ws.send(chunk) | ||
} | ||
self._ws.onmessage = function (event) { | ||
self._onMessage(event) | ||
} | ||
self._ws.onclose = function () { | ||
self._onClose() | ||
} | ||
self._ws.onerror = function () { | ||
self.destroy(new Error('connection error to ' + self.url)) | ||
} | ||
self._onFinishBound = function () { | ||
self._onFinish() | ||
// TODO: Delete this method once readable-stream is updated to contain a default | ||
// implementation of destroy() that automatically calls _destroy() | ||
// See: https://github.com/nodejs/readable-stream/issues/283 | ||
destroy (err) { | ||
this._destroy(err, () => {}) | ||
} | ||
self.once('finish', self._onFinishBound) | ||
} | ||
Socket.WEBSOCKET_SUPPORT = !!_WebSocket | ||
_destroy (err, cb) { | ||
if (this.destroyed) return | ||
/** | ||
* Send text/binary data to the WebSocket server. | ||
* @param {TypedArrayView|ArrayBuffer|Buffer|string|Blob|Object} chunk | ||
*/ | ||
Socket.prototype.send = function (chunk) { | ||
this._ws.send(chunk) | ||
} | ||
this._debug('destroy (error: %s)', err && (err.message || err)) | ||
// TODO: Delete this method once readable-stream is updated to contain a default | ||
// implementation of destroy() that automatically calls _destroy() | ||
// See: https://github.com/nodejs/readable-stream/issues/283 | ||
Socket.prototype.destroy = function (err) { | ||
this._destroy(err, function () {}) | ||
} | ||
this.readable = this.writable = false | ||
if (!this._readableState.ended) this.push(null) | ||
if (!this._writableState.finished) this.end() | ||
Socket.prototype._destroy = function (err, cb) { | ||
var self = this | ||
if (self.destroyed) return | ||
this.connected = false | ||
this.destroyed = true | ||
self._debug('destroy (error: %s)', err && (err.message || err)) | ||
clearInterval(this._interval) | ||
this._interval = null | ||
this._chunk = null | ||
this._cb = null | ||
self.readable = self.writable = false | ||
if (!self._readableState.ended) self.push(null) | ||
if (!self._writableState.finished) self.end() | ||
if (this._onFinishBound) this.removeListener('finish', this._onFinishBound) | ||
this._onFinishBound = null | ||
self.connected = false | ||
self.destroyed = true | ||
clearInterval(self._interval) | ||
self._interval = null | ||
self._chunk = null | ||
self._cb = null | ||
if (self._onFinishBound) self.removeListener('finish', self._onFinishBound) | ||
self._onFinishBound = null | ||
if (self._ws) { | ||
var ws = self._ws | ||
var onClose = function () { | ||
ws.onclose = null | ||
} | ||
if (ws.readyState === _WebSocket.CLOSED) { | ||
onClose() | ||
} else { | ||
try { | ||
ws.onclose = onClose | ||
ws.close() | ||
} catch (err) { | ||
if (this._ws) { | ||
const ws = this._ws | ||
const onClose = () => { | ||
ws.onclose = null | ||
} | ||
if (ws.readyState === _WebSocket.CLOSED) { | ||
onClose() | ||
} else { | ||
try { | ||
ws.onclose = onClose | ||
ws.close() | ||
} catch (err) { | ||
onClose() | ||
} | ||
} | ||
ws.onopen = null | ||
ws.onmessage = null | ||
ws.onerror = () => {} | ||
} | ||
this._ws = null | ||
ws.onopen = null | ||
ws.onmessage = null | ||
ws.onerror = function () {} | ||
} | ||
self._ws = null | ||
if (err) { | ||
if (typeof DOMException !== 'undefined' && err instanceof DOMException) { | ||
// Convert Edge DOMException object to Error object | ||
var code = err.code | ||
err = new Error(err.message) | ||
err.code = code | ||
if (err) { | ||
if (typeof DOMException !== 'undefined' && err instanceof DOMException) { | ||
// Convert Edge DOMException object to Error object | ||
const code = err.code | ||
err = new Error(err.message) | ||
err.code = code | ||
} | ||
this.emit('error', err) | ||
} | ||
self.emit('error', err) | ||
this.emit('close') | ||
cb() | ||
} | ||
self.emit('close') | ||
cb() | ||
} | ||
Socket.prototype._read = function () {} | ||
_read () {} | ||
Socket.prototype._write = function (chunk, encoding, cb) { | ||
if (this.destroyed) return cb(new Error('cannot write after socket is destroyed')) | ||
_write (chunk, encoding, cb) { | ||
if (this.destroyed) return cb(new Error('cannot write after socket is destroyed')) | ||
if (this.connected) { | ||
try { | ||
this.send(chunk) | ||
} catch (err) { | ||
return this.destroy(err) | ||
} | ||
if (typeof ws !== 'function' && this._ws.bufferedAmount > MAX_BUFFERED_AMOUNT) { | ||
this._debug('start backpressure: bufferedAmount %d', this._ws.bufferedAmount) | ||
if (this.connected) { | ||
try { | ||
this.send(chunk) | ||
} catch (err) { | ||
return this.destroy(err) | ||
} | ||
if (typeof ws !== 'function' && this._ws.bufferedAmount > MAX_BUFFERED_AMOUNT) { | ||
this._debug('start backpressure: bufferedAmount %d', this._ws.bufferedAmount) | ||
this._cb = cb | ||
} else { | ||
cb(null) | ||
} | ||
} else { | ||
this._debug('write before connect') | ||
this._chunk = chunk | ||
this._cb = cb | ||
} else { | ||
cb(null) | ||
} | ||
} else { | ||
this._debug('write before connect') | ||
this._chunk = chunk | ||
this._cb = cb | ||
} | ||
} | ||
// When stream finishes writing, close socket. Half open connections are not | ||
// supported. | ||
Socket.prototype._onFinish = function () { | ||
var self = this | ||
if (self.destroyed) return | ||
// When stream finishes writing, close socket. Half open connections are not | ||
// supported. | ||
_onFinish () { | ||
if (this.destroyed) return | ||
if (self.connected) { | ||
destroySoon() | ||
} else { | ||
self.once('connect', destroySoon) | ||
// Wait a bit before destroying so the socket flushes. | ||
// TODO: is there a more reliable way to accomplish this? | ||
const destroySoon = () => { | ||
setTimeout(() => this.destroy(), 1000) | ||
} | ||
if (this.connected) { | ||
destroySoon() | ||
} else { | ||
this.once('connect', destroySoon) | ||
} | ||
} | ||
// Wait a bit before destroying so the socket flushes. | ||
// TODO: is there a more reliable way to accomplish this? | ||
function destroySoon () { | ||
setTimeout(function () { | ||
self.destroy() | ||
}, 1000) | ||
_onMessage (event) { | ||
if (this.destroyed) return | ||
let data = event.data | ||
if (data instanceof ArrayBuffer) data = Buffer.from(data) | ||
this.push(data) | ||
} | ||
} | ||
Socket.prototype._onMessage = function (event) { | ||
if (this.destroyed) return | ||
var data = event.data | ||
if (data instanceof ArrayBuffer) data = Buffer.from(data) | ||
this.push(data) | ||
} | ||
_onOpen () { | ||
if (this.connected || this.destroyed) return | ||
this.connected = true | ||
Socket.prototype._onOpen = function () { | ||
var self = this | ||
if (self.connected || self.destroyed) return | ||
self.connected = true | ||
if (this._chunk) { | ||
try { | ||
this.send(this._chunk) | ||
} catch (err) { | ||
return this.destroy(err) | ||
} | ||
this._chunk = null | ||
this._debug('sent chunk from "write before connect"') | ||
if (self._chunk) { | ||
try { | ||
self.send(self._chunk) | ||
} catch (err) { | ||
return self.destroy(err) | ||
const cb = this._cb | ||
this._cb = null | ||
cb(null) | ||
} | ||
self._chunk = null | ||
self._debug('sent chunk from "write before connect"') | ||
var cb = self._cb | ||
self._cb = null | ||
// Backpressure is not implemented in Node.js. The `ws` module has a buggy | ||
// `bufferedAmount` property. See: https://github.com/websockets/ws/issues/492 | ||
if (typeof ws !== 'function') { | ||
this._interval = setInterval(() => this._onInterval(), 150) | ||
if (this._interval.unref) this._interval.unref() | ||
} | ||
this._debug('connect') | ||
this.emit('connect') | ||
} | ||
_onInterval () { | ||
if (!this._cb || !this._ws || this._ws.bufferedAmount > MAX_BUFFERED_AMOUNT) { | ||
return | ||
} | ||
this._debug('ending backpressure: bufferedAmount %d', this._ws.bufferedAmount) | ||
const cb = this._cb | ||
this._cb = null | ||
cb(null) | ||
} | ||
// Backpressure is not implemented in Node.js. The `ws` module has a buggy | ||
// `bufferedAmount` property. See: https://github.com/websockets/ws/issues/492 | ||
if (typeof ws !== 'function') { | ||
self._interval = setInterval(function () { | ||
self._onInterval() | ||
}, 150) | ||
if (self._interval.unref) self._interval.unref() | ||
_onClose () { | ||
if (this.destroyed) return | ||
this._debug('on close') | ||
this.destroy() | ||
} | ||
self._debug('connect') | ||
self.emit('connect') | ||
} | ||
Socket.prototype._onInterval = function () { | ||
if (!this._cb || !this._ws || this._ws.bufferedAmount > MAX_BUFFERED_AMOUNT) { | ||
return | ||
_debug () { | ||
const args = [].slice.call(arguments) | ||
args[0] = '[' + this._id + '] ' + args[0] | ||
debug.apply(null, args) | ||
} | ||
this._debug('ending backpressure: bufferedAmount %d', this._ws.bufferedAmount) | ||
var cb = this._cb | ||
this._cb = null | ||
cb(null) | ||
} | ||
Socket.prototype._onClose = function () { | ||
if (this.destroyed) return | ||
this._debug('on close') | ||
this.destroy() | ||
} | ||
Socket.WEBSOCKET_SUPPORT = !!_WebSocket | ||
Socket.prototype._debug = function () { | ||
var args = [].slice.call(arguments) | ||
args[0] = '[' + this._id + '] ' + args[0] | ||
debug.apply(null, args) | ||
} | ||
module.exports = Socket |
{ | ||
"name": "simple-websocket", | ||
"description": "Simple, EventEmitter API for WebSockets (browser)", | ||
"version": "7.2.0", | ||
"version": "8.0.0", | ||
"author": { | ||
@@ -17,10 +17,10 @@ "name": "Feross Aboukhadijeh", | ||
"dependencies": { | ||
"debug": "^3.1.0", | ||
"inherits": "^2.0.1", | ||
"debug": "^4.1.1", | ||
"randombytes": "^2.0.3", | ||
"readable-stream": "^2.0.5", | ||
"ws": "^6.0.0" | ||
"readable-stream": "^3.1.1", | ||
"ws": "^7.0.0" | ||
}, | ||
"devDependencies": { | ||
"airtap": "0.1.0", | ||
"airtap": "^2.0.3", | ||
"babel-minify": "^0.5.0", | ||
"browserify": "^16.1.0", | ||
@@ -30,4 +30,3 @@ "prettier-bytes": "^1.0.3", | ||
"standard": "*", | ||
"tape": "^4.0.0", | ||
"uglify-js": "^3.1.8" | ||
"tape": "^4.0.0" | ||
}, | ||
@@ -50,3 +49,3 @@ "homepage": "https://github.com/feross/simple-websocket", | ||
"scripts": { | ||
"build": "browserify -s SimpleWebsocket -r ./ | uglifyjs -c warnings=false -m > simplewebsocket.min.js", | ||
"build": "browserify -s SimpleWebsocket -r ./ | minify > simplewebsocket.min.js", | ||
"test": "standard && npm run test-node && npm run test-browser", | ||
@@ -53,0 +52,0 @@ "test-browser": "airtap -- test/*.js", |
@@ -38,2 +38,3 @@ # simple-websocket [![travis][travis-image]][travis-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] | ||
- [lxjs-chat](https://github.com/feross/lxjs-chat) - Omegle chat clone | ||
- [Metastream](https://github.com/samuelmaddock/metastream) - Watch streaming media with friends. | ||
- \[ your application here - send a PR \] | ||
@@ -40,0 +41,0 @@ |
106
server.js
@@ -1,77 +0,63 @@ | ||
module.exports = SocketServer | ||
const events = require('events') | ||
const Socket = require('./') | ||
const WebSocketServer = require('ws').Server | ||
var events = require('events') | ||
var inherits = require('inherits') | ||
var Socket = require('./') | ||
var WebSocketServer = require('ws').Server | ||
class SocketServer extends events.EventEmitter { | ||
constructor (opts) { | ||
opts = Object.assign({ | ||
clientTracking: false, | ||
perMessageDeflate: false | ||
}, opts) | ||
inherits(SocketServer, events.EventEmitter) | ||
super() | ||
function SocketServer (opts) { | ||
var self = this | ||
if (!(self instanceof SocketServer)) return new SocketServer(opts) | ||
this.destroyed = false | ||
opts = Object.assign({ | ||
clientTracking: false, | ||
perMessageDeflate: false | ||
}, opts) | ||
this._server = new WebSocketServer(opts) | ||
events.EventEmitter.call(self) | ||
this._onListeningBound = () => this._onListening() | ||
this._server.on('listening', this._onListeningBound) | ||
self.destroyed = false | ||
this._onConnectionBound = conn => this._onConnection(conn) | ||
this._server.on('connection', this._onConnectionBound) | ||
self._server = new WebSocketServer(opts) | ||
this._onErrorBound = err => this._onError(err) | ||
this._server.once('error', this._onErrorBound) | ||
} | ||
self._onListeningBound = function () { | ||
self._onListening() | ||
address () { | ||
return this._server.address() | ||
} | ||
self._server.on('listening', self._onListeningBound) | ||
self._onConnectionBound = function (conn) { | ||
self._onConnection(conn) | ||
close (cb) { | ||
if (this.destroyed) return cb(new Error('server is closed')) | ||
this.destroyed = true | ||
if (cb) this.once('close', cb) | ||
this._server.removeListener('listening', this._onListeningBound) | ||
this._server.removeListener('connection', this._onConnectionBound) | ||
this._server.removeListener('error', this._onErrorBound) | ||
this._server.close(() => this.emit('close')) | ||
} | ||
self._server.on('connection', self._onConnectionBound) | ||
self._onErrorBound = function (err) { | ||
self._onError(err) | ||
_onListening () { | ||
this.emit('listening') | ||
} | ||
self._server.once('error', self._onErrorBound) | ||
} | ||
SocketServer.prototype.address = function () { | ||
return this._server.address() | ||
} | ||
_onConnection (conn) { | ||
const socket = new Socket({ socket: conn }) | ||
socket._onOpen() | ||
socket.upgradeReq = conn.upgradeReq | ||
this.emit('connection', socket) | ||
this.once('close', () => { | ||
socket.upgradeReq = null | ||
}) | ||
} | ||
SocketServer.prototype.close = function (cb) { | ||
var self = this | ||
if (self.destroyed) return cb(new Error('server is closed')) | ||
self.destroyed = true | ||
if (cb) self.once('close', cb) | ||
self._server.removeListener('listening', self._onListeningBound) | ||
self._server.removeListener('connection', self._onConnectionBound) | ||
self._server.removeListener('error', self._onErrorBound) | ||
self._server.close(function () { | ||
self.emit('close') | ||
}) | ||
_onError (err) { | ||
this.emit('error', err) | ||
this.close() | ||
} | ||
} | ||
SocketServer.prototype._onListening = function () { | ||
this.emit('listening') | ||
} | ||
SocketServer.prototype._onConnection = function (conn) { | ||
var socket = new Socket({ socket: conn }) | ||
socket._onOpen() | ||
socket.upgradeReq = conn.upgradeReq | ||
this.emit('connection', socket) | ||
this.once('close', function () { | ||
socket.upgradeReq = null | ||
}) | ||
} | ||
SocketServer.prototype._onError = function (err) { | ||
this.emit('error', err) | ||
this.close() | ||
} | ||
module.exports = SocketServer |
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
4
157
98179
6
280
1
+ Addeddebug@4.4.0(transitive)
+ Addedreadable-stream@3.6.2(transitive)
+ Addedstring_decoder@1.3.0(transitive)
+ Addedws@7.5.10(transitive)
- Removedinherits@^2.0.1
- Removedasync-limiter@1.0.1(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removeddebug@3.2.7(transitive)
- Removedisarray@1.0.0(transitive)
- Removedprocess-nextick-args@2.0.1(transitive)
- Removedreadable-stream@2.3.8(transitive)
- Removedsafe-buffer@5.1.2(transitive)
- Removedstring_decoder@1.1.1(transitive)
- Removedws@6.2.3(transitive)
Updateddebug@^4.1.1
Updatedreadable-stream@^3.1.1
Updatedws@^7.0.0