@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.
@@ -15,13 +15,15 @@ declare class Batcher {
15
15
  private readonly backendUrl;
16
16
  private readonly getToken;
17
17
  private readonly init;
18
+ private readonly standalone;
18
19
  private readonly autosendInterval;
19
20
  private readonly retryTimeout;
20
21
  private readonly retryLimit;
21
22
  private readonly apiEdp;
22
23
  private batch;
23
24
  private intervalId;
24
- constructor(backendUrl: string, getToken: () => string | null, init: () => Promise<void>);
25
+ private stopped;
26
+ constructor(backendUrl: string, getToken: () => string | null, init: () => Promise<void>, standalone: boolean);
25
27
  getBatches(): {
26
28
  data: {
27
29
  user_actions: PeopleEvent[];
@@ -38,8 +40,11 @@ declare class Batcher {
38
40
  dedupePeopleEvents(): PeopleEvent[];
39
41
  private squashPeopleEvents;
40
42
  private sendBatch;
43
+ private paused;
44
+ private onVisibilityChange;
41
45
  startAutosend(): void;
42
46
  flush(): void;
43
47
  stop(): void;
48
+ restart(): void;
44
49
  }
45
50
  export default Batcher;
@@ -51,6 +51,11 @@ export default class Analytics {
51
51
  _getToken: () => string | null;
52
52
  _getTimestamp: () => number;
53
53
  init: () => Promise<void>;
54
+ /**
55
+ * Used by tracker when running in blundled mode
56
+ */
57
+ onStart: () => void;
58
+ onStop: () => void;
54
59
  reset: () => void;
55
60
  /**
56
61
  * COMPATIBILITY LAYER
package/dist/lib/entry.js CHANGED
@@ -1742,7 +1742,7 @@ class CanvasRecorder {
1742
1742
  setTimeout(() => {
1743
1743
  this.app.nodes.scanTree(this.captureCanvas);
1744
1744
  this.app.nodes.attachNodeCallback(this.captureCanvas);
1745
- }, 250);
1745
+ }, 125);
1746
1746
  }
1747
1747
  sendSnaps(images, canvasId, createdAt) {
1748
1748
  if (Object.keys(this.snapshots).length === 0) {
@@ -1773,14 +1773,20 @@ class CanvasRecorder {
1773
1773
  }
1774
1774
  async uploadBatch(images, canvasId, createdAt) {
1775
1775
  if (this.options.isDebug) {
1776
- const fileEntries = [];
1777
- images.forEach((snapshot) => {
1778
- if (!snapshot.data)
1779
- return;
1780
- fileEntries.push({ data: snapshot.data, name: `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}` });
1781
- });
1782
- void saveArchive(fileEntries, `canvas_${canvasId}_${createdAt}`);
1783
- return;
1776
+ const packed = await packFrames(images);
1777
+ if (packed) {
1778
+ const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
1779
+ const url = URL.createObjectURL(new Blob([packed]));
1780
+ const link = document.createElement('a');
1781
+ link.href = url;
1782
+ link.download = fileName;
1783
+ link.style.display = 'none';
1784
+ document.body.appendChild(link);
1785
+ link.click();
1786
+ document.body.removeChild(link);
1787
+ URL.revokeObjectURL(url);
1788
+ }
1789
+ // fall through to also send to backend
1784
1790
  }
1785
1791
  let formData;
1786
1792
  if (this.options.framesSupport) {
@@ -1925,80 +1931,36 @@ function captureSnapshot(canvas, quality = 'medium', dummy, fixedScaling = false
1925
1931
  canvas.toBlob(onBlob, imageFormat, qualityInt[quality]);
1926
1932
  }
1927
1933
  }
1928
- async function saveArchive(files, archiveName) {
1929
- const zipBlob = await createZipBlob(files);
1930
- const url = URL.createObjectURL(zipBlob);
1931
- const link = document.createElement('a');
1932
- link.href = url;
1933
- link.download = `${archiveName}.zip`;
1934
- link.style.display = 'none';
1935
- document.body.appendChild(link);
1936
- link.click();
1937
- document.body.removeChild(link);
1938
- URL.revokeObjectURL(url);
1939
- }
1940
- async function createZipBlob(files) {
1941
- const parts = [];
1942
- const centralDir = [];
1934
+ async function packFrames(images) {
1935
+ const buffers = [];
1936
+ let totalSize = 0;
1937
+ for (const snapshot of images) {
1938
+ if (!snapshot.data)
1939
+ continue;
1940
+ const ab = await snapshot.data.arrayBuffer();
1941
+ buffers.push(ab);
1942
+ totalSize += 8 + 4 + ab.byteLength;
1943
+ }
1944
+ if (totalSize === 0)
1945
+ return null;
1946
+ const packed = new ArrayBuffer(totalSize);
1947
+ const view = new DataView(packed);
1948
+ const bytes = new Uint8Array(packed);
1943
1949
  let offset = 0;
1944
- for (const file of files) {
1945
- const nameBytes = new TextEncoder().encode(file.name);
1946
- const dataBytes = new Uint8Array(await file.data.arrayBuffer());
1947
- const crc = crc32(dataBytes);
1948
- // Local file header (30 bytes + filename)
1949
- const local = new Uint8Array(30 + nameBytes.length);
1950
- const lv = new DataView(local.buffer);
1951
- lv.setUint32(0, 0x04034b50, true);
1952
- lv.setUint16(4, 20, true);
1953
- lv.setUint16(8, 0, true);
1954
- lv.setUint32(14, crc, true);
1955
- lv.setUint32(18, dataBytes.length, true);
1956
- lv.setUint32(22, dataBytes.length, true);
1957
- lv.setUint16(26, nameBytes.length, true);
1958
- local.set(nameBytes, 30);
1959
- // Central directory entry (46 bytes + filename)
1960
- const cd = new Uint8Array(46 + nameBytes.length);
1961
- const cv = new DataView(cd.buffer);
1962
- cv.setUint32(0, 0x02014b50, true);
1963
- cv.setUint16(4, 20, true);
1964
- cv.setUint16(6, 20, true);
1965
- cv.setUint32(16, crc, true);
1966
- cv.setUint32(20, dataBytes.length, true);
1967
- cv.setUint32(24, dataBytes.length, true);
1968
- cv.setUint16(28, nameBytes.length, true);
1969
- cv.setUint32(42, offset, true);
1970
- cd.set(nameBytes, 46);
1971
- parts.push(local);
1972
- parts.push(dataBytes);
1973
- centralDir.push(cd);
1974
- offset += local.length + dataBytes.length;
1975
- }
1976
- const cdOffset = offset;
1977
- let cdSize = 0;
1978
- for (const entry of centralDir) {
1979
- parts.push(entry);
1980
- cdSize += entry.length;
1981
- }
1982
- // End of central directory (22 bytes)
1983
- const eocd = new Uint8Array(22);
1984
- const ev = new DataView(eocd.buffer);
1985
- ev.setUint32(0, 0x06054b50, true);
1986
- ev.setUint16(8, files.length, true);
1987
- ev.setUint16(10, files.length, true);
1988
- ev.setUint32(12, cdSize, true);
1989
- ev.setUint32(16, cdOffset, true);
1990
- parts.push(eocd);
1991
- return new Blob(parts, { type: 'application/zip' });
1992
- }
1993
- function crc32(data) {
1994
- let crc = 0xffffffff;
1995
- for (let i = 0; i < data.length; i++) {
1996
- crc ^= data[i];
1997
- for (let j = 0; j < 8; j++) {
1998
- crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
1999
- }
2000
- }
2001
- return (crc ^ 0xffffffff) >>> 0;
1950
+ for (let i = 0; i < images.length; i++) {
1951
+ if (!images[i].data)
1952
+ continue;
1953
+ const ab = buffers.shift();
1954
+ const ts = images[i].id;
1955
+ view.setUint32(offset, ts % 0x100000000, true);
1956
+ view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
1957
+ offset += 8;
1958
+ view.setUint32(offset, ab.byteLength, true);
1959
+ offset += 4;
1960
+ bytes.set(new Uint8Array(ab), offset);
1961
+ offset += ab.byteLength;
1962
+ }
1963
+ return packed;
2002
1964
  }
2003
1965
 
2004
1966
  const LogLevel = {
@@ -3848,7 +3810,7 @@ class App {
3848
3810
  this.stopCallbacks = [];
3849
3811
  this.commitCallbacks = [];
3850
3812
  this.activityState = ActivityState.NotActive;
3851
- this.version = '17.2.3'; // TODO: version compatability check inside each plugin.
3813
+ this.version = '17.2.5'; // TODO: version compatability check inside each plugin.
3852
3814
  this.socketMode = false;
3853
3815
  this.compressionThreshold = 24 * 1000;
3854
3816
  this.bc = null;
@@ -5032,7 +4994,6 @@ class App {
5032
4994
  if (startOpts.startCallback) {
5033
4995
  startOpts.startCallback(SuccessfulStart(onStartInfo));
5034
4996
  }
5035
- await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
5036
4997
  this.activityState = ActivityState.Active;
5037
4998
  if (this.options.crossdomain?.enabled) {
5038
4999
  void this.bootChildrenFrames();
@@ -5069,6 +5030,7 @@ class App {
5069
5030
  }
5070
5031
  this.ticker.start();
5071
5032
  this.canvasRecorder?.startTracking();
5033
+ void this.tagWatcher.fetchTags(this.options.ingestPoint, token);
5072
5034
  return SuccessfulStart(onStartInfo);
5073
5035
  }
5074
5036
  catch (reason) {
@@ -8290,6 +8252,8 @@ function webAnimations(app, options = {}) {
8290
8252
  const lastKF = anim.effect.getKeyframes().at(-1);
8291
8253
  if (!lastKF)
8292
8254
  return;
8255
+ if (!el || !(el instanceof Element) || !el.isConnected)
8256
+ return;
8293
8257
  const computedStyle = getComputedStyle(el);
8294
8258
  const keys = Object.keys(lastKF).filter((p) => !toIgnore.includes(p));
8295
8259
  // @ts-ignore
@@ -8701,7 +8665,7 @@ class ConstantProperties {
8701
8665
  user_id: this.user_id,
8702
8666
  distinct_id: this.deviceId,
8703
8667
  sdk_edition: 'web',
8704
- sdk_version: '17.2.3',
8668
+ sdk_version: '17.2.5',
8705
8669
  timezone: getUTCOffsetString(),
8706
8670
  search_engine: this.searchEngine,
8707
8671
  };
@@ -9034,10 +8998,11 @@ class People {
9034
8998
  * Creates batches of events, then sends them at intervals.
9035
8999
  */
9036
9000
  class Batcher {
9037
- constructor(backendUrl, getToken, init) {
9001
+ constructor(backendUrl, getToken, init, standalone) {
9038
9002
  this.backendUrl = backendUrl;
9039
9003
  this.getToken = getToken;
9040
9004
  this.init = init;
9005
+ this.standalone = standalone;
9041
9006
  this.autosendInterval = 5 * 1000;
9042
9007
  this.retryTimeout = 3 * 1000;
9043
9008
  this.retryLimit = 3;
@@ -9047,10 +9012,20 @@ class Batcher {
9047
9012
  [categories.events]: [],
9048
9013
  };
9049
9014
  this.intervalId = null;
9015
+ this.stopped = false;
9016
+ this.paused = false;
9017
+ this.onVisibilityChange = () => {
9018
+ this.paused = document.hidden;
9019
+ };
9050
9020
  }
9051
9021
  getBatches() {
9052
9022
  this.batch[categories.people] = this.dedupePeopleEvents();
9053
- const finalData = { data: this.batch };
9023
+ const finalData = {
9024
+ data: {
9025
+ [categories.people]: [...this.batch[categories.people]],
9026
+ [categories.events]: [...this.batch[categories.events]],
9027
+ },
9028
+ };
9054
9029
  return finalData;
9055
9030
  }
9056
9031
  addEvent(event) {
@@ -9126,6 +9101,9 @@ class Batcher {
9126
9101
  return Array.from(uniqueEventsByType.values());
9127
9102
  }
9128
9103
  sendBatch(batch) {
9104
+ if (this.stopped) {
9105
+ return;
9106
+ }
9129
9107
  const sentBatch = batch;
9130
9108
  let attempts = 0;
9131
9109
  const send = () => {
@@ -9142,8 +9120,21 @@ class Batcher {
9142
9120
  },
9143
9121
  body: JSON.stringify(sentBatch),
9144
9122
  })
9145
- .then((response) => {
9146
- if ([403, 401].includes(response.status)) {
9123
+ .then(async (response) => {
9124
+ if (response.status === 401) {
9125
+ const body = await response.json().catch(() => null);
9126
+ if (!this.standalone && body?.error === 'token expired') {
9127
+ this.stop();
9128
+ return;
9129
+ }
9130
+ if (attempts < this.retryLimit) {
9131
+ return this.init().then(() => {
9132
+ send();
9133
+ });
9134
+ }
9135
+ return;
9136
+ }
9137
+ if (response.status === 403) {
9147
9138
  if (attempts < this.retryLimit) {
9148
9139
  return this.init().then(() => {
9149
9140
  send();
@@ -9167,8 +9158,12 @@ class Batcher {
9167
9158
  if (this.intervalId) {
9168
9159
  clearInterval(this.intervalId);
9169
9160
  }
9161
+ this.paused = document.hidden;
9162
+ document.addEventListener('visibilitychange', this.onVisibilityChange);
9170
9163
  this.intervalId = setInterval(() => {
9171
- this.flush();
9164
+ if (!this.paused) {
9165
+ this.flush();
9166
+ }
9172
9167
  }, this.autosendInterval);
9173
9168
  }
9174
9169
  flush() {
@@ -9188,6 +9183,12 @@ class Batcher {
9188
9183
  clearInterval(this.intervalId);
9189
9184
  this.intervalId = null;
9190
9185
  }
9186
+ document.removeEventListener('visibilitychange', this.onVisibilityChange);
9187
+ this.stopped = true;
9188
+ }
9189
+ restart() {
9190
+ this.stopped = false;
9191
+ this.startAutosend();
9191
9192
  }
9192
9193
  }
9193
9194
 
@@ -9254,6 +9255,19 @@ class Analytics {
9254
9255
  }
9255
9256
  }
9256
9257
  };
9258
+ /**
9259
+ * Used by tracker when running in blundled mode
9260
+ */
9261
+ this.onStart = () => {
9262
+ if (!this.standalone) {
9263
+ this.batcher.restart();
9264
+ }
9265
+ };
9266
+ this.onStop = () => {
9267
+ if (!this.standalone) {
9268
+ this.batcher.stop();
9269
+ }
9270
+ };
9257
9271
  this.reset = () => {
9258
9272
  this.people.reset(true);
9259
9273
  this.events.reset();
@@ -9292,7 +9306,7 @@ class Analytics {
9292
9306
  this.standalone = !options.notStandalone;
9293
9307
  this.token = this.sessionStorage.getItem(STORAGEKEY);
9294
9308
  this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
9295
- this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
9309
+ this.batcher = new Batcher(this.backendUrl, this._getToken, this.init, this.standalone);
9296
9310
  this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
9297
9311
  this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
9298
9312
  if (options.notStandalone) {
@@ -9353,7 +9367,7 @@ class API {
9353
9367
  this.signalStartIssue = (reason, missingApi) => {
9354
9368
  const doNotTrack = this.checkDoNotTrack();
9355
9369
  console.log("Tracker couldn't start due to:", JSON.stringify({
9356
- trackerVersion: '17.2.3',
9370
+ trackerVersion: '17.2.5',
9357
9371
  projectKey: this.options.projectKey,
9358
9372
  doNotTrack,
9359
9373
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -9505,6 +9519,12 @@ class API {
9505
9519
  : (options.analytics?.ingestPoint ?? options.ingestPoint ?? defaultEdp),
9506
9520
  projectKey: options.projectKey,
9507
9521
  });
9522
+ app.attachStartCallback(() => {
9523
+ this.analytics?.onStart();
9524
+ });
9525
+ app.attachStopCallback(() => {
9526
+ this.analytics?.onStop();
9527
+ });
9508
9528
  }
9509
9529
  if (!this.crossdomainMode) {
9510
9530
  // no need to send iframe viewport data since its a node for us