@agent-native/core 0.77.24 → 0.78.1
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/template-analytics.md +5 -0
- package/corpus/core/docs/content/tracking.md +45 -0
- package/corpus/core/package.json +3 -1
- package/corpus/core/src/agent/types.ts +22 -0
- package/corpus/core/src/client/AgentPanel.tsx +34 -18
- package/corpus/core/src/client/AssistantChat.tsx +40 -0
- package/corpus/core/src/client/agent-chat.ts +173 -0
- package/corpus/core/src/client/analytics.ts +141 -5
- package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
- package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
- package/corpus/core/src/client/composer/types.ts +22 -0
- package/corpus/core/src/client/index.ts +14 -0
- package/corpus/core/src/client/session-replay.ts +800 -0
- package/corpus/core/src/db/schema.ts +12 -0
- package/corpus/core/src/deploy/build.ts +75 -0
- package/corpus/core/src/private-blob/index.ts +18 -0
- package/corpus/core/src/private-blob/registry.ts +241 -0
- package/corpus/core/src/private-blob/types.ts +55 -0
- package/corpus/core/src/scripts/utils.ts +34 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
- package/corpus/core/src/server/email.ts +5 -0
- package/corpus/core/src/server/index.ts +1 -0
- package/corpus/core/src/styles/agent-native.css +24 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +43 -0
- package/corpus/templates/analytics/AGENTS.md +32 -1
- package/corpus/templates/analytics/DEVELOPING.md +9 -1
- package/corpus/templates/analytics/actions/delete-dashboard-report-subscription.ts +24 -0
- package/corpus/templates/analytics/actions/get-session-replay-events.ts +50 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +27 -0
- package/corpus/templates/analytics/actions/list-dashboard-report-subscriptions.ts +24 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +59 -0
- package/corpus/templates/analytics/actions/navigate.ts +15 -5
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
- package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +36 -0
- package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +60 -0
- package/corpus/templates/analytics/actions/view-screen.ts +50 -2
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +51 -2
- package/corpus/templates/analytics/app/components/layout/Header.tsx +4 -1
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +14 -2
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +6 -6
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +35 -6
- package/corpus/templates/analytics/app/i18n-data.ts +1119 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +437 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -12
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +43 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +136 -43
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-filters.ts +26 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +450 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +419 -0
- package/corpus/templates/analytics/app/routes/overview.tsx +13 -6
- package/corpus/templates/analytics/app/routes/sessions.$recordingId.tsx +10 -0
- package/corpus/templates/analytics/app/routes/sessions._index.tsx +10 -0
- package/corpus/templates/analytics/changelog/2026-06-25-analytics-now-opens-directly-to-ask-instead-of-the-old-overv.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-now-show-reliable-blue-placement-lines-whil.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboards-can-now-send-scheduled-daily-email-reports-from-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-browser-sessions-can-now-be-reviewed-from.md +6 -0
- package/corpus/templates/analytics/package.json +5 -2
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +131 -0
- package/corpus/templates/analytics/scripts/seed-session-replay.ts +198 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +140 -27
- package/corpus/templates/analytics/server/db/index.ts +12 -0
- package/corpus/templates/analytics/server/db/schema.ts +131 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +281 -0
- package/corpus/templates/analytics/server/handlers/sql-query.ts +8 -348
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +66 -0
- package/corpus/templates/analytics/server/jobs/session-replay-retention.ts +29 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +365 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +473 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +405 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +63 -18
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +98 -26
- package/corpus/templates/analytics/server/lib/session-replay.ts +1645 -0
- package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +47 -0
- package/corpus/templates/analytics/server/plugins/db.ts +200 -0
- package/corpus/templates/analytics/server/plugins/session-replay-retention-jobs.ts +41 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.options.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.post.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-reports/run.post.ts +61 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks/[seq].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/events.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/manifest.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
- package/corpus/templates/assets/AGENTS.md +18 -3
- package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
- package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
- package/corpus/templates/assets/actions/generate-asset.ts +30 -23
- package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
- package/corpus/templates/assets/actions/generate-image.ts +46 -30
- package/corpus/templates/assets/actions/generate-video.ts +18 -3
- package/corpus/templates/assets/actions/list-assets.ts +39 -8
- package/corpus/templates/assets/actions/list-libraries.ts +24 -2
- package/corpus/templates/assets/actions/navigate.ts +1 -1
- package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
- package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
- package/corpus/templates/assets/actions/variant-slots.ts +131 -28
- package/corpus/templates/assets/actions/view-screen.ts +9 -0
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
- package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
- package/corpus/templates/assets/app/i18n-data.ts +1815 -0
- package/corpus/templates/assets/app/lib/libraries.ts +6 -0
- package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/audit.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
- package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
- package/corpus/templates/assets/app/routes/library.tsx +1454 -175
- package/corpus/templates/assets/server/db/index.ts +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +86 -15
- package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
- package/corpus/templates/assets/server/plugins/db.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +6 -3
- package/dist/agent/types.d.ts +21 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +6 -2
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +10 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +15 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +32 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +113 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/analytics.d.ts +22 -3
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +96 -4
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +2 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +228 -56
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/MentionReference.js +3 -0
- package/dist/client/composer/extensions/MentionReference.js.map +1 -1
- package/dist/client/composer/types.d.ts +21 -0
- package/dist/client/composer/types.d.ts.map +1 -1
- package/dist/client/composer/types.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +41 -0
- package/dist/client/session-replay.d.ts.map +1 -0
- package/dist/client/session-replay.js +595 -0
- package/dist/client/session-replay.js.map +1 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/schema.d.ts +3 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +6 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/deploy/build.d.ts +8 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +66 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +8 -8
- package/dist/private-blob/index.d.ts +3 -0
- package/dist/private-blob/index.d.ts.map +1 -0
- package/dist/private-blob/index.js +2 -0
- package/dist/private-blob/index.js.map +1 -0
- package/dist/private-blob/registry.d.ts +10 -0
- package/dist/private-blob/registry.d.ts.map +1 -0
- package/dist/private-blob/registry.js +152 -0
- package/dist/private-blob/registry.js.map +1 -0
- package/dist/private-blob/types.d.ts +51 -0
- package/dist/private-blob/types.d.ts.map +1 -0
- package/dist/private-blob/types.js +2 -0
- package/dist/private-blob/types.js.map +1 -0
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/scripts/utils.d.ts +4 -4
- package/dist/scripts/utils.d.ts.map +1 -1
- package/dist/scripts/utils.js +30 -6
- package/dist/scripts/utils.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/email.d.ts +2 -0
- package/dist/server/email.d.ts.map +1 -1
- package/dist/server/email.js +3 -0
- package/dist/server/email.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +24 -0
- package/docs/content/template-analytics.md +5 -0
- package/docs/content/tracking.md +45 -0
- package/package.json +3 -1
- package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +0 -209
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { getAnalyticsSessionId, getAnalyticsAnonymousId, scrubUrl, } from "./analytics.js";
|
|
2
|
+
const DEFAULT_REPLAY_PATH = "/api/analytics/replay";
|
|
3
|
+
const DEFAULT_SAMPLING_SALT = "agent-native-session-replay";
|
|
4
|
+
const SESSION_REPLAY_STATE_KEY = Symbol.for("agent-native.client.sessionReplay");
|
|
5
|
+
const SESSION_REPLAY_ID_STORAGE_KEY = "agent-native.session_replay_id";
|
|
6
|
+
const DEFAULT_BLOCK_SELECTOR = [
|
|
7
|
+
"[data-sensitive]",
|
|
8
|
+
"[data-an-block]",
|
|
9
|
+
"[data-an-private]",
|
|
10
|
+
"[data-private]",
|
|
11
|
+
".an-block",
|
|
12
|
+
".an-replay-block",
|
|
13
|
+
".an-private",
|
|
14
|
+
".rr-block",
|
|
15
|
+
"[autocomplete='cc-number']",
|
|
16
|
+
"[autocomplete='cc-csc']",
|
|
17
|
+
"[autocomplete='cc-exp']",
|
|
18
|
+
"[name*='password' i]",
|
|
19
|
+
"[name*='credit' i]",
|
|
20
|
+
"[name*='card' i]",
|
|
21
|
+
"[name*='ssn' i]",
|
|
22
|
+
].join(", ");
|
|
23
|
+
const DEFAULT_IGNORE_SELECTOR = ".an-ignore, [data-an-ignore]";
|
|
24
|
+
const DEFAULT_MASK_TEXT_CLASS = "an-mask";
|
|
25
|
+
const DEFAULT_MASK_TEXT_SELECTOR = "[data-an-mask]";
|
|
26
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
27
|
+
const DEFAULT_MAX_DURATION_MS = 30 * 60 * 1000;
|
|
28
|
+
const DEFAULT_MAX_EVENTS_PER_BATCH = 50;
|
|
29
|
+
const DEFAULT_MAX_BATCH_BYTES = 256 * 1024;
|
|
30
|
+
const URL_LIKE_KEYS = new Set([
|
|
31
|
+
"url",
|
|
32
|
+
"uri",
|
|
33
|
+
"href",
|
|
34
|
+
"src",
|
|
35
|
+
"currentUrl",
|
|
36
|
+
"referrer",
|
|
37
|
+
"from",
|
|
38
|
+
"to",
|
|
39
|
+
]);
|
|
40
|
+
function getState() {
|
|
41
|
+
const g = globalThis;
|
|
42
|
+
if (!g[SESSION_REPLAY_STATE_KEY]) {
|
|
43
|
+
g[SESSION_REPLAY_STATE_KEY] = {
|
|
44
|
+
active: false,
|
|
45
|
+
replayId: null,
|
|
46
|
+
sequence: 0,
|
|
47
|
+
queue: [],
|
|
48
|
+
queuedBytes: 0,
|
|
49
|
+
flushTimer: null,
|
|
50
|
+
maxDurationTimer: null,
|
|
51
|
+
flushing: false,
|
|
52
|
+
stopRecorder: null,
|
|
53
|
+
restoreUrlMonitor: null,
|
|
54
|
+
removeLifecycleListeners: null,
|
|
55
|
+
options: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return g[SESSION_REPLAY_STATE_KEY];
|
|
59
|
+
}
|
|
60
|
+
function safeStorageGet(key) {
|
|
61
|
+
try {
|
|
62
|
+
return window.localStorage.getItem(key);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function safeStorageSet(key, value) {
|
|
69
|
+
try {
|
|
70
|
+
window.localStorage.setItem(key, value);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// private browsing / storage disabled -- replay still works for this page
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function generateReplayId() {
|
|
77
|
+
try {
|
|
78
|
+
if (typeof crypto !== "undefined" &&
|
|
79
|
+
typeof crypto.randomUUID === "function") {
|
|
80
|
+
return crypto.randomUUID();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// fall through
|
|
85
|
+
}
|
|
86
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
87
|
+
}
|
|
88
|
+
function readStoredReplaySession() {
|
|
89
|
+
const raw = safeStorageGet(SESSION_REPLAY_ID_STORAGE_KEY);
|
|
90
|
+
if (!raw)
|
|
91
|
+
return null;
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(raw);
|
|
94
|
+
if (!parsed || typeof parsed !== "object")
|
|
95
|
+
return null;
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function writeStoredReplaySession(value) {
|
|
103
|
+
safeStorageSet(SESSION_REPLAY_ID_STORAGE_KEY, JSON.stringify(value));
|
|
104
|
+
}
|
|
105
|
+
function getOrCreateReplaySession(sessionId) {
|
|
106
|
+
const parsed = readStoredReplaySession();
|
|
107
|
+
if (parsed?.sessionId === sessionId && parsed.replayId) {
|
|
108
|
+
const sequence = typeof parsed.sequence === "number" &&
|
|
109
|
+
Number.isFinite(parsed.sequence) &&
|
|
110
|
+
parsed.sequence >= 0
|
|
111
|
+
? Math.floor(parsed.sequence)
|
|
112
|
+
: 0;
|
|
113
|
+
return { replayId: parsed.replayId, sequence };
|
|
114
|
+
}
|
|
115
|
+
const replayId = generateReplayId();
|
|
116
|
+
writeStoredReplaySession({ sessionId, replayId, sequence: 0 });
|
|
117
|
+
return { replayId, sequence: 0 };
|
|
118
|
+
}
|
|
119
|
+
function persistReplaySequence(sessionId, replayId, sequence) {
|
|
120
|
+
writeStoredReplaySession({ sessionId, replayId, sequence });
|
|
121
|
+
}
|
|
122
|
+
function clampSamplingRate(value) {
|
|
123
|
+
if (typeof value !== "number" || Number.isNaN(value))
|
|
124
|
+
return 1;
|
|
125
|
+
return Math.min(1, Math.max(0, value));
|
|
126
|
+
}
|
|
127
|
+
function readEnvString(key) {
|
|
128
|
+
return import.meta.env?.[key]?.trim();
|
|
129
|
+
}
|
|
130
|
+
function readEnvNumber(key) {
|
|
131
|
+
const raw = readEnvString(key);
|
|
132
|
+
if (!raw)
|
|
133
|
+
return undefined;
|
|
134
|
+
const value = Number(raw);
|
|
135
|
+
return Number.isFinite(value) ? value : undefined;
|
|
136
|
+
}
|
|
137
|
+
function readFirstEnvString(keys) {
|
|
138
|
+
for (const key of keys) {
|
|
139
|
+
const value = readEnvString(key);
|
|
140
|
+
if (value)
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
function readFirstEnvNumber(keys) {
|
|
146
|
+
for (const key of keys) {
|
|
147
|
+
const value = readEnvNumber(key);
|
|
148
|
+
if (value !== undefined)
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
function replayEndpointFromAnalyticsEndpoint(value) {
|
|
154
|
+
try {
|
|
155
|
+
const url = new URL(value);
|
|
156
|
+
if (url.pathname.endsWith("/api/analytics/track")) {
|
|
157
|
+
url.pathname = url.pathname.replace(/\/api\/analytics\/track$/, "/api/analytics/replay");
|
|
158
|
+
return url.toString();
|
|
159
|
+
}
|
|
160
|
+
if (url.pathname.endsWith("/track")) {
|
|
161
|
+
url.pathname = url.pathname.replace(/\/track$/, "/api/analytics/replay");
|
|
162
|
+
return url.toString();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Fall through to the relative path cases below.
|
|
167
|
+
}
|
|
168
|
+
if (value.endsWith("/api/analytics/track")) {
|
|
169
|
+
return value.replace(/\/api\/analytics\/track$/, "/api/analytics/replay");
|
|
170
|
+
}
|
|
171
|
+
if (value.endsWith("/track")) {
|
|
172
|
+
return value.replace(/\/track$/, "/api/analytics/replay");
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
function defaultReplayEndpoint() {
|
|
177
|
+
const analyticsEndpoint = readEnvString("VITE_AGENT_NATIVE_ANALYTICS_ENDPOINT");
|
|
178
|
+
const derived = analyticsEndpoint
|
|
179
|
+
? replayEndpointFromAnalyticsEndpoint(analyticsEndpoint)
|
|
180
|
+
: null;
|
|
181
|
+
if (derived)
|
|
182
|
+
return derived;
|
|
183
|
+
return typeof window !== "undefined"
|
|
184
|
+
? `${window.location.origin}${DEFAULT_REPLAY_PATH}`
|
|
185
|
+
: DEFAULT_REPLAY_PATH;
|
|
186
|
+
}
|
|
187
|
+
export function getSessionReplaySamplingScore(sessionId, salt = DEFAULT_SAMPLING_SALT) {
|
|
188
|
+
let hash = 2166136261;
|
|
189
|
+
const input = `${salt}:${sessionId}`;
|
|
190
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
191
|
+
hash ^= input.charCodeAt(i);
|
|
192
|
+
hash = Math.imul(hash, 16777619);
|
|
193
|
+
}
|
|
194
|
+
return (hash >>> 0) / 4294967296;
|
|
195
|
+
}
|
|
196
|
+
export function shouldSampleSessionReplay(sessionId, sampleRate = 1, salt = DEFAULT_SAMPLING_SALT) {
|
|
197
|
+
const rate = clampSamplingRate(sampleRate);
|
|
198
|
+
if (rate <= 0)
|
|
199
|
+
return false;
|
|
200
|
+
if (rate >= 1)
|
|
201
|
+
return true;
|
|
202
|
+
return getSessionReplaySamplingScore(sessionId, salt) < rate;
|
|
203
|
+
}
|
|
204
|
+
function matcherAllows(matcher, url) {
|
|
205
|
+
if (typeof matcher === "string")
|
|
206
|
+
return url.includes(matcher);
|
|
207
|
+
if (matcher instanceof RegExp)
|
|
208
|
+
return matcher.test(url);
|
|
209
|
+
return matcher(url);
|
|
210
|
+
}
|
|
211
|
+
function isUrlRecordable(url, options) {
|
|
212
|
+
if (options.allowUrls.length > 0) {
|
|
213
|
+
const allowed = options.allowUrls.some((matcher) => matcherAllows(matcher, url));
|
|
214
|
+
if (!allowed)
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return !options.blockUrls.some((matcher) => matcherAllows(matcher, url));
|
|
218
|
+
}
|
|
219
|
+
function normalizeOptions(options) {
|
|
220
|
+
const publicKey = options.publicKey ||
|
|
221
|
+
readFirstEnvString([
|
|
222
|
+
"VITE_AGENT_NATIVE_SESSION_REPLAY_PUBLIC_KEY",
|
|
223
|
+
"VITE_SESSION_REPLAY_PUBLIC_KEY",
|
|
224
|
+
]) ||
|
|
225
|
+
import.meta.env
|
|
226
|
+
?.VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY;
|
|
227
|
+
if (!publicKey)
|
|
228
|
+
return null;
|
|
229
|
+
const endpoint = options.endpoint ||
|
|
230
|
+
readFirstEnvString([
|
|
231
|
+
"VITE_AGENT_NATIVE_ANALYTICS_REPLAY_ENDPOINT",
|
|
232
|
+
"VITE_AGENT_NATIVE_SESSION_REPLAY_ENDPOINT",
|
|
233
|
+
"VITE_SESSION_REPLAY_INGEST_URL",
|
|
234
|
+
]) ||
|
|
235
|
+
defaultReplayEndpoint();
|
|
236
|
+
return {
|
|
237
|
+
publicKey,
|
|
238
|
+
endpoint,
|
|
239
|
+
sampleRate: clampSamplingRate(options.sampleRate ??
|
|
240
|
+
readFirstEnvNumber([
|
|
241
|
+
"VITE_AGENT_NATIVE_SESSION_REPLAY_SAMPLE_RATE",
|
|
242
|
+
"VITE_SESSION_REPLAY_SAMPLE_RATE",
|
|
243
|
+
])),
|
|
244
|
+
samplingSalt: options.samplingSalt || DEFAULT_SAMPLING_SALT,
|
|
245
|
+
allowUrls: options.allowUrls ?? [],
|
|
246
|
+
blockUrls: options.blockUrls ?? [],
|
|
247
|
+
flushIntervalMs: Math.max(250, options.flushIntervalMs ??
|
|
248
|
+
readFirstEnvNumber([
|
|
249
|
+
"VITE_AGENT_NATIVE_SESSION_REPLAY_CHUNK_INTERVAL_MS",
|
|
250
|
+
"VITE_SESSION_REPLAY_CHUNK_INTERVAL_MS",
|
|
251
|
+
]) ??
|
|
252
|
+
DEFAULT_FLUSH_INTERVAL_MS),
|
|
253
|
+
maxDurationMs: Math.max(1000, options.maxDurationMs ??
|
|
254
|
+
readFirstEnvNumber([
|
|
255
|
+
"VITE_AGENT_NATIVE_SESSION_REPLAY_MAX_DURATION_MS",
|
|
256
|
+
"VITE_SESSION_REPLAY_MAX_DURATION_MS",
|
|
257
|
+
]) ??
|
|
258
|
+
DEFAULT_MAX_DURATION_MS),
|
|
259
|
+
maxEventsPerBatch: Math.max(1, options.maxEventsPerBatch ?? DEFAULT_MAX_EVENTS_PER_BATCH),
|
|
260
|
+
maxBatchBytes: Math.max(1024, options.maxBatchBytes ??
|
|
261
|
+
readFirstEnvNumber([
|
|
262
|
+
"VITE_AGENT_NATIVE_SESSION_REPLAY_CHUNK_MAX_BYTES",
|
|
263
|
+
"VITE_SESSION_REPLAY_CHUNK_MAX_BYTES",
|
|
264
|
+
]) ??
|
|
265
|
+
DEFAULT_MAX_BATCH_BYTES),
|
|
266
|
+
checkoutEveryNth: options.checkoutEveryNth,
|
|
267
|
+
checkoutEveryNms: options.checkoutEveryNms,
|
|
268
|
+
blockSelector: options.blockSelector || DEFAULT_BLOCK_SELECTOR,
|
|
269
|
+
ignoreSelector: options.ignoreSelector || DEFAULT_IGNORE_SELECTOR,
|
|
270
|
+
maskTextClass: options.maskTextClass || DEFAULT_MASK_TEXT_CLASS,
|
|
271
|
+
maskTextSelector: options.maskTextSelector || DEFAULT_MASK_TEXT_SELECTOR,
|
|
272
|
+
maskAllInputs: options.maskAllInputs ?? true,
|
|
273
|
+
recordCanvas: options.recordCanvas ?? false,
|
|
274
|
+
recordCrossOriginIframes: options.recordCrossOriginIframes ?? false,
|
|
275
|
+
collectFonts: options.collectFonts ?? false,
|
|
276
|
+
inlineImages: options.inlineImages ?? false,
|
|
277
|
+
extraProperties: options.extraProperties,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function scrubStringValue(key, value) {
|
|
281
|
+
const lowerKey = key.toLowerCase();
|
|
282
|
+
const isUrlKey = URL_LIKE_KEYS.has(key) || URL_LIKE_KEYS.has(lowerKey);
|
|
283
|
+
if (isUrlKey ||
|
|
284
|
+
value.startsWith("http://") ||
|
|
285
|
+
value.startsWith("https://") ||
|
|
286
|
+
value.startsWith("/")) {
|
|
287
|
+
return scrubUrl(value) ?? value;
|
|
288
|
+
}
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
function scrubReplayValue(value, key = "", depth = 0, seen = new WeakSet()) {
|
|
292
|
+
if (typeof value === "string")
|
|
293
|
+
return scrubStringValue(key, value);
|
|
294
|
+
if (!value || typeof value !== "object")
|
|
295
|
+
return value;
|
|
296
|
+
if (depth > 12)
|
|
297
|
+
return value;
|
|
298
|
+
if (seen.has(value))
|
|
299
|
+
return value;
|
|
300
|
+
seen.add(value);
|
|
301
|
+
if (Array.isArray(value)) {
|
|
302
|
+
return value.map((item) => scrubReplayValue(item, key, depth + 1, seen));
|
|
303
|
+
}
|
|
304
|
+
const out = {};
|
|
305
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
306
|
+
out[childKey] = scrubReplayValue(childValue, childKey, depth + 1, seen);
|
|
307
|
+
}
|
|
308
|
+
return out;
|
|
309
|
+
}
|
|
310
|
+
function scrubReplayEvent(event) {
|
|
311
|
+
return scrubReplayValue(event);
|
|
312
|
+
}
|
|
313
|
+
function estimateJsonBytes(value) {
|
|
314
|
+
try {
|
|
315
|
+
return JSON.stringify(value).length;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return 0;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function enqueueReplayEvent(state, event) {
|
|
322
|
+
if (!state.options)
|
|
323
|
+
return;
|
|
324
|
+
const sanitized = scrubReplayEvent(event);
|
|
325
|
+
const estimatedBytes = estimateJsonBytes(sanitized);
|
|
326
|
+
if (state.queue.length > 0 &&
|
|
327
|
+
state.queuedBytes + estimatedBytes > state.options.maxBatchBytes) {
|
|
328
|
+
void flushSessionReplay("max-bytes");
|
|
329
|
+
}
|
|
330
|
+
state.queue.push(sanitized);
|
|
331
|
+
state.queuedBytes += estimatedBytes;
|
|
332
|
+
if (state.queue.length >= state.options.maxEventsPerBatch) {
|
|
333
|
+
void flushSessionReplay("max-events");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function replayExtraProperties(options) {
|
|
337
|
+
const source = options.extraProperties;
|
|
338
|
+
if (!source)
|
|
339
|
+
return undefined;
|
|
340
|
+
try {
|
|
341
|
+
const props = typeof source === "function" ? source() : source;
|
|
342
|
+
if (!props || typeof props !== "object")
|
|
343
|
+
return undefined;
|
|
344
|
+
return scrubReplayValue(props);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function buildReplayBody(state, reason, events) {
|
|
351
|
+
const options = state.options;
|
|
352
|
+
if (!options || !state.replayId)
|
|
353
|
+
return null;
|
|
354
|
+
const sessionId = getAnalyticsSessionId();
|
|
355
|
+
if (!sessionId)
|
|
356
|
+
return null;
|
|
357
|
+
const body = {
|
|
358
|
+
publicKey: options.publicKey,
|
|
359
|
+
type: "session_replay",
|
|
360
|
+
replayId: state.replayId,
|
|
361
|
+
sessionId,
|
|
362
|
+
anonymousId: getAnalyticsAnonymousId(),
|
|
363
|
+
sequence: state.sequence,
|
|
364
|
+
reason,
|
|
365
|
+
status: isFinalFlushReason(reason) ? "completed" : "active",
|
|
366
|
+
privacyMode: "mask-inputs-and-selected-text",
|
|
367
|
+
url: typeof window !== "undefined"
|
|
368
|
+
? scrubUrl(window.location.href)
|
|
369
|
+
: undefined,
|
|
370
|
+
timestamp: new Date().toISOString(),
|
|
371
|
+
properties: replayExtraProperties(options),
|
|
372
|
+
events,
|
|
373
|
+
};
|
|
374
|
+
state.sequence += 1;
|
|
375
|
+
persistReplaySequence(sessionId, state.replayId, state.sequence);
|
|
376
|
+
return JSON.stringify(body);
|
|
377
|
+
}
|
|
378
|
+
function isFinalFlushReason(reason) {
|
|
379
|
+
return [
|
|
380
|
+
"manual",
|
|
381
|
+
"pagehide",
|
|
382
|
+
"beforeunload",
|
|
383
|
+
"url-blocked",
|
|
384
|
+
"max-duration",
|
|
385
|
+
].includes(reason);
|
|
386
|
+
}
|
|
387
|
+
export async function flushSessionReplay(reason = "manual") {
|
|
388
|
+
const state = getState();
|
|
389
|
+
if (!state.options || state.queue.length === 0 || state.flushing)
|
|
390
|
+
return;
|
|
391
|
+
const events = state.queue.splice(0, state.queue.length);
|
|
392
|
+
state.queuedBytes = 0;
|
|
393
|
+
const body = buildReplayBody(state, reason, events);
|
|
394
|
+
if (!body || !state.options)
|
|
395
|
+
return;
|
|
396
|
+
state.flushing = true;
|
|
397
|
+
try {
|
|
398
|
+
if (navigator.sendBeacon) {
|
|
399
|
+
const sent = navigator.sendBeacon(state.options.endpoint, body);
|
|
400
|
+
if (sent)
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
await fetch(state.options.endpoint, {
|
|
404
|
+
method: "POST",
|
|
405
|
+
body,
|
|
406
|
+
keepalive: true,
|
|
407
|
+
headers: {
|
|
408
|
+
"Content-Type": "text/plain;charset=UTF-8",
|
|
409
|
+
"X-Agent-Native-Analytics-Key": state.options.publicKey,
|
|
410
|
+
},
|
|
411
|
+
}).catch(() => { });
|
|
412
|
+
}
|
|
413
|
+
finally {
|
|
414
|
+
state.flushing = false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function installUrlMonitor(state) {
|
|
418
|
+
if (!state.options || state.restoreUrlMonitor)
|
|
419
|
+
return;
|
|
420
|
+
const options = state.options;
|
|
421
|
+
const check = () => {
|
|
422
|
+
if (!isUrlRecordable(window.location.href, options)) {
|
|
423
|
+
stopSessionReplay("url-blocked");
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
const originalPushState = window.history.pushState;
|
|
427
|
+
const originalReplaceState = window.history.replaceState;
|
|
428
|
+
window.history.pushState = function pushState(...args) {
|
|
429
|
+
const result = originalPushState.apply(this, args);
|
|
430
|
+
queueMicrotask(check);
|
|
431
|
+
return result;
|
|
432
|
+
};
|
|
433
|
+
window.history.replaceState = function replaceState(...args) {
|
|
434
|
+
const result = originalReplaceState.apply(this, args);
|
|
435
|
+
queueMicrotask(check);
|
|
436
|
+
return result;
|
|
437
|
+
};
|
|
438
|
+
window.addEventListener("popstate", check);
|
|
439
|
+
state.restoreUrlMonitor = () => {
|
|
440
|
+
window.history.pushState = originalPushState;
|
|
441
|
+
window.history.replaceState = originalReplaceState;
|
|
442
|
+
window.removeEventListener("popstate", check);
|
|
443
|
+
state.restoreUrlMonitor = null;
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function installLifecycleListeners(state) {
|
|
447
|
+
if (state.removeLifecycleListeners)
|
|
448
|
+
return;
|
|
449
|
+
const flushOnHidden = () => {
|
|
450
|
+
if (document.visibilityState === "hidden") {
|
|
451
|
+
void flushSessionReplay("visibility-hidden");
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
const flushOnUnload = () => {
|
|
455
|
+
void flushSessionReplay("pagehide");
|
|
456
|
+
};
|
|
457
|
+
document.addEventListener("visibilitychange", flushOnHidden);
|
|
458
|
+
window.addEventListener("pagehide", flushOnUnload);
|
|
459
|
+
state.removeLifecycleListeners = () => {
|
|
460
|
+
document.removeEventListener("visibilitychange", flushOnHidden);
|
|
461
|
+
window.removeEventListener("pagehide", flushOnUnload);
|
|
462
|
+
state.removeLifecycleListeners = null;
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
export async function startSessionReplay(options = {}) {
|
|
466
|
+
if (options.enabled === false)
|
|
467
|
+
return { started: false, reason: "disabled" };
|
|
468
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
469
|
+
return { started: false, reason: "not-browser" };
|
|
470
|
+
}
|
|
471
|
+
const normalized = normalizeOptions(options);
|
|
472
|
+
if (!normalized)
|
|
473
|
+
return { started: false, reason: "missing-public-key" };
|
|
474
|
+
const sessionId = getAnalyticsSessionId();
|
|
475
|
+
if (!sessionId)
|
|
476
|
+
return { started: false, reason: "missing-session-id" };
|
|
477
|
+
const sampled = shouldSampleSessionReplay(sessionId, normalized.sampleRate, normalized.samplingSalt);
|
|
478
|
+
if (!sampled) {
|
|
479
|
+
return { started: false, reason: "sampled-out", sessionId, sampled };
|
|
480
|
+
}
|
|
481
|
+
if (!isUrlRecordable(window.location.href, normalized)) {
|
|
482
|
+
return { started: false, reason: "url-blocked", sessionId, sampled };
|
|
483
|
+
}
|
|
484
|
+
const state = getState();
|
|
485
|
+
if (state.active && state.replayId) {
|
|
486
|
+
return {
|
|
487
|
+
started: true,
|
|
488
|
+
reason: "already-active",
|
|
489
|
+
replayId: state.replayId,
|
|
490
|
+
sessionId,
|
|
491
|
+
sampled,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
let rrweb;
|
|
495
|
+
try {
|
|
496
|
+
rrweb = (await import("@rrweb/record"));
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return { started: false, reason: "import-failed", sessionId, sampled };
|
|
500
|
+
}
|
|
501
|
+
const replaySession = getOrCreateReplaySession(sessionId);
|
|
502
|
+
state.options = normalized;
|
|
503
|
+
state.replayId = replaySession.replayId;
|
|
504
|
+
state.sequence = replaySession.sequence;
|
|
505
|
+
state.queue = [];
|
|
506
|
+
state.queuedBytes = 0;
|
|
507
|
+
state.stopRecorder = null;
|
|
508
|
+
state.active = true;
|
|
509
|
+
try {
|
|
510
|
+
const stopRecorder = rrweb.record({
|
|
511
|
+
emit: (event) => enqueueReplayEvent(state, event),
|
|
512
|
+
checkoutEveryNth: normalized.checkoutEveryNth,
|
|
513
|
+
checkoutEveryNms: normalized.checkoutEveryNms,
|
|
514
|
+
blockSelector: normalized.blockSelector,
|
|
515
|
+
ignoreSelector: normalized.ignoreSelector,
|
|
516
|
+
maskTextClass: normalized.maskTextClass,
|
|
517
|
+
maskTextSelector: normalized.maskTextSelector,
|
|
518
|
+
maskAllInputs: normalized.maskAllInputs,
|
|
519
|
+
recordCanvas: normalized.recordCanvas,
|
|
520
|
+
recordCrossOriginIframes: normalized.recordCrossOriginIframes,
|
|
521
|
+
collectFonts: normalized.collectFonts,
|
|
522
|
+
inlineImages: normalized.inlineImages,
|
|
523
|
+
maskInputOptions: {
|
|
524
|
+
color: true,
|
|
525
|
+
date: true,
|
|
526
|
+
"datetime-local": true,
|
|
527
|
+
email: true,
|
|
528
|
+
month: true,
|
|
529
|
+
number: true,
|
|
530
|
+
password: true,
|
|
531
|
+
range: true,
|
|
532
|
+
search: true,
|
|
533
|
+
tel: true,
|
|
534
|
+
text: true,
|
|
535
|
+
time: true,
|
|
536
|
+
url: true,
|
|
537
|
+
week: true,
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
if (typeof stopRecorder !== "function") {
|
|
541
|
+
state.active = false;
|
|
542
|
+
state.options = null;
|
|
543
|
+
state.replayId = null;
|
|
544
|
+
return { started: false, reason: "record-failed", sessionId, sampled };
|
|
545
|
+
}
|
|
546
|
+
state.stopRecorder = stopRecorder;
|
|
547
|
+
state.flushTimer = window.setInterval(() => void flushSessionReplay("interval"), normalized.flushIntervalMs);
|
|
548
|
+
state.maxDurationTimer = window.setTimeout(() => stopSessionReplay("max-duration"), normalized.maxDurationMs);
|
|
549
|
+
installUrlMonitor(state);
|
|
550
|
+
installLifecycleListeners(state);
|
|
551
|
+
return {
|
|
552
|
+
started: true,
|
|
553
|
+
replayId: state.replayId,
|
|
554
|
+
sessionId,
|
|
555
|
+
sampled,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
state.active = false;
|
|
560
|
+
state.options = null;
|
|
561
|
+
state.replayId = null;
|
|
562
|
+
return { started: false, reason: "record-failed", sessionId, sampled };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
export function stopSessionReplay(reason = "manual") {
|
|
566
|
+
const state = getState();
|
|
567
|
+
if (!state.active)
|
|
568
|
+
return;
|
|
569
|
+
state.active = false;
|
|
570
|
+
try {
|
|
571
|
+
state.stopRecorder?.();
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
// best-effort recorder shutdown
|
|
575
|
+
}
|
|
576
|
+
state.stopRecorder = null;
|
|
577
|
+
if (state.flushTimer) {
|
|
578
|
+
window.clearInterval(state.flushTimer);
|
|
579
|
+
state.flushTimer = null;
|
|
580
|
+
}
|
|
581
|
+
if (state.maxDurationTimer) {
|
|
582
|
+
window.clearTimeout(state.maxDurationTimer);
|
|
583
|
+
state.maxDurationTimer = null;
|
|
584
|
+
}
|
|
585
|
+
state.restoreUrlMonitor?.();
|
|
586
|
+
state.removeLifecycleListeners?.();
|
|
587
|
+
void flushSessionReplay(reason);
|
|
588
|
+
}
|
|
589
|
+
export function maybeStartSessionReplay(options = {}) {
|
|
590
|
+
return startSessionReplay(options);
|
|
591
|
+
}
|
|
592
|
+
export function isSessionReplayActive() {
|
|
593
|
+
return getState().active;
|
|
594
|
+
}
|
|
595
|
+
//# sourceMappingURL=session-replay.js.map
|