start werk
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
This software is licensed under the MIT License.
|
||||
|
||||
Copyright (c) 2017, Christopher Jeffrey (https://github.com/chjj)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# bsock
|
||||
|
||||
A minimal websocket-only implementation of the socket.io protocol, complete
|
||||
with ES6/ES7 features.
|
||||
|
||||
## Usage
|
||||
|
||||
``` js
|
||||
const http = require('http');
|
||||
const bsock = require('bsock');
|
||||
const io = bsock.createServer();
|
||||
const server = http.createServer();
|
||||
|
||||
io.attach(server);
|
||||
|
||||
io.on('socket', (socket) => {
|
||||
// Bind = listen for event
|
||||
socket.bind('bar', (data) => {
|
||||
console.log('Received bar: %s.', data.toString('ascii'));
|
||||
});
|
||||
// Hook = listen for call (event + ack)
|
||||
socket.hook('foo', async () => {
|
||||
return Buffer.from('bar');
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(8000);
|
||||
|
||||
const socket = bsock.connect(8000);
|
||||
|
||||
socket.on('connect', async () => {
|
||||
console.log('Calling foo...');
|
||||
// Call = emit event and wait for ack
|
||||
const data = await socket.call('foo');
|
||||
console.log('Response for foo: %s.', data.toString('ascii'));
|
||||
console.log('Sending bar...');
|
||||
// Fire = emit event
|
||||
socket.fire('bar', Buffer.from('baz'));
|
||||
});
|
||||
```
|
||||
|
||||
## Contribution and License Agreement
|
||||
|
||||
If you contribute code to this project, you are implicitly allowing your code
|
||||
to be distributed under the MIT license. You are also implicitly verifying that
|
||||
all code is your original work. `</legalese>`
|
||||
|
||||
## License
|
||||
|
||||
- Copyright (c) 2017, Christopher Jeffrey (MIT License).
|
||||
|
||||
See LICENSE for more info.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
Client: global.WebSocket || global.MozWebSocket,
|
||||
EventSource: global.EventSource
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('../vendor/faye-websocket');
|
||||
+17
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+39
@@ -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
@@ -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;
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"_from": "bsock",
|
||||
"_id": "bsock@0.1.9",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-/l9Kg/c5o+n/0AqreMxh2jpzDMl1ikl4gUxT7RFNe3A3YRIyZkiREhwcjmqxiymJSRI/Qhew357xGn1SLw/xEw==",
|
||||
"_location": "/bsock",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "tag",
|
||||
"registry": true,
|
||||
"raw": "bsock",
|
||||
"name": "bsock",
|
||||
"escapedName": "bsock",
|
||||
"rawSpec": "",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "latest"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER",
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/bsock/-/bsock-0.1.9.tgz",
|
||||
"_shasum": "6aa14b8e4bda730e0f60ec73eb52a8a888ac22c8",
|
||||
"_spec": "bsock",
|
||||
"_where": "/home/daniel/development/repos/fw-vendor/rpclib",
|
||||
"author": {
|
||||
"name": "Christopher Jeffrey",
|
||||
"email": "chjjeffrey@gmail.com"
|
||||
},
|
||||
"browser": {
|
||||
"./lib/backend": "./lib/backend-browser.js",
|
||||
"./lib/server": "./lib/server-browser.js"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/bcoin-org/bsock/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"bsert": "~0.0.10"
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Websocket bike-shed",
|
||||
"devDependencies": {
|
||||
"bmocha": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"homepage": "https://github.com/bcoin-org/bsock",
|
||||
"keywords": [
|
||||
"tcp",
|
||||
"http",
|
||||
"socket.io",
|
||||
"websockets"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "./lib/bsock.js",
|
||||
"name": "bsock",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/bcoin-org/bsock.git"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint lib/ test/socket-test.js || exit 0",
|
||||
"test": "bmocha --reporter spec test/*-test.js"
|
||||
},
|
||||
"version": "0.1.9"
|
||||
}
|
||||
+3494
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user