1import type { DesktopBridge, DesktopDiscoveredSshHost } from "@t3tools/contracts";
2import * as Effect from "effect/Effect";
3import * as Schema from "effect/Schema";
4import { Atom } from "effect/reactivity";
5
6type DesktopSshDiscoveryBridge = Pick<DesktopBridge, "discoverSshHosts">;
7
8/** Filters and ranks SSH host suggestions as the user types in the host field. */
9export function filterDiscoveredSshHosts(
10 hosts: ReadonlyArray<DesktopDiscoveredSshHost>,
11 query: string,
12): ReadonlyArray<DesktopDiscoveredSshHost> {
13 const normalizedQuery = query.trim().toLowerCase();
14 if (normalizedQuery.length === 0) return hosts;
15
16 const prefixMatches: Array<DesktopDiscoveredSshHost> = [];
17 const substringMatches: Array<DesktopDiscoveredSshHost> = [];
18 for (const host of hosts) {
19 const alias = host.alias.toLowerCase();
20 if (alias.startsWith(normalizedQuery)) {
21 prefixMatches.push(host);
22 } else if (alias.includes(normalizedQuery)) {
23 substringMatches.push(host);
24 }
25 }
26
27 return [...prefixMatches, ...substringMatches];
28}
29
30class DesktopSshDiscoveryUnavailableError extends Schema.TaggedError<DesktopSshDiscoveryUnavailableError>()(
31 "DesktopSshDiscoveryUnavailableError",
32 {},
33) {
34 override get message(): string {
35 return "Desktop SSH host discovery is unavailable.";
36 }
37}
38
39class DesktopSshDiscoveryError extends Schema.TaggedError<DesktopSshDiscoveryError>()(
40 "DesktopSshDiscoveryError",
41 { cause: Schema.Defect() },
42) {
43 override get message(): string {
44 return "Failed to discover SSH hosts.";
45 }
46}
47
48function getDesktopSshDiscoveryBridge(): DesktopSshDiscoveryBridge | undefined {
49 return typeof window === "undefined" ? undefined : window.desktopBridge;
50}
51
52export function createDesktopSshHostsStateAtom(
53 getBridge: () => DesktopSshDiscoveryBridge | undefined,
54) {
55 const discoverDesktopSshHosts = Effect.fn("discoverDesktopSshHosts")(function* () {
56 const bridge = getBridge();
57 if (!bridge) {
58 return yield* new DesktopSshDiscoveryUnavailableError();
59 }
60 return yield* Effect.tryPromise({
61 try: (): Promise<ReadonlyArray<DesktopDiscoveredSshHost>> => bridge.discoverSshHosts(),
62 catch: (cause) => new DesktopSshDiscoveryError({ cause }),
63 });
64 });
65
66 return Atom.make(discoverDesktopSshHosts()).pipe(
67 Atom.swr({ staleTime: 30_000, revalidateOnMount: true }),
68 Atom.keepAlive,
69 Atom.withLabel("desktop:ssh-hosts"),
70 );
71}
72
73export const desktopSshHostsStateAtom = createDesktopSshHostsStateAtom(
74 getDesktopSshDiscoveryBridge,
75);