@alexeimyshkouski/realtime-rooms
Version:
Realtime rooms
764 lines (628 loc) • 21 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var Hub = _interopDefault(require('@alexeimyshkouski/pubsub'));
var RealtimeRouter = _interopDefault(require('@alexeimyshkouski/realtime-router'));
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _toArray(arr) {
return _arrayWithHoles(arr) || _iterableToArray(arr) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
function _decorate(decorators, factory, superClass, mixins) {
var api = _getDecoratorsApi();
if (mixins) {
for (var i = 0; i < mixins.length; i++) {
api = mixins[i](api);
}
}
var r = factory(function initialize(O) {
api.initializeInstanceElements(O, decorated.elements);
}, superClass);
var decorated = api.decorateClass(_coalesceClassElements(r.d.map(_createElementDescriptor)), decorators);
api.initializeClassElements(r.F, decorated.elements);
return api.runClassFinishers(r.F, decorated.finishers);
}
function _getDecoratorsApi() {
_getDecoratorsApi = function () {
return api;
};
var api = {
elementsDefinitionOrder: [["method"], ["field"]],
initializeInstanceElements: function (O, elements) {
["method", "field"].forEach(function (kind) {
elements.forEach(function (element) {
if (element.kind === kind && element.placement === "own") {
this.defineClassElement(O, element);
}
}, this);
}, this);
},
initializeClassElements: function (F, elements) {
var proto = F.prototype;
["method", "field"].forEach(function (kind) {
elements.forEach(function (element) {
var placement = element.placement;
if (element.kind === kind && (placement === "static" || placement === "prototype")) {
var receiver = placement === "static" ? F : proto;
this.defineClassElement(receiver, element);
}
}, this);
}, this);
},
defineClassElement: function (receiver, element) {
var descriptor = element.descriptor;
if (element.kind === "field") {
var initializer = element.initializer;
descriptor = {
enumerable: descriptor.enumerable,
writable: descriptor.writable,
configurable: descriptor.configurable,
value: initializer === void 0 ? void 0 : initializer.call(receiver)
};
}
Object.defineProperty(receiver, element.key, descriptor);
},
decorateClass: function (elements, decorators) {
var newElements = [];
var finishers = [];
var placements = {
static: [],
prototype: [],
own: []
};
elements.forEach(function (element) {
this.addElementPlacement(element, placements);
}, this);
elements.forEach(function (element) {
if (!_hasDecorators(element)) return newElements.push(element);
var elementFinishersExtras = this.decorateElement(element, placements);
newElements.push(elementFinishersExtras.element);
newElements.push.apply(newElements, elementFinishersExtras.extras);
finishers.push.apply(finishers, elementFinishersExtras.finishers);
}, this);
if (!decorators) {
return {
elements: newElements,
finishers: finishers
};
}
var result = this.decorateConstructor(newElements, decorators);
finishers.push.apply(finishers, result.finishers);
result.finishers = finishers;
return result;
},
addElementPlacement: function (element, placements, silent) {
var keys = placements[element.placement];
if (!silent && keys.indexOf(element.key) !== -1) {
throw new TypeError("Duplicated element (" + element.key + ")");
}
keys.push(element.key);
},
decorateElement: function (element, placements) {
var extras = [];
var finishers = [];
for (var decorators = element.decorators, i = decorators.length - 1; i >= 0; i--) {
var keys = placements[element.placement];
keys.splice(keys.indexOf(element.key), 1);
var elementObject = this.fromElementDescriptor(element);
var elementFinisherExtras = this.toElementFinisherExtras((0, decorators[i])(elementObject) || elementObject);
element = elementFinisherExtras.element;
this.addElementPlacement(element, placements);
if (elementFinisherExtras.finisher) {
finishers.push(elementFinisherExtras.finisher);
}
var newExtras = elementFinisherExtras.extras;
if (newExtras) {
for (var j = 0; j < newExtras.length; j++) {
this.addElementPlacement(newExtras[j], placements);
}
extras.push.apply(extras, newExtras);
}
}
return {
element: element,
finishers: finishers,
extras: extras
};
},
decorateConstructor: function (elements, decorators) {
var finishers = [];
for (var i = decorators.length - 1; i >= 0; i--) {
var obj = this.fromClassDescriptor(elements);
var elementsAndFinisher = this.toClassDescriptor((0, decorators[i])(obj) || obj);
if (elementsAndFinisher.finisher !== undefined) {
finishers.push(elementsAndFinisher.finisher);
}
if (elementsAndFinisher.elements !== undefined) {
elements = elementsAndFinisher.elements;
for (var j = 0; j < elements.length - 1; j++) {
for (var k = j + 1; k < elements.length; k++) {
if (elements[j].key === elements[k].key && elements[j].placement === elements[k].placement) {
throw new TypeError("Duplicated element (" + elements[j].key + ")");
}
}
}
}
}
return {
elements: elements,
finishers: finishers
};
},
fromElementDescriptor: function (element) {
var obj = {
kind: element.kind,
key: element.key,
placement: element.placement,
descriptor: element.descriptor
};
var desc = {
value: "Descriptor",
configurable: true
};
Object.defineProperty(obj, Symbol.toStringTag, desc);
if (element.kind === "field") obj.initializer = element.initializer;
return obj;
},
toElementDescriptors: function (elementObjects) {
if (elementObjects === undefined) return;
return _toArray(elementObjects).map(function (elementObject) {
var element = this.toElementDescriptor(elementObject);
this.disallowProperty(elementObject, "finisher", "An element descriptor");
this.disallowProperty(elementObject, "extras", "An element descriptor");
return element;
}, this);
},
toElementDescriptor: function (elementObject) {
var kind = String(elementObject.kind);
if (kind !== "method" && kind !== "field") {
throw new TypeError('An element descriptor\'s .kind property must be either "method" or' + ' "field", but a decorator created an element descriptor with' + ' .kind "' + kind + '"');
}
var key = _toPropertyKey(elementObject.key);
var placement = String(elementObject.placement);
if (placement !== "static" && placement !== "prototype" && placement !== "own") {
throw new TypeError('An element descriptor\'s .placement property must be one of "static",' + ' "prototype" or "own", but a decorator created an element descriptor' + ' with .placement "' + placement + '"');
}
var descriptor = elementObject.descriptor;
this.disallowProperty(elementObject, "elements", "An element descriptor");
var element = {
kind: kind,
key: key,
placement: placement,
descriptor: Object.assign({}, descriptor)
};
if (kind !== "field") {
this.disallowProperty(elementObject, "initializer", "A method descriptor");
} else {
this.disallowProperty(descriptor, "get", "The property descriptor of a field descriptor");
this.disallowProperty(descriptor, "set", "The property descriptor of a field descriptor");
this.disallowProperty(descriptor, "value", "The property descriptor of a field descriptor");
element.initializer = elementObject.initializer;
}
return element;
},
toElementFinisherExtras: function (elementObject) {
var element = this.toElementDescriptor(elementObject);
var finisher = _optionalCallableProperty(elementObject, "finisher");
var extras = this.toElementDescriptors(elementObject.extras);
return {
element: element,
finisher: finisher,
extras: extras
};
},
fromClassDescriptor: function (elements) {
var obj = {
kind: "class",
elements: elements.map(this.fromElementDescriptor, this)
};
var desc = {
value: "Descriptor",
configurable: true
};
Object.defineProperty(obj, Symbol.toStringTag, desc);
return obj;
},
toClassDescriptor: function (obj) {
var kind = String(obj.kind);
if (kind !== "class") {
throw new TypeError('A class descriptor\'s .kind property must be "class", but a decorator' + ' created a class descriptor with .kind "' + kind + '"');
}
this.disallowProperty(obj, "key", "A class descriptor");
this.disallowProperty(obj, "placement", "A class descriptor");
this.disallowProperty(obj, "descriptor", "A class descriptor");
this.disallowProperty(obj, "initializer", "A class descriptor");
this.disallowProperty(obj, "extras", "A class descriptor");
var finisher = _optionalCallableProperty(obj, "finisher");
var elements = this.toElementDescriptors(obj.elements);
return {
elements: elements,
finisher: finisher
};
},
runClassFinishers: function (constructor, finishers) {
for (var i = 0; i < finishers.length; i++) {
var newConstructor = (0, finishers[i])(constructor);
if (newConstructor !== undefined) {
if (typeof newConstructor !== "function") {
throw new TypeError("Finishers must return a constructor.");
}
constructor = newConstructor;
}
}
return constructor;
},
disallowProperty: function (obj, name, objectType) {
if (obj[name] !== undefined) {
throw new TypeError(objectType + " can't have a ." + name + " property.");
}
}
};
return api;
}
function _createElementDescriptor(def) {
var key = _toPropertyKey(def.key);
var descriptor;
if (def.kind === "method") {
descriptor = {
value: def.value,
writable: true,
configurable: true,
enumerable: false
};
} else if (def.kind === "get") {
descriptor = {
get: def.value,
configurable: true,
enumerable: false
};
} else if (def.kind === "set") {
descriptor = {
set: def.value,
configurable: true,
enumerable: false
};
} else if (def.kind === "field") {
descriptor = {
configurable: true,
writable: true,
enumerable: true
};
}
var element = {
kind: def.kind === "field" ? "field" : "method",
key: key,
placement: def.static ? "static" : def.kind === "field" ? "own" : "prototype",
descriptor: descriptor
};
if (def.decorators) element.decorators = def.decorators;
if (def.kind === "field") element.initializer = def.value;
return element;
}
function _coalesceGetterSetter(element, other) {
if (element.descriptor.get !== undefined) {
other.descriptor.get = element.descriptor.get;
} else {
other.descriptor.set = element.descriptor.set;
}
}
function _coalesceClassElements(elements) {
var newElements = [];
var isSameElement = function (other) {
return other.kind === "method" && other.key === element.key && other.placement === element.placement;
};
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var other;
if (element.kind === "method" && (other = newElements.find(isSameElement))) {
if (_isDataDescriptor(element.descriptor) || _isDataDescriptor(other.descriptor)) {
if (_hasDecorators(element) || _hasDecorators(other)) {
throw new ReferenceError("Duplicated methods (" + element.key + ") can't be decorated.");
}
other.descriptor = element.descriptor;
} else {
if (_hasDecorators(element)) {
if (_hasDecorators(other)) {
throw new ReferenceError("Decorators can't be placed on different accessors with for " + "the same property (" + element.key + ").");
}
other.decorators = element.decorators;
}
_coalesceGetterSetter(element, other);
}
} else {
newElements.push(element);
}
}
return newElements;
}
function _hasDecorators(element) {
return element.decorators && element.decorators.length;
}
function _isDataDescriptor(desc) {
return desc !== undefined && !(desc.value === undefined && desc.writable === undefined);
}
function _optionalCallableProperty(obj, name) {
var value = obj[name];
if (value !== undefined && typeof value !== "function") {
throw new TypeError("Expected '" + name + "' to be a function");
}
return value;
}
const event = {
token: '!/',
re: '(!|/event)/'
};
const enter = {
token: '+/',
re: '(\\+|/enter)/'
};
const leave = {
token: '-/',
re: '(\\-|/leave)/'
};
function deserialize(message) {
const typeOfMessage = typeof message;
if (typeOfMessage !== 'object') {
if (Buffer.isBuffer(message)) {
message = message.toString();
} else if (typeOfMessage !== 'string') {
throw new TypeError('Cannot parse message of type "' + typeOfMessage + '"');
}
message = JSON.parse(message);
}
let scope, payload, id;
if (Array.isArray(message)) {
scope = message[0];
payload = message[1];
if (2 in message) {
id = message[2];
}
} else {
scope = message.scope;
payload = message.payload;
if ('id' in message) {
id = message.id;
}
}
return {
scope,
payload,
id
};
}
function serialize(message) {
try {
return JSON.stringify(message);
} catch (error) {
throw new TypeError('Cannot stringify message "' + message + '"');
}
}
function createEventMessage(channel, payload) {
return [event.token + channel, payload];
}
var parseMessage = (() => {
return (ctx, next) => {
let {
message
} = ctx;
if (!ctx.rawMessage) {
ctx.rawMessage = message;
}
Object.assign(ctx, {
serialize,
deserialize
});
try {
const {
scope,
payload,
id
} = deserialize(message);
Object.assign(ctx, {
scope,
payload,
id
});
} catch (error) {
ctx.send(['!/*', {
code: error.code || undefined,
message: error.message || undefined
}]);
return;
}
return next();
};
});
const channelParamString = ':channel';
async function onEnter(ctx, next) {
await next();
const entered = ctx.enter();
ctx.respond(entered);
}
async function onLeave(ctx, next) {
await next();
const left = ctx.leave();
ctx.respond(left);
}
async function onEvent(ctx, next) {
if (ctx.rooms.has(ctx.channel)) {
await next();
ctx.room.broadcast(ctx.payload);
}
}
class RoomsRouter extends RealtimeRouter {
constructor(...args) {
super(...args);
this.message(parseMessage()).message(enter.token + channelParamString, onEnter).message(leave.token + channelParamString, onLeave).message(event.token + channelParamString, onEvent.bind(this));
}
}
function _emit(connection, serialize, channel, payload) {
const message = serialize(createEventMessage(channel, payload));
connection.send(message);
}
const _extend = Context => class extends Context {
get channel() {
return this.params && this.params.channel; // return Hub.normalizeName(this.params && this.params.channel)
}
get room() {
return this.rooms.get(this.channel);
}
get rooms() {
return this.app.rooms.get(this.socket);
}
enter(channel = this.channel) {
const ctx = this;
if (!ctx.rooms.has(channel)) {
const token = ctx.app.hub.subscribe(channel, _emit.bind(this, ctx.connection, ctx.serialize.bind(undefined), channel));
if (token) {
ctx.rooms.set(token.channel, token);
return true;
}
}
return false;
}
leave(channel = this.channel) {
const ctx = this;
if (ctx.rooms.has(channel)) {
const token = ctx.room;
token.unsubscribe();
ctx.rooms.delete(channel);
return true;
}
return false;
}
respond(payload) {
const ctx = this;
return ctx.send([ctx.originalScope, payload]);
}
emit(payload) {
const ctx = this;
return ctx.send(createEventMessage(ctx.channel, payload));
}
send(message) {
const {
id
} = this;
if (id !== undefined) {
if (Array.isArray(message)) {
message = [...message, id];
} else if ((typeof message)[0] == 'o') {
message = Object.assign({}, message, {
id
});
}
}
return super.send(message);
}
};
const withRooms = target => {
if (target.kind === 'class') {
target.finisher = value => _extend(value);
} else if (target.placement === 'static') {
let value;
if (target.kind === 'field') {
if (!target.initializer) {
throw new ReferenceError(`Property "${target.key}" should have an initializer`);
}
value = target.initializer();
value = _extend(value);
target.initializer = () => value;
} else if (target.kind === 'method') {
if (!target.descriptor.get) {
throw new ReferenceError('Property descriptor should have a getter');
}
value = target.descriptor.get();
value = _extend(value);
target.descriptor.get = () => value;
} else {
throw new TypeError('Decorator descriptor should have "field" or "method" kind');
}
} else {
throw new TypeError('Decorator descriptor should have "class" kind or "static" placement');
}
return target;
};
var upgradeMiddleware = (async (ctx, next) => {
await next();
if (!ctx.app.rooms.has(ctx.socket)) {
ctx.app.rooms.set(ctx.socket, new Map());
ctx.socket.once('close', function () {
this.removeAllListeners();
const rooms = ctx.app.rooms.get(this);
for (const token of rooms.values()) {
token.unsubscribe();
}
ctx.app.rooms.delete(this);
});
}
});
const service = Node => {
var _class, _temp;
return _temp = _class = class extends Node {
constructor() {
super();
_defineProperty(this, "_router", new RoomsRouter());
_defineProperty(this, "hub", new Hub());
_defineProperty(this, "rooms", new Map());
this.upgrade(upgradeMiddleware).incoming(this._router.middleware());
}
enter(room, fn) {
this._router.message(enter.token + room, fn);
return this;
}
leave(room, fn) {
this._router.message(leave.token + room, fn);
return this;
}
event(room, fn) {
this._router.message(event.token + room, fn);
}
}, _defineProperty(_class, "IncomingContext", _decorate([withRooms], function (_initialize, _Node$IncomingContext) {
class RoomIncomingContext extends _Node$IncomingContext {
constructor(...args) {
super(...args);
_initialize(this);
}
}
return {
F: RoomIncomingContext,
d: []
};
}, Node.IncomingContext)), _temp;
};
exports.Router = RoomsRouter;
exports.service = service;