1import Constants from "expo-constants";
2import { relayClerkTokenOptions } from "@t3tools/shared/relayAuth";
3import { normalizeSecureRelayUrl } from "@t3tools/shared/relayUrl";
4import * as Schema from "effect/Schema";
5
6export class CloudPublicConfigMissingError extends Schema.TaggedError<CloudPublicConfigMissingError>()(
7 "CloudPublicConfigMissingError",
8 {
9 key: Schema.Literal("T3CODE_CLERK_JWT_TEMPLATE"),
10 },
11) {
12 override get message(): string {
13 return `${this.key} is not configured.`;
14 }
15}
16
17export interface CloudPublicConfig {
18 readonly clerk: {
19 readonly publishableKey: string | null;
20 readonly jwtTemplate: string | null;
21 };
22 readonly relay: {
23 readonly url: string | null;
24 };
25}
26
27type UntrustedSection<T> = {
28 readonly [Key in keyof T]?: unknown;
29};
30
31type ExpoExtra =
32 | {
33 readonly [Section in keyof CloudPublicConfig]?: UntrustedSection<CloudPublicConfig[Section]>;
34 }
35 | undefined;
36
37function trimNonEmpty(value: unknown): string | null {
38 return typeof value === "string" && value.trim() ? value.trim() : null;
39}
40
41export function resolveCloudPublicConfig(extra: ExpoExtra = Constants.expoConfig?.extra) {
42 return {
43 clerk: {
44 publishableKey: trimNonEmpty(extra?.clerk?.publishableKey),
45 jwtTemplate: trimNonEmpty(extra?.clerk?.jwtTemplate),
46 },
47 relay: {
48 url: normalizeSecureRelayUrl(trimNonEmpty(extra?.relay?.url) ?? ""),
49 },
50 } satisfies CloudPublicConfig;
51}
52
53export function hasCloudPublicConfig(): boolean {
54 const config = resolveCloudPublicConfig();
55 return Boolean(config.clerk.publishableKey && config.clerk.jwtTemplate && config.relay.url);
56}
57
58export function resolveRelayClerkTokenOptions() {
59 const { jwtTemplate } = resolveCloudPublicConfig().clerk;
60 if (!jwtTemplate) {
61 throw new CloudPublicConfigMissingError({ key: "T3CODE_CLERK_JWT_TEMPLATE" });
62 }
63 return relayClerkTokenOptions(jwtTemplate);
64}