universal-message
Version: 
A universal message communication library for JavaScript/TypeScript that works across different environments including Worker threads, WebSocket, and other communication channels
718 lines (694 loc) • 25 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5;// src/plugs/MessageEcoders.ts
require('reflect-metadata');
// src/plugs/ECoders.ts
require('class-transformer');
var ECoderAbortController = {
  target: (t) => t instanceof AbortController,
  key: "AbortController",
  encode: (data, tool) => {
    if (!data.signal.aborted) {
      const abortHandler = async () => {
        tool.sendUpdate(data.signal.aborted);
        tool.remove();
        data.signal.removeEventListener("abort", abortHandler);
      };
      data.signal.addEventListener("abort", abortHandler);
    }
    return data.signal.aborted;
  },
  decode: (body, tool) => {
    const data = new AbortController();
    if (!!body.data) {
      data.abort();
    } else {
      const abortHandler = async () => {
        tool.sendUpdate(data.signal.aborted);
        tool.remove();
        data.signal.removeEventListener("abort", abortHandler);
      };
      data.signal.addEventListener("abort", abortHandler);
    }
    return data;
  },
  update: (newValue, target, tool) => {
    if (newValue) {
      tool.remove();
      target.abort();
    }
  }
};
// src/plugs/MessageEcoders.ts
var MessageEcoders = [
  ECoderAbortController
];
// 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 = class {
  constructor(client) {;_class.prototype.__init.call(this);
    this.client = client;
  }
  __init() {this.name = "MessageEcoderPlugin"}
  async onSend(body) {
    if (isNull(_optionalChain([body, 'optionalAccess', _ => _.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)
        };
        if (ecoder.update) {
          this.client.on(id, (value) => {
            ecoder.update && ecoder.update(value, target, tool, this.client);
          });
          this.client.addParamListener(body.id, id);
        }
        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, ndps));
    } 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, ndps);
        }
      }
      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);
          if (ecoder.update) {
            this.client.on(id, (value) => {
              ecoder.update && ecoder.update(value, target, tool, this.client);
            });
            this.client.addParamListener(body.id, id);
          }
          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, ndps));
    } else if (data instanceof Object) {
      ndps.add(data);
      for (const key in data) {
        if (data.hasOwnProperty(key)) {
          data[key] = this.dataDecode(data[key], body, ndps);
        }
      }
    }
    return data;
  }
}, _class);
// src/MessageBase.ts
// 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 = (_class2 = class {
  static __initStatic() {this.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 (_optionalChain([data, 'optionalAccess', _2 => _2.__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;
  }
}, _class2.__initStatic(), _class2);
// src/MessageBase.ts
var _fastcopy = require('fast-copy');
var MessageBase = (_class3 = class {
  // 消息ID -> 参数监听器ID集合
  constructor(option) {;_class3.prototype.__init2.call(this);_class3.prototype.__init3.call(this);_class3.prototype.__init4.call(this);_class3.prototype.__init5.call(this);_class3.prototype.__init6.call(this);
    this.option = option;
    this.init();
  }
  __init2() {this.ons = /* @__PURE__ */ new Map()}
  __init3() {this.fns = /* @__PURE__ */ new Map()}
  __init4() {this.plugins = /* @__PURE__ */ new Map()}
  // 参数监听器管理
  __init5() {this.paramListeners = /* @__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));
  }
  // 添加参数监听器关联
  addParamListener(messageId, paramListenerId) {
    if (!this.paramListeners.has(messageId)) {
      this.paramListeners.set(messageId, /* @__PURE__ */ new Set());
    }
    this.paramListeners.get(messageId).add(paramListenerId);
  }
  // 清理消息相关的所有参数监听器
  cleanupParamListeners(messageId) {
    const listeners = this.paramListeners.get(messageId);
    if (listeners) {
      listeners.forEach((listenerId) => {
        if (this.fns.has(listenerId)) {
          this.fns.delete(listenerId);
          console.log(`Cleaned up param listener: ${listenerId}`);
        }
      });
      this.paramListeners.delete(messageId);
    }
  }
  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;
      try {
        if (!isNull(data.error)) {
          if (call.reject) {
            call.reject(data.error);
            call.reject = void 0;
          } else {
            throw new Error(`Message error: ${data.error}`);
          }
        } else {
          if (call.resolve) {
            call.resolve(body);
            call.resolve = void 0;
          }
        }
      } finally {
        this.ons.delete(data.id);
        this.cleanupParamListeners(data.id);
      }
      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) {
    let body = { id: msg.id, data: _fastcopy.copyStrict.call(void 0, result), key: msg.key, result: true, error };
    body = await this.emitPlugin("onSend", body);
    body.data = Json2.stringify(body.data);
    this.option.send(body);
  }
  async send(key, data, id = rid(), other = {}) {
    return new Promise(async (resolve, reject) => {
      this.ons.set(id, {
        resolve,
        reject,
        once: true
      });
      let body = { id, data: _fastcopy.copyStrict.call(void 0, 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, id = rid()) {
    return this.send(key, data, id);
  }
  //----------
  __init6() {this.$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);
  }
  // 获取当前活跃连接数
  getActiveConnectionsCount() {
    return this.ons.size;
  }
  // 手动清理所有资源
  cleanup() {
    for (const [id, listener] of this.ons) {
      if (listener.reject) {
        listener.reject(new Error("Connection cleaned up"));
      }
    }
    this.ons.clear();
    this.fns.clear();
    this.paramListeners.clear();
    console.log("MessageBase cleaned up");
  }
}, _class3);
// src/MessageShared.ts
var MessageShared = (_class4 = class extends MessageBase {
  __init7() {this.shareds = /* @__PURE__ */ new Map()}
  constructor(option) {
    super(option);_class4.prototype.__init7.call(this);;
    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 await target(body.data);
      return await objectEval(target, "call", 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)
    };
  }
}, _class4);
// 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 }, 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:close", { id, className }, 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 = (_class5 = class extends MessageShared {
  constructor(option) {
    super(option);_class5.prototype.__init8.call(this);;
    this.initClass();
  }
  //---------代理类----------
  __init8() {this.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);
    });
  }
}, _class5);
exports.ECoderAbortController = ECoderAbortController; exports.Json2 = Json2; exports.MessageBase = MessageBase; exports.MessageClient = MessageClient; exports.MessageEcoderPlugin = MessageEcoderPlugin; exports.MessageEcoders = MessageEcoders; exports.MessageServer = MessageServer; exports.MessageShared = MessageShared; exports.isNull = isNull; exports.objectEval = objectEval; exports.rid = rid;