start werk

This commit is contained in:
Daniel Hübleitner
2019-09-17 23:18:00 +02:00
commit 615619e6e7
3598 changed files with 1238825 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
'use strict';
module.exports = {
Client: global.WebSocket || global.MozWebSocket,
EventSource: global.EventSource
};
+3
View File
@@ -0,0 +1,3 @@
'use strict';
module.exports = require('../vendor/faye-websocket');
+17
View File
@@ -0,0 +1,17 @@
'use strict';
module.exports = {
connect: true,
connect_error: true,
connect_timeout: true,
connecting: true,
disconnect: true,
error: true,
reconnect: true,
reconnect_attempt: true,
reconnect_failed: true,
reconnect_error: true,
reconnecting: true,
ping: true,
pong: true
};
+14
View File
@@ -0,0 +1,14 @@
'use strict';
const WebSocket = require('./backend');
const Server = require('./server');
const Socket = require('./socket');
exports.WebSocket = WebSocket;
exports.Server = Server;
exports.server = () => new Server();
exports.createServer = Server.createServer.bind(Server);
exports.attach = Server.attach.bind(Server);
exports.Socket = Socket;
exports.socket = () => new Socket();
exports.connect = Socket.connect.bind(Socket);
+21
View File
@@ -0,0 +1,21 @@
'use strict';
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
module.exports = {
1000: 'NORMAL_CLOSURE',
1001: 'GOING_AWAY',
1002: 'PROTOCOL_ERROR',
1003: 'UNSUPPORTED_DATA',
1004: 'RESERVED',
1005: 'NO_STATUS_RECVD',
1006: 'ABNORMAL_CLOSURE',
1007: 'INVALID_FRAME_PAYLOAD_DATA',
1008: 'POLICY_VIOLATION',
1009: 'MESSAGE_TOO_BIG',
1010: 'MISSING_EXTENSION',
1011: 'INTERNAL_ERROR',
1012: 'SERVICE_RESTART',
1013: 'TRY_AGAIN_LATER',
1014: 'BAD_GATEWAY',
1015: 'TLS_HANDSHAKE'
};
+127
View File
@@ -0,0 +1,127 @@
'use strict';
const assert = require('bsert');
const DUMMY = Buffer.alloc(0);
const types = {
OPEN: 0,
CLOSE: 1,
PING: 2,
PONG: 3,
MESSAGE: 4,
UPGRADE: 5,
NOOP: 6
};
const table = [
'open',
'close',
'ping',
'pong',
'message',
'upgrade',
'noop'
];
class Frame {
constructor(type, data, binary) {
assert(typeof type === 'number');
assert((type >>> 0) === type);
assert(type <= types.NOOP);
assert(typeof binary === 'boolean');
if (binary) {
if (data == null)
data = DUMMY;
assert(Buffer.isBuffer(data));
} else {
if (data == null)
data = '';
assert(typeof data === 'string');
}
this.type = type;
this.data = data;
this.binary = binary;
}
toString() {
let str = '';
if (this.binary) {
str += 'b';
str += this.type.toString(10);
str += this.data.toString('base64');
} else {
str += this.type.toString(10);
str += this.data;
}
return str;
}
static fromString(str) {
assert(typeof str === 'string');
let type = str.charCodeAt(0);
let binary = false;
let data;
// 'b' - base64
if (type === 0x62) {
assert(str.length > 1);
type = str.charCodeAt(1);
data = Buffer.from(str.substring(2), 'base64');
binary = true;
} else {
data = str.substring(1);
}
type -= 0x30;
assert(type >= 0 && type <= 9);
assert(type <= types.NOOP);
return new this(type, data, binary);
}
size() {
let len = 1;
if (this.binary)
len += this.data.length;
else
len += Buffer.byteLength(this.data, 'utf8');
return len;
}
toRaw() {
const data = Buffer.allocUnsafe(this.size());
data[0] = this.type;
if (this.binary) {
this.data.copy(data, 1);
} else {
if (this.data.length > 0)
data.write(this.data, 1, 'utf8');
}
return data;
}
static fromRaw(data) {
assert(Buffer.isBuffer(data));
assert(data.length > 0);
const type = data[0];
assert(type <= types.NOOP);
return new this(type, data.slice(1), true);
}
}
Frame.types = types;
Frame.table = table;
module.exports = Frame;
+253
View File
@@ -0,0 +1,253 @@
'use strict';
const assert = require('bsert');
const types = {
CONNECT: 0,
DISCONNECT: 1,
EVENT: 2,
ACK: 3,
ERROR: 4,
BINARY_EVENT: 5,
BINARY_ACK: 6
};
class Packet {
constructor(type) {
this.type = type || 0;
this.attachments = 0;
this.nsp = '/';
this.id = -1;
this.data = '';
this.buffers = [];
}
setData(data) {
assert(data !== undefined);
assert(typeof data !== 'number');
assert(typeof data !== 'function');
const [str, buffers] = deconstruct(data);
this.data = str;
this.buffers = buffers;
this.attachments = buffers.length;
if (this.attachments > 0) {
switch (this.type) {
case types.EVENT:
this.type = types.BINARY_EVENT;
break;
case types.ACK:
this.type = types.BINARY_ACK;
break;
}
}
return this;
}
getData() {
if (this.data.length === 0)
return null;
return reconstruct(this.data, this.buffers);
}
toString() {
let str = this.type.toString(10);
switch (this.type) {
case types.BINARY_EVENT:
case types.BINARY_ACK:
str += this.attachments.toString(10) + '-';
break;
}
if (this.nsp !== '/')
str += this.nsp + ',';
if (this.id !== -1)
str += this.id.toString(10);
str += this.data;
return str;
}
static fromString(str) {
assert(typeof str === 'string');
assert(str.length > 0);
let i = 0;
let type = 0;
let attachments = 0;
let nsp = '/';
let id = -1;
let data = '';
[i, type] = readChar(str, i);
assert(type !== -1);
assert(type <= types.BINARY_ACK);
switch (type) {
case types.BINARY_EVENT:
case types.BINARY_ACK: {
[i, attachments] = readInt(str, i);
assert(attachments !== -1);
assert(i < str.length);
assert(str[i] === '-');
i += 1;
break;
}
}
if (i < str.length && str[i] === '/')
[i, nsp] = readTo(str, i, ',');
[i, id] = readInt(str, i);
if (i < str.length)
data = str.substring(i);
const packet = new this();
packet.type = type;
packet.attachments = attachments;
packet.nsp = nsp;
packet.id = id;
packet.data = data;
return packet;
}
}
Packet.types = types;
function isPlaceholder(obj) {
return obj !== null
&& typeof obj === 'object'
&& obj._placeholder === true
&& (obj.num >>> 0) === obj.num;
}
function deconstruct(obj) {
const buffers = [];
const out = replace('', obj, buffers, new Map());
const str = JSON.stringify(out);
return [str, buffers];
}
function replace(key, value, buffers, seen) {
if (value === null || typeof value !== 'object')
return value;
if (Buffer.isBuffer(value)) {
const placeholder = seen.get(value);
// De-duplicate.
if (placeholder != null)
return placeholder;
const out = { _placeholder: true, num: buffers.length };
seen.set(value, out);
buffers.push(value);
return out;
}
if (seen.has(value))
throw new TypeError('Converting circular structure to JSON.');
if (Array.isArray(value)) {
const out = [];
seen.set(value, null);
for (let i = 0; i < value.length; i++)
out.push(replace(i, value[i], buffers, seen));
seen.delete(value);
return out;
}
const out = Object.create(null);
const json = typeof value.toJSON === 'function'
? value.toJSON(key)
: value;
seen.set(value, null);
for (const key of Object.keys(json))
out[key] = replace(key, json[key], buffers, seen);
seen.delete(value);
return out;
}
function reconstruct(str, buffers) {
return JSON.parse(str, (key, value) => {
if (isPlaceholder(value)) {
if (value.num < buffers.length)
return buffers[value.num];
}
return value;
});
}
function readChar(str, i) {
const ch = str.charCodeAt(i) - 0x30;
if (ch < 0 || ch > 9)
return -1;
return [i + 1, ch];
}
function readInt(str, i) {
let len = 0;
let num = 0;
for (; i < str.length; i++) {
const ch = str.charCodeAt(i) - 0x30;
if (ch < 0 || ch > 9)
break;
num *= 10;
num += ch;
len += 1;
assert(len <= 10);
}
assert(num <= 0xffffffff);
if (len === 0)
num = -1;
return [i, num];
}
function readTo(str, i, ch) {
let j = i;
for (; j < str.length; j++) {
if (str[j] === ch)
break;
}
assert(j < str.length);
return [j + 1, str.substring(i, j)];
}
/*
* Expose
*/
module.exports = Packet;
+67
View File
@@ -0,0 +1,67 @@
/*!
* parser.js - packet parser
* Copyright (c) 2017, Christopher Jeffrey (MIT License).
* https://github.com/chjj
*/
'use strict';
const assert = require('bsert');
const EventEmitter = require('events');
const Frame = require('./frame');
const MAX_MESSAGE = 100000000;
class Parser extends EventEmitter {
constructor() {
super();
}
error(msg) {
this.emit('error', new Error(msg));
}
feedBinary(data) {
assert(Buffer.isBuffer(data));
if (data.length > MAX_MESSAGE) {
this.error('Frame too large.');
return;
}
let frame;
try {
frame = Frame.fromRaw(data);
} catch (e) {
this.emit('error', e);
return;
}
this.emit('frame', frame);
}
feedString(data) {
assert(typeof data === 'string');
if (Buffer.byteLength(data, 'utf8') > MAX_MESSAGE) {
this.error('Frame too large.');
return;
}
let frame;
try {
frame = Frame.fromString(data);
} catch (e) {
this.emit('error', e);
return;
}
this.emit('frame', frame);
}
}
/*
* Expose
*/
module.exports = Parser;
+50
View File
@@ -0,0 +1,50 @@
'use strict';
const EventEmitter = require('events');
class Server extends EventEmitter {
constructor(options) {
super();
this.sockets = new Set();
this.channels = new Map();
this.mounts = [];
}
attach() {
return this;
}
mount() {}
async open() {}
async close() {}
join() {
return true;
}
leave() {
return true;
}
channel() {
return null;
}
to() {}
all() {}
static attach(parent, options) {
const server = new this(options);
return server.attach(parent);
}
static createServer(options) {
return new this(options);
}
}
module.exports = Server;
+180
View File
@@ -0,0 +1,180 @@
'use strict';
const assert = require('bsert');
const EventEmitter = require('events');
const Packet = require('./packet');
const WebSocket = require('./backend');
const Socket = require('./socket');
class Server extends EventEmitter {
constructor(options = {}) {
super();
assert(!options.protocols || Array.isArray(options.protocols));
this.protocols = options.protocols || undefined;
this.sockets = new Set();
this.channels = new Map();
this.mounts = [];
this.mounted = false;
}
handleSocket(socket) {
this.add(socket);
socket.on('close', () => {
this.remove(socket);
});
this.emit('socket', socket);
for (const server of this.mounts)
server.emit('socket', socket);
}
mount(server) {
assert(!server.mounted);
server.mounted = true;
server.sockets = this.sockets;
server.channels = this.channels;
this.mounts.push(server);
}
async open() {
;
}
async close() {
if (this.mounted)
return;
for (const socket of this.sockets)
socket.destroy();
}
attach(server) {
const onUpgrade = (req, socket, body) => {
if (!socket.remoteAddress) {
socket.destroy();
return;
}
if (!WebSocket.isWebSocket(req)) {
socket.destroy();
return;
}
const ws = new WebSocket(req, socket, body, this.protocols);
const sock = Socket.accept(this, req, socket, ws);
this.handleSocket(sock);
};
server.on('upgrade', (req, socket, body) => {
try {
onUpgrade(req, socket, body);
} catch (e) {
this.emit('error', e);
}
});
return this;
}
add(socket) {
this.sockets.add(socket);
}
remove(socket) {
for (const name of socket.channels)
this.leave(socket, name);
assert(this.sockets.delete(socket));
}
join(socket, name) {
if (socket.channels.has(name))
return false;
if (!this.channels.has(name))
this.channels.set(name, new Set());
const sockets = this.channels.get(name);
sockets.add(socket);
socket.channels.add(name);
return true;
}
leave(socket, name) {
if (!socket.channels.has(name))
return false;
const sockets = this.channels.get(name);
assert(sockets);
assert(sockets.delete(socket));
if (sockets.size === 0)
this.channels.delete(name);
socket.channels.delete(name);
return true;
}
channel(name) {
const sockets = this.channels.get(name);
if (!sockets)
return null;
assert(sockets.size > 0);
return sockets;
}
event(args) {
assert(args.length > 0, 'Event must be present.');
assert(typeof args[0] === 'string', 'Event must be a string.');
const packet = new Packet();
packet.type = Packet.types.EVENT;
packet.setData(args);
return packet;
}
to(name, ...args) {
const sockets = this.channels.get(name);
if (!sockets)
return;
assert(sockets.size > 0);
// Pre-serialize for speed.
const packet = this.event(args);
for (const socket of sockets)
socket.sendPacket(packet);
}
all(...args) {
// Pre-serialize for speed.
const packet = this.event(args);
for (const socket of this.sockets)
socket.sendPacket(packet);
}
static attach(parent, options) {
const server = new this(options);
return server.attach(parent);
}
static createServer(options) {
return new this(options);
}
}
module.exports = Server;
+1007
View File
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
'use strict';
const assert = require('bsert');
const URL = require('url');
exports.parseURL = function parseURL(url) {
if (url.indexOf('://') === -1)
url = `ws://${url}`;
const data = URL.parse(url);
if (data.protocol !== 'http:'
&& data.protocol !== 'https:'
&& data.protocol !== 'ws:'
&& data.protocol !== 'wss:') {
throw new Error('Invalid protocol for websocket URL.');
}
if (!data.hostname)
throw new Error('Malformed URL.');
const host = data.hostname;
let port = 80;
let ssl = false;
if (data.protocol === 'https:' || data.protocol === 'wss:') {
port = 443;
ssl = true;
}
if (data.port) {
port = parseInt(data.port, 10);
assert((port & 0xffff) === port);
assert(port !== 0);
}
return [port, host, ssl];
};
+237
View File
@@ -0,0 +1,237 @@
'use strict';
const assert = require('bsert');
const EventEmitter = require('events');
const UWS = require('uws');
const UWSClient = UWS;
const UWSServer = UWS.Server;
const noop = () => {};
let server = null;
// Make UWS look like Faye.
class API extends EventEmitter {
constructor() {
super();
this.ws = null;
this.readable = true;
this.writable = true;
this.url = '';
this.binaryType = 'arraybuffer';
this.version = 'hybi-13';
this.protocol = '';
this.extensions = '';
this.bufferedAmount = 0;
this.onopen = noop;
this.onclose = noop;
this.onerror = noop;
this.onmessage = noop;
this.on('error', noop);
}
_open(ws, outbound) {
assert(ws);
this.ws = ws;
if (outbound) {
ws.onopen = () => {
this.onopen();
this.emit('open');
};
}
ws.onclose = ({code, reason}) => {
const event = {
code: code >>> 0,
reason: String(reason)
};
this.onclose(event);
this.emit('close', event);
};
ws.onerror = ({message}) => {
if (message === 'uWs client connection error')
message = `Network error: ${this.url}: connect ECONNREFUSED`;
const event = {
message: String(message)
};
this.onerror(event);
this.emit('error', event);
};
ws.onmessage = ({data}) => {
// UWS is zero copy.
if (typeof data !== 'string') {
assert(data instanceof ArrayBuffer);
const ab = Buffer.from(data);
const raw = Buffer.allocUnsafe(ab.length);
ab.copy(raw, 0);
data = raw;
}
const event = { data };
this.onmessage(event);
this.emit('message', event);
};
}
write(data) {
return this.send(data);
}
end(data) {
if (data !== undefined)
this.write(data);
this.close();
}
pause() {
;
}
resume() {
;
}
send(data) {
if (!this.ws)
return true;
this.ws.send(data);
return true;
}
get readyState() {
if (!this.ws)
return API.CONNECTING;
return this.ws.readyState;
}
ping(msg, callback) {
if (!this.ws)
return false;
if (this.readyState > API.OPEN)
return false;
this.ws.ping(msg);
if (callback)
callback();
return true;
}
close() {
if (!this.ws)
return;
this.ws.close();
}
static isWebSocket(req, socket) {
if (socket) {
if (socket._isNative && (!server || server.serverGroup))
return true;
}
if (req.method !== 'GET')
return false;
const connection = req.headers.connection;
if (!connection)
return false;
const conn = connection.toLowerCase().split(/ *, */);
if (conn.indexOf('upgrade') === -1)
return false;
const upgrade = req.headers.upgrade;
if (!upgrade)
return false;
if (upgrade.toLowerCase() !== 'websocket')
return false;
const key = req.headers['sec-websocket-key'];
if (!key)
return false;
if (key.length !== 24)
return false;
if (socket && (!socket.ssl || socket._parent)) {
const {ssl, _handle, _parent} = socket;
const handle = ssl ? _parent._handle : _handle;
if (!handle)
return false;
}
return true;
}
}
API.CONNECTING = 0;
API.OPEN = 1;
API.CLOSING = 2;
API.CLOSED = 3;
API.CLOSE_TIMEOUT = 3000;
class Client extends API {
constructor(url) {
super();
assert(typeof url === 'string');
url = url.replace(/^http:/, 'ws:');
url = url.replace(/^https:/, 'wss:');
if (url.indexOf('://') === -1)
url = `ws://${url}`;
url = url.replace('://localhost', '://127.0.0.1');
this.url = url;
this._open(new UWSClient(url), true);
}
}
class WebSocket extends API {
constructor(req, socket, body) {
super();
assert(req);
this.url = req.url;
if (!server)
server = new UWSServer({ noServer: true });
server.handleUpgrade(req, socket, body, (ws) => {
setImmediate(() => {
this._open(ws, false);
this.onopen();
this.emit('open');
});
});
}
}
WebSocket.Client = Client;
module.exports = WebSocket;