strong-tie/inbound-calls
0
1# ws: a Node.js WebSocket library2 3[](https://www.npmjs.com/package/ws)4[](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)5[](https://coveralls.io/github/websockets/ws)6 7ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and8server implementation.9 10Passes the quite extensive Autobahn test suite: [server][server-report],11[client][client-report].12 13**Note**: This module does not work in the browser. The client in the docs is a14reference to a backend with the role of a client in the WebSocket communication.15Browser clients must use the native16[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)17object. To make the same code work seamlessly on Node.js and the browser, you18can use one of the many wrappers available on npm, like19[isomorphic-ws](https://github.com/heineiuo/isomorphic-ws).20 21## Table of Contents22 23- [Protocol support](#protocol-support)24- [Installing](#installing)25 - [Opt-in for performance](#opt-in-for-performance)26 - [Legacy opt-in for performance](#legacy-opt-in-for-performance)27- [API docs](#api-docs)28- [WebSocket compression](#websocket-compression)29- [Usage examples](#usage-examples)30 - [Sending and receiving text data](#sending-and-receiving-text-data)31 - [Sending binary data](#sending-binary-data)32 - [Simple server](#simple-server)33 - [External HTTP/S server](#external-https-server)34 - [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server)35 - [Client authentication](#client-authentication)36 - [Server broadcast](#server-broadcast)37 - [Round-trip time](#round-trip-time)38 - [Use the Node.js streams API](#use-the-nodejs-streams-api)39 - [Other examples](#other-examples)40- [FAQ](#faq)41 - [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client)42 - [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections)43 - [How to connect via a proxy?](#how-to-connect-via-a-proxy)44- [Changelog](#changelog)45- [License](#license)46 47## Protocol support48 49- **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)50- **HyBi drafts 13-17** (Current default, alternatively option51 `protocolVersion: 13`)52 53## Installing54 55```56npm install ws57```58 59### Opt-in for performance60 61[bufferutil][] is an optional module that can be installed alongside the ws62module:63 64```65npm install --save-optional bufferutil66```67 68This is a binary addon that improves the performance of certain operations such69as masking and unmasking the data payload of the WebSocket frames. Prebuilt70binaries are available for the most popular platforms, so you don't necessarily71need to have a C++ compiler installed on your machine.72 73To force ws to not use bufferutil, use the74[`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) environment variable. This75can be useful to enhance security in systems where a user can put a package in76the package search path of an application of another user, due to how the77Node.js resolver algorithm works.78 79#### Legacy opt-in for performance80 81If you are running on an old version of Node.js (prior to v18.14.0), ws also82supports the [utf-8-validate][] module:83 84```85npm install --save-optional utf-8-validate86```87 88This contains a binary polyfill for [`buffer.isUtf8()`][].89 90To force ws not to use utf-8-validate, use the91[`WS_NO_UTF_8_VALIDATE`](./doc/ws.md#ws_no_utf_8_validate) environment variable.92 93## API docs94 95See [`/doc/ws.md`](./doc/ws.md) for Node.js-like documentation of ws classes and96utility functions.97 98## WebSocket compression99 100ws supports the [permessage-deflate extension][permessage-deflate] which enables101the client and server to negotiate a compression algorithm and its parameters,102and then selectively apply it to the data payloads of each WebSocket message.103 104The extension is disabled by default on the server and enabled by default on the105client. It adds a significant overhead in terms of performance and memory106consumption so we suggest to enable it only if it is really needed.107 108Note that Node.js has a variety of issues with high-performance compression,109where increased concurrency, especially on Linux, can lead to [catastrophic110memory fragmentation][node-zlib-bug] and slow performance. If you intend to use111permessage-deflate in production, it is worthwhile to set up a test112representative of your workload and ensure Node.js/zlib will handle it with113acceptable performance and memory usage.114 115Tuning of permessage-deflate can be done via the options defined below. You can116also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly117into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs].118 119See [the docs][ws-server-options] for more options.120 121```js122import WebSocket, { WebSocketServer } from 'ws';123 124const wss = new WebSocketServer({125 port: 8080,126 perMessageDeflate: {127 zlibDeflateOptions: {128 // See zlib defaults.129 chunkSize: 1024,130 memLevel: 7,131 level: 3132 },133 zlibInflateOptions: {134 chunkSize: 10 * 1024135 },136 // Other options settable:137 clientNoContextTakeover: true, // Defaults to negotiated value.138 serverNoContextTakeover: true, // Defaults to negotiated value.139 serverMaxWindowBits: 10, // Defaults to negotiated value.140 // Below options specified as default values.141 concurrencyLimit: 10, // Limits zlib concurrency for perf.142 threshold: 1024 // Size (in bytes) below which messages143 // should not be compressed if context takeover is disabled.144 }145});146```147 148The client will only use the extension if it is supported and enabled on the149server. To always disable the extension on the client, set the150`perMessageDeflate` option to `false`.151 152```js153import WebSocket from 'ws';154 155const ws = new WebSocket('ws://www.host.com/path', {156 perMessageDeflate: false157});158```159 160## Usage examples161 162### Sending and receiving text data163 164```js165import WebSocket from 'ws';166 167const ws = new WebSocket('ws://www.host.com/path');168 169ws.on('error', console.error);170 171ws.on('open', function open() {172 ws.send('something');173});174 175ws.on('message', function message(data) {176 console.log('received: %s', data);177});178```179 180### Sending binary data181 182```js183import WebSocket from 'ws';184 185const ws = new WebSocket('ws://www.host.com/path');186 187ws.on('error', console.error);188 189ws.on('open', function open() {190 const array = new Float32Array(5);191 192 for (var i = 0; i < array.length; ++i) {193 array[i] = i / 2;194 }195 196 ws.send(array);197});198```199 200### Simple server201 202```js203import { WebSocketServer } from 'ws';204 205const wss = new WebSocketServer({ port: 8080 });206 207wss.on('connection', function connection(ws) {208 ws.on('error', console.error);209 210 ws.on('message', function message(data) {211 console.log('received: %s', data);212 });213 214 ws.send('something');215});216```217 218### External HTTP/S server219 220```js221import { createServer } from 'https';222import { readFileSync } from 'fs';223import { WebSocketServer } from 'ws';224 225const server = createServer({226 cert: readFileSync('/path/to/cert.pem'),227 key: readFileSync('/path/to/key.pem')228});229const wss = new WebSocketServer({ server });230 231wss.on('connection', function connection(ws) {232 ws.on('error', console.error);233 234 ws.on('message', function message(data) {235 console.log('received: %s', data);236 });237 238 ws.send('something');239});240 241server.listen(8080);242```243 244### Multiple servers sharing a single HTTP/S server245 246```js247import { createServer } from 'http';248import { WebSocketServer } from 'ws';249 250const server = createServer();251const wss1 = new WebSocketServer({ noServer: true });252const wss2 = new WebSocketServer({ noServer: true });253 254wss1.on('connection', function connection(ws) {255 ws.on('error', console.error);256 257 // ...258});259 260wss2.on('connection', function connection(ws) {261 ws.on('error', console.error);262 263 // ...264});265 266server.on('upgrade', function upgrade(request, socket, head) {267 const { pathname } = new URL(request.url, 'wss://base.url');268 269 if (pathname === '/foo') {270 wss1.handleUpgrade(request, socket, head, function done(ws) {271 wss1.emit('connection', ws, request);272 });273 } else if (pathname === '/bar') {274 wss2.handleUpgrade(request, socket, head, function done(ws) {275 wss2.emit('connection', ws, request);276 });277 } else {278 socket.destroy();279 }280});281 282server.listen(8080);283```284 285### Client authentication286 287```js288import { createServer } from 'http';289import { WebSocketServer } from 'ws';290 291function onSocketError(err) {292 console.error(err);293}294 295const server = createServer();296const wss = new WebSocketServer({ noServer: true });297 298wss.on('connection', function connection(ws, request, client) {299 ws.on('error', console.error);300 301 ws.on('message', function message(data) {302 console.log(`Received message ${data} from user ${client}`);303 });304});305 306server.on('upgrade', function upgrade(request, socket, head) {307 socket.on('error', onSocketError);308 309 // This function is not defined on purpose. Implement it with your own logic.310 authenticate(request, function next(err, client) {311 if (err || !client) {312 socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');313 socket.destroy();314 return;315 }316 317 socket.removeListener('error', onSocketError);318 319 wss.handleUpgrade(request, socket, head, function done(ws) {320 wss.emit('connection', ws, request, client);321 });322 });323});324 325server.listen(8080);326```327 328Also see the provided [example][session-parse-example] using `express-session`.329 330### Server broadcast331 332A client WebSocket broadcasting to all connected WebSocket clients, including333itself.334 335```js336import WebSocket, { WebSocketServer } from 'ws';337 338const wss = new WebSocketServer({ port: 8080 });339 340wss.on('connection', function connection(ws) {341 ws.on('error', console.error);342 343 ws.on('message', function message(data, isBinary) {344 wss.clients.forEach(function each(client) {345 if (client.readyState === WebSocket.OPEN) {346 client.send(data, { binary: isBinary });347 }348 });349 });350});351```352 353A client WebSocket broadcasting to every other connected WebSocket clients,354excluding itself.355 356```js357import WebSocket, { WebSocketServer } from 'ws';358 359const wss = new WebSocketServer({ port: 8080 });360 361wss.on('connection', function connection(ws) {362 ws.on('error', console.error);363 364 ws.on('message', function message(data, isBinary) {365 wss.clients.forEach(function each(client) {366 if (client !== ws && client.readyState === WebSocket.OPEN) {367 client.send(data, { binary: isBinary });368 }369 });370 });371});372```373 374### Round-trip time375 376```js377import WebSocket from 'ws';378 379const ws = new WebSocket('wss://websocket-echo.com/');380 381ws.on('error', console.error);382 383ws.on('open', function open() {384 console.log('connected');385 ws.send(Date.now());386});387 388ws.on('close', function close() {389 console.log('disconnected');390});391 392ws.on('message', function message(data) {393 console.log(`Round-trip time: ${Date.now() - data} ms`);394 395 setTimeout(function timeout() {396 ws.send(Date.now());397 }, 500);398});399```400 401### Use the Node.js streams API402 403```js404import WebSocket, { createWebSocketStream } from 'ws';405 406const ws = new WebSocket('wss://websocket-echo.com/');407 408const duplex = createWebSocketStream(ws, { encoding: 'utf8' });409 410duplex.on('error', console.error);411 412duplex.pipe(process.stdout);413process.stdin.pipe(duplex);414```415 416### Other examples417 418For a full example with a browser client communicating with a ws server, see the419examples folder.420 421Otherwise, see the test cases.422 423## FAQ424 425### How to get the IP address of the client?426 427The remote IP address can be obtained from the raw socket.428 429```js430import { WebSocketServer } from 'ws';431 432const wss = new WebSocketServer({ port: 8080 });433 434wss.on('connection', function connection(ws, req) {435 const ip = req.socket.remoteAddress;436 437 ws.on('error', console.error);438});439```440 441When the server runs behind a proxy like NGINX, the de-facto standard is to use442the `X-Forwarded-For` header.443 444```js445wss.on('connection', function connection(ws, req) {446 const ip = req.headers['x-forwarded-for'].split(',')[0].trim();447 448 ws.on('error', console.error);449});450```451 452### How to detect and close broken connections?453 454Sometimes, the link between the server and the client can be interrupted in a455way that keeps both the server and the client unaware of the broken state of the456connection (e.g. when pulling the cord).457 458In these cases, ping messages can be used as a means to verify that the remote459endpoint is still responsive.460 461```js462import { WebSocketServer } from 'ws';463 464function heartbeat() {465 this.isAlive = true;466}467 468const wss = new WebSocketServer({ port: 8080 });469 470wss.on('connection', function connection(ws) {471 ws.isAlive = true;472 ws.on('error', console.error);473 ws.on('pong', heartbeat);474});475 476const interval = setInterval(function ping() {477 wss.clients.forEach(function each(ws) {478 if (ws.isAlive === false) return ws.terminate();479 480 ws.isAlive = false;481 ws.ping();482 });483}, 30000);484 485wss.on('close', function close() {486 clearInterval(interval);487});488```489 490Pong messages are automatically sent in response to ping messages as required by491the spec.492 493Just like the server example above, your clients might as well lose connection494without knowing it. You might want to add a ping listener on your clients to495prevent that. A simple implementation would be:496 497```js498import WebSocket from 'ws';499 500function heartbeat() {501 clearTimeout(this.pingTimeout);502 503 // Use `WebSocket#terminate()`, which immediately destroys the connection,504 // instead of `WebSocket#close()`, which waits for the close timer.505 // Delay should be equal to the interval at which your server506 // sends out pings plus a conservative assumption of the latency.507 this.pingTimeout = setTimeout(() => {508 this.terminate();509 }, 30000 + 1000);510}511 512const client = new WebSocket('wss://websocket-echo.com/');513 514client.on('error', console.error);515client.on('open', heartbeat);516client.on('ping', heartbeat);517client.on('close', function clear() {518 clearTimeout(this.pingTimeout);519});520```521 522### How to connect via a proxy?523 524Use a custom `http.Agent` implementation like [https-proxy-agent][] or525[socks-proxy-agent][].526 527## Changelog528 529We're using the GitHub [releases][changelog] for changelog entries.530 531## License532 533[MIT](LICENSE)534 535[`buffer.isutf8()`]: https://nodejs.org/api/buffer.html#bufferisutf8input536[bufferutil]: https://github.com/websockets/bufferutil537[changelog]: https://github.com/websockets/ws/releases538[client-report]: http://websockets.github.io/ws/autobahn/clients/539[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent540[node-zlib-bug]: https://github.com/nodejs/node/issues/8871541[node-zlib-deflaterawdocs]:542 https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options543[permessage-deflate]: https://tools.ietf.org/html/rfc7692544[server-report]: http://websockets.github.io/ws/autobahn/servers/545[session-parse-example]: ./examples/express-session-parse546[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent547[utf-8-validate]: https://github.com/websockets/utf-8-validate548[ws-server-options]: ./doc/ws.md#new-websocketserveroptions-callback549 