2024-09-20 15:15:24 +08:00
|
|
|
import { nanoid } from 'nanoid';
|
|
|
|
|
|
2024-09-22 02:06:34 +08:00
|
|
|
export type IframeMessageData<T> = {
|
2024-09-20 15:15:24 +08:00
|
|
|
action: string;
|
|
|
|
|
id: string;
|
2024-09-22 02:06:34 +08:00
|
|
|
data?: T;
|
2024-09-20 15:15:24 +08:00
|
|
|
replyId?: string;
|
2024-09-22 02:06:34 +08:00
|
|
|
errorCode?: number; //0为成功
|
|
|
|
|
message?: string;
|
2024-09-20 15:15:24 +08:00
|
|
|
};
|
|
|
|
|
|
2024-09-22 02:06:34 +08:00
|
|
|
export type IframeMessageReq<T = any, R = any> = {
|
|
|
|
|
req: IframeMessageData<T>;
|
|
|
|
|
onReply: (data: IframeMessageData<R>) => void;
|
2024-09-20 15:15:24 +08:00
|
|
|
};
|
|
|
|
|
|
2024-09-22 02:06:34 +08:00
|
|
|
export class IframeException extends Error {
|
|
|
|
|
code?: number = 0;
|
|
|
|
|
constructor(data: IframeMessageData<any>) {
|
|
|
|
|
super(data.message);
|
|
|
|
|
this.code = data.errorCode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 15:15:24 +08:00
|
|
|
export class IframeClient {
|
2024-09-22 02:06:34 +08:00
|
|
|
requestQueue: Record<string, IframeMessageReq> = {};
|
2024-09-20 15:15:24 +08:00
|
|
|
//当前客户端是否是父级页面
|
|
|
|
|
iframe?: HTMLIFrameElement;
|
2024-09-23 14:04:33 +08:00
|
|
|
onError?: any;
|
2024-09-22 02:06:34 +08:00
|
|
|
|
|
|
|
|
handlers: Record<string, (data: IframeMessageData<any>) => Promise<void>> = {};
|
2024-09-23 14:04:33 +08:00
|
|
|
constructor(iframe?: HTMLIFrameElement, onError?: (e: any) => void) {
|
2024-09-20 15:15:24 +08:00
|
|
|
this.iframe = iframe;
|
2024-09-23 14:04:33 +08:00
|
|
|
this.onError = onError;
|
2024-09-22 02:06:34 +08:00
|
|
|
window.addEventListener('message', async (event: MessageEvent<IframeMessageData<any>>) => {
|
2024-09-20 15:15:24 +08:00
|
|
|
const data = event.data;
|
2024-09-22 02:06:34 +08:00
|
|
|
if (data.action) {
|
2024-09-23 13:23:49 +08:00
|
|
|
console.log(`收到消息[isSub:${this.isInFrame()}]`, data);
|
2024-09-22 02:06:34 +08:00
|
|
|
try {
|
|
|
|
|
const handler = this.handlers[data.action];
|
|
|
|
|
if (handler) {
|
|
|
|
|
const res = await handler(data);
|
|
|
|
|
if (data.id && data.action !== 'reply') {
|
|
|
|
|
await this.send('reply', res, data.id);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-09-23 14:04:33 +08:00
|
|
|
throw new Error(`action:${data.action} 未注册处理器,可能版本过低`);
|
2024-09-22 02:06:34 +08:00
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
2024-09-23 13:23:49 +08:00
|
|
|
console.error(e);
|
2024-09-22 02:06:34 +08:00
|
|
|
await this.send('reply', {}, data.id, 500, e.message);
|
2024-09-20 15:15:24 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-09-22 02:06:34 +08:00
|
|
|
|
|
|
|
|
this.register('reply', async data => {
|
|
|
|
|
const req = this.requestQueue[data.replyId!];
|
|
|
|
|
if (req) {
|
|
|
|
|
req.onReply(data);
|
|
|
|
|
delete this.requestQueue[data.replyId!];
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-09-20 15:15:24 +08:00
|
|
|
}
|
|
|
|
|
isInFrame() {
|
|
|
|
|
return window.self !== window.top;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-19 11:48:23 +08:00
|
|
|
register<T = any>(action: string, handler: (data: IframeMessageData<T>) => Promise<any>) {
|
2024-09-22 02:06:34 +08:00
|
|
|
this.handlers[action] = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async send<R = any, T = any>(action: string, data?: T, replyId?: string, errorCode?: number, message?: string): Promise<IframeMessageData<R>> {
|
2024-09-23 14:04:33 +08:00
|
|
|
try {
|
|
|
|
|
return await this.doSend<R, T>(action, data, replyId, errorCode, message);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (this.onError) {
|
|
|
|
|
this.onError(e);
|
|
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async doSend<R = any, T = any>(action: string, data?: T, replyId?: string, errorCode?: number, message?: string): Promise<IframeMessageData<R>> {
|
2024-09-22 02:06:34 +08:00
|
|
|
const reqMessageData: IframeMessageData<T> = {
|
2024-09-20 15:15:24 +08:00
|
|
|
id: nanoid(),
|
|
|
|
|
action,
|
|
|
|
|
data,
|
|
|
|
|
replyId,
|
2024-09-22 02:06:34 +08:00
|
|
|
errorCode,
|
|
|
|
|
message,
|
2024-09-20 15:15:24 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
2024-09-23 13:23:49 +08:00
|
|
|
const onReply = (reply: IframeMessageData<R>) => {
|
2024-09-22 02:06:34 +08:00
|
|
|
if (reply.errorCode && reply.errorCode > 0) {
|
|
|
|
|
reject(new IframeException(reply));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-09-20 15:15:24 +08:00
|
|
|
resolve(reply);
|
|
|
|
|
};
|
2024-09-22 02:06:34 +08:00
|
|
|
this.requestQueue[reqMessageData.id] = {
|
2024-09-20 15:15:24 +08:00
|
|
|
req: reqMessageData,
|
|
|
|
|
onReply,
|
|
|
|
|
};
|
|
|
|
|
try {
|
2024-09-23 13:23:49 +08:00
|
|
|
console.log(`send message[isSub:${this.isInFrame()}]:`, reqMessageData);
|
2024-09-20 15:15:24 +08:00
|
|
|
if (!this.iframe) {
|
|
|
|
|
if (!window.parent) {
|
|
|
|
|
reject('当前页面不在 iframe 中');
|
|
|
|
|
}
|
|
|
|
|
window.parent.postMessage(reqMessageData, '*');
|
|
|
|
|
} else {
|
|
|
|
|
//子页面
|
|
|
|
|
this.iframe.contentWindow?.postMessage(reqMessageData, '*');
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
reject(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|