@openreplay/tracker 18.0.12-beta.1 → 18.0.13

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/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
- let id = node[this.node_id];
2344
- const isNew = id === undefined;
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,57 @@ 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
- // mutations order is sequential
2972
- const target = mutation.target;
2973
- const type = mutation.type;
2974
- if (!isObservable(target)) {
2975
- continue;
2976
- }
2977
- if (type === 'childList') {
2978
- for (let i = 0; i < mutation.removedNodes.length; i++) {
2979
- // Should be the same as bindTree(mutation.removedNodes[i]), but logic needs to be be untied
2980
- if (isObservable(mutation.removedNodes[i])) {
2981
- this.bindNode(mutation.removedNodes[i]);
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
+ }
2982
2997
  }
2998
+ for (let i = 0; i < mutation.addedNodes.length; i++) {
2999
+ this.bindTree(mutation.addedNodes[i]);
3000
+ }
3001
+ continue;
2983
3002
  }
2984
- for (let i = 0; i < mutation.addedNodes.length; i++) {
2985
- this.bindTree(mutation.addedNodes[i]);
3003
+ const id = this.app.nodes.getID(target);
3004
+ if (id === undefined) {
3005
+ continue;
2986
3006
  }
2987
- continue;
2988
- }
2989
- const id = this.app.nodes.getID(target);
2990
- if (id === undefined) {
2991
- continue;
2992
- }
2993
- if (!this.recents.has(id)) {
2994
- this.recents.set(id, RecentsType.Changed); // TODO only when altered
2995
- }
2996
- if (type === 'attributes') {
2997
- const name = mutation.attributeName;
2998
- if (name === null) {
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
- let attr = this.attributesMap.get(id);
3002
- if (attr === undefined) {
3003
- this.attributesMap.set(id, (attr = new Set()));
3022
+ if (type === 'characterData') {
3023
+ this.textSet.add(id);
3024
+ continue;
3004
3025
  }
3005
- attr.add(name);
3006
- continue;
3007
3026
  }
3008
- if (type === 'characterData') {
3009
- this.textSet.add(id);
3010
- continue;
3027
+ catch (mutationErr) {
3028
+ mutation.target;
3011
3029
  }
3012
3030
  }
3013
3031
  this.commitNodes();
@@ -3173,10 +3191,11 @@ class Observer {
3173
3191
  this.bindNode(node);
3174
3192
  const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
3175
3193
  acceptNode: (node) => {
3176
- if (this.app.nodes.getID(node) !== undefined) {
3177
- this.app.debug.info('! Node is already bound', node);
3178
- }
3179
- return isIgnored(node) || this.app.nodes.getID(node) !== undefined
3194
+ // Use the Map-aware isBound() rather than the raw __openreplay_id
3195
+ // property: a node carrying a stale id from a previous observe cycle
3196
+ // must be re-accepted and re-bound here, otherwise the snapshot walk
3197
+ // silently skips it (and its subtree, e.g. a <style>'s CSS text).
3198
+ return isIgnored(node) || this.app.nodes.isBound(node)
3180
3199
  ? NodeFilter.FILTER_REJECT
3181
3200
  : NodeFilter.FILTER_ACCEPT;
3182
3201
  },
@@ -3228,8 +3247,6 @@ class Observer {
3228
3247
  // TODO: Clean the logic (though now it workd fine)
3229
3248
  if (!hasTag(node, 'html') || !this.isTopContext) {
3230
3249
  if (parent === null) {
3231
- // Sometimes one observation contains attribute mutations for the removimg node, which gets ignored here.
3232
- // That shouldn't affect the visual rendering ( should it? maybe when transition applied? )
3233
3250
  this.unbindTree(node);
3234
3251
  return false;
3235
3252
  }
@@ -3383,6 +3400,26 @@ class Observer {
3383
3400
  this.commitNodes(true);
3384
3401
  }
3385
3402
  disconnect() {
3403
+ // THEORY S3: a disconnect may discard MutationRecords still queued by the
3404
+ // browser. takeRecords() drains them — they would be discarded by
3405
+ // disconnect() below anyway, so this changes nothing functionally, it only
3406
+ // lets us SEE whether a disconnect strands un-processed DOM additions
3407
+ // (e.g. a freshly appended <head> <style>). NOTE: if this disconnect is part
3408
+ // of stop(), the surrounding buffer may be cleared and this log lost; it is
3409
+ // most reliable for mid-session re-observes (cold-start cycle / iframe).
3410
+ const pending = this.observer.takeRecords();
3411
+ if (pending.length) {
3412
+ const tags = [];
3413
+ for (const m of pending) {
3414
+ for (let i = 0; i < m.addedNodes.length; i++) {
3415
+ const n = m.addedNodes[i];
3416
+ if (n.tagName) {
3417
+ if (tags.length < 10)
3418
+ tags.push(n.tagName);
3419
+ }
3420
+ }
3421
+ }
3422
+ }
3386
3423
  this.observer.disconnect();
3387
3424
  this.clear();
3388
3425
  this.throttledSetNodeData.clear();
@@ -4058,7 +4095,7 @@ class App {
4058
4095
  this.stopCallbacks = [];
4059
4096
  this.commitCallbacks = [];
4060
4097
  this.activityState = ActivityState.NotActive;
4061
- this.version = '18.0.12-beta.1'; // TODO: version compatability check inside each plugin.
4098
+ this.version = '18.0.13'; // TODO: version compatability check inside each plugin.
4062
4099
  this.socketMode = false;
4063
4100
  this.compressionThreshold = 24 * 1000;
4064
4101
  this.bc = null;
@@ -9356,7 +9393,7 @@ class ConstantProperties {
9356
9393
  user_id: this.user_id,
9357
9394
  distinct_id: this.deviceId,
9358
9395
  sdk_edition: 'web',
9359
- sdk_version: '18.0.12-beta.1',
9396
+ sdk_version: '18.0.13',
9360
9397
  timezone: getUTCOffsetString(),
9361
9398
  search_engine: this.searchEngine,
9362
9399
  };
@@ -10058,7 +10095,7 @@ class API {
10058
10095
  this.signalStartIssue = (reason, missingApi) => {
10059
10096
  const doNotTrack = this.checkDoNotTrack();
10060
10097
  console.log("Tracker couldn't start due to:", JSON.stringify({
10061
- trackerVersion: '18.0.12-beta.1',
10098
+ trackerVersion: '18.0.13',
10062
10099
  projectKey: this.options.projectKey,
10063
10100
  doNotTrack,
10064
10101
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,