mirror of
https://github.com/molvqingtai/WebChat.git
synced 2025-11-25 11:18:33 +08:00
refactor: migrate from webext-core/messaging to comctx
Replace @webext-core/messaging with comctx for extension messaging. Implement service-based architecture for AppAction and Notification with runtime message adapters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -65,15 +65,16 @@
|
||||
"@rtco/client": "^0.3.6",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@webcomponents/custom-elements": "^1.6.0",
|
||||
"@webext-core/messaging": "^2.3.0",
|
||||
"@webext-core/proxy-service": "^1.2.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cobe": "^0.6.5",
|
||||
"comctx": "^1.4.3",
|
||||
"danmu": "^0.18.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.23.22",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"imgcap": "^1.0.2",
|
||||
"lucide-react": "^0.544.0",
|
||||
"motion": "^12.23.22",
|
||||
"nanoid": "^5.1.6",
|
||||
|
||||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@@ -71,9 +71,6 @@ importers:
|
||||
'@webcomponents/custom-elements':
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
'@webext-core/messaging':
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0
|
||||
'@webext-core/proxy-service':
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1(@webext-core/messaging@2.3.0)(webextension-polyfill@0.12.0)
|
||||
@@ -86,6 +83,9 @@ importers:
|
||||
cobe:
|
||||
specifier: ^0.6.5
|
||||
version: 0.6.5
|
||||
comctx:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3
|
||||
danmu:
|
||||
specifier: ^0.18.1
|
||||
version: 0.18.1
|
||||
@@ -98,6 +98,9 @@ importers:
|
||||
idb-keyval:
|
||||
specifier: ^6.2.2
|
||||
version: 6.2.2
|
||||
imgcap:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
lucide-react:
|
||||
specifier: ^0.544.0
|
||||
version: 0.544.0(react@19.1.1)
|
||||
@@ -2183,6 +2186,9 @@ packages:
|
||||
colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
|
||||
comctx@1.4.3:
|
||||
resolution: {integrity: sha512-Z1fisV3l6kucW+GZDg3AI2+ffWGd+Q/RCBKGbpQ8oe6U3p6wmTM/NciP4RRgLYYDW4aZgZIXRUQz1YnFX1QsjQ==}
|
||||
|
||||
comma-separated-tokens@2.0.3:
|
||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||
|
||||
@@ -3154,6 +3160,9 @@ packages:
|
||||
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
imgcap@1.0.2:
|
||||
resolution: {integrity: sha512-KY04sGVRupPrRBIruSBDnBBEe/5iwyivnukzmhOBclIkdyMJcbfrlqZrSZjQi1254NJXxOtpB3ddmHUjdKNydg==}
|
||||
|
||||
immediate@3.0.6:
|
||||
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||
|
||||
@@ -7796,6 +7805,8 @@ snapshots:
|
||||
|
||||
colorette@2.0.20: {}
|
||||
|
||||
comctx@1.4.3: {}
|
||||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
commander@14.0.1: {}
|
||||
@@ -8959,6 +8970,8 @@ snapshots:
|
||||
|
||||
ignore@7.0.5: {}
|
||||
|
||||
imgcap@1.0.2: {}
|
||||
|
||||
immediate@3.0.6: {}
|
||||
|
||||
import-fresh@3.3.1:
|
||||
|
||||
@@ -1,69 +1,23 @@
|
||||
import { EVENT } from '@/constants/event'
|
||||
import { messenger } from '@/messenger'
|
||||
import { browser, defineBackground } from '#imports'
|
||||
import type { Browser } from 'wxt/browser'
|
||||
import { ProvideAdapter } from '@/service/adapter/runtimeMessage'
|
||||
import { defineProxy } from 'comctx'
|
||||
import { AppAction } from '@/service/AppAction'
|
||||
import { Notification } from '@/service/Notification'
|
||||
|
||||
export default defineBackground({
|
||||
type: 'module',
|
||||
main() {
|
||||
browser.action.onClicked.addListener(() => {
|
||||
browser.runtime.openOptionsPage()
|
||||
const [provideNotification] = defineProxy(() => new Notification(), {
|
||||
namespace: browser.runtime.id
|
||||
})
|
||||
const [provideAppAction] = defineProxy(() => new AppAction(), {
|
||||
namespace: browser.runtime.id
|
||||
})
|
||||
|
||||
const historyNotificationTabs = new Map<string, Browser.tabs.Tab>()
|
||||
messenger.onMessage(EVENT.OPTIONS_PAGE_OPEN, () => {
|
||||
browser.runtime.openOptionsPage()
|
||||
})
|
||||
provideNotification(new ProvideAdapter())
|
||||
|
||||
messenger.onMessage(EVENT.NOTIFICATION_PUSH, async ({ data: message, sender }) => {
|
||||
// Check if there is an active tab on the same site
|
||||
const tabs = await browser.tabs.query({ active: true })
|
||||
const hasActiveSomeSiteTab = tabs.some((tab) => {
|
||||
return new URL(tab.url!).origin === new URL(sender.tab!.url!).origin
|
||||
})
|
||||
const appAction = provideAppAction(new ProvideAdapter())
|
||||
|
||||
if (hasActiveSomeSiteTab) return
|
||||
|
||||
browser.notifications.create(message.id, {
|
||||
type: 'basic',
|
||||
iconUrl: message.userAvatar,
|
||||
title: message.username,
|
||||
message: message.body,
|
||||
contextMessage: sender.tab!.url!
|
||||
})
|
||||
historyNotificationTabs.set(message.id, sender.tab! as Browser.tabs.Tab)
|
||||
})
|
||||
messenger.onMessage(EVENT.NOTIFICATION_CLEAR, async ({ data: id }) => {
|
||||
browser.notifications.clear(id)
|
||||
})
|
||||
|
||||
browser.notifications.onButtonClicked.addListener(async (id) => {
|
||||
const fromTab = historyNotificationTabs.get(id)
|
||||
if (fromTab?.id) {
|
||||
try {
|
||||
const tab = await browser.tabs.get(fromTab.id)
|
||||
browser.tabs.update(tab.id!, { active: true, highlighted: true })
|
||||
browser.windows.update(tab.windowId!, { focused: true })
|
||||
} catch {
|
||||
browser.tabs.create({ url: fromTab.url })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
browser.notifications.onClicked.addListener(async (id) => {
|
||||
const fromTab = historyNotificationTabs.get(id)
|
||||
if (fromTab?.id) {
|
||||
try {
|
||||
const tab = await browser.tabs.get(fromTab.id)
|
||||
browser.tabs.update(tab.id!, { active: true })
|
||||
} catch {
|
||||
browser.tabs.create({ url: fromTab.url })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
browser.notifications.onClosed.addListener(async (id) => {
|
||||
historyNotificationTabs.delete(id)
|
||||
})
|
||||
browser.action.onClicked.addListener(() => appAction.openOptionsPage())
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@ import { motion, AnimatePresence } from 'framer-motion'
|
||||
|
||||
import { useRemeshDomain, useRemeshQuery, useRemeshSend } from 'remesh-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { EVENT } from '@/constants/event'
|
||||
import UserInfoDomain from '@/domain/UserInfo'
|
||||
import useTriggerAway from '@/hooks/useTriggerAway'
|
||||
import { checkDarkMode, cn } from '@/utils'
|
||||
@@ -17,9 +16,9 @@ import LogoIcon5 from '@/assets/images/logo-5.svg'
|
||||
import LogoIcon6 from '@/assets/images/logo-6.svg'
|
||||
import AppStatusDomain from '@/domain/AppStatus'
|
||||
import { getDay } from 'date-fns'
|
||||
import { messenger } from '@/messenger'
|
||||
import useDraggable from '@/hooks/useDraggable'
|
||||
import useWindowResize from '@/hooks/useWindowResize'
|
||||
import { AppActionImpl } from '@/domain/impls/AppAction'
|
||||
|
||||
export interface AppButtonProps {
|
||||
className?: string
|
||||
@@ -80,7 +79,7 @@ const AppButton: FC<AppButtonProps> = ({ className }) => {
|
||||
}
|
||||
|
||||
const handleOpenOptionsPage = () => {
|
||||
messenger.sendMessage(EVENT.OPTIONS_PAGE_OPEN, undefined)
|
||||
AppActionImpl.value.openOptionsPage()
|
||||
}
|
||||
|
||||
const handleToggleApp = () => {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
export enum EVENT {
|
||||
OPTIONS_PAGE_OPEN = `WEB_CHAT_OPTIONS_PAGE_OPEN`,
|
||||
APP_OPEN = 'WEB_CHAT_APP_OPEN',
|
||||
NOTIFICATION_PUSH = 'WEB_CHAT_NOTIFICATION_PUSH',
|
||||
NOTIFICATION_CLEAR = 'WEB_CHAT_NOTIFICATION_CLEAR'
|
||||
APP_OPEN = 'WEB_CHAT_APP_OPEN'
|
||||
}
|
||||
|
||||
13
src/domain/externs/AppAction.ts
Normal file
13
src/domain/externs/AppAction.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Remesh } from 'remesh'
|
||||
|
||||
export interface AppAction {
|
||||
openOptionsPage: () => Promise<void>
|
||||
}
|
||||
|
||||
export const AppActionExtern = Remesh.extern<AppAction>({
|
||||
default: {
|
||||
openOptionsPage: () => {
|
||||
throw new Error('"openOptionsPage" not implemented.')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -2,7 +2,7 @@ import { Remesh } from 'remesh'
|
||||
import type { TextMessage } from '@/domain/ChatRoom'
|
||||
|
||||
export interface Notification {
|
||||
push: (message: TextMessage) => Promise<string>
|
||||
push: (message: TextMessage) => Promise<string | void>
|
||||
}
|
||||
|
||||
export const NotificationExtern = Remesh.extern<Notification>({
|
||||
|
||||
13
src/domain/impls/AppAction.ts
Normal file
13
src/domain/impls/AppAction.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { browser } from '#imports'
|
||||
import { AppActionExtern, type AppAction } from '@/domain/externs/AppAction'
|
||||
|
||||
import { InjectAdapter } from '@/service/adapter/runtimeMessage'
|
||||
import { defineProxy } from 'comctx'
|
||||
|
||||
const [, injectAppAction] = defineProxy(() => ({}) as AppAction, {
|
||||
namespace: browser.runtime.id
|
||||
})
|
||||
|
||||
const appAction = injectAppAction(new InjectAdapter())
|
||||
|
||||
export const AppActionImpl = AppActionExtern.impl(appAction)
|
||||
@@ -1,13 +1,10 @@
|
||||
import { NotificationExtern } from '@/domain/externs/Notification'
|
||||
import type { TextMessage } from '@/domain/ChatRoom'
|
||||
import { EVENT } from '@/constants/event'
|
||||
import { messenger } from '@/messenger'
|
||||
import { NotificationExtern, type Notification } from '@/domain/externs/Notification'
|
||||
|
||||
class Notification {
|
||||
async push(message: TextMessage) {
|
||||
await messenger.sendMessage(EVENT.NOTIFICATION_PUSH, message)
|
||||
return message.id
|
||||
}
|
||||
}
|
||||
import { InjectAdapter } from '@/service/adapter/runtimeMessage'
|
||||
import { defineProxy } from 'comctx'
|
||||
|
||||
export const NotificationImpl = NotificationExtern.impl(new Notification())
|
||||
const [, injectNotification] = defineProxy(() => ({}) as Notification)
|
||||
|
||||
const notification = injectNotification(new InjectAdapter())
|
||||
|
||||
export const NotificationImpl = NotificationExtern.impl(notification)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { EVENT } from '@/constants/event'
|
||||
import { defineExtensionMessaging } from '@webext-core/messaging'
|
||||
import type { TextMessage } from '@/domain/ChatRoom'
|
||||
|
||||
interface ProtocolMap {
|
||||
[EVENT.OPTIONS_PAGE_OPEN]: () => void
|
||||
[EVENT.NOTIFICATION_PUSH]: (message: TextMessage) => void
|
||||
[EVENT.NOTIFICATION_CLEAR]: (id: string) => void
|
||||
}
|
||||
|
||||
export const messenger = defineExtensionMessaging<ProtocolMap>()
|
||||
8
src/service/AppAction/index.ts
Normal file
8
src/service/AppAction/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { AppAction as AppActionExternType } from '@/domain/externs/AppAction'
|
||||
import { browser } from '#imports'
|
||||
|
||||
export class AppAction implements AppActionExternType {
|
||||
async openOptionsPage() {
|
||||
await browser.runtime.openOptionsPage()
|
||||
}
|
||||
}
|
||||
49
src/service/Notification/index.ts
Normal file
49
src/service/Notification/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { Notification as NotificationExternType } from '@/domain/externs/Notification'
|
||||
import type { TextMessage } from '@/domain/ChatRoom'
|
||||
import { browser } from '#imports'
|
||||
import type { MessageTab } from '@/service/adapter/runtimeMessage'
|
||||
|
||||
export class Notification implements NotificationExternType {
|
||||
historyNotificationTabs = new Map<string, MessageTab>()
|
||||
constructor() {
|
||||
browser.notifications.onButtonClicked.addListener(async (id) => {
|
||||
const formTab = this.historyNotificationTabs.get(id)
|
||||
if (formTab?.id) {
|
||||
try {
|
||||
const tab = await browser.tabs.get(formTab.id)
|
||||
browser.tabs.update(tab.id!, { active: true, highlighted: true })
|
||||
browser.windows.update(tab.windowId!, { focused: true })
|
||||
} catch {
|
||||
browser.tabs.create({ url: formTab.url })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
browser.notifications.onClicked.addListener(async (id) => {
|
||||
const fromTab = this.historyNotificationTabs.get(id)
|
||||
if (fromTab?.id) {
|
||||
try {
|
||||
const tab = await browser.tabs.get(fromTab.id)
|
||||
browser.tabs.update(tab.id!, { active: true })
|
||||
} catch {
|
||||
browser.tabs.create({ url: fromTab.url })
|
||||
}
|
||||
}
|
||||
})
|
||||
browser.notifications.onClosed.addListener(async (id) => {
|
||||
this.historyNotificationTabs.delete(id)
|
||||
})
|
||||
}
|
||||
async push(message: TextMessage & { meta?: { tab?: MessageTab } }) {
|
||||
const tab = message.meta?.tab
|
||||
console.log(tab)
|
||||
const id = await browser.notifications.create({
|
||||
type: 'basic',
|
||||
iconUrl: message.userAvatar,
|
||||
title: message.username,
|
||||
message: message.body,
|
||||
contextMessage: tab?.url
|
||||
})
|
||||
tab && this.historyNotificationTabs.set(id, tab)
|
||||
}
|
||||
}
|
||||
46
src/service/adapter/runtimeMessage.ts
Normal file
46
src/service/adapter/runtimeMessage.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { browser } from '#imports'
|
||||
import type { Browser } from '#imports'
|
||||
import type { Adapter, Message, SendMessage, OnMessage } from 'comctx'
|
||||
|
||||
export interface MessageTab {
|
||||
id?: number
|
||||
url?: string
|
||||
}
|
||||
|
||||
export interface MessageMeta {
|
||||
tab?: MessageTab
|
||||
}
|
||||
|
||||
export class ProvideAdapter implements Adapter<MessageMeta> {
|
||||
sendMessage: SendMessage<MessageMeta> = async (message) => {
|
||||
const tabs = await browser.tabs.query({ url: message.meta.tab?.url })
|
||||
|
||||
tabs.map((tab) => browser.tabs.sendMessage(tab.id!, message))
|
||||
|
||||
browser.runtime.sendMessage(message)
|
||||
}
|
||||
|
||||
onMessage: OnMessage<MessageMeta> = (callback) => {
|
||||
const handler = (message: Partial<Message<MessageMeta>>, sender: Browser.runtime.MessageSender) => {
|
||||
callback({ ...message, meta: { tab: { id: sender.tab?.id, url: sender.tab?.url } } })
|
||||
}
|
||||
browser.runtime.onMessage.addListener(handler)
|
||||
return () => browser.runtime.onMessage.removeListener(handler)
|
||||
}
|
||||
}
|
||||
|
||||
export class InjectAdapter implements Adapter<MessageMeta> {
|
||||
sendMessage: SendMessage<MessageMeta> = (message) => {
|
||||
browser.runtime.sendMessage(browser.runtime.id, {
|
||||
...message,
|
||||
meta: { tab: { url: document.location.href } }
|
||||
})
|
||||
}
|
||||
onMessage: OnMessage<MessageMeta> = (callback) => {
|
||||
const handler = (message?: Partial<Message<MessageMeta>>) => {
|
||||
callback(message)
|
||||
}
|
||||
browser.runtime.onMessage.addListener(handler)
|
||||
return () => browser.runtime.onMessage.removeListener(handler)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user