feat: 内置受管浏览器(CDP + AX 快照 ref 体系)+ 本地浏览器配置导入
This commit is contained in:
@@ -2,6 +2,8 @@ import { BrowserWindow, dialog, ipcMain, shell } from 'electron'
|
||||
import { mkdirSync } from 'node:fs'
|
||||
import { userInfo } from 'node:os'
|
||||
import { AgentService } from './pi/agent-service'
|
||||
import { getBrowserController } from './browser/browser-controller'
|
||||
import { detectBrowserSources, importBrowserProfile } from './browser/browser-profile'
|
||||
|
||||
/**
|
||||
* 注册 IPC 通道并把 agent 事件转发到所有渲染窗口。
|
||||
@@ -257,5 +259,70 @@ export function registerIpc(): AgentService {
|
||||
return service.pickAttachments(win ?? undefined)
|
||||
})
|
||||
|
||||
// ---- 内置受管浏览器 ----
|
||||
const browser = getBrowserController()
|
||||
// 面板状态广播(与 agent:event 同样转发到所有窗口)
|
||||
browser.onState((state) => {
|
||||
for (const win of BrowserWindow.getAllWindows()) {
|
||||
if (!win.isDestroyed()) win.webContents.send('browser:event', { type: 'state', state })
|
||||
}
|
||||
})
|
||||
|
||||
const browserPass = <T,>(fn: () => T): T => {
|
||||
try {
|
||||
return fn()
|
||||
} catch (err) {
|
||||
throw new Error(err instanceof Error ? err.message : String(err))
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle('browser:getState', () => browser.getState())
|
||||
ipcMain.handle('browser:navigate', (_e, input: unknown) =>
|
||||
browserPass(() => browser.navigateDisplay(typeof input === 'string' ? input : ''))
|
||||
)
|
||||
ipcMain.handle('browser:goBack', () => browserPass(() => browser.goBack()))
|
||||
ipcMain.handle('browser:goForward', () => browserPass(() => browser.goForward()))
|
||||
ipcMain.handle('browser:reload', () => browserPass(() => browser.reload()))
|
||||
ipcMain.handle('browser:newTab', (_e, url: unknown) =>
|
||||
browserPass(() => browser.newTab(typeof url === 'string' && url ? url : undefined, false))
|
||||
)
|
||||
ipcMain.handle('browser:selectTab', (_e, tabId: unknown) =>
|
||||
browserPass(() => browser.selectTab(typeof tabId === 'string' ? tabId : ''))
|
||||
)
|
||||
ipcMain.handle('browser:closeTab', (_e, tabId: unknown) =>
|
||||
browserPass(() => browser.closeTab(typeof tabId === 'string' ? tabId : undefined))
|
||||
)
|
||||
ipcMain.handle('browser:close', () => browserPass(() => browser.close()))
|
||||
ipcMain.handle('browser:acknowledgeRisk', () => browserPass(() => browser.acknowledgeRisk()))
|
||||
// 渲染层 BrowserSlot 量出的占位矩形(原生 WebContentsView 对齐到 DOM 布局)
|
||||
ipcMain.handle('browser:layout', (_e, layout: unknown) => {
|
||||
const l = layout as { tabId?: unknown; visible?: unknown; revision?: unknown; bounds?: unknown }
|
||||
const { tabId, visible, revision } = l
|
||||
const bounds = l.bounds as { x?: unknown; y?: unknown; width?: unknown; height?: unknown } | undefined
|
||||
const x = bounds?.x
|
||||
const y = bounds?.y
|
||||
const width = bounds?.width
|
||||
const height = bounds?.height
|
||||
if (
|
||||
typeof tabId !== 'string' ||
|
||||
typeof visible !== 'boolean' ||
|
||||
typeof revision !== 'number' ||
|
||||
typeof x !== 'number' ||
|
||||
typeof y !== 'number' ||
|
||||
typeof width !== 'number' ||
|
||||
typeof height !== 'number'
|
||||
) {
|
||||
throw new Error('invalid browser layout')
|
||||
}
|
||||
return browserPass(() => browser.applyLayout({ tabId, visible, revision, bounds: { x, y, width, height } }))
|
||||
})
|
||||
|
||||
// ---- 本地浏览器配置导入 ----
|
||||
ipcMain.handle('browser:detectSources', () => browserPass(() => detectBrowserSources()))
|
||||
ipcMain.handle('browser:importProfile', (_e, sourceId: unknown, profileDir: unknown) => {
|
||||
if (typeof sourceId !== 'string' || typeof profileDir !== 'string') throw new Error('invalid import source')
|
||||
return browserPass(() => importBrowserProfile(sourceId, profileDir))
|
||||
})
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user