From 4929850d0b4a652800905fc4f919fab9e57df5e5 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 15 Feb 2026 08:40:53 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BC=98=E5=8C=96=20MessageConnect=20?= =?UTF-8?q?=E7=9A=84=E8=AE=B0=E5=BF=86=E7=AE=A1=E7=90=86=E5=8F=8ACleanup?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/message/extension_message.ts | 62 +++++++++++++++++++++++---- packages/message/mock_message.ts | 4 +- packages/message/server.ts | 16 +++---- packages/message/types.ts | 2 +- packages/message/window_message.ts | 56 +++++++++++++++++++----- 5 files changed, 111 insertions(+), 29 deletions(-) diff --git a/packages/message/extension_message.ts b/packages/message/extension_message.ts index 7b6dfc745..d3f72b58c 100644 --- a/packages/message/extension_message.ts +++ b/packages/message/extension_message.ts @@ -1,4 +1,8 @@ +import EventEmitter from "eventemitter3"; import type { Message, MessageConnect, MessageSend, RuntimeMessageSender, TMessage, TMessageCommAction } from "./types"; +import { uuidv4 } from "@App/pkg/utils/uuid"; + +const listenerMgr = new EventEmitter(); // 单一管理器 export class ExtensionMessage implements Message { constructor(private backgroundPrimary = false) {} @@ -19,8 +23,6 @@ export class ExtensionMessage implements Message { if (lastError) { console.error("chrome.runtime.lastError in chrome.runtime.sendMessage:", lastError); // 通信API出错不回继续对话 - resolve = null; - return; } resolve!(resp); resolve = null; @@ -146,25 +148,69 @@ export class ExtensionMessage implements Message { } export class ExtensionMessageConnect implements MessageConnect { - constructor(private con: chrome.runtime.Port) {} + private readonly listenerId = `${uuidv4()}`; // 使用 uuidv4 确保唯一 + private con: chrome.runtime.Port | null; + private isSelfDisconnected = false; + + constructor(con: chrome.runtime.Port) { + this.con = con; // 强引用 + const handler = (msg: TMessage, _con: chrome.runtime.Port) => { + listenerMgr.emit(`onMessage:${this.listenerId}`, msg); + }; + const cleanup = (con: chrome.runtime.Port) => { + if (this.con) { + listenerMgr.removeAllListeners(`cleanup:${this.listenerId}`); + con.onMessage.removeListener(handler); + con.onDisconnect.removeListener(cleanup); + listenerMgr.emit(`onDisconnect:${this.listenerId}`, this.isSelfDisconnected); + listenerMgr.removeAllListeners(`onDisconnect:${this.listenerId}`); + listenerMgr.removeAllListeners(`onMessage:${this.listenerId}`); + this.con = null; + } + }; + con.onMessage.addListener(handler); + con.onDisconnect.addListener(cleanup); + listenerMgr.once(`cleanup:${this.listenerId}`, handler); + } sendMessage(data: TMessage) { - this.con.postMessage(data); + if (!this.con) { + console.warn("Attempted to sendMessage on a disconnected port."); + return; + } + this.con?.postMessage(data); } onMessage(callback: (data: TMessage) => void) { - this.con.onMessage.addListener(callback); + if (!this.con) { + console.error("onMessage Invalid Port"); + } + listenerMgr.addListener(`onMessage:${this.listenerId}`, callback); } disconnect() { - this.con.disconnect(); + if (!this.con) { + console.warn("Attempted to disconnect on a disconnected port."); + return; + } + this.isSelfDisconnected = true; + this.con?.disconnect(); + // Note: .disconnect() will NOT automatically trigger the 'cleanup' listener + listenerMgr.emit(`cleanup:${this.listenerId}`); } - onDisconnect(callback: () => void) { - this.con.onDisconnect.addListener(callback); + onDisconnect(callback: (isSelfDisconnected: boolean) => void) { + if (!this.con) { + console.error("onDisconnect Invalid Port"); + } + listenerMgr.once(`onDisconnect:${this.listenerId}`, callback); } getPort(): chrome.runtime.Port { + if (!this.con) { + console.error("Port is already disconnected."); + throw new Error("Port is already disconnected."); + } return this.con; } } diff --git a/packages/message/mock_message.ts b/packages/message/mock_message.ts index f4bc3ea47..6e143f9d6 100644 --- a/packages/message/mock_message.ts +++ b/packages/message/mock_message.ts @@ -16,10 +16,10 @@ export class MockMessageConnect implements MessageConnect { } disconnect(): void { - this.EE.emit("disconnect"); + this.EE.emit("disconnect", true); // MockMessageConnect 未有模拟由另一端触发 disconnect() 的情况 } - onDisconnect(callback: () => void): void { + onDisconnect(callback: (isSelfDisconnected: boolean) => void) { this.EE.on("disconnect", callback); } } diff --git a/packages/message/server.ts b/packages/message/server.ts index e69e7f0e7..05c8169e3 100644 --- a/packages/message/server.ts +++ b/packages/message/server.ts @@ -273,15 +273,15 @@ export function forwardMessage( senderTo: MessageSend, middleware?: ApiFunctionSync ) { - const handler = (params: any, fromCon: IGetSender) => { - const fromConnect = fromCon.getConnect(); + const handler = async (params: any, fromCon: IGetSender): Promise => { + const fromConnect: MessageConnect | undefined = fromCon.getConnect(); if (fromConnect) { - connect(senderTo, `${prefix}/${path}`, params).then((toCon: MessageConnect) => { - fromConnect.onMessage(toCon.sendMessage.bind(toCon)); - toCon.onMessage(fromConnect.sendMessage.bind(fromConnect)); - fromConnect.onDisconnect(toCon.disconnect.bind(toCon)); - toCon.onDisconnect(fromConnect.disconnect.bind(fromConnect)); - }); + const toCon: MessageConnect = await connect(senderTo, `${prefix}/${path}`, params); + fromConnect.onMessage(toCon.sendMessage.bind(toCon)); + toCon.onMessage(fromConnect.sendMessage.bind(fromConnect)); + fromConnect.onDisconnect(toCon.disconnect.bind(toCon)); + toCon.onDisconnect(fromConnect.disconnect.bind(fromConnect)); + return undefined; } else { return sendMessage(senderTo, prefix + "/" + path, params); } diff --git a/packages/message/types.ts b/packages/message/types.ts index 23cd59aaf..3c3e94a91 100644 --- a/packages/message/types.ts +++ b/packages/message/types.ts @@ -50,7 +50,7 @@ export interface MessageConnect { onMessage(callback: (data: TMessage) => void): void; sendMessage(data: TMessage): void; disconnect(): void; - onDisconnect(callback: () => void): void; + onDisconnect(callback: (isSelfDisconnected: boolean) => void): void; } export type ExtMessageSender = { diff --git a/packages/message/window_message.ts b/packages/message/window_message.ts index baedbbe30..affc57256 100644 --- a/packages/message/window_message.ts +++ b/packages/message/window_message.ts @@ -2,6 +2,8 @@ import type { Message, MessageConnect, MessageSend, RuntimeMessageSender, TMessa import { uuidv4 } from "@App/pkg/utils/uuid"; import EventEmitter from "eventemitter3"; +const listenerMgr = new EventEmitter(); // 单一管理器 + // 通过 window.postMessage/onmessage 实现通信 export interface PostMessage { @@ -121,19 +123,40 @@ export class WindowMessage implements Message { } export class WindowMessageConnect implements MessageConnect { + private readonly listenerId = `${uuidv4()}`; // 使用 uuidv4 确保唯一 + private target: PostMessage | null; + private isSelfDisconnected = false; + constructor( private messageId: string, - private EE: EventEmitter, - private target: PostMessage + EE: EventEmitter, + target: PostMessage ) { - this.onDisconnect(() => { - // 移除所有监听 - this.EE.removeAllListeners("connectMessage:" + this.messageId); - this.EE.removeAllListeners("disconnect:" + this.messageId); - }); + this.target = target; // 强引用 + const handler = (msg: TMessage) => { + listenerMgr.emit(`onMessage:${this.listenerId}`, msg); + }; + const cleanup = () => { + if (this.target) { + listenerMgr.removeAllListeners(`cleanup:${this.listenerId}`); + EE.removeAllListeners("connectMessage:" + this.messageId); // 模拟 con.onMessage.removeListener + EE.removeAllListeners("disconnect:" + this.messageId); // 模拟 con.onDisconnect.removeListener + listenerMgr.emit(`onDisconnect:${this.listenerId}`, this.isSelfDisconnected); + listenerMgr.removeAllListeners(`onDisconnect:${this.listenerId}`); + listenerMgr.removeAllListeners(`onMessage:${this.listenerId}`); + this.target = null; + } + }; + EE.addListener(`connectMessage:${this.messageId}`, handler); // 模拟 con.onMessage.addListener + EE.addListener(`disconnect:${this.messageId}`, cleanup); // 模拟 con.onDisconnect.addListener + listenerMgr.once(`cleanup:${this.listenerId}`, handler); } sendMessage(data: TMessage) { + if (!this.target) { + console.warn("Attempted to sendMessage on a disconnected port."); + return; + } const body: WindowMessageBody = { messageId: this.messageId, type: "connectMessage", @@ -143,20 +166,33 @@ export class WindowMessageConnect implements MessageConnect { } onMessage(callback: (data: TMessage) => void) { - this.EE.addListener(`connectMessage:${this.messageId}`, callback); + if (!this.target) { + console.error("onMessage Invalid Target"); + } + listenerMgr.addListener(`onMessage:${this.listenerId}`, callback); } disconnect() { + if (!this.target) { + console.warn("Attempted to disconnect on a disconnected port."); + return; + } + this.isSelfDisconnected = true; const body: WindowMessageBody = { messageId: this.messageId, type: "disconnect", data: null, }; this.target.postMessage(body); + // Note: .disconnect() will NOT automatically trigger the 'cleanup' listener + listenerMgr.emit(`cleanup:${this.listenerId}`); } - onDisconnect(callback: () => void) { - this.EE.addListener(`disconnect:${this.messageId}`, callback); + onDisconnect(callback: (isSelfDisconnected: boolean) => void) { + if (!this.target) { + console.error("onDisconnect Invalid Target"); + } + listenerMgr.once(`onDisconnect:${this.listenerId}`, callback); } } From d49eaf7913ab19bd699bb34f5038eb3d57eda543 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:46:41 +0900 Subject: [PATCH 2/6] handler -> cleanup --- packages/message/extension_message.ts | 2 +- packages/message/window_message.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/message/extension_message.ts b/packages/message/extension_message.ts index d3f72b58c..b565bb76b 100644 --- a/packages/message/extension_message.ts +++ b/packages/message/extension_message.ts @@ -170,7 +170,7 @@ export class ExtensionMessageConnect implements MessageConnect { }; con.onMessage.addListener(handler); con.onDisconnect.addListener(cleanup); - listenerMgr.once(`cleanup:${this.listenerId}`, handler); + listenerMgr.once(`cleanup:${this.listenerId}`, cleanup); } sendMessage(data: TMessage) { diff --git a/packages/message/window_message.ts b/packages/message/window_message.ts index affc57256..8a087d819 100644 --- a/packages/message/window_message.ts +++ b/packages/message/window_message.ts @@ -149,7 +149,7 @@ export class WindowMessageConnect implements MessageConnect { }; EE.addListener(`connectMessage:${this.messageId}`, handler); // 模拟 con.onMessage.addListener EE.addListener(`disconnect:${this.messageId}`, cleanup); // 模拟 con.onDisconnect.addListener - listenerMgr.once(`cleanup:${this.listenerId}`, handler); + listenerMgr.once(`cleanup:${this.listenerId}`, cleanup); } sendMessage(data: TMessage) { From bdc4a9ed443ad202a1e4cccbed7cd6b38a121cbc Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:50:23 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E6=AC=A1=E5=BA=8F=E5=8F=AF=E6=8E=A8?= =?UTF-8?q?=E5=89=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/message/extension_message.ts | 2 +- packages/message/window_message.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/message/extension_message.ts b/packages/message/extension_message.ts index b565bb76b..6f02c20fe 100644 --- a/packages/message/extension_message.ts +++ b/packages/message/extension_message.ts @@ -159,13 +159,13 @@ export class ExtensionMessageConnect implements MessageConnect { }; const cleanup = (con: chrome.runtime.Port) => { if (this.con) { + this.con = null; listenerMgr.removeAllListeners(`cleanup:${this.listenerId}`); con.onMessage.removeListener(handler); con.onDisconnect.removeListener(cleanup); listenerMgr.emit(`onDisconnect:${this.listenerId}`, this.isSelfDisconnected); listenerMgr.removeAllListeners(`onDisconnect:${this.listenerId}`); listenerMgr.removeAllListeners(`onMessage:${this.listenerId}`); - this.con = null; } }; con.onMessage.addListener(handler); diff --git a/packages/message/window_message.ts b/packages/message/window_message.ts index 8a087d819..0b6868588 100644 --- a/packages/message/window_message.ts +++ b/packages/message/window_message.ts @@ -138,13 +138,13 @@ export class WindowMessageConnect implements MessageConnect { }; const cleanup = () => { if (this.target) { + this.target = null; listenerMgr.removeAllListeners(`cleanup:${this.listenerId}`); EE.removeAllListeners("connectMessage:" + this.messageId); // 模拟 con.onMessage.removeListener EE.removeAllListeners("disconnect:" + this.messageId); // 模拟 con.onDisconnect.removeListener listenerMgr.emit(`onDisconnect:${this.listenerId}`, this.isSelfDisconnected); listenerMgr.removeAllListeners(`onDisconnect:${this.listenerId}`); listenerMgr.removeAllListeners(`onMessage:${this.listenerId}`); - this.target = null; } }; EE.addListener(`connectMessage:${this.messageId}`, handler); // 模拟 con.onMessage.addListener From a46bca678d95ee37d9d807f03f3b8bb5e45b1ae1 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:08:31 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E6=8A=9B=E5=87=BA=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=98=AF=E5=90=88=E7=90=86=E7=9A=84=EF=BC=8C=E4=B8=8D=E5=BA=94?= =?UTF-8?q?=E8=AF=A5=E5=B1=8F=E8=94=BD=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/message/extension_message.ts | 10 ++++++++-- packages/message/window_message.ts | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/message/extension_message.ts b/packages/message/extension_message.ts index 6f02c20fe..bd51c02d8 100644 --- a/packages/message/extension_message.ts +++ b/packages/message/extension_message.ts @@ -176,7 +176,8 @@ export class ExtensionMessageConnect implements MessageConnect { sendMessage(data: TMessage) { if (!this.con) { console.warn("Attempted to sendMessage on a disconnected port."); - return; + // 無法 sendMessage 不应该屏蔽错误 + throw new Error("Attempted to sendMessage on a disconnected port."); } this.con?.postMessage(data); } @@ -184,6 +185,8 @@ export class ExtensionMessageConnect implements MessageConnect { onMessage(callback: (data: TMessage) => void) { if (!this.con) { console.error("onMessage Invalid Port"); + // 無法監聽的話不应该屏蔽错误 + throw new Error("onMessage Invalid Target"); } listenerMgr.addListener(`onMessage:${this.listenerId}`, callback); } @@ -191,7 +194,8 @@ export class ExtensionMessageConnect implements MessageConnect { disconnect() { if (!this.con) { console.warn("Attempted to disconnect on a disconnected port."); - return; + // 重复 disconnect() 不应该屏蔽错误 + throw new Error("Attempted to disconnect on a disconnected port."); } this.isSelfDisconnected = true; this.con?.disconnect(); @@ -202,6 +206,8 @@ export class ExtensionMessageConnect implements MessageConnect { onDisconnect(callback: (isSelfDisconnected: boolean) => void) { if (!this.con) { console.error("onDisconnect Invalid Port"); + // 無法監聽的話不应该屏蔽错误 + throw new Error("onDisconnect Invalid Target"); } listenerMgr.once(`onDisconnect:${this.listenerId}`, callback); } diff --git a/packages/message/window_message.ts b/packages/message/window_message.ts index 0b6868588..9b6f6ef33 100644 --- a/packages/message/window_message.ts +++ b/packages/message/window_message.ts @@ -154,8 +154,9 @@ export class WindowMessageConnect implements MessageConnect { sendMessage(data: TMessage) { if (!this.target) { - console.warn("Attempted to sendMessage on a disconnected port."); - return; + console.error("Attempted to sendMessage on a disconnected port."); + // 無法 sendMessage 不应该屏蔽错误 + throw new Error("Attempted to sendMessage on a disconnected port."); } const body: WindowMessageBody = { messageId: this.messageId, @@ -168,6 +169,8 @@ export class WindowMessageConnect implements MessageConnect { onMessage(callback: (data: TMessage) => void) { if (!this.target) { console.error("onMessage Invalid Target"); + // 無法監聽的話不应该屏蔽错误 + throw new Error("onMessage Invalid Target"); } listenerMgr.addListener(`onMessage:${this.listenerId}`, callback); } @@ -175,7 +178,8 @@ export class WindowMessageConnect implements MessageConnect { disconnect() { if (!this.target) { console.warn("Attempted to disconnect on a disconnected port."); - return; + // 重复 disconnect() 不应该屏蔽错误 + throw new Error("Attempted to disconnect on a disconnected port."); } this.isSelfDisconnected = true; const body: WindowMessageBody = { @@ -191,6 +195,8 @@ export class WindowMessageConnect implements MessageConnect { onDisconnect(callback: (isSelfDisconnected: boolean) => void) { if (!this.target) { console.error("onDisconnect Invalid Target"); + // 無法監聽的話不应该屏蔽错误 + throw new Error("onDisconnect Invalid Target"); } listenerMgr.once(`onDisconnect:${this.listenerId}`, callback); } From b719ff82a22b5fe22809345c21e05de9da9f8479 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:09:40 +0900 Subject: [PATCH 5/6] Update packages/message/mock_message.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/message/mock_message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/message/mock_message.ts b/packages/message/mock_message.ts index 6e143f9d6..e49d04481 100644 --- a/packages/message/mock_message.ts +++ b/packages/message/mock_message.ts @@ -20,7 +20,7 @@ export class MockMessageConnect implements MessageConnect { } onDisconnect(callback: (isSelfDisconnected: boolean) => void) { - this.EE.on("disconnect", callback); + this.EE.once("disconnect", callback); } } From 2403c40331ad90610b2dc3595b5fc3eb54cf2505 Mon Sep 17 00:00:00 2001 From: cyfung1031 <44498510+cyfung1031@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:27:03 +0900 Subject: [PATCH 6/6] fix --- packages/message/extension_message.ts | 4 ++-- packages/message/window_message.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/message/extension_message.ts b/packages/message/extension_message.ts index bd51c02d8..e1fd75720 100644 --- a/packages/message/extension_message.ts +++ b/packages/message/extension_message.ts @@ -186,7 +186,7 @@ export class ExtensionMessageConnect implements MessageConnect { if (!this.con) { console.error("onMessage Invalid Port"); // 無法監聽的話不应该屏蔽错误 - throw new Error("onMessage Invalid Target"); + throw new Error("onMessage Invalid Port"); } listenerMgr.addListener(`onMessage:${this.listenerId}`, callback); } @@ -207,7 +207,7 @@ export class ExtensionMessageConnect implements MessageConnect { if (!this.con) { console.error("onDisconnect Invalid Port"); // 無法監聽的話不应该屏蔽错误 - throw new Error("onDisconnect Invalid Target"); + throw new Error("onDisconnect Invalid Port"); } listenerMgr.once(`onDisconnect:${this.listenerId}`, callback); } diff --git a/packages/message/window_message.ts b/packages/message/window_message.ts index 9b6f6ef33..9dee3bafe 100644 --- a/packages/message/window_message.ts +++ b/packages/message/window_message.ts @@ -154,9 +154,9 @@ export class WindowMessageConnect implements MessageConnect { sendMessage(data: TMessage) { if (!this.target) { - console.error("Attempted to sendMessage on a disconnected port."); + console.error("Attempted to sendMessage on a disconnected Target."); // 無法 sendMessage 不应该屏蔽错误 - throw new Error("Attempted to sendMessage on a disconnected port."); + throw new Error("Attempted to sendMessage on a disconnected Target."); } const body: WindowMessageBody = { messageId: this.messageId, @@ -177,9 +177,9 @@ export class WindowMessageConnect implements MessageConnect { disconnect() { if (!this.target) { - console.warn("Attempted to disconnect on a disconnected port."); + console.warn("Attempted to disconnect on a disconnected Target."); // 重复 disconnect() 不应该屏蔽错误 - throw new Error("Attempted to disconnect on a disconnected port."); + throw new Error("Attempted to disconnect on a disconnected Target."); } this.isSelfDisconnected = true; const body: WindowMessageBody = {