UNPKG

websocketer-cluster

Version:
231 lines (226 loc) 6.49 kB
import EventEmitter from 'eventemitter3'; import { createClient } from 'redis'; import { generateId, WebSocketerError, endRequestData, Client as Client$1 } from 'websocketer'; const rxSpace = / /ig; class RedisClusterClient extends EventEmitter { constructor(options) { super(); this._channel = "websocketer"; options = options || {}; options.id = options.id || generateId(24); options.timeout = options.timeout || 60; options.host = options.host || "127.0.0.1:6379"; this._id = options.id; this._options = options; const parts = options.host.split(":"); this._publisher = options.client || createClient({ host: parts[0], port: parts[1] && parseInt(parts[1], 10), user: options.username || "default", password: options.password || "", retry_strategy: (r) => Math.min(r.attempt * 100, 3e3) }); this._subscriber = createClient(this._publisher.options); this._subscriber.subscribe(this._channel); this._subscriber.on( "message", async (channel, message) => { try { if (channel !== this._channel) return; const data = JSON.parse(message); if (data.ns !== this._channel) return; this.emit("message", data); } catch (error) { } } ); this._publisher.on("ready", () => this.emit("ready")); this._publisher.on("connect", () => this.emit("connect")); this._publisher.on("error", () => this.emit("error")); this._publisher.on("end", () => this.emit("end")); this.setId(this._id); } get options() { return this._options; } get id() { return this._id; } get channel() { return this._channel; } get client() { return this._publisher; } get subscriber() { return this._subscriber; } get redisOptions() { return this._options.client.options; } destroy() { this.removeAllListeners(); this._publisher.quit(); this._subscriber.quit(); } setId(id) { this._id = id; this._publisher.client("setname", `${this._channel}:${this._id}`); } async clients() { return new Promise((resolve, reject) => { this._publisher.client("list", (err, data) => { if (err) return reject(err); const _clients = data.split("\n").map((c) => { return Object.fromEntries(new URLSearchParams(c.replace(rxSpace, "&"))); }).filter((c) => { return c.name?.startsWith(this._channel + ":"); }); resolve(_clients); }); }); } async clientIds() { const clients = await this.clients(); if (!clients) return []; return clients.map((c) => c.name.split(":")[1]); } sendRequest(request) { this._publisher.publish(this._channel, JSON.stringify(request)); } } class RedisCluster extends EventEmitter { constructor(options) { super(); this._clients = /* @__PURE__ */ new Set(); this._requests = /* @__PURE__ */ new Map(); this._clusterClient = new RedisClusterClient({ id: options.id, client: options.client, host: options.host, username: options.username, password: options.password, debug: options.debug }); this._timeout = options.timeout || 60; this._handleEvents(); } get cluster() { return this._clusterClient; } get socketers() { return this._clients; } get clients() { return this._clients; } get requests() { return this._requests; } destroy() { this.removeAllListeners(); this._clients.clear(); this._clusterClient.destroy(); } register(client) { this._clients.add(client); } unregister(client) { this._clients.delete(client); } async handleRequest(request) { const _request = { ...request }; if (_request.rq) { const clients = await this._clusterClient.clientIds(); this._requests.set(_request.id, { clients: new Set(clients), timeout: setTimeout(() => { this._requests.delete(_request.id); }, 1e3 * this._timeout) }); } this._clusterClient.sendRequest(_request); } _handleEvents() { this._clusterClient.on( "ready", () => this.emit("ready") ); this._clusterClient.on( "message", async (data) => { let reply; let targetClient; let targetIsHere; for (const client of this._clients) { if (client.id === data.to) { targetClient = client; targetIsHere = true; break; } else if (data.to && client.remotes.has(data.to)) { targetClient = client; targetIsHere = false; break; } } try { const request = this._requests.get(data.id); if (request && !data.rq) { if (data.er?.code === "ERR_WSR_NO_DESTINATION") { request.clients.delete(data.er.payload); if (request.clients.size) return; } clearTimeout(request.timeout); this._requests.delete(data.id); } if (targetIsHere === true) { reply = await targetClient?.handleMessage(data); } else if (targetIsHere === false) { reply = await targetClient?.request("_request_", data); } else if (data.rq) { const error = new WebSocketerError( "No destination in cluster", "ERR_WSR_NO_DESTINATION", this._clusterClient.id ); reply = endRequestData(data, { error: { name: error.name, code: error.code || "ERR_WSR_INTERNAL", message: error.message, payload: error.payload } }); } } catch (error) { reply = endRequestData(data, { error: { name: error.name, code: error.code || "ERR_WSR_INTERNAL", message: error.message, payload: error.payload } }); } if (reply && data.rq) this.handleRequest(reply); } ); } } class Client extends Client$1 { constructor(options) { super(void 0, options); if (this._cluster) this._cluster.cluster.setId(this._options.id); } _send(data) { this._cluster?.handleRequest(data); } } export { Client, RedisCluster, RedisClusterClient };