@squasher-ai/browser 0.1.1 → 0.2.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/LICENSE +21 -21
- package/README.md +2 -41
- package/dist/__tests__/client/harness.d.ts +18 -0
- package/dist/__tests__/client/harness.d.ts.map +1 -0
- package/dist/__tests__/client/harness.js +106 -0
- package/dist/__tests__/replay/coverage-harness.d.ts +29 -0
- package/dist/__tests__/replay/coverage-harness.d.ts.map +1 -0
- package/dist/__tests__/replay/coverage-harness.js +123 -0
- package/dist/__tests__/replay/test-helpers.d.ts +13 -0
- package/dist/__tests__/replay/test-helpers.d.ts.map +1 -0
- package/dist/__tests__/replay/test-helpers.js +9 -0
- package/dist/autocapture.d.ts +14 -18
- package/dist/autocapture.d.ts.map +1 -1
- package/dist/autocapture.js +77 -95
- package/dist/client.d.ts +12 -19
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +56 -72
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +26 -0
- package/dist/console-capture.d.ts +8 -0
- package/dist/console-capture.d.ts.map +1 -0
- package/dist/console-capture.js +79 -0
- package/dist/errors.d.ts +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +8 -5
- package/dist/index.d.ts +5 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -82
- package/dist/next.d.ts +40 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +67 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +12 -10
- package/dist/replay-network.d.ts +21 -0
- package/dist/replay-network.d.ts.map +1 -0
- package/dist/replay-network.js +193 -0
- package/dist/replay-privacy.d.ts +2 -2
- package/dist/replay-privacy.d.ts.map +1 -1
- package/dist/replay-privacy.js +47 -17
- package/dist/replay-retry.d.ts +41 -0
- package/dist/replay-retry.d.ts.map +1 -0
- package/dist/replay-retry.js +193 -0
- package/dist/replay.d.ts +18 -5
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +131 -71
- package/dist/runtime/client-singleton.d.ts +25 -0
- package/dist/runtime/client-singleton.d.ts.map +1 -0
- package/dist/runtime/client-singleton.js +66 -0
- package/dist/runtime/client.d.ts +22 -0
- package/dist/runtime/client.d.ts.map +1 -0
- package/dist/runtime/client.js +12 -0
- package/dist/runtime/replay.d.ts +13 -0
- package/dist/runtime/replay.d.ts.map +1 -0
- package/dist/runtime/replay.js +15 -0
- package/dist/session.d.ts +0 -10
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +20 -21
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +13 -10
- package/dist/trace-context.d.ts +9 -0
- package/dist/trace-context.d.ts.map +1 -0
- package/dist/trace-context.js +13 -0
- package/dist/transport.d.ts +11 -19
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +86 -66
- package/dist/types.d.ts +37 -135
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -7
- package/dist/vitals.d.ts +55 -2
- package/dist/vitals.d.ts.map +1 -1
- package/dist/vitals.js +115 -16
- package/package.json +25 -3
- package/dist/__tests__/client.test.d.ts +0 -2
- package/dist/__tests__/client.test.d.ts.map +0 -1
- package/dist/__tests__/client.test.js +0 -103
- package/dist/__tests__/errors.test.d.ts +0 -2
- package/dist/__tests__/errors.test.d.ts.map +0 -1
- package/dist/__tests__/errors.test.js +0 -80
- package/dist/__tests__/replay-privacy.test.d.ts +0 -2
- package/dist/__tests__/replay-privacy.test.d.ts.map +0 -1
- package/dist/__tests__/replay-privacy.test.js +0 -124
- package/dist/__tests__/replay-startup.test.d.ts +0 -2
- package/dist/__tests__/replay-startup.test.d.ts.map +0 -1
- package/dist/__tests__/replay-startup.test.js +0 -99
- package/dist/__tests__/replay.test.d.ts +0 -2
- package/dist/__tests__/replay.test.d.ts.map +0 -1
- package/dist/__tests__/replay.test.js +0 -217
- package/dist/__tests__/session.test.d.ts +0 -2
- package/dist/__tests__/session.test.d.ts.map +0 -1
- package/dist/__tests__/session.test.js +0 -46
- package/dist/__tests__/transport.test.d.ts +0 -2
- package/dist/__tests__/transport.test.d.ts.map +0 -1
- package/dist/__tests__/transport.test.js +0 -101
- package/dist/__tests__/types.test.d.ts +0 -2
- package/dist/__tests__/types.test.d.ts.map +0 -1
- package/dist/__tests__/types.test.js +0 -139
package/dist/replay.js
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { applyReplayEventPrivacy, buildReplayRecordOptions } from "./replay-privacy";
|
|
3
|
-
|
|
1
|
+
import { attempt } from "@squasher-ai/result-utils";
|
|
2
|
+
import { applyReplayEventPrivacy, buildReplayRecordOptions } from "./replay-privacy.js";
|
|
3
|
+
import { ReplayUploader } from "./replay-retry.js";
|
|
4
|
+
import { defaultReplayRecorderRuntime, } from "./runtime/replay.js";
|
|
5
|
+
import { getSessionId } from "./session.js";
|
|
6
|
+
// Flush on the same 5s cadence Sentry uses for session-mode replays. Anything
|
|
7
|
+
// shorter creates lots of tiny R2 blobs; anything longer hurts time-to-first-
|
|
8
|
+
// segment on the dashboard.
|
|
4
9
|
const REPLAY_FLUSH_INTERVAL_MS = 5_000;
|
|
10
|
+
// Soft cap on buffered JSON bytes before we flush ahead of the timer. ~900KB
|
|
11
|
+
// stays well under our worker's 5MB body limit and PostHog's ~1MB-per-event
|
|
12
|
+
// guidance, while still letting a FullSnapshot ship in one segment.
|
|
13
|
+
const REPLAY_FLUSH_BYTES = 900_000;
|
|
14
|
+
const RRWEB_FULL_SNAPSHOT_EVENT = 2;
|
|
15
|
+
function estimateEventBytes(event) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(event).length;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
5
23
|
function shouldRecord(sampleRate) {
|
|
6
24
|
if (sampleRate >= 1)
|
|
7
25
|
return true;
|
|
@@ -9,8 +27,11 @@ function shouldRecord(sampleRate) {
|
|
|
9
27
|
return false;
|
|
10
28
|
return Math.random() < sampleRate;
|
|
11
29
|
}
|
|
30
|
+
function isReplayFullSnapshotEvent(event) {
|
|
31
|
+
return event instanceof Object && "type" in event && event.type === RRWEB_FULL_SNAPSHOT_EVENT;
|
|
32
|
+
}
|
|
12
33
|
function getPageUrl() {
|
|
13
|
-
return
|
|
34
|
+
return globalThis.location?.href ?? "";
|
|
14
35
|
}
|
|
15
36
|
export class ReplayRecorder {
|
|
16
37
|
ready;
|
|
@@ -18,12 +39,21 @@ export class ReplayRecorder {
|
|
|
18
39
|
startedAt = new Date().toISOString();
|
|
19
40
|
cleanups = [];
|
|
20
41
|
config;
|
|
42
|
+
runtime;
|
|
21
43
|
buffer = [];
|
|
44
|
+
bufferBytes = 0;
|
|
22
45
|
flushTimer = null;
|
|
23
46
|
stopRecording = null;
|
|
24
47
|
disposed = false;
|
|
25
|
-
|
|
48
|
+
uploader;
|
|
49
|
+
constructor(config, runtime = defaultReplayRecorderRuntime) {
|
|
26
50
|
this.config = config;
|
|
51
|
+
this.runtime = runtime;
|
|
52
|
+
this.uploader = new ReplayUploader({
|
|
53
|
+
url: `${config.endpoint}/v1/projects/${config.projectId}/replays`,
|
|
54
|
+
headerKey: config.apiKey,
|
|
55
|
+
debug: config.debug,
|
|
56
|
+
});
|
|
27
57
|
if (!shouldRecord(config.sampleRate)) {
|
|
28
58
|
if (this.config.debug) {
|
|
29
59
|
console.log("[squasher] Replay recording skipped by sample rate");
|
|
@@ -50,70 +80,121 @@ export class ReplayRecorder {
|
|
|
50
80
|
}
|
|
51
81
|
this.cleanups.length = 0;
|
|
52
82
|
this.flushBufferedEvents({ final: true, allowWhenDisposed: true });
|
|
83
|
+
this.uploader.dispose();
|
|
53
84
|
}
|
|
54
85
|
async start() {
|
|
55
|
-
if (
|
|
86
|
+
if (this.disposed || !globalThis.window || !globalThis.document) {
|
|
56
87
|
return;
|
|
57
88
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
89
|
+
let recording;
|
|
90
|
+
try {
|
|
91
|
+
recording = await this.runtime.loadRecordingAdapter();
|
|
61
92
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
privacy: this.config.privacy,
|
|
67
|
-
}));
|
|
68
|
-
if (!stopRecording) {
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (this.config.debug) {
|
|
95
|
+
console.warn("[squasher] Replay recording could not start", error);
|
|
96
|
+
}
|
|
69
97
|
return;
|
|
70
98
|
}
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
if (!this.disposed) {
|
|
100
|
+
const stopRecording = recording.start(buildReplayRecordOptions({
|
|
101
|
+
emit: (event) => {
|
|
102
|
+
this.enqueue(applyReplayEventPrivacy(event, this.config.privacy));
|
|
103
|
+
},
|
|
104
|
+
privacy: this.config.privacy,
|
|
105
|
+
}));
|
|
106
|
+
if (stopRecording) {
|
|
107
|
+
this.stopRecording = stopRecording;
|
|
108
|
+
this.flushTimer = setInterval(() => {
|
|
109
|
+
this.flushBufferedEvents();
|
|
110
|
+
}, REPLAY_FLUSH_INTERVAL_MS);
|
|
111
|
+
const finalFlush = () => {
|
|
112
|
+
this.flushBufferedEvents({ final: true });
|
|
113
|
+
};
|
|
114
|
+
window.addEventListener("pagehide", finalFlush);
|
|
115
|
+
window.addEventListener("beforeunload", finalFlush);
|
|
116
|
+
this.cleanups.push(() => window.removeEventListener("pagehide", finalFlush));
|
|
117
|
+
this.cleanups.push(() => window.removeEventListener("beforeunload", finalFlush));
|
|
118
|
+
this.cleanups.push(this.runtime.installNetworkCapture({
|
|
119
|
+
debug: this.config.debug,
|
|
120
|
+
emitCustomEvent: recording.emitCustomEvent,
|
|
121
|
+
ignoredUrlPrefixes: [
|
|
122
|
+
`${this.config.endpoint}/v1/ingest/${this.config.projectId}`,
|
|
123
|
+
`${this.config.endpoint}/v1/projects/${this.config.projectId}/replays`,
|
|
124
|
+
`${this.config.endpoint}/v1/vitals/${this.config.projectId}`,
|
|
125
|
+
],
|
|
126
|
+
replayStartedAtMs: new Date(this.startedAt).getTime(),
|
|
127
|
+
}));
|
|
128
|
+
if (this.config.debug) {
|
|
129
|
+
console.log("[squasher] Replay recording started", {
|
|
130
|
+
sessionId: this.sessionId,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
86
134
|
}
|
|
87
135
|
}
|
|
88
136
|
enqueue(event) {
|
|
89
137
|
if (this.disposed)
|
|
90
138
|
return;
|
|
91
139
|
this.buffer.push(event);
|
|
92
|
-
|
|
140
|
+
this.bufferBytes += estimateEventBytes(event);
|
|
141
|
+
if (isReplayFullSnapshotEvent(event) || this.bufferBytes >= REPLAY_FLUSH_BYTES) {
|
|
93
142
|
this.flushBufferedEvents();
|
|
94
143
|
}
|
|
95
144
|
}
|
|
96
145
|
flushBufferedEvents(options) {
|
|
97
|
-
if (this.buffer.length === 0)
|
|
146
|
+
if (this.buffer.length === 0 && !options?.final)
|
|
98
147
|
return;
|
|
99
148
|
if (this.disposed && !options?.allowWhenDisposed)
|
|
100
149
|
return;
|
|
150
|
+
if (options?.final) {
|
|
151
|
+
this.finalFlush();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (this.buffer.length === 0)
|
|
155
|
+
return;
|
|
101
156
|
const events = this.buffer.splice(0);
|
|
157
|
+
this.bufferBytes = 0;
|
|
158
|
+
const body = this.buildPayloadBody(events);
|
|
159
|
+
this.uploader.enqueue(body);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Terminal flush during pagehide/beforeunload. Tries sendBeacon first for the
|
|
163
|
+
* current buffer and any pending retries, then falls back to keepalive fetch.
|
|
164
|
+
*/
|
|
165
|
+
finalFlush() {
|
|
166
|
+
const pending = this.uploader.drain();
|
|
167
|
+
const bodies = [...pending];
|
|
168
|
+
if (this.buffer.length > 0) {
|
|
169
|
+
const events = this.buffer.splice(0);
|
|
170
|
+
this.bufferBytes = 0;
|
|
171
|
+
bodies.push(this.buildPayloadBody(events));
|
|
172
|
+
}
|
|
173
|
+
if (bodies.length === 0)
|
|
174
|
+
return;
|
|
175
|
+
const beaconUrl = this.buildReplayUrl({ beacon: true });
|
|
176
|
+
for (const body of bodies) {
|
|
177
|
+
if (!this.trySendBeacon(beaconUrl, body)) {
|
|
178
|
+
this.uploader.enqueue(body);
|
|
179
|
+
}
|
|
180
|
+
else if (this.config.debug) {
|
|
181
|
+
console.log("[squasher] Flushed replay payload via sendBeacon");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
buildPayloadBody(events) {
|
|
102
186
|
const payload = {
|
|
103
187
|
session_id: this.sessionId,
|
|
104
188
|
events,
|
|
105
189
|
page_url: getPageUrl(),
|
|
106
190
|
started_at: this.startedAt,
|
|
191
|
+
distinct_id: this.config.getDistinctId?.(),
|
|
192
|
+
user_id: this.config.getUserId?.(),
|
|
193
|
+
visitor_id: this.config.getVisitorId?.() ?? this.sessionId,
|
|
194
|
+
environment: this.config.environment,
|
|
195
|
+
release: this.config.release,
|
|
107
196
|
};
|
|
108
|
-
|
|
109
|
-
const url = this.buildReplayUrl();
|
|
110
|
-
if (options?.final && this.trySendBeacon(this.buildReplayUrl({ beacon: true }), body)) {
|
|
111
|
-
if (this.config.debug) {
|
|
112
|
-
console.log(`[squasher] Flushed ${events.length} replay events via sendBeacon`);
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
void this.sendFetch(url, body);
|
|
197
|
+
return JSON.stringify(payload);
|
|
117
198
|
}
|
|
118
199
|
buildReplayUrl(options) {
|
|
119
200
|
const baseUrl = `${this.config.endpoint}/v1/projects/${this.config.projectId}/replays`;
|
|
@@ -123,36 +204,15 @@ export class ReplayRecorder {
|
|
|
123
204
|
return `${baseUrl}?key=${encodeURIComponent(this.config.apiKey)}`;
|
|
124
205
|
}
|
|
125
206
|
trySendBeacon(url, body) {
|
|
126
|
-
if (
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
const blob = new Blob([body], { type: "application/json" });
|
|
131
|
-
return navigator.sendBeacon(url, blob);
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
207
|
+
if (!globalThis.navigator?.sendBeacon) {
|
|
134
208
|
return false;
|
|
135
209
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
"x-squasher-key": this.config.apiKey,
|
|
144
|
-
},
|
|
145
|
-
body,
|
|
146
|
-
keepalive: true,
|
|
147
|
-
});
|
|
148
|
-
if (this.config.debug) {
|
|
149
|
-
console.log("[squasher] Replay events sent via fetch");
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
if (this.config.debug) {
|
|
154
|
-
console.warn("[squasher] Failed to upload replay events:", error);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
210
|
+
return attempt({
|
|
211
|
+
try: () => {
|
|
212
|
+
const blob = new Blob([body], { type: "application/json" });
|
|
213
|
+
return globalThis.navigator.sendBeacon(url, blob);
|
|
214
|
+
},
|
|
215
|
+
catch: () => false,
|
|
216
|
+
});
|
|
157
217
|
}
|
|
158
218
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BrowserClient } from "../client.js";
|
|
2
|
+
import type { Breadcrumb, BrowserConfig, BrowserErrorEvent, JsonObject, Level, UserContext } from "../types.js";
|
|
3
|
+
type CaptureErrorExtra = Parameters<BrowserClient["captureError"]>[1];
|
|
4
|
+
/** Initialize the global Squasher browser client. Throws if called twice. */
|
|
5
|
+
export declare function init(config: BrowserConfig): BrowserClient;
|
|
6
|
+
/** Get the global client (throws if not initialized). */
|
|
7
|
+
export declare function getClient(): BrowserClient;
|
|
8
|
+
export declare function captureError(error: Error, extra?: CaptureErrorExtra): void;
|
|
9
|
+
export declare function captureMessage(message: string, level?: Level): void;
|
|
10
|
+
export declare function captureTelemetry(event: BrowserErrorEvent): void;
|
|
11
|
+
export declare function track(eventName: string, properties?: JsonObject, context?: Partial<BrowserErrorEvent>): void;
|
|
12
|
+
export declare function identify(distinctId: string, traits?: JsonObject, context?: Partial<BrowserErrorEvent>): void;
|
|
13
|
+
export declare function page(name: string, properties?: JsonObject, context?: Partial<BrowserErrorEvent>): void;
|
|
14
|
+
export declare function screen(name: string, properties?: JsonObject, context?: Partial<BrowserErrorEvent>): void;
|
|
15
|
+
export declare function captureSpan(name: string, context?: Partial<BrowserErrorEvent>): void;
|
|
16
|
+
export declare function captureToolCall(name: string, context?: Partial<BrowserErrorEvent>): void;
|
|
17
|
+
export declare function captureGeneration(message: string, context?: Partial<BrowserErrorEvent>): void;
|
|
18
|
+
export declare function setUser(user: UserContext | undefined): void;
|
|
19
|
+
export declare function setTag(key: string, value: string): void;
|
|
20
|
+
export declare function setTags(tags: Record<string, string>): void;
|
|
21
|
+
export declare function addBreadcrumb(crumb: Omit<Breadcrumb, "timestamp">): void;
|
|
22
|
+
/** Stops timers, flushes vitals, and removes global handlers. */
|
|
23
|
+
export declare function close(): void;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=client-singleton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-singleton.d.ts","sourceRoot":"","sources":["../../src/runtime/client-singleton.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,KAAK,EACL,WAAW,EACZ,MAAM,aAAa,CAAC;AAErB,KAAK,iBAAiB,GAAG,UAAU,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAItE,6EAA6E;AAC7E,wBAAgB,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAQzD;AAED,yDAAyD;AACzD,wBAAgB,SAAS,IAAI,aAAa,CAKzC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAE1E;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAEnE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAE/D;AAED,wBAAgB,KAAK,CACnB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,UAAU,EACvB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GACnC,IAAI,CAEN;AAED,wBAAgB,QAAQ,CACtB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,UAAU,EACnB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GACnC,IAAI,CAEN;AAED,wBAAgB,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,UAAU,EACvB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GACnC,IAAI,CAEN;AAED,wBAAgB,MAAM,CACpB,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,UAAU,EACvB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GACnC,IAAI,CAEN;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAEpF;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAExF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAE7F;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAE3D;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAE1D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,GAAG,IAAI,CAExE;AAED,iEAAiE;AACjE,wBAAgB,KAAK,IAAI,IAAI,CAK5B"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { BrowserClient } from "../client.js";
|
|
2
|
+
let client = null;
|
|
3
|
+
/** Initialize the global Squasher browser client. Throws if called twice. */
|
|
4
|
+
export function init(config) {
|
|
5
|
+
if (client) {
|
|
6
|
+
throw new Error("squasher.init() called more than once. Call close() first if re-initializing.");
|
|
7
|
+
}
|
|
8
|
+
client = new BrowserClient(config);
|
|
9
|
+
return client;
|
|
10
|
+
}
|
|
11
|
+
/** Get the global client (throws if not initialized). */
|
|
12
|
+
export function getClient() {
|
|
13
|
+
if (!client) {
|
|
14
|
+
throw new Error("Squasher not initialized. Call init() first.");
|
|
15
|
+
}
|
|
16
|
+
return client;
|
|
17
|
+
}
|
|
18
|
+
export function captureError(error, extra) {
|
|
19
|
+
getClient().captureError(error, extra);
|
|
20
|
+
}
|
|
21
|
+
export function captureMessage(message, level) {
|
|
22
|
+
getClient().captureMessage(message, level);
|
|
23
|
+
}
|
|
24
|
+
export function captureTelemetry(event) {
|
|
25
|
+
getClient().captureTelemetry(event);
|
|
26
|
+
}
|
|
27
|
+
export function track(eventName, properties, context) {
|
|
28
|
+
getClient().track(eventName, properties, context);
|
|
29
|
+
}
|
|
30
|
+
export function identify(distinctId, traits, context) {
|
|
31
|
+
getClient().identify(distinctId, traits, context);
|
|
32
|
+
}
|
|
33
|
+
export function page(name, properties, context) {
|
|
34
|
+
getClient().page(name, properties, context);
|
|
35
|
+
}
|
|
36
|
+
export function screen(name, properties, context) {
|
|
37
|
+
getClient().screen(name, properties, context);
|
|
38
|
+
}
|
|
39
|
+
export function captureSpan(name, context) {
|
|
40
|
+
getClient().captureSpan(name, context);
|
|
41
|
+
}
|
|
42
|
+
export function captureToolCall(name, context) {
|
|
43
|
+
getClient().captureToolCall(name, context);
|
|
44
|
+
}
|
|
45
|
+
export function captureGeneration(message, context) {
|
|
46
|
+
getClient().captureGeneration(message, context);
|
|
47
|
+
}
|
|
48
|
+
export function setUser(user) {
|
|
49
|
+
getClient().setUser(user);
|
|
50
|
+
}
|
|
51
|
+
export function setTag(key, value) {
|
|
52
|
+
getClient().setTag(key, value);
|
|
53
|
+
}
|
|
54
|
+
export function setTags(tags) {
|
|
55
|
+
getClient().setTags(tags);
|
|
56
|
+
}
|
|
57
|
+
export function addBreadcrumb(crumb) {
|
|
58
|
+
getClient().addBreadcrumb(crumb);
|
|
59
|
+
}
|
|
60
|
+
/** Stops timers, flushes vitals, and removes global handlers. */
|
|
61
|
+
export function close() {
|
|
62
|
+
if (client) {
|
|
63
|
+
client.close();
|
|
64
|
+
client = null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AutocaptureOptions, type BreadcrumbCallback } from "../autocapture.js";
|
|
2
|
+
import { type ReplayRecorderConfig } from "../replay.js";
|
|
3
|
+
import { type TransportConfig } from "../transport.js";
|
|
4
|
+
import type { BrowserErrorEvent, VitalEvent } from "../types.js";
|
|
5
|
+
import { type VitalsCollectorConfig, type VitalsCollectorHandle } from "../vitals.js";
|
|
6
|
+
export interface BrowserTransport {
|
|
7
|
+
dispose(): void;
|
|
8
|
+
enqueueVital(event: VitalEvent): void;
|
|
9
|
+
sendEvent(event: BrowserErrorEvent): void;
|
|
10
|
+
}
|
|
11
|
+
export interface ReplayRecorderHandle {
|
|
12
|
+
dispose(): void;
|
|
13
|
+
}
|
|
14
|
+
export interface BrowserClientRuntime {
|
|
15
|
+
createReplayRecorder(config: ReplayRecorderConfig): ReplayRecorderHandle;
|
|
16
|
+
createTransport(config: TransportConfig): BrowserTransport;
|
|
17
|
+
installAutocapture(onBreadcrumb: BreadcrumbCallback, options: AutocaptureOptions): () => void;
|
|
18
|
+
installGlobalErrorHandlers(onError: (event: BrowserErrorEvent) => void): () => void;
|
|
19
|
+
startVitalsCollection(transport: BrowserTransport, config: VitalsCollectorConfig): VitalsCollectorHandle;
|
|
20
|
+
}
|
|
21
|
+
export declare const defaultBrowserClientRuntime: BrowserClientRuntime;
|
|
22
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/runtime/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC3B,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,IAAI,CAAC;IAChB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACtC,SAAS,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAAC;IACzE,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAAC;IAC3D,kBAAkB,CAAC,YAAY,EAAE,kBAAkB,EAAE,OAAO,EAAE,kBAAkB,GAAG,MAAM,IAAI,CAAC;IAC9F,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACpF,qBAAqB,CACnB,SAAS,EAAE,gBAAgB,EAC3B,MAAM,EAAE,qBAAqB,GAC5B,qBAAqB,CAAC;CAC1B;AAED,eAAO,MAAM,2BAA2B,EAAE,oBAMzC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { installAutocapture, } from "../autocapture.js";
|
|
2
|
+
import { installGlobalErrorHandlers } from "../errors.js";
|
|
3
|
+
import { ReplayRecorder } from "../replay.js";
|
|
4
|
+
import { Transport } from "../transport.js";
|
|
5
|
+
import { startVitalsCollection, } from "../vitals.js";
|
|
6
|
+
export const defaultBrowserClientRuntime = {
|
|
7
|
+
createReplayRecorder: (config) => new ReplayRecorder(config),
|
|
8
|
+
createTransport: (config) => new Transport(config),
|
|
9
|
+
installAutocapture,
|
|
10
|
+
installGlobalErrorHandlers,
|
|
11
|
+
startVitalsCollection,
|
|
12
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReplayNetworkCaptureOptions } from "../replay-network.js";
|
|
2
|
+
import type { ReplayPrivacyRecordOptions } from "../replay-privacy.js";
|
|
3
|
+
export type StopRecording = () => void;
|
|
4
|
+
export interface ReplayRecordingAdapter {
|
|
5
|
+
emitCustomEvent?: ReplayNetworkCaptureOptions["emitCustomEvent"];
|
|
6
|
+
start(options: ReplayPrivacyRecordOptions): StopRecording | undefined;
|
|
7
|
+
}
|
|
8
|
+
export interface ReplayRecorderRuntime {
|
|
9
|
+
installNetworkCapture(options: ReplayNetworkCaptureOptions): () => void;
|
|
10
|
+
loadRecordingAdapter(): Promise<ReplayRecordingAdapter>;
|
|
11
|
+
}
|
|
12
|
+
export declare const defaultReplayRecorderRuntime: ReplayRecorderRuntime;
|
|
13
|
+
//# sourceMappingURL=replay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/runtime/replay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEvE,MAAM,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC;AAEvC,MAAM,WAAW,sBAAsB;IACrC,eAAe,CAAC,EAAE,2BAA2B,CAAC,iBAAiB,CAAC,CAAC;IACjE,KAAK,CAAC,OAAO,EAAE,0BAA0B,GAAG,aAAa,GAAG,SAAS,CAAC;CACvE;AAED,MAAM,WAAW,qBAAqB;IACpC,qBAAqB,CAAC,OAAO,EAAE,2BAA2B,GAAG,MAAM,IAAI,CAAC;IACxE,oBAAoB,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACzD;AAaD,eAAO,MAAM,4BAA4B,EAAE,qBAG1C,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { installReplayNetworkCapture } from "../replay-network.js";
|
|
2
|
+
async function loadRecordingAdapter() {
|
|
3
|
+
const { record } = await import("rrweb");
|
|
4
|
+
const adapter = {
|
|
5
|
+
start: (options) => record(options),
|
|
6
|
+
};
|
|
7
|
+
if (record.addCustomEvent) {
|
|
8
|
+
adapter.emitCustomEvent = record.addCustomEvent;
|
|
9
|
+
}
|
|
10
|
+
return adapter;
|
|
11
|
+
}
|
|
12
|
+
export const defaultReplayRecorderRuntime = {
|
|
13
|
+
installNetworkCapture: installReplayNetworkCapture,
|
|
14
|
+
loadRecordingAdapter,
|
|
15
|
+
};
|
package/dist/session.d.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session management for the browser SDK.
|
|
3
|
-
*
|
|
4
|
-
* Generates a unique session ID per tab visit using crypto.randomUUID().
|
|
5
|
-
* Stored in sessionStorage so it persists across SPA navigations but dies
|
|
6
|
-
* when the tab closes — one session per tab visit.
|
|
7
|
-
*
|
|
8
|
-
* The session ID is attached to both error events and Web Vitals for
|
|
9
|
-
* cross-correlation in the dashboard.
|
|
10
|
-
*/
|
|
11
1
|
/**
|
|
12
2
|
* Get (or create) the session ID for this tab.
|
|
13
3
|
* Uses sessionStorage for persistence across SPA navigations.
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAiBA;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAuBrC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAQrC"}
|
package/dist/session.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attempt } from "@squasher-ai/result-utils";
|
|
1
2
|
/**
|
|
2
3
|
* Session management for the browser SDK.
|
|
3
4
|
*
|
|
@@ -18,24 +19,22 @@ let cachedSessionId = null;
|
|
|
18
19
|
export function getSessionId() {
|
|
19
20
|
if (cachedSessionId)
|
|
20
21
|
return cachedSessionId;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
catch {
|
|
29
|
-
// sessionStorage unavailable (e.g. SSR, sandboxed iframes)
|
|
22
|
+
const stored = attempt({
|
|
23
|
+
try: () => sessionStorage.getItem(SESSION_KEY),
|
|
24
|
+
catch: () => null,
|
|
25
|
+
});
|
|
26
|
+
if (stored) {
|
|
27
|
+
cachedSessionId = stored;
|
|
28
|
+
return stored;
|
|
30
29
|
}
|
|
31
30
|
const id = crypto.randomUUID();
|
|
32
31
|
cachedSessionId = id;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
32
|
+
attempt({
|
|
33
|
+
try: () => {
|
|
34
|
+
sessionStorage.setItem(SESSION_KEY, id);
|
|
35
|
+
},
|
|
36
|
+
catch: () => undefined,
|
|
37
|
+
});
|
|
39
38
|
return id;
|
|
40
39
|
}
|
|
41
40
|
/**
|
|
@@ -43,10 +42,10 @@ export function getSessionId() {
|
|
|
43
42
|
*/
|
|
44
43
|
export function resetSessionId() {
|
|
45
44
|
cachedSessionId = null;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
45
|
+
attempt({
|
|
46
|
+
try: () => {
|
|
47
|
+
sessionStorage.removeItem(SESSION_KEY);
|
|
48
|
+
},
|
|
49
|
+
catch: () => undefined,
|
|
50
|
+
});
|
|
52
51
|
}
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Breadcrumb, BrowserErrorEvent, JsonObject, UserContext } from "./types";
|
|
1
|
+
import type { Breadcrumb, BrowserErrorEvent, JsonObject, UserContext } from "./types.js";
|
|
2
2
|
interface PrepareBrowserEventInput {
|
|
3
3
|
beforeSend?: (event: BrowserErrorEvent) => BrowserErrorEvent | null;
|
|
4
4
|
breadcrumbs: Breadcrumb[];
|
package/dist/telemetry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzF,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,iBAAiB,GAAG,IAAI,CAAC;IACpE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,UAAU,EAAE,MAAM,GACjB,WAAW,CAMb;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAanB;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAkBnB;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAqBnB;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,GAAG,SAAS,EAClC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAkBnB;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAYnB;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAYnB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAClC,iBAAiB,CAQnB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,iBAAiB,EACxB,KAAK,EAAE,wBAAwB,GAC9B,iBAAiB,GAAG,IAAI,CAkC1B"}
|
package/dist/telemetry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getSessionId } from "./session";
|
|
1
|
+
import { getSessionId } from "./session.js";
|
|
2
2
|
export function mergeIdentifiedUser(currentUser, contextUser, distinctId) {
|
|
3
3
|
return {
|
|
4
4
|
...currentUser,
|
|
@@ -54,10 +54,10 @@ export function buildPageEvent(name, properties, context) {
|
|
|
54
54
|
page: {
|
|
55
55
|
...context.page,
|
|
56
56
|
name: context.page?.name ?? name,
|
|
57
|
-
path: context.page?.path ??
|
|
58
|
-
title: context.page?.title ??
|
|
59
|
-
referrer: context.page?.referrer ??
|
|
60
|
-
search: context.page?.search ??
|
|
57
|
+
path: context.page?.path ?? globalThis.location?.pathname,
|
|
58
|
+
title: context.page?.title ?? globalThis.document?.title,
|
|
59
|
+
referrer: context.page?.referrer ?? globalThis.document?.referrer,
|
|
60
|
+
search: context.page?.search ?? globalThis.location?.search,
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
63
|
}
|
|
@@ -126,19 +126,22 @@ export function prepareBrowserEvent(event, input) {
|
|
|
126
126
|
environment: filtered.environment ?? input.environment,
|
|
127
127
|
release: filtered.release ?? input.release,
|
|
128
128
|
kind: filtered.kind ?? (filtered.level === "error" || filtered.level === "fatal" ? "error" : "log"),
|
|
129
|
-
...(input.user ? { user: { ...input.user } } : {}),
|
|
130
|
-
...(Object.keys(input.tags).length > 0 ? { tags: { ...input.tags, ...filtered.tags } } : {}),
|
|
131
|
-
...(input.breadcrumbs.length > 0 ? { breadcrumbs: [...input.breadcrumbs] } : {}),
|
|
132
129
|
};
|
|
130
|
+
if (input.user)
|
|
131
|
+
prepared.user = { ...input.user };
|
|
132
|
+
if (Object.keys(input.tags).length > 0)
|
|
133
|
+
prepared.tags = { ...input.tags, ...filtered.tags };
|
|
134
|
+
if (input.breadcrumbs.length > 0)
|
|
135
|
+
prepared.breadcrumbs = [...input.breadcrumbs];
|
|
133
136
|
if (!prepared.visitor &&
|
|
134
137
|
prepared.kind &&
|
|
135
138
|
["analytics", "identify", "page", "screen", "visitor"].includes(prepared.kind)) {
|
|
136
139
|
prepared.visitor = { anonymous_id: prepared.session_id };
|
|
137
140
|
}
|
|
138
|
-
if (
|
|
141
|
+
if (globalThis.location) {
|
|
139
142
|
prepared.request = {
|
|
140
143
|
...prepared.request,
|
|
141
|
-
url: prepared.request?.url ?? location.href,
|
|
144
|
+
url: prepared.request?.url ?? globalThis.location.href,
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
147
|
return prepared;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TraceContext } from "./types.js";
|
|
2
|
+
export declare function createTraceId(): string;
|
|
3
|
+
export declare function createSpanId(): string;
|
|
4
|
+
export declare function createTraceContext(parent?: Pick<TraceContext, "span_id" | "trace_id">): {
|
|
5
|
+
parent_span_id: string | undefined;
|
|
6
|
+
span_id: string;
|
|
7
|
+
trace_id: string;
|
|
8
|
+
};
|
|
9
|
+
//# 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,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,wBAAgB,aAAa,WAE5B;AAED,wBAAgB,YAAY,WAE3B;AAED,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,UAAU,CAAC;;;;EAMrF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function createTraceId() {
|
|
2
|
+
return crypto.randomUUID().replaceAll("-", "");
|
|
3
|
+
}
|
|
4
|
+
export function createSpanId() {
|
|
5
|
+
return crypto.randomUUID().replaceAll("-", "").slice(0, 16);
|
|
6
|
+
}
|
|
7
|
+
export function createTraceContext(parent) {
|
|
8
|
+
return {
|
|
9
|
+
parent_span_id: parent?.span_id,
|
|
10
|
+
span_id: createSpanId(),
|
|
11
|
+
trace_id: parent?.trace_id ?? createTraceId(),
|
|
12
|
+
};
|
|
13
|
+
}
|