feat(sites): site requests follow browser UA, smooth UA switch, CF cookies browser-only (BR-4)

- every outbound site request (adapter list/search/detail/download/health,
  RSS, login, user data) carries the account UA via SourcesRuntime.siteFetch;
  no more Deno default UA
- priority: account manual > pending > global setting > browser identity
  (fallback: same-shape Windows Firefox UA); settings UA default is empty
- migration 0056: existing cookie/password accounts keep their old UA as
  ua_pending until re-login or browser cookie write-back switches cookie+UA
  in one transaction; browser_provider.identity caches the browser identity
- cf_clearance/__cf_bm stripped on browser write-back (names kept only)
- browser sessions get per-site egress (same proxy as HTTP, or direct)
- site account page shows UA source; raw UA in tech info

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 03:17:13 +08:00
1 parent ebd07c9230
commit adc853aa64
21 files changed
+955 -72

No files matched your search

@@ -10,6 +10,7 @@ import { FormField } from "~/components/console/form-field.tsx";
import { KvList } from "~/components/console/kv-list.tsx";
import { SecretInput } from "~/components/console/secret-input.tsx";
import { StatusBadge } from "~/components/console/status-badge.tsx";
import { TechInfo } from "~/components/console/tech-info.tsx";
import { Button } from "~/components/ui/button.tsx";
import { relTime } from "~/components/workbench/release-fields.ts";
import { errorMessage } from "~/lib/api.ts";
@@ -143,7 +144,7 @@ function AccountBlock({
label: "会话 Cookie",
value: a.authKind === "password" ? (a.hasSession ? "已保存" : "还没有") : null,
},
{ label: "User-Agent", value: a.userAgent ?? "站点默认", wide: true, mono: !!a.userAgent },
{ label: "User-Agent", value: UA_SOURCE_TEXT[a.userAgentSource], wide: true },
...(a.lastError
? [
{
@@ -159,12 +160,32 @@ function AccountBlock({
: []),
]}
/>
<TechInfo
className="mt-3"
columns={1}
items={[
{ label: "当前 User-Agent", value: a.effectiveUserAgent, wide: true },
{
label: "仅浏览器使用的 Cookie",
value: a.browserOnlyCookies.length ? a.browserOnlyCookies.join(", ") : null,
wide: true,
},
]}
/>
</section>
<LoginHistory accountId={a.id} />
</div>
);
}
/** 站点请求 UA 的来源(BR-4);原始 UA 在技术信息里 */
const UA_SOURCE_TEXT: Record<SiteAccount["userAgentSource"], string> = {
browser: "跟随浏览器",
manual: "手动填写",
global: "使用设置里的全局 UA",
pending: "待切换:下次刷新登录,或在原站视图里用过后改为跟随浏览器",
};
const ACTION_LABEL: Record<string, string> = {
"site_account.login_refreshed": "刷新登录",
"site_account.login_refresh_requested": "请求刷新登录",