feat(browser): unify site credentials with browser profiles (BR-2)
- Inject the site account Cookie (manual + auto-login session, session wins)
into the camofox profile before the first tab opens; password accounts
without a session run refresh-login first; API Key sites are skipped.
- Cookie expired after injection -> credential.status=expired; cookie
accounts go to waiting/login_required ("log in once, applies site-wide").
- Write back site-domain cookies from the browser on handover, session close,
every 10 min of activity, and from refresh-login when the stored cookie is
invalid (verified before saving). user_info.cookieOrigin records
source=browser; source shown as manual/login/browser in the origin view.
- New camofox plugin umt-cookies (GET/POST /umt/sessions/:userId/cookies
under the access key); profile dir 0700/files 0600 in the desktop image.
- Single entry: sources-identity/service/credentials.ts.
- Real check: HHClub cookie injected into a fresh profile -> home page
logged in (logout link present), 5 cookies read back.
Requires rebuilding umt-browser-desktop (docker compose build camofox &&
docker compose up -d camofox) to take effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
901b01d1a1
commit
28974d1e1a
58 files changed
+2843
-1112
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import type { BrowserController } from "@umt/contracts";
|
||||
import type { BrowserController, BrowserCredentialState } from "@umt/contracts";
|
||||
|
||||
/** 截图上的点击位置 → 浏览器视口像素坐标(按图片实际像素与显示尺寸的比例换算,四舍五入并夹在画面内) */
|
||||
export function toViewport(
|
||||
@@ -28,7 +28,7 @@ const WAITING_TEXT: Record<string, string> = {
|
||||
challenge: "页面需要人工验证",
|
||||
two_factor: "需要二次验证或本人设备确认",
|
||||
challenge_unsolved: "自动处理页面挑战未成功",
|
||||
login_required: "登录已失效",
|
||||
login_required: "登录已失效:接管后在画面里登录一次,交还后全站生效",
|
||||
lease_expired: "接管超时未交还",
|
||||
agent_request: "Agent 请求人工处理",
|
||||
human_released_waiting: "交还时选择继续等待",
|
||||
@@ -44,3 +44,27 @@ export function holderName(holder: string): string {
|
||||
const i = holder.indexOf(":");
|
||||
return i >= 0 ? holder.slice(i + 1) : holder;
|
||||
}
|
||||
|
||||
const COOKIE_SOURCE: Record<string, string> = { manual: "手填", login: "自动登录", browser: "浏览器回写" };
|
||||
|
||||
/** 会话登录态徽标(BR-2):来源(手填 / 自动登录 / 浏览器回写)与时间;没有账号或不适用时不显示 */
|
||||
export function credentialBadge(
|
||||
c: BrowserCredentialState | null | undefined,
|
||||
): { tone: "success" | "warning" | "neutral"; text: string; title: string | undefined } | null {
|
||||
if (!c || c.status === "not_applicable") return null;
|
||||
const when = c.sourceAt ? new Date(c.sourceAt).toLocaleString() : null;
|
||||
const src = c.source ? `${COOKIE_SOURCE[c.source] ?? c.source}${when ? ` · ${when}` : ""}` : null;
|
||||
const title = c.message ?? undefined;
|
||||
switch (c.status) {
|
||||
case "injected":
|
||||
return { tone: "success", text: `登录态:${src ?? "已注入"}`, title };
|
||||
case "expired":
|
||||
return { tone: "warning", text: "登录态已过期:在画面里登录一次即可全站生效", title };
|
||||
case "none":
|
||||
return { tone: "neutral", text: "账号没有登录态:在画面里登录一次即可全站生效", title };
|
||||
case "unsupported":
|
||||
return { tone: "warning", text: "浏览器服务不支持注入登录态", title };
|
||||
default:
|
||||
return { tone: "warning", text: "登录态注入失败", title };
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { StatusBadge, type StatusTone } from "~/components/console/status-badge.
|
||||
import { Button } from "~/components/ui/button.tsx";
|
||||
import { apiData, errorMessage } from "~/lib/api.ts";
|
||||
import { qk } from "~/lib/query-keys.ts";
|
||||
import { CONTROLLER_LABEL, holderName, waitingText } from "./coords.ts";
|
||||
import { CONTROLLER_LABEL, credentialBadge, holderName, waitingText } from "./coords.ts";
|
||||
import { SiteOriginView } from "./site-origin-view.tsx";
|
||||
|
||||
// 原站视图桌面模式(docs/design/webtop-remote-browser.md §5.7):iframe 嵌浏览器桌面(Selkies),
|
||||
@@ -115,6 +115,7 @@ function DesktopView({
|
||||
const mine = role === "controller";
|
||||
const busy = control.acquire.isPending || control.release.isPending;
|
||||
const waiting = waitingText(s.waitingReason);
|
||||
const cred = credentialBadge(s.credential);
|
||||
const leaseHolder = s.lease ? holderName(s.lease.holder) : null;
|
||||
const conn: { tone: StatusTone; text: string } = loaded
|
||||
? mine
|
||||
@@ -131,6 +132,11 @@ function DesktopView({
|
||||
{human && leaseHolder ? `(${leaseHolder})` : ""}
|
||||
</StatusBadge>
|
||||
{waiting ? <StatusBadge tone="warning">{waiting}</StatusBadge> : null}
|
||||
{cred ? (
|
||||
<span title={cred.title}>
|
||||
<StatusBadge tone={cred.tone}>{cred.text}</StatusBadge>
|
||||
</span>
|
||||
) : null}
|
||||
<div className="ml-auto flex flex-wrap items-center gap-2">
|
||||
{human && mine ? (
|
||||
<Button
|
||||
|
||||
@@ -34,7 +34,7 @@ import { StatusBadge, type StatusTone } from "~/components/console/status-badge.
|
||||
import { Button } from "~/components/ui/button.tsx";
|
||||
import { Input } from "~/components/ui/input.tsx";
|
||||
import { errorMessage } from "~/lib/api.ts";
|
||||
import { CONTROLLER_LABEL, holderName, toViewport, waitingText } from "./coords.ts";
|
||||
import { CONTROLLER_LABEL, credentialBadge, holderName, toViewport, waitingText } from "./coords.ts";
|
||||
|
||||
// 原站视图(REQ §4.7.1、§4.7.3):同一个 camofox 会话的定时截图 + 人工输入映射。
|
||||
// 人工接管前服务端先停掉该会话的自动化输入;交还后 Agent 必须重新读取页面。
|
||||
@@ -114,6 +114,7 @@ export function SiteOriginView({ site }: { site: SiteListItem }) {
|
||||
? { tone: "success", text: "已连接" }
|
||||
: { tone: "neutral", text: "连接中" };
|
||||
const waiting = waitingText(s.waitingReason);
|
||||
const cred = credentialBadge(s.credential);
|
||||
const leaseHolder = s.lease ? holderName(s.lease.holder) : null;
|
||||
|
||||
const onScreenClick = (e: MouseEvent<HTMLImageElement>) => {
|
||||
@@ -149,6 +150,11 @@ export function SiteOriginView({ site }: { site: SiteListItem }) {
|
||||
{human && leaseHolder ? `(${leaseHolder})` : ""}
|
||||
</StatusBadge>
|
||||
{waiting ? <StatusBadge tone="warning">{waiting}</StatusBadge> : null}
|
||||
{cred ? (
|
||||
<span title={cred.title}>
|
||||
<StatusBadge tone={cred.tone}>{cred.text}</StatusBadge>
|
||||
</span>
|
||||
) : null}
|
||||
{unsupported ? <StatusBadge tone="destructive">该浏览器服务不支持画面与输入</StatusBadge> : null}
|
||||
{unverified ? <StatusBadge tone="warning">画面与输入未对真实实例核验</StatusBadge> : null}
|
||||
<div className="ml-auto flex flex-wrap items-center gap-2">
|
||||
|
||||
Reference in new issue
Block a user