@openreplay/tracker 7.0.3 → 8.0.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 (94) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/cjs/app/index.d.ts +10 -0
  3. package/cjs/app/index.js +56 -5
  4. package/cjs/app/messages.gen.d.ts +2 -0
  5. package/cjs/app/messages.gen.js +15 -1
  6. package/cjs/app/observer/observer.js +2 -2
  7. package/cjs/app/sanitizer.d.ts +1 -0
  8. package/cjs/app/sanitizer.js +6 -4
  9. package/cjs/app/session.d.ts +4 -0
  10. package/cjs/app/session.js +18 -0
  11. package/cjs/common/interaction.d.ts +1 -0
  12. package/cjs/common/messages.gen.d.ts +12 -2
  13. package/cjs/index.d.ts +1 -0
  14. package/cjs/index.js +9 -1
  15. package/cjs/modules/attributeSender.d.ts +14 -0
  16. package/cjs/modules/attributeSender.js +44 -0
  17. package/cjs/modules/img.js +4 -4
  18. package/cjs/modules/tabs.d.ts +2 -0
  19. package/cjs/modules/tabs.js +13 -0
  20. package/cjs/utils.d.ts +1 -1
  21. package/cjs/utils.js +13 -11
  22. package/coverage/clover.xml +683 -620
  23. package/coverage/coverage-final.json +13 -12
  24. package/coverage/lcov-report/index.html +47 -47
  25. package/coverage/lcov-report/main/app/guards.ts.html +42 -42
  26. package/coverage/lcov-report/main/app/index.html +34 -34
  27. package/coverage/lcov-report/main/app/index.ts.html +62 -8
  28. package/coverage/lcov-report/main/app/logger.ts.html +1 -1
  29. package/coverage/lcov-report/main/app/messages.gen.ts.html +1 -1
  30. package/coverage/lcov-report/main/app/observer/iframe_observer.ts.html +1 -1
  31. package/coverage/lcov-report/main/app/observer/iframe_offsets.ts.html +1 -1
  32. package/coverage/lcov-report/main/app/observer/index.html +1 -1
  33. package/coverage/lcov-report/main/app/observer/shadow_root_observer.ts.html +1 -1
  34. package/coverage/lcov-report/main/app/observer/top_observer.ts.html +1 -1
  35. package/coverage/lcov-report/main/app/sanitizer.ts.html +98 -98
  36. package/coverage/lcov-report/main/app/session.ts.html +1 -1
  37. package/coverage/lcov-report/main/app/ticker.ts.html +1 -1
  38. package/coverage/lcov-report/main/index.html +24 -24
  39. package/coverage/lcov-report/main/index.ts.html +126 -6
  40. package/coverage/lcov-report/main/modules/attributeSender.ts.html +217 -0
  41. package/coverage/lcov-report/main/modules/axiosSpy.ts.html +1 -1
  42. package/coverage/lcov-report/main/modules/connection.ts.html +1 -1
  43. package/coverage/lcov-report/main/modules/console.ts.html +1 -1
  44. package/coverage/lcov-report/main/modules/constructedStyleSheets.ts.html +1 -1
  45. package/coverage/lcov-report/main/modules/cssrules.ts.html +1 -1
  46. package/coverage/lcov-report/main/modules/exception.ts.html +1 -1
  47. package/coverage/lcov-report/main/{app/nodes.ts.html → modules/featureFlags.ts.html} +124 -127
  48. package/coverage/lcov-report/main/modules/focus.ts.html +1 -1
  49. package/coverage/lcov-report/main/modules/fonts.ts.html +1 -1
  50. package/coverage/lcov-report/main/modules/img.ts.html +6 -6
  51. package/coverage/lcov-report/main/modules/index.html +38 -8
  52. package/coverage/lcov-report/main/modules/input.ts.html +1 -1
  53. package/coverage/lcov-report/main/modules/mouse.ts.html +1 -1
  54. package/coverage/lcov-report/main/modules/network.ts.html +1 -1
  55. package/coverage/lcov-report/main/modules/performance.ts.html +1 -1
  56. package/coverage/lcov-report/main/modules/scroll.ts.html +1 -1
  57. package/coverage/lcov-report/main/modules/selection.ts.html +1 -1
  58. package/coverage/lcov-report/main/modules/tabs.ts.html +1 -1
  59. package/coverage/lcov-report/main/modules/timing.ts.html +1 -1
  60. package/coverage/lcov-report/main/modules/viewport.ts.html +1 -1
  61. package/coverage/lcov-report/main/utils.ts.html +112 -112
  62. package/coverage/lcov-report/webworker/BatchWriter.ts.html +125 -176
  63. package/coverage/lcov-report/webworker/MessageEncoder.gen.ts.html +88 -88
  64. package/coverage/lcov-report/webworker/PrimitiveEncoder.ts.html +110 -110
  65. package/coverage/lcov-report/webworker/QueueSender.ts.html +140 -110
  66. package/coverage/lcov-report/webworker/index.html +56 -71
  67. package/coverage/lcov-report/webworker/index.ts.html +34 -10
  68. package/coverage/lcov.info +1232 -1092
  69. package/lib/app/index.d.ts +10 -0
  70. package/lib/app/index.js +57 -6
  71. package/lib/app/messages.gen.d.ts +2 -0
  72. package/lib/app/messages.gen.js +12 -0
  73. package/lib/app/observer/observer.js +3 -3
  74. package/lib/app/sanitizer.d.ts +1 -0
  75. package/lib/app/sanitizer.js +4 -3
  76. package/lib/app/session.d.ts +4 -0
  77. package/lib/app/session.js +18 -0
  78. package/lib/common/interaction.d.ts +1 -0
  79. package/lib/common/messages.gen.d.ts +12 -2
  80. package/lib/common/tsconfig.tsbuildinfo +1 -1
  81. package/lib/index.d.ts +1 -0
  82. package/lib/index.js +9 -1
  83. package/lib/modules/attributeSender.d.ts +14 -0
  84. package/lib/modules/attributeSender.js +39 -0
  85. package/lib/modules/img.js +5 -5
  86. package/lib/modules/tabs.d.ts +2 -0
  87. package/lib/modules/tabs.js +10 -0
  88. package/lib/utils.d.ts +1 -1
  89. package/lib/utils.js +11 -9
  90. package/package.json +5 -2
  91. package/coverage/lcov-report/main/app/observer/observer.ts.html +0 -1282
  92. package/coverage/lcov-report/main/vendors/finder/finder.ts.html +0 -1381
  93. package/coverage/lcov-report/main/vendors/finder/index.html +0 -116
  94. package/coverage/lcov-report/webworker/StringDictionary.ts.html +0 -124
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 8.0.0
2
+
3
+ - **[breaking]** support for multi-tab sessions
4
+
1
5
  # 7.0.3
