vident-rum 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +424 -0
- package/dist/client.d.ts +67 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +207 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +67 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +78 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +11 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/fetch.d.ts +16 -0
- package/dist/instrumentation/fetch.d.ts.map +1 -0
- package/dist/instrumentation/fetch.js +109 -0
- package/dist/instrumentation/fetch.js.map +1 -0
- package/dist/instrumentation/xhr.d.ts +16 -0
- package/dist/instrumentation/xhr.d.ts.map +1 -0
- package/dist/instrumentation/xhr.js +129 -0
- package/dist/instrumentation/xhr.js.map +1 -0
- package/dist/replay.d.ts +66 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +289 -0
- package/dist/replay.js.map +1 -0
- package/dist/session.d.ts +11 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +176 -0
- package/dist/session.js.map +1 -0
- package/dist/trace-context.d.ts +37 -0
- package/dist/trace-context.d.ts.map +1 -0
- package/dist/trace-context.js +113 -0
- package/dist/trace-context.js.map +1 -0
- package/dist/transport.d.ts +15 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +101 -0
- package/dist/transport.js.map +1 -0
- package/dist/vitals.d.ts +12 -0
- package/dist/vitals.d.ts.map +1 -0
- package/dist/vitals.js +48 -0
- package/dist/vitals.js.map +1 -0
- package/package.json +34 -0
package/dist/replay.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { gzipSync } from "fflate";
|
|
2
|
+
import { record } from "rrweb";
|
|
3
|
+
const MAX_CHUNK_SIZE = 64 * 1024; // 64KB uncompressed
|
|
4
|
+
const FLUSH_INTERVAL = 5000; // 5 seconds
|
|
5
|
+
const BUFFER_DURATION_MS = 60_000; // 60 seconds rolling buffer
|
|
6
|
+
const BUFFER_CLEANUP_INTERVAL = 10_000; // Cleanup every 10 seconds
|
|
7
|
+
// Selectors for privacy control
|
|
8
|
+
const BLOCK_SELECTOR = "[data-sw-block], .rr-block";
|
|
9
|
+
const MASK_SELECTOR = "[data-sw-mask], .rr-mask";
|
|
10
|
+
const UNMASK_SELECTOR = "[data-sw-unmask]";
|
|
11
|
+
// Efficient base64 encoding for Uint8Array
|
|
12
|
+
function uint8ToBase64(bytes) {
|
|
13
|
+
let binary = "";
|
|
14
|
+
for (const byte of bytes) {
|
|
15
|
+
binary += String.fromCharCode(byte);
|
|
16
|
+
}
|
|
17
|
+
return btoa(binary);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Session Replay Recorder with Rolling Buffer
|
|
21
|
+
*
|
|
22
|
+
* Architecture:
|
|
23
|
+
* - Always records to memory when enabled
|
|
24
|
+
* - If sampled (sampleRate): uploads immediately
|
|
25
|
+
* - If not sampled: keeps 60s rolling buffer in memory
|
|
26
|
+
* - On error: flushes buffer and starts uploading (if onErrorSampleRate passes)
|
|
27
|
+
* - Zero network cost for sessions without errors that weren't sampled
|
|
28
|
+
*/
|
|
29
|
+
export function createReplayRecorder(config, transportConfig, onError) {
|
|
30
|
+
// Event buffer - always populated while recording
|
|
31
|
+
const buffer = [];
|
|
32
|
+
let estimatedSize = 0;
|
|
33
|
+
// Upload state
|
|
34
|
+
let chunkIndex = 0;
|
|
35
|
+
let flushTimer = null;
|
|
36
|
+
let bufferCleanupTimer = null;
|
|
37
|
+
// Recording state
|
|
38
|
+
let isRecording = false;
|
|
39
|
+
let stopRecordingFn = null;
|
|
40
|
+
let hasErrors = false;
|
|
41
|
+
let mode = "buffer";
|
|
42
|
+
// Sampling decisions (made once at session start)
|
|
43
|
+
const shouldUploadImmediately = Math.random() < (config.sampleRate ?? 0.1);
|
|
44
|
+
let wasUpgradedByError = false;
|
|
45
|
+
function getRecordOptions() {
|
|
46
|
+
const privacyMode = config.privacyMode ?? "strict";
|
|
47
|
+
const maskAllInputs = privacyMode !== "permissive";
|
|
48
|
+
const maskAllText = privacyMode === "strict";
|
|
49
|
+
return {
|
|
50
|
+
emit: handleEvent,
|
|
51
|
+
sampling: {
|
|
52
|
+
mousemove: true,
|
|
53
|
+
mouseInteraction: true,
|
|
54
|
+
scroll: 150,
|
|
55
|
+
input: "last",
|
|
56
|
+
},
|
|
57
|
+
maskAllInputs,
|
|
58
|
+
maskAllText,
|
|
59
|
+
maskTextSelector: maskAllText ? undefined : MASK_SELECTOR,
|
|
60
|
+
unmaskTextSelector: maskAllText ? UNMASK_SELECTOR : undefined,
|
|
61
|
+
blockSelector: BLOCK_SELECTOR,
|
|
62
|
+
slimDOMOptions: {
|
|
63
|
+
script: true,
|
|
64
|
+
comment: true,
|
|
65
|
+
headFavicon: true,
|
|
66
|
+
headWhitespace: true,
|
|
67
|
+
headMetaSocial: true,
|
|
68
|
+
headMetaRobots: true,
|
|
69
|
+
headMetaHttpEquiv: true,
|
|
70
|
+
headMetaVerification: true,
|
|
71
|
+
headMetaAuthorship: true,
|
|
72
|
+
},
|
|
73
|
+
// Handle rrweb internal errors (e.g., e.matches on non-Element nodes)
|
|
74
|
+
errorHandler: (error) => {
|
|
75
|
+
// Suppress known rrweb bugs, log others
|
|
76
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
77
|
+
if (msg.includes("matches is not a function")) {
|
|
78
|
+
return; // Known rrweb bug with Text/Comment nodes
|
|
79
|
+
}
|
|
80
|
+
console.warn("[Vident Replay] Recording error:", msg);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function handleEvent(event) {
|
|
85
|
+
buffer.push(event);
|
|
86
|
+
estimatedSize += JSON.stringify(event).length + 1;
|
|
87
|
+
if (mode === "upload") {
|
|
88
|
+
// In upload mode, flush when chunk is large enough
|
|
89
|
+
if (estimatedSize >= MAX_CHUNK_SIZE) {
|
|
90
|
+
flush();
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
scheduleFlush();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// In buffer mode, events just accumulate (cleaned up periodically)
|
|
97
|
+
}
|
|
98
|
+
function scheduleFlush() {
|
|
99
|
+
if (flushTimer || mode !== "upload")
|
|
100
|
+
return;
|
|
101
|
+
flushTimer = setTimeout(() => {
|
|
102
|
+
flushTimer = null;
|
|
103
|
+
flush();
|
|
104
|
+
}, FLUSH_INTERVAL);
|
|
105
|
+
}
|
|
106
|
+
function preparePayload(batch) {
|
|
107
|
+
const compressed = gzipSync(new TextEncoder().encode(JSON.stringify(batch)));
|
|
108
|
+
return JSON.stringify({
|
|
109
|
+
sessionId: transportConfig.sessionId,
|
|
110
|
+
chunkIndex,
|
|
111
|
+
data: uint8ToBase64(compressed),
|
|
112
|
+
isFullSnapshot: chunkIndex === 0,
|
|
113
|
+
timestamp: Date.now(),
|
|
114
|
+
hasErrors,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function flush() {
|
|
118
|
+
if (buffer.length === 0 || mode !== "upload")
|
|
119
|
+
return;
|
|
120
|
+
const batch = buffer.splice(0);
|
|
121
|
+
estimatedSize = 0;
|
|
122
|
+
try {
|
|
123
|
+
await fetch(`${transportConfig.baseUrl}/v1/ingest/replays`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
"x-api-key": transportConfig.apiKey,
|
|
128
|
+
},
|
|
129
|
+
body: preparePayload(batch),
|
|
130
|
+
keepalive: true,
|
|
131
|
+
});
|
|
132
|
+
chunkIndex++;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.warn("[Vident Replay] Failed to send chunk:", error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function flushBeacon() {
|
|
139
|
+
if (buffer.length === 0 || mode !== "upload")
|
|
140
|
+
return;
|
|
141
|
+
const batch = buffer.splice(0);
|
|
142
|
+
estimatedSize = 0;
|
|
143
|
+
try {
|
|
144
|
+
fetch(`${transportConfig.baseUrl}/v1/ingest/replays`, {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: {
|
|
147
|
+
"Content-Type": "application/json",
|
|
148
|
+
"x-api-key": transportConfig.apiKey,
|
|
149
|
+
},
|
|
150
|
+
body: preparePayload(batch),
|
|
151
|
+
keepalive: true,
|
|
152
|
+
}).catch(() => {
|
|
153
|
+
// Silently ignore - page is likely unloading
|
|
154
|
+
});
|
|
155
|
+
chunkIndex++;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Ignore errors during page unload
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Clean up old events from buffer (only in buffer mode)
|
|
163
|
+
* Keeps last 60 seconds of events
|
|
164
|
+
*/
|
|
165
|
+
function cleanupBuffer() {
|
|
166
|
+
if (mode !== "buffer" || buffer.length === 0)
|
|
167
|
+
return;
|
|
168
|
+
const cutoff = Date.now() - BUFFER_DURATION_MS;
|
|
169
|
+
let removeCount = 0;
|
|
170
|
+
// Find how many events to remove (they're chronologically ordered)
|
|
171
|
+
for (const event of buffer) {
|
|
172
|
+
if (event.timestamp < cutoff) {
|
|
173
|
+
removeCount++;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (removeCount > 0) {
|
|
180
|
+
buffer.splice(0, removeCount);
|
|
181
|
+
// Recalculate size estimate
|
|
182
|
+
estimatedSize = buffer.reduce((sum, e) => sum + JSON.stringify(e).length + 1, 0);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Switch from buffer mode to upload mode
|
|
187
|
+
* Flushes the entire buffer and starts uploading
|
|
188
|
+
*/
|
|
189
|
+
function switchToUploadMode() {
|
|
190
|
+
if (mode === "upload")
|
|
191
|
+
return;
|
|
192
|
+
mode = "upload";
|
|
193
|
+
// Stop buffer cleanup
|
|
194
|
+
if (bufferCleanupTimer) {
|
|
195
|
+
clearInterval(bufferCleanupTimer);
|
|
196
|
+
bufferCleanupTimer = null;
|
|
197
|
+
}
|
|
198
|
+
// Flush the buffer immediately
|
|
199
|
+
if (buffer.length > 0) {
|
|
200
|
+
flush();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function start() {
|
|
204
|
+
if (isRecording)
|
|
205
|
+
return;
|
|
206
|
+
isRecording = true;
|
|
207
|
+
// Determine initial mode
|
|
208
|
+
if (shouldUploadImmediately) {
|
|
209
|
+
mode = "upload";
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
mode = "buffer";
|
|
213
|
+
// Start buffer cleanup interval
|
|
214
|
+
bufferCleanupTimer = setInterval(cleanupBuffer, BUFFER_CLEANUP_INTERVAL);
|
|
215
|
+
}
|
|
216
|
+
// Start rrweb recording
|
|
217
|
+
const stop = record(getRecordOptions());
|
|
218
|
+
stopRecordingFn = stop ?? null;
|
|
219
|
+
// Setup unload handlers
|
|
220
|
+
window.addEventListener("visibilitychange", handleVisibilityChange);
|
|
221
|
+
window.addEventListener("pagehide", handlePageHide);
|
|
222
|
+
}
|
|
223
|
+
function stop() {
|
|
224
|
+
if (!isRecording)
|
|
225
|
+
return;
|
|
226
|
+
isRecording = false;
|
|
227
|
+
if (stopRecordingFn) {
|
|
228
|
+
stopRecordingFn();
|
|
229
|
+
stopRecordingFn = null;
|
|
230
|
+
}
|
|
231
|
+
if (flushTimer) {
|
|
232
|
+
clearTimeout(flushTimer);
|
|
233
|
+
flushTimer = null;
|
|
234
|
+
}
|
|
235
|
+
if (bufferCleanupTimer) {
|
|
236
|
+
clearInterval(bufferCleanupTimer);
|
|
237
|
+
bufferCleanupTimer = null;
|
|
238
|
+
}
|
|
239
|
+
// Final flush only if in upload mode
|
|
240
|
+
if (mode === "upload") {
|
|
241
|
+
flush();
|
|
242
|
+
}
|
|
243
|
+
window.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
244
|
+
window.removeEventListener("pagehide", handlePageHide);
|
|
245
|
+
}
|
|
246
|
+
function handleVisibilityChange() {
|
|
247
|
+
if (document.visibilityState === "hidden" && mode === "upload") {
|
|
248
|
+
flushBeacon();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function handlePageHide() {
|
|
252
|
+
if (mode === "upload") {
|
|
253
|
+
flushBeacon();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Mark that an error occurred in this session
|
|
258
|
+
* If in buffer mode, switches to upload mode (subject to onErrorSampleRate)
|
|
259
|
+
*/
|
|
260
|
+
function markError() {
|
|
261
|
+
hasErrors = true;
|
|
262
|
+
// If already uploading, nothing to do
|
|
263
|
+
if (mode === "upload")
|
|
264
|
+
return;
|
|
265
|
+
// Check if we should upgrade to upload mode
|
|
266
|
+
const onErrorRate = config.onErrorSampleRate ?? 1.0;
|
|
267
|
+
if (Math.random() < onErrorRate) {
|
|
268
|
+
wasUpgradedByError = true;
|
|
269
|
+
switchToUploadMode();
|
|
270
|
+
onError?.();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Auto-start recording when enabled
|
|
274
|
+
if (config.enabled) {
|
|
275
|
+
// Defer to next tick to ensure DOM is ready
|
|
276
|
+
setTimeout(start, 0);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
start,
|
|
280
|
+
stop,
|
|
281
|
+
markError,
|
|
282
|
+
isRecording: () => isRecording,
|
|
283
|
+
isUploading: () => mode === "upload",
|
|
284
|
+
wasUpgradedByError: () => wasUpgradedByError,
|
|
285
|
+
/** Force switch to upload mode (for manual control) */
|
|
286
|
+
startUploading: switchToUploadMode,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type StorageMode = "cookie" | "sessionStorage";
|
|
2
|
+
export declare function setStorageMode(mode: StorageMode): void;
|
|
3
|
+
export type SessionInfo = {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
isNew: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function getOrCreateSession(): SessionInfo;
|
|
8
|
+
export declare function refreshSession(): void;
|
|
9
|
+
export declare function endSession(): void;
|
|
10
|
+
export declare function getSessionId(): string | null;
|
|
11
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,gBAAgB,CAAA;AAIrD,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAEtD;AAuJD,MAAM,MAAM,WAAW,GAAG;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,OAAO,CAAA;CACd,CAAA;AAED,wBAAgB,kBAAkB,IAAI,WAAW,CAehD;AAED,wBAAgB,cAAc,IAAI,IAAI,CAMrC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAG5C"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const SESSION_KEY = "vident_session";
|
|
2
|
+
const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 min inactivity timeout
|
|
3
|
+
const SESSION_MAX_DURATION_MS = 4 * 60 * 60 * 1000; // 4 hour max duration
|
|
4
|
+
let storageMode = "cookie";
|
|
5
|
+
export function setStorageMode(mode) {
|
|
6
|
+
storageMode = mode;
|
|
7
|
+
}
|
|
8
|
+
function generateSessionId() {
|
|
9
|
+
const bytes = new Uint8Array(16);
|
|
10
|
+
crypto.getRandomValues(bytes);
|
|
11
|
+
return Array.from(bytes)
|
|
12
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
13
|
+
.join("");
|
|
14
|
+
}
|
|
15
|
+
// Cookie operations
|
|
16
|
+
function parseCookieValue(name) {
|
|
17
|
+
const cookies = document.cookie.split("; ");
|
|
18
|
+
for (const cookie of cookies) {
|
|
19
|
+
const [key, ...rest] = cookie.split("=");
|
|
20
|
+
if (key === name) {
|
|
21
|
+
const value = rest.join("=");
|
|
22
|
+
return value ? decodeURIComponent(value) : null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function getCookie(name) {
|
|
28
|
+
try {
|
|
29
|
+
return parseCookieValue(name);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function setCookie(name, value) {
|
|
36
|
+
try {
|
|
37
|
+
// Session cookie (no max-age) - cleared when browser closes
|
|
38
|
+
// SameSite=Lax allows cookie to be sent on OAuth redirects back to our site
|
|
39
|
+
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; SameSite=Lax`;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Cookies may be unavailable
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function deleteCookie(name) {
|
|
46
|
+
try {
|
|
47
|
+
document.cookie = `${name}=; path=/; max-age=0`;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Ignore errors
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// localStorage fallback for environments where cookies are restricted
|
|
54
|
+
function getLocalStorage(key) {
|
|
55
|
+
try {
|
|
56
|
+
return localStorage.getItem(key);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function setLocalStorage(key, value) {
|
|
63
|
+
try {
|
|
64
|
+
localStorage.setItem(key, value);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// localStorage may be unavailable
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function deleteLocalStorage(key) {
|
|
71
|
+
try {
|
|
72
|
+
localStorage.removeItem(key);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Ignore errors
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// sessionStorage operations
|
|
79
|
+
function getSessionStorage(key) {
|
|
80
|
+
try {
|
|
81
|
+
return sessionStorage.getItem(key);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function setSessionStorage(key, value) {
|
|
88
|
+
try {
|
|
89
|
+
sessionStorage.setItem(key, value);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// sessionStorage may be unavailable
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function deleteSessionStorage(key) {
|
|
96
|
+
try {
|
|
97
|
+
sessionStorage.removeItem(key);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Ignore errors
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function parseSessionData(value) {
|
|
104
|
+
if (!value)
|
|
105
|
+
return null;
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(value);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Storage abstraction based on mode
|
|
114
|
+
function getStoredSession() {
|
|
115
|
+
if (storageMode === "sessionStorage") {
|
|
116
|
+
return parseSessionData(getSessionStorage(SESSION_KEY));
|
|
117
|
+
}
|
|
118
|
+
// Cookie mode: try cookie first, fallback to localStorage
|
|
119
|
+
const cookieData = parseSessionData(getCookie(SESSION_KEY));
|
|
120
|
+
if (cookieData)
|
|
121
|
+
return cookieData;
|
|
122
|
+
return parseSessionData(getLocalStorage(SESSION_KEY));
|
|
123
|
+
}
|
|
124
|
+
function storeSession(data) {
|
|
125
|
+
const value = JSON.stringify(data);
|
|
126
|
+
if (storageMode === "sessionStorage") {
|
|
127
|
+
setSessionStorage(SESSION_KEY, value);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Cookie mode: store in both cookie and localStorage (fallback)
|
|
131
|
+
setCookie(SESSION_KEY, value);
|
|
132
|
+
setLocalStorage(SESSION_KEY, value);
|
|
133
|
+
}
|
|
134
|
+
function clearSession() {
|
|
135
|
+
if (storageMode === "sessionStorage") {
|
|
136
|
+
deleteSessionStorage(SESSION_KEY);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
deleteCookie(SESSION_KEY);
|
|
140
|
+
deleteLocalStorage(SESSION_KEY);
|
|
141
|
+
}
|
|
142
|
+
function isSessionValid(session) {
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
const inactivityValid = now - session.lastActivity < SESSION_TIMEOUT_MS;
|
|
145
|
+
const durationValid = now - session.startTime < SESSION_MAX_DURATION_MS;
|
|
146
|
+
return inactivityValid && durationValid;
|
|
147
|
+
}
|
|
148
|
+
export function getOrCreateSession() {
|
|
149
|
+
const existing = getStoredSession();
|
|
150
|
+
if (existing && isSessionValid(existing)) {
|
|
151
|
+
return { sessionId: existing.id, isNew: false };
|
|
152
|
+
}
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
const newSession = {
|
|
155
|
+
id: generateSessionId(),
|
|
156
|
+
startTime: now,
|
|
157
|
+
lastActivity: now,
|
|
158
|
+
};
|
|
159
|
+
storeSession(newSession);
|
|
160
|
+
return { sessionId: newSession.id, isNew: true };
|
|
161
|
+
}
|
|
162
|
+
export function refreshSession() {
|
|
163
|
+
const existing = getStoredSession();
|
|
164
|
+
if (existing && isSessionValid(existing)) {
|
|
165
|
+
existing.lastActivity = Date.now();
|
|
166
|
+
storeSession(existing);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export function endSession() {
|
|
170
|
+
clearSession();
|
|
171
|
+
}
|
|
172
|
+
export function getSessionId() {
|
|
173
|
+
const existing = getStoredSession();
|
|
174
|
+
return existing && isSessionValid(existing) ? existing.id : null;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W3C Trace Context utilities for browser-side distributed tracing.
|
|
3
|
+
* Implements the W3C Trace Context specification:
|
|
4
|
+
* https://www.w3.org/TR/trace-context/
|
|
5
|
+
*
|
|
6
|
+
* traceparent format: version-traceId-spanId-flags
|
|
7
|
+
* Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
|
|
8
|
+
*/
|
|
9
|
+
export type TraceparentData = {
|
|
10
|
+
traceId: string;
|
|
11
|
+
spanId: string;
|
|
12
|
+
sampled: boolean;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Generate a random 32-character hex trace ID (16 bytes).
|
|
16
|
+
* Uses crypto.getRandomValues for cryptographically secure randomness.
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateTraceId(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Generate a random 16-character hex span ID (8 bytes).
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateSpanId(): string;
|
|
23
|
+
/**
|
|
24
|
+
* Parse a W3C traceparent header into its components.
|
|
25
|
+
* Returns null if the header is invalid.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseTraceparent(header: string): TraceparentData | null;
|
|
28
|
+
/**
|
|
29
|
+
* Create a W3C traceparent header value.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createTraceparent(traceId: string, spanId: string, sampled: boolean): string;
|
|
32
|
+
/**
|
|
33
|
+
* Check if a URL should have trace context propagated.
|
|
34
|
+
* Propagates to same-origin, same root domain, or explicitly allowed origins.
|
|
35
|
+
*/
|
|
36
|
+
export declare function shouldPropagate(url: string, propagateToOrigins?: (string | RegExp)[]): boolean;
|
|
37
|
+
//# sourceMappingURL=trace-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-context.d.ts","sourceRoot":"","sources":["../src/trace-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,eAAe,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;CAChB,CAAA;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAMxC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAMvC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAkBvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,GACd,MAAM,CAGR;AAeD;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,GAAG,EAAE,MAAM,EACX,kBAAkB,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GACtC,OAAO,CAuCT"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W3C Trace Context utilities for browser-side distributed tracing.
|
|
3
|
+
* Implements the W3C Trace Context specification:
|
|
4
|
+
* https://www.w3.org/TR/trace-context/
|
|
5
|
+
*
|
|
6
|
+
* traceparent format: version-traceId-spanId-flags
|
|
7
|
+
* Example: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
|
|
8
|
+
*/
|
|
9
|
+
const TRACEPARENT_REGEX = /^00-([a-f0-9]{32})-([a-f0-9]{16})-([a-f0-9]{2})$/;
|
|
10
|
+
/**
|
|
11
|
+
* Generate a random 32-character hex trace ID (16 bytes).
|
|
12
|
+
* Uses crypto.getRandomValues for cryptographically secure randomness.
|
|
13
|
+
*/
|
|
14
|
+
export function generateTraceId() {
|
|
15
|
+
const bytes = new Uint8Array(16);
|
|
16
|
+
crypto.getRandomValues(bytes);
|
|
17
|
+
return Array.from(bytes)
|
|
18
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
19
|
+
.join("");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate a random 16-character hex span ID (8 bytes).
|
|
23
|
+
*/
|
|
24
|
+
export function generateSpanId() {
|
|
25
|
+
const bytes = new Uint8Array(8);
|
|
26
|
+
crypto.getRandomValues(bytes);
|
|
27
|
+
return Array.from(bytes)
|
|
28
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
29
|
+
.join("");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Parse a W3C traceparent header into its components.
|
|
33
|
+
* Returns null if the header is invalid.
|
|
34
|
+
*/
|
|
35
|
+
export function parseTraceparent(header) {
|
|
36
|
+
const match = header.match(TRACEPARENT_REGEX);
|
|
37
|
+
if (!match)
|
|
38
|
+
return null;
|
|
39
|
+
const [, traceId, spanId, flags] = match;
|
|
40
|
+
if (!traceId || !spanId || !flags)
|
|
41
|
+
return null;
|
|
42
|
+
// Validate trace ID is not all zeros
|
|
43
|
+
if (traceId === "00000000000000000000000000000000")
|
|
44
|
+
return null;
|
|
45
|
+
// Validate span ID is not all zeros
|
|
46
|
+
if (spanId === "0000000000000000")
|
|
47
|
+
return null;
|
|
48
|
+
return {
|
|
49
|
+
traceId,
|
|
50
|
+
spanId,
|
|
51
|
+
sampled: (Number.parseInt(flags, 16) & 0x01) === 1,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create a W3C traceparent header value.
|
|
56
|
+
*/
|
|
57
|
+
export function createTraceparent(traceId, spanId, sampled) {
|
|
58
|
+
const flags = sampled ? "01" : "00";
|
|
59
|
+
return `00-${traceId}-${spanId}-${flags}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract the root domain from a hostname (e.g., "app.example.com" -> "example.com")
|
|
63
|
+
*/
|
|
64
|
+
function getRootDomain(hostname) {
|
|
65
|
+
const parts = hostname.split(".");
|
|
66
|
+
// Handle localhost and IP addresses
|
|
67
|
+
if (parts.length <= 2 || hostname === "localhost") {
|
|
68
|
+
return hostname;
|
|
69
|
+
}
|
|
70
|
+
// Return last two parts (e.g., "example.com")
|
|
71
|
+
return parts.slice(-2).join(".");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if a URL should have trace context propagated.
|
|
75
|
+
* Propagates to same-origin, same root domain, or explicitly allowed origins.
|
|
76
|
+
*/
|
|
77
|
+
export function shouldPropagate(url, propagateToOrigins) {
|
|
78
|
+
let parsedUrl;
|
|
79
|
+
try {
|
|
80
|
+
parsedUrl = new URL(url, window.location.origin);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
// Always propagate to same origin
|
|
86
|
+
if (parsedUrl.origin === window.location.origin) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// Auto-propagate to same root domain (e.g., app.example.com -> api.example.com)
|
|
90
|
+
const currentRootDomain = getRootDomain(window.location.hostname);
|
|
91
|
+
const targetRootDomain = getRootDomain(parsedUrl.hostname);
|
|
92
|
+
if (currentRootDomain === targetRootDomain) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
// Check against allowlist
|
|
96
|
+
if (propagateToOrigins && propagateToOrigins.length > 0) {
|
|
97
|
+
for (const pattern of propagateToOrigins) {
|
|
98
|
+
if (typeof pattern === "string") {
|
|
99
|
+
if (parsedUrl.origin === pattern ||
|
|
100
|
+
parsedUrl.href.startsWith(pattern)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (pattern instanceof RegExp) {
|
|
105
|
+
if (pattern.test(parsedUrl.href)) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=trace-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-context.js","sourceRoot":"","sources":["../src/trace-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RumEvent } from "./events.js";
|
|
2
|
+
export type TransportConfig = {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
appName?: string;
|
|
6
|
+
flushInterval?: number;
|
|
7
|
+
maxBatchSize?: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function createTransport(config: TransportConfig): {
|
|
10
|
+
enqueue: (event: RumEvent) => void;
|
|
11
|
+
flush: () => Promise<void>;
|
|
12
|
+
flushBeacon: () => void;
|
|
13
|
+
};
|
|
14
|
+
export type Transport = ReturnType<typeof createTransport>;
|
|
15
|
+
//# sourceMappingURL=transport.d.ts.map
|