@openreplay/tracker 18.0.12-beta.1 → 18.0.13-beta.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/dist/cjs/entry.js +120 -42
- package/dist/cjs/entry.js.map +1 -1
- package/dist/cjs/index.js +120 -42
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/main/app/nodes/index.d.ts +1 -0
- package/dist/cjs/main/app/observer/observer.d.ts +6 -0
- package/dist/lib/entry.js +120 -42
- package/dist/lib/entry.js.map +1 -1
- package/dist/lib/index.js +120 -42
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/main/app/nodes/index.d.ts +1 -0
- package/dist/lib/main/app/observer/observer.d.ts +6 -0
- package/dist/types/main/app/nodes/index.d.ts +1 -0
- package/dist/types/main/app/observer/observer.d.ts +6 -0
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -2344,8 +2344,9 @@ class Nodes {
|
|
|
2344
2344
|
this.nextNodeId = this.createFrameId(level, frameOrder);
|
|
2345
2345
|
}
|
|
2346
2346
|
registerNode(node) {
|
|
2347
|
-
|
|
2348
|
-
const isNew =
|
|
2347
|
+
const existing = node[this.node_id];
|
|
2348
|
+
const isNew = existing === undefined || this.nodes.get(existing) !== node;
|
|
2349
|
+
let id = existing;
|
|
2349
2350
|
if (isNew) {
|
|
2350
2351
|
id = this.nextNodeId;
|
|
2351
2352
|
this.totalNodeAmount++;
|
|
@@ -2374,6 +2375,12 @@ class Nodes {
|
|
|
2374
2375
|
return undefined;
|
|
2375
2376
|
return node[this.node_id];
|
|
2376
2377
|
}
|
|
2378
|
+
isBound(node) {
|
|
2379
|
+
if (!node)
|
|
2380
|
+
return false;
|
|
2381
|
+
const id = node[this.node_id];
|
|
2382
|
+
return id !== undefined && this.nodes.get(id) === node;
|
|
2383
|
+
}
|
|
2377
2384
|
getNode(id) {
|
|
2378
2385
|
return this.nodes.get(id);
|
|
2379
2386
|
}
|
|
@@ -2972,46 +2979,58 @@ class Observer {
|
|
|
2972
2979
|
this.inlinerOptions = options.inlinerOptions;
|
|
2973
2980
|
this.observer = createMutationObserver(this.app.safe((mutations) => {
|
|
2974
2981
|
for (const mutation of mutations) {
|
|
2975
|
-
//
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2982
|
+
// THEORY S1: app.safe() wraps this whole callback in a try/catch that
|
|
2983
|
+
// SILENTLY swallows — so a throw while processing one mutation aborts
|
|
2984
|
+
// the entire batch (the single commitNodes() below never runs) and
|
|
2985
|
+
// every pending node in it is lost. Isolating + logging per mutation
|
|
2986
|
+
// both surfaces it into the session and prevents one bad node from
|
|
2987
|
+
// dropping the rest of the batch.
|
|
2988
|
+
try {
|
|
2989
|
+
// mutations order is sequential
|
|
2990
|
+
const target = mutation.target;
|
|
2991
|
+
const type = mutation.type;
|
|
2992
|
+
if (!isObservable(target)) {
|
|
2993
|
+
continue;
|
|
2994
|
+
}
|
|
2995
|
+
if (type === 'childList') {
|
|
2996
|
+
for (let i = 0; i < mutation.removedNodes.length; i++) {
|
|
2997
|
+
// Should be the same as bindTree(mutation.removedNodes[i]), but logic needs to be be untied
|
|
2998
|
+
if (isObservable(mutation.removedNodes[i])) {
|
|
2999
|
+
this.bindNode(mutation.removedNodes[i]);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
|
3003
|
+
this.bindTree(mutation.addedNodes[i]);
|
|
2986
3004
|
}
|
|
3005
|
+
continue;
|
|
2987
3006
|
}
|
|
2988
|
-
|
|
2989
|
-
|
|
3007
|
+
const id = this.app.nodes.getID(target);
|
|
3008
|
+
if (id === undefined) {
|
|
3009
|
+
continue;
|
|
2990
3010
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3011
|
+
if (!this.recents.has(id)) {
|
|
3012
|
+
this.recents.set(id, RecentsType.Changed); // TODO only when altered
|
|
3013
|
+
}
|
|
3014
|
+
if (type === 'attributes') {
|
|
3015
|
+
const name = mutation.attributeName;
|
|
3016
|
+
if (name === null) {
|
|
3017
|
+
continue;
|
|
3018
|
+
}
|
|
3019
|
+
let attr = this.attributesMap.get(id);
|
|
3020
|
+
if (attr === undefined) {
|
|
3021
|
+
this.attributesMap.set(id, (attr = new Set()));
|
|
3022
|
+
}
|
|
3023
|
+
attr.add(name);
|
|
3003
3024
|
continue;
|
|
3004
3025
|
}
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3026
|
+
if (type === 'characterData') {
|
|
3027
|
+
this.textSet.add(id);
|
|
3028
|
+
continue;
|
|
3008
3029
|
}
|
|
3009
|
-
attr.add(name);
|
|
3010
|
-
continue;
|
|
3011
3030
|
}
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3031
|
+
catch (mutationErr) {
|
|
3032
|
+
const t = mutation.target;
|
|
3033
|
+
this.orDebug(`mutation processing threw type=${mutation.type} target=<${t.tagName ?? t.nodeName ?? 'unknown'}>: ${mutationErr?.message ?? String(mutationErr)}`);
|
|
3015
3034
|
}
|
|
3016
3035
|
}
|
|
3017
3036
|
this.commitNodes();
|
|
@@ -3177,10 +3196,11 @@ class Observer {
|
|
|
3177
3196
|
this.bindNode(node);
|
|
3178
3197
|
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
|
|
3179
3198
|
acceptNode: (node) => {
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3199
|
+
// Use the Map-aware isBound() rather than the raw __openreplay_id
|
|
3200
|
+
// property: a node carrying a stale id from a previous observe cycle
|
|
3201
|
+
// must be re-accepted and re-bound here, otherwise the snapshot walk
|
|
3202
|
+
// silently skips it (and its subtree, e.g. a <style>'s CSS text).
|
|
3203
|
+
return isIgnored(node) || this.app.nodes.isBound(node)
|
|
3184
3204
|
? NodeFilter.FILTER_REJECT
|
|
3185
3205
|
: NodeFilter.FILTER_ACCEPT;
|
|
3186
3206
|
},
|
|
@@ -3234,15 +3254,25 @@ class Observer {
|
|
|
3234
3254
|
if (parent === null) {
|
|
3235
3255
|
// Sometimes one observation contains attribute mutations for the removimg node, which gets ignored here.
|
|
3236
3256
|
// That shouldn't affect the visual rendering ( should it? maybe when transition applied? )
|
|
3257
|
+
// THEORY: an element silently dropped at commit time (no message emitted).
|
|
3258
|
+
if (isElementNode(node)) {
|
|
3259
|
+
this.orDebug(`commit drop <${node.tagName}> reason=no-parent`);
|
|
3260
|
+
}
|
|
3237
3261
|
this.unbindTree(node);
|
|
3238
3262
|
return false;
|
|
3239
3263
|
}
|
|
3240
3264
|
parentID = this.app.nodes.getID(parent);
|
|
3241
3265
|
if (parentID === undefined) {
|
|
3266
|
+
if (isElementNode(node)) {
|
|
3267
|
+
this.orDebug(`commit drop <${node.tagName}> reason=parent-unbound parent=<${parent.tagName ?? parent.nodeName}>`);
|
|
3268
|
+
}
|
|
3242
3269
|
this.unbindTree(node);
|
|
3243
3270
|
return false;
|
|
3244
3271
|
}
|
|
3245
3272
|
if (!this.commitNode(parentID)) {
|
|
3273
|
+
if (isElementNode(node)) {
|
|
3274
|
+
this.orDebug(`commit drop <${node.tagName}> reason=parent-commit-failed`);
|
|
3275
|
+
}
|
|
3246
3276
|
this.unbindTree(node);
|
|
3247
3277
|
return false;
|
|
3248
3278
|
}
|
|
@@ -3386,7 +3416,45 @@ class Observer {
|
|
|
3386
3416
|
beforeCommit(this.app.nodes.getID(node));
|
|
3387
3417
|
this.commitNodes(true);
|
|
3388
3418
|
}
|
|
3419
|
+
/**
|
|
3420
|
+
* [OPENREPLAYDEBUG] Emits a diagnostic line INTO the recorded session's
|
|
3421
|
+
* console (searchable in replay by "[OPENREPLAYDEBUG]"), NOT the local
|
|
3422
|
+
* devtools console — used to test capture-loss theories against real traffic.
|
|
3423
|
+
*/
|
|
3424
|
+
orDebug(message) {
|
|
3425
|
+
try {
|
|
3426
|
+
this.app.send(ConsoleLog('warn', `[OPENREPLAYDEBUG] ${message}`));
|
|
3427
|
+
}
|
|
3428
|
+
catch (_) {
|
|
3429
|
+
/* diagnostics must never break recording */
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3389
3432
|
disconnect() {
|
|
3433
|
+
// THEORY S3: a disconnect may discard MutationRecords still queued by the
|
|
3434
|
+
// browser. takeRecords() drains them — they would be discarded by
|
|
3435
|
+
// disconnect() below anyway, so this changes nothing functionally, it only
|
|
3436
|
+
// lets us SEE whether a disconnect strands un-processed DOM additions
|
|
3437
|
+
// (e.g. a freshly appended <head> <style>). NOTE: if this disconnect is part
|
|
3438
|
+
// of stop(), the surrounding buffer may be cleared and this log lost; it is
|
|
3439
|
+
// most reliable for mid-session re-observes (cold-start cycle / iframe).
|
|
3440
|
+
const pending = this.observer.takeRecords();
|
|
3441
|
+
if (pending.length) {
|
|
3442
|
+
let addedEls = 0;
|
|
3443
|
+
const tags = [];
|
|
3444
|
+
for (const m of pending) {
|
|
3445
|
+
for (let i = 0; i < m.addedNodes.length; i++) {
|
|
3446
|
+
const n = m.addedNodes[i];
|
|
3447
|
+
if (n.tagName) {
|
|
3448
|
+
addedEls++;
|
|
3449
|
+
if (tags.length < 10)
|
|
3450
|
+
tags.push(n.tagName);
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
if (addedEls > 0) {
|
|
3455
|
+
this.orDebug(`disconnect stranded ${pending.length} pending record(s), ${addedEls} added element(s): ${tags.join(',')}`);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3390
3458
|
this.observer.disconnect();
|
|
3391
3459
|
this.clear();
|
|
3392
3460
|
this.throttledSetNodeData.clear();
|
|
@@ -4062,7 +4130,7 @@ class App {
|
|
|
4062
4130
|
this.stopCallbacks = [];
|
|
4063
4131
|
this.commitCallbacks = [];
|
|
4064
4132
|
this.activityState = ActivityState.NotActive;
|
|
4065
|
-
this.version = '18.0.
|
|
4133
|
+
this.version = '18.0.13-beta.0'; // TODO: version compatability check inside each plugin.
|
|
4066
4134
|
this.socketMode = false;
|
|
4067
4135
|
this.compressionThreshold = 24 * 1000;
|
|
4068
4136
|
this.bc = null;
|
|
@@ -4986,6 +5054,16 @@ class App {
|
|
|
4986
5054
|
}
|
|
4987
5055
|
else if (data === 'not_init') {
|
|
4988
5056
|
this.debug.warn('OR WebWorker: writer not initialised. Restarting tracker');
|
|
5057
|
+
// [OPENREPLAYDEBUG] THEORY: a message batch reached the worker before the
|
|
5058
|
+
// writer was initialised (start handshake not finished) and was DISCARDED.
|
|
5059
|
+
// Surface it into the session (searchable in replay) to test whether
|
|
5060
|
+
// startup batches — which can carry the initial DOM snapshot — are lost.
|
|
5061
|
+
try {
|
|
5062
|
+
this.send(ConsoleLog('warn', '[OPENREPLAYDEBUG] worker not_init: a pre-start message batch was discarded'));
|
|
5063
|
+
}
|
|
5064
|
+
catch (_) {
|
|
5065
|
+
/* diagnostics must never break recording */
|
|
5066
|
+
}
|
|
4989
5067
|
}
|
|
4990
5068
|
else if (data.type === 'failure') {
|
|
4991
5069
|
this.stop(false);
|
|
@@ -9360,7 +9438,7 @@ class ConstantProperties {
|
|
|
9360
9438
|
user_id: this.user_id,
|
|
9361
9439
|
distinct_id: this.deviceId,
|
|
9362
9440
|
sdk_edition: 'web',
|
|
9363
|
-
sdk_version: '18.0.
|
|
9441
|
+
sdk_version: '18.0.13-beta.0',
|
|
9364
9442
|
timezone: getUTCOffsetString(),
|
|
9365
9443
|
search_engine: this.searchEngine,
|
|
9366
9444
|
};
|
|
@@ -10062,7 +10140,7 @@ class API {
|
|
|
10062
10140
|
this.signalStartIssue = (reason, missingApi) => {
|
|
10063
10141
|
const doNotTrack = this.checkDoNotTrack();
|
|
10064
10142
|
console.log("Tracker couldn't start due to:", JSON.stringify({
|
|
10065
|
-
trackerVersion: '18.0.
|
|
10143
|
+
trackerVersion: '18.0.13-beta.0',
|
|
10066
10144
|
projectKey: this.options.projectKey,
|
|
10067
10145
|
doNotTrack,
|
|
10068
10146
|
reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
|