@agent-native/core 0.84.28 → 0.84.30

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.
Files changed (39) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/chat/tool-call-display.tsx +50 -8
  5. package/corpus/core/src/client/sse-event-processor.ts +17 -4
  6. package/corpus/core/src/deploy/build.ts +5 -0
  7. package/corpus/core/src/server/analytics.ts +29 -7
  8. package/corpus/core/src/server/ssr-handler.ts +305 -25
  9. package/corpus/core/src/vite/client.ts +3 -0
  10. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +324 -24
  11. package/corpus/templates/analytics/app/i18n/zh-TW.ts +11 -0
  12. package/corpus/templates/analytics/app/i18n-data.ts +110 -0
  13. package/corpus/templates/design/app/pages/DesignEditor.tsx +45 -2
  14. package/corpus/templates/design/changelog/2026-07-01-design-previews-refresh-immediately-after-agent-screen-edits.md +6 -0
  15. package/dist/client/chat/tool-call-display.d.ts +1 -0
  16. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  17. package/dist/client/chat/tool-call-display.js +22 -5
  18. package/dist/client/chat/tool-call-display.js.map +1 -1
  19. package/dist/client/sse-event-processor.d.ts.map +1 -1
  20. package/dist/client/sse-event-processor.js +13 -4
  21. package/dist/client/sse-event-processor.js.map +1 -1
  22. package/dist/collab/routes.d.ts +1 -1
  23. package/dist/deploy/build.js +3 -0
  24. package/dist/deploy/build.js.map +1 -1
  25. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  26. package/dist/notifications/routes.d.ts +2 -2
  27. package/dist/observability/routes.d.ts +8 -8
  28. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  29. package/dist/server/analytics.d.ts +10 -2
  30. package/dist/server/analytics.d.ts.map +1 -1
  31. package/dist/server/analytics.js +26 -5
  32. package/dist/server/analytics.js.map +1 -1
  33. package/dist/server/ssr-handler.d.ts.map +1 -1
  34. package/dist/server/ssr-handler.js +206 -21
  35. package/dist/server/ssr-handler.js.map +1 -1
  36. package/dist/vite/client.d.ts.map +1 -1
  37. package/dist/vite/client.js +1 -0
  38. package/dist/vite/client.js.map +1 -1
  39. package/package.json +1 -1
@@ -3,18 +3,18 @@ declare const _default: import("../../action.js").ActionDefinition<{
3
3
  url?: string;
4
4
  filename?: string;
5
5
  }, {
6
+ id?: undefined;
6
7
  error: string;
7
8
  configured?: undefined;
8
9
  connectPath?: undefined;
9
10
  url?: undefined;
10
- id?: undefined;
11
11
  provider?: undefined;
12
12
  } | {
13
+ id?: undefined;
13
14
  error: string;
14
15
  configured: boolean;
15
16
  connectPath: string;
16
17
  url?: undefined;
17
- id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
20
  error?: undefined;
@@ -13,13 +13,13 @@
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
14
  count: number;
15
15
  updated?: undefined;
16
- ok?: undefined;
17
16
  error?: undefined;
17
+ ok?: undefined;
18
18
  } | {
19
19
  count?: undefined;
20
20
  updated: number;
21
- ok?: undefined;
22
21
  error?: undefined;
22
+ ok?: undefined;
23
23
  } | {
24
24
  count?: undefined;
25
25
  updated?: undefined;
@@ -41,28 +41,28 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- ok?: undefined;
45
- error?: undefined;
46
44
  summary: import("./types.js").TraceSummary;
47
45
  spans: import("./types.js").TraceSpan[];
48
46
  id?: undefined;
49
- } | {
50
- ok?: undefined;
51
47
  error?: undefined;
48
+ ok?: undefined;
49
+ } | {
52
50
  summary?: undefined;
53
51
  spans?: undefined;
54
52
  id: string;
55
- } | {
53
+ error?: undefined;
56
54
  ok?: undefined;
55
+ } | {
57
56
  summary?: undefined;
58
57
  spans?: undefined;
59
- error: any;
60
58
  id?: undefined;
59
+ error: any;
60
+ ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
- ok: boolean;
66
64
  id?: undefined;
65
+ error?: undefined;
66
+ ok: boolean;
67
67
  }>>;
68
68
  //# sourceMappingURL=routes.d.ts.map
@@ -27,11 +27,11 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
27
27
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
28
28
  error: any;
