@amplitude/session-replay-browser 1.47.0-sr-trc-debug-log.4 → 1.47.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.
Files changed (100) hide show
  1. package/README.md +203 -0
  2. package/lib/cjs/beacon-transport.d.ts +2 -0
  3. package/lib/cjs/beacon-transport.d.ts.map +1 -1
  4. package/lib/cjs/beacon-transport.js +39 -1
  5. package/lib/cjs/beacon-transport.js.map +1 -1
  6. package/lib/cjs/config/joined-config.d.ts +1 -6
  7. package/lib/cjs/config/joined-config.d.ts.map +1 -1
  8. package/lib/cjs/config/joined-config.js +23 -64
  9. package/lib/cjs/config/joined-config.js.map +1 -1
  10. package/lib/cjs/config/local-config.d.ts +4 -6
  11. package/lib/cjs/config/local-config.d.ts.map +1 -1
  12. package/lib/cjs/config/local-config.js +3 -19
  13. package/lib/cjs/config/local-config.js.map +1 -1
  14. package/lib/cjs/config/types.d.ts +74 -19
  15. package/lib/cjs/config/types.d.ts.map +1 -1
  16. package/lib/cjs/config/types.js.map +1 -1
  17. package/lib/cjs/index.d.ts +1 -0
  18. package/lib/cjs/index.d.ts.map +1 -1
  19. package/lib/cjs/index.js.map +1 -1
  20. package/lib/cjs/plugins/url-tracking-plugin.d.ts +0 -13
  21. package/lib/cjs/plugins/url-tracking-plugin.d.ts.map +1 -1
  22. package/lib/cjs/plugins/url-tracking-plugin.js +2 -6
  23. package/lib/cjs/plugins/url-tracking-plugin.js.map +1 -1
  24. package/lib/cjs/session-replay.d.ts +0 -33
  25. package/lib/cjs/session-replay.d.ts.map +1 -1
  26. package/lib/cjs/session-replay.js +40 -339
  27. package/lib/cjs/session-replay.js.map +1 -1
  28. package/lib/cjs/targeting/targeting-manager.d.ts +1 -4
  29. package/lib/cjs/targeting/targeting-manager.d.ts.map +1 -1
  30. package/lib/cjs/targeting/targeting-manager.js +10 -56
  31. package/lib/cjs/targeting/targeting-manager.js.map +1 -1
  32. package/lib/cjs/track-destination.d.ts +5 -1
  33. package/lib/cjs/track-destination.d.ts.map +1 -1
  34. package/lib/cjs/track-destination.js +212 -55
  35. package/lib/cjs/track-destination.js.map +1 -1
  36. package/lib/cjs/version.d.ts +1 -1
  37. package/lib/cjs/version.d.ts.map +1 -1
  38. package/lib/cjs/version.js +1 -1
  39. package/lib/cjs/version.js.map +1 -1
  40. package/lib/cjs/worker/index.js +1 -1
  41. package/lib/esm/beacon-transport.d.ts +2 -0
  42. package/lib/esm/beacon-transport.d.ts.map +1 -1
  43. package/lib/esm/beacon-transport.js +39 -1
  44. package/lib/esm/beacon-transport.js.map +1 -1
  45. package/lib/esm/config/joined-config.d.ts +1 -6
  46. package/lib/esm/config/joined-config.d.ts.map +1 -1
  47. package/lib/esm/config/joined-config.js +23 -64
  48. package/lib/esm/config/joined-config.js.map +1 -1
  49. package/lib/esm/config/local-config.d.ts +4 -6
  50. package/lib/esm/config/local-config.d.ts.map +1 -1
  51. package/lib/esm/config/local-config.js +4 -20
  52. package/lib/esm/config/local-config.js.map +1 -1
  53. package/lib/esm/config/types.d.ts +74 -19
  54. package/lib/esm/config/types.d.ts.map +1 -1
  55. package/lib/esm/config/types.js.map +1 -1
  56. package/lib/esm/index.d.ts +1 -0
  57. package/lib/esm/index.d.ts.map +1 -1
  58. package/lib/esm/index.js.map +1 -1
  59. package/lib/esm/plugins/url-tracking-plugin.d.ts +0 -13
  60. package/lib/esm/plugins/url-tracking-plugin.d.ts.map +1 -1
  61. package/lib/esm/plugins/url-tracking-plugin.js +2 -6
  62. package/lib/esm/plugins/url-tracking-plugin.js.map +1 -1
  63. package/lib/esm/session-replay.d.ts +0 -33
  64. package/lib/esm/session-replay.d.ts.map +1 -1
  65. package/lib/esm/session-replay.js +40 -339
  66. package/lib/esm/session-replay.js.map +1 -1
  67. package/lib/esm/targeting/targeting-manager.d.ts +1 -4
  68. package/lib/esm/targeting/targeting-manager.d.ts.map +1 -1
  69. package/lib/esm/targeting/targeting-manager.js +10 -56
  70. package/lib/esm/targeting/targeting-manager.js.map +1 -1
  71. package/lib/esm/track-destination.d.ts +5 -1
  72. package/lib/esm/track-destination.d.ts.map +1 -1
  73. package/lib/esm/track-destination.js +212 -55
  74. package/lib/esm/track-destination.js.map +1 -1
  75. package/lib/esm/version.d.ts +1 -1
  76. package/lib/esm/version.d.ts.map +1 -1
  77. package/lib/esm/version.js +1 -1
  78. package/lib/esm/version.js.map +1 -1
  79. package/lib/esm/worker/index.js +1 -1
  80. package/lib/scripts/index-min.js +1 -1
  81. package/lib/scripts/index-min.js.gz +0 -0
  82. package/lib/scripts/index-min.js.map +1 -1
  83. package/lib/scripts/observers-min.js +1 -1
  84. package/lib/scripts/observers-min.js.gz +0 -0
  85. package/lib/scripts/session-replay-browser-min.js +1 -1
  86. package/lib/scripts/session-replay-browser-min.js.gz +0 -0
  87. package/lib/scripts/session-replay-browser-min.js.map +1 -1
  88. package/lib/scripts/targeting-min.js +1 -1
  89. package/lib/scripts/targeting-min.js.gz +0 -0
  90. package/lib/scripts/worker-min.js +1 -1
  91. package/lib/scripts/worker-min.js.gz +0 -0
  92. package/package.json +4 -4
  93. package/lib/cjs/diagnostics.d.ts +0 -50
  94. package/lib/cjs/diagnostics.d.ts.map +0 -1
  95. package/lib/cjs/diagnostics.js +0 -67
  96. package/lib/cjs/diagnostics.js.map +0 -1
  97. package/lib/esm/diagnostics.d.ts +0 -50
  98. package/lib/esm/diagnostics.d.ts.map +0 -1
  99. package/lib/esm/diagnostics.js +0 -64
  100. package/lib/esm/diagnostics.js.map +0 -1
