{"version":3,"sources":["../src/platform-domains.ts"],"names":[],"mappings":"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","file":"platform-domains.cjs","sourcesContent":["/**\n * Platform-Domain SSOT (single source of truth) + derivations.\n *\n * ONE registry maps each platform → its canonical production URL (`defaultUrl`,\n * the load-bearing source) with an optional per-deploy `NEXT_PUBLIC_*_URL`\n * OVERRIDE. Everything else — the reverse host→platform resolver, the cookie\n * base-domain set (the cross-subdomain SSO mechanism), www/apex expansion, the\n * URL→host parse, preview detection — derives from this one table.\n *\n * EDGE-SAFE + PURE: no React/clsx/tailwind, no `server-only`, no `node:`\n * builtins, no `'use client'`. So it is legal in the Edge middleware\n * (`proxy.ts`), in `'use client'` providers, AND in `server-only` modules\n * (e.g. cookie-domain-server.ts) simultaneously. The ONLY non-pure export is\n * `getAllPlatformBaseDomains`, which reads `typeof window`/`process.env` to\n * preserve byte-identical cookie behavior.\n */\n\nimport type { PlatformName } from './types/platform'\n\nexport type PlatformDomainKey = PlatformName | 'openframe-dashboard'\n\nexport interface PlatformDomainEntry {\n  /** Platform key (matches `PlatformName`, plus the forward-only `openframe-dashboard`). */\n  key: PlatformDomainKey\n  /** Canonical production URL — the LOAD-BEARING source of truth (today's hardcoded fallbacks). */\n  defaultUrl: string\n  /** `NEXT_PUBLIC_*_URL` per-deploy OVERRIDE. The `defaultUrl` covers it when unset. */\n  envVar: string\n  /** Legacy/secondary hosts that REVERSE-map to this key (no env var exists — NOT canonical). */\n  aliasHostnames?: string[]\n  /** Forward-only: no DB row, excluded from the reverse index + cookie set (e.g. the product-CTA dashboard). */\n  pseudo?: boolean\n}\n\nexport const PLATFORM_DOMAINS: readonly PlatformDomainEntry[] = [\n  { key: 'marketing-hub', defaultUrl: 'https://marketing-hub.flamingo.so', envVar: 'NEXT_PUBLIC_MARKETING_HUB_URL' },\n  { key: 'company-hub',   defaultUrl: 'https://company-hub.flamingo.so',   envVar: 'NEXT_PUBLIC_COMPANY_HUB_URL' },\n  { key: 'product-hub',   defaultUrl: 'https://product-hub.flamingo.so',   envVar: 'NEXT_PUBLIC_PRODUCT_HUB_URL' },\n  { key: 'revenue-hub',   defaultUrl: 'https://revenue-hub.flamingo.so',   envVar: 'NEXT_PUBLIC_REVENUE_HUB_URL' },\n  { key: 'people-hub',    defaultUrl: 'https://people-hub.flamingo.so',    envVar: 'NEXT_PUBLIC_PEOPLE_HUB_URL' },\n  { key: 'openmsp',       defaultUrl: 'https://www.openmsp.ai',            envVar: 'NEXT_PUBLIC_OPENMSP_URL' },\n  // ORDERING INVARIANT (first-wins, load-bearing): `flamingo` MUST precede `flamingo-teaser` + `universal`.\n  // All three resolve to www.flamingo.run; the reverse index is first-wins → flamingo claims the shared host,\n  // teaser keeps only its unique flamingo.cx aliases, universal contributes no unique host.\n  // ⚠️ DO NOT REORDER — enforced by the module-load self-check at the bottom of this file.\n  { key: 'flamingo',         defaultUrl: 'https://www.flamingo.run',  envVar: 'NEXT_PUBLIC_FLAMINGO_URL' },\n  { key: 'tmcg',             defaultUrl: 'https://www.tmcg.miami',    envVar: 'NEXT_PUBLIC_TMCG_URL' },\n  { key: 'flamingo-teaser',  defaultUrl: 'https://www.flamingo.run',  envVar: 'NEXT_PUBLIC_FLAMINGO_URL', aliasHostnames: ['flamingo.cx', 'www.flamingo.cx'] },\n  { key: 'openframe',        defaultUrl: 'https://openframe.ai',      envVar: 'NEXT_PUBLIC_OPENFRAME_URL', aliasHostnames: ['openframe.ai', 'www.openframe.ai', 'hub.openframe.ai'] },\n  { key: 'openframe-dashboard', defaultUrl: 'https://openframe.ai',   envVar: 'NEXT_PUBLIC_OPENFRAME_DASHBOARD_URL', pseudo: true },\n  { key: 'universal',        defaultUrl: 'https://www.flamingo.run',  envVar: 'NEXT_PUBLIC_FLAMINGO_URL' },\n]\n\n// ── Compile-time key guards (anchor the table on PlatformName, both directions) ──\n// (1) Removal/typo guard: every table key must be a valid PlatformDomainKey.\nconst _tableSatisfies = PLATFORM_DOMAINS satisfies readonly PlatformDomainEntry[]\nvoid _tableSatisfies\n// (2) Addition guard: a NEW PlatformName member that lacks a table row fails the build.\ntype _MissingKey = Exclude<PlatformName, (typeof PLATFORM_DOMAINS)[number]['key']>\nconst _exhaustive: [_MissingKey] extends [never] ? true : false = true\nvoid _exhaustive\n\n// ── Env overrides (the ONLY place env URLs enter — literal-key inlined + compile-time-guarded) ──\n// `process.env.NEXT_PUBLIC_X` is build-inlined ONLY with a LITERAL key, so the env-var name is the\n// irreducible two-copy (the registry `envVar` column + the literal access below). The `satisfies` makes\n// `tsc`/`next build` FAIL if this map is missing a registry env var OR carries a stale one (bidirectional).\ntype EnvVarKey = (typeof PLATFORM_DOMAINS)[number]['envVar']\nconst ENV_OVERRIDES = {\n  NEXT_PUBLIC_MARKETING_HUB_URL: process.env.NEXT_PUBLIC_MARKETING_HUB_URL,\n  NEXT_PUBLIC_COMPANY_HUB_URL: process.env.NEXT_PUBLIC_COMPANY_HUB_URL,\n  NEXT_PUBLIC_PRODUCT_HUB_URL: process.env.NEXT_PUBLIC_PRODUCT_HUB_URL,\n  NEXT_PUBLIC_REVENUE_HUB_URL: process.env.NEXT_PUBLIC_REVENUE_HUB_URL,\n  NEXT_PUBLIC_PEOPLE_HUB_URL: process.env.NEXT_PUBLIC_PEOPLE_HUB_URL,\n  NEXT_PUBLIC_OPENMSP_URL: process.env.NEXT_PUBLIC_OPENMSP_URL,\n  NEXT_PUBLIC_FLAMINGO_URL: process.env.NEXT_PUBLIC_FLAMINGO_URL,\n  NEXT_PUBLIC_TMCG_URL: process.env.NEXT_PUBLIC_TMCG_URL,\n  NEXT_PUBLIC_OPENFRAME_URL: process.env.NEXT_PUBLIC_OPENFRAME_URL,\n  NEXT_PUBLIC_OPENFRAME_DASHBOARD_URL: process.env.NEXT_PUBLIC_OPENFRAME_DASHBOARD_URL,\n} satisfies Record<EnvVarKey, string | undefined>\n\n/** The registry entry for a key (undefined for an unknown key). */\nexport function byKey(key: string): PlatformDomainEntry | undefined {\n  return PLATFORM_DOMAINS.find((e) => e.key === key)\n}\n\n/** Read a platform's `NEXT_PUBLIC_*_URL` override (or null). */\nfunction envOverrideFor(key: string): string | null {\n  const envVar = byKey(key)?.envVar\n  if (!envVar) return null\n  return (ENV_OVERRIDES as Record<string, string | undefined>)[envVar] || null\n}\n\n/**\n * Ensure a URL string carries a scheme. Per-deploy `NEXT_PUBLIC_*_URL` overrides are\n * stored SCHEME-LESS (bare host, e.g. `www.openmsp.ai` / `hub.openframe.ai`) — the\n * canonical convention in the Vercel shared-env store. This normalizes them to a full\n * `https://` URL so every downstream consumer (`hostOf`/`new URL`, hrefs, the cookie\n * base-domain derivation, CSP) receives a parseable URL. Full-URL inputs (the registry\n * `defaultUrl`s, any scheme'd override) pass through unchanged.\n *\n * EXPORTED as the single owner of the scheme-normalization rule (next.config.mjs keeps a\n * byte-identical local copy ONLY because Next evaluates its config outside the TS module\n * graph and cannot import this — see the comment there).\n *\n * Handles a (theoretical) protocol-relative `//host` too: strips the leading slashes so it\n * doesn't become `https:////host` (empty-host → hostOf null → silent platform drop).\n */\nexport function ensureScheme(url: string): string {\n  const trimmed = url.trim()\n  if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed)) return trimmed // already has a scheme\n  return `https://${trimmed.replace(/^\\/+/, '')}` // bare host or protocol-relative `//host`\n}\n\n/**\n * Canonical production URL for a platform: env override wins, else the `defaultUrl`.\n * NEVER throws / undefined — the default guarantees a host (this is what keeps the\n * cookie base-domains, the reverse map, and CSP intact even with every override unset).\n * The result ALWAYS carries a scheme (`ensureScheme`), so the scheme-less env overrides\n * resolve to valid URLs. Unknown-key fallback preserves cn.ts's flamingo.run default.\n */\nexport function getPlatformProductionUrl(platform: string): string {\n  const resolved =\n    envOverrideFor(platform) ??\n    byKey(platform)?.defaultUrl ??\n    envOverrideFor('flamingo') ??\n    'https://www.flamingo.run'\n  return ensureScheme(resolved)\n}\n\n// ── Single-owner host primitives ──\n\n/** Canonical URL→host parser: `.hostname` (PORT-STRIPPED, lowercased), null on parse failure. */\nexport function hostOf(value: string | null | undefined): string | null {\n  if (!value) return null\n  try {\n    return new URL(value).hostname.toLowerCase()\n  } catch {\n    return null\n  }\n}\n\n/** Expand a host into its `www.`/apex pair. 3+-label and single-label hosts return `[host]`. */\nexport function expandWwwApex(host: string): string[] {\n  if (host.startsWith('www.')) return [host, host.slice(4)]\n  if (host.split('.').length === 2) return [host, `www.${host}`]\n  return [host]\n}\n\n/** Registrable base domain (`parts.slice(-2).join('.')`), dotless; undefined for <2-label. */\nexport function toRegistrableBaseDomain(host: string): string | undefined {\n  const parts = host.split('.')\n  if (parts.length >= 2) return parts.slice(-2).join('.')\n  return undefined\n}\n\n/** An entry's alias hosts (single-owner reader). */\nexport function aliasHostsOf(key: string): string[] {\n  return byKey(key)?.aliasHostnames ?? []\n}\n\n/** All hosts an entry contributes to the reverse index (resolved host + optional aliases). */\nfunction hostsForEntry(entry: PlatformDomainEntry, opts: { includeAliases: boolean }): string[] {\n  const resolved = hostOf(getPlatformProductionUrl(entry.key))\n  const hosts = resolved ? expandWwwApex(resolved) : []\n  if (opts.includeAliases) hosts.push(...aliasHostsOf(entry.key))\n  return hosts\n}\n\n/**\n * Reverse resolver: hostname → platform key (first-wins over registry order, non-pseudo only).\n * Guarantees openframe.ai / www.openframe.ai / hub.openframe.ai → openframe in every env.\n * Replaces the hub `PLATFORM_DOMAIN_MAP`.\n */\nexport function getPlatformByHostname(hostname: string): PlatformDomainKey | null {\n  const host = hostname.toLowerCase()\n  for (const entry of PLATFORM_DOMAINS) {\n    if (entry.pseudo) continue\n    if (hostsForEntry(entry, { includeAliases: true }).includes(host)) return entry.key\n  }\n  return null\n}\n\n// ── Preview detection (two distinct predicates — env-form vs host-form) ──\n\n/** Env-form preview predicate (Vercel `VERCEL_ENV`). */\nexport function isPreviewEnv(): boolean {\n  return process.env.VERCEL_ENV === 'preview'\n}\n\n/** Host-form preview predicate (a `*.vercel.app` host). Dot-bounded suffix so a\n *  malicious `foo.vercel.app.evil.com` is NOT treated as preview. */\nexport function isPreviewHost(hostname: string): boolean {\n  return hostname.endsWith('.vercel.app')\n}\n\n/**\n * ALL unique cookie base domains (the cross-subdomain SSO mechanism).\n *\n * NON-PURE (the sole such export): reads `typeof window` + `process.env` to\n * preserve byte-identical cookie behavior. Keeps the original three branches:\n *   1. localhost / private IP → [] (host-only cookies)\n *   2. Vercel preview (env OR host) → ['.vercel.app','vercel.app']\n *   3. production → for each non-pseudo platform, registrable base of its resolved\n *      host, emitted as both `.base` and bare `base`.\n *\n * Because `getPlatformProductionUrl` always yields a host (override OR default),\n * `.flamingo.so` / `.flamingo.run` / `.openmsp.ai` / `.tmcg.miami` / `.openframe.ai`\n * are ALWAYS present → cross-hub SSO is byte-identical to today.\n */\nexport function getAllPlatformBaseDomains(): string[] {\n  if (typeof window === 'undefined') return []\n\n  const hostname = window.location.hostname\n\n  // Case 1: localhost / private IP — no domains\n  if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.startsWith('127.')) {\n    return []\n  }\n\n  // Case 2: Vercel preview — vercel.app domain\n  const previewEnv =\n    process.env.VERCEL_ENV === 'preview' ||\n    process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' ||\n    isPreviewHost(hostname)\n  if (previewEnv) {\n    return ['.vercel.app', 'vercel.app']\n  }\n\n  // Case 3: production — registrable base of every non-pseudo platform's resolved host\n  const baseDomains = new Set<string>()\n  for (const entry of PLATFORM_DOMAINS) {\n    if (entry.pseudo) continue\n    const host = hostOf(getPlatformProductionUrl(entry.key))\n    if (!host) continue\n    const base = toRegistrableBaseDomain(host)\n    if (base) {\n      baseDomains.add(`.${base}`)\n      baseDomains.add(base)\n    }\n  }\n  return Array.from(baseDomains)\n}\n\n// ── Cookie-domain guard + match (single owner; shared by the client cookie-domain.ts +\n//    server-only cookie-domain-server.ts resolvers, which previously hand-rolled both 3×) ──\n\n/**\n * Hosts that must NOT receive a `Domain=` cookie → the caller returns undefined (host-only):\n * localhost, loopback/private IPs, and any `*.vercel.app`. `vercel.app` is on the Public Suffix\n * List, so browsers SILENTLY drop `Set-Cookie: Domain=.vercel.app` — which broke the PKCE verifier\n * + session cookies on preview deploys. Host-only is sufficient there (the same preview host\n * round-trips the OAuth chain); production hosts (`.flamingo.so`/`.openmsp.ai`/…) fall through.\n */\nexport function isNonCookieableHost(hostname: string): boolean {\n  return (\n    hostname === 'localhost' ||\n    hostname === '127.0.0.1' ||\n    hostname.startsWith('127.') ||\n    hostname.startsWith('192.168.') ||\n    hostname.startsWith('10.') ||\n    hostname.includes('.vercel.app')\n  )\n}\n\n/**\n * Match a hostname against a set of registrable base domains → the dotted cookie `Domain`\n * (`.flamingo.so`), or undefined when none contains the host. Accepts bases with or without a\n * leading dot and always returns the dotted form. Single owner for the match loop both resolvers ran.\n */\nexport function matchCookieDomain(hostname: string, baseDomains: string[]): string | undefined {\n  for (const domain of baseDomains) {\n    const bare = domain.startsWith('.') ? domain.slice(1) : domain\n    if (hostname === bare || hostname.endsWith(`.${bare}`)) {\n      return domain.startsWith('.') ? domain : `.${domain}`\n    }\n  }\n  return undefined\n}\n\n// ── Module-load ordering self-check (NON-fatal — this module is imported by cn.ts → ~everything,\n// so a hard throw would be a total outage if the assertion were ever over-strict). Checks the REAL\n// invariant — table order — STRUCTURALLY (findIndex), NOT via getPlatformByHostname on a hardcoded\n// host: the latter false-positives when NEXT_PUBLIC_FLAMINGO_URL is overridden to a non-default host\n// (flamingo's resolved host changes, so `www.flamingo.run` no longer reverse-maps to it though the\n// ordering is fine). The alias checks below ARE env-immune (aliasHostnames are unique per key). The\n// authoritative guard is the reverse-map vitest. ⚠️ keep `flamingo` before `flamingo-teaser`/`universal`.\nconst _orderIdx = (k: PlatformDomainKey) => PLATFORM_DOMAINS.findIndex((e) => e.key === k)\nif (\n  _orderIdx('flamingo') > _orderIdx('flamingo-teaser') ||\n  _orderIdx('flamingo') > _orderIdx('universal') ||\n  getPlatformByHostname('flamingo.cx') !== 'flamingo-teaser' ||\n  getPlatformByHostname('hub.openframe.ai') !== 'openframe'\n) {\n  // eslint-disable-next-line no-console\n  console.error(\n    '[platform-domains] ⚠️ PLATFORM_DOMAINS ordering invariant violated — `flamingo` must precede ' +\n      '`flamingo-teaser`/`universal`, and the openframe/teaser aliases must be intact. Do not reorder the table.',\n  )\n}\n"]}