@ziziyi/invoker
Version:
463 lines (456 loc) • 12.1 kB
JavaScript
"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
});