257 lines · 8.1 KB · TypeScript
RawDownload
import {  ClientPresentation,  CloudSession,  EnvironmentOwnedDataCleanup,  PlatformConnectionSource,  PrimaryEnvironmentAuth,  RelayDeviceIdentity,  SshEnvironmentGateway,} from "@t3tools/client-runtime/platform";import {  ConnectionBlockedError,  ConnectionTransientError,  Connectivity,  Wakeups,} from "@t3tools/client-runtime/connection";import { managedRelayAccountChanges, managedRelaySessionAtom } from "@t3tools/client-runtime/relay";import { AuthStandardClientScopes } from "@t3tools/contracts";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Queue from "effect/Queue";import * as Stream from "effect/Stream";import Constants from "expo-constants";import * as Network from "expo-network";import { AppState } from "react-native"; import { authClientMetadata } from "../lib/authClientMetadata";import * as Runtime from "../lib/runtime";import * as MobileStorage from "../persistence/mobile-storage";import { appAtomRegistry } from "../state/atom-registry";import { clearThreadOutboxEnvironment } from "../state/thread-outbox-removal";import { clearComposerDraftsEnvironment } from "../state/use-composer-drafts";import { mobileApplicationActiveWakeup } from "./app-state-wakeups";import { connectionStorageLayer } from "./storage"; function networkStatus(state: Network.NetworkState): "unknown" | "offline" | "online" {  if (state.isConnected === false) {    return "offline";  }  if (state.isConnected === true) {    return "online";  }  return "unknown";} const connectivityLayer = Connectivity.layer({  status: Effect.tryPromise({    try: () => Network.getNetworkStateAsync(),    catch: () => undefined,  }).pipe(    Effect.match({      onFailure: () => "unknown" as const,      onSuccess: networkStatus,    }),  ),  changes: Stream.callback((queue) =>    Effect.acquireRelease(      Effect.sync(() => {        let active = true;        const networkSubscription = Network.addNetworkStateListener((state) => {          Queue.offerUnsafe(queue, networkStatus(state));        });        const appStateSubscription = AppState.addEventListener("change", (state) => {          if (state !== "active") {            return;          }          void Network.getNetworkStateAsync()            .then((current) => {              if (active) {                Queue.offerUnsafe(queue, networkStatus(current));              }            })            .catch(() => undefined);        });        return {          close: () => {            active = false;            networkSubscription.remove();            appStateSubscription.remove();          },        };      }),      ({ close }) => Effect.sync(close),    ).pipe(Effect.asVoid),  ),}); const wakeupsLayer = Wakeups.layer({  changes: Stream.merge(    Stream.callback<"application-active-probe" | "application-active-reconnect">((queue) =>      Effect.acquireRelease(        Effect.sync(() => {          let backgroundedAtMs = AppState.currentState === "background" ? Date.now() : null;          return AppState.addEventListener("change", (state) => {            if (state === "background") {              backgroundedAtMs = Date.now();              return;            }            if (state === "active") {              Queue.offerUnsafe(queue, mobileApplicationActiveWakeup(backgroundedAtMs, Date.now()));              backgroundedAtMs = null;            }          });        }),        (subscription) => Effect.sync(() => subscription.remove()),      ).pipe(Effect.asVoid),    ),    managedRelayAccountChanges(appAtomRegistry).pipe(      Stream.map(() => "credentials-changed" as const),    ),  ),}); const capabilitiesLayer = Layer.effectContext(  Effect.gen(function* () {    const storage = yield* MobileStorage.MobileStorage;    return Context.make(      CloudSession,      CloudSession.of({        identity: Effect.sync(() =>          Option.fromNullishOr(appAtomRegistry.get(managedRelaySessionAtom)),        ),        clerkToken: Effect.gen(function* () {          const session = appAtomRegistry.get(managedRelaySessionAtom);          if (session === null) {            return yield* new ConnectionBlockedError({              reason: "authentication",              detail: "Sign in to T3 Connect to connect this environment.",            });          }          const token = yield* session.readClerkToken().pipe(            Effect.mapError(              (error) =>                new ConnectionTransientError({                  reason: "network",                  detail: error.message,                }),            ),          );          if (token === null) {            return yield* new ConnectionBlockedError({              reason: "authentication",              detail: "The T3 Connect session is unavailable.",            });          }          return token;        }),      }),    ).pipe(      Context.add(        PrimaryEnvironmentAuth,        PrimaryEnvironmentAuth.of({ bearerToken: Effect.succeed(Option.none()) }),      ),      Context.add(        RelayDeviceIdentity,        RelayDeviceIdentity.of({          deviceId: storage.loadOrCreateAgentAwarenessDeviceId.pipe(            Effect.mapError(              (cause) =>                new ConnectionTransientError({                  reason: "remote-unavailable",                  detail: `Could not load the mobile device identity: ${String(cause)}`,                }),            ),            Effect.map(Option.some),          ),        }),      ),      Context.add(        ClientPresentation,        ClientPresentation.of({          metadata: authClientMetadata(Constants.expoConfig?.version),          scopes: AuthStandardClientScopes,        }),      ),      Context.add(        SshEnvironmentGateway,        SshEnvironmentGateway.of({          provision: () =>            Effect.fail(              new ConnectionBlockedError({                reason: "unsupported",                detail: "SSH environments are only available in the desktop app.",              }),            ),          prepare: () =>            Effect.fail(              new ConnectionBlockedError({                reason: "unsupported",                detail: "SSH environments are only available in the desktop app.",              }),            ),          disconnect: () => Effect.void,        }),      ),    );  }),); const platformConnectionSourceLayer = Layer.succeed(  PlatformConnectionSource,  PlatformConnectionSource.of({    registrations: Stream.empty,  }),); const providedConnectionStorageLayer = connectionStorageLayer.pipe(  Layer.provide(Runtime.runtimeContextLayer),);const providedCapabilitiesLayer = capabilitiesLayer.pipe(  Layer.provide(Runtime.runtimeContextLayer),); const environmentOwnedDataCleanupLayer = Layer.succeed(  EnvironmentOwnedDataCleanup,  EnvironmentOwnedDataCleanup.of({    clear: (environmentId) =>      Effect.all(        [          Effect.promise(() => clearThreadOutboxEnvironment(environmentId)),          Effect.promise(() => clearComposerDraftsEnvironment(environmentId)),        ],        { concurrency: "unbounded", discard: true },      ).pipe(        Effect.catch((cause) =>          Effect.logWarning("Could not clear mobile environment-owned data.", {            environmentId,            cause,          }),        ),      ),  }),); type ConnectionPlatformLayerSource =  | typeof providedConnectionStorageLayer  | typeof Runtime.runtimeContextLayer  | typeof connectivityLayer  | typeof wakeupsLayer  | typeof providedCapabilitiesLayer  | typeof platformConnectionSourceLayer  | typeof environmentOwnedDataCleanupLayer; export const connectionPlatformLayer: Layer.Layer<  Layer.Success<ConnectionPlatformLayerSource>,  Layer.Error<ConnectionPlatformLayerSource>,  Layer.Services<ConnectionPlatformLayerSource>> = Layer.mergeAll(  providedConnectionStorageLayer,  Runtime.runtimeContextLayer,  connectivityLayer,  wakeupsLayer,  providedCapabilitiesLayer,  platformConnectionSourceLayer,  environmentOwnedDataCleanupLayer,);