├── .gitignore ├── LICENSE ├── collaborators.md ├── echo-server.js ├── index.d.ts ├── index.js ├── package.json ├── readme.md ├── server.js ├── stream.js ├── test-client.js ├── test-server.js ├── test.js ├── ts-tests.ts └── ws-fallback.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | yarn.lock 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Max Ogden 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /collaborators.md: -------------------------------------------------------------------------------- 1 | ## Collaborators 2 | 3 | websocket-stream is only possible due to the excellent work of the following collaborators: 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
gstGitHub/gst
maxogdenGitHub/maxogden
deanlandoltGitHub/deanlandolt
mcollinaGitHub/mcollina
jnordbergGitHub/jnordberg
mafintoshGitHub/mafintosh
12 | -------------------------------------------------------------------------------- /echo-server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | var websocket = require('./') 5 | var server = null 6 | 7 | var port = module.exports.port = 8343 8 | var url = module.exports.url = 'ws://localhost:' + module.exports.port 9 | 10 | module.exports.start = function(opts, cb) { 11 | if (server) { 12 | cb(new Error('already started')); 13 | return; 14 | } 15 | 16 | if (typeof opts == 'function') { 17 | cb = opts; 18 | opts = {}; 19 | } 20 | 21 | server = http.createServer() 22 | opts.server = server 23 | 24 | websocket.createServer(opts, echo) 25 | 26 | server.listen(port, cb) 27 | 28 | function echo(stream) { 29 | stream.pipe(stream) 30 | } 31 | } 32 | 33 | module.exports.stop = function(cb) { 34 | if (!server) { 35 | cb(new Error('not started')) 36 | return 37 | } 38 | 39 | server.close(cb) 40 | server = null 41 | } 42 | 43 | if (!module.parent) { 44 | module.exports.start(function(err) { 45 | if (err) { 46 | console.error(err); 47 | return; 48 | } 49 | console.log('Echo server started on port ' + port); 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for websocket-stream 5.3 2 | // Project: https://github.com/maxogden/websocket-stream#readme 3 | // Original definitions by: Ben Burns 4 | 5 | import * as WebSocket from 'ws'; 6 | import { Duplex } from 'stream'; 7 | import * as http from 'http'; 8 | 9 | declare namespace WebSocketStream { 10 | type WebSocketDuplex = Duplex & { socket: WebSocket }; 11 | 12 | class Server extends WebSocket.Server { 13 | on(event: 'connection', cb: (this: WebSocket, socket: WebSocket, request: http.IncomingMessage) => void): this; 14 | on(event: 'error', cb: (this: WebSocket, error: Error) => void): this; 15 | on(event: 'headers', cb: (this: WebSocket, headers: string[], request: http.IncomingMessage) => void): this; 16 | on(event: 'listening', cb: (this: WebSocket) => void): this; 17 | on(event: 'stream', cb: (this: WebSocket, stream: WebSocketDuplex, request: http.IncomingMessage) => void): this; 18 | on(event: string | symbol, listener: (this: WebSocket, ...args: any[]) => void): this; 19 | } 20 | 21 | function createServer(opts?: WebSocket.ServerOptions, callback?: () => void): Server; 22 | } 23 | 24 | declare function WebSocketStream(target: string | WebSocket, options?: WebSocket.ClientOptions): WebSocketStream.WebSocketDuplex; 25 | declare function WebSocketStream(target: string | WebSocket, protocols?: string | string[], options?: WebSocket.ClientOptions): WebSocketStream.WebSocketDuplex; 26 | 27 | export = WebSocketStream; 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var server = require('./server.js') 3 | 4 | module.exports = require('./stream.js') 5 | module.exports.Server = server.Server 6 | module.exports.createServer = server.createServer 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "websocket-stream", 3 | "version": "5.5.2", 4 | "license": "BSD-2-Clause", 5 | "description": "Use websockets with the node streams API. Works in browser and node", 6 | "scripts": { 7 | "test": "node test.js", 8 | "start": "beefy test-client.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+ssh://git@github.com/maxogden/websocket-stream.git" 13 | }, 14 | "keywords": [ 15 | "websocket", 16 | "websockets", 17 | "stream", 18 | "streams", 19 | "realtime" 20 | ], 21 | "_npmUser": { 22 | "name": "maxogden", 23 | "email": "max@maxogden.com" 24 | }, 25 | "dependencies": { 26 | "duplexify": "^3.5.1", 27 | "inherits": "^2.0.1", 28 | "readable-stream": "^2.3.3", 29 | "safe-buffer": "^5.1.2", 30 | "ws": "^3.2.0", 31 | "xtend": "^4.0.0" 32 | }, 33 | "devDependencies": { 34 | "@types/node": "^11.13.4", 35 | "@types/ws": "^6.0.1", 36 | "beefy": "^2.1.8", 37 | "browserify": "^16.2.3", 38 | "concat-stream": "^1.6.2", 39 | "tape": "^4.9.1", 40 | "typescript": "^3.4.3" 41 | }, 42 | "optionalDependencies": {}, 43 | "browser": { 44 | "./echo-server.js": "./fake-server.js", 45 | "./index.js": "./stream.js", 46 | "ws": "./ws-fallback.js" 47 | }, 48 | "bugs": { 49 | "url": "https://github.com/maxogden/websocket-stream/issues" 50 | }, 51 | "homepage": "https://github.com/maxogden/websocket-stream#readme", 52 | "main": "index.js", 53 | "author": "" 54 | } 55 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # websocket-stream 2 | 3 | [![NPM](https://nodei.co/npm/websocket-stream.png?global=true)](https://nodei.co/npm/websocket-stream/) 4 | 5 | Use HTML5 [websockets](https://developer.mozilla.org/en-US/docs/WebSockets) using the Node Streams API. 6 | 7 | ### Usage 8 | 9 | This module works in Node or in Browsers that support WebSockets. You can use [browserify](http://github.com/substack/node-browserify) to package this module for browser use. 10 | 11 | ```javascript 12 | var websocket = require('websocket-stream') 13 | var ws = websocket('ws://echo.websocket.org') 14 | process.stdin.pipe(ws) 15 | ws.pipe(process.stdout) 16 | ``` 17 | 18 | In the example above `ws` is a duplex stream. That means you can pipe output to anything that accepts streams. You can also pipe data into streams (such as a webcam feed or audio data). 19 | 20 | The underlying `WebSocket` instance is available as `ws.socket`. 21 | 22 | #### Options 23 | 24 | The available options differs depending on if you use this module in the browser or with node.js. Options can be passed in as the third or second argument - `WebSocket(address, [protocols], [options])`. 25 | 26 | ##### `options.browserBufferSize` 27 | 28 | How much to allow the [socket.bufferedAmount](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Attributes) to grow before starting to throttle writes. This option has no effect in node.js. 29 | 30 | Default: `1024 * 512` (512KiB) 31 | 32 | ##### `options.browserBufferTimeout` 33 | 34 | How long to wait before checking if the socket buffer has drained sufficently for another write. This option has no effect in node.js. 35 | 36 | Default: `1000` (1 second) 37 | 38 | ##### `options.objectMode` 39 | 40 | Send each chunk on its own, and do not try to pack them in a single 41 | websocket frame. 42 | 43 | Default: `false` 44 | 45 | ##### `options.binary` 46 | 47 | Always convert to `Buffer` in Node.js before sending. 48 | Forces `options.objectMode` to `false`. 49 | 50 | Default: `true` 51 | 52 | ##### `options.perMessageDeflate` 53 | 54 | We recommend disabling the [per message deflate 55 | extension](https://tools.ietf.org/html/rfc7692) to achieve the best 56 | throughput. 57 | 58 | Default: `true` on the client, `false` on the server. 59 | 60 | Example: 61 | 62 | ```js 63 | var websocket = require('websocket-stream') 64 | var ws = websocket('ws://realtimecats.com', { 65 | perMessageDeflate: false 66 | }) 67 | ``` 68 | 69 | Beware that this option is ignored by browser clients. To make sure that permessage-deflate is never used, disable it on the server. 70 | 71 | ##### Other options 72 | 73 | When used in node.js see the [ws.WebSocket documentation](https://github.com/websockets/ws/blob/master/doc/ws.md#class-wswebsocket) 74 | 75 | ### On the server 76 | 77 | Using the [`ws`](http://npmjs.org/ws) module you can make a websocket server and use this module to get websocket streams on the server: 78 | 79 | ```javascript 80 | var websocket = require('websocket-stream') 81 | var wss = websocket.createServer({server: someHTTPServer}, handle) 82 | 83 | function handle(stream, request) { 84 | // `request` is the upgrade request sent by the client. 85 | fs.createReadStream('bigdata.json').pipe(stream) 86 | } 87 | ``` 88 | 89 | We recommend disabling the [per message deflate 90 | extension](https://tools.ietf.org/html/rfc7692) to achieve the best 91 | throughput: 92 | 93 | ```javascript 94 | var websocket = require('websocket-stream') 95 | var wss = websocket.createServer({ 96 | perMessageDeflate: false, 97 | server: someHTTPServer 98 | }, handle) 99 | 100 | function handle(stream) { 101 | fs.createReadStream('bigdata.json').pipe(stream) 102 | } 103 | ``` 104 | 105 | You can even use it on express.js with the [express-ws](https://www.npmjs.com/package/express-ws) library: 106 | 107 | ```js 108 | const express = require('express'); 109 | const expressWebSocket = require('express-ws'); 110 | const websocketStream = require('websocket-stream/stream'); 111 | const app = express(); 112 | 113 | // extend express app with app.ws() 114 | expressWebSocket(app, null, { 115 | // ws options here 116 | perMessageDeflate: false, 117 | }); 118 | 119 | app.ws('/bigdata.json', function(ws, req) { 120 | // convert ws instance to stream 121 | const stream = websocketStream(ws, { 122 | // websocket-stream options here 123 | binary: true, 124 | }); 125 | 126 | fs.createReadStream('bigdata.json').pipe(stream); 127 | }); 128 | 129 | app.listen(3000); 130 | ``` 131 | 132 | ## Run the tests 133 | 134 | ### Server-side tests 135 | 136 | ``` 137 | npm test 138 | ``` 139 | 140 | ### Client-side tests 141 | 142 | First start the echo server by running `node test-server.js` 143 | 144 | Then run `npm start` and open `localhost:9966` in your browser and open the Dev Tools console to see test output. 145 | 146 | ## license 147 | 148 | BSD LICENSE 149 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var WebSocketServer = require('ws').Server 4 | var stream = require('./stream') 5 | 6 | class Server extends WebSocketServer{ 7 | constructor(opts, cb) { 8 | super(opts) 9 | 10 | var proxied = false 11 | this.on('newListener', function(event) { 12 | if (!proxied && event === 'stream') { 13 | proxied = true 14 | this.on('connection', function(conn, req) { 15 | this.emit('stream', stream(conn, opts), req) 16 | }) 17 | } 18 | }) 19 | 20 | if (cb) { 21 | this.on('stream', cb) 22 | } 23 | } 24 | } 25 | 26 | module.exports.Server = Server 27 | module.exports.createServer = function(opts, cb) { 28 | return new Server(opts, cb) 29 | } 30 | -------------------------------------------------------------------------------- /stream.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var Transform = require('readable-stream').Transform 4 | var duplexify = require('duplexify') 5 | var WS = require('ws') 6 | var Buffer = require('safe-buffer').Buffer 7 | 8 | module.exports = WebSocketStream 9 | 10 | function buildProxy (options, socketWrite, socketEnd) { 11 | var proxy = new Transform({ 12 | objectMode: options.objectMode 13 | }) 14 | 15 | proxy._write = socketWrite 16 | proxy._flush = socketEnd 17 | 18 | return proxy 19 | } 20 | 21 | function WebSocketStream(target, protocols, options) { 22 | var stream, socket 23 | 24 | var isBrowser = process.title === 'browser' 25 | var isNative = !!global.WebSocket 26 | var socketWrite = isBrowser ? socketWriteBrowser : socketWriteNode 27 | 28 | if (protocols && !Array.isArray(protocols) && 'object' === typeof protocols) { 29 | // accept the "options" Object as the 2nd argument 30 | options = protocols 31 | protocols = null 32 | 33 | if (typeof options.protocol === 'string' || Array.isArray(options.protocol)) { 34 | protocols = options.protocol; 35 | } 36 | } 37 | 38 | if (!options) options = {} 39 | 40 | if (options.objectMode === undefined) { 41 | options.objectMode = !(options.binary === true || options.binary === undefined) 42 | } 43 | 44 | var proxy = buildProxy(options, socketWrite, socketEnd) 45 | 46 | if (!options.objectMode) { 47 | proxy._writev = writev 48 | } 49 | 50 | // browser only: sets the maximum socket buffer size before throttling 51 | var bufferSize = options.browserBufferSize || 1024 * 512 52 | 53 | // browser only: how long to wait when throttling 54 | var bufferTimeout = options.browserBufferTimeout || 1000 55 | 56 | // use existing WebSocket object that was passed in 57 | if (typeof target === 'object') { 58 | socket = target 59 | // otherwise make a new one 60 | } else { 61 | // special constructor treatment for native websockets in browsers, see 62 | // https://github.com/maxogden/websocket-stream/issues/82 63 | if (isNative && isBrowser) { 64 | socket = new WS(target, protocols) 65 | } else { 66 | socket = new WS(target, protocols, options) 67 | } 68 | 69 | socket.binaryType = 'arraybuffer' 70 | } 71 | 72 | // according to https://github.com/baygeldin/ws-streamify/issues/1 73 | // Nodejs WebSocketServer cause memory leak 74 | // Handlers like onerror, onclose, onmessage and onopen are accessible via setter/getter 75 | // And setter first of all fires removeAllListeners, that doesnt make inner array of clients on WebSocketServer cleared ever 76 | var eventListenerSupport = ('undefined' === typeof socket.addEventListener) 77 | 78 | // was already open when passed in 79 | if (socket.readyState === socket.OPEN) { 80 | stream = proxy 81 | } else { 82 | stream = stream = duplexify(undefined, undefined, options) 83 | if (!options.objectMode) { 84 | stream._writev = writev 85 | } 86 | 87 | if (eventListenerSupport) { 88 | socket.addEventListener('open', onopen) 89 | } else { 90 | socket.onopen = onopen 91 | } 92 | } 93 | 94 | stream.socket = socket 95 | 96 | if (eventListenerSupport) { 97 | socket.addEventListener('close', onclose) 98 | socket.addEventListener('error', onerror) 99 | socket.addEventListener('message', onmessage) 100 | } else { 101 | socket.onclose = onclose 102 | socket.onerror = onerror 103 | socket.onmessage = onmessage 104 | } 105 | 106 | proxy.on('close', destroy) 107 | 108 | var coerceToBuffer = !options.objectMode 109 | 110 | function socketWriteNode(chunk, enc, next) { 111 | // avoid errors, this never happens unless 112 | // destroy() is called 113 | if (socket.readyState !== socket.OPEN) { 114 | next() 115 | return 116 | } 117 | 118 | if (coerceToBuffer && typeof chunk === 'string') { 119 | chunk = Buffer.from(chunk, 'utf8') 120 | } 121 | socket.send(chunk, next) 122 | } 123 | 124 | function socketWriteBrowser(chunk, enc, next) { 125 | if (socket.bufferedAmount > bufferSize) { 126 | setTimeout(socketWriteBrowser, bufferTimeout, chunk, enc, next) 127 | return 128 | } 129 | 130 | if (coerceToBuffer && typeof chunk === 'string') { 131 | chunk = Buffer.from(chunk, 'utf8') 132 | } 133 | 134 | try { 135 | socket.send(chunk) 136 | } catch(err) { 137 | return next(err) 138 | } 139 | 140 | next() 141 | } 142 | 143 | function socketEnd(done) { 144 | socket.close() 145 | done() 146 | } 147 | 148 | function onopen() { 149 | stream.setReadable(proxy) 150 | stream.setWritable(proxy) 151 | stream.emit('connect') 152 | } 153 | 154 | function onclose() { 155 | stream.end() 156 | stream.destroy() 157 | } 158 | 159 | function onerror(err) { 160 | stream.destroy(err) 161 | } 162 | 163 | function onmessage(event) { 164 | var data = event.data 165 | if (data instanceof ArrayBuffer) data = Buffer.from(data) 166 | else data = Buffer.from(data, 'utf8') 167 | proxy.push(data) 168 | } 169 | 170 | function destroy() { 171 | socket.close() 172 | } 173 | 174 | // this is to be enabled only if objectMode is false 175 | function writev (chunks, cb) { 176 | var buffers = new Array(chunks.length) 177 | for (var i = 0; i < chunks.length; i++) { 178 | if (typeof chunks[i].chunk === 'string') { 179 | buffers[i] = Buffer.from(chunks[i], 'utf8') 180 | } else { 181 | buffers[i] = chunks[i].chunk 182 | } 183 | } 184 | 185 | this._write(Buffer.concat(buffers), 'binary', cb) 186 | } 187 | 188 | return stream 189 | } 190 | -------------------------------------------------------------------------------- /test-client.js: -------------------------------------------------------------------------------- 1 | var ws = require('./') 2 | var test = require('tape') 3 | var Buffer = require('safe-buffer').Buffer 4 | 5 | test('echo works', function(t) { 6 | var stream = ws('ws://localhost:8343') 7 | stream.on('data', function(o) { 8 | t.ok(Buffer.isBuffer(o), 'is buffer') 9 | t.equal(o.toString(), 'hello', 'got hello back') 10 | stream.destroy() 11 | t.end() 12 | }) 13 | stream.write(Buffer.from('hello')) 14 | }) 15 | 16 | test('echo works two times', function(t) { 17 | var stream = ws('ws://localhost:8343') 18 | stream.once('data', function(o) { 19 | t.equal(o.toString(), 'hello', 'got first hello back') 20 | stream.write(Buffer.from('hello')) 21 | stream.once('data', function(o) { 22 | t.equal(o.toString(), 'hello', 'got second hello back') 23 | stream.destroy() 24 | t.end() 25 | }) 26 | }) 27 | stream.write(Buffer.from('hello')) 28 | }) 29 | 30 | test('with bare WebSocket, strings as strings', function (t) { 31 | var socket = new WebSocket('ws://localhost:8344') 32 | 33 | socket.onmessage = function (e) { 34 | var data = e.data 35 | t.ok(typeof data === 'string', 'data must be a string') 36 | socket.close() 37 | t.end() 38 | } 39 | }) 40 | 41 | test('with bare WebSocket, binary only', function (t) { 42 | var socket = new WebSocket('ws://localhost:8345') 43 | 44 | socket.onmessage = function (e) { 45 | var data = e.data 46 | t.notOk(typeof data === 'string', 'data must not be a string') 47 | socket.close() 48 | t.end() 49 | } 50 | }) 51 | 52 | test('coerce client data as binary', function(t) { 53 | var stream = ws('ws://localhost:8346', { binary: true }) 54 | stream.on('data', function(o) { 55 | t.ok(Buffer.isBuffer(o), 'is buffer') 56 | t.equal(o.toString(), 'success', 'success!') 57 | stream.destroy() 58 | t.end() 59 | }) 60 | stream.write('hello') 61 | }) 62 | test('cork logic test', function (t) { 63 | var stream = ws('ws://localhost:8343', { binary: true }) 64 | stream.on('data', function(o) { 65 | t.equal(o.toString(), 'hello', 'success!') 66 | stream.destroy() 67 | t.end() 68 | }) 69 | stream.cork() 70 | stream.write('he') 71 | stream.write('l') 72 | stream.write('lo') 73 | stream.uncork() 74 | }) -------------------------------------------------------------------------------- /test-server.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | var websocket = require('./') 3 | var echo = require('./echo-server.js') 4 | var WebSocketServer = require('ws').Server 5 | var Buffer = require('safe-buffer').Buffer 6 | 7 | echo.start(function(){ 8 | console.log('echo server is running') 9 | }) 10 | 11 | function forBare (opts) { 12 | var server = http.createServer() 13 | 14 | websocket.createServer({ 15 | server: server, 16 | binary: opts.binary 17 | }, sendString) 18 | 19 | server.listen(opts.port) 20 | 21 | function sendString (stream) { 22 | stream.write('hello world') 23 | } 24 | } 25 | 26 | forBare({ 27 | port: 8344, 28 | binary: false 29 | }) 30 | 31 | forBare({ 32 | port: 8345 33 | }) 34 | 35 | function checkIfDataIsBinary () { 36 | var server = http.createServer() 37 | var wss = new WebSocketServer({ 38 | server: server 39 | }) 40 | 41 | server.listen(8346) 42 | 43 | wss.on('connection', waitFor) 44 | 45 | function waitFor (ws) { 46 | ws.on('message', function (data) { 47 | if (!Buffer.isBuffer(data)) { 48 | ws.send(Buffer.from('fail')) 49 | } else { 50 | ws.send(Buffer.from('success')) 51 | } 52 | }) 53 | } 54 | } 55 | 56 | checkIfDataIsBinary() 57 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var websocket = require('./') 3 | var echo = require("./echo-server") 4 | var WebSocketServer = require('ws').Server 5 | var http = require('http') 6 | var concat = require('concat-stream') 7 | var Buffer = require('safe-buffer').Buffer 8 | var ts = require('typescript') 9 | 10 | test('echo server', function(t) { 11 | 12 | echo.start(function() { 13 | var client = websocket(echo.url) 14 | 15 | client.on('error', console.error) 16 | 17 | client.on('data', function(data) { 18 | t.ok(Buffer.isBuffer(data), 'is a buffer') 19 | t.equal(data.toString(), 'hello world') 20 | client.end() 21 | echo.stop(function() { 22 | t.end() 23 | }) 24 | }) 25 | 26 | client.write('hello world') 27 | }) 28 | 29 | }) 30 | 31 | test('emitting not connected errors', function(t) { 32 | 33 | echo.start(function() { 34 | var client = websocket(echo.url) 35 | 36 | client.on('error', function() { 37 | echo.stop(function() { 38 | t.true(true, 'should emit error') 39 | t.end() 40 | }) 41 | }) 42 | 43 | client.once('data', function(data) { 44 | client.end() 45 | client.write('abcde') 46 | }) 47 | 48 | client.write('hello world') 49 | }) 50 | 51 | }) 52 | 53 | test('passes options to websocket constructor', function(t) { 54 | t.plan(3) 55 | 56 | opts = { 57 | verifyClient: function verifyClient(info) { 58 | t.equal(info.req.headers['x-custom-header'], 'Custom Value') 59 | return true 60 | } 61 | } 62 | echo.start(opts, function() { 63 | var options = {headers: {'x-custom-header': 'Custom Value'}} 64 | var client = websocket(echo.url, options) 65 | 66 | client.on('error', console.error) 67 | 68 | client.on('data', function(data) { 69 | t.ok(Buffer.isBuffer(data), 'is a buffer') 70 | t.equal(data.toString(), 'hello world') 71 | client.end() 72 | echo.stop(function() {}) 73 | }) 74 | 75 | client.write('hello world') 76 | }) 77 | 78 | }) 79 | 80 | 81 | test('destroy', function(t) { 82 | t.plan(1) 83 | 84 | echo.start(function() { 85 | var client = websocket(echo.url, echo.options) 86 | 87 | client.on('close', function() { 88 | echo.stop(function() { 89 | t.pass('destroyed') 90 | }) 91 | }) 92 | 93 | setTimeout(function() { 94 | client.destroy() 95 | }, 200) 96 | }) 97 | 98 | }) 99 | 100 | test('drain', function(t) { 101 | t.plan(1) 102 | 103 | echo.start(function() { 104 | var client = websocket(echo.url, echo.options) 105 | 106 | client.on('drain', function() { 107 | client.destroy() 108 | echo.stop(function() { 109 | t.pass('drained') 110 | }) 111 | }) 112 | 113 | // write until buffer is full 114 | while (client.write('foobar')) {} 115 | }) 116 | 117 | }) 118 | 119 | test('emit sending errors if the socket is closed by the other party', function(t) { 120 | 121 | var server = http.createServer() 122 | var wss = new WebSocketServer({ server: server }) 123 | 124 | server.listen(8344, function() { 125 | var client = websocket('ws://localhost:8344') 126 | 127 | wss.on('connection', function(ws) { 128 | var stream = websocket(ws) 129 | 130 | client.destroy() 131 | 132 | setTimeout(function() { 133 | stream.write('hello world') 134 | }, 50) 135 | 136 | stream.on('error', function(err) { 137 | t.ok(err, 'client errors') 138 | server.close(t.end.bind(t)) 139 | }) 140 | }) 141 | }) 142 | }) 143 | 144 | test('destroy client pipe should close server pipe', function(t) { 145 | t.plan(1) 146 | 147 | var clientDestroy = function() { 148 | var client = websocket(echo.url, echo.options) 149 | client.on('data', function(o) { 150 | client.destroy() 151 | }) 152 | client.write(Buffer.from('hello')) 153 | } 154 | 155 | var opts = {} 156 | var server = http.createServer() 157 | opts.server = server 158 | var wss = new WebSocketServer(opts) 159 | wss.on('connection', function(ws) { 160 | var stream = websocket(ws) 161 | stream.on('close', function() { 162 | server.close(function() { 163 | t.pass('close is called') 164 | }) 165 | }) 166 | stream.pipe(stream) 167 | }) 168 | server.listen(echo.port, clientDestroy) 169 | }) 170 | 171 | 172 | test('error on socket should forward it to pipe', function(t) { 173 | t.plan(1) 174 | 175 | var clientConnect = function() { 176 | websocket(echo.url, echo.options) 177 | } 178 | 179 | var opts = {} 180 | var server = http.createServer() 181 | opts.server = server 182 | var wss = new WebSocketServer(opts) 183 | wss.on('connection', function(ws) { 184 | var stream = websocket(ws) 185 | stream.on('error', function() { 186 | server.close(function() { 187 | t.pass('error is called') 188 | }) 189 | }) 190 | stream.socket.emit('error', new Error('Fake error')) 191 | }) 192 | server.listen(echo.port, clientConnect) 193 | }) 194 | 195 | test('stream end', function(t) { 196 | t.plan(1) 197 | 198 | var server = http.createServer() 199 | websocket.createServer({ server: server }, handle) 200 | 201 | function handle (stream) { 202 | stream.pipe(concat(function (body) { 203 | t.equal(body.toString(), 'pizza cats\n') 204 | server.close() 205 | })) 206 | } 207 | server.listen(0, function () { 208 | var w = websocket('ws://localhost:' + server.address().port) 209 | w.end('pizza cats\n') 210 | }) 211 | }) 212 | 213 | test('stream handlers should fire once per connection', function(t) { 214 | t.plan(2) 215 | 216 | var server = http.createServer() 217 | var wss = websocket.createServer({ server: server }, function() { 218 | server.close(function() { 219 | t.equal(m, 1) 220 | }) 221 | }) 222 | 223 | var m = 0 224 | wss.on('stream', function(stream, request) { 225 | t.ok(request instanceof http.IncomingMessage) 226 | m++ 227 | }) 228 | server.listen(0, function() { 229 | var w = websocket('ws://localhost:' + server.address().port) 230 | w.end('pizza cats\n') 231 | }) 232 | }) 233 | 234 | test('client with writev', function(t) { 235 | var server = http.createServer() 236 | 237 | var str = '' 238 | var wss = websocket.createServer({ 239 | server: server 240 | }, function (stream) { 241 | stream.once('data', function(data) { 242 | t.ok(Buffer.isBuffer(data), 'is a buffer') 243 | t.equal(data.toString(), 'hello world') 244 | 245 | stream.once('data', function(data) { 246 | t.ok(Buffer.isBuffer(data), 'is a buffer') 247 | t.equal(data.toString(), str) 248 | stream.end() 249 | server.close() 250 | t.end() 251 | }) 252 | }) 253 | }) 254 | 255 | server.listen(8352, function () { 256 | var client = websocket('ws://localhost:8352', { 257 | objectMode: false 258 | }) 259 | 260 | client.on('error', console.error) 261 | 262 | client.once('connect', function () { 263 | client.cork() 264 | do { 265 | str += 'foobar' 266 | } while (client.write('foobar')) 267 | client.uncork() 268 | }) 269 | 270 | client.write('hello world') 271 | }) 272 | }) 273 | 274 | test('server with writev', function(t) { 275 | var server = http.createServer() 276 | 277 | var str = '' 278 | var wss = websocket.createServer({ 279 | server: server, 280 | objectMode: false 281 | }, function (stream) { 282 | stream.cork() 283 | do { 284 | str += 'foobar' 285 | } while (stream.write('foobar')) 286 | stream.uncork() 287 | }) 288 | 289 | server.listen(8352, function () { 290 | var client = websocket('ws://localhost:8352') 291 | 292 | client.on('error', console.error) 293 | 294 | client.once('data', function(data) { 295 | t.ok(Buffer.isBuffer(data), 'is a buffer') 296 | t.equal(data.toString(), str) 297 | client.end() 298 | server.close() 299 | t.end() 300 | }) 301 | }) 302 | }) 303 | 304 | test('stop echo', function(t) { 305 | echo.stop(function() { 306 | t.end() 307 | }) 308 | }) 309 | 310 | test('typescript compilation', function(t) { 311 | function compile(fileNames, options) { 312 | const program = ts.createProgram(fileNames, options) 313 | const emitResult = program.emit() 314 | 315 | const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics) 316 | 317 | return allDiagnostics.map(diagnostic => { 318 | if (diagnostic.file) { 319 | let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start) 320 | let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n") 321 | return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}` 322 | } else { 323 | return `${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}` 324 | } 325 | }); 326 | } 327 | 328 | const messages = compile(['./ts-tests.ts'], { 329 | noEmit: true, 330 | noImplicitAny: true, 331 | target: ts.ScriptTarget.ES5, 332 | module: ts.ModuleKind.CommonJS 333 | }) 334 | 335 | t.equal(messages.length, 0, 'no errors emitted') 336 | t.end() 337 | }) 338 | 339 | -------------------------------------------------------------------------------- /ts-tests.ts: -------------------------------------------------------------------------------- 1 | import * as WebSocket from 'ws'; 2 | import * as WebSocketStream from './'; 3 | 4 | { 5 | let ws = new WebSocket('ws://www.host.com/path'); 6 | const stream = WebSocketStream(ws); 7 | ws = stream.socket; 8 | 9 | stream.setEncoding("utf8"); 10 | stream.write("hello world"); 11 | 12 | const message = stream.read(10); 13 | const message2 = stream.read(); 14 | } 15 | 16 | { 17 | const stream = WebSocketStream('ws://www.host.com/path'); 18 | } 19 | 20 | { 21 | // stream - url target with subprotocol 22 | const stream = WebSocketStream('ws://www.host.com/path', 'appProtocol-v1'); 23 | } 24 | 25 | { 26 | // stream - url target with subprotocols, no options 27 | const stream = WebSocketStream('ws://www.host.com/path', ['appProtocol-v1', 'appProtocol-v2']); 28 | } 29 | 30 | { 31 | // stream - url target with options, no subprotocols 32 | const stream = WebSocketStream('ws://www.host.com/path', { maxPayload: 1024 }); 33 | } 34 | 35 | { 36 | // stream - url target with subprotocol and options 37 | const stream = WebSocketStream( 38 | 'ws://www.host.com/path', 39 | ['appProtocol-v1', 'appProtocol-v2'], 40 | { maxPayload: 1024 }, 41 | ); 42 | } 43 | 44 | { 45 | // stream - url target with subprotocols and options 46 | const stream = WebSocketStream( 47 | 'ws://www.host.com/path', 48 | ['appProtocol-v1', 'appProtocol-v2'], 49 | { maxPayload: 1024 }, 50 | ); 51 | } 52 | 53 | { 54 | // dot server 55 | const wss = new WebSocketStream.Server({port: 8081}); 56 | wss.on('stream', (stream, req) => { 57 | stream.write(stream.read()); 58 | stream.end(); 59 | }); 60 | } 61 | 62 | { 63 | // dot createServer 64 | const wss = WebSocketStream.createServer({port: 8081}); 65 | wss.on('stream', (stream, req) => { 66 | stream.write(stream.read()); 67 | stream.end(); // closes underlying socket 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /ws-fallback.js: -------------------------------------------------------------------------------- 1 | 2 | var ws = null 3 | 4 | if (typeof WebSocket !== 'undefined') { 5 | ws = WebSocket 6 | } else if (typeof MozWebSocket !== 'undefined') { 7 | ws = MozWebSocket 8 | } else if (typeof window !== 'undefined') { 9 | ws = window.WebSocket || window.MozWebSocket 10 | } 11 | 12 | module.exports = ws 13 | --------------------------------------------------------------------------------