Skip to content

refactor(platform-domains): every URL rule lives once beside the registry; getBaseUrl deleted - #2219

Merged
michaelassraf merged 3 commits into
mainfrom
refactor/platform-url-ssot
Sep 17, 2026
Merged

michaelassraf merged 3 commits into
mainfrom
refactor/platform-url-ssot

Conversation

@michaelassraf

@michaelassraf michaelassraf commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Why

Every app resolved its URLs itself, and the rules had drifted apart:

  • The lib's getBaseUrl(): with a platform it returned the registry URL. Without one it returned VERCEL_PROJECT_PRODUCTION_URL, whichever domain Vercel lists first (flamingo.cx on flamingo, a 308; production on every preview), falling back to openmsp.
  • multi-platform-hub re-implemented the platform URL, its own deployment URL, the request origin, local-host detection and redirect-target checks.
  • openframe-oss-frontend had four copies of its app URL (getBaseUrl, getMetadataBaseUrl, two NEXT_PUBLIC_APP_URL reads in layout.tsx) with two different fallbacks (openframe.dev / openframe.ai).

What

Every URL rule now lives once, in the pure platform-domains subpath next to the registry. All five are also re-exported from ./utils.

Function What it resolves
getPlatformUrl(platform, { environment }) Where a platform lives. platform is required. Dev URL outside a production build, registry URL otherwise; environment: 'production' forces the registry.
getDeploymentUrl({ platform, configuredUrl }) Where the running app is reachable, in order: the browser origin; a runtime-configured URL (a self-hosted OpenFrame install); a Vercel preview's immutable VERCEL_URL; the registry URL in production; NEXT_PUBLIC_DEV_URL / localhost on PORT in development.
getRequestOrigin(headers) The origin a request arrived on, from host only (never x-forwarded-host); null without a Host header.
isLocalUrl(url) A whole local hostname, so localhost.example.com is public.
resolveRedirectTarget(origin, target) Throws when a relative target would leave the origin (//evil.example, /\evil.example).

Also in this PR:

  • getBaseUrl is deleted from utils/cn.ts and the barrel. source-row-cta (+ test) and made-with-love use getPlatformUrl; READMEs, .cn.md and the embedding example's docs are updated.
  • ensureScheme doc: no longer claims next.config.mjs keeps a copy, since this subpath is plain ESM.

Consumers (each moves in its own PR, after this is released)

  • multi-platform-hub (flamingo-stack/multi-platform-hub#1296): lib/urls.ts becomes a binding that only supplies the current platform.
  • openframe-oss-frontend: getAppUrl() = getDeploymentUrl({ platform: 'openframe-dashboard', configuredUrl: NEXT_PUBLIC_APP_URL }), replacing its four copies.
  • react-embedding-example: doc comments only.

Verification

  • npm run type-check: 0 errors.
  • eslint: clean.
  • vitest: full suite 5427 passed, including new tests for every function above (registry URL in production, VERCEL_PROJECT_PRODUCTION_URL ignored, preview URL, runtime-configured URL, dev port, browser origin, host-only request origin, local hostnames, refused redirect targets).

Release note

Breaking: getBaseUrl is removed from @flamingo-stack/openframe-frontend-core/utils. Use getPlatformUrl(platform) for a platform's URL, or getDeploymentUrl({ platform, configuredUrl }) for the running app's.

…solver; getBaseUrl deleted [preview:none]

getBaseUrl() without a platform returned VERCEL_PROJECT_PRODUCTION_URL, whichever domain Vercel lists first for a project: flamingo.cx on flamingo (a 308) and production on every preview, and it silently fell back to openmsp. getPlatformUrl(platform, { environment }) lives in the pure platform-domains subpath beside the registry, takes a required platform, and resolves the dev URL outside a production build and the registry URL otherwise; environment production forces the registry. getBaseUrl is removed from utils/cn and the utils barrel; every lib caller (source-row-cta, made-with-love), the example app, READMEs and docs move to it.
@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

🦩 Flamingo Code Review

No findings on the current head.

Mode: advisory


Need another pass? Commits pushed after this review are not reviewed automatically.

  • Review the new commits — the commits added since this review
  • Review the whole diff again — ignoring what was already reviewed

Prefer typing? Comment @flamingo-review, or @flamingo-review full. To review every push on this pull request, add the flamingo-review-always label.

React 👍/👎 on inline comments to teach the reviewer.

Started 2026-09-17 00:12 UTC · updated 2026-09-17 00:12 UTC · workflow run

… redirect-target resolvers live beside the registry [preview:none]

Every URL rule in one module for every consumer: getDeploymentUrl({ platform, configuredUrl }) is where the running app is reachable (browser origin, a runtime-configured URL such as a self-hosted OpenFrame install, a Vercel preview's immutable URL, the registry URL in production, localhost in development); getRequestOrigin reads the host header only; isLocalUrl matches a whole local hostname; resolveRedirectTarget refuses a relative target that leaves the origin. Moved from the hub, which re-implemented them, and from openframe-oss-frontend's app-URL copies. Re-exported from ./utils; tests ported.
@michaelassraf
michaelassraf force-pushed the refactor/platform-url-ssot branch from fa742eb to 016dc68 Compare September 17, 2026 00:20
@michaelassraf michaelassraf changed the title refactor(platform-domains): getPlatformUrl is the one platform-URL resolver; getBaseUrl deleted refactor(platform-domains): every URL rule lives once beside the registry; getBaseUrl deleted Sep 17, 2026
…, so consumers keep no URL rule [preview:none]
@michaelassraf
michaelassraf merged commit 80e4e9f into main Sep 17, 2026
7 of 10 checks passed
@michaelassraf
michaelassraf deleted the refactor/platform-url-ssot branch September 17, 2026 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant