feat(browser): Cookie 备份导入——配套扩展走 chrome.cookies 官方明文通道
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
# ASO Cookie 导出助手
|
||||||
|
|
||||||
|
Chrome 127+ 对 Cookie 启用了应用绑定加密(app-bound encryption),浏览器外的程序(包括本应用)无法解密 Cookie 文件。Chrome 官方留给扩展的 `chrome.cookies` API 仍返回明文——这个扩展就是走这条通道,把 Cookie 导出成备份文件,再到 Inkreach Agent 里导入。
|
||||||
|
|
||||||
|
## 使用步骤(一次性)
|
||||||
|
|
||||||
|
1. 打开 Chrome,地址栏输入 `chrome://extensions/` 回车。
|
||||||
|
2. 右上角打开「开发者模式」。
|
||||||
|
3. 点「加载已解压的扩展程序」,选择本目录(`cookie-exporter` 文件夹)。
|
||||||
|
4. 点浏览器工具栏里的扩展图标(拼图形状)→ 找到「ASO Cookie 导出助手」→ 点「导出全部 Cookie」,选择保存位置。
|
||||||
|
5. 回到 Inkreach Agent:设置 → 浏览器 → 「Cookie 备份导入」→ 选择刚保存的 JSON 文件,立即生效。
|
||||||
|
|
||||||
|
Edge / Brave 等 Chromium 系浏览器同样适用(各自扩展页加载本目录即可)。
|
||||||
|
|
||||||
|
> 导出的备份文件包含明文登录凭据,用完建议删除,不要发给别人。
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "ASO Cookie 导出助手",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "把本浏览器的全部 Cookie 导出为 JSON 备份,供 Inkreach Agent 受管浏览器导入(Chrome 应用绑定加密下的官方通道)。",
|
||||||
|
"permissions": ["cookies", "downloads"],
|
||||||
|
"host_permissions": ["<all_urls>"],
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>ASO Cookie 导出助手</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
width: 280px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 14px;
|
||||||
|
font: 13px/1.6 system-ui, 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||||||
|
color: #1f2328;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #fff;
|
||||||
|
background: #2563eb;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #1d4ed8;
|
||||||
|
}
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
#status {
|
||||||
|
margin-top: 10px;
|
||||||
|
min-height: 18px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #374151;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Cookie 导出助手</h1>
|
||||||
|
<p>导出本浏览器全部 Cookie(明文),到 Inkreach Agent 的 设置 → 浏览器 → Cookie 备份导入 里使用。</p>
|
||||||
|
<button id="export">导出全部 Cookie</button>
|
||||||
|
<div id="status"></div>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// 导出全部 Cookie 为 JSON 备份(chrome.cookies API 返回明文——Chrome 官方提供给扩展的通道)
|
||||||
|
const statusEl = document.getElementById('status')
|
||||||
|
const button = document.getElementById('export')
|
||||||
|
|
||||||
|
function setStatus(text) {
|
||||||
|
statusEl.textContent = text
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener('click', async () => {
|
||||||
|
button.disabled = true
|
||||||
|
try {
|
||||||
|
setStatus('正在读取 Cookie…')
|
||||||
|
const cookies = await chrome.cookies.getAll({})
|
||||||
|
if (!cookies || cookies.length === 0) {
|
||||||
|
setStatus('未读取到任何 Cookie。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const payload = {
|
||||||
|
format: 'aso-cookie-export',
|
||||||
|
version: 1,
|
||||||
|
exportedAt: new Date().toISOString(),
|
||||||
|
cookies: cookies.map((c) => ({
|
||||||
|
name: c.name,
|
||||||
|
value: c.value,
|
||||||
|
domain: c.domain,
|
||||||
|
hostOnly: c.hostOnly === true,
|
||||||
|
path: c.path || '/',
|
||||||
|
secure: c.secure === true,
|
||||||
|
httpOnly: c.httpOnly === true,
|
||||||
|
...(typeof c.expirationDate === 'number' ? { expirationDate: c.expirationDate } : {}),
|
||||||
|
...(c.sameSite && c.sameSite !== 'unspecified' ? { sameSite: c.sameSite } : {})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
const blob = new Blob([JSON.stringify(payload)], { type: 'application/json' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const d = new Date()
|
||||||
|
const pad = (n) => String(n).padStart(2, '0')
|
||||||
|
const filename = `aso-cookies-${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}.json`
|
||||||
|
await chrome.downloads.download({ url, filename, saveAs: true })
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
setStatus(`已导出 ${payload.cookies.length} 条 Cookie。回到 Inkreach Agent:设置 → 浏览器 → Cookie 备份导入,选择刚保存的文件。`)
|
||||||
|
} catch (err) {
|
||||||
|
setStatus('导出失败:' + (err && err.message ? err.message : String(err)))
|
||||||
|
} finally {
|
||||||
|
button.disabled = false
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -438,11 +438,16 @@ async function importBrowserProfileInner(sourceId: string, profileDir: string):
|
|||||||
if (sourceRead.kind === 'ok') {
|
if (sourceRead.kind === 'ok') {
|
||||||
const result = await browser.importCookies(sourceRead.cookies)
|
const result = await browser.importCookies(sourceRead.cookies)
|
||||||
injected = result.imported
|
injected = result.imported
|
||||||
const skipNotes: string[] = []
|
if (sourceRead.cookies.length === 0 && sourceRead.v20 > 0) {
|
||||||
if (sourceRead.v20 > 0) skipNotes.push(`${sourceRead.v20} 条为应用绑定加密(v20)无法迁移`)
|
// 全量 v20:文件路径对 Chrome 127+ 已封死,明确引导走扩展备份通道
|
||||||
const failedCount = result.failed + sourceRead.undecryptable
|
cookieNote = `Chrome 已启用应用绑定加密(v20),${sourceRead.v20} 条 Cookie 在浏览器外无法解密;请用上方「Cookie 备份导入」补齐(配套扩展一键导出)。LocalStorage 已生效。`
|
||||||
if (failedCount > 0) skipNotes.push(`${failedCount} 条解密失败已跳过`)
|
} else {
|
||||||
if (skipNotes.length > 0) cookieNote = `源 Cookie ${injected} 条已注入,${skipNotes.join(',')}。`
|
const skipNotes: string[] = []
|
||||||
|
if (sourceRead.v20 > 0) skipNotes.push(`${sourceRead.v20} 条为应用绑定加密(v20)无法迁移,可用「Cookie 备份导入」补齐`)
|
||||||
|
const failedCount = result.failed + sourceRead.undecryptable
|
||||||
|
if (failedCount > 0) skipNotes.push(`${failedCount} 条解密失败已跳过`)
|
||||||
|
if (skipNotes.length > 0) cookieNote = `源 Cookie ${injected} 条已注入,${skipNotes.join(',')}。`
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cookieNote = sourceRead.note
|
cookieNote = sourceRead.note
|
||||||
}
|
}
|
||||||
@@ -480,6 +485,89 @@ async function importBrowserProfileInner(sourceId: string, profileDir: string):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入配套扩展导出的 Cookie 备份文件(chrome.cookies API 明文通道——Chrome 127+
|
||||||
|
* 应用绑定加密后浏览器外无法解密 Cookie,官方留给扩展的 API 仍返回明文)。
|
||||||
|
* 纯会话 API 注入:无需重启、无需关闭标签,立即生效。
|
||||||
|
*/
|
||||||
|
const MAX_BACKUP_BYTES = 32 * 1024 * 1024
|
||||||
|
const MAX_BACKUP_COOKIES = 50_000
|
||||||
|
|
||||||
|
export async function importCookiesFromFile(filePath: string): Promise<BrowserImportResult> {
|
||||||
|
const browser = getBrowserController()
|
||||||
|
if (!/\.json$/i.test(filePath)) throw new Error('请选择扩展导出的 .json 备份文件。')
|
||||||
|
let raw: string
|
||||||
|
try {
|
||||||
|
if (statSync(filePath).size > MAX_BACKUP_BYTES) throw new Error('文件过大(超过 32MB),不是有效的备份文件。')
|
||||||
|
raw = readFileSync(filePath, 'utf8')
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error && err.message.startsWith('文件过大')) throw err
|
||||||
|
throw new Error('读取备份文件失败,请重新选择。')
|
||||||
|
}
|
||||||
|
let parsed: { format?: unknown; version?: unknown; cookies?: unknown }
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw) as typeof parsed
|
||||||
|
} catch {
|
||||||
|
throw new Error('备份文件不是有效的 JSON,请用「ASO Cookie 导出助手」扩展重新导出。')
|
||||||
|
}
|
||||||
|
if (parsed.format !== 'aso-cookie-export' || !Array.isArray(parsed.cookies)) {
|
||||||
|
throw new Error('备份文件格式不识别,请用「ASO Cookie 导出助手」扩展导出。')
|
||||||
|
}
|
||||||
|
if (parsed.cookies.length === 0) throw new Error('备份文件里没有 Cookie。')
|
||||||
|
if (parsed.cookies.length > MAX_BACKUP_COOKIES) throw new Error('Cookie 数量异常(超过 5 万条),已拒绝导入。')
|
||||||
|
|
||||||
|
const seeds: BrowserCookieSeed[] = []
|
||||||
|
let invalid = 0
|
||||||
|
for (const item of parsed.cookies) {
|
||||||
|
try {
|
||||||
|
const c = item as Record<string, unknown>
|
||||||
|
if (typeof c.name !== 'string' || typeof c.value !== 'string' || typeof c.domain !== 'string' || !c.name || !c.domain) {
|
||||||
|
invalid++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seeds.push({
|
||||||
|
name: c.name,
|
||||||
|
value: c.value,
|
||||||
|
domain: c.domain,
|
||||||
|
hostOnly: c.hostOnly === true,
|
||||||
|
path: typeof c.path === 'string' && c.path ? c.path : '/',
|
||||||
|
secure: c.secure === true,
|
||||||
|
httpOnly: c.httpOnly === true,
|
||||||
|
...(typeof c.expirationDate === 'number' && c.expirationDate > 0 ? { expirationDate: c.expirationDate } : {}),
|
||||||
|
...(c.sameSite === 'no_restriction' || c.sameSite === 'lax' || c.sameSite === 'strict' ? { sameSite: c.sameSite } : {})
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
invalid++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (seeds.length === 0) throw new Error('备份文件里没有可用的 Cookie 条目。')
|
||||||
|
|
||||||
|
const result = await browser.importCookies(seeds)
|
||||||
|
const previous = browser.importedInfo
|
||||||
|
browser.setImportedInfo({
|
||||||
|
sourceId: 'file',
|
||||||
|
browser: 'Cookie 备份文件',
|
||||||
|
profile: '',
|
||||||
|
profileDir: '',
|
||||||
|
at: new Date().toISOString(),
|
||||||
|
cookies: result.imported > 0,
|
||||||
|
localStorage: previous?.localStorage ?? false,
|
||||||
|
...(result.imported > 0 ? { cookieCount: result.imported } : {}),
|
||||||
|
note: result.failed > 0 || invalid > 0 ? `${result.failed + invalid} 条无效已跳过。` : undefined
|
||||||
|
})
|
||||||
|
log.info('cookie backup imported', { injected: result.imported, failed: result.failed, invalid })
|
||||||
|
return {
|
||||||
|
ok: result.imported > 0,
|
||||||
|
cookies: result.imported > 0,
|
||||||
|
localStorage: previous?.localStorage ?? false,
|
||||||
|
...(result.failed > 0 || invalid > 0 ? { cookieNote: `${result.failed + invalid} 条无效已跳过。` } : {}),
|
||||||
|
message:
|
||||||
|
result.imported > 0
|
||||||
|
? `已从备份文件注入 Cookie ${result.imported} 条,立即生效。${result.failed + invalid > 0 ? `${result.failed + invalid} 条无效已跳过。` : ''}`
|
||||||
|
: '备份文件里的 Cookie 全部写入失败,请重试。'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动早期调用(分区尚未被任何页面打开):清理上一代分区目录与残留暂存。
|
* 启动早期调用(分区尚未被任何页面打开):清理上一代分区目录与残留暂存。
|
||||||
* 失败只记日志(旧目录多留一启动无害,下次再试)。
|
* 失败只记日志(旧目录多留一启动无害,下次再试)。
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import { app, BrowserWindow, dialog, ipcMain, shell } from 'electron'
|
import { app, BrowserWindow, dialog, ipcMain, shell } from 'electron'
|
||||||
import { mkdirSync, readFileSync } from 'node:fs'
|
import { mkdirSync, readFileSync } from 'node:fs'
|
||||||
|
import { join } from 'node:path'
|
||||||
import { userInfo } from 'node:os'
|
import { userInfo } from 'node:os'
|
||||||
import { AgentService } from './pi/agent-service'
|
import { AgentService } from './pi/agent-service'
|
||||||
import { getPluginHost } from './plugins/plugin-host'
|
import { getPluginHost } from './plugins/plugin-host'
|
||||||
import { getBrowserController } from './browser/browser-controller'
|
import { getBrowserController } from './browser/browser-controller'
|
||||||
import { cleanupStaleBrowserPartitions, detectBrowserSources, importBrowserProfile } from './browser/browser-profile'
|
import {
|
||||||
|
cleanupStaleBrowserPartitions,
|
||||||
|
detectBrowserSources,
|
||||||
|
importBrowserProfile,
|
||||||
|
importCookiesFromFile
|
||||||
|
} from './browser/browser-profile'
|
||||||
import { getLogger } from './logger'
|
import { getLogger } from './logger'
|
||||||
|
|
||||||
const log = getLogger('ipc')
|
const log = getLogger('ipc')
|
||||||
@@ -409,6 +415,18 @@ export function registerIpc(): AgentService {
|
|||||||
return browserPass(() => importBrowserProfile(sourceId, profileDir))
|
return browserPass(() => importBrowserProfile(sourceId, profileDir))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Cookie 备份文件导入(Chrome 应用绑定加密下的扩展通道)+ 配套扩展目录
|
||||||
|
handle('browser:importCookiesFile', (_e, path: unknown) => {
|
||||||
|
if (typeof path !== 'string' || !path) throw new Error('invalid cookie backup path')
|
||||||
|
return browserPass(() => importCookiesFromFile(path))
|
||||||
|
})
|
||||||
|
handle('browser:openExporterFolder', () => {
|
||||||
|
const folder = app.isPackaged
|
||||||
|
? join(process.resourcesPath, 'cookie-exporter')
|
||||||
|
: join(app.getAppPath(), 'resources', 'cookie-exporter')
|
||||||
|
return shell.openPath(folder)
|
||||||
|
})
|
||||||
|
|
||||||
// ---- 受管浏览器代理设置(persist:aso-browser 分区,保存即生效) ----
|
// ---- 受管浏览器代理设置(persist:aso-browser 分区,保存即生效) ----
|
||||||
handle('browser:getProxy', () => getBrowserController().getProxySettings())
|
handle('browser:getProxy', () => getBrowserController().getProxySettings())
|
||||||
handle('browser:setProxy', (_e, input: unknown) => {
|
handle('browser:setProxy', (_e, input: unknown) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { contextBridge, ipcRenderer, webFrame } from 'electron'
|
import { contextBridge, ipcRenderer, webFrame, webUtils } from 'electron'
|
||||||
import type {
|
import type {
|
||||||
AgentUiEvent,
|
AgentUiEvent,
|
||||||
AppSettings,
|
AppSettings,
|
||||||
@@ -130,6 +130,10 @@ const api = {
|
|||||||
browserDetectSources: (): Promise<BrowserSource[]> => ipcRenderer.invoke('browser:detectSources'),
|
browserDetectSources: (): Promise<BrowserSource[]> => ipcRenderer.invoke('browser:detectSources'),
|
||||||
browserImportProfile: (sourceId: string, profileDir: string): Promise<BrowserImportResult> =>
|
browserImportProfile: (sourceId: string, profileDir: string): Promise<BrowserImportResult> =>
|
||||||
ipcRenderer.invoke('browser:importProfile', sourceId, profileDir),
|
ipcRenderer.invoke('browser:importProfile', sourceId, profileDir),
|
||||||
|
browserImportCookiesFile: (path: string): Promise<BrowserImportResult> =>
|
||||||
|
ipcRenderer.invoke('browser:importCookiesFile', path),
|
||||||
|
browserOpenExporterFolder: (): Promise<string> => ipcRenderer.invoke('browser:openExporterFolder'),
|
||||||
|
pathForFile: (file: File): string => webUtils.getPathForFile(file),
|
||||||
browserGetProxy: (): Promise<BrowserProxySettings> => ipcRenderer.invoke('browser:getProxy'),
|
browserGetProxy: (): Promise<BrowserProxySettings> => ipcRenderer.invoke('browser:getProxy'),
|
||||||
browserSetProxy: (input: BrowserProxySettings): Promise<BrowserProxySettings> =>
|
browserSetProxy: (input: BrowserProxySettings): Promise<BrowserProxySettings> =>
|
||||||
ipcRenderer.invoke('browser:setProxy', input),
|
ipcRenderer.invoke('browser:setProxy', input),
|
||||||
|
|||||||
@@ -71,6 +71,24 @@ export function BrowserImportForm(): JSX.Element {
|
|||||||
.finally(() => setBusy(false))
|
.finally(() => setBusy(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const runBackupImport = (file: File | null): void => {
|
||||||
|
if (!file || busy) return
|
||||||
|
setBusy(true)
|
||||||
|
setError(null)
|
||||||
|
setResult(null)
|
||||||
|
try {
|
||||||
|
const path = window.api.pathForFile(file)
|
||||||
|
window.api
|
||||||
|
.browserImportCookiesFile(path)
|
||||||
|
.then(setResult)
|
||||||
|
.catch((err) => setError(err instanceof Error ? err.message : String(err)))
|
||||||
|
.finally(() => setBusy(false))
|
||||||
|
} catch (err) {
|
||||||
|
setBusy(false)
|
||||||
|
setError(err instanceof Error ? err.message : String(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const imported = state?.imported ?? null
|
const imported = state?.imported ?? null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -80,12 +98,51 @@ export function BrowserImportForm(): JSX.Element {
|
|||||||
<span className="font-medium text-ink-2">完全退出</span>源浏览器(含后台进程)。
|
<span className="font-medium text-ink-2">完全退出</span>源浏览器(含后台进程)。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* Chrome 新版应用绑定加密:文件导入拿不到 Cookie,走配套扩展的官方明文通道 */}
|
||||||
|
<div className="mb-3 rounded border border-line px-2.5 py-2">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<span className="text-[12px] font-medium text-ink-2">Cookie 备份导入</span>
|
||||||
|
<div className="flex shrink-0 items-center gap-1.5">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={busy}
|
||||||
|
onClick={() => void window.api.browserOpenExporterFolder()}
|
||||||
|
className="rounded border border-line-strong px-2 py-0.5 text-[11px] text-ink-2 transition-colors hover:bg-hover hover:text-ink disabled:opacity-40"
|
||||||
|
>
|
||||||
|
扩展目录
|
||||||
|
</button>
|
||||||
|
<label
|
||||||
|
className={`cursor-pointer rounded border border-line-strong px-2 py-0.5 text-[11px] text-ink-2 transition-colors hover:bg-hover hover:text-ink ${
|
||||||
|
busy ? 'pointer-events-none opacity-40' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
选择备份文件
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".json,application/json"
|
||||||
|
className="hidden"
|
||||||
|
disabled={busy}
|
||||||
|
onChange={(e) => {
|
||||||
|
runBackupImport(e.target.files?.[0] ?? null)
|
||||||
|
e.currentTarget.value = ''
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-[10.5px] leading-relaxed text-ink-3">
|
||||||
|
Chrome 新版已加密 Cookie(文件导入拿不到)。用配套扩展导出备份:点「扩展目录」,在 Chrome 的
|
||||||
|
chrome://extensions 加载该目录后一键导出,再在这里选择备份文件,立即生效。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{imported && (
|
{imported && (
|
||||||
<div className="mb-3 rounded border border-line bg-inset px-2.5 py-2">
|
<div className="mb-3 rounded border border-line bg-inset px-2.5 py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="shrink-0 text-[11px] text-ink-3">已导入</span>
|
<span className="shrink-0 text-[11px] text-ink-3">已导入</span>
|
||||||
<span className="min-w-0 flex-1 truncate text-[12px] text-ink">
|
<span className="min-w-0 flex-1 truncate text-[12px] text-ink">
|
||||||
{imported.browser} · {imported.profile}
|
{imported.browser}
|
||||||
|
{imported.profile && <span> · {imported.profile}</span>}
|
||||||
{imported.profileDir && <span className="ml-1.5 text-[10.5px] text-ink-3">{imported.profileDir}</span>}
|
{imported.profileDir && <span className="ml-1.5 text-[10.5px] text-ink-3">{imported.profileDir}</span>}
|
||||||
</span>
|
</span>
|
||||||
{imported.sourceId && imported.profileDir && (
|
{imported.sourceId && imported.profileDir && (
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ declare global {
|
|||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
browserDetectSources: () => Promise<BrowserSource[]>
|
browserDetectSources: () => Promise<BrowserSource[]>
|
||||||
browserImportProfile: (sourceId: string, profileDir: string) => Promise<BrowserImportResult>
|
browserImportProfile: (sourceId: string, profileDir: string) => Promise<BrowserImportResult>
|
||||||
|
browserImportCookiesFile: (path: string) => Promise<BrowserImportResult>
|
||||||
|
browserOpenExporterFolder: () => Promise<string>
|
||||||
|
pathForFile: (file: File) => string
|
||||||
browserGetProxy: () => Promise<BrowserProxySettings>
|
browserGetProxy: () => Promise<BrowserProxySettings>
|
||||||
browserSetProxy: (input: BrowserProxySettings) => Promise<BrowserProxySettings>
|
browserSetProxy: (input: BrowserProxySettings) => Promise<BrowserProxySettings>
|
||||||
onBrowserEvent: (callback: (event: BrowserUiEvent) => void) => () => void
|
onBrowserEvent: (callback: (event: BrowserUiEvent) => void) => () => void
|
||||||
|
|||||||
Reference in New Issue
Block a user