UNPKG

socio

Version:

A WebSocket Real-Time Communication (RTC) API framework.

110 lines (109 loc) 4.58 kB
import { LogHandler, E } from './logging.js'; import { RateLimiter } from './ratelimit.js'; import { socio_encode, FastHash, ClientMessageKind } from './utils.js'; export class SocioSession extends LogHandler { #ws; #subs = new Map(); #authenticated = false; #perms = new Map(); #destroyed = 0; verbose = false; last_seen = 0; session_opts = { session_timeout_ttl_ms: Infinity, max_payload_size: 1024 }; name; storage = {}; constructor(client_id, ws_client, client_ipAddr, { logging = { verbose: false, hard_crash: false }, default_perms, session_opts, name } = {}) { super({ ...logging, prefix: 'SocioSession' }); this.#ws = ws_client; this.#ws['socio_client_id'] = client_id; this.#ws['socio_client_ipAddr'] = client_ipAddr; if (default_perms) this.#perms = default_perms; this.verbose = logging.verbose || false; this.session_opts = Object.assign(this.session_opts, session_opts); this.name = name; this.last_seen_now(); } get web_socket() { return this.#ws; } get id() { return this.#ws['socio_client_id']; } get ipAddr() { return this.#ws['socio_client_ipAddr']; } Send(kind, ...data) { if (this.#destroyed) return; if (data.length < 1) throw new E('Not enough arguments to send data!', { kind, data }); return new Promise((resolve) => { setImmediate(() => { const payload = socio_encode(Object.assign({}, { kind: kind, data: data[0] }, ...data.slice(1))); if (this.session_opts?.max_payload_size && payload.length < this.session_opts.max_payload_size) { this.HandleDebug(`blocked a send: [${ClientMessageKind[kind]}] to [${this.id}] for exceeding max payload size [${this.session_opts.max_payload_size}] with size [${payload.length}]`); } else { this.#ws.send(payload); if (this.verbose) this.HandleInfo(`sent: [${ClientMessageKind[kind]}] to [${this.name ? this.name + ' | ' : ''}${this.id}]`, ...(kind != ClientMessageKind.RECV_FILES ? data : [])); this.last_seen_now(); } resolve(); }); }); } RegisterSub(tables, id, sql, params, rate_limit) { if (!this.#subs.has(id)) this.#subs.set(id, { tables, sql, params, rate_limiter: rate_limit ? new RateLimiter(rate_limit) : undefined, cache_hash: FastHash(sql + JSON.stringify(params)) }); else throw new E('MSG ID already registered as Sub!', tables, id, sql, params); } UnRegisterSub(id) { return this.#subs.delete(id) ? 1 : 0; } *GetSubsForTables(tables = []) { for (const [id, hook] of this.#subs.entries()) if (hook.tables.some(t => tables.includes(t))) yield { ...hook, id }; } get authenticated() { return this.#authenticated; } async Authenticate(auth_func, params = null) { const auth = await auth_func(this, params); this.#authenticated = auth === true; return auth; } HasPermFor(verb = '', table = '') { return this.#perms.has(verb) && this.#perms.get(verb)?.includes(table); } AddPermFor(verb = '', table = '') { if (this.#perms.has(verb)) { if (!this.#perms.get(verb).includes(table)) this.#perms.get(verb).push(table); } else this.#perms.set(verb, [table]); } last_seen_now() { this.last_seen = (new Date()).getTime(); } CloseConnection(code) { if (this.#ws?.close) this.#ws.close(code); if (this.#ws?.terminate) this.#ws.terminate(); } Destroy(remove_session_callback, ttl_ms, force = false) { if (force) { this.CloseConnection(); remove_session_callback(); } else this.#destroyed = setTimeout(remove_session_callback, ttl_ms); } Restore() { if (this.#destroyed) clearTimeout(this.#destroyed); this.#destroyed = 0; } ClearSubs() { this.#subs.clear(); } CopySessionFrom(old_client) { this.#authenticated = old_client.#authenticated; this.#perms = structuredClone(old_client.#perms); this.verbose = old_client.verbose; this.last_seen = old_client.last_seen; this.name = old_client.name; this.storage = structuredClone(old_client.storage); } }