@openreplay/tracker 17.0.0 → 17.1.0

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.
Files changed (45) hide show
  1. package/dist/cjs/entry.js +1072 -47
  2. package/dist/cjs/entry.js.map +1 -1
  3. package/dist/cjs/index.js +1070 -47
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/main/app/index.d.ts +1 -0
  6. package/dist/cjs/main/app/session.d.ts +1 -0
  7. package/dist/cjs/main/entry.d.ts +5 -4
  8. package/dist/cjs/main/index.d.ts +17 -5
  9. package/dist/cjs/main/modules/analytics/batcher.d.ts +46 -0
  10. package/dist/cjs/main/modules/analytics/constantProperties.d.ts +53 -0
  11. package/dist/cjs/main/modules/analytics/demo.d.ts +0 -0
  12. package/dist/cjs/main/modules/analytics/events.d.ts +37 -0
  13. package/dist/cjs/main/modules/analytics/index.d.ts +73 -0
  14. package/dist/cjs/main/modules/analytics/people.d.ts +51 -0
  15. package/dist/cjs/main/modules/analytics/types.d.ts +32 -0
  16. package/dist/cjs/main/modules/analytics/utils.d.ts +19 -0
  17. package/dist/lib/entry.js +1071 -48
  18. package/dist/lib/entry.js.map +1 -1
  19. package/dist/lib/index.js +1070 -47
  20. package/dist/lib/index.js.map +1 -1
  21. package/dist/lib/main/app/index.d.ts +1 -0
  22. package/dist/lib/main/app/session.d.ts +1 -0
  23. package/dist/lib/main/entry.d.ts +5 -4
  24. package/dist/lib/main/index.d.ts +17 -5
  25. package/dist/lib/main/modules/analytics/batcher.d.ts +46 -0
  26. package/dist/lib/main/modules/analytics/constantProperties.d.ts +53 -0
  27. package/dist/lib/main/modules/analytics/demo.d.ts +0 -0
  28. package/dist/lib/main/modules/analytics/events.d.ts +37 -0
  29. package/dist/lib/main/modules/analytics/index.d.ts +73 -0
  30. package/dist/lib/main/modules/analytics/people.d.ts +51 -0
  31. package/dist/lib/main/modules/analytics/types.d.ts +32 -0
  32. package/dist/lib/main/modules/analytics/utils.d.ts +19 -0
  33. package/dist/types/main/app/index.d.ts +1 -0
  34. package/dist/types/main/app/session.d.ts +1 -0
  35. package/dist/types/main/entry.d.ts +5 -4
  36. package/dist/types/main/index.d.ts +17 -5
  37. package/dist/types/main/modules/analytics/batcher.d.ts +46 -0
  38. package/dist/types/main/modules/analytics/constantProperties.d.ts +53 -0
  39. package/dist/types/main/modules/analytics/demo.d.ts +0 -0
  40. package/dist/types/main/modules/analytics/events.d.ts +37 -0
  41. package/dist/types/main/modules/analytics/index.d.ts +73 -0
  42. package/dist/types/main/modules/analytics/people.d.ts +51 -0
  43. package/dist/types/main/modules/analytics/types.d.ts +32 -0
  44. package/dist/types/main/modules/analytics/utils.d.ts +19 -0
  45. package/package.json +3 -3
package/dist/cjs/entry.js CHANGED
@@ -3519,8 +3519,7 @@ class Observer {
3519
3519
  sl.assignedNodes({ flatten: true }).forEach((n) => {
3520
3520
  const nid = this.app.nodes.getID(n);
3521
3521
  if (nid !== undefined) {
3522
- this.recents.set(nid, RecentsType.Removed);
3523
- this.commitNode(nid);
3522
+ this.recents.set(nid, RecentsType.Changed);
3524
3523
  }
3525
3524
  });
3526
3525
  });
@@ -3587,13 +3586,6 @@ class Observer {
3587
3586
  return true;
3588
3587
  }
3589
3588
  let slot = node.assignedSlot;
3590
- let isLightDom = false;
3591
- if (slot) {
3592
- // Check if the node is in light DOM (not in shadow DOM)
3593
- // This is a workaround for the issue with shadow DOM and slots
3594
- // where the slot is not assigned to the node in shadow DOM.
3595
- isLightDom = node.getRootNode() instanceof ShadowRoot;
3596
- }
3597
3589
  const parent = node.parentNode;
3598
3590
  let parentID;
3599
3591
  // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
@@ -3606,15 +3598,7 @@ class Observer {
3606
3598
  this.unbindTree(node);
3607
3599
  return false;
3608
3600
  }