@@ -1,74 +1,44 @@
1
1
  import { __assign, __awaiter, __generator } from "tslib";
2
2
  import { targetingIDBStore } from './targeting-idb-store';
3
- import { SrDiagnostic } from '../diagnostics';
4
3
  export var evaluateTargetingAndStore = function (_a) {
5
- var sessionId = _a.sessionId, targetingConfig = _a.targetingConfig, loggerProvider = _a.loggerProvider, apiKey = _a.apiKey, targetingParams = _a.targetingParams, _b = _a.urlChange, urlChange = _b === void 0 ? false : _b, diagnosticsClient = _a.diagnosticsClient, deviceId = _a.deviceId;
4
+ var sessionId = _a.sessionId, targetingConfig = _a.targetingConfig, loggerProvider = _a.loggerProvider, apiKey = _a.apiKey, targetingParams = _a.targetingParams, _b = _a.urlChange, urlChange = _b === void 0 ? false : _b;
6
5
  return __awaiter(void 0, void 0, void 0, function () {
7
6
  var idbTargetingMatch, sessionTargetingMatch, evaluateTargetingPackage, params, targetingResult, err_1, knownError;
8
- var _c, _d, _e, _f, _g, _h, _j;
9
- return __generator(this, function (_k) {
10
- switch (_k.label) {
7
+ return __generator(this, function (_c) {
8
+ switch (_c.label) {
11
9
  case 0: return [4 /*yield*/, targetingIDBStore.clearStoreOfOldSessions({
12
10
  loggerProvider: loggerProvider,
13
11
  apiKey: apiKey,
14
12
  currentSessionId: sessionId,
15
13
  })];
16
14
  case 1:
17
- _k.sent();
15
+ _c.sent();
18
16
  return [4 /*yield*/, targetingIDBStore.getTargetingMatchForSession({
19
17
  loggerProvider: loggerProvider,
20
18
  apiKey: apiKey,
21
19
  sessionId: sessionId,
22
20
  })];
23
21
  case 2:
24
- idbTargetingMatch = _k.sent();
22
+ idbTargetingMatch = _c.sent();
25
23
  // Skip IDB cache when re-evaluating with a new page (e.g. URL change); otherwise we'd never
26
24
  // re-evaluate and would keep returning true after navigating to a non-matching page.
27
25
  if (idbTargetingMatch === true && !urlChange) {
28
26
  return [2 /*return*/, true];
29
27
  }
30
28
  sessionTargetingMatch = true;
31
- _k.label = 3;
29
+ _c.label = 3;
32
30
  case 3:
33
- _k.trys.push([3, 6, , 7]);
31
+ _c.trys.push([3, 6, , 7]);
34
32
  return [4 /*yield*/, import('@amplitude/targeting')];
35
33
  case 4:
36
- evaluateTargetingPackage = (_k.sent()).evaluateTargeting;
34
+ evaluateTargetingPackage = (_c.sent()).evaluateTargeting;
37
35
  params = __assign(__assign({}, targetingParams), { flag: targetingConfig, sessionId: typeof sessionId === 'string' ? parseInt(sessionId, 10) : sessionId, apiKey: apiKey, loggerProvider: loggerProvider });
38
- diagnosticsClient === null || diagnosticsClient === void 0 ? void 0 : diagnosticsClient.recordEvent(SrDiagnostic.targetingTrigger, {
39
- sessionId: sessionId,
40
- deviceId: deviceId,
41
- srId: deviceId != null && sessionId != null ? "".concat(deviceId, "/").concat(sessionId) : undefined,
42
- pageUrl: (_c = targetingParams === null || targetingParams === void 0 ? void 0 : targetingParams.page) === null || _c === void 0 ? void 0 : _c.url,
43
- matched: sessionTargetingMatch,
44
- targetingConfig: targetingConfig,
45
- });
46
36
  return [4 /*yield*/, evaluateTargetingPackage(params)];
47
37
  case 5:
48
- targetingResult = _k.sent();
38
+ targetingResult = _c.sent();
49
39
  if (targetingResult && targetingResult.sr_targeting_config) {
50
40
  sessionTargetingMatch = targetingResult.sr_targeting_config.key === 'on';
51
41
  }
52
- // gap #2: the raw engine verdict. variantKey 'on' => matched; 'off'/undefined => not.
53
- // Pair with the eval event's pageUrl to tell "URL condition didn't match" from "URL matched
54
- // but the segment bucket (capture %) didn't allocate this session".
55
- try {
56
- diagnosticsClient === null || diagnosticsClient === void 0 ? void 0 : diagnosticsClient.recordEvent(SrDiagnostic.evalResult, {
57
- sessionId: sessionId,
58
- deviceId: deviceId,
59
- srId: deviceId != null && sessionId != null ? "".concat(deviceId, "/").concat(sessionId) : undefined,
60
- pageUrl: (_d = targetingParams === null || targetingParams === void 0 ? void 0 : targetingParams.page) === null || _d === void 0 ? void 0 : _d.url,
61
- variantKey: (_f = (_e = targetingResult === null || targetingResult === void 0 ? void 0 : targetingResult.sr_targeting_config) === null || _e === void 0 ? void 0 : _e.key) !== null && _f !== void 0 ? _f : null,
62
- matched: sessionTargetingMatch,
63
- targetingConfig: targetingConfig,
64
- });
65
- // Flush now so the verdict ships immediately (vs the client's ~5-min timer). One capture
66
- // POST per event — higher volume; revisit/gate before production.
67
- void ((_g = diagnosticsClient === null || diagnosticsClient === void 0 ? void 0 : diagnosticsClient._flush) === null || _g === void 0 ? void 0 : _g.call(diagnosticsClient));
68
- }
69
- catch (_l) {
70
- // diagnostics is best-effort
71
- }
72
42
  void targetingIDBStore.storeTargetingMatchForSession({
73
43
  loggerProvider: loggerProvider,
74
44
  apiKey: apiKey,
@@ -77,24 +47,8 @@ export var evaluateTargetingAndStore = function (_a) {
77
47
  });
78
48
  return [3 /*break*/, 7];
79
49
  case 6:
80
- err_1 = _k.sent();
50
+ err_1 = _c.sent();
81
51
  knownError = err_1;
82
- // Q3 "is the TRC failing?": the targeting engine threw — record it so the team sees TRC
83
- // errors in DataDog (this exception is otherwise swallowed). Best-effort, never re-throws.
84
- try {
85
- diagnosticsClient === null || diagnosticsClient === void 0 ? void 0 : diagnosticsClient.increment(SrDiagnostic.evalError);
86
- diagnosticsClient === null || diagnosticsClient === void 0 ? void 0 : diagnosticsClient.recordEvent(SrDiagnostic.evalError, {
87
- sessionId: sessionId,
88
- deviceId: deviceId,
89
- srId: deviceId != null && sessionId != null ? "".concat(deviceId, "/").concat(sessionId) : undefined,
90
- pageUrl: (_h = targetingParams === null || targetingParams === void 0 ? void 0 : targetingParams.page) === null || _h === void 0 ? void 0 : _h.url,
91
- message: knownError.message,
92
- });
93
- void ((_j = diagnosticsClient === null || diagnosticsClient === void 0 ? void 0 : diagnosticsClient._flush) === null || _j === void 0 ? void 0 : _j.call(diagnosticsClient));
94
- }
95
- catch (_m) {
96
- // diagnostics is best-effort
97
- }
98
52
  loggerProvider.warn(knownError.message);
99
53
  return [3 /*break*/, 7];
100
54
  case 7: return [2 /*return*/, sessionTargetingMatch];
@@ -1 +1 @@
1
- {"version":3,"file":"targeting-manager.js","sourceRoot":"","sources":["../../../src/targeting/targeting-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TargetingParameters } from '@amplitude/targeting';\nimport { TargetingConfig } from '../config/types';\nimport { Logger } from '@amplitude/analytics-types';\nimport { IDiagnosticsClient } from '@amplitude/analytics-core';\nimport { SessionReplayTargetingInput } from '../typings/session-replay';\nimport { targetingIDBStore } from './targeting-idb-store';\nimport { SrDiagnostic } from '../diagnostics';\n\nexport const evaluateTargetingAndStore = async ({\n sessionId,\n targetingConfig,\n loggerProvider,\n apiKey,\n targetingParams,\n urlChange = false,\n diagnosticsClient,\n deviceId,\n}: {\n sessionId: string | number;\n targetingConfig: TargetingConfig;\n loggerProvider: Logger;\n apiKey: string;\n targetingParams?: SessionReplayTargetingInput;\n urlChange?: boolean;\n diagnosticsClient?: IDiagnosticsClient;\n deviceId?: string;\n}) => {\n await targetingIDBStore.clearStoreOfOldSessions({\n loggerProvider: loggerProvider,\n apiKey: apiKey,\n currentSessionId: sessionId,\n });\n\n const idbTargetingMatch = await targetingIDBStore.getTargetingMatchForSession({\n loggerProvider: loggerProvider,\n apiKey: apiKey,\n sessionId: sessionId,\n });\n\n // Skip IDB cache when re-evaluating with a new page (e.g. URL change); otherwise we'd never\n // re-evaluate and would keep returning true after navigating to a non-matching page.\n if (idbTargetingMatch === true && !urlChange) {\n return true;\n }\n\n // If the targeting config is undefined or an empty object,\n // assume the response was valid but no conditions were set,\n // so all users match targeting\n let sessionTargetingMatch = true;\n try {\n // Dynamic import of the targeting package\n const { evaluateTargeting: evaluateTargetingPackage } = await import('@amplitude/targeting');\n\n const params: TargetingParameters = {\n ...targetingParams,\n flag: targetingConfig,\n sessionId: typeof sessionId === 'string' ? parseInt(sessionId, 10) : sessionId,\n apiKey: apiKey,\n loggerProvider: loggerProvider,\n };\n diagnosticsClient?.recordEvent(SrDiagnostic.targetingTrigger, {\n sessionId,\n deviceId,\n srId: deviceId != null && sessionId != null ? `${deviceId}/${sessionId}` : undefined,\n pageUrl: targetingParams?.page?.url,\n matched: sessionTargetingMatch,\n targetingConfig: targetingConfig,\n });\n const targetingResult = await evaluateTargetingPackage(params);\n if (targetingResult && targetingResult.sr_targeting_config) {\n sessionTargetingMatch = targetingResult.sr_targeting_config.key === 'on';\n }\n // gap #2: the raw engine verdict. variantKey 'on' => matched; 'off'/undefined => not.\n // Pair with the eval event's pageUrl to tell \"URL condition didn't match\" from \"URL matched\n // but the segment bucket (capture %) didn't allocate this session\".\n try {\n diagnosticsClient?.recordEvent(SrDiagnostic.evalResult, {\n sessionId,\n deviceId,\n srId: deviceId != null && sessionId != null ? `${deviceId}/${sessionId}` : undefined,\n pageUrl: targetingParams?.page?.url,\n variantKey: targetingResult?.sr_targeting_config?.key ?? null,\n matched: sessionTargetingMatch,\n targetingConfig: targetingConfig,\n });\n // Flush now so the verdict ships immediately (vs the client's ~5-min timer). One capture\n // POST per event — higher volume; revisit/gate before production.\n void diagnosticsClient?._flush?.();\n } catch {\n // diagnostics is best-effort\n }\n\n void targetingIDBStore.storeTargetingMatchForSession({\n loggerProvider: loggerProvider,\n apiKey: apiKey,\n sessionId: sessionId,\n targetingMatch: sessionTargetingMatch,\n });\n } catch (err: unknown) {\n const knownError = err as Error;\n // Q3 \"is the TRC failing?\": the targeting engine threw — record it so the team sees TRC\n // errors in DataDog (this exception is otherwise swallowed). Best-effort, never re-throws.\n try {\n diagnosticsClient?.increment(SrDiagnostic.evalError);\n diagnosticsClient?.recordEvent(SrDiagnostic.evalError, {\n sessionId,\n deviceId,\n srId: deviceId != null && sessionId != null ? `${deviceId}/${sessionId}` : undefined,\n pageUrl: targetingParams?.page?.url,\n message: knownError.message,\n });\n void diagnosticsClient?._flush?.();\n } catch {\n // diagnostics is best-effort\n }\n loggerProvider.warn(knownError.message);\n }\n return sessionTargetingMatch;\n};\n"]}
1
+ {"version":3,"file":"targeting-manager.js","sourceRoot":"","sources":["../../../src/targeting/targeting-manager.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,CAAC,IAAM,yBAAyB,GAAG,UAAO,EAc/C;QAbC,SAAS,eAAA,EACT,eAAe,qBAAA,EACf,cAAc,oBAAA,EACd,MAAM,YAAA,EACN,eAAe,qBAAA,EACf,iBAAiB,EAAjB,SAAS,mBAAG,KAAK,KAAA;;;;;wBASjB,qBAAM,iBAAiB,CAAC,uBAAuB,CAAC;wBAC9C,cAAc,EAAE,cAAc;wBAC9B,MAAM,EAAE,MAAM;wBACd,gBAAgB,EAAE,SAAS;qBAC5B,CAAC,EAAA;;oBAJF,SAIE,CAAC;oBAEuB,qBAAM,iBAAiB,CAAC,2BAA2B,CAAC;4BAC5E,cAAc,EAAE,cAAc;4BAC9B,MAAM,EAAE,MAAM;4BACd,SAAS,EAAE,SAAS;yBACrB,CAAC,EAAA;;oBAJI,iBAAiB,GAAG,SAIxB;oBACF,4FAA4F;oBAC5F,qFAAqF;oBACrF,IAAI,iBAAiB,KAAK,IAAI,IAAI,CAAC,SAAS,EAAE;wBAC5C,sBAAO,IAAI,EAAC;qBACb;oBAKG,qBAAqB,GAAG,IAAI,CAAC;;;;oBAGyB,qBAAM,MAAM,CAAC,sBAAsB,CAAC,EAAA;;oBAAjE,wBAAwB,GAAK,CAAA,SAAoC,CAAA,kBAAzC;oBAE7C,MAAM,yBACP,eAAe,KAClB,IAAI,EAAE,eAAe,EACrB,SAAS,EAAE,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,EAC9E,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,cAAc,GAC/B,CAAC;oBACsB,qBAAM,wBAAwB,CAAC,MAAM,CAAC,EAAA;;oBAAxD,eAAe,GAAG,SAAsC;oBAC9D,IAAI,eAAe,IAAI,eAAe,CAAC,mBAAmB,EAAE;wBAC1D,qBAAqB,GAAG,eAAe,CAAC,mBAAmB,CAAC,GAAG,KAAK,IAAI,CAAC;qBAC1E;oBAED,KAAK,iBAAiB,CAAC,6BAA6B,CAAC;wBACnD,cAAc,EAAE,cAAc;wBAC9B,MAAM,EAAE,MAAM;wBACd,SAAS,EAAE,SAAS;wBACpB,cAAc,EAAE,qBAAqB;qBACtC,CAAC,CAAC;;;;oBAEG,UAAU,GAAG,KAAY,CAAC;oBAChC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;;wBAE1C,sBAAO,qBAAqB,EAAC;;;;CAC9B,CAAC","sourcesContent":["import type { TargetingParameters } from '@amplitude/targeting';\nimport { TargetingConfig } from '../config/types';\nimport { Logger } from '@amplitude/analytics-types';\nimport { SessionReplayTargetingInput } from '../typings/session-replay';\nimport { targetingIDBStore } from './targeting-idb-store';\n\nexport const evaluateTargetingAndStore = async ({\n sessionId,\n targetingConfig,\n loggerProvider,\n apiKey,\n targetingParams,\n urlChange = false,\n}: {\n sessionId: string | number;\n targetingConfig: TargetingConfig;\n loggerProvider: Logger;\n apiKey: string;\n targetingParams?: SessionReplayTargetingInput;\n urlChange?: boolean;\n}) => {\n await targetingIDBStore.clearStoreOfOldSessions({\n loggerProvider: loggerProvider,\n apiKey: apiKey,\n currentSessionId: sessionId,\n });\n\n const idbTargetingMatch = await targetingIDBStore.getTargetingMatchForSession({\n loggerProvider: loggerProvider,\n apiKey: apiKey,\n sessionId: sessionId,\n });\n // Skip IDB cache when re-evaluating with a new page (e.g. URL change); otherwise we'd never\n // re-evaluate and would keep returning true after navigating to a non-matching page.\n if (idbTargetingMatch === true && !urlChange) {\n return true;\n }\n\n // If the targeting config is undefined or an empty object,\n // assume the response was valid but no conditions were set,\n // so all users match targeting\n let sessionTargetingMatch = true;\n try {\n // Dynamic import of the targeting package\n const { evaluateTargeting: evaluateTargetingPackage } = await import('@amplitude/targeting');\n\n const params: TargetingParameters = {\n ...targetingParams,\n flag: targetingConfig,\n sessionId: typeof sessionId === 'string' ? parseInt(sessionId, 10) : sessionId,\n apiKey: apiKey,\n loggerProvider: loggerProvider,\n };\n const targetingResult = await evaluateTargetingPackage(params);\n if (targetingResult && targetingResult.sr_targeting_config) {\n sessionTargetingMatch = targetingResult.sr_targeting_config.key === 'on';\n }\n\n void targetingIDBStore.storeTargetingMatchForSession({\n loggerProvider: loggerProvider,\n apiKey: apiKey,\n sessionId: sessionId,\n targetingMatch: sessionTargetingMatch,\n });\n } catch (err: unknown) {\n const knownError = err as Error;\n loggerProvider.warn(knownError.message);\n }\n return sessionTargetingMatch;\n};\n"]}
@@ -1,5 +1,6 @@
1
1
  import { ILogger, ServerZone } from '@amplitude/analytics-core';
2
2
  import { SessionReplayTrackDestination as AmplitudeSessionReplayTrackDestination, SessionReplayDestination, SessionReplayDestinationContext } from './typings/session-replay';
3
+ import { SessionReplaySendEventsHandler } from './config/types';
3
4
  export type PayloadBatcher = ({ version, events }: {
4
5
  version: number;
5
6
  events: string[];
@@ -26,13 +27,15 @@ export declare class SessionReplayTrackDestination implements AmplitudeSessionRe
26
27
  private coalesceNextFlush;
27
28
  private mergeLogFiredThisPause;
28
29
  private killedSessions;
29
- constructor({ trackServerUrl, loggerProvider, payloadBatcher, workerScript, enableTransportCompression, sendTimeoutMs, }: {
30
+ private handleSendEvents?;
31
+ constructor({ trackServerUrl, loggerProvider, payloadBatcher, workerScript, enableTransportCompression, sendTimeoutMs, handleSendEvents, }: {
30
32
  trackServerUrl?: string;
31
33
  loggerProvider: ILogger;
32
34
  payloadBatcher?: PayloadBatcher;
33
35
  workerScript?: string;
34
36
  enableTransportCompression?: boolean;
35
37
  sendTimeoutMs?: number;
38
+ handleSendEvents?: SessionReplaySendEventsHandler;
36
39
  });
37
40
  sendEventsList(destinationData: SessionReplayDestination): void;
38
41
  /**
@@ -99,6 +102,7 @@ export declare class SessionReplayTrackDestination implements AmplitudeSessionRe
99
102
  send(context: SessionReplayDestinationContext, useRetry?: boolean): Promise<void>;
100
103
  private sendViaWorker;
101
104
  private rememberTimedOutRequest;
105
+ private handleDelegatedFetch;
102
106
  private sendOnMainThread;
103
107
  handleReponse(status: number, context: SessionReplayDestinationContext, responseBody?: string): Promise<void>;
104
108
  handlePayloadTooLargeResponse(context: SessionReplayDestinationContext, isWaf: boolean): void;
@@ -1 +1 @@
1
- {"version":3,"file":"track-destination.d.ts","sourceRoot":"","sources":["../../src/track-destination.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"track-destination.d.ts","sourceRoot":"","sources":["../../src/track-destination.ts"],"names":[],"mappings":"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"}
@@ -12,9 +12,33 @@ var MAX_KILLED_SESSIONS = 256;
12
12
  // In normal operation each entry is removed when the worker's late message arrives; this
13
13
  // cap only guards the pathological case of a wedged worker that never replies at all.
14
14
  var MAX_TIMED_OUT_WORKER_REQUESTS = 256;
15
+ // Settles with `promise`, but rejects early with an AbortError when `signal` fires. Used to apply
16
+ // the send timeout to a custom transport whose own promise can't be cancelled: a hung transport
17
+ // then surfaces as a retryable timeout (matching the built-in fetch and worker delegation paths)
18
+ // instead of blocking the serial flush loop. The transport's promise keeps running; we just stop
19
+ // awaiting it. `signal` is always supplied with the send-timeout controller's signal.
20
+ function abortable(promise, signal) {
21
+ return new Promise(function (resolve, reject) {
22
+ var onAbort = function () {
23
+ // Match how browsers reject an aborted fetch (DOMException 'AbortError'); sendOnMainThread's
24
+ // catch keys off the name to route it through the retry budget rather than failing fatally.
25
+ var err = new Error('Custom session replay transport aborted by send timeout');
26
+ err.name = 'AbortError';
27
+ reject(err);
28
+ };
29
+ signal.addEventListener('abort', onAbort, { once: true });
30
+ promise.then(function (value) {
31
+ signal.removeEventListener('abort', onAbort);
32
+ resolve(value);
33
+ }, function (err) {
34
+ signal.removeEventListener('abort', onAbort);
35
+ reject(err);
36
+ });
37
+ });
38
+ }
15
39
  var SessionReplayTrackDestination = /** @class */ (function () {
16
40
  function SessionReplayTrackDestination(_a) {
17
- var trackServerUrl = _a.trackServerUrl, loggerProvider = _a.loggerProvider, payloadBatcher = _a.payloadBatcher, workerScript = _a.workerScript, enableTransportCompression = _a.enableTransportCompression, sendTimeoutMs = _a.sendTimeoutMs;
41
+ var trackServerUrl = _a.trackServerUrl, loggerProvider = _a.loggerProvider, payloadBatcher = _a.payloadBatcher, workerScript = _a.workerScript, enableTransportCompression = _a.enableTransportCompression, sendTimeoutMs = _a.sendTimeoutMs, handleSendEvents = _a.handleSendEvents;
18
42
  var _this = this;
19
43
  this.storageKey = '';
20
44
  this.retryTimeout = 1000;
@@ -50,6 +74,7 @@ var SessionReplayTrackDestination = /** @class */ (function () {
50
74
  this.trackServerUrl = trackServerUrl;
51
75
  this.enableTransportCompression = enableTransportCompression !== null && enableTransportCompression !== void 0 ? enableTransportCompression : true;
52
76
  this.sendTimeoutMs = sendTimeoutMs !== null && sendTimeoutMs !== void 0 ? sendTimeoutMs : SEND_TIMEOUT_MS;
77
+ this.handleSendEvents = handleSendEvents;
53
78
  if (workerScript) {
54
79
  try {
55
80
  var blob = new Blob([workerScript], { type: 'application/javascript' });
@@ -141,6 +166,16 @@ var SessionReplayTrackDestination = /** @class */ (function () {
141
166
  }
142
167
  }
143
168
  }
169
+ else if (msg.type === 'fetch-request') {
170
+ // Worker is delegating one network attempt to the custom transport (which lives on
171
+ // the main thread). Run it and post the result back into the worker's retry loop.
172
+ // Guard the fire-and-forget call: if posting the result back fails (e.g. the worker
173
+ // was terminated mid-flight), surface it to the logger instead of leaving an
174
+ // unhandled rejection.
175
+ void _this.handleDelegatedFetch(worker_1, msg).catch(function (e) {
176
+ loggerProvider.warn('Failed to handle delegated session replay fetch:', e);
177
+ });
178
+ }
144
179
  };
145
180
  this.worker = worker_1;
146
181
  }
@@ -178,6 +213,7 @@ var SessionReplayTrackDestination = /** @class */ (function () {
178
213
  * to cover the remaining incremental events since the last fetch flush.
179
214
  */
180
215
  SessionReplayTrackDestination.prototype.sendBeacon = function (_a) {
216
+ var _this = this;
181
217
  var _b, _c;
182
218
  var events = _a.events, sessionId = _a.sessionId, deviceId = _a.deviceId, apiKey = _a.apiKey, serverZone = _a.serverZone;
183
219
  var MAX_BEACON_BYTES = 64 * 1024;
@@ -205,6 +241,37 @@ var SessionReplayTrackDestination = /** @class */ (function () {
205
241
  if (trimmedEvents.length === 0) {
206
242
  return;
207
243
  }
244
+ // Custom-transport page-exit path: navigator.sendBeacon cannot carry custom headers, so a
245
+ // JWT customer's exit batch would go out unauthenticated and be rejected by their proxy.
246
+ // Instead, route it through the callback with keepalive (which survives unload AND carries
247
+ // headers). Mirrors the main-send request shape: Authorization header + device_id/session_id/
248
+ // type params (api_key is NOT placed in the URL, unlike the legacy beacon path below).
249
+ if (this.handleSendEvents) {
250
+ var exitParams = new URLSearchParams({
251
+ device_id: deviceId,
252
+ session_id: String(sessionId),
253
+ type: 'replay',
254
+ });
255
+ var exitUrl = "".concat(getServerUrl(serverZone, this.trackServerUrl), "?").concat(exitParams.toString());
256
+ var headers = {
257
+ 'Content-Type': 'application/json',
258
+ Accept: '*/*',
259
+ Authorization: "Bearer ".concat(apiKey),
260
+ };
261
+ this.loggerProvider.debug("Routing session replay exit batch (".concat(trimmedEvents.length, " events) through custom transport for session id ").concat(sessionId, "."));
262
+ try {
263
+ // Fire-and-forget: we cannot await during unload. The request is well under 64 KB
264
+ // (trimmed above) and keepalive: true is requested so it survives page teardown.
265
+ void this.handleSendEvents({ url: exitUrl, method: 'POST', headers: headers, body: payload, keepalive: true }).catch(function (e) {
266
+ // best effort on exit, but still surface the failure so it's diagnosable in logs.
267
+ _this.loggerProvider.warn('Custom transport failed to send session replay exit batch:', e);
268
+ });
269
+ }
270
+ catch (e) {
271
+ this.loggerProvider.warn('Custom transport threw while sending session replay exit batch:', e);
272
+ }
273
+ return;
274
+ }
208
275
  var urlParams = new URLSearchParams({
209
276
  device_id: deviceId,
210
277
  session_id: String(sessionId),
@@ -573,6 +640,10 @@ var SessionReplayTrackDestination = /** @class */ (function () {
573
640
  id: id,
574
641
  payload: payload,
575
642
  useRetry: useRetry,
643
+ // Tell the worker to delegate each network attempt back to the main thread, where the
644
+ // custom transport callback lives (functions can't cross postMessage). Absent/false
645
+ // keeps the worker's own internal fetch — the unchanged path for existing integrations.
646
+ useCustomTransport: !!_this.handleSendEvents,
576
647
  context: {
577
648
  apiKey: context.apiKey,
578
649
  deviceId: context.deviceId,
@@ -616,12 +687,82 @@ var SessionReplayTrackDestination = /** @class */ (function () {
616
687
  }
617
688
  }
618
689
  };
690
+ // Runs the custom transport on the main thread for a single attempt the worker delegated,
691
+ // then posts the result back into the worker's retry loop as a 'fetch-response'. We read only
692
+ // what the worker's status handling needs: the skip header on a 2xx and the body text on a
693
+ // 413 (for WAF detection). A thrown/rejected transport is reported as an error so the worker
694
+ // surfaces it the same way a thrown fetch would (no retry) — matching the main-thread path.
695
+ SessionReplayTrackDestination.prototype.handleDelegatedFetch = function (worker, msg) {
696
+ var _a, _b, _c;
697
+ return __awaiter(this, void 0, void 0, function () {
698
+ var requestId, url, method, headers, body, keepalive, res, status_1, skipHeader, responseBody, _d, e_7;
699
+ return __generator(this, function (_e) {
700
+ switch (_e.label) {
701
+ case 0:
702
+ requestId = msg.requestId, url = msg.url, method = msg.method, headers = msg.headers, body = msg.body, keepalive = msg.keepalive;
703
+ _e.label = 1;
704
+ case 1:
705
+ _e.trys.push([1, 10, , 11]);
706
+ res = void 0;
707
+ if (!this.handleSendEvents) return [3 /*break*/, 3];
708
+ return [4 /*yield*/, this.handleSendEvents({ url: url, method: method, headers: headers, body: body, keepalive: keepalive })];
709
+ case 2:
710
+ res = _e.sent();
711
+ return [3 /*break*/, 5];
712
+ case 3:
713
+ // Defensive only: the worker emits 'fetch-request' solely when useCustomTransport is set,
714
+ // so this branch shouldn't be reachable. Warn so a regression that delegates without a
715
+ // transport is diagnosable rather than silently routing to the built-in fetch.
716
+ this.loggerProvider.warn('Delegated session replay fetch received without a custom transport configured; falling back to built-in fetch.');
717
+ return [4 /*yield*/, fetch(url, { method: method, headers: headers, body: body, keepalive: keepalive })];
718
+ case 4:
719
+ res = _e.sent();
720
+ _e.label = 5;
721
+ case 5:
722
+ status_1 = res.status;
723
+ skipHeader = null;
724
+ responseBody = '';
725
+ if (status_1 >= 200 && status_1 < 300) {
726
+ skipHeader = (_c = (_b = (_a = res.headers) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(_a, EVENT_SKIPPED_HEADER)) !== null && _c !== void 0 ? _c : null;
727
+ }
728
+ if (!(status_1 === 413)) return [3 /*break*/, 9];
729
+ _e.label = 6;
730
+ case 6:
731
+ _e.trys.push([6, 8, , 9]);
732
+ return [4 /*yield*/, res.text()];
733
+ case 7:
734
+ responseBody = _e.sent();
735
+ return [3 /*break*/, 9];
736
+ case 8:
737
+ _d = _e.sent();
738
+ return [3 /*break*/, 9];
739
+ case 9:
740
+ this.loggerProvider.debug("Delegated session replay fetch (request ".concat(requestId, ") returned status ").concat(status_1, "."));
741
+ worker.postMessage({ type: 'fetch-response', requestId: requestId, status: status_1, skipHeader: skipHeader, body: responseBody });
742
+ return [3 /*break*/, 11];
743
+ case 10:
744
+ e_7 = _e.sent();
745
+ this.loggerProvider.debug("Delegated session replay fetch (request ".concat(requestId, ") failed:"), e_7);
746
+ worker.postMessage({
747
+ type: 'fetch-response',
748
+ requestId: requestId,
749
+ status: 0,
750
+ skipHeader: null,
751
+ body: String(e_7),
752
+ error: true,
753
+ });
754
+ return [3 /*break*/, 11];
755
+ case 11: return [2 /*return*/];
756
+ }
757
+ });
758
+ });
759
+ };
619
760
  SessionReplayTrackDestination.prototype.sendOnMainThread = function (apiKey, deviceId, context, payload, useRetry) {
620
761
  var _a, _b, _c, _d, _e, _f, _g;
621
762
  return __awaiter(this, void 0, void 0, function () {
622
- var url, version, sampleRate, urlParams, sessionReplayLibrary, payloadJson, globalScope, gzipped, _h, payloadSize, controller_1, options, serverUrl, sendTimeout, res, skipCode, responseBody, responseBody, _j, e_7, isAbort;
623
- return __generator(this, function (_k) {
624
- switch (_k.label) {
763
+ var url, version, sampleRate, urlParams, sessionReplayLibrary, payloadJson, globalScope, gzipped, _h, payloadSize, controller_1, headers, body, keepalive, options, serverUrl, sendTimeout, res, _j, skipCode, responseBody, responseBody, _k, e_8, isAbort;
764
+ return __generator(this, function (_l) {
765
+ switch (_l.label) {
625
766
  case 0:
626
767
  url = getCurrentUrl();
627
768
  version = VERSION;
@@ -632,30 +773,31 @@ var SessionReplayTrackDestination = /** @class */ (function () {
632
773
  type: "".concat(context.type),
633
774
  });
634
775
  sessionReplayLibrary = "".concat((_b = (_a = context.version) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'standalone', "/").concat((_d = (_c = context.version) === null || _c === void 0 ? void 0 : _c.version) !== null && _d !== void 0 ? _d : version);
635
- _k.label = 1;
776
+ _l.label = 1;
636
777
  case 1:
637
- _k.trys.push([1, 16, , 20]);
778
+ _l.trys.push([1, 19, , 23]);
638
779
  payloadJson = JSON.stringify(payload);
639
780
  globalScope = getGlobalScope();
640
781
  if (!(this.enableTransportCompression && globalScope && 'CompressionStream' in globalScope)) return [3 /*break*/, 3];
641
782
  return [4 /*yield*/, gzipJson(payloadJson, globalScope)];
642
783
  case 2:
643
- _h = _k.sent();
784
+ _h = _l.sent();
644
785
  return [3 /*break*/, 4];
645
786
  case 3:
646
787
  _h = null;
647
- _k.label = 4;
788
+ _l.label = 4;
648
789
  case 4:
649
790
  gzipped = _h;
650
791
  payloadSize = gzipped ? gzipped.byteLength : new Blob([payloadJson]).size;
651
792
  controller_1 = new AbortController();
793
+ headers = __assign({ 'Content-Type': 'application/json', Accept: '*/*', Authorization: "Bearer ".concat(apiKey), 'X-Client-Version': version, 'X-Client-Library': sessionReplayLibrary, 'X-Client-Url': url.substring(0, MAX_URL_LENGTH), 'X-Client-Sample-Rate': "".concat(sampleRate), 'X-Sampling-Hash-Alg': 'xxhash32' }, (gzipped ? { 'Content-Encoding': 'gzip' } : {}));
794
+ body = gzipped !== null && gzipped !== void 0 ? gzipped : payloadJson;
795
+ keepalive = payloadSize <= MAX_KEEPALIVE_BYTES;
652
796
  options = {
653
- headers: __assign({ 'Content-Type': 'application/json', Accept: '*/*', Authorization: "Bearer ".concat(apiKey), 'X-Client-Version': version, 'X-Client-Library': sessionReplayLibrary, 'X-Client-Url': url.substring(0, MAX_URL_LENGTH), 'X-Client-Sample-Rate': "".concat(sampleRate), 'X-Sampling-Hash-Alg': 'xxhash32' }, (gzipped ? { 'Content-Encoding': 'gzip' } : {})),
654
- body: (gzipped !== null && gzipped !== void 0 ? gzipped : payloadJson),
797
+ headers: headers,
798
+ body: body,
655
799
  method: 'POST',
656
- // keepalive lets the request survive page navigation, preventing 499 (client-closed) errors.
657
- // Must stay under the browser's 64 KB keepalive budget; large payloads skip it.
658
- keepalive: payloadSize <= MAX_KEEPALIVE_BYTES,
800
+ keepalive: keepalive,
659
801
  signal: controller_1.signal,
660
802
  };
661
803
  serverUrl = "".concat(getServerUrl(context.serverZone, this.trackServerUrl), "?").concat(urlParams.toString());
@@ -674,20 +816,35 @@ var SessionReplayTrackDestination = /** @class */ (function () {
674
816
  }, this.sendTimeoutMs)
675
817
  : undefined;
676
818
  res = void 0;
677
- _k.label = 5;
819
+ _l.label = 5;
678
820
  case 5:
679
- _k.trys.push([5, , 7, 8]);
680
- return [4 /*yield*/, fetch(serverUrl, options)];
821
+ _l.trys.push([5, , 10, 11]);
822
+ if (!this.handleSendEvents) return [3 /*break*/, 7];
823
+ return [4 /*yield*/, abortable(this.handleSendEvents({ url: serverUrl, method: 'POST', headers: headers, body: body, keepalive: keepalive }), controller_1.signal)];
681
824
  case 6:
682
- res = _k.sent();
683
- return [3 /*break*/, 8];
684
- case 7:
825
+ _j = _l.sent();
826
+ return [3 /*break*/, 9];
827
+ case 7: return [4 /*yield*/, fetch(serverUrl, options)];
828
+ case 8:
829
+ _j = _l.sent();
830
+ _l.label = 9;
831
+ case 9:
832
+ // When a custom transport is configured, hand it the fully-formed request and let it own
833
+ // the network call (e.g. to attach a JWT and route through a proxy). Otherwise use the
834
+ // built-in fetch. Retry/response handling below is identical for both paths. The built-in
835
+ // fetch cancels directly on controller.signal; the custom callback can't be cancelled, so
836
+ // we race it against the abort — a hung/never-settling transport then rejects with an
837
+ // AbortError (retryable, like the worker delegation path) instead of blocking the serial
838
+ // flush loop forever. The callback's own promise keeps running; we just stop awaiting it.
839
+ res = _j;
840
+ return [3 /*break*/, 11];
841
+ case 10:
685
842
  // Clear on success and on error alike so a settled request never leaves an armed
686
843
  // timer that would abort a later reused controller or fire a stray callback.
687
844
  if (sendTimeout)
688
845
  clearTimeout(sendTimeout);
689
846
  return [7 /*endfinally*/];
690
- case 8:
847
+ case 11:
691
848
  if (res === null) {
692
849
  this.completeRequest({ context: context, err: UNEXPECTED_ERROR_MESSAGE });
693
850
  return [2 /*return*/];
@@ -696,47 +853,47 @@ var SessionReplayTrackDestination = /** @class */ (function () {
696
853
  skipCode = (_g = (_f = (_e = res.headers) === null || _e === void 0 ? void 0 : _e.get) === null || _f === void 0 ? void 0 : _f.call(_e, EVENT_SKIPPED_HEADER)) !== null && _g !== void 0 ? _g : null;
697
854
  this.applyServerDirective(context.sessionId, skipCode);
698
855
  }
699
- if (!!useRetry) return [3 /*break*/, 9];
856
+ if (!!useRetry) return [3 /*break*/, 12];
700
857
  responseBody = '';
701
858
  try {
702
859
  responseBody = JSON.stringify(res.body, null, 2);
703
860
  }
704
- catch (_l) {
861
+ catch (_m) {
705
862
  // to avoid crash, but don't care about the error, add comment to avoid empty block lint error
706
863
  }
707
864
  this.completeRequest({ context: context, success: "".concat(res.status, ": ").concat(responseBody) });
708
- return [3 /*break*/, 15];
709
- case 9:
865
+ return [3 /*break*/, 18];
866
+ case 12:
710
867
  responseBody = '';
711
- if (!(res.status === 413)) return [3 /*break*/, 13];
712
- _k.label = 10;
713
- case 10:
714
- _k.trys.push([10, 12, , 13]);
868
+ if (!(res.status === 413)) return [3 /*break*/, 16];
869
+ _l.label = 13;
870
+ case 13:
871
+ _l.trys.push([13, 15, , 16]);
715
872
  return [4 /*yield*/, res.text()];
716
- case 11:
717
- responseBody = _k.sent();
718
- return [3 /*break*/, 13];
719
- case 12:
720
- _j = _k.sent();
721
- return [3 /*break*/, 13];
722
- case 13: return [4 /*yield*/, this.handleReponse(res.status, context, responseBody)];
723
873
  case 14:
724
- _k.sent();
725
- _k.label = 15;
726
- case 15: return [3 /*break*/, 20];
727
- case 16:
728
- e_7 = _k.sent();
729
- isAbort = !!e_7 && typeof e_7 === 'object' && e_7.name === 'AbortError';
730
- if (!(isAbort && useRetry)) return [3 /*break*/, 18];
731
- return [4 /*yield*/, this.handleOtherResponse(context)];
874
+ responseBody = _l.sent();
875
+ return [3 /*break*/, 16];
876
+ case 15:
877
+ _k = _l.sent();
878
+ return [3 /*break*/, 16];
879
+ case 16: return [4 /*yield*/, this.handleReponse(res.status, context, responseBody)];
732
880
  case 17:
733
- _k.sent();
734
- return [3 /*break*/, 19];
735
- case 18:
736
- this.completeRequest({ context: context, err: e_7 });
737
- _k.label = 19;
738
- case 19: return [3 /*break*/, 20];
739
- case 20: return [2 /*return*/];
881
+ _l.sent();
882
+ _l.label = 18;
883
+ case 18: return [3 /*break*/, 23];
884
+ case 19:
885
+ e_8 = _l.sent();
886
+ isAbort = !!e_8 && typeof e_8 === 'object' && e_8.name === 'AbortError';
887
+ if (!(isAbort && useRetry)) return [3 /*break*/, 21];
888
+ return [4 /*yield*/, this.handleOtherResponse(context)];
889
+ case 20:
890
+ _l.sent();
891
+ return [3 /*break*/, 22];
892
+ case 21:
893
+ this.completeRequest({ context: context, err: e_8 });
894
+ _l.label = 22;
895
+ case 22: return [3 /*break*/, 23];
896
+ case 23: return [2 /*return*/];
740
897
  }
741
898
  });
742
899
  });
@@ -890,7 +1047,7 @@ var SessionReplayTrackDestination = /** @class */ (function () {
890
1047
  // session for a code we don't recognize.
891
1048
  };
892
1049
  SessionReplayTrackDestination.prototype.killSession = function (sessionId, skipCode) {
893
- var e_8, _a, e_9, _b;
1050
+ var e_9, _a, e_10, _b;
894
1051
  if (this.killedSessions.has(sessionId))
895
1052
  return;
896
1053
  this.killedSessions.add(sessionId);
@@ -903,12 +1060,12 @@ var SessionReplayTrackDestination = /** @class */ (function () {
903
1060
  break;
904
1061
  }
905
1062
  }
906
- catch (e_8_1) { e_8 = { error: e_8_1 }; }
1063
+ catch (e_9_1) { e_9 = { error: e_9_1 }; }
907
1064
  finally {
908
1065
  try {
909
1066
  if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
910
1067
  }
911
- finally { if (e_8) throw e_8.error; }
1068
+ finally { if (e_9) throw e_9.error; }
912
1069
  }
913
1070
  }
914
1071
  this.loggerProvider.log("Session replay capture stopped for session ".concat(sessionId, " by server directive ").concat(skipCode, "; remaining events will be dropped"));
@@ -926,12 +1083,12 @@ var SessionReplayTrackDestination = /** @class */ (function () {
926
1083
  }
927
1084
  }
928
1085
  }
929
- catch (e_9_1) { e_9 = { error: e_9_1 }; }
1086
+ catch (e_10_1) { e_10 = { error: e_10_1 }; }
930
1087
  finally {
931
1088
  try {
932
1089
  if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
933
1090
  }
934
- finally { if (e_9) throw e_9.error; }
1091
+ finally { if (e_10) throw e_10.error; }
935
1092
  }
936
1093
  this.queue = remaining;
937
1094
  };