2
6
 
3
7
  - Prevent auto restart after manual stop
@@ -5,6 +5,7 @@ import Sanitizer from './sanitizer.js';
5
5
  import Ticker from './ticker.js';
6
6
  import Logger from './logger.js';
7
7
  import Session from './session.js';
8
+ import AttributeSender from '../modules/attributeSender.js';
8
9
  import type { Options as ObserverOptions } from './observer/top_observer.js';
9
10
  import type { Options as SanitizerOptions } from './sanitizer.js';
10
11
  import type { Options as LoggerOptions } from './logger.js';
@@ -41,6 +42,7 @@ type AppOptions = {
41
42
  session_reset_key: string;
42
43
  session_token_key: string;
43
44
  session_pageno_key: string;
45
+ session_tabid_key: string;
44
46
  local_uuid_key: string;
45
47
  ingestPoint: string;
46
48
  resourceBaseHref: string | null;
@@ -50,6 +52,7 @@ type AppOptions = {
50
52
  __debug__?: LoggerOptions;
51
53
  localStorage: Storage | null;
52
54
  sessionStorage: Storage | null;
55
+ forceSingleTab?: boolean;
53
56
  onStart?: StartCallback;
54
57
  network?: NetworkOptions;
55
58
  } & WebworkerOptions & SessOptions;
@@ -78,6 +81,8 @@ export default class App {
78
81
  private readonly worker?;
79
82
  private compressionThreshold;
80
83
  private restartAttempts;
84
+ private readonly bc;
85
+ attributeSender: AttributeSender;
81
86
  constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>);
82
87
  private _debug;
83
88
  private _usingOldFetchPlugin;
@@ -117,7 +122,12 @@ export default class App {
117
122
  active(): boolean;
118
123
  resetNextPageSession(flag: boolean): void;
119
124
  private _start;
125
+ /**
126
+ * basically we ask other tabs during constructor
127
+ * and here we just apply 10ms delay just in case
128
+ * */
120
129
  start(...args: Parameters<App['_start']>): Promise<StartPromiseReturn>;
130
+ getTabId(): string;
121
131
  stop(stopWorker?: boolean): void;
122
132
  }
123
133
  export {};
package/cjs/app/index.js CHANGED
@@ -11,6 +11,7 @@ const logger_js_1 = require("./logger.js");
11
11
  const session_js_1 = require("./session.js");
12
12
  const fflate_1 = require("fflate");
13
13
  const performance_js_1 = require("../modules/performance.js");
14
+ const attributeSender_js_1 = require("../modules/attributeSender.js");
14
15
  const CANCELED = 'canceled';
15
16
  const START_ERROR = ':(';
16
17
  const UnsuccessfulStart = (reason) => ({ reason, success: false });
@@ -34,9 +35,10 @@ class App {
34
35
  this.stopCallbacks = [];
35
36
  this.commitCallbacks = [];
36
37
  this.activityState = ActivityState.NotActive;
37
- this.version = '7.0.3'; // TODO: version compatability check inside each plugin.
38
+ this.version = '8.0.0'; // TODO: version compatability check inside each plugin.
38
39
  this.compressionThreshold = 24 * 1000;
39
40
  this.restartAttempts = 0;
41
+ this.bc = new BroadcastChannel('rick');
40
42
  this._usingOldFetchPlugin = false;
41
43
  this.delay = 0;
42
44
  this.projectKey = projectKey;
@@ -47,6 +49,7 @@ class App {
47
49
  session_token_key: '__openreplay_token',
48
50
  session_pageno_key: '__openreplay_pageno',
49
51
  session_reset_key: '__openreplay_reset',
52
+ session_tabid_key: '__openreplay_tabid',
50
53
  local_uuid_key: '__openreplay_uuid',
51
54
  ingestPoint: exports.DEFAULT_INGEST_POINT,
52
55
  resourceBaseHref: null,
@@ -55,6 +58,7 @@ class App {
55
58
  __debug_report_edp: null,
56
59
  localStorage: null,
57
60
  sessionStorage: null,
61
+ forceSingleTab: false,
58
62
  }, options);
59
63
  this.revID = this.options.revID;
60
64
  this.localStorage = (_a = this.options.localStorage) !== null && _a !== void 0 ? _a : window.localStorage;
@@ -67,6 +71,7 @@ class App {
67
71
  this.debug = new logger_js_1.default(this.options.__debug__);
68
72
  this.notify = new logger_js_1.default(this.options.verbose ? logger_js_1.LogLevel.Warnings : logger_js_1.LogLevel.Silent);
69
73
  this.session = new session_js_1.default(this, this.options);
74
+ this.attributeSender = new attributeSender_js_1.default(this);
70
75
  this.session.attachUpdateCallback(({ userID, metadata }) => {
71
76
  if (userID != null) {
72
77
  // TODO: nullable userID
@@ -81,7 +86,7 @@ class App {
81
86
  this.session.applySessionHash(sessionToken);
82
87
  }
83
88
  try {
84
- this.worker = new Worker(URL.createObjectURL(new Blob(['"use strict";class t{constructor(t,i,s,e=10,n=1e3,r){this.onUnauthorised=i,this.onFailure=s,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.ingestURL=t+"/v1/web/i",this.isCompressing=void 0!==r}authorise(t){this.token=t,this.busy||this.sendNext()}push(t){this.busy||!this.token?this.queue.push(t):(this.busy=!0,this.isCompressing&&this.onCompress?this.onCompress(t):this.sendBatch(t))}sendNext(){const t=this.queue.shift();t?(this.busy=!0,this.isCompressing&&this.onCompress?this.onCompress(t):this.sendBatch(t)):this.busy=!1}retry(t,i){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout(()=>this.sendBatch(t,i),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,i){this.busy=!0;const s={Authorization:"Bearer "+this.token};i&&(s["Content-Encoding"]="gzip"),null!==this.token?fetch(this.ingestURL,{body:t,method:"POST",headers:s,keepalive:t.length<65536}).then(s=>{if(401===s.status)return this.busy=!1,void this.onUnauthorised();s.status>=400?this.retry(t,i):(this.attemptsCount=0,this.sendNext())}).catch(s=>{console.warn("OpenReplay:",s),this.retry(t,i)}):setTimeout(()=>{this.sendBatch(t,i)},500)}sendCompressed(t){this.sendBatch(t,!0)}sendUncompressed(t){this.sendBatch(t,!1)}clean(){this.sendNext(),setTimeout(()=>{this.token=null,this.queue.length=0},100)}}const i="function"==typeof TextEncoder?new TextEncoder:{encode(t){const i=t.length,s=new Uint8Array(3*i);let e=-1;for(let n=0,r=0,h=0;h!==i;){if(n=t.charCodeAt(h),h+=1,n>=55296&&n<=56319){if(h===i){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;break}if(r=t.charCodeAt(h),!(r>=56320&&r<=57343)){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;continue}if(n=1024*(n-55296)+r-56320+65536,h+=1,n>65535){s[e+=1]=240|n>>>18,s[e+=1]=128|n>>>12&63,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n;continue}}n<=127?s[e+=1]=0|n:n<=2047?(s[e+=1]=192|n>>>6,s[e+=1]=128|63&n):(s[e+=1]=224|n>>>12,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n)}return s.subarray(0,e+1)}};class s extends class{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,i){this.data.set(t,i)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=i.encode(t),e=s.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(s,this.offset),this.offset+=e,!0)}reset(){this.offset=0,this.checkpointOffset=0}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}{encode(t){switch(t[0]){case 0:return this.uint(t[1]);case 4:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 11:return this.uint(t[1]);case 12:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 17:return this.uint(t[1])&&this.string(t[2]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 20:return this.uint(t[1])&&this.uint(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 24:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 27:return this.string(t[1])&&this.string(t[2]);case 28:case 29:return this.string(t[1]);case 30:return this.string(t[1])&&this.string(t[2]);case 37:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3]);case 38:return this.uint(t[1])&&this.uint(t[2]);case 39:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 41:return this.string(t[1])&&this.string(t[2]);case 42:return this.string(t[1]);case 44:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 45:case 46:return this.string(t[1])&&this.string(t[2]);case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 50:return this.uint(t[1])&&this.string(t[2]);case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 54:return this.uint(t[1])&&this.string(t[2]);case 55:return this.boolean(t[1]);case 57:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:return this.int(t[1]);case 59:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6])&&this.string(t[7]);case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 61:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 63:case 64:return this.string(t[1])&&this.string(t[2]);case 67:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 70:return this.uint(t[1])&&this.uint(t[2]);case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 79:return this.string(t[1])&&this.string(t[2]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 82:return this.uint(t[1])&&this.uint(t[2]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 114:case 115:return this.uint(t[1]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10])}}}class e{constructor(){this.idx=1,this.backDict={}}getKey(t){let i=!1;return this.backDict[t]||(i=!0,this.backDict[t]=this.idx++),[this.backDict[t],i]}}class n{constructor(t,i,n,r){this.pageNo=t,this.timestamp=i,this.url=n,this.onBatch=r,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new s(this.beaconSize),this.strDict=new e,this.sizeBuffer=new Uint8Array(3),this.isEmpty=!0,this.beaconSizeLimit=1e6,this.prepare()}writeType(t){return this.encoder.uint(t[0])}writeFields(t){return this.encoder.encode(t)}writeSizeAt(t,i){for(let i=0;i<3;i++)this.sizeBuffer[i]=t>>8*i;this.encoder.set(this.sizeBuffer,i)}prepare(){if(!this.encoder.isEmpty)return;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url];this.writeType(t),this.writeFields(t),this.isEmpty=!0}writeWithSize(t){const i=this.encoder;if(!this.writeType(t)||!i.skip(3))return!1;const s=i.getCurrentOffset(),e=this.writeFields(t);if(e){const e=i.getCurrentOffset()-s;if(e>16777215)return console.warn("OpenReplay: max message size overflow."),!1;this.writeSizeAt(e,s-3),i.checkpoint(),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}applyDict(t){const[i,s]=this.strDict.getKey(t);return s&&this.writeMessage([50,i,t]),i}writeMessage(t){0===t[0]&&(this.timestamp=t[1]),4===t[0]&&(this.url=t[1]),12===t[0]&&(t=[51,t[1],this.applyDict(t[2]),this.applyDict(t[3])]),this.writeWithSize(t)||(this.finaliseBatch(),this.writeWithSize(t)||(this.encoder=new s(this.beaconSizeLimit),this.prepare(),this.writeWithSize(t)?this.finaliseBatch():console.warn("OpenReplay: beacon size overflow. Skipping large message.",t,this),this.encoder=new s(this.beaconSize),this.prepare()))}finaliseBatch(){if(this.isEmpty)return;const t=this.encoder.flush();this.onBatch(t),this.prepare()}clean(){this.encoder.reset()}}var r;!function(t){t[t.NotActive=0]="NotActive",t[t.Starting=1]="Starting",t[t.Stopping=2]="Stopping",t[t.Active=3]="Active",t[t.Stopped=4]="Stopped"}(r||(r={}));let h=null,u=null,a=r.NotActive;function o(){u&&u.finaliseBatch()}function c(){a=r.Stopping,null!==l&&(clearInterval(l),l=null),u&&(u.clean(),u=null),h&&(h.clean(),setTimeout(()=>{h=null},500)),a=r.NotActive}function g(){a!==r.Stopped&&(postMessage("restart"),c())}let p,l=null;self.onmessage=({data:i})=>{if(null!=i){if("stop"===i)return o(),c(),a=r.Stopped;if(!Array.isArray(i)){if("compressed"===i.type){if(!h)return console.debug("WebWorker: sender not initialised. Compressed batch."),void g();h.sendCompressed(i.batch)}if("uncompressed"===i.type){if(!h)return console.debug("WebWorker: sender not initialised. Uncompressed batch."),void g();h.sendUncompressed(i.batch)}return"start"===i.type?(a=r.Starting,h=new t(i.ingestPoint,()=>{g()},t=>{!function(t){postMessage({type:"failure",reason:t}),c()}(t)},i.connAttemptCount,i.connAttemptGap,t=>{postMessage({type:"compress",batch:t},[t.buffer])}),u=new n(i.pageNo,i.timestamp,i.url,t=>h&&h.push(t)),null===l&&(l=setInterval(o,1e4)),a=r.Active):"auth"===i.type?h?u?(h.authorise(i.token),void(i.beaconSizeLimit&&u.setBeaconSizeLimit(i.beaconSizeLimit))):(console.debug("WebWorker: writer not initialised. Received auth."),void g()):(console.debug("WebWorker: sender not initialised. Received auth."),void g()):void 0}if(null!==u){const t=u;i.forEach(i=>{55===i[0]&&(i[1]?p=setTimeout(()=>g(),18e5):clearTimeout(p)),t.writeMessage(i)})}u||(postMessage("not_init"),g())}else o()};'], { type: 'text/javascript' })));
89
+ this.worker = new Worker(URL.createObjectURL(new Blob(['"use strict";class t{constructor(t,i,s,e=10,n=1e3,r){this.onUnauthorised=i,this.onFailure=s,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.ingestURL=t+"/v1/web/i",this.isCompressing=void 0!==r}authorise(t){this.token=t,this.busy||this.sendNext()}push(t){this.busy||!this.token?this.queue.push(t):(this.busy=!0,this.isCompressing&&this.onCompress?this.onCompress(t):this.sendBatch(t))}sendNext(){const t=this.queue.shift();t?(this.busy=!0,this.isCompressing&&this.onCompress?this.onCompress(t):this.sendBatch(t)):this.busy=!1}retry(t,i){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout(()=>this.sendBatch(t,i),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,i){this.busy=!0;const s={Authorization:"Bearer "+this.token};i&&(s["Content-Encoding"]="gzip"),null!==this.token?fetch(this.ingestURL,{body:t,method:"POST",headers:s,keepalive:t.length<65536}).then(s=>{if(401===s.status)return this.busy=!1,void this.onUnauthorised();s.status>=400?this.retry(t,i):(this.attemptsCount=0,this.sendNext())}).catch(s=>{console.warn("OpenReplay:",s),this.retry(t,i)}):setTimeout(()=>{this.sendBatch(t,i)},500)}sendCompressed(t){this.sendBatch(t,!0)}sendUncompressed(t){this.sendBatch(t,!1)}clean(){this.sendNext(),setTimeout(()=>{this.token=null,this.queue.length=0},10)}}const i="function"==typeof TextEncoder?new TextEncoder:{encode(t){const i=t.length,s=new Uint8Array(3*i);let e=-1;for(let n=0,r=0,h=0;h!==i;){if(n=t.charCodeAt(h),h+=1,n>=55296&&n<=56319){if(h===i){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;break}if(r=t.charCodeAt(h),!(r>=56320&&r<=57343)){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;continue}if(n=1024*(n-55296)+r-56320+65536,h+=1,n>65535){s[e+=1]=240|n>>>18,s[e+=1]=128|n>>>12&63,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n;continue}}n<=127?s[e+=1]=0|n:n<=2047?(s[e+=1]=192|n>>>6,s[e+=1]=128|63&n):(s[e+=1]=224|n>>>12,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n)}return s.subarray(0,e+1)}};class s extends class{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,i){this.data.set(t,i)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=i.encode(t),e=s.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(s,this.offset),this.offset+=e,!0)}reset(){this.offset=0,this.checkpointOffset=0}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}{encode(t){switch(t[0]){case 0:return this.uint(t[1]);case 4:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 11:return this.uint(t[1]);case 12:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 17:return this.uint(t[1])&&this.string(t[2]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 20:return this.uint(t[1])&&this.uint(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 24:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 27:return this.string(t[1])&&this.string(t[2]);case 28:case 29:return this.string(t[1]);case 30:return this.string(t[1])&&this.string(t[2]);case 37:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3]);case 38:return this.uint(t[1])&&this.uint(t[2]);case 39:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 41:return this.string(t[1])&&this.string(t[2]);case 42:return this.string(t[1]);case 44:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 45:case 46:return this.string(t[1])&&this.string(t[2]);case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 50:return this.uint(t[1])&&this.string(t[2]);case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 54:return this.uint(t[1])&&this.string(t[2]);case 55:return this.boolean(t[1]);case 57:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:return this.int(t[1]);case 59:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6])&&this.string(t[7]);case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 61:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 63:case 64:return this.string(t[1])&&this.string(t[2]);case 67:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 70:return this.uint(t[1])&&this.uint(t[2]);case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 79:return this.string(t[1])&&this.string(t[2]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 82:return this.uint(t[1])&&this.uint(t[2]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 114:case 115:return this.uint(t[1]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 117:case 118:return this.string(t[1])}}}class e{constructor(t,i,e,n,r){this.pageNo=t,this.timestamp=i,this.url=e,this.onBatch=n,this.tabId=r,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new s(this.beaconSize),this.sizeBuffer=new Uint8Array(3),this.isEmpty=!0,this.beaconSizeLimit=1e6,this.prepare()}writeType(t){return this.encoder.uint(t[0])}writeFields(t){return this.encoder.encode(t)}writeSizeAt(t,i){for(let i=0;i<3;i++)this.sizeBuffer[i]=t>>8*i;this.encoder.set(this.sizeBuffer,i)}prepare(){if(!this.encoder.isEmpty)return;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url],i=[118,this.tabId];this.writeType(t),this.writeFields(t),this.writeWithSize(i),this.isEmpty=!0}writeWithSize(t){const i=this.encoder;if(!this.writeType(t)||!i.skip(3))return!1;const s=i.getCurrentOffset(),e=this.writeFields(t);if(e){const e=i.getCurrentOffset()-s;if(e>16777215)return console.warn("OpenReplay: max message size overflow."),!1;this.writeSizeAt(e,s-3),i.checkpoint(),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}writeMessage(t){0===t[0]&&(this.timestamp=t[1]),4===t[0]&&(this.url=t[1]),this.writeWithSize(t)||(this.finaliseBatch(),this.writeWithSize(t)||(this.encoder=new s(this.beaconSizeLimit),this.prepare(),this.writeWithSize(t)?this.finaliseBatch():console.warn("OpenReplay: beacon size overflow. Skipping large message.",t,this),this.encoder=new s(this.beaconSize),this.prepare()))}finaliseBatch(){if(this.isEmpty)return;const t=this.encoder.flush();this.onBatch(t),this.prepare()}clean(){this.encoder.reset()}}var n;!function(t){t[t.NotActive=0]="NotActive",t[t.Starting=1]="Starting",t[t.Stopping=2]="Stopping",t[t.Active=3]="Active",t[t.Stopped=4]="Stopped"}(n||(n={}));let r=null,h=null,u=n.NotActive;function a(){h&&h.finaliseBatch()}function o(){u=n.Stopping,null!==p&&(clearInterval(p),p=null),h&&(h.clean(),h=null),r&&(r.clean(),setTimeout(()=>{r=null},20)),setTimeout(()=>{u=n.NotActive},100)}function c(){u!==n.Stopped&&(postMessage("restart"),o())}let g,p=null;self.onmessage=({data:i})=>{if(null!=i){if("stop"===i)return a(),o(),u=n.Stopped;if(!Array.isArray(i)){if("compressed"===i.type){if(!r)return console.debug("WebWorker: sender not initialised. Compressed batch."),void c();r.sendCompressed(i.batch)}if("uncompressed"===i.type){if(!r)return console.debug("WebWorker: sender not initialised. Uncompressed batch."),void c();r.sendUncompressed(i.batch)}return"start"===i.type?(u=n.Starting,r=new t(i.ingestPoint,()=>{c()},t=>{!function(t){postMessage({type:"failure",reason:t}),o()}(t)},i.connAttemptCount,i.connAttemptGap,t=>{postMessage({type:"compress",batch:t},[t.buffer])}),h=new e(i.pageNo,i.timestamp,i.url,t=>r&&r.push(t),i.tabId),null===p&&(p=setInterval(a,1e4)),u=n.Active):"auth"===i.type?r?h?(r.authorise(i.token),void(i.beaconSizeLimit&&h.setBeaconSizeLimit(i.beaconSizeLimit))):(console.debug("WebWorker: writer not initialised. Received auth."),void c()):(console.debug("WebWorker: sender not initialised. Received auth."),void c()):void 0}if(null!==h){const t=h;i.forEach(i=>{55===i[0]&&(i[1]?g=setTimeout(()=>c(),18e5):clearTimeout(g)),t.writeMessage(i)})}h||(postMessage("not_init"),c())}else a()};'], { type: 'text/javascript' })));
85
90
  this.worker.onerror = (e) => {
86
91
  this._debug('webworker_error', e);
87
92
  };
@@ -135,6 +140,28 @@ class App {
135
140
  catch (e) {
136
141
  this._debug('worker_start', e);
137
142
  }
143
+ const thisTab = this.session.getTabId();
144
+ if (!this.session.getSessionToken() && !this.options.forceSingleTab) {
145
+ this.bc.postMessage({ line: 'never-gonna-give-you-up', source: thisTab });
146
+ }
147
+ this.bc.onmessage = (ev) => {
148
+ if (ev.data.source === thisTab)
149
+ return;
150
+ if (ev.data.line === 'never-gonna-let-you-down') {
151
+ const sessionToken = ev.data.token;
152
+ this.session.setSessionToken(sessionToken);
153
+ }
154
+ if (ev.data.line === 'never-gonna-give-you-up') {
155
+ const token = this.session.getSessionToken();
156
+ if (token) {
157
+ this.bc.postMessage({
158
+ line: 'never-gonna-let-you-down',
159
+ token,
160
+ source: thisTab,
161
+ });
162
+ }
163
+ }
164
+ };
138
165
  }
139
166
  _debug(context, e) {
140
167
  if (this.options.__debug_report_edp !== null) {
@@ -176,6 +203,7 @@ class App {
176
203
  }
177
204
  commit() {
178
205
  if (this.worker && this.messages.length) {
206
+ this.messages.unshift((0, messages_gen_js_1.TabData)(this.session.getTabId()));
179
207
  this.messages.unshift((0, messages_gen_js_1.Timestamp)(this.timestamp()));
180
208
  this.worker.postMessage(this.messages);
181
209
  this.commitCallbacks.forEach((cb) => cb(this.messages));
@@ -345,17 +373,21 @@ class App {
345
373
  url: document.URL,
346
374
  connAttemptCount: this.options.connAttemptCount,
347
375
  connAttemptGap: this.options.connAttemptGap,
376
+ tabId: this.session.getTabId(),
348
377
  });
349
378
  const lsReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
350
379
  this.sessionStorage.removeItem(this.options.session_reset_key);
351
380
  const needNewSessionID = startOpts.forceNew || lsReset || resetByWorker;
381
+ const sessionToken = this.session.getSessionToken();
382
+ const isNewSession = needNewSessionID || !sessionToken;
383
+ console.log('OpenReplay: starting session', needNewSessionID, sessionToken);
352
384
  return window
353
385
  .fetch(this.options.ingestPoint + '/v1/web/start', {
354
386
  method: 'POST',
355
387
  headers: {
356
388
  'Content-Type': 'application/json',
357
389
  },
358
- body: JSON.stringify(Object.assign(Object.assign({}, this.getTrackerInfo()), { timestamp, userID: this.session.getInfo().userID, token: needNewSessionID ? undefined : this.session.getSessionToken(), deviceMemory: performance_js_1.deviceMemory,
390
+ body: JSON.stringify(Object.assign(Object.assign({}, this.getTrackerInfo()), { timestamp, userID: this.session.getInfo().userID, token: isNewSession ? undefined : sessionToken, deviceMemory: performance_js_1.deviceMemory,
359
391
  jsHeapSizeLimit: performance_js_1.jsHeapSizeLimit })),
360
392
  })
361
393
  .then((r) => {
@@ -397,6 +429,11 @@ class App {
397
429
  timestamp: startTimestamp || timestamp,
398
430
  projectID,
399
431
  });
432
+ if (!isNewSession && token === sessionToken) {
433
+ console.log('continuing session on new tab', this.session.getTabId());
434
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
435
+ this.send((0, messages_gen_js_1.TabChange)(this.session.getTabId()));
436
+ }
400
437
  // (Re)send Metadata for the case of a new session
401
438
  Object.entries(this.session.getInfo().metadata).forEach(([key, value]) => this.send((0, messages_gen_js_1.Metadata)(key, value)));
402
439
  this.localStorage.setItem(this.options.local_uuid_key, userUUID);
@@ -431,25 +468,39 @@ class App {
431
468
  return UnsuccessfulStart(START_ERROR);
432
469
  });
433
470
  }
471
+ /**
472
+ * basically we ask other tabs during constructor
473
+ * and here we just apply 10ms delay just in case
474
+ * */
434
475
  start(...args) {
435
476
  if (!document.hidden) {
436
- return this._start(...args);
477
+ return new Promise((resolve) => {
478
+ setTimeout(() => {
479
+ resolve(this._start(...args));
480
+ }, 10);
481
+ });
437
482
  }
438
483
  else {
439
484
  return new Promise((resolve) => {
440
485
  const onVisibilityChange = () => {
441
486
  if (!document.hidden) {
442
487
  document.removeEventListener('visibilitychange', onVisibilityChange);
443
- resolve(this._start(...args));
488
+ setTimeout(() => {
489
+ resolve(this._start(...args));
490
+ }, 10);
444
491
  }
445
492
  };
446
493
  document.addEventListener('visibilitychange', onVisibilityChange);
447
494
  });
448
495
  }
449
496
  }
497
+ getTabId() {
498
+ return this.session.getTabId();
499
+ }
450
500
  stop(stopWorker = true) {
451
501
  if (this.activityState !== ActivityState.NotActive) {
452
502
  try {
503
+ this.attributeSender.clear();
453
504
  this.sanitizer.clear();
454
505
  this.observer.disconnect();
455
506
  this.nodes.clear();
@@ -66,3 +66,5 @@ export declare function SelectionChange(selectionStart: number, selectionEnd: nu
66
66
  export declare function MouseThrashing(timestamp: number): Messages.MouseThrashing;
67
67
  export declare function UnbindNodes(totalRemovedPercent: number): Messages.UnbindNodes;
68
68
  export declare function ResourceTiming(timestamp: number, duration: number, ttfb: number, headerSize: number, encodedBodySize: number, decodedBodySize: number, url: string, initiator: string, transferredSize: number, cached: boolean): Messages.ResourceTiming;
69
+ export declare function TabChange(tabId: string): Messages.TabChange;
70
+ export declare function TabData(tabId: string): Messages.TabData;
@@ -3,7 +3,7 @@
3
3
  /* eslint-disable */
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.CSSInsertRuleURLBased = exports.CustomIssue = exports.TechnicalInfo = exports.SetCSSDataURLBased = exports.SetNodeAttributeURLBased = exports.LongTask = exports.SetNodeFocus = exports.LoadFontFace = exports.SetPageVisibility = exports.ConnectionInformation = exports.ResourceTimingDeprecated = exports.SetNodeAttributeDict = exports.StringDict = exports.PerformanceTrack = exports.GraphQL = exports.NgRx = exports.MobX = exports.Vuex = exports.Redux = exports.StateAction = exports.OTable = exports.Profiler = exports.Fetch = exports.CSSDeleteRule = exports.CSSInsertRule = exports.Metadata = exports.UserAnonymousID = exports.UserID = exports.CustomEvent = exports.PageRenderTiming = exports.PageLoadTiming = exports.ConsoleLog = exports.NetworkRequest = exports.MouseMove = exports.SetInputChecked = exports.SetInputValue = exports.SetInputTarget = exports.SetNodeScroll = exports.SetNodeData = exports.RemoveNodeAttribute = exports.SetNodeAttribute = exports.RemoveNode = exports.MoveNode = exports.CreateTextNode = exports.CreateElementNode = exports.CreateDocument = exports.SetViewportScroll = exports.SetViewportSize = exports.SetPageLocation = exports.Timestamp = void 0;
6
- exports.ResourceTiming = exports.UnbindNodes = exports.MouseThrashing = exports.SelectionChange = exports.InputChange = exports.PartitionedMessage = exports.BatchMetadata = exports.Zustand = exports.JSException = exports.AdoptedSSRemoveOwner = exports.AdoptedSSAddOwner = exports.AdoptedSSDeleteRule = exports.AdoptedSSInsertRuleURLBased = exports.AdoptedSSReplaceURLBased = exports.CreateIFrameDocument = exports.MouseClick = void 0;
6
+ exports.TabData = exports.TabChange = exports.ResourceTiming = exports.UnbindNodes = exports.MouseThrashing = exports.SelectionChange = exports.InputChange = exports.PartitionedMessage = exports.BatchMetadata = exports.Zustand = exports.JSException = exports.AdoptedSSRemoveOwner = exports.AdoptedSSAddOwner = exports.AdoptedSSDeleteRule = exports.AdoptedSSInsertRuleURLBased = exports.AdoptedSSReplaceURLBased = exports.CreateIFrameDocument = exports.MouseClick = void 0;
7
7
  function Timestamp(timestamp) {
8
8
  return [
9
9
  0 /* Messages.Type.Timestamp */,
@@ -600,3 +600,17 @@ function ResourceTiming(timestamp, duration, ttfb, headerSize, encodedBodySize,
600
600
  ];
601
601
  }
602
602
  exports.ResourceTiming = ResourceTiming;
603
+ function TabChange(tabId) {
604
+ return [
605
+ 117 /* Messages.Type.TabChange */,
606
+ tabId,
607
+ ];
608
+ }
609
+ exports.TabChange = TabChange;
610
+ function TabData(tabId) {
611
+ return [
612
+ 118 /* Messages.Type.TabData */,
613
+ tabId,
614
+ ];
615
+ }
616
+ exports.TabData = TabData;
@@ -115,7 +115,7 @@ class Observer {
115
115
  this.app.send((0, messages_gen_js_1.SetNodeAttributeURLBased)(id, name, value, this.app.getBaseHref()));
116
116
  }
117
117
  else {
118
- this.app.send((0, messages_gen_js_1.SetNodeAttribute)(id, name, value));
118
+ this.app.attributeSender.sendSetAttribute(id, name, value);
119
119
  }
120
120
  return;
121
121
  }
@@ -145,7 +145,7 @@ class Observer {
145
145
  if (name === 'href' || value.length > 1e5) {
146
146
  value = '';
147
147
  }
148
- this.app.send((0, messages_gen_js_1.SetNodeAttribute)(id, name, value));
148
+ this.app.attributeSender.sendSetAttribute(id, name, value);
149
149
  }
150
150
  sendNodeData(id, parentElement, data) {
151
151
  if ((0, guards_js_1.hasTag)(parentElement, 'style')) {
@@ -9,6 +9,7 @@ export interface Options {
9
9
  obscureTextNumbers: boolean;
10
10
  domSanitizer?: (node: Element) => SanitizeLevel;
11
11
  }
12
+ export declare const stringWiper: (input: string) => string;
12
13
  export default class Sanitizer {
13
14
  private readonly app;
14
15
  private readonly obscured;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SanitizeLevel = void 0;
3
+ exports.stringWiper = exports.SanitizeLevel = void 0;
4
4
  const utils_js_1 = require("../utils.js");
5
5
  const guards_js_1 = require("./guards.js");
6
6
  var SanitizeLevel;
@@ -9,6 +9,10 @@ var SanitizeLevel;
9
9
  SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
10
10
  SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
11
11
  })(SanitizeLevel = exports.SanitizeLevel || (exports.SanitizeLevel = {}));
12
+ const stringWiper = (input) => input
13
+ .trim()
14
+ .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
15
+ exports.stringWiper = stringWiper;
12
16
  class Sanitizer {
13
17
  constructor(app, options) {
14
18
  this.app = app;
@@ -43,9 +47,7 @@ class Sanitizer {
43
47
  sanitize(id, data) {
44
48
  if (this.obscured.has(id)) {
45
49
  // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
46
- return data
47
- .trim()
48
- .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
50
+ return (0, exports.stringWiper)(data);
49
51
  }
50
52
  if (this.options.obscureTextNumbers) {
51
53
  data = data.replace(/\d/g, '0');
@@ -10,6 +10,7 @@ type OnUpdateCallback = (i: Partial<SessionInfo>) => void;
10
10
  export type Options = {
11
11
  session_token_key: string;
12
12
  session_pageno_key: string;
13
+ session_tabid_key: string;
13
14
  };
14
15
  export default class Session {
15
16
  private readonly app;
@@ -20,6 +21,7 @@ export default class Session {
20
21
  private readonly callbacks;
21
22
  private timestamp;
22
23
  private projectID;
24
+ private tabId;
23
25
  constructor(app: App, options: Options);
24
26
  attachUpdateCallback(cb: OnUpdateCallback): void;
25
27
  private handleUpdate;
@@ -32,6 +34,8 @@ export default class Session {
32
34
  setSessionToken(token: string): void;
33
35
  applySessionHash(hash: string): void;
34
36
  getSessionHash(): string | undefined;
37
+ getTabId(): string;
38
+ private createTabId;
35
39
  getInfo(): SessionInfo;
36
40
  reset(): void;
37
41
  }
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_js_1 = require("../utils.js");
3
4
  class Session {
4
5
  constructor(app, options) {
5
6
  this.app = app;
@@ -8,6 +9,7 @@ class Session {
8
9
  this.userID = null;
9
10
  this.callbacks = [];
10
11
  this.timestamp = 0;
12
+ this.createTabId();
11
13
  }
12
14
  attachUpdateCallback(cb) {
13
15
  this.callbacks.push(cb);
@@ -94,6 +96,22 @@ class Session {
94
96
  }
95
97
  return encodeURI(String(pageNo) + '&' + token);
96
98
  }
99
+ getTabId() {
100
+ if (!this.tabId)
101
+ this.createTabId();
102
+ return this.tabId;
103
+ }
104
+ createTabId() {
105
+ const localId = this.app.sessionStorage.getItem(this.options.session_tabid_key);
106
+ if (localId) {
107
+ this.tabId = localId;
108
+ }
109
+ else {
110
+ const randomId = (0, utils_js_1.generateRandomId)(12);
111
+ this.app.sessionStorage.setItem(this.options.session_tabid_key, randomId);
112
+ this.tabId = randomId;
113
+ }
114
+ }
97
115
  getInfo() {
98
116
  return {
99
117
  sessionID: this.sessionID,
@@ -9,6 +9,7 @@ type Start = {
9
9
  pageNo: number;
10
10
  timestamp: number;
11
11
  url: string;
12
+ tabId: string;
12
13
  } & Options;
13
14
  type Auth = {
14
15
  type: 'auth';
@@ -64,7 +64,9 @@ export declare const enum Type {
64
64
  SelectionChange = 113,
65
65
  MouseThrashing = 114,
66
66
  UnbindNodes = 115,
67
- ResourceTiming = 116
67
+ ResourceTiming = 116,
68
+ TabChange = 117,
69
+ TabData = 118
68
70
  }
69
71
  export type Timestamp = [
70
72
  Type.Timestamp,
@@ -464,5 +466,13 @@ export type ResourceTiming = [
464
466
  number,
465
467
  boolean
466
468
  ];
467
- type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequest | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming;
469
+ export type TabChange = [
470
+ Type.TabChange,
471
+ string
472
+ ];
473
+ export type TabData = [
474
+ Type.TabData,
475
+ string
476
+ ];
477
+ type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequest | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData;
468
478
  export default Message;
package/cjs/index.d.ts CHANGED
@@ -33,6 +33,7 @@ export default class API {
33
33
  stop(): string | undefined;
34
34
  getSessionToken(): string | null | undefined;
35
35
  getSessionID(): string | null | undefined;
36
+ getTabId(): string | null;
36
37
  sessionID(): string | null | undefined;
37
38
  getSessionURL(options?: {
38
39
  withCurrentTime?: boolean;
package/cjs/index.js CHANGED
@@ -25,6 +25,7 @@ const fonts_js_1 = require("./modules/fonts.js");
25
25
  const network_js_1 = require("./modules/network.js");
26
26
  const constructedStyleSheets_js_1 = require("./modules/constructedStyleSheets.js");
27
27
  const selection_js_1 = require("./modules/selection.js");
28
+ const tabs_js_1 = require("./modules/tabs.js");
28
29
  const utils_js_1 = require("./utils.js");
29
30
  const DOCS_SETUP = '/installation/javascript-sdk';
30
31
  function processOptions(obj) {
@@ -118,6 +119,7 @@ class API {
118
119
  (0, fonts_js_1.default)(app);
119
120
  (0, network_js_1.default)(app, options.network);
120
121
  (0, selection_js_1.default)(app);
122
+ (0, tabs_js_1.default)(app);
121
123
  window.__OPENREPLAY__ = this;
122
124
  if (options.autoResetOnWindowOpen) {
123
125
  const wOpen = window.open;
@@ -142,7 +144,7 @@ class API {
142
144
  // no-cors issue only with text/plain or not-set Content-Type
143
145
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
144
146
  req.send(JSON.stringify({
145
- trackerVersion: '7.0.3',
147
+ trackerVersion: '8.0.0',
146
148
  projectKey: options.projectKey,
147
149
  doNotTrack,
148
150
  // TODO: add precise reason (an exact API missing)
@@ -188,6 +190,12 @@ class API {
188
190
  }
189
191
  return this.app.getSessionID();
190
192
  }
193
+ getTabId() {
194
+ if (this.app === null) {
195
+ return null;
196
+ }
197
+ return this.app.getTabId();
198
+ }
191
199
  sessionID() {
192
200
  (0, utils_js_1.deprecationWarn)("'sessionID' method", "'getSessionID' method", '/');
193
201
  return this.getSessionID();
@@ -0,0 +1,14 @@
1
+ import App from '../app/index.js';
2
+ export declare class StringDictionary {
3
+ private idx;
4
+ private backDict;
5
+ getKey(str: string): [number, boolean];
6
+ }
7
+ export default class AttributeSender {
8
+ private readonly app;
9
+ private dict;
10
+ constructor(app: App);
11
+ sendSetAttribute(id: number, name: string, value: string): void;
12
+ private applyDict;
13
+ clear(): void;
14
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StringDictionary = void 0;
4
+ class StringDictionary {
5
+ constructor() {
6
+ this.idx = 1;
7
+ this.backDict = {};
8
+ }
9
+ getKey(str) {
10
+ let isNew = false;
11
+ if (!this.backDict[str]) {
12
+ isNew = true;
13
+ this.backDict[str] = this.idx++;
14
+ }
15
+ return [this.backDict[str], isNew];
16
+ }
17
+ }
18
+ exports.StringDictionary = StringDictionary;
19
+ class AttributeSender {
20
+ constructor(app) {
21
+ this.app = app;
22
+ this.dict = new StringDictionary();
23
+ }
24
+ sendSetAttribute(id, name, value) {
25
+ const message = [
26
+ 51 /* Type.SetNodeAttributeDict */,
27
+ id,
28
+ this.applyDict(name),
29
+ this.applyDict(value),
30
+ ];
31
+ this.app.send(message);
32
+ }
33
+ applyDict(str) {
34
+ const [key, isNew] = this.dict.getKey(str);
35
+ if (isNew) {
36
+ this.app.send([50 /* Type.StringDict */, key, str]);
37
+ }
38
+ return key;
39
+ }
40
+ clear() {
41
+ this.dict = new StringDictionary();
42
+ }
43
+ }
44
+ exports.default = AttributeSender;
@@ -26,13 +26,13 @@ function isSVGInFireFox(url) {
26
26
  const PLACEHOLDER_SRC = 'https://static.openreplay.com/tracker/placeholder.jpeg';
27
27
  function default_1(app) {
28
28
  function sendPlaceholder(id, node) {
29
- app.send((0, messages_gen_js_1.SetNodeAttribute)(id, 'src', PLACEHOLDER_SRC));
29
+ app.attributeSender.sendSetAttribute(id, 'src', PLACEHOLDER_SRC);
30
30
  const { width, height } = node.getBoundingClientRect();
31
31
  if (!node.hasAttribute('width')) {
32
- app.send((0, messages_gen_js_1.SetNodeAttribute)(id, 'width', String(width)));
32
+ app.attributeSender.sendSetAttribute(id, 'width', String(width));
33
33
  }
34
34
  if (!node.hasAttribute('height')) {
35
- app.send((0, messages_gen_js_1.SetNodeAttribute)(id, 'height', String(height)));
35
+ app.attributeSender.sendSetAttribute(id, 'height', String(height));
36
36
  }
37
37
  }
38
38
  const sendSrcset = function (id, img) {
@@ -44,7 +44,7 @@ function default_1(app) {
44
44
  .split(',')
45
45
  .map((str) => resolveURL(str))
46
46
  .join(',');
47
- app.send((0, messages_gen_js_1.SetNodeAttribute)(id, 'srcset', resolvedSrcset));
47
+ app.attributeSender.sendSetAttribute(id, 'srcset', resolvedSrcset);
48
48
  };
49
49
  const sendSrc = function (id, img) {
50
50
  if (img.src.length > utils_js_1.MAX_STR_LEN) {
@@ -0,0 +1,2 @@
1
+ import type App from '../app/index.js';
2
+ export default function (app: App): void;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const messages_gen_js_1 = require("../app/messages.gen.js");
4
+ function default_1(app) {
5
+ function changeTab() {
6
+ if (!document.hidden) {
7
+ app.debug.log('Openreplay: tab change to' + app.session.getTabId());
8
+ app.send((0, messages_gen_js_1.TabChange)(app.session.getTabId()));
9
+ }
10
+ }
11
+ app.attachEventListener(window, 'focus', changeTab, false, false);
12
+ }
13
+ exports.default = default_1;
package/cjs/utils.d.ts CHANGED
@@ -11,8 +11,8 @@ export declare const DOCS_HOST = "https://docs.openreplay.com";
11
11
  export declare function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath?: string): void;
12
12
  export declare function getLabelAttribute(e: Element): string | null;
13
13
  export declare function hasOpenreplayAttribute(e: Element, attr: string): boolean;
14
- export declare function isIframeCrossdomain(e: HTMLIFrameElement): boolean;
15
14
  /**
16
15
  * checks if iframe is accessible
17
16
  **/
18
17
  export declare function canAccessIframe(iframe: HTMLIFrameElement): boolean;
18
+ export declare function generateRandomId(len?: number): string;