2026-01-20 17:36:33 +08:00
|
|
|
/**
|
2026-01-24 19:29:27 +08:00
|
|
|
* Background Script (Service Worker) - Stateless Message Relay
|
2026-01-20 17:36:33 +08:00
|
|
|
*
|
2026-01-26 21:03:51 +08:00
|
|
|
* Completely stateless. Only two responsibilities:
|
|
|
|
|
* 1. Relay AGENT_TO_PAGE messages from SidePanel to ContentScript
|
|
|
|
|
* 2. Broadcast TAB_CHANGE events to all extension pages
|
2026-01-20 17:36:33 +08:00
|
|
|
*/
|
|
|
|
|
import {
|
2026-01-26 21:03:51 +08:00
|
|
|
type AgentToPageMessage,
|
|
|
|
|
type TabChangeMessage,
|
2026-01-24 19:29:27 +08:00
|
|
|
isExtensionMessage,
|
2026-01-26 19:33:57 +08:00
|
|
|
} from '../agent/protocol'
|
2026-01-20 17:36:33 +08:00
|
|
|
|
2026-01-24 19:29:27 +08:00
|
|
|
// ============================================================================
|
2026-01-26 21:03:51 +08:00
|
|
|
// Message Relay
|
2026-01-24 19:29:27 +08:00
|
|
|
// ============================================================================
|
2026-01-20 17:36:33 +08:00
|
|
|
|
2026-01-24 19:29:27 +08:00
|
|
|
chrome.runtime.onMessage.addListener(
|
|
|
|
|
(
|
|
|
|
|
message: unknown,
|
2026-01-26 21:03:51 +08:00
|
|
|
_sender: chrome.runtime.MessageSender,
|
2026-01-24 19:29:27 +08:00
|
|
|
sendResponse: (response?: unknown) => void
|
|
|
|
|
): boolean => {
|
|
|
|
|
if (!isExtensionMessage(message)) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2026-01-20 17:36:33 +08:00
|
|
|
|
2026-01-26 21:03:51 +08:00
|
|
|
if (message.type === 'AGENT_TO_PAGE') {
|
|
|
|
|
handleAgentToPage(message as AgentToPageMessage, sendResponse)
|
|
|
|
|
return true // Async response
|
2026-01-24 19:29:27 +08:00
|
|
|
}
|
2026-01-26 21:03:51 +08:00
|
|
|
|
|
|
|
|
return false
|
2026-01-24 19:29:27 +08:00
|
|
|
}
|
|
|
|
|
)
|
2026-01-20 17:36:33 +08:00
|
|
|
|
|
|
|
|
/**
|
2026-01-24 19:29:27 +08:00
|
|
|
* Forward RPC call from SidePanel to ContentScript
|
2026-01-20 17:36:33 +08:00
|
|
|
*/
|
2026-01-26 21:03:51 +08:00
|
|
|
async function handleAgentToPage(
|
|
|
|
|
msg: AgentToPageMessage,
|
2026-01-26 19:17:52 +08:00
|
|
|
sendResponse: (response: { success: boolean; result?: unknown; error?: string }) => void
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const { tabId, method, args } = msg
|
2026-01-24 19:29:27 +08:00
|
|
|
|
|
|
|
|
try {
|
2026-01-26 21:03:51 +08:00
|
|
|
// Forward directly to content script, same message format
|
|
|
|
|
const result = await chrome.tabs.sendMessage(tabId, msg)
|
2026-01-26 19:17:52 +08:00
|
|
|
sendResponse({ success: true, result })
|
2026-01-24 19:29:27 +08:00
|
|
|
} catch (error) {
|
2026-01-26 19:17:52 +08:00
|
|
|
sendResponse({
|
2026-01-24 19:29:27 +08:00
|
|
|
success: false,
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
})
|
2026-01-20 17:36:33 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 19:29:27 +08:00
|
|
|
// ============================================================================
|
2026-01-26 21:03:51 +08:00
|
|
|
// Tab Event Broadcasting
|
2026-01-24 19:29:27 +08:00
|
|
|
// ============================================================================
|
|
|
|
|
|
2026-01-26 21:03:51 +08:00
|
|
|
function broadcastTabChange(message: TabChangeMessage): void {
|
|
|
|
|
chrome.runtime.sendMessage(message).catch(() => {
|
|
|
|
|
// No listeners (sidepanel not open)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 19:29:27 +08:00
|
|
|
chrome.tabs.onRemoved.addListener((tabId) => {
|
2026-01-26 21:03:51 +08:00
|
|
|
broadcastTabChange({
|
|
|
|
|
type: 'TAB_CHANGE',
|
2026-01-24 19:29:27 +08:00
|
|
|
eventType: 'removed',
|
|
|
|
|
tabId,
|
2026-01-20 17:36:33 +08:00
|
|
|
})
|
2026-01-24 19:29:27 +08:00
|
|
|
})
|
2026-01-21 18:46:50 +08:00
|
|
|
|
2026-01-24 19:29:27 +08:00
|
|
|
chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
|
|
|
|
|
if (!changeInfo.status) return
|
|
|
|
|
|
2026-01-26 21:03:51 +08:00
|
|
|
broadcastTabChange({
|
|
|
|
|
type: 'TAB_CHANGE',
|
2026-01-24 19:29:27 +08:00
|
|
|
eventType: 'updated',
|
|
|
|
|
tabId,
|
|
|
|
|
data: {
|
|
|
|
|
status: changeInfo.status,
|
|
|
|
|
url: changeInfo.url,
|
|
|
|
|
},
|
2026-01-21 18:46:50 +08:00
|
|
|
})
|
2026-01-24 19:29:27 +08:00
|
|
|
})
|
2026-01-21 18:46:50 +08:00
|
|
|
|
2026-01-24 19:37:46 +08:00
|
|
|
chrome.tabs.onActivated.addListener((activeInfo) => {
|
2026-01-26 21:03:51 +08:00
|
|
|
broadcastTabChange({
|
|
|
|
|
type: 'TAB_CHANGE',
|
2026-01-24 19:37:46 +08:00
|
|
|
eventType: 'activated',
|
|
|
|
|
tabId: activeInfo.tabId,
|
|
|
|
|
data: {
|
|
|
|
|
windowId: activeInfo.windowId,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
chrome.windows.onFocusChanged.addListener((windowId) => {
|
|
|
|
|
const focused = windowId !== chrome.windows.WINDOW_ID_NONE
|
2026-01-26 21:03:51 +08:00
|
|
|
broadcastTabChange({
|
|
|
|
|
type: 'TAB_CHANGE',
|
2026-01-24 19:37:46 +08:00
|
|
|
eventType: 'windowFocusChanged',
|
2026-01-26 21:03:51 +08:00
|
|
|
tabId: -1,
|
2026-01-24 19:37:46 +08:00
|
|
|
data: {
|
|
|
|
|
windowId: focused ? windowId : undefined,
|
|
|
|
|
focused,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
2026-01-24 19:29:27 +08:00
|
|
|
// ============================================================================
|
|
|
|
|
// Extension Setup
|
|
|
|
|
// ============================================================================
|
2026-01-21 18:46:50 +08:00
|
|
|
|
2026-01-24 19:29:27 +08:00
|
|
|
export default defineBackground(() => {
|
2026-01-26 21:03:51 +08:00
|
|
|
console.log('[Background] Service Worker started')
|
2026-01-21 18:46:50 +08:00
|
|
|
|
2026-01-26 21:03:51 +08:00
|
|
|
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
|
2026-01-24 19:29:27 +08:00
|
|
|
})
|