29
29
  } | {
30
- error?: undefined;
31
30
  ok: boolean;
32
31
  key: string;
33
32
  baseUrlKey?: string;
34
33
  scope: AgentEngineApiKeyScope;
34
+ error?: undefined;
35
35
  }>>;
36
36
  export {};
37
37
  //# sourceMappingURL=agent-engine-api-key-route.d.ts.map
@@ -4,8 +4,8 @@
4
4
  * - `GA_MEASUREMENT_ID` — Google Analytics 4 measurement ID
5
5
  *
6
6
  * Netlify configuration-file env vars are build-time only for serverless
7
- * functions, so the Vite plugin also bakes this public value into SSR bundles
8
- * as `__AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__`.
7
+ * functions, so the Vite/Nitro build paths also bake this public value into
8
+ * SSR bundles.
9
9
  *
10
10
  * Amplitude and Sentry are initialized client-side via their npm packages
11
11
  * (see `packages/core/src/client/analytics.ts`). Only GA requires script
@@ -28,6 +28,14 @@
28
28
  * `applyDocumentCsp` in `ssr-handler.ts`).
29
29
  */
30
30
  export declare const GA_CSP_SCRIPT_HOSTS: readonly ["https://www.googletagmanager.com", "https://www.google-analytics.com"];
31
+ /**
32
+ * Network/image hosts used by the GA4 loader when it sends page-view and event
33
+ * beacons. These are separate from `script-src`: a stricter deployment CSP with
34
+ * `connect-src 'self'` or `img-src 'self'` can load gtag.js but still drop all
35
+ * analytics events unless these hosts are present too.
36
+ */
37
+ export declare const GA_CSP_CONNECT_HOSTS: readonly ["https://www.google-analytics.com", "https://analytics.google.com", "https://stats.g.doubleclick.net", "https://region1.google-analytics.com"];
38
+ export declare const GA_CSP_IMG_HOSTS: readonly ["https://www.google-analytics.com", "https://www.googletagmanager.com", "https://stats.g.doubleclick.net"];
31
39
  /**
32
40
  * The exact JS body (no surrounding `<script>` tags) of the inline gtag config
33
41
  * block injected next to the gtag.js loader. Returned so the SSR handler can
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/server/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAoBH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,YAC9B,kCAAkC,EAClC,kCAAkC,CAC1B,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,IAAI,CAc3D;AAaD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,CA4BtE"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/server/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAuBH;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,YAC9B,kCAAkC,EAClC,kCAAkC,CAC1B,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,YAC/B,kCAAkC,EAClC,8BAA8B,EAC9B,iCAAiC,EACjC,sCAAsC,CAC9B,CAAC;AAEX,eAAO,MAAM,gBAAgB,YAC3B,kCAAkC,EAClC,kCAAkC,EAClC,iCAAiC,CACzB,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,IAAI,CAc3D;AAaD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,CA4BtE"}
@@ -4,8 +4,8 @@
4
4
  * - `GA_MEASUREMENT_ID` — Google Analytics 4 measurement ID
5
5
  *
6
6
  * Netlify configuration-file env vars are build-time only for serverless
7
- * functions, so the Vite plugin also bakes this public value into SSR bundles
8
- * as `__AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__`.
7
+ * functions, so the Vite/Nitro build paths also bake this public value into
8
+ * SSR bundles.
9
9
  *
10
10
  * Amplitude and Sentry are initialized client-side via their npm packages
11
11
  * (see `packages/core/src/client/analytics.ts`). Only GA requires script
@@ -24,11 +24,15 @@ function normalizeMeasurementId(value) {
24
24
  const trimmed = value?.trim();
25
25
  return trimmed ? trimmed : null;
26
26
  }
27
+ function getViteBakedGaMeasurementId() {
28
+ return typeof __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__ === "string"
29
+ ? __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__
30
+ : undefined;
31
+ }
27
32
  function getGaMeasurementId() {
28
33
  return (normalizeMeasurementId(process.env.GA_MEASUREMENT_ID) ||
29
- normalizeMeasurementId(typeof __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__ === "string"
30
- ? __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__
31
- : undefined));
34
+ normalizeMeasurementId(process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID) ||
35
+ normalizeMeasurementId(getViteBakedGaMeasurementId()));
32
36
  }
33
37
  /**
34
38
  * Script hosts the injected GA loader pulls executable code from. Google Tag
@@ -41,6 +45,23 @@ export const GA_CSP_SCRIPT_HOSTS = [
41
45
  "https://www.googletagmanager.com",
42
46
  "https://www.google-analytics.com",
43
47
  ];
48
+ /**
49
+ * Network/image hosts used by the GA4 loader when it sends page-view and event
50
+ * beacons. These are separate from `script-src`: a stricter deployment CSP with
51
+ * `connect-src 'self'` or `img-src 'self'` can load gtag.js but still drop all
52
+ * analytics events unless these hosts are present too.
53
+ */
54
+ export const GA_CSP_CONNECT_HOSTS = [
55
+ "https://www.google-analytics.com",
56
+ "https://analytics.google.com",
57
+ "https://stats.g.doubleclick.net",
58
+ "https://region1.google-analytics.com",
59
+ ];
60
+ export const GA_CSP_IMG_HOSTS = [
61
+ "https://www.google-analytics.com",
62
+ "https://www.googletagmanager.com",
63
+ "https://stats.g.doubleclick.net",
64
+ ];
44
65
  /**
45
66
  * The exact JS body (no surrounding `<script>` tags) of the inline gtag config
46
67
  * block injected next to the gtag.js loader. Returned so the SSR handler can
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/server/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,SAAS,sBAAsB,CAAC,KAAyB;IACvD,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9B,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAClC,CAAC;AAED,SAAS,kBAAkB;IACzB,OAAO,CACL,sBAAsB,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;QACrD,sBAAsB,CACpB,OAAO,wCAAwC,KAAK,QAAQ;YAC1D,CAAC,CAAC,wCAAwC;YAC1C,CAAC,CAAC,SAAS,CACd,CACF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,kCAAkC;IAClC,kCAAkC;CAC1B,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B;IACzC,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;IAChC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACrB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAChC,OAAO,CACL,wCAAwC;QACxC,6CAA6C;QAC7C,wBAAwB;QACxB,iBAAiB,IAAI,IAAI;QACzB,iFAAiF;QACjF,2CAA2C;QAC3C,0BAA0B;QAC1B,GAAG,CACJ,CAAC;AACJ,CAAC;AAED,SAAS,WAAW;IAClB,MAAM,EAAE,GAAG,kBAAkB,EAAE,CAAC;IAChC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACrB,MAAM,KAAK,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,2BAA2B,EAAE,CAAC;IACjD,OAAO,CACL,kEAAkE,KAAK,aAAa;QACpF,WAAW,UAAU,WAAW,CACjC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAoB;IACpD,MAAM,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,OAAO,IAAI,CAAC,WAAW,CACrB,IAAI,eAAe,CAAC;QAClB,SAAS,CAAC,KAAK,EAAE,UAAU;YACzB,IAAI,QAAQ,EAAE,CAAC;gBACb,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBAC1B,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC7C,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBACxB,MAAM,QAAQ,GACZ,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;gBACnE,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7C,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;KACF,CAAC,CACH,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Opt-in analytics injection for SSR streams.\n * Supported environment variables:\n * - `GA_MEASUREMENT_ID` — Google Analytics 4 measurement ID\n *\n * Netlify configuration-file env vars are build-time only for serverless\n * functions, so the Vite plugin also bakes this public value into SSR bundles\n * as `__AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__`.\n *\n * Amplitude and Sentry are initialized client-side via their npm packages\n * (see `packages/core/src/client/analytics.ts`). Only GA requires script\n * tag injection because the gtag.js loader must be a `<script src>`.\n *\n * When set, the corresponding script tags are injected before `</head>`.\n * When not set, the stream passes through untouched (zero overhead).\n *\n * Usage in entry.server.tsx:\n * ```ts\n * import { wrapWithAnalytics } from \"@agent-native/core/server\";\n * return new Response(wrapWithAnalytics(body), { ... });\n * ```\n */\n\ndeclare const __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__: string | undefined;\n\nfunction normalizeMeasurementId(value: string | undefined): string | null {\n const trimmed = value?.trim();\n return trimmed ? trimmed : null;\n}\n\nfunction getGaMeasurementId(): string | null {\n return (\n normalizeMeasurementId(process.env.GA_MEASUREMENT_ID) ||\n normalizeMeasurementId(\n typeof __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__ === \"string\"\n ? __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__\n : undefined,\n )\n );\n}\n\n/**\n * Script hosts the injected GA loader pulls executable code from. Google Tag\n * Manager serves `gtag/js`, and GA4 can lazy-load additional collectors from\n * `www.google-analytics.com`. These must be listed in the document `script-src`\n * so the CSP reflects the code the framework itself injects (see\n * `applyDocumentCsp` in `ssr-handler.ts`).\n */\nexport const GA_CSP_SCRIPT_HOSTS = [\n \"https://www.googletagmanager.com\",\n \"https://www.google-analytics.com\",\n] as const;\n\n/**\n * The exact JS body (no surrounding `<script>` tags) of the inline gtag config\n * block injected next to the gtag.js loader. Returned so the SSR handler can\n * hash it for the `script-src` CSP directive — the hash must be computed from\n * the identical string that `getGaScript()` embeds, so both call this helper.\n * Returns `null` when GA is not configured.\n */\nexport function getGaInlineConfigScriptBody(): string | null {\n const id = getGaMeasurementId();\n if (!id) return null;\n const jsId = JSON.stringify(id);\n return (\n `window.dataLayer=window.dataLayer||[];` +\n `function gtag(){dataLayer.push(arguments);}` +\n `gtag('js',new Date());` +\n `gtag('config',${jsId});` +\n `if(typeof sessionStorage!=='undefined'&&sessionStorage.getItem('__an_signin')){` +\n `sessionStorage.removeItem('__an_signin');` +\n `gtag('event','sign_in');` +\n `}`\n );\n}\n\nfunction getGaScript(): string | null {\n const id = getGaMeasurementId();\n if (!id) return null;\n const srcId = encodeURIComponent(id);\n const inlineBody = getGaInlineConfigScriptBody();\n return (\n `<script async src=\"https://www.googletagmanager.com/gtag/js?id=${srcId}\"></script>` +\n `<script>${inlineBody}</script>`\n );\n}\n\nexport function wrapWithAnalytics(body: ReadableStream): ReadableStream {\n const scripts = [getGaScript()].filter(Boolean).join(\"\");\n if (!scripts) return body;\n\n const decoder = new TextDecoder();\n const encoder = new TextEncoder();\n let injected = false;\n\n return body.pipeThrough(\n new TransformStream({\n transform(chunk, controller) {\n if (injected) {\n controller.enqueue(chunk);\n return;\n }\n const text = decoder.decode(chunk, { stream: true });\n const headCloseIdx = text.indexOf(\"</head>\");\n if (headCloseIdx !== -1) {\n const modified =\n text.slice(0, headCloseIdx) + scripts + text.slice(headCloseIdx);\n controller.enqueue(encoder.encode(modified));\n injected = true;\n } else {\n controller.enqueue(chunk);\n }\n },\n }),\n );\n}\n"]}
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/server/analytics.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Opt-in analytics injection for SSR streams.\n * Supported environment variables:\n * - `GA_MEASUREMENT_ID` — Google Analytics 4 measurement ID\n *\n * Netlify configuration-file env vars are build-time only for serverless\n * functions, so the Vite/Nitro build paths also bake this public value into\n * SSR bundles.\n *\n * Amplitude and Sentry are initialized client-side via their npm packages\n * (see `packages/core/src/client/analytics.ts`). Only GA requires script\n * tag injection because the gtag.js loader must be a `<script src>`.\n *\n * When set, the corresponding script tags are injected before `</head>`.\n * When not set, the stream passes through untouched (zero overhead).\n *\n * Usage in entry.server.tsx:\n * ```ts\n * import { wrapWithAnalytics } from \"@agent-native/core/server\";\n * return new Response(wrapWithAnalytics(body), { ... });\n * ```\n */\n\ndeclare const __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__: string | undefined;\n\nfunction normalizeMeasurementId(value: string | undefined): string | null {\n const trimmed = value?.trim();\n return trimmed ? trimmed : null;\n}\n\nfunction getViteBakedGaMeasurementId(): string | undefined {\n return typeof __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__ === \"string\"\n ? __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__\n : undefined;\n}\n\nfunction getGaMeasurementId(): string | null {\n return (\n normalizeMeasurementId(process.env.GA_MEASUREMENT_ID) ||\n normalizeMeasurementId(process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID) ||\n normalizeMeasurementId(getViteBakedGaMeasurementId())\n );\n}\n\n/**\n * Script hosts the injected GA loader pulls executable code from. Google Tag\n * Manager serves `gtag/js`, and GA4 can lazy-load additional collectors from\n * `www.google-analytics.com`. These must be listed in the document `script-src`\n * so the CSP reflects the code the framework itself injects (see\n * `applyDocumentCsp` in `ssr-handler.ts`).\n */\nexport const GA_CSP_SCRIPT_HOSTS = [\n \"https://www.googletagmanager.com\",\n \"https://www.google-analytics.com\",\n] as const;\n\n/**\n * Network/image hosts used by the GA4 loader when it sends page-view and event\n * beacons. These are separate from `script-src`: a stricter deployment CSP with\n * `connect-src 'self'` or `img-src 'self'` can load gtag.js but still drop all\n * analytics events unless these hosts are present too.\n */\nexport const GA_CSP_CONNECT_HOSTS = [\n \"https://www.google-analytics.com\",\n \"https://analytics.google.com\",\n \"https://stats.g.doubleclick.net\",\n \"https://region1.google-analytics.com\",\n] as const;\n\nexport const GA_CSP_IMG_HOSTS = [\n \"https://www.google-analytics.com\",\n \"https://www.googletagmanager.com\",\n \"https://stats.g.doubleclick.net\",\n] as const;\n\n/**\n * The exact JS body (no surrounding `<script>` tags) of the inline gtag config\n * block injected next to the gtag.js loader. Returned so the SSR handler can\n * hash it for the `script-src` CSP directive — the hash must be computed from\n * the identical string that `getGaScript()` embeds, so both call this helper.\n * Returns `null` when GA is not configured.\n */\nexport function getGaInlineConfigScriptBody(): string | null {\n const id = getGaMeasurementId();\n if (!id) return null;\n const jsId = JSON.stringify(id);\n return (\n `window.dataLayer=window.dataLayer||[];` +\n `function gtag(){dataLayer.push(arguments);}` +\n `gtag('js',new Date());` +\n `gtag('config',${jsId});` +\n `if(typeof sessionStorage!=='undefined'&&sessionStorage.getItem('__an_signin')){` +\n `sessionStorage.removeItem('__an_signin');` +\n `gtag('event','sign_in');` +\n `}`\n );\n}\n\nfunction getGaScript(): string | null {\n const id = getGaMeasurementId();\n if (!id) return null;\n const srcId = encodeURIComponent(id);\n const inlineBody = getGaInlineConfigScriptBody();\n return (\n `<script async src=\"https://www.googletagmanager.com/gtag/js?id=${srcId}\"></script>` +\n `<script>${inlineBody}</script>`\n );\n}\n\nexport function wrapWithAnalytics(body: ReadableStream): ReadableStream {\n const scripts = [getGaScript()].filter(Boolean).join(\"\");\n if (!scripts) return body;\n\n const decoder = new TextDecoder();\n const encoder = new TextEncoder();\n let injected = false;\n\n return body.pipeThrough(\n new TransformStream({\n transform(chunk, controller) {\n if (injected) {\n controller.enqueue(chunk);\n return;\n }\n const text = decoder.decode(chunk, { stream: true });\n const headCloseIdx = text.indexOf(\"</head>\");\n if (headCloseIdx !== -1) {\n const modified =\n text.slice(0, headCloseIdx) + scripts + text.slice(headCloseIdx);\n controller.enqueue(encoder.encode(modified));\n injected = true;\n } else {\n controller.enqueue(chunk);\n }\n },\n }),\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AA4CA,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AAoWpC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAgE5E"}
1
+ {"version":3,"file":"ssr-handler.d.ts","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AA8CA,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AA0nBpC;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,2FAgE5E"}
@@ -19,7 +19,7 @@ import { defineEventHandler } from "h3";
19
19
  import { createRequestHandler } from "react-router";
20
20
  import { DEFAULT_SSR_CACHE_HEADERS, DEFAULT_SPECULATION_RULES_PATH, } from "../shared/cache-control.js";
21
21
  import { AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, withAgentNativeSocialImageCacheBuster, } from "../shared/social-meta.js";
22
- import { GA_CSP_SCRIPT_HOSTS, getGaInlineConfigScriptBody, } from "./analytics.js";
22
+ import { GA_CSP_CONNECT_HOSTS, GA_CSP_IMG_HOSTS, GA_CSP_SCRIPT_HOSTS, getGaInlineConfigScriptBody, } from "./analytics.js";
23
23
  import { getAppBasePathFromViteEnv, stripAppBasePath as canonicalStripAppBasePath, } from "./app-base-path.js";
24
24
  import { runWithRequestContext } from "./request-context.js";
25
25
  import { computeInlineScriptHash } from "./security-headers.js";
@@ -207,6 +207,173 @@ function extractScriptBody(scriptTag) {
207
207
  return null;
208
208
  return scriptTag.slice(start, end);
209
209
  }
210
+ const CSP_DIRECTIVES_WITH_VALUE_TOKENS = new Set([
211
+ "base-uri",
212
+ "block-all-mixed-content",
213
+ "child-src",
214
+ "connect-src",
215
+ "default-src",
216
+ "fenced-frame-src",
217
+ "font-src",
218
+ "form-action",
219
+ "frame-ancestors",
220
+ "frame-src",
221
+ "img-src",
222
+ "manifest-src",
223
+ "media-src",
224
+ "navigate-to",
225
+ "object-src",
226
+ "plugin-types",
227
+ "prefetch-src",
228
+ "referrer",
229
+ "reflected-xss",
230
+ "require-sri-for",
231
+ "require-trusted-types-for",
232
+ "report-to",
233
+ "report-uri",
234
+ "sandbox",
235
+ "script-src",
236
+ "script-src-attr",
237
+ "script-src-elem",
238
+ "style-src",
239
+ "style-src-attr",
240
+ "style-src-elem",
241
+ "trusted-types",
242
+ "upgrade-insecure-requests",
243
+ "webrtc",
244
+ "worker-src",
245
+ ]);
246
+ function hasCommaJoinedCspPolicies(policy) {
247
+ let commaIndex = policy.indexOf(",");
248
+ while (commaIndex !== -1) {
249
+ const afterComma = policy.slice(commaIndex + 1);
250
+ const directive = /^\s+([a-z][a-z0-9-]*)(?=\s|;|$)/i.exec(afterComma)?.[1];
251
+ if (directive &&
252
+ CSP_DIRECTIVES_WITH_VALUE_TOKENS.has(directive.toLowerCase())) {
253
+ return true;
254
+ }
255
+ commaIndex = policy.indexOf(",", commaIndex + 1);
256
+ }
257
+ return false;
258
+ }
259
+ function parseCsp(policy) {
260
+ return policy
261
+ .split(";")
262
+ .map((part) => part.trim())
263
+ .filter(Boolean)
264
+ .map((part) => {
265
+ const [name = "", ...tokens] = part.split(/\s+/);
266
+ return { name: name.toLowerCase(), tokens };
267
+ })
268
+ .filter((directive) => directive.name);
269
+ }
270
+ function serializeCsp(directives) {
271
+ return directives
272
+ .map((directive) => [directive.name, ...directive.tokens].filter(Boolean).join(" "))
273
+ .join("; ");
274
+ }
275
+ function appendCspTokens(tokens, additions) {
276
+ if (!additions.length)
277
+ return tokens;
278
+ const next = tokens.filter((token) => token !== "'none'");
279
+ const seen = new Set(next);
280
+ for (const token of additions) {
281
+ if (!token || seen.has(token))
282
+ continue;
283
+ next.push(token);
284
+ seen.add(token);
285
+ }
286
+ return next;
287
+ }
288
+ function findCspDirective(directives, name) {
289
+ return directives.find((directive) => directive.name === name);
290
+ }
291
+ function appendToExistingOrDefaultCspDirective(directives, name, additions) {
292
+ if (!additions.length)
293
+ return;
294
+ const existing = findCspDirective(directives, name);
295
+ if (existing) {
296
+ existing.tokens = appendCspTokens(existing.tokens, additions);
297
+ return;
298
+ }
299
+ const defaultSrc = findCspDirective(directives, "default-src");
300
+ if (!defaultSrc)
301
+ return;
302
+ directives.push({
303
+ name,
304
+ tokens: appendCspTokens([...defaultSrc.tokens], additions),
305
+ });
306
+ }
307
+ function appendToExistingCspDirective(directives, name, additions) {
308
+ const existing = findCspDirective(directives, name);
309
+ if (!existing)
310
+ return;
311
+ existing.tokens = appendCspTokens(existing.tokens, additions);
312
+ }
313
+ function hasStrictNonceScriptPolicy(tokens) {
314
+ return tokens.some((token) => token === "'strict-dynamic'" || token.startsWith("'nonce-"));
315
+ }
316
+ function appendToScriptCspDirective(directives, name, additions) {
317
+ const existing = findCspDirective(directives, name);
318
+ if (existing) {
319
+ if (hasStrictNonceScriptPolicy(existing.tokens))
320
+ return false;
321
+ existing.tokens = appendCspTokens(existing.tokens, additions);
322
+ return true;
323
+ }
324
+ const defaultSrc = findCspDirective(directives, "default-src");
325
+ if (!defaultSrc || hasStrictNonceScriptPolicy(defaultSrc.tokens)) {
326
+ return false;
327
+ }
328
+ directives.push({
329
+ name,
330
+ tokens: appendCspTokens([...defaultSrc.tokens], additions),
331
+ });
332
+ return true;
333
+ }
334
+ function appendToEffectiveScriptElementCspDirective(directives, additions) {
335
+ const scriptSrcElem = findCspDirective(directives, "script-src-elem");
336
+ if (scriptSrcElem) {
337
+ if (hasStrictNonceScriptPolicy(scriptSrcElem.tokens))
338
+ return false;
339
+ scriptSrcElem.tokens = appendCspTokens(scriptSrcElem.tokens, additions);
340
+ return true;
341
+ }
342
+ return appendToScriptCspDirective(directives, "script-src", additions);
343
+ }
344
+ function augmentExistingEnforcedCspForFrameworkScripts(policy, options) {
345
+ // Multiple CSP headers are surfaced by Headers.get() as one comma-joined
346
+ // string. CSP is not a comma-list header, so serializing a parsed combined
347
+ // value would turn two policies into one invalid policy. Leave those headers
348
+ // app-owned; a comma inside a source/report URL is still safe to parse.
349
+ if (hasCommaJoinedCspPolicies(policy))
350
+ return policy;
351
+ const directives = parseCsp(policy);
352
+ if (!directives.length)
353
+ return policy;
354
+ if (options.gaEnabled) {
355
+ const addedScriptElement = appendToEffectiveScriptElementCspDirective(directives, options.gaScriptSrcTokens);
356
+ if (addedScriptElement) {
357
+ appendToExistingOrDefaultCspDirective(directives, "connect-src", GA_CSP_CONNECT_HOSTS);
358
+ appendToExistingOrDefaultCspDirective(directives, "img-src", GA_CSP_IMG_HOSTS);
359
+ }
360
+ }
361
+ return serializeCsp(directives);
362
+ }
363
+ function augmentExistingReportOnlyCspForFrameworkScripts(policy, options) {
364
+ if (hasCommaJoinedCspPolicies(policy))
365
+ return policy;
366
+ const directives = parseCsp(policy);
367
+ if (!directives.length)
368
+ return policy;
369
+ appendToExistingOrDefaultCspDirective(directives, "script-src", options.scriptSrcTokens);
370
+ appendToExistingCspDirective(directives, "script-src-elem", options.scriptSrcTokens);
371
+ if (options.gaEnabled) {
372
+ appendToExistingOrDefaultCspDirective(directives, "connect-src", GA_CSP_CONNECT_HOSTS);
373
+ appendToExistingOrDefaultCspDirective(directives, "img-src", GA_CSP_IMG_HOSTS);
374
+ }
375
+ return serializeCsp(directives);
376
+ }
210
377
  /**
211
378
  * Apply a Content-Security-Policy header to HTML document responses.
212
379
  *
@@ -219,20 +386,28 @@ function extractScriptBody(scriptTag) {
219
386
  * user-controlled content reaches the HTML).
220
387
  *
221
388
  * A third directive, `script-src`, is emitted via `Content-Security-Policy-
222
- * Report-Only` rather than enforced. The framework injects deterministic inline
223
- * scripts (the Sentry config block, whose hash is computed once at process
224
- * startup from the resolved env vars, and — when `GA_MEASUREMENT_ID` is set —
225
- * the gtag config block, whose hash is derived from the same string
226
- * `wrapWithAnalytics` embeds). It also loads Google Tag Manager / GA4 from
227
- * `GA_CSP_SCRIPT_HOSTS`. All of those are listed here so the report-only policy
228
- * reflects the code the framework itself injects instead of reporting a
229
- * violation on every page load. Templates additionally render a theme-init
230
- * inline script whose exact content varies by template (default theme param,
231
- * custom docs variant, etc.) and which is rendered by React Router, not this
232
- * handler, so its hash is not available here. Shipping script-src as
233
- * Report-Only surfaces the remaining violations without breaking template
234
- * customisations; teams can graduate to enforcement once their hashes are
235
- * enumerated.
389
+ * Report-Only` rather than enforced when the app has no existing document CSP.
390
+ * The framework injects deterministic inline scripts (the Sentry config block,
391
+ * whose hash is computed once at process startup from the resolved env vars,
392
+ * and — when `GA_MEASUREMENT_ID` is set — the gtag config block, whose hash is
393
+ * derived from the same string `wrapWithAnalytics` embeds). It also loads
394
+ * Google Tag Manager / GA4 from `GA_CSP_SCRIPT_HOSTS`. All of those are listed
395
+ * here so the report-only policy reflects the code the framework itself injects
396
+ * instead of reporting a violation on every page load.
397
+ *
398
+ * If an app or host already sends an enforced CSP with `script-src`,
399
+ * `script-src-elem`, `connect-src`, `img-src`, or `default-src`, we merge only
400
+ * GA-specific allowances into existing host/hash policies. Strict nonce or
401
+ * `strict-dynamic` script policies stay app-owned because blindly appending
402
+ * hashes or hosts would widen the policy without reliably loading our injected
403
+ * scripts.
404
+ *
405
+ * Templates additionally render a theme-init inline script whose exact content
406
+ * varies by template (default theme param, custom docs variant, etc.) and which
407
+ * is rendered by React Router, not this handler, so its hash is not available
408
+ * here. Shipping script-src as Report-Only surfaces the remaining violations
409
+ * without breaking template customisations; teams can graduate to enforcement
410
+ * once their hashes are enumerated.
236
411
  *
237
412
  * Skipped in development (`NODE_ENV !== 'production'`) so HMR eval and Vite
238
413
  * dev-server injects are never blocked. Set `AGENT_NATIVE_DISABLE_DOC_CSP=1`
@@ -243,12 +418,6 @@ function applyDocumentCsp(headers, sentryScript) {
243
418
  return;
244
419
  if (process.env.AGENT_NATIVE_DISABLE_DOC_CSP === "1")
245
420
  return;
246
- // object-src / base-uri: enforced; neither directive mentions scripts, so
247
- // they are safe even when a template's inline script hashes are unknown.
248
- const existing = headers.get("content-security-policy") ?? "";
249
- if (!existing) {
250
- headers.set("content-security-policy", "object-src 'none'; base-uri 'self'");
251
- }
252
421
  // script-src as Report-Only: list 'self', the framework-injected inline
253
422
  // script hashes (Sentry config + gtag config), and the Google Analytics /
254
423
  // Tag Manager loader hosts. These are exactly the scripts the framework
@@ -260,17 +429,33 @@ function applyDocumentCsp(headers, sentryScript) {
260
429
  const gaInlineBody = getGaInlineConfigScriptBody();
261
430
  const gaHash = gaInlineBody ? computeInlineScriptHash(gaInlineBody) : null;
262
431
  const gaHosts = gaInlineBody ? [...GA_CSP_SCRIPT_HOSTS] : [];
432
+ const gaScriptSrcTokens = [...(gaHash ? [gaHash] : []), ...gaHosts];
263
433
  const scriptSrcTokens = [
264
434
  "'self'",
265
435
  ...(sentryHash ? [sentryHash] : []),
266
436
  ...(gaHash ? [gaHash] : []),
267
437
  ...gaHosts,
268
438
  ];
439
+ const cspAugmentOptions = {
440
+ scriptSrcTokens,
441
+ gaScriptSrcTokens,
442
+ gaEnabled: Boolean(gaInlineBody),
443
+ };
444
+ const existing = headers.get("content-security-policy") ?? "";
445
+ if (!existing) {
446
+ headers.set("content-security-policy", "object-src 'none'; base-uri 'self'");
447
+ }
448
+ else {
449
+ headers.set("content-security-policy", augmentExistingEnforcedCspForFrameworkScripts(existing, cspAugmentOptions));
450
+ }
269
451
  const scriptSrc = `script-src ${scriptSrcTokens.join(" ")}`;
270
452
  const existingRo = headers.get("content-security-policy-report-only") ?? "";
271
453
  if (!existingRo) {
272
454
  headers.set("content-security-policy-report-only", scriptSrc);
273
455
  }
456
+ else {
457
+ headers.set("content-security-policy-report-only", augmentExistingReportOnlyCspForFrameworkScripts(existingRo, cspAugmentOptions));
458
+ }
274
459
  }
275
460
  function isFrameworkOrAssetPath(pathname) {
276
461
  return (pathname.startsWith("/.well-known/") ||