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.TaggedError<CloudPublicConfigMissingError>()( "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<T> = { readonly [Key in keyof T]?: unknown;}; type ExpoExtra = | { readonly [Section in keyof CloudPublicConfig]?: UntrustedSection<CloudPublicConfig[Section]>; } | 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<T> = { readonly [Key in keyof T]: NonNullable<T[Key]>;}; type TracingPublicConfig = Omit<CloudPublicConfig, "observability"> & { readonly observability: Configured<CloudPublicConfig["observability"]>;}; 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);}
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.TaggedError<CloudPublicConfigMissingError>()(
"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<T> = {
readonly [Key in keyof T]?: unknown;
};
type ExpoExtra =
| {
readonly [Section in keyof CloudPublicConfig]?: UntrustedSection<CloudPublicConfig[Section]>;
}
| 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<T> = {
readonly [Key in keyof T]: NonNullable<T[Key]>;
};
type TracingPublicConfig = Omit<CloudPublicConfig, "observability"> & {
readonly observability: Configured<CloudPublicConfig["observability"]>;
};
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);
}