@agent-native/core 0.84.66 → 0.85.0
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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +199 -38
- package/corpus/core/src/agent/run-manager.ts +79 -0
- package/corpus/core/src/agent/run-store.ts +117 -5
- package/corpus/core/src/agent/types.ts +13 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +14 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +475 -0
- package/corpus/core/src/client/analytics.ts +2 -0
- package/corpus/core/src/client/error-format.ts +4 -0
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +1 -1
- package/corpus/core/src/client/session-replay.ts +774 -2
- package/corpus/core/src/client/sse-event-processor.ts +65 -9
- package/corpus/core/src/client/use-run-stuck-detection.ts +31 -7
- package/corpus/core/src/db/runtime-diagnostics.ts +331 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +115 -2
- package/corpus/core/src/server/analytics.ts +1 -34
- package/corpus/core/src/server/core-routes-plugin.ts +91 -4
- package/corpus/core/src/server/http-response-telemetry.ts +189 -0
- package/corpus/core/src/server/self-dispatch.ts +29 -0
- package/corpus/core/src/server/ssr-handler.ts +18 -377
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +53 -1
- package/corpus/templates/analytics/AGENTS.md +13 -0
- package/corpus/templates/analytics/actions/view-screen.ts +25 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +20 -0
- package/corpus/templates/analytics/app/global.css +55 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +100 -1
- package/corpus/templates/analytics/app/i18n-data.ts +1226 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +3 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +182 -144
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +11 -18
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +11 -18
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +49 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +557 -0
- package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +274 -0
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +974 -0
- package/corpus/templates/analytics/changelog/2026-07-02-alert-rules-can-now-be-viewed-and-managed-from-settings.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-recent-ask-chats-show-a-loading-placeholder-while-your-chat-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-session-replays-now-capture-console-logs-and-network-request.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-sharing-status-labels-use-neutral-icons-instead-of-colored-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-the-session-replay-viewer-has-a-dev-tools-panel-with-console.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +3 -0
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -0
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +142 -1
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +369 -8
- package/corpus/templates/analytics/server/lib/session-replay.ts +76 -2
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +101 -0
- package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +78 -0
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +0 -2
- package/corpus/templates/design/app/pages/DesignEditor.tsx +24 -12
- package/corpus/templates/design/changelog/2026-07-02-the-temporary-code-tab-is-hidden-from-the-design-editor-side.md +6 -0
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +0 -1
- package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
- package/corpus/templates/plan/actions/update-local-plan-folder.ts +3 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-02-trying-to-edit-a-recap-or-plan-you-can-only-view-now-explain.md +6 -0
- package/corpus/templates/plan/server/plans.ts +26 -9
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +155 -34
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +29 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +73 -0
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +41 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +100 -4
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +13 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +13 -0
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +410 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +2 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/session-replay.d.ts +36 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +579 -1
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +20 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +42 -11
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +14 -4
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +18 -4
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/runtime-diagnostics.d.ts +52 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -0
- package/dist/db/runtime-diagnostics.js +250 -0
- package/dist/db/runtime-diagnostics.js.map +1 -0
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +101 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/analytics.d.ts +1 -19
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +1 -31
- package/dist/server/analytics.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +17 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +65 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts +4 -0
- package/dist/server/http-response-telemetry.d.ts.map +1 -0
- package/dist/server/http-response-telemetry.js +174 -0
- package/dist/server/http-response-telemetry.js.map +1 -0
- package/dist/server/self-dispatch.d.ts +17 -0
- package/dist/server/self-dispatch.d.ts.map +1 -1
- package/dist/server/self-dispatch.js +11 -0
- package/dist/server/self-dispatch.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +18 -271
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/docs/content/tracking.mdx +2 -0
- package/package.json +1 -1
|
@@ -19,10 +19,8 @@ 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_CONNECT_HOSTS, GA_CSP_IMG_HOSTS, GA_CSP_SCRIPT_HOSTS, getGaInlineConfigScriptBody, } from "./analytics.js";
|
|
23
22
|
import { getAppBasePathFromViteEnv, stripAppBasePath as canonicalStripAppBasePath, } from "./app-base-path.js";
|
|
24
23
|
import { runWithRequestContext } from "./request-context.js";
|
|
25
|
-
import { computeInlineScriptHash } from "./security-headers.js";
|
|
26
24
|
import { getSentryClientConfigScript } from "./sentry-config.js";
|
|
27
25
|
export { DEFAULT_SSR_CACHE_HEADERS, DEFAULT_SPECULATION_RULES_HEADER, DEFAULT_SSR_CACHE_CONTROL, } from "../shared/cache-control.js";
|
|
28
26
|
function getAppBasePath() {
|
|
@@ -192,275 +190,17 @@ function applyDefaultSpeculationRulesHeader(headers, status, basePath) {
|
|
|
192
190
|
headers.set("speculation-rules", `"${rulesPath}"`);
|
|
193
191
|
}
|
|
194
192
|
/**
|
|
195
|
-
*
|
|
196
|
-
* Returns `null` if the input is falsy or has no recognisable `</script>` end.
|
|
197
|
-
* Used to compute the sha256 hash of framework-injected inline scripts so the
|
|
198
|
-
* hash can be listed in app-owned `script-src` CSP directives.
|
|
199
|
-
*/
|
|
200
|
-
function extractScriptBody(scriptTag) {
|
|
201
|
-
if (!scriptTag)
|
|
202
|
-
return null;
|
|
203
|
-
const start = scriptTag.indexOf(">") + 1;
|
|
204
|
-
const end = scriptTag.lastIndexOf("</script>");
|
|
205
|
-
if (start <= 0 || end < start)
|
|
206
|
-
return null;
|
|
207
|
-
return scriptTag.slice(start, end);
|
|
208
|
-
}
|
|
209
|
-
const CSP_DIRECTIVES_WITH_VALUE_TOKENS = new Set([
|
|
210
|
-
"base-uri",
|
|
211
|
-
"block-all-mixed-content",
|
|
212
|
-
"child-src",
|
|
213
|
-
"connect-src",
|
|
214
|
-
"default-src",
|
|
215
|
-
"fenced-frame-src",
|
|
216
|
-
"font-src",
|
|
217
|
-
"form-action",
|
|
218
|
-
"frame-ancestors",
|
|
219
|
-
"frame-src",
|
|
220
|
-
"img-src",
|
|
221
|
-
"manifest-src",
|
|
222
|
-
"media-src",
|
|
223
|
-
"navigate-to",
|
|
224
|
-
"object-src",
|
|
225
|
-
"plugin-types",
|
|
226
|
-
"prefetch-src",
|
|
227
|
-
"referrer",
|
|
228
|
-
"reflected-xss",
|
|
229
|
-
"require-sri-for",
|
|
230
|
-
"require-trusted-types-for",
|
|
231
|
-
"report-to",
|
|
232
|
-
"report-uri",
|
|
233
|
-
"sandbox",
|
|
234
|
-
"script-src",
|
|
235
|
-
"script-src-attr",
|
|
236
|
-
"script-src-elem",
|
|
237
|
-
"style-src",
|
|
238
|
-
"style-src-attr",
|
|
239
|
-
"style-src-elem",
|
|
240
|
-
"trusted-types",
|
|
241
|
-
"upgrade-insecure-requests",
|
|
242
|
-
"webrtc",
|
|
243
|
-
"worker-src",
|
|
244
|
-
]);
|
|
245
|
-
function hasCommaJoinedCspPolicies(policy) {
|
|
246
|
-
let commaIndex = policy.indexOf(",");
|
|
247
|
-
while (commaIndex !== -1) {
|
|
248
|
-
const afterComma = policy.slice(commaIndex + 1);
|
|
249
|
-
const directive = /^\s+([a-z][a-z0-9-]*)(?=\s|;|$)/i.exec(afterComma)?.[1];
|
|
250
|
-
if (directive &&
|
|
251
|
-
CSP_DIRECTIVES_WITH_VALUE_TOKENS.has(directive.toLowerCase())) {
|
|
252
|
-
return true;
|
|
253
|
-
}
|
|
254
|
-
commaIndex = policy.indexOf(",", commaIndex + 1);
|
|
255
|
-
}
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
function parseCsp(policy) {
|
|
259
|
-
return policy
|
|
260
|
-
.split(";")
|
|
261
|
-
.map((part) => part.trim())
|
|
262
|
-
.filter(Boolean)
|
|
263
|
-
.map((part) => {
|
|
264
|
-
const [name = "", ...tokens] = part.split(/\s+/);
|
|
265
|
-
return { name: name.toLowerCase(), tokens };
|
|
266
|
-
})
|
|
267
|
-
.filter((directive) => directive.name);
|
|
268
|
-
}
|
|
269
|
-
function serializeCsp(directives) {
|
|
270
|
-
return directives
|
|
271
|
-
.map((directive) => [directive.name, ...directive.tokens].filter(Boolean).join(" "))
|
|
272
|
-
.join("; ");
|
|
273
|
-
}
|
|
274
|
-
function appendCspTokens(tokens, additions) {
|
|
275
|
-
if (!additions.length)
|
|
276
|
-
return tokens;
|
|
277
|
-
const next = tokens.filter((token) => token !== "'none'");
|
|
278
|
-
const seen = new Set(next);
|
|
279
|
-
for (const token of additions) {
|
|
280
|
-
if (!token || seen.has(token))
|
|
281
|
-
continue;
|
|
282
|
-
next.push(token);
|
|
283
|
-
seen.add(token);
|
|
284
|
-
}
|
|
285
|
-
return next;
|
|
286
|
-
}
|
|
287
|
-
function findCspDirective(directives, name) {
|
|
288
|
-
return directives.find((directive) => directive.name === name);
|
|
289
|
-
}
|
|
290
|
-
function appendToExistingOrDefaultCspDirective(directives, name, additions) {
|
|
291
|
-
if (!additions.length)
|
|
292
|
-
return;
|
|
293
|
-
const existing = findCspDirective(directives, name);
|
|
294
|
-
if (existing) {
|
|
295
|
-
existing.tokens = appendCspTokens(existing.tokens, additions);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const defaultSrc = findCspDirective(directives, "default-src");
|
|
299
|
-
if (!defaultSrc)
|
|
300
|
-
return;
|
|
301
|
-
directives.push({
|
|
302
|
-
name,
|
|
303
|
-
tokens: appendCspTokens([...defaultSrc.tokens], additions),
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
function appendToExistingCspDirective(directives, name, additions) {
|
|
307
|
-
const existing = findCspDirective(directives, name);
|
|
308
|
-
if (!existing)
|
|
309
|
-
return;
|
|
310
|
-
existing.tokens = appendCspTokens(existing.tokens, additions);
|
|
311
|
-
}
|
|
312
|
-
function ensureCspDirective(directives, name, tokens) {
|
|
313
|
-
if (findCspDirective(directives, name))
|
|
314
|
-
return;
|
|
315
|
-
directives.push({ name, tokens: [...tokens] });
|
|
316
|
-
}
|
|
317
|
-
function hasStrictNonceScriptPolicy(tokens) {
|
|
318
|
-
return tokens.some((token) => token === "'strict-dynamic'" || token.startsWith("'nonce-"));
|
|
319
|
-
}
|
|
320
|
-
function appendToScriptCspDirective(directives, name, additions) {
|
|
321
|
-
const existing = findCspDirective(directives, name);
|
|
322
|
-
if (existing) {
|
|
323
|
-
if (hasStrictNonceScriptPolicy(existing.tokens))
|
|
324
|
-
return false;
|
|
325
|
-
existing.tokens = appendCspTokens(existing.tokens, additions);
|
|
326
|
-
return true;
|
|
327
|
-
}
|
|
328
|
-
const defaultSrc = findCspDirective(directives, "default-src");
|
|
329
|
-
if (!defaultSrc || hasStrictNonceScriptPolicy(defaultSrc.tokens)) {
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
directives.push({
|
|
333
|
-
name,
|
|
334
|
-
tokens: appendCspTokens([...defaultSrc.tokens], additions),
|
|
335
|
-
});
|
|
336
|
-
return true;
|
|
337
|
-
}
|
|
338
|
-
function appendToEffectiveScriptElementCspDirective(directives, additions) {
|
|
339
|
-
const scriptSrcElem = findCspDirective(directives, "script-src-elem");
|
|
340
|
-
if (scriptSrcElem) {
|
|
341
|
-
if (hasStrictNonceScriptPolicy(scriptSrcElem.tokens))
|
|
342
|
-
return false;
|
|
343
|
-
scriptSrcElem.tokens = appendCspTokens(scriptSrcElem.tokens, additions);
|
|
344
|
-
return true;
|
|
345
|
-
}
|
|
346
|
-
return appendToScriptCspDirective(directives, "script-src", additions);
|
|
347
|
-
}
|
|
348
|
-
function augmentExistingEnforcedCspForFrameworkScripts(policy, options) {
|
|
349
|
-
// Multiple CSP headers are surfaced by Headers.get() as one comma-joined
|
|
350
|
-
// string. CSP is not a comma-list header, so serializing a parsed combined
|
|
351
|
-
// value would turn two policies into one invalid policy. Leave those headers
|
|
352
|
-
// app-owned; a comma inside a source/report URL is still safe to parse.
|
|
353
|
-
if (hasCommaJoinedCspPolicies(policy))
|
|
354
|
-
return policy;
|
|
355
|
-
const directives = parseCsp(policy);
|
|
356
|
-
if (!directives.length)
|
|
357
|
-
return policy;
|
|
358
|
-
if (options.gaEnabled) {
|
|
359
|
-
const addedScriptElement = appendToEffectiveScriptElementCspDirective(directives, options.gaScriptSrcTokens);
|
|
360
|
-
if (addedScriptElement) {
|
|
361
|
-
appendToExistingOrDefaultCspDirective(directives, "connect-src", GA_CSP_CONNECT_HOSTS);
|
|
362
|
-
appendToExistingOrDefaultCspDirective(directives, "img-src", GA_CSP_IMG_HOSTS);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
ensureCspDirective(directives, "object-src", ["'none'"]);
|
|
366
|
-
ensureCspDirective(directives, "base-uri", ["'self'"]);
|
|
367
|
-
return serializeCsp(directives);
|
|
368
|
-
}
|
|
369
|
-
function augmentExistingReportOnlyCspForFrameworkScripts(policy, options) {
|
|
370
|
-
if (hasCommaJoinedCspPolicies(policy))
|
|
371
|
-
return policy;
|
|
372
|
-
const directives = parseCsp(policy);
|
|
373
|
-
if (!directives.length)
|
|
374
|
-
return policy;
|
|
375
|
-
appendToExistingOrDefaultCspDirective(directives, "script-src", options.scriptSrcTokens);
|
|
376
|
-
appendToExistingCspDirective(directives, "script-src-elem", options.scriptSrcTokens);
|
|
377
|
-
if (options.gaEnabled) {
|
|
378
|
-
appendToExistingOrDefaultCspDirective(directives, "connect-src", GA_CSP_CONNECT_HOSTS);
|
|
379
|
-
appendToExistingOrDefaultCspDirective(directives, "img-src", GA_CSP_IMG_HOSTS);
|
|
380
|
-
}
|
|
381
|
-
return serializeCsp(directives);
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Apply a Content-Security-Policy header to HTML document responses.
|
|
385
|
-
*
|
|
386
|
-
* Two directives are always enforced in production:
|
|
387
|
-
*
|
|
388
|
-
* - `object-src 'none'` — disables Flash / Java / PDF plugin execution,
|
|
389
|
-
* which are a reliable code-execution vector even in modern browsers.
|
|
390
|
-
* - `base-uri 'self'` — prevents a `<base href="...">` injection from
|
|
391
|
-
* hijacking all relative URLs in the document (a common attack target when
|
|
392
|
-
* user-controlled content reaches the HTML).
|
|
393
|
-
*
|
|
394
|
-
* A third directive, `script-src`, is emitted via `Content-Security-Policy-
|
|
395
|
-
* Report-Only` rather than enforced when the app has no existing document CSP.
|
|
396
|
-
* The framework injects inline scripts for analytics, Sentry, and template
|
|
397
|
-
* setup, and hosted apps need Google Tag Manager to load without noisy CSP
|
|
398
|
-
* diagnostics. The report-only policy is intentionally permissive for scripts:
|
|
399
|
-
* it includes `'unsafe-inline'` plus the known GA/GTM loader hosts.
|
|
400
|
-
*
|
|
401
|
-
* If an app or host already sends an enforced CSP with `script-src`,
|
|
402
|
-
* `script-src-elem`, `connect-src`, `img-src`, or `default-src`, we merge only
|
|
403
|
-
* GA-specific allowances into existing host/hash policies. Strict nonce or
|
|
404
|
-
* `strict-dynamic` script policies stay app-owned because blindly appending
|
|
405
|
-
* hashes or hosts would widen the policy without reliably loading our injected
|
|
406
|
-
* scripts.
|
|
407
|
-
*
|
|
408
|
-
* Templates additionally render a theme-init inline script whose exact content
|
|
409
|
-
* varies by template (default theme param, custom docs variant, etc.) and which
|
|
410
|
-
* is rendered by React Router, not this handler, so its hash is not available
|
|
411
|
-
* here. Shipping script-src as Report-Only surfaces the remaining violations
|
|
412
|
-
* without breaking template customisations; teams can graduate to enforcement
|
|
413
|
-
* once their hashes are enumerated.
|
|
193
|
+
* Strip document-level CSP from app HTML responses.
|
|
414
194
|
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
195
|
+
* Hosted templates inject framework bootstrap scripts, analytics, Sentry config,
|
|
196
|
+
* and app-owned inline scripts whose exact bytes vary by build/template. Any
|
|
197
|
+
* shared CSP header, even Report-Only, can block or noisily report Google Tag
|
|
198
|
+
* Manager and those bootstraps. Extension iframes and webviews keep their own
|
|
199
|
+
* route-specific sandboxes; normal app documents deliberately do not emit CSP.
|
|
418
200
|
*/
|
|
419
|
-
function
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (process.env.AGENT_NATIVE_DISABLE_DOC_CSP === "1")
|
|
423
|
-
return;
|
|
424
|
-
// script-src as Report-Only: keep this deliberately loose so the framework's
|
|
425
|
-
// injected analytics and template bootstrap scripts do not look blocked in
|
|
426
|
-
// browser diagnostics.
|
|
427
|
-
const sentryBody = extractScriptBody(sentryScript);
|
|
428
|
-
const sentryHash = sentryBody ? computeInlineScriptHash(sentryBody) : null;
|
|
429
|
-
const gaInlineBody = getGaInlineConfigScriptBody();
|
|
430
|
-
const gaHash = gaInlineBody ? computeInlineScriptHash(gaInlineBody) : null;
|
|
431
|
-
const gaHosts = gaInlineBody ? [...GA_CSP_SCRIPT_HOSTS] : [];
|
|
432
|
-
const gaScriptSrcTokens = [
|
|
433
|
-
"'unsafe-inline'",
|
|
434
|
-
...(gaHash ? [gaHash] : []),
|
|
435
|
-
...gaHosts,
|
|
436
|
-
];
|
|
437
|
-
const scriptSrcTokens = [
|
|
438
|
-
"'self'",
|
|
439
|
-
"'unsafe-inline'",
|
|
440
|
-
...(sentryHash ? [sentryHash] : []),
|
|
441
|
-
...(gaHash ? [gaHash] : []),
|
|
442
|
-
...gaHosts,
|
|
443
|
-
];
|
|
444
|
-
const cspAugmentOptions = {
|
|
445
|
-
scriptSrcTokens,
|
|
446
|
-
gaScriptSrcTokens,
|
|
447
|
-
gaEnabled: Boolean(gaInlineBody),
|
|
448
|
-
};
|
|
449
|
-
const existing = headers.get("content-security-policy") ?? "";
|
|
450
|
-
if (!existing) {
|
|
451
|
-
headers.set("content-security-policy", "object-src 'none'; base-uri 'self'");
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
headers.set("content-security-policy", augmentExistingEnforcedCspForFrameworkScripts(existing, cspAugmentOptions));
|
|
455
|
-
}
|
|
456
|
-
const scriptSrc = `script-src ${scriptSrcTokens.join(" ")}`;
|
|
457
|
-
const existingRo = headers.get("content-security-policy-report-only") ?? "";
|
|
458
|
-
if (!existingRo) {
|
|
459
|
-
headers.set("content-security-policy-report-only", scriptSrc);
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
headers.set("content-security-policy-report-only", augmentExistingReportOnlyCspForFrameworkScripts(existingRo, cspAugmentOptions));
|
|
463
|
-
}
|
|
201
|
+
function removeDocumentCsp(headers) {
|
|
202
|
+
headers.delete("content-security-policy");
|
|
203
|
+
headers.delete("content-security-policy-report-only");
|
|
464
204
|
}
|
|
465
205
|
function isFrameworkOrAssetPath(pathname) {
|
|
466
206
|
return (pathname.startsWith("/.well-known/") ||
|
|
@@ -487,7 +227,15 @@ async function rewriteMountedResponse(response, basePath, pathname, requestUrl)
|
|
|
487
227
|
headers.set("location", prefixMountedPath(location, basePath));
|
|
488
228
|
}
|
|
489
229
|
const contentType = headers.get("content-type") ?? "";
|
|
490
|
-
if (!contentType.toLowerCase().includes("text/html")
|
|
230
|
+
if (!contentType.toLowerCase().includes("text/html")) {
|
|
231
|
+
return new Response(response.body, {
|
|
232
|
+
status: response.status,
|
|
233
|
+
statusText: response.statusText,
|
|
234
|
+
headers,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
removeDocumentCsp(headers);
|
|
238
|
+
if (!response.body) {
|
|
491
239
|
return new Response(response.body, {
|
|
492
240
|
status: response.status,
|
|
493
241
|
statusText: response.statusText,
|
|
@@ -496,7 +244,6 @@ async function rewriteMountedResponse(response, basePath, pathname, requestUrl)
|
|
|
496
244
|
}
|
|
497
245
|
const html = await response.text();
|
|
498
246
|
headers.delete("content-length");
|
|
499
|
-
applyDocumentCsp(headers, sentryClientConfigScript);
|
|
500
247
|
return new Response(injectHeadScript(injectDefaultSocialImageMeta(prefixMountedHtml(html, basePath), defaultSocialImageUrl(requestUrl, basePath)), sentryClientConfigScript), {
|
|
501
248
|
status: response.status,
|
|
502
249
|
statusText: response.statusText,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-handler.js","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineEventHandler } from \"h3\";\n/**\n * Shared SSR catch-all handler for React Router framework mode.\n *\n * Templates wire this up via:\n *\n * // server/routes/[...page].get.ts\n * import { createH3SSRHandler } from \"@agent-native/core/server/ssr-handler\";\n * export default createH3SSRHandler(\n * () => import(\"virtual:react-router/server-build\"),\n * );\n *\n * The `getBuild` callback MUST live in the template's own source so Vite's\n * @react-router/dev plugin can resolve the `virtual:` module. Pulling the\n * import into core (e.g. via a re-export) puts it in node_modules where\n * Vite's SSR externalizer leaves it untouched and Node's ESM loader rejects\n * the unknown scheme — silently 302'ing every request to \"/\".\n */\nimport { createRequestHandler } from \"react-router\";\n\nimport {\n DEFAULT_SSR_CACHE_HEADERS,\n DEFAULT_SPECULATION_RULES_PATH,\n} from \"../shared/cache-control.js\";\nimport {\n AGENT_NATIVE_SOCIAL_IMAGE_ALT,\n AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT,\n AGENT_NATIVE_SOCIAL_IMAGE_PATH,\n AGENT_NATIVE_SOCIAL_IMAGE_TYPE,\n AGENT_NATIVE_SOCIAL_IMAGE_WIDTH,\n withAgentNativeSocialImageCacheBuster,\n} from \"../shared/social-meta.js\";\nimport {\n GA_CSP_CONNECT_HOSTS,\n GA_CSP_IMG_HOSTS,\n GA_CSP_SCRIPT_HOSTS,\n getGaInlineConfigScriptBody,\n} from \"./analytics.js\";\nimport {\n getAppBasePathFromViteEnv,\n stripAppBasePath as canonicalStripAppBasePath,\n} from \"./app-base-path.js\";\nimport { runWithRequestContext } from \"./request-context.js\";\nimport { computeInlineScriptHash } from \"./security-headers.js\";\nimport { getSentryClientConfigScript } from \"./sentry-config.js\";\n\nexport {\n DEFAULT_SSR_CACHE_HEADERS,\n DEFAULT_SPECULATION_RULES_HEADER,\n DEFAULT_SSR_CACHE_CONTROL,\n} from \"../shared/cache-control.js\";\n\nfunction getAppBasePath(): string {\n return getAppBasePathFromViteEnv();\n}\n\nfunction stripAppBasePath(pathname: string): string {\n return canonicalStripAppBasePath(pathname, getAppBasePath());\n}\n\nfunction stripBasePath(pathname: string, basePath: string): string {\n if (!basePath) return pathname;\n if (pathname === basePath) return \"/\";\n if (pathname.startsWith(`${basePath}/`)) {\n return pathname.slice(basePath.length) || \"/\";\n }\n return pathname;\n}\n\nfunction requestWithPathname(\n request: Request,\n pathname: string,\n basePath: string,\n): Request {\n const url = new URL(request.url);\n let changed = false;\n if (basePath && pathname === \"/__manifest\") {\n const paths = url.searchParams.get(\"paths\");\n if (paths) {\n const strippedPaths = paths\n .split(\",\")\n .map((path) => stripBasePath(path, basePath))\n .join(\",\");\n if (strippedPaths !== paths) {\n url.searchParams.set(\"paths\", strippedPaths);\n changed = true;\n }\n }\n }\n if (url.pathname !== pathname) {\n url.pathname = pathname;\n changed = true;\n }\n if (!changed) return request;\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n headers: request.headers,\n signal: request.signal,\n };\n if (request.body && ![\"GET\", \"HEAD\"].includes(request.method.toUpperCase())) {\n init.body = request.body;\n init.duplex = \"half\";\n }\n return new Request(url, init);\n}\n\nfunction prefixMountedPath(path: string, basePath: string): string {\n if (!basePath || !path.startsWith(\"/\") || path.startsWith(\"//\")) return path;\n if (path === basePath || path.startsWith(`${basePath}/`)) return path;\n return `${basePath}${path}`;\n}\n\nfunction prefixMountedHtml(html: string, basePath: string): string {\n if (!basePath) return html;\n return html\n .replace(\n /\\b(href|src|action|formaction|poster)=([\"'])(\\/(?!\\/)[^\"']*)\\2/g,\n (_match, attr: string, quote: string, path: string) =>\n `${attr}=${quote}${prefixMountedPath(path, basePath)}${quote}`,\n )\n .replace(/url\\(([\"']?)(\\/(?!\\/)[^)'\" ]+)\\1\\)/g, (_match, quote, path) => {\n const q = quote || \"\";\n return `url(${q}${prefixMountedPath(path, basePath)}${q})`;\n });\n}\n\nfunction injectHeadScript(html: string, script: string | null): string {\n if (!script) return html;\n const headCloseIdx = html.indexOf(\"</head>\");\n if (headCloseIdx === -1) return html;\n return html.slice(0, headCloseIdx) + script + html.slice(headCloseIdx);\n}\n\nconst OG_IMAGE_META_RE = /<meta\\b(?=[^>]*\\bproperty=([\"'])og:image\\1)[^>]*>/i;\nconst TWITTER_CARD_META_RE =\n /<meta\\b(?=[^>]*\\bname=([\"'])twitter:card\\1)[^>]*>/i;\nconst TWITTER_IMAGE_META_RE =\n /<meta\\b(?=[^>]*\\bname=([\"'])twitter:image\\1)[^>]*>/i;\n\nfunction defaultSocialImageUrl(requestUrl: string, basePath: string): string {\n return withAgentNativeSocialImageCacheBuster(\n new URL(\n prefixMountedPath(AGENT_NATIVE_SOCIAL_IMAGE_PATH, basePath),\n requestUrl,\n ).toString(),\n );\n}\n\nfunction injectDefaultSocialImageMeta(html: string, imageUrl: string): string {\n const headCloseIdx = html.indexOf(\"</head>\");\n if (headCloseIdx === -1) return html;\n\n const hasAnySocialImage =\n OG_IMAGE_META_RE.test(html) || TWITTER_IMAGE_META_RE.test(html);\n const tags: string[] = [];\n\n if (!hasAnySocialImage) {\n tags.push(`<meta property=\"og:image\" content=\"${imageUrl}\">`);\n tags.push(`<meta property=\"og:image:secure_url\" content=\"${imageUrl}\">`);\n tags.push(\n `<meta property=\"og:image:type\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_TYPE}\">`,\n );\n tags.push(\n `<meta property=\"og:image:width\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_WIDTH}\">`,\n );\n tags.push(\n `<meta property=\"og:image:height\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT}\">`,\n );\n tags.push(\n `<meta property=\"og:image:alt\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_ALT}\">`,\n );\n }\n if (!TWITTER_CARD_META_RE.test(html)) {\n tags.push(`<meta name=\"twitter:card\" content=\"summary_large_image\">`);\n }\n if (!hasAnySocialImage) {\n tags.push(`<meta name=\"twitter:image\" content=\"${imageUrl}\">`);\n tags.push(\n `<meta name=\"twitter:image:alt\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_ALT}\">`,\n );\n }\n\n if (tags.length === 0) return html;\n return html.slice(0, headCloseIdx) + tags.join(\"\") + html.slice(headCloseIdx);\n}\n\nfunction isSsrHtmlOrDataResponse(\n headers: Headers,\n status: number,\n pathname: string,\n): boolean {\n if (status < 200 || status >= 400) return false;\n const contentType = headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n if (contentType.includes(\"text/html\")) return true;\n return pathname.endsWith(\".data\") && contentType.includes(\"text/x-script\");\n}\n\n/**\n * Apply the SSR cache policy to the response headers.\n *\n * ┌──────────────────────────────────────────────────────────────────────────┐\n * │ SSR IS A PUBLIC, HARD-CDN-CACHED SHELL — SERVED IDENTICALLY TO EVERYONE. │\n * │ │\n * │ Every SSR HTML / React Router `.data` response gets the same public │\n * │ stale-while-revalidate policy for ALL visitors, authenticated or not, so │\n * │ the edge serves one shared copy and never stampedes origin. │\n * │ │\n * │ DO NOT reintroduce per-user / cookie-based cache variation here (no │\n * │ `private`, no `no-store`, no `Vary: Cookie`, no \"authenticated → don't │\n * │ cache\" branch). That makes pages uncacheable for every logged-in visitor, │\n * │ which is slow and expensive — exactly the regression this guardrail │\n * │ prevents. The reason it is SAFE to hard-cache is that the SSR response is │\n * │ impersonal: `createH3SSRHandler` renders without reading the request's │\n * │ session/cookies, so there is no per-user data baked into the HTML. ALL │\n * │ per-user state (who's logged in, private records, access checks) is │\n * │ resolved CLIENT-SIDE after load. Keep it that way: if you need the SSR │\n * │ output to differ per user, the fix is to move that work client-side, not │\n * │ to disable caching here. │\n * └──────────────────────────────────────────────────────────────────────────┘\n */\nfunction applyDefaultSsrCacheHeader(\n headers: Headers,\n status: number,\n pathname: string,\n) {\n if (!isSsrHtmlOrDataResponse(headers, status, pathname)) return;\n\n // Netlify Functions/proxies are not cached by default. Set all three cache\n // headers: Cache-Control for browsers, CDN-Cache-Control for generic CDNs,\n // and Netlify-CDN-Cache-Control (with durable) so Netlify's shared cache\n // actually serves SSR HTML/.data from the edge instead of forwarding every\n // request to origin — for every visitor, authenticated or not.\n for (const [name, value] of Object.entries(DEFAULT_SSR_CACHE_HEADERS)) {\n headers.set(name, value);\n }\n}\n\nfunction applyDefaultSpeculationRulesHeader(\n headers: Headers,\n status: number,\n basePath: string,\n) {\n if (status < 200 || status >= 400) return;\n if (headers.has(\"speculation-rules\")) return;\n\n const contentType = headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n if (!contentType.includes(\"text/html\")) return;\n\n // Cloudflare Speed Brain injects its own Speculation-Rules header when the\n // origin omits one. Those browser prefetches carry `Sec-Purpose: prefetch`,\n // and Cloudflare refuses cache-ineligible dynamic pages with a 503 before\n // the request can reach Netlify/origin. We publish an explicit no-op ruleset\n // by default so Cloudflare does not inject its edge prefetch rules. Preserve\n // an app-provided Speculation-Rules header above if a template deliberately\n // owns this behavior.\n const rulesPath = prefixMountedPath(DEFAULT_SPECULATION_RULES_PATH, basePath);\n headers.set(\"speculation-rules\", `\"${rulesPath}\"`);\n}\n\n/**\n * Extract the plain JS body from a `<script ...>body</script>` string.\n * Returns `null` if the input is falsy or has no recognisable `</script>` end.\n * Used to compute the sha256 hash of framework-injected inline scripts so the\n * hash can be listed in app-owned `script-src` CSP directives.\n */\nfunction extractScriptBody(scriptTag: string | null): string | null {\n if (!scriptTag) return null;\n const start = scriptTag.indexOf(\">\") + 1;\n const end = scriptTag.lastIndexOf(\"</script>\");\n if (start <= 0 || end < start) return null;\n return scriptTag.slice(start, end);\n}\n\ntype CspDirective = {\n name: string;\n tokens: string[];\n};\n\nconst CSP_DIRECTIVES_WITH_VALUE_TOKENS = new Set([\n \"base-uri\",\n \"block-all-mixed-content\",\n \"child-src\",\n \"connect-src\",\n \"default-src\",\n \"fenced-frame-src\",\n \"font-src\",\n \"form-action\",\n \"frame-ancestors\",\n \"frame-src\",\n \"img-src\",\n \"manifest-src\",\n \"media-src\",\n \"navigate-to\",\n \"object-src\",\n \"plugin-types\",\n \"prefetch-src\",\n \"referrer\",\n \"reflected-xss\",\n \"require-sri-for\",\n \"require-trusted-types-for\",\n \"report-to\",\n \"report-uri\",\n \"sandbox\",\n \"script-src\",\n \"script-src-attr\",\n \"script-src-elem\",\n \"style-src\",\n \"style-src-attr\",\n \"style-src-elem\",\n \"trusted-types\",\n \"upgrade-insecure-requests\",\n \"webrtc\",\n \"worker-src\",\n]);\n\nfunction hasCommaJoinedCspPolicies(policy: string): boolean {\n let commaIndex = policy.indexOf(\",\");\n while (commaIndex !== -1) {\n const afterComma = policy.slice(commaIndex + 1);\n const directive = /^\\s+([a-z][a-z0-9-]*)(?=\\s|;|$)/i.exec(afterComma)?.[1];\n if (\n directive &&\n CSP_DIRECTIVES_WITH_VALUE_TOKENS.has(directive.toLowerCase())\n ) {\n return true;\n }\n commaIndex = policy.indexOf(\",\", commaIndex + 1);\n }\n return false;\n}\n\nfunction parseCsp(policy: string): CspDirective[] {\n return policy\n .split(\";\")\n .map((part) => part.trim())\n .filter(Boolean)\n .map((part) => {\n const [name = \"\", ...tokens] = part.split(/\\s+/);\n return { name: name.toLowerCase(), tokens };\n })\n .filter((directive) => directive.name);\n}\n\nfunction serializeCsp(directives: CspDirective[]): string {\n return directives\n .map((directive) =>\n [directive.name, ...directive.tokens].filter(Boolean).join(\" \"),\n )\n .join(\"; \");\n}\n\nfunction appendCspTokens(\n tokens: string[],\n additions: readonly string[],\n): string[] {\n if (!additions.length) return tokens;\n const next = tokens.filter((token) => token !== \"'none'\");\n const seen = new Set(next);\n for (const token of additions) {\n if (!token || seen.has(token)) continue;\n next.push(token);\n seen.add(token);\n }\n return next;\n}\n\nfunction findCspDirective(\n directives: CspDirective[],\n name: string,\n): CspDirective | undefined {\n return directives.find((directive) => directive.name === name);\n}\n\nfunction appendToExistingOrDefaultCspDirective(\n directives: CspDirective[],\n name: string,\n additions: readonly string[],\n): void {\n if (!additions.length) return;\n const existing = findCspDirective(directives, name);\n if (existing) {\n existing.tokens = appendCspTokens(existing.tokens, additions);\n return;\n }\n\n const defaultSrc = findCspDirective(directives, \"default-src\");\n if (!defaultSrc) return;\n directives.push({\n name,\n tokens: appendCspTokens([...defaultSrc.tokens], additions),\n });\n}\n\nfunction appendToExistingCspDirective(\n directives: CspDirective[],\n name: string,\n additions: readonly string[],\n): void {\n const existing = findCspDirective(directives, name);\n if (!existing) return;\n existing.tokens = appendCspTokens(existing.tokens, additions);\n}\n\nfunction ensureCspDirective(\n directives: CspDirective[],\n name: string,\n tokens: readonly string[],\n): void {\n if (findCspDirective(directives, name)) return;\n directives.push({ name, tokens: [...tokens] });\n}\n\nfunction hasStrictNonceScriptPolicy(tokens: readonly string[]): boolean {\n return tokens.some(\n (token) => token === \"'strict-dynamic'\" || token.startsWith(\"'nonce-\"),\n );\n}\n\nfunction appendToScriptCspDirective(\n directives: CspDirective[],\n name: string,\n additions: readonly string[],\n): boolean {\n const existing = findCspDirective(directives, name);\n if (existing) {\n if (hasStrictNonceScriptPolicy(existing.tokens)) return false;\n existing.tokens = appendCspTokens(existing.tokens, additions);\n return true;\n }\n\n const defaultSrc = findCspDirective(directives, \"default-src\");\n if (!defaultSrc || hasStrictNonceScriptPolicy(defaultSrc.tokens)) {\n return false;\n }\n directives.push({\n name,\n tokens: appendCspTokens([...defaultSrc.tokens], additions),\n });\n return true;\n}\n\nfunction appendToEffectiveScriptElementCspDirective(\n directives: CspDirective[],\n additions: readonly string[],\n): boolean {\n const scriptSrcElem = findCspDirective(directives, \"script-src-elem\");\n if (scriptSrcElem) {\n if (hasStrictNonceScriptPolicy(scriptSrcElem.tokens)) return false;\n scriptSrcElem.tokens = appendCspTokens(scriptSrcElem.tokens, additions);\n return true;\n }\n\n return appendToScriptCspDirective(directives, \"script-src\", additions);\n}\n\nfunction augmentExistingEnforcedCspForFrameworkScripts(\n policy: string,\n options: {\n gaScriptSrcTokens: readonly string[];\n gaEnabled: boolean;\n },\n): string {\n // Multiple CSP headers are surfaced by Headers.get() as one comma-joined\n // string. CSP is not a comma-list header, so serializing a parsed combined\n // value would turn two policies into one invalid policy. Leave those headers\n // app-owned; a comma inside a source/report URL is still safe to parse.\n if (hasCommaJoinedCspPolicies(policy)) return policy;\n\n const directives = parseCsp(policy);\n if (!directives.length) return policy;\n\n if (options.gaEnabled) {\n const addedScriptElement = appendToEffectiveScriptElementCspDirective(\n directives,\n options.gaScriptSrcTokens,\n );\n if (addedScriptElement) {\n appendToExistingOrDefaultCspDirective(\n directives,\n \"connect-src\",\n GA_CSP_CONNECT_HOSTS,\n );\n appendToExistingOrDefaultCspDirective(\n directives,\n \"img-src\",\n GA_CSP_IMG_HOSTS,\n );\n }\n }\n\n ensureCspDirective(directives, \"object-src\", [\"'none'\"]);\n ensureCspDirective(directives, \"base-uri\", [\"'self'\"]);\n\n return serializeCsp(directives);\n}\n\nfunction augmentExistingReportOnlyCspForFrameworkScripts(\n policy: string,\n options: {\n scriptSrcTokens: readonly string[];\n gaEnabled: boolean;\n },\n): string {\n if (hasCommaJoinedCspPolicies(policy)) return policy;\n\n const directives = parseCsp(policy);\n if (!directives.length) return policy;\n\n appendToExistingOrDefaultCspDirective(\n directives,\n \"script-src\",\n options.scriptSrcTokens,\n );\n appendToExistingCspDirective(\n directives,\n \"script-src-elem\",\n options.scriptSrcTokens,\n );\n\n if (options.gaEnabled) {\n appendToExistingOrDefaultCspDirective(\n directives,\n \"connect-src\",\n GA_CSP_CONNECT_HOSTS,\n );\n appendToExistingOrDefaultCspDirective(\n directives,\n \"img-src\",\n GA_CSP_IMG_HOSTS,\n );\n }\n\n return serializeCsp(directives);\n}\n\n/**\n * Apply a Content-Security-Policy header to HTML document responses.\n *\n * Two directives are always enforced in production:\n *\n * - `object-src 'none'` — disables Flash / Java / PDF plugin execution,\n * which are a reliable code-execution vector even in modern browsers.\n * - `base-uri 'self'` — prevents a `<base href=\"...\">` injection from\n * hijacking all relative URLs in the document (a common attack target when\n * user-controlled content reaches the HTML).\n *\n * A third directive, `script-src`, is emitted via `Content-Security-Policy-\n * Report-Only` rather than enforced when the app has no existing document CSP.\n * The framework injects inline scripts for analytics, Sentry, and template\n * setup, and hosted apps need Google Tag Manager to load without noisy CSP\n * diagnostics. The report-only policy is intentionally permissive for scripts:\n * it includes `'unsafe-inline'` plus the known GA/GTM loader hosts.\n *\n * If an app or host already sends an enforced CSP with `script-src`,\n * `script-src-elem`, `connect-src`, `img-src`, or `default-src`, we merge only\n * GA-specific allowances into existing host/hash policies. Strict nonce or\n * `strict-dynamic` script policies stay app-owned because blindly appending\n * hashes or hosts would widen the policy without reliably loading our injected\n * scripts.\n *\n * Templates additionally render a theme-init inline script whose exact content\n * varies by template (default theme param, custom docs variant, etc.) and which\n * is rendered by React Router, not this handler, so its hash is not available\n * here. Shipping script-src as Report-Only surfaces the remaining violations\n * without breaking template customisations; teams can graduate to enforcement\n * once their hashes are enumerated.\n *\n * Skipped in development (`NODE_ENV !== 'production'`) so HMR eval and Vite\n * dev-server injects are never blocked. Set `AGENT_NATIVE_DISABLE_DOC_CSP=1`\n * to opt out in production for a template with exotic needs.\n */\nfunction applyDocumentCsp(headers: Headers, sentryScript: string | null): void {\n if (process.env.NODE_ENV !== \"production\") return;\n if (process.env.AGENT_NATIVE_DISABLE_DOC_CSP === \"1\") return;\n\n // script-src as Report-Only: keep this deliberately loose so the framework's\n // injected analytics and template bootstrap scripts do not look blocked in\n // browser diagnostics.\n const sentryBody = extractScriptBody(sentryScript);\n const sentryHash = sentryBody ? computeInlineScriptHash(sentryBody) : null;\n const gaInlineBody = getGaInlineConfigScriptBody();\n const gaHash = gaInlineBody ? computeInlineScriptHash(gaInlineBody) : null;\n const gaHosts = gaInlineBody ? [...GA_CSP_SCRIPT_HOSTS] : [];\n const gaScriptSrcTokens = [\n \"'unsafe-inline'\",\n ...(gaHash ? [gaHash] : []),\n ...gaHosts,\n ];\n const scriptSrcTokens = [\n \"'self'\",\n \"'unsafe-inline'\",\n ...(sentryHash ? [sentryHash] : []),\n ...(gaHash ? [gaHash] : []),\n ...gaHosts,\n ];\n\n const cspAugmentOptions = {\n scriptSrcTokens,\n gaScriptSrcTokens,\n gaEnabled: Boolean(gaInlineBody),\n };\n const existing = headers.get(\"content-security-policy\") ?? \"\";\n if (!existing) {\n headers.set(\n \"content-security-policy\",\n \"object-src 'none'; base-uri 'self'\",\n );\n } else {\n headers.set(\n \"content-security-policy\",\n augmentExistingEnforcedCspForFrameworkScripts(\n existing,\n cspAugmentOptions,\n ),\n );\n }\n\n const scriptSrc = `script-src ${scriptSrcTokens.join(\" \")}`;\n const existingRo = headers.get(\"content-security-policy-report-only\") ?? \"\";\n if (!existingRo) {\n headers.set(\"content-security-policy-report-only\", scriptSrc);\n } else {\n headers.set(\n \"content-security-policy-report-only\",\n augmentExistingReportOnlyCspForFrameworkScripts(\n existingRo,\n cspAugmentOptions,\n ),\n );\n }\n}\n\nfunction isFrameworkOrAssetPath(pathname: string): boolean {\n return (\n pathname.startsWith(\"/.well-known/\") ||\n pathname.startsWith(\"/_agent_native/\") ||\n pathname.startsWith(\"/_agent-native/\") ||\n pathname.startsWith(\"/api/\") ||\n pathname.startsWith(\"/@vite/\") ||\n pathname.startsWith(\"/@id/\") ||\n pathname.startsWith(\"/@fs/\") ||\n pathname === \"/@react-refresh\" ||\n pathname === \"/__vite_ping\" ||\n pathname === \"/__open-in-editor\" ||\n pathname === \"/favicon.ico\" ||\n pathname === \"/favicon.png\" ||\n (/\\.\\w+$/.test(pathname) && !pathname.endsWith(\".data\"))\n );\n}\n\nasync function rewriteMountedResponse(\n response: Response,\n basePath: string,\n pathname: string,\n requestUrl: string,\n): Promise<Response> {\n const sentryClientConfigScript = getSentryClientConfigScript();\n const headers = new Headers(response.headers);\n applyDefaultSsrCacheHeader(headers, response.status, pathname);\n applyDefaultSpeculationRulesHeader(headers, response.status, basePath);\n\n const location = headers.get(\"location\");\n if (location?.startsWith(\"/\") && !location.startsWith(\"//\")) {\n headers.set(\"location\", prefixMountedPath(location, basePath));\n }\n\n const contentType = headers.get(\"content-type\") ?? \"\";\n if (!contentType.toLowerCase().includes(\"text/html\") || !response.body) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n const html = await response.text();\n headers.delete(\"content-length\");\n applyDocumentCsp(headers, sentryClientConfigScript);\n return new Response(\n injectHeadScript(\n injectDefaultSocialImageMeta(\n prefixMountedHtml(html, basePath),\n defaultSocialImageUrl(requestUrl, basePath),\n ),\n sentryClientConfigScript,\n ),\n {\n status: response.status,\n statusText: response.statusText,\n headers,\n },\n );\n}\n\n/**\n * Create an h3 catch-all that hands page routes to React Router and\n * returns 404 for framework / asset paths that React Router doesn't own.\n */\nexport function createH3SSRHandler(getBuild: () => Promise<unknown> | unknown) {\n const handler = createRequestHandler(getBuild as any);\n return defineEventHandler(async (event) => {\n const basePath = getAppBasePath();\n const p = stripAppBasePath(event.url.pathname);\n if (isFrameworkOrAssetPath(p)) {\n return new Response(null, { status: 404 });\n }\n try {\n const request = requestWithPathname(event.req as Request, p, basePath);\n // SSR renders an IMPERSONAL public shell — we deliberately do NOT read the\n // request's session/cookies here, and pin an explicitly anonymous request\n // context. That keeps the SSR HTML/.data identical for every visitor so it\n // can be hard-cached at the CDN for everyone (see applyDefaultSsrCacheHeader).\n //\n // Consequence: SSR loaders that call `getRequestUserEmail()` / `accessFilter()`\n // always see the unauthenticated branch and render public content only. Any\n // per-user view (private records, share-grant access, who's logged in) MUST\n // be resolved CLIENT-SIDE after load, never baked into SSR. Do not re-pin the\n // session here to \"fix\" a per-user page — that silently makes the page\n // uncacheable and/or leaks one user's data into another's cached copy.\n const ctx = { userEmail: undefined, orgId: undefined };\n if (request.method === \"HEAD\") {\n const getRequest = new Request(request.url, {\n method: \"GET\",\n headers: request.headers,\n signal: request.signal,\n });\n const response = await runWithRequestContext(ctx, () =>\n handler(getRequest),\n );\n return await rewriteMountedResponse(\n new Response(null, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n }),\n basePath,\n p,\n request.url,\n );\n }\n return await rewriteMountedResponse(\n await runWithRequestContext(ctx, () => handler(request)),\n basePath,\n p,\n request.url,\n );\n } catch (err) {\n // Log the full stack server-side, but never leak it to the client.\n // Stack traces expose file paths, library versions, and code structure\n // that aid reconnaissance attacks. In dev we surface the message text\n // so devtools shows something useful; in prod we return a bare 500.\n console.error(\"[ssr-handler] SSR error:\", err);\n const isProd = process.env.NODE_ENV === \"production\";\n const body = isProd\n ? \"Internal Server Error\"\n : `Internal Server Error: ${(err as Error)?.message ?? err}`;\n return new Response(body, {\n status: 500,\n headers: { \"content-type\": \"text/plain\" },\n });\n }\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ssr-handler.js","sourceRoot":"","sources":["../../src/server/ssr-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,IAAI,CAAC;AACxC;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EACL,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,6BAA6B,EAC7B,gCAAgC,EAChC,8BAA8B,EAC9B,8BAA8B,EAC9B,+BAA+B,EAC/B,qCAAqC,GACtC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,yBAAyB,EACzB,gBAAgB,IAAI,yBAAyB,GAC9C,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEjE,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AAEpC,SAAS,cAAc;IACrB,OAAO,yBAAyB,EAAE,CAAC;AACrC,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAgB;IACxC,OAAO,yBAAyB,CAAC,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB,EAAE,QAAgB;IACvD,IAAI,CAAC,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/B,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACtC,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,CAAC,EAAE,CAAC;QACxC,OAAO,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;IAChD,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,mBAAmB,CAC1B,OAAgB,EAChB,QAAgB,EAChB,QAAgB;IAEhB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,QAAQ,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,aAAa,GAAG,KAAK;iBACxB,KAAK,CAAC,GAAG,CAAC;iBACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;iBAC5C,IAAI,CAAC,GAAG,CAAC,CAAC;YACb,IAAI,aAAa,KAAK,KAAK,EAAE,CAAC;gBAC5B,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;gBAC7C,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC9B,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACxB,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,IAAI,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC;IAC7B,MAAM,IAAI,GAAsC;QAC9C,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC;IACF,IAAI,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QAC5E,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY,EAAE,QAAgB;IACvD,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7E,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtE,OAAO,GAAG,QAAQ,GAAG,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY,EAAE,QAAgB;IACvD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO,IAAI;SACR,OAAO,CACN,iEAAiE,EACjE,CAAC,MAAM,EAAE,IAAY,EAAE,KAAa,EAAE,IAAY,EAAE,EAAE,CACpD,GAAG,IAAI,IAAI,KAAK,GAAG,iBAAiB,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,KAAK,EAAE,CACjE;SACA,OAAO,CAAC,qCAAqC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACtE,MAAM,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7D,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,MAAqB;IAC3D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,YAAY,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,gBAAgB,GAAG,oDAAoD,CAAC;AAC9E,MAAM,oBAAoB,GACxB,oDAAoD,CAAC;AACvD,MAAM,qBAAqB,GACzB,qDAAqD,CAAC;AAExD,SAAS,qBAAqB,CAAC,UAAkB,EAAE,QAAgB;IACjE,OAAO,qCAAqC,CAC1C,IAAI,GAAG,CACL,iBAAiB,CAAC,8BAA8B,EAAE,QAAQ,CAAC,EAC3D,UAAU,CACX,CAAC,QAAQ,EAAE,CACb,CAAC;AACJ,CAAC;AAED,SAAS,4BAA4B,CAAC,IAAY,EAAE,QAAgB;IAClE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,YAAY,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,iBAAiB,GACrB,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,sCAAsC,QAAQ,IAAI,CAAC,CAAC;QAC9D,IAAI,CAAC,IAAI,CAAC,iDAAiD,QAAQ,IAAI,CAAC,CAAC;QACzE,IAAI,CAAC,IAAI,CACP,2CAA2C,8BAA8B,IAAI,CAC9E,CAAC;QACF,IAAI,CAAC,IAAI,CACP,4CAA4C,+BAA+B,IAAI,CAChF,CAAC;QACF,IAAI,CAAC,IAAI,CACP,6CAA6C,gCAAgC,IAAI,CAClF,CAAC;QACF,IAAI,CAAC,IAAI,CACP,0CAA0C,6BAA6B,IAAI,CAC5E,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,uCAAuC,QAAQ,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI,CACP,2CAA2C,6BAA6B,IAAI,CAC7E,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;AAChF,CAAC;AAED,SAAS,uBAAuB,CAC9B,OAAgB,EAChB,MAAc,EACd,QAAgB;IAEhB,IAAI,MAAM,GAAG,GAAG,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC;IAChD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACrE,IAAI,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IACnD,OAAO,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAS,0BAA0B,CACjC,OAAgB,EAChB,MAAc,EACd,QAAgB;IAEhB,IAAI,CAAC,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC;QAAE,OAAO;IAEhE,2EAA2E;IAC3E,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,+DAA+D;IAC/D,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,yBAAyB,CAAC,EAAE,CAAC;QACtE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,SAAS,kCAAkC,CACzC,OAAgB,EAChB,MAAc,EACd,QAAgB;IAEhB,IAAI,MAAM,GAAG,GAAG,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO;IAC1C,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAAE,OAAO;IAE7C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACrE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO;IAE/C,2EAA2E;IAC3E,4EAA4E;IAC5E,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,4EAA4E;IAC5E,sBAAsB;IACtB,MAAM,SAAS,GAAG,iBAAiB,CAAC,8BAA8B,EAAE,QAAQ,CAAC,CAAC;IAC9E,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,SAAS,GAAG,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,iBAAiB,CAAC,OAAgB;IACzC,OAAO,CAAC,MAAM,CAAC,yBAAyB,CAAC,CAAC;IAC1C,OAAO,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,sBAAsB,CAAC,QAAgB;IAC9C,OAAO,CACL,QAAQ,CAAC,UAAU,CAAC,eAAe,CAAC;QACpC,QAAQ,CAAC,UAAU,CAAC,iBAAiB,CAAC;QACtC,QAAQ,CAAC,UAAU,CAAC,iBAAiB,CAAC;QACtC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC;QAC5B,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC;QAC9B,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC;QAC5B,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC;QAC5B,QAAQ,KAAK,iBAAiB;QAC9B,QAAQ,KAAK,cAAc;QAC3B,QAAQ,KAAK,mBAAmB;QAChC,QAAQ,KAAK,cAAc;QAC3B,QAAQ,KAAK,cAAc;QAC3B,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CACzD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,sBAAsB,CACnC,QAAkB,EAClB,QAAgB,EAChB,QAAgB,EAChB,UAAkB;IAElB,MAAM,wBAAwB,GAAG,2BAA2B,EAAE,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9C,0BAA0B,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/D,kCAAkC,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEvE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACzC,IAAI,QAAQ,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5D,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IACtD,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACrD,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;YACjC,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IACD,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC3B,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;YACjC,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACjC,OAAO,IAAI,QAAQ,CACjB,gBAAgB,CACd,4BAA4B,CAC1B,iBAAiB,CAAC,IAAI,EAAE,QAAQ,CAAC,EACjC,qBAAqB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAC5C,EACD,wBAAwB,CACzB,EACD;QACE,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO;KACR,CACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAA0C;IAC3E,MAAM,OAAO,GAAG,oBAAoB,CAAC,QAAe,CAAC,CAAC;IACtD,OAAO,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACxC,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,gBAAgB,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9B,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,GAAc,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;YACvE,2EAA2E;YAC3E,0EAA0E;YAC1E,2EAA2E;YAC3E,+EAA+E;YAC/E,EAAE;YACF,gFAAgF;YAChF,4EAA4E;YAC5E,4EAA4E;YAC5E,8EAA8E;YAC9E,uEAAuE;YACvE,uEAAuE;YACvE,MAAM,GAAG,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YACvD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC9B,MAAM,UAAU,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE;oBAC1C,MAAM,EAAE,KAAK;oBACb,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,MAAM,EAAE,OAAO,CAAC,MAAM;iBACvB,CAAC,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,CACrD,OAAO,CAAC,UAAU,CAAC,CACpB,CAAC;gBACF,OAAO,MAAM,sBAAsB,CACjC,IAAI,QAAQ,CAAC,IAAI,EAAE;oBACjB,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC/B,OAAO,EAAE,QAAQ,CAAC,OAAO;iBAC1B,CAAC,EACF,QAAQ,EACR,CAAC,EACD,OAAO,CAAC,GAAG,CACZ,CAAC;YACJ,CAAC;YACD,OAAO,MAAM,sBAAsB,CACjC,MAAM,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EACxD,QAAQ,EACR,CAAC,EACD,OAAO,CAAC,GAAG,CACZ,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,mEAAmE;YACnE,uEAAuE;YACvE,sEAAsE;YACtE,oEAAoE;YACpE,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;YACrD,MAAM,IAAI,GAAG,MAAM;gBACjB,CAAC,CAAC,uBAAuB;gBACzB,CAAC,CAAC,0BAA2B,GAAa,EAAE,OAAO,IAAI,GAAG,EAAE,CAAC;YAC/D,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;gBACxB,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE;aAC1C,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { defineEventHandler } from \"h3\";\n/**\n * Shared SSR catch-all handler for React Router framework mode.\n *\n * Templates wire this up via:\n *\n * // server/routes/[...page].get.ts\n * import { createH3SSRHandler } from \"@agent-native/core/server/ssr-handler\";\n * export default createH3SSRHandler(\n * () => import(\"virtual:react-router/server-build\"),\n * );\n *\n * The `getBuild` callback MUST live in the template's own source so Vite's\n * @react-router/dev plugin can resolve the `virtual:` module. Pulling the\n * import into core (e.g. via a re-export) puts it in node_modules where\n * Vite's SSR externalizer leaves it untouched and Node's ESM loader rejects\n * the unknown scheme — silently 302'ing every request to \"/\".\n */\nimport { createRequestHandler } from \"react-router\";\n\nimport {\n DEFAULT_SSR_CACHE_HEADERS,\n DEFAULT_SPECULATION_RULES_PATH,\n} from \"../shared/cache-control.js\";\nimport {\n AGENT_NATIVE_SOCIAL_IMAGE_ALT,\n AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT,\n AGENT_NATIVE_SOCIAL_IMAGE_PATH,\n AGENT_NATIVE_SOCIAL_IMAGE_TYPE,\n AGENT_NATIVE_SOCIAL_IMAGE_WIDTH,\n withAgentNativeSocialImageCacheBuster,\n} from \"../shared/social-meta.js\";\nimport {\n getAppBasePathFromViteEnv,\n stripAppBasePath as canonicalStripAppBasePath,\n} from \"./app-base-path.js\";\nimport { runWithRequestContext } from \"./request-context.js\";\nimport { getSentryClientConfigScript } from \"./sentry-config.js\";\n\nexport {\n DEFAULT_SSR_CACHE_HEADERS,\n DEFAULT_SPECULATION_RULES_HEADER,\n DEFAULT_SSR_CACHE_CONTROL,\n} from \"../shared/cache-control.js\";\n\nfunction getAppBasePath(): string {\n return getAppBasePathFromViteEnv();\n}\n\nfunction stripAppBasePath(pathname: string): string {\n return canonicalStripAppBasePath(pathname, getAppBasePath());\n}\n\nfunction stripBasePath(pathname: string, basePath: string): string {\n if (!basePath) return pathname;\n if (pathname === basePath) return \"/\";\n if (pathname.startsWith(`${basePath}/`)) {\n return pathname.slice(basePath.length) || \"/\";\n }\n return pathname;\n}\n\nfunction requestWithPathname(\n request: Request,\n pathname: string,\n basePath: string,\n): Request {\n const url = new URL(request.url);\n let changed = false;\n if (basePath && pathname === \"/__manifest\") {\n const paths = url.searchParams.get(\"paths\");\n if (paths) {\n const strippedPaths = paths\n .split(\",\")\n .map((path) => stripBasePath(path, basePath))\n .join(\",\");\n if (strippedPaths !== paths) {\n url.searchParams.set(\"paths\", strippedPaths);\n changed = true;\n }\n }\n }\n if (url.pathname !== pathname) {\n url.pathname = pathname;\n changed = true;\n }\n if (!changed) return request;\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n headers: request.headers,\n signal: request.signal,\n };\n if (request.body && ![\"GET\", \"HEAD\"].includes(request.method.toUpperCase())) {\n init.body = request.body;\n init.duplex = \"half\";\n }\n return new Request(url, init);\n}\n\nfunction prefixMountedPath(path: string, basePath: string): string {\n if (!basePath || !path.startsWith(\"/\") || path.startsWith(\"//\")) return path;\n if (path === basePath || path.startsWith(`${basePath}/`)) return path;\n return `${basePath}${path}`;\n}\n\nfunction prefixMountedHtml(html: string, basePath: string): string {\n if (!basePath) return html;\n return html\n .replace(\n /\\b(href|src|action|formaction|poster)=([\"'])(\\/(?!\\/)[^\"']*)\\2/g,\n (_match, attr: string, quote: string, path: string) =>\n `${attr}=${quote}${prefixMountedPath(path, basePath)}${quote}`,\n )\n .replace(/url\\(([\"']?)(\\/(?!\\/)[^)'\" ]+)\\1\\)/g, (_match, quote, path) => {\n const q = quote || \"\";\n return `url(${q}${prefixMountedPath(path, basePath)}${q})`;\n });\n}\n\nfunction injectHeadScript(html: string, script: string | null): string {\n if (!script) return html;\n const headCloseIdx = html.indexOf(\"</head>\");\n if (headCloseIdx === -1) return html;\n return html.slice(0, headCloseIdx) + script + html.slice(headCloseIdx);\n}\n\nconst OG_IMAGE_META_RE = /<meta\\b(?=[^>]*\\bproperty=([\"'])og:image\\1)[^>]*>/i;\nconst TWITTER_CARD_META_RE =\n /<meta\\b(?=[^>]*\\bname=([\"'])twitter:card\\1)[^>]*>/i;\nconst TWITTER_IMAGE_META_RE =\n /<meta\\b(?=[^>]*\\bname=([\"'])twitter:image\\1)[^>]*>/i;\n\nfunction defaultSocialImageUrl(requestUrl: string, basePath: string): string {\n return withAgentNativeSocialImageCacheBuster(\n new URL(\n prefixMountedPath(AGENT_NATIVE_SOCIAL_IMAGE_PATH, basePath),\n requestUrl,\n ).toString(),\n );\n}\n\nfunction injectDefaultSocialImageMeta(html: string, imageUrl: string): string {\n const headCloseIdx = html.indexOf(\"</head>\");\n if (headCloseIdx === -1) return html;\n\n const hasAnySocialImage =\n OG_IMAGE_META_RE.test(html) || TWITTER_IMAGE_META_RE.test(html);\n const tags: string[] = [];\n\n if (!hasAnySocialImage) {\n tags.push(`<meta property=\"og:image\" content=\"${imageUrl}\">`);\n tags.push(`<meta property=\"og:image:secure_url\" content=\"${imageUrl}\">`);\n tags.push(\n `<meta property=\"og:image:type\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_TYPE}\">`,\n );\n tags.push(\n `<meta property=\"og:image:width\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_WIDTH}\">`,\n );\n tags.push(\n `<meta property=\"og:image:height\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT}\">`,\n );\n tags.push(\n `<meta property=\"og:image:alt\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_ALT}\">`,\n );\n }\n if (!TWITTER_CARD_META_RE.test(html)) {\n tags.push(`<meta name=\"twitter:card\" content=\"summary_large_image\">`);\n }\n if (!hasAnySocialImage) {\n tags.push(`<meta name=\"twitter:image\" content=\"${imageUrl}\">`);\n tags.push(\n `<meta name=\"twitter:image:alt\" content=\"${AGENT_NATIVE_SOCIAL_IMAGE_ALT}\">`,\n );\n }\n\n if (tags.length === 0) return html;\n return html.slice(0, headCloseIdx) + tags.join(\"\") + html.slice(headCloseIdx);\n}\n\nfunction isSsrHtmlOrDataResponse(\n headers: Headers,\n status: number,\n pathname: string,\n): boolean {\n if (status < 200 || status >= 400) return false;\n const contentType = headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n if (contentType.includes(\"text/html\")) return true;\n return pathname.endsWith(\".data\") && contentType.includes(\"text/x-script\");\n}\n\n/**\n * Apply the SSR cache policy to the response headers.\n *\n * ┌──────────────────────────────────────────────────────────────────────────┐\n * │ SSR IS A PUBLIC, HARD-CDN-CACHED SHELL — SERVED IDENTICALLY TO EVERYONE. │\n * │ │\n * │ Every SSR HTML / React Router `.data` response gets the same public │\n * │ stale-while-revalidate policy for ALL visitors, authenticated or not, so │\n * │ the edge serves one shared copy and never stampedes origin. │\n * │ │\n * │ DO NOT reintroduce per-user / cookie-based cache variation here (no │\n * │ `private`, no `no-store`, no `Vary: Cookie`, no \"authenticated → don't │\n * │ cache\" branch). That makes pages uncacheable for every logged-in visitor, │\n * │ which is slow and expensive — exactly the regression this guardrail │\n * │ prevents. The reason it is SAFE to hard-cache is that the SSR response is │\n * │ impersonal: `createH3SSRHandler` renders without reading the request's │\n * │ session/cookies, so there is no per-user data baked into the HTML. ALL │\n * │ per-user state (who's logged in, private records, access checks) is │\n * │ resolved CLIENT-SIDE after load. Keep it that way: if you need the SSR │\n * │ output to differ per user, the fix is to move that work client-side, not │\n * │ to disable caching here. │\n * └──────────────────────────────────────────────────────────────────────────┘\n */\nfunction applyDefaultSsrCacheHeader(\n headers: Headers,\n status: number,\n pathname: string,\n) {\n if (!isSsrHtmlOrDataResponse(headers, status, pathname)) return;\n\n // Netlify Functions/proxies are not cached by default. Set all three cache\n // headers: Cache-Control for browsers, CDN-Cache-Control for generic CDNs,\n // and Netlify-CDN-Cache-Control (with durable) so Netlify's shared cache\n // actually serves SSR HTML/.data from the edge instead of forwarding every\n // request to origin — for every visitor, authenticated or not.\n for (const [name, value] of Object.entries(DEFAULT_SSR_CACHE_HEADERS)) {\n headers.set(name, value);\n }\n}\n\nfunction applyDefaultSpeculationRulesHeader(\n headers: Headers,\n status: number,\n basePath: string,\n) {\n if (status < 200 || status >= 400) return;\n if (headers.has(\"speculation-rules\")) return;\n\n const contentType = headers.get(\"content-type\")?.toLowerCase() ?? \"\";\n if (!contentType.includes(\"text/html\")) return;\n\n // Cloudflare Speed Brain injects its own Speculation-Rules header when the\n // origin omits one. Those browser prefetches carry `Sec-Purpose: prefetch`,\n // and Cloudflare refuses cache-ineligible dynamic pages with a 503 before\n // the request can reach Netlify/origin. We publish an explicit no-op ruleset\n // by default so Cloudflare does not inject its edge prefetch rules. Preserve\n // an app-provided Speculation-Rules header above if a template deliberately\n // owns this behavior.\n const rulesPath = prefixMountedPath(DEFAULT_SPECULATION_RULES_PATH, basePath);\n headers.set(\"speculation-rules\", `\"${rulesPath}\"`);\n}\n\n/**\n * Strip document-level CSP from app HTML responses.\n *\n * Hosted templates inject framework bootstrap scripts, analytics, Sentry config,\n * and app-owned inline scripts whose exact bytes vary by build/template. Any\n * shared CSP header, even Report-Only, can block or noisily report Google Tag\n * Manager and those bootstraps. Extension iframes and webviews keep their own\n * route-specific sandboxes; normal app documents deliberately do not emit CSP.\n */\nfunction removeDocumentCsp(headers: Headers): void {\n headers.delete(\"content-security-policy\");\n headers.delete(\"content-security-policy-report-only\");\n}\n\nfunction isFrameworkOrAssetPath(pathname: string): boolean {\n return (\n pathname.startsWith(\"/.well-known/\") ||\n pathname.startsWith(\"/_agent_native/\") ||\n pathname.startsWith(\"/_agent-native/\") ||\n pathname.startsWith(\"/api/\") ||\n pathname.startsWith(\"/@vite/\") ||\n pathname.startsWith(\"/@id/\") ||\n pathname.startsWith(\"/@fs/\") ||\n pathname === \"/@react-refresh\" ||\n pathname === \"/__vite_ping\" ||\n pathname === \"/__open-in-editor\" ||\n pathname === \"/favicon.ico\" ||\n pathname === \"/favicon.png\" ||\n (/\\.\\w+$/.test(pathname) && !pathname.endsWith(\".data\"))\n );\n}\n\nasync function rewriteMountedResponse(\n response: Response,\n basePath: string,\n pathname: string,\n requestUrl: string,\n): Promise<Response> {\n const sentryClientConfigScript = getSentryClientConfigScript();\n const headers = new Headers(response.headers);\n applyDefaultSsrCacheHeader(headers, response.status, pathname);\n applyDefaultSpeculationRulesHeader(headers, response.status, basePath);\n\n const location = headers.get(\"location\");\n if (location?.startsWith(\"/\") && !location.startsWith(\"//\")) {\n headers.set(\"location\", prefixMountedPath(location, basePath));\n }\n\n const contentType = headers.get(\"content-type\") ?? \"\";\n if (!contentType.toLowerCase().includes(\"text/html\")) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n removeDocumentCsp(headers);\n if (!response.body) {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n const html = await response.text();\n headers.delete(\"content-length\");\n return new Response(\n injectHeadScript(\n injectDefaultSocialImageMeta(\n prefixMountedHtml(html, basePath),\n defaultSocialImageUrl(requestUrl, basePath),\n ),\n sentryClientConfigScript,\n ),\n {\n status: response.status,\n statusText: response.statusText,\n headers,\n },\n );\n}\n\n/**\n * Create an h3 catch-all that hands page routes to React Router and\n * returns 404 for framework / asset paths that React Router doesn't own.\n */\nexport function createH3SSRHandler(getBuild: () => Promise<unknown> | unknown) {\n const handler = createRequestHandler(getBuild as any);\n return defineEventHandler(async (event) => {\n const basePath = getAppBasePath();\n const p = stripAppBasePath(event.url.pathname);\n if (isFrameworkOrAssetPath(p)) {\n return new Response(null, { status: 404 });\n }\n try {\n const request = requestWithPathname(event.req as Request, p, basePath);\n // SSR renders an IMPERSONAL public shell — we deliberately do NOT read the\n // request's session/cookies here, and pin an explicitly anonymous request\n // context. That keeps the SSR HTML/.data identical for every visitor so it\n // can be hard-cached at the CDN for everyone (see applyDefaultSsrCacheHeader).\n //\n // Consequence: SSR loaders that call `getRequestUserEmail()` / `accessFilter()`\n // always see the unauthenticated branch and render public content only. Any\n // per-user view (private records, share-grant access, who's logged in) MUST\n // be resolved CLIENT-SIDE after load, never baked into SSR. Do not re-pin the\n // session here to \"fix\" a per-user page — that silently makes the page\n // uncacheable and/or leaks one user's data into another's cached copy.\n const ctx = { userEmail: undefined, orgId: undefined };\n if (request.method === \"HEAD\") {\n const getRequest = new Request(request.url, {\n method: \"GET\",\n headers: request.headers,\n signal: request.signal,\n });\n const response = await runWithRequestContext(ctx, () =>\n handler(getRequest),\n );\n return await rewriteMountedResponse(\n new Response(null, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n }),\n basePath,\n p,\n request.url,\n );\n }\n return await rewriteMountedResponse(\n await runWithRequestContext(ctx, () => handler(request)),\n basePath,\n p,\n request.url,\n );\n } catch (err) {\n // Log the full stack server-side, but never leak it to the client.\n // Stack traces expose file paths, library versions, and code structure\n // that aid reconnaissance attacks. In dev we surface the message text\n // so devtools shows something useful; in prod we return a bare 500.\n console.error(\"[ssr-handler] SSR error:\", err);\n const isProd = process.env.NODE_ENV === \"production\";\n const body = isProd\n ? \"Internal Server Error\"\n : `Internal Server Error: ${(err as Error)?.message ?? err}`;\n return new Response(body, {\n status: 500,\n headers: { \"content-type\": \"text/plain\" },\n });\n }\n });\n}\n"]}
|
|
@@ -188,6 +188,61 @@ track("checkout.completed", { total: 49.99, items: 3 });
|
|
|
188
188
|
|
|
189
189
|
يختلف هذا عن القياس عن بعد للمتصفح الداخلي لإطار العمل (`trackEvent()` / مشاهدات الصفحة التلقائية - راجع [Browser defaults](#browser-defaults) أدناه), والذي يدعم تحليلات المنتج الخاصة بـ Agent Native. استخدم `track()` لأحداث التحليلات الخاصة بتطبيقك والتي يجب أن تصل إلى مقدمي الخدمة الذين تم تكوينهم.
|
|
190
190
|
|
|
191
|
+
## إعادة تشغيل الجلسة {#session-replay}
|
|
192
|
+
|
|
193
|
+
يمكن لتطبيقات Agent Native الاشتراك في إعادة تشغيل جلسة المتصفح من الطرف الأول دون إضافة SDK تحليلات ثانٍ. استدعِ `configureTracking()` مرة واحدة في جذر المتصفح ومرر مفتاح Analytics العام بالإضافة إلى نقطة نهاية المُجمِّع:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { configureTracking } from "@agent-native/core/client";
|
|
197
|
+
|
|
198
|
+
configureTracking({
|
|
199
|
+
key: "anpk_...",
|
|
200
|
+
endpoint: "https://analytics.example.com/api/analytics/track",
|
|
201
|
+
sessionReplay: {
|
|
202
|
+
enabled: true,
|
|
203
|
+
sampleRate: 0.1,
|
|
204
|
+
},
|
|
205
|
+
getDefaultProps: (_event, props) => ({
|
|
206
|
+
...props,
|
|
207
|
+
app: "my-app",
|
|
208
|
+
template: "my-template",
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
عندما يكون `sessionReplay.enabled` صادقًا (truthy)، يقوم العميل باستيراد `@rrweb/record` ديناميكيًا بعد بدء التشغيل وينشر أجزاء إعادة التشغيل إلى نقطة نهاية إعادة التشغيل. إذا كانت `endpoint` تنتهي بـ `/api/analytics/track` أو `/track`، يتم اشتقاق نقطة نهاية إعادة التشغيل تلقائيًا كـ `/api/analytics/replay`. تجاوز ذلك صراحةً باستخدام `sessionReplay.endpoint` عندما يكون مُجمِّع إعادة التشغيل موجودًا في مكان آخر.
|
|
214
|
+
|
|
215
|
+
تستدعي جذور قوالب Agent Native بالفعل `configureTracking()`. يمكن لعمليات نشر القوالب المستضافة تفعيل إعادة التشغيل باستخدام متغيرات بيئة Vite/Netlify، بينما يجب أن يُفضّل مستهلكو المكتبة الصيغة الصريحة `configureTracking({ key, endpoint, sessionReplay })` أعلاه:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY=anpk_...
|
|
219
|
+
VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT=https://analytics.example.com/api/analytics/track
|
|
220
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_ENABLED=true
|
|
221
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=0.1
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
يقوم مساعد المتصفح أيضًا بإجراء قراءة غير محظِّرة وبأفضل جهد ممكن لجلسة مصادقة Agent Native الحالية. إعادة التشغيل مخصصة لتسجيل الدخول فقط بشكل افتراضي: عندما يكون `sessionReplay` مفعّلاً، لا يبدأ التسجيل ما لم تُحلّ الجلسة إلى عنوان بريد إلكتروني لمستخدم. تتضمن إعادة تشغيل الجلسات المسجَّلة `userId`/`userEmail` المدعومين بالبريد الإلكتروني بالإضافة إلى `orgId`.
|
|
225
|
+
|
|
226
|
+
اضبط `sessionReplay.requireSignedInUser: false` أو `VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH=false` فقط من أجل نشر إعادة تشغيل مجهولة الهوية مقصود. عندما يتم تعطيل تحكم المصادقة، تظل التسجيلات المجهولة قابلة للاستعلام حسب الزائر المجهول، والجلسة، والتطبيق/القالب، واسم المضيف، والمسار.
|
|
227
|
+
|
|
228
|
+
يتم أخذ عينات من إعادة تشغيل الجلسة بشكل حتمي لكل جلسة متصفح. يقوم `sampleRate` بقيمة `0.1` بتسجيل حوالي 10% من الجلسات المؤهلة؛ استخدم `1` عندما تكون المجموعة المؤهلة صغيرة عن قصد، مثل اختبار الاستخدام الداخلي لتسجيل الدخول فقط.
|
|
229
|
+
|
|
230
|
+
إعدادات الخصوصية الافتراضية متحفظة عن قصد لكنها لا تزال مفيدة لإعادة التشغيل:
|
|
231
|
+
|
|
232
|
+
- يتم إخفاء المدخلات بشكل افتراضي (`maskAllInputs: true`).
|
|
233
|
+
- يظل نص الصفحة مرئيًا ما لم يتم تمييز عنصر بـ `.an-mask` أو `data-an-mask`.
|
|
234
|
+
- يتم حظر المناطق الحساسة بمحددات مثل `[data-sensitive]`، و`.an-block`، و`.an-private`، و`data-an-block`، و`data-an-private`، والحقول الشبيهة ببطاقات الائتمان/كلمات المرور/أرقام الضمان الاجتماعي.
|
|
235
|
+
- يتم تنظيف عناوين URL باستخدام نفس مساعد `scrubUrl()` المستخدم بواسطة تحليلات المتصفح.
|
|
236
|
+
- التقاط إعادة التشغيل مخصص للويب فقط وباختيار المستخدم؛ فهو لا يسجل شاشات سطح المكتب الأصلية.
|
|
237
|
+
|
|
238
|
+
أثناء التسجيل، تلتقط إعادة تشغيل الجلسة أيضًا مخرجات وحدة تحكم المتصفح (`log`، و`info`، و`warn`، و`error`، و`debug`، بالإضافة إلى `error` / `unhandledrejection` الخاصة بالنافذة) وبيانات وصفية لطلبات الشبكة (`fetch` و XHR) كأحداث rrweb مخصصة موسومة، بحيث يمكن للوكلاء وعارض إعادة التشغيل تصحيح المشكلات التي يبلغ عنها المستخدمون. الالتقاط مفعّل افتراضيًا عند تفعيل إعادة التشغيل؛ اضبطه أو عطّله باستخدام خياري `sessionReplay.console` و`sessionReplay.network`، حيث يقبل كل منهما قيمة منطقية (boolean) أو كائن خيارات. لا يتم التقاط نصوص/رؤوس الطلبات والاستجابات مطلقًا، ويتم تنظيف عناوين URL، ويتم اقتطاع الرسائل، ويُستثنى حركة استيعاب/تتبع المسجِّل نفسه، وتضيف ميزانيات كل جلسة (1000 حدث وحدة تحكم / 2000 حدث شبكة) إشعار اقتطاع عند تجاوزها.
|
|
239
|
+
|
|
240
|
+
يخزّن قالب Analytics بيانات إعادة التشغيل الوصفية في SQL (`session_recordings`) ويخزّن الأجزاء عبر مراجع blob خاصة (`session_replay_chunks`). لا تتلقى المتصفحات والوكلاء أبدًا عناوين URL الخاصة بالموفر. تمر إعادة التشغيل عبر مسارات خادم محددة النطاق، وتُرجع أدوات الوكيل الافتراضية ملخصات أو أحداث إعادة تشغيل محدودة، وليس وصولاً خامًا إلى جدول الأجزاء.
|
|
241
|
+
|
|
242
|
+
عندما تحتاج إلى تسليم تسجيل خاص إلى وكيل خارجي، استخدم عنصر التحكم **نسخ للوكيل** في صفحة تفاصيل الجلسة. فهو يُنشئ رابط `agent_access` محدد النطاق بالتسجيل ومدته ساعتان، ويقوم بعرض SSR لحمولة اكتشاف صغيرة على `/sessions/:recordingId`، ولا يكشف إلا عن واجهات برمجة تطبيقات JSON الخاصة بسياق/أحداث الوكيل. لا تتغير رؤية التسجيل.
|
|
243
|
+
|
|
244
|
+
للتطوير المحلي فقط، يمكن لـ Analytics الرجوع إلى أجزاء SQL مضمّنة محدودة السعة عندما يكون تخزين blob الخاص غير متاح. يجب أن تُهيّئ عمليات النشر في الإنتاج تخزين blob خاصًا أو مشفّرًا بدلاً من الاعتماد على Postgres لحمولات إعادة التشغيل.
|
|
245
|
+
|
|
191
246
|
## متقدم: الموفرون المخصصون والداخليون {#advanced}
|
|
192
247
|
|
|
193
248
|
تحتاج معظم التطبيقات فقط إلى `track()` / `identify()` وموفر مدمج. بقية السطح - تسجيل موفري الخدمة المخصصين، واجهة `TrackingProvider`، الأجزاء الداخلية المجمعة، والقياس عن بعد للمتصفح الخاص بإطار العمل - موجود أدناه.
|
|
@@ -188,6 +188,61 @@ Hauptunterschiede zum [server `track()`](#track):
|
|
|
188
188
|
|
|
189
189
|
Dies unterscheidet sich von der internen Browser-Telemetrie des Frameworks (`trackEvent()` / automatische Seitenaufrufe – siehe [Browser defaults](#browser-defaults) unten), die die eigene Produktanalyse von Agent Native unterstützt. Verwenden Sie `track()` für die eigenen Analyseereignisse Ihrer App, die Ihre konfigurierten Anbieter erreichen sollen.
|
|
190
190
|
|
|
191
|
+
## Session Replay {#session-replay}
|
|
192
|
+
|
|
193
|
+
Agent-Native-Apps können sich für erstklassiges browserseitiges Session Replay entscheiden, ohne ein zweites Analytics-SDK hinzuzufügen. Rufen Sie `configureTracking()` einmal im Browser-Root auf und übergeben Sie den öffentlichen Analytics-Schlüssel sowie den Collector-Endpunkt:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { configureTracking } from "@agent-native/core/client";
|
|
197
|
+
|
|
198
|
+
configureTracking({
|
|
199
|
+
key: "anpk_...",
|
|
200
|
+
endpoint: "https://analytics.example.com/api/analytics/track",
|
|
201
|
+
sessionReplay: {
|
|
202
|
+
enabled: true,
|
|
203
|
+
sampleRate: 0.1,
|
|
204
|
+
},
|
|
205
|
+
getDefaultProps: (_event, props) => ({
|
|
206
|
+
...props,
|
|
207
|
+
app: "my-app",
|
|
208
|
+
template: "my-template",
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Wenn `sessionReplay.enabled` einen truthy Wert hat, importiert der Client nach dem Start dynamisch `@rrweb/record` und sendet Replay-Chunks an den Replay-Endpunkt. Wenn `endpoint` auf `/api/analytics/track` oder `/track` endet, wird der Replay-Endpunkt automatisch als `/api/analytics/replay` abgeleitet. Überschreiben Sie ihn explizit mit `sessionReplay.endpoint`, wenn der Replay-Collector an anderer Stelle liegt.
|
|
214
|
+
|
|
215
|
+
Agent-Native-Template-Roots rufen `configureTracking()` bereits auf. Gehostete Template-Deployments können Replay über Vite-/Netlify-Umgebungsvariablen aktivieren, während Bibliotheks-Konsumenten die explizite Form `configureTracking({ key, endpoint, sessionReplay })` von oben bevorzugen sollten:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY=anpk_...
|
|
219
|
+
VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT=https://analytics.example.com/api/analytics/track
|
|
220
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_ENABLED=true
|
|
221
|
+
VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE=0.1
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Der Browser-Helfer führt außerdem einen nach bestem Bemühen erfolgenden, nicht blockierenden Lesevorgang der aktuellen Agent-Native-Auth-Sitzung durch. Replay ist standardmäßig nur für angemeldete Benutzer verfügbar: Wenn `sessionReplay` aktiviert ist, startet die Aufzeichnung nur, wenn die Sitzung zu einer Benutzer-E-Mail-Adresse aufgelöst werden kann. Replays angemeldeter Benutzer enthalten E-Mail-basierte `userId`/`userEmail` sowie `orgId`.
|
|
225
|
+
|
|
226
|
+
Setzen Sie `sessionReplay.requireSignedInUser: false` oder `VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH=false` nur für ein absichtlich anonymes Replay-Deployment. Wenn die Auth-Prüfung deaktiviert ist, bleiben anonyme Aufzeichnungen nach anonymem Besucher, Sitzung, App/Template, Hostname und Pfad abfragbar.
|
|
227
|
+
|
|
228
|
+
Session Replay wird deterministisch pro Browser-Sitzung gesampelt. Eine `sampleRate` von `0.1` zeichnet etwa 10 % der infrage kommenden Sitzungen auf; verwenden Sie `1`, wenn die infrage kommende Population absichtlich klein ist, etwa beim Dogfooding nur für angemeldete Benutzer.
|
|
229
|
+
|
|
230
|
+
Die Datenschutz-Standardeinstellungen sind bewusst konservativ, aber für die Wiedergabe weiterhin nützlich:
|
|
231
|
+
|
|
232
|
+
- Eingaben werden standardmäßig maskiert (`maskAllInputs: true`).
|
|
233
|
+
- Seitentext bleibt sichtbar, sofern ein Element nicht mit `.an-mask` oder `data-an-mask` markiert ist.
|
|
234
|
+
- Sensible Bereiche werden mit Selektoren wie `[data-sensitive]`, `.an-block`, `.an-private`, `data-an-block`, `data-an-private` sowie kreditkarten-/passwort-/sozialversicherungsnummerähnlichen Feldern blockiert.
|
|
235
|
+
- URLs werden mit demselben `scrubUrl()`-Helfer bereinigt, der auch von der Browser-Analyse verwendet wird.
|
|
236
|
+
- Die Replay-Aufzeichnung ist ausschließlich webbasiert und opt-in; native Desktop-Bildschirme werden nicht aufgezeichnet.
|
|
237
|
+
|
|
238
|
+
Während der Aufzeichnung erfasst Session Replay außerdem die Browser-Konsolenausgabe (`log`, `info`, `warn`, `error`, `debug`, sowie die Fenster-Ereignisse `error` / `unhandledrejection`) und Netzwerkanfrage-Metadaten (`fetch` und XHR) als getaggte rrweb-Custom-Events, damit Agenten und der Replay-Viewer von Nutzern gemeldete Probleme debuggen können. Die Erfassung ist standardmäßig aktiviert, wenn Replay aktiviert ist; passen Sie sie mit den Optionen `sessionReplay.console` und `sessionReplay.network` an oder deaktivieren Sie sie, wobei jede Option entweder einen booleschen Wert oder ein Optionsobjekt akzeptiert. Request-/Response-Bodies und -Header werden nie erfasst, URLs werden bereinigt, Nachrichten werden gekürzt, der eigene Ingest-/Tracking-Traffic des Recorders wird ausgeschlossen, und sitzungsbezogene Budgets (1000 Konsolen- / 2000 Netzwerkereignisse) fügen bei Überschreitung einen Kürzungshinweis hinzu.
|
|
239
|
+
|
|
240
|
+
Das Analytics-Template speichert Replay-Metadaten in SQL (`session_recordings`) und Chunks über private Blob-Referenzen (`session_replay_chunks`). Browser und Agenten erhalten niemals Anbieter-URLs. Die Wiedergabe läuft über zugriffsbeschränkte Server-Routen, und die Standard-Agent-Tools liefern Zusammenfassungen oder begrenzte Replay-Ereignisse zurück, keinen Rohzugriff auf die Chunk-Tabelle.
|
|
241
|
+
|
|
242
|
+
Wenn Sie eine private Aufzeichnung an einen externen Agenten übergeben müssen, verwenden Sie auf der Sitzungsdetailseite die Funktion **Copy for agent**. Sie erzeugt einen zwei Stunden gültigen, aufzeichnungsgebundenen `agent_access`-Link, rendert eine kleine Discovery-Payload per SSR unter `/sessions/:recordingId` und stellt ausschließlich die JSON-APIs für Agent-Kontext/-Ereignisse bereit. Die Sichtbarkeit der Aufzeichnung ändert sich dadurch nicht.
|
|
243
|
+
|
|
244
|
+
Nur für die lokale Entwicklung kann Analytics auf begrenzte SQL-Inline-Chunks zurückgreifen, wenn kein privater Blob-Speicher verfügbar ist. Produktions-Deployments sollten privaten oder verschlüsselten Blob-Speicher konfigurieren, anstatt sich für Replay-Payloads auf Postgres zu verlassen.
|
|
245
|
+
|
|
191
246
|
## Erweitert: benutzerdefinierte Anbieter und Interna {#advanced}
|
|
192
247
|
|
|
193
248
|
Die meisten Apps benötigen nur `track()` / `identify()` und einen integrierten Anbieter. Der Rest der Oberfläche – Registrierung benutzerdefinierter Anbieter, die `TrackingProvider`-Schnittstelle, Batch-Interna und die eigene Browser-Telemetrie des Frameworks – finden Sie unten.
|