iframe-connect
Version:
A lightweight, type-safe cross-frame communication library with plugin system and multi-window isolation
446 lines (440 loc) • 12.8 kB
JavaScript
import { __spreadValues, __objRest, createLogger, createRetryManager, createQueueManager } from './chunk-JI2PIO5G.mjs';
export { Logger, QueueManager, RetryManager, createLogger, createQueueManager, createRetryManager } from './chunk-JI2PIO5G.mjs';
// src/message-handler.ts
var MessageHandler = class {
// 检查间隔(毫秒)
constructor(targetOrigin, timeout, debug = false) {
this.pendingRequests = /* @__PURE__ */ new Map();
this.eventListeners = /* @__PURE__ */ new Map();
this.messageId = 0;
this.timeoutChecker = null;
// 集中式超时检查器
this.checkInterval = 1e3;
this.targetOrigin = targetOrigin;
this.timeout = timeout;
this.debug = debug;
this.startTimeoutChecker();
}
/**
* 启动集中式超时检查器
*/
startTimeoutChecker() {
if (this.timeoutChecker !== null) return;
this.timeoutChecker = setInterval(() => {
this.checkTimeouts();
}, this.checkInterval);
}
/**
* 检查所有待处理请求的超时
*/
checkTimeouts() {
if (this.pendingRequests.size === 0) return;
const now = Date.now();
const timedOutIds = [];
this.pendingRequests.forEach((request, id) => {
if (request.expirationTime && now >= request.expirationTime) {
timedOutIds.push(id);
}
});
timedOutIds.forEach((id) => {
const request = this.pendingRequests.get(id);
if (request) {
this.pendingRequests.delete(id);
request.reject(new Error(`Request timeout: ${id}`));
if (this.debug) {
this.log(`\u8BF7\u6C42\u8D85\u65F6: ${id}`);
}
}
});
}
/**
* 生成唯一的消息ID
*/
generateMessageId() {
return `msg_${Date.now()}_${++this.messageId}`;
}
/**
* 调试日志
*/
log(...args) {
if (this.debug) {
console.log("[CrossFrame]", ...args);
}
}
/**
* 处理响应消息
*/
handleResponse(response) {
const pending = this.pendingRequests.get(response.id);
if (!pending) {
return;
}
this.pendingRequests.delete(response.id);
if (response.success) {
pending.resolve(response.data);
} else {
pending.reject(new Error(response.error || "Unknown error"));
}
}
/**
* 处理请求消息
*/
async handleRequest(request, source) {
this.log("Received request:", request);
const listeners = this.eventListeners.get(request.type);
if (!listeners || listeners.size === 0) {
const response = {
id: request.id,
success: false,
error: `No handler for message type: ${request.type}`,
isResponse: true,
timestamp: Date.now()
};
source.postMessage(response, this.targetOrigin);
return;
}
try {
const results = await Promise.all(
Array.from(listeners).map((listener) => listener(request.payload))
);
const response = {
id: request.id,
success: true,
data: results[0],
isResponse: true,
timestamp: Date.now()
};
source.postMessage(response, this.targetOrigin);
} catch (error) {
const response = {
id: request.id,
success: false,
error: error instanceof Error ? error.message : "Unknown error",
isResponse: true,
timestamp: Date.now()
};
source.postMessage(response, this.targetOrigin);
}
}
/**
* 创建待处理的请求
* @param id 请求ID
* @param resolve 成功回调
* @param reject 失败回调
* @param requestTimeout 可选的单个请求超时时间,覆盖全局设置
*/
createPendingRequest(id, resolve, reject, requestTimeout) {
const timeout = requestTimeout !== void 0 ? requestTimeout : this.timeout;
const expirationTime = timeout !== void 0 ? Date.now() + timeout : void 0;
this.pendingRequests.set(id, {
resolve,
reject,
expirationTime
});
}
/**
* 添加事件监听器
*/
addEventListener(type, listener) {
if (!this.eventListeners.has(type)) {
this.eventListeners.set(type, /* @__PURE__ */ new Set());
}
this.eventListeners.get(type).add(listener);
return () => {
const listeners = this.eventListeners.get(type);
if (listeners) {
listeners.delete(listener);
if (listeners.size === 0) {
this.eventListeners.delete(type);
}
}
};
}
/**
* 移除事件监听器
*/
removeEventListener(type, listener) {
const listeners = this.eventListeners.get(type);
if (!listeners) {
return;
}
if (listener) {
listeners.delete(listener);
if (listeners.size === 0) {
this.eventListeners.delete(type);
}
} else {
this.eventListeners.delete(type);
}
}
/**
* 清理所有待处理的请求
*/
cleanup() {
if (this.timeoutChecker !== null) {
clearInterval(this.timeoutChecker);
this.timeoutChecker = null;
}
this.pendingRequests.forEach(({ reject }) => {
reject(new Error("Bridge destroyed"));
});
this.pendingRequests.clear();
this.eventListeners.clear();
}
/**
* 获取待处理请求数量(用于调试)
*/
getPendingRequestsCount() {
return this.pendingRequests.size;
}
/**
* 获取监听器数量(用于调试)
*/
getListenersCount() {
return this.eventListeners.size;
}
};
// src/base-bridge.ts
var IframeBridge = class {
constructor(config = {}) {
this.config = __spreadValues({
targetOrigin: "*",
timeout: 5e3,
debug: false,
enableMultiWindow: false,
windowId: ""
}, config);
if (this.config.enableMultiWindow) {
this.windowId = this.config.windowId || this.generateWindowId();
if (this.config.debug) {
console.log(`[CrossFrame] \u591A\u7A97\u53E3\u6A21\u5F0F\u542F\u7528\uFF0C\u7A97\u53E3ID: ${this.windowId}`);
}
}
this.messageHandler = new MessageHandler(
this.config.targetOrigin,
this.config.timeout,
this.config.debug
);
this.setupMessageListener();
}
/**
* 生成唯一窗口ID
*/
generateWindowId() {
return `window_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* 发送消息(无返回值)
*/
send(type, payload) {
const targetWindow = this.getTargetWindow();
if (!targetWindow) {
this.messageHandler.log("\u76EE\u6807\u7A97\u53E3\u4E0D\u53EF\u7528");
return;
}
const message = {
type,
payload,
timestamp: Date.now()
};
if (this.config.enableMultiWindow && this.windowId) {
message.windowId = this.windowId;
}
this.messageHandler.log("\u53D1\u9001\u6D88\u606F:", message);
targetWindow.postMessage(message, this.config.targetOrigin);
}
/**
* 发送请求(有返回值)
* @param type 消息类型
* @param payload 消息负载
* @param requestTimeout 单个请求的超时时间(毫秒),覆盖全局设置
*/
request(type, payload, requestTimeout) {
return new Promise((resolve, reject) => {
const targetWindow = this.getTargetWindow();
if (!targetWindow) {
reject(new Error("\u76EE\u6807\u7A97\u53E3\u4E0D\u53EF\u7528"));
return;
}
const messageId = this.messageHandler.generateMessageId();
this.messageHandler.createPendingRequest(
messageId,
resolve,
reject,
requestTimeout !== void 0 ? requestTimeout : void 0
);
const message = {
id: messageId,
type,
payload,
needResponse: true,
timestamp: Date.now()
};
if (this.config.enableMultiWindow && this.windowId) {
message.windowId = this.windowId;
}
this.messageHandler.log("\u53D1\u9001\u8BF7\u6C42:", message);
targetWindow.postMessage(message, this.config.targetOrigin);
});
}
/**
* 监听消息
*/
on(type, listener) {
return this.messageHandler.addEventListener(type, listener);
}
/**
* 取消监听
*/
off(type, listener) {
this.messageHandler.removeEventListener(type, listener);
}
/**
* 处理接收到的消息
*/
handleIncomingMessage(event) {
const targetWindow = this.getTargetWindow();
if (!targetWindow || event.source !== targetWindow) {
return;
}
if (this.config.enableMultiWindow) {
if (!event.data || !event.data.windowId || event.data.windowId !== this.windowId) {
if (this.config.debug) {
this.messageHandler.log("\u5FFD\u7565\u5176\u4ED6\u7A97\u53E3\u6D88\u606F:", event.data);
}
return;
}
}
if (event.data && event.data.isResponse) {
this.messageHandler.handleResponse(event.data);
return;
}
if (event.data && event.data.needResponse) {
this.messageHandler.handleRequest(
event.data,
targetWindow
);
return;
}
if (event.data && event.data.type) {
const listeners = this.messageHandler.eventListeners.get(
event.data.type
);
if (listeners && listeners.size > 0) {
listeners.forEach((listener) => {
try {
listener(event.data.payload);
} catch (error) {
this.messageHandler.log("\u4E8B\u4EF6\u76D1\u542C\u5668\u6267\u884C\u9519\u8BEF:", error);
}
});
}
}
}
/**
* 获取调试信息
*/
getDebugInfo() {
return {
config: this.config,
windowId: this.windowId,
pendingRequests: this.messageHandler.getPendingRequestsCount(),
listeners: this.messageHandler.getListenersCount()
};
}
/**
* 销毁桥梁
*/
destroy() {
this.messageHandler.cleanup();
window.removeEventListener(
"message",
this.handleIncomingMessage.bind(this)
);
}
};
// src/host-bridge.ts
var HostBridge = class extends IframeBridge {
constructor(iframe, config = {}) {
super(config);
this.iframe = iframe;
}
setupMessageListener() {
window.addEventListener("message", this.handleIncomingMessage.bind(this));
}
getTargetWindow() {
return this.iframe.contentWindow;
}
};
// src/child-bridge.ts
var ChildBridge = class extends IframeBridge {
constructor(config = {}) {
super(config);
}
setupMessageListener() {
window.addEventListener("message", this.handleIncomingMessage.bind(this));
}
getTargetWindow() {
return window.parent;
}
};
// src/factory.ts
function createEnhancedHostBridge(iframe, config = {}) {
const _a = config, { plugins: pluginsConfig } = _a, bridgeConfig = __objRest(_a, ["plugins"]);
const bridge = new HostBridge(iframe, bridgeConfig);
if (pluginsConfig) {
applyPlugins(bridge, pluginsConfig);
}
return bridge;
}
function createEnhancedChildBridge(config = {}) {
const _a = config, { plugins: pluginsConfig } = _a, bridgeConfig = __objRest(_a, ["plugins"]);
const bridge = new ChildBridge(bridgeConfig);
if (pluginsConfig) {
applyPlugins(bridge, pluginsConfig);
}
return bridge;
}
function createEnhancedBridge(iframeOrConfig, config) {
if (window !== window.parent) {
const bridgeConfig = iframeOrConfig instanceof HTMLElement ? config : iframeOrConfig;
return createEnhancedChildBridge(bridgeConfig);
}
if (iframeOrConfig instanceof HTMLElement) {
return createEnhancedHostBridge(iframeOrConfig, config);
}
throw new Error("\u5728\u4E3B\u7A97\u53E3\u73AF\u5883\u4E2D\u5FC5\u987B\u63D0\u4F9B iframe \u5143\u7D20");
}
function applyPlugins(bridge, config) {
if (config.logger) {
const options = config.logger === true ? {} : config.logger;
createLogger(bridge, options);
}
if (config.retry) {
const options = config.retry === true ? {} : config.retry;
createRetryManager(bridge, options);
}
if (config.queue) {
const options = config.queue === true ? {} : config.queue;
createQueueManager(bridge, options);
}
}
function createHostBridge(iframe, config) {
return new HostBridge(iframe, config);
}
function createChildBridge(config) {
return new ChildBridge(config);
}
function createAutoBridge(iframeOrConfig, config) {
if (window !== window.parent) {
const bridgeConfig = iframeOrConfig instanceof HTMLElement ? config : iframeOrConfig;
return new ChildBridge(bridgeConfig);
}
if (iframeOrConfig instanceof HTMLElement) {
return new HostBridge(iframeOrConfig, config);
}
throw new Error("\u5728\u4E3B\u7A97\u53E3\u73AF\u5883\u4E2D\u5FC5\u987B\u63D0\u4F9B iframe \u5143\u7D20");
}
export { ChildBridge, HostBridge, IframeBridge, MessageHandler, createAutoBridge, createEnhancedBridge as createBridge, createChildBridge, createEnhancedBridge, createEnhancedChildBridge, createEnhancedHostBridge, createHostBridge };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map