UNPKG

@ziziyi/invoker

Version:
463 lines (456 loc) 12.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { EventInvoker: () => EventInvoker, ExtConnectInvoker: () => ExtConnectInvoker, ExtMsgInvoker: () => ExtMsgInvoker, FrameMsgInvoker: () => FrameMsgInvoker, Invoker: () => Invoker }); module.exports = __toCommonJS(index_exports); // src/Invoker.ts var Invoker = class { name; uniqueId; count; services; responsePromises; waitingPromises; pendingInvokers; IGNORE = Symbol("INVOKE_IGNORE"); currentSender = null; constructor(name) { this.name = name; this.uniqueId = name + Math.round(Math.random() * 1e6); this.count = 0; this.responsePromises = /* @__PURE__ */ new Map(); this.services = /* @__PURE__ */ new Map(); this.waitingPromises = /* @__PURE__ */ new Map(); this.pendingInvokers = 0; } get key() { return `${this.name}-${this.uniqueId}-${this.count++}`; } get pendingReqs() { return this.pendingInvokers; } async invoke(req) { const key = req.key || this.key; const receipt = await this.send( { key, func: req.func, args: req.args, reply: req.reply, invoker: this.uniqueId }, req ); return this.getReturnValue(key, req, receipt); } add(func, service) { this.services.set(func, service); return this; } remove(func) { this.services.delete(func); return this; } waitInvoke({ func, timeout }) { const p = this.waitingPromises.get(func) || Promise.withResolvers(); this.waitingPromises.set(func, p); let timer; if (timeout && timeout > 0) { timer = setTimeout( () => p.reject(`wait invoke "${this.name}" timeout: ${func}`), timeout ); } p.promise.finally(() => { this.waitingPromises.delete(func); timer && clearTimeout(timer); }); return p.promise; } async getReturnValue(key, req, receipt) { if (receipt && receipt.res) { return receipt.res; } const { func, timeout, signal, reply } = req; if (reply == false) { return null; } const p = this.responsePromises.get(key) || Promise.withResolvers(); this.responsePromises.set(key, p); if (signal) { signal.addEventListener( "abort", () => p.reject(`invoke aborted: ${signal.reason}`) ); } let timer; if (timeout && timeout > 0) { timer = setTimeout( () => p.reject(`"${this.name}" invoke timeout: ${func} key: ${key}`), timeout ?? 2e4 ); } p.promise.finally(() => { this.responsePromises.delete(key); timer && clearTimeout(timer); }); return p.promise; } setReturnValue(key, success, value) { const p = this.responsePromises.get(key); if (p) { const fn = success != false ? p.resolve : p.reject; fn(value); } else { console.error(`unknown invoke callback message: ${key}`); console.log(this.responsePromises); } } handleResMsg(message) { const { key, success, value } = message; if (!key || typeof success !== "boolean") { console.error(`invalid invoke response: ${key}`, message); } this.setReturnValue(key, success, value); } async handleReqMsg(req, sender) { this.pendingInvokers++; try { const { key, func, args, reply, invoker } = req; if (invoker == this.uniqueId) { return; } this.currentSender = sender; let result = null; let error = null; try { const waiting = this.waitingPromises.get(func); if (waiting) { waiting.resolve(args); } const service = this.services.get(func); if (service) { result = await service(...args || []); } else { error = `unknown service: ${func}`; console.warn(`unknown service: ${func}`); return null; } } catch (err) { console.error("invoke error: ", err); error = err; } if (result == this.IGNORE) { return; } const res = { key, success: !error, value: !error ? result : error, func }; if (sender && reply != false) { await this.sendRes(res, sender); } return res; } finally { this.pendingInvokers--; } } }; var Invoker_default = Invoker; // src/ExtMsgInvoker.ts var defaultOptions = { invokeMsgType: "invoke-request", resMsgType: "invoke-response" }; var ExtMsgInvoker = class extends Invoker_default { invokeMsgType; resMsgType; currentSender = null; constructor(name, options = defaultOptions) { super(name); this.invokeMsgType = options.invokeMsgType; this.resMsgType = options.resMsgType; } async send(msg, req) { if (req.tabId) { chrome.tabs.sendMessage(req.tabId, { type: this.invokeMsgType, tabId: req.tabId, ...msg }); } else { chrome.runtime.sendMessage({ type: this.invokeMsgType, tabId: req.tabId, ...msg }); } } async sendRes(res, sender) { if (!sender) { return; } if (chrome.tabs && sender.tab?.id) { chrome.tabs.sendMessage(sender.tab.id, { type: this.resMsgType, ...res }); } else { chrome.runtime.sendMessage({ type: this.resMsgType, ...res }); } } listen() { const self = this; const onMessage = (message, sender, sendResponse) => { switch (message.type) { case self.invokeMsgType: self.handleReqMsg(message, sender); break; case self.resMsgType: self.handleResMsg(message); break; } }; chrome.runtime.onMessage.addListener(onMessage); return () => { chrome.runtime.onMessage.removeListener(onMessage); }; } }; // src/FrameMsgInvoker.ts var defaultOptions2 = { peerOrigin: "*", invokeMsgType: "invoke-request", resMsgType: "invoke-response" }; var FrameMsgInvoker = class extends Invoker_default { peer; peerOrigin; invokeMsgType; resMsgType; constructor(name, options) { super(name); const { peer, peerOrigin, invokeMsgType, resMsgType } = { ...defaultOptions2, ...options }; this.peer = peer; this.peerOrigin = peerOrigin; this.invokeMsgType = invokeMsgType; this.resMsgType = resMsgType; } async send(msg, req) { if (!this.peer) { throw new Error("Peer is not set"); } try { const peer = typeof this.peer == "function" ? this.peer() : this.peer; const win = "postMessage" in peer ? peer : peer.contentWindow; win.postMessage({ type: this.invokeMsgType, ...msg }, this.peerOrigin); } catch (error) { console.warn("WebviewInvoke: frame not ready", this.peer, error); } } async sendRes(res, sender) { if (!sender) { return; } sender.postMessage( { type: this.resMsgType, ...res }, this.peerOrigin ); } listen() { const onMessage = (event) => { if (!event.data || typeof event.data !== "object") { return; } console.log("onMessage: ", event.data); const { type, ...message } = event.data; switch (type) { case this.invokeMsgType: event.stopImmediatePropagation(); this.handleReqMsg(message, event.source); break; case this.resMsgType: event.stopImmediatePropagation(); this.handleResMsg(message); break; } }; window.addEventListener("message", onMessage); return () => { window.removeEventListener("message", onMessage); }; } }; // src/EventInvoker.ts var defaultOptions3 = { eventType: "ziziyi-invoke", invokeMsgType: "invoke-request", resMsgType: "invoke-response" }; var EventInvoker = class extends Invoker { eventType; invokeMsgType; resMsgType; constructor(name, options = defaultOptions3) { super(name); this.eventType = options.eventType || name; this.invokeMsgType = options.invokeMsgType; this.resMsgType = options.resMsgType; } async send(msg, req) { const event = new CustomEvent(this.eventType, { detail: { type: this.invokeMsgType, message: msg } }); document.dispatchEvent(event); } async sendRes(res, sender) { const event = new CustomEvent(this.eventType, { detail: { type: this.resMsgType, message: res } }); document.dispatchEvent(event); } listen() { const onMessage = (event) => { if ("detail" in event) { const { type, message } = event.detail; switch (type) { case this.invokeMsgType: this.handleReqMsg(message, event.type); break; case this.resMsgType: this.handleResMsg(message); break; } } }; document.addEventListener(this.eventType, onMessage); return () => { document.removeEventListener(this.eventType, onMessage); }; } }; // src/ExtConnectInvoker.ts var defaultOptions4 = { invokeMsgType: "invoke-request", resMsgType: "invoke-response" }; var ExtConnectInvoker = class extends Invoker_default { invokeMsgType; resMsgType; port = null; constructor(name, options = defaultOptions4) { super(name); this.invokeMsgType = options.invokeMsgType; this.resMsgType = options.resMsgType; } async send(msg, req) { if (!this.port) { throw new Error("Port is not connected"); } this.port.postMessage({ type: this.invokeMsgType, ...msg }); } async sendRes(res, port) { if (!this.port) { return; } this.port.postMessage({ type: this.resMsgType, ...res }); } listen(onConnect) { const self = this; const handleConnect = (port) => { if (port.name === self.name) { self.port = port; self.port.onDisconnect.addListener(() => { self.port = null; }); self.port.onMessage.addListener((message) => { if (message.type === self.invokeMsgType) { self.handleReqMsg(message, self.port); } else if (message.type === self.resMsgType) { self.handleResMsg(message); } }); onConnect?.(port); } }; chrome.runtime.onConnect.addListener(handleConnect); return () => { chrome.runtime.onConnect.removeListener(handleConnect); }; } connect(tabId) { if (tabId) { this.port = chrome.tabs.connect(tabId, { name: this.name }); } else { this.port = chrome.runtime.connect({ name: this.name }); } this.port.onDisconnect.addListener(() => { this.port = null; }); this.port.onMessage.addListener((message) => { if (message.type === this.invokeMsgType) { this.handleReqMsg(message, this.port); } else if (message.type === this.resMsgType) { this.handleResMsg(message); } }); } get isConnected() { return !!this.port; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { EventInvoker, ExtConnectInvoker, ExtMsgInvoker, FrameMsgInvoker, Invoker });