1import type { ProviderAuthResponse } from "@t3tools/contracts";
2import { useEffect, useRef, useState } from "react";
3import { TerminalSurface } from "../../terminal/TerminalSurface";
4import { ensureLocalApi } from "../../localApi";
5import { terminalThemeFromApp } from "../ThreadTerminalDrawer";
6
7/** Loaded only for an interactive login. PTY input is serialized by the parent. */
8export default function ProviderAuthTerminal({
9 output,
10 outputOffset,
11 onResponse,
12}: {
13 readonly output: string;
14 readonly outputOffset?: number | undefined;
15 readonly onResponse: (response: Extract<ProviderAuthResponse, { type: "terminal" }>) => void;
16}) {
17 const mount = useRef<HTMLDivElement>(null);
18 const surface = useRef<TerminalSurface | null>(null);
19 const latest = useRef({ output, offset: outputOffset ?? output.length, onResponse });
20 const written = useRef(0);
21 const [error, setError] = useState<string | null>(null);
22 useEffect(() => {
23 latest.current = { output, offset: outputOffset ?? output.length, onResponse };
24 const terminal = surface.current;
25 if (!terminal) return;
26 const delta = latest.current.offset - written.current;
27 if (delta > 0 && delta <= output.length) terminal.write(output.slice(-delta));
28 else if (delta !== 0) terminal.resetAndWrite(output);
29 written.current = latest.current.offset;
30 }, [output, outputOffset, onResponse]);
31 useEffect(() => {
32 const element = mount.current;
33 if (!element) return;
34 let disposed = false;
35 void TerminalSurface.create(element, {
36 theme: terminalThemeFromApp(element),
37 font: { size: 13 },
38 onData: (data) => latest.current.onResponse({ type: "terminal", data }),
39 onResize: (cols, rows) =>
40 latest.current.onResponse({ type: "terminal", data: "", size: { cols, rows } }),
41 beforeKey: (event) => event.key !== "Tab",
42 onLinkActivate: (url) => {
43 if (/^https?:\/\//i.test(url))
44 void ensureLocalApi()
45 .shell.openExternal(url)
46 .catch(() => setError("Could not open the provider link."));
47 },
48 })
49 .then((terminal) => {
50 if (disposed) {
51 terminal.dispose();
52 return;
53 }
54 surface.current = terminal;
55 terminal.resetAndWrite(latest.current.output);
56 written.current = latest.current.offset;
57 })
58 .catch(() => setError("Could not load the sign-in terminal. Cancel and retry sign-in."));
59 return () => {
60 disposed = true;
61 surface.current?.dispose();
62 surface.current = null;
63 };
64 }, []);
65 return (
66 <>
67 <div
68 ref={mount}
69 aria-label="Provider sign-in terminal"
70 className="relative h-64 overflow-hidden rounded-md border border-border"
71 />
72 {error ? (
73 <p role="alert" className="text-destructive">
74 {error}
75 </p>
76 ) : null}
77 </>
78 );
79}