import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import { HttpClient } from "effect/unstable/http"; import { EnvironmentCloudEndpointUnavailableError, type EnvironmentCloudLinkStateResult, EnvironmentHttpBadRequestError, EnvironmentHttpConflictError, EnvironmentHttpForbiddenError, EnvironmentHttpInternalServerError, EnvironmentHttpUnauthorizedError, EnvironmentId, WS_METHODS, } from "@t3tools/contracts"; import { type RelayClientDeviceRecord, type RelayClientEnvironmentRecord, type RelayEnvironmentLinkResponse, type RelayProtectedError as RelayProtectedErrorType, type RelayManagedEndpointProviderKind, } from "@t3tools/contracts/relay"; import { EnvironmentRegistry } from "@t3tools/client-runtime/connection"; import { request, runStream } from "@t3tools/client-runtime/rpc"; import { makeEnvironmentHttpApiClient } from "@t3tools/client-runtime/rpc"; import { ManagedRelay } from "@t3tools/client-runtime/relay"; import { readPrimaryEnvironmentDescriptor, readPrimaryEnvironmentTarget, } from "../environments/primary"; import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; import { resolveCloudPublicConfig } from "./publicConfig"; import { finishRelayClientInstall, reportRelayClientInstallProgress, requestRelayClientInstallConfirmation, } from "./relayClientInstallDialog"; export function normalizeRelayBaseUrl(value: string | null | undefined): string | null { const trimmed = value?.trim(); if (!trimmed) { return null; } return trimmed.replace(/\/+$/g, ""); } function relayUrl(): string | null { return resolveCloudPublicConfig().relayUrl; } export class CloudEnvironmentLinkError extends Data.TaggedError("CloudEnvironmentLinkError")<{ readonly message: string; readonly cause?: unknown; readonly traceId?: string; }> {} const relayClientRpcError = (message: string) => (cause: unknown) => new CloudEnvironmentLinkError({ message, cause, }); function ensureRelayClientAvailable( environmentId: EnvironmentId, ): Effect.Effect { return Effect.gen(function* () { const registry = yield* EnvironmentRegistry; const status = yield* registry .run(environmentId, request(WS_METHODS.cloudGetRelayClientStatus, {})) .pipe(Effect.mapError(relayClientRpcError("Could not check relay client availability."))); if (status.status === "available") return; if (status.status === "unsupported") { return yield* new CloudEnvironmentLinkError({ message: `T3 Code cannot install the relay client automatically on ${status.platform}-${status.arch}.`, }); } const confirmed = yield* Effect.tryPromise({ try: () => requestRelayClientInstallConfirmation(status.version), catch: relayClientRpcError("Could not confirm relay client installation."), }); if (!confirmed) { return yield* new CloudEnvironmentLinkError({ message: "Relay client installation was cancelled.", }); } const installed = yield* registry .runStream( environmentId, runStream(WS_METHODS.cloudInstallRelayClient, {}).pipe( Stream.tap((event) => Effect.sync(() => reportRelayClientInstallProgress(event))), ), ) .pipe( Stream.runLast, Effect.mapError(relayClientRpcError("Could not install the relay client.")), Effect.ensuring(Effect.sync(finishRelayClientInstall)), ); if (Option.isNone(installed) || installed.value.type !== "complete") { return yield* new CloudEnvironmentLinkError({ message: "The relay client install completed without a final status.", }); } const installedStatus = installed.value.status; if (installedStatus.status !== "available") { return yield* new CloudEnvironmentLinkError({ message: installedStatus.status === "unsupported" ? `T3 Code cannot install the relay client automatically on ${installedStatus.platform}-${installedStatus.arch}.` : "The relay client is still unavailable after installation.", }); } }); } const isEnvironmentCloudApiError = Schema.is( Schema.Union([ EnvironmentHttpBadRequestError, EnvironmentHttpUnauthorizedError, EnvironmentHttpForbiddenError, EnvironmentHttpConflictError, EnvironmentHttpInternalServerError, EnvironmentCloudEndpointUnavailableError, ]), ); function relayProtectedErrorMessage(error: RelayProtectedErrorType): string { switch (error._tag) { case "RelayAuthInvalidError": switch (error.reason) { case "missing_bearer": case "invalid_bearer": return "Relay rejected the cloud session token."; case "invalid_dpop": return "Relay rejected the DPoP proof."; case "not_authorized": return "Relay rejected the authenticated request."; } case "RelayEnvironmentLinkProofExpiredError": return "Relay rejected an expired environment link proof."; case "RelayEnvironmentLinkProofInvalidError": return `Relay rejected the environment link proof (${error.reason}).`; case "RelayEnvironmentConnectNotAuthorizedError": return "Relay rejected the environment connection request."; case "RelayEnvironmentEndpointUnavailableError": return `Relay could not reach the environment endpoint (${error.reason}).`; case "RelayEnvironmentEndpointTimedOutError": return "Relay timed out while contacting the environment endpoint."; case "RelayEnvironmentLinkFailedError": return `Relay could not link the environment (${error.reason}).`; case "RelayEnvironmentLinkUnavailableError": return `Relay cannot provision the managed endpoint (${error.reason}).`; case "RelayAgentActivityPublishProofExpiredError": return "Relay rejected an expired agent activity publish proof."; case "RelayAgentActivityPublishProofInvalidError": return `Relay rejected the agent activity publish proof (${error.reason}).`; case "RelayInternalError": return `Relay encountered an internal error (${error.reason}).`; } } function decodedRelayClientError(message: string) { return (cause: ManagedRelay.ManagedRelayClientError) => { const relayError = cause._tag === "ManagedRelayRequestFailedError" ? cause.relayError : undefined; const traceId = cause._tag === "ManagedRelayRequestFailedError" ? cause.traceId : undefined; const detail = relayError ? relayProtectedErrorMessage(relayError) : null; return new CloudEnvironmentLinkError({ message: detail ? `${message}: ${detail}` : message, cause, ...(traceId ? { traceId } : {}), }); }; } function findEnvironmentCloudApiError(cause: unknown): { readonly message: string } | null { if (isEnvironmentCloudApiError(cause)) { return cause; } if (typeof cause !== "object" || cause === null) { return null; } return "cause" in cause ? findEnvironmentCloudApiError(cause.cause) : null; } const environmentApiError = (message: string) => (cause: unknown) => { const environmentError = findEnvironmentCloudApiError(cause); return new CloudEnvironmentLinkError({ message: environmentError ? `${message.replace(/[.:]$/, "")}: ${environmentError.message}` : message, cause, }); }; function endpointOrigin(httpBaseUrl: string) { const url = new URL(httpBaseUrl); return { localHttpHost: "127.0.0.1", localHttpPort: Number(url.port || (url.protocol === "https:" ? 443 : 80)), }; } const MANAGED_ENDPOINT_PROVIDER_KIND = "cloudflare_tunnel" satisfies RelayManagedEndpointProviderKind; function ensureLinkedEnvironmentMatches(input: { readonly expectedEnvironmentId: string; readonly expectedProviderKind: RelayManagedEndpointProviderKind; readonly link: RelayEnvironmentLinkResponse; }): Effect.Effect { if (input.link.environmentId !== input.expectedEnvironmentId) { return new CloudEnvironmentLinkError({ message: "Relay returned credentials for a different environment.", }); } if (input.link.endpoint.providerKind !== input.expectedProviderKind) { return new CloudEnvironmentLinkError({ message: "Relay returned credentials for a different endpoint provider.", }); } return Effect.void; } export interface CloudLinkTarget { readonly environmentId: string; readonly label: string; readonly httpBaseUrl: string; readonly wsBaseUrl: string; } export type CloudLinkState = EnvironmentCloudLinkStateResult; export function collectCloudLinkTargets(input: { readonly primary: CloudLinkTarget | null; readonly saved: ReadonlyArray; }): ReadonlyArray { const byId = new Map(); if (input.primary) { byId.set(input.primary.environmentId, input.primary); } for (const environment of input.saved) { if (!byId.has(environment.environmentId)) { byId.set(environment.environmentId, environment); } } return [...byId.values()]; } export function readPrimaryCloudLinkTarget(): CloudLinkTarget | null { const descriptor = readPrimaryEnvironmentDescriptor(); const target = readPrimaryEnvironmentTarget(); if (!descriptor || !target) { return null; } return { environmentId: descriptor.environmentId, label: descriptor.label, httpBaseUrl: target.target.httpBaseUrl, wsBaseUrl: target.target.wsBaseUrl, }; } export function listManagedCloudEnvironments(input: { readonly clerkToken: string; }): Effect.Effect< ReadonlyArray, CloudEnvironmentLinkError, ManagedRelay.ManagedRelayClient > { return Effect.gen(function* () { const configuredRelayUrl = relayUrl(); if (!configuredRelayUrl) { return yield* new CloudEnvironmentLinkError({ message: "T3CODE_RELAY_URL is not configured.", }); } const relayClient = yield* ManagedRelay.ManagedRelayClient; return yield* relayClient .listEnvironments({ clerkToken: input.clerkToken, }) .pipe( Effect.mapError( (cause) => new CloudEnvironmentLinkError({ message: "Could not list relay-managed environments.", cause, }), ), ); }); } export function listCloudDevices(input: { readonly clerkToken: string; }): Effect.Effect< ReadonlyArray, CloudEnvironmentLinkError, ManagedRelay.ManagedRelayClient > { return Effect.gen(function* () { if (!relayUrl()) { return yield* new CloudEnvironmentLinkError({ message: "T3CODE_RELAY_URL is not configured.", }); } const relayClient = yield* ManagedRelay.ManagedRelayClient; return yield* relayClient.listDevices({ clerkToken: input.clerkToken }).pipe( Effect.mapError( (cause) => new CloudEnvironmentLinkError({ message: "Could not list cloud devices.", cause, }), ), ); }); } export function readPrimaryCloudLinkState(input: { readonly target: CloudLinkTarget; }): Effect.Effect { return Effect.gen(function* () { const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); return yield* client.connect .linkState({ headers: {} }) .pipe(Effect.mapError(environmentApiError("Could not read environment cloud link state."))); }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); } export function updatePrimaryCloudPreferences(input: { readonly target: CloudLinkTarget; readonly publishAgentActivity: boolean; }): Effect.Effect { return Effect.gen(function* () { const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); return yield* client.connect .preferences({ headers: {}, payload: input, }) .pipe( Effect.mapError(environmentApiError("Could not update environment cloud preferences.")), ); }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); } export function unlinkPrimaryEnvironmentFromCloud(input: { readonly target: CloudLinkTarget; readonly clerkToken: string | null; }): Effect.Effect< void, CloudEnvironmentLinkError, HttpClient.HttpClient | ManagedRelay.ManagedRelayClient > { return Effect.gen(function* () { const client = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); yield* client.connect .unlink({ headers: {} }) .pipe(Effect.mapError(environmentApiError("Could not unlink the environment from cloud."))); const configuredRelayUrl = relayUrl(); if (configuredRelayUrl && input.clerkToken) { const relayClient = yield* ManagedRelay.ManagedRelayClient; yield* relayClient .unlinkEnvironment({ clerkToken: input.clerkToken, environmentId: EnvironmentId.make(input.target.environmentId), }) .pipe( Effect.catch((cause) => Effect.logWarning("Could not revoke cloud environment link after local unlink.", { cause, }), ), ); } }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); } // "publish_only" links the environment to the relay for agent-activity // publishing alone: no managed tunnel is provisioned, so it can be toggled // independently of T3 Connect while clients reach the environment out of band. export type CloudLinkMode = "managed" | "publish_only"; const PUBLISH_ONLY_PROVIDER_KIND = "manual" satisfies RelayManagedEndpointProviderKind; export function linkPrimaryEnvironmentToCloud(input: { readonly target: CloudLinkTarget; readonly clerkToken: string; readonly mode?: CloudLinkMode; }): Effect.Effect< void, CloudEnvironmentLinkError, EnvironmentRegistry | HttpClient.HttpClient | ManagedRelay.ManagedRelayClient > { return Effect.gen(function* () { const configuredRelayUrl = relayUrl(); if (!configuredRelayUrl) { return yield* new CloudEnvironmentLinkError({ message: "T3CODE_RELAY_URL is not configured.", }); } const managedTunnelsEnabled = (input.mode ?? "managed") === "managed"; const providerKind = managedTunnelsEnabled ? MANAGED_ENDPOINT_PROVIDER_KIND : PUBLISH_ONLY_PROVIDER_KIND; const relayClient = yield* ManagedRelay.ManagedRelayClient; const environmentClient = yield* makeEnvironmentHttpApiClient(input.target.httpBaseUrl); if (managedTunnelsEnabled) { yield* ensureRelayClientAvailable(EnvironmentId.make(input.target.environmentId)); } const challenge = yield* relayClient .createEnvironmentLinkChallenge({ clerkToken: input.clerkToken, payload: { notificationsEnabled: true, liveActivitiesEnabled: true, managedTunnelsEnabled, }, }) .pipe( Effect.mapError( decodedRelayClientError( `${configuredRelayUrl}/v1/client/environment-link-challenges failed`, ), ), ); const proof = yield* environmentClient.connect .linkProof({ headers: {}, payload: { challenge: challenge.challenge, relayIssuer: configuredRelayUrl, endpoint: { httpBaseUrl: input.target.httpBaseUrl, wsBaseUrl: input.target.wsBaseUrl, providerKind, }, origin: endpointOrigin(input.target.httpBaseUrl), }, }) .pipe(Effect.mapError(environmentApiError("Could not obtain environment link proof."))); const link = yield* relayClient .linkEnvironment({ clerkToken: input.clerkToken, payload: { proof, notificationsEnabled: true, liveActivitiesEnabled: true, managedTunnelsEnabled, }, }) .pipe( Effect.mapError( decodedRelayClientError(`${configuredRelayUrl}/v1/client/environment-links failed`), ), ); yield* ensureLinkedEnvironmentMatches({ expectedEnvironmentId: input.target.environmentId, expectedProviderKind: providerKind, link, }); yield* environmentClient.connect .relayConfig({ headers: {}, payload: { relayUrl: configuredRelayUrl, relayIssuer: link.relayIssuer, cloudUserId: link.cloudUserId, environmentCredential: link.environmentCredential, cloudMintPublicKey: link.cloudMintPublicKey, endpointRuntime: link.endpointRuntime, }, }) .pipe(Effect.mapError(environmentApiError("Could not configure environment relay access."))); }).pipe(Effect.provide(primaryEnvironmentHttpLayer)); }