import { useCallback, useEffect, useState } from "react"; import { useI18n } from "../i18n"; import { getConnectedInjectedAddress, getInjectedWallet, signInWithInjectedWallet, type WalletKind, } from "./injected"; import { shortenAddress, useWallet } from "./WalletProvider"; const AUTO_LOGIN_PARAM = "autoLogin"; const ETHEREUM_WAIT_MS = 8000; const ETHEREUM_POLL_MS = 200; type AutoLoginRequest = { kind: WalletKind; ready: boolean; address: string | null; signing: boolean; error: string; }; function parseKind(value: string | null): WalletKind | null { if (value === "tokenPocket" || value === "metaMask" || value === "imToken") { return value; } return null; } function stripAutoLoginParam(): void { const url = new URL(window.location.href); url.searchParams.delete(AUTO_LOGIN_PARAM); const qs = url.searchParams.toString(); const next = url.pathname + (qs ? `?${qs}` : "") + url.hash; window.history.replaceState({}, "", next); } function waitForInjected(kind: WalletKind): Promise { return new Promise((resolve) => { const start = Date.now(); const tick = () => { if (getInjectedWallet(kind)) { resolve(true); return; } if (Date.now() - start >= ETHEREUM_WAIT_MS) { resolve(false); return; } window.setTimeout(tick, ETHEREUM_POLL_MS); }; tick(); }); } /** * When the page is opened via a `?autoLogin=` deeplink (typically from * inside TokenPocket / imToken in-app browsers), show an explicit verification * prompt first. The wallet signature and backend verification only start after * the user taps the verification button, so Chrome -> wallet handoff never logs * in silently from a cached in-app-browser session. */ export function AutoInjectedLogin() { const { t } = useI18n(); const { completeLogin, status } = useWallet(); const [request, setRequest] = useState(null); useEffect(() => { if (typeof window === "undefined") return; const params = new URLSearchParams(window.location.search); const kind = parseKind(params.get(AUTO_LOGIN_PARAM)); if (!kind) return; if (status === "loading") return; stripAutoLoginParam(); if (status === "loggedIn") { setRequest(null); return; } let cancelled = false; setRequest({ kind, ready: false, address: null, signing: false, error: "", }); void waitForInjected(kind).then(async (ready) => { if (cancelled) return; if (!ready) { setRequest((current) => current?.kind === kind ? { ...current, ready: false, error: t("walletNoBrowserWallet") } : current, ); return; } const address = await getConnectedInjectedAddress(kind); if (cancelled) return; setRequest((current) => current?.kind === kind ? { ...current, ready: true, address, error: "" } : current, ); }); return () => { cancelled = true; }; }, [status, t]); const verifyAddress = useCallback(async () => { if (!request || !request.ready || request.signing) return; setRequest((current) => current ? { ...current, signing: true, error: "" } : current, ); try { const res = await signInWithInjectedWallet(request.kind); completeLogin(res.token, res.wallet); setRequest(null); } catch (err) { const message = err instanceof Error ? err.message : t("walletLoginFailed"); setRequest((current) => current ? { ...current, signing: false, error: message || t("walletLoginFailed"), } : current, ); // eslint-disable-next-line no-console console.warn("[wallet-autologin] verification failed", err); } }, [completeLogin, request, t]); if (!request) return null; const addressLabel = request.address ? shortenAddress(request.address) : t("walletVerifyAddressUnknown"); return (

{t(walletNameKey(request.kind))}

{t("walletVerifyAddressTitle")}

{t("walletVerifyAddressDesc")}

{t("walletDetectedAddress")}

{addressLabel}

{request.error ? (

{request.error}

) : null}
); } function walletNameKey(kind: WalletKind): string { if (kind === "tokenPocket") return "walletTokenPocket"; if (kind === "metaMask") return "walletMetaMask"; return "walletImToken"; }