This commit is contained in:
zhu
2026-05-13 16:59:46 +08:00
parent 3d2dc708cc
commit 95ef40c03d
10 changed files with 326 additions and 392 deletions

View File

@@ -0,0 +1,144 @@
"use client";
import {useEffect, useState} from "react";
import {useSearchParams} from "next/navigation";
import useUserStore from "@/store/user";
import {pairExtensionTokenApi} from "@/api/scan";
import {getSettingApi} from "@/api/set";
import {EXTENSION_ID} from "@/utils/extension/detect_extension";
declare const chrome: any;
/**
* 网页侧“配对回传”:
* - 由扩展 popup 的“登录”按钮打开本页(带 from=extension
* - 若网页已登录:调用 `/api/auth/extension-pair` 拿到 extension token然后回传给扩展保存。
* 中文备注:这里不搞复杂类型,按需求全用 any。
*/
export default function PairHandoff() {
const params = useSearchParams();
const token = useUserStore((s) => s.token);
const [status, setStatus] = useState<"idle" | "pairing" | "ok" | "need_login" | "error">("idle");
const [errorText, setErrorText] = useState("");
useEffect(() => {
const from = params.get("from");
if (from !== "extension") return;
// 中文备注:必须在浏览器环境 + 可调用 chrome.runtime 的情况下才能回传。
if (typeof chrome === "undefined" || !chrome.runtime?.sendMessage) {
return;
}
if (!token) {
setStatus("need_login");
return;
}
void (async () => {
setStatus("pairing");
setErrorText("");
try {
const extId = params.get("extId") || EXTENSION_ID;
// 1) 先问扩展是否已经配对(避免重复 pair
const authCheck: any = await new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
extId,
{type: "DIANSHAN_AUTH_CHECK"},
(resp: any) => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
return;
}
resolve(resp);
},
);
});
if (authCheck?.data?.authed) {
setStatus("ok");
return;
}
// 2) 换取扩展 tokenWeb token -> extension token
const pair = await pairExtensionTokenApi();
// 3) 读取品牌/店铺/定时配置(给扩展做 ingest 与 scheduled scan 使用)
const settings: any = await getSettingApi();
const brand = settings?.brand || null;
const storeId = brand?.stores?.[0]?.id || brand?.storeId || brand?.store_id || null;
const authState: any = {
token: pair?.token,
apiBaseUrl: pair?.apiBaseUrl,
userEmail: pair?.userEmail ?? null,
brandId: brand?.id ?? null,
storeId,
timezone: brand?.timezone ?? null,
morningBriefHour: brand?.morning_brief_hour ?? null,
eveningRecapHour: brand?.evening_recap_hour ?? null,
};
// 4) 回传给扩展保存
await new Promise<void>((resolve, reject) => {
chrome.runtime.sendMessage(
extId,
{type: "DIANSHAN_SSO_HANDOFF", payload: {authState}},
(resp: any) => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
return;
}
if (!resp?.ok) {
reject(new Error(resp?.error || "handoff_failed"));
return;
}
resolve();
},
);
});
setStatus("ok");
} catch (e: any) {
setStatus("error");
setErrorText(e?.message || "Pairing failed.");
}
})();
}, [params, token]);
if (status === "idle") return null;
if (status === "need_login") {
return (
<div className="rounded-lg border border-border/60 bg-muted/20 p-4 text-xs text-muted-foreground">
</div>
);
}
if (status === "pairing") {
return (
<div className="rounded-lg border border-border/60 bg-muted/20 p-4 text-xs text-muted-foreground">
</div>
);
}
if (status === "ok") {
return (
<div className="rounded-lg border border-emerald-200 bg-emerald-50/60 p-4 text-xs text-emerald-700">
</div>
);
}
return (
<div className="rounded-lg border border-rose-200 bg-rose-50/60 p-4 text-xs text-rose-700">
{errorText || "unknown error"}
</div>
);
}