diff --git a/package.json b/package.json index 1eeceb10..2df98f07 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "sprintf-js": "^1.0.3", "toml": "^2.3.0", "uuid": "^3.2.1", + "uws": "^10.148.0", "yamljs": "^0.2.8" }, "scripts": { diff --git a/src/channel/channel.js b/src/channel/channel.js index e0f4071d..77cf7a89 100644 --- a/src/channel/channel.js +++ b/src/channel/channel.js @@ -725,6 +725,7 @@ Channel.prototype.handleReadLog = function (user) { Channel.prototype.broadcastToRoom = function (msg, data, ns) { sio.instance.in(ns).emit(msg, data); + require('../io/uws').in(ns).emit(msg, data); }; Channel.prototype.broadcastAll = function (msg, data) { diff --git a/src/io/uws.js b/src/io/uws.js new file mode 100644 index 00000000..b4c85eee --- /dev/null +++ b/src/io/uws.js @@ -0,0 +1,106 @@ +import { EventEmitter } from 'events'; +import { Multimap } from '../util/multimap'; +import User from '../user'; + +const rooms = new Multimap(); + +class UWSWrapper extends EventEmitter { + constructor(socket, context) { + super(); + + this._uwsSocket = socket; + this._joined = new Set(); + this._connected = true; + this.context = context; + + this._uwsSocket.on('message', message => { + this._emit.apply(this, this._decode(message)); + }); + + this._uwsSocket.on('close', () => { + this._connected = false; + + for (let room of this._joined) { + rooms.delete(room, this); + } + + this._joined.clear(); + this._emit('disconnect'); + }); + } + + disconnect() { + this._uwsSocket.terminate(); + } + + get disconnected() { + return !this._connected; + } + + emit(frame, ...args) { + this._uwsSocket.send(encode(frame, args)); + } + + join(room) { + this._joined.add(room); + rooms.set(room, this); + } + + leave(room) { + this._joined.delete(room); + rooms.delete(room, this); + } + + typecheckedOn(frame, typeDef, cb) { + this.on(frame, cb); + } + + typecheckedOnce(frame, typeDef, cb) { + this.once(frame, cb); + } + + _decode(message) { + // TODO: handle error and kill clients with protocol violations + return JSON.parse(message); + } +} + +Object.assign(UWSWrapper.prototype, { _emit: EventEmitter.prototype.emit }); + +function encode(frame, args) { + return JSON.stringify([frame].concat(args)); +} + +function inRoom(room) { + return { + emit(frame, ...args) { + const encoded = encode(frame, args); + + for (let wrapper of rooms.get(room)) { + wrapper._uwsSocket.send(encoded); + } + } + }; +} + +export { UWSWrapper }; +exports['in'] = inRoom; + +export function init() { + const uws = require('uws'); + + const server = new uws.Server({ port: 3000 }); + + server.on('connection', socket => { + const wrap = new UWSWrapper( + socket, + { + aliases: [], + ipSessionFirstSeen: new Date(), + torConnection: false, + ipAddress: null + } + ); + new User(wrap, '127.0.0.1', null); + }); +} diff --git a/src/server.js b/src/server.js index 1ed26111..ba256ed8 100644 --- a/src/server.js +++ b/src/server.js @@ -190,6 +190,7 @@ var Server = function () { }); require("./io/ioserver").init(self, webConfig); + require("./io/uws").init(); // background tasks init ---------------------------------------------- require("./bgtask")(self); diff --git a/src/util/multimap.js b/src/util/multimap.js new file mode 100644 index 00000000..3fcb0336 --- /dev/null +++ b/src/util/multimap.js @@ -0,0 +1,45 @@ +class Multimap { + constructor() { + this._items = new Map(); + } + + get(key) { + if (this._items.has(key)) { + return this._items.get(key); + } + + return new Set(); + } + + has(key, value) { + if (!this._items.has(key)) { + return false; + } + + return this._items.get(key).has(value); + } + + set(key, value) { + if (!this._items.has(key)) { + this._items.set(key, new Set()); + } + + return this._items.get(key).add(value); + } + + delete(key, value) { + if (!this._items.has(key)) { + return false; + } + + const res = this._items.get(key).delete(value); + + if (this._items.get(key).size == 0) { + this._items.delete(key); + } + + return res; + } +} + +export { Multimap }; diff --git a/test/util/multimap.js b/test/util/multimap.js new file mode 100644 index 00000000..dc30eb4f --- /dev/null +++ b/test/util/multimap.js @@ -0,0 +1,34 @@ +const assert = require('assert'); +const { Multimap } = require('../../lib/util/multimap'); + +describe('Multimap', () => { + let map; + + beforeEach(() => { + map = new Multimap(); + }); + + it('returns the empty set for an unset key', () => { + assert.deepEqual(map.get('unknown'), new Set()); + }); + + it('returns a set of values for a given key', () => { + map.set('a', 1); + map.set('a', 2); + map.set('a', 1); + + assert.deepEqual(map.get('a'), new Set([1, 2])); + }); + + it('deletes a value for a given key', () => { + map.set('a', 1); + map.set('a', 2); + map.delete('a', 1); + + assert.deepEqual(map.get('a'), new Set([2])); + + map.delete('a', 2); + + assert.deepEqual(map.get('a'), new Set()); + }); +});