import type { AuthBrowserSessionResult, AuthClientMetadata, AuthEnvironmentScope, AuthPairingCredentialResult, ServerAuthSessionMethod, AuthSessionId, AuthSessionState, } from "@t3tools/contracts"; import { EnvironmentHttpCommonError, PRIMARY_LOCAL_ENVIRONMENT_ID } from "@t3tools/contracts"; import type { EnvironmentHttpCommonError as EnvironmentHttpCommonErrorType } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; import { HttpClientError } from "effect/unstable/http"; import { getPairingTokenFromUrl, stripPairingTokenFromUrl as stripPairingTokenUrl, } from "../../pairingUrl"; import { PrimaryEnvironmentHttpClient } from "./httpClient"; import { runPrimaryHttp } from "../../lib/runtime"; const PrimaryEnvironmentRequestOperation = Schema.Literals([ "fetch-session-state", "exchange-bootstrap-credential", "fetch-environment-descriptor", "create-pairing-credential", "list-pairing-links", "revoke-pairing-link", "list-client-sessions", "revoke-client-session", "revoke-other-client-sessions", ]); type PrimaryEnvironmentRequestOperation = typeof PrimaryEnvironmentRequestOperation.Type; export class PrimaryEnvironmentRequestError extends Schema.TaggedError()( "PrimaryEnvironmentRequestError", { operation: PrimaryEnvironmentRequestOperation, status: Schema.Number, pairingLinkId: Schema.optional(Schema.String), sessionId: Schema.optional(Schema.String), cause: Schema.Defect(), }, ) { static fromCause(input: { readonly operation: PrimaryEnvironmentRequestOperation; readonly cause: unknown; readonly pairingLinkId?: string; readonly sessionId?: string; }): PrimaryEnvironmentRequestError { const status = readHttpApiStatus(input.cause) ?? 500; return new PrimaryEnvironmentRequestError({ operation: input.operation, status, ...(input.pairingLinkId !== undefined ? { pairingLinkId: input.pairingLinkId } : {}), ...(input.sessionId !== undefined ? { sessionId: input.sessionId } : {}), cause: input.cause, }); } override get message(): string { return `Primary environment request failed during ${this.operation} (HTTP ${this.status}).`; } } const isPrimaryEnvironmentRequestError = Schema.is(PrimaryEnvironmentRequestError); export class PrimaryEnvironmentPairingCredentialRejectedError extends Schema.TaggedError()( "PrimaryEnvironmentPairingCredentialRejectedError", { providedLength: Schema.Number, cause: Schema.Defect(), }, ) { override get message(): string { return "Invalid pairing token. Check the token and try again."; } } export const isPrimaryEnvironmentPairingCredentialRejectedError = Schema.is( PrimaryEnvironmentPairingCredentialRejectedError, ); export class PrimaryEnvironmentAuthSessionTimeoutError extends Schema.TaggedError()( "PrimaryEnvironmentAuthSessionTimeoutError", { timeoutMs: Schema.Number, elapsedMs: Schema.Number, }, ) { override get message(): string { return "Timed out waiting for authenticated session after bootstrap."; } } export class PrimaryEnvironmentPairingCredentialRequiredError extends Schema.TaggedError()( "PrimaryEnvironmentPairingCredentialRequiredError", { providedLength: Schema.Number, }, ) { override get message(): string { return "Enter a pairing token to continue."; } } const isEnvironmentHttpCommonError = Schema.is(EnvironmentHttpCommonError); export interface ServerPairingLinkRecord { readonly id: string; readonly scopes: ReadonlyArray; readonly subject: string; readonly label?: string; readonly createdAt: string; readonly expiresAt: string; } export interface ServerClientSessionRecord { readonly sessionId: AuthSessionId; readonly subject: string; readonly scopes: ReadonlyArray; readonly method: ServerAuthSessionMethod; readonly client: AuthClientMetadata; readonly issuedAt: string; readonly expiresAt: string; readonly lastConnectedAt: string | null; readonly connected: boolean; readonly current: boolean; } type ServerAuthGateState = | { status: "authenticated" } | { status: "requires-auth"; auth: AuthSessionState["auth"]; errorMessage?: string; }; let bootstrapPromise: Promise | null = null; let resolvedAuthenticatedGateState: ServerAuthGateState | null = null; const AUTH_SESSION_ESTABLISH_TIMEOUT_MS = 2_000; const AUTH_SESSION_ESTABLISH_STEP_MS = 100; export function peekPairingTokenFromUrl(): string | null { return getPairingTokenFromUrl(new URL(window.location.href)); } export function stripPairingTokenFromUrl() { const url = new URL(window.location.href); const next = stripPairingTokenUrl(url); if (next.toString() === url.toString()) { return; } window.history.replaceState({}, document.title, next.toString()); } export function takePairingTokenFromUrl(): string | null { const token = peekPairingTokenFromUrl(); if (!token) { return null; } stripPairingTokenFromUrl(); return token; } function getDesktopBootstrapCredential(): string | null { // Both backends share the same bootstrap token (DesktopBackendConfiguration // mints one tokenRef and feeds it to both resolvers), so picking the // primary entry is fine even when the WSL backend is also registered. const bootstraps = window.desktopBridge?.getLocalEnvironmentBootstraps() ?? []; const primary = bootstraps.find((entry) => entry.id === PRIMARY_LOCAL_ENVIRONMENT_ID); return typeof primary?.bootstrapToken === "string" && primary.bootstrapToken.length > 0 ? primary.bootstrapToken : null; } export async function fetchSessionState(): Promise { return retryTransientBootstrap(async () => { try { return await runPrimaryHttp( PrimaryEnvironmentHttpClient.pipe( Effect.flatMap((client) => client.auth.session({ headers: {} })), ), ); } catch (error) { throw PrimaryEnvironmentRequestError.fromCause({ operation: "fetch-session-state", cause: error, }); } }); } function readHttpApiStatus(error: unknown): number | null { if (isEnvironmentHttpCommonError(error)) { return readEnvironmentHttpErrorStatus(error); } return HttpClientError.isHttpClientError(error) && error.response !== undefined ? error.response.status : null; } function readEnvironmentHttpErrorStatus(error: EnvironmentHttpCommonErrorType): number { switch (error._tag) { case "EnvironmentRequestInvalidError": return 400; case "EnvironmentAuthInvalidError": return 401; case "EnvironmentScopeRequiredError": case "EnvironmentOperationForbiddenError": return 403; case "EnvironmentResourceNotFoundError": return 404; case "EnvironmentInternalError": return 500; } } async function exchangeBootstrapCredential(credential: string): Promise { return retryTransientBootstrap(async () => { try { return await runPrimaryHttp( PrimaryEnvironmentHttpClient.pipe( Effect.flatMap((client) => client.auth.browserSession({ payload: { credential } })), ), ); } catch (error) { if ( isEnvironmentHttpCommonError(error) && error._tag === "EnvironmentAuthInvalidError" && error.reason === "invalid_credential" ) { throw new PrimaryEnvironmentPairingCredentialRejectedError({ providedLength: credential.length, cause: error, }); } throw PrimaryEnvironmentRequestError.fromCause({ operation: "exchange-bootstrap-credential", cause: error, }); } }); } async function waitForAuthenticatedSessionAfterBootstrap(): Promise { const startedAt = Date.now(); while (true) { const session = await fetchSessionState(); if (session.authenticated) { return session; } const elapsedMs = Date.now() - startedAt; if (elapsedMs >= AUTH_SESSION_ESTABLISH_TIMEOUT_MS) { throw new PrimaryEnvironmentAuthSessionTimeoutError({ timeoutMs: AUTH_SESSION_ESTABLISH_TIMEOUT_MS, elapsedMs, }); } await waitForBootstrapRetry(AUTH_SESSION_ESTABLISH_STEP_MS); } } const TRANSIENT_BOOTSTRAP_STATUS_CODES = new Set([502, 503, 504]); const BOOTSTRAP_RETRY_TIMEOUT_MS = 15_000; const BOOTSTRAP_RETRY_STEP_MS = 500; export async function retryTransientBootstrap(operation: () => Promise): Promise { const startedAt = Date.now(); while (true) { try { return await operation(); } catch (error) { if (!isTransientBootstrapError(error)) { throw error; } if (Date.now() - startedAt >= BOOTSTRAP_RETRY_TIMEOUT_MS) { throw error; } await waitForBootstrapRetry(BOOTSTRAP_RETRY_STEP_MS); } } } function waitForBootstrapRetry(delayMs: number): Promise { return new Promise((resolve) => { setTimeout(resolve, delayMs); }); } function isTransientBootstrapError(error: unknown): boolean { if (isPrimaryEnvironmentRequestError(error)) { return TRANSIENT_BOOTSTRAP_STATUS_CODES.has(error.status); } if (error instanceof TypeError) { return true; } return error instanceof DOMException && error.name === "AbortError"; } async function bootstrapServerAuth(): Promise { const bootstrapCredential = getDesktopBootstrapCredential(); const currentSession = await fetchSessionState(); if (currentSession.authenticated) { return { status: "authenticated" }; } if (!bootstrapCredential) { return { status: "requires-auth", auth: currentSession.auth, }; } try { await exchangeBootstrapCredential(bootstrapCredential); await waitForAuthenticatedSessionAfterBootstrap(); return { status: "authenticated" }; } catch (error) { return { status: "requires-auth", auth: currentSession.auth, errorMessage: error instanceof Error ? error.message : "Authentication failed.", }; } } export async function submitServerAuthCredential(credential: string): Promise { const trimmedCredential = credential.trim(); if (!trimmedCredential) { throw new PrimaryEnvironmentPairingCredentialRequiredError({ providedLength: credential.length, }); } resolvedAuthenticatedGateState = null; await exchangeBootstrapCredential(trimmedCredential); await waitForAuthenticatedSessionAfterBootstrap(); resolvedAuthenticatedGateState = { status: "authenticated" }; bootstrapPromise = null; stripPairingTokenFromUrl(); } export async function createServerPairingCredential(input?: { readonly label?: string; readonly scopes?: ReadonlyArray; }): Promise { const trimmedLabel = input?.label?.trim(); try { return await runPrimaryHttp( PrimaryEnvironmentHttpClient.pipe( Effect.flatMap((client) => client.auth.pairingCredential({ headers: {}, payload: { ...(trimmedLabel ? { label: trimmedLabel } : {}), ...(input?.scopes ? { scopes: input.scopes } : {}), }, }), ), ), ); } catch (error) { throw PrimaryEnvironmentRequestError.fromCause({ operation: "create-pairing-credential", cause: error, }); } } export async function revokeServerPairingLink(id: string): Promise { try { await runPrimaryHttp( PrimaryEnvironmentHttpClient.pipe( Effect.flatMap((client) => client.auth.revokePairingLink({ headers: {}, payload: { id } })), ), ); } catch (error) { throw PrimaryEnvironmentRequestError.fromCause({ operation: "revoke-pairing-link", pairingLinkId: id, cause: error, }); } } export async function revokeServerClientSession(sessionId: AuthSessionId): Promise { try { await runPrimaryHttp( PrimaryEnvironmentHttpClient.pipe( Effect.flatMap((client) => client.auth.revokeClient({ headers: {}, payload: { sessionId } }), ), ), ); } catch (error) { throw PrimaryEnvironmentRequestError.fromCause({ operation: "revoke-client-session", sessionId, cause: error, }); } } export async function revokeOtherServerClientSessions(): Promise { try { const result = await runPrimaryHttp( PrimaryEnvironmentHttpClient.pipe( Effect.flatMap((client) => client.auth.revokeOtherClients({ headers: {} })), ), ); return result.revokedCount; } catch (error) { throw PrimaryEnvironmentRequestError.fromCause({ operation: "revoke-other-client-sessions", cause: error, }); } } export async function resolveInitialServerAuthGateState(): Promise { if (resolvedAuthenticatedGateState?.status === "authenticated") { return resolvedAuthenticatedGateState; } if (bootstrapPromise) { return bootstrapPromise; } const nextPromise = bootstrapServerAuth(); bootstrapPromise = nextPromise; return nextPromise .then((result) => { if (result.status === "authenticated") { resolvedAuthenticatedGateState = result; } return result; }) .finally(() => { if (bootstrapPromise === nextPromise) { bootstrapPromise = null; } }); } export function __resetServerAuthBootstrapForTests() { bootstrapPromise = null; resolvedAuthenticatedGateState = null; }