@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/cjs/index.js CHANGED
@@ -438,6 +438,23 @@ function deprecationWarn(nameOfFeature, useInstead, docsPath = '/') {
438
438
  console.warn(`OpenReplay: ${nameOfFeature} is deprecated. ${useInstead ? `Please, use ${useInstead} instead.` : ''} Visit ${DOCS_HOST}${docsPath} for more information.`);
439
439
  warnedFeatures[nameOfFeature] = true;
440
440
  }
441
+ function getCustomAttributeLabel(e, customAttributes) {
442
+ if (!customAttributes || customAttributes.length === 0)
443
+ return '';
444
+ const parts = [];
445
+ for (const attr of customAttributes) {
446
+ const value = e.getAttribute(attr);
447
+ if (value !== null) {
448
+ parts.push(`[${attr}=${value}]`);
449
+ }
450
+ }
451
+ return parts.join('');
452
+ }
453
+ function getClassSelector(e) {
454
+ if (!e.classList || e.classList.length === 0)
455
+ return '';
456
+ return '.' + Array.from(e.classList).join('.');
457
+ }
441
458
  function getLabelAttribute(e) {
442
459
  let value = e.getAttribute('data-openreplay-label');
443
460
  if (value !== null) {
@@ -3409,7 +3426,10 @@ class TopObserver extends Observer {
3409
3426
  this.app.debug.info('doc already observed for', id);
3410
3427
  return;
3411
3428
  }
3412
- const observer = new IFrameObserver(this.app, false, {});
3429
+ const observer = new IFrameObserver(this.app, false, {
3430
+ disableSprites: this.options.disableSprites,
3431
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3432
+ });
3413
3433
  this.iframeObservers.set(iframe, observer);
3414
3434
  this.docObservers.set(currentDoc, observer);
3415
3435
  this.iframeObserversArr.push(observer);
@@ -3431,7 +3451,10 @@ class TopObserver extends Observer {
3431
3451
  handle();
3432
3452
  }
3433
3453
  handleShadowRoot(shRoot) {
3434
- const observer = new ShadowRootObserver(this.app);
3454
+ const observer = new ShadowRootObserver(this.app, false, {
3455
+ disableSprites: this.options.disableSprites,
3456
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3457
+ });
3435
3458
  this.shadowRootObservers.set(shRoot, observer);
3436
3459
  observer.observe(shRoot.host);
3437
3460
  }
@@ -3467,7 +3490,10 @@ class TopObserver extends Observer {
3467
3490
  };
3468
3491
  this.app.nodes.clear();
3469
3492
  this.app.nodes.crossdomainMode(frameLevel, frameOder);
3470
- const iframeObserver = new IFrameObserver(this.app);
3493
+ const iframeObserver = new IFrameObserver(this.app, false, {
3494
+ disableSprites: this.options.disableSprites,
3495
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3496
+ });
3471
3497
  this.iframeObservers.set(window.document, iframeObserver);
3472
3498
  iframeObserver.syntheticObserve(rootNodeId, window.document);
3473
3499
  }
@@ -3826,7 +3852,7 @@ class App {
3826
3852
  this.stopCallbacks = [];
3827
3853
  this.commitCallbacks = [];
3828
3854
  this.activityState = ActivityState.NotActive;
3829
- this.version = '17.2.2'; // TODO: version compatability check inside each plugin.
3855
+ this.version = '17.2.4'; // TODO: version compatability check inside each plugin.
3830
3856
  this.socketMode = false;
3831
3857
  this.compressionThreshold = 24 * 1000;
3832
3858
  this.bc = null;
@@ -4256,9 +4282,6 @@ class App {
4256
4282
  window.addEventListener('message', this.parentCrossDomainFrameListener);
4257
4283
  window.addEventListener('message', this.crossDomainIframeListener);
4258
4284
  setInterval(() => {
4259
- if (document.hidden) {
4260
- return;
4261
- }
4262
4285
  window.parent.postMessage({
4263
4286
  line: proto.polling,
4264
4287
  context: this.contextId,
@@ -5728,19 +5751,21 @@ const labelElementFor = IN_BROWSER && 'labels' in HTMLInputElement.prototype
5728
5751
  }
5729
5752
  }
5730
5753
  };
5731
- function getInputLabel(node) {
5732
- let label = getLabelAttribute(node);
5733
- if (label === null) {
5734
- const labelElement = labelElementFor(node);
5735
- label =
5736
- (labelElement && labelElement.innerText) ||
5737
- node.placeholder ||
5738
- node.name ||
5739
- node.id ||
5740
- node.className ||
5741
- node.type;
5742
- }
5743
- return normSpaces(label).slice(0, 100);
5754
+ function getInputLabel(node, customAttributes) {
5755
+ const openreplayLabel = getLabelAttribute(node);
5756
+ if (openreplayLabel !== null)
5757
+ return normSpaces(openreplayLabel).slice(0, 100);
5758
+ const customAttributeLabel = getCustomAttributeLabel(node, customAttributes);
5759
+ if (customAttributeLabel)
5760
+ return customAttributeLabel;
5761
+ if (node.id)
5762
+ return `#${node.id}`;
5763
+ const classLabel = getClassSelector(node);
5764
+ if (classLabel)
5765
+ return classLabel;
5766
+ const labelElement = labelElementFor(node);
5767
+ const label = node.name || node.placeholder || (labelElement && labelElement.innerText) || node.type;
5768
+ return normSpaces(label || '').slice(0, 100);
5744
5769
  }
5745
5770
  const InputMode = {
5746
5771
  Plain: 0,
@@ -5821,7 +5846,7 @@ function Input (app, opts) {
5821
5846
  }, 3);
5822
5847
  function sendInputChange(id, node, hesitationTime, inputTime) {
5823
5848
  const { value, mask } = getInputValue(id, node);
5824
- let label = getInputLabel(node);
5849
+ let label = getInputLabel(node, options.customAttributes);
5825
5850
  if (app.sanitizer.privateMode) {
5826
5851
  label = label.replaceAll(/./g, '*');
5827
5852
  }
@@ -5930,21 +5955,28 @@ function _getTarget(target, document) {
5930
5955
  return target === document.documentElement ? null : target;
5931
5956
  }
5932
5957
  function Mouse (app, options) {
5933
- const { disableClickmaps = false } = options || {};
5958
+ const { disableClickmaps = false, customAttributes } = options || {};
5934
5959
  function getTargetLabel(target) {
5935
5960
  const dl = getLabelAttribute(target);
5936
5961
  if (dl !== null) {
5937
5962
  return dl;
5938
5963
  }
5939
5964
  if (hasTag(target, 'input')) {
5940
- return getInputLabel(target);
5941
- }
5965
+ return getInputLabel(target, customAttributes);
5966
+ }
5967
+ const customAttributeLabel = getCustomAttributeLabel(target, customAttributes);
5968
+ if (customAttributeLabel)
5969
+ return customAttributeLabel;
5970
+ if (target.id)
5971
+ return `#${target.id}`;
5972
+ const classLabel = getClassSelector(target);
5973
+ if (classLabel)
5974
+ return classLabel;
5942
5975
  if (isClickable(target)) {
5943
5976
  let label = '';
5944
5977
  if (target instanceof HTMLElement) {
5945
5978
  label = app.sanitizer.getInnerTextSecure(target);
5946
5979
  }
5947
- label = label || target.id || target.className;
5948
5980
  return normSpaces(label).slice(0, 100);
5949
5981
  }
5950
5982
  return '';
@@ -6088,6 +6120,11 @@ function getCSSPath(el) {
6088
6120
  return false;
6089
6121
  if (el.id)
6090
6122
  return `#${cssEscape(el.id)}`;
6123
+ // if has data attributes - use them as they are more likely to be stable and unique
6124
+ const dataAttr = Array.from(el.attributes).find(attr => attr.name.startsWith('data-'));
6125
+ if (dataAttr) {
6126
+ return `[${dataAttr.name}="${cssEscape(dataAttr.value)}"]`;
6127
+ }
6091
6128
  const parts = [];
6092
6129
  while (el && el.nodeType === 1 && el !== el.ownerDocument) {
6093
6130
  if (el.id) {
@@ -8619,7 +8656,7 @@ class ConstantProperties {
8619
8656
  user_id: this.user_id,
8620
8657
  distinct_id: this.deviceId,
8621
8658
  sdk_edition: 'web',
8622
- sdk_version: '17.2.2',
8659
+ sdk_version: '17.2.4',
8623
8660
  timezone: getUTCOffsetString(),
8624
8661
  search_engine: this.searchEngine,
8625
8662
  };
@@ -8952,10 +8989,11 @@ class People {
8952
8989
  * Creates batches of events, then sends them at intervals.
8953
8990
  */
8954
8991
  class Batcher {
8955
- constructor(backendUrl, getToken, init) {
8992
+ constructor(backendUrl, getToken, init, standalone) {
8956
8993
  this.backendUrl = backendUrl;
8957
8994
  this.getToken = getToken;
8958
8995
  this.init = init;
8996
+ this.standalone = standalone;
8959
8997
  this.autosendInterval = 5 * 1000;
8960
8998
  this.retryTimeout = 3 * 1000;
8961
8999
  this.retryLimit = 3;
@@ -8965,10 +9003,20 @@ class Batcher {
8965
9003
  [categories.events]: [],
8966
9004
  };
8967
9005
  this.intervalId = null;
9006
+ this.stopped = false;
9007
+ this.paused = false;
9008
+ this.onVisibilityChange = () => {
9009
+ this.paused = document.hidden;
9010
+ };
8968
9011
  }
8969
9012
  getBatches() {
8970
9013
  this.batch[categories.people] = this.dedupePeopleEvents();
8971
- const finalData = { data: this.batch };
9014
+ const finalData = {
9015
+ data: {
9016
+ [categories.people]: [...this.batch[categories.people]],
9017
+ [categories.events]: [...this.batch[categories.events]],
9018
+ },
9019
+ };
8972
9020
  return finalData;
8973
9021
  }
8974
9022
  addEvent(event) {
@@ -9044,6 +9092,9 @@ class Batcher {
9044
9092
  return Array.from(uniqueEventsByType.values());
9045
9093
  }
9046
9094
  sendBatch(batch) {
9095
+ if (this.stopped) {
9096
+ return;
9097
+ }
9047
9098
  const sentBatch = batch;
9048
9099
  let attempts = 0;
9049
9100
  const send = () => {
@@ -9060,8 +9111,21 @@ class Batcher {
9060
9111
  },
9061
9112
  body: JSON.stringify(sentBatch),
9062
9113
  })
9063
- .then((response) => {
9064
- if ([403, 401].includes(response.status)) {
9114
+ .then(async (response) => {
9115
+ if (response.status === 401) {
9116
+ const body = await response.json().catch(() => null);
9117
+ if (!this.standalone && body?.error === 'token expired') {
9118
+ this.stop();
9119
+ return;
9120
+ }
9121
+ if (attempts < this.retryLimit) {
9122
+ return this.init().then(() => {
9123
+ send();
9124
+ });
9125
+ }
9126
+ return;
9127
+ }
9128
+ if (response.status === 403) {
9065
9129
  if (attempts < this.retryLimit) {
9066
9130
  return this.init().then(() => {
9067
9131
  send();
@@ -9085,8 +9149,12 @@ class Batcher {
9085
9149
  if (this.intervalId) {
9086
9150
  clearInterval(this.intervalId);
9087
9151
  }
9152
+ this.paused = document.hidden;
9153
+ document.addEventListener('visibilitychange', this.onVisibilityChange);
9088
9154
  this.intervalId = setInterval(() => {
9089
- this.flush();
9155
+ if (!this.paused) {
9156
+ this.flush();
9157
+ }
9090
9158
  }, this.autosendInterval);
9091
9159
  }
9092
9160
  flush() {
@@ -9106,6 +9174,12 @@ class Batcher {
9106
9174
  clearInterval(this.intervalId);
9107
9175
  this.intervalId = null;
9108
9176
  }
9177
+ document.removeEventListener('visibilitychange', this.onVisibilityChange);
9178
+ this.stopped = true;
9179
+ }
9180
+ restart() {
9181
+ this.stopped = false;
9182
+ this.startAutosend();
9109
9183
  }
9110
9184
  }
9111
9185
 
@@ -9172,6 +9246,19 @@ class Analytics {
9172
9246
  }
9173
9247
  }
9174
9248
  };
9249
+ /**
9250
+ * Used by tracker when running in blundled mode
9251
+ */
9252
+ this.onStart = () => {
9253
+ if (!this.standalone) {
9254
+ this.batcher.restart();
9255
+ }
9256
+ };
9257
+ this.onStop = () => {
9258
+ if (!this.standalone) {
9259
+ this.batcher.stop();
9260
+ }
9261
+ };
9175
9262
  this.reset = () => {
9176
9263
  this.people.reset(true);
9177
9264
  this.events.reset();
@@ -9210,7 +9297,7 @@ class Analytics {
9210
9297
  this.standalone = !options.notStandalone;
9211
9298
  this.token = this.sessionStorage.getItem(STORAGEKEY);
9212
9299
  this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
9213
- this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
9300
+ this.batcher = new Batcher(this.backendUrl, this._getToken, this.init, this.standalone);
9214
9301
  this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
9215
9302
  this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
9216
9303
  if (options.notStandalone) {
@@ -9271,7 +9358,7 @@ class API {
9271
9358
  this.signalStartIssue = (reason, missingApi) => {
9272
9359
  const doNotTrack = this.checkDoNotTrack();
9273
9360
  console.log("Tracker couldn't start due to:", JSON.stringify({
9274
- trackerVersion: '17.2.2',
9361
+ trackerVersion: '17.2.4',
9275
9362
  projectKey: this.options.projectKey,
9276
9363
  doNotTrack,
9277
9364
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -9423,6 +9510,12 @@ class API {
9423
9510
  : (options.analytics?.ingestPoint ?? options.ingestPoint ?? defaultEdp),
9424
9511
  projectKey: options.projectKey,
9425
9512
  });
9513
+ app.attachStartCallback(() => {
9514
+ this.analytics?.onStart();
9515
+ });
9516
+ app.attachStopCallback(() => {
9517
+ this.analytics?.onStop();
9518
+ });
9426
9519
  }
9427
9520
  if (!this.crossdomainMode) {
9428
9521
  // no need to send iframe viewport data since its a node for us
@@ -9434,7 +9527,7 @@ class API {
9434
9527
  // no tabs in iframes yet
9435
9528
  Tabs(app);
9436
9529
  }
9437
- Mouse(app, options.mouse);
9530
+ Mouse(app, { ...options.mouse, customAttributes: options.customAttributes });
9438
9531
  // inside iframe, we ignore viewport scroll
9439
9532
  Scroll(app, this.crossdomainMode);
9440
9533
  CSSRules(app, options.css);