feat(browser): 受管浏览器代理设置(profile 导入已有实现,本轮补全代理)
This commit is contained in:
@@ -4,7 +4,7 @@ const log = getLogger('browser')
|
||||
import { app, BrowserWindow, session as electronSession, WebContentsView, type Rectangle, type Session } from 'electron'
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import type { BrowserImportInfo, BrowserTabInfo, BrowserUiState } from '@shared/types'
|
||||
import type { BrowserImportInfo, BrowserProxySettings, BrowserTabInfo, BrowserUiState } from '@shared/types'
|
||||
import {
|
||||
BrowserCdpTimeoutError,
|
||||
BROWSER_OBSERVE_TIMEOUT_MS,
|
||||
@@ -32,6 +32,9 @@ import {
|
||||
* + Input.dispatch* 真实输入事件;不在页面里执行模拟点击。
|
||||
* - 登录态隔离在 persist:aso-browser 分区;本地浏览器配置导入由 browser-profile.ts
|
||||
* 直接迁移分区目录数据(Cookies/Local Storage + 密钥移植)。
|
||||
* - 代理设置作用于同一分区会话(session.setProxy):跟随系统 / 直连 / 手动规则
|
||||
* (Chromium proxyRules 语法 + 绕过列表),持久化进 aso-browser.json,
|
||||
* 启动即应用、修改后断开在途连接立即生效(对 Agent 的 browser_* 工具同样生效)。
|
||||
*/
|
||||
|
||||
const PARTITION = 'persist:aso-browser'
|
||||
@@ -71,6 +74,7 @@ interface TabRecord {
|
||||
interface BrowserSettings {
|
||||
riskAcknowledged: boolean
|
||||
imported?: BrowserImportInfo | null
|
||||
proxy?: BrowserProxySettings
|
||||
}
|
||||
|
||||
export interface BrowserObservation {
|
||||
@@ -111,6 +115,36 @@ function textValue(v: unknown): string {
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 校验/规范化代理输入(fixed 模式必须有可解析的 host:port 规则;其余模式丢弃规则字段) */
|
||||
function normalizeProxy(input: BrowserProxySettings): BrowserProxySettings {
|
||||
if (input.mode !== 'fixed') return { mode: input.mode }
|
||||
const entries = (input.rules ?? '')
|
||||
.split(';')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
if (entries.length === 0) {
|
||||
throw new Error('手动代理需要填写规则,如 127.0.0.1:7890(多协议用分号分隔,如 http=127.0.0.1:7890;https=127.0.0.1:7890)。')
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const hostPort = entry.includes('://')
|
||||
? entry.slice(entry.indexOf('://') + 3)
|
||||
: entry.includes('=')
|
||||
? entry.slice(entry.indexOf('=') + 1)
|
||||
: entry
|
||||
const colon = hostPort.lastIndexOf(':')
|
||||
const port = colon >= 0 ? hostPort.slice(colon + 1) : ''
|
||||
if (!hostPort || colon < 0 || !/^\d{1,5}$/.test(port) || Number(port) < 1 || Number(port) > 65535) {
|
||||
throw new Error(`代理规则「${entry}」无效:应为 host:port(如 127.0.0.1:7890)。`)
|
||||
}
|
||||
}
|
||||
const bypass = (input.bypass ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.join(',')
|
||||
return { mode: 'fixed', rules: entries.join(';'), bypass: bypass || undefined }
|
||||
}
|
||||
|
||||
function isEditableAxNode(node: Record<string, unknown>): boolean {
|
||||
const role = textValue(node.role).toLowerCase()
|
||||
if (role === 'textfield' || role === 'textbox' || role === 'searchbox') return true
|
||||
@@ -310,6 +344,10 @@ export class BrowserController {
|
||||
item.setSavePath(target)
|
||||
})
|
||||
this.settings = this.loadSettings()
|
||||
// 启动即应用持久化的代理设置(setProxy 异步;失败不阻塞浏览器初始化,日志可查)
|
||||
this.applyProxy(this.proxySettings()).catch((err: unknown) => {
|
||||
log.error('启动时代理设置应用失败:', { err })
|
||||
})
|
||||
}
|
||||
|
||||
// ---- 设置持久化 ----
|
||||
@@ -321,12 +359,17 @@ export class BrowserController {
|
||||
private loadSettings(): BrowserSettings {
|
||||
try {
|
||||
const raw = JSON.parse(readFileSync(this.settingsFile, 'utf8')) as Partial<BrowserSettings>
|
||||
const proxy = raw.proxy
|
||||
return {
|
||||
riskAcknowledged: raw.riskAcknowledged === true,
|
||||
imported: raw.imported ?? null
|
||||
imported: raw.imported ?? null,
|
||||
proxy:
|
||||
proxy && (proxy.mode === 'direct' || proxy.mode === 'fixed' || proxy.mode === 'system')
|
||||
? proxy
|
||||
: { mode: 'system' }
|
||||
}
|
||||
} catch {
|
||||
return { riskAcknowledged: false }
|
||||
return { riskAcknowledged: false, proxy: { mode: 'system' } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +392,38 @@ export class BrowserController {
|
||||
return this.settings.imported ?? null
|
||||
}
|
||||
|
||||
// ---- 代理设置 ----
|
||||
|
||||
private proxySettings(): BrowserProxySettings {
|
||||
return this.settings.proxy ?? { mode: 'system' }
|
||||
}
|
||||
|
||||
getProxySettings(): BrowserProxySettings {
|
||||
return this.proxySettings()
|
||||
}
|
||||
|
||||
/** 应用到分区会话;切换后断开在途连接,让新规则对已打开的标签立即生效 */
|
||||
private async applyProxy(proxy: BrowserProxySettings): Promise<void> {
|
||||
await this.browserSession.setProxy(
|
||||
proxy.mode === 'fixed'
|
||||
? { mode: 'fixed_servers', proxyRules: proxy.rules ?? '', proxyBypassRules: proxy.bypass }
|
||||
: proxy.mode === 'direct'
|
||||
? { mode: 'direct' }
|
||||
: { mode: 'system' }
|
||||
)
|
||||
await this.browserSession.closeAllConnections()
|
||||
}
|
||||
|
||||
/** 更新并持久化代理设置:先应用成功才落盘(失败抛错给 UI,原设置保持不变) */
|
||||
async setProxySettings(input: BrowserProxySettings): Promise<BrowserProxySettings> {
|
||||
const next = normalizeProxy(input)
|
||||
await this.applyProxy(next)
|
||||
this.settings.proxy = next
|
||||
this.saveSettings()
|
||||
this.emitState()
|
||||
return next
|
||||
}
|
||||
|
||||
/** 当前是否有活跃标签页(导入配置前必须没有) */
|
||||
get hasTabs(): boolean {
|
||||
return this.tabs.size > 0
|
||||
@@ -395,7 +470,8 @@ export class BrowserController {
|
||||
activeTabId: this.activeTabId,
|
||||
recentClosed: this.closedTabs.map(({ title, url }) => ({ title, url })),
|
||||
trace: this.traceText,
|
||||
imported: this.settings.imported ?? null
|
||||
imported: this.settings.imported ?? null,
|
||||
proxy: this.proxySettings()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user