1import type {
2 AdvertisedEndpoint,
3 DesktopBridge,
4 DesktopServerExposureState,
5} from "@t3tools/contracts";
6import * as Effect from "effect/Effect";
7import * as Schema from "effect/Schema";
8import { Atom } from "effect/reactivity";
9
10import { appAtomRegistry } from "~/rpc/atomRegistry";
11
12const DESKTOP_NETWORK_ACCESS_STALE_TIME_MS = 30_000;
13
14type DesktopNetworkAccessBridge = Pick<
15 DesktopBridge,
16 "getAdvertisedEndpoints" | "getServerExposureState"
17>;
18
19export interface DesktopNetworkAccessSnapshot {
20 readonly advertisedEndpoints: ReadonlyArray<AdvertisedEndpoint>;
21 readonly serverExposureState: DesktopServerExposureState;
22}
23
24class DesktopNetworkAccessUnavailableError extends Schema.TaggedError<DesktopNetworkAccessUnavailableError>()(
25 "DesktopNetworkAccessUnavailableError",
26 {},
27) {
28 override get message(): string {
29 return "Desktop network access is unavailable.";
30 }
31}
32
33class DesktopServerExposureStateLoadError extends Schema.TaggedError<DesktopServerExposureStateLoadError>()(
34 "DesktopServerExposureStateLoadError",
35 { cause: Schema.Defect() },
36) {
37 override get message(): string {
38 return "Failed to load desktop server exposure state.";
39 }
40}
41
42class DesktopAdvertisedEndpointsLoadError extends Schema.TaggedError<DesktopAdvertisedEndpointsLoadError>()(
43 "DesktopAdvertisedEndpointsLoadError",
44 { cause: Schema.Defect() },
45) {
46 override get message(): string {
47 return "Failed to load advertised desktop endpoints.";
48 }
49}
50
51function getDesktopNetworkAccessBridge(): DesktopNetworkAccessBridge | undefined {
52 return typeof window === "undefined" ? undefined : window.desktopBridge;
53}
54
55export function createDesktopNetworkAccessStateAtom(
56 getBridge: () => DesktopNetworkAccessBridge | undefined,
57) {
58 const loadDesktopNetworkAccess = Effect.fn("loadDesktopNetworkAccess")(function* () {
59 const bridge = getBridge();
60 if (!bridge) {
61 return yield* new DesktopNetworkAccessUnavailableError();
62 }
63 const [serverExposureState, advertisedEndpoints] = yield* Effect.all(
64 [
65 Effect.tryPromise({
66 try: () => bridge.getServerExposureState(),
67 catch: (cause) => new DesktopServerExposureStateLoadError({ cause }),
68 }),
69 Effect.tryPromise({
70 try: () => bridge.getAdvertisedEndpoints(),
71 catch: (cause) => new DesktopAdvertisedEndpointsLoadError({ cause }),
72 }),
73 ],
74 { concurrency: "unbounded" },
75 );
76 return {
77 advertisedEndpoints,
78 serverExposureState,
79 } satisfies DesktopNetworkAccessSnapshot;
80 });
81
82 return Atom.make(loadDesktopNetworkAccess()).pipe(
83 Atom.swr({
84 staleTime: DESKTOP_NETWORK_ACCESS_STALE_TIME_MS,
85 revalidateOnMount: true,
86 }),
87 Atom.keepAlive,
88 Atom.withLabel("desktop:network-access"),
89 );
90}
91
92export const desktopNetworkAccessStateAtom = createDesktopNetworkAccessStateAtom(
93 getDesktopNetworkAccessBridge,
94);
95
96export function refreshDesktopNetworkAccessState(): void {
97 appAtomRegistry.refresh(desktopNetworkAccessStateAtom);
98}