1import * as Config from "effect/Config";
2import * as Context from "effect/Context";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import * as Redacted from "effect/Redacted";
6import * as Schema from "effect/Schema";
7
8export const ApnsEnvironment = Schema.Literals(["sandbox", "production"]);
9export type ApnsEnvironment = typeof ApnsEnvironment.Type;
10
11export const ManagedEndpointCleanupMode = Schema.Literals(["off", "dry-run", "enabled"]);
12export type ManagedEndpointCleanupMode = typeof ManagedEndpointCleanupMode.Type;
13const decodeManagedEndpointCleanupMode = Schema.decodeUnknownEffect(ManagedEndpointCleanupMode);
14
15const RELAY_TUNNEL_CLEANUP_MODE = "RELAY_TUNNEL_CLEANUP_MODE";
16const RELAY_LEGACY_TUNNEL_CLEANUP_MODE = "RELAY_LEGACY_TUNNEL_CLEANUP_MODE";
17
18const cleanupModeConfig = (name: string) =>
19 Config.String(name).pipe(
20 Config.withDefault("off"),
21 Config.map((value) => value.trim() || "off"),
22 Config.mapEffect((value) =>
23 decodeManagedEndpointCleanupMode(value).pipe(
24 Effect.mapError((error) => new Config.ConfigError(error)),
25 ),
26 ),
27 );
28
29export const managedEndpointCleanupModeConfig = cleanupModeConfig(RELAY_TUNNEL_CLEANUP_MODE);
30export const legacyManagedEndpointCleanupModeConfig = cleanupModeConfig(
31 RELAY_LEGACY_TUNNEL_CLEANUP_MODE,
32);
33
34const RELAY_LEGACY_TUNNEL_GRACE_MINUTES = "RELAY_LEGACY_TUNNEL_GRACE_MINUTES";
35
36export const legacyTunnelGraceMinutesConfig = Config.option(
37 Config.schema(
38 Schema.NumberFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThan(0))),
39 RELAY_LEGACY_TUNNEL_GRACE_MINUTES,
40 ),
41);
42
43export interface ApnsCredentials {
44 readonly teamId: string;
45 readonly keyId: string;
46 readonly privateKey: Redacted.Redacted<string>;
47 readonly bundleId: string;
48 readonly environment: ApnsEnvironment;
49}
50
51export interface T3ChatSettings {
52 readonly baseUrl: string;
53 // Shared with T3 Chat: signs link tokens and authenticates relay calls.
54 readonly linkSecret: Redacted.Redacted<string>;
55}
56
57export class RelayConfiguration extends Context.Service<
58 RelayConfiguration,
59 {
60 readonly relayIssuer: string;
61 readonly apns: ApnsCredentials | null;
62 readonly fcmServiceAccount?: Redacted.Redacted<string>;
63 readonly clerkSecretKey: Redacted.Redacted<string>;
64 readonly clerkPublishableKey: string;
65 readonly clerkJwtAudience: string;
66 readonly apnsDeliveryJobSigningSecret: Redacted.Redacted<string>;
67 readonly cloudMintPrivateKey: Redacted.Redacted<string>;
68 readonly cloudMintPublicKey: string;
69 readonly managedEndpointBaseDomain: string | undefined;
70 readonly managedEndpointNamespace: string | undefined;
71 readonly managedEndpointCleanupMode?: ManagedEndpointCleanupMode;
72 readonly legacyManagedEndpointCleanupMode?: ManagedEndpointCleanupMode;
73 readonly legacyTunnelGraceMinutes?: number;
74 readonly t3Chat?: T3ChatSettings;
75 }
76>()("t3code-relay/Config/RelayConfiguration") {}
77
78export const layer = (configuration: RelayConfiguration["Service"]) =>
79 Layer.succeed(RelayConfiguration, RelayConfiguration.of(configuration));