@amplitude/session-replay-browser 1.47.0-sr-trc-debug-log.1 → 1.47.0-sr-trc-debug-log.2

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.
Files changed (43) hide show
  1. package/lib/cjs/diagnostics.d.ts +5 -0
  2. package/lib/cjs/diagnostics.d.ts.map +1 -1
  3. package/lib/cjs/diagnostics.js +11 -1
  4. package/lib/cjs/diagnostics.js.map +1 -1
  5. package/lib/cjs/plugins/url-tracking-plugin.d.ts +6 -0
  6. package/lib/cjs/plugins/url-tracking-plugin.d.ts.map +1 -1
  7. package/lib/cjs/plugins/url-tracking-plugin.js +3 -1
  8. package/lib/cjs/plugins/url-tracking-plugin.js.map +1 -1
  9. package/lib/cjs/session-replay.d.ts +7 -0
  10. package/lib/cjs/session-replay.d.ts.map +1 -1
  11. package/lib/cjs/session-replay.js +117 -24
  12. package/lib/cjs/session-replay.js.map +1 -1
  13. package/lib/cjs/targeting/targeting-manager.d.ts.map +1 -1
  14. package/lib/cjs/targeting/targeting-manager.js +26 -17
  15. package/lib/cjs/targeting/targeting-manager.js.map +1 -1
  16. package/lib/cjs/version.d.ts +1 -1
  17. package/lib/cjs/version.js +1 -1
  18. package/lib/cjs/version.js.map +1 -1
  19. package/lib/esm/diagnostics.d.ts +5 -0
  20. package/lib/esm/diagnostics.d.ts.map +1 -1
  21. package/lib/esm/diagnostics.js +11 -1
  22. package/lib/esm/diagnostics.js.map +1 -1
  23. package/lib/esm/plugins/url-tracking-plugin.d.ts +6 -0
  24. package/lib/esm/plugins/url-tracking-plugin.d.ts.map +1 -1
  25. package/lib/esm/plugins/url-tracking-plugin.js +3 -1
  26. package/lib/esm/plugins/url-tracking-plugin.js.map +1 -1
  27. package/lib/esm/session-replay.d.ts +7 -0
  28. package/lib/esm/session-replay.d.ts.map +1 -1
  29. package/lib/esm/session-replay.js +117 -24
  30. package/lib/esm/session-replay.js.map +1 -1
  31. package/lib/esm/targeting/targeting-manager.d.ts.map +1 -1
  32. package/lib/esm/targeting/targeting-manager.js +26 -17
  33. package/lib/esm/targeting/targeting-manager.js.map +1 -1
  34. package/lib/esm/version.d.ts +1 -1
  35. package/lib/esm/version.js +1 -1
  36. package/lib/esm/version.js.map +1 -1
  37. package/lib/scripts/index-min.js +1 -1
  38. package/lib/scripts/index-min.js.gz +0 -0
  39. package/lib/scripts/index-min.js.map +1 -1
  40. package/lib/scripts/session-replay-browser-min.js +1 -1
  41. package/lib/scripts/session-replay-browser-min.js.gz +0 -0
  42. package/lib/scripts/session-replay-browser-min.js.map +1 -1
  43. package/package.json +3 -3
