@amplitude/session-replay-browser 1.46.0 → 1.47.0-sr-trc-debug-log.1
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 +63 -19
- 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 +250 -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 +47 -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 +63 -19
- 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 +250 -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 +47 -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 +4 -4
|
@@ -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,111 @@ 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, _d, _e, _f;
|
|
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
|
+
// Flush immediately so the event ships now rather than on the client's ~5-min timer (and so
|
|
822
|
+
// short sessions that never reach the timer aren't lost). NOTE: this sends one capture POST
|
|
823
|
+
// per event — higher request volume; revisit/gate before production.
|
|
824
|
+
void ((_f = (_e = (_d = this.config) === null || _d === void 0 ? void 0 : _d.diagnosticsClient) === null || _e === void 0 ? void 0 : _e._flush) === null || _f === void 0 ? void 0 : _f.call(_e));
|
|
825
|
+
}
|
|
826
|
+
catch (_g) {
|
|
827
|
+
// swallow — diagnostics is best-effort
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
/**
|
|
831
|
+
* Emit a single rich TRC decision snapshot per session to diagnostics — surfaced even when
|
|
832
|
+
* nothing records (the exact case customers can't reproduce locally).
|
|
833
|
+
*/
|
|
834
|
+
SessionReplay.prototype.recordTrcDecisionDiagnostic = function (shouldRecord) {
|
|
835
|
+
var _a, _b;
|
|
836
|
+
var config = this.config;
|
|
837
|
+
// Every caller is past getShouldRecord's `!this.identifiers`/`!this.config` guard, so both are
|
|
838
|
+
// defined here; the `?.` arms below are unreachable defensive narrowing and excluded from
|
|
839
|
+
// coverage. The reachable guards (no diagnosticsClient, undefined/duplicate sessionId) are tested.
|
|
840
|
+
/* istanbul ignore next */
|
|
841
|
+
if (!config) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
/* istanbul ignore next */
|
|
845
|
+
var sessionId = (_a = this.identifiers) === null || _a === void 0 ? void 0 : _a.sessionId;
|
|
846
|
+
// Once past this guard, config.diagnosticsClient is truthy, so the fields below read config
|
|
847
|
+
// directly rather than re-asserting with `?.` on every access.
|
|
848
|
+
if (!config.diagnosticsClient || sessionId === undefined || sessionId === this.trcDiagnosticSessionId) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
this.trcDiagnosticSessionId = sessionId;
|
|
852
|
+
// Goes through recordDiagnosticEvent so it carries sessionId + deviceId like every other event.
|
|
853
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.decision, {
|
|
854
|
+
shouldRecord: shouldRecord,
|
|
855
|
+
captureEnabled: config.captureEnabled,
|
|
856
|
+
hasTargetingConfig: !!config.targetingConfig,
|
|
857
|
+
sessionTargetingMatch: this.sessionTargetingMatch,
|
|
858
|
+
sampleRate: config.sampleRate,
|
|
859
|
+
pageUrl: (_b = this.getCurrentPageForTargeting()) === null || _b === void 0 ? void 0 : _b.url,
|
|
860
|
+
sdkVersion: version_1.VERSION,
|
|
861
|
+
});
|
|
862
|
+
};
|
|
682
863
|
SessionReplay.prototype.getShouldRecord = function () {
|
|
683
864
|
if (!this.identifiers || !this.config || !this.identifiers.sessionId) {
|
|
684
865
|
this.loggerProvider.warn("Session is not being recorded due to lack of config, please call sessionReplay.init.");
|
|
866
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateNoIdentifiers);
|
|
685
867
|
return false;
|
|
686
868
|
}
|
|
687
869
|
if (!this.config.captureEnabled) {
|
|
688
870
|
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."));
|
|
871
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateCaptureDisabled);
|
|
872
|
+
this.recordTrcDecisionDiagnostic(false);
|
|
689
873
|
return false;
|
|
690
874
|
}
|
|
691
875
|
if (this.shouldOptOut()) {
|
|
692
876
|
this.loggerProvider.log("Opting session ".concat(this.identifiers.sessionId, " out of recording due to optOut config."));
|
|
877
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateOptOut);
|
|
878
|
+
this.recordTrcDecisionDiagnostic(false);
|
|
693
879
|
return false;
|
|
694
880
|
}
|
|
695
881
|
var shouldRecord = false;
|
|
@@ -703,12 +889,14 @@ var SessionReplay = /** @class */ (function () {
|
|
|
703
889
|
this.loggerProvider.log(message);
|
|
704
890
|
shouldRecord = false;
|
|
705
891
|
matched = false;
|
|
892
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateTrcNoMatch);
|
|
706
893
|
}
|
|
707
894
|
else {
|
|
708
895
|
message = "Capturing replays for session ".concat(this.identifiers.sessionId, " due to matching targeting conditions.");
|
|
709
896
|
this.loggerProvider.log(message);
|
|
710
897
|
shouldRecord = true;
|
|
711
898
|
matched = true;
|
|
899
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateTrcMatch);
|
|
712
900
|
}
|
|
713
901
|
}
|
|
714
902
|
else {
|
|
@@ -718,10 +906,12 @@ var SessionReplay = /** @class */ (function () {
|
|
|
718
906
|
this.loggerProvider.log(message);
|
|
719
907
|
shouldRecord = false;
|
|
720
908
|
matched = false;
|
|
909
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateSampleOut);
|
|
721
910
|
}
|
|
722
911
|
else {
|
|
723
912
|
shouldRecord = true;
|
|
724
913
|
matched = true;
|
|
914
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.gateSampleIn);
|
|
725
915
|
}
|
|
726
916
|
}
|
|
727
917
|
// Only send custom rrweb event for targeting decision when the decision changes
|
|
@@ -734,6 +924,7 @@ var SessionReplay = /** @class */ (function () {
|
|
|
734
924
|
});
|
|
735
925
|
this.lastShouldRecordDecision = shouldRecord;
|
|
736
926
|
}
|
|
927
|
+
this.recordTrcDecisionDiagnostic(shouldRecord);
|
|
737
928
|
return shouldRecord;
|
|
738
929
|
};
|
|
739
930
|
SessionReplay.prototype.getBlockSelectors = function () {
|
|
@@ -898,6 +1089,9 @@ var SessionReplay = /** @class */ (function () {
|
|
|
898
1089
|
recordFunction = _o.sent();
|
|
899
1090
|
// May be undefined if cannot import rrweb-record
|
|
900
1091
|
if (!recordFunction) {
|
|
1092
|
+
// gap #1: gate said record, but rrweb couldn't load → no replay despite shouldRecord=true.
|
|
1093
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.recordNoRecordFn);
|
|
1094
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.recordNoRecordFn, {});
|
|
901
1095
|
return [2 /*return*/];
|
|
902
1096
|
}
|
|
903
1097
|
return [4 /*yield*/, this.initializeNetworkObservers()];
|
|
@@ -928,6 +1122,11 @@ var SessionReplay = /** @class */ (function () {
|
|
|
928
1122
|
: {};
|
|
929
1123
|
ugcFilterRules = (interactionConfig === null || interactionConfig === void 0 ? void 0 : interactionConfig.enabled) && interactionConfig.ugcFilterRules ? interactionConfig.ugcFilterRules : [];
|
|
930
1124
|
this.loggerProvider.log("Session Replay capture beginning for ".concat(sessionId, "."));
|
|
1125
|
+
// gap #1: rrweb is actually starting. Closes the "gate said yes but no replay" blind spot,
|
|
1126
|
+
// and its srId is the id the replay uploads under (compare with analytics events to catch the
|
|
1127
|
+
// device-id mismatch that breaks stitching).
|
|
1128
|
+
this.incrementDiagnostic(diagnostics_1.SrDiagnostic.recordStarted);
|
|
1129
|
+
this.recordDiagnosticEvent(diagnostics_1.SrDiagnostic.recordStarted, {});
|
|
931
1130
|
_o.label = 3;
|
|
932
1131
|
case 3:
|
|
933
1132
|
_o.trys.push([3, 5, , 6]);
|