@agent-native/core 0.98.4 → 0.98.5
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 +16 -0
- package/corpus/core/docs/content/durable-background-runs.mdx +37 -0
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +4 -2
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +2 -0
- package/corpus/core/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +2 -0
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +16 -16
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +25 -8
- package/corpus/core/src/agent/engine/anthropic-engine.ts +17 -3
- package/corpus/core/src/agent/engine/output-tokens.ts +6 -6
- package/corpus/core/src/agent/production-agent.ts +200 -27
- package/corpus/core/src/agent/run-manager.ts +81 -1
- package/corpus/core/src/agent/run-store.ts +301 -41
- package/corpus/core/src/client/AgentPanel.tsx +6 -2
- package/corpus/core/src/client/AssistantChat.tsx +364 -69
- package/corpus/core/src/client/agent-chat-adapter.ts +169 -1
- package/corpus/core/src/client/chat/message-components.tsx +3 -0
- package/corpus/core/src/client/chat/tool-call-display.tsx +20 -36
- package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +7 -0
- package/corpus/core/src/client/extensions/AgentNativeExtensionFrame.tsx +2 -0
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +2 -0
- package/corpus/core/src/client/extensions/ExtensionEditor.tsx +9 -2
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +2 -0
- package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +2 -0
- package/corpus/core/src/client/extensions/portable-extension.ts +2 -0
- package/corpus/core/src/client/session-replay.ts +361 -29
- package/corpus/core/src/client/sse-event-processor.ts +43 -1
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +1 -1
- package/corpus/core/src/demo/config.ts +6 -6
- package/corpus/core/src/demo/fetch-interceptor.ts +13 -4
- package/corpus/core/src/demo/redact.ts +13 -149
- package/corpus/core/src/extensions/html-shell.ts +3 -0
- package/corpus/core/src/extensions/session-replay-iframe.ts +131 -0
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -20
- package/corpus/core/src/session-replay-iframe-protocol.ts +58 -0
- package/corpus/core/src/shared/reasoning-effort.ts +35 -0
- package/corpus/core/src/styles/agent-conversation.css +26 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +30 -21
- package/corpus/templates/analytics/AGENTS.md +12 -0
- package/corpus/templates/analytics/actions/get-error-issue.ts +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-events.ts +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +1 -0
- package/corpus/templates/analytics/actions/list-error-issues.ts +1 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -0
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -1
- package/corpus/templates/analytics/app/global.css +2 -2
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +41 -4
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +52 -203
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +34 -12
- package/corpus/templates/analytics/changelog/2026-07-12-analytics-chat-retries-before-showing-a-generic-no-data-mess.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-analytics-sessions-search-no-longer-loses-fast-keystrokes-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-chart-selection-respects-chat-state.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-demo-mode-anonymizes-error-reporting-emails.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-replays-show-iframe-content.md +6 -0
- package/corpus/templates/analytics/netlify.toml +0 -3
- package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +14 -0
- package/corpus/templates/analytics/server/lib/error-capture.ts +59 -2
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +24 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +2 -9
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +39 -6
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/brain/app/global.css +2 -2
- package/corpus/templates/brain/changelog/2026-07-12-fix-long-response-progress.md +6 -0
- package/corpus/templates/forms/app/components/CloudUpgrade.tsx +1 -1
- package/corpus/templates/forms/app/components/ThemeToggle.tsx +22 -6
- package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +5 -5
- package/corpus/templates/forms/app/components/builder/FieldRenderer.tsx +3 -3
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +16 -16
- package/corpus/templates/forms/app/global.css +110 -5
- package/corpus/templates/forms/app/pages/AskPage.tsx +3 -3
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +134 -52
- package/corpus/templates/forms/app/pages/FormFillPage.tsx +44 -11
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +42 -24
- package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +28 -15
- package/corpus/templates/forms/app/pages/ResponsesPage.tsx +38 -15
- package/corpus/templates/forms/app/routes/_app.settings.tsx +1 -1
- package/corpus/templates/forms/changelog/2026-07-12-form-builder-surfaces-are-calmer-fields-stay-stable-on-hover.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-12-smoother-more-tactile-controls-throughout-animated-icon-and-.md +6 -0
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +21 -13
- package/corpus/templates/mail/app/components/email/email-iframe-document.ts +18 -0
- package/dist/agent/durable-background.d.ts +10 -10
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +16 -16
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +19 -8
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +11 -3
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/output-tokens.js +6 -6
- package/dist/agent/engine/output-tokens.js.map +1 -1
- package/dist/agent/production-agent.d.ts +52 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +154 -26
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +9 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +71 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +9 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +243 -48
- package/dist/agent/run-store.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +21 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +298 -67
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +146 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +2 -1
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +3 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +13 -37
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +1 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.js +6 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.d.ts.map +1 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.js +2 -1
- package/dist/client/extensions/AgentNativeExtensionFrame.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +2 -1
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionEditor.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionEditor.js +5 -2
- package/dist/client/extensions/ExtensionEditor.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +2 -1
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.js +2 -1
- package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
- package/dist/client/extensions/portable-extension.d.ts.map +1 -1
- package/dist/client/extensions/portable-extension.js +2 -0
- package/dist/client/extensions/portable-extension.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +308 -29
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +5 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +37 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/demo/actions/toggle-demo-mode.js +1 -1
- package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
- package/dist/demo/config.js +6 -6
- package/dist/demo/config.js.map +1 -1
- package/dist/demo/fetch-interceptor.d.ts.map +1 -1
- package/dist/demo/fetch-interceptor.js +13 -4
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/demo/redact.d.ts +7 -7
- package/dist/demo/redact.d.ts.map +1 -1
- package/dist/demo/redact.js +8 -145
- package/dist/demo/redact.js.map +1 -1
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js +2 -0
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/session-replay-iframe.d.ts +10 -0
- package/dist/extensions/session-replay-iframe.d.ts.map +1 -0
- package/dist/extensions/session-replay-iframe.js +121 -0
- package/dist/extensions/session-replay-iframe.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +1 -19
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/session-replay-iframe-protocol.d.ts +36 -0
- package/dist/session-replay-iframe-protocol.d.ts.map +1 -0
- package/dist/session-replay-iframe-protocol.js +24 -0
- package/dist/session-replay-iframe-protocol.js.map +1 -0
- package/dist/shared/reasoning-effort.d.ts +11 -0
- package/dist/shared/reasoning-effort.d.ts.map +1 -1
- package/dist/shared/reasoning-effort.js +36 -0
- package/dist/shared/reasoning-effort.js.map +1 -1
- package/dist/styles/agent-conversation.css +26 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
- package/docs/content/durable-background-runs.mdx +37 -0
- package/docs/content/locales/ar-SA/tracking.mdx +2 -0
- package/docs/content/locales/de-DE/tracking.mdx +2 -0
- package/docs/content/locales/es-ES/tracking.mdx +2 -0
- package/docs/content/locales/fr-FR/tracking.mdx +4 -2
- package/docs/content/locales/hi-IN/tracking.mdx +2 -0
- package/docs/content/locales/ja-JP/tracking.mdx +2 -0
- package/docs/content/locales/ko-KR/tracking.mdx +2 -0
- package/docs/content/locales/pt-BR/tracking.mdx +2 -0
- package/docs/content/locales/zh-CN/tracking.mdx +2 -0
- package/docs/content/locales/zh-TW/durable-background-runs.mdx +14 -0
- package/docs/content/locales/zh-TW/tracking.mdx +2 -0
- package/docs/content/tracking.mdx +2 -0
- package/package.json +2 -2
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +0 -15
- package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
- package/dist/observability/hosted-model-experiment.d.ts +0 -39
- package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
- package/dist/observability/hosted-model-experiment.js +0 -90
- package/dist/observability/hosted-model-experiment.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SESSION_REPLAY_IFRAME_ATTRIBUTE, SESSION_REPLAY_IFRAME_PROBE, SESSION_REPLAY_IFRAME_START, SESSION_REPLAY_IFRAME_STOP, } from "../session-replay-iframe-protocol.js";
|
|
1
2
|
import { getOrCreateAnalyticsAnonymousId, getOrCreateAnalyticsSessionId, } from "./analytics-session.js";
|
|
2
3
|
import { scrubUrl } from "./url-scrub.js";
|
|
3
4
|
const DEFAULT_REPLAY_PATH = "/api/analytics/replay";
|
|
@@ -16,7 +17,9 @@ const DEFAULT_EVENT_SAMPLING = {
|
|
|
16
17
|
};
|
|
17
18
|
const SESSION_REPLAY_STATE_KEY = Symbol.for("agent-native.client.sessionReplay");
|
|
18
19
|
const SESSION_REPLAY_ID_STORAGE_KEY = "agent-native.session_replay_id";
|
|
20
|
+
const SESSION_REPLAY_IFRAME_BLOCK_SELECTOR = `iframe[${SESSION_REPLAY_IFRAME_ATTRIBUTE}]`;
|
|
19
21
|
const DEFAULT_BLOCK_SELECTOR = [
|
|
22
|
+
SESSION_REPLAY_IFRAME_BLOCK_SELECTOR,
|
|
20
23
|
"[data-sensitive]",
|
|
21
24
|
"[data-an-block]",
|
|
22
25
|
"[data-an-private]",
|
|
@@ -36,11 +39,28 @@ const DEFAULT_BLOCK_SELECTOR = [
|
|
|
36
39
|
const DEFAULT_IGNORE_SELECTOR = ".an-ignore, [data-an-ignore]";
|
|
37
40
|
const DEFAULT_MASK_TEXT_CLASS = "an-mask";
|
|
38
41
|
const DEFAULT_MASK_TEXT_SELECTOR = "[data-an-mask]";
|
|
42
|
+
const DEFAULT_MASK_INPUT_OPTIONS = {
|
|
43
|
+
color: true,
|
|
44
|
+
date: true,
|
|
45
|
+
"datetime-local": true,
|
|
46
|
+
email: true,
|
|
47
|
+
month: true,
|
|
48
|
+
number: true,
|
|
49
|
+
password: true,
|
|
50
|
+
range: true,
|
|
51
|
+
search: true,
|
|
52
|
+
tel: true,
|
|
53
|
+
text: true,
|
|
54
|
+
time: true,
|
|
55
|
+
url: true,
|
|
56
|
+
week: true,
|
|
57
|
+
};
|
|
39
58
|
const DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
40
59
|
const DEFAULT_MAX_DURATION_MS = 30 * 60 * 1000;
|
|
41
60
|
const DEFAULT_MAX_EVENTS_PER_BATCH = 50;
|
|
42
61
|
const DEFAULT_MAX_BATCH_BYTES = 256 * 1024;
|
|
43
62
|
const MAX_KEEPALIVE_REPLAY_UPLOAD_BYTES = 60 * 1024;
|
|
63
|
+
const REPLAY_TEXT_ENCODER = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;
|
|
44
64
|
const RRWEB_FULL_SNAPSHOT_EVENT_TYPE = 2;
|
|
45
65
|
/** Cross-tab channel name used by the duplicated-tab claim guard. */
|
|
46
66
|
const SESSION_REPLAY_BROADCAST_CHANNEL_NAME = "agent-native-session-replay";
|
|
@@ -109,9 +129,13 @@ function getState() {
|
|
|
109
129
|
flushTimer: null,
|
|
110
130
|
maxDurationTimer: null,
|
|
111
131
|
flushing: false,
|
|
132
|
+
pendingFlushReason: null,
|
|
133
|
+
pendingFlushWaiters: [],
|
|
134
|
+
awaitingFullSnapshot: false,
|
|
112
135
|
stopRecorder: null,
|
|
113
136
|
restoreUrlMonitor: null,
|
|
114
137
|
removeLifecycleListeners: null,
|
|
138
|
+
restoreIframeBridge: null,
|
|
115
139
|
addCustomEvent: null,
|
|
116
140
|
restoreCaptures: null,
|
|
117
141
|
options: null,
|
|
@@ -124,6 +148,10 @@ function getState() {
|
|
|
124
148
|
const state = g[SESSION_REPLAY_STATE_KEY];
|
|
125
149
|
// Keep Vite HMR safe when an older recorder state survives a module reload.
|
|
126
150
|
state.resourceNodes ??= new Map();
|
|
151
|
+
state.restoreIframeBridge ??= null;
|
|
152
|
+
state.pendingFlushReason ??= null;
|
|
153
|
+
state.pendingFlushWaiters ??= [];
|
|
154
|
+
state.awaitingFullSnapshot ??= false;
|
|
127
155
|
return state;
|
|
128
156
|
}
|
|
129
157
|
// The replay session record (replayId + sequence counter) lives in
|
|
@@ -522,13 +550,17 @@ function normalizeOptions(options) {
|
|
|
522
550
|
checkoutEveryNth: options.checkoutEveryNth,
|
|
523
551
|
checkoutEveryNms: options.checkoutEveryNms,
|
|
524
552
|
inlineStylesheet: options.inlineStylesheet ?? true,
|
|
525
|
-
blockSelector: options.blockSelector || DEFAULT_BLOCK_SELECTOR,
|
|
553
|
+
blockSelector: mergeReplayBlockSelector(options.blockSelector || DEFAULT_BLOCK_SELECTOR),
|
|
526
554
|
ignoreSelector: options.ignoreSelector || DEFAULT_IGNORE_SELECTOR,
|
|
527
555
|
maskTextClass: options.maskTextClass || DEFAULT_MASK_TEXT_CLASS,
|
|
528
556
|
maskTextSelector: options.maskTextSelector || DEFAULT_MASK_TEXT_SELECTOR,
|
|
529
557
|
maskAllInputs: options.maskAllInputs ?? true,
|
|
530
558
|
recordCanvas: options.recordCanvas ?? false,
|
|
531
|
-
|
|
559
|
+
// A top-level app can safely aggregate cooperative child recorders. An
|
|
560
|
+
// app embedded by an unrelated cross-origin host must continue emitting
|
|
561
|
+
// and uploading its own events, so it only forwards to a parent when the
|
|
562
|
+
// caller explicitly opts in.
|
|
563
|
+
recordCrossOriginIframes: options.recordCrossOriginIframes ?? window.parent === window,
|
|
532
564
|
collectFonts: options.collectFonts ?? false,
|
|
533
565
|
inlineImages: options.inlineImages ?? false,
|
|
534
566
|
eventSampling: options.eventSampling ?? DEFAULT_EVENT_SAMPLING,
|
|
@@ -539,6 +571,11 @@ function normalizeOptions(options) {
|
|
|
539
571
|
shouldStart: options.shouldStart,
|
|
540
572
|
};
|
|
541
573
|
}
|
|
574
|
+
function mergeReplayBlockSelector(blockSelector) {
|
|
575
|
+
return blockSelector.includes(SESSION_REPLAY_IFRAME_BLOCK_SELECTOR)
|
|
576
|
+
? blockSelector
|
|
577
|
+
: `${blockSelector}, ${SESSION_REPLAY_IFRAME_BLOCK_SELECTOR}`;
|
|
578
|
+
}
|
|
542
579
|
/**
|
|
543
580
|
* Console/network capture default to ON whenever session replay records;
|
|
544
581
|
* `false` disables a category, an object form overrides its caps. Network
|
|
@@ -753,18 +790,25 @@ function replayEventTimestampMs(event) {
|
|
|
753
790
|
function enqueueReplayEvent(state, event) {
|
|
754
791
|
if (!state.options)
|
|
755
792
|
return;
|
|
793
|
+
const eventType = typeof event.type === "number" ? event.type : null;
|
|
794
|
+
if (state.awaitingFullSnapshot) {
|
|
795
|
+
if (eventType !== RRWEB_FULL_SNAPSHOT_EVENT_TYPE)
|
|
796
|
+
return;
|
|
797
|
+
state.awaitingFullSnapshot = false;
|
|
798
|
+
}
|
|
756
799
|
const serialized = serializeReplayEvent(event, state.resourceNodes);
|
|
757
800
|
if (!serialized)
|
|
758
801
|
return;
|
|
759
|
-
const estimatedBytes = serialized
|
|
802
|
+
const estimatedBytes = replaySerializedBytes(serialized);
|
|
760
803
|
if (state.queue.length > 0 &&
|
|
761
804
|
state.queuedBytes + estimatedBytes > state.options.maxBatchBytes) {
|
|
762
805
|
void flushSessionReplay("max-bytes");
|
|
763
806
|
}
|
|
764
807
|
state.queue.push({
|
|
765
808
|
json: serialized,
|
|
809
|
+
byteLength: estimatedBytes,
|
|
766
810
|
timestampMs: replayEventTimestampMs(event),
|
|
767
|
-
type:
|
|
811
|
+
type: eventType,
|
|
768
812
|
});
|
|
769
813
|
state.queuedBytes += estimatedBytes;
|
|
770
814
|
flushQueuedReplayIfNeeded(state);
|
|
@@ -989,6 +1033,24 @@ function isFinalFlushReason(reason) {
|
|
|
989
1033
|
"max-duration",
|
|
990
1034
|
].includes(reason);
|
|
991
1035
|
}
|
|
1036
|
+
function flushReasonPriority(reason) {
|
|
1037
|
+
// Unload reasons must retain their keepalive sequence reservation even when
|
|
1038
|
+
// another final request (for example, an explicit manual stop) is coalesced.
|
|
1039
|
+
if (reason === "pagehide" || reason === "beforeunload")
|
|
1040
|
+
return 3;
|
|
1041
|
+
if (isFinalFlushReason(reason))
|
|
1042
|
+
return 2;
|
|
1043
|
+
if (reason === "visibility-hidden")
|
|
1044
|
+
return 1;
|
|
1045
|
+
return 0;
|
|
1046
|
+
}
|
|
1047
|
+
function mergePendingFlushReason(current, requested) {
|
|
1048
|
+
if (!current)
|
|
1049
|
+
return requested;
|
|
1050
|
+
return flushReasonPriority(requested) > flushReasonPriority(current)
|
|
1051
|
+
? requested
|
|
1052
|
+
: current;
|
|
1053
|
+
}
|
|
992
1054
|
function shouldReserveSequenceBeforeKeepalive(reason) {
|
|
993
1055
|
return (reason === "pagehide" ||
|
|
994
1056
|
reason === "beforeunload" ||
|
|
@@ -1023,7 +1085,90 @@ function flushQueuedReplayIfNeeded(state) {
|
|
|
1023
1085
|
void flushSessionReplay(reason);
|
|
1024
1086
|
}
|
|
1025
1087
|
function queuedReplayBytes(events) {
|
|
1026
|
-
return events.reduce((total, event) => total + event
|
|
1088
|
+
return events.reduce((total, event) => total + queuedReplayEventBytes(event), 0);
|
|
1089
|
+
}
|
|
1090
|
+
function replaySerializedBytes(value) {
|
|
1091
|
+
if (REPLAY_TEXT_ENCODER)
|
|
1092
|
+
return REPLAY_TEXT_ENCODER.encode(value).byteLength;
|
|
1093
|
+
if (typeof Blob !== "undefined")
|
|
1094
|
+
return new Blob([value]).size;
|
|
1095
|
+
return value.length;
|
|
1096
|
+
}
|
|
1097
|
+
function queuedReplayEventBytes(event) {
|
|
1098
|
+
return Number.isFinite(event.byteLength)
|
|
1099
|
+
? event.byteLength
|
|
1100
|
+
: replaySerializedBytes(event.json);
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Remove one bounded FIFO prefix from the live queue.
|
|
1104
|
+
*
|
|
1105
|
+
* Threshold-triggered flushes may arrive while another upload is active. The
|
|
1106
|
+
* old `queue.splice(0)` drained that entire accumulated backlog on the next
|
|
1107
|
+
* flush, bypassing both byte and event caps. Keep FullSnapshots isolated and
|
|
1108
|
+
* always take at least one event so an individually large snapshot can still
|
|
1109
|
+
* make progress.
|
|
1110
|
+
*/
|
|
1111
|
+
function takeQueuedReplayBatch(state) {
|
|
1112
|
+
const options = state.options;
|
|
1113
|
+
if (!options || state.queue.length === 0)
|
|
1114
|
+
return [];
|
|
1115
|
+
let count = 0;
|
|
1116
|
+
let bytes = 0;
|
|
1117
|
+
for (const event of state.queue) {
|
|
1118
|
+
if (count > 0 && event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE)
|
|
1119
|
+
break;
|
|
1120
|
+
if (count > 0 &&
|
|
1121
|
+
(count >= options.maxEventsPerBatch ||
|
|
1122
|
+
bytes + queuedReplayEventBytes(event) > options.maxBatchBytes)) {
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
count += 1;
|
|
1126
|
+
bytes += queuedReplayEventBytes(event);
|
|
1127
|
+
if (event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE)
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
const events = state.queue.splice(0, Math.max(1, count));
|
|
1131
|
+
state.queuedBytes = queuedReplayBytes(state.queue);
|
|
1132
|
+
return events;
|
|
1133
|
+
}
|
|
1134
|
+
function splitReplayBatch(events) {
|
|
1135
|
+
if (events.length < 2)
|
|
1136
|
+
return null;
|
|
1137
|
+
const targetBytes = queuedReplayBytes(events) / 2;
|
|
1138
|
+
let splitAt = 1;
|
|
1139
|
+
let bytes = events[0] ? queuedReplayEventBytes(events[0]) : 0;
|
|
1140
|
+
while (splitAt < events.length - 1 && bytes < targetBytes) {
|
|
1141
|
+
const event = events[splitAt];
|
|
1142
|
+
if (event)
|
|
1143
|
+
bytes += queuedReplayEventBytes(event);
|
|
1144
|
+
splitAt += 1;
|
|
1145
|
+
}
|
|
1146
|
+
return [events.slice(0, splitAt), events.slice(splitAt)];
|
|
1147
|
+
}
|
|
1148
|
+
function replayBatchNeedsDomReset(events) {
|
|
1149
|
+
return events.some((event) => {
|
|
1150
|
+
if (event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE)
|
|
1151
|
+
return true;
|
|
1152
|
+
if (event.type !== 3)
|
|
1153
|
+
return false;
|
|
1154
|
+
try {
|
|
1155
|
+
const parsed = JSON.parse(event.json);
|
|
1156
|
+
// rrweb IncrementalSource.Mutation is zero. Dropping one may leave later
|
|
1157
|
+
// node references dangling until another FullSnapshot resets the mirror.
|
|
1158
|
+
return parsed.data?.source === 0;
|
|
1159
|
+
}
|
|
1160
|
+
catch {
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
function quarantinePendingReplayUntilFullSnapshot(state) {
|
|
1166
|
+
const pending = [...state.retryBatches.flat(), ...state.queue];
|
|
1167
|
+
const resetAt = pending.findIndex((event) => event.type === RRWEB_FULL_SNAPSHOT_EVENT_TYPE);
|
|
1168
|
+
state.retryBatches = [];
|
|
1169
|
+
state.queue = resetAt >= 0 ? pending.slice(resetAt) : [];
|
|
1170
|
+
state.queuedBytes = queuedReplayBytes(state.queue);
|
|
1171
|
+
state.awaitingFullSnapshot = resetAt < 0;
|
|
1027
1172
|
}
|
|
1028
1173
|
function restoreReplayEvents(state, events) {
|
|
1029
1174
|
state.retryBatches.unshift(events);
|
|
@@ -1044,10 +1189,17 @@ function rollbackReplaySequenceReservation(state, payload) {
|
|
|
1044
1189
|
}
|
|
1045
1190
|
export async function flushSessionReplay(reason = "manual") {
|
|
1046
1191
|
const state = getState();
|
|
1047
|
-
if (!state.options
|
|
1192
|
+
if (!state.options)
|
|
1048
1193
|
return;
|
|
1049
|
-
|
|
1050
|
-
|
|
1194
|
+
if (state.flushing) {
|
|
1195
|
+
state.pendingFlushReason = mergePendingFlushReason(state.pendingFlushReason, reason);
|
|
1196
|
+
return new Promise((resolve) => {
|
|
1197
|
+
state.pendingFlushWaiters.push(resolve);
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
if (!hasPendingReplayBatch(state))
|
|
1201
|
+
return;
|
|
1202
|
+
const events = state.retryBatches.shift() ?? takeQueuedReplayBatch(state);
|
|
1051
1203
|
const payload = buildReplayBody(state, reason, events);
|
|
1052
1204
|
if (!payload || !state.options) {
|
|
1053
1205
|
restoreReplayEvents(state, events);
|
|
@@ -1056,6 +1208,8 @@ export async function flushSessionReplay(reason = "manual") {
|
|
|
1056
1208
|
state.flushing = true;
|
|
1057
1209
|
let uploaded = false;
|
|
1058
1210
|
let reservedSequence = false;
|
|
1211
|
+
let splitRejectedBatch = false;
|
|
1212
|
+
let droppedOversizedBatch = false;
|
|
1059
1213
|
let definitiveClientErrorStatus = null;
|
|
1060
1214
|
try {
|
|
1061
1215
|
await sendReplayUpload(state.options, payload.body, {
|
|
@@ -1078,9 +1232,32 @@ export async function flushSessionReplay(reason = "manual") {
|
|
|
1078
1232
|
// never succeed by retrying the exact same batch -- requeuing it would
|
|
1079
1233
|
// just spin forever, blocking every later batch behind it (flushes are
|
|
1080
1234
|
// FIFO via `retryBatches`). Drop it and move on instead.
|
|
1235
|
+
const rejectedStatus = error instanceof ReplayUploadHttpError ? error.status : null;
|
|
1236
|
+
const splitBatch = rejectedStatus === 413 ? splitReplayBatch(events) : null;
|
|
1237
|
+
const isUnsplittableOversizedBatch = rejectedStatus === 413 && splitBatch === null;
|
|
1081
1238
|
const isDefinitiveClientError = error instanceof ReplayUploadHttpError &&
|
|
1082
|
-
isDefinitiveReplayUploadClientError(error.status)
|
|
1083
|
-
|
|
1239
|
+
isDefinitiveReplayUploadClientError(error.status) &&
|
|
1240
|
+
!splitBatch &&
|
|
1241
|
+
!isUnsplittableOversizedBatch;
|
|
1242
|
+
if (splitBatch) {
|
|
1243
|
+
// A server or platform can enforce a stricter decompressed-body limit
|
|
1244
|
+
// than the recorder's configured queue cap. Bisect in FIFO order and
|
|
1245
|
+
// retry both halves at the same sequence; only successful halves advance
|
|
1246
|
+
// it, so no event is duplicated or skipped.
|
|
1247
|
+
state.retryBatches.unshift(...splitBatch);
|
|
1248
|
+
splitRejectedBatch = true;
|
|
1249
|
+
}
|
|
1250
|
+
else if (isUnsplittableOversizedBatch) {
|
|
1251
|
+
// This one event cannot be made any smaller. Drop only the rejected
|
|
1252
|
+
// singleton and keep later retry halves/live batches in FIFO order. If
|
|
1253
|
+
// it carried DOM structure, quarantine dependent mutations until a new
|
|
1254
|
+
// FullSnapshot can safely re-anchor rrweb's node mirror.
|
|
1255
|
+
droppedOversizedBatch = true;
|
|
1256
|
+
if (replayBatchNeedsDomReset(events)) {
|
|
1257
|
+
quarantinePendingReplayUntilFullSnapshot(state);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
else if (isDefinitiveClientError) {
|
|
1084
1261
|
// Continuing after a checksum/sequence conflict would reuse the same
|
|
1085
1262
|
// rejected sequence forever. More importantly, advancing past it would
|
|
1086
1263
|
// append mutations to a replay whose DOM stream may belong to another
|
|
@@ -1100,7 +1277,13 @@ export async function flushSessionReplay(reason = "manual") {
|
|
|
1100
1277
|
const previousInternal = replayCaptureInternal;
|
|
1101
1278
|
replayCaptureInternal = true;
|
|
1102
1279
|
try {
|
|
1103
|
-
if (
|
|
1280
|
+
if (splitRejectedBatch) {
|
|
1281
|
+
console.warn("[session-replay] splitting oversized upload (HTTP 413)", error);
|
|
1282
|
+
}
|
|
1283
|
+
else if (droppedOversizedBatch) {
|
|
1284
|
+
console.warn("[session-replay] dropping oversized replay event (HTTP 413)", error);
|
|
1285
|
+
}
|
|
1286
|
+
else if (isDefinitiveClientError) {
|
|
1104
1287
|
console.warn(`[session-replay] dropping upload (HTTP ${error.status})`, error);
|
|
1105
1288
|
}
|
|
1106
1289
|
else {
|
|
@@ -1114,8 +1297,36 @@ export async function flushSessionReplay(reason = "manual") {
|
|
|
1114
1297
|
finally {
|
|
1115
1298
|
state.flushing = false;
|
|
1116
1299
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1300
|
+
const coalescedReason = state.pendingFlushReason;
|
|
1301
|
+
const coalescedWaiters = coalescedReason
|
|
1302
|
+
? state.pendingFlushWaiters.splice(0)
|
|
1303
|
+
: [];
|
|
1304
|
+
if (coalescedReason)
|
|
1305
|
+
state.pendingFlushReason = null;
|
|
1306
|
+
if (coalescedReason) {
|
|
1307
|
+
// A stop/unload request that arrived during this upload owns the tail.
|
|
1308
|
+
// Its higher-priority reason must replace threshold/internal reasons so a
|
|
1309
|
+
// small final batch is not stranded or mislabeled as active.
|
|
1310
|
+
await flushSessionReplay(coalescedReason);
|
|
1311
|
+
}
|
|
1312
|
+
else if (splitRejectedBatch || droppedOversizedBatch) {
|
|
1313
|
+
// Preserve final-status and unload semantics through every half. In
|
|
1314
|
+
// particular, pagehide/beforeunload retries must still reserve their
|
|
1315
|
+
// sequence before a keepalive fetch, and completed flushes must not be
|
|
1316
|
+
// rewritten to an internal recovery reason/status.
|
|
1317
|
+
await flushSessionReplay(reason);
|
|
1318
|
+
}
|
|
1319
|
+
else if (uploaded && hasPendingReplayBatch(state)) {
|
|
1320
|
+
const mustContinue = state.retryBatches.length > 0 ||
|
|
1321
|
+
isFinalFlushReason(reason) ||
|
|
1322
|
+
shouldFlushQueuedReplay(state);
|
|
1323
|
+
if (mustContinue) {
|
|
1324
|
+
if (isFinalFlushReason(reason)) {
|
|
1325
|
+
await flushSessionReplay(reason);
|
|
1326
|
+
}
|
|
1327
|
+
else
|
|
1328
|
+
void flushSessionReplay(reason);
|
|
1329
|
+
}
|
|
1119
1330
|
}
|
|
1120
1331
|
if (definitiveClientErrorStatus !== null &&
|
|
1121
1332
|
state.replayId === payload.replayId) {
|
|
@@ -1155,6 +1366,8 @@ export async function flushSessionReplay(reason = "manual") {
|
|
|
1155
1366
|
// best-effort telemetry must never interfere with recording recovery
|
|
1156
1367
|
}
|
|
1157
1368
|
}
|
|
1369
|
+
for (const resolve of coalescedWaiters)
|
|
1370
|
+
resolve();
|
|
1158
1371
|
}
|
|
1159
1372
|
/**
|
|
1160
1373
|
* Restart a recorder whose replay identity was rejected without routing the
|
|
@@ -1239,6 +1452,80 @@ function installLifecycleListeners(state) {
|
|
|
1239
1452
|
state.removeLifecycleListeners = null;
|
|
1240
1453
|
};
|
|
1241
1454
|
}
|
|
1455
|
+
function markedSessionReplayIframes() {
|
|
1456
|
+
if (typeof document.querySelectorAll !== "function")
|
|
1457
|
+
return [];
|
|
1458
|
+
return Array.from(document.querySelectorAll(`iframe[${SESSION_REPLAY_IFRAME_ATTRIBUTE}]`));
|
|
1459
|
+
}
|
|
1460
|
+
function markedSessionReplayIframeForSource(source) {
|
|
1461
|
+
if (!source)
|
|
1462
|
+
return null;
|
|
1463
|
+
return (markedSessionReplayIframes().find((iframe) => iframe.contentWindow === source) ?? null);
|
|
1464
|
+
}
|
|
1465
|
+
function sessionReplayIframePrivacyOptions(options) {
|
|
1466
|
+
return {
|
|
1467
|
+
blockSelector: options.blockSelector,
|
|
1468
|
+
ignoreSelector: options.ignoreSelector,
|
|
1469
|
+
maskTextClass: options.maskTextClass,
|
|
1470
|
+
maskTextSelector: options.maskTextSelector,
|
|
1471
|
+
maskAllInputs: options.maskAllInputs,
|
|
1472
|
+
maskInputOptions: DEFAULT_MASK_INPUT_OPTIONS,
|
|
1473
|
+
recordCanvas: options.recordCanvas,
|
|
1474
|
+
collectFonts: options.collectFonts,
|
|
1475
|
+
inlineImages: options.inlineImages,
|
|
1476
|
+
sampling: options.eventSampling,
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function postSessionReplayIframeMessage(iframe, message) {
|
|
1480
|
+
try {
|
|
1481
|
+
iframe.contentWindow?.postMessage(message, "*");
|
|
1482
|
+
}
|
|
1483
|
+
catch {
|
|
1484
|
+
// The frame may have navigated or detached between discovery and send.
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Cooperative opaque/cross-origin iframe recording.
|
|
1489
|
+
*
|
|
1490
|
+
* rrweb cannot inspect these documents from the host. Framework-owned child
|
|
1491
|
+
* frames carry a marker and inject a tiny recorder that probes its direct
|
|
1492
|
+
* parent. Only an active host recorder responds, and only when the probe's
|
|
1493
|
+
* source is the contentWindow of a currently marked direct iframe.
|
|
1494
|
+
*/
|
|
1495
|
+
function installSessionReplayIframeBridge(state, options) {
|
|
1496
|
+
if (!options.recordCrossOriginIframes || state.restoreIframeBridge)
|
|
1497
|
+
return;
|
|
1498
|
+
const startMessage = {
|
|
1499
|
+
type: SESSION_REPLAY_IFRAME_START,
|
|
1500
|
+
options: sessionReplayIframePrivacyOptions(options),
|
|
1501
|
+
};
|
|
1502
|
+
const stopMessage = {
|
|
1503
|
+
type: SESSION_REPLAY_IFRAME_STOP,
|
|
1504
|
+
};
|
|
1505
|
+
const sendStart = (iframe) => postSessionReplayIframeMessage(iframe, startMessage);
|
|
1506
|
+
const onMessage = (event) => {
|
|
1507
|
+
if (!state.active)
|
|
1508
|
+
return;
|
|
1509
|
+
if (!event.data ||
|
|
1510
|
+
typeof event.data !== "object" ||
|
|
1511
|
+
event.data.type !== SESSION_REPLAY_IFRAME_PROBE) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
const iframe = markedSessionReplayIframeForSource(event.source);
|
|
1515
|
+
if (iframe)
|
|
1516
|
+
sendStart(iframe);
|
|
1517
|
+
};
|
|
1518
|
+
window.addEventListener("message", onMessage);
|
|
1519
|
+
for (const iframe of markedSessionReplayIframes())
|
|
1520
|
+
sendStart(iframe);
|
|
1521
|
+
state.restoreIframeBridge = () => {
|
|
1522
|
+
window.removeEventListener("message", onMessage);
|
|
1523
|
+
for (const iframe of markedSessionReplayIframes()) {
|
|
1524
|
+
postSessionReplayIframeMessage(iframe, stopMessage);
|
|
1525
|
+
}
|
|
1526
|
+
state.restoreIframeBridge = null;
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1242
1529
|
function truncateCaptureText(value, maxLength) {
|
|
1243
1530
|
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
1244
1531
|
}
|
|
@@ -1952,6 +2239,10 @@ async function startSessionReplayRecorder(state, normalized, sessionId, sampled,
|
|
|
1952
2239
|
state.queue = [];
|
|
1953
2240
|
state.queuedBytes = 0;
|
|
1954
2241
|
state.retryBatches = [];
|
|
2242
|
+
state.pendingFlushReason = null;
|
|
2243
|
+
for (const resolve of state.pendingFlushWaiters.splice(0))
|
|
2244
|
+
resolve();
|
|
2245
|
+
state.awaitingFullSnapshot = false;
|
|
1955
2246
|
state.resourceNodes.clear();
|
|
1956
2247
|
state.stopRecorder = null;
|
|
1957
2248
|
state.broadcastChannel = replayChannel;
|
|
@@ -1975,22 +2266,7 @@ async function startSessionReplayRecorder(state, normalized, sessionId, sampled,
|
|
|
1975
2266
|
recordCrossOriginIframes: normalized.recordCrossOriginIframes,
|
|
1976
2267
|
collectFonts: normalized.collectFonts,
|
|
1977
2268
|
inlineImages: normalized.inlineImages,
|
|
1978
|
-
maskInputOptions:
|
|
1979
|
-
color: true,
|
|
1980
|
-
date: true,
|
|
1981
|
-
"datetime-local": true,
|
|
1982
|
-
email: true,
|
|
1983
|
-
month: true,
|
|
1984
|
-
number: true,
|
|
1985
|
-
password: true,
|
|
1986
|
-
range: true,
|
|
1987
|
-
search: true,
|
|
1988
|
-
tel: true,
|
|
1989
|
-
text: true,
|
|
1990
|
-
time: true,
|
|
1991
|
-
url: true,
|
|
1992
|
-
week: true,
|
|
1993
|
-
},
|
|
2269
|
+
maskInputOptions: DEFAULT_MASK_INPUT_OPTIONS,
|
|
1994
2270
|
});
|
|
1995
2271
|
if (typeof stopRecorder !== "function") {
|
|
1996
2272
|
state.active = false;
|
|
@@ -2012,6 +2288,7 @@ async function startSessionReplayRecorder(state, normalized, sessionId, sampled,
|
|
|
2012
2288
|
state.maxDurationTimer = window.setTimeout(() => stopSessionReplay("max-duration"), normalized.maxDurationMs);
|
|
2013
2289
|
installUrlMonitor(state);
|
|
2014
2290
|
installLifecycleListeners(state);
|
|
2291
|
+
installSessionReplayIframeBridge(state, normalized);
|
|
2015
2292
|
state.addCustomEvent =
|
|
2016
2293
|
typeof rrweb.record.addCustomEvent === "function"
|
|
2017
2294
|
? rrweb.record.addCustomEvent
|
|
@@ -2032,6 +2309,7 @@ async function startSessionReplayRecorder(state, normalized, sessionId, sampled,
|
|
|
2032
2309
|
// best-effort interceptor teardown
|
|
2033
2310
|
}
|
|
2034
2311
|
state.restoreCaptures = null;
|
|
2312
|
+
state.restoreIframeBridge?.();
|
|
2035
2313
|
state.addCustomEvent = null;
|
|
2036
2314
|
state.active = false;
|
|
2037
2315
|
state.options = null;
|
|
@@ -2062,6 +2340,7 @@ export async function stopSessionReplay(reason = "manual") {
|
|
|
2062
2340
|
// best-effort interceptor teardown
|
|
2063
2341
|
}
|
|
2064
2342
|
state.restoreCaptures = null;
|
|
2343
|
+
state.restoreIframeBridge?.();
|
|
2065
2344
|
state.active = false;
|
|
2066
2345
|
state.addCustomEvent = null;
|
|
2067
2346
|
try {
|