Skip to content
Source
Sign inSign up

portDiscoveryState.ts

80 lines2.6 KB

1import {
2 CONFIGURED_LOCAL_SERVER_URLS_MAX_ITEMS,
3 PREVIEW_URL_MAX_LENGTH,
4 type DiscoveredLocalServer,
5 type EnvironmentId,
6 type ThreadId,
7} from "@t3tools/contracts";
8import { isLoopbackHost } from "@t3tools/shared/preview";
9​
10import { previewEnvironment } from "./state/preview";
11import { useEnvironmentQuery } from "./state/query";
12​
13const EMPTY_PORTS: ReadonlyArray<DiscoveredLocalServer> = Object.freeze([]);
14​
15interface DiscoveredPortsState {
16 readonly servers: ReadonlyArray<DiscoveredLocalServer>;
17 readonly configuredUrlProbing: boolean;
19​
20export function boundConfiguredLocalServerUrls(
21 urls: ReadonlyArray<string> | undefined,
22): ReadonlyArray<string> {
23 const bounded: string[] = [];
24 const seen = new Set<string>();
25 for (const raw of urls ?? []) {
26 if (raw.length === 0 || raw.length > PREVIEW_URL_MAX_LENGTH || raw.trim().length !== raw.length)
27 continue;
28 try {
29 const url = new URL(raw);
30 if (url.protocol !== "http:" && url.protocol !== "https:") continue;
31 if (!isLoopbackHost(url.hostname) || url.href.length > PREVIEW_URL_MAX_LENGTH) continue;
32 const resourceUrl = new URL(url.href);
33 resourceUrl.hash = "";
34 if (seen.has(resourceUrl.href)) continue;
35 seen.add(resourceUrl.href);
36 bounded.push(url.href);
37 if (bounded.length >= CONFIGURED_LOCAL_SERVER_URLS_MAX_ITEMS) break;
38 } catch {}
39 }
40 return bounded;
42​
43function useDiscoveredPorts(
44 environmentId: EnvironmentId | null,
45 configuredUrls?: ReadonlyArray<string>,
46): ReadonlyArray<DiscoveredLocalServer> {
47 return useDiscoveredPortsState(environmentId, configuredUrls).servers;
49​
50export function useDiscoveredPortsState(
51 environmentId: EnvironmentId | null,
52 configuredUrls?: ReadonlyArray<string>,
53): DiscoveredPortsState {
54 const boundedConfiguredUrls = boundConfiguredLocalServerUrls(configuredUrls);
55 const query = useEnvironmentQuery(
56 environmentId === null
57 ? null
58 : previewEnvironment.discoveredServers({
59 environmentId,
60 input: boundedConfiguredUrls.length ? { configuredUrls: boundedConfiguredUrls } : {},
61 }),
62 );
63 return {
64 servers: query.data?.servers ?? EMPTY_PORTS,
65 configuredUrlProbing: query.data?.configuredUrlProbing === true,
66 };
68​
69export function useThreadDiscoveredPorts(input: {
70 readonly environmentId: EnvironmentId | null;
71 readonly threadId: ThreadId | null;
72}): ReadonlyArray<DiscoveredLocalServer> {
73 const ports = useDiscoveredPorts(input.environmentId);
74 return input.threadId
75 ? ports.filter(
76 (port) =>
77 port.terminal?.threadId === input.threadId || port.agent?.threadId === input.threadId,
78 )
79 : EMPTY_PORTS;