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