import { env } from "$env/dynamic/private"; // accepts "0" to disable a limit; non-numeric values fall back const bytes = (value: string | undefined, fallback: number) => { const raw = value?.trim(); if (!raw) return fallback; const parsed = Number(raw); return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback; }; const cleanUrl = (value: string | undefined, fallback: string) => { const raw = value?.trim() || fallback; return raw.replace(/\/+$/, ""); }; export type WebConfig = { bobbinUrl: string; knotMirrorUrl: string; apiUrl: string; deliberiUrl: string; /** the domain user sites are served under, e.g. "tngl.io" */ sitesDomain: string; camoUrl: string; avatarUrl: string; ogreUrl: string; /** the secrets camo and avatar sign with, so neither leaves the server */ camoSecret: string; avatarSecret: string; moderationServiceDid: string; turnstileSiteKey: string; /** see SSR_DIFF_MAX_BYTES */ ssrDiffMaxBytes: number; migratorUrl: string; }; export type PublicWebConfig = Pick< WebConfig, | "bobbinUrl" | "knotMirrorUrl" | "apiUrl" | "deliberiUrl" | "sitesDomain" | "moderationServiceDid" | "turnstileSiteKey" | "ssrDiffMaxBytes" | "ogreUrl" | "migratorUrl" > & { /** camo has a secret, so markup can route images through it */ camoEnabled: boolean; }; type WebConfigEnv = { BOBBIN_URL?: string; KNOTMIRROR_URL?: string; TANGLED_API_URL?: string; API_URL?: string; DELIBERI_URL?: string; KNOT_RESOLVER_URL?: string; SITES_DOMAIN?: string; CAMO_URL?: string; CAMO_SHARED_SECRET?: string; AVATAR_URL?: string; AVATAR_SHARED_SECRET?: string; OGRE_URL?: string; MODERATION_SERVICE_DID?: string; TURNSTILE_SITE_KEY?: string; MIGRATOR_URL?: string; /** diff payloads above this many bytes are not buffered through the ssr * worker; the page refetches them from the browser instead. "0" disables */ SSR_DIFF_MAX_BYTES?: string; }; export const resolveConfig = (values: WebConfigEnv): WebConfig => ({ bobbinUrl: cleanUrl(values.BOBBIN_URL, "http://127.0.0.1:8090"), knotMirrorUrl: cleanUrl(values.KNOTMIRROR_URL, ""), apiUrl: cleanUrl(values.TANGLED_API_URL ?? values.API_URL, "http://127.0.0.1:8080"), deliberiUrl: cleanUrl(values.DELIBERI_URL, "https://notifs-dev.tangled.network"), sitesDomain: values.SITES_DOMAIN?.trim() || "tngl.io", camoUrl: cleanUrl(values.CAMO_URL, "https://camo.tangled.sh"), avatarUrl: cleanUrl(values.AVATAR_URL, "https://avatar.tangled.sh"), ogreUrl: cleanUrl(values.OGRE_URL, "https://ogre.tangled.network"), migratorUrl: cleanUrl(values.MIGRATOR_URL, "https://migrator.tangled.sh"), camoSecret: values.CAMO_SHARED_SECRET?.trim() ?? "", avatarSecret: values.AVATAR_SHARED_SECRET?.trim() ?? "", moderationServiceDid: values.MODERATION_SERVICE_DID?.trim() ?? "", turnstileSiteKey: values.TURNSTILE_SITE_KEY?.trim() ?? "", ssrDiffMaxBytes: bytes(values.SSR_DIFF_MAX_BYTES, 5 * 1024 * 1024) }); // process env takes precedence over platform bindings in dev export const getConfig = (platform?: App.Platform): WebConfig => resolveConfig({ ...((platform?.env as WebConfigEnv | undefined) ?? {}), ...(env as WebConfigEnv) }); // listed one by one, so anything new in WebConfig stays private until it is // named here export const getPublicConfig = (platform?: App.Platform): PublicWebConfig => { const config = getConfig(platform); return { bobbinUrl: config.bobbinUrl, knotMirrorUrl: config.knotMirrorUrl, apiUrl: config.apiUrl, deliberiUrl: config.deliberiUrl, sitesDomain: config.sitesDomain, moderationServiceDid: config.moderationServiceDid, turnstileSiteKey: config.turnstileSiteKey, ssrDiffMaxBytes: config.ssrDiffMaxBytes, ogreUrl: config.ogreUrl, migratorUrl: config.migratorUrl, camoEnabled: config.camoSecret !== "" }; };