From ef2d115742e424c7465db7f209722663997600d9 Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:47:15 +0800 Subject: [PATCH] feat(ext): expose extension to main-world and auth with token --- packages/extension/src/agent/constants.ts | 8 +++ packages/extension/src/agent/useAgent.ts | 9 +-- .../extension/src/entrypoints/background.ts | 7 +++ packages/extension/src/entrypoints/content.ts | 58 ++++++++++++++++++ .../extension/src/entrypoints/main-world.ts | 53 ++++++++++++++++ .../sidepanel/components/ConfigPanel.tsx | 60 ++++++++++++++++++- packages/extension/wxt.config.js | 6 ++ 7 files changed, 192 insertions(+), 9 deletions(-) create mode 100644 packages/extension/src/entrypoints/main-world.ts diff --git a/packages/extension/src/agent/constants.ts b/packages/extension/src/agent/constants.ts index ebef66a..8512beb 100644 --- a/packages/extension/src/agent/constants.ts +++ b/packages/extension/src/agent/constants.ts @@ -1,5 +1,13 @@ +import type { LLMConfig } from '@page-agent/llms' + // Demo LLM for testing export const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM' export const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy' export const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM' + +export const DEMO_CONFIG: LLMConfig = { + apiKey: DEMO_API_KEY, + baseURL: DEMO_BASE_URL, + model: DEMO_MODEL, +} diff --git a/packages/extension/src/agent/useAgent.ts b/packages/extension/src/agent/useAgent.ts index b3eeb48..c549c70 100644 --- a/packages/extension/src/agent/useAgent.ts +++ b/packages/extension/src/agent/useAgent.ts @@ -5,9 +5,8 @@ import type { AgentActivity, AgentStatus, HistoricalEvent } from '@page-agent/co import type { LLMConfig } from '@page-agent/llms' import { useCallback, useEffect, useRef, useState } from 'react' -import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL } from '@/agent/constants' - import { MultiPageAgent } from './MultiPageAgent' +import { DEMO_CONFIG } from './constants' export interface UseAgentResult { status: AgentStatus @@ -20,12 +19,6 @@ export interface UseAgentResult { configure: (config: LLMConfig) => Promise } -const DEMO_CONFIG: LLMConfig = { - apiKey: DEMO_API_KEY, - baseURL: DEMO_BASE_URL, - model: DEMO_MODEL, -} - export function useAgent(): UseAgentResult { const agentRef = useRef(null) const [status, setStatus] = useState('idle') diff --git a/packages/extension/src/entrypoints/background.ts b/packages/extension/src/entrypoints/background.ts index 90cb142..cf2d705 100644 --- a/packages/extension/src/entrypoints/background.ts +++ b/packages/extension/src/entrypoints/background.ts @@ -19,5 +19,12 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { export default defineBackground(() => { console.log('[Background] Service Worker started') + chrome.storage.local.get('PageAgentExtUserAuthToken').then((result) => { + if (result.PageAgentExtUserAuthToken) return + + const userAuthToken = crypto.randomUUID() + chrome.storage.local.set({ PageAgentExtUserAuthToken: userAuthToken }) + }) + chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {}) }) diff --git a/packages/extension/src/entrypoints/content.ts b/packages/extension/src/entrypoints/content.ts index cf7effb..28bcb52 100644 --- a/packages/extension/src/entrypoints/content.ts +++ b/packages/extension/src/entrypoints/content.ts @@ -1,4 +1,5 @@ import { initPageController } from '@/agent/RemotePageController.content' +import { DEMO_CONFIG } from '@/agent/constants' const DEBUG_PREFIX = '[Content]' @@ -9,5 +10,62 @@ export default defineContentScript({ main() { console.debug(`${DEBUG_PREFIX} Loaded on ${window.location.href}`) initPageController() + + // if auth token matches, expose agent to page + chrome.storage.local.get('PageAgentExtUserAuthToken').then((result) => { + if (!result.PageAgentExtUserAuthToken) return + if (!localStorage.getItem('PageAgentExtUserAuthToken')) return + if (localStorage.getItem('PageAgentExtUserAuthToken') !== result.PageAgentExtUserAuthToken) + return + + exposeAgentToPage() + injectScript('/main-world.js') + }) }, }) + +async function exposeAgentToPage() { + const { MultiPageAgent } = await import('@/agent/MultiPageAgent') + console.log('MultiPageAgent loaded', MultiPageAgent) + + /** + * singleton MultiPageAgent to handle requests from the page + */ + let multiPageAgent: InstanceType | null = null + + window.addEventListener('message', async (e) => { + const data = e.data + if (typeof data !== 'object' || data === null) return + if (data.channel !== 'PAGE_AGENT_EXT_REQUEST') return + + const { action, payload, id } = data + + switch (action) { + case 'execute': { + if (!multiPageAgent) multiPageAgent = new MultiPageAgent(DEMO_CONFIG) + + const result = await multiPageAgent.execute(payload) + window.postMessage( + { + channel: 'PAGE_AGENT_EXT_RESPONSE', + id, + action: 'execute_result', + payload: result, + }, + '*' + ) + break + } + + case 'dispose': { + // @note stop ongoing processes but can still be re-used later + multiPageAgent?.dispose() + break + } + + default: + console.warn(`${DEBUG_PREFIX} Unknown action from page:`, action) + break + } + }) +} diff --git a/packages/extension/src/entrypoints/main-world.ts b/packages/extension/src/entrypoints/main-world.ts new file mode 100644 index 0000000..f5a5a6b --- /dev/null +++ b/packages/extension/src/entrypoints/main-world.ts @@ -0,0 +1,53 @@ +export default defineUnlistedScript(() => { + const w = window as any + + let _lastId = 0 + function getId() { + _lastId += 1 + return _lastId + } + + w.execute = async (task: string) => { + const id = getId() + + const promise = new Promise((resolve) => { + function handleMessage(e: MessageEvent) { + const data = e.data + if (typeof data !== 'object' || data === null) return + if (data.channel !== 'PAGE_AGENT_EXT_RESPONSE') return + if (data.action !== 'execute_result') return + if (data.id !== id) return + + window.removeEventListener('message', handleMessage) + resolve(data.payload) + } + + window.addEventListener('message', handleMessage) + }) + + window.postMessage( + { + channel: 'PAGE_AGENT_EXT_REQUEST', + id, + action: 'execute', + payload: task, + }, + '*' + ) + + return promise + } + + w.dispose = () => { + const id = getId() + + window.postMessage( + { + channel: 'PAGE_AGENT_EXT_REQUEST', + id, + action: 'dispose', + }, + '*' + ) + } +}) diff --git a/packages/extension/src/entrypoints/sidepanel/components/ConfigPanel.tsx b/packages/extension/src/entrypoints/sidepanel/components/ConfigPanel.tsx index 646483c..a136fe1 100644 --- a/packages/extension/src/entrypoints/sidepanel/components/ConfigPanel.tsx +++ b/packages/extension/src/entrypoints/sidepanel/components/ConfigPanel.tsx @@ -1,5 +1,5 @@ import type { LLMConfig } from '@page-agent/llms' -import { Loader2 } from 'lucide-react' +import { Copy, Loader2 } from 'lucide-react' import { useEffect, useState } from 'react' import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL } from '@/agent/constants' @@ -17,6 +17,8 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) { const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL) const [model, setModel] = useState(config?.model || DEMO_MODEL) const [saving, setSaving] = useState(false) + const [userAuthToken, setUserAuthToken] = useState('') + const [copied, setCopied] = useState(false) // Update local state when config prop changes useEffect(() => { @@ -25,6 +27,38 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) { setModel(config?.model || DEMO_MODEL) }, [config]) + // Poll for user auth token every second until found + useEffect(() => { + let interval: NodeJS.Timeout | null = null + + const fetchToken = async () => { + const result = await chrome.storage.local.get('PageAgentExtUserAuthID') + const token = result.PageAgentExtUserAuthID + if (typeof token === 'string' && token) { + setUserAuthToken(token) + if (interval) { + clearInterval(interval) + interval = null + } + } + } + + fetchToken() + interval = setInterval(fetchToken, 1000) + + return () => { + if (interval) clearInterval(interval) + } + }, []) + + const handleCopyToken = async () => { + if (userAuthToken) { + await navigator.clipboard.writeText(userAuthToken) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + } + const handleSave = async () => { setSaving(true) try { @@ -38,6 +72,30 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {

Settings

+ {/* User Auth Token Section */} +
+ +

+ Add this token to a website's localStorage to give it authorization to call this extension +

+
+ + +
+
+