@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/lib/index.js
CHANGED
|
@@ -2340,8 +2340,9 @@ class Nodes {
|
|
|
2340
2340
|
this.nextNodeId = this.createFrameId(level, frameOrder);
|
|
2341
2341
|
}
|
|
2342
2342
|
registerNode(node) {
|
|
2343
|
-
|
|
2344
|
-
const isNew =
|
|
2343
|
+
const existing = node[this.node_id];
|
|
2344
|
+
const isNew = existing === undefined || this.nodes.get(existing) !== node;
|
|
2345
|
+
let id = existing;
|
|
2345
2346
|
if (isNew) {
|
|
2346
2347
|
id = this.nextNodeId;
|
|
2347
2348
|
this.totalNodeAmount++;
|
|
@@ -2370,6 +2371,12 @@ class Nodes {
|
|
|
2370
2371
|
return undefined;
|
|
2371
2372
|
return node[this.node_id];
|
|
2372
2373
|
}
|
|
2374
|
+
isBound(node) {
|
|
2375
|
+
if (!node)
|
|
2376
|
+
return false;
|
|
2377
|
+
const id = node[this.node_id];
|
|
2378
|
+
return id !== undefined && this.nodes.get(id) === node;
|
|
2379
|
+
}
|
|
2373
2380
|
getNode(id) {
|
|
2374
2381
|
return this.nodes.get(id);
|
|
2375
2382
|
}
|
|
@@ -2968,46 +2975,58 @@ class Observer {
|
|
|
2968
2975
|
this.inlinerOptions = options.inlinerOptions;
|
|
2969
2976
|
this.observer = createMutationObserver(this.app.safe((mutations) => {
|
|
2970
2977
|
for (const mutation of mutations) {
|
|
2971
|
-
//
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2978
|
+
// THEORY S1: app.safe() wraps this whole callback in a try/catch that
|
|
2979
|
+
// SILENTLY swallows — so a throw while processing one mutation aborts
|
|
2980
|
+
// the entire batch (the single commitNodes() below never runs) and
|
|
2981
|
+
// every pending node in it is lost. Isolating + logging per mutation
|
|
2982
|
+
// both surfaces it into the session and prevents one bad node from
|
|
2983
|
+
// dropping the rest of the batch.
|
|
2984
|
+
try {
|
|
2985
|
+
// mutations order is sequential
|
|
2986
|
+
const target = mutation.target;
|
|
2987
|
+
const type = mutation.type;
|
|
2988
|
+
if (!isObservable(target)) {
|
|
2989
|
+
continue;
|
|
2990
|
+
}
|
|
2991
|
+
if (type === 'childList') {
|
|
2992
|
+
for (let i = 0; i < mutation.removedNodes.length; i++) {
|
|
2993
|
+
// Should be the same as bindTree(mutation.removedNodes[i]), but logic needs to be be untied
|
|
2994
|
+
if (isObservable(mutation.removedNodes[i])) {
|
|
2995
|
+
this.bindNode(mutation.removedNodes[i]);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
for (let i = 0; i < mutation.addedNodes.length; i++) {
|
|
2999
|
+
this.bindTree(mutation.addedNodes[i]);
|
|
2982
3000
|
}
|
|
3001
|
+
continue;
|
|
2983
3002
|
}
|
|
2984
|
-
|
|
2985
|
-
|
|
3003
|
+
const id = this.app.nodes.getID(target);
|
|
3004
|
+
if (id === undefined) {
|
|
3005
|
+
continue;
|
|
2986
3006
|
}
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
3007
|
+
if (!this.recents.has(id)) {
|
|
3008
|
+
this.recents.set(id, RecentsType.Changed); // TODO only when altered
|
|
3009
|
+
}
|
|
3010
|
+
if (type === 'attributes') {
|
|
3011
|
+
const name = mutation.attributeName;
|
|
3012
|
+
if (name === null) {
|
|
3013
|
+
continue;
|
|
3014
|
+
}
|
|
3015
|
+
let attr = this.attributesMap.get(id);
|
|
3016
|
+
if (attr === undefined) {
|
|
3017
|
+
this.attributesMap.set(id, (attr = new Set()));
|
|
3018
|
+
}
|
|
3019
|
+
attr.add(name);
|
|
2999
3020
|
continue;
|
|
3000
3021
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3022
|
+
if (type === 'characterData') {
|
|
3023
|
+
this.textSet.add(id);
|
|
3024
|
+
continue;
|
|
3004
3025
|
}
|
|
3005
|
-
attr.add(name);
|
|
3006
|
-
continue;
|
|
3007
3026
|
}
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3027
|
+
catch (mutationErr) {
|
|
3028
|
+
const t = mutation.target;
|
|
3029
|
+
this.orDebug(`mutation processing threw type=${mutation.type} target=<${t.tagName ?? t.nodeName ?? 'unknown'}>: ${mutationErr?.message ?? String(mutationErr)}`);
|
|
3011
3030
|
}
|
|
3012
3031
|
}
|
|
3013
3032
|
this.commitNodes();
|
|
@@ -3173,10 +3192,11 @@ class Observer {
|
|
|
3173
3192
|
this.bindNode(node);
|
|
3174
3193
|
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
|
|
3175
3194
|
acceptNode: (node) => {
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3195
|
+
// Use the Map-aware isBound() rather than the raw __openreplay_id
|
|
3196
|
+
// property: a node carrying a stale id from a previous observe cycle
|
|
3197
|
+
// must be re-accepted and re-bound here, otherwise the snapshot walk
|
|
3198
|
+
// silently skips it (and its subtree, e.g. a <style>'s CSS text).
|
|
3199
|
+
return isIgnored(node) || this.app.nodes.isBound(node)
|
|
3180
3200
|
? NodeFilter.FILTER_REJECT
|
|
3181
3201
|
: NodeFilter.FILTER_ACCEPT;
|
|
3182
3202
|
},
|
|
@@ -3230,15 +3250,25 @@ class Observer {
|
|
|
3230
3250
|
if (parent === null) {
|
|
3231
3251
|
// Sometimes one observation contains attribute mutations for the removimg node, which gets ignored here.
|
|
3232
3252
|
// That shouldn't affect the visual rendering ( should it? maybe when transition applied? )
|
|
3253
|
+
// THEORY: an element silently dropped at commit time (no message emitted).
|
|
3254
|
+
if (isElementNode(node)) {
|
|
3255
|
+
this.orDebug(`commit drop <${node.tagName}> reason=no-parent`);
|
|
3256
|
+
}
|
|
3233
3257
|
this.unbindTree(node);
|
|
3234
3258
|
return false;
|
|
3235
3259
|
}
|
|
3236
3260
|
parentID = this.app.nodes.getID(parent);
|
|
3237
3261
|
if (parentID === undefined) {
|
|
3262
|
+
if (isElementNode(node)) {
|
|
3263
|
+
this.orDebug(`commit drop <${node.tagName}> reason=parent-unbound parent=<${parent.tagName ?? parent.nodeName}>`);
|
|
3264
|
+
}
|
|
3238
3265
|
this.unbindTree(node);
|
|
3239
3266
|
return false;
|
|
3240
3267
|
}
|
|
3241
3268
|
if (!this.commitNode(parentID)) {
|
|
3269
|
+
if (isElementNode(node)) {
|
|
3270
|
+
this.orDebug(`commit drop <${node.tagName}> reason=parent-commit-failed`);
|
|
3271
|
+
}
|
|
3242
3272
|
this.unbindTree(node);
|
|
3243
3273
|
return false;
|
|
3244
3274
|
}
|
|
@@ -3382,7 +3412,45 @@ class Observer {
|
|
|
3382
3412
|
beforeCommit(this.app.nodes.getID(node));
|
|
3383
3413
|
this.commitNodes(true);
|
|
3384
3414
|
}
|
|
3415
|
+
/**
|
|
3416
|
+
* [OPENREPLAYDEBUG] Emits a diagnostic line INTO the recorded session's
|
|
3417
|
+
* console (searchable in replay by "[OPENREPLAYDEBUG]"), NOT the local
|
|
3418
|
+
* devtools console — used to test capture-loss theories against real traffic.
|
|
3419
|
+
*/
|
|
3420
|
+
orDebug(message) {
|
|
3421
|
+
try {
|
|
3422
|
+
this.app.send(ConsoleLog('warn', `[OPENREPLAYDEBUG] ${message}`));
|
|
3423
|
+
}
|
|
3424
|
+
catch (_) {
|
|
3425
|
+
/* diagnostics must never break recording */
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3385
3428
|
disconnect() {
|
|
3429
|
+
// THEORY S3: a disconnect may discard MutationRecords still queued by the
|
|
3430
|
+
// browser. takeRecords() drains them — they would be discarded by
|
|
3431
|
+
// disconnect() below anyway, so this changes nothing functionally, it only
|
|
3432
|
+
// lets us SEE whether a disconnect strands un-processed DOM additions
|
|
3433
|
+
// (e.g. a freshly appended <head> <style>). NOTE: if this disconnect is part
|
|
3434
|
+
// of stop(), the surrounding buffer may be cleared and this log lost; it is
|
|
3435
|
+
// most reliable for mid-session re-observes (cold-start cycle / iframe).
|
|
3436
|
+
const pending = this.observer.takeRecords();
|
|
3437
|
+
if (pending.length) {
|
|
3438
|
+
let addedEls = 0;
|
|
3439
|
+
const tags = [];
|
|
3440
|
+
for (const m of pending) {
|
|
3441
|
+
for (let i = 0; i < m.addedNodes.length; i++) {
|
|
3442
|
+
const n = m.addedNodes[i];
|
|
3443
|
+
if (n.tagName) {
|
|
3444
|
+
addedEls++;
|
|
3445
|
+
if (tags.length < 10)
|
|
3446
|
+
tags.push(n.tagName);
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
if (addedEls > 0) {
|
|
3451
|
+
this.orDebug(`disconnect stranded ${pending.length} pending record(s), ${addedEls} added element(s): ${tags.join(',')}`);
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3386
3454
|
this.observer.disconnect();
|
|
3387
3455
|
this.clear();
|
|
3388
3456
|
this.throttledSetNodeData.clear();
|
|
@@ -4058,7 +4126,7 @@ class App {
|
|
|
4058
4126
|
this.stopCallbacks = [];
|
|
4059
4127
|
this.commitCallbacks = [];
|
|
4060
4128
|
this.activityState = ActivityState.NotActive;
|
|
4061
|
-
this.version = '18.0.
|
|
4129
|
+
this.version = '18.0.13-beta.0'; // TODO: version compatability check inside each plugin.
|
|
4062
4130
|
this.socketMode = false;
|
|
4063
4131
|
this.compressionThreshold = 24 * 1000;
|
|
4064
4132
|
this.bc = null;
|
|
@@ -4982,6 +5050,16 @@ class App {
|
|
|
4982
5050
|
}
|
|
4983
5051
|
else if (data === 'not_init') {
|
|
4984
5052
|
this.debug.warn('OR WebWorker: writer not initialised. Restarting tracker');
|
|
5053
|
+
// [OPENREPLAYDEBUG] THEORY: a message batch reached the worker before the
|
|
5054
|
+
// writer was initialised (start handshake not finished) and was DISCARDED.
|
|
5055
|
+
// Surface it into the session (searchable in replay) to test whether
|
|
5056
|
+
// startup batches — which can carry the initial DOM snapshot — are lost.
|
|
5057
|
+
try {
|
|
5058
|
+
this.send(ConsoleLog('warn', '[OPENREPLAYDEBUG] worker not_init: a pre-start message batch was discarded'));
|
|
5059
|
+
}
|
|
5060
|
+
catch (_) {
|
|
5061
|
+
/* diagnostics must never break recording */
|
|
5062
|
+
}
|
|
4985
5063
|
}
|
|
4986
5064
|
else if (data.type === 'failure') {
|
|
4987
5065
|
this.stop(false);
|
|
@@ -9356,7 +9434,7 @@ class ConstantProperties {
|
|
|
9356
9434
|
user_id: this.user_id,
|
|
9357
9435
|
distinct_id: this.deviceId,
|
|
9358
9436
|
sdk_edition: 'web',
|
|
9359
|
-
sdk_version: '18.0.
|
|
9437
|
+
sdk_version: '18.0.13-beta.0',
|
|
9360
9438
|
timezone: getUTCOffsetString(),
|
|
9361
9439
|
search_engine: this.searchEngine,
|
|
9362
9440
|
};
|
|
@@ -10058,7 +10136,7 @@ class API {
|
|
|
10058
10136
|
this.signalStartIssue = (reason, missingApi) => {
|
|
10059
10137
|
const doNotTrack = this.checkDoNotTrack();
|
|
10060
10138
|
console.log("Tracker couldn't start due to:", JSON.stringify({
|
|
10061
|
-
trackerVersion: '18.0.
|
|
10139
|
+
trackerVersion: '18.0.13-beta.0',
|
|
10062
10140
|
projectKey: this.options.projectKey,
|
|
10063
10141
|
doNotTrack,
|
|
10064
10142
|
reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
|