@@ -169,6 +169,17 @@ var SessionReplay = /** @class */ (function () {
169
169
  }
170
170
  pageUrl = (_f = (_c = (_b = targetingParams.page) === null || _b === void 0 ? void 0 : _b.url) !== null && _c !== void 0 ? _c : (_e = (_d = getGlobalScope()) === null || _d === void 0 ? void 0 : _d.location) === null || _e === void 0 ? void 0 : _e.href) !== null && _f !== void 0 ? _f : '';
171
171
  pageForTargeting = (_g = targetingParams.page) !== null && _g !== void 0 ? _g : (pageUrl !== '' ? { url: pageUrl } : undefined);
172
+ // Record the targeting trigger event
173
+ this.recordDiagnosticEvent(SrDiagnostic.targetingTrigger, {
174
+ sessionId: this.identifiers.sessionId,
175
+ deviceId: this.getDeviceId(),
176
+ targetingConfig: this.config.targetingConfig,
177
+ targetingParams: {
178
+ userProperties: targetingParams.userProperties,
179
+ event: eventForTargeting,
180
+ page: pageForTargeting,
181
+ },
182
+ });
172
183
  evalStart = Date.now();
173
184
  return [4 /*yield*/, evaluateTargetingAndStore({
174
185
  sessionId: this.identifiers.sessionId,
@@ -326,12 +337,17 @@ var SessionReplay = /** @class */ (function () {
326
337
  */
327
338
  SessionReplay.prototype.setupUrlChangeListener = function () {
328
339
  var _this = this;
329
- var _a, _b;
340
+ var _a, _b, _c, _d, _e, _f;
330
341
  // If init() runs multiple times, remove the previous URL-change subscription first
331
342
  // so we don't leak callbacks and trigger duplicate targeting evaluations.
332
343
  (_a = this.urlChangeCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
333
344
  var globalScope = getGlobalScope();
334
345
  if (!(globalScope === null || globalScope === void 0 ? void 0 : globalScope.location)) {
346
+ // No window/location (SSR, worker, or pre-render) — the listener can't attach, so TRC will
347
+ // never re-evaluate on navigation. Surface it rather than failing silently.
348
+ this.incrementDiagnostic(SrDiagnostic.urlListenerSkipped);
349
+ this.recordDiagnosticEvent(SrDiagnostic.urlListenerSkipped, { reason: 'no_global_scope' });
350
+ this.loggerProvider.debug('URL-change listener not attached: no global scope/location.');
335
351
  return;
336
352
  }
337
353
  var hasTargeting = !!((_b = this.config) === null || _b === void 0 ? void 0 : _b.targetingConfig);
@@ -356,7 +372,27 @@ var SessionReplay = /** @class */ (function () {
356
372
  _this.loggerProvider.debug("Queued URL-change targeting re-evaluation #".concat(evaluationId, " for ").concat(href, "."));
357
373
  }
358
374
  };
359
- var unsubscribe = subscribeToUrlChanges(globalScope, onUrlChange);
375
+ // Pass the polling options so targeting re-evaluation also respects `enableUrlChangePolling`.
376
+ // Without this, the targeting listener only sees history.pushState/replaceState + popstate +
377
+ // hashchange — so SPA navigations that bypass the history API never re-evaluate TRC and
378
+ // recording never starts on the new URL (enableUrlChangePolling previously only affected the
379
+ // rrweb URL-tracking plugin, which runs only once recording is already active).
380
+ var enablePolling = (_d = (_c = this.config) === null || _c === void 0 ? void 0 : _c.enableUrlChangePolling) !== null && _d !== void 0 ? _d : false;
381
+ var unsubscribe = subscribeToUrlChanges(globalScope, onUrlChange, {
382
+ enablePolling: enablePolling,
383
+ pollingInterval: (_e = this.config) === null || _e === void 0 ? void 0 : _e.urlChangePollingInterval,
384
+ // Mirror each poll tick to the console (Debug level) so we can confirm polling is firing.
385
+ log: this.loggerProvider.debug.bind(this.loggerProvider),
386
+ });
387
+ // Confirm the listener is actually live, and under what settings — if recording never starts on
388
+ // navigation, this tells us whether the listener existed and whether polling (the fallback for
389
+ // SPAs that bypass the History API) was on.
390
+ this.recordDiagnosticEvent(SrDiagnostic.urlListenerAttached, {
391
+ hasTargeting: hasTargeting,
392
+ enablePolling: enablePolling,
393
+ pollingInterval: (_f = this.config) === null || _f === void 0 ? void 0 : _f.urlChangePollingInterval,
394
+ });
395
+ this.loggerProvider.debug("URL-change listener attached (polling: ".concat(String(enablePolling), ")."));
360
396
  this.urlChangeCleanup = function () {
361
397
  unsubscribe();
362
398
  _this.urlChangeCleanup = null;
@@ -385,14 +421,34 @@ var SessionReplay = /** @class */ (function () {
385
421
  var currentUrl = (_b = (_a = getGlobalScope()) === null || _a === void 0 ? void 0 : _a.location) === null || _b === void 0 ? void 0 : _b.href;
386
422
  return currentUrl != null ? { url: currentUrl } : undefined;
387
423
  };
424
+ /**
425
+ * Best-effort navigation type from the Navigation Timing API: 'reload' | 'navigate' |
426
+ * 'back_forward' | 'prerender'. Surfaced in the init diagnostic so a page refresh ('reload')
427
+ * is distinguishable from a fresh load ('navigate') — neither changes the session id, so this
428
+ * is the only way to tell them apart. Returns undefined when the API is unavailable.
429
+ */
430
+ SessionReplay.prototype.getNavigationType = function () {
431
+ try {
432
+ var globalScope = getGlobalScope();
433
+ var performance_1 = globalScope && globalScope.performance;
434
+ if (!performance_1 || typeof performance_1.getEntriesByType !== 'function') {
435
+ return undefined;
436
+ }
437
+ var navEntries = performance_1.getEntriesByType('navigation');
438
+ return navEntries.length > 0 ? navEntries[0].type : undefined;
439
+ }
440
+ catch (_a) {
441
+ return undefined;
442
+ }
443
+ };
388
444
  SessionReplay.prototype._init = function (apiKey, options) {
389
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
445
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
390
446
  return __awaiter(this, void 0, void 0, function () {
391
- var now, _r, _s, joinedConfig, localConfig, remoteConfig, scrollWatcher, managers, storeType, compressionWorkerScript, trackDestinationWorkerScript, globalScope, _t, compressionScript, trackDestinationScript, rrwebEventManager, error_1, typedError, payloadBatcher, interactionEventManager, error_2, typedError, onFullSnapshotProcessed, pending_2, pending_1, pending_1_1, _u, event_1, sessionId, messenger, needsUrlTracking;
392
- var e_2, _v;
447
+ var now, _u, _v, joinedConfig, localConfig, remoteConfig, scrollWatcher, managers, storeType, compressionWorkerScript, trackDestinationWorkerScript, globalScope, _w, compressionScript, trackDestinationScript, rrwebEventManager, error_1, typedError, payloadBatcher, interactionEventManager, error_2, typedError, onFullSnapshotProcessed, pending_2, pending_1, pending_1_1, _x, event_1, sessionId, messenger, needsUrlTracking;
448
+ var e_2, _y;
393
449
  var _this = this;
394
- return __generator(this, function (_w) {
395
- switch (_w.label) {
450
+ return __generator(this, function (_z) {
451
+ switch (_z.label) {
396
452
  case 0:
397
453
  // Re-init should always tear down any previous URL-change subscription, even when the
398
454
  // next config has no targeting config and we don't subscribe again.
@@ -408,13 +464,13 @@ var SessionReplay = /** @class */ (function () {
408
464
  options.sessionId !== undefined
409
465
  ? (_b = getOrInitReplayStartTime(apiKey, options.sessionId, now, this.loggerProvider)) !== null && _b !== void 0 ? _b : now
410
466
  : now;
411
- _r = this;
467
+ _u = this;
412
468
  return [4 /*yield*/, createSessionReplayJoinedConfigGenerator(apiKey, options)];
413
469
  case 1:
414
- _r.joinedConfigGenerator = _w.sent();
470
+ _u.joinedConfigGenerator = _z.sent();
415
471
  return [4 /*yield*/, this.joinedConfigGenerator.generateJoinedConfig()];
416
472
  case 2:
417
- _s = _w.sent(), joinedConfig = _s.joinedConfig, localConfig = _s.localConfig, remoteConfig = _s.remoteConfig;
473
+ _v = _z.sent(), joinedConfig = _v.joinedConfig, localConfig = _v.localConfig, remoteConfig = _v.remoteConfig;
418
474
  this.config = joinedConfig;
419
475
  this.setMetadata(options.sessionId, joinedConfig, localConfig, remoteConfig, (_c = options.version) === null || _c === void 0 ? void 0 : _c.version, VERSION, (_d = options.version) === null || _d === void 0 ? void 0 : _d.type);
420
476
  this.pageLeaveFns = [];
@@ -438,12 +494,12 @@ var SessionReplay = /** @class */ (function () {
438
494
  if (!(this.config.useWebWorker && globalScope && globalScope.Worker)) return [3 /*break*/, 4];
439
495
  return [4 /*yield*/, import('./worker')];
440
496
  case 3:
441
- _t = _w.sent(), compressionScript = _t.compressionScript, trackDestinationScript = _t.trackDestinationScript;
497
+ _w = _z.sent(), compressionScript = _w.compressionScript, trackDestinationScript = _w.trackDestinationScript;
442
498
  compressionWorkerScript = compressionScript;
443
499
  trackDestinationWorkerScript = trackDestinationScript;
444
- _w.label = 4;
500
+ _z.label = 4;
445
501
  case 4:
446
- _w.trys.push([4, 6, , 7]);
502
+ _z.trys.push([4, 6, , 7]);
447
503
  return [4 /*yield*/, createEventsManager({
448
504
  config: this.config,
449
505
  type: 'replay',
@@ -455,21 +511,21 @@ var SessionReplay = /** @class */ (function () {
455
511
  shouldSend: function () { return !_this.isBelowMinSessionDuration(); },
456
512
  })];
457
513
  case 5:
458
- rrwebEventManager = _w.sent();
514
+ rrwebEventManager = _z.sent();
459
515
  this.rrwebEventManager = rrwebEventManager;
460
516
  managers.push({ name: 'replay', manager: rrwebEventManager });
461
517
  return [3 /*break*/, 7];
462
518
  case 6:
463
- error_1 = _w.sent();
519
+ error_1 = _z.sent();
464
520
  typedError = error_1;
465
521
  this.loggerProvider.warn("Error occurred while creating replay events manager: ".concat(typedError.toString()));
466
522
  return [3 /*break*/, 7];
467
523
  case 7:
468
524
  if (!((_j = this.config.interactionConfig) === null || _j === void 0 ? void 0 : _j.enabled)) return [3 /*break*/, 11];
469
525
  payloadBatcher = this.config.interactionConfig.batch ? clickBatcher : clickNonBatcher;
470
- _w.label = 8;
526
+ _z.label = 8;
471
527
  case 8:
472
- _w.trys.push([8, 10, , 11]);
528
+ _z.trys.push([8, 10, , 11]);
473
529
  return [4 /*yield*/, createEventsManager({
474
530
  config: this.config,
475
531
  type: 'interaction',
@@ -481,11 +537,11 @@ var SessionReplay = /** @class */ (function () {
481
537
  trackDestinationWorkerScript: trackDestinationWorkerScript,
482
538
  })];
483
539
  case 9:
484
- interactionEventManager = _w.sent();
540
+ interactionEventManager = _z.sent();
485
541
  managers.push({ name: 'interaction', manager: interactionEventManager });
486
542
  return [3 /*break*/, 11];
487
543
  case 10:
488
- error_2 = _w.sent();
544
+ error_2 = _z.sent();
489
545
  typedError = error_2;
490
546
  this.loggerProvider.warn("Error occurred while creating interaction events manager: ".concat(typedError.toString()));
491
547
  return [3 /*break*/, 11];
@@ -503,14 +559,14 @@ var SessionReplay = /** @class */ (function () {
503
559
  pending_2 = this.pendingEmitEvents.splice(0);
504
560
  try {
505
561
  for (pending_1 = __values(pending_2), pending_1_1 = pending_1.next(); !pending_1_1.done; pending_1_1 = pending_1.next()) {
506
- _u = pending_1_1.value, event_1 = _u.event, sessionId = _u.sessionId;
562
+ _x = pending_1_1.value, event_1 = _x.event, sessionId = _x.sessionId;
507
563
  this.eventCompressor.enqueueEvent(event_1, sessionId);
508
564
  }
509
565
  }
510
566
  catch (e_2_1) { e_2 = { error: e_2_1 }; }
511
567
  finally {
512
568
  try {
513
- if (pending_1_1 && !pending_1_1.done && (_v = pending_1.return)) _v.call(pending_1);
569
+ if (pending_1_1 && !pending_1_1.done && (_y = pending_1.return)) _y.call(pending_1);
514
570
  }
515
571
  finally { if (e_2) throw e_2.error; }
516
572
  }
@@ -547,7 +603,7 @@ var SessionReplay = /** @class */ (function () {
547
603
  ], false);
548
604
  return [4 /*yield*/, this.initializeNetworkObservers()];
549
605
  case 12:
550
- _w.sent();
606
+ _z.sent();
551
607
  // Enable background capture when this page is opened by the Amplitude app
552
608
  // (window.opener exists). Uses the shared messenger singleton so that if
553
609
  // autocapture is also loaded, both share a single messenger and script load.
@@ -569,11 +625,27 @@ var SessionReplay = /** @class */ (function () {
569
625
  sampleRate: this.config.sampleRate,
570
626
  optOut: this.shouldOptOut(),
571
627
  currentUrl: (_m = this.getCurrentPageForTargeting()) === null || _m === void 0 ? void 0 : _m.url,
628
+ // 'reload' tells a page refresh apart from a fresh load ('navigate') or back/forward
629
+ // ('back_forward'). New tabs and refreshes keep the same session id, so this is the only
630
+ // way to distinguish a refresh from a brand-new init within a session.
631
+ navigationType: this.getNavigationType(),
572
632
  });
573
633
  return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options.userProperties, page: this.getCurrentPageForTargeting() }, true)];
574
634
  case 13:
575
- _w.sent();
635
+ _z.sent();
576
636
  needsUrlTracking = this.config.targetingConfig || ((_q = (_p = (_o = this.config.privacyConfig) === null || _o === void 0 ? void 0 : _o.urlMaskLevels) === null || _p === void 0 ? void 0 : _p.length) !== null && _q !== void 0 ? _q : 0) > 0;
637
+ // Record whether we even attempt to wire up the URL-change listener, and the inputs behind that
638
+ // decision. If `needsUrlTracking` is false the listener is never attached, so SPA navigations are
639
+ // invisible to TRC — this is the first thing to check when "TRC is on but never re-evaluates".
640
+ this.recordDiagnosticEvent(SrDiagnostic.urlListenerSetup, {
641
+ needsUrlTracking: !!needsUrlTracking,
642
+ hasTargetingConfig: !!this.config.targetingConfig,
643
+ urlMaskLevels: (_t = (_s = (_r = this.config.privacyConfig) === null || _r === void 0 ? void 0 : _r.urlMaskLevels) === null || _s === void 0 ? void 0 : _s.length) !== null && _t !== void 0 ? _t : 0,
644
+ // config always defaults this to a boolean (see local-config), so no `?? false` needed here.
645
+ enableUrlChangePolling: this.config.enableUrlChangePolling,
646
+ urlChangePollingInterval: this.config.urlChangePollingInterval,
647
+ });
648
+ this.loggerProvider.debug("URL-change listener needed: ".concat(String(!!needsUrlTracking), "."));
577
649
  if (needsUrlTracking) {
578
650
  this.setupUrlChangeListener();
579
651
  }
@@ -613,6 +685,18 @@ var SessionReplay = /** @class */ (function () {
613
685
  this.sendEvents(previousSessionId);
614
686
  }
615
687
  isSessionChange = previousSessionId !== sessionId;
688
+ // A real session transition (not the first set): recording stops/restarts and targeting is
689
+ // re-evaluated here, so session churn (timeout, multi-tab custom ids, explicit setSessionId)
690
+ // is a prime suspect for "sometimes records, sometimes not". recordDiagnosticEvent ships to
691
+ // DataDog AND mirrors to loggerProvider.debug. New tabs / refreshes keep the same id and won't
692
+ // hit this — use the init diagnostic's navigationType for those.
693
+ if (isSessionChange && previousSessionId !== undefined) {
694
+ this.recordDiagnosticEvent(SrDiagnostic.sessionChanged, {
695
+ from: previousSessionId,
696
+ to: sessionId,
697
+ deviceIdChanged: deviceId !== undefined && deviceId !== currentDeviceId,
698
+ });
699
+ }
616
700
  // Drop any beacon-buffered events from the previous session BEFORE installing the
617
701
  // new identifiers / start time. Otherwise the page-leave beacon path could attribute
618
702
  // old-session events to the new session id, and the gate (using the new start time)
@@ -777,6 +861,9 @@ var SessionReplay = /** @class */ (function () {
777
861
  SessionReplay.prototype.incrementDiagnostic = function (counter) {
778
862
  var _a, _b;
779
863
  try {
864
+ // Mirror to the console (Debug level) so the full set of diagnostics is visible in the
865
+ // browser, not only in DataDog — helps debugging when a customer can't share a repro env.
866
+ this.loggerProvider.debug("[SR diagnostics] ".concat(counter));
780
867
  (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.diagnosticsClient) === null || _b === void 0 ? void 0 : _b.increment(counter);
781
868
  }
782
869
  catch (_c) {
@@ -790,6 +877,8 @@ var SessionReplay = /** @class */ (function () {
790
877
  SessionReplay.prototype.recordDiagnosticHistogram = function (name, value) {
791
878
  var _a, _b;
792
879
  try {
880
+ // Mirror to the console (Debug level) — see incrementDiagnostic.
881
+ this.loggerProvider.debug("[SR diagnostics] ".concat(name, "=").concat(value));
793
882
  // The only caller runs inside evaluateTargetingAndCapture's `this.config` guard, so the
794
883
  // `config == null` arm of this optional chain is unreachable here (kept as defensive parity
795
884
  // with recordDiagnosticEvent). The diagnosticsClient-absent arm IS exercised by no-client tests.
@@ -814,7 +903,11 @@ var SessionReplay = /** @class */ (function () {
814
903
  // Always stamp sessionId, deviceId and srId so every diagnostics event (→ DataDog Logs) can
815
904
  // be correlated to a single session/device — and to the actual replay, since the Session
816
905
  // Replay ID is `${deviceId}/${sessionId}`. Caller props override if they pass their own.
817
- (_c = (_b = this.config) === null || _b === void 0 ? void 0 : _b.diagnosticsClient) === null || _c === void 0 ? void 0 : _c.recordEvent(name, __assign({ sessionId: sessionId, deviceId: deviceId, srId: deviceId != null && sessionId != null ? "".concat(deviceId, "/").concat(sessionId) : undefined }, properties));
906
+ var enriched = __assign({ sessionId: sessionId, deviceId: deviceId, srId: deviceId != null && sessionId != null ? "".concat(deviceId, "/").concat(sessionId) : undefined }, properties);
907
+ // Mirror to the console (Debug level) so the full event + props are visible in the browser,
908
+ // not only in DataDog — see incrementDiagnostic.
909
+ this.loggerProvider.debug("[SR diagnostics] ".concat(name, " ").concat(JSON.stringify(enriched)));
910
+ (_c = (_b = this.config) === null || _b === void 0 ? void 0 : _b.diagnosticsClient) === null || _c === void 0 ? void 0 : _c.recordEvent(name, enriched);
818
911
  // Flush immediately so the event ships now rather than on the client's ~5-min timer (and so
819
912
  // short sessions that never reach the timer aren't lost). NOTE: this sends one capture POST
820
913
  // per event — higher request volume; revisit/gate before production.