UNPKG

tinybase

Version:

A reactive data store and sync engine.

88 lines (80 loc) 3.02 kB
const EMPTY_STRING = ''; const UTF8 = 'utf8'; const MESSAGE = 'message'; const strMatch = (str, regex) => str?.match(regex); const isUndefined = (thing) => thing == void 0; const ifNotUndefined = (value, then, otherwise) => isUndefined(value) ? otherwise?.() : then(value); const slice = (arrayOrString, start, end) => arrayOrString.slice(start, end); const collSize = (coll) => coll?.size ?? 0; const collHas = (coll, keyOrValue) => coll?.has(keyOrValue) ?? false; const collIsEmpty = (coll) => isUndefined(coll) || collSize(coll) == 0; const collClear = (coll) => coll.clear(); const collForEach = (coll, cb) => coll?.forEach(cb); const collDel = (coll, keyOrValue) => coll?.delete(keyOrValue); const object = Object; const objFreeze = object.freeze; const mapNew = (entries) => new Map(entries); const mapGet = (map, key) => map?.get(key); const mapForEach = (map, cb) => collForEach(map, (value, key) => cb(key, value)); const mapSet = (map, key, value) => isUndefined(value) ? (collDel(map, key), map) : map?.set(key, value); const mapEnsure = (map, key, getDefaultValue, hadExistingValue) => { if (!collHas(map, key)) { mapSet(map, key, getDefaultValue()); } return mapGet(map, key); }; const MESSAGE_SEPARATOR = '\n'; const ifPayloadValid = (payload, then) => { const splitAt = payload.indexOf(MESSAGE_SEPARATOR); if (splitAt !== -1) { then(slice(payload, 0, splitAt), slice(payload, splitAt + 1)); } }; const createRawPayload = (clientId, remainder) => clientId + MESSAGE_SEPARATOR + remainder; const PATH_REGEX = /\/([^?]*)/; const createWsServerSimple = (webSocketServer) => { const clientsByPath = mapNew(); webSocketServer.on('connection', (client, request) => ifNotUndefined(strMatch(request.url, PATH_REGEX), ([, pathId]) => ifNotUndefined(request.headers['sec-websocket-key'], async (clientId) => { const clients = mapEnsure(clientsByPath, pathId, mapNew); mapSet(clients, clientId, client); client.on(MESSAGE, (data) => ifPayloadValid(data.toString(UTF8), (toClientId, remainder) => { const forwardedPayload = createRawPayload(clientId, remainder); if (toClientId === EMPTY_STRING) { mapForEach(clients, (otherClientId, otherClient) => otherClientId !== clientId ? otherClient.send(forwardedPayload) : 0, ); } else { mapGet(clients, toClientId)?.send(forwardedPayload); } }), ); client.on('close', () => { collDel(clients, clientId); if (collIsEmpty(clients)) { collDel(clientsByPath, pathId); } }); }), ), ); const getWebSocketServer = () => webSocketServer; const destroy = () => { collClear(clientsByPath); webSocketServer.close(); }; const wsServerSimple = { getWebSocketServer, destroy, }; return objFreeze(wsServerSimple); }; export {createWsServerSimple};