@openreplay/tracker 17.2.2 → 17.2.4

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
@@ -434,6 +434,23 @@ function deprecationWarn(nameOfFeature, useInstead, docsPath = '/') {
434
434
  console.warn(`OpenReplay: ${nameOfFeature} is deprecated. ${useInstead ? `Please, use ${useInstead} instead.` : ''} Visit ${DOCS_HOST}${docsPath} for more information.`);
435
435
  warnedFeatures[nameOfFeature] = true;
436
436
  }
437
+ function getCustomAttributeLabel(e, customAttributes) {
438
+ if (!customAttributes || customAttributes.length === 0)
439
+ return '';
440
+ const parts = [];
441
+ for (const attr of customAttributes) {
442
+ const value = e.getAttribute(attr);
443
+ if (value !== null) {
444
+ parts.push(`[${attr}=${value}]`);
445
+ }
446
+ }
447
+ return parts.join('');
448
+ }
449
+ function getClassSelector(e) {
450
+ if (!e.classList || e.classList.length === 0)
451
+ return '';
452
+ return '.' + Array.from(e.classList).join('.');
453
+ }
437
454
  function getLabelAttribute(e) {
438
455
  let value = e.getAttribute('data-openreplay-label');
439
456
  if (value !== null) {
@@ -3405,7 +3422,10 @@ class TopObserver extends Observer {
3405
3422
  this.app.debug.info('doc already observed for', id);
3406
3423
  return;
3407
3424
  }
3408
- const observer = new IFrameObserver(this.app, false, {});
3425
+ const observer = new IFrameObserver(this.app, false, {
3426
+ disableSprites: this.options.disableSprites,
3427
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3428
+ });
3409
3429
  this.iframeObservers.set(iframe, observer);
3410
3430
  this.docObservers.set(currentDoc, observer);
3411
3431
  this.iframeObserversArr.push(observer);
@@ -3427,7 +3447,10 @@ class TopObserver extends Observer {
3427
3447
  handle();
3428
3448
  }
3429
3449
  handleShadowRoot(shRoot) {
3430
- const observer = new ShadowRootObserver(this.app);
3450
+ const observer = new ShadowRootObserver(this.app, false, {
3451
+ disableSprites: this.options.disableSprites,
3452
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3453
+ });
3431
3454
  this.shadowRootObservers.set(shRoot, observer);
3432
3455
  observer.observe(shRoot.host);
3433
3456
  }
@@ -3463,7 +3486,10 @@ class TopObserver extends Observer {
3463
3486
  };
3464
3487
  this.app.nodes.clear();
3465
3488
  this.app.nodes.crossdomainMode(frameLevel, frameOder);
3466
- const iframeObserver = new IFrameObserver(this.app);
3489
+ const iframeObserver = new IFrameObserver(this.app, false, {
3490
+ disableSprites: this.options.disableSprites,
3491
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3492
+ });
3467
3493
  this.iframeObservers.set(window.document, iframeObserver);
3468
3494
  iframeObserver.syntheticObserve(rootNodeId, window.document);
3469
3495
  }
@@ -3822,7 +3848,7 @@ class App {
3822
3848
  this.stopCallbacks = [];
3823
3849
  this.commitCallbacks = [];
3824
3850
  this.activityState = ActivityState.NotActive;
3825
- this.version = '17.2.2'; // TODO: version compatability check inside each plugin.
3851
+ this.version = '17.2.4'; // TODO: version compatability check inside each plugin.
3826
3852
  this.socketMode = false;
3827
3853
  this.compressionThreshold = 24 * 1000;
3828
3854
  this.bc = null;
@@ -4252,9 +4278,6 @@ class App {
4252
4278
  window.addEventListener('message', this.parentCrossDomainFrameListener);
4253
4279
  window.addEventListener('message', this.crossDomainIframeListener);
4254
4280
  setInterval(() => {
4255
- if (document.hidden) {
4256
- return;
4257
- }
4258
4281
  window.parent.postMessage({
4259
4282
  line: proto.polling,
4260
4283
  context: this.contextId,
@@ -5724,19 +5747,21 @@ const labelElementFor = IN_BROWSER && 'labels' in HTMLInputElement.prototype
5724
5747
  }
5725
5748
  }
5726
5749
  };
5727
- function getInputLabel(node) {
5728
- let label = getLabelAttribute(node);
5729
- if (label === null) {
5730
- const labelElement = labelElementFor(node);
5731
- label =
5732
- (labelElement && labelElement.innerText) ||
5733
- node.placeholder ||
5734
- node.name ||
5735
- node.id ||
5736
- node.className ||
5737
- node.type;
5738
- }
5739
- return normSpaces(label).slice(0, 100);
5750
+ function getInputLabel(node, customAttributes) {
5751
+ const openreplayLabel = getLabelAttribute(node);
5752
+ if (openreplayLabel !== null)
5753
+ return normSpaces(openreplayLabel).slice(0, 100);
5754
+ const customAttributeLabel = getCustomAttributeLabel(node, customAttributes);
5755
+ if (customAttributeLabel)
5756
+ return customAttributeLabel;
5757
+ if (node.id)
5758
+ return `#${node.id}`;
5759
+ const classLabel = getClassSelector(node);
5760
+ if (classLabel)
5761
+ return classLabel;
5762
+ const labelElement = labelElementFor(node);
5763
+ const label = node.name || node.placeholder || (labelElement && labelElement.innerText) || node.type;
5764
+ return normSpaces(label || '').slice(0, 100);
5740
5765
  }
5741
5766
  const InputMode = {
5742
5767
  Plain: 0,
@@ -5817,7 +5842,7 @@ function Input (app, opts) {
5817
5842
  }, 3);
5818
5843
  function sendInputChange(id, node, hesitationTime, inputTime) {
5819
5844
  const { value, mask } = getInputValue(id, node);
5820
- let label = getInputLabel(node);
5845
+ let label = getInputLabel(node, options.customAttributes);
5821
5846
  if (app.sanitizer.privateMode) {
5822
5847
  label = label.replaceAll(/./g, '*');
5823
5848
  }
@@ -5926,21 +5951,28 @@ function _getTarget(target, document) {
5926
5951
  return target === document.documentElement ? null : target;
5927
5952
  }
5928
5953
  function Mouse (app, options) {
5929
- const { disableClickmaps = false } = options || {};
5954
+ const { disableClickmaps = false, customAttributes } = options || {};
5930
5955
  function getTargetLabel(target) {
5931
5956
  const dl = getLabelAttribute(target);
5932
5957
  if (dl !== null) {
5933
5958
  return dl;
5934
5959
  }
5935
5960
  if (hasTag(target, 'input')) {
5936
- return getInputLabel(target);
5937
- }
5961
+ return getInputLabel(target, customAttributes);
5962
+ }
5963
+ const customAttributeLabel = getCustomAttributeLabel(target, customAttributes);
5964
+ if (customAttributeLabel)
5965
+ return customAttributeLabel;
5966
+ if (target.id)
5967
+ return `#${target.id}`;
5968
+ const classLabel = getClassSelector(target);
5969
+ if (classLabel)
5970
+ return classLabel;
5938
5971
  if (isClickable(target)) {
5939
5972
  let label = '';
5940
5973
  if (target instanceof HTMLElement) {
5941
5974
  label = app.sanitizer.getInnerTextSecure(target);
5942
5975
  }
5943
- label = label || target.id || target.className;
5944
5976
  return normSpaces(label).slice(0, 100);
5945
5977
  }
5946
5978
  return '';
@@ -6084,6 +6116,11 @@ function getCSSPath(el) {
6084
6116
  return false;
6085
6117
  if (el.id)
6086
6118
  return `#${cssEscape(el.id)}`;
6119
+ // if has data attributes - use them as they are more likely to be stable and unique
6120
+ const dataAttr = Array.from(el.attributes).find(attr => attr.name.startsWith('data-'));
6121
+ if (dataAttr) {
6122
+ return `[${dataAttr.name}="${cssEscape(dataAttr.value)}"]`;
6123
+ }
6087
6124
  const parts = [];
6088
6125
  while (el && el.nodeType === 1 && el !== el.ownerDocument) {
6089
6126
  if (el.id) {
@@ -8615,7 +8652,7 @@ class ConstantProperties {
8615
8652
  user_id: this.user_id,
8616
8653
  distinct_id: this.deviceId,
8617
8654
  sdk_edition: 'web',
8618
- sdk_version: '17.2.2',
8655
+ sdk_version: '17.2.4',
8619
8656
  timezone: getUTCOffsetString(),
8620
8657
  search_engine: this.searchEngine,
8621
8658
  };
@@ -8948,10 +8985,11 @@ class People {
8948
8985
  * Creates batches of events, then sends them at intervals.
8949
8986
  */
8950
8987
  class Batcher {
8951
- constructor(backendUrl, getToken, init) {
8988
+ constructor(backendUrl, getToken, init, standalone) {
8952
8989
  this.backendUrl = backendUrl;
8953
8990
  this.getToken = getToken;
8954
8991
  this.init = init;
8992
+ this.standalone = standalone;
8955
8993
  this.autosendInterval = 5 * 1000;
8956
8994
  this.retryTimeout = 3 * 1000;
8957
8995
  this.retryLimit = 3;
@@ -8961,10 +8999,20 @@ class Batcher {
8961
8999
  [categories.events]: [],
8962
9000
  };
8963
9001
  this.intervalId = null;
9002
+ this.stopped = false;
9003
+ this.paused = false;
9004
+ this.onVisibilityChange = () => {
9005
+ this.paused = document.hidden;
9006
+ };
8964
9007
  }
8965
9008
  getBatches() {
8966
9009
  this.batch[categories.people] = this.dedupePeopleEvents();
8967
- const finalData = { data: this.batch };
9010
+ const finalData = {
9011
+ data: {
9012
+ [categories.people]: [...this.batch[categories.people]],
9013
+ [categories.events]: [...this.batch[categories.events]],
9014
+ },
9015
+ };
8968
9016
  return finalData;
8969
9017
  }
8970
9018
  addEvent(event) {
@@ -9040,6 +9088,9 @@ class Batcher {
9040
9088
  return Array.from(uniqueEventsByType.values());
9041
9089
  }
9042
9090
  sendBatch(batch) {
9091
+ if (this.stopped) {
9092
+ return;
9093
+ }
9043
9094
  const sentBatch = batch;
9044
9095
  let attempts = 0;
9045
9096
  const send = () => {
@@ -9056,8 +9107,21 @@ class Batcher {
9056
9107
  },
9057
9108
  body: JSON.stringify(sentBatch),
9058
9109
  })
9059
- .then((response) => {
9060
- if ([403, 401].includes(response.status)) {
9110
+ .then(async (response) => {
9111
+ if (response.status === 401) {
9112
+ const body = await response.json().catch(() => null);
9113
+ if (!this.standalone && body?.error === 'token expired') {
9114
+ this.stop();
9115
+ return;
9116
+ }
9117
+ if (attempts < this.retryLimit) {
9118
+ return this.init().then(() => {
9119
+ send();
9120
+ });
9121
+ }
9122
+ return;
9123
+ }
9124
+ if (response.status === 403) {
9061
9125
  if (attempts < this.retryLimit) {
9062
9126
  return this.init().then(() => {
9063
9127
  send();
@@ -9081,8 +9145,12 @@ class Batcher {
9081
9145
  if (this.intervalId) {
9082
9146
  clearInterval(this.intervalId);
9083
9147
  }
9148
+ this.paused = document.hidden;
9149
+ document.addEventListener('visibilitychange', this.onVisibilityChange);
9084
9150
  this.intervalId = setInterval(() => {
9085
- this.flush();
9151
+ if (!this.paused) {
9152
+ this.flush();
9153
+ }
9086
9154
  }, this.autosendInterval);
9087
9155
  }
9088
9156
  flush() {
@@ -9102,6 +9170,12 @@ class Batcher {
9102
9170
  clearInterval(this.intervalId);
9103
9171
  this.intervalId = null;
9104
9172
  }
9173
+ document.removeEventListener('visibilitychange', this.onVisibilityChange);
9174
+ this.stopped = true;
9175
+ }
9176
+ restart() {
9177
+ this.stopped = false;
9178
+ this.startAutosend();
9105
9179
  }
9106
9180
  }
9107
9181
 
@@ -9168,6 +9242,19 @@ class Analytics {
9168
9242
  }
9169
9243
  }
9170
9244
  };
9245
+ /**
9246
+ * Used by tracker when running in blundled mode
9247
+ */
9248
+ this.onStart = () => {
9249
+ if (!this.standalone) {
9250
+ this.batcher.restart();
9251
+ }
9252
+ };
9253
+ this.onStop = () => {
9254
+ if (!this.standalone) {
9255
+ this.batcher.stop();
9256
+ }
9257
+ };
9171
9258
  this.reset = () => {
9172
9259
  this.people.reset(true);
9173
9260
  this.events.reset();
@@ -9206,7 +9293,7 @@ class Analytics {
9206
9293
  this.standalone = !options.notStandalone;
9207
9294
  this.token = this.sessionStorage.getItem(STORAGEKEY);
9208
9295
  this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
9209
- this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
9296
+ this.batcher = new Batcher(this.backendUrl, this._getToken, this.init, this.standalone);
9210
9297
  this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
9211
9298
  this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
9212
9299
  if (options.notStandalone) {
@@ -9267,7 +9354,7 @@ class API {
9267
9354
  this.signalStartIssue = (reason, missingApi) => {
9268
9355
  const doNotTrack = this.checkDoNotTrack();
9269
9356
  console.log("Tracker couldn't start due to:", JSON.stringify({
9270
- trackerVersion: '17.2.2',
9357
+ trackerVersion: '17.2.4',
9271
9358
  projectKey: this.options.projectKey,
9272
9359
  doNotTrack,
9273
9360
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -9419,6 +9506,12 @@ class API {
9419
9506
  : (options.analytics?.ingestPoint ?? options.ingestPoint ?? defaultEdp),
9420
9507
  projectKey: options.projectKey,
9421
9508
  });
9509
+ app.attachStartCallback(() => {
9510
+ this.analytics?.onStart();
9511
+ });
9512
+ app.attachStopCallback(() => {
9513
+ this.analytics?.onStop();
9514
+ });
9422
9515
  }
9423
9516
  if (!this.crossdomainMode) {
9424
9517
  // no need to send iframe viewport data since its a node for us
@@ -9430,7 +9523,7 @@ class API {
9430
9523
  // no tabs in iframes yet
9431
9524
  Tabs(app);
9432
9525
  }
9433
- Mouse(app, options.mouse);
9526
+ Mouse(app, { ...options.mouse, customAttributes: options.customAttributes });
9434
9527
  // inside iframe, we ignore viewport scroll
9435
9528
  Scroll(app, this.crossdomainMode);
9436
9529
  CSSRules(app, options.css);