import { AuthBearerBootstrapResult, AuthSessionState, AuthWebSocketTokenResult, type AuthBearerBootstrapResult as AuthBearerBootstrapResultType, type AuthSessionState as AuthSessionStateType, type AuthWebSocketTokenResult as AuthWebSocketTokenResultType, ExecutionEnvironmentDescriptor, type ExecutionEnvironmentDescriptor as ExecutionEnvironmentDescriptorType, } from "@t3tools/contracts"; import { SshHttpBridgeError } from "@t3tools/ssh/errors"; import { fetchLoopbackSshJson } from "@t3tools/ssh/tunnel"; import * as Context from "effect/Context"; import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; import { HttpClient } from "effect/unstable/http"; export type DesktopSshRemoteApiOperation = | "fetch-environment-descriptor" | "bootstrap-bearer-session" | "fetch-session-state" | "issue-websocket-token"; export class DesktopSshRemoteApiError extends Data.TaggedError("DesktopSshRemoteApiError")<{ readonly operation: DesktopSshRemoteApiOperation; readonly cause: SshHttpBridgeError | Schema.SchemaError; }> { override get message() { return `SSH remote API request failed during ${this.operation}.`; } } export interface DesktopSshRemoteApiShape { readonly fetchEnvironmentDescriptor: (input: { readonly httpBaseUrl: string; }) => Effect.Effect; readonly bootstrapBearerSession: (input: { readonly httpBaseUrl: string; readonly credential: string; }) => Effect.Effect; readonly fetchSessionState: (input: { readonly httpBaseUrl: string; readonly bearerToken: string; }) => Effect.Effect; readonly issueWebSocketToken: (input: { readonly httpBaseUrl: string; readonly bearerToken: string; }) => Effect.Effect; } export class DesktopSshRemoteApi extends Context.Service< DesktopSshRemoteApi, DesktopSshRemoteApiShape >()("@t3tools/desktop/ssh/DesktopSshRemoteApi") {} const decodeExecutionEnvironmentDescriptor = Schema.decodeUnknownEffect( ExecutionEnvironmentDescriptor, ); const decodeAuthBearerBootstrapResult = Schema.decodeUnknownEffect(AuthBearerBootstrapResult); const decodeAuthSessionState = Schema.decodeUnknownEffect(AuthSessionState); const decodeAuthWebSocketTokenResult = Schema.decodeUnknownEffect(AuthWebSocketTokenResult); const mapError = (operation: DesktopSshRemoteApiOperation) => (cause: SshHttpBridgeError | Schema.SchemaError): DesktopSshRemoteApiError => new DesktopSshRemoteApiError({ operation, cause }); const make = Effect.gen(function* () { const httpClient = yield* HttpClient.HttpClient; const provideHttpClient = (effect: Effect.Effect) => effect.pipe(Effect.provideService(HttpClient.HttpClient, httpClient)); return DesktopSshRemoteApi.of({ fetchEnvironmentDescriptor: ({ httpBaseUrl }) => fetchLoopbackSshJson({ httpBaseUrl, pathname: "/.well-known/t3/environment", }).pipe( Effect.flatMap(decodeExecutionEnvironmentDescriptor), Effect.mapError(mapError("fetch-environment-descriptor")), provideHttpClient, Effect.withSpan("desktop.sshRemoteApi.fetchEnvironmentDescriptor"), ), bootstrapBearerSession: ({ httpBaseUrl, credential }) => fetchLoopbackSshJson({ httpBaseUrl, pathname: "/api/auth/bootstrap/bearer", method: "POST", body: { credential }, }).pipe( Effect.flatMap(decodeAuthBearerBootstrapResult), Effect.mapError(mapError("bootstrap-bearer-session")), provideHttpClient, Effect.withSpan("desktop.sshRemoteApi.bootstrapBearerSession"), ), fetchSessionState: ({ httpBaseUrl, bearerToken }) => fetchLoopbackSshJson({ httpBaseUrl, pathname: "/api/auth/session", bearerToken, }).pipe( Effect.flatMap(decodeAuthSessionState), Effect.mapError(mapError("fetch-session-state")), provideHttpClient, Effect.withSpan("desktop.sshRemoteApi.fetchSessionState"), ), issueWebSocketToken: ({ httpBaseUrl, bearerToken }) => fetchLoopbackSshJson({ httpBaseUrl, pathname: "/api/auth/ws-token", method: "POST", bearerToken, }).pipe( Effect.flatMap(decodeAuthWebSocketTokenResult), Effect.mapError(mapError("issue-websocket-token")), provideHttpClient, Effect.withSpan("desktop.sshRemoteApi.issueWebSocketToken"), ), }); }); export const layer = Layer.effect(DesktopSshRemoteApi, make);