消息传递重构&支持侧边面板

This commit is contained in:
IndieKKY
2024-10-05 19:55:43 +08:00
parent c1c21c2fee
commit b283695b02
18 changed files with 454 additions and 214 deletions

View File

@@ -1,9 +1,23 @@
import { MESSAGE_TARGET_EXTENSION } from '@/const'
import { MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ROUTE_MSG } from '@/const'
import PortMessageHandler from './PortMessageHandler'
export type PortContext = {
id: string
name: string
tabId: number
type: 'inject' | 'app'
port: chrome.runtime.Port
portMessageHandler: PortMessageHandler
}
class ExtensionMessage {
portIdToPort: Map<string, PortContext> = new Map()
methods?: {
[key: string]: (params: any, context: MethodContext) => Promise<any>
}
innerMethods?: {
[key: string]: (params: any, context: MethodContext) => Promise<any>
}
debug = (...args: any[]) => {
console.debug('[Extension Messaging]', ...args)
@@ -12,78 +26,162 @@ class ExtensionMessage {
init = (methods: {
[key: string]: (params: any, context: MethodContext) => Promise<any>
}) => {
this.methods = methods
/**
* Note: Return true when sending a response asynchronously.
*/
chrome.runtime.onMessage.addListener((event: MessageData, sender: chrome.runtime.MessageSender, sendResponse: (result: any) => void) => {
this.debug((sender.tab != null) ? `tab ${sender.tab.url ?? ''} => ` : 'extension => ', event)
// check event target
if (event.target !== MESSAGE_TARGET_EXTENSION) return
this.innerMethods = {
[MESSAGE_TO_EXTENSION_ROUTE_MSG]: (params: any, context: MethodContext) => {
return this.broadcastMessageExact([context.tabId!], params.target, params.method, params.params)
}
}
this.methods = {...this.innerMethods, ...methods}
const handler = async (event: MessageData, portContext: PortContext): Promise<MessageResult> => {
const { tabId } = portContext
const method = this.methods?.[event.method]
if (method != null) {
method(event.params, {
return method(event.params, {
from: event.from,
event,
sender,
}).then(data => sendResponse({
tabId,
// sender: portContext.port.sender,
}).then(data => ({
success: true,
code: 200,
data,
})).catch(err => {
console.error(err)
let message
if (err instanceof Error) {
message = err.message
} else if (typeof err === 'string') {
message = err
} else {
message = 'error: ' + JSON.stringify(err)
}
sendResponse({
return {
success: false,
code: 500,
message,
})
message: err.message,
}
})
return true
} else {
console.error('Unknown method:', event.method)
sendResponse({
return {
success: false,
code: 501,
message: 'Unknown method: ' + event.method,
})
}
}
}
chrome.runtime.onConnect.addListener((port: chrome.runtime.Port) => {
this.debug('onConnect', port)
const id = crypto.randomUUID()
const name = port.name
const listener = async (firstMessage: any) => {
console.log('firstMessage', name, firstMessage)
let tabId = firstMessage.tabId
let type = firstMessage.type
if (tabId == null) {
//get current tabId
const tabs = await chrome.tabs.query({
active: true,
currentWindow: true,
})
tabId = tabs[0]?.id
console.log('current tabId: ', tabId)
}
if (tabId != null) {
// @ts-ignore
const portContext: PortContext = {id, name, tabId, port, type}
const portMessageHandler = new PortMessageHandler<MessageData, MessageResult>(async (value: MessageData) => {
return handler(value, portContext)
}, port)
portContext.portMessageHandler = portMessageHandler
this.portIdToPort.set(id, portContext)
// 移除监听
port.onMessage.removeListener(listener)
// 开始监听
portMessageHandler.startListen()
console.log('start listen>>>', name)
}else {
console.log('no tabId>>>', name)
}
}
port.onMessage.addListener(listener)
port.onDisconnect.addListener(() => {
this.portIdToPort.delete(id)
})
})
/**
* Note: Return true when sending a response asynchronously.
*/
// chrome.runtime.onMessage.addListener((event: MessageData, sender: chrome.runtime.MessageSender, sendResponse: (result: any) => void) => {
// this.debug((sender.tab != null) ? `tab ${sender.tab.url ?? ''} => ` : 'extension => ', event)
// // check event target
// if (event.target !== MESSAGE_TARGET_EXTENSION) return
// const method = this.methods?.[event.method]
// if (method != null) {
// method(event.params, {
// from: event.from,
// event,
// sender,
// }).then(data => sendResponse({
// success: true,
// code: 200,
// data,
// })).catch(err => {
// console.error(err)
// let message
// if (err instanceof Error) {
// message = err.message
// } else if (typeof err === 'string') {
// message = err
// } else {
// message = 'error: ' + JSON.stringify(err)
// }
// sendResponse({
// success: false,
// code: 500,
// message,
// })
// })
// return true
// } else {
// console.error('Unknown method:', event.method)
// sendResponse({
// success: false,
// code: 501,
// message: 'Unknown method: ' + event.method,
// })
// }
// })
}
broadcastMessageExact = async (tabIds: number[], target: string, method: string, params?: any) => {
for (const tabId of tabIds) {
try {
await chrome.tabs.sendMessage(tabId, {target, method, params})
} catch (e) {
console.error('send message to tab error', tabId, e)
//遍历portIdToPort
const targetType = target === MESSAGE_TARGET_INJECT ? 'inject' : 'app'
let resp: MessageResult | undefined
for (const portContext of this.portIdToPort.values()) {
if (tabIds.includes(portContext.tabId)) {
if (targetType === portContext.type) {
try {
const messageData: MessageData = {target, method, params, from: 'extension'}
resp = await portContext.portMessageHandler.sendMessage(messageData)
} catch (e) {
console.error('send message to port error', portContext.id, e)
}
}
}
}
return resp?.data
}
broadcastMessage = async (ignoreTabIds: number[] | undefined | null, target: string, method: string, params?: any) => {
const tabs = await chrome.tabs.query({
discarded: false,
})
for (const tab of tabs) {
try {
if (tab.id && ((ignoreTabIds == null) || !ignoreTabIds.includes(tab.id))) {
await chrome.tabs.sendMessage(tab.id, {target, method, params})
}
} catch (e) {
console.error('send message to tab error', tab.id, e)
}
}
const tabIds: number[] = tabs.map(tab => tab.id).filter(tabId => tabId != null) as number[]
const filteredTabIds: number[] = tabIds.filter(tabId => !ignoreTabIds?.includes(tabId))
await this.broadcastMessageExact(filteredTabIds, target, method, params)
}
}