Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/org-switcher-same-origin-links.md
Original file line number Diff line number Diff line change
@@ -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 `<origin>/<app>/`, which bakes
`VITE_APP_BASE_PATH`) linked sibling apps to the first-party hosted site instead
of the current deployment. It now builds `<origin>/<app>/` links when the app is
path-prefixed, and keeps the prod URLs for the first-party subdomain layout.
32 changes: 32 additions & 0 deletions packages/core/src/client/org/workspace-app-links.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,38 @@ describe("org switcher app links", () => {
expect(apps.map((app) => app.id)).not.toContain("videos");
});

it("uses this origin under /<app>/ 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(
{
Expand Down
42 changes: 28 additions & 14 deletions packages/core/src/client/org/workspace-app-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/<app>/) bakes
// VITE_APP_BASE_PATH=/<app> into the client at build time. There, the sibling
// apps live on THIS origin under /<app>/ — 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,
};
}),
);
}

Expand Down Expand Up @@ -318,7 +332,7 @@ export function useOrgSwitcherAppLinks(
const env = useMemo(() => runtimeEnv(), []);
const isWorkspace = useMemo(() => isWorkspaceAppEnvironment(env), [env]);
const [apps, setApps] = useState<OrgSwitcherAppLink[]>(() =>
isWorkspace ? initialWorkspaceLinks(env) : defaultOrgAppLinks(),
isWorkspace ? initialWorkspaceLinks(env) : defaultOrgAppLinks(env),
);
const [isLoading, setIsLoading] = useState(false);

Expand Down
Loading