@brunyee-studio/onus-sdk 0.1.0 → 1.0.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 +108 -6
- package/dist/{chunk-SLWBPQ6C.js → chunk-YPQXIZ4X.js} +134 -19
- package/dist/index.cjs +364 -43
- package/dist/index.d.ts +16 -9
- package/dist/index.js +7 -3
- package/dist/{replay-D7ejwI0s.d.ts → replay-BElhLiw8.d.ts} +52 -14
- package/dist/replay.d.ts +1 -1
- package/dist/replay.js +221 -23
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -128,18 +128,39 @@ async function gzipText(text) {
|
|
|
128
128
|
function delay(ms) {
|
|
129
129
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
130
130
|
}
|
|
131
|
+
function untilAborted(operation, signal) {
|
|
132
|
+
if (!signal) return operation;
|
|
133
|
+
if (signal.aborted) return Promise.resolve(void 0);
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const onAbort = () => resolve(void 0);
|
|
136
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
137
|
+
operation.then(
|
|
138
|
+
(value) => {
|
|
139
|
+
signal.removeEventListener("abort", onAbort);
|
|
140
|
+
resolve(value);
|
|
141
|
+
},
|
|
142
|
+
(error) => {
|
|
143
|
+
signal.removeEventListener("abort", onAbort);
|
|
144
|
+
reject(error);
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
131
149
|
async function sendEnvelope(opts) {
|
|
132
150
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
133
151
|
const maxRetries = opts.maxRetries ?? 1;
|
|
134
152
|
const backoffMs = opts.backoffMs ?? 500;
|
|
135
153
|
const sleep = opts.delayImpl ?? delay;
|
|
136
|
-
|
|
154
|
+
if (opts.signal?.aborted) return { ok: false, error: "aborted" };
|
|
155
|
+
const gz = await untilAborted(gzipText(opts.body), opts.signal);
|
|
156
|
+
if (opts.signal?.aborted) return { ok: false, error: "aborted" };
|
|
137
157
|
const headers = {
|
|
138
158
|
"Content-Type": "application/x-sentry-envelope",
|
|
139
159
|
"X-Sentry-Auth": opts.authHeader,
|
|
140
160
|
...gz ? { "Content-Encoding": "gzip" } : {}
|
|
141
161
|
};
|
|
142
162
|
for (let attempt = 0; ; attempt++) {
|
|
163
|
+
if (opts.signal?.aborted) return { ok: false, error: "aborted" };
|
|
143
164
|
let res;
|
|
144
165
|
try {
|
|
145
166
|
res = await fetchImpl(opts.url, {
|
|
@@ -147,11 +168,13 @@ async function sendEnvelope(opts) {
|
|
|
147
168
|
headers,
|
|
148
169
|
body: gz ?? opts.body,
|
|
149
170
|
// Analytics-style telemetry: never block page unload on the response.
|
|
150
|
-
keepalive: true
|
|
171
|
+
keepalive: true,
|
|
172
|
+
signal: opts.signal
|
|
151
173
|
});
|
|
152
174
|
} catch (e) {
|
|
153
175
|
return { ok: false, error: e instanceof Error ? e.message : "network error" };
|
|
154
176
|
}
|
|
177
|
+
if (opts.signal?.aborted) return { ok: false, error: "aborted" };
|
|
155
178
|
if (res.ok) return { ok: true, status: res.status };
|
|
156
179
|
if (res.status === 429 && attempt < maxRetries) {
|
|
157
180
|
const retryAfter = Number(res.headers.get("retry-after") ?? "");
|
|
@@ -159,7 +182,8 @@ async function sendEnvelope(opts) {
|
|
|
159
182
|
Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1e3 : backoffMs * (attempt + 1),
|
|
160
183
|
1e4
|
|
161
184
|
);
|
|
162
|
-
await sleep(wait);
|
|
185
|
+
await untilAborted(sleep(wait), opts.signal);
|
|
186
|
+
if (opts.signal?.aborted) return { ok: false, error: "aborted" };
|
|
163
187
|
continue;
|
|
164
188
|
}
|
|
165
189
|
if (res.status === 429) {
|
|
@@ -180,6 +204,152 @@ var init_transport = __esm({
|
|
|
180
204
|
}
|
|
181
205
|
});
|
|
182
206
|
|
|
207
|
+
// src/capture-gate.ts
|
|
208
|
+
function isReplayCaptureAllowed() {
|
|
209
|
+
return allowed;
|
|
210
|
+
}
|
|
211
|
+
function deferAutoStart(start) {
|
|
212
|
+
pendingAutoStart = start;
|
|
213
|
+
const generation = ++autoStartGeneration;
|
|
214
|
+
releaseStaleAutoReplay?.(generation);
|
|
215
|
+
return generation;
|
|
216
|
+
}
|
|
217
|
+
function clearAutoStart() {
|
|
218
|
+
pendingAutoStart = null;
|
|
219
|
+
releaseStaleAutoReplay?.(++autoStartGeneration);
|
|
220
|
+
}
|
|
221
|
+
function isAutoStartGenerationCurrent(generation) {
|
|
222
|
+
return generation === autoStartGeneration && pendingAutoStart !== null;
|
|
223
|
+
}
|
|
224
|
+
function getReplayCaptureGrantEpoch() {
|
|
225
|
+
return grantEpoch;
|
|
226
|
+
}
|
|
227
|
+
function registerReplayLinkClear(clear) {
|
|
228
|
+
clearReplayLink = clear;
|
|
229
|
+
}
|
|
230
|
+
function registerStaleAutoReplayRelease(release) {
|
|
231
|
+
releaseStaleAutoReplay = release;
|
|
232
|
+
}
|
|
233
|
+
function registerReplayStop(stop) {
|
|
234
|
+
stopReplay = stop;
|
|
235
|
+
}
|
|
236
|
+
async function setReplayCaptureAllowed(next) {
|
|
237
|
+
if (next === allowed) return;
|
|
238
|
+
allowed = next;
|
|
239
|
+
grantEpoch++;
|
|
240
|
+
if (!next) {
|
|
241
|
+
const stop = stopReplay;
|
|
242
|
+
stopReplay = null;
|
|
243
|
+
stop?.();
|
|
244
|
+
clearReplayLink?.();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const start = pendingAutoStart;
|
|
248
|
+
if (start) await start();
|
|
249
|
+
}
|
|
250
|
+
function resetCaptureGateForTest() {
|
|
251
|
+
allowed = false;
|
|
252
|
+
grantEpoch++;
|
|
253
|
+
clearAutoStart();
|
|
254
|
+
releaseStaleAutoReplay = null;
|
|
255
|
+
const stop = stopReplay;
|
|
256
|
+
stopReplay = null;
|
|
257
|
+
stop?.();
|
|
258
|
+
clearReplayLink?.();
|
|
259
|
+
clearReplayLink = null;
|
|
260
|
+
}
|
|
261
|
+
var allowed, pendingAutoStart, autoStartGeneration, grantEpoch, stopReplay, clearReplayLink, releaseStaleAutoReplay;
|
|
262
|
+
var init_capture_gate = __esm({
|
|
263
|
+
"src/capture-gate.ts"() {
|
|
264
|
+
"use strict";
|
|
265
|
+
allowed = false;
|
|
266
|
+
pendingAutoStart = null;
|
|
267
|
+
autoStartGeneration = 0;
|
|
268
|
+
grantEpoch = 0;
|
|
269
|
+
stopReplay = null;
|
|
270
|
+
clearReplayLink = null;
|
|
271
|
+
releaseStaleAutoReplay = null;
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// src/replay-privacy.ts
|
|
276
|
+
function isValidCssSelector(raw) {
|
|
277
|
+
if (typeof document === "undefined") return false;
|
|
278
|
+
try {
|
|
279
|
+
document.querySelector(raw);
|
|
280
|
+
return true;
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function validSelectorExtra(raw) {
|
|
286
|
+
if (typeof raw !== "string" || raw.trim().length === 0) return "";
|
|
287
|
+
return isValidCssSelector(raw) ? raw : "";
|
|
288
|
+
}
|
|
289
|
+
function maskTextSelectorOrFallback(raw) {
|
|
290
|
+
if (typeof raw !== "string" || raw.trim().length === 0) return PRIVACY_DEFAULTS.maskTextSelector;
|
|
291
|
+
return isValidCssSelector(raw) ? raw : PRIVACY_DEFAULTS.maskTextSelector;
|
|
292
|
+
}
|
|
293
|
+
function blockSelectorWithExtra(extra) {
|
|
294
|
+
const extra_ = validSelectorExtra(extra);
|
|
295
|
+
return extra_ ? `${PRIVACY_DEFAULTS.blockSelector}, ${extra_}` : PRIVACY_DEFAULTS.blockSelector;
|
|
296
|
+
}
|
|
297
|
+
function resolveRecordPrivacy(recordOptions, privacy) {
|
|
298
|
+
const passthrough = {};
|
|
299
|
+
for (const [key, value] of Object.entries(recordOptions ?? {})) {
|
|
300
|
+
if (!PRIVACY_CONTROLLED_KEYS.has(key)) passthrough[key] = value;
|
|
301
|
+
}
|
|
302
|
+
const effective = {
|
|
303
|
+
...passthrough,
|
|
304
|
+
maskAllInputs: privacy?.maskAllInputs ?? PRIVACY_DEFAULTS.maskAllInputs,
|
|
305
|
+
maskTextSelector: maskTextSelectorOrFallback(privacy?.maskTextSelector),
|
|
306
|
+
blockSelector: blockSelectorWithExtra(privacy?.blockSelector),
|
|
307
|
+
recordCanvas: false,
|
|
308
|
+
recordCrossOriginIframes: false,
|
|
309
|
+
inlineImages: false,
|
|
310
|
+
// Password masking is non-negotiable, regardless of opt-downs above.
|
|
311
|
+
maskInputOptions: {
|
|
312
|
+
...privacy?.maskInputOptions ?? {},
|
|
313
|
+
password: true
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
if (privacy?.maskTextFn) effective["maskTextFn"] = privacy.maskTextFn;
|
|
317
|
+
if (privacy?.ignoreSelector) effective["ignoreSelector"] = privacy.ignoreSelector;
|
|
318
|
+
return effective;
|
|
319
|
+
}
|
|
320
|
+
var PRIVACY_CONTROLLED_KEYS, PRIVACY_DEFAULTS;
|
|
321
|
+
var init_replay_privacy = __esm({
|
|
322
|
+
"src/replay-privacy.ts"() {
|
|
323
|
+
"use strict";
|
|
324
|
+
PRIVACY_CONTROLLED_KEYS = /* @__PURE__ */ new Set([
|
|
325
|
+
"emit",
|
|
326
|
+
"maskAllInputs",
|
|
327
|
+
"maskTextSelector",
|
|
328
|
+
"maskTextClass",
|
|
329
|
+
"maskInputOptions",
|
|
330
|
+
"maskInputFn",
|
|
331
|
+
"maskTextFn",
|
|
332
|
+
"blockSelector",
|
|
333
|
+
"blockClass",
|
|
334
|
+
"ignoreClass",
|
|
335
|
+
"ignoreSelector",
|
|
336
|
+
// rrweb runs packers/plugins BEFORE the emit callback, so an allowed
|
|
337
|
+
// passthrough could serialize events that never pass through the SDK's
|
|
338
|
+
// Meta href sanitization. They are SDK-controlled, never passthrough.
|
|
339
|
+
"packFn",
|
|
340
|
+
"plugins",
|
|
341
|
+
"recordCanvas",
|
|
342
|
+
"recordCrossOriginIframes",
|
|
343
|
+
"inlineImages"
|
|
344
|
+
]);
|
|
345
|
+
PRIVACY_DEFAULTS = {
|
|
346
|
+
maskAllInputs: true,
|
|
347
|
+
maskTextSelector: "*",
|
|
348
|
+
blockSelector: '[data-onus-private], input[type="hidden"], input[type="radio"], input[type="checkbox"]'
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
183
353
|
// src/replay.ts
|
|
184
354
|
var replay_exports = {};
|
|
185
355
|
__export(replay_exports, {
|
|
@@ -188,27 +358,89 @@ __export(replay_exports, {
|
|
|
188
358
|
emitSegment: () => emitSegment,
|
|
189
359
|
parseDsn: () => parseDsn,
|
|
190
360
|
resetReplayForTest: () => resetReplayForTest,
|
|
361
|
+
revokeActiveReplay: () => revokeActiveReplay,
|
|
362
|
+
sanitizeRecordedEvent: () => sanitizeRecordedEvent,
|
|
363
|
+
startAutoSessionReplay: () => startAutoSessionReplay,
|
|
191
364
|
startSessionReplay: () => startSessionReplay,
|
|
192
|
-
stopSessionReplay: () => stopSessionReplay
|
|
365
|
+
stopSessionReplay: () => stopSessionReplay,
|
|
366
|
+
stripUrlQueryAndFragment: () => stripUrlQueryAndFragment
|
|
193
367
|
});
|
|
194
|
-
|
|
368
|
+
function stripUrlQueryAndFragment(raw) {
|
|
369
|
+
try {
|
|
370
|
+
const url = new URL(raw);
|
|
371
|
+
url.username = "";
|
|
372
|
+
url.password = "";
|
|
373
|
+
url.search = "";
|
|
374
|
+
url.hash = "";
|
|
375
|
+
return url.toString();
|
|
376
|
+
} catch {
|
|
377
|
+
const noHash = raw.split("#")[0] ?? raw;
|
|
378
|
+
return noHash.split("?")[0] ?? noHash;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function sanitizeRecordedEvent(event) {
|
|
382
|
+
const candidate = event;
|
|
383
|
+
if (!candidate || typeof candidate !== "object" || candidate.type !== 4 || !candidate.data || typeof candidate.data !== "object") {
|
|
384
|
+
return event;
|
|
385
|
+
}
|
|
386
|
+
const data = { ...candidate.data };
|
|
387
|
+
if (typeof data["href"] === "string") data["href"] = stripUrlQueryAndFragment(data["href"]);
|
|
388
|
+
return { ...candidate, data };
|
|
389
|
+
}
|
|
390
|
+
function retainPendingTail(state2) {
|
|
391
|
+
pendingTails.add(state2);
|
|
392
|
+
if (pendingTails.size <= MAX_PENDING_TAILS) return;
|
|
393
|
+
const oldest = pendingTails.values().next().value;
|
|
394
|
+
if (!oldest) return;
|
|
395
|
+
pendingTails.delete(oldest);
|
|
396
|
+
oldest.abort?.abort();
|
|
397
|
+
oldest.buffer.length = 0;
|
|
398
|
+
}
|
|
399
|
+
function clearFailedClaim(state2) {
|
|
400
|
+
if (active === state2) active = null;
|
|
401
|
+
if (getActiveReplayId() === state2.replayId) setActiveReplayId(null);
|
|
402
|
+
}
|
|
403
|
+
function releaseStaleAutoReplay2(generation) {
|
|
404
|
+
if (active?.origin.kind === "auto" && active.origin.generation !== generation) {
|
|
405
|
+
dropActiveReplay();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function startSessionReplay(options = {}) {
|
|
409
|
+
return startRecording(options, { kind: "manual" });
|
|
410
|
+
}
|
|
411
|
+
function startAutoSessionReplay(options, generation) {
|
|
412
|
+
return startRecording(options, { kind: "auto", generation });
|
|
413
|
+
}
|
|
414
|
+
async function startRecording(options, origin) {
|
|
415
|
+
if (!isReplayCaptureAllowed() || options.shouldProceed?.() === false) return null;
|
|
416
|
+
registerStaleAutoReplayRelease(releaseStaleAutoReplay2);
|
|
195
417
|
if (active) return active.replayId;
|
|
196
418
|
const dsn = parseDsn(options.dsn);
|
|
197
419
|
if (!dsn) return null;
|
|
198
420
|
const replayId = eventId().replace(/-/g, "");
|
|
199
|
-
const state2 = {
|
|
421
|
+
const state2 = {
|
|
422
|
+
replayId,
|
|
423
|
+
origin,
|
|
424
|
+
stop: null,
|
|
425
|
+
rawStop: null,
|
|
426
|
+
abort: new AbortController(),
|
|
427
|
+
pendingSends: 0,
|
|
428
|
+
buffer: [],
|
|
429
|
+
timer: null
|
|
430
|
+
};
|
|
200
431
|
active = state2;
|
|
432
|
+
registerReplayStop(revokeActiveReplay);
|
|
201
433
|
let record = null;
|
|
202
434
|
if (options.recorderFactory) {
|
|
203
435
|
let recorder;
|
|
204
436
|
try {
|
|
205
437
|
recorder = await options.recorderFactory();
|
|
206
438
|
} catch {
|
|
207
|
-
|
|
439
|
+
clearFailedClaim(state2);
|
|
208
440
|
return null;
|
|
209
441
|
}
|
|
210
442
|
if (!recorder) {
|
|
211
|
-
|
|
443
|
+
clearFailedClaim(state2);
|
|
212
444
|
return null;
|
|
213
445
|
}
|
|
214
446
|
record = (_opts) => {
|
|
@@ -220,15 +452,19 @@ async function startSessionReplay(options = {}) {
|
|
|
220
452
|
const mod = await import("rrweb");
|
|
221
453
|
const fn = mod.record;
|
|
222
454
|
if (typeof fn !== "function") {
|
|
223
|
-
|
|
455
|
+
clearFailedClaim(state2);
|
|
224
456
|
return null;
|
|
225
457
|
}
|
|
226
458
|
record = fn;
|
|
227
459
|
} catch {
|
|
228
|
-
|
|
460
|
+
clearFailedClaim(state2);
|
|
229
461
|
return null;
|
|
230
462
|
}
|
|
231
463
|
}
|
|
464
|
+
if (active !== state2 || !isReplayCaptureAllowed() || options.shouldProceed?.() === false) {
|
|
465
|
+
clearFailedClaim(state2);
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
232
468
|
const segmentIntervalMs = options.segmentIntervalMs ?? 5e3;
|
|
233
469
|
const maxEventsPerSegment = options.maxEventsPerSegment ?? 100;
|
|
234
470
|
const narrowedDsn = dsn;
|
|
@@ -237,21 +473,36 @@ async function startSessionReplay(options = {}) {
|
|
|
237
473
|
clearTimeout(state2.timer);
|
|
238
474
|
state2.timer = null;
|
|
239
475
|
}
|
|
476
|
+
if (state2.abort?.signal.aborted || options.shouldProceed?.() === false) {
|
|
477
|
+
state2.buffer.length = 0;
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
240
480
|
if (state2.buffer.length === 0) return;
|
|
241
481
|
const events = state2.buffer;
|
|
242
482
|
state2.buffer = [];
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
483
|
+
state2.pendingSends++;
|
|
484
|
+
try {
|
|
485
|
+
await emitSegment(
|
|
486
|
+
narrowedDsn,
|
|
487
|
+
options,
|
|
488
|
+
state2,
|
|
489
|
+
events,
|
|
490
|
+
++segmentCounter,
|
|
491
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
492
|
+
);
|
|
493
|
+
} finally {
|
|
494
|
+
state2.pendingSends--;
|
|
495
|
+
if (state2.pendingSends === 0 && active !== state2) pendingTails.delete(state2);
|
|
496
|
+
}
|
|
251
497
|
}
|
|
498
|
+
const effectiveRecordOptions = resolveRecordPrivacy(options.recordOptions, options.privacy);
|
|
252
499
|
const stop = record({
|
|
500
|
+
...effectiveRecordOptions,
|
|
253
501
|
emit: (event) => {
|
|
254
|
-
state2.
|
|
502
|
+
if (active !== state2 || !isReplayCaptureAllowed() || options.shouldProceed?.() === false) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
state2.buffer.push(sanitizeRecordedEvent(event));
|
|
255
506
|
if (state2.buffer.length >= maxEventsPerSegment) {
|
|
256
507
|
void flushBuffer();
|
|
257
508
|
return;
|
|
@@ -262,9 +513,16 @@ async function startSessionReplay(options = {}) {
|
|
|
262
513
|
void flushBuffer();
|
|
263
514
|
}, segmentIntervalMs);
|
|
264
515
|
}
|
|
265
|
-
}
|
|
266
|
-
...options.recordOptions
|
|
516
|
+
}
|
|
267
517
|
});
|
|
518
|
+
if (active !== state2 || !isReplayCaptureAllowed() || options.shouldProceed?.() === false) {
|
|
519
|
+
stop?.();
|
|
520
|
+
state2.abort?.abort();
|
|
521
|
+
state2.buffer.length = 0;
|
|
522
|
+
clearFailedClaim(state2);
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
state2.rawStop = typeof stop === "function" ? stop : null;
|
|
268
526
|
state2.stop = typeof stop === "function" ? () => {
|
|
269
527
|
stop();
|
|
270
528
|
void flushBuffer();
|
|
@@ -284,19 +542,48 @@ function stopSessionReplay() {
|
|
|
284
542
|
const stopping = active;
|
|
285
543
|
active = null;
|
|
286
544
|
if (!stopping) return;
|
|
545
|
+
retainPendingTail(stopping);
|
|
287
546
|
stopping.stop?.();
|
|
288
547
|
if (stopping.timer !== null) {
|
|
289
548
|
clearTimeout(stopping.timer);
|
|
290
549
|
stopping.timer = null;
|
|
291
550
|
}
|
|
292
551
|
stopping.buffer.length = 0;
|
|
552
|
+
stopping.rawStop = null;
|
|
553
|
+
if (stopping.pendingSends === 0) pendingTails.delete(stopping);
|
|
554
|
+
setActiveReplayId(null);
|
|
555
|
+
}
|
|
556
|
+
function dropActiveReplay() {
|
|
557
|
+
const stopping = active;
|
|
558
|
+
active = null;
|
|
559
|
+
if (!stopping) return;
|
|
560
|
+
try {
|
|
561
|
+
stopping.rawStop?.();
|
|
562
|
+
} catch {
|
|
563
|
+
}
|
|
564
|
+
if (stopping.timer !== null) {
|
|
565
|
+
clearTimeout(stopping.timer);
|
|
566
|
+
stopping.timer = null;
|
|
567
|
+
}
|
|
568
|
+
stopping.abort?.abort();
|
|
569
|
+
stopping.buffer.length = 0;
|
|
570
|
+
setActiveReplayId(null);
|
|
571
|
+
}
|
|
572
|
+
function revokeActiveReplay() {
|
|
573
|
+
dropActiveReplay();
|
|
574
|
+
for (const tail of pendingTails) {
|
|
575
|
+
tail.abort?.abort();
|
|
576
|
+
tail.buffer.length = 0;
|
|
577
|
+
}
|
|
578
|
+
pendingTails.clear();
|
|
579
|
+
setActiveReplayId(null);
|
|
293
580
|
}
|
|
294
581
|
function activeReplayId() {
|
|
295
582
|
return active?.replayId ?? null;
|
|
296
583
|
}
|
|
297
584
|
function resetReplayForTest() {
|
|
298
|
-
|
|
299
|
-
|
|
585
|
+
revokeActiveReplay();
|
|
586
|
+
resetCaptureGateForTest();
|
|
300
587
|
segmentCounter = 0;
|
|
301
588
|
}
|
|
302
589
|
function resolveSessionId(options) {
|
|
@@ -340,11 +627,12 @@ ${JSON.stringify(event ?? [])}`;
|
|
|
340
627
|
url: dsn.envelopeUrl,
|
|
341
628
|
authHeader: `Sentry sentry_key=${dsn.key}, sentry_version=7, sentry_client=onus.javascript/0.1.0`,
|
|
342
629
|
body: serialized,
|
|
343
|
-
fetchImpl: options.fetchImpl
|
|
630
|
+
fetchImpl: options.fetchImpl,
|
|
631
|
+
signal: state2.abort?.signal
|
|
344
632
|
});
|
|
345
633
|
return result.ok;
|
|
346
634
|
}
|
|
347
|
-
var active, segmentCounter;
|
|
635
|
+
var active, pendingTails, MAX_PENDING_TAILS, segmentCounter;
|
|
348
636
|
var init_replay = __esm({
|
|
349
637
|
"src/replay.ts"() {
|
|
350
638
|
"use strict";
|
|
@@ -352,9 +640,13 @@ var init_replay = __esm({
|
|
|
352
640
|
init_transport();
|
|
353
641
|
init_dsn();
|
|
354
642
|
init_client();
|
|
643
|
+
init_capture_gate();
|
|
644
|
+
init_replay_privacy();
|
|
355
645
|
init_envelope();
|
|
356
646
|
init_dsn();
|
|
357
647
|
active = null;
|
|
648
|
+
pendingTails = /* @__PURE__ */ new Set();
|
|
649
|
+
MAX_PENDING_TAILS = 8;
|
|
358
650
|
segmentCounter = 0;
|
|
359
651
|
}
|
|
360
652
|
});
|
|
@@ -404,28 +696,50 @@ function init(options) {
|
|
|
404
696
|
authHeader: dsn ? buildSentryAuthHeader(dsn, SDK_VERSION) : "",
|
|
405
697
|
replayId: null
|
|
406
698
|
};
|
|
699
|
+
registerReplayLinkClear(() => setActiveReplayId(null));
|
|
407
700
|
autoReplayStart = null;
|
|
408
|
-
|
|
409
|
-
|
|
701
|
+
const replays = options.replays;
|
|
702
|
+
if (replays && replays.enabled !== false && dsn) {
|
|
703
|
+
let generation;
|
|
704
|
+
const start = () => {
|
|
705
|
+
if (!isAutoStartGenerationCurrent(generation) || !isReplayCaptureAllowed()) {
|
|
706
|
+
return Promise.resolve();
|
|
707
|
+
}
|
|
708
|
+
autoReplayStart = startAutoReplay(replays, options, generation, getReplayCaptureGrantEpoch());
|
|
709
|
+
return autoReplayStart;
|
|
710
|
+
};
|
|
711
|
+
generation = deferAutoStart(start);
|
|
712
|
+
if (isReplayCaptureAllowed()) void start();
|
|
713
|
+
} else {
|
|
714
|
+
clearAutoStart();
|
|
410
715
|
}
|
|
411
716
|
}
|
|
412
|
-
async function startAutoReplay(replays, options) {
|
|
717
|
+
async function startAutoReplay(replays, options, generation, grantEpoch2) {
|
|
718
|
+
const shouldProceed = () => isAutoStartGenerationCurrent(generation) && isReplayCaptureAllowed() && getReplayCaptureGrantEpoch() === grantEpoch2;
|
|
413
719
|
try {
|
|
414
720
|
const rate = typeof replays.sampleRate === "number" && replays.sampleRate >= 0 && replays.sampleRate <= 1 ? replays.sampleRate : 1;
|
|
415
721
|
if (Math.random() >= rate) return;
|
|
416
722
|
const replay = await Promise.resolve().then(() => (init_replay(), replay_exports));
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
723
|
+
if (!shouldProceed()) return;
|
|
724
|
+
const replayId = await replay.startAutoSessionReplay(
|
|
725
|
+
{
|
|
726
|
+
shouldProceed,
|
|
727
|
+
dsn: options.dsn,
|
|
728
|
+
environment: replays.environment ?? options.environment,
|
|
729
|
+
release: replays.release ?? options.release,
|
|
730
|
+
sessionId: replays.sessionId,
|
|
731
|
+
segmentIntervalMs: replays.segmentIntervalMs,
|
|
732
|
+
maxEventsPerSegment: replays.maxEventsPerSegment,
|
|
733
|
+
recordOptions: replays.recordOptions,
|
|
734
|
+
privacy: replays.privacy,
|
|
735
|
+
recorderFactory: replays.recorderFactory,
|
|
736
|
+
fetchImpl: replays.fetchImpl ?? options.fetchImpl
|
|
737
|
+
},
|
|
738
|
+
generation
|
|
739
|
+
);
|
|
740
|
+
if (replayId && shouldProceed() && replay.activeReplayId() === replayId) {
|
|
741
|
+
setActiveReplayId(replayId);
|
|
742
|
+
}
|
|
429
743
|
} catch (e) {
|
|
430
744
|
debugLog("replay auto-start failed", e);
|
|
431
745
|
}
|
|
@@ -434,6 +748,7 @@ function isEnabled() {
|
|
|
434
748
|
return state?.dsn !== null && state?.dsn !== void 0;
|
|
435
749
|
}
|
|
436
750
|
function resetForTest() {
|
|
751
|
+
resetCaptureGateForTest();
|
|
437
752
|
state = null;
|
|
438
753
|
pending.length = 0;
|
|
439
754
|
autoReplayStart = null;
|
|
@@ -513,7 +828,8 @@ function captureCustomEvent(name, props) {
|
|
|
513
828
|
return id;
|
|
514
829
|
}
|
|
515
830
|
function setActiveReplayId(replayId) {
|
|
516
|
-
if (state
|
|
831
|
+
if (!state || replayId && !isReplayCaptureAllowed()) return;
|
|
832
|
+
state.replayId = replayId && /^[a-zA-Z0-9_-]{1,64}$/.test(replayId) ? replayId : null;
|
|
517
833
|
}
|
|
518
834
|
function getActiveReplayId() {
|
|
519
835
|
return state?.replayId ?? null;
|
|
@@ -530,6 +846,7 @@ var init_client = __esm({
|
|
|
530
846
|
init_dsn();
|
|
531
847
|
init_envelope();
|
|
532
848
|
init_transport();
|
|
849
|
+
init_capture_gate();
|
|
533
850
|
SDK_NAME = "onus.javascript";
|
|
534
851
|
SDK_VERSION = "0.1.0";
|
|
535
852
|
autoReplayStart = null;
|
|
@@ -557,9 +874,11 @@ __export(src_exports, {
|
|
|
557
874
|
getActiveReplayId: () => getActiveReplayId,
|
|
558
875
|
init: () => init,
|
|
559
876
|
isEnabled: () => isEnabled,
|
|
877
|
+
isReplayCaptureAllowed: () => isReplayCaptureAllowed,
|
|
560
878
|
parseDsn: () => parseDsn,
|
|
561
879
|
resetForTest: () => resetForTest,
|
|
562
|
-
setActiveReplayId: () => setActiveReplayId
|
|
880
|
+
setActiveReplayId: () => setActiveReplayId,
|
|
881
|
+
setReplayCaptureAllowed: () => setReplayCaptureAllowed
|
|
563
882
|
});
|
|
564
883
|
module.exports = __toCommonJS(src_exports);
|
|
565
884
|
init_client();
|
|
@@ -582,7 +901,9 @@ init_envelope();
|
|
|
582
901
|
getActiveReplayId,
|
|
583
902
|
init,
|
|
584
903
|
isEnabled,
|
|
904
|
+
isReplayCaptureAllowed,
|
|
585
905
|
parseDsn,
|
|
586
906
|
resetForTest,
|
|
587
|
-
setActiveReplayId
|
|
907
|
+
setActiveReplayId,
|
|
908
|
+
setReplayCaptureAllowed
|
|
588
909
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { R as Recorder } from './replay-
|
|
2
|
-
export { E as EnvelopeItem, M as MAX_ENVELOPE_BYTES, P as ParsedDsn,
|
|
1
|
+
import { R as ReplayPrivacyOptions, a as Recorder } from './replay-BElhLiw8.js';
|
|
2
|
+
export { E as EnvelopeItem, M as MAX_ENVELOPE_BYTES, P as ParsedDsn, b as REPLAY_ID_RE, c as buildEnvelope, d as buildSentryAuthHeader, e as eventId, p as parseDsn } from './replay-BElhLiw8.js';
|
|
3
|
+
|
|
4
|
+
declare function isReplayCaptureAllowed(): boolean;
|
|
5
|
+
declare function setReplayCaptureAllowed(next: boolean): Promise<void>;
|
|
3
6
|
|
|
4
7
|
declare const SDK_NAME = "onus.javascript";
|
|
5
8
|
declare const SDK_VERSION = "0.1.0";
|
|
6
9
|
/**
|
|
7
10
|
* Session-replay capture config (ONUS-155). Passing this block opts the app
|
|
8
|
-
* into automatic replay recording
|
|
11
|
+
* into automatic replay recording after host authorization; within the block, capture
|
|
9
12
|
* defaults to **all sessions** (`sampleRate` 1) — sampling is an opt-down.
|
|
10
|
-
* rrweb itself stays behind the lazy `@onus
|
|
13
|
+
* rrweb itself stays behind the lazy `@brunyee-studio/onus-sdk/replay` subpath, so the
|
|
11
14
|
* recorder chunk only downloads once recording actually starts.
|
|
12
15
|
*/
|
|
13
16
|
interface ReplaysOptions {
|
|
@@ -16,7 +19,7 @@ interface ReplaysOptions {
|
|
|
16
19
|
/** Fraction of sessions recorded, 0..1 (default 1 — capture all users). */
|
|
17
20
|
sampleRate?: number;
|
|
18
21
|
/**
|
|
19
|
-
* @onus
|
|
22
|
+
* @brunyee-studio/onus-analytics session id (or getter) attached to each replay_event so
|
|
20
23
|
* the sessions surface can deep-link playback. A getter is evaluated per
|
|
21
24
|
* segment flush so rotated session ids stay fresh.
|
|
22
25
|
*/
|
|
@@ -29,8 +32,11 @@ interface ReplaysOptions {
|
|
|
29
32
|
segmentIntervalMs?: number;
|
|
30
33
|
/** Buffered events that force an immediate segment flush (default 100). */
|
|
31
34
|
maxEventsPerSegment?: number;
|
|
32
|
-
/** rrweb record
|
|
35
|
+
/** Non-privacy rrweb record tuning (sampling caps, etc.); privacy-controlled
|
|
36
|
+
* keys (masking, emit, packFn/plugins, canvas/iframe) are stripped here. */
|
|
33
37
|
recordOptions?: Record<string, unknown>;
|
|
38
|
+
/** Deliberate privacy opt-downs; forwarded to the recorder. */
|
|
39
|
+
privacy?: ReplayPrivacyOptions;
|
|
34
40
|
/** Test seam: recorder injection (defaults to lazy rrweb import). */
|
|
35
41
|
recorderFactory?: () => Promise<Recorder | null>;
|
|
36
42
|
/** Test seam: fetch implementation (defaults to the core `fetchImpl`). */
|
|
@@ -62,6 +68,7 @@ declare function init(options: OnusSdkOptions): void;
|
|
|
62
68
|
declare function isEnabled(): boolean;
|
|
63
69
|
/** Test seam: reset module state. */
|
|
64
70
|
declare function resetForTest(): void;
|
|
71
|
+
|
|
65
72
|
/** Capture a handled/unhandled exception. Returns the event id. */
|
|
66
73
|
declare function captureException(exception: unknown, hint?: OnusErrorEvent): string;
|
|
67
74
|
/** Capture a textual message (level defaults to info). */
|
|
@@ -71,7 +78,7 @@ declare function captureEvent(event: OnusErrorEvent): string;
|
|
|
71
78
|
/**
|
|
72
79
|
* Capture a custom analytics event. Routed through the error envelope as an
|
|
73
80
|
* `event` item with `tags.__onus_custom` marker so the same ingest path
|
|
74
|
-
* serves both surfaces (the @onus
|
|
81
|
+
* serves both surfaces (the @brunyee-studio/onus-analytics SDK remains the PostHog-style
|
|
75
82
|
* events pipeline; this is for lightweight app-level breadcrumbs).
|
|
76
83
|
*/
|
|
77
84
|
declare function captureCustomEvent(name: string, props?: Record<string, unknown>): string;
|
|
@@ -83,7 +90,7 @@ declare function getActiveReplayId(): string | null;
|
|
|
83
90
|
declare function flush(): Promise<boolean>;
|
|
84
91
|
|
|
85
92
|
/**
|
|
86
|
-
* Envelope transport for @onus
|
|
93
|
+
* Envelope transport for @brunyee-studio/onus-sdk (ONUS-68).
|
|
87
94
|
*
|
|
88
95
|
* POSTs a serialized envelope to the ingest route with `X-Sentry-Auth`.
|
|
89
96
|
* Honors the server contract: 429 + `retry-after` (rate limits, migration 34),
|
|
@@ -99,4 +106,4 @@ interface SendResult {
|
|
|
99
106
|
error?: string;
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
export { type OnusErrorEvent, type OnusSdkOptions, type ReplaysOptions, SDK_NAME, SDK_VERSION, type SendResult, captureCustomEvent, captureEvent, captureException, captureMessage, flush, getActiveReplayId, init, isEnabled, resetForTest, setActiveReplayId };
|
|
109
|
+
export { type OnusErrorEvent, type OnusSdkOptions, ReplayPrivacyOptions, type ReplaysOptions, SDK_NAME, SDK_VERSION, type SendResult, captureCustomEvent, captureEvent, captureException, captureMessage, flush, getActiveReplayId, init, isEnabled, isReplayCaptureAllowed, resetForTest, setActiveReplayId, setReplayCaptureAllowed };
|
package/dist/index.js
CHANGED
|
@@ -14,10 +14,12 @@ import {
|
|
|
14
14
|
getActiveReplayId,
|
|
15
15
|
init,
|
|
16
16
|
isEnabled,
|
|
17
|
+
isReplayCaptureAllowed,
|
|
17
18
|
parseDsn,
|
|
18
19
|
resetForTest,
|
|
19
|
-
setActiveReplayId
|
|
20
|
-
|
|
20
|
+
setActiveReplayId,
|
|
21
|
+
setReplayCaptureAllowed
|
|
22
|
+
} from "./chunk-YPQXIZ4X.js";
|
|
21
23
|
export {
|
|
22
24
|
MAX_ENVELOPE_BYTES,
|
|
23
25
|
REPLAY_ID_RE,
|
|
@@ -34,7 +36,9 @@ export {
|
|
|
34
36
|
getActiveReplayId,
|
|
35
37
|
init,
|
|
36
38
|
isEnabled,
|
|
39
|
+
isReplayCaptureAllowed,
|
|
37
40
|
parseDsn,
|
|
38
41
|
resetForTest,
|
|
39
|
-
setActiveReplayId
|
|
42
|
+
setActiveReplayId,
|
|
43
|
+
setReplayCaptureAllowed
|
|
40
44
|
};
|