@maple-dev/browser 0.9.0 → 0.10.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 +85 -3
- package/dist/console-BrjBnOiX.mjs +73 -0
- package/dist/deferred-CdtlxG0Q.mjs +723 -0
- package/dist/index.d.mts +164 -14
- package/dist/index.mjs +1 -854
- package/dist/react.d.mts +88 -0
- package/dist/react.mjs +130 -0
- package/dist/replay-session-DdVYdHc_.mjs +527 -0
- package/dist/{sink-DHPiMgU0.mjs → sink-BOR9_z4b.mjs} +293 -104
- package/dist/src-Bzmp8rVr.mjs +1722 -0
- package/package.json +33 -11
- package/dist/replay-session-gSlAYwqV.mjs +0 -360
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maple-dev/browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Maple browser SDK — OpenTelemetry tracing and rrweb session replay in one package. Every span and replay event shares a session id for trace↔replay correlation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"maple",
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
".": {
|
|
31
31
|
"types": "./dist/index.d.mts",
|
|
32
32
|
"import": "./dist/index.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./react": {
|
|
35
|
+
"types": "./dist/react.d.mts",
|
|
36
|
+
"import": "./dist/react.mjs"
|
|
33
37
|
}
|
|
34
38
|
},
|
|
35
39
|
"publishConfig": {
|
|
@@ -43,21 +47,39 @@
|
|
|
43
47
|
},
|
|
44
48
|
"dependencies": {
|
|
45
49
|
"@opentelemetry/api": "^1.9.0",
|
|
46
|
-
"@opentelemetry/exporter-
|
|
47
|
-
"@opentelemetry/
|
|
48
|
-
"@opentelemetry/instrumentation
|
|
50
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.222.0",
|
|
51
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
|
|
52
|
+
"@opentelemetry/instrumentation": "^0.222.0",
|
|
53
|
+
"@opentelemetry/instrumentation-fetch": "^0.222.0",
|
|
54
|
+
"@opentelemetry/instrumentation-xml-http-request": "^0.222.0",
|
|
55
|
+
"@opentelemetry/otlp-transformer": "^0.222.0",
|
|
49
56
|
"@opentelemetry/resources": "^2.10.0",
|
|
57
|
+
"@opentelemetry/sdk-logs": "^0.222.0",
|
|
50
58
|
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
51
59
|
"@opentelemetry/sdk-trace-web": "^2.10.0",
|
|
52
60
|
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
53
|
-
"rrweb": "^2.0.0-alpha.18"
|
|
61
|
+
"rrweb": "^2.0.0-alpha.18",
|
|
62
|
+
"web-vitals": "^6.2.2"
|
|
54
63
|
},
|
|
55
64
|
"devDependencies": {
|
|
56
65
|
"@maple/browser-session": "0.1.0",
|
|
57
|
-
"@
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
66
|
+
"@maple/sdk-core": "0.1.0",
|
|
67
|
+
"@types/react": "^19.3.0",
|
|
68
|
+
"@types/react-dom": "^19.3.0",
|
|
69
|
+
"@vitest/browser-playwright": "^5.0.2",
|
|
70
|
+
"playwright": "^1.63.0",
|
|
71
|
+
"react": "^19.3.0",
|
|
72
|
+
"react-dom": "^19.3.0",
|
|
73
|
+
"tsdown": "^0.23.0",
|
|
74
|
+
"typescript": "^7.0.2",
|
|
75
|
+
"vitest": "^5.0.2"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"react": ">=18"
|
|
79
|
+
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"react": {
|
|
82
|
+
"optional": true
|
|
83
|
+
}
|
|
62
84
|
}
|
|
63
|
-
}
|
|
85
|
+
}
|
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
import { C as warnDropped, E as activeTraceId, M as scrubUrl, O as withStartedTraceId, T as safeEmit, b as postSessionBlob, m as nextChunkSeq, n as getObservedTraceIds, o as startSessionLifecycle, p as markActivity, r as publishSessionSink, u as startEventSink, w as BLOCK_SELECTOR, x as postSessionMeta, y as gzip } from "./sink-DHPiMgU0.mjs";
|
|
2
|
-
import { record } from "rrweb";
|
|
3
|
-
//#region ../browser-session/src/replay/capture/console.ts
|
|
4
|
-
const LEVELS = [
|
|
5
|
-
"log",
|
|
6
|
-
"info",
|
|
7
|
-
"warn",
|
|
8
|
-
"error",
|
|
9
|
-
"debug"
|
|
10
|
-
];
|
|
11
|
-
const MAX_MESSAGE = 2e3;
|
|
12
|
-
/** No array logged into a 2,000-character message needs more elements than this. */
|
|
13
|
-
const MAX_ARRAY_ELEMENTS = 100;
|
|
14
|
-
/**
|
|
15
|
-
* Capture `console.*` calls as session events. Wraps each method, emits a
|
|
16
|
-
* distilled record, then forwards to the original so the host app's console
|
|
17
|
-
* behaves normally. Never throws into the call site.
|
|
18
|
-
*/
|
|
19
|
-
function installConsoleCapture(emit) {
|
|
20
|
-
const original = {};
|
|
21
|
-
for (const level of LEVELS) {
|
|
22
|
-
const orig = console[level];
|
|
23
|
-
original[level] = orig;
|
|
24
|
-
console[level] = (...args) => {
|
|
25
|
-
safeEmit(emit, {
|
|
26
|
-
type: "console",
|
|
27
|
-
level,
|
|
28
|
-
message: formatArgs(args)
|
|
29
|
-
});
|
|
30
|
-
orig.apply(console, args);
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
return () => {
|
|
34
|
-
for (const level of LEVELS) {
|
|
35
|
-
const orig = original[level];
|
|
36
|
-
if (orig) console[level] = orig;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function formatArgs(args) {
|
|
41
|
-
const text = args.map((a) => {
|
|
42
|
-
if (typeof a === "string") return a;
|
|
43
|
-
if (a instanceof Error) return `${a.name}: ${a.message}`;
|
|
44
|
-
try {
|
|
45
|
-
return boundedStringify(a, MAX_MESSAGE) ?? String(a);
|
|
46
|
-
} catch {
|
|
47
|
-
return String(a);
|
|
48
|
-
}
|
|
49
|
-
}).join(" ");
|
|
50
|
-
return text.length > MAX_MESSAGE ? `${text.slice(0, MAX_MESSAGE)}…` : text;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* `JSON.stringify` that stops descending once roughly `budget` characters have
|
|
54
|
-
* been produced. The message is cut to `MAX_MESSAGE` anyway, and serializing a
|
|
55
|
-
* whole store object on every `console.log` first was a main-thread cost paid
|
|
56
|
-
* on the host app's hot path. A replacer returning `undefined` skips a value
|
|
57
|
-
* without visiting its children, so the walk is bounded by the budget.
|
|
58
|
-
*/
|
|
59
|
-
function boundedStringify(value, budget) {
|
|
60
|
-
let used = 0;
|
|
61
|
-
return JSON.stringify(value, (key, nested) => {
|
|
62
|
-
if (used > budget) return void 0;
|
|
63
|
-
used += key.length + (typeof nested === "string" ? nested.length : 4);
|
|
64
|
-
if (Array.isArray(nested) && nested.length > MAX_ARRAY_ELEMENTS) return nested.slice(0, MAX_ARRAY_ELEMENTS);
|
|
65
|
-
return nested;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
//#endregion
|
|
69
|
-
//#region ../browser-session/src/replay/capture/network.ts
|
|
70
|
-
/**
|
|
71
|
-
* Capture fetch + XHR requests as session events, tagged with the active trace
|
|
72
|
-
* id so each request links to its backend trace. `ignoreUrl` skips Maple's own
|
|
73
|
-
* ingest endpoints (otherwise capturing the session-events POST would loop).
|
|
74
|
-
*/
|
|
75
|
-
function installNetworkCapture(emit, ignoreUrl) {
|
|
76
|
-
const origFetch = typeof window !== "undefined" ? window.fetch : void 0;
|
|
77
|
-
if (origFetch) window.fetch = async (input, init) => {
|
|
78
|
-
const url = requestUrl(input);
|
|
79
|
-
const method = requestMethod(input, init);
|
|
80
|
-
const ambientTraceId = activeTraceId();
|
|
81
|
-
const start = performance.now();
|
|
82
|
-
let traceId = ambientTraceId;
|
|
83
|
-
try {
|
|
84
|
-
const call = withStartedTraceId(() => origFetch(input, init));
|
|
85
|
-
traceId = call.traceId ?? ambientTraceId;
|
|
86
|
-
const res = await call.result;
|
|
87
|
-
record(url, method, res.status, start, traceId);
|
|
88
|
-
return res;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
record(url, method, 0, start, traceId, String(error));
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
const record = (url, method, status, start, traceId, error) => {
|
|
95
|
-
if (ignoreUrl(url)) return;
|
|
96
|
-
safeEmit(emit, {
|
|
97
|
-
type: "network",
|
|
98
|
-
net: {
|
|
99
|
-
method,
|
|
100
|
-
url,
|
|
101
|
-
status,
|
|
102
|
-
durationMs: Math.round(performance.now() - start)
|
|
103
|
-
},
|
|
104
|
-
traceId,
|
|
105
|
-
...error ? { attrs: { error } } : void 0
|
|
106
|
-
});
|
|
107
|
-
};
|
|
108
|
-
const XHR = typeof window !== "undefined" ? window.XMLHttpRequest : void 0;
|
|
109
|
-
const origOpen = XHR?.prototype.open;
|
|
110
|
-
const origSend = XHR?.prototype.send;
|
|
111
|
-
if (XHR && origOpen && origSend) {
|
|
112
|
-
XHR.prototype.open = function(method, url, ...rest) {
|
|
113
|
-
this.__mapleMethod = String(method).toUpperCase();
|
|
114
|
-
this.__mapleUrl = typeof url === "string" ? url : url.href;
|
|
115
|
-
return origOpen.apply(this, [
|
|
116
|
-
method,
|
|
117
|
-
url,
|
|
118
|
-
...rest
|
|
119
|
-
]);
|
|
120
|
-
};
|
|
121
|
-
XHR.prototype.send = function(...args) {
|
|
122
|
-
const meta = this;
|
|
123
|
-
const start = performance.now();
|
|
124
|
-
let traceId = activeTraceId();
|
|
125
|
-
this.addEventListener("loadend", () => {
|
|
126
|
-
record(meta.__mapleUrl ?? "", meta.__mapleMethod ?? "GET", this.status, start, traceId);
|
|
127
|
-
});
|
|
128
|
-
const call = withStartedTraceId(() => origSend.apply(this, args));
|
|
129
|
-
traceId = call.traceId ?? traceId;
|
|
130
|
-
return call.result;
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
return () => {
|
|
134
|
-
if (origFetch) window.fetch = origFetch;
|
|
135
|
-
if (XHR && origOpen) XHR.prototype.open = origOpen;
|
|
136
|
-
if (XHR && origSend) XHR.prototype.send = origSend;
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
function requestUrl(input) {
|
|
140
|
-
if (typeof input === "string") return input;
|
|
141
|
-
if (input instanceof URL) return input.href;
|
|
142
|
-
return input.url;
|
|
143
|
-
}
|
|
144
|
-
function requestMethod(input, init) {
|
|
145
|
-
return (init?.method ?? (typeof input === "object" && "method" in input ? input.method : void 0) ?? "GET").toUpperCase();
|
|
146
|
-
}
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region ../browser-session/src/replay/events.ts
|
|
149
|
-
/**
|
|
150
|
-
* Install the capture modules that turn console output and network requests
|
|
151
|
-
* into distilled session events.
|
|
152
|
-
*
|
|
153
|
-
* Navigation, errors and interactions are deliberately absent: the sink
|
|
154
|
-
* installs those itself (see `startBaselineCapture`), because page views, error
|
|
155
|
-
* counts and click counts have to be counted even when replay is unsampled —
|
|
156
|
-
* and a second listener here would double-count every one of them.
|
|
157
|
-
*/
|
|
158
|
-
function startEventCapture(config, sessionId) {
|
|
159
|
-
const sink = startEventSink(config, sessionId);
|
|
160
|
-
const emit = sink.emit;
|
|
161
|
-
const uninstall = [installConsoleCapture(emit), installNetworkCapture(emit, sink.ignoreUrl)];
|
|
162
|
-
return {
|
|
163
|
-
stop: () => {
|
|
164
|
-
for (const off of uninstall) off();
|
|
165
|
-
},
|
|
166
|
-
flush: sink.flush
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
//#endregion
|
|
170
|
-
//#region ../browser-session/src/replay/record.ts
|
|
171
|
-
const FULL_SNAPSHOT = 2;
|
|
172
|
-
const META = 4;
|
|
173
|
-
const INCREMENTAL = 3;
|
|
174
|
-
const SOURCE_MOUSE_INTERACTION = 2;
|
|
175
|
-
const MOUSE_CLICK = 2;
|
|
176
|
-
const FLUSH_INTERVAL_MS = 5e3;
|
|
177
|
-
const FLUSH_BYTES = 102400;
|
|
178
|
-
const CHECKOUT_EVERY_MS = 3e5;
|
|
179
|
-
const MAX_BUFFER_BYTES = 4194304;
|
|
180
|
-
function warnExhausted(sessionId) {
|
|
181
|
-
console.warn(`[maple] session replay ${sessionId} reached its maximum recorded size; recording stopped for this session (metadata and events continue)`);
|
|
182
|
-
}
|
|
183
|
-
let lastDropWarnAt = 0;
|
|
184
|
-
function warnBufferDropped(bytes) {
|
|
185
|
-
const now = Date.now();
|
|
186
|
-
if (now - lastDropWarnAt < 3e4) return;
|
|
187
|
-
lastDropWarnAt = now;
|
|
188
|
-
console.warn(`[maple] session replay buffer exceeded ${MAX_BUFFER_BYTES} bytes (dropping ${bytes} buffered bytes; recording continues from the next full snapshot)`);
|
|
189
|
-
}
|
|
190
|
-
function startRecording(config, sessionId) {
|
|
191
|
-
let parts = [];
|
|
192
|
-
let bufferBytes = 0;
|
|
193
|
-
let bufferHasCheckpoint = false;
|
|
194
|
-
let firstTimestamp = 0;
|
|
195
|
-
let lastTimestamp = 0;
|
|
196
|
-
let droppedChunk = false;
|
|
197
|
-
let clickCount = 0;
|
|
198
|
-
let stopped = false;
|
|
199
|
-
let exhausted = false;
|
|
200
|
-
const resetBuffer = () => {
|
|
201
|
-
parts = [];
|
|
202
|
-
bufferBytes = 0;
|
|
203
|
-
bufferHasCheckpoint = false;
|
|
204
|
-
firstTimestamp = 0;
|
|
205
|
-
lastTimestamp = 0;
|
|
206
|
-
};
|
|
207
|
-
const flush = async (keepalive = false) => {
|
|
208
|
-
if (stopped || exhausted || parts.length === 0) return;
|
|
209
|
-
const body = `[${parts.join(",")}]`;
|
|
210
|
-
const isCheckpoint = bufferHasCheckpoint;
|
|
211
|
-
const eventCount = parts.length;
|
|
212
|
-
const durationMs = Math.max(0, lastTimestamp - firstTimestamp);
|
|
213
|
-
const seq = nextChunkSeq();
|
|
214
|
-
resetBuffer();
|
|
215
|
-
let gzipped;
|
|
216
|
-
try {
|
|
217
|
-
gzipped = await gzip(new TextEncoder().encode(body));
|
|
218
|
-
} catch (error) {
|
|
219
|
-
warnDropped("chunk compression", error);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
if (await postSessionBlob(config, {
|
|
223
|
-
sessionId,
|
|
224
|
-
chunkSeq: seq,
|
|
225
|
-
isCheckpoint,
|
|
226
|
-
eventCount,
|
|
227
|
-
durationMs
|
|
228
|
-
}, gzipped, keepalive) === "exhausted" && !exhausted) {
|
|
229
|
-
exhausted = true;
|
|
230
|
-
warnExhausted(sessionId);
|
|
231
|
-
haltCapture();
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
let haltCapture = () => {};
|
|
235
|
-
const stop = record({
|
|
236
|
-
emit: (event, isCheckpoint) => {
|
|
237
|
-
const active = markActivity();
|
|
238
|
-
if (active && active.id !== sessionId) return;
|
|
239
|
-
const e = event;
|
|
240
|
-
if (e.type === META && e.data && typeof e.data.href === "string") e.data.href = scrubUrl(e.data.href);
|
|
241
|
-
const isFullSnapshot = isCheckpoint === true || e.type === FULL_SNAPSHOT;
|
|
242
|
-
if (e.type === INCREMENTAL && e.data?.source === SOURCE_MOUSE_INTERACTION && e.data.type === MOUSE_CLICK) clickCount++;
|
|
243
|
-
let json;
|
|
244
|
-
try {
|
|
245
|
-
json = JSON.stringify(e);
|
|
246
|
-
} catch {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
if (droppedChunk && !isFullSnapshot) return;
|
|
250
|
-
droppedChunk = false;
|
|
251
|
-
if (bufferBytes + json.length > MAX_BUFFER_BYTES) {
|
|
252
|
-
warnBufferDropped(bufferBytes + json.length);
|
|
253
|
-
resetBuffer();
|
|
254
|
-
droppedChunk = true;
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
if (isFullSnapshot) bufferHasCheckpoint = true;
|
|
258
|
-
if (parts.length === 0) firstTimestamp = e.timestamp;
|
|
259
|
-
lastTimestamp = e.timestamp;
|
|
260
|
-
parts.push(json);
|
|
261
|
-
bufferBytes += json.length;
|
|
262
|
-
if (bufferBytes >= FLUSH_BYTES) flush();
|
|
263
|
-
},
|
|
264
|
-
maskAllInputs: config.maskAllInputs,
|
|
265
|
-
blockSelector: BLOCK_SELECTOR,
|
|
266
|
-
...config.maskAllText ? { maskTextSelector: "*" } : void 0,
|
|
267
|
-
checkoutEveryNms: CHECKOUT_EVERY_MS
|
|
268
|
-
});
|
|
269
|
-
let idleHandle;
|
|
270
|
-
const scheduleFlush = () => {
|
|
271
|
-
if (typeof requestIdleCallback === "function") idleHandle = requestIdleCallback(() => {
|
|
272
|
-
idleHandle = void 0;
|
|
273
|
-
flush();
|
|
274
|
-
}, { timeout: 2e3 });
|
|
275
|
-
else flush();
|
|
276
|
-
};
|
|
277
|
-
const flushTimer = setInterval(scheduleFlush, FLUSH_INTERVAL_MS);
|
|
278
|
-
let halted = false;
|
|
279
|
-
haltCapture = () => {
|
|
280
|
-
if (halted) return;
|
|
281
|
-
halted = true;
|
|
282
|
-
clearInterval(flushTimer);
|
|
283
|
-
if (idleHandle !== void 0 && typeof cancelIdleCallback === "function") {
|
|
284
|
-
cancelIdleCallback(idleHandle);
|
|
285
|
-
idleHandle = void 0;
|
|
286
|
-
}
|
|
287
|
-
resetBuffer();
|
|
288
|
-
stop?.();
|
|
289
|
-
};
|
|
290
|
-
return {
|
|
291
|
-
stop: () => {
|
|
292
|
-
stopped = true;
|
|
293
|
-
haltCapture();
|
|
294
|
-
},
|
|
295
|
-
flush,
|
|
296
|
-
getClickCount: () => clickCount
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
//#endregion
|
|
300
|
-
//#region ../browser-session/src/session/replay-session.ts
|
|
301
|
-
/**
|
|
302
|
-
* Start recording the current browser session. Publishes the session sink,
|
|
303
|
-
* posts an `active` metadata row, and installs visibility handlers:
|
|
304
|
-
* hidden → flush + `ended` row (with observed trace ids) + stop capture;
|
|
305
|
-
* visible → re-resolve the session (rotating if idle-expired), republish the
|
|
306
|
-
* sink, restart capture, post a fresh `active` row. Metadata versions are
|
|
307
|
-
* monotonic per session, so the latest row always wins on the backend.
|
|
308
|
-
*
|
|
309
|
-
* Returns undefined outside a browser. Sampling is the caller's decision.
|
|
310
|
-
*/
|
|
311
|
-
function startReplaySession(options) {
|
|
312
|
-
if (typeof window === "undefined") return void 0;
|
|
313
|
-
const engineConfig = {
|
|
314
|
-
endpoint: options.endpoint.replace(/\/$/, ""),
|
|
315
|
-
ingestKey: options.ingestKey,
|
|
316
|
-
sdk: options.sdk,
|
|
317
|
-
maskAllInputs: options.maskAllInputs,
|
|
318
|
-
maskAllText: options.maskAllText,
|
|
319
|
-
getIdentity: options.getIdentity
|
|
320
|
-
};
|
|
321
|
-
let recorder;
|
|
322
|
-
let events;
|
|
323
|
-
let publishedSessionId;
|
|
324
|
-
const publish = (sessionId) => {
|
|
325
|
-
if (publishedSessionId === sessionId) return;
|
|
326
|
-
publishedSessionId = sessionId;
|
|
327
|
-
publishSessionSink(sessionId);
|
|
328
|
-
};
|
|
329
|
-
return startSessionLifecycle({
|
|
330
|
-
...options,
|
|
331
|
-
getTraceIds: getObservedTraceIds
|
|
332
|
-
}, {
|
|
333
|
-
recorded: true,
|
|
334
|
-
post: (row, keepalive) => {
|
|
335
|
-
postSessionMeta(engineConfig, row, keepalive);
|
|
336
|
-
},
|
|
337
|
-
clicksSinceStart: () => recorder?.getClickCount() ?? 0,
|
|
338
|
-
onStart: (record) => {
|
|
339
|
-
publish(record.id);
|
|
340
|
-
recorder = startRecording(engineConfig, record.id);
|
|
341
|
-
events = startEventCapture(engineConfig, record.id);
|
|
342
|
-
},
|
|
343
|
-
onSuspend: ({ flush, keepalive }) => {
|
|
344
|
-
const stoppingRecorder = recorder;
|
|
345
|
-
const stoppingEvents = events;
|
|
346
|
-
recorder = void 0;
|
|
347
|
-
events = void 0;
|
|
348
|
-
const flushed = flush ? Promise.all([stoppingRecorder?.flush(keepalive), stoppingEvents?.flush(keepalive)]).then(() => {}) : void 0;
|
|
349
|
-
stoppingRecorder?.stop();
|
|
350
|
-
stoppingEvents?.stop();
|
|
351
|
-
return flushed;
|
|
352
|
-
},
|
|
353
|
-
onSessionChange: (sessionId) => {
|
|
354
|
-
publish(sessionId);
|
|
355
|
-
options.onSessionChange?.(sessionId);
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
//#endregion
|
|
360
|
-
export { startReplaySession };
|