This commit is contained in:
IndieKKY
2024-10-05 20:17:22 +08:00
parent 090651c4b5
commit 4189818939
6 changed files with 118 additions and 113 deletions

View File

@@ -1,5 +1,5 @@
import { MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ROUTE_MSG } from '@/consts/const' import { MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ROUTE_MSG } from '@/consts/const'
import PortMessageHandler from './PortMessageHandler' import Layer1Protocol from './Layer1Protocol'
export type PortContext = { export type PortContext = {
id: string id: string
@@ -7,7 +7,7 @@ export type PortContext = {
tabId: number tabId: number
type: 'inject' | 'app' type: 'inject' | 'app'
port: chrome.runtime.Port port: chrome.runtime.Port
portMessageHandler: PortMessageHandler portMessageHandler: Layer1Protocol
} }
class ExtensionMessage { class ExtensionMessage {
@@ -86,7 +86,7 @@ class ExtensionMessage {
if (tabId != null) { if (tabId != null) {
// @ts-ignore // @ts-ignore
const portContext: PortContext = {id, name, tabId, port, type} const portContext: PortContext = {id, name, tabId, port, type}
const portMessageHandler = new PortMessageHandler<MessageData, MessageResult>(async (value: MessageData) => { const portMessageHandler = new Layer1Protocol<MessageData, MessageResult>(async (value: MessageData) => {
return handler(value, portContext) return handler(value, portContext)
}, port) }, port)
portContext.portMessageHandler = portMessageHandler portContext.portMessageHandler = portMessageHandler

View File

@@ -1,9 +1,9 @@
import { MESSAGE_TARGET_APP, MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ROUTE_MSG } from '@/consts/const' import { MESSAGE_TARGET_APP, MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ROUTE_MSG } from '@/consts/const'
import PortMessageHandler from './PortMessageHandler' import Layer1Protocol from './Layer1Protocol'
class InjectMessage { class InjectMessage {
port?: chrome.runtime.Port port?: chrome.runtime.Port
portMessageHandler?: PortMessageHandler portMessageHandler?: Layer1Protocol
//类实例 //类实例
methods?: { methods?: {
[key: string]: (params: any, context: MethodContext) => Promise<any> [key: string]: (params: any, context: MethodContext) => Promise<any>
@@ -71,7 +71,7 @@ class InjectMessage {
this.port = chrome.runtime.connect(import.meta.env.VITE_EXTENSION_ID, { this.port = chrome.runtime.connect(import.meta.env.VITE_EXTENSION_ID, {
name: MESSAGE_TARGET_INJECT, name: MESSAGE_TARGET_INJECT,
}) })
this.portMessageHandler = new PortMessageHandler<MessageData, MessageResult>(this.messageHandler, this.port) this.portMessageHandler = new Layer1Protocol<MessageData, MessageResult>(this.messageHandler, this.port)
this.portMessageHandler!.startListen() this.portMessageHandler!.startListen()
this.portMessageHandler!.init('inject') this.portMessageHandler!.init('inject')
this.methods = methods this.methods = methods

View File

@@ -0,0 +1,107 @@
// 请求信息
type ReqMsg<L1Req = any, L1Res = any> = {
id: string
// 类型
type: 'req' | 'res'
// 请求
req?: L1Req
// 响应
res?: RespMsg<L1Res>
}
// 响应信息
type RespMsg<T = any> = {
code: number
data?: T
msg?: string
}
// 创建一个 Layer1Protocol 类,用于持久监听 port 并通过消息 ID 处理响应,支持超时
class Layer1Protocol<L1Req = any, L1Res = any> {
private port: chrome.runtime.Port
private timeout: number
private messageMap: Map<string, { resolve: (value: L1Res) => void, timer: number }>
private handler: (value: L1Req) => Promise<L1Res>
private type?: 'inject' | 'app'
private tabId?: number
constructor(handler: (value: L1Req) => Promise<L1Res>, port: chrome.runtime.Port, timeout = 30000) { // 默认超时 30 秒
this.port = port;
this.timeout = timeout;
this.messageMap = new Map();
this.handler = handler
}
init(type: 'inject' | 'app', tabId?: number) {
this.type = type
this.tabId = tabId
this.port.postMessage({
type,
tabId,
})
}
startListen() {
// 持久监听 port.onMessage
this.port.onMessage.addListener((msg: ReqMsg<L1Req, L1Res>) => {
const { id, type, req, res } = msg;
if (type === 'req') {
this.handler(req!).then(res => {
const response: RespMsg<L1Res> = {
code: 200,
msg: 'success',
data: res
}
this.port.postMessage({ id, type: 'res', res: response });
}).catch(error => {
const response: RespMsg<L1Res> = {
code: 500,
msg: error.message,
}
this.port.postMessage({ id, type: 'res', res: response });
});
} else if (type === 'res') {
if (this.messageMap.has(id)) {
const { resolve, timer } = this.messageMap.get(id)!;
// 清除超时定时器
clearTimeout(timer);
// 移除消息 ID
this.messageMap.delete(id);
// 通过 ID 找到对应的 Promise 并 resolve
resolve(res!.data!);
}else {
console.error('unknown response message id: ', id)
}
} else {
console.error('unknown message type: ', type)
}
});
}
// 使用 Promise 发送消息并等待响应,支持超时
sendMessage(req: L1Req): Promise<L1Res> {
const id = this._generateUniqueId();
return new Promise((resolve, reject) => {
// 设置一个超时定时器
const timer = setTimeout(() => {
// 超时后执行 reject 并从 Map 中删除
this.messageMap.delete(id);
reject(new Error(`Request timed out after ${this.timeout / 1000} seconds`));
}, this.timeout);
// 将 resolve 和 timer 函数与消息 ID 绑定,存入 Map
this.messageMap.set(id, { resolve, timer });
// 发送消息,并附带 ID
this.port.postMessage({ id, type: 'req', req });
});
}
// 生成唯一 ID简单示例可以使用更复杂的生成策略
_generateUniqueId() {
return crypto.randomUUID()
}
}
export default Layer1Protocol

View File

@@ -1,102 +0,0 @@
export type RespMsg<T = any> = {
code: number
data?: T
msg?: string
}
export type PortMessageType = 'req' | 'res'
export type PortMessage<Req = any, Res = any> = {
msgId: string
msgType: PortMessageType
req?: Req
res?: RespMsg<Res>
}
// 创建一个 PortMessageHandler 类,用于持久监听 port 并通过消息 ID 处理响应,支持超时
class PortMessageHandler<Req = any, Res = any> {
private port: chrome.runtime.Port
private timeout: number
private messageMap: Map<string, { resolve: (value: Res) => void, timer: number }>
private handler: (value: Req) => Promise<Res>
private type?: 'inject' | 'app'
private tabId?: number
constructor(handler: (value: Req) => Promise<Res>, port: chrome.runtime.Port, timeout = 30000) { // 默认超时 30 秒
this.port = port;
this.timeout = timeout;
this.messageMap = new Map();
this.handler = handler
}
init(type: 'inject' | 'app', tabId?: number) {
this.type = type
this.tabId = tabId
this.port.postMessage({
type,
tabId,
})
}
startListen() {
// 持久监听 port.onMessage
this.port.onMessage.addListener((msg: PortMessage<Req, Res>) => {
console.log('msg', this.type, this.tabId, msg)
const { msgId, msgType, req, res } = msg;
if (msgType === 'req') {
this.handler(req!).then(res => {
const response: RespMsg<Res> = {
code: 200,
msg: 'success',
data: res
}
this.port.postMessage({ msgId, msgType: 'res', res: response });
}).catch(error => {
const response: RespMsg<Res> = {
code: 500,
msg: error.message,
}
this.port.postMessage({ msgId, msgType: 'res', res: response });
});
} else if (msgType === 'res') {
if (this.messageMap.has(msgId)) {
const { resolve, timer } = this.messageMap.get(msgId)!;
// 清除超时定时器
clearTimeout(timer);
// 处理完毕后,移除该消息 ID
this.messageMap.delete(msgId);
// 通过 ID 找到对应的 Promise 并 resolve
resolve(res!.data!);
}
}
});
}
// 使用 Promise 发送消息并等待响应,支持超时
sendMessage(req: Req): Promise<Res> {
const msgId = this._generateUniqueId();
return new Promise((resolve, reject) => {
// 设置一个超时定时器
const timer = setTimeout(() => {
// 超时后执行 reject 并从 Map 中删除
this.messageMap.delete(msgId);
reject(new Error(`Request timed out after ${this.timeout / 1000} seconds`));
}, this.timeout);
// 将 resolve 和 timer 函数与消息 ID 绑定,存入 Map
this.messageMap.set(msgId, { resolve, timer });
// 发送消息,并附带 ID
this.port.postMessage({ msgId, msgType: 'req', req });
console.log('sendMessage>>>', msgId, 'req', req)
});
}
// 生成唯一 ID简单示例可以使用更复杂的生成策略
_generateUniqueId() {
return crypto.randomUUID()
}
}
export default PortMessageHandler

View File

@@ -1,12 +1,12 @@
import { MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ROUTE_MSG } from '@/consts/const' import { MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ROUTE_MSG } from '@/consts/const'
import { injectWaiter } from './useMessageService' import { injectWaiter } from './useMessageService'
import { useCallback } from 'react' import { useCallback } from 'react'
import PortMessageHandler from './PortMessageHandler' import Layer1Protocol from './Layer1Protocol'
const useMessage = () => { const useMessage = () => {
const sendExtension = useCallback(async <T = any>(method: string, params?: any) => { const sendExtension = useCallback(async <T = any>(method: string, params?: any) => {
// wait // wait
const portMessageHandler = await injectWaiter.wait() as PortMessageHandler<MessageData, MessageResult> const portMessageHandler = await injectWaiter.wait() as Layer1Protocol<MessageData, MessageResult>
// send message // send message
const messageResult = await portMessageHandler.sendMessage({ const messageResult = await portMessageHandler.sendMessage({
from: 'app', from: 'app',

View File

@@ -3,14 +3,14 @@ import {
MESSAGE_TARGET_APP, MESSAGE_TARGET_APP,
} from '@/consts/const' } from '@/consts/const'
import { Waiter } from '@kky002/kky-util' import { Waiter } from '@kky002/kky-util'
import PortMessageHandler from './PortMessageHandler' import Layer1Protocol from './Layer1Protocol'
const debug = (...args: any[]) => { const debug = (...args: any[]) => {
console.debug('[App Messaging]', ...args) console.debug('[App Messaging]', ...args)
} }
let portMessageHandlerInit: boolean = false let portMessageHandlerInit: boolean = false
let portMessageHandler: PortMessageHandler<MessageData, MessageResult> | undefined let portMessageHandler: Layer1Protocol<MessageData, MessageResult> | undefined
// let postInjectMessage: (method: string, params: PostMessagePayload) => Promise<PostMessageResponse> | undefined // let postInjectMessage: (method: string, params: PostMessagePayload) => Promise<PostMessageResponse> | undefined
export const injectWaiter = new Waiter<any>(() => ({ export const injectWaiter = new Waiter<any>(() => ({
@@ -75,7 +75,7 @@ const useMessageService = (methods?: {
}, []) }, [])
portMessageHandler = useMemo(() => { portMessageHandler = useMemo(() => {
if (messageHandler && port) { if (messageHandler && port) {
const pmh = new PortMessageHandler<MessageData, MessageResult>(messageHandler, port) const pmh = new Layer1Protocol<MessageData, MessageResult>(messageHandler, port)
//get tabId from url params //get tabId from url params
let tabId = window.location.search.split('tabId=')[1] let tabId = window.location.search.split('tabId=')[1]