| 1 | import { createHubEnvironmentCapabilitiesAtom } from "@t3tools/client-runtime/state/hub"; |
| 2 | import { hubConnection } from "../connection/hub"; |
| 3 | import { |
| 4 | DEFAULT_SERVER_SETTINGS, |
| 5 | type EditorId, |
| 6 | type EnvironmentTheme, |
| 7 | type ServerConfig, |
| 8 | type ServerConfigStreamEvent, |
| 9 | type ServerLifecycleLegacyThreadMigrationPayload, |
| 10 | type ServerLifecycleWelcomePayload, |
| 11 | type ServerProvider, |
| 12 | type ServerSettings, |
| 13 | } from "@t3tools/contracts"; |
| 14 | import { createServerEnvironmentAtoms } from "@t3tools/client-runtime/state/server"; |
| 15 | import { createOutdatedServerUpdateCommand } from "@t3tools/client-runtime/state/outdatedServerUpdate"; |
| 16 | import { createEnvironmentServerConfigsAtom } from "@t3tools/client-runtime/state/shell"; |
| 17 | import { mergeWithDefaultKeybindings } from "@t3tools/shared/keybindings"; |
| 18 | import * as Option from "effect/Option"; |
| 19 | import { AsyncResult, Atom } from "effect/reactivity"; |
| 20 | |
| 21 | import { environmentCatalog } from "../connection/catalog"; |
| 22 | import { connectionAtomRuntime } from "../connection/runtime"; |
| 23 | import { primaryEnvironmentIdAtom } from "./primaryEnvironment"; |
| 24 | import { environmentSession } from "./session"; |
| 25 | |
| 26 | // Opted in for every environment, not just the primary one. Only the primary |
| 27 | // environment's themes are rendered, but which environment is primary changes |
| 28 | // at runtime and the subscription payload is fixed when it is established -- |
| 29 | // gating on "primary right now" would leave a newly promoted environment with |
| 30 | // no themes until it reconnected. The set is capped server-side and stripped |
| 31 | // before caching, so following all of them costs a few KB per environment. |
| 32 | export const serverEnvironment = createServerEnvironmentAtoms(connectionAtomRuntime, { |
| 33 | initialConfigValueAtom: environmentSession.initialConfigValueAtom, |
| 34 | environmentThemes: true, |
| 35 | usageLimitSources: true, |
| 36 | usageLimitsCommand: true, |
| 37 | }); |
| 38 | /** Updates a host whose protocol is too old for this client to connect to. */ |
| 39 | export const updateOutdatedServer = createOutdatedServerUpdateCommand(connectionAtomRuntime); |
| 40 | export const environmentServerConfigsAtom = createEnvironmentServerConfigsAtom({ |
| 41 | catalogValueAtom: environmentCatalog.catalogValueAtom, |
| 42 | serverConfigValueAtom: serverEnvironment.configValueAtom, |
| 43 | }); |
| 44 | export const environmentCapabilitiesAtom = createHubEnvironmentCapabilitiesAtom( |
| 45 | environmentServerConfigsAtom, |
| 46 | hubConnection, |
| 47 | ); |
| 48 | |
| 49 | interface PrimaryServerState { |
| 50 | readonly config: ServerConfig | null; |
| 51 | readonly latestEvent: ServerConfigStreamEvent | null; |
| 52 | readonly welcome: ServerLifecycleWelcomePayload | null; |
| 53 | } |
| 54 | |
| 55 | const EMPTY_AVAILABLE_EDITORS: ReadonlyArray<EditorId> = []; |
| 56 | export const EMPTY_SERVER_PROVIDERS: ReadonlyArray<ServerProvider> = []; |
| 57 | const EMPTY_PRIMARY_SERVER_STATE: PrimaryServerState = { |
| 58 | config: null, |
| 59 | latestEvent: null, |
| 60 | welcome: null, |
| 61 | }; |
| 62 | |
| 63 | const primaryServerStateAtom = Atom.make((get): PrimaryServerState => { |
| 64 | const environmentId = get(primaryEnvironmentIdAtom); |
| 65 | if (environmentId === null) { |
| 66 | return EMPTY_PRIMARY_SERVER_STATE; |
| 67 | } |
| 68 | |
| 69 | const target = { environmentId, input: {} }; |
| 70 | const configProjection = Option.getOrNull( |
| 71 | AsyncResult.value(get(serverEnvironment.configProjection(target))), |
| 72 | ); |
| 73 | const welcome = Option.getOrNull(AsyncResult.value(get(serverEnvironment.welcome(target)))); |
| 74 | |
| 75 | return { |
| 76 | config: get(serverEnvironment.configValueAtom(environmentId)), |
| 77 | latestEvent: configProjection?.latestEvent ?? null, |
| 78 | welcome, |
| 79 | }; |
| 80 | }).pipe(Atom.withLabel("web-primary-server-state")); |
| 81 | |
| 82 | export const primaryServerConfigAtom = Atom.make( |
| 83 | (get): ServerConfig | null => get(primaryServerStateAtom).config, |
| 84 | ).pipe(Atom.withLabel("web-primary-server-config")); |
| 85 | |
| 86 | export const primaryServerConfigEventAtom = Atom.make( |
| 87 | (get): ServerConfigStreamEvent | null => get(primaryServerStateAtom).latestEvent, |
| 88 | ).pipe(Atom.withLabel("web-primary-server-config-event")); |
| 89 | |
| 90 | export const primaryServerWelcomeAtom = Atom.make( |
| 91 | (get): ServerLifecycleWelcomePayload | null => get(primaryServerStateAtom).welcome, |
| 92 | ).pipe(Atom.withLabel("web-primary-server-welcome")); |
| 93 | |
| 94 | export const primaryServerLegacyThreadMigrationAtom = Atom.make( |
| 95 | (get): ServerLifecycleLegacyThreadMigrationPayload | null => { |
| 96 | const environmentId = get(primaryEnvironmentIdAtom); |
| 97 | if (environmentId === null) { |
| 98 | return null; |
| 99 | } |
| 100 | return Option.getOrNull( |
| 101 | AsyncResult.value(get(serverEnvironment.legacyThreadMigration({ environmentId, input: {} }))), |
| 102 | ); |
| 103 | }, |
| 104 | ).pipe(Atom.withLabel("web-primary-server-legacy-thread-migration")); |
| 105 | |
| 106 | export const primaryServerSettingsAtom = Atom.make( |
| 107 | (get): ServerSettings => get(primaryServerConfigAtom)?.settings ?? DEFAULT_SERVER_SETTINGS, |
| 108 | ).pipe(Atom.withLabel("web-primary-server-settings")); |
| 109 | |
| 110 | export const primaryServerProvidersAtom = Atom.make( |
| 111 | (get): ReadonlyArray<ServerProvider> => |
| 112 | get(primaryServerConfigAtom)?.providers ?? EMPTY_SERVER_PROVIDERS, |
| 113 | ).pipe(Atom.withLabel("web-primary-server-providers")); |
| 114 | |
| 115 | export const primaryServerKeybindingsAtom = Atom.make((get): ServerConfig["keybindings"] => |
| 116 | mergeWithDefaultKeybindings(get(primaryServerConfigAtom)?.keybindings ?? []), |
| 117 | ).pipe(Atom.withLabel("web-primary-server-keybindings")); |
| 118 | |
| 119 | export const primaryServerAvailableEditorsAtom = Atom.make( |
| 120 | (get): ReadonlyArray<EditorId> => |
| 121 | get(primaryServerConfigAtom)?.availableEditors ?? EMPTY_AVAILABLE_EDITORS, |
| 122 | ).pipe(Atom.withLabel("web-primary-server-available-editors")); |
| 123 | |
| 124 | const EMPTY_ENVIRONMENT_THEMES: ReadonlyArray<EnvironmentTheme> = []; |
| 125 | |
| 126 | /** |
| 127 | * Palettes published by the primary environment's machine. Only the primary |
| 128 | * environment: a client follows the machine it is anchored to, not every |
| 129 | * environment it happens to be connected to. |
| 130 | */ |
| 131 | export const primaryServerEnvironmentThemesAtom = Atom.make( |
| 132 | (get): ReadonlyArray<EnvironmentTheme> => |
| 133 | get(primaryServerConfigAtom)?.environmentThemes ?? EMPTY_ENVIRONMENT_THEMES, |
| 134 | ).pipe(Atom.withLabel("web-primary-server-environment-themes")); |