Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: websockets/ws
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 6.2.1
Choose a base ref
...
head repository: websockets/ws
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 7.0.0
Choose a head ref

Commits on Mar 30, 2019

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    736c082 View commit details

Commits on Apr 8, 2019

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7e7c8d6 View commit details

Commits on Apr 12, 2019

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e8e39ea View commit details

Commits on Apr 13, 2019

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8a5a2cf View commit details
  2. [pkg] Add package-lock.json

    lpinca committed Apr 13, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    078336e View commit details
  3. [pkg] Add greenkeeper.json

    lpinca committed Apr 13, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3a7faf7 View commit details
  4. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ebdf0f7 View commit details

Commits on Apr 17, 2019

  1. [pkg] Remove package-lock.json

    lpinca committed Apr 17, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    beccc79 View commit details

Commits on Apr 18, 2019

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1a15120 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5479eae View commit details

Commits on Apr 25, 2019

  1. [major] Change WebSocket#{p{i,o}ng,send}() behavior (#1532)

    - If the `readyState` attribute is `CONNECTING`, throw an exception.
    - If the `readyState` attribute is `CLOSING` or `CLOSED`
      - Increase the `bufferedAmount` attribute by the length of the `data`
        argument in bytes.
      - If specified, call the `callback` function with an error.
    
    Fixes #1515
    lpinca authored Apr 25, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5d751fb View commit details
  2. 1

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1e6999b View commit details
  3. Copy the full SHA
    692d7b4 View commit details
  4. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ddf0aca View commit details
  5. [minor] Use Reflect.apply()

    It is faster than `Function.prototype.apply()`.
    lpinca committed Apr 25, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6824e8c View commit details
  6. [ci] Test on node 12

    lpinca committed Apr 25, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    01bb91d View commit details
  7. [lint] enable no-var rule

    lpinca committed Apr 25, 2019
    Copy the full SHA
    379def6 View commit details
  8. Copy the full SHA
    3eff077 View commit details
  9. Copy the full SHA
    4a9a773 View commit details
  10. [example] Clean up examples

    lpinca committed Apr 25, 2019
    Copy the full SHA
    aca3858 View commit details

Commits on Apr 26, 2019

  1. Copy the full SHA
    993b0cd View commit details
  2. Copy the full SHA
    1b85466 View commit details

Commits on Apr 30, 2019

  1. [dist] 7.0.0

    lpinca committed Apr 30, 2019
    Copy the full SHA
    092a822 View commit details
