@agent-native/core 0.98.3 → 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 +29 -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/analytics.ts +12 -0
- 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 +971 -53
- package/corpus/core/src/client/settings/DemoModeSection.tsx +3 -3
- package/corpus/core/src/client/sse-event-processor.ts +43 -1
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
- package/corpus/core/src/demo/config.ts +6 -6
- package/corpus/core/src/demo/fetch-interceptor.ts +34 -2
- package/corpus/core/src/demo/redact.ts +71 -158
- 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 +67 -16
- 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 +6 -64
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +41 -4
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +143 -121
- 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-fixed-session-replays-that-appeared-ultra-wide-hid-recorded-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-12-session-replays-load-faster-in-demo-mode-while-visitor-email.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/calendar/app/components/calendar/DeleteEventDialog.tsx +11 -2
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +14 -2
- package/corpus/templates/calendar/changelog/2026-07-12-calendar-responses-can-now-be-saved-with-command-enter-or-ct.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/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +11 -0
- package/dist/client/analytics.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 +12 -3
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +793 -49
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/settings/DemoModeSection.js +1 -1
- package/dist/client/settings/DemoModeSection.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/demo/actions/toggle-demo-mode.js +2 -2
- 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 +1 -0
- package/dist/demo/fetch-interceptor.d.ts.map +1 -1
- package/dist/demo/fetch-interceptor.js +28 -2
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/demo/redact.d.ts +14 -7
- package/dist/demo/redact.d.ts.map +1 -1
- package/dist/demo/redact.js +27 -155
- 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/notifications/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- 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/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +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
|
@@ -64,9 +64,9 @@ export function DemoModeSection() {
|
|
|
64
64
|
Enable demo mode
|
|
65
65
|
</div>
|
|
66
66
|
<p className="text-[10px] text-muted-foreground mt-0.5">
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
Anonymize emails in the UI and reshape supported dashboard charts for
|
|
68
|
+
presentations. Agent-visible emails and numbers are also anonymized.
|
|
69
|
+
Names, labels, IDs, and structure stay intact.
|
|
70
70
|
</p>
|
|
71
71
|
{forced && (
|
|
72
72
|
<p className="text-[10px] text-muted-foreground mt-0.5">
|
|
@@ -707,6 +707,22 @@ function dispatchActivityClear(tabId: string | undefined) {
|
|
|
707
707
|
);
|
|
708
708
|
}
|
|
709
709
|
|
|
710
|
+
// Fires once per chunk (see ProcessEventState.streamProgressDispatched) the
|
|
711
|
+
// moment a chunk produces real model output (visible text or a reasoning
|
|
712
|
+
// delta). Unlike `agent-chat:activity-clear` — which also fires for
|
|
713
|
+
// old-chunk `tool_done` replays and server-retry `clear` events, and so
|
|
714
|
+
// cannot be used to detect genuine forward progress — this event exists
|
|
715
|
+
// solely so listeners can distinguish "the run is actually producing new
|
|
716
|
+
// output" from those replay/retry cases.
|
|
717
|
+
function dispatchStreamProgress(tabId: string | undefined) {
|
|
718
|
+
if (typeof window === "undefined") return;
|
|
719
|
+
window.dispatchEvent(
|
|
720
|
+
new CustomEvent("agent-chat:stream-progress", {
|
|
721
|
+
detail: { tabId },
|
|
722
|
+
}),
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
710
726
|
function dispatchMissingApiKey(tabId: string | undefined) {
|
|
711
727
|
if (typeof window === "undefined") return;
|
|
712
728
|
window.dispatchEvent(
|
|
@@ -946,6 +962,11 @@ function completedToolOnlyMessage(toolNames: string[]): string | null {
|
|
|
946
962
|
|
|
947
963
|
interface ProcessEventState {
|
|
948
964
|
completedToolsAfterLastAssistantText: Set<string>;
|
|
965
|
+
/** Set once `agent-chat:stream-progress` has been dispatched for the
|
|
966
|
+
* current chunk, so a burst of per-token text/reasoning deltas only fires
|
|
967
|
+
* it once. Cleared by `resetProcessEventState` on a server `clear` retry
|
|
968
|
+
* so the next batch of real output re-arms it. */
|
|
969
|
+
streamProgressDispatched: boolean;
|
|
949
970
|
}
|
|
950
971
|
|
|
951
972
|
function markAssistantText(state: ProcessEventState | undefined) {
|
|
@@ -961,6 +982,18 @@ function markCompletedToolAfterAssistantText(
|
|
|
961
982
|
|
|
962
983
|
function resetProcessEventState(state: ProcessEventState | undefined) {
|
|
963
984
|
state?.completedToolsAfterLastAssistantText.clear();
|
|
985
|
+
if (state) state.streamProgressDispatched = false;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Returns true the first time it's called for a given state (or every time
|
|
989
|
+
// when state is unavailable, since there is nothing to dedupe against) and
|
|
990
|
+
// false on subsequent calls until `resetProcessEventState` re-arms it.
|
|
991
|
+
function shouldDispatchStreamProgress(
|
|
992
|
+
state: ProcessEventState | undefined,
|
|
993
|
+
): boolean {
|
|
994
|
+
if (state?.streamProgressDispatched) return false;
|
|
995
|
+
if (state) state.streamProgressDispatched = true;
|
|
996
|
+
return true;
|
|
964
997
|
}
|
|
965
998
|
|
|
966
999
|
/**
|
|
@@ -1005,7 +1038,13 @@ export function processEvent(
|
|
|
1005
1038
|
// activity label so a transient "Contacting model" / "Still generating
|
|
1006
1039
|
// image" doesn't linger beside streamed text. Idempotent (clears once, then
|
|
1007
1040
|
// no-ops) so per-token text deltas stay cheap.
|
|
1008
|
-
if (ev.text)
|
|
1041
|
+
if (ev.text) {
|
|
1042
|
+
dispatchActivityClear(tabId);
|
|
1043
|
+
// Real output resumed — let listeners (e.g. the auto-resume "Resuming…"
|
|
1044
|
+
// indicator) clear state that must NOT be cleared by activity-clear
|
|
1045
|
+
// alone, since activity-clear also fires for old-chunk/retry replays.
|
|
1046
|
+
if (shouldDispatchStreamProgress(state)) dispatchStreamProgress(tabId);
|
|
1047
|
+
}
|
|
1009
1048
|
if (ev.text?.trim()) markAssistantText(state);
|
|
1010
1049
|
const lastPart = content[content.length - 1];
|
|
1011
1050
|
if (lastPart && lastPart.type === "text") {
|
|
@@ -1024,6 +1063,7 @@ export function processEvent(
|
|
|
1024
1063
|
// part so the UI can render a single collapsible "Thinking" cell.
|
|
1025
1064
|
const delta = ev.text ?? "";
|
|
1026
1065
|
if (!delta) return { action: "continue" };
|
|
1066
|
+
if (shouldDispatchStreamProgress(state)) dispatchStreamProgress(tabId);
|
|
1027
1067
|
const lastPart = content[content.length - 1];
|
|
1028
1068
|
if (lastPart && lastPart.type === "reasoning") {
|
|
1029
1069
|
lastPart.text += delta;
|
|
@@ -1567,6 +1607,7 @@ export async function* readSSEStream(
|
|
|
1567
1607
|
options?.preparingActionState ?? {};
|
|
1568
1608
|
const processEventState: ProcessEventState = {
|
|
1569
1609
|
completedToolsAfterLastAssistantText: new Set(),
|
|
1610
|
+
streamProgressDispatched: false,
|
|
1570
1611
|
};
|
|
1571
1612
|
|
|
1572
1613
|
const withStreamMetadata = (r: ChatModelRunResult): ChatModelRunResult => {
|
|
@@ -1769,6 +1810,7 @@ export async function readSSEStreamRaw(
|
|
|
1769
1810
|
options?.preparingActionState ?? {};
|
|
1770
1811
|
const processEventState: ProcessEventState = {
|
|
1771
1812
|
completedToolsAfterLastAssistantText: new Set(),
|
|
1813
|
+
streamProgressDispatched: false,
|
|
1772
1814
|
};
|
|
1773
1815
|
// Tracks whether the most recent content state was already pushed via
|
|
1774
1816
|
// onUpdate inside the loop, so the post-loop flush below doesn't emit the
|
|
@@ -5,7 +5,7 @@ import { writeAppState } from "../../application-state/script-helpers.js";
|
|
|
5
5
|
|
|
6
6
|
export default defineAction({
|
|
7
7
|
description:
|
|
8
|
-
"Turn demo mode on or off. When demo mode is on, the
|
|
8
|
+
"Turn demo mode on or off. When demo mode is on, the UI replaces every email address with anonymous@builder.io and supported dashboards reshape chart values for presentations. Agent-visible results use the same anonymous email and anonymize numbers while keeping names, free text, labels, IDs, dates, and structure intact. Use when the user asks to 'hide my data', 'turn on demo mode', 'anonymize this for a screen share / recording', or similar. This is the same toggle as the Demo mode switch in settings.",
|
|
9
9
|
schema: z.object({
|
|
10
10
|
enabled: z
|
|
11
11
|
.boolean()
|
|
@@ -16,7 +16,7 @@ export default defineAction({
|
|
|
16
16
|
return {
|
|
17
17
|
enabled,
|
|
18
18
|
message: enabled
|
|
19
|
-
? "Demo mode is ON — emails and
|
|
19
|
+
? "Demo mode is ON — UI emails are anonymized and supported dashboard charts are reshaped for presentation. Agent-visible emails and numbers are also anonymized. Names, free text, labels, IDs, and structure are preserved."
|
|
20
20
|
: "Demo mode is OFF — real data is shown again.",
|
|
21
21
|
};
|
|
22
22
|
},
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Demo mode gate.
|
|
3
3
|
*
|
|
4
|
-
* Demo mode replaces email
|
|
5
|
-
*
|
|
6
|
-
* preserving names, free text, labels, IDs, dates, URLs,
|
|
7
|
-
* app keeps working. The redaction WALK (see ./redact.ts)
|
|
8
|
-
* large payloads, so callers MUST gate it behind this function
|
|
9
|
-
* when it returns true.
|
|
4
|
+
* Demo mode replaces every email address with `anonymous@builder.io` and
|
|
5
|
+
* anonymizes numbers in every action result — for both the UI and what the
|
|
6
|
+
* agent sees — while preserving names, free text, labels, IDs, dates, URLs,
|
|
7
|
+
* and structure so the app keeps working. The redaction WALK (see ./redact.ts)
|
|
8
|
+
* is expensive on large payloads, so callers MUST gate it behind this function
|
|
9
|
+
* and only walk when it returns true.
|
|
10
10
|
*
|
|
11
11
|
* This gate itself is intentionally cheap:
|
|
12
12
|
* - An env-forced deployment (a hosted demo site) short-circuits with zero
|
|
@@ -46,6 +46,32 @@ const SKIP_SUBSTRINGS = [
|
|
|
46
46
|
"/_agent-native/runs",
|
|
47
47
|
];
|
|
48
48
|
|
|
49
|
+
// Raw rrweb payloads are playback data, not ordinary app UI records. Skipping
|
|
50
|
+
// these is not just a perf optimization that avoids walking/cloning huge
|
|
51
|
+
// DOM/event trees on the main thread: demo number
|
|
52
|
+
// redaction previously ran over this exact raw replay JSON and faked any
|
|
53
|
+
// integer >= 1000 it found — Meta/ViewportResize widths, pointer x/y
|
|
54
|
+
// coordinates, and numeric values inside `_cssText` and SVG attributes. That
|
|
55
|
+
// was the root cause of the 2026-07 "ultra-wide replay" bugs (stages
|
|
56
|
+
// rendered thousands of pixels wide, frozen/teleporting cursors, giant
|
|
57
|
+
// icons) even though every stored recording was always geometrically sane —
|
|
58
|
+
// the corruption happened at *view* time, not at capture/storage time. Small
|
|
59
|
+
// list/summary/manifest responses stay eligible so rendered visitor identities
|
|
60
|
+
// are still anonymized. NEVER remove the raw payload skips or broaden them to
|
|
61
|
+
// metadata endpoints that the UI renders directly.
|
|
62
|
+
const RAW_REPLAY_PAYLOAD_RE =
|
|
63
|
+
/\/api\/session-replay\/recordings\/[^/?#]+\/(?:chunks(?:\/[^/?#]+)?|events)(?:[/?#]|$)/;
|
|
64
|
+
const RAW_AGENT_REPLAY_EVENTS_RE =
|
|
65
|
+
/\/api\/session-replay\/agent-events\.json(?:[?#]|$)/;
|
|
66
|
+
|
|
67
|
+
export function shouldSkipDemoResponseRedaction(url: string): boolean {
|
|
68
|
+
return (
|
|
69
|
+
SKIP_SUBSTRINGS.some((substring) => url.includes(substring)) ||
|
|
70
|
+
RAW_REPLAY_PAYLOAD_RE.test(url) ||
|
|
71
|
+
RAW_AGENT_REPLAY_EVENTS_RE.test(url)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
49
75
|
let installed = false;
|
|
50
76
|
let demoEnabled = false;
|
|
51
77
|
let originalFetch: typeof fetch | null = null;
|
|
@@ -148,7 +174,7 @@ export function ensureDemoModeFetchInterceptor(): void {
|
|
|
148
174
|
|
|
149
175
|
try {
|
|
150
176
|
const url = urlOf(input);
|
|
151
|
-
if (
|
|
177
|
+
if (shouldSkipDemoResponseRedaction(url)) return res;
|
|
152
178
|
|
|
153
179
|
// Only buffered, finite JSON. SSE / streaming / chunked-forever bodies
|
|
154
180
|
// never reach `redactDemoData`: streaming content-types are excluded,
|
|
@@ -167,7 +193,13 @@ export function ensureDemoModeFetchInterceptor(): void {
|
|
|
167
193
|
if (res.bodyUsed) return res;
|
|
168
194
|
|
|
169
195
|
const data = await withTimeout(res.clone().json(), 3_000);
|
|
170
|
-
|
|
196
|
+
// Frontend reads only need identity privacy. Dashboard charts apply
|
|
197
|
+
// their purpose-built demo trend transform at render time, so mutating
|
|
198
|
+
// every numeric field in every JSON response is unnecessary work.
|
|
199
|
+
const redacted = redactDemoData(data, {
|
|
200
|
+
redactNumbers: false,
|
|
201
|
+
redactProtectedEmails: true,
|
|
202
|
+
});
|
|
171
203
|
|
|
172
204
|
const headers = new Headers(res.headers);
|
|
173
205
|
// Body is re-serialized — these would be wrong now.
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure, dependency-free, deterministic demo-mode redactor.
|
|
3
3
|
*
|
|
4
|
-
* Replaces
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* placeholders before any transform runs, restore them byte-identical
|
|
4
|
+
* Replaces every email with one canonical anonymous address and free numbers
|
|
5
|
+
* with stable fake substitutes. Crucially, it NEVER rewrites identifiers,
|
|
6
|
+
* structural tokens, or timestamps. Names and other free text are deliberately
|
|
7
|
+
* left alone because guessing whether arbitrary text is a person's name is too
|
|
8
|
+
* inaccurate. The string redactor uses a protect-first strategy (mask IDs with
|
|
9
|
+
* opaque placeholders before any transform runs, restore them byte-identical
|
|
11
10
|
* afterwards), and the structure-aware walker additionally protects leaf
|
|
12
11
|
* values by key name.
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
14
|
export interface RedactOptions {
|
|
16
15
|
salt?: string;
|
|
16
|
+
/** Redact numeric values. Defaults to true for backward compatibility. */
|
|
17
|
+
redactNumbers?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Redact emails stored in otherwise protected structural fields such as
|
|
20
|
+
* `userId` and `userKey`. Intended for display-only frontend responses.
|
|
21
|
+
*/
|
|
22
|
+
redactProtectedEmails?: boolean;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
/* ------------------------------------------------------------------ *
|
|
@@ -51,10 +57,6 @@ function seededRng(value: string, salt: string): () => number {
|
|
|
51
57
|
return mulberry32(seedFn());
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
function pick<T>(rng: () => number, pool: readonly T[]): T {
|
|
55
|
-
return pool[Math.floor(rng() * pool.length) % pool.length];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
60
|
/* ------------------------------------------------------------------ *
|
|
59
61
|
* Stable mapping cache (bounded, TTL, leak-free)
|
|
60
62
|
*
|
|
@@ -150,150 +152,19 @@ function memoFake(
|
|
|
150
152
|
return value;
|
|
151
153
|
}
|
|
152
154
|
|
|
153
|
-
/* ------------------------------------------------------------------ *
|
|
154
|
-
* Curated name pools
|
|
155
|
-
* ------------------------------------------------------------------ */
|
|
156
|
-
|
|
157
|
-
const FIRST_NAMES: readonly string[] = [
|
|
158
|
-
"Jane",
|
|
159
|
-
"John",
|
|
160
|
-
"Alex",
|
|
161
|
-
"Maria",
|
|
162
|
-
"Sam",
|
|
163
|
-
"Olivia",
|
|
164
|
-
"Liam",
|
|
165
|
-
"Emma",
|
|
166
|
-
"Noah",
|
|
167
|
-
"Ava",
|
|
168
|
-
"Lucas",
|
|
169
|
-
"Mia",
|
|
170
|
-
"Ethan",
|
|
171
|
-
"Sofia",
|
|
172
|
-
"Mason",
|
|
173
|
-
"Isla",
|
|
174
|
-
"Leo",
|
|
175
|
-
"Aria",
|
|
176
|
-
"Henry",
|
|
177
|
-
"Zoe",
|
|
178
|
-
"Owen",
|
|
179
|
-
"Nora",
|
|
180
|
-
"Caleb",
|
|
181
|
-
"Lily",
|
|
182
|
-
"Ryan",
|
|
183
|
-
"Ruby",
|
|
184
|
-
"Adam",
|
|
185
|
-
"Chloe",
|
|
186
|
-
"Eli",
|
|
187
|
-
"Hazel",
|
|
188
|
-
"Max",
|
|
189
|
-
"Iris",
|
|
190
|
-
"Ben",
|
|
191
|
-
"Clara",
|
|
192
|
-
"Theo",
|
|
193
|
-
"Maya",
|
|
194
|
-
"Felix",
|
|
195
|
-
"Elena",
|
|
196
|
-
"Jonah",
|
|
197
|
-
"Greta",
|
|
198
|
-
];
|
|
199
|
-
|
|
200
|
-
const LAST_NAMES: readonly string[] = [
|
|
201
|
-
"Doe",
|
|
202
|
-
"Smith",
|
|
203
|
-
"Johnson",
|
|
204
|
-
"Lee",
|
|
205
|
-
"Brown",
|
|
206
|
-
"Garcia",
|
|
207
|
-
"Miller",
|
|
208
|
-
"Davis",
|
|
209
|
-
"Wilson",
|
|
210
|
-
"Moore",
|
|
211
|
-
"Taylor",
|
|
212
|
-
"Anderson",
|
|
213
|
-
"Thomas",
|
|
214
|
-
"Jackson",
|
|
215
|
-
"White",
|
|
216
|
-
"Harris",
|
|
217
|
-
"Martin",
|
|
218
|
-
"Clark",
|
|
219
|
-
"Lewis",
|
|
220
|
-
"Walker",
|
|
221
|
-
"Hall",
|
|
222
|
-
"Young",
|
|
223
|
-
"King",
|
|
224
|
-
"Wright",
|
|
225
|
-
"Hill",
|
|
226
|
-
"Green",
|
|
227
|
-
"Adams",
|
|
228
|
-
"Baker",
|
|
229
|
-
"Nelson",
|
|
230
|
-
"Carter",
|
|
231
|
-
"Mitchell",
|
|
232
|
-
"Perez",
|
|
233
|
-
"Roberts",
|
|
234
|
-
"Turner",
|
|
235
|
-
"Phillips",
|
|
236
|
-
"Campbell",
|
|
237
|
-
"Parker",
|
|
238
|
-
"Evans",
|
|
239
|
-
"Edwards",
|
|
240
|
-
"Collins",
|
|
241
|
-
];
|
|
242
|
-
|
|
243
155
|
/* ------------------------------------------------------------------ *
|
|
244
156
|
* Fake value generators (deterministic in value + salt)
|
|
245
157
|
* ------------------------------------------------------------------ */
|
|
246
158
|
|
|
247
|
-
|
|
248
|
-
// local part is fake, so no real mailbox is implied) plus the canonical
|
|
249
|
-
// reserved fictional-company domains (Contoso/Fabrikam/Northwind), which read
|
|
250
|
-
// as real businesses but are intentional placeholders. No example.com.
|
|
251
|
-
const FAKE_EMAIL_DOMAINS: readonly string[] = [
|
|
252
|
-
"gmail.com",
|
|
253
|
-
"outlook.com",
|
|
254
|
-
"icloud.com",
|
|
255
|
-
"yahoo.com",
|
|
256
|
-
"proton.me",
|
|
257
|
-
"hey.com",
|
|
258
|
-
"fastmail.com",
|
|
259
|
-
"contoso.com",
|
|
260
|
-
"fabrikam.com",
|
|
261
|
-
"northwind.com",
|
|
262
|
-
"acme.io",
|
|
263
|
-
"globex.net",
|
|
264
|
-
];
|
|
159
|
+
export const DEMO_ANONYMOUS_EMAIL = "anonymous@builder.io";
|
|
265
160
|
|
|
266
161
|
function fakeEmail(original: string, salt: string): string {
|
|
267
|
-
return memoFake(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const shape = Math.floor(rng() * 6);
|
|
274
|
-
let local: string;
|
|
275
|
-
switch (shape) {
|
|
276
|
-
case 0:
|
|
277
|
-
local = `${first}.${last}`;
|
|
278
|
-
break;
|
|
279
|
-
case 1:
|
|
280
|
-
local = `${first}${last}`;
|
|
281
|
-
break;
|
|
282
|
-
case 2:
|
|
283
|
-
local = `${first[0]}${last}`;
|
|
284
|
-
break;
|
|
285
|
-
case 3:
|
|
286
|
-
local = `${first}.${last}${1 + Math.floor(rng() * 89)}`;
|
|
287
|
-
break;
|
|
288
|
-
case 4:
|
|
289
|
-
local = `${first}_${last}`;
|
|
290
|
-
break;
|
|
291
|
-
default:
|
|
292
|
-
local = `${last}.${first}`;
|
|
293
|
-
break;
|
|
294
|
-
}
|
|
295
|
-
return `${local}@${domain}`;
|
|
296
|
-
});
|
|
162
|
+
return memoFake(
|
|
163
|
+
"email",
|
|
164
|
+
original.toLowerCase(),
|
|
165
|
+
salt,
|
|
166
|
+
() => DEMO_ANONYMOUS_EMAIL,
|
|
167
|
+
);
|
|
297
168
|
}
|
|
298
169
|
|
|
299
170
|
/**
|
|
@@ -532,18 +403,29 @@ function transformNumbers(text: string, salt: string): string {
|
|
|
532
403
|
* Public: string redactor
|
|
533
404
|
* ------------------------------------------------------------------ */
|
|
534
405
|
|
|
535
|
-
function redactDemoStringInternal(
|
|
406
|
+
function redactDemoStringInternal(
|
|
407
|
+
text: string,
|
|
408
|
+
salt: string,
|
|
409
|
+
redactNumbers: boolean,
|
|
410
|
+
): string {
|
|
536
411
|
if (typeof text !== "string" || text.length === 0) return text;
|
|
412
|
+
if (!text.includes("@") && (!redactNumbers || !/[\d$€£¥]/.test(text))) {
|
|
413
|
+
return text;
|
|
414
|
+
}
|
|
537
415
|
|
|
538
416
|
const { text: masked, restore } = protect(text);
|
|
539
417
|
let out = masked;
|
|
540
418
|
out = transformEmails(out, salt);
|
|
541
|
-
out = transformNumbers(out, salt);
|
|
419
|
+
if (redactNumbers) out = transformNumbers(out, salt);
|
|
542
420
|
return unprotect(out, restore);
|
|
543
421
|
}
|
|
544
422
|
|
|
545
423
|
export function redactDemoString(text: string, opts?: RedactOptions): string {
|
|
546
|
-
return redactDemoStringInternal(
|
|
424
|
+
return redactDemoStringInternal(
|
|
425
|
+
text,
|
|
426
|
+
opts?.salt ?? "",
|
|
427
|
+
opts?.redactNumbers !== false,
|
|
428
|
+
);
|
|
547
429
|
}
|
|
548
430
|
|
|
549
431
|
/* ------------------------------------------------------------------ *
|
|
@@ -586,6 +468,8 @@ function walk(
|
|
|
586
468
|
salt: string,
|
|
587
469
|
depth: number,
|
|
588
470
|
seen: WeakSet<object>,
|
|
471
|
+
redactNumbers: boolean,
|
|
472
|
+
redactProtectedEmails: boolean,
|
|
589
473
|
): unknown {
|
|
590
474
|
if (depth > MAX_DEPTH) return value;
|
|
591
475
|
|
|
@@ -594,11 +478,11 @@ function walk(
|
|
|
594
478
|
const t = typeof value;
|
|
595
479
|
|
|
596
480
|
if (t === "string") {
|
|
597
|
-
return redactDemoStringInternal(value as string, salt);
|
|
481
|
+
return redactDemoStringInternal(value as string, salt, redactNumbers);
|
|
598
482
|
}
|
|
599
483
|
|
|
600
484
|
if (t === "number") {
|
|
601
|
-
return redactNumberLeaf(value as number, salt);
|
|
485
|
+
return redactNumbers ? redactNumberLeaf(value as number, salt) : value;
|
|
602
486
|
}
|
|
603
487
|
|
|
604
488
|
if (t === "boolean" || t === "bigint" || t === "function" || t === "symbol") {
|
|
@@ -610,7 +494,9 @@ function walk(
|
|
|
610
494
|
if (Array.isArray(value)) {
|
|
611
495
|
if (seen.has(value)) return value;
|
|
612
496
|
seen.add(value);
|
|
613
|
-
const out = value.map((item) =>
|
|
497
|
+
const out = value.map((item) =>
|
|
498
|
+
walk(item, salt, depth + 1, seen, redactNumbers, redactProtectedEmails),
|
|
499
|
+
);
|
|
614
500
|
seen.delete(value);
|
|
615
501
|
return out;
|
|
616
502
|
}
|
|
@@ -629,14 +515,34 @@ function walk(
|
|
|
629
515
|
) {
|
|
630
516
|
// Still recurse into nested structures, but the protected key does
|
|
631
517
|
// not transform its own leaf value.
|
|
632
|
-
out[key] = walk(
|
|
518
|
+
out[key] = walk(
|
|
519
|
+
entry,
|
|
520
|
+
salt,
|
|
521
|
+
depth + 1,
|
|
522
|
+
seen,
|
|
523
|
+
redactNumbers,
|
|
524
|
+
redactProtectedEmails,
|
|
525
|
+
);
|
|
526
|
+
} else if (
|
|
527
|
+
redactProtectedEmails &&
|
|
528
|
+
typeof entry === "string" &&
|
|
529
|
+
entry.includes("@")
|
|
530
|
+
) {
|
|
531
|
+
out[key] = redactDemoStringInternal(entry, salt, false);
|
|
633
532
|
} else {
|
|
634
533
|
// Leaf under a protected key: pass through completely untouched.
|
|
635
534
|
out[key] = entry;
|
|
636
535
|
}
|
|
637
536
|
continue;
|
|
638
537
|
}
|
|
639
|
-
out[key] = walk(
|
|
538
|
+
out[key] = walk(
|
|
539
|
+
entry,
|
|
540
|
+
salt,
|
|
541
|
+
depth + 1,
|
|
542
|
+
seen,
|
|
543
|
+
redactNumbers,
|
|
544
|
+
redactProtectedEmails,
|
|
545
|
+
);
|
|
640
546
|
}
|
|
641
547
|
seen.delete(value);
|
|
642
548
|
return out;
|
|
@@ -648,7 +554,14 @@ function walk(
|
|
|
648
554
|
|
|
649
555
|
export function redactDemoData<T>(value: T, opts?: RedactOptions): T {
|
|
650
556
|
const salt = opts?.salt ?? "";
|
|
651
|
-
return walk(
|
|
557
|
+
return walk(
|
|
558
|
+
value,
|
|
559
|
+
salt,
|
|
560
|
+
0,
|
|
561
|
+
new WeakSet<object>(),
|
|
562
|
+
opts?.redactNumbers !== false,
|
|
563
|
+
opts?.redactProtectedEmails === true,
|
|
564
|
+
) as T;
|
|
652
565
|
}
|
|
653
566
|
|
|
654
567
|
/**
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buildSessionReplayIframeBootstrap } from "./session-replay-iframe.js";
|
|
2
|
+
|
|
1
3
|
const EXTENSION_IFRAME_CSP_BASE =
|
|
2
4
|
"default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' data: blob:; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';";
|
|
3
5
|
|
|
@@ -744,6 +746,7 @@ export function buildExtensionHtml(
|
|
|
744
746
|
}
|
|
745
747
|
});
|
|
746
748
|
</script>
|
|
749
|
+
${buildSessionReplayIframeBootstrap()}
|
|
747
750
|
</head>
|
|
748
751
|
<body${extensionId ? ` data-extension-id="${extensionIdAttr}" data-tool-id="${extensionIdAttr}"` : ""} class="text-foreground">
|
|
749
752
|
${content}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isTrustedSessionReplayIframeParentOrigin,
|
|
3
|
+
SESSION_REPLAY_IFRAME_PROBE,
|
|
4
|
+
SESSION_REPLAY_IFRAME_START,
|
|
5
|
+
SESSION_REPLAY_IFRAME_STOP,
|
|
6
|
+
} from "../session-replay-iframe-protocol.js";
|
|
7
|
+
|
|
8
|
+
export const RRWEB_RECORD_IFRAME_CDN_URL =
|
|
9
|
+
"https://cdn.jsdelivr.net/npm/@rrweb/record@2.1.0/umd/record.min.js";
|
|
10
|
+
export const RRWEB_RECORD_IFRAME_SRI =
|
|
11
|
+
"sha384-MrD66HBNSykaP2N95+6hQCFlF5oH2tvL3TD/zyvHNkP/sAFWZx98DX9MEDy8MdVT";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Installs the cooperative side of rrweb's cross-origin iframe protocol.
|
|
15
|
+
* Nothing is fetched or recorded until a trusted first-party parent asks the
|
|
16
|
+
* frame to start. rrweb then forwards its events directly to that parent.
|
|
17
|
+
*/
|
|
18
|
+
export function buildSessionReplayIframeBootstrap(): string {
|
|
19
|
+
const probeType = JSON.stringify(SESSION_REPLAY_IFRAME_PROBE);
|
|
20
|
+
const startType = JSON.stringify(SESSION_REPLAY_IFRAME_START);
|
|
21
|
+
const stopType = JSON.stringify(SESSION_REPLAY_IFRAME_STOP);
|
|
22
|
+
const scriptUrl = JSON.stringify(RRWEB_RECORD_IFRAME_CDN_URL);
|
|
23
|
+
const integrity = JSON.stringify(RRWEB_RECORD_IFRAME_SRI);
|
|
24
|
+
|
|
25
|
+
return `<script>
|
|
26
|
+
(function() {
|
|
27
|
+
var recorderStop = null;
|
|
28
|
+
var recorderLoad = null;
|
|
29
|
+
var startGeneration = 0;
|
|
30
|
+
var isTrustedParentOrigin = ${isTrustedSessionReplayIframeParentOrigin.toString()};
|
|
31
|
+
|
|
32
|
+
function stopRecorder() {
|
|
33
|
+
startGeneration += 1;
|
|
34
|
+
if (typeof recorderStop === 'function') {
|
|
35
|
+
try { recorderStop(); } catch (_) {}
|
|
36
|
+
}
|
|
37
|
+
recorderStop = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getRecord() {
|
|
41
|
+
return window.rrwebRecord && typeof window.rrwebRecord.record === 'function'
|
|
42
|
+
? window.rrwebRecord.record
|
|
43
|
+
: null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function loadRecorder() {
|
|
47
|
+
if (recorderLoad) return recorderLoad;
|
|
48
|
+
recorderLoad = new Promise(function(resolve, reject) {
|
|
49
|
+
var script = document.createElement('script');
|
|
50
|
+
script.src = ${scriptUrl};
|
|
51
|
+
script.integrity = ${integrity};
|
|
52
|
+
script.crossOrigin = 'anonymous';
|
|
53
|
+
script.referrerPolicy = 'no-referrer';
|
|
54
|
+
script.async = true;
|
|
55
|
+
script.onload = function() {
|
|
56
|
+
var record = getRecord();
|
|
57
|
+
if (record) resolve(record);
|
|
58
|
+
else reject(new Error('rrweb recorder did not initialize'));
|
|
59
|
+
};
|
|
60
|
+
script.onerror = function() {
|
|
61
|
+
recorderLoad = null;
|
|
62
|
+
reject(new Error('rrweb recorder failed to load'));
|
|
63
|
+
};
|
|
64
|
+
document.head.appendChild(script);
|
|
65
|
+
});
|
|
66
|
+
return recorderLoad;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validOptions(options) {
|
|
70
|
+
return !!options &&
|
|
71
|
+
typeof options === 'object' &&
|
|
72
|
+
typeof options.blockSelector === 'string' &&
|
|
73
|
+
typeof options.ignoreSelector === 'string' &&
|
|
74
|
+
typeof options.maskTextSelector === 'string' &&
|
|
75
|
+
typeof options.maskAllInputs === 'boolean' &&
|
|
76
|
+
typeof options.recordCanvas === 'boolean' &&
|
|
77
|
+
typeof options.collectFonts === 'boolean' &&
|
|
78
|
+
typeof options.inlineImages === 'boolean' &&
|
|
79
|
+
!!options.sampling && typeof options.sampling === 'object';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function startRecorder(options) {
|
|
83
|
+
if (!validOptions(options)) return;
|
|
84
|
+
stopRecorder();
|
|
85
|
+
var generation = startGeneration;
|
|
86
|
+
loadRecorder().then(function(record) {
|
|
87
|
+
if (generation !== startGeneration) return;
|
|
88
|
+
recorderStop = record({
|
|
89
|
+
blockSelector: options.blockSelector,
|
|
90
|
+
ignoreSelector: options.ignoreSelector,
|
|
91
|
+
maskTextClass: options.maskTextClass,
|
|
92
|
+
maskTextSelector: options.maskTextSelector,
|
|
93
|
+
maskAllInputs: options.maskAllInputs,
|
|
94
|
+
maskInputOptions: options.maskInputOptions,
|
|
95
|
+
recordCanvas: options.recordCanvas,
|
|
96
|
+
collectFonts: options.collectFonts,
|
|
97
|
+
inlineImages: options.inlineImages,
|
|
98
|
+
sampling: options.sampling,
|
|
99
|
+
recordCrossOriginIframes: true
|
|
100
|
+
}) || null;
|
|
101
|
+
}).catch(function() {});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
window.addEventListener('message', function(event) {
|
|
105
|
+
if (event.source !== window.parent || !isTrustedParentOrigin(event.origin, window.location.href)) return;
|
|
106
|
+
var message = event.data;
|
|
107
|
+
if (!message || typeof message !== 'object') return;
|
|
108
|
+
if (message.type === ${startType}) startRecorder(message.options);
|
|
109
|
+
else if (message.type === ${stopType}) stopRecorder();
|
|
110
|
+
});
|
|
111
|
+
window.addEventListener('pagehide', stopRecorder);
|
|
112
|
+
|
|
113
|
+
if (window.parent !== window) {
|
|
114
|
+
window.parent.postMessage({ type: ${probeType} }, '*');
|
|
115
|
+
}
|
|
116
|
+
})();
|
|
117
|
+
</script>`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function injectSessionReplayIframeBootstrap(html: string): string {
|
|
121
|
+
const bootstrap = buildSessionReplayIframeBootstrap();
|
|
122
|
+
const headClose = html.search(/<\/head\s*>/i);
|
|
123
|
+
if (headClose >= 0) {
|
|
124
|
+
return `${html.slice(0, headClose)}${bootstrap}${html.slice(headClose)}`;
|
|
125
|
+
}
|
|
126
|
+
const bodyOpen = html.search(/<body(?:\s[^>]*)?>/i);
|
|
127
|
+
if (bodyOpen >= 0) {
|
|
128
|
+
return `${html.slice(0, bodyOpen)}${bootstrap}${html.slice(bodyOpen)}`;
|
|
129
|
+
}
|
|
130
|
+
return `${bootstrap}${html}`;
|
|
131
|
+
}
|