You've already forked bilibili-subtitle
消息传递重构&支持侧边面板
This commit is contained in:
@@ -1 +1,3 @@
|
|||||||
VITE_ENV=web-dev
|
VITE_ENV=web-dev
|
||||||
|
|
||||||
|
VITE_EXTENSION_ID=nejipfbcnfhckfpmlgibfommbnahchko
|
@@ -1,3 +1,4 @@
|
|||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
|
|
||||||
VITE_ENV=chrome
|
VITE_ENV=chrome
|
||||||
|
VITE_EXTENSION_ID=bciglihaegkdhoogebcdblfhppoilclp
|
@@ -17,6 +17,7 @@ export default defineManifest(async (env) => ({
|
|||||||
"version": `${major}.${minor}.${patch}`,
|
"version": `${major}.${minor}.${patch}`,
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
"sidePanel",
|
||||||
"storage",
|
"storage",
|
||||||
],
|
],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
|
16
sidepanel.html
Normal file
16
sidepanel.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<title>哔哩哔哩字幕列表</title>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||||
|
<link rel="icon" href="/favicon-128x128.png" sizes="128x128">
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="哔哩哔哩字幕" />
|
||||||
|
<meta name="keywords" content="哔哩哔哩,b站,字幕" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module" src="/src/Main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -1,6 +1,6 @@
|
|||||||
import {v4} from 'uuid'
|
import {v4} from 'uuid'
|
||||||
import {handleTask, initTaskService, tasksMap} from './taskService'
|
import {handleTask, initTaskService, tasksMap} from './taskService'
|
||||||
import {MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ADD_TASK, MESSAGE_TO_EXTENSION_GET_TASK, MESSAGE_TO_EXTENSION_SHOW_FLAG, MESSAGE_TO_INJECT_TOGGLE_DISPLAY} from '@/const'
|
import {MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ADD_TASK, MESSAGE_TO_EXTENSION_GET_TASK, MESSAGE_TO_EXTENSION_SHOW_FLAG, MESSAGE_TO_INJECT_TOGGLE_DISPLAY, STORAGE_ENV} from '@/const'
|
||||||
import ExtensionMessage from '@/messaging/ExtensionMessage'
|
import ExtensionMessage from '@/messaging/ExtensionMessage'
|
||||||
|
|
||||||
const setBadgeOk = async (tabId: number, ok: boolean) => {
|
const setBadgeOk = async (tabId: number, ok: boolean) => {
|
||||||
@@ -58,7 +58,7 @@ const methods: {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[MESSAGE_TO_EXTENSION_SHOW_FLAG]: async (params, context) => {
|
[MESSAGE_TO_EXTENSION_SHOW_FLAG]: async (params, context) => {
|
||||||
await setBadgeOk(context.sender?.tab?.id!, params.show)
|
await setBadgeOk(context.tabId!, params.show)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// 初始化backgroundMessage
|
// 初始化backgroundMessage
|
||||||
@@ -85,7 +85,21 @@ chrome.runtime.onMessage.addListener((event: MessageData, sender: chrome.runtime
|
|||||||
|
|
||||||
//点击扩展图标
|
//点击扩展图标
|
||||||
chrome.action.onClicked.addListener(async (tab) => {
|
chrome.action.onClicked.addListener(async (tab) => {
|
||||||
extensionMessage.broadcastMessageExact([tab.id!], MESSAGE_TARGET_INJECT, MESSAGE_TO_INJECT_TOGGLE_DISPLAY).catch(console.error)
|
chrome.storage.sync.get(STORAGE_ENV, envDatas => {
|
||||||
|
const envDataStr = envDatas[STORAGE_ENV]
|
||||||
|
const envData = envDataStr ? JSON.parse(envDataStr) : {}
|
||||||
|
if (envData.sidePanel) {
|
||||||
|
chrome.sidePanel.setOptions({
|
||||||
|
tabId: tab.id!,
|
||||||
|
path: 'sidepanel.html?tabId=' + tab.id,
|
||||||
|
})
|
||||||
|
chrome.sidePanel.open({
|
||||||
|
tabId: tab.id!,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
extensionMessage.broadcastMessageExact([tab.id!], MESSAGE_TARGET_INJECT, MESSAGE_TO_INJECT_TOGGLE_DISPLAY).catch(console.error)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
initTaskService()
|
initTaskService()
|
||||||
|
@@ -2,6 +2,7 @@ export const MESSAGE_TARGET_EXTENSION = 'BilibiliExtension'
|
|||||||
export const MESSAGE_TARGET_INJECT = 'BilibiliInject'
|
export const MESSAGE_TARGET_INJECT = 'BilibiliInject'
|
||||||
export const MESSAGE_TARGET_APP = 'BilibiliAPP'
|
export const MESSAGE_TARGET_APP = 'BilibiliAPP'
|
||||||
|
|
||||||
|
export const MESSAGE_TO_EXTENSION_ROUTE_MSG = 'routeMsg'
|
||||||
export const MESSAGE_TO_EXTENSION_ADD_TASK = 'addTask'
|
export const MESSAGE_TO_EXTENSION_ADD_TASK = 'addTask'
|
||||||
export const MESSAGE_TO_EXTENSION_GET_TASK = 'getTask'
|
export const MESSAGE_TO_EXTENSION_GET_TASK = 'getTask'
|
||||||
export const MESSAGE_TO_EXTENSION_SHOW_FLAG = 'showFlag'
|
export const MESSAGE_TO_EXTENSION_SHOW_FLAG = 'showFlag'
|
||||||
|
@@ -9,21 +9,19 @@ const useMessagingService = () => {
|
|||||||
|
|
||||||
//methods
|
//methods
|
||||||
const methods: {
|
const methods: {
|
||||||
[key: string]: (params: any, context: MethodContext) => boolean
|
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
||||||
} = useMemo(() => ({
|
} = useMemo(() => ({
|
||||||
[MESSAGE_TO_APP_SET_INFOS]: (params: any, context: MethodContext) => {
|
[MESSAGE_TO_APP_SET_INFOS]: async (params: any, context: MethodContext) => {
|
||||||
dispatch(setInfos(params.infos))
|
dispatch(setInfos(params.infos))
|
||||||
dispatch(setCurInfo(undefined))
|
dispatch(setCurInfo(undefined))
|
||||||
dispatch(setCurFetched(false))
|
dispatch(setCurFetched(false))
|
||||||
dispatch(setData(undefined))
|
dispatch(setData(undefined))
|
||||||
return true
|
|
||||||
},
|
},
|
||||||
[MESSAGE_TO_APP_SET_VIDEO_INFO]: (params: any, context: MethodContext) => {
|
[MESSAGE_TO_APP_SET_VIDEO_INFO]: async (params: any, context: MethodContext) => {
|
||||||
dispatch(setInfos(params.infos))
|
dispatch(setInfos(params.infos))
|
||||||
dispatch(setUrl(params.url))
|
dispatch(setUrl(params.url))
|
||||||
dispatch(setTitle(params.title))
|
dispatch(setTitle(params.title))
|
||||||
console.debug('video title: ', params.title)
|
console.debug('video title: ', params.title)
|
||||||
return true
|
|
||||||
},
|
},
|
||||||
}), [dispatch])
|
}), [dispatch])
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ import {
|
|||||||
setTempData,
|
setTempData,
|
||||||
} from '../redux/envReducer'
|
} from '../redux/envReducer'
|
||||||
import {EventBusContext} from '../Router'
|
import {EventBusContext} from '../Router'
|
||||||
import {EVENT_EXPAND, GEMINI_TOKENS, TOTAL_HEIGHT_MAX, TOTAL_HEIGHT_MIN, WORDS_MIN, WORDS_RATE, MESSAGE_TO_INJECT_GET_VIDEO_STATUS, MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO, MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO, MESSAGE_TO_INJECT_HIDE_TRANS, MESSAGE_TO_INJECT_UPDATETRANSRESULT} from '../const'
|
import {EVENT_EXPAND, GEMINI_TOKENS, TOTAL_HEIGHT_MAX, TOTAL_HEIGHT_MIN, WORDS_MIN, WORDS_RATE, MESSAGE_TO_INJECT_GET_VIDEO_STATUS, MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO, MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO, MESSAGE_TO_INJECT_HIDE_TRANS, MESSAGE_TO_INJECT_UPDATETRANSRESULT, TOTAL_HEIGHT_DEF} from '../const'
|
||||||
import {useAsyncEffect, useInterval} from 'ahooks'
|
import {useAsyncEffect, useInterval} from 'ahooks'
|
||||||
import {getModelMaxTokens, getWholeText} from '../util/biz_util'
|
import {getModelMaxTokens, getWholeText} from '../util/biz_util'
|
||||||
import {MESSAGE_TO_INJECT_GET_SUBTITLE} from '../const'
|
import {MESSAGE_TO_INJECT_GET_SUBTITLE} from '../const'
|
||||||
@@ -67,7 +67,12 @@ const useSubtitleService = () => {
|
|||||||
|
|
||||||
// 当前未展示 & (未折叠 | 自动展开) & 有列表 => 展示第一个
|
// 当前未展示 & (未折叠 | 自动展开) & 有列表 => 展示第一个
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!curInfo && (!fold || (envReady && envData.autoExpand)) && (infos != null) && infos.length > 0) {
|
let autoExpand = envData.autoExpand
|
||||||
|
// 如果显示在侧边栏,则自动展开
|
||||||
|
if (envData.sidePanel) {
|
||||||
|
autoExpand = true
|
||||||
|
}
|
||||||
|
if (!curInfo && (!fold || (envReady && autoExpand)) && (infos != null) && infos.length > 0) {
|
||||||
dispatch(setCurInfo(infos[0]))
|
dispatch(setCurInfo(infos[0]))
|
||||||
dispatch(setCurFetched(false))
|
dispatch(setCurFetched(false))
|
||||||
}
|
}
|
||||||
@@ -92,15 +97,18 @@ const useSubtitleService = () => {
|
|||||||
// 等待inject准备好
|
// 等待inject准备好
|
||||||
await injectWaiter.wait()
|
await injectWaiter.wait()
|
||||||
// 初始获取列表
|
// 初始获取列表
|
||||||
sendInject(MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO, {})
|
sendInject(MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO, {force: true})
|
||||||
// 初始获取设置信息
|
// 初始获取设置信息
|
||||||
sendInject(MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO, {}).then(info => {
|
sendInject(MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO, {}).then(info => {
|
||||||
dispatch(setNoVideo(info.noVideo))
|
dispatch(setNoVideo(info.noVideo))
|
||||||
if (info.totalHeight) {
|
if (envData.sidePanel) {
|
||||||
|
// get screen height
|
||||||
|
dispatch(setTotalHeight(window.innerHeight))
|
||||||
|
}else {
|
||||||
dispatch(setTotalHeight(Math.min(Math.max(info.totalHeight, TOTAL_HEIGHT_MIN), TOTAL_HEIGHT_MAX)))
|
dispatch(setTotalHeight(Math.min(Math.max(info.totalHeight, TOTAL_HEIGHT_MIN), TOTAL_HEIGHT_MAX)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [])
|
}, [envData.sidePanel])
|
||||||
|
|
||||||
// 更新当前位置
|
// 更新当前位置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -185,7 +193,7 @@ const useSubtitleService = () => {
|
|||||||
dispatch(setSegments(segments))
|
dispatch(setSegments(segments))
|
||||||
}, [data?.body, dispatch, envData])
|
}, [data?.body, dispatch, envData])
|
||||||
|
|
||||||
// 每秒更新当前视频时间
|
// 每0.5秒更新当前视频时间
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
sendInject(MESSAGE_TO_INJECT_GET_VIDEO_STATUS, {}).then(status => {
|
sendInject(MESSAGE_TO_INJECT_GET_VIDEO_STATUS, {}).then(status => {
|
||||||
dispatch(setCurrentTime(status.currentTime))
|
dispatch(setCurrentTime(status.currentTime))
|
||||||
|
@@ -15,12 +15,14 @@ const debug = (...args: any[]) => {
|
|||||||
|
|
||||||
//读取envData
|
//读取envData
|
||||||
const envDataStr = (await chrome.storage.sync.get(STORAGE_ENV))[STORAGE_ENV]
|
const envDataStr = (await chrome.storage.sync.get(STORAGE_ENV))[STORAGE_ENV]
|
||||||
|
let sidePanel: boolean | null = null
|
||||||
let manualInsert: boolean | null = null
|
let manualInsert: boolean | null = null
|
||||||
if (envDataStr) {
|
if (envDataStr) {
|
||||||
try {
|
try {
|
||||||
const envData = JSON.parse(envDataStr)
|
const envData = JSON.parse(envDataStr)
|
||||||
debug('envData: ', envData)
|
debug('envData: ', envData)
|
||||||
|
|
||||||
|
sidePanel = envData.sidePanel
|
||||||
manualInsert = envData.manualInsert
|
manualInsert = envData.manualInsert
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing envData:', error)
|
console.error('Error parsing envData:', error)
|
||||||
@@ -106,7 +108,7 @@ const debug = (...args: any[]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!manualInsert) {
|
if (!sidePanel && !manualInsert) {
|
||||||
const timerIframe = setInterval(function () {
|
const timerIframe = setInterval(function () {
|
||||||
var danmukuBox = document.getElementById('danmukuBox')
|
var danmukuBox = document.getElementById('danmukuBox')
|
||||||
if (danmukuBox) {
|
if (danmukuBox) {
|
||||||
@@ -124,9 +126,14 @@ const debug = (...args: any[]) => {
|
|||||||
let pagesMap: Record<string, any> = {}
|
let pagesMap: Record<string, any> = {}
|
||||||
|
|
||||||
let lastAidOrBvid: string | null = null
|
let lastAidOrBvid: string | null = null
|
||||||
const refreshVideoInfo = async () => {
|
const refreshVideoInfo = async (force: boolean = false) => {
|
||||||
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
if (force) {
|
||||||
if (!iframe) return
|
lastAidOrBvid = null
|
||||||
|
}
|
||||||
|
if (!sidePanel) {
|
||||||
|
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
||||||
|
if (!iframe) return
|
||||||
|
}
|
||||||
|
|
||||||
// fix: https://github.com/IndieKKY/bilibili-subtitle/issues/5
|
// fix: https://github.com/IndieKKY/bilibili-subtitle/issues/5
|
||||||
// 处理稍后再看的url( https://www.bilibili.com/list/watchlater?bvid=xxx&oid=xxx )
|
// 处理稍后再看的url( https://www.bilibili.com/list/watchlater?bvid=xxx&oid=xxx )
|
||||||
@@ -195,8 +202,10 @@ const debug = (...args: any[]) => {
|
|||||||
let lastAid: number | null = null
|
let lastAid: number | null = null
|
||||||
let lastCid: number | null = null
|
let lastCid: number | null = null
|
||||||
const refreshSubtitles = () => {
|
const refreshSubtitles = () => {
|
||||||
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
if (!sidePanel) {
|
||||||
if (!iframe) return
|
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
||||||
|
if (!iframe) return
|
||||||
|
}
|
||||||
|
|
||||||
const urlSearchParams = new URLSearchParams(window.location.search)
|
const urlSearchParams = new URLSearchParams(window.location.search)
|
||||||
const p = urlSearchParams.get('p') || 1
|
const p = urlSearchParams.get('p') || 1
|
||||||
@@ -282,7 +291,7 @@ const debug = (...args: any[]) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO]: async (params) => {
|
[MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO]: async (params) => {
|
||||||
refreshVideoInfo()
|
refreshVideoInfo(params.force)
|
||||||
},
|
},
|
||||||
[MESSAGE_TO_INJECT_UPDATETRANSRESULT]: async (params) => {
|
[MESSAGE_TO_INJECT_UPDATETRANSRESULT]: async (params) => {
|
||||||
runtime.showTrans = true
|
runtime.showTrans = true
|
||||||
@@ -350,10 +359,12 @@ const debug = (...args: any[]) => {
|
|||||||
runtime.injectMessage.init(methods)
|
runtime.injectMessage.init(methods)
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
if (!sidePanel) {
|
||||||
if (iframe != null && iframe.style.display !== 'none') {
|
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
||||||
refreshVideoInfo().catch(console.error)
|
if (!iframe || iframe.style.display === 'none') return
|
||||||
refreshSubtitles()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshVideoInfo().catch(console.error)
|
||||||
|
refreshSubtitles()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
})()
|
})()
|
||||||
|
@@ -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 {
|
class ExtensionMessage {
|
||||||
|
portIdToPort: Map<string, PortContext> = new Map()
|
||||||
methods?: {
|
methods?: {
|
||||||
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
||||||
}
|
}
|
||||||
|
innerMethods?: {
|
||||||
|
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
||||||
|
}
|
||||||
|
|
||||||
debug = (...args: any[]) => {
|
debug = (...args: any[]) => {
|
||||||
console.debug('[Extension Messaging]', ...args)
|
console.debug('[Extension Messaging]', ...args)
|
||||||
@@ -12,78 +26,162 @@ class ExtensionMessage {
|
|||||||
init = (methods: {
|
init = (methods: {
|
||||||
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
||||||
}) => {
|
}) => {
|
||||||
this.methods = methods
|
this.innerMethods = {
|
||||||
|
[MESSAGE_TO_EXTENSION_ROUTE_MSG]: (params: any, context: MethodContext) => {
|
||||||
/**
|
return this.broadcastMessageExact([context.tabId!], params.target, params.method, params.params)
|
||||||
* Note: Return true when sending a response asynchronously.
|
}
|
||||||
*/
|
}
|
||||||
chrome.runtime.onMessage.addListener((event: MessageData, sender: chrome.runtime.MessageSender, sendResponse: (result: any) => void) => {
|
this.methods = {...this.innerMethods, ...methods}
|
||||||
this.debug((sender.tab != null) ? `tab ${sender.tab.url ?? ''} => ` : 'extension => ', event)
|
|
||||||
|
|
||||||
// check event target
|
|
||||||
if (event.target !== MESSAGE_TARGET_EXTENSION) return
|
|
||||||
|
|
||||||
|
const handler = async (event: MessageData, portContext: PortContext): Promise<MessageResult> => {
|
||||||
|
const { tabId } = portContext
|
||||||
const method = this.methods?.[event.method]
|
const method = this.methods?.[event.method]
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
method(event.params, {
|
return method(event.params, {
|
||||||
from: event.from,
|
from: event.from,
|
||||||
event,
|
event,
|
||||||
sender,
|
tabId,
|
||||||
}).then(data => sendResponse({
|
// sender: portContext.port.sender,
|
||||||
|
}).then(data => ({
|
||||||
success: true,
|
success: true,
|
||||||
code: 200,
|
code: 200,
|
||||||
data,
|
data,
|
||||||
})).catch(err => {
|
})).catch(err => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
let message
|
return {
|
||||||
if (err instanceof Error) {
|
|
||||||
message = err.message
|
|
||||||
} else if (typeof err === 'string') {
|
|
||||||
message = err
|
|
||||||
} else {
|
|
||||||
message = 'error: ' + JSON.stringify(err)
|
|
||||||
}
|
|
||||||
sendResponse({
|
|
||||||
success: false,
|
success: false,
|
||||||
code: 500,
|
code: 500,
|
||||||
message,
|
message: err.message,
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
return true
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Unknown method:', event.method)
|
return {
|
||||||
sendResponse({
|
|
||||||
success: false,
|
success: false,
|
||||||
code: 501,
|
code: 501,
|
||||||
message: 'Unknown method: ' + event.method,
|
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) => {
|
broadcastMessageExact = async (tabIds: number[], target: string, method: string, params?: any) => {
|
||||||
for (const tabId of tabIds) {
|
//遍历portIdToPort
|
||||||
try {
|
const targetType = target === MESSAGE_TARGET_INJECT ? 'inject' : 'app'
|
||||||
await chrome.tabs.sendMessage(tabId, {target, method, params})
|
let resp: MessageResult | undefined
|
||||||
} catch (e) {
|
for (const portContext of this.portIdToPort.values()) {
|
||||||
console.error('send message to tab error', tabId, e)
|
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) => {
|
broadcastMessage = async (ignoreTabIds: number[] | undefined | null, target: string, method: string, params?: any) => {
|
||||||
const tabs = await chrome.tabs.query({
|
const tabs = await chrome.tabs.query({
|
||||||
discarded: false,
|
discarded: false,
|
||||||
})
|
})
|
||||||
for (const tab of tabs) {
|
const tabIds: number[] = tabs.map(tab => tab.id).filter(tabId => tabId != null) as number[]
|
||||||
try {
|
const filteredTabIds: number[] = tabIds.filter(tabId => !ignoreTabIds?.includes(tabId))
|
||||||
if (tab.id && ((ignoreTabIds == null) || !ignoreTabIds.includes(tab.id))) {
|
await this.broadcastMessageExact(filteredTabIds, target, method, params)
|
||||||
await chrome.tabs.sendMessage(tab.id, {target, method, params})
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('send message to tab error', tab.id, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,12 +1,15 @@
|
|||||||
import { MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT } from '@/const'
|
import { MESSAGE_TARGET_APP, MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ROUTE_MSG } from '@/const'
|
||||||
import { PostMessagePayload, PostMessageResponse, startListening } from 'postmessage-promise'
|
import { PostMessagePayload, PostMessageResponse, startListening } from 'postmessage-promise'
|
||||||
|
import PortMessageHandler from './PortMessageHandler'
|
||||||
|
|
||||||
class InjectMessage {
|
class InjectMessage {
|
||||||
|
port?: chrome.runtime.Port
|
||||||
|
portMessageHandler?: PortMessageHandler
|
||||||
//类实例
|
//类实例
|
||||||
methods?: {
|
methods?: {
|
||||||
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
||||||
}
|
}
|
||||||
postMessageToApp?: (method: string, payload: PostMessagePayload) => Promise<PostMessageResponse>
|
// postMessageToApp?: (method: string, payload: PostMessagePayload) => Promise<PostMessageResponse>
|
||||||
|
|
||||||
debug = (...args: any[]) => {
|
debug = (...args: any[]) => {
|
||||||
console.debug('[Inject Messaging]', ...args)
|
console.debug('[Inject Messaging]', ...args)
|
||||||
@@ -15,27 +18,30 @@ class InjectMessage {
|
|||||||
/**
|
/**
|
||||||
* @param sendResponse No matter what is returned, this method will definitely be called.
|
* @param sendResponse No matter what is returned, this method will definitely be called.
|
||||||
*/
|
*/
|
||||||
messageHandler = (event: MessageData, sender: chrome.runtime.MessageSender | null, sendResponse: (response?: MessageResult) => void) => {
|
messageHandler = async (event: MessageData): Promise<MessageResult> => {
|
||||||
const source = sender != null ? ((sender.tab != null) ? `tab ${sender.tab.url ?? ''}` : 'extension') : 'app'
|
this.debug(`${event.from} => `, JSON.stringify(event))
|
||||||
this.debug(`${source} => `, JSON.stringify(event))
|
|
||||||
|
|
||||||
// check event target
|
// check event target
|
||||||
if (event.target !== MESSAGE_TARGET_INJECT) return
|
if (event.target !== MESSAGE_TARGET_INJECT) return Promise.resolve({
|
||||||
|
success: false,
|
||||||
|
code: 501,
|
||||||
|
message: 'Target Error: ' + event.target,
|
||||||
|
})
|
||||||
|
|
||||||
const method = this.methods?.[event.method]
|
const method = this.methods?.[event.method]
|
||||||
if (method != null) {
|
if (method != null) {
|
||||||
method(event.params, {
|
return method(event.params, {
|
||||||
from: event.from,
|
from: event.from,
|
||||||
event,
|
event,
|
||||||
sender,
|
// sender,
|
||||||
}).then(data => {
|
}).then(data => {
|
||||||
// debug(`${source} <= `, event.method, JSON.stringify(data))
|
// debug(`${source} <= `, event.method, JSON.stringify(data))
|
||||||
return data
|
return {
|
||||||
}).then(data => sendResponse({
|
success: true,
|
||||||
success: true,
|
code: 200,
|
||||||
code: 200,
|
data,
|
||||||
data,
|
}
|
||||||
})).catch(err => {
|
}).catch(err => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
let message
|
let message
|
||||||
if (err instanceof Error) {
|
if (err instanceof Error) {
|
||||||
@@ -45,55 +51,60 @@ class InjectMessage {
|
|||||||
} else {
|
} else {
|
||||||
message = 'error: ' + JSON.stringify(err)
|
message = 'error: ' + JSON.stringify(err)
|
||||||
}
|
}
|
||||||
sendResponse({
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
code: 500,
|
code: 500,
|
||||||
message,
|
message,
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
return true
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Unknown method:', event.method)
|
return {
|
||||||
sendResponse({
|
|
||||||
success: false,
|
success: false,
|
||||||
code: 501,
|
code: 501,
|
||||||
message: 'Unknown method: ' + event.method,
|
message: 'Unknown method: ' + event.method,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(methods: {
|
init(methods: {
|
||||||
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
||||||
}) {
|
}) {
|
||||||
|
this.port = chrome.runtime.connect(import.meta.env.VITE_EXTENSION_ID, {
|
||||||
|
name: MESSAGE_TARGET_INJECT,
|
||||||
|
})
|
||||||
|
this.portMessageHandler = new PortMessageHandler<MessageData, MessageResult>(this.messageHandler, this.port)
|
||||||
|
this.portMessageHandler!.startListen()
|
||||||
|
this.portMessageHandler!.init('inject')
|
||||||
this.methods = methods
|
this.methods = methods
|
||||||
// listen message from app
|
// listen message from app
|
||||||
startListening({}).then(e => {
|
// startListening({}).then(e => {
|
||||||
const { postMessage, listenMessage, destroy } = e
|
// const { postMessage, listenMessage, destroy } = e
|
||||||
this.postMessageToApp = postMessage
|
// this.postMessageToApp = postMessage
|
||||||
listenMessage((method, params, sendResponse) => {
|
// listenMessage((method, params, sendResponse) => {
|
||||||
this.messageHandler({
|
// this.messageHandler({
|
||||||
from: 'app',
|
// from: 'app',
|
||||||
target: MESSAGE_TARGET_INJECT,
|
// target: MESSAGE_TARGET_INJECT,
|
||||||
method,
|
// method,
|
||||||
params,
|
// params,
|
||||||
}, null, sendResponse)
|
// }, null, sendResponse)
|
||||||
})
|
// })
|
||||||
}).catch(console.error)
|
// }).catch(console.error)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* listen message from extension
|
* listen message from extension
|
||||||
* Attention: return true if you need to sendResponse asynchronously
|
* Attention: return true if you need to sendResponse asynchronously
|
||||||
*/
|
*/
|
||||||
chrome.runtime.onMessage.addListener(this.messageHandler)
|
// chrome.runtime.onMessage.addListener(this.messageHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendExtension = async <T = any>(method: string, params?: any): Promise<T> => {
|
sendExtension = async <T = any>(method: string, params?: any): Promise<T> => {
|
||||||
return await chrome.runtime.sendMessage<MessageData, MessageResult>({
|
const messageData: MessageData = {
|
||||||
from: 'inject',
|
from: 'inject',
|
||||||
target: MESSAGE_TARGET_EXTENSION,
|
target: MESSAGE_TARGET_EXTENSION,
|
||||||
method,
|
method,
|
||||||
params: params ?? {},
|
params: params ?? {},
|
||||||
}).then((messageResult) => {
|
}
|
||||||
|
return await this.portMessageHandler!.sendMessage(messageData).then((messageResult) => {
|
||||||
if (messageResult.success) {
|
if (messageResult.success) {
|
||||||
return messageResult.data as T
|
return messageResult.data as T
|
||||||
} else {
|
} else {
|
||||||
@@ -103,20 +114,14 @@ class InjectMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendApp = async <T>(method: string, params: any): Promise<T> => {
|
sendApp = async <T>(method: string, params: any): Promise<T> => {
|
||||||
if (this.postMessageToApp != null) {
|
if (method === 'setVideoInfo') {
|
||||||
const messageResult = await this.postMessageToApp(method, params) as MessageResult | undefined
|
console.log('sendApp>>>', method, params)
|
||||||
if (messageResult != null) {
|
|
||||||
if (messageResult.success) {
|
|
||||||
return messageResult.data as T
|
|
||||||
} else {
|
|
||||||
throw new Error(messageResult.message)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('no response')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('error: postMessageToApp is not initialized')
|
|
||||||
}
|
}
|
||||||
|
return this.sendExtension(MESSAGE_TO_EXTENSION_ROUTE_MSG, {
|
||||||
|
target: MESSAGE_TARGET_APP,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
//暂时没用到
|
|
||||||
|
|
||||||
export type RespMsg<T = any> = {
|
export type RespMsg<T = any> = {
|
||||||
code: number
|
code: number
|
||||||
data?: T
|
data?: T
|
||||||
@@ -21,15 +19,29 @@ class PortMessageHandler<Req = any, Res = any> {
|
|||||||
private timeout: number
|
private timeout: number
|
||||||
private messageMap: Map<string, { resolve: (value: Res) => void, timer: number }>
|
private messageMap: Map<string, { resolve: (value: Res) => void, timer: number }>
|
||||||
private handler: (value: Req) => Promise<Res>
|
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 秒
|
constructor(handler: (value: Req) => Promise<Res>, port: chrome.runtime.Port, timeout = 30000) { // 默认超时 30 秒
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
this.messageMap = new Map();
|
this.messageMap = new Map();
|
||||||
this.handler = handler
|
this.handler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
init(type: 'inject' | 'app', tabId?: number) {
|
||||||
|
this.type = type
|
||||||
|
this.tabId = tabId
|
||||||
|
this.port.postMessage({
|
||||||
|
type,
|
||||||
|
tabId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
startListen() {
|
||||||
// 持久监听 port.onMessage
|
// 持久监听 port.onMessage
|
||||||
this.port.onMessage.addListener((msg: PortMessage<Req, Res>) => {
|
this.port.onMessage.addListener((msg: PortMessage<Req, Res>) => {
|
||||||
|
console.log('msg', this.type, this.tabId, msg)
|
||||||
const { msgId, msgType, req, res } = msg;
|
const { msgId, msgType, req, res } = msg;
|
||||||
if (msgType === 'req') {
|
if (msgType === 'req') {
|
||||||
this.handler(req!).then(res => {
|
this.handler(req!).then(res => {
|
||||||
@@ -77,6 +89,7 @@ class PortMessageHandler<Req = any, Res = any> {
|
|||||||
|
|
||||||
// 发送消息,并附带 ID
|
// 发送消息,并附带 ID
|
||||||
this.port.postMessage({ msgId, msgType: 'req', req });
|
this.port.postMessage({ msgId, msgType: 'req', req });
|
||||||
|
console.log('sendMessage>>>', msgId, 'req', req)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,28 +1,19 @@
|
|||||||
import { MESSAGE_TARGET_EXTENSION } from '@/const'
|
import { MESSAGE_TARGET_EXTENSION, MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ROUTE_MSG } from '@/const'
|
||||||
import { injectWaiter } from './useMessageService'
|
import { injectWaiter } from './useMessageService'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
import PortMessageHandler from './PortMessageHandler'
|
||||||
|
|
||||||
const useMessage = () => {
|
const useMessage = () => {
|
||||||
const sendExtension = useCallback(async <T = any>(method: string, params?: any) => {
|
const sendExtension = useCallback(async <T = any>(method: string, params?: any) => {
|
||||||
return await chrome.runtime.sendMessage<MessageData, MessageResult>({
|
// wait
|
||||||
|
const portMessageHandler = await injectWaiter.wait() as PortMessageHandler<MessageData, MessageResult>
|
||||||
|
// send message
|
||||||
|
const messageResult = await portMessageHandler.sendMessage({
|
||||||
from: 'app',
|
from: 'app',
|
||||||
target: MESSAGE_TARGET_EXTENSION,
|
target: MESSAGE_TARGET_EXTENSION,
|
||||||
method,
|
method,
|
||||||
params: params ?? {},
|
params: params ?? {},
|
||||||
}).then((messageResult) => {
|
}) as MessageResult | undefined
|
||||||
if (messageResult.success) {
|
|
||||||
return messageResult.data as T
|
|
||||||
} else {
|
|
||||||
throw new Error(messageResult.message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const sendInject = useCallback(async <T = any>(method: string, params?: any) => {
|
|
||||||
// wait
|
|
||||||
const postInjectMessage = await injectWaiter.wait()
|
|
||||||
// send message
|
|
||||||
const messageResult = await postInjectMessage(method, params) as MessageResult | undefined
|
|
||||||
if (messageResult != null) {
|
if (messageResult != null) {
|
||||||
if (messageResult.success) {
|
if (messageResult.success) {
|
||||||
return messageResult.data as T
|
return messageResult.data as T
|
||||||
@@ -34,6 +25,14 @@ const useMessage = () => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const sendInject = useCallback(async <T = any>(method: string, params?: any): Promise<T> => {
|
||||||
|
return await sendExtension(MESSAGE_TO_EXTENSION_ROUTE_MSG, {
|
||||||
|
target: MESSAGE_TARGET_INJECT,
|
||||||
|
method,
|
||||||
|
params: params ?? {},
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sendExtension,
|
sendExtension,
|
||||||
sendInject
|
sendInject
|
||||||
|
@@ -1,94 +1,159 @@
|
|||||||
import {useCallback, useEffect} from 'react'
|
import { useCallback, useEffect, useMemo } from 'react'
|
||||||
import {
|
import {
|
||||||
MESSAGE_TARGET_APP,
|
MESSAGE_TARGET_APP,
|
||||||
MESSAGE_TARGET_EXTENSION,
|
MESSAGE_TARGET_EXTENSION,
|
||||||
MESSAGE_TARGET_INJECT,
|
MESSAGE_TARGET_INJECT,
|
||||||
} from '@/const'
|
} from '@/const'
|
||||||
import {callServer, PostMessagePayload, PostMessageResponse} from 'postmessage-promise'
|
import { callServer, PostMessagePayload, PostMessageResponse } from 'postmessage-promise'
|
||||||
import {Waiter} from '@kky002/kky-util'
|
import { Waiter } from '@kky002/kky-util'
|
||||||
|
import PortMessageHandler from './PortMessageHandler'
|
||||||
|
|
||||||
const debug = (...args: any[]) => {
|
const debug = (...args: any[]) => {
|
||||||
console.debug('[App Messaging]', ...args)
|
console.debug('[App Messaging]', ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
let postInjectMessage: (method: string, params: PostMessagePayload) => Promise<PostMessageResponse> | undefined
|
let portMessageHandlerInit: boolean = false
|
||||||
|
let portMessageHandler: PortMessageHandler<MessageData, MessageResult> | undefined
|
||||||
|
// let postInjectMessage: (method: string, params: PostMessagePayload) => Promise<PostMessageResponse> | undefined
|
||||||
|
|
||||||
export const injectWaiter = new Waiter<typeof postInjectMessage>(() => ({
|
export const injectWaiter = new Waiter<any>(() => ({
|
||||||
finished: postInjectMessage != null,
|
finished: portMessageHandlerInit,
|
||||||
data: postInjectMessage
|
data: portMessageHandler
|
||||||
}), 100, 15000)
|
}), 100, 15000)
|
||||||
|
|
||||||
const useMessageService = (methods?: {
|
const useMessageService = (methods?: {
|
||||||
[key: string]: (params: any, context: MethodContext) => boolean
|
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
||||||
}) => {
|
}) => {
|
||||||
const messageHandler = useCallback((method: string, params: any, context: MethodContext): boolean => {
|
const messageHandler = useCallback(async (event: MessageData): Promise<MessageResult> => {
|
||||||
const handler = methods?.[method]
|
debug(`${event.from} => `, JSON.stringify(event))
|
||||||
if (handler != null) {
|
|
||||||
return handler(params, context)
|
// check event target
|
||||||
}else {
|
if (event.target !== MESSAGE_TARGET_APP) return {
|
||||||
debug('unknown message method: ', method)
|
success: false,
|
||||||
return false
|
code: 501,
|
||||||
|
message: 'Target Error: ' + event.target,
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = methods?.[event.method]
|
||||||
|
if (method != null) {
|
||||||
|
return method(event.params, {
|
||||||
|
from: event.from,
|
||||||
|
event,
|
||||||
|
}).then(data => {
|
||||||
|
// debug(`${source} <= `, event.method, JSON.stringify(data))
|
||||||
|
return {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
code: 500,
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
code: 501,
|
||||||
|
message: 'Unknown method: ' + event.method,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [methods])
|
}, [methods])
|
||||||
|
|
||||||
// connect to inject
|
const port = useMemo(() => {
|
||||||
useEffect(() => {
|
return chrome.runtime.connect(import.meta.env.VITE_EXTENSION_ID, {
|
||||||
let destroyFunc: (() => void) | undefined
|
name: MESSAGE_TARGET_APP,
|
||||||
|
|
||||||
const serverObject = {
|
|
||||||
server: window.parent, // openedWindow / window.parent / window.opener;
|
|
||||||
origin: '*', // target-window's origin or *
|
|
||||||
}
|
|
||||||
const options = {}
|
|
||||||
callServer(serverObject, options).then(e => {
|
|
||||||
const { postMessage, listenMessage, destroy } = e
|
|
||||||
postInjectMessage = postMessage
|
|
||||||
destroyFunc = destroy
|
|
||||||
|
|
||||||
listenMessage((method, params, sendResponse) => {
|
|
||||||
debug('inject => ', method, params)
|
|
||||||
|
|
||||||
const success = messageHandler(method, params, {
|
|
||||||
from: 'inject',
|
|
||||||
event: {
|
|
||||||
method,
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
sendResponse({
|
|
||||||
success,
|
|
||||||
code: success ? 200 : 500
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
debug('message ready')
|
|
||||||
}).catch(console.error)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
destroyFunc?.()
|
|
||||||
}
|
|
||||||
}, [messageHandler])
|
|
||||||
|
|
||||||
const extensionMessageCallback = useCallback((event: MessageData, sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void) => {
|
|
||||||
debug((sender.tab != null) ? `tab ${sender.tab.url??''} => ` : 'extension => ', JSON.stringify(event))
|
|
||||||
|
|
||||||
// check event target
|
|
||||||
if (!event || event.target !== MESSAGE_TARGET_APP) return
|
|
||||||
|
|
||||||
messageHandler(event.method, event.params, {
|
|
||||||
from: 'extension',
|
|
||||||
event,
|
|
||||||
sender,
|
|
||||||
})
|
})
|
||||||
}, [messageHandler])
|
}, [])
|
||||||
|
portMessageHandler = useMemo(() => {
|
||||||
|
if (messageHandler && port) {
|
||||||
|
const pmh = new PortMessageHandler<MessageData, MessageResult>(messageHandler, port)
|
||||||
|
|
||||||
// listen for message
|
//get tabId from url params
|
||||||
useEffect(() => {
|
let tabId = window.location.search.split('tabId=')[1]
|
||||||
chrome.runtime.onMessage.addListener(extensionMessageCallback)
|
if (!tabId) {
|
||||||
return () => {
|
pmh.startListen()
|
||||||
chrome.runtime.onMessage.removeListener(extensionMessageCallback)
|
pmh.init('app')
|
||||||
|
portMessageHandlerInit = true
|
||||||
|
}else {
|
||||||
|
pmh.startListen()
|
||||||
|
pmh.init('app', parseInt(tabId))
|
||||||
|
portMessageHandlerInit = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return pmh
|
||||||
}
|
}
|
||||||
}, [extensionMessageCallback])
|
}, [messageHandler, port])
|
||||||
|
|
||||||
|
// connect to inject
|
||||||
|
// useEffect(() => {
|
||||||
|
// let destroyFunc: (() => void) | undefined
|
||||||
|
|
||||||
|
// const serverObject = {
|
||||||
|
// server: window.parent, // openedWindow / window.parent / window.opener;
|
||||||
|
// origin: '*', // target-window's origin or *
|
||||||
|
// }
|
||||||
|
// const options = {}
|
||||||
|
// callServer(serverObject, options).then(e => {
|
||||||
|
// const { postMessage, listenMessage, destroy } = e
|
||||||
|
// postInjectMessage = postMessage
|
||||||
|
// destroyFunc = destroy
|
||||||
|
|
||||||
|
// listenMessage((method, params, sendResponse) => {
|
||||||
|
// debug('inject => ', method, params)
|
||||||
|
|
||||||
|
// const success = messageHandler(method, params, {
|
||||||
|
// from: 'inject',
|
||||||
|
// event: {
|
||||||
|
// method,
|
||||||
|
// params,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// sendResponse({
|
||||||
|
// success,
|
||||||
|
// code: success ? 200 : 500
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
|
||||||
|
// debug('message ready')
|
||||||
|
// }).catch(console.error)
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// destroyFunc?.()
|
||||||
|
// }
|
||||||
|
// }, [messageHandler])
|
||||||
|
|
||||||
|
// const extensionMessageCallback = useCallback((event: MessageData, sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void) => {
|
||||||
|
// debug((sender.tab != null) ? `tab ${sender.tab.url??''} => ` : 'extension => ', JSON.stringify(event))
|
||||||
|
|
||||||
|
// // check event target
|
||||||
|
// if (!event || event.target !== MESSAGE_TARGET_APP) return
|
||||||
|
|
||||||
|
// messageHandler(event.method, event.params, {
|
||||||
|
// from: 'extension',
|
||||||
|
// event,
|
||||||
|
// sender,
|
||||||
|
// })
|
||||||
|
// }, [messageHandler])
|
||||||
|
|
||||||
|
// // listen for message
|
||||||
|
// useEffect(() => {
|
||||||
|
// chrome.runtime.onMessage.addListener(extensionMessageCallback)
|
||||||
|
// return () => {
|
||||||
|
// chrome.runtime.onMessage.removeListener(extensionMessageCallback)
|
||||||
|
// }
|
||||||
|
// }, [extensionMessageCallback])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useMessageService
|
export default useMessageService
|
||||||
|
@@ -14,7 +14,6 @@ import {Toaster} from 'react-hot-toast'
|
|||||||
import {setTheme} from '../util/biz_util'
|
import {setTheme} from '../util/biz_util'
|
||||||
import useSearchService from '../hooks/useSearchService'
|
import useSearchService from '../hooks/useSearchService'
|
||||||
import useMessage from '../messaging/useMessage'
|
import useMessage from '../messaging/useMessage'
|
||||||
import useMessagingService from '../hooks/useMessagingService'
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
@@ -59,6 +59,7 @@ const FormItem = (props: {
|
|||||||
const OptionsPage = () => {
|
const OptionsPage = () => {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const envData = useAppSelector(state => state.env.envData)
|
const envData = useAppSelector(state => state.env.envData)
|
||||||
|
const {value: sidePanelValue, onChange: setSidePanelValue} = useEventChecked(envData.sidePanel)
|
||||||
const {value: autoInsertValue, onChange: setAutoInsertValue} = useEventChecked(!envData.manualInsert)
|
const {value: autoInsertValue, onChange: setAutoInsertValue} = useEventChecked(!envData.manualInsert)
|
||||||
const {value: autoExpandValue, onChange: setAutoExpandValue} = useEventChecked(envData.autoExpand)
|
const {value: autoExpandValue, onChange: setAutoExpandValue} = useEventChecked(envData.autoExpand)
|
||||||
// const {value: autoScrollValue, onChange: setAutoScrollValue} = useEventChecked(envData.autoScroll)
|
// const {value: autoScrollValue, onChange: setAutoScrollValue} = useEventChecked(envData.autoScroll)
|
||||||
@@ -112,6 +113,7 @@ const OptionsPage = () => {
|
|||||||
|
|
||||||
const onSave = useCallback(() => {
|
const onSave = useCallback(() => {
|
||||||
dispatch(setEnvData({
|
dispatch(setEnvData({
|
||||||
|
sidePanel: sidePanelValue,
|
||||||
manualInsert: !autoInsertValue,
|
manualInsert: !autoInsertValue,
|
||||||
autoExpand: autoExpandValue,
|
autoExpand: autoExpandValue,
|
||||||
aiType: aiTypeValue,
|
aiType: aiTypeValue,
|
||||||
@@ -142,7 +144,7 @@ const OptionsPage = () => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.close()
|
window.close()
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}, [dispatch, autoInsertValue, autoExpandValue, aiTypeValue, apiKeyValue, serverUrlValue, modelValue, customModelValue, customModelTokensValue, geminiApiKeyValue, translateEnableValue, languageValue, hideOnDisableAutoTranslateValue, themeValue, transDisplayValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, wordsValue, fetchAmountValue, fontSizeValue, promptsValue, searchEnabledValue, cnSearchEnabledValue, askEnabledValue])
|
}, [dispatch, sidePanelValue, autoInsertValue, autoExpandValue, aiTypeValue, apiKeyValue, serverUrlValue, modelValue, customModelValue, customModelTokensValue, geminiApiKeyValue, translateEnableValue, languageValue, hideOnDisableAutoTranslateValue, themeValue, transDisplayValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, wordsValue, fetchAmountValue, fontSizeValue, promptsValue, searchEnabledValue, cnSearchEnabledValue, askEnabledValue])
|
||||||
|
|
||||||
const onCancel = useCallback(() => {
|
const onCancel = useCallback(() => {
|
||||||
window.close()
|
window.close()
|
||||||
@@ -199,14 +201,18 @@ const OptionsPage = () => {
|
|||||||
return <div className='flex justify-center'>
|
return <div className='flex justify-center'>
|
||||||
<div className="w-2/3 max-w-[600px] flex flex-col gap-3 p-2">
|
<div className="w-2/3 max-w-[600px] flex flex-col gap-3 p-2">
|
||||||
<Section title='通用配置'>
|
<Section title='通用配置'>
|
||||||
<FormItem title='自动插入' htmlFor='autoInsert' tip='是否自动插入字幕列表(可以手动点击扩展图标插入)'>
|
<FormItem title='侧边栏' htmlFor='sidePanel' tip='字幕列表是否显示在侧边栏'>
|
||||||
|
<input id='sidePanel' type='checkbox' className='toggle toggle-primary' checked={sidePanelValue}
|
||||||
|
onChange={setSidePanelValue}/>
|
||||||
|
</FormItem>
|
||||||
|
{!sidePanelValue && <FormItem title='自动插入' htmlFor='autoInsert' tip='是否自动插入字幕列表(可以手动点击扩展图标插入)'>
|
||||||
<input id='autoInsert' type='checkbox' className='toggle toggle-primary' checked={autoInsertValue}
|
<input id='autoInsert' type='checkbox' className='toggle toggle-primary' checked={autoInsertValue}
|
||||||
onChange={setAutoInsertValue}/>
|
onChange={setAutoInsertValue}/>
|
||||||
</FormItem>
|
</FormItem>}
|
||||||
<FormItem title='自动展开' htmlFor='autoExpand' tip='是否视频有字幕时自动展开字幕列表'>
|
{!sidePanelValue && <FormItem title='自动展开' htmlFor='autoExpand' tip='是否视频有字幕时自动展开字幕列表'>
|
||||||
<input id='autoExpand' type='checkbox' className='toggle toggle-primary' checked={autoExpandValue}
|
<input id='autoExpand' type='checkbox' className='toggle toggle-primary' checked={autoExpandValue}
|
||||||
onChange={setAutoExpandValue}/>
|
onChange={setAutoExpandValue}/>
|
||||||
</FormItem>
|
</FormItem>}
|
||||||
<FormItem title='主题'>
|
<FormItem title='主题'>
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
<button onClick={onSelTheme1} className={classNames('btn btn-xs no-animation', (!themeValue || themeValue === 'system')?'btn-active':'')}>系统</button>
|
<button onClick={onSelTheme1} className={classNames('btn btn-xs no-animation', (!themeValue || themeValue === 'system')?'btn-active':'')}>系统</button>
|
||||||
|
4
src/typings.d.ts
vendored
4
src/typings.d.ts
vendored
@@ -18,10 +18,12 @@ interface MessageResult {
|
|||||||
interface MethodContext {
|
interface MethodContext {
|
||||||
from: MessageFrom
|
from: MessageFrom
|
||||||
event: any
|
event: any
|
||||||
sender?: chrome.runtime.MessageSender | null
|
tabId?: number
|
||||||
|
// sender?: chrome.runtime.MessageSender | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface EnvData {
|
interface EnvData {
|
||||||
|
sidePanel?: boolean
|
||||||
manualInsert?: boolean //是否手动插入字幕列表
|
manualInsert?: boolean //是否手动插入字幕列表
|
||||||
autoExpand?: boolean
|
autoExpand?: boolean
|
||||||
flagDot?: boolean
|
flagDot?: boolean
|
||||||
|
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -1,6 +1,7 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_EXTENSION_ID: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
Reference in New Issue
Block a user