import { useActionMutation } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { IconBrowserCheck, IconPlugConnected } from "@tabler/icons-react"; import { useState } from "react"; import { useSearchParams } from "react-router"; import { Button } from "../../components/ui/button.js"; import { browserChatExtensionIdSchema, browserChatNonceSchema, } from "../../lib/browser-chat-protocol.js"; interface BrowserChatSessionResult { startPath: string; expiresAt: number; parentOrigin: string; remoteDevice: { id: string; token: string; }; relayBaseUrl: string; } interface ChromeRuntimeApi { lastError?: { message?: string }; sendMessage( extensionId: string, message: Record, callback: (response?: { ok?: boolean; error?: string }) => void, ): void; } function sendBrowserChatSession( extensionId: string, message: Record, ): Promise { const runtime = ( globalThis as typeof globalThis & { chrome?: { runtime?: ChromeRuntimeApi }; } ).chrome?.runtime; if (!runtime) { return Promise.reject(new Error("extension-unavailable")); } return new Promise((resolve, reject) => { runtime.sendMessage(extensionId, message, (response) => { const runtimeError = runtime.lastError?.message; if (runtimeError) { reject(new Error(runtimeError)); } else if (response?.ok !== true) { reject(new Error("extension-rejected")); } else { resolve(); } }); }); } export function meta() { return [{ title: "Dispatch" }]; } export default function BrowserConnectRoute() { const t = useT(); const [searchParams] = useSearchParams(); const extensionId = searchParams.get("extensionId") ?? ""; const parsedExtensionId = browserChatExtensionIdSchema.safeParse(extensionId); const nonce = browserChatNonceSchema.safeParse(searchParams.get("nonce")); const requestIsValid = parsedExtensionId.success && nonce.success; const [connected, setConnected] = useState(false); const [error, setError] = useState(null); const createSession = useActionMutation< BrowserChatSessionResult, { extensionId: string; nonce: string } >("create-browser-chat-session", {}); const connect = async () => { if (!requestIsValid) return; setError(null); try { const session = await createSession.mutateAsync({ extensionId, nonce: nonce.data, }); await sendBrowserChatSession(extensionId, { type: "browser-chat.session.v1", nonce: nonce.data, startPath: session.startPath, dispatchOrigin: window.location.origin, expiresAt: new Date(session.expiresAt).toISOString(), remoteDevice: session.remoteDevice, relayBaseUrl: session.relayBaseUrl, }); setConnected(true); } catch (cause) { setError( cause instanceof Error && cause.message === "extension-unavailable" ? t("dispatch.pages.browserConnectOpenFromExtension") : t("dispatch.pages.browserConnectFailed"), ); } }; return (

{t("dispatch.pages.browserConnectTitle")}

{t("dispatch.pages.browserConnectDescription")}

{!requestIsValid ? (

{t("dispatch.pages.browserConnectInvalid")}

) : connected ? (

{t("dispatch.pages.browserConnectConnected")}

) : ( )} {error ? (

{error}

) : null}
); }