import Constants from "expo-constants"; import { relayClerkTokenOptions } from "@t3tools/shared/relayAuth"; import { normalizeSecureRelayUrl } from "@t3tools/shared/relayUrl"; import * as Schema from "effect/Schema"; export class CloudPublicConfigMissingError extends Schema.TaggedErrorClass()( "CloudPublicConfigMissingError", { key: Schema.Literal("T3CODE_CLERK_JWT_TEMPLATE"), }, ) { override get message(): string { return `${this.key} is not configured.`; } } export interface CloudPublicConfig { readonly clerk: { readonly publishableKey: string | null; readonly jwtTemplate: string | null; }; readonly relay: { readonly url: string | null; }; readonly observability: { readonly tracesUrl: string | null; readonly tracesDataset: string | null; readonly tracesToken: string | null; }; } type UntrustedSection = { readonly [Key in keyof T]?: unknown; }; type ExpoExtra = | { readonly [Section in keyof CloudPublicConfig]?: UntrustedSection; } | undefined; function trimNonEmpty(value: unknown): string | null { return typeof value === "string" && value.trim() ? value.trim() : null; } function normalizeSecureUrl(value: unknown): string | null { const raw = trimNonEmpty(value); if (raw === null) { return null; } try { const url = new URL(raw); return url.protocol === "https:" ? url.toString() : null; } catch { return null; } } export function resolveCloudPublicConfig(extra: ExpoExtra = Constants.expoConfig?.extra) { return { clerk: { publishableKey: trimNonEmpty(extra?.clerk?.publishableKey), jwtTemplate: trimNonEmpty(extra?.clerk?.jwtTemplate), }, relay: { url: normalizeSecureRelayUrl(trimNonEmpty(extra?.relay?.url) ?? ""), }, observability: { tracesUrl: normalizeSecureUrl(extra?.observability?.tracesUrl), tracesDataset: trimNonEmpty(extra?.observability?.tracesDataset), tracesToken: trimNonEmpty(extra?.observability?.tracesToken), }, } satisfies CloudPublicConfig; } export function hasCloudPublicConfig(): boolean { const config = resolveCloudPublicConfig(); return Boolean(config.clerk.publishableKey && config.clerk.jwtTemplate && config.relay.url); } type Configured = { readonly [Key in keyof T]: NonNullable; }; type TracingPublicConfig = Omit & { readonly observability: Configured; }; export function hasTracingPublicConfig( config: CloudPublicConfig = resolveCloudPublicConfig(), ): config is TracingPublicConfig { return Boolean( config.observability.tracesUrl && config.observability.tracesDataset && config.observability.tracesToken, ); } export function resolveRelayClerkTokenOptions() { const { jwtTemplate } = resolveCloudPublicConfig().clerk; if (!jwtTemplate) { throw new CloudPublicConfigMissingError({ key: "T3CODE_CLERK_JWT_TEMPLATE" }); } return relayClerkTokenOptions(jwtTemplate); }