@agent-native/dispatch 0.15.22 → 0.15.24

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.
@@ -1,10 +1,16 @@
1
1
  import { afterEach, describe, expect, it, vi } from "vitest";
2
2
 
3
3
  const mocks = vi.hoisted(() => ({
4
+ currentOrgId: vi.fn(),
5
+ currentOwnerEmail: vi.fn(),
4
6
  deleteAppSecret: vi.fn(),
7
+ discoverAgents: vi.fn(),
5
8
  getDb: vi.fn(),
9
+ getOrgSetting: vi.fn(),
10
+ getUserSetting: vi.fn(),
6
11
  listAppSecretsForScope: vi.fn(),
7
12
  readAppSecret: vi.fn(),
13
+ recordAudit: vi.fn(),
8
14
  writeAppSecret: vi.fn(),
9
15
  }));
10
16
 
@@ -15,6 +21,23 @@ vi.mock("@agent-native/core/secrets", () => ({
15
21
  writeAppSecret: mocks.writeAppSecret,
16
22
  }));
17
23
 
24
+ vi.mock("@agent-native/core/server/agent-discovery", () => ({
25
+ discoverAgents: mocks.discoverAgents,
26
+ }));
27
+
28
+ vi.mock("@agent-native/core/settings", () => ({
29
+ getOrgSetting: mocks.getOrgSetting,
30
+ getUserSetting: mocks.getUserSetting,
31
+ putOrgSetting: vi.fn(),
32
+ putUserSetting: vi.fn(),
33
+ }));
34
+
35
+ vi.mock("./dispatch-store.js", () => ({
36
+ currentOrgId: mocks.currentOrgId,
37
+ currentOwnerEmail: mocks.currentOwnerEmail,
38
+ recordAudit: mocks.recordAudit,
39
+ }));
40
+
18
41
  vi.mock("../../db/index.js", async (importOriginal) => {
19
42
  const actual = await importOriginal<typeof import("../../db/index.js")>();
20
43
  return {
@@ -30,6 +53,7 @@ import {
30
53
  credentialStoreScopeForVaultCtx,
31
54
  isTrustedEnvVarSyncAgentUrl,
32
55
  resyncAllVaultSecretsToCredentialStore,
56
+ syncGrantsToApp,
33
57
  syncSecretsToCredentialStore,
34
58
  } from "./vault-store.js";
35
59
 
@@ -319,3 +343,138 @@ describe("resyncAllVaultSecretsToCredentialStore", () => {
319
343
  warnSpy.mockRestore();
320
344
  });
321
345
  });
346
+
347
+ describe("syncGrantsToApp", () => {
348
+ /** In-memory stand-in for app_secrets, keyed scope + scopeId + key. */
349
+ function fakeCredentialStore() {
350
+ const store = new Map<string, string>();
351
+ mocks.writeAppSecret.mockImplementation(async (args: any) => {
352
+ store.set(`${args.scope}:${args.scopeId}:${args.key}`, args.value);
353
+ return "app-secret-id";
354
+ });
355
+ return store;
356
+ }
357
+
358
+ /**
359
+ * `all-apps` vault access, one discovered app, and a caller whose active org
360
+ * is deliberately NOT the org that owns the secrets. The app URL is remote so
361
+ * the best-effort env-var push is skipped and no network call is attempted.
362
+ */
363
+ function mockWorkspace(
364
+ caller: { ownerEmail: string; orgId: string | null },
365
+ secretRows: Array<Record<string, unknown>>,
366
+ ) {
367
+ mocks.currentOwnerEmail.mockReturnValue(caller.ownerEmail);
368
+ mocks.currentOrgId.mockReturnValue(caller.orgId);
369
+ mocks.getOrgSetting.mockResolvedValue(null);
370
+ mocks.getUserSetting.mockResolvedValue(null);
371
+ mocks.discoverAgents.mockResolvedValue([
372
+ { id: "coach", url: "https://coach.example.test" },
373
+ ]);
374
+ mocks.getDb.mockReturnValue({
375
+ select: () => ({
376
+ from: () => ({
377
+ where: () => ({ orderBy: async () => secretRows }),
378
+ }),
379
+ }),
380
+ insert: () => ({ values: async () => undefined }),
381
+ });
382
+ }
383
+
384
+ afterEach(() => {
385
+ mocks.writeAppSecret.mockReset();
386
+ });
387
+
388
+ // The regression: `all-apps` mode lists secrets across orgs, and syncing them
389
+ // under the caller's ctx upserts copies of another org's credentials into
390
+ // whichever org the person clicking Sync happened to be in. Because
391
+ // writeAppSecret upserts, that copies rather than moves, so credential
392
+ // material accumulates permanently in the wrong org.
393
+ it("writes each secret under its own org, not the caller's active org", async () => {
394
+ const store = fakeCredentialStore();
395
+ mockWorkspace({ ownerEmail: "clicker@example.test", orgId: "org_caller" }, [
396
+ {
397
+ id: "secret_builder",
398
+ ownerEmail: "admin@example.test",
399
+ orgId: "org_owner",
400
+ name: "Academy Site URL",
401
+ credentialKey: "ACADEMY_CONVEX_SITE_URL",
402
+ value: "https://academy.example.test",
403
+ },
404
+ {
405
+ id: "secret_solo",
406
+ ownerEmail: "owner@example.test",
407
+ orgId: null,
408
+ name: "Personal API Key",
409
+ credentialKey: "PERSONAL_API_KEY",
410
+ value: "sk-test-personal",
411
+ },
412
+ ]);
413
+
414
+ const result = await syncGrantsToApp("coach");
415
+
416
+ expect(store.get("org:org_owner:ACADEMY_CONVEX_SITE_URL")).toBe(
417
+ "https://academy.example.test",
418
+ );
419
+ expect(
420
+ store.get("workspace:solo:owner@example.test:PERSONAL_API_KEY"),
421
+ ).toBe("sk-test-personal");
422
+
423
+ // Nothing may be written under the caller's org.
424
+ const callerScoped = [...store.keys()].filter((key) =>
425
+ key.startsWith("org:org_caller:"),
426
+ );
427
+ expect(callerScoped).toEqual([]);
428
+
429
+ expect(result.credentialStores).toEqual([
430
+ { scope: "org", scopeId: "org_owner", synced: 1 },
431
+ {
432
+ scope: "workspace",
433
+ scopeId: "solo:owner@example.test",
434
+ synced: 1,
435
+ },
436
+ ]);
437
+ expect(result.synced).toBe(2);
438
+ });
439
+
440
+ it("still syncs the caller's own org secrets into that org", async () => {
441
+ const store = fakeCredentialStore();
442
+ mockWorkspace({ ownerEmail: "clicker@example.test", orgId: "org_caller" }, [
443
+ {
444
+ id: "secret_own",
445
+ ownerEmail: "clicker@example.test",
446
+ orgId: "org_caller",
447
+ name: "Shared API Key",
448
+ credentialKey: "SHARED_API_KEY",
449
+ value: "sk-test-shared",
450
+ },
451
+ ]);
452
+
453
+ const result = await syncGrantsToApp("coach");
454
+
455
+ expect(store.get("org:org_caller:SHARED_API_KEY")).toBe("sk-test-shared");
456
+ expect(result.credentialStores).toEqual([
457
+ { scope: "org", scopeId: "org_caller", synced: 1 },
458
+ ]);
459
+ });
460
+
461
+ // A row with no ownerEmail cannot name its own tenant, so the caller's ctx is
462
+ // the only scope available — the pre-existing ctxForSecretRow fallback.
463
+ it("falls back to the caller ctx for a secret row with no owner", async () => {
464
+ const store = fakeCredentialStore();
465
+ mockWorkspace({ ownerEmail: "clicker@example.test", orgId: "org_caller" }, [
466
+ {
467
+ id: "secret_ownerless",
468
+ ownerEmail: "",
469
+ orgId: null,
470
+ name: "Legacy Key",
471
+ credentialKey: "LEGACY_KEY",
472
+ value: "sk-test-legacy",
473
+ },
474
+ ]);
475
+
476
+ await syncGrantsToApp("coach");
477
+
478
+ expect(store.get("org:org_caller:LEGACY_KEY")).toBe("sk-test-legacy");
479
+ });
480
+ });
@@ -763,6 +763,33 @@ export async function syncSecretsToCredentialStore(
763
763
  return { ...target, keys: syncedKeys };
764
764
  }
765
765
 
766
+ /**
767
+ * Group secrets by the tenant their credential-store rows must land in.
768
+ *
769
+ * Every sync path must write a secret under the org that *owns the row*, not
770
+ * under whoever happens to be syncing: `writeAppSecret` upserts, so syncing
771
+ * with the caller's ctx copies credential material into the caller's org
772
+ * instead of moving it, and it accumulates there permanently.
773
+ */
774
+ function groupSecretsByTenant(
775
+ rows: VaultSecretRow[],
776
+ resolveCtx: (row: VaultSecretRow) => VaultCtx,
777
+ ): { ctx: VaultCtx; rows: VaultSecretRow[] }[] {
778
+ const groups = new Map<string, { ctx: VaultCtx; rows: VaultSecretRow[] }>();
779
+ for (const row of rows) {
780
+ if (!row.credentialKey || !row.value) continue;
781
+ const ctx = resolveCtx(row);
782
+ const groupKey = `${ctx.orgId ?? ""}\u0000${ctx.ownerEmail}`;
783
+ const group = groups.get(groupKey);
784
+ if (group) {
785
+ group.rows.push(row);
786
+ } else {
787
+ groups.set(groupKey, { ctx, rows: [row] });
788
+ }
789
+ }
790
+ return [...groups.values()];
791
+ }
792
+
766
793
  /**
767
794
  * Re-sync every vault secret across every tenant into the shared credential
768
795
  * store, regardless of which request/ctx is currently active.
@@ -788,23 +815,12 @@ export async function resyncAllVaultSecretsToCredentialStore(): Promise<{
788
815
  const db = getDb();
789
816
  const rows = await db.select().from(schema.vaultSecrets);
790
817
 
791
- const groups = new Map<string, { ctx: VaultCtx; rows: VaultSecretRow[] }>();
792
- for (const row of rows) {
793
- if (!row.credentialKey || !row.value) continue;
794
- const ctx: VaultCtx = { ownerEmail: row.ownerEmail, orgId: row.orgId };
795
- const groupKey = `${ctx.orgId ?? ""}${ctx.ownerEmail}`;
796
- const group = groups.get(groupKey);
797
- if (group) {
798
- group.rows.push(row);
799
- } else {
800
- groups.set(groupKey, { ctx, rows: [row] });
801
- }
802
- }
818
+ const groups = groupSecretsByTenant(rows, ctxForRow);
803
819
 
804
820
  let failedGroups = 0;
805
821
  let syncedKeys = 0;
806
822
 
807
- for (const { ctx, rows: groupRows } of groups.values()) {
823
+ for (const { ctx, rows: groupRows } of groups) {
808
824
  try {
809
825
  const result = await syncSecretsToCredentialStore(groupRows, ctx);
810
826
  syncedKeys += result.keys.length;
@@ -818,7 +834,7 @@ export async function resyncAllVaultSecretsToCredentialStore(): Promise<{
818
834
  }
819
835
  }
820
836
 
821
- return { groups: groups.size, failedGroups, syncedKeys };
837
+ return { groups: groups.length, failedGroups, syncedKeys };
822
838
  }
823
839
 
824
840
  export async function cleanupSyncedCredentialKeysIfUnused(
@@ -889,13 +905,37 @@ export async function syncGrantsToApp(
889
905
  }
890
906
 
891
907
  if (secretsToSync.length === 0) {
892
- return { appId, accessMode: access.mode, synced: 0, keys: [] };
908
+ return {
909
+ appId,
910
+ accessMode: access.mode,
911
+ synced: 0,
912
+ keys: [],
913
+ credentialStores: [],
914
+ };
893
915
  }
894
916
 
895
- const credentialStoreSync = await syncSecretsToCredentialStore(
896
- secretsToSync,
897
- ctx,
917
+ // `all-apps` mode lists secrets across every org the caller can see, so each
918
+ // row must be written back under its own org. Syncing them all under `ctx`
919
+ // upserts copies of other orgs' credentials into the caller's org.
920
+ const credentialStoreGroups = groupSecretsByTenant(secretsToSync, (row) =>
921
+ ctxForSecretRow(row, ctx),
898
922
  );
923
+ const credentialStores: {
924
+ scope: ReturnType<typeof credentialStoreScopeForVaultCtx>["scope"];
925
+ scopeId: string;
926
+ synced: number;
927
+ }[] = [];
928
+ const credentialStoreKeys: string[] = [];
929
+ for (const group of credentialStoreGroups) {
930
+ const result = await syncSecretsToCredentialStore(group.rows, group.ctx);
931
+ credentialStores.push({
932
+ scope: result.scope,
933
+ scopeId: result.scopeId,
934
+ synced: result.keys.length,
935
+ });
936
+ credentialStoreKeys.push(...result.keys);
937
+ }
938
+
899
939
  const vars = secretsToSync.map((secret) => ({
900
940
  key: secret.credentialKey,
901
941
  value: secret.value,
@@ -942,7 +982,7 @@ export async function syncGrantsToApp(
942
982
  }
943
983
  }
944
984
 
945
- const syncedKeys = credentialStoreSync.keys;
985
+ const syncedKeys = credentialStoreKeys;
946
986
  const timestamp = now();
947
987
 
948
988
  // Update syncedAt on grants that were successfully pushed to the shared
@@ -964,10 +1004,7 @@ export async function syncGrantsToApp(
964
1004
  metadata: {
965
1005
  syncedKeys,
966
1006
  accessMode: access.mode,
967
- credentialStore: {
968
- scope: credentialStoreSync.scope,
969
- scopeId: credentialStoreSync.scopeId,
970
- },
1007
+ credentialStores,
971
1008
  envVars: envVarSync,
972
1009
  },
973
1010
  });
@@ -977,11 +1014,7 @@ export async function syncGrantsToApp(
977
1014
  accessMode: access.mode,
978
1015
  synced: syncedKeys.length,
979
1016
  keys: syncedKeys,
980
- credentialStore: {
981
- scope: credentialStoreSync.scope,
982
- scopeId: credentialStoreSync.scopeId,
983
- synced: credentialStoreSync.keys.length,
984
- },
1017
+ credentialStores,
985
1018
  envVars: envVarSync,
986
1019
  };
987
1020
  }
@@ -1,2 +0,0 @@
1
- export declare function rootDispatchRedirect(pathname: string, search: string): Response | null;
2
- //# sourceMappingURL=pre-auth-routing.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pre-auth-routing.d.ts","sourceRoot":"","sources":["../../../src/server/lib/pre-auth-routing.ts"],"names":[],"mappings":"AAoFA,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,QAAQ,GAAG,IAAI,CAuEjB"}
@@ -1,136 +0,0 @@
1
- function normalizeBasePath(value) {
2
- if (!value || value === "/")
3
- return "";
4
- const trimmed = value.trim();
5
- if (!trimmed || trimmed === "/")
6
- return "";
7
- return `/${trimmed.replace(/^\/+/, "").replace(/\/+$/, "")}`;
8
- }
9
- function normalizePathname(value) {
10
- if (value === "/")
11
- return "/";
12
- return value.replace(/\/+$/, "") || "/";
13
- }
14
- const DISPATCH_PAGE_PATHS = new Set([
15
- "/overview",
16
- "/metrics",
17
- "/login",
18
- "/signup",
19
- "/apps",
20
- "/new-app",
21
- "/vault",
22
- "/integrations",
23
- "/agents",
24
- "/workspace",
25
- "/tools",
26
- "/messaging",
27
- "/destinations",
28
- "/identities",
29
- "/approvals",
30
- "/automations",
31
- "/audit",
32
- "/team",
33
- ]);
34
- const DISPATCH_ROOT_ALIASES = new Map([
35
- ...Array.from(DISPATCH_PAGE_PATHS, (path) => [path, path]),
36
- ["/approval", "/approval"],
37
- ["/extensions", "/extensions"],
38
- ["/tools", "/tools"],
39
- ["/apps/new-app", "/new-app"],
40
- ]);
41
- const MOUNTED_DISPATCH_ALIASES = new Map([
42
- ["/apps/new-app", "/new-app"],
43
- ]);
44
- function isDispatchPagePath(pathname) {
45
- if (DISPATCH_PAGE_PATHS.has(pathname))
46
- return true;
47
- if (pathname === "/approval" || pathname === "/extensions")
48
- return true;
49
- if (pathname === "/tools")
50
- return true;
51
- return (/^\/extensions\/[^/]+$/.test(pathname) ||
52
- /^\/tools\/[^/]+$/.test(pathname) ||
53
- /^\/apps\/[^/]+$/.test(pathname));
54
- }
55
- function isDispatchAssetOrFrameworkPath(pathname) {
56
- return (pathname === "/__manifest" ||
57
- pathname.startsWith("/__manifest/") ||
58
- pathname === "/_agent-native" ||
59
- pathname.startsWith("/_agent-native/") ||
60
- pathname === "/.well-known" ||
61
- pathname.startsWith("/.well-known/") ||
62
- pathname.startsWith("/assets/") ||
63
- pathname.startsWith("/_build/") ||
64
- pathname.endsWith(".js") ||
65
- pathname.endsWith(".css") ||
66
- pathname.endsWith(".map") ||
67
- pathname.endsWith(".ico") ||
68
- pathname.endsWith(".png") ||
69
- pathname.endsWith(".svg") ||
70
- pathname.endsWith(".woff2") ||
71
- pathname.endsWith(".woff"));
72
- }
73
- function dispatchNotFoundResponse() {
74
- return new Response("Dispatch route not found", {
75
- status: 404,
76
- headers: { "content-type": "text/plain; charset=utf-8" },
77
- });
78
- }
79
- export function rootDispatchRedirect(pathname, search) {
80
- const normalizedPathname = normalizePathname(pathname);
81
- const basePath = normalizeBasePath(process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH);
82
- if (!basePath)
83
- return null;
84
- if (normalizedPathname === "/__manifest" ||
85
- normalizedPathname.startsWith("/__manifest/") ||
86
- normalizedPathname === "/_agent-native" ||
87
- normalizedPathname.startsWith("/_agent-native/")) {
88
- return null;
89
- }
90
- if (normalizedPathname === "/.well-known" ||
91
- normalizedPathname.startsWith("/.well-known/")) {
92
- return null;
93
- }
94
- if (normalizedPathname === "/") {
95
- return new Response(null, {
96
- status: 302,
97
- headers: { Location: `${basePath}/overview${search}` },
98
- });
99
- }
100
- if (normalizedPathname === basePath) {
101
- return new Response(null, {
102
- status: 302,
103
- headers: { Location: `${basePath}/overview${search}` },
104
- });
105
- }
106
- if (normalizedPathname.startsWith(`${basePath}/`)) {
107
- const dispatchPath = normalizedPathname.slice(basePath.length);
108
- const mountedAlias = MOUNTED_DISPATCH_ALIASES.get(dispatchPath);
109
- if (mountedAlias) {
110
- return new Response(null, {
111
- status: 302,
112
- headers: { Location: `${basePath}${mountedAlias}${search}` },
113
- });
114
- }
115
- if (isDispatchPagePath(dispatchPath) ||
116
- isDispatchAssetOrFrameworkPath(dispatchPath)) {
117
- return null;
118
- }
119
- return dispatchNotFoundResponse();
120
- }
121
- const rootAlias = DISPATCH_ROOT_ALIASES.get(normalizedPathname);
122
- if (rootAlias) {
123
- return new Response(null, {
124
- status: 302,
125
- headers: { Location: `${basePath}${rootAlias}${search}` },
126
- });
127
- }
128
- if (/^\/apps\/[^/]+$/.test(normalizedPathname)) {
129
- return new Response(null, {
130
- status: 302,
131
- headers: { Location: `${basePath}${normalizedPathname}${search}` },
132
- });
133
- }
134
- return dispatchNotFoundResponse();
135
- }
136
- //# sourceMappingURL=pre-auth-routing.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pre-auth-routing.js","sourceRoot":"","sources":["../../../src/server/lib/pre-auth-routing.ts"],"names":[],"mappings":"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","sourcesContent":["function normalizeBasePath(value?: string): string {\n if (!value || value === \"/\") return \"\";\n const trimmed = value.trim();\n if (!trimmed || trimmed === \"/\") return \"\";\n return `/${trimmed.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\")}`;\n}\n\nfunction normalizePathname(value: string): string {\n if (value === \"/\") return \"/\";\n return value.replace(/\\/+$/, \"\") || \"/\";\n}\n\nconst DISPATCH_PAGE_PATHS = new Set([\n \"/overview\",\n \"/metrics\",\n \"/login\",\n \"/signup\",\n \"/apps\",\n \"/new-app\",\n \"/vault\",\n \"/integrations\",\n \"/agents\",\n \"/workspace\",\n \"/tools\",\n \"/messaging\",\n \"/destinations\",\n \"/identities\",\n \"/approvals\",\n \"/automations\",\n \"/audit\",\n \"/team\",\n]);\n\nconst DISPATCH_ROOT_ALIASES = new Map<string, string>([\n ...Array.from(DISPATCH_PAGE_PATHS, (path) => [path, path] as const),\n [\"/approval\", \"/approval\"],\n [\"/extensions\", \"/extensions\"],\n [\"/tools\", \"/tools\"],\n [\"/apps/new-app\", \"/new-app\"],\n]);\n\nconst MOUNTED_DISPATCH_ALIASES = new Map<string, string>([\n [\"/apps/new-app\", \"/new-app\"],\n]);\n\nfunction isDispatchPagePath(pathname: string): boolean {\n if (DISPATCH_PAGE_PATHS.has(pathname)) return true;\n if (pathname === \"/approval\" || pathname === \"/extensions\") return true;\n if (pathname === \"/tools\") return true;\n return (\n /^\\/extensions\\/[^/]+$/.test(pathname) ||\n /^\\/tools\\/[^/]+$/.test(pathname) ||\n /^\\/apps\\/[^/]+$/.test(pathname)\n );\n}\n\nfunction isDispatchAssetOrFrameworkPath(pathname: string): boolean {\n return (\n pathname === \"/__manifest\" ||\n pathname.startsWith(\"/__manifest/\") ||\n pathname === \"/_agent-native\" ||\n pathname.startsWith(\"/_agent-native/\") ||\n pathname === \"/.well-known\" ||\n pathname.startsWith(\"/.well-known/\") ||\n pathname.startsWith(\"/assets/\") ||\n pathname.startsWith(\"/_build/\") ||\n pathname.endsWith(\".js\") ||\n pathname.endsWith(\".css\") ||\n pathname.endsWith(\".map\") ||\n pathname.endsWith(\".ico\") ||\n pathname.endsWith(\".png\") ||\n pathname.endsWith(\".svg\") ||\n pathname.endsWith(\".woff2\") ||\n pathname.endsWith(\".woff\")\n );\n}\n\nfunction dispatchNotFoundResponse(): Response {\n return new Response(\"Dispatch route not found\", {\n status: 404,\n headers: { \"content-type\": \"text/plain; charset=utf-8\" },\n });\n}\n\nexport function rootDispatchRedirect(\n pathname: string,\n search: string,\n): Response | null {\n const normalizedPathname = normalizePathname(pathname);\n const basePath = normalizeBasePath(\n process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH,\n );\n if (!basePath) return null;\n\n if (\n normalizedPathname === \"/__manifest\" ||\n normalizedPathname.startsWith(\"/__manifest/\") ||\n normalizedPathname === \"/_agent-native\" ||\n normalizedPathname.startsWith(\"/_agent-native/\")\n ) {\n return null;\n }\n\n if (\n normalizedPathname === \"/.well-known\" ||\n normalizedPathname.startsWith(\"/.well-known/\")\n ) {\n return null;\n }\n\n if (normalizedPathname === \"/\") {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}/overview${search}` },\n });\n }\n\n if (normalizedPathname === basePath) {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}/overview${search}` },\n });\n }\n\n if (normalizedPathname.startsWith(`${basePath}/`)) {\n const dispatchPath = normalizedPathname.slice(basePath.length);\n const mountedAlias = MOUNTED_DISPATCH_ALIASES.get(dispatchPath);\n if (mountedAlias) {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}${mountedAlias}${search}` },\n });\n }\n if (\n isDispatchPagePath(dispatchPath) ||\n isDispatchAssetOrFrameworkPath(dispatchPath)\n ) {\n return null;\n }\n return dispatchNotFoundResponse();\n }\n\n const rootAlias = DISPATCH_ROOT_ALIASES.get(normalizedPathname);\n if (rootAlias) {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}${rootAlias}${search}` },\n });\n }\n\n if (/^\\/apps\\/[^/]+$/.test(normalizedPathname)) {\n return new Response(null, {\n status: 302,\n headers: { Location: `${basePath}${normalizedPathname}${search}` },\n });\n }\n\n return dispatchNotFoundResponse();\n}\n"]}
@@ -1,159 +0,0 @@
1
- function normalizeBasePath(value?: string): string {
2
- if (!value || value === "/") return "";
3
- const trimmed = value.trim();
4
- if (!trimmed || trimmed === "/") return "";
5
- return `/${trimmed.replace(/^\/+/, "").replace(/\/+$/, "")}`;
6
- }
7
-
8
- function normalizePathname(value: string): string {
9
- if (value === "/") return "/";
10
- return value.replace(/\/+$/, "") || "/";
11
- }
12
-
13
- const DISPATCH_PAGE_PATHS = new Set([
14
- "/overview",
15
- "/metrics",
16
- "/login",
17
- "/signup",
18
- "/apps",
19
- "/new-app",
20
- "/vault",
21
- "/integrations",
22
- "/agents",
23
- "/workspace",
24
- "/tools",
25
- "/messaging",
26
- "/destinations",
27
- "/identities",
28
- "/approvals",
29
- "/automations",
30
- "/audit",
31
- "/team",
32
- ]);
33
-
34
- const DISPATCH_ROOT_ALIASES = new Map<string, string>([
35
- ...Array.from(DISPATCH_PAGE_PATHS, (path) => [path, path] as const),
36
- ["/approval", "/approval"],
37
- ["/extensions", "/extensions"],
38
- ["/tools", "/tools"],
39
- ["/apps/new-app", "/new-app"],
40
- ]);
41
-
42
- const MOUNTED_DISPATCH_ALIASES = new Map<string, string>([
43
- ["/apps/new-app", "/new-app"],
44
- ]);
45
-
46
- function isDispatchPagePath(pathname: string): boolean {
47
- if (DISPATCH_PAGE_PATHS.has(pathname)) return true;
48
- if (pathname === "/approval" || pathname === "/extensions") return true;
49
- if (pathname === "/tools") return true;
50
- return (
51
- /^\/extensions\/[^/]+$/.test(pathname) ||
52
- /^\/tools\/[^/]+$/.test(pathname) ||
53
- /^\/apps\/[^/]+$/.test(pathname)
54
- );
55
- }
56
-
57
- function isDispatchAssetOrFrameworkPath(pathname: string): boolean {
58
- return (
59
- pathname === "/__manifest" ||
60
- pathname.startsWith("/__manifest/") ||
61
- pathname === "/_agent-native" ||
62
- pathname.startsWith("/_agent-native/") ||
63
- pathname === "/.well-known" ||
64
- pathname.startsWith("/.well-known/") ||
65
- pathname.startsWith("/assets/") ||
66
- pathname.startsWith("/_build/") ||
67
- pathname.endsWith(".js") ||
68
- pathname.endsWith(".css") ||
69
- pathname.endsWith(".map") ||
70
- pathname.endsWith(".ico") ||
71
- pathname.endsWith(".png") ||
72
- pathname.endsWith(".svg") ||
73
- pathname.endsWith(".woff2") ||
74
- pathname.endsWith(".woff")
75
- );
76
- }
77
-
78
- function dispatchNotFoundResponse(): Response {
79
- return new Response("Dispatch route not found", {
80
- status: 404,
81
- headers: { "content-type": "text/plain; charset=utf-8" },
82
- });
83
- }
84
-
85
- export function rootDispatchRedirect(
86
- pathname: string,
87
- search: string,
88
- ): Response | null {
89
- const normalizedPathname = normalizePathname(pathname);
90
- const basePath = normalizeBasePath(
91
- process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH,
92
- );
93
- if (!basePath) return null;
94
-
95
- if (
96
- normalizedPathname === "/__manifest" ||
97
- normalizedPathname.startsWith("/__manifest/") ||
98
- normalizedPathname === "/_agent-native" ||
99
- normalizedPathname.startsWith("/_agent-native/")
100
- ) {
101
- return null;
102
- }
103
-
104
- if (
105
- normalizedPathname === "/.well-known" ||
106
- normalizedPathname.startsWith("/.well-known/")
107
- ) {
108
- return null;
109
- }
110
-
111
- if (normalizedPathname === "/") {
112
- return new Response(null, {
113
- status: 302,
114
- headers: { Location: `${basePath}/overview${search}` },
115
- });
116
- }
117
-
118
- if (normalizedPathname === basePath) {
119
- return new Response(null, {
120
- status: 302,
121
- headers: { Location: `${basePath}/overview${search}` },
122
- });
123
- }
124
-
125
- if (normalizedPathname.startsWith(`${basePath}/`)) {
126
- const dispatchPath = normalizedPathname.slice(basePath.length);
127
- const mountedAlias = MOUNTED_DISPATCH_ALIASES.get(dispatchPath);
128
- if (mountedAlias) {
129
- return new Response(null, {
130
- status: 302,
131
- headers: { Location: `${basePath}${mountedAlias}${search}` },
132
- });
133
- }
134
- if (
135
- isDispatchPagePath(dispatchPath) ||
136
- isDispatchAssetOrFrameworkPath(dispatchPath)
137
- ) {
138
- return null;
139
- }
140
- return dispatchNotFoundResponse();
141
- }
142
-
143
- const rootAlias = DISPATCH_ROOT_ALIASES.get(normalizedPathname);
144
- if (rootAlias) {
145
- return new Response(null, {
146
- status: 302,
147
- headers: { Location: `${basePath}${rootAlias}${search}` },
148
- });
149
- }
150
-
151
- if (/^\/apps\/[^/]+$/.test(normalizedPathname)) {
152
- return new Response(null, {
153
- status: 302,
154
- headers: { Location: `${basePath}${normalizedPathname}${search}` },
155
- });
156
- }
157
-
158
- return dispatchNotFoundResponse();
159
- }