3609
- if (isLightDom && slot) {
3610
- parentID = this.app.nodes.getID(slot);
3611
- // in light dom, we don't "slot" the node,
3612
- // but rather use the slot as a parent
3613
- slot = null;
3614
- }
3615
- else {
3616
- parentID = this.app.nodes.getID(parent);
3617
- }
3601
+ parentID = this.app.nodes.getID(parent);
3618
3602
  if (parentID === undefined) {
3619
3603
  this.unbindTree(node);
3620
3604
  return false;
@@ -3910,12 +3894,12 @@ function getInlineOptions(mode, logger) {
3910
3894
  case InlineCssMode.Unset:
3911
3895
  const isLocalhost = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?\/?/.test(window.location.href);
3912
3896
  if (isLocalhost) {
3913
- logger(`Enabling InlineCssMode by default on localhost to preserve css styles, refer to ${localhostStylesDoc} for details, set InlineCssMode to 0 to skip this behavior`);
3897
+ logger(`Enabling InlineCssMode.PlainFetched by default on localhost to preserve css styles, refer to ${localhostStylesDoc} for details, set InlineCssMode to 0 to skip this behavior`);
3914
3898
  return {
3915
3899
  inlineRemoteCss: true,
3916
3900
  inlinerOptions: {
3917
- forceFetch: false,
3918
- forcePlain: false,
3901
+ forceFetch: true,
3902
+ forcePlain: true,
3919
3903
  },
3920
3904
  };
3921
3905
  }
@@ -4163,6 +4147,7 @@ class Sanitizer {
4163
4147
  }
4164
4148
  }
4165
4149
 
4150
+ const tokenSeparator = '_$_';
4166
4151
  class Session {
4167
4152
  constructor(params) {
4168
4153
  this.metadata = {};
@@ -4190,19 +4175,22 @@ class Session {
4190
4175
  this.getSessionToken = (projectKey) => {
4191
4176
  const tokenWithProject = this.token || this.app.sessionStorage.getItem(this.options.session_token_key);
4192
4177
  if (projectKey && tokenWithProject) {
4193
- const savedProject = tokenWithProject.split('_&_')[1];
4178
+ const savedProject = tokenWithProject.split(tokenSeparator)[1];
4194
4179
  if (!savedProject || savedProject !== projectKey) {
4195
4180
  this.app.sessionStorage.removeItem(this.options.session_token_key);
4196
4181
  this.token = undefined;
4197
4182
  return undefined;
4198
4183
  }
4199
4184
  }
4200
- const token = tokenWithProject ? tokenWithProject.split('_&_')[0] : null;
4185
+ const token = tokenWithProject ? tokenWithProject.split(tokenSeparator)[0] : null;
4201
4186
  return token || undefined;
4202
4187
  };
4188
+ this.getRawTokenWithProject = () => {
4189
+ return this.token || this.app.sessionStorage.getItem(this.options.session_token_key);
4190
+ };
4203
4191
  this.setSessionToken = (token, projectKey) => {
4204
- this.token = `${token}_&_${projectKey}`;
4205
- this.app.sessionStorage.setItem(this.options.session_token_key, `${token}_&_${projectKey}`);
4192
+ this.token = `${token}${tokenSeparator}${projectKey}`;
4193
+ this.app.sessionStorage.setItem(this.options.session_token_key, `${token}${tokenSeparator}${projectKey}`);
4206
4194
  };
4207
4195
  this.app = params.app;
4208
4196
  this.options = params.options;
@@ -4265,7 +4253,7 @@ class Session {
4265
4253
  }
4266
4254
  getSessionHash() {
4267
4255
  const pageNo = this.getPageNumber();
4268
- const token = this.getSessionToken();
4256
+ const token = this.getRawTokenWithProject();
4269
4257
  if (pageNo === undefined || token === undefined) {
4270
4258
  return;
4271
4259
  }
@@ -4395,6 +4383,9 @@ const proto = {
4395
4383
  startIframe: 'start tracker inside frame',
4396
4384
  // checking updates
4397
4385
  polling: 'hello-how-are-you-im-under-the-water-please-help-me',
4386
+ // happens if tab is old and has outdated token but
4387
+ // not communicating with backend to update it (for whatever reason)
4388
+ reset: 'reset-your-session-please',
4398
4389
  };
4399
4390
  class App {
4400
4391
  constructor(projectKey, sessionToken, options, signalError, insideIframe) {
@@ -4411,7 +4402,7 @@ class App {
4411
4402
  this.stopCallbacks = [];
4412
4403
  this.commitCallbacks = [];
4413
4404
  this.activityState = ActivityState.NotActive;
4414
- this.version = '17.0.0'; // TODO: version compatability check inside each plugin.
4405
+ this.version = '17.1.0'; // TODO: version compatability check inside each plugin.
4415
4406
  this.socketMode = false;
4416
4407
  this.compressionThreshold = 24 * 1000;
4417
4408
  this.bc = null;
@@ -4645,6 +4636,19 @@ class App {
4645
4636
  }
4646
4637
  };
4647
4638
  this.startTimeout = null;
4639
+ this.restart = () => {
4640
+ this.stop(false);
4641
+ this.waitStatus(ActivityState.NotActive).then(() => {
4642
+ this.allowAppStart();
4643
+ this.start(this.prevOpts, true)
4644
+ .then((r) => {
4645
+ this.debug.info('Session restart', r);
4646
+ })
4647
+ .catch((e) => {
4648
+ this.debug.error('Session restart failed', e);
4649
+ });
4650
+ });
4651
+ };
4648
4652
  this.send = (message, urgent = false) => {
4649
4653
  if (this.activityState === ActivityState.NotActive) {
4650
4654
  return;
@@ -4806,7 +4810,12 @@ class App {
4806
4810
  });
4807
4811
  this.session.attachUpdateCallback(({ userID, metadata }) => {
4808
4812
  if (userID != null) {
4809
- // TODO: nullable userID
4813
+ if (!userID ||
4814
+ typeof userID !== 'string' ||
4815
+ userID.trim().length === 0) {
4816
+ this.debug.warn('Invalid userID (must be type string), ignoring.');
4817
+ return;
4818
+ }
4810
4819
  this.send(UserID(userID));
4811
4820
  }
4812
4821
  if (metadata != null) {
@@ -4881,6 +4890,12 @@ class App {
4881
4890
  });
4882
4891
  }
4883
4892
  }
4893
+ if (ev.data.line === proto.reset) {
4894
+ const newToken = ev.data.token;
4895
+ this.debug.log('Received reset signal from another tab');
4896
+ this.session.setSessionToken(newToken, this.projectKey);
4897
+ this.restart();
4898
+ }
4884
4899
  };
4885
4900
  }
4886
4901
  }
@@ -5453,9 +5468,10 @@ class App {
5453
5468
  // Reset session metadata only if requested directly
5454
5469
  this.session.reset();
5455
5470
  }
5471
+ const userId = startOpts.userID ? startOpts.userID.trim() : undefined;
5456
5472
  this.session.assign({
5457
5473
  // MBTODO: maybe it would make sense to `forceNew` if the `userID` was changed
5458
- userID: startOpts.userID,
5474
+ userID: userId || undefined,
5459
5475
  metadata: startOpts.metadata,
5460
5476
  });
5461
5477
  const timestamp = now();
@@ -5522,6 +5538,12 @@ class App {
5522
5538
  }
5523
5539
  this.delay = delay;
5524
5540
  this.session.setSessionToken(token, this.projectKey);
5541
+ if (sessionToken && sessionToken !== token) {
5542
+ this.bc?.postMessage({
5543
+ type: proto.reset,
5544
+ token: token,
5545
+ });
5546
+ }
5525
5547
  this.session.setUserInfo({
5526
5548
  userBrowser,
5527
5549
  userCity,
@@ -7412,9 +7434,9 @@ function axiosSpy (app, instance, opts, sanitize, stringify) {
7412
7434
  });
7413
7435
  }
7414
7436
  function isAxiosError(payload) {
7415
- return isObject(payload) && payload.isAxiosError === true;
7437
+ return isObject$1(payload) && payload.isAxiosError === true;
7416
7438
  }
7417
- function isObject(thing) {
7439
+ function isObject$1(thing) {
7418
7440
  return thing !== null && typeof thing === 'object';
7419
7441
  }
7420
7442
 
@@ -8828,6 +8850,969 @@ function webAnimations(app, options = {}) {
8828
8850
  });
8829
8851
  }
8830
8852
 
8853
+ /**
8854
+ * Detects client browser, OS, and device information
8855
+ */
8856
+ function uaParse(sWindow) {
8857
+ const unknown = '-';
8858
+ // Screen detection
8859
+ let width = 0;
8860
+ let height = 0;
8861
+ let screenSize = '';
8862
+ if (sWindow.screen.width) {
8863
+ width = sWindow.screen.width;
8864
+ height = sWindow.screen.height;
8865
+ screenSize = `${width} x ${height}`;
8866
+ }
8867
+ // Browser detection
8868
+ const nVer = sWindow.navigator.appVersion ?? '0';
8869
+ const nAgt = sWindow.navigator.userAgent ?? 'unknown';
8870
+ let browser = sWindow.navigator.appName ?? "unknown";
8871
+ let version = String(parseFloat(nVer));
8872
+ let nameOffset;
8873
+ let verOffset;
8874
+ let ix;
8875
+ // Browser detection logic
8876
+ if ((verOffset = nAgt.indexOf('YaBrowser')) !== -1) {
8877
+ browser = 'Yandex';
8878
+ version = nAgt.substring(verOffset + 10);
8879
+ }
8880
+ else if ((verOffset = nAgt.indexOf('SamsungBrowser')) !== -1) {
8881
+ browser = 'Samsung';
8882
+ version = nAgt.substring(verOffset + 15);
8883
+ }
8884
+ else if ((verOffset = nAgt.indexOf('UCBrowser')) !== -1) {
8885
+ browser = 'UC Browser';
8886
+ version = nAgt.substring(verOffset + 10);
8887
+ }
8888
+ else if ((verOffset = nAgt.indexOf('OPR')) !== -1) {
8889
+ browser = 'Opera';
8890
+ version = nAgt.substring(verOffset + 4);
8891
+ }
8892
+ else if ((verOffset = nAgt.indexOf('Opera')) !== -1) {
8893
+ browser = 'Opera';
8894
+ version = nAgt.substring(verOffset + 6);
8895
+ if ((verOffset = nAgt.indexOf('Version')) !== -1) {
8896
+ version = nAgt.substring(verOffset + 8);
8897
+ }
8898
+ }
8899
+ else if ((verOffset = nAgt.indexOf('Edge')) !== -1) {
8900
+ browser = 'Microsoft Legacy Edge';
8901
+ version = nAgt.substring(verOffset + 5);
8902
+ }
8903
+ else if ((verOffset = nAgt.indexOf('Edg')) !== -1) {
8904
+ browser = 'Microsoft Edge';
8905
+ version = nAgt.substring(verOffset + 4);
8906
+ }
8907
+ else if ((verOffset = nAgt.indexOf('MSIE')) !== -1) {
8908
+ browser = 'Microsoft Internet Explorer';
8909
+ version = nAgt.substring(verOffset + 5);
8910
+ }
8911
+ else if ((verOffset = nAgt.indexOf('Chrome')) !== -1) {
8912
+ browser = 'Chrome';
8913
+ version = nAgt.substring(verOffset + 7);
8914
+ }
8915
+ else if ((verOffset = nAgt.indexOf('Safari')) !== -1) {
8916
+ browser = 'Safari';
8917
+ version = nAgt.substring(verOffset + 7);
8918
+ if ((verOffset = nAgt.indexOf('Version')) !== -1) {
8919
+ version = nAgt.substring(verOffset + 8);
8920
+ }
8921
+ }
8922
+ else if ((verOffset = nAgt.indexOf('Firefox')) !== -1) {
8923
+ browser = 'Firefox';
8924
+ version = nAgt.substring(verOffset + 8);
8925
+ }
8926
+ else if (nAgt.indexOf('Trident/') !== -1) {
8927
+ browser = 'Microsoft Internet Explorer';
8928
+ version = nAgt.substring(nAgt.indexOf('rv:') + 3);
8929
+ }
8930
+ else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
8931
+ browser = nAgt.substring(nameOffset, verOffset);
8932
+ version = nAgt.substring(verOffset + 1);
8933
+ if (browser.toLowerCase() === browser.toUpperCase()) {
8934
+ browser = sWindow.navigator.appName;
8935
+ }
8936
+ }
8937
+ // Trim the version string
8938
+ if ((ix = version.indexOf(';')) !== -1) {
8939
+ version = version.substring(0, ix);
8940
+ }
8941
+ if ((ix = version.indexOf(' ')) !== -1) {
8942
+ version = version.substring(0, ix);
8943
+ }
8944
+ if ((ix = version.indexOf(')')) !== -1) {
8945
+ version = version.substring(0, ix);
8946
+ }
8947
+ let majorVersion = parseInt(version, 10);
8948
+ if (isNaN(majorVersion)) {
8949
+ version = String(parseFloat(nVer));
8950
+ majorVersion = parseInt(nVer, 10);
8951
+ }
8952
+ // Mobile detection
8953
+ const mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer);
8954
+ // Cookie detection
8955
+ let cookieEnabled = sWindow.navigator.cookieEnabled || false;
8956
+ if (typeof navigator.cookieEnabled === 'undefined' && !cookieEnabled) {
8957
+ sWindow.document.cookie = 'testcookie';
8958
+ cookieEnabled = sWindow.document.cookie.indexOf('testcookie') !== -1;
8959
+ }
8960
+ // OS detection
8961
+ let os = unknown;
8962
+ const clientStrings = [
8963
+ { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ },
8964
+ { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ },
8965
+ { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ },
8966
+ { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ },
8967
+ { s: 'Windows Vista', r: /Windows NT 6.0/ },
8968
+ { s: 'Windows Server 2003', r: /Windows NT 5.2/ },
8969
+ { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ },
8970
+ { s: 'Windows 2000', r: /(Windows NT 5.0|Windows 2000)/ },
8971
+ { s: 'Windows ME', r: /(Win 9x 4.90|Windows ME)/ },
8972
+ { s: 'Windows 98', r: /(Windows 98|Win98)/ },
8973
+ { s: 'Windows 95', r: /(Windows 95|Win95|Windows_95)/ },
8974
+ { s: 'Windows NT 4.0', r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ },
8975
+ { s: 'Windows CE', r: /Windows CE/ },
8976
+ { s: 'Windows 3.11', r: /Win16/ },
8977
+ { s: 'Android', r: /Android/ },
8978
+ { s: 'Open BSD', r: /OpenBSD/ },
8979
+ { s: 'Sun OS', r: /SunOS/ },
8980
+ { s: 'Chrome OS', r: /CrOS/ },
8981
+ { s: 'Linux', r: /(Linux|X11(?!.*CrOS))/ },
8982
+ { s: 'iOS', r: /(iPhone|iPad|iPod)/ },
8983
+ { s: 'Mac OS X', r: /Mac OS X/ },
8984
+ { s: 'Mac OS', r: /(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ },
8985
+ { s: 'QNX', r: /QNX/ },
8986
+ { s: 'UNIX', r: /UNIX/ },
8987
+ { s: 'BeOS', r: /BeOS/ },
8988
+ { s: 'OS/2', r: /OS\/2/ },
8989
+ {
8990
+ s: 'Search Bot',
8991
+ r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/,
8992
+ },
8993
+ ];
8994
+ // Find matching OS
8995
+ for (const client of clientStrings) {
8996
+ if (client.r.test(nAgt)) {
8997
+ os = client.s;
8998
+ break;
8999
+ }
9000
+ }
9001
+ // OS Version detection
9002
+ let osVersion = unknown;
9003
+ if (/Windows/.test(os)) {
9004
+ const matches = /Windows (.*)/.exec(os);
9005
+ if (matches && matches[1]) {
9006
+ osVersion = matches[1];
9007
+ // Handle Windows 10/11 detection with newer API if available
9008
+ if (osVersion === '10' && 'userAgentData' in sWindow.navigator) {
9009
+ const nav = navigator;
9010
+ if (nav.userAgentData) {
9011
+ nav.userAgentData
9012
+ .getHighEntropyValues(['platformVersion'])
9013
+ .then((ua) => {
9014
+ const version = parseInt(ua.platformVersion.split('.')[0], 10);
9015
+ osVersion = version < 13 ? '10' : '11';
9016
+ })
9017
+ .catch(() => {
9018
+ // ignore errors and keep osVersion as is
9019
+ });
9020
+ }
9021
+ }
9022
+ }
9023
+ os = 'Windows';
9024
+ }
9025
+ // OS version detection for Mac/Android/iOS
9026
+ switch (os) {
9027
+ case 'Mac OS':
9028
+ case 'Mac OS X':
9029
+ case 'Android': {
9030
+ const matches = /(?:Android|Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([\.\_\d]+)/.exec(nAgt);
9031
+ osVersion = matches && matches[1] ? matches[1] : unknown;
9032
+ break;
9033
+ }
9034
+ case 'iOS': {
9035
+ const matches = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
9036
+ if (matches && matches[1]) {
9037
+ osVersion = `${matches[1]}.${matches[2]}.${parseInt(matches[3] || '0', 10)}`;
9038
+ }
9039
+ break;
9040
+ }
9041
+ }
9042
+ // Return client data
9043
+ return {
9044
+ screen: screenSize,
9045
+ width,
9046
+ height,
9047
+ browser,
9048
+ browserVersion: version,
9049
+ browserMajorVersion: majorVersion,
9050
+ mobile,
9051
+ os,
9052
+ osVersion,
9053
+ cookies: cookieEnabled,
9054
+ };
9055
+ }
9056
+ function isObject(item) {
9057
+ const isNull = item === null;
9058
+ return Boolean(item && typeof item === 'object' && !Array.isArray(item) && !isNull);
9059
+ }
9060
+ function getUTCOffsetString() {
9061
+ const date = new Date();
9062
+ const offsetMinutes = date.getTimezoneOffset();
9063
+ const hours = Math.abs(Math.floor(offsetMinutes / 60));
9064
+ const minutes = Math.abs(offsetMinutes % 60);
9065
+ const sign = offsetMinutes <= 0 ? '+' : '-';
9066
+ const hoursStr = hours.toString().padStart(2, '0');
9067
+ const minutesStr = minutes.toString().padStart(2, '0');
9068
+ return `UTC${sign}${hoursStr}:${minutesStr}`;
9069
+ }
9070
+
9071
+ const refKey = '$__or__initial_ref__$';
9072
+ const distinctIdKey = '$__or__distinct_device_id__$';
9073
+ const utmParamsKey = '$__or__utm_params__$';
9074
+ const superPropKey = '$__or__super_properties__$';
9075
+ const userIdKey = '$__or__user_id__$';
9076
+ const win = 'window' in globalThis
9077
+ ? window
9078
+ : {
9079
+ navigator: { userAgent: '' },
9080
+ screen: {},
9081
+ document: {
9082
+ cookie: '',
9083
+ },
9084
+ location: { search: '' },
9085
+ };
9086
+ const doc = 'document' in globalThis ? document : { referrer: '' };
9087
+ const searchEngineList = [
9088
+ 'google',
9089
+ 'bing',
9090
+ 'yahoo',
9091
+ 'baidu',
9092
+ 'yandex',
9093
+ 'duckduckgo',
9094
+ 'ecosia',
9095
+ 'ask',
9096
+ 'aol',
9097
+ 'wolframalpha',
9098
+ 'startpage',
9099
+ 'swisscows',
9100
+ 'qwant',
9101
+ 'lycos',
9102
+ 'dogpile',
9103
+ 'info',
9104
+ 'teoma',
9105
+ 'webcrawler',
9106
+ 'naver',
9107
+ 'seznam',
9108
+ 'perplexity',
9109
+ ];
9110
+ class ConstantProperties {
9111
+ constructor(localStorage, sessionStorage) {
9112
+ this.localStorage = localStorage;
9113
+ this.sessionStorage = sessionStorage;
9114
+ this.user_id = null;
9115
+ this.setUserId = (user_id) => {
9116
+ this.user_id = user_id;
9117
+ this.sessionStorage.setItem(userIdKey, user_id ?? '');
9118
+ };
9119
+ this.resetUserId = (hard) => {
9120
+ this.user_id = null;
9121
+ if (hard) {
9122
+ this.deviceId = this.getDistinctDeviceId(true);
9123
+ }
9124
+ };
9125
+ this.getDistinctDeviceId = (force) => {
9126
+ const potentialStored = this.localStorage.getItem(distinctIdKey);
9127
+ if (potentialStored && !force) {
9128
+ return potentialStored;
9129
+ }
9130
+ else {
9131
+ const distinctId = `${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}`;
9132
+ this.localStorage.setItem(distinctIdKey, distinctId);
9133
+ return distinctId;
9134
+ }
9135
+ };
9136
+ this.getReferrer = () => {
9137
+ const potentialStored = this.sessionStorage.getItem(refKey);
9138
+ if (potentialStored) {
9139
+ return potentialStored;
9140
+ }
9141
+ else {
9142
+ const ref = doc.referrer;
9143
+ this.sessionStorage.setItem(refKey, ref);
9144
+ return ref;
9145
+ }
9146
+ };
9147
+ this.parseUTM = () => {
9148
+ const potentialStored = this.sessionStorage.getItem(utmParamsKey);
9149
+ if (potentialStored) {
9150
+ const obj = JSON.parse(potentialStored);
9151
+ this.utmSource = obj.utm_source;
9152
+ this.utmMedium = obj.utm_medium;
9153
+ this.utmCampaign = obj.utm_campaign;
9154
+ }
9155
+ else {
9156
+ const searchParams = new URLSearchParams(win.location.search);
9157
+ this.utmSource = searchParams.get('utm_source') || null;
9158
+ this.utmMedium = searchParams.get('utm_medium') || null;
9159
+ this.utmCampaign = searchParams.get('utm_campaign') || null;
9160
+ const obj = {
9161
+ utm_source: this.utmSource,
9162
+ utm_medium: this.utmMedium,
9163
+ utm_campaign: this.utmCampaign,
9164
+ };
9165
+ this.sessionStorage.setItem(utmParamsKey, JSON.stringify(obj));
9166
+ }
9167
+ };
9168
+ this.getSearchEngine = (ref) => {
9169
+ for (const searchEngine of searchEngineList) {
9170
+ if (ref.includes(searchEngine)) {
9171
+ return searchEngine;
9172
+ }
9173
+ }
9174
+ return null;
9175
+ };
9176
+ this.getSuperProperties = () => {
9177
+ const potentialStored = this.localStorage.getItem(superPropKey);
9178
+ if (potentialStored) {
9179
+ return JSON.parse(potentialStored);
9180
+ }
9181
+ else {
9182
+ return {};
9183
+ }
9184
+ };
9185
+ this.saveSuperProperties = (props) => {
9186
+ this.localStorage.setItem(superPropKey, JSON.stringify(props));
9187
+ };
9188
+ this.clearSuperProperties = () => {
9189
+ this.localStorage.setItem(superPropKey, JSON.stringify({}));
9190
+ };
9191
+ const { width, height, browser, browserVersion, browserMajorVersion, os, osVersion, mobile } = uaParse(win);
9192
+ const storedUserId = this.sessionStorage.getItem(userIdKey);
9193
+ if (storedUserId) {
9194
+ this.user_id = storedUserId;
9195
+ }
9196
+ this.os = os;
9197
+ this.osVersion = osVersion;
9198
+ this.browser = `${browser}`;
9199
+ this.browserVersion = `${browserVersion} (${browserMajorVersion})`;
9200
+ this.platform = mobile ? 'mobile' : 'desktop';
9201
+ this.screenHeight = height;
9202
+ this.screenWidth = width;
9203
+ this.initialReferrer = this.getReferrer();
9204
+ this.deviceId = this.getDistinctDeviceId();
9205
+ this.searchEngine = this.getSearchEngine(this.initialReferrer);
9206
+ this.parseUTM();
9207
+ }
9208
+ get all() {
9209
+ return {
9210
+ os: this.os,
9211
+ os_version: this.osVersion,
9212
+ browser: this.browser,
9213
+ browser_version: this.browserVersion,
9214
+ platform: this.platform,
9215
+ screen_height: this.screenHeight,
9216
+ screen_width: this.screenWidth,
9217
+ initial_referrer: this.initialReferrer,
9218
+ utm_source: this.utmSource,
9219
+ utm_medium: this.utmMedium,
9220
+ utm_campaign: this.utmCampaign,
9221
+ user_id: this.user_id,
9222
+ distinct_id: this.deviceId,
9223
+ sdk_edition: 'web',
9224
+ sdk_version: '17.1.0',
9225
+ timezone: getUTCOffsetString(),
9226
+ search_engine: this.searchEngine,
9227
+ };
9228
+ }
9229
+ get defaultPropertyKeys() {
9230
+ return Object.keys(this.all);
9231
+ }
9232
+ get distinctId() {
9233
+ return this.deviceId;
9234
+ }
9235
+ }
9236
+
9237
+ const mutationTypes = {
9238
+ identity: 'identity',
9239
+ deleteUser: 'delete_user',
9240
+ setProperty: 'set_property',
9241
+ setPropertyOnce: 'set_property_once',
9242
+ appendProperty: 'append_property',
9243
+ appendUniqueProperty: 'append_unique_property',
9244
+ incrementProperty: 'increment_property',
9245
+ };
9246
+ const categories = {
9247
+ people: 'user_actions',
9248
+ events: 'events',
9249
+ };
9250
+ const createEvent = (category, type, timestamp, payload) => {
9251
+ if (category === categories.people) {
9252
+ return {
9253
+ category,
9254
+ data: {
9255
+ type,
9256
+ user_id: payload.user_id,
9257
+ payload: payload.properties,
9258
+ timestamp,
9259
+ },
9260
+ };
9261
+ }
9262
+ else {
9263
+ if (!payload) {
9264
+ throw new Error('Payload is required for event creation');
9265
+ }
9266
+ return {
9267
+ category,
9268
+ data: {
9269
+ name: payload.name,
9270
+ payload: payload.properties,
9271
+ timestamp,
9272
+ },
9273
+ };
9274
+ }
9275
+ };
9276
+
9277
+ const reservedProps = ['properties', 'token', 'timestamp'];
9278
+ class Events {
9279
+ constructor(constantProperties, getTimestamp, batcher) {
9280
+ this.constantProperties = constantProperties;
9281
+ this.getTimestamp = getTimestamp;
9282
+ this.batcher = batcher;
9283
+ this.ownProperties = {};
9284
+ /**
9285
+ * Add event to batch with option to send it immediately,
9286
+ * properties are optional and will not be saved as super prop
9287
+ * */
9288
+ this.sendEvent = (eventName, properties, options) => {
9289
+ // add properties
9290
+ const eventProps = {};
9291
+ if (properties) {
9292
+ if (!isObject(properties)) {
9293
+ throw new Error('Properties must be an object');
9294
+ }
9295
+ Object.entries(properties).forEach(([key, value]) => {
9296
+ if (!this.constantProperties.defaultPropertyKeys.includes(key)) {
9297
+ eventProps[key] = value;
9298
+ }
9299
+ });
9300
+ }
9301
+ const eventPayload = {
9302
+ name: eventName,
9303
+ properties: { ...this.ownProperties, ...eventProps },
9304
+ };
9305
+ const event = createEvent(categories.events, undefined, this.getTimestamp(), eventPayload);
9306
+ if (options?.send_immediately) {
9307
+ void this.batcher.sendImmediately(event);
9308
+ }
9309
+ else {
9310
+ this.batcher.addEvent(event);
9311
+ }
9312
+ };
9313
+ /**
9314
+ * creates super property for all events
9315
+ * */
9316
+ this.setProperty = (nameOrProperties, value) => {
9317
+ let changed = false;
9318
+ if (isObject(nameOrProperties)) {
9319
+ Object.entries(nameOrProperties).forEach(([key, val]) => {
9320
+ if (!this.constantProperties.defaultPropertyKeys.includes(key)) {
9321
+ this.ownProperties[key] = val;
9322
+ changed = true;
9323
+ }
9324
+ });
9325
+ }
9326
+ if (typeof nameOrProperties === 'string' && value !== undefined) {
9327
+ if (!this.constantProperties.defaultPropertyKeys.includes(nameOrProperties)) {
9328
+ this.ownProperties[nameOrProperties] = value;
9329
+ changed = true;
9330
+ }
9331
+ }
9332
+ if (changed) {
9333
+ this.constantProperties.saveSuperProperties(this.ownProperties);
9334
+ }
9335
+ };
9336
+ /**
9337
+ * set super property only if it doesn't exist yet
9338
+ * */
9339
+ this.setPropertiesOnce = (nameOrProperties, value) => {
9340
+ let changed = false;
9341
+ if (isObject(nameOrProperties)) {
9342
+ Object.entries(nameOrProperties).forEach(([key, val]) => {
9343
+ if (!this.ownProperties[key] && !reservedProps.includes(key)) {
9344
+ this.ownProperties[key] = val;
9345
+ changed = true;
9346
+ }
9347
+ });
9348
+ }
9349
+ if (typeof nameOrProperties === 'string' && value !== undefined) {
9350
+ if (!this.ownProperties[nameOrProperties] && !reservedProps.includes(nameOrProperties)) {
9351
+ this.ownProperties[nameOrProperties] = value;
9352
+ changed = true;
9353
+ }
9354
+ }
9355
+ if (changed) {
9356
+ this.constantProperties.saveSuperProperties(this.ownProperties);
9357
+ }
9358
+ };
9359
+ /**
9360
+ * removes properties from list of super properties
9361
+ * */
9362
+ this.unsetProperties = (properties) => {
9363
+ let changed = false;
9364
+ if (Array.isArray(properties)) {
9365
+ properties.forEach((key) => {
9366
+ if (this.ownProperties[key] && !reservedProps.includes(key)) {
9367
+ delete this.ownProperties[key];
9368
+ changed = true;
9369
+ }
9370
+ });
9371
+ }
9372
+ else if (this.ownProperties[properties] && !reservedProps.includes(properties)) {
9373
+ delete this.ownProperties[properties];
9374
+ changed = true;
9375
+ }
9376
+ if (changed) {
9377
+ this.constantProperties.saveSuperProperties(this.ownProperties);
9378
+ }
9379
+ };
9380
+ /** clears all super properties */
9381
+ this.reset = () => {
9382
+ this.ownProperties = {};
9383
+ this.constantProperties.clearSuperProperties();
9384
+ };
9385
+ /** mixpanel compatibility */
9386
+ this.register = this.setProperty;
9387
+ this.register_once = this.setPropertiesOnce;
9388
+ this.unregister = this.unsetProperties;
9389
+ this.track = this.sendEvent;
9390
+ this.ownProperties = this.constantProperties.getSuperProperties();
9391
+ }
9392
+ }
9393
+
9394
+ class People {
9395
+ constructor(constantProperties, getTimestamp, onId, batcher) {
9396
+ this.constantProperties = constantProperties;
9397
+ this.getTimestamp = getTimestamp;
9398
+ this.onId = onId;
9399
+ this.batcher = batcher;
9400
+ this.ownProperties = {};
9401
+ this.identify = (user_id, options) => {
9402
+ if (!user_id || typeof user_id !== 'string') {
9403
+ throw new Error('OR SDK: user_id (string) is required for .identify()');
9404
+ }
9405
+ // if user exists already, reset properties
9406
+ if (this.constantProperties.user_id && this.constantProperties.user_id !== user_id) {
9407
+ this.reset();
9408
+ }
9409
+ this.constantProperties.setUserId(user_id);
9410
+ if (!options?.fromTracker) {
9411
+ this.onId(user_id);
9412
+ }
9413
+ const identityEvent = createEvent(categories.people, mutationTypes.identity, this.getTimestamp(), { user_id });
9414
+ this.batcher.addEvent(identityEvent);
9415
+ };
9416
+ /** Resets user id and own properties
9417
+ *
9418
+ * !hard reset will destroy persistent device id!
9419
+ * */
9420
+ this.reset = (hard) => {
9421
+ this.constantProperties.resetUserId(hard);
9422
+ this.ownProperties = {};
9423
+ };
9424
+ /**
9425
+ * Will delete user and its data from backend, then reset all local properties
9426
+ */
9427
+ this.deleteUser = () => {
9428
+ const removedUser = this.constantProperties.user_id;
9429
+ if (!removedUser)
9430
+ return;
9431
+ this.constantProperties.setUserId(null);
9432
+ this.ownProperties = {};
9433
+ const deleteEvent = createEvent(categories.people, mutationTypes.deleteUser, undefined, {
9434
+ user_id: removedUser,
9435
+ });
9436
+ this.batcher.addEvent(deleteEvent);
9437
+ this.reset();
9438
+ };
9439
+ /**
9440
+ * set user properties, overwriting existing ones
9441
+ * */
9442
+ this.setProperties = (propertyOrObj, value) => {
9443
+ if (!propertyOrObj) {
9444
+ throw new Error('OR SDK: no user properties provided to set');
9445
+ }
9446
+ const properties = {};
9447
+ if (typeof propertyOrObj === 'string' && propertyOrObj && value) {
9448
+ properties[propertyOrObj] = value;
9449
+ }
9450
+ else if (isObject(propertyOrObj)) {
9451
+ Object.assign(properties, propertyOrObj);
9452
+ }
9453
+ else {
9454
+ throw new Error('OR SDK: invalid user properties provided to set');
9455
+ }
9456
+ Object.entries(properties).forEach(([key, value]) => {
9457
+ if (!this.constantProperties.defaultPropertyKeys.includes(key)) {
9458
+ this.ownProperties[key] = value;
9459
+ }
9460
+ });
9461
+ const setEvent = createEvent(categories.people, mutationTypes.setProperty, undefined, {
9462
+ user_id: this.user_id,
9463
+ properties,
9464
+ });
9465
+ this.batcher.addEvent(setEvent);
9466
+ };
9467
+ /**
9468
+ * Set property if it doesn't exist yet
9469
+ * */
9470
+ this.setPropertiesOnce = (properties) => {
9471
+ if (!isObject(properties)) {
9472
+ throw new Error('Properties must be an object');
9473
+ }
9474
+ Object.entries(properties).forEach(([key, value]) => {
9475
+ if (!this.constantProperties.defaultPropertyKeys.includes(key) && !this.ownProperties[key]) {
9476
+ this.ownProperties[key] = value;
9477
+ }
9478
+ });
9479
+ const setEvent = createEvent(categories.people, mutationTypes.setPropertyOnce, undefined, {
9480
+ user_id: this.user_id,
9481
+ properties,
9482
+ });
9483
+ this.batcher.addEvent(setEvent);
9484
+ };
9485
+ /**
9486
+ * Add value to property (will turn string prop into array)
9487
+ * */
9488
+ this.appendValues = (key, value) => {
9489
+ if (!this.constantProperties.defaultPropertyKeys.includes(key) && this.ownProperties[key]) {
9490
+ if (Array.isArray(this.ownProperties[key])) {
9491
+ this.ownProperties[key].push(value);
9492
+ }
9493
+ else {
9494
+ this.ownProperties[key] = [this.ownProperties[key], value];
9495
+ }
9496
+ }
9497
+ const appendEvent = createEvent(categories.people, mutationTypes.appendProperty, undefined, {
9498
+ properties: { [key]: value },
9499
+ user_id: this.user_id,
9500
+ });
9501
+ this.batcher.addEvent(appendEvent);
9502
+ };
9503
+ /**
9504
+ * Add unique values to property (will turn string prop into array)
9505
+ * */
9506
+ this.appendUniqueValues = (key, value) => {
9507
+ if (!this.ownProperties[key])
9508
+ return;
9509
+ if (Array.isArray(this.ownProperties[key])) {
9510
+ if (!this.ownProperties[key].includes(value)) {
9511
+ this.appendValues(key, value);
9512
+ }
9513
+ }
9514
+ else if (this.ownProperties[key] !== value) {
9515
+ this.appendValues(key, value);
9516
+ }
9517
+ const unionEvent = createEvent(categories.people, mutationTypes.appendUniqueProperty, undefined, {
9518
+ properties: { [key]: value },
9519
+ user_id: this.user_id,
9520
+ });
9521
+ this.batcher.addEvent(unionEvent);
9522
+ };
9523
+ /**
9524
+ * Adds value (incl. negative) to existing numerical property
9525
+ * */
9526
+ this.increment = (key, value) => {
9527
+ if (!this.ownProperties[key]) {
9528
+ this.ownProperties[key] = 0;
9529
+ }
9530
+ if (this.ownProperties[key] && typeof this.ownProperties[key] !== 'number') {
9531
+ throw new Error('OR SDK: Property must be a number to increment');
9532
+ }
9533
+ // @ts-ignore
9534
+ this.ownProperties[key] += value;
9535
+ const incrementEvent = createEvent(categories.people, mutationTypes.incrementProperty, undefined, {
9536
+ user_id: this.user_id,
9537
+ properties: { [key]: value },
9538
+ });
9539
+ this.batcher.addEvent(incrementEvent);
9540
+ };
9541
+ /** mixpanel compatibility */
9542
+ this.union = this.appendUniqueValues;
9543
+ this.set = this.setProperties;
9544
+ this.set_once = this.setPropertiesOnce;
9545
+ this.append = this.appendValues;
9546
+ this.incrementBy = this.increment;
9547
+ }
9548
+ get user_id() {
9549
+ return this.constantProperties.user_id;
9550
+ }
9551
+ }
9552
+
9553
+ /**
9554
+ * Creates batches of events, then sends them at intervals.
9555
+ */
9556
+ class Batcher {
9557
+ constructor(backendUrl, getToken, init) {
9558
+ this.backendUrl = backendUrl;
9559
+ this.getToken = getToken;
9560
+ this.init = init;
9561
+ this.autosendInterval = 5 * 1000;
9562
+ this.retryTimeout = 3 * 1000;
9563
+ this.retryLimit = 3;
9564
+ this.apiEdp = '/v1/sdk/i';
9565
+ this.batch = {
9566
+ [categories.people]: [],
9567
+ [categories.events]: [],
9568
+ };
9569
+ this.intervalId = null;
9570
+ }
9571
+ getBatches() {
9572
+ this.batch[categories.people] = this.dedupePeopleEvents();
9573
+ const finalData = { data: this.batch };
9574
+ return finalData;
9575
+ }
9576
+ addEvent(event) {
9577
+ this.batch[event.category].push(event.data);
9578
+ }
9579
+ sendImmediately(event) {
9580
+ this.sendBatch({ [event.category]: [event.data] });
9581
+ }
9582
+ /**
9583
+ *
9584
+ * Essentially we're dividing the batch by identify events and squash all same category events into one in each part,
9585
+ * taking priority to the last one
9586
+ */
9587
+ dedupePeopleEvents() {
9588
+ const peopleEvents = this.batch[categories.people];
9589
+ const finalEvents = [];
9590
+ const currentPart = [];
9591
+ for (let event of peopleEvents) {
9592
+ if (event.type === 'identity') {
9593
+ if (currentPart.length > 0) {
9594
+ finalEvents.push(...this.squashPeopleEvents(currentPart), event);
9595
+ currentPart.length = 0;
9596
+ }
9597
+ else {
9598
+ finalEvents.push(event);
9599
+ }
9600
+ }
9601
+ else {
9602
+ currentPart.push(event);
9603
+ }
9604
+ }
9605
+ if (currentPart.length > 0) {
9606
+ finalEvents.push(...this.squashPeopleEvents(currentPart));
9607
+ }
9608
+ return finalEvents;
9609
+ }
9610
+ squashPeopleEvents(events) {
9611
+ if (!events || events.length === 0) {
9612
+ return [];
9613
+ }
9614
+ const uniqueEventsByType = new Map();
9615
+ for (let event of events) {
9616
+ const prev = uniqueEventsByType.get(event.type);
9617
+ if (prev) {
9618
+ if (event.type === 'increment_property') {
9619
+ const previousValues = Object.entries(prev.payload);
9620
+ const currentValues = Object.entries(event.payload);
9621
+ const uniqueKeys = new Set([...previousValues.map(([key]) => key), ...currentValues.map(([key]) => key)]);
9622
+ const mergedPayload = {};
9623
+ uniqueKeys.forEach((key) => {
9624
+ const prevValue = typeof prev.payload[key] === 'number' ? prev.payload[key] : 0;
9625
+ const currValue = typeof event.payload[key] === 'number' ? event.payload[key] : 0;
9626
+ mergedPayload[key] = prevValue + currValue;
9627
+ });
9628
+ uniqueEventsByType.set(event.type, {
9629
+ type: event.type,
9630
+ timestamp: event.timestamp,
9631
+ payload: mergedPayload,
9632
+ });
9633
+ continue;
9634
+ }
9635
+ // merge payloads, taking priority to the latest one
9636
+ uniqueEventsByType.set(event.type, {
9637
+ type: event.type,
9638
+ timestamp: event.timestamp,
9639
+ payload: { ...(prev.payload ?? {}), ...(event.payload ?? {}) },
9640
+ });
9641
+ }
9642
+ else {
9643
+ uniqueEventsByType.set(event.type, event);
9644
+ }
9645
+ }
9646
+ return Array.from(uniqueEventsByType.values());
9647
+ }
9648
+ sendBatch(batch) {
9649
+ const sentBatch = batch;
9650
+ let attempts = 0;
9651
+ const send = () => {
9652
+ const token = this.getToken();
9653
+ if (!token) {
9654
+ return;
9655
+ }
9656
+ attempts++;
9657
+ return fetch(`${this.backendUrl}${this.apiEdp}`, {
9658
+ method: 'POST',
9659
+ headers: {
9660
+ 'Content-Type': 'application/json',
9661
+ Authorization: `Bearer ${token}`,
9662
+ },
9663
+ body: JSON.stringify(sentBatch),
9664
+ })
9665
+ .then((response) => {
9666
+ if (response.status === 403) {
9667
+ this.init().then(() => {
9668
+ send();
9669
+ });
9670
+ }
9671
+ if (!response.ok) {
9672
+ throw new Error(`HTTP error! status: ${response.status}`);
9673
+ }
9674
+ })
9675
+ .catch(() => {
9676
+ if (attempts < this.retryLimit) {
9677
+ setTimeout(() => void send(), this.retryTimeout);
9678
+ }
9679
+ });
9680
+ };
9681
+ void send();
9682
+ }
9683
+ startAutosend() {
9684
+ this.intervalId = setInterval(() => {
9685
+ this.flush();
9686
+ }, this.autosendInterval);
9687
+ }
9688
+ flush() {
9689
+ const categories = Object.keys(this.batch);
9690
+ const isEmpty = categories.every((category) => this.batch[category].length === 0);
9691
+ if (isEmpty) {
9692
+ return;
9693
+ }
9694
+ this.sendBatch(this.getBatches());
9695
+ categories.forEach((key) => {
9696
+ this.batch[key] = [];
9697
+ });
9698
+ }
9699
+ stop() {
9700
+ this.flush();
9701
+ if (this.intervalId) {
9702
+ clearInterval(this.intervalId);
9703
+ this.intervalId = null;
9704
+ }
9705
+ }
9706
+ }
9707
+
9708
+ const STORAGEKEY = '__or_sdk_analytics_token';
9709
+ class Analytics {
9710
+ /**
9711
+ * @param localStorage Class or Object that implements Storage-like interface that stores
9712
+ * values persistently like window.localStorage or any other file-based storage
9713
+ *
9714
+ * @param sessionStorage Class or Object that implements Storage-like interface that stores values
9715
+ * on per-session basis like window.sessionStorage or any other in-memory storage
9716
+ *
9717
+ * @param getToken Function that returns token to bind events to a session
9718
+ *
9719
+ * @param getTimestamp returns current timestamp
9720
+ *
9721
+ * @param setUserId callback for people.identify
9722
+ *
9723
+ * @param standalone if true, analytics will manage its own token (instead of using with openreplay tracker session)
9724
+ * */
9725
+ constructor(options) {
9726
+ this.token = null;
9727
+ this.standalone = false;
9728
+ this._getToken = () => {
9729
+ if (this.standalone) {
9730
+ return this.token;
9731
+ }
9732
+ return this.getToken();
9733
+ };
9734
+ this._getTimestamp = () => {
9735
+ if (this.standalone) {
9736
+ return Date.now();
9737
+ }
9738
+ return this.getTimestamp();
9739
+ };
9740
+ this.init = async () => {
9741
+ if (!this.standalone) {
9742
+ this.batcher.startAutosend();
9743
+ return;
9744
+ }
9745
+ else {
9746
+ const defaultFields = this.constantProperties.all;
9747
+ const apiEdp = '/v1/sdk/start';
9748
+ const data = {
9749
+ projectKey: this.projectKey,
9750
+ defaultFields,
9751
+ };
9752
+ const resp = await fetch(apiEdp, {
9753
+ method: 'POST',
9754
+ body: JSON.stringify(data),
9755
+ });
9756
+ if (!resp.ok) {
9757
+ throw new Error(`HTTP error! status: ${resp.status}`);
9758
+ }
9759
+ const result = await resp.json();
9760
+ if (result.token) {
9761
+ this.token = result.token;
9762
+ this.sessionStorage.setItem(STORAGEKEY, result.token);
9763
+ }
9764
+ else {
9765
+ throw new Error('No token received from server');
9766
+ }
9767
+ }
9768
+ };
9769
+ this.reset = () => {
9770
+ this.people.reset(true);
9771
+ this.events.reset();
9772
+ this.batcher.stop();
9773
+ if (this.standalone) {
9774
+ this.token = null;
9775
+ this.sessionStorage.setItem(STORAGEKEY, '');
9776
+ }
9777
+ };
9778
+ /**
9779
+ * COMPATIBILITY LAYER
9780
+ * */
9781
+ /**
9782
+ * Identify a user with an id (e.g. email, username, etc.)
9783
+ * will bind all events and properties (including device_id) to this user
9784
+ *
9785
+ * you will need to manually call people.reset() to clear the id on logout event
9786
+ * */
9787
+ this.identify = (user_id) => {
9788
+ return this.people.identify(user_id);
9789
+ };
9790
+ /**
9791
+ * Add event to batch with option to send it immediately,
9792
+ * properties are optional and will not be saved as super prop
9793
+ * */
9794
+ this.track = (eventName, properties, options) => {
9795
+ return this.events.track(eventName, properties, options);
9796
+ };
9797
+ this.sessionStorage = options.sessionStorage || sessionStorage;
9798
+ this.localStorage = options.localStorage || localStorage;
9799
+ this.backendUrl = options.ingestPoint;
9800
+ this.projectKey = options.projectKey;
9801
+ this.getToken = options.getToken || (() => '');
9802
+ this.getTimestamp = options.getTimestamp || (() => Date.now());
9803
+ this.setUserId = options.setUserId || (() => { });
9804
+ this.standalone = !options.notStandalone;
9805
+ this.token = this.sessionStorage.getItem(STORAGEKEY);
9806
+ this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
9807
+ this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
9808
+ this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
9809
+ this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
9810
+ if (options.notStandalone) {
9811
+ this.init();
9812
+ }
9813
+ }
9814
+ }
9815
+
8831
9816
  const Messages = _Messages;
8832
9817
  const DOCS_SETUP = '/en/sdk';
8833
9818
  function processOptions(obj) {
@@ -8869,6 +9854,7 @@ class API {
8869
9854
  constructor(options) {
8870
9855
  this.options = options;
8871
9856
  this.app = null;
9857
+ this.analytics = null;
8872
9858
  this.crossdomainMode = false;
8873
9859
  this.checkDoNotTrack = () => {
8874
9860
  return (this.options.respectDoNotTrack &&
@@ -8879,7 +9865,7 @@ class API {
8879
9865
  this.signalStartIssue = (reason, missingApi) => {
8880
9866
  const doNotTrack = this.checkDoNotTrack();
8881
9867
  console.log("Tracker couldn't start due to:", JSON.stringify({
8882
- trackerVersion: '17.0.0',
9868
+ trackerVersion: '17.1.0',
8883
9869
  projectKey: this.options.projectKey,
8884
9870
  doNotTrack,
8885
9871
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -8891,6 +9877,22 @@ class API {
8891
9877
  }
8892
9878
  this.app.restartCanvasTracking();
8893
9879
  };
9880
+ this.getSessionURL = (options) => {
9881
+ if (this.app === null) {
9882
+ return undefined;
9883
+ }
9884
+ return this.app.getSessionURL(options);
9885
+ };
9886
+ this.setUserID = (id) => {
9887
+ if (typeof id === 'string' && this.app !== null) {
9888
+ this.app.session.setUserID(id);
9889
+ this.analytics?.people.identify(id, { fromTracker: true });
9890
+ }
9891
+ };
9892
+ this.userID = (id) => {
9893
+ deprecationWarn("'userID' method", "'setUserID' method", '/');
9894
+ this.setUserID(id);
9895
+ };
8894
9896
  this.handleError = (e, metadata = {}) => {
8895
9897
  if (this.app === null) {
8896
9898
  return;
@@ -8913,6 +9915,21 @@ class API {
8913
9915
  }
8914
9916
  this.app.send(Incident(options.label ?? '', options.startTime, options.endTime ?? options.startTime));
8915
9917
  };
9918
+ this.analyticsToken = null;
9919
+ /**
9920
+ * Use custom token for analytics events without session recording
9921
+ * */
9922
+ this.setAnalyticsToken = (token) => {
9923
+ this.analyticsToken = token;
9924
+ };
9925
+ this.getAnalyticsToken = () => {
9926
+ if (this.analyticsToken) {
9927
+ return this.analyticsToken;
9928
+ }
9929
+ else {
9930
+ return this.app?.session.getSessionToken() ?? '';
9931
+ }
9932
+ };
8916
9933
  this.crossdomainMode = Boolean(inIframe() && options.crossdomain?.enabled);
8917
9934
  if (!IN_BROWSER || !processOptions(options)) {
8918
9935
  return;
@@ -8971,6 +9988,24 @@ class API {
8971
9988
  }
8972
9989
  const app = new App(options.projectKey, options.sessionToken, options, this.signalStartIssue, this.crossdomainMode);
8973
9990
  this.app = app;
9991
+ if (options.projectKey && options.analytics?.active) {
9992
+ const isSaas = !options.ingestPoint || options.ingestPoint.includes('api.openreplay.com');
9993
+ const defaultEdp = 'https://api.openreplay.com/ingest';
9994
+ this.analytics = new Analytics({
9995
+ localStorage: options.localStorage ?? localStorage,
9996
+ sessionStorage: options.sessionStorage ?? sessionStorage,
9997
+ getToken: () => this.getAnalyticsToken(),
9998
+ getTimestamp: () => this.app?.timestamp() ?? Date.now(),
9999
+ setUserId: (id) => {
10000
+ this.app?.session.setUserID(id);
10001
+ },
10002
+ notStandalone: true,
10003
+ ingestPoint: isSaas
10004
+ ? defaultEdp
10005
+ : (options.analytics?.ingestPoint ?? options.ingestPoint ?? defaultEdp),
10006
+ projectKey: options.projectKey,
10007
+ });
10008
+ }
8974
10009
  if (!this.crossdomainMode) {
8975
10010
  // no need to send iframe viewport data since its a node for us
8976
10011
  Viewport(app, options.urls);
@@ -9050,6 +10085,9 @@ class API {
9050
10085
  if (this.app === null) {
9051
10086
  return Promise.reject("Browser doesn't support required api, or doNotTrack is active.");
9052
10087
  }
10088
+ if (startOpts?.userID) {
10089
+ this.analytics?.people.identify(startOpts.userID, { fromTracker: true });
10090
+ }
9053
10091
  return this.app.start(startOpts);
9054
10092
  }
9055
10093
  else {
@@ -9157,21 +10195,6 @@ class API {
9157
10195
  deprecationWarn("'sessionID' method", "'getSessionID' method", '/');
9158
10196
  return this.getSessionID();
9159
10197
  }
9160
- getSessionURL(options) {
9161
- if (this.app === null) {
9162
- return undefined;
9163
- }
9164
- return this.app.getSessionURL(options);
9165
- }
9166
- setUserID(id) {
9167
- if (typeof id === 'string' && this.app !== null) {
9168
- this.app.session.setUserID(id);
9169
- }
9170
- }
9171
- userID(id) {
9172
- deprecationWarn("'userID' method", "'setUserID' method", '/');
9173
- this.setUserID(id);
9174
- }
9175
10198
  setUserAnonymousID(id) {
9176
10199
  if (typeof id === 'string' && this.app !== null) {
9177
10200
  this.app.send(UserAnonymousID(id));
@@ -9458,8 +10481,10 @@ class TrackerSingleton {
9458
10481
  }
9459
10482
  const tracker = new TrackerSingleton();
9460
10483
 
10484
+ exports.Analytics = Analytics;
9461
10485
  exports.App = App;
9462
10486
  exports.Messages = Messages;
9463
10487
  exports.default = API;
10488
+ exports.openReplay = tracker;
9464
10489
  exports.tracker = tracker;
9465
10490
  //# sourceMappingURL=entry.js.map