@amplitude/session-replay-browser 1.46.0 → 1.47.0-sr-trc-debug-log.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/config/joined-config.d.ts +6 -1
- package/lib/cjs/config/joined-config.d.ts.map +1 -1
- package/lib/cjs/config/joined-config.js +58 -18
- package/lib/cjs/config/joined-config.js.map +1 -1
- package/lib/cjs/config/local-config.d.ts +5 -1
- package/lib/cjs/config/local-config.d.ts.map +1 -1
- package/lib/cjs/config/local-config.js +19 -1
- package/lib/cjs/config/local-config.js.map +1 -1
- package/lib/cjs/config/types.d.ts +19 -1
- package/lib/cjs/config/types.d.ts.map +1 -1
- package/lib/cjs/config/types.js.map +1 -1
- package/lib/cjs/diagnostics.d.ts +43 -0
- package/lib/cjs/diagnostics.d.ts.map +1 -0
- package/lib/cjs/diagnostics.js +54 -0
- package/lib/cjs/diagnostics.js.map +1 -0
- package/lib/cjs/session-replay.d.ts +25 -0
- package/lib/cjs/session-replay.d.ts.map +1 -1
- package/lib/cjs/session-replay.js +246 -51
- package/lib/cjs/session-replay.js.map +1 -1
- package/lib/cjs/targeting/targeting-manager.d.ts +4 -1
- package/lib/cjs/targeting/targeting-manager.d.ts.map +1 -1
- package/lib/cjs/targeting/targeting-manager.js +43 -10
- package/lib/cjs/targeting/targeting-manager.js.map +1 -1
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.d.ts.map +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/cjs/version.js.map +1 -1
- package/lib/esm/config/joined-config.d.ts +6 -1
- package/lib/esm/config/joined-config.d.ts.map +1 -1
- package/lib/esm/config/joined-config.js +58 -18
- package/lib/esm/config/joined-config.js.map +1 -1
- package/lib/esm/config/local-config.d.ts +5 -1
- package/lib/esm/config/local-config.d.ts.map +1 -1
- package/lib/esm/config/local-config.js +20 -2
- package/lib/esm/config/local-config.js.map +1 -1
- package/lib/esm/config/types.d.ts +19 -1
- package/lib/esm/config/types.d.ts.map +1 -1
- package/lib/esm/config/types.js.map +1 -1
- package/lib/esm/diagnostics.d.ts +43 -0
- package/lib/esm/diagnostics.d.ts.map +1 -0
- package/lib/esm/diagnostics.js +51 -0
- package/lib/esm/diagnostics.js.map +1 -0
- package/lib/esm/session-replay.d.ts +25 -0
- package/lib/esm/session-replay.d.ts.map +1 -1
- package/lib/esm/session-replay.js +246 -51
- package/lib/esm/session-replay.js.map +1 -1
- package/lib/esm/targeting/targeting-manager.d.ts +4 -1
- package/lib/esm/targeting/targeting-manager.d.ts.map +1 -1
- package/lib/esm/targeting/targeting-manager.js +43 -10
- package/lib/esm/targeting/targeting-manager.js.map +1 -1
- package/lib/esm/version.d.ts +1 -1
- package/lib/esm/version.d.ts.map +1 -1
- package/lib/esm/version.js +1 -1
- package/lib/esm/version.js.map +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/observers-min.js +1 -1
- package/lib/scripts/observers-min.js.gz +0 -0
- 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/targeting-min.js +1 -1
- package/lib/scripts/targeting-min.js.gz +0 -0
- package/package.json +2 -2
|
@@ -17,6 +17,7 @@ var identifiers_1 = require("./identifiers");
|
|
|
17
17
|
var logger_1 = require("./logger");
|
|
18
18
|
var replay_start_time_store_1 = require("./replay-start-time-store");
|
|
19
19
|
var targeting_manager_1 = require("./targeting/targeting-manager");
|
|
20
|
+
var diagnostics_1 = require("./diagnostics");
|
|
20
21
|
var sampling_1 = require("./sampling");
|
|
21
22
|
var version_1 = require("./version");
|
|
22
23
|
var url_tracking_plugin_1 = require("./plugins/url-tracking-plugin");
|
|
@@ -28,6 +29,10 @@ var SessionReplay = /** @class */ (function () {
|
|
|
28
29
|
this.recordCancelCallback = null;
|
|
29
30
|
this.eventCount = 0;
|
|
30
31
|
this.sessionTargetingMatch = false;
|
|
32
|
+
// Session for which the one-per-session TRC diagnostic event was already emitted. The
|
|
33
|
+
// diagnostics client caps in-memory events, so per-call signals go through counters and only
|
|
34
|
+
// a single rich snapshot event is recorded per session.
|
|
35
|
+
this.trcDiagnosticSessionId = undefined;
|
|
31
36
|
// Public on purpose. `pageLeaveFns` is iterated by `pageLeaveListener`,
|
|
32
37
|
// `rrwebEventManager` is dereferenced in `asyncSetSessionId` to drop the beacon buffer
|
|
33
38
|
// at a session boundary, and `sessionStartTime` drives `isBelowMinSessionDuration()`.
|
|
@@ -118,12 +123,23 @@ var SessionReplay = /** @class */ (function () {
|
|
|
118
123
|
if (forceRestart === void 0) { forceRestart = false; }
|
|
119
124
|
if (forceTargetingReevaluation === void 0) { forceTargetingReevaluation = false; }
|
|
120
125
|
return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
121
|
-
var targetingConfig, shouldReEvaluate, urlChangeEvaluationId, eventForTargeting, pageUrl, pageForTargeting, targetingMatch;
|
|
122
|
-
var _a, _b, _c, _d, _e, _f;
|
|
123
|
-
return tslib_1.__generator(this, function (
|
|
124
|
-
switch (
|
|
126
|
+
var targetingConfig, shouldReEvaluate, urlChangeEvaluationId, eventForTargeting, pageUrl, pageForTargeting, evalStart, targetingMatch, evalDurationMs, trigger;
|
|
127
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
128
|
+
return tslib_1.__generator(this, function (_h) {
|
|
129
|
+
switch (_h.label) {
|
|
125
130
|
case 0:
|
|
131
|
+
// What triggered this evaluation (init vs SPA URL change vs analytics event) — shows in
|
|
132
|
+
// DataDog how often re-evaluation actually runs.
|
|
133
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.evalTrigger(isInit ? 'init' : forceTargetingReevaluation ? 'urlchange' : 'event'));
|
|
126
134
|
if (!this.identifiers || !this.identifiers.sessionId || !this.config) {
|
|
135
|
+
// Q4: eval can't run because a prerequisite is missing — record exactly which one.
|
|
136
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.evalMissingPrereq);
|
|
137
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.evalMissingPrereq, {
|
|
138
|
+
hasIdentifiers: !!this.identifiers,
|
|
139
|
+
hasSessionId: !!((_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId),
|
|
140
|
+
hasConfig: !!this.config,
|
|
141
|
+
hasDeviceId: !!this.getDeviceId(),
|
|
142
|
+
});
|
|
127
143
|
if (this.identifiers && !this.identifiers.sessionId) {
|
|
128
144
|
this.loggerProvider.log('Session ID has not been set yet, cannot evaluate targeting for Session Replay.');
|
|
129
145
|
}
|
|
@@ -134,6 +150,7 @@ var SessionReplay = /** @class */ (function () {
|
|
|
134
150
|
}
|
|
135
151
|
// Handle cases where there's no targeting config
|
|
136
152
|
if (!this.config.targetingConfig) {
|
|
153
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.evalNoConfig);
|
|
137
154
|
if (isInit) {
|
|
138
155
|
this.loggerProvider.log('Targeting config has not been set yet, cannot evaluate targeting.');
|
|
139
156
|
}
|
|
@@ -153,8 +170,9 @@ var SessionReplay = /** @class */ (function () {
|
|
|
153
170
|
Object.values(analytics_core_1.SpecialEventType).includes(eventForTargeting.event_type)) {
|
|
154
171
|
eventForTargeting = undefined;
|
|
155
172
|
}
|
|
156
|
-
pageUrl = (
|
|
157
|
-
pageForTargeting = (
|
|
173
|
+
pageUrl = (_f = (_c = (_b = targetingParams.page) === null || _b === void 0 ? void 0 : _b.url) !== null && _c !== void 0 ? _c : (_e = (_d = (0, analytics_core_1.getGlobalScope)()) === null || _d === void 0 ? void 0 : _d.location) === null || _e === void 0 ? void 0 : _e.href) !== null && _f !== void 0 ? _f : '';
|
|
174
|
+
pageForTargeting = (_g = targetingParams.page) !== null && _g !== void 0 ? _g : (pageUrl !== '' ? { url: pageUrl } : undefined);
|
|
175
|
+
evalStart = Date.now();
|
|
158
176
|
return [4 /*yield*/, (0, targeting_manager_1.evaluateTargetingAndStore)({
|
|
159
177
|
sessionId: this.identifiers.sessionId,
|
|
160
178
|
targetingConfig: targetingConfig,
|
|
@@ -166,38 +184,76 @@ var SessionReplay = /** @class */ (function () {
|
|
|
166
184
|
page: pageForTargeting,
|
|
167
185
|
},
|
|
168
186
|
urlChange: forceTargetingReevaluation,
|
|
187
|
+
diagnosticsClient: this.config.diagnosticsClient,
|
|
188
|
+
deviceId: this.getDeviceId(),
|
|
169
189
|
})];
|
|
170
190
|
case 1:
|
|
171
|
-
targetingMatch =
|
|
191
|
+
targetingMatch = _h.sent();
|
|
192
|
+
evalDurationMs = Date.now() - evalStart;
|
|
193
|
+
trigger = isInit ? 'init' : forceTargetingReevaluation ? 'urlchange' : 'event';
|
|
194
|
+
// Evaluation latency — surfaces the slow-network residual gap (SR-4234) in DataDog as
|
|
195
|
+
// sdk.diagnostics.sr.trc.eval.duration_ms.{avg,max,...}.
|
|
196
|
+
this.recordDiagnosticHistogram(diagnostics_1.SrDiagnostic.evalDurationMs, evalDurationMs);
|
|
172
197
|
if (forceTargetingReevaluation &&
|
|
173
198
|
urlChangeEvaluationId !== undefined &&
|
|
174
199
|
urlChangeEvaluationId !== this.latestUrlChangeTargetingEvaluationId) {
|
|
200
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.evalStaleDiscarded);
|
|
201
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.evalStaleDiscarded, {
|
|
202
|
+
sessionId: this.identifiers.sessionId,
|
|
203
|
+
pageUrl: pageForTargeting === null || pageForTargeting === void 0 ? void 0 : pageForTargeting.url,
|
|
204
|
+
evaluationId: urlChangeEvaluationId,
|
|
205
|
+
latestEvaluationId: this.latestUrlChangeTargetingEvaluationId,
|
|
206
|
+
evalDurationMs: evalDurationMs,
|
|
207
|
+
});
|
|
175
208
|
this.loggerProvider.debug("Ignoring stale URL-change targeting result #".concat(urlChangeEvaluationId, "; latest is #").concat(this.latestUrlChangeTargetingEvaluationId, "."));
|
|
176
209
|
return [2 /*return*/];
|
|
177
210
|
}
|
|
211
|
+
// Per-evaluation outcome (distinct from the per-session sr.gate.* gate counters).
|
|
212
|
+
this.incrementDiagnostic(targetingMatch ? diagnostics_1.SrDiagnostic.evalMatch : diagnostics_1.SrDiagnostic.evalNoMatch);
|
|
178
213
|
// Keep targeting match monotonic within a session: once true, always true.
|
|
179
214
|
// This avoids races where an older in-flight evaluation resolves false after
|
|
180
215
|
// a newer evaluation already resolved true.
|
|
181
216
|
this.sessionTargetingMatch = this.sessionTargetingMatch || targetingMatch;
|
|
217
|
+
// Q5: record ALL evaluation params (→ DataDog Logs) so a single evaluation can be fully
|
|
218
|
+
// reconstructed by URL, identifiers, inputs, outcome, trigger and latency. userProperties
|
|
219
|
+
// values are intentionally reduced to keys to avoid logging PII.
|
|
220
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.evalEvent, {
|
|
221
|
+
// sessionId + deviceId are stamped by recordDiagnosticEvent.
|
|
222
|
+
trigger: trigger,
|
|
223
|
+
matched: targetingMatch,
|
|
224
|
+
sessionTargetingMatch: this.sessionTargetingMatch,
|
|
225
|
+
pageUrl: pageForTargeting === null || pageForTargeting === void 0 ? void 0 : pageForTargeting.url,
|
|
226
|
+
hasDeviceId: !!this.getDeviceId(),
|
|
227
|
+
hasEvent: !!eventForTargeting,
|
|
228
|
+
eventType: eventForTargeting === null || eventForTargeting === void 0 ? void 0 : eventForTargeting.event_type,
|
|
229
|
+
userPropertyKeys: targetingParams.userProperties ? Object.keys(targetingParams.userProperties) : [],
|
|
230
|
+
evalDurationMs: evalDurationMs,
|
|
231
|
+
});
|
|
182
232
|
this.loggerProvider.debug(JSON.stringify({
|
|
183
233
|
name: 'targeted replay capture config',
|
|
184
234
|
sessionTargetingMatch: this.sessionTargetingMatch,
|
|
185
235
|
event: eventForTargeting,
|
|
186
236
|
targetingParams: targetingParams,
|
|
187
237
|
}, null, 2));
|
|
188
|
-
|
|
238
|
+
return [3 /*break*/, 3];
|
|
189
239
|
case 2:
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
240
|
+
if (targetingConfig && this.sessionTargetingMatch) {
|
|
241
|
+
// Already matched earlier this session — recording continues without re-evaluation.
|
|
242
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.evalSkippedAlreadyMatched);
|
|
243
|
+
}
|
|
244
|
+
_h.label = 3;
|
|
193
245
|
case 3:
|
|
194
|
-
if (!
|
|
246
|
+
if (!isInit) return [3 /*break*/, 4];
|
|
247
|
+
void this.initialize(true);
|
|
248
|
+
return [3 /*break*/, 6];
|
|
249
|
+
case 4:
|
|
250
|
+
if (!(forceRestart || !this.recordCancelCallback)) return [3 /*break*/, 6];
|
|
195
251
|
this.loggerProvider.log('Recording events for session due to forceRestart or no ongoing recording.');
|
|
196
252
|
return [4 /*yield*/, this.recordEvents()];
|
|
197
|
-
case
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
case
|
|
253
|
+
case 5:
|
|
254
|
+
_h.sent();
|
|
255
|
+
_h.label = 6;
|
|
256
|
+
case 6: return [2 /*return*/];
|
|
201
257
|
}
|
|
202
258
|
});
|
|
203
259
|
});
|
|
@@ -284,6 +340,15 @@ var SessionReplay = /** @class */ (function () {
|
|
|
284
340
|
var hasTargeting = !!((_b = this.config) === null || _b === void 0 ? void 0 : _b.targetingConfig);
|
|
285
341
|
var onUrlChange = function (href) {
|
|
286
342
|
_this.currentPageUrl = href;
|
|
343
|
+
// Team-visible signal that an SPA navigation was detected at all (covers the "is the SDK
|
|
344
|
+
// even seeing route changes in this framework?" question — counter + a log with the href
|
|
345
|
+
// and whether it triggered a targeting re-eval).
|
|
346
|
+
_this.incrementDiagnostic(diagnostics_1.SrDiagnostic.urlChange);
|
|
347
|
+
_this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.urlChangeEvent, {
|
|
348
|
+
href: href,
|
|
349
|
+
hasTargeting: hasTargeting,
|
|
350
|
+
alreadyMatched: _this.sessionTargetingMatch,
|
|
351
|
+
});
|
|
287
352
|
if (hasTargeting) {
|
|
288
353
|
var evaluationId = ++_this.latestUrlChangeTargetingEvaluationId;
|
|
289
354
|
void _this.evaluateTargetingAndCapture({
|
|
@@ -324,13 +389,13 @@ var SessionReplay = /** @class */ (function () {
|
|
|
324
389
|
return currentUrl != null ? { url: currentUrl } : undefined;
|
|
325
390
|
};
|
|
326
391
|
SessionReplay.prototype._init = function (apiKey, options) {
|
|
327
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
392
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
328
393
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
329
|
-
var now,
|
|
330
|
-
var e_2,
|
|
394
|
+
var now, _r, _s, joinedConfig, localConfig, remoteConfig, scrollWatcher, managers, storeType, compressionWorkerScript, trackDestinationWorkerScript, globalScope, _t, compressionScript, trackDestinationScript, rrwebEventManager, error_1, typedError, payloadBatcher, interactionEventManager, error_2, typedError, onFullSnapshotProcessed, pending_2, pending_1, pending_1_1, _u, event_1, sessionId, messenger, needsUrlTracking;
|
|
395
|
+
var e_2, _v;
|
|
331
396
|
var _this = this;
|
|
332
|
-
return tslib_1.__generator(this, function (
|
|
333
|
-
switch (
|
|
397
|
+
return tslib_1.__generator(this, function (_w) {
|
|
398
|
+
switch (_w.label) {
|
|
334
399
|
case 0:
|
|
335
400
|
// Re-init should always tear down any previous URL-change subscription, even when the
|
|
336
401
|
// next config has no targeting config and we don't subscribe again.
|
|
@@ -346,13 +411,13 @@ var SessionReplay = /** @class */ (function () {
|
|
|
346
411
|
options.sessionId !== undefined
|
|
347
412
|
? (_b = (0, replay_start_time_store_1.getOrInitReplayStartTime)(apiKey, options.sessionId, now, this.loggerProvider)) !== null && _b !== void 0 ? _b : now
|
|
348
413
|
: now;
|
|
349
|
-
|
|
414
|
+
_r = this;
|
|
350
415
|
return [4 /*yield*/, (0, joined_config_1.createSessionReplayJoinedConfigGenerator)(apiKey, options)];
|
|
351
416
|
case 1:
|
|
352
|
-
|
|
417
|
+
_r.joinedConfigGenerator = _w.sent();
|
|
353
418
|
return [4 /*yield*/, this.joinedConfigGenerator.generateJoinedConfig()];
|
|
354
419
|
case 2:
|
|
355
|
-
|
|
420
|
+
_s = _w.sent(), joinedConfig = _s.joinedConfig, localConfig = _s.localConfig, remoteConfig = _s.remoteConfig;
|
|
356
421
|
this.config = joinedConfig;
|
|
357
422
|
this.setMetadata(options.sessionId, joinedConfig, localConfig, remoteConfig, (_c = options.version) === null || _c === void 0 ? void 0 : _c.version, version_1.VERSION, (_d = options.version) === null || _d === void 0 ? void 0 : _d.type);
|
|
358
423
|
this.pageLeaveFns = [];
|
|
@@ -376,12 +441,12 @@ var SessionReplay = /** @class */ (function () {
|
|
|
376
441
|
if (!(this.config.useWebWorker && globalScope && globalScope.Worker)) return [3 /*break*/, 4];
|
|
377
442
|
return [4 /*yield*/, Promise.resolve().then(function () { return tslib_1.__importStar(require('./worker')); })];
|
|
378
443
|
case 3:
|
|
379
|
-
|
|
444
|
+
_t = _w.sent(), compressionScript = _t.compressionScript, trackDestinationScript = _t.trackDestinationScript;
|
|
380
445
|
compressionWorkerScript = compressionScript;
|
|
381
446
|
trackDestinationWorkerScript = trackDestinationScript;
|
|
382
|
-
|
|
447
|
+
_w.label = 4;
|
|
383
448
|
case 4:
|
|
384
|
-
|
|
449
|
+
_w.trys.push([4, 6, , 7]);
|
|
385
450
|
return [4 /*yield*/, (0, events_manager_1.createEventsManager)({
|
|
386
451
|
config: this.config,
|
|
387
452
|
type: 'replay',
|
|
@@ -393,21 +458,21 @@ var SessionReplay = /** @class */ (function () {
|
|
|
393
458
|
shouldSend: function () { return !_this.isBelowMinSessionDuration(); },
|
|
394
459
|
})];
|
|
395
460
|
case 5:
|
|
396
|
-
rrwebEventManager =
|
|
461
|
+
rrwebEventManager = _w.sent();
|
|
397
462
|
this.rrwebEventManager = rrwebEventManager;
|
|
398
463
|
managers.push({ name: 'replay', manager: rrwebEventManager });
|
|
399
464
|
return [3 /*break*/, 7];
|
|
400
465
|
case 6:
|
|
401
|
-
error_1 =
|
|
466
|
+
error_1 = _w.sent();
|
|
402
467
|
typedError = error_1;
|
|
403
468
|
this.loggerProvider.warn("Error occurred while creating replay events manager: ".concat(typedError.toString()));
|
|
404
469
|
return [3 /*break*/, 7];
|
|
405
470
|
case 7:
|
|
406
471
|
if (!((_j = this.config.interactionConfig) === null || _j === void 0 ? void 0 : _j.enabled)) return [3 /*break*/, 11];
|
|
407
472
|
payloadBatcher = this.config.interactionConfig.batch ? click_1.clickBatcher : click_1.clickNonBatcher;
|
|
408
|
-
|
|
473
|
+
_w.label = 8;
|
|
409
474
|
case 8:
|
|
410
|
-
|
|
475
|
+
_w.trys.push([8, 10, , 11]);
|
|
411
476
|
return [4 /*yield*/, (0, events_manager_1.createEventsManager)({
|
|
412
477
|
config: this.config,
|
|
413
478
|
type: 'interaction',
|
|
@@ -419,11 +484,11 @@ var SessionReplay = /** @class */ (function () {
|
|
|
419
484
|
trackDestinationWorkerScript: trackDestinationWorkerScript,
|
|
420
485
|
})];
|
|
421
486
|
case 9:
|
|
422
|
-
interactionEventManager =
|
|
487
|
+
interactionEventManager = _w.sent();
|
|
423
488
|
managers.push({ name: 'interaction', manager: interactionEventManager });
|
|
424
489
|
return [3 /*break*/, 11];
|
|
425
490
|
case 10:
|
|
426
|
-
error_2 =
|
|
491
|
+
error_2 = _w.sent();
|
|
427
492
|
typedError = error_2;
|
|
428
493
|
this.loggerProvider.warn("Error occurred while creating interaction events manager: ".concat(typedError.toString()));
|
|
429
494
|
return [3 /*break*/, 11];
|
|
@@ -441,14 +506,14 @@ var SessionReplay = /** @class */ (function () {
|
|
|
441
506
|
pending_2 = this.pendingEmitEvents.splice(0);
|
|
442
507
|
try {
|
|
443
508
|
for (pending_1 = tslib_1.__values(pending_2), pending_1_1 = pending_1.next(); !pending_1_1.done; pending_1_1 = pending_1.next()) {
|
|
444
|
-
|
|
509
|
+
_u = pending_1_1.value, event_1 = _u.event, sessionId = _u.sessionId;
|
|
445
510
|
this.eventCompressor.enqueueEvent(event_1, sessionId);
|
|
446
511
|
}
|
|
447
512
|
}
|
|
448
513
|
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
449
514
|
finally {
|
|
450
515
|
try {
|
|
451
|
-
if (pending_1_1 && !pending_1_1.done && (
|
|
516
|
+
if (pending_1_1 && !pending_1_1.done && (_v = pending_1.return)) _v.call(pending_1);
|
|
452
517
|
}
|
|
453
518
|
finally { if (e_2) throw e_2.error; }
|
|
454
519
|
}
|
|
@@ -485,7 +550,7 @@ var SessionReplay = /** @class */ (function () {
|
|
|
485
550
|
], false);
|
|
486
551
|
return [4 /*yield*/, this.initializeNetworkObservers()];
|
|
487
552
|
case 12:
|
|
488
|
-
|
|
553
|
+
_w.sent();
|
|
489
554
|
// Enable background capture when this page is opened by the Amplitude app
|
|
490
555
|
// (window.opener exists). Uses the shared messenger singleton so that if
|
|
491
556
|
// autocapture is also loaded, both share a single messenger and script load.
|
|
@@ -496,10 +561,22 @@ var SessionReplay = /** @class */ (function () {
|
|
|
496
561
|
}
|
|
497
562
|
this.loggerProvider.log('Installing @amplitude/session-replay-browser.');
|
|
498
563
|
this.teardownEventListeners(false);
|
|
564
|
+
// Q1 "did init happen?": its presence in DataDog proves init() ran to completion for this
|
|
565
|
+
// session; the props show whether the prerequisites (session/device id, config) were present.
|
|
566
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.init);
|
|
567
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.init, {
|
|
568
|
+
// sessionId is always stamped by recordDiagnosticEvent, so no separate hasSessionId here.
|
|
569
|
+
hasDeviceId: !!this.getDeviceId(),
|
|
570
|
+
captureEnabled: this.config.captureEnabled,
|
|
571
|
+
hasTargetingConfig: !!this.config.targetingConfig,
|
|
572
|
+
sampleRate: this.config.sampleRate,
|
|
573
|
+
optOut: this.shouldOptOut(),
|
|
574
|
+
currentUrl: (_m = this.getCurrentPageForTargeting()) === null || _m === void 0 ? void 0 : _m.url,
|
|
575
|
+
});
|
|
499
576
|
return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options.userProperties, page: this.getCurrentPageForTargeting() }, true)];
|
|
500
577
|
case 13:
|
|
501
|
-
|
|
502
|
-
needsUrlTracking = this.config.targetingConfig || ((
|
|
578
|
+
_w.sent();
|
|
579
|
+
needsUrlTracking = this.config.targetingConfig || ((_q = (_p = (_o = this.config.privacyConfig) === null || _o === void 0 ? void 0 : _o.urlMaskLevels) === null || _p === void 0 ? void 0 : _p.length) !== null && _q !== void 0 ? _q : 0) > 0;
|
|
503
580
|
if (needsUrlTracking) {
|
|
504
581
|
this.setupUrlChangeListener();
|
|
505
582
|
}
|
|
@@ -512,17 +589,29 @@ var SessionReplay = /** @class */ (function () {
|
|
|
512
589
|
return (0, analytics_core_1.returnWrapper)(this.asyncSetSessionId(sessionId, deviceId));
|
|
513
590
|
};
|
|
514
591
|
SessionReplay.prototype.asyncSetSessionId = function (sessionId, deviceId, options) {
|
|
515
|
-
var _a, _b, _c;
|
|
592
|
+
var _a, _b, _c, _d;
|
|
516
593
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
517
|
-
var previousSessionId, isSessionChange, deviceIdForReplayId, joinedConfig;
|
|
518
|
-
return tslib_1.__generator(this, function (
|
|
519
|
-
switch (
|
|
594
|
+
var previousSessionId, currentDeviceId, isSessionChange, deviceIdForReplayId, joinedConfig;
|
|
595
|
+
return tslib_1.__generator(this, function (_e) {
|
|
596
|
+
switch (_e.label) {
|
|
520
597
|
case 0:
|
|
598
|
+
previousSessionId = (_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId;
|
|
599
|
+
currentDeviceId = this.getDeviceId();
|
|
600
|
+
// Standalone SDK callers may poll setSessionId with a stable bucket id (e.g. hour-aligned
|
|
601
|
+
// timestamps) and only need a no-op when the bucket hasn't rolled. Without this guard,
|
|
602
|
+
// the rest of asyncSetSessionId still runs: sendEvents, targeting reset, config refetch,
|
|
603
|
+
// and recordEvents (stop + restart rrweb). Proceed when deviceId changes or
|
|
604
|
+
// non-empty userProperties are passed so targeting can re-evaluate on Identify.
|
|
605
|
+
if (previousSessionId !== undefined &&
|
|
606
|
+
previousSessionId === sessionId &&
|
|
607
|
+
(deviceId === undefined || deviceId === currentDeviceId) &&
|
|
608
|
+
((options === null || options === void 0 ? void 0 : options.userProperties) === undefined || Object.keys(options.userProperties).length === 0)) {
|
|
609
|
+
return [2 /*return*/];
|
|
610
|
+
}
|
|
521
611
|
// Invalidate any in-flight URL-change re-evaluations from the previous session.
|
|
522
612
|
this.latestUrlChangeTargetingEvaluationId++;
|
|
523
613
|
this.sessionTargetingMatch = false;
|
|
524
614
|
this.lastShouldRecordDecision = undefined; // Reset targeting decision for new session
|
|
525
|
-
previousSessionId = this.identifiers && this.identifiers.sessionId;
|
|
526
615
|
if (previousSessionId) {
|
|
527
616
|
this.sendEvents(previousSessionId);
|
|
528
617
|
}
|
|
@@ -533,7 +622,7 @@ var SessionReplay = /** @class */ (function () {
|
|
|
533
622
|
// would compute the wrong elapsed duration. Skip on a redundant same-sessionId call —
|
|
534
623
|
// the buffer belongs to the *continuing* session and should ship via beacon as normal.
|
|
535
624
|
if (isSessionChange) {
|
|
536
|
-
(
|
|
625
|
+
(_b = this.rrwebEventManager) === null || _b === void 0 ? void 0 : _b.dropPendingBeaconEvents();
|
|
537
626
|
}
|
|
538
627
|
deviceIdForReplayId = deviceId || this.getDeviceId();
|
|
539
628
|
this.identifiers = new identifiers_1.SessionIdentifiers({
|
|
@@ -548,7 +637,7 @@ var SessionReplay = /** @class */ (function () {
|
|
|
548
637
|
this.sessionStartTime = Date.now();
|
|
549
638
|
this.suppressedSendCount = 0;
|
|
550
639
|
this.hasEmittedGateDecision = false;
|
|
551
|
-
if ((
|
|
640
|
+
if ((_c = this.config) === null || _c === void 0 ? void 0 : _c.apiKey) {
|
|
552
641
|
(0, replay_start_time_store_1.setReplayStartTime)(this.config.apiKey, sessionId, this.sessionStartTime, this.loggerProvider);
|
|
553
642
|
if (previousSessionId !== undefined) {
|
|
554
643
|
(0, replay_start_time_store_1.removeReplayStartTime)(this.config.apiKey, previousSessionId, this.loggerProvider);
|
|
@@ -558,19 +647,19 @@ var SessionReplay = /** @class */ (function () {
|
|
|
558
647
|
if (!(this.joinedConfigGenerator && previousSessionId)) return [3 /*break*/, 2];
|
|
559
648
|
return [4 /*yield*/, this.joinedConfigGenerator.generateJoinedConfig()];
|
|
560
649
|
case 1:
|
|
561
|
-
joinedConfig = (
|
|
650
|
+
joinedConfig = (_e.sent()).joinedConfig;
|
|
562
651
|
this.config = joinedConfig;
|
|
563
|
-
|
|
652
|
+
_e.label = 2;
|
|
564
653
|
case 2:
|
|
565
|
-
if (!((
|
|
654
|
+
if (!((_d = this.config) === null || _d === void 0 ? void 0 : _d.targetingConfig)) return [3 /*break*/, 4];
|
|
566
655
|
return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options === null || options === void 0 ? void 0 : options.userProperties, page: this.getCurrentPageForTargeting() }, false, true)];
|
|
567
656
|
case 3:
|
|
568
|
-
|
|
657
|
+
_e.sent();
|
|
569
658
|
return [3 /*break*/, 6];
|
|
570
659
|
case 4: return [4 /*yield*/, this.recordEvents()];
|
|
571
660
|
case 5:
|
|
572
|
-
|
|
573
|
-
|
|
661
|
+
_e.sent();
|
|
662
|
+
_e.label = 6;
|
|
574
663
|
case 6: return [2 /*return*/];
|
|
575
664
|
}
|
|
576
665
|
});
|
|
@@ -623,6 +712,9 @@ var SessionReplay = /** @class */ (function () {
|
|
|
623
712
|
// Cross-session leak is prevented in asyncSetSessionId, which drops the buffer
|
|
624
713
|
// at the session transition. The page-leave path also gates independently.
|
|
625
714
|
this.suppressedSendCount++;
|
|
715
|
+
// gap #1: recording but events held back by min_session_duration — another "recording yet
|
|
716
|
+
// no replay in Amplitude" cause.
|
|
717
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.sendSuppressedMinDuration);
|
|
626
718
|
return;
|
|
627
719
|
}
|
|
628
720
|
// On the first send-after-pass for the session, emit a custom rrweb event so the
|
|
@@ -679,17 +771,107 @@ var SessionReplay = /** @class */ (function () {
|
|
|
679
771
|
}
|
|
680
772
|
return identityStoreOptOut !== undefined ? identityStoreOptOut : (_b = this.config) === null || _b === void 0 ? void 0 : _b.optOut;
|
|
681
773
|
};
|
|
774
|
+
/**
|
|
775
|
+
* Increment a diagnostics counter so the team can see the distribution of recording decisions
|
|
776
|
+
* across a customer's sessions (e.g. lots of sr.gate.trc_no_match => rule isn't matching).
|
|
777
|
+
* Ships via the analytics SDK's DiagnosticsClient. No-op when there's no client or diagnostics
|
|
778
|
+
* isn't sampled in; never throws — diagnostics must never interfere with recording.
|
|
779
|
+
*/
|
|
780
|
+
SessionReplay.prototype.incrementDiagnostic = function (counter) {
|
|
781
|
+
var _a, _b;
|
|
782
|
+
try {
|
|
783
|
+
(_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.diagnosticsClient) === null || _b === void 0 ? void 0 : _b.increment(counter);
|
|
784
|
+
}
|
|
785
|
+
catch (_c) {
|
|
786
|
+
// swallow — diagnostics is best-effort
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
/**
|
|
790
|
+
* Record a diagnostics histogram value (min/max/avg/count in DataDog). Same best-effort, never-
|
|
791
|
+
* throws contract as incrementDiagnostic. Use for latencies/sizes, not per-call counts.
|
|
792
|
+
*/
|
|
793
|
+
SessionReplay.prototype.recordDiagnosticHistogram = function (name, value) {
|
|
794
|
+
var _a, _b;
|
|
795
|
+
try {
|
|
796
|
+
// The only caller runs inside evaluateTargetingAndCapture's `this.config` guard, so the
|
|
797
|
+
// `config == null` arm of this optional chain is unreachable here (kept as defensive parity
|
|
798
|
+
// with recordDiagnosticEvent). The diagnosticsClient-absent arm IS exercised by no-client tests.
|
|
799
|
+
/* istanbul ignore next */
|
|
800
|
+
(_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.diagnosticsClient) === null || _b === void 0 ? void 0 : _b.recordHistogram(name, value);
|
|
801
|
+
}
|
|
802
|
+
catch (_c) {
|
|
803
|
+
// swallow — diagnostics is best-effort
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
/**
|
|
807
|
+
* Record a diagnostics EVENT with properties. Events are forwarded to DataDog Logs, so the
|
|
808
|
+
* properties become queryable fields (e.g. @matched, @pageUrl). Use sparingly vs counters: the
|
|
809
|
+
* client caps in-memory events (~10 per save interval), so this is for context-rich signals at
|
|
810
|
+
* meaningful decision points, not per-call tallies. Best-effort, never throws.
|
|
811
|
+
*/
|
|
812
|
+
SessionReplay.prototype.recordDiagnosticEvent = function (name, properties) {
|
|
813
|
+
var _a, _b, _c;
|
|
814
|
+
try {
|
|
815
|
+
var sessionId = (_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId;
|
|
816
|
+
var deviceId = this.getDeviceId();
|
|
817
|
+
// Always stamp sessionId, deviceId and srId so every diagnostics event (→ DataDog Logs) can
|
|
818
|
+
// be correlated to a single session/device — and to the actual replay, since the Session
|
|
819
|
+
// Replay ID is `${deviceId}/${sessionId}`. Caller props override if they pass their own.
|
|
820
|
+
(_c = (_b = this.config) === null || _b === void 0 ? void 0 : _b.diagnosticsClient) === null || _c === void 0 ? void 0 : _c.recordEvent(name, tslib_1.__assign({ sessionId: sessionId, deviceId: deviceId, srId: deviceId != null && sessionId != null ? "".concat(deviceId, "/").concat(sessionId) : undefined }, properties));
|
|
821
|
+
}
|
|
822
|
+
catch (_d) {
|
|
823
|
+
// swallow — diagnostics is best-effort
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
/**
|
|
827
|
+
* Emit a single rich TRC decision snapshot per session to diagnostics — surfaced even when
|
|
828
|
+
* nothing records (the exact case customers can't reproduce locally).
|
|
829
|
+
*/
|
|
830
|
+
SessionReplay.prototype.recordTrcDecisionDiagnostic = function (shouldRecord) {
|
|
831
|
+
var _a, _b;
|
|
832
|
+
var config = this.config;
|
|
833
|
+
// Every caller is past getShouldRecord's `!this.identifiers`/`!this.config` guard, so both are
|
|
834
|
+
// defined here; the `?.` arms below are unreachable defensive narrowing and excluded from
|
|
835
|
+
// coverage. The reachable guards (no diagnosticsClient, undefined/duplicate sessionId) are tested.
|
|
836
|
+
/* istanbul ignore next */
|
|
837
|
+
if (!config) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
/* istanbul ignore next */
|
|
841
|
+
var sessionId = (_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId;
|
|
842
|
+
// Once past this guard, config.diagnosticsClient is truthy, so the fields below read config
|
|
843
|
+
// directly rather than re-asserting with `?.` on every access.
|
|
844
|
+
if (!config.diagnosticsClient || sessionId === undefined || sessionId === this.trcDiagnosticSessionId) {
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
this.trcDiagnosticSessionId = sessionId;
|
|
848
|
+
// Goes through recordDiagnosticEvent so it carries sessionId + deviceId like every other event.
|
|
849
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.decision, {
|
|
850
|
+
shouldRecord: shouldRecord,
|
|
851
|
+
captureEnabled: config.captureEnabled,
|
|
852
|
+
hasTargetingConfig: !!config.targetingConfig,
|
|
853
|
+
sessionTargetingMatch: this.sessionTargetingMatch,
|
|
854
|
+
sampleRate: config.sampleRate,
|
|
855
|
+
pageUrl: (_b = this.getCurrentPageForTargeting()) === null || _b === void 0 ? void 0 : _b.url,
|
|
856
|
+
sdkVersion: version_1.VERSION,
|
|
857
|
+
});
|
|
858
|
+
};
|
|
682
859
|
SessionReplay.prototype.getShouldRecord = function () {
|
|
683
860
|
if (!this.identifiers || !this.config || !this.identifiers.sessionId) {
|
|
684
861
|
this.loggerProvider.warn("Session is not being recorded due to lack of config, please call sessionReplay.init.");
|
|
862
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateNoIdentifiers);
|
|
685
863
|
return false;
|
|
686
864
|
}
|
|
687
865
|
if (!this.config.captureEnabled) {
|
|
688
866
|
this.loggerProvider.log("Session ".concat(this.identifiers.sessionId, " not being captured due to capture being disabled for project or because the remote config could not be fetched."));
|
|
867
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateCaptureDisabled);
|
|
868
|
+
this.recordTrcDecisionDiagnostic(false);
|
|
689
869
|
return false;
|
|
690
870
|
}
|
|
691
871
|
if (this.shouldOptOut()) {
|
|
692
872
|
this.loggerProvider.log("Opting session ".concat(this.identifiers.sessionId, " out of recording due to optOut config."));
|
|
873
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateOptOut);
|
|
874
|
+
this.recordTrcDecisionDiagnostic(false);
|
|
693
875
|
return false;
|
|
694
876
|
}
|
|
695
877
|
var shouldRecord = false;
|
|
@@ -703,12 +885,14 @@ var SessionReplay = /** @class */ (function () {
|
|
|
703
885
|
this.loggerProvider.log(message);
|
|
704
886
|
shouldRecord = false;
|
|
705
887
|
matched = false;
|
|
888
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateTrcNoMatch);
|
|
706
889
|
}
|
|
707
890
|
else {
|
|
708
891
|
message = "Capturing replays for session ".concat(this.identifiers.sessionId, " due to matching targeting conditions.");
|
|
709
892
|
this.loggerProvider.log(message);
|
|
710
893
|
shouldRecord = true;
|
|
711
894
|
matched = true;
|
|
895
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateTrcMatch);
|
|
712
896
|
}
|
|
713
897
|
}
|
|
714
898
|
else {
|
|
@@ -718,10 +902,12 @@ var SessionReplay = /** @class */ (function () {
|
|
|
718
902
|
this.loggerProvider.log(message);
|
|
719
903
|
shouldRecord = false;
|
|
720
904
|
matched = false;
|
|
905
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateSampleOut);
|
|
721
906
|
}
|
|
722
907
|
else {
|
|
723
908
|
shouldRecord = true;
|
|
724
909
|
matched = true;
|
|
910
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateSampleIn);
|
|
725
911
|
}
|
|
726
912
|
}
|
|
727
913
|
// Only send custom rrweb event for targeting decision when the decision changes
|
|
@@ -734,6 +920,7 @@ var SessionReplay = /** @class */ (function () {
|
|
|
734
920
|
});
|
|
735
921
|
this.lastShouldRecordDecision = shouldRecord;
|
|
736
922
|
}
|
|
923
|
+
this.recordTrcDecisionDiagnostic(shouldRecord);
|
|
737
924
|
return shouldRecord;
|
|
738
925
|
};
|
|
739
926
|
SessionReplay.prototype.getBlockSelectors = function () {
|
|
@@ -898,6 +1085,9 @@ var SessionReplay = /** @class */ (function () {
|
|
|
898
1085
|
recordFunction = _o.sent();
|
|
899
1086
|
// May be undefined if cannot import rrweb-record
|
|
900
1087
|
if (!recordFunction) {
|
|
1088
|
+
// gap #1: gate said record, but rrweb couldn't load → no replay despite shouldRecord=true.
|
|
1089
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.recordNoRecordFn);
|
|
1090
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.recordNoRecordFn, {});
|
|
901
1091
|
return [2 /*return*/];
|
|
902
1092
|
}
|
|
903
1093
|
return [4 /*yield*/, this.initializeNetworkObservers()];
|
|
@@ -928,6 +1118,11 @@ var SessionReplay = /** @class */ (function () {
|
|
|
928
1118
|
: {};
|
|
929
1119
|
ugcFilterRules = (interactionConfig === null || interactionConfig === void 0 ? void 0 : interactionConfig.enabled) && interactionConfig.ugcFilterRules ? interactionConfig.ugcFilterRules : [];
|
|
930
1120
|
this.loggerProvider.log("Session Replay capture beginning for ".concat(sessionId, "."));
|
|
1121
|
+
// gap #1: rrweb is actually starting. Closes the "gate said yes but no replay" blind spot,
|
|
1122
|
+
// and its srId is the id the replay uploads under (compare with analytics events to catch the
|
|
1123
|
+
// device-id mismatch that breaks stitching).
|
|
1124
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.recordStarted);
|
|
1125
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.recordStarted, {});
|
|
931
1126
|
_o.label = 3;
|
|
932
1127
|
case 3:
|
|
933
1128
|
_o.trys.push([3, 5, , 6]);
|