104 lines · 3.7 KB · TypeScript
RawDownload
import { useAtomValue } from "@effect/atom-react";import type {  DesktopPreviewBridge,  DesktopPreviewWebviewConfig,  EnvironmentId,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import { AsyncResult, Atom } from "effect/unstable/reactivity"; import { previewBridge } from "~/components/preview/previewBridge"; const PREVIEW_CONFIG_STALE_TIME_MS = 5 * 60_000;const PREVIEW_CONFIG_IDLE_TTL_MS = 10 * 60_000; export class PreviewWebviewBridgeUnavailableError extends Schema.TaggedErrorClass<PreviewWebviewBridgeUnavailableError>()(  "PreviewWebviewBridgeUnavailableError",  { environmentId: Schema.String },) {  override get message(): string {    return `Desktop preview configuration is unavailable for environment "${this.environmentId}".`;  }} export class PreviewWebviewConfigLoadError extends Schema.TaggedErrorClass<PreviewWebviewConfigLoadError>()(  "PreviewWebviewConfigLoadError",  {    environmentId: Schema.String,    cause: Schema.Defect(),  },) {  override get message(): string {    return `Failed to load desktop preview configuration for environment "${this.environmentId}".`;  }} export const PreviewWebviewConfigError = Schema.Union([  PreviewWebviewBridgeUnavailableError,  PreviewWebviewConfigLoadError,]);export type PreviewWebviewConfigError = typeof PreviewWebviewConfigError.Type; type PreviewConfigBridge = Pick<DesktopPreviewBridge, "getPreviewConfig">; export const loadPreviewWebviewConfig = (  environmentId: EnvironmentId,  profileId?: string,  bridge: PreviewConfigBridge | null = previewBridge,): Effect.Effect<DesktopPreviewWebviewConfig, PreviewWebviewConfigError> => {  if (bridge === null) {    return Effect.fail(new PreviewWebviewBridgeUnavailableError({ environmentId }));  }   return Effect.tryPromise({    try: () => bridge.getPreviewConfig(environmentId, profileId),    catch: (cause) => new PreviewWebviewConfigLoadError({ environmentId, cause }),  });}; /** * `Atom.family` keys on its argument, so the environment and profile are * folded into one string: passing an object would allocate a fresh entry on * every render. * * The profile is the tail rather than a second field, so an id containing the * delimiter round-trips whole instead of being truncated into a different * profile's key. `BrowserProfileId` rejects control characters, which is what * makes the environment side of the split unambiguous. */const CONFIG_KEY_DELIMITER = "\u0000"; const configKey = (environmentId: EnvironmentId, profileId: string | undefined): string =>  `${environmentId}${CONFIG_KEY_DELIMITER}${profileId ?? ""}`; const parseConfigKey = (key: string): { environmentId: EnvironmentId; profileId?: string } => {  const delimiter = key.indexOf(CONFIG_KEY_DELIMITER);  const environmentId = (delimiter === -1 ? key : key.slice(0, delimiter)) as EnvironmentId;  const profileId = delimiter === -1 ? "" : key.slice(delimiter + CONFIG_KEY_DELIMITER.length);  return {    environmentId,    ...(profileId === "" ? {} : { profileId }),  };}; const previewWebviewConfigAtom = Atom.family((key: string) => {  const { environmentId, profileId } = parseConfigKey(key);  return Atom.make(loadPreviewWebviewConfig(environmentId, profileId)).pipe(    Atom.swr({      staleTime: PREVIEW_CONFIG_STALE_TIME_MS,      revalidateOnMount: true,    }),    Atom.setIdleTTL(PREVIEW_CONFIG_IDLE_TTL_MS),    Atom.withLabel(`preview:webview-config:${key}`),  );}); export function usePreviewWebviewConfig(  environmentId: EnvironmentId,  profileId?: string,): DesktopPreviewWebviewConfig | null {  const result = useAtomValue(previewWebviewConfigAtom(configKey(environmentId, profileId)));  return Option.getOrNull(AsyncResult.value(result));}