@squasher-ai/browser 0.1.0 → 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/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 +17 -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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { attemptAsync } from "@squasher-ai/result-utils";
|
|
2
|
+
import { getDefaultHeaders, nextBackoffMs } from "@squasher-ai/sdk-core";
|
|
3
|
+
const MAX_ATTEMPTS = 3;
|
|
4
|
+
const MAX_BACKOFF_MS = 4_000;
|
|
5
|
+
const MAX_PENDING_PAYLOADS = 10;
|
|
6
|
+
const SDK_NAME = "@squasher-ai/browser";
|
|
7
|
+
const SDK_VERSION = "0.2.0";
|
|
8
|
+
const DEFAULT_TELEMETRY_HEADERS = getDefaultHeaders({
|
|
9
|
+
packageName: SDK_NAME,
|
|
10
|
+
packageVersion: SDK_VERSION,
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Compute next-attempt backoff. Delegates the math + Retry-After parsing to
|
|
14
|
+
* sdk-core's `nextBackoffMs` so every Squasher SDK shares one
|
|
15
|
+
* implementation. We keep the replay-specific 4-second cap because replay
|
|
16
|
+
* payloads are bursty and a longer wait throws off the page-unload window.
|
|
17
|
+
*/
|
|
18
|
+
function backoffForAttempt(attempts, headers) {
|
|
19
|
+
// sdk-core uses 0-indexed attempts; this module uses 1-indexed (1st retry).
|
|
20
|
+
return Math.round(nextBackoffMs({
|
|
21
|
+
attempt: attempts - 1,
|
|
22
|
+
headers: headers ?? undefined,
|
|
23
|
+
maxSleepMs: MAX_BACKOFF_MS,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* In-memory retry queue for replay payloads.
|
|
28
|
+
*
|
|
29
|
+
* Semantics:
|
|
30
|
+
* - Enqueue a payload; `flush()` tries to send the oldest pending job.
|
|
31
|
+
* - Successful 2xx -> drop the job.
|
|
32
|
+
* - 4xx (except 429) -> drop the job (non-retryable client error).
|
|
33
|
+
* - 5xx, 429, or network error -> increment attempts; reschedule with
|
|
34
|
+
* exponential backoff (1s, 2s, 4s). After MAX_ATTEMPTS, the job is dropped.
|
|
35
|
+
* - Concurrency: a `sending` flag prevents two concurrent `flush` runs from
|
|
36
|
+
* double-sending the same head-of-queue job.
|
|
37
|
+
* - Queue length is capped at MAX_PENDING_PAYLOADS to bound memory.
|
|
38
|
+
*/
|
|
39
|
+
export class ReplayUploader {
|
|
40
|
+
client;
|
|
41
|
+
queue = [];
|
|
42
|
+
sending = false;
|
|
43
|
+
activeController = null;
|
|
44
|
+
retryTimer = null;
|
|
45
|
+
disposed = false;
|
|
46
|
+
constructor(client) {
|
|
47
|
+
this.client = client;
|
|
48
|
+
}
|
|
49
|
+
enqueue(body) {
|
|
50
|
+
if (this.disposed)
|
|
51
|
+
return;
|
|
52
|
+
if (this.queue.length >= MAX_PENDING_PAYLOADS) {
|
|
53
|
+
if (this.client.debug) {
|
|
54
|
+
console.warn("[squasher] Replay retry queue full; dropping oldest payload");
|
|
55
|
+
}
|
|
56
|
+
this.queue.splice(this.oldestDroppableIndex(), 1);
|
|
57
|
+
}
|
|
58
|
+
this.queue.push({ body, attempts: 0 });
|
|
59
|
+
void this.flush();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns every payload body and clears the queue. The active fetch is
|
|
63
|
+
* aborted first so pagehide can transfer ownership of the head payload to
|
|
64
|
+
* sendBeacon instead of losing the initial replay snapshot on navigation.
|
|
65
|
+
*/
|
|
66
|
+
drain() {
|
|
67
|
+
this.activeController?.abort();
|
|
68
|
+
this.activeController = null;
|
|
69
|
+
const bodies = this.queue.map((job) => job.body);
|
|
70
|
+
this.queue.length = 0;
|
|
71
|
+
if (this.retryTimer) {
|
|
72
|
+
clearTimeout(this.retryTimer);
|
|
73
|
+
this.retryTimer = null;
|
|
74
|
+
}
|
|
75
|
+
return bodies;
|
|
76
|
+
}
|
|
77
|
+
oldestDroppableIndex() {
|
|
78
|
+
return this.sending && this.queue.length > 1 ? 1 : 0;
|
|
79
|
+
}
|
|
80
|
+
dispose() {
|
|
81
|
+
this.disposed = true;
|
|
82
|
+
this.activeController?.abort();
|
|
83
|
+
this.activeController = null;
|
|
84
|
+
this.queue.length = 0;
|
|
85
|
+
if (this.retryTimer) {
|
|
86
|
+
clearTimeout(this.retryTimer);
|
|
87
|
+
this.retryTimer = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async flush() {
|
|
91
|
+
if (this.sending || this.disposed)
|
|
92
|
+
return;
|
|
93
|
+
if (this.queue.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
if (this.retryTimer)
|
|
96
|
+
return; // waiting for scheduled retry
|
|
97
|
+
this.sending = true;
|
|
98
|
+
try {
|
|
99
|
+
const job = this.queue[0];
|
|
100
|
+
if (!job)
|
|
101
|
+
return;
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
this.activeController = controller;
|
|
104
|
+
const outcome = await this.send(job.body, controller.signal);
|
|
105
|
+
if (this.activeController === controller) {
|
|
106
|
+
this.activeController = null;
|
|
107
|
+
}
|
|
108
|
+
if (this.queue[0] === job) {
|
|
109
|
+
if (outcome.kind === "success") {
|
|
110
|
+
this.queue.shift();
|
|
111
|
+
}
|
|
112
|
+
else if (outcome.kind === "drop") {
|
|
113
|
+
this.queue.shift();
|
|
114
|
+
if (this.client.debug) {
|
|
115
|
+
console.warn(`[squasher] Replay upload failed with non-retryable status; dropping payload (${outcome.reason})`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
job.attempts += 1;
|
|
120
|
+
if (job.attempts >= MAX_ATTEMPTS) {
|
|
121
|
+
this.queue.shift();
|
|
122
|
+
if (this.client.debug) {
|
|
123
|
+
console.warn(`[squasher] Replay upload exhausted ${MAX_ATTEMPTS} attempts; dropping payload`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const delay = backoffForAttempt(job.attempts, outcome.retryHeaders);
|
|
128
|
+
this.scheduleRetry(delay);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
this.sending = false;
|
|
136
|
+
}
|
|
137
|
+
if (this.queue.length > 0 && !this.retryTimer) {
|
|
138
|
+
void this.flush();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
scheduleRetry(delayMs) {
|
|
142
|
+
if (this.retryTimer)
|
|
143
|
+
return;
|
|
144
|
+
this.retryTimer = setTimeout(() => {
|
|
145
|
+
this.retryTimer = null;
|
|
146
|
+
void this.flush();
|
|
147
|
+
}, delayMs);
|
|
148
|
+
}
|
|
149
|
+
async send(body, signal) {
|
|
150
|
+
const result = await attemptAsync({
|
|
151
|
+
try: async () => {
|
|
152
|
+
const response = await fetch(this.client.url, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: {
|
|
155
|
+
...DEFAULT_TELEMETRY_HEADERS,
|
|
156
|
+
"Content-Type": "application/json",
|
|
157
|
+
"x-squasher-key": this.client.headerKey,
|
|
158
|
+
},
|
|
159
|
+
body,
|
|
160
|
+
signal,
|
|
161
|
+
});
|
|
162
|
+
return response;
|
|
163
|
+
},
|
|
164
|
+
catch: ({ cause }) => {
|
|
165
|
+
if (this.client.debug) {
|
|
166
|
+
console.warn("[squasher] Replay upload network error:", cause);
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
if (!result) {
|
|
172
|
+
return { kind: "retry", retryHeaders: null };
|
|
173
|
+
}
|
|
174
|
+
if (result.status >= 200 && result.status < 300) {
|
|
175
|
+
if (this.client.debug) {
|
|
176
|
+
console.log("[squasher] Replay events sent via fetch");
|
|
177
|
+
}
|
|
178
|
+
return { kind: "success" };
|
|
179
|
+
}
|
|
180
|
+
if (result.status === 429) {
|
|
181
|
+
// Hand the response Headers straight to sdk-core's nextBackoffMs so it
|
|
182
|
+
// can read Retry-After-Ms or Retry-After (seconds or HTTP-date).
|
|
183
|
+
return {
|
|
184
|
+
kind: "retry",
|
|
185
|
+
retryHeaders: result.headers,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (result.status >= 500) {
|
|
189
|
+
return { kind: "retry", retryHeaders: null };
|
|
190
|
+
}
|
|
191
|
+
return { kind: "drop", reason: `HTTP ${result.status}` };
|
|
192
|
+
}
|
|
193
|
+
}
|
package/dist/replay.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
1
|
+
import { type ReplayRecorderRuntime } from "./runtime/replay.js";
|
|
2
|
+
import type { ReplayPrivacyConfig } from "./types.js";
|
|
3
|
+
export interface ReplayRecorderConfig {
|
|
3
4
|
endpoint: string;
|
|
4
5
|
projectId: string;
|
|
5
6
|
apiKey: string;
|
|
6
7
|
debug: boolean;
|
|
7
8
|
sampleRate: number;
|
|
9
|
+
environment?: string;
|
|
10
|
+
release?: string;
|
|
11
|
+
getDistinctId?: () => string | undefined;
|
|
12
|
+
getUserId?: () => string | undefined;
|
|
13
|
+
getVisitorId?: () => string | undefined;
|
|
8
14
|
privacy?: ReplayPrivacyConfig;
|
|
9
15
|
}
|
|
10
16
|
export declare class ReplayRecorder {
|
|
@@ -13,18 +19,25 @@ export declare class ReplayRecorder {
|
|
|
13
19
|
private readonly startedAt;
|
|
14
20
|
private readonly cleanups;
|
|
15
21
|
private readonly config;
|
|
22
|
+
private readonly runtime;
|
|
16
23
|
private buffer;
|
|
24
|
+
private bufferBytes;
|
|
17
25
|
private flushTimer;
|
|
18
26
|
private stopRecording;
|
|
19
27
|
private disposed;
|
|
20
|
-
|
|
28
|
+
private readonly uploader;
|
|
29
|
+
constructor(config: ReplayRecorderConfig, runtime?: ReplayRecorderRuntime);
|
|
21
30
|
dispose(): void;
|
|
22
31
|
private start;
|
|
23
32
|
private enqueue;
|
|
24
33
|
private flushBufferedEvents;
|
|
34
|
+
/**
|
|
35
|
+
* Terminal flush during pagehide/beforeunload. Tries sendBeacon first for the
|
|
36
|
+
* current buffer and any pending retries, then falls back to keepalive fetch.
|
|
37
|
+
*/
|
|
38
|
+
private finalFlush;
|
|
39
|
+
private buildPayloadBody;
|
|
25
40
|
private buildReplayUrl;
|
|
26
41
|
private trySendBeacon;
|
|
27
|
-
private sendFetch;
|
|
28
42
|
}
|
|
29
|
-
export {};
|
|
30
43
|
//# sourceMappingURL=replay.d.ts.map
|
package/dist/replay.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAgBtD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACrC,YAAY,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B;AAyCD,qBAAa,cAAc;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkB;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;gBAE9B,MAAM,EAAE,oBAAoB,EAAE,OAAO,wBAA+B;IAoBhF,OAAO,IAAI,IAAI;YAwBD,KAAK;IA6DnB,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,mBAAmB;IAgB3B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAoBlB,OAAO,CAAC,gBAAgB;IAexB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,aAAa;CAatB"}
|
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
|