@openreplay/tracker 17.2.3 → 17.2.5

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 CHANGED
@@ -1746,7 +1746,7 @@ class CanvasRecorder {
1746
1746
  setTimeout(() => {
1747
1747
  this.app.nodes.scanTree(this.captureCanvas);
1748
1748
  this.app.nodes.attachNodeCallback(this.captureCanvas);
1749
- }, 250);
1749
+ }, 125);
1750
1750
  }
1751
1751
  sendSnaps(images, canvasId, createdAt) {
1752
1752
  if (Object.keys(this.snapshots).length === 0) {
@@ -1777,14 +1777,20 @@ class CanvasRecorder {
1777
1777
  }
1778
1778
  async uploadBatch(images, canvasId, createdAt) {
1779
1779
  if (this.options.isDebug) {
1780
- const fileEntries = [];
1781
- images.forEach((snapshot) => {
1782
- if (!snapshot.data)
1783
- return;
1784
- fileEntries.push({ data: snapshot.data, name: `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}` });
1785
- });
1786
- void saveArchive(fileEntries, `canvas_${canvasId}_${createdAt}`);
1787
- return;
1780
+ const packed = await packFrames(images);
1781
+ if (packed) {
1782
+ const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
1783
+ const url = URL.createObjectURL(new Blob([packed]));
1784
+ const link = document.createElement('a');
1785
+ link.href = url;
1786
+ link.download = fileName;
1787
+ link.style.display = 'none';
1788
+ document.body.appendChild(link);
1789
+ link.click();
1790
+ document.body.removeChild(link);
1791
+ URL.revokeObjectURL(url);
1792
+ }
1793
+ // fall through to also send to backend
1788
1794
  }
1789
1795
  let formData;
1790
1796
  if (this.options.framesSupport) {
@@ -1929,80 +1935,36 @@ function captureSnapshot(canvas, quality = 'medium', dummy, fixedScaling = false
1929
1935
  canvas.toBlob(onBlob, imageFormat, qualityInt[quality]);
1930
1936
  }
1931
1937
  }
1932
- async function saveArchive(files, archiveName) {
1933
- const zipBlob = await createZipBlob(files);
1934
- const url = URL.createObjectURL(zipBlob);
1935
- const link = document.createElement('a');
1936
- link.href = url;
1937
- link.download = `${archiveName}.zip`;
1938
- link.style.display = 'none';
1939
- document.body.appendChild(link);
1940
- link.click();
1941
- document.body.removeChild(link);
1942
- URL.revokeObjectURL(url);
1943
- }
1944
- async function createZipBlob(files) {
1945
- const parts = [];
1946
- const centralDir = [];
1938
+ async function packFrames(images) {
1939
+ const buffers = [];
1940
+ let totalSize = 0;
1941
+ for (const snapshot of images) {
1942
+ if (!snapshot.data)
1943
+ continue;
1944
+ const ab = await snapshot.data.arrayBuffer();
1945
+ buffers.push(ab);
1946
+ totalSize += 8 + 4 + ab.byteLength;
1947
+ }
1948
+ if (totalSize === 0)
1949
+ return null;
1950
+ const packed = new ArrayBuffer(totalSize);
1951
+ const view = new DataView(packed);
1952
+ const bytes = new Uint8Array(packed);
1947
1953
  let offset = 0;
1948
- for (const file of files) {
1949
- const nameBytes = new TextEncoder().encode(file.name);
1950
- const dataBytes = new Uint8Array(await file.data.arrayBuffer());
1951
- const crc = crc32(dataBytes);
1952
- // Local file header (30 bytes + filename)
1953
- const local = new Uint8Array(30 + nameBytes.length);
1954
- const lv = new DataView(local.buffer);
1955
- lv.setUint32(0, 0x04034b50, true);
1956
- lv.setUint16(4, 20, true);
1957
- lv.setUint16(8, 0, true);
1958
- lv.setUint32(14, crc, true);
1959
- lv.setUint32(18, dataBytes.length, true);
1960
- lv.setUint32(22, dataBytes.length, true);
1961
- lv.setUint16(26, nameBytes.length, true);
1962
- local.set(nameBytes, 30);
1963
- // Central directory entry (46 bytes + filename)
1964
- const cd = new Uint8Array(46 + nameBytes.length);
1965
- const cv = new DataView(cd.buffer);
1966
- cv.setUint32(0, 0x02014b50, true);
1967
- cv.setUint16(4, 20, true);
1968
- cv.setUint16(6, 20, true);
1969
- cv.setUint32(16, crc, true);
1970
- cv.setUint32(20, dataBytes.length, true);
1971
- cv.setUint32(24, dataBytes.length, true);
1972
- cv.setUint16(28, nameBytes.length, true);
1973
- cv.setUint32(42, offset, true);
1974
- cd.set(nameBytes, 46);
1975
- parts.push(local);
1976
- parts.push(dataBytes);
1977
- centralDir.push(cd);
1978
- offset += local.length + dataBytes.length;
1979
- }
1980
- const cdOffset = offset;
1981
- let cdSize = 0;
1982
- for (const entry of centralDir) {
1983
- parts.push(entry);
1984
- cdSize += entry.length;
1985
- }
1986
- // End of central directory (22 bytes)
1987
- const eocd = new Uint8Array(22);
1988
- const ev = new DataView(eocd.buffer);
1989
- ev.setUint32(0, 0x06054b50, true);
1990
- ev.setUint16(8, files.length, true);
1991
- ev.setUint16(10, files.length, true);
1992
- ev.setUint32(12, cdSize, true);
1993
- ev.setUint32(16, cdOffset, true);
1994
- parts.push(eocd);
1995
- return new Blob(parts, { type: 'application/zip' });
1996
- }
1997
- function crc32(data) {
1998
- let crc = 0xffffffff;
1999
- for (let i = 0; i < data.length; i++) {
2000
- crc ^= data[i];
2001
- for (let j = 0; j < 8; j++) {
2002
- crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
2003
- }
2004
- }
2005
- return (crc ^ 0xffffffff) >>> 0;
1954
+ for (let i = 0; i < images.length; i++) {
1955
+ if (!images[i].data)
1956
+ continue;
1957
+ const ab = buffers.shift();
1958
+ const ts = images[i].id;
1959
+ view.setUint32(offset, ts % 0x100000000, true);
1960
+ view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
1961
+ offset += 8;
1962
+ view.setUint32(offset, ab.byteLength, true);
1963
+ offset += 4;
1964
+ bytes.set(new Uint8Array(ab), offset);
1965
+ offset += ab.byteLength;
1966
+ }
1967
+ return packed;
2006
1968
  }
2007
1969
 
2008
1970
  const LogLevel = {
@@ -3852,7 +3814,7 @@ class App {
3852
3814
  this.stopCallbacks = [];
3853
3815
  this.commitCallbacks = [];
3854
3816
  this.activityState = ActivityState.NotActive;
3855
- this.version = '17.2.3'; // TODO: version compatability check inside each plugin.
3817
+ this.version = '17.2.5'; // TODO: version compatability check inside each plugin.
3856
3818
  this.socketMode = false;
3857
3819
  this.compressionThreshold = 24 * 1000;
3858
3820
  this.bc = null;
@@ -5036,7 +4998,6 @@ class App {
5036
4998
  if (startOpts.startCallback) {
5037
4999
  startOpts.startCallback(SuccessfulStart(onStartInfo));
5038
5000
  }
5039
- await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
5040
5001
  this.activityState = ActivityState.Active;
5041
5002
  if (this.options.crossdomain?.enabled) {
5042
5003
  void this.bootChildrenFrames();
@@ -5073,6 +5034,7 @@ class App {
5073
5034
  }
5074
5035
  this.ticker.start();
5075
5036
  this.canvasRecorder?.startTracking();
5037
+ void this.tagWatcher.fetchTags(this.options.ingestPoint, token);
5076
5038
  return SuccessfulStart(onStartInfo);
5077
5039
  }
5078
5040
  catch (reason) {
@@ -8294,6 +8256,8 @@ function webAnimations(app, options = {}) {
8294
8256
  const lastKF = anim.effect.getKeyframes().at(-1);
8295
8257
  if (!lastKF)
8296
8258
  return;
8259
+ if (!el || !(el instanceof Element) || !el.isConnected)
8260
+ return;
8297
8261
  const computedStyle = getComputedStyle(el);
8298
8262
  const keys = Object.keys(lastKF).filter((p) => !toIgnore.includes(p));
8299
8263
  // @ts-ignore
@@ -8705,7 +8669,7 @@ class ConstantProperties {
8705
8669
  user_id: this.user_id,
8706
8670
  distinct_id: this.deviceId,
8707
8671
  sdk_edition: 'web',
8708
- sdk_version: '17.2.3',
8672
+ sdk_version: '17.2.5',
8709
8673
  timezone: getUTCOffsetString(),
8710
8674
  search_engine: this.searchEngine,
8711
8675
  };
@@ -9038,10 +9002,11 @@ class People {
9038
9002
  * Creates batches of events, then sends them at intervals.
9039
9003
  */
9040
9004
  class Batcher {
9041
- constructor(backendUrl, getToken, init) {
9005
+ constructor(backendUrl, getToken, init, standalone) {
9042
9006
  this.backendUrl = backendUrl;
9043
9007
  this.getToken = getToken;
9044
9008
  this.init = init;
9009
+ this.standalone = standalone;
9045
9010
  this.autosendInterval = 5 * 1000;
9046
9011
  this.retryTimeout = 3 * 1000;
9047
9012
  this.retryLimit = 3;
@@ -9051,10 +9016,20 @@ class Batcher {
9051
9016
  [categories.events]: [],
9052
9017
  };
9053
9018
  this.intervalId = null;
9019
+ this.stopped = false;
9020
+ this.paused = false;
9021
+ this.onVisibilityChange = () => {
9022
+ this.paused = document.hidden;
9023
+ };
9054
9024
  }
9055
9025
  getBatches() {
9056
9026
  this.batch[categories.people] = this.dedupePeopleEvents();
9057
- const finalData = { data: this.batch };
9027
+ const finalData = {
9028
+ data: {
9029
+ [categories.people]: [...this.batch[categories.people]],
9030
+ [categories.events]: [...this.batch[categories.events]],
9031
+ },
9032
+ };
9058
9033
  return finalData;
9059
9034
  }
9060
9035
  addEvent(event) {
@@ -9130,6 +9105,9 @@ class Batcher {
9130
9105
  return Array.from(uniqueEventsByType.values());
9131
9106
  }
9132
9107
  sendBatch(batch) {
9108
+ if (this.stopped) {
9109
+ return;
9110
+ }
9133
9111
  const sentBatch = batch;
9134
9112
  let attempts = 0;
9135
9113
  const send = () => {
@@ -9146,8 +9124,21 @@ class Batcher {
9146
9124
  },
9147
9125
  body: JSON.stringify(sentBatch),
9148
9126
  })
9149
- .then((response) => {
9150
- if ([403, 401].includes(response.status)) {
9127
+ .then(async (response) => {
9128
+ if (response.status === 401) {
9129
+ const body = await response.json().catch(() => null);
9130
+ if (!this.standalone && body?.error === 'token expired') {
9131
+ this.stop();
9132
+ return;
9133
+ }
9134
+ if (attempts < this.retryLimit) {
9135
+ return this.init().then(() => {
9136
+ send();
9137
+ });
9138
+ }
9139
+ return;
9140
+ }
9141
+ if (response.status === 403) {
9151
9142
  if (attempts < this.retryLimit) {
9152
9143
  return this.init().then(() => {
9153
9144
  send();
@@ -9171,8 +9162,12 @@ class Batcher {
9171
9162
  if (this.intervalId) {
9172
9163
  clearInterval(this.intervalId);
9173
9164
  }
9165
+ this.paused = document.hidden;
9166
+ document.addEventListener('visibilitychange', this.onVisibilityChange);
9174
9167
  this.intervalId = setInterval(() => {
9175
- this.flush();
9168
+ if (!this.paused) {
9169
+ this.flush();
9170
+ }
9176
9171
  }, this.autosendInterval);
9177
9172
  }
9178
9173
  flush() {
@@ -9192,6 +9187,12 @@ class Batcher {
9192
9187
  clearInterval(this.intervalId);
9193
9188
  this.intervalId = null;
9194
9189
  }
9190
+ document.removeEventListener('visibilitychange', this.onVisibilityChange);
9191
+ this.stopped = true;
9192
+ }
9193
+ restart() {
9194
+ this.stopped = false;
9195
+ this.startAutosend();
9195
9196
  }
9196
9197
  }
9197
9198
 
@@ -9258,6 +9259,19 @@ class Analytics {
9258
9259
  }
9259
9260
  }
9260
9261
  };
9262
+ /**
9263
+ * Used by tracker when running in blundled mode
9264
+ */
9265
+ this.onStart = () => {
9266
+ if (!this.standalone) {
9267
+ this.batcher.restart();
9268
+ }
9269
+ };
9270
+ this.onStop = () => {
9271
+ if (!this.standalone) {
9272
+ this.batcher.stop();
9273
+ }
9274
+ };
9261
9275
  this.reset = () => {
9262
9276
  this.people.reset(true);
9263
9277
  this.events.reset();
@@ -9296,7 +9310,7 @@ class Analytics {
9296
9310
  this.standalone = !options.notStandalone;
9297
9311
  this.token = this.sessionStorage.getItem(STORAGEKEY);
9298
9312
  this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
9299
- this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
9313
+ this.batcher = new Batcher(this.backendUrl, this._getToken, this.init, this.standalone);
9300
9314
  this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
9301
9315
  this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
9302
9316
  if (options.notStandalone) {
@@ -9357,7 +9371,7 @@ class API {
9357
9371
  this.signalStartIssue = (reason, missingApi) => {
9358
9372
  const doNotTrack = this.checkDoNotTrack();
9359
9373
  console.log("Tracker couldn't start due to:", JSON.stringify({
9360
- trackerVersion: '17.2.3',
9374
+ trackerVersion: '17.2.5',
9361
9375
  projectKey: this.options.projectKey,
9362
9376
  doNotTrack,
9363
9377
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -9509,6 +9523,12 @@ class API {
9509
9523
  : (options.analytics?.ingestPoint ?? options.ingestPoint ?? defaultEdp),
9510
9524
  projectKey: options.projectKey,
9511
9525
  });
9526
+ app.attachStartCallback(() => {
9527
+ this.analytics?.onStart();
9528
+ });
9529
+ app.attachStopCallback(() => {
9530
+ this.analytics?.onStop();
9531
+ });
9512
9532
  }
9513
9533
  if (!this.crossdomainMode) {
9514
9534
  // no need to send iframe viewport data since its a node for us