UNPKG

universal-message

Version:

A universal message communication library for JavaScript/TypeScript that works across different environments including Worker threads, WebSocket, and other communication channels

706 lines (695 loc) 22.8 kB
// src/plugs/MessageEcoders.ts import "reflect-metadata"; // src/plugs/ECoders.ts import { instanceToPlain, plainToInstance } from "class-transformer"; import "reflect-metadata"; function getClass(data, client) { for (const [key, value] of client.$class.entries()) { if (value && data instanceof value) return { value, key }; } return null; } var ECoderAbortController = { target: (t) => t instanceof AbortController, key: "AbortController", encode: (data, tool) => { data.data; if (!data.signal.aborted) { data.signal.addEventListener("abort", async () => { tool.sendUpdate(data.signal.aborted); tool.remove(); }); } return data.signal.aborted; }, decode: (body, tool) => { const data = new AbortController(); if (!!body.data) { data.abort(); } else { data.signal.addEventListener("abort", async () => { tool.sendUpdate(data.signal.aborted); tool.remove(); }); } return data; }, update: (newValue, target, tool) => { if (newValue) { tool.remove(); target.abort(); } } }; var ECoderClassTransformer = { target: (e, client) => !!getClass(e, client), key: "ClassTransformer", encode: (data, tool, client) => { const cls = getClass(data, client); if (!cls) return data; return { class: cls.key, data: instanceToPlain(data) }; }, decode: (body, tool, client) => { if (!body?.data) return body; const { class: classKey, data } = body.data; const cls = client.$class.get(classKey); if (!cls) return body.data; if (!data) return body.data; const instance = plainToInstance(cls, data); return instance; } }; // src/plugs/MessageEcoders.ts var MessageEcoders = [ ECoderAbortController, ECoderClassTransformer ]; // src/tools.ts var rid = () => Math.random().toString(36).substring(2, 15); function isNull(data) { return data === null || data === void 0; } async function objectEval(instance, type, prop, data) { if (type === "get") { return new Function("obj", `return obj.` + prop)(instance); } else if (type === "set") { return new Function("obj", "value", `obj.${prop}=value`)(instance, data); } else if (type === "call") { return await new Function("obj", "value", `return obj.${prop}(...value)`)(instance, data); } else if (type === "delete") { return await new Function("obj", `delete obj.${prop}`)(instance); } } // src/plugs/MessageEcoderPlugin.ts var MessageEcoderPlugin = class { constructor(client) { this.client = client; } name = "MessageEcoderPlugin"; async onSend(body) { if (isNull(body?.data)) return body; body.data = this.dataEncode(body.data, body); return body; } async onMessage(body) { body.data = this.dataDecode(body.data, body); return body; } dataEncode(data, body, ndps = /* @__PURE__ */ new Set()) { if (ndps.has(data)) return data; for (const ecoder of MessageEcoders) { if (ecoder.target(data, this.client)) { const target = data; const id = "$args:" + ecoder.key + ":" + rid(); const tool = { sendUpdate: (value) => { if (!this.client.hasOn(id)) return; this.client.emit(id, value); }, remove: () => this.client.removeAll(id) }; ecoder.update && this.client.on(id, (value) => { ecoder.update && ecoder.update(value, target, tool, this.client); }); const res = ecoder.encode(data, tool, this.client); return { id, key: ecoder.key, data: res }; } } if (data instanceof Array) { ndps.add(data); return data.map((item) => this.dataEncode(item, body)); } else if (data instanceof Object && data !== null) { ndps.add(data); let ndata = {}; for (const key in data) { if (data.hasOwnProperty(key)) { ndata[key] = this.dataEncode(data[key], body); } } return ndata; } return data; } dataDecode(data, body, ndps = /* @__PURE__ */ new Set()) { if (isNull(data)) return data; if (ndps.has(data)) return data; for (const ecoder of MessageEcoders.toReversed()) { if (ecoder.key === data.key) { const id = data.id; const tool = { sendUpdate: (value) => { if (!this.client.hasOn(id)) return; this.client.emit(id, value); }, remove: () => this.client.removeAll(id) }; try { const target = ecoder.decode(data, tool, this.client); ecoder.update && this.client.on(id, (value) => { ecoder.update && ecoder.update(value, target, tool, this.client); }); return target; } catch (e) { console.error("Decoder Error:", ecoder.key, data, e); return data; } } } if (data instanceof Array) { ndps.add(data); data = data.map((item) => this.dataDecode(item, body)); } else if (data instanceof Object) { ndps.add(data); for (const key in data) { if (data.hasOwnProperty(key)) { data[key] = this.dataDecode(data[key], body); } } } return data; } }; // src/MessageBase.ts import "reflect-metadata"; // src/utils/json2.ts function deepObject(data, callback) { for (const key in data) { if (data.hasOwnProperty(key)) { data[key] = callback(data[key]); } } return data; } var Json2 = class { static key = "$$ref:"; static stringify(data) { data = this.dataToRefs(data); data = this.encoder(data); return JSON.stringify(data); } //处理对象中的嵌套引用 static dataToRefs(data) { const refs = [data]; for (let index = 0; index < refs.length; index++) { let data2 = refs[index]; if (data2 instanceof Array) { data2 = data2.map((x) => this.addList(x, refs)); } else if (data2 instanceof Map) { const newMap = /* @__PURE__ */ new Map(); for (const [key, value] of data2.entries()) { newMap.set(key, this.addList(value, refs)); } data2 = newMap; } else if (data2 instanceof Set) { const newSet = /* @__PURE__ */ new Set(); for (const value of data2.values()) { newSet.add(this.addList(value, refs)); } data2 = newSet; } else if (typeof data2 === "object" && data2 !== null) { Object.keys(data2).forEach((key) => { data2[key] = this.addList(data2[key], refs); }); } refs[index] = data2; } return refs; } static addList(data, list) { if (typeof data === "object" && data !== null) { if (!list.find((x) => x === data)) { list.push(data); } return this.key + list.indexOf(data); } return data; } static refsToData(refs) { const dataDecoder = (data) => { if (typeof data === "string" && data.startsWith(this.key)) { const index = parseInt(data.substring(this.key.length)); return refs[index]; } else if (Array.isArray(data)) { return data.map((item) => dataDecoder(item)); } else if (data instanceof Map) { const newMap = /* @__PURE__ */ new Map(); for (const [key, value] of data.entries()) { newMap.set(key, dataDecoder(value)); } return newMap; } else if (data instanceof Set) { const newSet = /* @__PURE__ */ new Set(); for (const value of data.values()) { newSet.add(dataDecoder(value)); } return newSet; } else if (data instanceof Object) { const newObj = {}; for (const key in data) { if (data.hasOwnProperty(key)) { newObj[key] = dataDecoder(data[key]); } } return newObj; } return data; }; for (let index = refs.length - 1; index >= 0; index--) { let data = refs[index]; if (data instanceof Array) { refs[index] = data.map((x) => dataDecoder(x)); } else if (data instanceof Map) { const newMap = /* @__PURE__ */ new Map(); for (const [key, value] of data.entries()) { newMap.set(key, dataDecoder(value)); } refs[index] = newMap; } else if (data instanceof Set) { const newSet = /* @__PURE__ */ new Set(); for (const value of data.values()) { newSet.add(dataDecoder(value)); } refs[index] = newSet; } else if (typeof data === "object" && data !== null) { const newObj = {}; Object.keys(data).forEach((key) => { newObj[key] = dataDecoder(data[key]); }); refs[index] = newObj; } else { refs[index] = dataDecoder(data); } } return refs[0]; } static encoder(data) { if (Array.isArray(data)) { return data.map((item) => this.encoder(item)); } else if (data instanceof Date) { return { __e_type__: "date", value: data.getTime() }; } else if (data instanceof Map) { let obj = Object.fromEntries(data.entries()); obj = deepObject(obj, (data2) => this.encoder(data2)); return { __e_type__: "map", value: obj }; } else if (data instanceof Set) { return { __e_type__: "set", value: Array.from(data).map((x) => this.encoder(x)) }; } else if (data instanceof RegExp) { return { __e_type__: "regexp", value: data.toString() }; } else if (data instanceof Error) { return { __e_type__: "error", value: data.message, stack: data.stack }; } else if (data instanceof URL) { return { __e_type__: "url", value: data.toString() }; } else if (typeof Blob !== "undefined" && data instanceof Blob) { return { __e_type__: "blob", value: data }; } else if (data instanceof ArrayBuffer) { return { __e_type__: "arraybuffer", value: Array.from(new Uint8Array(data)) }; } else if (typeof Buffer !== "undefined" && data instanceof Buffer) { return { __e_type__: "buffer", value: Array.from(data) }; } else if (data instanceof BigInt || typeof data === "bigint") { return { __e_type__: "bigint", value: data.toString() }; } else if (data instanceof BigInt64Array) { return { __e_type__: "bigint64array", value: Array.from(data) }; } else if (data instanceof BigUint64Array) { return { __e_type__: "biguint64array", value: Array.from(data) }; } if (typeof data === "object") { return deepObject(data, (data2) => this.encoder(data2)); } return data; } static parse(str) { let start = Date.now(); let data = JSON.parse(str); data = this.decoder(data); data = this.refsToData(data); return data; } static decoder(data) { if (data?.__e_type__) { const type = data.__e_type__; const value = data.value; if (type === "date") { return new Date(value); } else if (type === "map") { const map = /* @__PURE__ */ new Map(); for (const key in value) { if (value.hasOwnProperty(key)) { map.set(key, this.decoder(value[key])); } } return map; } else if (type === "set") { return new Set(value.map((x) => this.decoder(x))); } else if (type === "regexp") { return new RegExp(value); } else if (type === "error") { const error = new Error(value); if (data.stack) { error.stack = data.stack; } return error; } else if (type === "url") { return new URL(value); } else if (type === "blob") { if (typeof Blob !== "undefined") return new Blob([value]); return new Uint8Array(value); } else if (type === "arraybuffer") { return new Uint8Array(value).buffer; } else if (type === "buffer") { if (typeof Buffer !== "undefined") return Buffer.from(value); return new ArrayBuffer(value); } else if (type === "bigint") { return BigInt(value); } else if (type === "bigint64array") { return BigInt64Array.from(value); } else if (type === "biguint64array") { return BigUint64Array.from(value); } } if (data instanceof Array) { return data.map((item) => this.decoder(item)); } else if (data instanceof Object) { data = deepObject(data, (dta) => this.decoder(dta)); } return data; } }; // src/MessageBase.ts import { copyStrict } from "fast-copy"; var MessageBase = class { constructor(option) { this.option = option; this.init(); } ons = /* @__PURE__ */ new Map(); fns = /* @__PURE__ */ new Map(); plugins = /* @__PURE__ */ new Map(); init() { this.option.on((data) => { if (data instanceof MessageEvent) { data = data.data; } this.listenCallback(data); }); this.on("$message:base:listen:remove", ({ key }) => this.ons.delete(key)); this.addPlugin(new MessageEcoderPlugin(this)); } addPlugin(plugin) { this.plugins.set(plugin.name, plugin); } removePlugin(name) { this.plugins.delete(name); } async emitPlugin(eventName, data) { for (const [name, plugin] of this.plugins) { if (plugin[eventName]) { try { data = await plugin[eventName](data); } catch (e) { console.error(`Plugin ${name} error on ${eventName}:`, e); } } } return data; } async listenCallback(data) { if (!(data && data.id && data.key)) return; data.data = Json2.parse(data.data || "null"); const result = (result2) => this.callback(data, result2); const error = (err) => this.callback(data, null, err); data = await this.emitPlugin("onMessage", data); const body = data.data; if (data.result) { const call = this.ons.get(data.id); if (!call) return; if (!isNull(data.error)) { this.ons.delete(data.id); if (call.reject) { call.reject(data.error); call.reject = void 0; } else { throw new Error(`Worker error: ${data.error}`); } return; } if (call.resolve) { call.resolve(body); call.resolve = void 0; if (!call.callback) { this.ons.delete(data.id); } } if (call.callback) { call.callback(body); } return; } const fn = this.fns.get(data.key); if (!fn) { return error(new Error(`Worker function not found: ${data.key}`)); } try { result(await fn(body, data)); } catch (e) { error(e); } finally { } } async callback(msg, result, error) { return await this.send(msg.key, result, void 0, msg.id, { result: true, error }); } async send(key, data, callback, id = rid(), other = {}) { return new Promise(async (resolve, reject) => { const back = (result) => { if (callback) { callback(result); } resolve(result); }; this.ons.set(id, { resolve, reject, callback: back, once: !callback }); let body = { id, data: copyStrict(data), key, ...other }; body = await this.emitPlugin("onSend", body); body.data = Json2.stringify(body.data); this.option.send(body); }); } on(key, callback) { this.fns.set(key, callback); } hasOn(key) { return this.fns.has(key); } remove(key) { this.fns.delete(key); } removeAll(key) { if (!this.hasOn(key)) return; this.remove(key); this.send("$message:base:listen:remove", { key }); } async emit(key, data, callback, id = rid()) { return this.send(key, data, callback, id); } //---------- $class = /* @__PURE__ */ new Map(); //存储类 /** * 添加代理类 * @param target 未初始化的类 */ addClass(target) { if (typeof target !== "function") throw new Error("Must provide a class constructor"); const name = target.name; if (!name) throw new Error("Class must have a name"); this.$class.set(name, target); } }; // src/MessageShared.ts var MessageShared = class extends MessageBase { shareds = /* @__PURE__ */ new Map(); constructor(option) { super(option); this.on("$shared:type", async (body, msg) => { const value = this.checkVar(body); return typeof value; }); this.on("$shared:get", async (body, msg) => { const target = this.checkVar(body); if (!body.attr) return target; return await objectEval(target, "get", body.attr); }); this.on("$shared:set", async (body, msg) => { const target = this.checkVar(body); if (!body.attr) { this.shareds.set(body.key, body.data); return; } return await objectEval(target, "set", body.attr); }); this.on("$shared:call", async (body, msg) => { const target = this.checkVar(body); if (!body.attr) return target(body.data); return await objectEval(target, "get", body.attr, body.data); }); this.on("$shared:remove", async (body, msg) => { const target = this.shareds.get(body.key); if (!target) return; if (!body.attr) { this.shareds.delete(body.key); return; } return await objectEval(target, "delete", body.attr); }); } checkVar(body) { const target = this.shareds.get(body.key); if (!target) throw new Error(`Variable ${body.key} not found`); return target; } addShared(key, target) { this.shareds.set(key, target); } delShared(key) { this.shareds.delete(key); } loadShared(key) { return { value: async () => this.send("$shared:get", { key }, void 0), del: async (attr) => this.send("$shared:remove", { key, attr }, void 0), set: async (attr = "", data) => this.send("$shared:set", { key, data, attr }, void 0), call: async (attr = "", ...data) => this.send("$shared:call", { key, data, attr }, void 0), get: async (attr) => this.send("$shared:get", { key, attr }, void 0), close: async () => this.send("$shared:remove", { key }, void 0) }; } }; // src/MessageServer.ts var MessageServer = class extends MessageShared { constructor(option) { super(option); } /** * [发送] 初始化类 * @param target 对象或类名 * @param data * @returns */ async newClass(target, ...data) { const id = rid(); const className = typeof target === "string" ? target : target.name; await this.send("$class:new", { className, data }, void 0, id); return { get: async (key) => await this.send("$class:get", { key, className, id }, void 0), set: async (key, data2) => await this.send("$class:set", { key, id, className, data: data2 }, void 0), call: async (key, ...data2) => await this.send("$class:call", { key, id, className, data: data2 }, void 0), close: async () => await this.send("$class:$class:close", { id, className, data }, void 0), static: this.staticClass(className) }; } /** * [发送] 获取类静态方法 * @param target 对象或类名 * @returns */ staticClass(target) { const className = typeof target === "string" ? target : target.name; return { get: async (key) => await this.send("$class:static:get", { key, className }, void 0), set: async (key, data) => await this.send("$class:static:set", { key, className, data }, void 0), call: async (key, ...data) => await this.send("$class:static:call", { key, className, data }, void 0), close: async () => await this.send("$class:static:close", { className }, void 0) }; } }; // src/MessageClient.ts var MessageClient = class extends MessageShared { constructor(option) { super(option); this.initClass(); } //---------代理类---------- clsNews = /* @__PURE__ */ new Map(); //存储类实例 initClass() { this.on("$class:new", async (body, msg) => { if (!body.className) throw new Error("Class name is required"); const target = this.$class.get(body.className); if (!target) throw new Error(`Class ${body.className} not found`); const instance = new target(...body.data); this.clsNews.set(msg.id, instance); }); this.on("$class:get", async (body, msg) => { if (!body.id) throw new Error(`Class instance id is required`); const target = this.clsNews.get(body.id); if (!target) throw new Error(`The ${body.className} instance does not exist. ` + body.id); return await objectEval(target, "get", body.key); }); this.on("$class:set", async (body, msg) => { if (!body.id) throw new Error(`Class instance id is required`); const target = this.clsNews.get(body.id); if (!target) throw new Error(`The ${body.className} instance does not exist. ` + body.id); return await objectEval(target, "set", body.key, body.data); }); this.on("$class:call", async (body, msg) => { if (!body.id) throw new Error(`Class instance id is required`); const target = this.clsNews.get(body.id); if (!target) throw new Error(`The ${body.className} instance does not exist. ` + body.id); return await objectEval(target, "call", body.key, body.data); }); this.on("$class:close", async (body, msg) => { if (!body.id) throw new Error(`Class instance id is required`); this.clsNews.delete(body.id); }); this.on("$class:static:get", async (body, msg) => { if (!body.className) throw new Error("Class name is required"); const target = this.$class.get(body.className); if (!target) throw new Error(`Class ${body.className} not found`); return await objectEval(target, "get", body.key); }); this.on("$class:static:set", async (body, msg) => { if (!body.className) throw new Error("Class name is required"); const target = this.$class.get(body.className); if (!target) throw new Error(`Class ${body.className} not found`); return await objectEval(target, "set", body.key, body.data); }); this.on("$class:static:call", async (body, msg) => { if (!body.className) throw new Error("Class name is required"); const target = this.$class.get(body.className); if (!target) throw new Error(`Class ${body.className} not found`); return await objectEval(target, "call", body.key, body.data); }); this.on("$class:static:close", async (body, msg) => { if (!body.className) throw new Error("Class name is required"); this.$class.delete(body.className); }); } }; export { ECoderAbortController, ECoderClassTransformer, Json2, MessageBase, MessageClient, MessageEcoderPlugin, MessageEcoders, MessageServer, MessageShared, isNull, objectEval, rid };