@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/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
- const gz = await gzipText(opts.body);
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
- async function startSessionReplay(options = {}) {
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 = { replayId, stop: null, buffer: [], timer: null };
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
- active = null;
439
+ clearFailedClaim(state2);
208
440
  return null;
209
441
  }
210
442
  if (!recorder) {
211
- active = null;
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
- active = null;
455
+ clearFailedClaim(state2);
224
456
  return null;
225
457
  }
226
458
  record = fn;
227
459
  } catch {
228
- active = null;
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
- await emitSegment(
244
- narrowedDsn,
245
- options,
246
- state2,
247
- events,
248
- ++segmentCounter,
249
- (/* @__PURE__ */ new Date()).toISOString()
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.buffer.push(event);
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
- active?.stop?.();
299
- active = null;
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
- if (options.replays && options.replays.enabled !== false && dsn) {
409
- autoReplayStart = startAutoReplay(options.replays, options);
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
- const replayId = await replay.startSessionReplay({
418
- dsn: options.dsn,
419
- environment: replays.environment ?? options.environment,
420
- release: replays.release ?? options.release,
421
- sessionId: replays.sessionId,
422
- segmentIntervalMs: replays.segmentIntervalMs,
423
- maxEventsPerSegment: replays.maxEventsPerSegment,
424
- recordOptions: replays.recordOptions,
425
- recorderFactory: replays.recorderFactory,
426
- fetchImpl: replays.fetchImpl ?? options.fetchImpl
427
- });
428
- if (replayId) setActiveReplayId(replayId);
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) state.replayId = replayId && /^[a-zA-Z0-9_-]{1,64}$/.test(replayId) ? replayId : null;
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-D7ejwI0s.js';
2
- export { E as EnvelopeItem, M as MAX_ENVELOPE_BYTES, P as ParsedDsn, a as REPLAY_ID_RE, b as buildEnvelope, c as buildSentryAuthHeader, e as eventId, p as parseDsn } from './replay-D7ejwI0s.js';
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 from `init()`; within the block, capture
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/sdk/replay` subpath, so the
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/analytics session id (or getter) attached to each replay_event so
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 options passthrough (privacy masking, etc.). */
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/analytics SDK remains the PostHog-style
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/sdk (ONUS-68).
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
- } from "./chunk-SLWBPQ6C.js";
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
  };