Files
page-agent/packages/extension/src/agent/RemotePageController.ts
T

171 lines
4.3 KiB
TypeScript
Raw Normal View History

import type { BrowserState, PageController } from '@page-agent/page-controller'
import { isContentScriptAllowed } from '@/utils'
import { TabsController } from './TabsController'
2026-01-24 19:29:27 +08:00
/**
* Agent side page controller.
* - live in the agent env (extension page or content script)
* - communicates with remote PageController via sw
2026-01-24 19:29:27 +08:00
*/
export class RemotePageController {
tabsController!: TabsController
2026-01-24 19:29:27 +08:00
get currentTabId(): number | null {
return this.tabsController.currentTabId
}
async getCurrentUrl(): Promise<string> {
if (!this.currentTabId) return ''
const { url } = await this.tabsController.getTabInfo(this.currentTabId)
return url || ''
}
get currentTabUrl(): Promise<string> {
return this.getCurrentUrl()
2026-01-24 19:29:27 +08:00
}
async getCurrentTitle(): Promise<string> {
if (!this.currentTabId) return ''
const { title } = await this.tabsController.getTabInfo(this.currentTabId)
return title || ''
2026-01-24 19:29:27 +08:00
}
get currentTabTitle(): Promise<string> {
return this.getCurrentTitle()
2026-01-24 19:29:27 +08:00
}
async getLastUpdateTime(): Promise<number> {
if (!this.currentTabId) throw new Error('tabsController not initialized.')
return await chrome.runtime.sendMessage({
type: 'PAGE_CONTROL',
action: 'get_last_update_time',
targetTabId: this.currentTabId,
})
2026-01-24 19:29:27 +08:00
}
// getBrowserState
async getBrowserState(): Promise<BrowserState> {
let browserState = {} as BrowserState
if (!this.currentTabId || !isContentScriptAllowed(await this.currentTabUrl)) {
browserState = {
url: await this.currentTabUrl,
title: await this.currentTabTitle,
header: '',
content: '(empty page)',
footer: '',
}
} else {
browserState = await chrome.runtime.sendMessage({
type: 'PAGE_CONTROL',
action: 'get_browser_state',
targetTabId: this.currentTabId,
})
2026-01-24 19:29:27 +08:00
}
const sum = await this.tabsController.summarizeTabs()
browserState.header = sum + '\n' + (browserState.header || '')
return browserState
}
// updateTree
async updateTree(): Promise<void> {
if (!this.currentTabId || !isContentScriptAllowed(await this.currentTabUrl)) {
return
2026-01-24 19:29:27 +08:00
}
await chrome.runtime.sendMessage({
type: 'PAGE_CONTROL',
action: 'update_tree',
targetTabId: this.currentTabId,
})
}
// cleanUpHighlights
async cleanUpHighlights(): Promise<void> {
if (!this.currentTabId || !isContentScriptAllowed(await this.currentTabUrl)) {
return
}
await chrome.runtime.sendMessage({
type: 'PAGE_CONTROL',
action: 'clean_up_highlights',
targetTabId: this.currentTabId,
})
}
// clickElement
async clickElement(...args: any[]): Promise<DomActionReturn> {
return this.remoteCallDomAction('click_element', args)
}
// inputText
async inputText(...args: any[]): Promise<DomActionReturn> {
return this.remoteCallDomAction('input_text', args)
}
// selectOption
async selectOption(...args: any[]): Promise<DomActionReturn> {
return this.remoteCallDomAction('select_option', args)
}
// scroll
async scroll(...args: any[]): Promise<DomActionReturn> {
return this.remoteCallDomAction('scroll', args)
}
// scrollHorizontally
async scrollHorizontally(...args: any[]): Promise<DomActionReturn> {
return this.remoteCallDomAction('scroll_horizontally', args)
}
// executeJavascript
async executeJavascript(...args: any[]): Promise<DomActionReturn> {
return this.remoteCallDomAction('execute_javascript', args)
}
/** @note Mask visibility is managed by content script via storage polling. */
async showMask(): Promise<void> {}
/** @note Mask visibility is managed by content script via storage polling. */
async hideMask(): Promise<void> {}
// dispose
dispose(): void {}
private async preCheck() {
if (!this.currentTabId) {
return 'RemotePageController not initialized.'
}
if (!isContentScriptAllowed(await this.currentTabUrl)) {
return 'Operation not allowed on this page. Use open_new_tab to navigate to a web page first.'
}
return null
2026-01-24 19:29:27 +08:00
}
private async remoteCallDomAction(action: string, payload: any[]): Promise<DomActionReturn> {
const preCheckError = await this.preCheck()
if (preCheckError) {
return { success: false, message: preCheckError }
}
return await chrome.runtime.sendMessage({
type: 'PAGE_CONTROL',
action: action,
targetTabId: this.currentTabId!,
payload,
})
}
}
interface DomActionReturn {
success: boolean
message: string
}