import type { AuthClientPresentationMetadata } from "@t3tools/contracts"; import { withRelayClientTracing } from "@t3tools/shared/relayTracing"; 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 Schema from "effect/Schema"; import * as HttpClient from "effect/unstable/http/HttpClient"; import { appendClientConnectionParams } from "../authorization/remote.ts"; import * as RemoteEnvironmentAuthorization from "../authorization/service.ts"; import * as ClientCapabilities from "../platform/capabilities.ts"; import { BearerConnectionCredential, BearerConnectionProfile, type ConnectionCatalogEntry, SshConnectionProfile, } from "./catalog.ts"; import * as ConnectionCredentialStore from "./credentialStore.ts"; import { credentialMissingError, environmentMismatchError, mapRemoteEnvironmentError, profileMissingError, } from "./errors.ts"; import type { BearerConnectionTarget, ConnectionTarget, PreparedConnection, PrimaryConnectionTarget, RelayConnectionTarget, SshConnectionTarget, } from "./model.ts"; import { ConnectionBlockedError, type ConnectionAttemptError } from "./model.ts"; import * as ConnectionProfileStore from "./profileStore.ts"; import { appendOrchestrationProtocol, orchestrationProtocolCompatibilityError, } from "./compatibility.ts"; import { fetchRemoteEnvironmentDescriptor } from "../environment/descriptor.ts"; export class ConnectionResolver extends Context.Service< ConnectionResolver, { readonly prepare: ( entry: ConnectionCatalogEntry, ) => Effect.Effect; } >()("@t3tools/client-runtime/connection/resolver/ConnectionResolver") {} const isBearerProfile = Schema.is(BearerConnectionProfile); const isSshProfile = Schema.is(SshConnectionProfile); const isBearerCredential = Schema.is(BearerConnectionCredential); function primarySocketUrl( target: PrimaryConnectionTarget, clientMetadata: AuthClientPresentationMetadata | undefined, ): string { const url = new URL(target.wsBaseUrl); if (url.pathname === "" || url.pathname === "/") { url.pathname = "/ws"; } appendClientConnectionParams(url, clientMetadata, "direct"); return url.toString(); } const makePrimaryBroker = Effect.fn("clientRuntime.connection.broker.makePrimary")(function* () { const auth = yield* ClientCapabilities.PrimaryEnvironmentAuth; const presentation = yield* ClientCapabilities.ClientPresentation; const remote = yield* RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization; return Effect.fn("clientRuntime.connection.broker.primary")(function* ( target: PrimaryConnectionTarget, ) { const bearerToken = yield* auth.bearerToken; if (Option.isNone(bearerToken)) { return { environmentId: target.environmentId, label: target.label, httpBaseUrl: target.httpBaseUrl, socketUrl: primarySocketUrl(target, presentation.metadata), httpAuthorization: null, target, } satisfies PreparedConnection; } const authorized = yield* remote.authorizeBearer({ expectedEnvironmentId: target.environmentId, httpBaseUrl: target.httpBaseUrl, wsBaseUrl: target.wsBaseUrl, bearerToken: bearerToken.value, connectionMethod: "direct", }); return { ...authorized, target, } satisfies PreparedConnection; }); }); const makeBearerBroker = Effect.fn("clientRuntime.connection.broker.makeBearer")(function* () { const credentials = yield* ConnectionCredentialStore.ConnectionCredentialStore; const remote = yield* RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization; return Effect.fn("clientRuntime.connection.broker.bearer")(function* ( entry: ConnectionCatalogEntry & { readonly target: BearerConnectionTarget }, ) { const target = entry.target; const profile = yield* Option.match(entry.profile, { onNone: () => Effect.fail(profileMissingError(target.connectionId)), onSome: Effect.succeed, }); if (!isBearerProfile(profile)) { return yield* new ConnectionBlockedError({ reason: "configuration", detail: `Connection profile ${target.connectionId} is not a bearer connection.`, }); } if (profile.environmentId !== target.environmentId) { return yield* environmentMismatchError({ expected: target.environmentId, actual: profile.environmentId, }); } const credential = yield* credentials.get(target.connectionId).pipe( Effect.flatMap( Option.match({ onNone: () => Effect.fail(credentialMissingError(target.connectionId)), onSome: Effect.succeed, }), ), ); if (!isBearerCredential(credential)) { return yield* credentialMissingError(target.connectionId); } const authorized = yield* remote.authorizeBearer({ expectedEnvironmentId: target.environmentId, httpBaseUrl: profile.httpBaseUrl, wsBaseUrl: profile.wsBaseUrl, bearerToken: credential.token, connectionMethod: "direct", }); return { environmentId: authorized.environmentId, label: authorized.label, httpBaseUrl: authorized.httpBaseUrl, socketUrl: authorized.socketUrl, httpAuthorization: authorized.httpAuthorization, target, } satisfies PreparedConnection; }); }); const makeRelayBroker = Effect.fn("clientRuntime.connection.broker.makeRelay")(function* () { const remote = yield* RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization; return Effect.fnUntraced( function* (target: RelayConnectionTarget) { const authorized = yield* remote.authorizeDpop({ expectedEnvironmentId: target.environmentId, }); return { environmentId: authorized.environmentId, label: authorized.label, httpBaseUrl: authorized.httpBaseUrl, socketUrl: authorized.socketUrl, httpAuthorization: authorized.httpAuthorization, target, } satisfies PreparedConnection; }, Effect.withSpan("clientRuntime.connection.broker.relay"), withRelayClientTracing, ); }); const makeSshBroker = Effect.fn("clientRuntime.connection.broker.makeSsh")(function* () { const profiles = yield* ConnectionProfileStore.ConnectionProfileStore; const ssh = yield* ClientCapabilities.SshEnvironmentGateway; const remote = yield* RemoteEnvironmentAuthorization.RemoteEnvironmentAuthorization; return Effect.fn("clientRuntime.connection.broker.ssh")(function* ( entry: ConnectionCatalogEntry & { readonly target: SshConnectionTarget }, ) { const target = entry.target; const profile = yield* Option.match(entry.profile, { onNone: () => Effect.fail(profileMissingError(target.connectionId)), onSome: Effect.succeed, }); if (!isSshProfile(profile)) { return yield* new ConnectionBlockedError({ reason: "configuration", detail: `Connection profile ${target.connectionId} is not an SSH connection.`, }); } if (profile.environmentId !== target.environmentId) { return yield* environmentMismatchError({ expected: target.environmentId, actual: profile.environmentId, }); } const prepared = yield* ssh.prepare({ connectionId: target.connectionId, expectedEnvironmentId: target.environmentId, target: profile.target, }); yield* profiles.put( new SshConnectionProfile({ connectionId: profile.connectionId, environmentId: profile.environmentId, label: profile.label, target: prepared.bootstrap.target, }), ); const authorized = yield* remote.authorizeBearer({ expectedEnvironmentId: target.environmentId, httpBaseUrl: prepared.bootstrap.httpBaseUrl, wsBaseUrl: prepared.bootstrap.wsBaseUrl, bearerToken: prepared.bearerToken, connectionMethod: "ssh", }); return { environmentId: authorized.environmentId, label: authorized.label, httpBaseUrl: authorized.httpBaseUrl, socketUrl: authorized.socketUrl, httpAuthorization: authorized.httpAuthorization, target, } satisfies PreparedConnection; }); }); export const make = Effect.gen(function* () { const primary = yield* makePrimaryBroker(); const bearer = yield* makeBearerBroker(); const relay = yield* makeRelayBroker(); const ssh = yield* makeSshBroker(); const httpClient = yield* HttpClient.HttpClient; const prepare = Effect.fn("clientRuntime.connection.broker.prepare")(function* ( entry: ConnectionCatalogEntry, ) { const target: ConnectionTarget = entry.target; yield* Effect.annotateCurrentSpan({ "connection.environment.id": target.environmentId, "connection.target.kind": target._tag, }); const prepared = yield* (() => { switch (target._tag) { case "PrimaryConnectionTarget": return primary(target); case "BearerConnectionTarget": return bearer({ ...entry, target }); case "RelayConnectionTarget": return relay(target); case "SshConnectionTarget": return ssh({ ...entry, target }); } })(); const descriptor = yield* fetchRemoteEnvironmentDescriptor({ httpBaseUrl: prepared.httpBaseUrl, }).pipe( Effect.mapError(mapRemoteEnvironmentError), Effect.provideService(HttpClient.HttpClient, httpClient), ); if (descriptor.environmentId !== target.environmentId) { return yield* environmentMismatchError({ expected: target.environmentId, actual: descriptor.environmentId, }); } const compatibilityError = orchestrationProtocolCompatibilityError(descriptor); if (compatibilityError !== null) { return yield* compatibilityError; } return { ...prepared, socketUrl: appendOrchestrationProtocol(prepared.socketUrl), }; }); return ConnectionResolver.of({ prepare }); }); export const layer = Layer.effect(ConnectionResolver, make);