123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- 'use strict';
- const http = require('http');
- const EventEmitter = require('events');
- const EE_ERROR = 'Registering more than one listener to a WebSocket is not supported.';
- const DEFAULT_PAYLOAD_LIMIT = 16777216;
- function noop() {}
- function abortConnection(socket, code, name) {
- socket.end('HTTP/1.1 ' + code + ' ' + name + '\r\n\r\n');
- }
- function emitConnection(ws) {
- this.emit('connection', ws);
- }
- function onServerMessage(message, webSocket) {
- webSocket.internalOnMessage(message);
- }
- const native = (() => {
- try {
- try {
- return process.binding('uws_builtin');
- } catch (e) {
- return require(`./uws_${process.platform}_${process.versions.modules}`);
- }
- } catch (e) {
- const version = process.version.substring(1).split('.').map(function(n) {
- return parseInt(n);
- });
- const lessThanSixFour = version[0] < 6 || (version[0] === 6 && version[1] < 4);
- if (process.platform === 'win32' && lessThanSixFour) {
- throw new Error('µWebSockets requires Node.js 6.4.0 or greater on Windows.');
- } else {
- throw new Error('Compilation of µWebSockets has failed and there is no pre-compiled binary ' +
- 'available for your system. Please install a supported C++11 compiler and reinstall the module \'uws\'.');
- }
- }
- })();
- native.setNoop(noop);
- var _upgradeReq = null;
- const clientGroup = native.client.group.create(0, DEFAULT_PAYLOAD_LIMIT);
- native.client.group.onConnection(clientGroup, (external) => {
- const webSocket = native.getUserData(external);
- webSocket.external = external;
- webSocket.internalOnOpen();
- });
- native.client.group.onMessage(clientGroup, (message, webSocket) => {
- webSocket.internalOnMessage(message);
- });
- native.client.group.onDisconnection(clientGroup, (external, code, message, webSocket) => {
- webSocket.external = null;
- process.nextTick(() => {
- webSocket.internalOnClose(code, message);
- });
- native.clearUserData(external);
- });
- native.client.group.onPing(clientGroup, (message, webSocket) => {
- webSocket.onping(message);
- });
- native.client.group.onPong(clientGroup, (message, webSocket) => {
- webSocket.onpong(message);
- });
- native.client.group.onError(clientGroup, (webSocket) => {
- process.nextTick(() => {
- webSocket.internalOnError({
- message: 'uWs client connection error',
- stack: 'uWs client connection error'
- });
- });
- });
- class WebSocket {
- constructor(external) {
- this.external = external;
- this.internalOnMessage = noop;
- this.internalOnClose = noop;
- this.onping = noop;
- this.onpong = noop;
- }
- get upgradeReq() {
- return _upgradeReq;
- }
- set onmessage(f) {
- if (f) {
- this.internalOnMessage = (message) => {
- f({data: message});
- };
- } else {
- this.internalOnMessage = noop;
- }
- }
- set onopen(f) {
- if (f) {
- this.internalOnOpen = f;
- } else {
- this.internalOnOpen = noop;
- }
- }
- set onclose(f) {
- if (f) {
- this.internalOnClose = (code, message) => {
- f({code: code, reason: message});
- };
- } else {
- this.internalOnClose = noop;
- }
- }
- set onerror(f) {
- if (f && this instanceof WebSocketClient) {
- this.internalOnError = f;
- } else {
- this.internalOnError = noop;
- }
- }
- emit(eventName, arg1, arg2) {
- if (eventName === 'message') {
- this.internalOnMessage(arg1);
- } else if (eventName === 'close') {
- this.internalOnClose(arg1, arg2);
- } else if (eventName === 'ping') {
- this.onping(arg1);
- } else if (eventName === 'pong') {
- this.onpong(arg1);
- }
- return this;
- }
- on(eventName, f) {
- if (eventName === 'message') {
- if (this.internalOnMessage !== noop) {
- throw Error(EE_ERROR);
- }
- this.internalOnMessage = f;
- } else if (eventName === 'close') {
- if (this.internalOnClose !== noop) {
- throw Error(EE_ERROR);
- }
- this.internalOnClose = f;
- } else if (eventName === 'ping') {
- if (this.onping !== noop) {
- throw Error(EE_ERROR);
- }
- this.onping = f;
- } else if (eventName === 'pong') {
- if (this.onpong !== noop) {
- throw Error(EE_ERROR);
- }
- this.onpong = f;
- } else if (eventName === 'open') {
- if (this.internalOnOpen !== noop) {
- throw Error(EE_ERROR);
- }
- this.internalOnOpen = f;
- } else if (eventName === 'error' && this instanceof WebSocketClient) {
- if (this.internalOnError !== noop) {
- throw Error(EE_ERROR);
- }
- this.internalOnError = f;
- }
- return this;
- }
- once(eventName, f) {
- if (eventName === 'message') {
- if (this.internalOnMessage !== noop) {
- throw Error(EE_ERROR);
- }
- this.internalOnMessage = (message) => {
- this.internalOnMessage = noop;
- f(message);
- };
- } else if (eventName === 'close') {
- if (this.internalOnClose !== noop) {
- throw Error(EE_ERROR);
- }
- this.internalOnClose = (code, message) => {
- this.internalOnClose = noop;
- f(code, message);
- };
- } else if (eventName === 'ping') {
- if (this.onping !== noop) {
- throw Error(EE_ERROR);
- }
- this.onping = () => {
- this.onping = noop;
- f();
- };
- } else if (eventName === 'pong') {
- if (this.onpong !== noop) {
- throw Error(EE_ERROR);
- }
- this.onpong = () => {
- this.onpong = noop;
- f();
- };
- }
- return this;
- }
- removeAllListeners(eventName) {
- if (!eventName || eventName === 'message') {
- this.internalOnMessage = noop;
- }
- if (!eventName || eventName === 'close') {
- this.internalOnClose = noop;
- }
- if (!eventName || eventName === 'ping') {
- this.onping = noop;
- }
- if (!eventName || eventName === 'pong') {
- this.onpong = noop;
- }
- return this;
- }
- removeListener(eventName, cb) {
- if (eventName === 'message' && this.internalOnMessage === cb) {
- this.internalOnMessage = noop;
- } else if (eventName === 'close' && this.internalOnClose === cb) {
- this.internalOnClose = noop;
- } else if (eventName === 'ping' && this.onping === cb) {
- this.onping = noop;
- } else if (eventName === 'pong' && this.onpong === cb) {
- this.onpong = noop;
- }
- return this;
- }
- get OPEN() {
- return WebSocketClient.OPEN;
- }
- get CLOSED() {
- return WebSocketClient.CLOSED;
- }
- get readyState() {
- return this.external ? WebSocketClient.OPEN : WebSocketClient.CLOSED;
- }
- get _socket() {
- const address = this.external ? native.getAddress(this.external) : new Array(3);
- return {
- remotePort: address[0],
- remoteAddress: address[1],
- remoteFamily: address[2]
- };
- }
- // from here down, functions are not common between client and server
- ping(message, options, dontFailWhenClosed) {
- if (this.external) {
- native.server.send(this.external, message, WebSocketClient.OPCODE_PING);
- }
- }
- terminate() {
- if (this.external) {
- native.server.terminate(this.external);
- this.external = null;
- }
- }
- send(message, options, cb) {
- if (this.external) {
- if (typeof options === 'function') {
- cb = options;
- options = null;
- }
- const binary = options && options.binary || typeof message !== 'string';
- native.server.send(this.external, message, binary ? WebSocketClient.OPCODE_BINARY : WebSocketClient.OPCODE_TEXT, cb ? (() => {
- process.nextTick(cb);
- }) : undefined);
- } else if (cb) {
- cb(new Error('not opened'));
- }
- }
- close(code, data) {
- if (this.external) {
- native.server.close(this.external, code, data);
- this.external = null;
- }
- }
- }
- class WebSocketClient extends WebSocket {
- constructor(uri) {
- super(null);
- this.internalOnOpen = noop;
- this.internalOnError = noop;
- native.connect(clientGroup, uri, this);
- }
- ping(message, options, dontFailWhenClosed) {
- if (this.external) {
- native.client.send(this.external, message, WebSocketClient.OPCODE_PING);
- }
- }
- terminate() {
- if (this.external) {
- native.client.terminate(this.external);
- this.external = null;
- }
- }
- send(message, options, cb) {
- if (this.external) {
- if (typeof options === 'function') {
- cb = options;
- options = null;
- }
- const binary = options && options.binary || typeof message !== 'string';
- native.client.send(this.external, message, binary ? WebSocketClient.OPCODE_BINARY : WebSocketClient.OPCODE_TEXT, cb ? (() => {
- process.nextTick(cb);
- }) : undefined);
- } else if (cb) {
- cb(new Error('not opened'));
- }
- }
- close(code, data) {
- if (this.external) {
- native.client.close(this.external, code, data);
- this.external = null;
- }
- }
- }
- class Server extends EventEmitter {
- constructor(options, callback) {
- super();
- if (!options) {
- throw new TypeError('missing options');
- }
- if (options.port === undefined && !options.server && !options.noServer) {
- throw new TypeError('invalid options');
- }
- var nativeOptions = WebSocketClient.PERMESSAGE_DEFLATE;
- if (options.perMessageDeflate !== undefined) {
- if (options.perMessageDeflate === false) {
- nativeOptions = 0;
- }
- }
- this.serverGroup = native.server.group.create(nativeOptions, options.maxPayload === undefined ? DEFAULT_PAYLOAD_LIMIT : options.maxPayload);
- // can these be made private?
- this._upgradeCallback = noop;
- this._upgradeListener = null;
- this._noDelay = options.noDelay === undefined ? true : options.noDelay;
- this._lastUpgradeListener = true;
- this._passedHttpServer = options.server;
- if (!options.noServer) {
- this.httpServer = options.server ? options.server : http.createServer((request, response) => {
- // todo: default HTTP response
- response.end();
- });
- if (options.path && (!options.path.length || options.path[0] !== '/')) {
- options.path = '/' + options.path;
- }
- this.httpServer.on('upgrade', this._upgradeListener = ((request, socket, head) => {
- if (!options.path || options.path == request.url.split('?')[0].split('#')[0]) {
- if (options.verifyClient) {
- const info = {
- origin: request.headers.origin,
- secure: request.connection.authorized !== undefined || request.connection.encrypted !== undefined,
- req: request
- };
- if (options.verifyClient.length === 2) {
- options.verifyClient(info, (result, code, name) => {
- if (result) {
- this.handleUpgrade(request, socket, head, emitConnection);
- } else {
- abortConnection(socket, code, name);
- }
- });
- } else {
- if (options.verifyClient(info)) {
- this.handleUpgrade(request, socket, head, emitConnection);
- } else {
- abortConnection(socket, 400, 'Client verification failed');
- }
- }
- } else {
- this.handleUpgrade(request, socket, head, emitConnection);
- }
- } else {
- if (this._lastUpgradeListener) {
- abortConnection(socket, 400, 'URL not supported');
- }
- }
- }));
- this.httpServer.on('newListener', (eventName, listener) => {
- if (eventName === 'upgrade') {
- this._lastUpgradeListener = false;
- }
- });
- this.httpServer.on('error', (err) => {
- this.emit('error', err);
- });
- }
- native.server.group.onDisconnection(this.serverGroup, (external, code, message, webSocket) => {
- webSocket.external = null;
- process.nextTick(() => {
- webSocket.internalOnClose(code, message);
- });
- native.clearUserData(external);
- });
- native.server.group.onMessage(this.serverGroup, onServerMessage);
- native.server.group.onPing(this.serverGroup, (message, webSocket) => {
- webSocket.onping(message);
- });
- native.server.group.onPong(this.serverGroup, (message, webSocket) => {
- webSocket.onpong(message);
- });
- native.server.group.onConnection(this.serverGroup, (external) => {
- const webSocket = new WebSocket(external);
- native.setUserData(external, webSocket);
- this._upgradeCallback(webSocket);
- _upgradeReq = null;
- });
- if (options.port !== undefined) {
- if (options.host) {
- this.httpServer.listen(options.port, options.host, () => {
- this.emit('listening');
- callback && callback();
- });
- } else {
- this.httpServer.listen(options.port, () => {
- this.emit('listening');
- callback && callback();
- });
- }
- }
- }
- handleUpgrade(request, socket, upgradeHead, callback) {
- if (socket._isNative) {
- if (this.serverGroup) {
- _upgradeReq = request;
- this._upgradeCallback = callback ? callback : noop;
- native.upgrade(this.serverGroup, socket.external, secKey, request.headers['sec-websocket-extensions'], request.headers['sec-websocket-protocol']);
- }
- } else {
- const secKey = request.headers['sec-websocket-key'];
- const socketHandle = socket.ssl ? socket._parent._handle : socket._handle;
- const sslState = socket.ssl ? socket.ssl._external : null;
- if (socketHandle && secKey && secKey.length == 24) {
- socket.setNoDelay(this._noDelay);
- const ticket = native.transfer(socketHandle.fd === -1 ? socketHandle : socketHandle.fd, sslState);
- socket.on('close', (error) => {
- if (this.serverGroup) {
- _upgradeReq = request;
- this._upgradeCallback = callback ? callback : noop;
- native.upgrade(this.serverGroup, ticket, secKey, request.headers['sec-websocket-extensions'], request.headers['sec-websocket-protocol']);
- }
- });
- }
- socket.destroy();
- }
- }
- broadcast(message, options) {
- if (this.serverGroup) {
- native.server.group.broadcast(this.serverGroup, message, options && options.binary || false);
- }
- }
- startAutoPing(interval, userMessage) {
- if (this.serverGroup) {
- native.server.group.startAutoPing(this.serverGroup, interval, userMessage);
- }
- }
- close(cb) {
- if (this._upgradeListener && this.httpServer) {
- this.httpServer.removeListener('upgrade', this._upgradeListener);
- if (!this._passedHttpServer) {
- this.httpServer.close();
- }
- }
- if (this.serverGroup) {
- native.server.group.close(this.serverGroup);
- this.serverGroup = null;
- }
- if (typeof cb === 'function') {
- // compatibility hack, 15 seconds timeout
- setTimeout(cb, 20000);
- }
- }
- get clients() {
- if (this.serverGroup) {
- return {
- length: native.server.group.getSize(this.serverGroup),
- forEach: ((cb) => {native.server.group.forEach(this.serverGroup, cb)})
- };
- }
- }
- }
- WebSocketClient.PERMESSAGE_DEFLATE = 1;
- WebSocketClient.SERVER_NO_CONTEXT_TAKEOVER = 2;
- WebSocketClient.CLIENT_NO_CONTEXT_TAKEOVER = 4;
- WebSocketClient.OPCODE_TEXT = 1;
- WebSocketClient.OPCODE_BINARY = 2;
- WebSocketClient.OPCODE_PING = 9;
- WebSocketClient.OPEN = 1;
- WebSocketClient.CLOSED = 0;
- WebSocketClient.Server = Server;
- WebSocketClient.http = native.httpServer;
- WebSocketClient.native = native;
- module.exports = WebSocketClient;
|