1 change: 1 addition & 0 deletions .eslintrc.yaml
Original file line number Diff line number Diff line change
@@ -12,5 +12,6 @@ plugins:
- prettier
rules:
no-console: off
no-var: error
prefer-const: error
prettier/prettier: error
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
language: node_js
node_js:
- '11'
- '12'
- '10'
- '8'
- '6'
os:
- linux
- osx
27 changes: 1 addition & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -35,7 +35,6 @@ can use one of the many wrappers available on npm, like
- [Server broadcast](#server-broadcast)
- [echo.websocket.org demo](#echowebsocketorg-demo)
- [Other examples](#other-examples)
- [Error handling best practices](#error-handling-best-practices)
- [FAQ](#faq)
- [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client)
- [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections)
@@ -193,7 +192,7 @@ const fs = require('fs');
const https = require('https');
const WebSocket = require('ws');

const server = new https.createServer({
const server = https.createServer({
cert: fs.readFileSync('/path/to/cert.pem'),
key: fs.readFileSync('/path/to/key.pem')
});
@@ -309,30 +308,6 @@ examples folder.

Otherwise, see the test cases.

## Error handling best practices

```js
// If the WebSocket is closed before the following send is attempted
ws.send('something');

// Errors (both immediate and async write errors) can be detected in an optional
// callback. The callback is also the only way of being notified that data has
// actually been sent.
ws.send('something', function ack(error) {
// If error is not defined, the send has been completed, otherwise the error
// object will indicate what failed.
});

// Immediate errors can also be handled with `try...catch`, but **note** that
// since sends are inherently asynchronous, socket write failures will *not* be
// captured when this technique is used.
try {
ws.send('something');
} catch (e) {
/* handle error */
}
```

## FAQ

### How to get the IP address of the client?
3 changes: 1 addition & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
environment:
matrix:
- nodejs_version: '11'
- nodejs_version: '12'
- nodejs_version: '10'
- nodejs_version: '8'
- nodejs_version: '6'
platform:
- x86
matrix:
10 changes: 5 additions & 5 deletions bench/speed.js
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ if (cluster.isMaster) {
console.log('Generating %s of test data...', humanSize(largest));
const randomBytes = Buffer.allocUnsafe(largest);

for (var i = 0; i < largest; ++i) {
for (let i = 0; i < largest; ++i) {
randomBytes[i] = ~~(Math.random() * 127);
}

@@ -72,8 +72,8 @@ if (cluster.isMaster) {
const ws = new WebSocket(url, {
maxPayload: 600 * 1024 * 1024
});
var roundtrip = 0;
var time;
let roundtrip = 0;
let time;

ws.on('error', (err) => {
console.error(err.stack);
@@ -87,7 +87,7 @@ if (cluster.isMaster) {
if (++roundtrip !== roundtrips)
return ws.send(data, { binary: useBinary });

var elapsed = process.hrtime(time);
let elapsed = process.hrtime(time);
elapsed = elapsed[0] * 1e9 + elapsed[1];

console.log(
@@ -106,7 +106,7 @@ if (cluster.isMaster) {

(function run() {
if (configs.length === 0) return cluster.worker.disconnect();
var config = configs.shift();
const config = configs.shift();
config.push(run);
runConfig.apply(null, config);
})();
2 changes: 1 addition & 1 deletion doc/ws.md
Original file line number Diff line number Diff line change
@@ -234,7 +234,7 @@ This class represents a WebSocket. It extends the `EventEmitter`.

### new WebSocket(address[, protocols][, options])

- `address` {String|url.Url|url.URL} The URL to which to connect.
- `address` {String|url.URL} The URL to which to connect.
- `protocols` {String|Array} The list of subprotocols.
- `options` {Object}
- `followRedirects` {Boolean} Whether or not to follow redirects. Defaults to
21 changes: 12 additions & 9 deletions examples/express-session-parse/index.js
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ const sessionParser = session({
app.use(express.static('public'));
app.use(sessionParser);

app.post('/login', (req, res) => {
app.post('/login', function(req, res) {
//
// "Log in" user and set userId to session.
//
@@ -36,10 +36,11 @@ app.post('/login', (req, res) => {
res.send({ result: 'OK', message: 'Session updated' });
});

app.delete('/logout', (request, response) => {
app.delete('/logout', function(request, response) {
console.log('Destroying session');
request.session.destroy();
response.send({ result: 'OK', message: 'Session destroyed' });
request.session.destroy(function() {
response.send({ result: 'OK', message: 'Session destroyed' });
});
});

//
@@ -48,7 +49,7 @@ app.delete('/logout', (request, response) => {
const server = http.createServer(app);

const wss = new WebSocket.Server({
verifyClient: (info, done) => {
verifyClient: function(info, done) {
console.log('Parsing session from request...');
sessionParser(info.req, {}, () => {
console.log('Session is parsed!');
@@ -63,16 +64,18 @@ const wss = new WebSocket.Server({
server
});

wss.on('connection', (ws, req) => {
ws.on('message', (message) => {
wss.on('connection', function(ws, request) {
ws.on('message', function(message) {
//
// Here we can now use session parameters.
//
console.log(`WS message ${message} from user ${req.session.userId}`);
console.log(`WS message ${message} from user ${request.session.userId}`);
});
});

//
// Start the server.
//
server.listen(8080, () => console.log('Listening on http://localhost:8080'));
server.listen(8080, function() {
console.log('Listening on http://localhost:8080');
});
6 changes: 3 additions & 3 deletions examples/express-session-parse/package.json
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@
"version": "0.0.0",
"repository": "websockets/ws",
"dependencies": {
"express": "~4.16.3",
"express-session": "~1.15.6",
"uuid": "~3.3.2"
"express": "^4.16.4",
"express-session": "^1.16.1",
"uuid": "^3.3.2"
}
}
37 changes: 23 additions & 14 deletions examples/express-session-parse/public/app.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,55 @@
/* global fetch, WebSocket, location */
(() => {
(function() {
const messages = document.querySelector('#messages');
const wsButton = document.querySelector('#wsButton');
const logout = document.querySelector('#logout');
const login = document.querySelector('#login');

const showMessage = (message) => {
function showMessage(message) {
messages.textContent += `\n${message}`;
messages.scrollTop = messages.scrollHeight;
};
}

const handleResponse = (response) => {
function handleResponse(response) {
return response.ok
? response.json().then((data) => JSON.stringify(data, null, 2))
: Promise.reject(new Error('Unexpected response'));
};
}

login.onclick = () => {
login.onclick = function() {
fetch('/login', { method: 'POST', credentials: 'same-origin' })
.then(handleResponse)
.then(showMessage)
.catch((err) => showMessage(err.message));
.catch(function(err) {
showMessage(err.message);
});
};

logout.onclick = () => {
logout.onclick = function() {
fetch('/logout', { method: 'DELETE', credentials: 'same-origin' })
.then(handleResponse)
.then(showMessage)
.catch((err) => showMessage(err.message));
.catch(function(err) {
showMessage(err.message);
});
};

let ws;

wsButton.onclick = () => {
wsButton.onclick = function() {
if (ws) {
ws.onerror = ws.onopen = ws.onclose = null;
ws.close();
}

ws = new WebSocket(`ws://${location.host}`);
ws.onerror = () => showMessage('WebSocket error');
ws.onopen = () => showMessage('WebSocket connection established');
ws.onclose = () => showMessage('WebSocket connection closed');
ws.onerror = function() {
showMessage('WebSocket error');
};
ws.onopen = function() {
showMessage('WebSocket connection established');
};
ws.onclose = function() {
showMessage('WebSocket connection closed');
};
};
})();
1 change: 0 additions & 1 deletion examples/fileapi/.gitignore

This file was deleted.

10 changes: 0 additions & 10 deletions examples/fileapi/package.json

This file was deleted.

40 changes: 0 additions & 40 deletions examples/fileapi/public/app.js

This file was deleted.

22 changes: 0 additions & 22 deletions examples/fileapi/public/index.html

This file was deleted.

58 changes: 0 additions & 58 deletions examples/fileapi/public/uploader.js

This file was deleted.

134 changes: 0 additions & 134 deletions examples/fileapi/server.js

This file was deleted.

25 changes: 16 additions & 9 deletions examples/serverstats/server.js → examples/server-stats/index.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
var WebSocketServer = require('../../').Server;
var express = require('express');
var path = require('path');
var app = express();
var server = require('http').createServer();
'use strict';

const express = require('express');
const path = require('path');
const { createServer } = require('http');

const WebSocket = require('../../');

const app = express();
app.use(express.static(path.join(__dirname, '/public')));

var wss = new WebSocketServer({ server: server });
const server = createServer(app);
const wss = new WebSocket.Server({ server });

wss.on('connection', function(ws) {
var id = setInterval(function() {
const id = setInterval(function() {
ws.send(JSON.stringify(process.memoryUsage()), function() {
/* ignore errors */
//
// Ignore errors.
//
});
}, 100);
console.log('started client interval');

ws.on('close', function() {
console.log('stopping client interval');
clearInterval(id);
});
});

server.on('request', app);
server.listen(8080, function() {
console.log('Listening on http://localhost:8080');
});
Original file line number Diff line number Diff line change
@@ -4,6 +4,6 @@
"version": "0.0.0",
"repository": "websockets/ws",
"dependencies": {
"express": "~4.16.3"
"express": "^4.16.4"
}
}
63 changes: 63 additions & 0 deletions examples/server-stats/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Server stats</title>
<style>
table, td {
border: 1px solid #333;
}

thead {
background-color: #333;
color: #fff;
}
</style>
</head>
<body>
<h1>Server stats</h1>
<table>
<thead>
<tr>
<th colspan="2">Memory usage</th>
</tr>
</thead>
<tbody>
<tr>
<td>RSS</td>
<td id="rss"></td>
</tr>
<tr>
<td>Heap total</td>
<td id="heapTotal"></td>
</tr>
<tr>
<td>Heap used</td>
<td id="heapUsed"></td>
</tr>
<tr>
<td>External</td>
<td id="external"></td>
</tr>
</tbody>
</table>
<script>
(function() {
const rss = document.getElementById('rss');
const heapTotal = document.getElementById('heapTotal');
const heapUsed = document.getElementById('heapUsed');
const external = document.getElementById('external');
const ws = new WebSocket(`ws://${location.host}`);

ws.onmessage = function(event) {
const data = JSON.parse(event.data);

rss.textContent = data.rss;
heapTotal.textContent = data.heapTotal;
heapUsed.textContent = data.heapUsed;
external.textContent = data.external;
};
})();
</script>
</body>
</html>
33 changes: 0 additions & 33 deletions examples/serverstats/public/index.html

This file was deleted.

10 changes: 5 additions & 5 deletions lib/buffer-util.js
Original file line number Diff line number Diff line change
@@ -15,9 +15,9 @@ function concat(list, totalLength) {
if (list.length === 1) return list[0];

const target = Buffer.allocUnsafe(totalLength);
var offset = 0;
let offset = 0;

for (var i = 0; i < list.length; i++) {
for (let i = 0; i < list.length; i++) {
const buf = list[i];
buf.copy(target, offset);
offset += buf.length;
@@ -37,7 +37,7 @@ function concat(list, totalLength) {
* @public
*/
function _mask(source, mask, output, offset, length) {
for (var i = 0; i < length; i++) {
for (let i = 0; i < length; i++) {
output[offset + i] = source[i] ^ mask[i & 3];
}
}
@@ -52,7 +52,7 @@ function _mask(source, mask, output, offset, length) {
function _unmask(buffer, mask) {
// Required until https://github.com/nodejs/node/issues/9006 is resolved.
const length = buffer.length;
for (var i = 0; i < length; i++) {
for (let i = 0; i < length; i++) {
buffer[i] ^= mask[i & 3];
}
}
@@ -85,7 +85,7 @@ function toBuffer(data) {

if (Buffer.isBuffer(data)) return data;

var buf;
let buf;

if (data instanceof ArrayBuffer) {
buf = Buffer.from(data);
2 changes: 1 addition & 1 deletion lib/event-target.js
Original file line number Diff line number Diff line change
@@ -159,7 +159,7 @@ const EventTarget = {
removeEventListener(method, listener) {
const listeners = this.listeners(method);

for (var i = 0; i < listeners.length; i++) {
for (let i = 0; i < listeners.length; i++) {
if (listeners[i] === listener || listeners[i]._listener === listener) {
this.removeListener(method, listeners[i]);
}
41 changes: 21 additions & 20 deletions lib/extension.js
Original file line number Diff line number Diff line change
@@ -34,8 +34,8 @@ const tokenChars = [
* @private
*/
function push(dest, name, elem) {
if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem);
else dest[name] = [elem];
if (dest[name] === undefined) dest[name] = [elem];
else dest[name].push(elem);
}

/**
@@ -46,20 +46,21 @@ function push(dest, name, elem) {
* @public
*/
function parse(header) {
const offers = {};
const offers = Object.create(null);

if (header === undefined || header === '') return offers;

var params = {};
var mustUnescape = false;
var isEscaping = false;
var inQuotes = false;
var extensionName;
var paramName;
var start = -1;
var end = -1;

for (var i = 0; i < header.length; i++) {
let params = Object.create(null);
let mustUnescape = false;
let isEscaping = false;
let inQuotes = false;
let extensionName;
let paramName;
let start = -1;
let end = -1;
let i = 0;

for (; i < header.length; i++) {
const code = header.charCodeAt(i);

if (extensionName === undefined) {
@@ -76,7 +77,7 @@ function parse(header) {
const name = header.slice(start, end);
if (code === 0x2c) {
push(offers, name, params);
params = {};
params = Object.create(null);
} else {
extensionName = name;
}
@@ -99,7 +100,7 @@ function parse(header) {
push(params, header.slice(start, end), true);
if (code === 0x2c) {
push(offers, extensionName, params);
params = {};
params = Object.create(null);
extensionName = undefined;
}

@@ -146,15 +147,15 @@ function parse(header) {
}

if (end === -1) end = i;
var value = header.slice(start, end);
let value = header.slice(start, end);
if (mustUnescape) {
value = value.replace(/\\/g, '');
mustUnescape = false;
}
push(params, paramName, value);
if (code === 0x2c) {
push(offers, extensionName, params);
params = {};
params = Object.create(null);
extensionName = undefined;
}

@@ -173,7 +174,7 @@ function parse(header) {
if (end === -1) end = i;
const token = header.slice(start, end);
if (extensionName === undefined) {
push(offers, token, {});
push(offers, token, params);
} else {
if (paramName === undefined) {
push(params, token, true);
@@ -198,14 +199,14 @@ function parse(header) {
function format(extensions) {
return Object.keys(extensions)
.map((extension) => {
var configurations = extensions[extension];
let configurations = extensions[extension];
if (!Array.isArray(configurations)) configurations = [configurations];
return configurations
.map((params) => {
return [extension]
.concat(
Object.keys(params).map((k) => {
var values = params[k];
let values = params[k];
if (!Array.isArray(values)) values = [values];
return values
.map((v) => (v === true ? k : `${k}=${v}`))
18 changes: 10 additions & 8 deletions lib/permessage-deflate.js
Original file line number Diff line number Diff line change
@@ -233,7 +233,7 @@ class PerMessageDeflate {
normalizeParams(configurations) {
configurations.forEach((params) => {
Object.keys(params).forEach((key) => {
var value = params[key];
let value = params[key];

if (value.length > 1) {
throw new Error(`Parameter "${key}" must have only a single value`);
@@ -335,9 +335,10 @@ class PerMessageDeflate {
? zlib.Z_DEFAULT_WINDOWBITS
: this.params[key];

this._inflate = zlib.createInflateRaw(
Object.assign({}, this._options.zlibInflateOptions, { windowBits })
);
this._inflate = zlib.createInflateRaw({
...this._options.zlibInflateOptions,
windowBits
});
this._inflate[kPerMessageDeflate] = this;
this._inflate[kTotalLength] = 0;
this._inflate[kBuffers] = [];
@@ -400,9 +401,10 @@ class PerMessageDeflate {
? zlib.Z_DEFAULT_WINDOWBITS
: this.params[key];

this._deflate = zlib.createDeflateRaw(
Object.assign({}, this._options.zlibDeflateOptions, { windowBits })
);
this._deflate = zlib.createDeflateRaw({
...this._options.zlibDeflateOptions,
windowBits
});

this._deflate[kTotalLength] = 0;
this._deflate[kBuffers] = [];
@@ -429,7 +431,7 @@ class PerMessageDeflate {
return;
}

var data = bufferUtil.concat(
let data = bufferUtil.concat(
this._deflate[kBuffers],
this._deflate[kTotalLength]
);
6 changes: 3 additions & 3 deletions lib/receiver.js
Original file line number Diff line number Diff line change
@@ -117,7 +117,7 @@ class Receiver extends Writable {
* @private
*/
startLoop(cb) {
var err;
let err;
this._loop = true;

do {
@@ -320,7 +320,7 @@ class Receiver extends Writable {
* @private
*/
getData(cb) {
var data = EMPTY_BUFFER;
let data = EMPTY_BUFFER;

if (this._payloadLength) {
if (this._bufferedBytes < this._payloadLength) {
@@ -400,7 +400,7 @@ class Receiver extends Writable {
this._fragments = [];

if (this._opcode === 2) {
var data;
let data;

if (this._binaryType === 'nodebuffer') {
data = concat(fragments, messageLength);
18 changes: 10 additions & 8 deletions lib/sender.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
'use strict';

const { randomBytes } = require('crypto');
const { randomFillSync } = require('crypto');

const PerMessageDeflate = require('./permessage-deflate');
const { EMPTY_BUFFER } = require('./constants');
const { isValidStatusCode } = require('./validation');
const { mask: applyMask, toBuffer } = require('./buffer-util');

const mask = Buffer.alloc(4);

/**
* HyBi Sender implementation.
*/
@@ -44,8 +46,8 @@ class Sender {
*/
static frame(data, options) {
const merge = options.mask && options.readOnly;
var offset = options.mask ? 6 : 2;
var payloadLength = data.length;
let offset = options.mask ? 6 : 2;
let payloadLength = data.length;

if (data.length >= 65536) {
offset += 8;
@@ -71,7 +73,7 @@ class Sender {

if (!options.mask) return [target, data];

const mask = randomBytes(4);
randomFillSync(mask, 0, 4);

target[1] |= 0x80;
target[offset - 4] = mask[0];
@@ -98,7 +100,7 @@ class Sender {
* @public
*/
close(code, data, mask, cb) {
var buf;
let buf;

if (code === undefined) {
buf = EMPTY_BUFFER;
@@ -236,8 +238,8 @@ class Sender {
send(data, options, cb) {
const buf = toBuffer(data);
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
var opcode = options.binary ? 2 : 1;
var rsv1 = options.compress;
let opcode = options.binary ? 2 : 1;
let rsv1 = options.compress;

if (this._firstFragment) {
this._firstFragment = false;
@@ -321,7 +323,7 @@ class Sender {
const params = this._queue.shift();

this._bufferedBytes -= params[1].length;
params[0].apply(this, params.slice(1));
Reflect.apply(params[0], this, params.slice(1));
}
}

67 changes: 31 additions & 36 deletions lib/websocket-server.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict';

const EventEmitter = require('events');
const crypto = require('crypto');
const http = require('http');
const { createHash } = require('crypto');
const { createServer, STATUS_CODES } = require('http');

const PerMessageDeflate = require('./permessage-deflate');
const extension = require('./extension');
const WebSocket = require('./websocket');
const { format, parse } = require('./extension');
const { GUID } = require('./constants');

const keyRegex = /^[+/0-9A-Za-z]{22}==$/;
@@ -40,22 +40,20 @@ class WebSocketServer extends EventEmitter {
constructor(options, callback) {
super();

options = Object.assign(
{
maxPayload: 100 * 1024 * 1024,
perMessageDeflate: false,
handleProtocols: null,
clientTracking: true,
verifyClient: null,
noServer: false,
backlog: null, // use default (511 as implemented in net.js)
server: null,
host: null,
path: null,
port: null
},
options
);
options = {
maxPayload: 100 * 1024 * 1024,
perMessageDeflate: false,
handleProtocols: null,
clientTracking: true,
verifyClient: null,
noServer: false,
backlog: null, // use default (511 as implemented in net.js)
server: null,
host: null,
path: null,
port: null,
...options
};

if (options.port == null && !options.server && !options.noServer) {
throw new TypeError(
@@ -64,8 +62,8 @@ class WebSocketServer extends EventEmitter {
}

if (options.port != null) {
this._server = http.createServer((req, res) => {
const body = http.STATUS_CODES[426];
this._server = createServer((req, res) => {
const body = STATUS_CODES[426];

res.writeHead(426, {
'Content-Length': body.length,
@@ -208,7 +206,7 @@ class WebSocketServer extends EventEmitter {
);

try {
const offers = extension.parse(req.headers['sec-websocket-extensions']);
const offers = parse(req.headers['sec-websocket-extensions']);

if (offers[PerMessageDeflate.extensionName]) {
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
@@ -264,8 +262,7 @@ class WebSocketServer extends EventEmitter {
//
if (!socket.readable || !socket.writable) return socket.destroy();

const digest = crypto
.createHash('sha1')
const digest = createHash('sha1')
.update(key + GUID)
.digest('base64');

@@ -277,7 +274,7 @@ class WebSocketServer extends EventEmitter {
];

const ws = new WebSocket(null);
var protocol = req.headers['sec-websocket-protocol'];
let protocol = req.headers['sec-websocket-protocol'];

if (protocol) {
protocol = protocol.trim().split(/ *, */);
@@ -299,7 +296,7 @@ class WebSocketServer extends EventEmitter {

if (extensions[PerMessageDeflate.extensionName]) {
const params = extensions[PerMessageDeflate.extensionName].params;
const value = extension.format({
const value = format({
[PerMessageDeflate.extensionName]: [params]
});
headers.push(`Sec-WebSocket-Extensions: ${value}`);
@@ -376,18 +373,16 @@ function socketOnError() {
*/
function abortHandshake(socket, code, message, headers) {
if (socket.writable) {
message = message || http.STATUS_CODES[code];
headers = Object.assign(
{
Connection: 'close',
'Content-type': 'text/html',
'Content-Length': Buffer.byteLength(message)
},
headers
);
message = message || STATUS_CODES[code];
headers = {
Connection: 'close',
'Content-type': 'text/html',
'Content-Length': Buffer.byteLength(message),
...headers
};

socket.write(
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
`HTTP/1.1 ${code} ${STATUS_CODES[code]}\r\n` +
Object.keys(headers)
.map((h) => `${h}: ${headers[h]}`)
.join('\r\n') +
229 changes: 118 additions & 111 deletions lib/websocket.js

Large diffs are not rendered by default.

32 changes: 19 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ws",
"version": "6.2.1",
"version": "7.0.0",
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
"keywords": [
"HyBi",
@@ -25,21 +25,27 @@
"scripts": {
"test": "npm run lint && nyc --reporter=html --reporter=text mocha test/*.test.js",
"integration": "npm run lint && mocha test/*.integration.js",
"lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yml}\""
"lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""
},
"dependencies": {
"async-limiter": "~1.0.0"
"async-limiter": "^1.0.0"
},
"devDependencies": {
"benchmark": "~2.1.4",
"bufferutil": "~4.0.0",
"coveralls": "~3.0.3",
"eslint": "~5.15.0",
"eslint-config-prettier": "~4.1.0",
"eslint-plugin-prettier": "~3.0.0",
"mocha": "~6.0.0",
"nyc": "~13.3.0",
"prettier": "~1.16.1",
"utf-8-validate": "~5.0.0"
"benchmark": "^2.1.4",
"bufferutil": "^4.0.1",
"coveralls": "^3.0.3",
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.1.0",
"eslint-plugin-prettier": "^3.0.1",
"mocha": "^6.1.3",
"nyc": "^14.0.0",
"prettier": "^1.17.0",
"utf-8-validate": "^5.0.2"
},
"greenkeeper": {
"commitMessages": {
"dependencyUpdate": "[pkg] Update ${dependency} to version ${version}",
"devDependencyUpdate": "[pkg] Update ${dependency} to version ${version}"
}
}
}
84 changes: 44 additions & 40 deletions test/extension.test.js
Original file line number Diff line number Diff line change
@@ -2,79 +2,83 @@

const assert = require('assert');

const extension = require('../lib/extension');
const { format, parse } = require('../lib/extension');

describe('extension', () => {
describe('parse', () => {
it('returns an empty object if the argument is `undefined`', () => {
assert.deepStrictEqual(extension.parse(), {});
assert.deepStrictEqual(extension.parse(''), {});
assert.deepStrictEqual(parse(), { __proto__: null });
assert.deepStrictEqual(parse(''), { __proto__: null });
});

it('parses a single extension', () => {
const extensions = extension.parse('foo');

assert.deepStrictEqual(extensions, { foo: [{}] });
assert.deepStrictEqual(parse('foo'), {
foo: [{ __proto__: null }],
__proto__: null
});
});

it('parses params', () => {
const extensions = extension.parse('foo;bar;baz=1;bar=2');

assert.deepStrictEqual(extensions, {
foo: [{ bar: [true, '2'], baz: ['1'] }]
assert.deepStrictEqual(parse('foo;bar;baz=1;bar=2'), {
foo: [{ bar: [true, '2'], baz: ['1'], __proto__: null }],
__proto__: null
});
});

it('parses multiple extensions', () => {
const extensions = extension.parse('foo,bar;baz,foo;baz');

assert.deepStrictEqual(extensions, {
foo: [{}, { baz: [true] }],
bar: [{ baz: [true] }]
assert.deepStrictEqual(parse('foo,bar;baz,foo;baz'), {
foo: [{ __proto__: null }, { baz: [true], __proto__: null }],
bar: [{ baz: [true], __proto__: null }],
__proto__: null
});
});

it('parses quoted params', () => {
assert.deepStrictEqual(extension.parse('foo;bar="hi"'), {
foo: [{ bar: ['hi'] }]
assert.deepStrictEqual(parse('foo;bar="hi"'), {
foo: [{ bar: ['hi'], __proto__: null }],
__proto__: null
});
assert.deepStrictEqual(extension.parse('foo;bar="\\0"'), {
foo: [{ bar: ['0'] }]
assert.deepStrictEqual(parse('foo;bar="\\0"'), {
foo: [{ bar: ['0'], __proto__: null }],
__proto__: null
});
assert.deepStrictEqual(extension.parse('foo;bar="b\\a\\z"'), {
foo: [{ bar: ['baz'] }]
assert.deepStrictEqual(parse('foo;bar="b\\a\\z"'), {
foo: [{ bar: ['baz'], __proto__: null }],
__proto__: null
});
assert.deepStrictEqual(extension.parse('foo;bar="b\\az";bar'), {
foo: [{ bar: ['baz', true] }]
assert.deepStrictEqual(parse('foo;bar="b\\az";bar'), {
foo: [{ bar: ['baz', true], __proto__: null }],
__proto__: null
});
assert.throws(
() => extension.parse('foo;bar="baz"qux'),
() => parse('foo;bar="baz"qux'),
/^SyntaxError: Unexpected character at index 13$/
);
assert.throws(
() => extension.parse('foo;bar="baz" qux'),
() => parse('foo;bar="baz" qux'),
/^SyntaxError: Unexpected character at index 14$/
);
});

it('works with names that match `Object.prototype` property names', () => {
const parse = extension.parse;

assert.deepStrictEqual(parse('hasOwnProperty, toString'), {
hasOwnProperty: [{}],
toString: [{}]
hasOwnProperty: [{ __proto__: null }],
toString: [{ __proto__: null }],
__proto__: null
});
assert.deepStrictEqual(parse('foo;constructor'), {
foo: [{ constructor: [true] }]
foo: [{ constructor: [true], __proto__: null }],
__proto__: null
});
});

it('ignores the optional white spaces', () => {
const header = 'foo; bar\t; \tbaz=1\t ; bar="1"\t\t, \tqux\t ;norf ';

assert.deepStrictEqual(extension.parse(header), {
foo: [{ bar: [true, '1'], baz: ['1'] }],
qux: [{ norf: [true] }]
assert.deepStrictEqual(parse(header), {
foo: [{ bar: [true, '1'], baz: ['1'], __proto__: null }],
qux: [{ norf: [true], __proto__: null }],
__proto__: null
});
});

@@ -91,7 +95,7 @@ describe('extension', () => {
['foo;bar=""', 9]
].forEach((element) => {
assert.throws(
() => extension.parse(element[0]),
() => parse(element[0]),
new RegExp(
`^SyntaxError: Unexpected character at index ${element[1]}$`
)
@@ -107,7 +111,7 @@ describe('extension', () => {
['foo;bar= ', 8]
].forEach((element) => {
assert.throws(
() => extension.parse(element[0]),
() => parse(element[0]),
new RegExp(
`^SyntaxError: Unexpected character at index ${element[1]}$`
)
@@ -133,7 +137,7 @@ describe('extension', () => {
['foo;bar="\\\\"', 10]
].forEach((element) => {
assert.throws(
() => extension.parse(element[0]),
() => parse(element[0]),
new RegExp(
`^SyntaxError: Unexpected character at index ${element[1]}$`
)
@@ -152,7 +156,7 @@ describe('extension', () => {
'foo;bar="1\\'
].forEach((header) => {
assert.throws(
() => extension.parse(header),
() => parse(header),
/^SyntaxError: Unexpected end of input$/
);
});
@@ -161,19 +165,19 @@ describe('extension', () => {

describe('format', () => {
it('formats a single extension', () => {
const extensions = extension.format({ foo: {} });
const extensions = format({ foo: {} });

assert.strictEqual(extensions, 'foo');
});

it('formats params', () => {
const extensions = extension.format({ foo: { bar: [true, 2], baz: 1 } });
const extensions = format({ foo: { bar: [true, 2], baz: 1 } });

assert.strictEqual(extensions, 'foo; bar; bar=2; baz=1');
});

it('formats multiple extensions', () => {
const extensions = extension.format({
const extensions = format({
foo: [{}, { baz: true }],
bar: { baz: true }
});
12 changes: 8 additions & 4 deletions test/permessage-deflate.test.js
Original file line number Diff line number Diff line change
@@ -120,7 +120,8 @@ describe('PerMessageDeflate', () => {
server_no_context_takeover: true,
client_no_context_takeover: true,
server_max_window_bits: 10,
client_max_window_bits: 11
client_max_window_bits: 11,
__proto__: null
}
);
});
@@ -145,7 +146,8 @@ describe('PerMessageDeflate', () => {
server_no_context_takeover: true,
client_no_context_takeover: true,
server_max_window_bits: 12,
client_max_window_bits: 11
client_max_window_bits: 11,
__proto__: null
}
);
});
@@ -162,7 +164,8 @@ describe('PerMessageDeflate', () => {
assert.deepStrictEqual(
perMessageDeflate.accept(extensions['permessage-deflate']),
{
server_max_window_bits: 11
server_max_window_bits: 11,
__proto__: null
}
);
});
@@ -259,7 +262,8 @@ describe('PerMessageDeflate', () => {
server_no_context_takeover: true,
client_no_context_takeover: true,
server_max_window_bits: 10,
client_max_window_bits: 11
client_max_window_bits: 11,
__proto__: null
}
);
});
373 changes: 267 additions & 106 deletions test/websocket.test.js

Large diffs are not rendered by default.