From 6f6ea99353ac79dc153606bcd8f6f1020c9515c2 Mon Sep 17 00:00:00 2001 From: Aryeh Stark Date: Wed, 1 Jul 2026 06:52:40 +0300 Subject: [PATCH] Fix org app-switcher linking to the official site on custom deployments defaultOrgAppLinks hardcoded each template's *.agent-native.com prod URL, so a path-prefixed deployment (served at //, e.g. apps.example.com/mail/) linked every sibling app to the first-party hosted site instead of the current deployment. Build // links when the client is path-prefixed (VITE_APP_BASE_PATH baked at build); keep the prod URLs for the subdomain layout. --- .changeset/org-switcher-same-origin-links.md | 10 +++++ .../client/org/workspace-app-links.spec.ts | 32 ++++++++++++++ .../src/client/org/workspace-app-links.ts | 42 ++++++++++++------- 3 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 .changeset/org-switcher-same-origin-links.md diff --git a/.changeset/org-switcher-same-origin-links.md b/.changeset/org-switcher-same-origin-links.md new file mode 100644 index 0000000000..9d0bd25843 --- /dev/null +++ b/.changeset/org-switcher-same-origin-links.md @@ -0,0 +1,10 @@ +--- +"@agent-native/core": patch +--- + +Fix the org app-switcher escaping to the official *.agent-native.com site on +custom deployments. `defaultOrgAppLinks` hardcoded each template's prod URL, so a +path-prefixed deployment (served at `//`, which bakes +`VITE_APP_BASE_PATH`) linked sibling apps to the first-party hosted site instead +of the current deployment. It now builds `//` links when the app is +path-prefixed, and keeps the prod URLs for the first-party subdomain layout. diff --git a/packages/core/src/client/org/workspace-app-links.spec.ts b/packages/core/src/client/org/workspace-app-links.spec.ts index 31eb8a6274..1dca033fcc 100644 --- a/packages/core/src/client/org/workspace-app-links.spec.ts +++ b/packages/core/src/client/org/workspace-app-links.spec.ts @@ -41,6 +41,38 @@ describe("org switcher app links", () => { expect(apps.map((app) => app.id)).not.toContain("videos"); }); + it("uses this origin under // for a path-prefixed deployment", () => { + const prevWindow = (globalThis as { window?: unknown }).window; + (globalThis as { window?: unknown }).window = { + location: { origin: "https://apps.evinced.tech" }, + }; + try { + // VITE_APP_BASE_PATH is baked by a path-prefixed deploy — links must stay + // on this origin, not escape to *.agent-native.com. + const apps = defaultOrgAppLinks({ VITE_APP_BASE_PATH: "/mail" }); + expect(apps.find((a) => a.id === "mail")?.href).toBe( + "https://apps.evinced.tech/mail", + ); + expect(apps.find((a) => a.id === "dispatch")?.href).toBe( + "https://apps.evinced.tech/dispatch/overview", + ); + expect(apps.every((a) => !a.href.includes("agent-native.com"))).toBe( + true, + ); + } finally { + (globalThis as { window?: unknown }).window = prevWindow; + } + }); + + it("keeps the first-party *.agent-native.com URLs when not path-prefixed", () => { + // No VITE_APP_BASE_PATH → the app is served at its own root (subdomain + // layout); keep the prod URLs. + const apps = defaultOrgAppLinks({}); + expect(apps.find((a) => a.id === "mail")?.href).toBe( + "https://mail.agent-native.com", + ); + }); + it("normalizes workspace app manifests against the workspace gateway", () => { const apps = parseWorkspaceAppLinks( { diff --git a/packages/core/src/client/org/workspace-app-links.ts b/packages/core/src/client/org/workspace-app-links.ts index 76d5ed060b..445d463a10 100644 --- a/packages/core/src/client/org/workspace-app-links.ts +++ b/packages/core/src/client/org/workspace-app-links.ts @@ -206,22 +206,36 @@ export function parseWorkspaceAppLinksJson( } } -export function defaultOrgAppLinks(): OrgSwitcherAppLink[] { +export function defaultOrgAppLinks( + env: RuntimeEnv = runtimeEnv(), +): OrgSwitcherAppLink[] { + // A path-prefixed deployment (e.g. apps.example.com//) bakes + // VITE_APP_BASE_PATH=/ into the client at build time. There, the sibling + // apps live on THIS origin under // — using the hardcoded + // *.agent-native.com prod URLs would send users off to the official hosted + // site instead of the current deployment. Fall back to prodUrl only when the + // app is served at its own root (the first-party agent-native.com layout). + const pathPrefixed = Boolean(envString(env, "VITE_APP_BASE_PATH")); + const origin = + pathPrefixed && typeof window !== "undefined" + ? window.location.origin + : null; return sortAppLinks( coreTemplates() .filter((template) => !template.hidden && template.prodUrl) - .map((template) => ({ - id: template.name, - name: template.label, - href: - template.name === DISPATCH_ID - ? appendPath(template.prodUrl!, "overview") - : template.prodUrl!, - description: template.hint, - icon: template.icon, - isDispatch: template.name === DISPATCH_ID, - status: "ready" as const, - })), + .map((template) => { + const base = origin ? `${origin}/${template.name}` : template.prodUrl!; + return { + id: template.name, + name: template.label, + href: + template.name === DISPATCH_ID ? appendPath(base, "overview") : base, + description: template.hint, + icon: template.icon, + isDispatch: template.name === DISPATCH_ID, + status: "ready" as const, + }; + }), ); } @@ -318,7 +332,7 @@ export function useOrgSwitcherAppLinks( const env = useMemo(() => runtimeEnv(), []); const isWorkspace = useMemo(() => isWorkspaceAppEnvironment(env), [env]); const [apps, setApps] = useState(() => - isWorkspace ? initialWorkspaceLinks(env) : defaultOrgAppLinks(), + isWorkspace ? initialWorkspaceLinks(env) : defaultOrgAppLinks(env), ); const [isLoading, setIsLoading] = useState(false);