77 * configured per tenant via Helm env, with no per-tenant builds and no writable
88 * filesystem.
99 *
10+ * Caddy renders every value as a JSON *string* (so a malformed env value can't
11+ * produce a syntax error that breaks the whole file); `getConfig()` coerces
12+ * booleans/numbers, so a bad value degrades that one feature to "off" rather
13+ * than silently disabling everything.
14+ *
1015 * The committed placeholder `public/config.js` (empty object) is what `bun run
11- * dev` serves locally; in a container Caddy's handler supersedes it.
12- * For local-dev convenience `getConfig()` falls back to Vite env when a field is
13- * absent — in a built image `window.__NB_CONFIG__` always wins because CI builds
14- * with no `VITE_*` values.
16+ * dev` serves locally; in a container Caddy's handler supersedes it. For local
17+ * dev `getConfig()` falls back to Vite env when a field is absent — in a built
18+ * image `window.__NB_CONFIG__` always wins because CI builds with no `VITE_*`.
1519 *
1620 * This is the single source the web client reads for Sentry, Turnstile, and
1721 * PostHog config. All values here are public client keys — never secrets.
1822 */
23+
24+ /** Resolved config the app reads, with booleans/numbers coerced. */
1925export interface NbRuntimeConfig {
2026 /** Deployment tenant id (from NB_TENANT_ID); the Sentry `tenant_id` tag. */
2127 tenantId ?: string ;
2228 /** Deployment environment label (from NB_SENTRY_ENV), e.g. "production". */
2329 environment ?: string ;
2430 /** Build SHA, optional. */
2531 release ?: string ;
26- /** Platform API base URL (for distributed-trace propagation). */
27- platformUrl ?: string ;
2832 sentry ?: {
2933 dsn ?: string ;
3034 enabled ?: boolean ;
@@ -34,33 +38,60 @@ export interface NbRuntimeConfig {
3438 posthog ?: { key ?: string ; enabled ?: boolean } ;
3539}
3640
41+ /**
42+ * Raw injected shape: Caddy renders every value as a JSON string, and the dev
43+ * placeholder may omit fields. Booleans/numbers arrive as strings and are
44+ * coerced by `getConfig()`.
45+ */
46+ interface RawConfig {
47+ tenantId ?: string ;
48+ environment ?: string ;
49+ release ?: string ;
50+ sentry ?: { dsn ?: string ; enabled ?: string | boolean ; tracesSampleRate ?: string | number } ;
51+ turnstile ?: { siteKey ?: string ; enabled ?: string | boolean } ;
52+ posthog ?: { key ?: string ; enabled ?: string | boolean } ;
53+ }
54+
3755declare global {
3856 interface Window {
39- __NB_CONFIG__ ?: NbRuntimeConfig ;
57+ __NB_CONFIG__ ?: RawConfig ;
4058 }
4159}
4260
61+ /** Coerce a string/boolean flag; `undefined` stays undefined (dev default). */
62+ function asBool ( v : string | boolean | undefined ) : boolean | undefined {
63+ if ( v === undefined ) return undefined ;
64+ return v === true || v === "true" ;
65+ }
66+
67+ /** Coerce a string/number to a finite number, falling back on garbage/empty. */
68+ function asNum ( v : string | number | undefined , fallback : number ) : number {
69+ const n = typeof v === "number" ? v : Number ( v ) ;
70+ return Number . isFinite ( n ) ? n : fallback ;
71+ }
72+
4373/** Resolved runtime config: `window.__NB_CONFIG__` over Vite-env dev fallbacks. */
4474export function getConfig ( ) : NbRuntimeConfig {
45- const w = ( typeof window !== "undefined" && window . __NB_CONFIG__ ) || { } ;
75+ const w : RawConfig = ( typeof window !== "undefined" && window . __NB_CONFIG__ ) || { } ;
4676 return {
47- tenantId : w . tenantId ,
48- environment : w . environment ?? import . meta. env . MODE ,
49- release : w . release ,
50- platformUrl : w . platformUrl ?? import . meta. env . VITE_API_BASE ,
77+ tenantId : w . tenantId || undefined ,
78+ environment : w . environment || import . meta. env . MODE ,
79+ release : w . release || undefined ,
5180 sentry : {
52- dsn : w . sentry ?. dsn ?? import . meta. env . VITE_SENTRY_DSN ,
53- enabled : w . sentry ?. enabled ,
54- tracesSampleRate :
55- w . sentry ?. tracesSampleRate ?? Number ( import . meta. env . VITE_SENTRY_TRACES_SAMPLE_RATE ?? 0 ) ,
81+ dsn : w . sentry ?. dsn || import . meta. env . VITE_SENTRY_DSN ,
82+ enabled : asBool ( w . sentry ?. enabled ) ,
83+ tracesSampleRate : asNum (
84+ w . sentry ?. tracesSampleRate ?? import . meta. env . VITE_SENTRY_TRACES_SAMPLE_RATE ,
85+ 0 ,
86+ ) ,
5687 } ,
5788 turnstile : {
58- siteKey : w . turnstile ?. siteKey ?? import . meta. env . VITE_TURNSTILE_SITE_KEY ,
59- enabled : w . turnstile ?. enabled ,
89+ siteKey : w . turnstile ?. siteKey || import . meta. env . VITE_TURNSTILE_SITE_KEY ,
90+ enabled : asBool ( w . turnstile ?. enabled ) ,
6091 } ,
6192 posthog : {
62- key : w . posthog ?. key ?? import . meta. env . VITE_POSTHOG_KEY ,
63- enabled : w . posthog ?. enabled ,
93+ key : w . posthog ?. key || import . meta. env . VITE_POSTHOG_KEY ,
94+ enabled : asBool ( w . posthog ?. enabled ) ,
6495 } ,
6596 } ;
6697}
0 commit comments