@amplitude/session-replay-browser 1.42.1 → 1.42.3
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/lib/cjs/config/joined-config.d.ts.map +1 -1
- package/lib/cjs/config/joined-config.js +19 -4
- package/lib/cjs/config/joined-config.js.map +1 -1
- package/lib/cjs/constants.d.ts +5 -0
- package/lib/cjs/constants.d.ts.map +1 -1
- package/lib/cjs/constants.js +10 -1
- package/lib/cjs/constants.js.map +1 -1
- package/lib/cjs/messages.d.ts +1 -0
- package/lib/cjs/messages.d.ts.map +1 -1
- package/lib/cjs/messages.js +2 -1
- package/lib/cjs/messages.js.map +1 -1
- package/lib/cjs/track-destination.d.ts +15 -0
- package/lib/cjs/track-destination.d.ts.map +1 -1
- package/lib/cjs/track-destination.js +133 -20
- package/lib/cjs/track-destination.js.map +1 -1
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/cjs/version.js.map +1 -1
- package/lib/cjs/worker/index.js +1 -1
- package/lib/esm/config/joined-config.d.ts.map +1 -1
- package/lib/esm/config/joined-config.js +19 -4
- package/lib/esm/config/joined-config.js.map +1 -1
- package/lib/esm/constants.d.ts +5 -0
- package/lib/esm/constants.d.ts.map +1 -1
- package/lib/esm/constants.js +9 -0
- package/lib/esm/constants.js.map +1 -1
- package/lib/esm/messages.d.ts +1 -0
- package/lib/esm/messages.d.ts.map +1 -1
- package/lib/esm/messages.js +1 -0
- package/lib/esm/messages.js.map +1 -1
- package/lib/esm/track-destination.d.ts +15 -0
- package/lib/esm/track-destination.d.ts.map +1 -1
- package/lib/esm/track-destination.js +135 -22
- package/lib/esm/track-destination.js.map +1 -1
- package/lib/esm/version.d.ts +1 -1
- package/lib/esm/version.js +1 -1
- package/lib/esm/version.js.map +1 -1
- package/lib/esm/worker/index.js +1 -1
- package/lib/scripts/index-min.js +1 -1
- package/lib/scripts/index-min.js.gz +0 -0
- package/lib/scripts/index-min.js.map +1 -1
- package/lib/scripts/session-replay-browser-min.js +1 -1
- package/lib/scripts/session-replay-browser-min.js.gz +0 -0
- package/lib/scripts/session-replay-browser-min.js.map +1 -1
- package/lib/scripts/worker-min.js +1 -1
- package/lib/scripts/worker-min.js.gz +0 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"joined-config.d.ts","sourceRoot":"","sources":["../../../src/config/joined-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAA4C,MAAM,2BAA2B,CAAC;AAEnH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,OAAO,EACL,wBAAwB,IAAI,yBAAyB,EACrD,aAAa,EACb,oBAAoB,EAGrB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"joined-config.d.ts","sourceRoot":"","sources":["../../../src/config/joined-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAA4C,MAAM,2BAA2B,CAAC;AAEnH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,OAAO,EACL,wBAAwB,IAAI,yBAAyB,EACrD,aAAa,EACb,oBAAoB,EAGrB,MAAM,SAAS,CAAC;AAajB,eAAO,MAAM,uCAAuC,kBAAmB,aAAa,kBAAkB,OAAO,kBA0B5G,CAAC;AACF,qBAAa,kCAAkC;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4B;IACxD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAsB;gBAE7C,kBAAkB,EAAE,mBAAmB,EAAE,WAAW,EAAE,yBAAyB;IAKrF,oBAAoB,IAAI,OAAO,CAAC,oBAAoB,CAAC;CA0L5D;AAED,eAAO,MAAM,wCAAwC,WAAkB,MAAM,WAAW,oBAAoB,gDAW3G,CAAC"}
|
|
@@ -5,6 +5,16 @@ var tslib_1 = require("tslib");
|
|
|
5
5
|
var analytics_core_1 = require("@amplitude/analytics-core");
|
|
6
6
|
var helpers_1 = require("../helpers");
|
|
7
7
|
var local_config_1 = require("./local-config");
|
|
8
|
+
// Budget for waiting on the remote config response before falling back to the local cache.
|
|
9
|
+
// The inner fetch in analytics-core uses a 1000ms per-attempt AbortController timeout with
|
|
10
|
+
// up to 3 retries — but this outer timeout is the only thing the SR plugin waits on, so
|
|
11
|
+
// only the first attempt can possibly complete in time (attempt 2 starts at ~1000-1333ms
|
|
12
|
+
// after backoff jitter and runs to ~2000-2333ms, well past any reasonable outer budget).
|
|
13
|
+
// 1500ms is set above the 1000ms inner abort to avoid a tie with the inner cutoff and
|
|
14
|
+
// give the first attempt's resolution path room to finish; everything else falls through
|
|
15
|
+
// to the cache fallback. See SR-4234: prefer remote over a stale cache that would
|
|
16
|
+
// otherwise win the synchronous race in 'all' mode.
|
|
17
|
+
var REMOTE_CONFIG_TIMEOUT_MS = 1500;
|
|
8
18
|
var removeInvalidSelectorsFromPrivacyConfig = function (privacyConfig, loggerProvider) {
|
|
9
19
|
// This allows us to not search the DOM.
|
|
10
20
|
var fragment = document.createDocumentFragment();
|
|
@@ -57,9 +67,12 @@ var SessionReplayJoinedConfigGenerator = /** @class */ (function () {
|
|
|
57
67
|
_m.label = 1;
|
|
58
68
|
case 1:
|
|
59
69
|
_m.trys.push([1, 3, , 4]);
|
|
60
|
-
// Subscribe
|
|
70
|
+
// Subscribe with a timeout so the SDK prefers the remote response and only falls back
|
|
71
|
+
// to cache after the budget elapses. 'all' mode would race a synchronous cache read
|
|
72
|
+
// against the network and resolve on whichever fires first — cache always wins, so a
|
|
73
|
+
// stale cache silently overrides the live config (SR-4234).
|
|
61
74
|
return [4 /*yield*/, new Promise(function (resolve, reject) {
|
|
62
|
-
_this.remoteConfigClient.subscribe('configs.sessionReplay',
|
|
75
|
+
_this.remoteConfigClient.subscribe('configs.sessionReplay', { timeout: REMOTE_CONFIG_TIMEOUT_MS }, function (remoteConfig, source) {
|
|
63
76
|
var _a;
|
|
64
77
|
_this.localConfig.loggerProvider.debug("Session Replay remote configuration received from ".concat(source, ":"), JSON.stringify(remoteConfig, null, 2));
|
|
65
78
|
if (!remoteConfig) {
|
|
@@ -91,12 +104,14 @@ var SessionReplayJoinedConfigGenerator = /** @class */ (function () {
|
|
|
91
104
|
sessionReplayRemoteConfig.sr_targeting_config = targetingConfig;
|
|
92
105
|
}
|
|
93
106
|
}
|
|
94
|
-
// Resolve on first callback
|
|
95
107
|
resolve();
|
|
96
108
|
});
|
|
97
109
|
})];
|
|
98
110
|
case 2:
|
|
99
|
-
// Subscribe
|
|
111
|
+
// Subscribe with a timeout so the SDK prefers the remote response and only falls back
|
|
112
|
+
// to cache after the budget elapses. 'all' mode would race a synchronous cache read
|
|
113
|
+
// against the network and resolve on whichever fires first — cache always wins, so a
|
|
114
|
+
// stale cache silently overrides the live config (SR-4234).
|
|
100
115
|
_m.sent();
|
|
101
116
|
return [3 /*break*/, 4];
|
|
102
117
|
case 3:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"joined-config.js","sourceRoot":"","sources":["../../../src/config/joined-config.ts"],"names":[],"mappings":";;;;AAAA,4DAAmH;AACnH,sCAA4C;AAE5C,+CAA0D;AASnD,IAAM,uCAAuC,GAAG,UAAC,aAA4B,EAAE,cAAuB;IAC3G,wCAAwC;IACxC,IAAM,QAAQ,GAAG,QAAQ,CAAC,sBAAsB,EAAE,CAAC;IAEnD,IAAM,oBAAoB,GAAG,UAAC,SAAiC;QAAjC,0BAAA,EAAA,cAAiC;QAC7D,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE;YACjC,SAAS,GAAG,CAAC,SAAS,CAAC,CAAC;SACzB;QACD,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,UAAC,QAAgB;YAC5C,IAAI;gBACF,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;aAClC;YAAC,WAAM;gBACN,cAAc,CAAC,IAAI,CAAC,uDAA+C,QAAQ,6BAAyB,CAAC,CAAC;gBACtG,OAAO,KAAK,CAAC;aACd;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;QACH,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE;YAC1B,OAAO,SAAS,CAAC;SAClB;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IACF,aAAa,CAAC,aAAa,GAAG,oBAAoB,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;IAChF,aAAa,CAAC,YAAY,GAAG,oBAAoB,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IAC9E,aAAa,CAAC,cAAc,GAAG,oBAAoB,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;IAClF,OAAO,aAAa,CAAC;AACvB,CAAC,CAAC;AA1BW,QAAA,uCAAuC,2CA0BlD;AACF;IAIE,4CAAY,kBAAuC,EAAE,WAAsC;QACzF,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;IAC/C,CAAC;IAEK,iEAAoB,GAA1B;;;;;;;;;wBACQ,MAAM,wBAAmC,IAAI,CAAC,WAAW,CAAE,CAAC;wBAClE,+DAA+D;wBAC/D,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;wBACxC,uEAAuE;wBACvE,wBAAwB;wBACxB,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;;;;wBAI3B,gFAAgF;wBAChF,qBAAM,IAAI,OAAO,CAAO,UAAC,OAAO,EAAE,MAAM;gCACtC,KAAI,CAAC,kBAAkB,CAAC,SAAS,CAC/B,uBAAuB,EACvB,KAAK,EACL,UAAC,YAAiC,EAAE,MAAc;;oCAChD,KAAI,CAAC,WAAW,CAAC,cAAc,CAAC,KAAK,CACnC,4DAAqD,MAAM,MAAG,EAC9D,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CACtC,CAAC;oCAEF,IAAI,CAAC,YAAY,EAAE;wCACjB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC;wCAC/C,OAAO;qCACR;oCAED,wEAAwE;oCACxE,IAAM,eAAe,GAAG,YAAyC,CAAC;oCAClE,IAAM,cAAc,GAAG,eAAe,CAAC,kBAAkB,CAAC;oCAC1D,IAAM,aAAa,GAAG,eAAe,CAAC,iBAAiB,CAAC;oCACxD,IAAM,eAAe,GAAG,eAAe,CAAC,mBAAmB,CAAC;oCAE5D,IAAM,cAAc,GAAG,MAAA,MAAM,CAAC,iBAAiB,0CAAE,cAAc,CAAC;oCAChE,yEAAyE;oCACzE,MAAM,CAAC,iBAAiB,GAAG,eAAe,CAAC,qBAAqB,CAAC;oCACjE,IAAI,MAAM,CAAC,iBAAiB,IAAI,cAAc,EAAE;wCAC9C,MAAM,CAAC,iBAAiB,CAAC,cAAc,GAAG,cAAc,CAAC;qCAC1D;oCAED,yEAAyE;oCACzE,MAAM,CAAC,aAAa,GAAG,eAAe,CAAC,iBAAiB,CAAC;oCAEzD,IAAI,cAAc,IAAI,aAAa,IAAI,eAAe,EAAE;wCACtD,yBAAyB,GAAG,EAAE,CAAC;wCAC/B,IAAI,cAAc,EAAE;4CAClB,yBAAyB,CAAC,kBAAkB,GAAG,cAAc,CAAC;yCAC/D;wCACD,IAAI,aAAa,EAAE;4CACjB,yBAAyB,CAAC,iBAAiB,GAAG,aAAa,CAAC;yCAC7D;wCACD,IAAI,eAAe,EAAE;4CACnB,yBAAyB,CAAC,mBAAmB,GAAG,eAAe,CAAC;yCACjE;qCACF;oCAED,4BAA4B;oCAC5B,OAAO,EAAE,CAAC;gCACZ,CAAC,CACF,CAAC;4BACJ,CAAC,CAAC,EAAA;;wBAjDF,gFAAgF;wBAChF,SAgDE,CAAC;;;;wBAEH,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,oCAAoC,EAAE,OAAK,CAAC,CAAC;wBACnF,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC;wBAC9B,sBAAO;gCACL,WAAW,EAAE,IAAI,CAAC,WAAW;gCAC7B,YAAY,EAAE,MAAM;gCACpB,YAAY,EAAE,SAAS;6BACxB,EAAC;;wBAGJ,IAAI,CAAC,yBAAyB,EAAE;4BAC9B,sBAAO;oCACL,WAAW,EAAE,IAAI,CAAC,WAAW;oCAC7B,YAAY,EAAE,MAAM;oCACpB,YAAY,EAAE,yBAAyB;iCACxC,EAAC;yBACH;wBAGqB,cAAc,GAGhC,yBAAyB,mBAHO,EACf,mBAAmB,GAEpC,yBAAyB,kBAFW,EACjB,eAAe,GAClC,yBAAyB,oBADS,CACR;wBAC9B,IAAI,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE;4BAC5D,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,cAAc,EAAE,iBAAiB,CAAC,EAAE;gCAC3E,MAAM,CAAC,cAAc,GAAG,cAAc,CAAC,eAAe,CAAC;6BACxD;iCAAM;gCACL,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC;6BAC/B;4BAED,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,EAAE;gCACvE,MAAM,CAAC,UAAU,GAAG,cAAc,CAAC,WAAW,CAAC;6BAChD;yBACF;6BAAM;4BACL,iFAAiF;4BACjF,4EAA4E;4BAC5E,wCAAwC;4BACxC,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;4BAC7B,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,KAAK,CACnC,oGAAoG,CACrG,CAAC;yBACH;wBAED,qFAAqF;wBACrF,oFAAoF;wBACpF,8DAA8D;wBAC9D,mCAAmC;wBACnC,EAAE;wBACF,oEAAoE;wBACpE,0FAA0F;wBAC1F,EAAE;wBACF,0FAA0F;wBAC1F,2BAA2B;wBAC3B,qCAAqC;wBACrC,4BAA4B;wBAC5B,KAAK;wBAEL,IAAI,mBAAmB,EAAE;4BACjB,kBAAkB,GAAkB,MAAA,MAAM,CAAC,aAAa,mCAAI,EAAE,CAAC;4BAE/D,mBAAmB,GAA0D;gCACjF,gBAAgB,EAAE,MAAA,MAAA,mBAAmB,CAAC,gBAAgB,mCAAI,kBAAkB,CAAC,gBAAgB,mCAAI,QAAQ;gCACzG,aAAa,EAAE,EAAE;gCACjB,YAAY,EAAE,EAAE;gCAChB,cAAc,EAAE,EAAE;gCAClB,cAAc,2CACT,IAAI,GAAG,gEAAK,CAAC,MAAA,kBAAkB,CAAC,cAAc,mCAAI,EAAE,CAAC,0BAAK,CAAC,MAAA,mBAAmB,CAAC,cAAc,mCAAI,EAAE,CAAC,UAAE,SAC1G;gCACD,aAAa,iEAAM,CAAC,MAAA,mBAAmB,CAAC,aAAa,mCAAI,EAAE,CAAC,0BAAK,CAAC,MAAA,kBAAkB,CAAC,aAAa,mCAAI,EAAE,CAAC,SAAC;6BAC3G,CAAC;4BAEI,wBAAwB,GAAG,UAAC,aAA4B;;;gCAC5D,IAAM,WAAW,GAAgD,EAAE,CAAC;gCACpE,IAAI,OAAO,aAAa,CAAC,aAAa,KAAK,QAAQ,EAAE;oCACnD,aAAa,CAAC,aAAa,GAAG,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;iCAC7D;;oCAED,KAAuB,IAAA,KAAA,iBAAA,MAAA,aAAa,CAAC,aAAa,mCAAI,EAAE,CAAA,gBAAA,4BAAE;wCAArD,IAAM,QAAQ,WAAA;wCACjB,WAAW,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC;qCACjC;;;;;;;;;;oCACD,KAAuB,IAAA,KAAA,iBAAA,MAAA,aAAa,CAAC,YAAY,mCAAI,EAAE,CAAA,gBAAA,4BAAE;wCAApD,IAAM,QAAQ,WAAA;wCACjB,WAAW,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;qCAChC;;;;;;;;;;oCACD,KAAuB,IAAA,KAAA,iBAAA,MAAA,aAAa,CAAC,cAAc,mCAAI,EAAE,CAAA,gBAAA,4BAAE;wCAAtD,IAAM,QAAQ,WAAA;wCACjB,WAAW,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;qCAClC;;;;;;;;;gCACD,OAAO,WAAW,CAAC;4BACrB,CAAC,CAAC;4BAEI,WAAW,yCACZ,wBAAwB,CAAC,kBAAkB,CAAC,GAC5C,wBAAwB,CAAC,mBAAmB,CAAC,CACjD,CAAC;;gCAEF,KAAuC,KAAA,iBAAA,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA,4CAAE;oCAAzD,KAAA,2BAAwB,EAAvB,QAAQ,QAAA,EAAE,YAAY,QAAA;oCAChC,IAAI,YAAY,KAAK,MAAM,EAAE;wCAC3B,mBAAmB,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;qCACjD;yCAAM,IAAI,YAAY,KAAK,OAAO,EAAE;wCACnC,mBAAmB,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;qCAClD;yCAAM,IAAI,YAAY,KAAK,QAAQ,EAAE;wCACpC,mBAAmB,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;qCACnD;iCACF;;;;;;;;;4BAED,MAAM,CAAC,aAAa,GAAG,IAAA,+CAAuC,EAC5D,mBAAmB,EACnB,IAAI,CAAC,WAAW,CAAC,cAAc,CAChC,CAAC;yBACH;wBAED,IAAI,eAAe,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE;4BAC9D,MAAM,CAAC,eAAe,GAAG,eAAe,CAAC;yBAC1C;wBAED,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,KAAK,CACnC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,8BAA8B,EAAE,MAAM,EAAE,IAAA,wBAAc,EAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAClG,CAAC;wBAEF,sBAAO;gCACL,WAAW,EAAE,IAAI,CAAC,WAAW;gCAC7B,YAAY,EAAE,MAAM;gCACpB,YAAY,EAAE,yBAAyB;6BACxC,EAAC;;;;KACH;IACH,yCAAC;AAAD,CAAC,AAjMD,IAiMC;AAjMY,gFAAkC;AAmMxC,IAAM,wCAAwC,GAAG,UAAO,MAAc,EAAE,OAA6B;;;QACpG,WAAW,GAAG,IAAI,uCAAwB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAE5D,kBAAkB,GAAG,IAAI,mCAAkB,CAC/C,MAAM,EACN,WAAW,CAAC,cAAc,EAC1B,WAAW,CAAC,UAAU,EACtB,OAAO,CAAC,eAAe,CACxB,CAAC;QAEF,sBAAO,IAAI,kCAAkC,CAAC,kBAAkB,EAAE,WAAW,CAAC,EAAC;;KAChF,CAAC;AAXW,QAAA,wCAAwC,4CAWnD","sourcesContent":["import { ILogger, IRemoteConfigClient, RemoteConfigClient, RemoteConfig, Source } from '@amplitude/analytics-core';\nimport { getDebugConfig } from '../helpers';\nimport { SessionReplayOptions } from '../typings/session-replay';\nimport { SessionReplayLocalConfig } from './local-config';\nimport {\n SessionReplayLocalConfig as ISessionReplayLocalConfig,\n PrivacyConfig,\n SessionReplayConfigs,\n SessionReplayJoinedConfig,\n SessionReplayRemoteConfig,\n} from './types';\n\nexport const removeInvalidSelectorsFromPrivacyConfig = (privacyConfig: PrivacyConfig, loggerProvider: ILogger) => {\n // This allows us to not search the DOM.\n const fragment = document.createDocumentFragment();\n\n const dropInvalidSelectors = (selectors: string[] | string = []): string[] | undefined => {\n if (typeof selectors === 'string') {\n selectors = [selectors];\n }\n selectors = selectors.filter((selector: string) => {\n try {\n fragment.querySelector(selector);\n } catch {\n loggerProvider.warn(`[session-replay-browser] omitting selector \"${selector}\" because it is invalid`);\n return false;\n }\n return true;\n });\n if (selectors.length === 0) {\n return undefined;\n }\n return selectors;\n };\n privacyConfig.blockSelector = dropInvalidSelectors(privacyConfig.blockSelector);\n privacyConfig.maskSelector = dropInvalidSelectors(privacyConfig.maskSelector);\n privacyConfig.unmaskSelector = dropInvalidSelectors(privacyConfig.unmaskSelector);\n return privacyConfig;\n};\nexport class SessionReplayJoinedConfigGenerator {\n private readonly localConfig: ISessionReplayLocalConfig;\n private readonly remoteConfigClient: IRemoteConfigClient;\n\n constructor(remoteConfigClient: IRemoteConfigClient, localConfig: ISessionReplayLocalConfig) {\n this.localConfig = localConfig;\n this.remoteConfigClient = remoteConfigClient;\n }\n\n async generateJoinedConfig(): Promise<SessionReplayConfigs> {\n const config: SessionReplayJoinedConfig = { ...this.localConfig };\n // Special case here as optOut is implemented via getter/setter\n config.optOut = this.localConfig.optOut;\n // We always want captureEnabled to be true, unless there's an override\n // in the remote config.\n config.captureEnabled = true;\n let sessionReplayRemoteConfig: SessionReplayRemoteConfig | undefined;\n\n try {\n // Subscribe to remote config client to get the config (uses cache if available)\n await new Promise<void>((resolve, reject) => {\n this.remoteConfigClient.subscribe(\n 'configs.sessionReplay',\n 'all',\n (remoteConfig: RemoteConfig | null, source: Source) => {\n this.localConfig.loggerProvider.debug(\n `Session Replay remote configuration received from ${source}:`,\n JSON.stringify(remoteConfig, null, 2),\n );\n\n if (!remoteConfig) {\n reject(new Error('No remote config received'));\n return;\n }\n\n // remoteConfig is already filtered to 'configs.sessionReplay' namespace\n const namespaceConfig = remoteConfig as SessionReplayRemoteConfig;\n const samplingConfig = namespaceConfig.sr_sampling_config;\n const privacyConfig = namespaceConfig.sr_privacy_config;\n const targetingConfig = namespaceConfig.sr_targeting_config;\n\n const ugcFilterRules = config.interactionConfig?.ugcFilterRules;\n // This is intentionally forced to only be set through the remote config.\n config.interactionConfig = namespaceConfig.sr_interaction_config;\n if (config.interactionConfig && ugcFilterRules) {\n config.interactionConfig.ugcFilterRules = ugcFilterRules;\n }\n\n // This is intentionally forced to only be set through the remote config.\n config.loggingConfig = namespaceConfig.sr_logging_config;\n\n if (samplingConfig || privacyConfig || targetingConfig) {\n sessionReplayRemoteConfig = {};\n if (samplingConfig) {\n sessionReplayRemoteConfig.sr_sampling_config = samplingConfig;\n }\n if (privacyConfig) {\n sessionReplayRemoteConfig.sr_privacy_config = privacyConfig;\n }\n if (targetingConfig) {\n sessionReplayRemoteConfig.sr_targeting_config = targetingConfig;\n }\n }\n\n // Resolve on first callback\n resolve();\n },\n );\n });\n } catch (error) {\n this.localConfig.loggerProvider.error('Failed to generate joined config: ', error);\n config.captureEnabled = false;\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: undefined,\n };\n }\n\n if (!sessionReplayRemoteConfig) {\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: sessionReplayRemoteConfig,\n };\n }\n\n const {\n sr_sampling_config: samplingConfig,\n sr_privacy_config: remotePrivacyConfig,\n sr_targeting_config: targetingConfig,\n } = sessionReplayRemoteConfig;\n if (samplingConfig && Object.keys(samplingConfig).length > 0) {\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'capture_enabled')) {\n config.captureEnabled = samplingConfig.capture_enabled;\n } else {\n config.captureEnabled = false;\n }\n\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'sample_rate')) {\n config.sampleRate = samplingConfig.sample_rate;\n }\n } else {\n // If config API response was valid (ie 200), but no config returned, assume that\n // customer has not yet set up config, and use sample rate from SDK options,\n // allowing for immediate replay capture\n config.captureEnabled = true;\n this.localConfig.loggerProvider.debug(\n 'Remote config successfully fetched, but no values set for project, Session Replay capture enabled.',\n );\n }\n\n // Remote config join acts somewhat like a left join between the remote and the local\n // config. That is, remote config has precedence over local values as with sampling.\n // However, non conflicting values will be added to the lists.\n // Here's an example to illustrate:\n //\n // Remote config: {'.selector1': 'MASK', '.selector2': 'UNMASK'}\n // Local config: {'.selector1': 'UNMASK', '.selector3': 'MASK'}\n //\n // Resolved config: {'.selector1': 'MASK', '.selector2': 'UNMASK', '.selector3': 'MASK'}\n // config.privacyConfig = {\n // ...(config.privacyConfig ?? {}),\n // ...remotePrivacyConfig,\n // };\n\n if (remotePrivacyConfig) {\n const localPrivacyConfig: PrivacyConfig = config.privacyConfig ?? {};\n\n const joinedPrivacyConfig: Required<PrivacyConfig> & { blockSelector: string[] } = {\n defaultMaskLevel: remotePrivacyConfig.defaultMaskLevel ?? localPrivacyConfig.defaultMaskLevel ?? 'medium',\n blockSelector: [],\n maskSelector: [],\n unmaskSelector: [],\n maskAttributes: [\n ...new Set([...(localPrivacyConfig.maskAttributes ?? []), ...(remotePrivacyConfig.maskAttributes ?? [])]),\n ],\n urlMaskLevels: [...(remotePrivacyConfig.urlMaskLevels ?? []), ...(localPrivacyConfig.urlMaskLevels ?? [])],\n };\n\n const privacyConfigSelectorMap = (privacyConfig: PrivacyConfig): Record<string, 'mask' | 'unmask' | 'block'> => {\n const selectorMap: Record<string, 'mask' | 'unmask' | 'block'> = {};\n if (typeof privacyConfig.blockSelector === 'string') {\n privacyConfig.blockSelector = [privacyConfig.blockSelector];\n }\n\n for (const selector of privacyConfig.blockSelector ?? []) {\n selectorMap[selector] = 'block';\n }\n for (const selector of privacyConfig.maskSelector ?? []) {\n selectorMap[selector] = 'mask';\n }\n for (const selector of privacyConfig.unmaskSelector ?? []) {\n selectorMap[selector] = 'unmask';\n }\n return selectorMap;\n };\n\n const selectorMap: Record<string, 'mask' | 'unmask' | 'block'> = {\n ...privacyConfigSelectorMap(localPrivacyConfig),\n ...privacyConfigSelectorMap(remotePrivacyConfig),\n };\n\n for (const [selector, selectorType] of Object.entries(selectorMap)) {\n if (selectorType === 'mask') {\n joinedPrivacyConfig.maskSelector.push(selector);\n } else if (selectorType === 'block') {\n joinedPrivacyConfig.blockSelector.push(selector);\n } else if (selectorType === 'unmask') {\n joinedPrivacyConfig.unmaskSelector.push(selector);\n }\n }\n\n config.privacyConfig = removeInvalidSelectorsFromPrivacyConfig(\n joinedPrivacyConfig,\n this.localConfig.loggerProvider,\n );\n }\n\n if (targetingConfig && Object.keys(targetingConfig).length > 0) {\n config.targetingConfig = targetingConfig;\n }\n\n this.localConfig.loggerProvider.debug(\n JSON.stringify({ name: 'session replay joined config', config: getDebugConfig(config) }, null, 2),\n );\n\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: sessionReplayRemoteConfig,\n };\n }\n}\n\nexport const createSessionReplayJoinedConfigGenerator = async (apiKey: string, options: SessionReplayOptions) => {\n const localConfig = new SessionReplayLocalConfig(apiKey, options);\n\n const remoteConfigClient = new RemoteConfigClient(\n apiKey,\n localConfig.loggerProvider,\n localConfig.serverZone,\n options.configServerUrl,\n );\n\n return new SessionReplayJoinedConfigGenerator(remoteConfigClient, localConfig);\n};\n"]}
|
|
1
|
+
{"version":3,"file":"joined-config.js","sourceRoot":"","sources":["../../../src/config/joined-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { ILogger, IRemoteConfigClient, RemoteConfigClient, RemoteConfig, Source } from '@amplitude/analytics-core';\nimport { getDebugConfig } from '../helpers';\nimport { SessionReplayOptions } from '../typings/session-replay';\nimport { SessionReplayLocalConfig } from './local-config';\nimport {\n SessionReplayLocalConfig as ISessionReplayLocalConfig,\n PrivacyConfig,\n SessionReplayConfigs,\n SessionReplayJoinedConfig,\n SessionReplayRemoteConfig,\n} from './types';\n\n// Budget for waiting on the remote config response before falling back to the local cache.\n// The inner fetch in analytics-core uses a 1000ms per-attempt AbortController timeout with\n// up to 3 retries — but this outer timeout is the only thing the SR plugin waits on, so\n// only the first attempt can possibly complete in time (attempt 2 starts at ~1000-1333ms\n// after backoff jitter and runs to ~2000-2333ms, well past any reasonable outer budget).\n// 1500ms is set above the 1000ms inner abort to avoid a tie with the inner cutoff and\n// give the first attempt's resolution path room to finish; everything else falls through\n// to the cache fallback. See SR-4234: prefer remote over a stale cache that would\n// otherwise win the synchronous race in 'all' mode.\nconst REMOTE_CONFIG_TIMEOUT_MS = 1500;\n\nexport const removeInvalidSelectorsFromPrivacyConfig = (privacyConfig: PrivacyConfig, loggerProvider: ILogger) => {\n // This allows us to not search the DOM.\n const fragment = document.createDocumentFragment();\n\n const dropInvalidSelectors = (selectors: string[] | string = []): string[] | undefined => {\n if (typeof selectors === 'string') {\n selectors = [selectors];\n }\n selectors = selectors.filter((selector: string) => {\n try {\n fragment.querySelector(selector);\n } catch {\n loggerProvider.warn(`[session-replay-browser] omitting selector \"${selector}\" because it is invalid`);\n return false;\n }\n return true;\n });\n if (selectors.length === 0) {\n return undefined;\n }\n return selectors;\n };\n privacyConfig.blockSelector = dropInvalidSelectors(privacyConfig.blockSelector);\n privacyConfig.maskSelector = dropInvalidSelectors(privacyConfig.maskSelector);\n privacyConfig.unmaskSelector = dropInvalidSelectors(privacyConfig.unmaskSelector);\n return privacyConfig;\n};\nexport class SessionReplayJoinedConfigGenerator {\n private readonly localConfig: ISessionReplayLocalConfig;\n private readonly remoteConfigClient: IRemoteConfigClient;\n\n constructor(remoteConfigClient: IRemoteConfigClient, localConfig: ISessionReplayLocalConfig) {\n this.localConfig = localConfig;\n this.remoteConfigClient = remoteConfigClient;\n }\n\n async generateJoinedConfig(): Promise<SessionReplayConfigs> {\n const config: SessionReplayJoinedConfig = { ...this.localConfig };\n // Special case here as optOut is implemented via getter/setter\n config.optOut = this.localConfig.optOut;\n // We always want captureEnabled to be true, unless there's an override\n // in the remote config.\n config.captureEnabled = true;\n let sessionReplayRemoteConfig: SessionReplayRemoteConfig | undefined;\n\n try {\n // Subscribe with a timeout so the SDK prefers the remote response and only falls back\n // to cache after the budget elapses. 'all' mode would race a synchronous cache read\n // against the network and resolve on whichever fires first — cache always wins, so a\n // stale cache silently overrides the live config (SR-4234).\n await new Promise<void>((resolve, reject) => {\n this.remoteConfigClient.subscribe(\n 'configs.sessionReplay',\n { timeout: REMOTE_CONFIG_TIMEOUT_MS },\n (remoteConfig: RemoteConfig | null, source: Source) => {\n this.localConfig.loggerProvider.debug(\n `Session Replay remote configuration received from ${source}:`,\n JSON.stringify(remoteConfig, null, 2),\n );\n\n if (!remoteConfig) {\n reject(new Error('No remote config received'));\n return;\n }\n\n // remoteConfig is already filtered to 'configs.sessionReplay' namespace\n const namespaceConfig = remoteConfig as SessionReplayRemoteConfig;\n const samplingConfig = namespaceConfig.sr_sampling_config;\n const privacyConfig = namespaceConfig.sr_privacy_config;\n const targetingConfig = namespaceConfig.sr_targeting_config;\n\n const ugcFilterRules = config.interactionConfig?.ugcFilterRules;\n // This is intentionally forced to only be set through the remote config.\n config.interactionConfig = namespaceConfig.sr_interaction_config;\n if (config.interactionConfig && ugcFilterRules) {\n config.interactionConfig.ugcFilterRules = ugcFilterRules;\n }\n\n // This is intentionally forced to only be set through the remote config.\n config.loggingConfig = namespaceConfig.sr_logging_config;\n\n if (samplingConfig || privacyConfig || targetingConfig) {\n sessionReplayRemoteConfig = {};\n if (samplingConfig) {\n sessionReplayRemoteConfig.sr_sampling_config = samplingConfig;\n }\n if (privacyConfig) {\n sessionReplayRemoteConfig.sr_privacy_config = privacyConfig;\n }\n if (targetingConfig) {\n sessionReplayRemoteConfig.sr_targeting_config = targetingConfig;\n }\n }\n\n resolve();\n },\n );\n });\n } catch (error) {\n this.localConfig.loggerProvider.error('Failed to generate joined config: ', error);\n config.captureEnabled = false;\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: undefined,\n };\n }\n\n if (!sessionReplayRemoteConfig) {\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: sessionReplayRemoteConfig,\n };\n }\n\n const {\n sr_sampling_config: samplingConfig,\n sr_privacy_config: remotePrivacyConfig,\n sr_targeting_config: targetingConfig,\n } = sessionReplayRemoteConfig;\n if (samplingConfig && Object.keys(samplingConfig).length > 0) {\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'capture_enabled')) {\n config.captureEnabled = samplingConfig.capture_enabled;\n } else {\n config.captureEnabled = false;\n }\n\n if (Object.prototype.hasOwnProperty.call(samplingConfig, 'sample_rate')) {\n config.sampleRate = samplingConfig.sample_rate;\n }\n } else {\n // If config API response was valid (ie 200), but no config returned, assume that\n // customer has not yet set up config, and use sample rate from SDK options,\n // allowing for immediate replay capture\n config.captureEnabled = true;\n this.localConfig.loggerProvider.debug(\n 'Remote config successfully fetched, but no values set for project, Session Replay capture enabled.',\n );\n }\n\n // Remote config join acts somewhat like a left join between the remote and the local\n // config. That is, remote config has precedence over local values as with sampling.\n // However, non conflicting values will be added to the lists.\n // Here's an example to illustrate:\n //\n // Remote config: {'.selector1': 'MASK', '.selector2': 'UNMASK'}\n // Local config: {'.selector1': 'UNMASK', '.selector3': 'MASK'}\n //\n // Resolved config: {'.selector1': 'MASK', '.selector2': 'UNMASK', '.selector3': 'MASK'}\n // config.privacyConfig = {\n // ...(config.privacyConfig ?? {}),\n // ...remotePrivacyConfig,\n // };\n\n if (remotePrivacyConfig) {\n const localPrivacyConfig: PrivacyConfig = config.privacyConfig ?? {};\n\n const joinedPrivacyConfig: Required<PrivacyConfig> & { blockSelector: string[] } = {\n defaultMaskLevel: remotePrivacyConfig.defaultMaskLevel ?? localPrivacyConfig.defaultMaskLevel ?? 'medium',\n blockSelector: [],\n maskSelector: [],\n unmaskSelector: [],\n maskAttributes: [\n ...new Set([...(localPrivacyConfig.maskAttributes ?? []), ...(remotePrivacyConfig.maskAttributes ?? [])]),\n ],\n urlMaskLevels: [...(remotePrivacyConfig.urlMaskLevels ?? []), ...(localPrivacyConfig.urlMaskLevels ?? [])],\n };\n\n const privacyConfigSelectorMap = (privacyConfig: PrivacyConfig): Record<string, 'mask' | 'unmask' | 'block'> => {\n const selectorMap: Record<string, 'mask' | 'unmask' | 'block'> = {};\n if (typeof privacyConfig.blockSelector === 'string') {\n privacyConfig.blockSelector = [privacyConfig.blockSelector];\n }\n\n for (const selector of privacyConfig.blockSelector ?? []) {\n selectorMap[selector] = 'block';\n }\n for (const selector of privacyConfig.maskSelector ?? []) {\n selectorMap[selector] = 'mask';\n }\n for (const selector of privacyConfig.unmaskSelector ?? []) {\n selectorMap[selector] = 'unmask';\n }\n return selectorMap;\n };\n\n const selectorMap: Record<string, 'mask' | 'unmask' | 'block'> = {\n ...privacyConfigSelectorMap(localPrivacyConfig),\n ...privacyConfigSelectorMap(remotePrivacyConfig),\n };\n\n for (const [selector, selectorType] of Object.entries(selectorMap)) {\n if (selectorType === 'mask') {\n joinedPrivacyConfig.maskSelector.push(selector);\n } else if (selectorType === 'block') {\n joinedPrivacyConfig.blockSelector.push(selector);\n } else if (selectorType === 'unmask') {\n joinedPrivacyConfig.unmaskSelector.push(selector);\n }\n }\n\n config.privacyConfig = removeInvalidSelectorsFromPrivacyConfig(\n joinedPrivacyConfig,\n this.localConfig.loggerProvider,\n );\n }\n\n if (targetingConfig && Object.keys(targetingConfig).length > 0) {\n config.targetingConfig = targetingConfig;\n }\n\n this.localConfig.loggerProvider.debug(\n JSON.stringify({ name: 'session replay joined config', config: getDebugConfig(config) }, null, 2),\n );\n\n return {\n localConfig: this.localConfig,\n joinedConfig: config,\n remoteConfig: sessionReplayRemoteConfig,\n };\n }\n}\n\nexport const createSessionReplayJoinedConfigGenerator = async (apiKey: string, options: SessionReplayOptions) => {\n const localConfig = new SessionReplayLocalConfig(apiKey, options);\n\n const remoteConfigClient = new RemoteConfigClient(\n apiKey,\n localConfig.loggerProvider,\n localConfig.serverZone,\n options.configServerUrl,\n );\n\n return new SessionReplayJoinedConfigGenerator(remoteConfigClient, localConfig);\n};\n"]}
|
package/lib/cjs/constants.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ export declare const KB_SIZE = 1024;
|
|
|
29
29
|
export declare const MAX_URL_LENGTH = 1000;
|
|
30
30
|
export declare const RETRY_TIMEOUT_MS = 1000;
|
|
31
31
|
export declare const MAX_KEEPALIVE_BYTES: number;
|
|
32
|
+
export declare const EVENT_SKIPPED_HEADER = "X-Session-Replay-Event-Skipped";
|
|
33
|
+
export declare const EVENT_SKIP_CODE_THROTTLED = "429";
|
|
34
|
+
export declare const EVENT_SKIP_CODE_INVALID_RANGE = "4004";
|
|
35
|
+
export declare const EVENT_SKIP_CODE_CAPTURE_DISABLED = "4005";
|
|
36
|
+
export declare const THROTTLED_FLUSH_PAUSE_MS = 60000;
|
|
32
37
|
export declare const CROSS_ORIGIN_IFRAME_MESSAGE_TYPE = "amplitude-sr-iframe";
|
|
33
38
|
export declare enum CustomRRwebEvent {
|
|
34
39
|
GET_SR_PROPS = "get-sr-props",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEzE,eAAO,MAAM,6BAA6B,gBAAgB,CAAC;AAE3D,eAAO,MAAM,+BAA+B,QAAuD,CAAC;AACpG,eAAO,MAAM,2BAA2B,kBAAkB,CAAC;AAC3D,eAAO,MAAM,yBAAyB,gBAAgB,CAAC;AACvD,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AACjD,eAAO,MAAM,0BAA0B;;CAAoB,CAAC;AAC5D,eAAO,MAAM,mCAAmC,OAAO,CAAC;AAExD,eAAO,MAAM,6BAA6B,QAA0D,CAAC;AAErG,eAAO,MAAM,WAAW,cAAc,CAAC;AACvC,eAAO,MAAM,eAAe,aAAa,CAAC;AAC1C,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAC9C,eAAO,MAAM,yBAAyB,mDAAmD,CAAC;AAC1F,eAAO,MAAM,qBAAqB,sDAAsD,CAAC;AACzF,eAAO,MAAM,0BAA0B,yDAAyD,CAAC;AACjG,eAAO,MAAM,cAAc,QAAsC,CAAC;AAIlE,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAI3C,eAAO,MAAM,qBAAqB,QAAc,CAAC;AAIjD,eAAO,MAAM,6BAA6B,QAAqB,CAAC;AAChE,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,YAAY,QAAY,CAAC;AACtC,eAAO,MAAM,sBAAsB,QAA0B,CAAC;AAC9D,eAAO,MAAM,OAAO,OAAO,CAAC;AAC5B,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AACrC,eAAO,MAAM,mBAAmB,QAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEzE,eAAO,MAAM,6BAA6B,gBAAgB,CAAC;AAE3D,eAAO,MAAM,+BAA+B,QAAuD,CAAC;AACpG,eAAO,MAAM,2BAA2B,kBAAkB,CAAC;AAC3D,eAAO,MAAM,yBAAyB,gBAAgB,CAAC;AACvD,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AACjD,eAAO,MAAM,0BAA0B;;CAAoB,CAAC;AAC5D,eAAO,MAAM,mCAAmC,OAAO,CAAC;AAExD,eAAO,MAAM,6BAA6B,QAA0D,CAAC;AAErG,eAAO,MAAM,WAAW,cAAc,CAAC;AACvC,eAAO,MAAM,eAAe,aAAa,CAAC;AAC1C,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAC9C,eAAO,MAAM,yBAAyB,mDAAmD,CAAC;AAC1F,eAAO,MAAM,qBAAqB,sDAAsD,CAAC;AACzF,eAAO,MAAM,0BAA0B,yDAAyD,CAAC;AACjG,eAAO,MAAM,cAAc,QAAsC,CAAC;AAIlE,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAI3C,eAAO,MAAM,qBAAqB,QAAc,CAAC;AAIjD,eAAO,MAAM,6BAA6B,QAAqB,CAAC;AAChE,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,YAAY,QAAY,CAAC;AACtC,eAAO,MAAM,sBAAsB,QAA0B,CAAC;AAC9D,eAAO,MAAM,OAAO,OAAO,CAAC;AAC5B,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AACrC,eAAO,MAAM,mBAAmB,QAAY,CAAC;AAK7C,eAAO,MAAM,oBAAoB,mCAAmC,CAAC;AACrE,eAAO,MAAM,yBAAyB,QAAQ,CAAC;AAC/C,eAAO,MAAM,6BAA6B,SAAS,CAAC;AACpD,eAAO,MAAM,gCAAgC,SAAS,CAAC;AAEvD,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,eAAO,MAAM,gCAAgC,wBAAwB,CAAC;AAEtE,oBAAY,gBAAgB;IAC1B,YAAY,iBAAiB;IAC7B,UAAU,eAAe;IACzB,aAAa,kBAAkB;IAC/B,QAAQ,aAAa;IACrB,kBAAkB,uBAAuB;CAC1C"}
|
package/lib/cjs/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CustomRRwebEvent = exports.CROSS_ORIGIN_IFRAME_MESSAGE_TYPE = exports.MAX_KEEPALIVE_BYTES = exports.RETRY_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.KB_SIZE = exports.MAX_IDB_STORAGE_LENGTH = exports.MAX_INTERVAL = exports.MIN_INTERVAL = exports.INTERACTION_MAX_INTERVAL = exports.INTERACTION_MIN_INTERVAL = exports.WAF_PAYLOAD_TOO_LARGE_PATTERN = exports.MAX_SINGLE_EVENT_SIZE = exports.MAX_EVENT_LIST_SIZE = exports.STORAGE_PREFIX = exports.SESSION_REPLAY_STAGING_URL = exports.SESSION_REPLAY_EU_URL = exports.SESSION_REPLAY_SERVER_URL = exports.UNMASK_TEXT_CLASS = exports.MASK_TEXT_CLASS = exports.BLOCK_CLASS = exports.SESSION_REPLAY_DEBUG_PROPERTY = exports.DEFAULT_URL_CHANGE_POLLING_INTERVAL = exports.DEFAULT_PERFORMANCE_CONFIG = exports.DEFAULT_SERVER_ZONE = exports.DEFAULT_SAMPLE_RATE = exports.DEFAULT_SESSION_END_EVENT = exports.DEFAULT_SESSION_START_EVENT = exports.DEFAULT_SESSION_REPLAY_PROPERTY = exports.DEFAULT_EVENT_PROPERTY_PREFIX = void 0;
|
|
3
|
+
exports.CustomRRwebEvent = exports.CROSS_ORIGIN_IFRAME_MESSAGE_TYPE = exports.THROTTLED_FLUSH_PAUSE_MS = exports.EVENT_SKIP_CODE_CAPTURE_DISABLED = exports.EVENT_SKIP_CODE_INVALID_RANGE = exports.EVENT_SKIP_CODE_THROTTLED = exports.EVENT_SKIPPED_HEADER = exports.MAX_KEEPALIVE_BYTES = exports.RETRY_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.KB_SIZE = exports.MAX_IDB_STORAGE_LENGTH = exports.MAX_INTERVAL = exports.MIN_INTERVAL = exports.INTERACTION_MAX_INTERVAL = exports.INTERACTION_MIN_INTERVAL = exports.WAF_PAYLOAD_TOO_LARGE_PATTERN = exports.MAX_SINGLE_EVENT_SIZE = exports.MAX_EVENT_LIST_SIZE = exports.STORAGE_PREFIX = exports.SESSION_REPLAY_STAGING_URL = exports.SESSION_REPLAY_EU_URL = exports.SESSION_REPLAY_SERVER_URL = exports.UNMASK_TEXT_CLASS = exports.MASK_TEXT_CLASS = exports.BLOCK_CLASS = exports.SESSION_REPLAY_DEBUG_PROPERTY = exports.DEFAULT_URL_CHANGE_POLLING_INTERVAL = exports.DEFAULT_PERFORMANCE_CONFIG = exports.DEFAULT_SERVER_ZONE = exports.DEFAULT_SAMPLE_RATE = exports.DEFAULT_SESSION_END_EVENT = exports.DEFAULT_SESSION_START_EVENT = exports.DEFAULT_SESSION_REPLAY_PROPERTY = exports.DEFAULT_EVENT_PROPERTY_PREFIX = void 0;
|
|
4
4
|
var analytics_core_1 = require("@amplitude/analytics-core");
|
|
5
5
|
exports.DEFAULT_EVENT_PROPERTY_PREFIX = '[Amplitude]';
|
|
6
6
|
exports.DEFAULT_SESSION_REPLAY_PROPERTY = "".concat(exports.DEFAULT_EVENT_PROPERTY_PREFIX, " Session Replay ID");
|
|
@@ -39,6 +39,15 @@ exports.KB_SIZE = 1024;
|
|
|
39
39
|
exports.MAX_URL_LENGTH = 1000;
|
|
40
40
|
exports.RETRY_TIMEOUT_MS = 1000;
|
|
41
41
|
exports.MAX_KEEPALIVE_BYTES = 64 * 1024; // browser keepalive budget shared with sendBeacon
|
|
42
|
+
// Server returns 200 + this header for "no-retry" drops (throttle / capture disabled / out-of-range).
|
|
43
|
+
// See projects/sessionreplay/sessionreplay-ingestion/.../SessionReplayError.java.
|
|
44
|
+
// Header value is the numeric error code as a string.
|
|
45
|
+
exports.EVENT_SKIPPED_HEADER = 'X-Session-Replay-Event-Skipped';
|
|
46
|
+
exports.EVENT_SKIP_CODE_THROTTLED = '429';
|
|
47
|
+
exports.EVENT_SKIP_CODE_INVALID_RANGE = '4004';
|
|
48
|
+
exports.EVENT_SKIP_CODE_CAPTURE_DISABLED = '4005';
|
|
49
|
+
// How long to pause the flush schedule after the server signals a throttle.
|
|
50
|
+
exports.THROTTLED_FLUSH_PAUSE_MS = 60000;
|
|
42
51
|
exports.CROSS_ORIGIN_IFRAME_MESSAGE_TYPE = 'amplitude-sr-iframe';
|
|
43
52
|
var CustomRRwebEvent;
|
|
44
53
|
(function (CustomRRwebEvent) {
|
package/lib/cjs/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":";;;AAAA,4DAAyE;AAE5D,QAAA,6BAA6B,GAAG,aAAa,CAAC;AAE9C,QAAA,+BAA+B,GAAG,UAAG,qCAA6B,uBAAoB,CAAC;AACvF,QAAA,2BAA2B,GAAG,eAAe,CAAC;AAC9C,QAAA,yBAAyB,GAAG,aAAa,CAAC;AAC1C,QAAA,mBAAmB,GAAG,CAAC,CAAC;AACxB,QAAA,mBAAmB,GAAG,2BAAU,CAAC,EAAE,CAAC;AACpC,QAAA,0BAA0B,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC/C,QAAA,mCAAmC,GAAG,IAAI,CAAC;AAE3C,QAAA,6BAA6B,GAAG,UAAG,qCAA6B,0BAAuB,CAAC;AAExF,QAAA,WAAW,GAAG,WAAW,CAAC;AAC1B,QAAA,eAAe,GAAG,UAAU,CAAC;AAC7B,QAAA,iBAAiB,GAAG,YAAY,CAAC;AACjC,QAAA,yBAAyB,GAAG,gDAAgD,CAAC;AAC7E,QAAA,qBAAqB,GAAG,mDAAmD,CAAC;AAC5E,QAAA,0BAA0B,GAAG,sDAAsD,CAAC;AACpF,QAAA,cAAc,GAAG,UAAG,iCAAgB,mBAAgB,CAAC;AAClE,qFAAqF;AACrF,qFAAqF;AACrF,wFAAwF;AAC3E,QAAA,mBAAmB,GAAG,MAAO,CAAC;AAC3C,6FAA6F;AAC7F,8FAA8F;AAC9F,2DAA2D;AAC9C,QAAA,qBAAqB,GAAG,CAAC,GAAG,OAAO,CAAC;AACjD,gFAAgF;AAChF,sFAAsF;AACtF,mFAAmF;AACtE,QAAA,6BAA6B,GAAG,kBAAkB,CAAC;AACnD,QAAA,wBAAwB,GAAG,KAAM,CAAC,CAAC,aAAa;AAChD,QAAA,wBAAwB,GAAG,KAAM,CAAC,CAAC,WAAW;AAC9C,QAAA,YAAY,GAAG,GAAG,CAAC,CAAC,SAAS;AAC7B,QAAA,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AACvC,QAAA,sBAAsB,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,SAAS;AAC3D,QAAA,OAAO,GAAG,IAAI,CAAC;AACf,QAAA,cAAc,GAAG,IAAI,CAAC;AACtB,QAAA,gBAAgB,GAAG,IAAI,CAAC;AACxB,QAAA,mBAAmB,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,kDAAkD;
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":";;;AAAA,4DAAyE;AAE5D,QAAA,6BAA6B,GAAG,aAAa,CAAC;AAE9C,QAAA,+BAA+B,GAAG,UAAG,qCAA6B,uBAAoB,CAAC;AACvF,QAAA,2BAA2B,GAAG,eAAe,CAAC;AAC9C,QAAA,yBAAyB,GAAG,aAAa,CAAC;AAC1C,QAAA,mBAAmB,GAAG,CAAC,CAAC;AACxB,QAAA,mBAAmB,GAAG,2BAAU,CAAC,EAAE,CAAC;AACpC,QAAA,0BAA0B,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC/C,QAAA,mCAAmC,GAAG,IAAI,CAAC;AAE3C,QAAA,6BAA6B,GAAG,UAAG,qCAA6B,0BAAuB,CAAC;AAExF,QAAA,WAAW,GAAG,WAAW,CAAC;AAC1B,QAAA,eAAe,GAAG,UAAU,CAAC;AAC7B,QAAA,iBAAiB,GAAG,YAAY,CAAC;AACjC,QAAA,yBAAyB,GAAG,gDAAgD,CAAC;AAC7E,QAAA,qBAAqB,GAAG,mDAAmD,CAAC;AAC5E,QAAA,0BAA0B,GAAG,sDAAsD,CAAC;AACpF,QAAA,cAAc,GAAG,UAAG,iCAAgB,mBAAgB,CAAC;AAClE,qFAAqF;AACrF,qFAAqF;AACrF,wFAAwF;AAC3E,QAAA,mBAAmB,GAAG,MAAO,CAAC;AAC3C,6FAA6F;AAC7F,8FAA8F;AAC9F,2DAA2D;AAC9C,QAAA,qBAAqB,GAAG,CAAC,GAAG,OAAO,CAAC;AACjD,gFAAgF;AAChF,sFAAsF;AACtF,mFAAmF;AACtE,QAAA,6BAA6B,GAAG,kBAAkB,CAAC;AACnD,QAAA,wBAAwB,GAAG,KAAM,CAAC,CAAC,aAAa;AAChD,QAAA,wBAAwB,GAAG,KAAM,CAAC,CAAC,WAAW;AAC9C,QAAA,YAAY,GAAG,GAAG,CAAC,CAAC,SAAS;AAC7B,QAAA,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AACvC,QAAA,sBAAsB,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,SAAS;AAC3D,QAAA,OAAO,GAAG,IAAI,CAAC;AACf,QAAA,cAAc,GAAG,IAAI,CAAC;AACtB,QAAA,gBAAgB,GAAG,IAAI,CAAC;AACxB,QAAA,mBAAmB,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,kDAAkD;AAEhG,sGAAsG;AACtG,kFAAkF;AAClF,sDAAsD;AACzC,QAAA,oBAAoB,GAAG,gCAAgC,CAAC;AACxD,QAAA,yBAAyB,GAAG,KAAK,CAAC;AAClC,QAAA,6BAA6B,GAAG,MAAM,CAAC;AACvC,QAAA,gCAAgC,GAAG,MAAM,CAAC;AACvD,4EAA4E;AAC/D,QAAA,wBAAwB,GAAG,KAAM,CAAC;AAElC,QAAA,gCAAgC,GAAG,qBAAqB,CAAC;AAEtE,IAAY,gBAMX;AAND,WAAY,gBAAgB;IAC1B,iDAA6B,CAAA;IAC7B,6CAAyB,CAAA;IACzB,mDAA+B,CAAA;IAC/B,yCAAqB,CAAA;IACrB,6DAAyC,CAAA;AAC3C,CAAC,EANW,gBAAgB,GAAhB,wBAAgB,KAAhB,wBAAgB,QAM3B","sourcesContent":["import { AMPLITUDE_PREFIX, ServerZone } from '@amplitude/analytics-core';\n\nexport const DEFAULT_EVENT_PROPERTY_PREFIX = '[Amplitude]';\n\nexport const DEFAULT_SESSION_REPLAY_PROPERTY = `${DEFAULT_EVENT_PROPERTY_PREFIX} Session Replay ID`;\nexport const DEFAULT_SESSION_START_EVENT = 'session_start';\nexport const DEFAULT_SESSION_END_EVENT = 'session_end';\nexport const DEFAULT_SAMPLE_RATE = 0;\nexport const DEFAULT_SERVER_ZONE = ServerZone.US;\nexport const DEFAULT_PERFORMANCE_CONFIG = { enabled: true };\nexport const DEFAULT_URL_CHANGE_POLLING_INTERVAL = 1000;\n\nexport const SESSION_REPLAY_DEBUG_PROPERTY = `${DEFAULT_EVENT_PROPERTY_PREFIX} Session Replay Debug`;\n\nexport const BLOCK_CLASS = 'amp-block';\nexport const MASK_TEXT_CLASS = 'amp-mask';\nexport const UNMASK_TEXT_CLASS = 'amp-unmask';\nexport const SESSION_REPLAY_SERVER_URL = 'https://api-sr.amplitude.com/sessions/v2/track';\nexport const SESSION_REPLAY_EU_URL = 'https://api-sr.eu.amplitude.com/sessions/v2/track';\nexport const SESSION_REPLAY_STAGING_URL = 'https://api-sr.stag2.amplitude.com/sessions/v2/track';\nexport const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;\n// Reduced from 1,000,000 to leave headroom for double-JSON-encoding overhead and the\n// uncompressed fallback path. The HTTP body is ~10-30% larger than raw string length\n// because events are re-serialized inside the { version, events } wrapper at send time.\nexport const MAX_EVENT_LIST_SIZE = 700_000;\n// 9 MB UTF-8 bytes — just under the server's 10 MB per-event threshold. Compared against the\n// UTF-8 byte length of the serialized event (via Blob/TextEncoder), not the JS string length,\n// so multi-byte payloads (CJK, emoji) are gated correctly.\nexport const MAX_SINGLE_EVENT_SIZE = 9 * 1000000;\n// WAF rejects oversized compressed payloads with a body containing wording like\n// \"Payload exceeds the maximum allowed size of 10MB\". Match loosely so vendor wording\n// tweaks (rule updates, capitalization, etc.) don't silently disable bisect-retry.\nexport const WAF_PAYLOAD_TOO_LARGE_PATTERN = /payload.*exceed/i;\nexport const INTERACTION_MIN_INTERVAL = 30_000; // 30 seconds\nexport const INTERACTION_MAX_INTERVAL = 60_000; // 1 minute\nexport const MIN_INTERVAL = 500; // 500 ms\nexport const MAX_INTERVAL = 10 * 1000; // 10 seconds\nexport const MAX_IDB_STORAGE_LENGTH = 1000 * 60 * 60 * 24 * 3; // 3 days\nexport const KB_SIZE = 1024;\nexport const MAX_URL_LENGTH = 1000;\nexport const RETRY_TIMEOUT_MS = 1000;\nexport const MAX_KEEPALIVE_BYTES = 64 * 1024; // browser keepalive budget shared with sendBeacon\n\n// Server returns 200 + this header for \"no-retry\" drops (throttle / capture disabled / out-of-range).\n// See projects/sessionreplay/sessionreplay-ingestion/.../SessionReplayError.java.\n// Header value is the numeric error code as a string.\nexport const EVENT_SKIPPED_HEADER = 'X-Session-Replay-Event-Skipped';\nexport const EVENT_SKIP_CODE_THROTTLED = '429';\nexport const EVENT_SKIP_CODE_INVALID_RANGE = '4004';\nexport const EVENT_SKIP_CODE_CAPTURE_DISABLED = '4005';\n// How long to pause the flush schedule after the server signals a throttle.\nexport const THROTTLED_FLUSH_PAUSE_MS = 60_000;\n\nexport const CROSS_ORIGIN_IFRAME_MESSAGE_TYPE = 'amplitude-sr-iframe';\n\nexport enum CustomRRwebEvent {\n GET_SR_PROPS = 'get-sr-props',\n DEBUG_INFO = 'debug-info',\n FETCH_REQUEST = 'fetch-request',\n METADATA = 'metadata',\n TARGETING_DECISION = 'targeting-decision',\n}\n"]}
|
package/lib/cjs/messages.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export declare const MAX_RETRIES_EXCEEDED_MESSAGE = "Session replay event batch
|
|
|
4
4
|
export declare const STORAGE_FAILURE = "Failed to store session replay events in IndexedDB";
|
|
5
5
|
export declare const MISSING_DEVICE_ID_MESSAGE = "Session replay event batch not sent due to missing device ID";
|
|
6
6
|
export declare const MISSING_API_KEY_MESSAGE = "Session replay event batch not sent due to missing api key";
|
|
7
|
+
export declare const SESSION_KILLED_MESSAGE = "Session replay event batch dropped: server signalled capture disabled or session out of valid range for this session";
|
|
7
8
|
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/messages.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,8BAA8B,CAAC;AACpE,eAAO,MAAM,gCAAgC,iDAAiD,CAAC;AAC/F,eAAO,MAAM,4BAA4B,oEAAoE,CAAC;AAC9G,eAAO,MAAM,eAAe,uDAAuD,CAAC;AACpF,eAAO,MAAM,yBAAyB,iEAAiE,CAAC;AACxG,eAAO,MAAM,uBAAuB,+DAA+D,CAAC"}
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/messages.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,8BAA8B,CAAC;AACpE,eAAO,MAAM,gCAAgC,iDAAiD,CAAC;AAC/F,eAAO,MAAM,4BAA4B,oEAAoE,CAAC;AAC9G,eAAO,MAAM,eAAe,uDAAuD,CAAC;AACpF,eAAO,MAAM,yBAAyB,iEAAiE,CAAC;AACxG,eAAO,MAAM,uBAAuB,+DAA+D,CAAC;AACpG,eAAO,MAAM,sBAAsB,yHACqF,CAAC"}
|
package/lib/cjs/messages.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MISSING_API_KEY_MESSAGE = exports.MISSING_DEVICE_ID_MESSAGE = exports.STORAGE_FAILURE = exports.MAX_RETRIES_EXCEEDED_MESSAGE = exports.UNEXPECTED_NETWORK_ERROR_MESSAGE = exports.UNEXPECTED_ERROR_MESSAGE = void 0;
|
|
3
|
+
exports.SESSION_KILLED_MESSAGE = exports.MISSING_API_KEY_MESSAGE = exports.MISSING_DEVICE_ID_MESSAGE = exports.STORAGE_FAILURE = exports.MAX_RETRIES_EXCEEDED_MESSAGE = exports.UNEXPECTED_NETWORK_ERROR_MESSAGE = exports.UNEXPECTED_ERROR_MESSAGE = void 0;
|
|
4
4
|
exports.UNEXPECTED_ERROR_MESSAGE = 'Unexpected error occurred';
|
|
5
5
|
exports.UNEXPECTED_NETWORK_ERROR_MESSAGE = 'Network error occurred, event batch rejected';
|
|
6
6
|
exports.MAX_RETRIES_EXCEEDED_MESSAGE = 'Session replay event batch rejected due to exceeded retry count';
|
|
7
7
|
exports.STORAGE_FAILURE = 'Failed to store session replay events in IndexedDB';
|
|
8
8
|
exports.MISSING_DEVICE_ID_MESSAGE = 'Session replay event batch not sent due to missing device ID';
|
|
9
9
|
exports.MISSING_API_KEY_MESSAGE = 'Session replay event batch not sent due to missing api key';
|
|
10
|
+
exports.SESSION_KILLED_MESSAGE = 'Session replay event batch dropped: server signalled capture disabled or session out of valid range for this session';
|
|
10
11
|
//# sourceMappingURL=messages.js.map
|
package/lib/cjs/messages.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/messages.ts"],"names":[],"mappings":";;;AAAa,QAAA,wBAAwB,GAAG,2BAA2B,CAAC;AACvD,QAAA,gCAAgC,GAAG,8CAA8C,CAAC;AAClF,QAAA,4BAA4B,GAAG,iEAAiE,CAAC;AACjG,QAAA,eAAe,GAAG,oDAAoD,CAAC;AACvE,QAAA,yBAAyB,GAAG,8DAA8D,CAAC;AAC3F,QAAA,uBAAuB,GAAG,4DAA4D,CAAC","sourcesContent":["export const UNEXPECTED_ERROR_MESSAGE = 'Unexpected error occurred';\nexport const UNEXPECTED_NETWORK_ERROR_MESSAGE = 'Network error occurred, event batch rejected';\nexport const MAX_RETRIES_EXCEEDED_MESSAGE = 'Session replay event batch rejected due to exceeded retry count';\nexport const STORAGE_FAILURE = 'Failed to store session replay events in IndexedDB';\nexport const MISSING_DEVICE_ID_MESSAGE = 'Session replay event batch not sent due to missing device ID';\nexport const MISSING_API_KEY_MESSAGE = 'Session replay event batch not sent due to missing api key';\n"]}
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/messages.ts"],"names":[],"mappings":";;;AAAa,QAAA,wBAAwB,GAAG,2BAA2B,CAAC;AACvD,QAAA,gCAAgC,GAAG,8CAA8C,CAAC;AAClF,QAAA,4BAA4B,GAAG,iEAAiE,CAAC;AACjG,QAAA,eAAe,GAAG,oDAAoD,CAAC;AACvE,QAAA,yBAAyB,GAAG,8DAA8D,CAAC;AAC3F,QAAA,uBAAuB,GAAG,4DAA4D,CAAC;AACvF,QAAA,sBAAsB,GACjC,sHAAsH,CAAC","sourcesContent":["export const UNEXPECTED_ERROR_MESSAGE = 'Unexpected error occurred';\nexport const UNEXPECTED_NETWORK_ERROR_MESSAGE = 'Network error occurred, event batch rejected';\nexport const MAX_RETRIES_EXCEEDED_MESSAGE = 'Session replay event batch rejected due to exceeded retry count';\nexport const STORAGE_FAILURE = 'Failed to store session replay events in IndexedDB';\nexport const MISSING_DEVICE_ID_MESSAGE = 'Session replay event batch not sent due to missing device ID';\nexport const MISSING_API_KEY_MESSAGE = 'Session replay event batch not sent due to missing api key';\nexport const SESSION_KILLED_MESSAGE =\n 'Session replay event batch dropped: server signalled capture disabled or session out of valid range for this session';\n"]}
|
|
@@ -18,6 +18,8 @@ export declare class SessionReplayTrackDestination implements AmplitudeSessionRe
|
|
|
18
18
|
private worker?;
|
|
19
19
|
private sendIdCounter;
|
|
20
20
|
private pendingWorkerRequests;
|
|
21
|
+
private flushPauseUntilMs;
|
|
22
|
+
private killedSessions;
|
|
21
23
|
constructor({ trackServerUrl, loggerProvider, payloadBatcher, workerScript, }: {
|
|
22
24
|
trackServerUrl?: string;
|
|
23
25
|
loggerProvider: ILogger;
|
|
@@ -54,5 +56,18 @@ export declare class SessionReplayTrackDestination implements AmplitudeSessionRe
|
|
|
54
56
|
err?: string;
|
|
55
57
|
success?: string;
|
|
56
58
|
}): void;
|
|
59
|
+
/**
|
|
60
|
+
* Applies the server's back-pressure signal carried on a 200 response.
|
|
61
|
+
*
|
|
62
|
+
* - `EVENT_SKIP_CODE_THROTTLED` (server-side rate limit): pause the flush schedule
|
|
63
|
+
* for `THROTTLED_FLUSH_PAUSE_MS` so we keep batching events instead of retry-storming.
|
|
64
|
+
* - `EVENT_SKIP_CODE_CAPTURE_DISABLED` / `EVENT_SKIP_CODE_INVALID_RANGE`: hard kill
|
|
65
|
+
* switch for this session — drop the queued contexts and stop accepting new ones.
|
|
66
|
+
* New sessions are unaffected.
|
|
67
|
+
* - `null` (clean 200, no header): clear any throttle pause; subsequent flushes resume
|
|
68
|
+
* on the normal cadence.
|
|
69
|
+
*/
|
|
70
|
+
private applyServerDirective;
|
|
71
|
+
private killSession;
|
|
57
72
|
}
|
|
58
73
|
//# sourceMappingURL=track-destination.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-destination.d.ts","sourceRoot":"","sources":["../../src/track-destination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,OAAO,EAAE,UAAU,EAAU,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"track-destination.d.ts","sourceRoot":"","sources":["../../src/track-destination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,OAAO,EAAE,UAAU,EAAU,MAAM,2BAA2B,CAAC;AAUvG,OAAO,EACL,6BAA6B,IAAI,sCAAsC,EACvE,wBAAwB,EACxB,+BAA+B,EAChC,MAAM,0BAA0B,CAAC;AAmClC,MAAM,MAAM,cAAc,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,KAAK;IAC3F,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB,CAAC;AAMF,qBAAa,6BAA8B,YAAW,sCAAsC;IAC1F,cAAc,EAAE,OAAO,CAAC;IACxB,UAAU,SAAM;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,SAAQ;IACpB,OAAO,CAAC,SAAS,CAA8C;IAC/D,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,+BAA+B,EAAE,CAAM;IAC9C,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,qBAAqB,CAAwF;IAIrH,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAA8B;gBAExC,EACV,cAAc,EACd,cAAc,EACd,cAAc,EACd,YAAY,GACb,EAAE;QACD,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,OAAO,CAAC;QACxB,cAAc,CAAC,EAAE,cAAc,CAAC;QAChC,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB;IA0DD,cAAc,CAAC,eAAe,EAAE,wBAAwB;IAQxD;;;;;;OAMG;IACH,UAAU,CAAC,EACT,MAAM,EACN,SAAS,EACT,QAAQ,EACR,MAAM,EACN,UAAU,GACX,EAAE;QACD,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;QAC3B,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,OAAO,UAAU,CAAC;KACtC;IAgDD,UAAU,CAAC,GAAG,IAAI,EAAE,+BAA+B,EAAE;IA2BrD,QAAQ,CAAC,OAAO,EAAE,MAAM;IAelB,KAAK,CAAC,QAAQ,UAAQ;IActB,IAAI,CAAC,OAAO,EAAE,+BAA+B,EAAE,QAAQ,UAAO;YAkCtD,aAAa;YAgCb,gBAAgB;IA+ExB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,+BAA+B,EAAE,YAAY,SAAK;IAwB/F,6BAA6B,CAAC,OAAO,EAAE,+BAA+B,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAgC7F,qBAAqB,CAAC,OAAO,EAAE,+BAA+B;IAQxD,mBAAmB,CAAC,OAAO,EAAE,+BAA+B;IAWlE,eAAe,CAAC,EACd,OAAO,EACP,GAAG,EACH,OAAO,GACR,EAAE;QACD,OAAO,EAAE,+BAA+B,CAAC;QACzC,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB;IASD;;;;;;;;;;OAUG;IACH,OAAO,CAAC,oBAAoB;IAyB5B,OAAO,CAAC,WAAW;CAyBpB"}
|
|
@@ -8,6 +8,9 @@ var messages_1 = require("./messages");
|
|
|
8
8
|
var version_1 = require("./version");
|
|
9
9
|
var constants_1 = require("./constants");
|
|
10
10
|
var gzip_1 = require("./utils/gzip");
|
|
11
|
+
// Bounded so a long-lived SDK instance can't accumulate kill records indefinitely;
|
|
12
|
+
// sessions are time-bounded in practice, this cap is just a defensive ceiling.
|
|
13
|
+
var MAX_KILLED_SESSIONS = 256;
|
|
11
14
|
var SessionReplayTrackDestination = /** @class */ (function () {
|
|
12
15
|
function SessionReplayTrackDestination(_a) {
|
|
13
16
|
var trackServerUrl = _a.trackServerUrl, loggerProvider = _a.loggerProvider, payloadBatcher = _a.payloadBatcher, workerScript = _a.workerScript;
|
|
@@ -18,6 +21,11 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
18
21
|
this.queue = [];
|
|
19
22
|
this.sendIdCounter = 0;
|
|
20
23
|
this.pendingWorkerRequests = new Map();
|
|
24
|
+
// Server back-pressure state, fed by the X-Session-Replay-Event-Skipped header on 200s.
|
|
25
|
+
// The server uses this header (instead of 4xx) to signal a deliberate no-retry drop so SDKs
|
|
26
|
+
// don't retry-storm. We honor it here by slowing or stopping our flush schedule.
|
|
27
|
+
this.flushPauseUntilMs = 0;
|
|
28
|
+
this.killedSessions = new Set();
|
|
21
29
|
this.loggerProvider = loggerProvider;
|
|
22
30
|
this.payloadBatcher = payloadBatcher ? payloadBatcher : function (payload) { return payload; };
|
|
23
31
|
this.trackServerUrl = trackServerUrl;
|
|
@@ -70,6 +78,9 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
70
78
|
else if (msg.type === 'complete') {
|
|
71
79
|
var pending_3 = _this.pendingWorkerRequests.get(msg.id);
|
|
72
80
|
if (pending_3) {
|
|
81
|
+
if (msg.skipCode !== undefined) {
|
|
82
|
+
_this.applyServerDirective(pending_3.context.sessionId, msg.skipCode);
|
|
83
|
+
}
|
|
73
84
|
_this.completeRequest({ context: pending_3.context });
|
|
74
85
|
pending_3.resolve();
|
|
75
86
|
_this.pendingWorkerRequests.delete(msg.id);
|
|
@@ -149,6 +160,15 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
149
160
|
list[_i] = arguments[_i];
|
|
150
161
|
}
|
|
151
162
|
var tryable = list.filter(function (context) {
|
|
163
|
+
if (_this.killedSessions.has(context.sessionId)) {
|
|
164
|
+
// Server has signaled capture_disabled or session_in_invalid_range for this session;
|
|
165
|
+
// drop the batch (and clean up its IDB record via onComplete) instead of POSTing.
|
|
166
|
+
_this.completeRequest({
|
|
167
|
+
context: context,
|
|
168
|
+
err: messages_1.SESSION_KILLED_MESSAGE,
|
|
169
|
+
});
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
152
172
|
if (context.attempts < (context.flushMaxRetries || 0)) {
|
|
153
173
|
context.attempts += 1;
|
|
154
174
|
return true;
|
|
@@ -168,13 +188,17 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
168
188
|
var _this = this;
|
|
169
189
|
if (this.scheduled)
|
|
170
190
|
return;
|
|
191
|
+
// If the server signaled throttling on a recent 200, defer the next flush until the
|
|
192
|
+
// pause window ends. This lets us keep batching events without retry-storming the server.
|
|
193
|
+
var pauseRemaining = this.flushPauseUntilMs - Date.now();
|
|
194
|
+
var effectiveTimeout = pauseRemaining > timeout ? pauseRemaining : timeout;
|
|
171
195
|
this.scheduled = setTimeout(function () {
|
|
172
196
|
void _this.flush(true).then(function () {
|
|
173
197
|
if (_this.queue.length > 0) {
|
|
174
198
|
_this.schedule(timeout);
|
|
175
199
|
}
|
|
176
200
|
});
|
|
177
|
-
},
|
|
201
|
+
}, effectiveTimeout);
|
|
178
202
|
};
|
|
179
203
|
SessionReplayTrackDestination.prototype.flush = function (useRetry) {
|
|
180
204
|
if (useRetry === void 0) { useRetry = false; }
|
|
@@ -226,6 +250,12 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
226
250
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
227
251
|
var apiKey, deviceId, payload, worker;
|
|
228
252
|
return tslib_1.__generator(this, function (_a) {
|
|
253
|
+
// A kill directive can arrive between flush() snapshotting the queue and us reaching
|
|
254
|
+
// each context. Re-check before hitting the network so we don't waste POSTs on a
|
|
255
|
+
// session the server has already told us to stop sending for.
|
|
256
|
+
if (this.killedSessions.has(context.sessionId)) {
|
|
257
|
+
return [2 /*return*/, this.completeRequest({ context: context, err: messages_1.SESSION_KILLED_MESSAGE })];
|
|
258
|
+
}
|
|
229
259
|
apiKey = context.apiKey;
|
|
230
260
|
if (!apiKey) {
|
|
231
261
|
return [2 /*return*/, this.completeRequest({ context: context, err: messages_1.MISSING_API_KEY_MESSAGE })];
|
|
@@ -284,11 +314,11 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
284
314
|
});
|
|
285
315
|
};
|
|
286
316
|
SessionReplayTrackDestination.prototype.sendOnMainThread = function (apiKey, deviceId, context, payload, useRetry) {
|
|
287
|
-
var _a, _b, _c, _d;
|
|
317
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
288
318
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
289
|
-
var url, version, sampleRate, urlParams, sessionReplayLibrary, payloadJson, globalScope, gzipped,
|
|
290
|
-
return tslib_1.__generator(this, function (
|
|
291
|
-
switch (
|
|
319
|
+
var url, version, sampleRate, urlParams, sessionReplayLibrary, payloadJson, globalScope, gzipped, _h, payloadSize, options, serverUrl, res, skipCode, responseBody, responseBody, _j, e_3;
|
|
320
|
+
return tslib_1.__generator(this, function (_k) {
|
|
321
|
+
switch (_k.label) {
|
|
292
322
|
case 0:
|
|
293
323
|
url = (0, helpers_1.getCurrentUrl)();
|
|
294
324
|
version = version_1.VERSION;
|
|
@@ -299,21 +329,21 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
299
329
|
type: "".concat(context.type),
|
|
300
330
|
});
|
|
301
331
|
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);
|
|
302
|
-
|
|
332
|
+
_k.label = 1;
|
|
303
333
|
case 1:
|
|
304
|
-
|
|
334
|
+
_k.trys.push([1, 13, , 14]);
|
|
305
335
|
payloadJson = JSON.stringify(payload);
|
|
306
336
|
globalScope = (0, analytics_core_1.getGlobalScope)();
|
|
307
337
|
if (!(globalScope && 'CompressionStream' in globalScope)) return [3 /*break*/, 3];
|
|
308
338
|
return [4 /*yield*/, (0, gzip_1.gzipJson)(payloadJson, globalScope)];
|
|
309
339
|
case 2:
|
|
310
|
-
|
|
340
|
+
_h = _k.sent();
|
|
311
341
|
return [3 /*break*/, 4];
|
|
312
342
|
case 3:
|
|
313
|
-
|
|
314
|
-
|
|
343
|
+
_h = null;
|
|
344
|
+
_k.label = 4;
|
|
315
345
|
case 4:
|
|
316
|
-
gzipped =
|
|
346
|
+
gzipped = _h;
|
|
317
347
|
payloadSize = gzipped ? gzipped.byteLength : new Blob([payloadJson]).size;
|
|
318
348
|
options = {
|
|
319
349
|
headers: tslib_1.__assign({ 'Content-Type': 'application/json', Accept: '*/*', Authorization: "Bearer ".concat(apiKey), 'X-Client-Version': version, 'X-Client-Library': sessionReplayLibrary, 'X-Client-Url': url.substring(0, constants_1.MAX_URL_LENGTH), 'X-Client-Sample-Rate': "".concat(sampleRate), 'X-Sampling-Hash-Alg': 'xxhash32' }, (gzipped ? { 'Content-Encoding': 'gzip' } : {})),
|
|
@@ -326,17 +356,21 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
326
356
|
serverUrl = "".concat((0, helpers_1.getServerUrl)(context.serverZone, this.trackServerUrl), "?").concat(urlParams.toString());
|
|
327
357
|
return [4 /*yield*/, fetch(serverUrl, options)];
|
|
328
358
|
case 5:
|
|
329
|
-
res =
|
|
359
|
+
res = _k.sent();
|
|
330
360
|
if (res === null) {
|
|
331
361
|
this.completeRequest({ context: context, err: messages_1.UNEXPECTED_ERROR_MESSAGE });
|
|
332
362
|
return [2 /*return*/];
|
|
333
363
|
}
|
|
364
|
+
if (res.status >= 200 && res.status < 300) {
|
|
365
|
+
skipCode = (_g = (_f = (_e = res.headers) === null || _e === void 0 ? void 0 : _e.get) === null || _f === void 0 ? void 0 : _f.call(_e, constants_1.EVENT_SKIPPED_HEADER)) !== null && _g !== void 0 ? _g : null;
|
|
366
|
+
this.applyServerDirective(context.sessionId, skipCode);
|
|
367
|
+
}
|
|
334
368
|
if (!!useRetry) return [3 /*break*/, 6];
|
|
335
369
|
responseBody = '';
|
|
336
370
|
try {
|
|
337
371
|
responseBody = JSON.stringify(res.body, null, 2);
|
|
338
372
|
}
|
|
339
|
-
catch (
|
|
373
|
+
catch (_l) {
|
|
340
374
|
// to avoid crash, but don't care about the error, add comment to avoid empty block lint error
|
|
341
375
|
}
|
|
342
376
|
this.completeRequest({ context: context, success: "".concat(res.status, ": ").concat(responseBody) });
|
|
@@ -344,23 +378,23 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
344
378
|
case 6:
|
|
345
379
|
responseBody = '';
|
|
346
380
|
if (!(res.status === 413)) return [3 /*break*/, 10];
|
|
347
|
-
|
|
381
|
+
_k.label = 7;
|
|
348
382
|
case 7:
|
|
349
|
-
|
|
383
|
+
_k.trys.push([7, 9, , 10]);
|
|
350
384
|
return [4 /*yield*/, res.text()];
|
|
351
385
|
case 8:
|
|
352
|
-
responseBody =
|
|
386
|
+
responseBody = _k.sent();
|
|
353
387
|
return [3 /*break*/, 10];
|
|
354
388
|
case 9:
|
|
355
|
-
|
|
389
|
+
_j = _k.sent();
|
|
356
390
|
return [3 /*break*/, 10];
|
|
357
391
|
case 10: return [4 /*yield*/, this.handleReponse(res.status, context, responseBody)];
|
|
358
392
|
case 11:
|
|
359
|
-
|
|
360
|
-
|
|
393
|
+
_k.sent();
|
|
394
|
+
_k.label = 12;
|
|
361
395
|
case 12: return [3 /*break*/, 14];
|
|
362
396
|
case 13:
|
|
363
|
-
e_3 =
|
|
397
|
+
e_3 = _k.sent();
|
|
364
398
|
this.completeRequest({ context: context, err: e_3 });
|
|
365
399
|
return [3 /*break*/, 14];
|
|
366
400
|
case 14: return [2 /*return*/];
|
|
@@ -475,6 +509,85 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
475
509
|
this.loggerProvider.log(success);
|
|
476
510
|
}
|
|
477
511
|
};
|
|
512
|
+
/**
|
|
513
|
+
* Applies the server's back-pressure signal carried on a 200 response.
|
|
514
|
+
*
|
|
515
|
+
* - `EVENT_SKIP_CODE_THROTTLED` (server-side rate limit): pause the flush schedule
|
|
516
|
+
* for `THROTTLED_FLUSH_PAUSE_MS` so we keep batching events instead of retry-storming.
|
|
517
|
+
* - `EVENT_SKIP_CODE_CAPTURE_DISABLED` / `EVENT_SKIP_CODE_INVALID_RANGE`: hard kill
|
|
518
|
+
* switch for this session — drop the queued contexts and stop accepting new ones.
|
|
519
|
+
* New sessions are unaffected.
|
|
520
|
+
* - `null` (clean 200, no header): clear any throttle pause; subsequent flushes resume
|
|
521
|
+
* on the normal cadence.
|
|
522
|
+
*/
|
|
523
|
+
SessionReplayTrackDestination.prototype.applyServerDirective = function (sessionId, skipCode) {
|
|
524
|
+
if (skipCode === null) {
|
|
525
|
+
this.flushPauseUntilMs = 0;
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (skipCode === constants_1.EVENT_SKIP_CODE_THROTTLED) {
|
|
529
|
+
var wasInPause = this.flushPauseUntilMs > Date.now();
|
|
530
|
+
this.flushPauseUntilMs = Date.now() + constants_1.THROTTLED_FLUSH_PAUSE_MS;
|
|
531
|
+
// Log only on pause-state transitions — a throttled server may reply to many
|
|
532
|
+
// batches per minute, and one log per batch would flood the console.
|
|
533
|
+
if (!wasInPause) {
|
|
534
|
+
this.loggerProvider.log("Session replay throttled by server; pausing flush schedule for ".concat(constants_1.THROTTLED_FLUSH_PAUSE_MS / 1000, "s"));
|
|
535
|
+
}
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (skipCode === constants_1.EVENT_SKIP_CODE_CAPTURE_DISABLED || skipCode === constants_1.EVENT_SKIP_CODE_INVALID_RANGE) {
|
|
539
|
+
this.killSession(sessionId, skipCode);
|
|
540
|
+
}
|
|
541
|
+
// Unknown skip codes are ignored — the server may add new ones, and our default
|
|
542
|
+
// behavior (treat as a normal 200) preserves throughput rather than penalizing the
|
|
543
|
+
// session for a code we don't recognize.
|
|
544
|
+
};
|
|
545
|
+
SessionReplayTrackDestination.prototype.killSession = function (sessionId, skipCode) {
|
|
546
|
+
var e_4, _a, e_5, _b;
|
|
547
|
+
if (this.killedSessions.has(sessionId))
|
|
548
|
+
return;
|
|
549
|
+
this.killedSessions.add(sessionId);
|
|
550
|
+
// Set preserves insertion order, so deleting the first key evicts the oldest entry.
|
|
551
|
+
if (this.killedSessions.size > MAX_KILLED_SESSIONS) {
|
|
552
|
+
try {
|
|
553
|
+
for (var _c = tslib_1.__values(this.killedSessions), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
554
|
+
var oldest = _d.value;
|
|
555
|
+
this.killedSessions.delete(oldest);
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
560
|
+
finally {
|
|
561
|
+
try {
|
|
562
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
563
|
+
}
|
|
564
|
+
finally { if (e_4) throw e_4.error; }
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
this.loggerProvider.log("Session replay capture stopped for session ".concat(sessionId, " by server directive ").concat(skipCode, "; remaining events will be dropped"));
|
|
568
|
+
// Drain any queued contexts for this session so their IDB records get cleaned up
|
|
569
|
+
// via onComplete, instead of sitting in the queue waiting for a flush we'll never make.
|
|
570
|
+
var remaining = [];
|
|
571
|
+
try {
|
|
572
|
+
for (var _e = tslib_1.__values(this.queue), _f = _e.next(); !_f.done; _f = _e.next()) {
|
|
573
|
+
var queued = _f.value;
|
|
574
|
+
if (queued.sessionId === sessionId) {
|
|
575
|
+
this.completeRequest({ context: queued, err: messages_1.SESSION_KILLED_MESSAGE });
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
remaining.push(queued);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
583
|
+
finally {
|
|
584
|
+
try {
|
|
585
|
+
if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
|
|
586
|
+
}
|
|
587
|
+
finally { if (e_5) throw e_5.error; }
|
|
588
|
+
}
|
|
589
|
+
this.queue = remaining;
|
|
590
|
+
};
|
|
478
591
|
return SessionReplayTrackDestination;
|
|
479
592
|
}());
|
|
480
593
|
exports.SessionReplayTrackDestination = SessionReplayTrackDestination;
|