@flamingo-stack/openframe-frontend-core 0.0.235 → 0.0.236
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ABSGXVGP.js → chunk-265KKBV2.js} +2 -2
- package/dist/{chunk-VO3K4EEG.js → chunk-2LEMRJ7Z.js} +3 -3
- package/dist/{chunk-MDWFPCHN.cjs → chunk-7SOVT7RJ.cjs} +16 -16
- package/dist/{chunk-MDWFPCHN.cjs.map → chunk-7SOVT7RJ.cjs.map} +1 -1
- package/dist/{chunk-FWYMYXL7.js → chunk-AGU44B7B.js} +2 -2
- package/dist/{chunk-2MHQE5ET.js → chunk-AZGRKZJ7.js} +7 -5
- package/dist/{chunk-X4SQ2S7Z.cjs → chunk-BXMMMSIK.cjs} +1277 -1275
- package/dist/chunk-BXMMMSIK.cjs.map +1 -0
- package/dist/{chunk-M4CWPAIB.cjs → chunk-E3VQ53IM.cjs} +7 -7
- package/dist/{chunk-M4CWPAIB.cjs.map → chunk-E3VQ53IM.cjs.map} +1 -1
- package/dist/{chunk-CG6AZ2MC.cjs → chunk-FIG2RKZF.cjs} +12 -5
- package/dist/chunk-FIG2RKZF.cjs.map +1 -0
- package/dist/{chunk-4Y7427BF.cjs → chunk-GRTZ23GL.cjs} +49 -49
- package/dist/{chunk-4Y7427BF.cjs.map → chunk-GRTZ23GL.cjs.map} +1 -1
- package/dist/{chunk-GW6J7IPD.js → chunk-HGYOMQM3.js} +2 -2
- package/dist/{chunk-EI36Q3EC.js → chunk-JNXFCV7K.js} +4 -4
- package/dist/{chunk-ZLQT3T4F.js → chunk-KIGEKMBB.js} +4 -4
- package/dist/{chunk-X6WUWOL5.cjs → chunk-LPJPTBRI.cjs} +38 -38
- package/dist/{chunk-X6WUWOL5.cjs.map → chunk-LPJPTBRI.cjs.map} +1 -1
- package/dist/{chunk-ETFVD7QU.cjs → chunk-NOS6DCYY.cjs} +12 -12
- package/dist/{chunk-ETFVD7QU.cjs.map → chunk-NOS6DCYY.cjs.map} +1 -1
- package/dist/{chunk-Z6FT2JDQ.js → chunk-Q3GY66ST.js} +3 -3
- package/dist/{chunk-76NMNINV.js → chunk-XTCBRQN2.js} +12 -5
- package/dist/chunk-XTCBRQN2.js.map +1 -0
- package/dist/{chunk-W4NBBPQL.cjs → chunk-Z4IBEIWD.cjs} +8 -8
- package/dist/{chunk-W4NBBPQL.cjs.map → chunk-Z4IBEIWD.cjs.map} +1 -1
- package/dist/{chunk-HJRWBA7M.cjs → chunk-ZP2DKI5P.cjs} +32 -32
- package/dist/{chunk-HJRWBA7M.cjs.map → chunk-ZP2DKI5P.cjs.map} +1 -1
- package/dist/components/chat/index.cjs +10 -6
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +9 -5
- package/dist/components/contact/index.cjs +7 -7
- package/dist/components/contact/index.js +6 -6
- package/dist/components/features/index.cjs +6 -6
- package/dist/components/features/index.js +5 -5
- package/dist/components/icons/index.cjs +3 -3
- package/dist/components/icons/index.js +2 -2
- package/dist/components/index.cjs +241 -238
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +13 -10
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +30 -30
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/tickets/index.cjs +89 -89
- package/dist/components/tickets/index.js +8 -8
- package/dist/components/ui/file-manager/index.cjs +69 -69
- package/dist/components/ui/file-manager/index.js +2 -2
- package/dist/components/ui/index.cjs +10 -6
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +9 -5
- package/dist/components/vendor-display-button.d.ts.map +1 -1
- package/dist/hooks/index.cjs +4 -4
- package/dist/hooks/index.js +3 -3
- package/dist/index.cjs +10 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +9 -5
- package/dist/platform-domains.cjs +25 -3
- package/dist/platform-domains.cjs.map +1 -1
- package/dist/platform-domains.d.ts +32 -1
- package/dist/platform-domains.d.ts.map +1 -1
- package/dist/platform-domains.js +23 -4
- package/dist/platform-domains.js.map +1 -1
- package/dist/types/index.cjs +10 -3
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +10 -3
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.cjs +10 -3
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +10 -3
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/platform-domains.test.ts +52 -0
- package/src/components/chat/index.ts +1 -0
- package/src/components/vendor-display-button.tsx +4 -3
- package/src/platform-domains.ts +71 -10
- package/dist/chunk-76NMNINV.js.map +0 -1
- package/dist/chunk-CG6AZ2MC.cjs.map +0 -1
- package/dist/chunk-X4SQ2S7Z.cjs.map +0 -1
- /package/dist/{chunk-ABSGXVGP.js.map → chunk-265KKBV2.js.map} +0 -0
- /package/dist/{chunk-VO3K4EEG.js.map → chunk-2LEMRJ7Z.js.map} +0 -0
- /package/dist/{chunk-FWYMYXL7.js.map → chunk-AGU44B7B.js.map} +0 -0
- /package/dist/{chunk-2MHQE5ET.js.map → chunk-AZGRKZJ7.js.map} +0 -0
- /package/dist/{chunk-GW6J7IPD.js.map → chunk-HGYOMQM3.js.map} +0 -0
- /package/dist/{chunk-EI36Q3EC.js.map → chunk-JNXFCV7K.js.map} +0 -0
- /package/dist/{chunk-ZLQT3T4F.js.map → chunk-KIGEKMBB.js.map} +0 -0
- /package/dist/{chunk-Z6FT2JDQ.js.map → chunk-Q3GY66ST.js.map} +0 -0
|
@@ -39,8 +39,14 @@ function envOverrideFor(key) {
|
|
|
39
39
|
if (!envVar) return null;
|
|
40
40
|
return ENV_OVERRIDES[envVar] || null;
|
|
41
41
|
}
|
|
42
|
+
function ensureScheme(url) {
|
|
43
|
+
const trimmed = url.trim();
|
|
44
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return trimmed;
|
|
45
|
+
return `https://${trimmed.replace(/^\/+/, "")}`;
|
|
46
|
+
}
|
|
42
47
|
function getPlatformProductionUrl(platform) {
|
|
43
|
-
|
|
48
|
+
const resolved = envOverrideFor(platform) ?? byKey(platform)?.defaultUrl ?? envOverrideFor("flamingo") ?? "https://www.flamingo.run";
|
|
49
|
+
return ensureScheme(resolved);
|
|
44
50
|
}
|
|
45
51
|
function hostOf(value) {
|
|
46
52
|
if (!value) return null;
|
|
@@ -106,22 +112,38 @@ function getAllPlatformBaseDomains() {
|
|
|
106
112
|
}
|
|
107
113
|
return Array.from(baseDomains);
|
|
108
114
|
}
|
|
109
|
-
|
|
115
|
+
function isNonCookieableHost(hostname) {
|
|
116
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("127.") || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.includes(".vercel.app");
|
|
117
|
+
}
|
|
118
|
+
function matchCookieDomain(hostname, baseDomains) {
|
|
119
|
+
for (const domain of baseDomains) {
|
|
120
|
+
const bare = domain.startsWith(".") ? domain.slice(1) : domain;
|
|
121
|
+
if (hostname === bare || hostname.endsWith(`.${bare}`)) {
|
|
122
|
+
return domain.startsWith(".") ? domain : `.${domain}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return void 0;
|
|
126
|
+
}
|
|
127
|
+
var _orderIdx = (k) => PLATFORM_DOMAINS.findIndex((e) => e.key === k);
|
|
128
|
+
if (_orderIdx("flamingo") > _orderIdx("flamingo-teaser") || _orderIdx("flamingo") > _orderIdx("universal") || getPlatformByHostname("flamingo.cx") !== "flamingo-teaser" || getPlatformByHostname("hub.openframe.ai") !== "openframe") {
|
|
110
129
|
console.error(
|
|
111
|
-
"[platform-domains] \u26A0\uFE0F PLATFORM_DOMAINS ordering invariant violated \u2014 `flamingo` must precede `flamingo-teaser`/`universal`, and the openframe aliases must be intact. Do not reorder the table."
|
|
130
|
+
"[platform-domains] \u26A0\uFE0F PLATFORM_DOMAINS ordering invariant violated \u2014 `flamingo` must precede `flamingo-teaser`/`universal`, and the openframe/teaser aliases must be intact. Do not reorder the table."
|
|
112
131
|
);
|
|
113
132
|
}
|
|
114
133
|
|
|
115
134
|
exports.PLATFORM_DOMAINS = PLATFORM_DOMAINS;
|
|
116
135
|
exports.aliasHostsOf = aliasHostsOf;
|
|
117
136
|
exports.byKey = byKey;
|
|
137
|
+
exports.ensureScheme = ensureScheme;
|
|
118
138
|
exports.expandWwwApex = expandWwwApex;
|
|
119
139
|
exports.getAllPlatformBaseDomains = getAllPlatformBaseDomains;
|
|
120
140
|
exports.getPlatformByHostname = getPlatformByHostname;
|
|
121
141
|
exports.getPlatformProductionUrl = getPlatformProductionUrl;
|
|
122
142
|
exports.hostOf = hostOf;
|
|
143
|
+
exports.isNonCookieableHost = isNonCookieableHost;
|
|
123
144
|
exports.isPreviewEnv = isPreviewEnv;
|
|
124
145
|
exports.isPreviewHost = isPreviewHost;
|
|
146
|
+
exports.matchCookieDomain = matchCookieDomain;
|
|
125
147
|
exports.toRegistrableBaseDomain = toRegistrableBaseDomain;
|
|
126
148
|
//# sourceMappingURL=platform-domains.cjs.map
|
|
127
149
|
//# sourceMappingURL=platform-domains.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"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 * 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 * Byte-identical to the old cn.ts switch (incl. the unknown-key flamingo.run fallback).\n */\nexport function getPlatformProductionUrl(platform: string): string {\n return (\n envOverrideFor(platform) ??\n byKey(platform)?.defaultUrl ??\n envOverrideFor('flamingo') ??\n 'https://www.flamingo.run'\n )\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// ── 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). A reorder that\n// moves flamingo-teaser/universal before flamingo (e.g. an IDE alphabetize) surfaces loudly in the\n// build + prod logs instead of silently cross-tabbing flamingo links to the wrong owner. The\n// authoritative guard is the reverse-map vitest (src/__tests__/platform-domains.test.ts). ⚠️ keep\n// `flamingo` before `flamingo-teaser`/`universal` in PLATFORM_DOMAINS.\nif (\n getPlatformByHostname('www.flamingo.run') !== 'flamingo' ||\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 aliases must be intact. Do not reorder the table.',\n )\n}\n"]}
|
|
1
|
+
{"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"]}
|
|
@@ -31,11 +31,28 @@ export interface PlatformDomainEntry {
|
|
|
31
31
|
export declare const PLATFORM_DOMAINS: readonly PlatformDomainEntry[];
|
|
32
32
|
/** The registry entry for a key (undefined for an unknown key). */
|
|
33
33
|
export declare function byKey(key: string): PlatformDomainEntry | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Ensure a URL string carries a scheme. Per-deploy `NEXT_PUBLIC_*_URL` overrides are
|
|
36
|
+
* stored SCHEME-LESS (bare host, e.g. `www.openmsp.ai` / `hub.openframe.ai`) — the
|
|
37
|
+
* canonical convention in the Vercel shared-env store. This normalizes them to a full
|
|
38
|
+
* `https://` URL so every downstream consumer (`hostOf`/`new URL`, hrefs, the cookie
|
|
39
|
+
* base-domain derivation, CSP) receives a parseable URL. Full-URL inputs (the registry
|
|
40
|
+
* `defaultUrl`s, any scheme'd override) pass through unchanged.
|
|
41
|
+
*
|
|
42
|
+
* EXPORTED as the single owner of the scheme-normalization rule (next.config.mjs keeps a
|
|
43
|
+
* byte-identical local copy ONLY because Next evaluates its config outside the TS module
|
|
44
|
+
* graph and cannot import this — see the comment there).
|
|
45
|
+
*
|
|
46
|
+
* Handles a (theoretical) protocol-relative `//host` too: strips the leading slashes so it
|
|
47
|
+
* doesn't become `https:////host` (empty-host → hostOf null → silent platform drop).
|
|
48
|
+
*/
|
|
49
|
+
export declare function ensureScheme(url: string): string;
|
|
34
50
|
/**
|
|
35
51
|
* Canonical production URL for a platform: env override wins, else the `defaultUrl`.
|
|
36
52
|
* NEVER throws / undefined — the default guarantees a host (this is what keeps the
|
|
37
53
|
* cookie base-domains, the reverse map, and CSP intact even with every override unset).
|
|
38
|
-
*
|
|
54
|
+
* The result ALWAYS carries a scheme (`ensureScheme`), so the scheme-less env overrides
|
|
55
|
+
* resolve to valid URLs. Unknown-key fallback preserves cn.ts's flamingo.run default.
|
|
39
56
|
*/
|
|
40
57
|
export declare function getPlatformProductionUrl(platform: string): string;
|
|
41
58
|
/** Canonical URL→host parser: `.hostname` (PORT-STRIPPED, lowercased), null on parse failure. */
|
|
@@ -72,4 +89,18 @@ export declare function isPreviewHost(hostname: string): boolean;
|
|
|
72
89
|
* are ALWAYS present → cross-hub SSO is byte-identical to today.
|
|
73
90
|
*/
|
|
74
91
|
export declare function getAllPlatformBaseDomains(): string[];
|
|
92
|
+
/**
|
|
93
|
+
* Hosts that must NOT receive a `Domain=` cookie → the caller returns undefined (host-only):
|
|
94
|
+
* localhost, loopback/private IPs, and any `*.vercel.app`. `vercel.app` is on the Public Suffix
|
|
95
|
+
* List, so browsers SILENTLY drop `Set-Cookie: Domain=.vercel.app` — which broke the PKCE verifier
|
|
96
|
+
* + session cookies on preview deploys. Host-only is sufficient there (the same preview host
|
|
97
|
+
* round-trips the OAuth chain); production hosts (`.flamingo.so`/`.openmsp.ai`/…) fall through.
|
|
98
|
+
*/
|
|
99
|
+
export declare function isNonCookieableHost(hostname: string): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Match a hostname against a set of registrable base domains → the dotted cookie `Domain`
|
|
102
|
+
* (`.flamingo.so`), or undefined when none contains the host. Accepts bases with or without a
|
|
103
|
+
* leading dot and always returns the dotted form. Single owner for the match loop both resolvers ran.
|
|
104
|
+
*/
|
|
105
|
+
export declare function matchCookieDomain(hostname: string, baseDomains: string[]): string | undefined;
|
|
75
106
|
//# sourceMappingURL=platform-domains.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-domains.d.ts","sourceRoot":"","sources":["../src/platform-domains.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,qBAAqB,CAAA;AAEpE,MAAM,WAAW,mBAAmB;IAClC,0FAA0F;IAC1F,GAAG,EAAE,iBAAiB,CAAA;IACtB,iGAAiG;IACjG,UAAU,EAAE,MAAM,CAAA;IAClB,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAA;IACd,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,eAAO,MAAM,gBAAgB,EAAE,SAAS,mBAAmB,EAiB1D,CAAA;AA6BD,mEAAmE;AACnE,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAElE;AASD
|
|
1
|
+
{"version":3,"file":"platform-domains.d.ts","sourceRoot":"","sources":["../src/platform-domains.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,qBAAqB,CAAA;AAEpE,MAAM,WAAW,mBAAmB;IAClC,0FAA0F;IAC1F,GAAG,EAAE,iBAAiB,CAAA;IACtB,iGAAiG;IACjG,UAAU,EAAE,MAAM,CAAA;IAClB,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAA;IACd,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,eAAO,MAAM,gBAAgB,EAAE,SAAS,mBAAmB,EAiB1D,CAAA;AA6BD,mEAAmE;AACnE,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAElE;AASD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOjE;AAID,iGAAiG;AACjG,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAOtE;AAED,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAIpD;AAED,8FAA8F;AAC9F,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIxE;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAElD;AAUD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAOhF;AAID,wDAAwD;AACxD,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;qEACqE;AACrE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,EAAE,CAgCpD;AAKD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAS7D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAQ7F"}
|
package/dist/platform-domains.js
CHANGED
|
@@ -37,8 +37,14 @@ function envOverrideFor(key) {
|
|
|
37
37
|
if (!envVar) return null;
|
|
38
38
|
return ENV_OVERRIDES[envVar] || null;
|
|
39
39
|
}
|
|
40
|
+
function ensureScheme(url) {
|
|
41
|
+
const trimmed = url.trim();
|
|
42
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return trimmed;
|
|
43
|
+
return `https://${trimmed.replace(/^\/+/, "")}`;
|
|
44
|
+
}
|
|
40
45
|
function getPlatformProductionUrl(platform) {
|
|
41
|
-
|
|
46
|
+
const resolved = envOverrideFor(platform) ?? byKey(platform)?.defaultUrl ?? envOverrideFor("flamingo") ?? "https://www.flamingo.run";
|
|
47
|
+
return ensureScheme(resolved);
|
|
42
48
|
}
|
|
43
49
|
function hostOf(value) {
|
|
44
50
|
if (!value) return null;
|
|
@@ -104,12 +110,25 @@ function getAllPlatformBaseDomains() {
|
|
|
104
110
|
}
|
|
105
111
|
return Array.from(baseDomains);
|
|
106
112
|
}
|
|
107
|
-
|
|
113
|
+
function isNonCookieableHost(hostname) {
|
|
114
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("127.") || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.includes(".vercel.app");
|
|
115
|
+
}
|
|
116
|
+
function matchCookieDomain(hostname, baseDomains) {
|
|
117
|
+
for (const domain of baseDomains) {
|
|
118
|
+
const bare = domain.startsWith(".") ? domain.slice(1) : domain;
|
|
119
|
+
if (hostname === bare || hostname.endsWith(`.${bare}`)) {
|
|
120
|
+
return domain.startsWith(".") ? domain : `.${domain}`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return void 0;
|
|
124
|
+
}
|
|
125
|
+
var _orderIdx = (k) => PLATFORM_DOMAINS.findIndex((e) => e.key === k);
|
|
126
|
+
if (_orderIdx("flamingo") > _orderIdx("flamingo-teaser") || _orderIdx("flamingo") > _orderIdx("universal") || getPlatformByHostname("flamingo.cx") !== "flamingo-teaser" || getPlatformByHostname("hub.openframe.ai") !== "openframe") {
|
|
108
127
|
console.error(
|
|
109
|
-
"[platform-domains] \u26A0\uFE0F PLATFORM_DOMAINS ordering invariant violated \u2014 `flamingo` must precede `flamingo-teaser`/`universal`, and the openframe aliases must be intact. Do not reorder the table."
|
|
128
|
+
"[platform-domains] \u26A0\uFE0F PLATFORM_DOMAINS ordering invariant violated \u2014 `flamingo` must precede `flamingo-teaser`/`universal`, and the openframe/teaser aliases must be intact. Do not reorder the table."
|
|
110
129
|
);
|
|
111
130
|
}
|
|
112
131
|
|
|
113
|
-
export { PLATFORM_DOMAINS, aliasHostsOf, byKey, expandWwwApex, getAllPlatformBaseDomains, getPlatformByHostname, getPlatformProductionUrl, hostOf, isPreviewEnv, isPreviewHost, toRegistrableBaseDomain };
|
|
132
|
+
export { PLATFORM_DOMAINS, aliasHostsOf, byKey, ensureScheme, expandWwwApex, getAllPlatformBaseDomains, getPlatformByHostname, getPlatformProductionUrl, hostOf, isNonCookieableHost, isPreviewEnv, isPreviewHost, matchCookieDomain, toRegistrableBaseDomain };
|
|
114
133
|
//# sourceMappingURL=platform-domains.js.map
|
|
115
134
|
//# sourceMappingURL=platform-domains.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/platform-domains.ts"],"names":[],"mappings":"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","file":"platform-domains.js","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 * 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 * Byte-identical to the old cn.ts switch (incl. the unknown-key flamingo.run fallback).\n */\nexport function getPlatformProductionUrl(platform: string): string {\n return (\n envOverrideFor(platform) ??\n byKey(platform)?.defaultUrl ??\n envOverrideFor('flamingo') ??\n 'https://www.flamingo.run'\n )\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// ── 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). A reorder that\n// moves flamingo-teaser/universal before flamingo (e.g. an IDE alphabetize) surfaces loudly in the\n// build + prod logs instead of silently cross-tabbing flamingo links to the wrong owner. The\n// authoritative guard is the reverse-map vitest (src/__tests__/platform-domains.test.ts). ⚠️ keep\n// `flamingo` before `flamingo-teaser`/`universal` in PLATFORM_DOMAINS.\nif (\n getPlatformByHostname('www.flamingo.run') !== 'flamingo' ||\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 aliases must be intact. Do not reorder the table.',\n )\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/platform-domains.ts"],"names":[],"mappings":"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","file":"platform-domains.js","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"]}
|
package/dist/types/index.cjs
CHANGED
|
@@ -220,8 +220,14 @@ function envOverrideFor(key) {
|
|
|
220
220
|
if (!envVar) return null;
|
|
221
221
|
return ENV_OVERRIDES[envVar] || null;
|
|
222
222
|
}
|
|
223
|
+
function ensureScheme(url) {
|
|
224
|
+
const trimmed = url.trim();
|
|
225
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return trimmed;
|
|
226
|
+
return `https://${trimmed.replace(/^\/+/, "")}`;
|
|
227
|
+
}
|
|
223
228
|
function getPlatformProductionUrl(platform) {
|
|
224
|
-
|
|
229
|
+
const resolved = envOverrideFor(platform) ?? byKey(platform)?.defaultUrl ?? envOverrideFor("flamingo") ?? "https://www.flamingo.run";
|
|
230
|
+
return ensureScheme(resolved);
|
|
225
231
|
}
|
|
226
232
|
function hostOf(value) {
|
|
227
233
|
if (!value) return null;
|
|
@@ -253,9 +259,10 @@ function getPlatformByHostname(hostname) {
|
|
|
253
259
|
}
|
|
254
260
|
return null;
|
|
255
261
|
}
|
|
256
|
-
|
|
262
|
+
var _orderIdx = (k) => PLATFORM_DOMAINS.findIndex((e) => e.key === k);
|
|
263
|
+
if (_orderIdx("flamingo") > _orderIdx("flamingo-teaser") || _orderIdx("flamingo") > _orderIdx("universal") || getPlatformByHostname("flamingo.cx") !== "flamingo-teaser" || getPlatformByHostname("hub.openframe.ai") !== "openframe") {
|
|
257
264
|
console.error(
|
|
258
|
-
"[platform-domains] \u26A0\uFE0F PLATFORM_DOMAINS ordering invariant violated \u2014 `flamingo` must precede `flamingo-teaser`/`universal`, and the openframe aliases must be intact. Do not reorder the table."
|
|
265
|
+
"[platform-domains] \u26A0\uFE0F PLATFORM_DOMAINS ordering invariant violated \u2014 `flamingo` must precede `flamingo-teaser`/`universal`, and the openframe/teaser aliases must be intact. Do not reorder the table."
|
|
259
266
|
);
|
|
260
267
|
}
|
|
261
268
|
|