@openreplay/tracker 13.0.1 → 14.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 (50) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bun.lockb +0 -0
  3. package/cjs/app/canvas.d.ts +2 -0
  4. package/cjs/app/canvas.js +6 -5
  5. package/cjs/app/index.d.ts +55 -16
  6. package/cjs/app/index.js +417 -236
  7. package/cjs/app/messages.gen.d.ts +6 -3
  8. package/cjs/app/messages.gen.js +44 -10
  9. package/cjs/app/nodes.d.ts +2 -0
  10. package/cjs/app/nodes.js +15 -1
  11. package/cjs/app/observer/iframe_observer.d.ts +1 -0
  12. package/cjs/app/observer/iframe_observer.js +9 -0
  13. package/cjs/app/observer/iframe_offsets.js +0 -1
  14. package/cjs/app/observer/top_observer.d.ts +1 -0
  15. package/cjs/app/observer/top_observer.js +14 -0
  16. package/cjs/common/messages.gen.d.ts +38 -10
  17. package/cjs/index.d.ts +1 -0
  18. package/cjs/index.js +17 -8
  19. package/cjs/modules/conditionsManager.js +2 -2
  20. package/cjs/modules/mouse.js +14 -1
  21. package/cjs/modules/scroll.d.ts +1 -1
  22. package/cjs/modules/scroll.js +9 -4
  23. package/cjs/modules/viewport.js +2 -2
  24. package/cjs/utils.d.ts +2 -1
  25. package/cjs/utils.js +33 -6
  26. package/lib/app/canvas.d.ts +2 -0
  27. package/lib/app/canvas.js +6 -5
  28. package/lib/app/index.d.ts +55 -16
  29. package/lib/app/index.js +399 -218
  30. package/lib/app/messages.gen.d.ts +6 -3
  31. package/lib/app/messages.gen.js +37 -6
  32. package/lib/app/nodes.d.ts +2 -0
  33. package/lib/app/nodes.js +15 -1
  34. package/lib/app/observer/iframe_observer.d.ts +1 -0
  35. package/lib/app/observer/iframe_observer.js +9 -0
  36. package/lib/app/observer/iframe_offsets.js +0 -1
  37. package/lib/app/observer/top_observer.d.ts +1 -0
  38. package/lib/app/observer/top_observer.js +14 -0
  39. package/lib/common/messages.gen.d.ts +38 -10
  40. package/lib/common/tsconfig.tsbuildinfo +1 -1
  41. package/lib/index.d.ts +1 -0
  42. package/lib/index.js +18 -9
  43. package/lib/modules/conditionsManager.js +2 -2
  44. package/lib/modules/mouse.js +14 -1
  45. package/lib/modules/scroll.d.ts +1 -1
  46. package/lib/modules/scroll.js +9 -4
  47. package/lib/modules/viewport.js +2 -2
  48. package/lib/utils.d.ts +2 -1
  49. package/lib/utils.js +31 -5
  50. package/package.json +1 -1
package/lib/app/index.js CHANGED
@@ -1,24 +1,22 @@
1
+ import { gzip } from 'fflate';
2
+ import AttributeSender from '../modules/attributeSender.js';
1
3
  import ConditionsManager from '../modules/conditionsManager.js';
2
4
  import FeatureFlags from '../modules/featureFlags.js';
3
- import { TagTrigger } from './messages.gen.js';
4
- import { Timestamp, Metadata, UserID, TabChange, TabData, WSChannel, } from './messages.gen.js';
5
- import { now, adjustTimeOrigin, deprecationWarn, inIframe, createEventListener, deleteEventListener, requestIdleCb, } from '../utils.js';
5
+ import { deviceMemory, jsHeapSizeLimit } from '../modules/performance.js';
6
+ import TagWatcher from '../modules/tagWatcher.js';
7
+ import UserTestManager from '../modules/userTesting/index.js';
8
+ import { adjustTimeOrigin, createEventListener, deleteEventListener, now, requestIdleCb, simpleMerge, } from '../utils.js';
9
+ import CanvasRecorder from './canvas.js';
10
+ import Logger, { LogLevel } from './logger.js';
11
+ import { Metadata, TabChange, TabData, TagTrigger, Timestamp, UserID, WSChannel, } from './messages.gen.js';
6
12
  import Nodes from './nodes.js';
7
13
  import Observer from './observer/top_observer.js';
8
14
  import Sanitizer from './sanitizer.js';
9
- import Ticker from './ticker.js';
10
- import Logger, { LogLevel } from './logger.js';
11
15
  import Session from './session.js';
12
- import { gzip } from 'fflate';
13
- import { deviceMemory, jsHeapSizeLimit } from '../modules/performance.js';
14
- import AttributeSender from '../modules/attributeSender.js';
15
- import CanvasRecorder from './canvas.js';
16
- import UserTestManager from '../modules/userTesting/index.js';
17
- import TagWatcher from '../modules/tagWatcher.js';
16
+ import Ticker from './ticker.js';
18
17
  const CANCELED = 'canceled';
19
18
  const uxtStorageKey = 'or_uxt_active';
20
19
  const bufferStorageKey = 'or_buffer_1';
21
- const START_ERROR = ':(';
22
20
  const UnsuccessfulStart = (reason) => ({ reason, success: false });
23
21
  const SuccessfulStart = (body) => ({ ...body, success: true });
24
22
  var ActivityState;
@@ -37,9 +35,25 @@ function getTimezone() {
37
35
  const minutes = Math.abs(offset) % 60;
38
36
  return `UTC${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
39
37
  }
38
+ const delay = (ms) => new Promise((res) => setTimeout(res, ms));
39
+ const proto = {
40
+ // ask if there are any tabs alive
41
+ ask: 'never-gonna-give-you-up',
42
+ // response from another tab
43
+ resp: 'never-gonna-let-you-down',
44
+ // regenerating id (copied other tab)
45
+ reg: 'never-gonna-run-around-and-desert-you',
46
+ // tracker inside a child iframe
47
+ iframeSignal: 'never-gonna-make-you-cry',
48
+ // getting node id for child iframe
49
+ iframeId: 'never-gonna-say-goodbye',
50
+ // batch of messages from an iframe window
51
+ iframeBatch: 'never-gonna-tell-a-lie-and-hurt-you',
52
+ };
40
53
  export default class App {
41
- constructor(projectKey, sessionToken, options, signalError) {
54
+ constructor(projectKey, sessionToken, options, signalError, insideIframe) {
42
55
  this.signalError = signalError;
56
+ this.insideIframe = insideIframe;
43
57
  this.messages = [];
44
58
  /**
45
59
  * we need 2 buffers, so we don't lose anything
@@ -51,14 +65,18 @@ export default class App {
51
65
  this.stopCallbacks = [];
52
66
  this.commitCallbacks = [];
53
67
  this.activityState = ActivityState.NotActive;
54
- this.version = '13.0.1'; // TODO: version compatability check inside each plugin.
68
+ this.version = '14.0.0'; // TODO: version compatability check inside each plugin.
55
69
  this.socketMode = false;
56
70
  this.compressionThreshold = 24 * 1000;
57
- this.restartAttempts = 0;
58
71
  this.bc = null;
59
72
  this.canvasRecorder = null;
60
73
  this.conditionsManager = null;
61
- this._usingOldFetchPlugin = false;
74
+ this.canStart = false;
75
+ this.rootId = null;
76
+ this.pageFrames = [];
77
+ this.frameOderNumber = 0;
78
+ this.initialHostName = location.hostname;
79
+ this.startTimeout = null;
62
80
  this.coldStartCommitN = 0;
63
81
  this.delay = 0;
64
82
  this.coldInterval = null;
@@ -89,6 +107,8 @@ export default class App {
89
107
  });
90
108
  };
91
109
  this.onUxtCb = [];
110
+ this.contextId = Math.random().toString(36).slice(2);
111
+ this.projectKey = projectKey;
92
112
  if (Object.keys(options).findIndex((k) => ['fixedCanvasScaling', 'disableCanvas'].includes(k)) !==
93
113
  -1) {
94
114
  console.warn('Openreplay: canvas options are moving to separate key "canvas" in next update. Please update your configuration.');
@@ -101,10 +121,8 @@ export default class App {
101
121
  },
102
122
  };
103
123
  }
104
- this.contextId = Math.random().toString(36).slice(2);
105
- this.projectKey = projectKey;
106
124
  this.networkOptions = options.network;
107
- this.options = Object.assign({
125
+ const defaultOptions = {
108
126
  revID: '',
109
127
  node_id: '__openreplay_id',
110
128
  session_token_key: '__openreplay_token',
@@ -125,17 +143,26 @@ export default class App {
125
143
  assistSocketHost: '',
126
144
  fixedCanvasScaling: false,
127
145
  disableCanvas: false,
128
- assistOnly: false,
146
+ captureIFrames: true,
147
+ obscureTextEmails: true,
148
+ obscureTextNumbers: false,
149
+ crossdomain: {
150
+ parentDomain: '*',
151
+ },
129
152
  canvas: {
130
153
  disableCanvas: false,
131
154
  fixedCanvasScaling: false,
132
155
  __save_canvas_locally: false,
133
156
  useAnimationFrame: false,
134
157
  },
135
- }, options);
136
- if (!this.options.forceSingleTab && globalThis && 'BroadcastChannel' in globalThis) {
158
+ };
159
+ this.options = simpleMerge(defaultOptions, options);
160
+ if (!this.insideIframe &&
161
+ !this.options.forceSingleTab &&
162
+ globalThis &&
163
+ 'BroadcastChannel' in globalThis) {
137
164
  const host = location.hostname.split('.').slice(-2).join('_');
138
- this.bc = inIframe() ? null : new BroadcastChannel(`rick_${host}`);
165
+ this.bc = new BroadcastChannel(`rick_${host}`);
139
166
  }
140
167
  this.revID = this.options.revID;
141
168
  this.localStorage = this.options.localStorage ?? window.localStorage;
@@ -165,80 +192,140 @@ export default class App {
165
192
  if (sessionToken != null) {
166
193
  this.session.applySessionHash(sessionToken);
167
194
  }
168
- try {
169
- this.worker = new Worker(URL.createObjectURL(new Blob(['"use strict";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.ingestURL=t+"/v1/web/i",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t){if(this.busy||!this.token)this.queue.push(t);else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}else this.busy=!1}retry(t,s,i){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout((()=>this.sendBatch(t,s,i)),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,s,i){const e=i?.toString().replace(/^([^_]+)_([^_]+).*/,"$1_$2_$3");this.busy=!0;const n={Authorization:`Bearer ${this.token}`};s&&(n["Content-Encoding"]="gzip"),null!==this.token?fetch(`${this.ingestURL}?batch=${this.pageNo??"noPageNum"}_${e??"noBatchNum"}`,{body:t,method:"POST",headers:n,keepalive:t.length<65536}).then((e=>{if(401===e.status)return this.busy=!1,void this.onUnauthorised();e.status>=400?this.retry(t,s,`${i??"noBatchNum"}_network:${e.status}`):(this.attemptsCount=0,this.sendNext())})).catch((e=>{console.warn("OpenReplay:",e),this.retry(t,s,`${i??"noBatchNum"}_reject:${e.message}`)})):setTimeout((()=>{this.sendBatch(t,s,`${i??"noBatchNum"}_newToken`)}),500)}sendCompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!0,s)}sendUncompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s="function"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class i{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,s){this.data.set(t,s)}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 i=s.encode(t),e=i.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(i,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}}class e extends i{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 38:case 70:case 75:case 76:case 77:case 82: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:case 24:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 50:case 54: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 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 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:case 27:case 30:case 41:case 45:case 46:case 63:case 64:case 79: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 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 37:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3]);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 48:case 78: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 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 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120: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 67:case 73: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 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83: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])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);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 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 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4])}}}class n{constructor(t,s,i,n,h,r){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=n,this.tabId=h,this.onOfflineEnd=r,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new e(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,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}prepare(){if(!this.encoder.isEmpty)return;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url],s=[118,this.tabId];this.writeType(t),this.writeFields(t),this.writeWithSize(s),this.isEmpty=!0}writeWithSize(t){const s=this.encoder;if(!this.writeType(t)||!s.skip(3))return!1;const i=s.getCurrentOffset(),e=this.writeFields(t);if(e){const e=s.getCurrentOffset()-i;if(e>16777215)return console.warn("OpenReplay: max message size overflow."),!1;this.writeSizeAt(e,i-3),s.checkpoint(),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}writeMessage(t){if("q_end"===t[0])return this.finaliseBatch(),this.onOfflineEnd();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 e(this.beaconSizeLimit),this.prepare(),this.writeWithSize(t)?this.finaliseBatch():console.warn("OpenReplay: beacon size overflow. Skipping large message.",t,this),this.encoder=new e(this.beaconSize),this.prepare()))}finaliseBatch(){if(this.isEmpty)return;const t=this.encoder.flush();this.onBatch(t),this.prepare()}clean(){this.encoder.reset()}}var h;!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"}(h||(h={}));let r=null,a=null,u=h.NotActive;function o(){a&&a.finaliseBatch()}function c(){return new Promise((t=>{u=h.Stopping,null!==l&&(clearInterval(l),l=null),a&&(a.clean(),a=null),r&&(r.clean(),setTimeout((()=>{r=null}),20)),setTimeout((()=>{u=h.NotActive,t(null)}),100)}))}function p(){u!==h.Stopped&&(postMessage("a_stop"),c().then((()=>{postMessage("a_start")})))}let g,l=null;self.onmessage=({data:s})=>{if(null!=s){if("stop"===s)return o(),void c().then((()=>{u=h.Stopped}));if("forceFlushBatch"!==s){if(!Array.isArray(s)){if("compressed"===s.type){if(!r)return console.debug("OR WebWorker: sender not initialised. Compressed batch."),void p();s.batch&&r.sendCompressed(s.batch)}if("uncompressed"===s.type){if(!r)return console.debug("OR WebWorker: sender not initialised. Uncompressed batch."),void p();s.batch&&r.sendUncompressed(s.batch)}return"start"===s.type?(u=h.Starting,r=new t(s.ingestPoint,(()=>{p()}),(t=>{!function(t){postMessage({type:"failure",reason:t}),c()}(t)}),s.connAttemptCount,s.connAttemptGap,(t=>{postMessage({type:"compress",batch:t},[t.buffer])}),s.pageNo),a=new n(s.pageNo,s.timestamp,s.url,(t=>{r&&r.push(t)}),s.tabId,(()=>postMessage({type:"queue_empty"}))),null===l&&(l=setInterval(o,1e4)),u=h.Active):"auth"===s.type?r?a?(r.authorise(s.token),void(s.beaconSizeLimit&&a.setBeaconSizeLimit(s.beaconSizeLimit))):(console.debug("OR WebWorker: writer not initialised. Received auth."),void p()):(console.debug("OR WebWorker: sender not initialised. Received auth."),void p()):void 0}if(a){const t=a;s.forEach((s=>{55===s[0]&&(s[1]?g=setTimeout((()=>p()),18e5):clearTimeout(g)),t.writeMessage(s)}))}else postMessage("not_init"),p()}else o()}else o()};'], { type: 'text/javascript' })));
170
- this.worker.onerror = (e) => {
171
- this._debug('webworker_error', e);
172
- };
173
- this.worker.onmessage = ({ data }) => {
174
- // handling 401 auth restart (new token assignment)
175
- if (data === 'a_stop') {
176
- this.stop(false);
177
- }
178
- else if (data === 'a_start') {
179
- void this.start({}, true);
180
- }
181
- else if (data === 'not_init') {
182
- this.debug.warn('OR WebWorker: writer not initialised. Restarting tracker');
183
- }
184
- else if (data.type === 'failure') {
185
- this.stop(false);
186
- this.debug.error('worker_failed', data.reason);
187
- this._debug('worker_failed', data.reason);
188
- }
189
- else if (data.type === 'compress') {
190
- const batch = data.batch;
191
- const batchSize = batch.byteLength;
192
- if (batchSize > this.compressionThreshold) {
193
- gzip(data.batch, { mtime: 0 }, (err, result) => {
194
- if (err) {
195
- this.debug.error('Openreplay compression error:', err);
196
- this.worker?.postMessage({ type: 'uncompressed', batch: batch });
197
- }
198
- else {
199
- this.worker?.postMessage({ type: 'compressed', batch: result });
195
+ this.initWorker();
196
+ const thisTab = this.session.getTabId();
197
+ if (!this.insideIframe) {
198
+ /**
199
+ * if we get a signal from child iframes, we check for their node_id and send it back,
200
+ * so they can act as if it was just a same-domain iframe
201
+ * */
202
+ let crossdomainFrameCount = 0;
203
+ const catchIframeMessage = (event) => {
204
+ const { data } = event;
205
+ if (data.line === proto.iframeSignal) {
206
+ const childIframeDomain = data.domain;
207
+ const pageIframes = Array.from(document.querySelectorAll('iframe'));
208
+ this.pageFrames = pageIframes;
209
+ const signalId = async () => {
210
+ let tries = 0;
211
+ while (tries < 10) {
212
+ const id = this.checkNodeId(pageIframes, childIframeDomain);
213
+ if (id) {
214
+ this.waitStarted()
215
+ .then(() => {
216
+ crossdomainFrameCount++;
217
+ const token = this.session.getSessionToken();
218
+ const iframeData = {
219
+ line: proto.iframeId,
220
+ context: this.contextId,
221
+ domain: childIframeDomain,
222
+ id,
223
+ token,
224
+ frameOrderNumber: crossdomainFrameCount,
225
+ };
226
+ this.debug.log('iframe_data', iframeData);
227
+ // @ts-ignore
228
+ event.source?.postMessage(iframeData, '*');
229
+ })
230
+ .catch(console.error);
231
+ tries = 10;
232
+ break;
200
233
  }
201
- });
202
- }
203
- else {
204
- this.worker?.postMessage({ type: 'uncompressed', batch: batch });
205
- }
234
+ tries++;
235
+ await delay(100);
236
+ }
237
+ };
238
+ void signalId();
206
239
  }
207
- else if (data.type === 'queue_empty') {
208
- this.onSessionSent();
240
+ /**
241
+ * proxying messages from iframe to main body, so they can be in one batch (same indexes, etc)
242
+ * plus we rewrite some of the messages to be relative to the main context/window
243
+ * */
244
+ if (data.line === proto.iframeBatch) {
245
+ const msgBatch = data.messages;
246
+ const mappedMessages = msgBatch.map((msg) => {
247
+ if (msg[0] === 20 /* MType.MouseMove */) {
248
+ let fixedMessage = msg;
249
+ this.pageFrames.forEach((frame) => {
250
+ if (frame.dataset.domain === event.data.domain) {
251
+ const [type, x, y] = msg;
252
+ const { left, top } = frame.getBoundingClientRect();
253
+ fixedMessage = [type, x + left, y + top];
254
+ }
255
+ });
256
+ return fixedMessage;
257
+ }
258
+ if (msg[0] === 68 /* MType.MouseClick */) {
259
+ let fixedMessage = msg;
260
+ this.pageFrames.forEach((frame) => {
261
+ if (frame.dataset.domain === event.data.domain) {
262
+ const [type, id, hesitationTime, label, selector, normX, normY] = msg;
263
+ const { left, top, width, height } = frame.getBoundingClientRect();
264
+ const contentWidth = document.documentElement.scrollWidth;
265
+ const contentHeight = document.documentElement.scrollHeight;
266
+ // (normalizedX * frameWidth + frameLeftOffset)/docSize
267
+ const fullX = (normX / 100) * width + left;
268
+ const fullY = (normY / 100) * height + top;
269
+ const fixedX = fullX / contentWidth;
270
+ const fixedY = fullY / contentHeight;
271
+ fixedMessage = [
272
+ type,
273
+ id,
274
+ hesitationTime,
275
+ label,
276
+ selector,
277
+ Math.round(fixedX * 1e3) / 1e1,
278
+ Math.round(fixedY * 1e3) / 1e1,
279
+ ];
280
+ }
281
+ });
282
+ return fixedMessage;
283
+ }
284
+ return msg;
285
+ });
286
+ this.messages.push(...mappedMessages);
209
287
  }
210
288
  };
211
- const alertWorker = () => {
212
- if (this.worker) {
213
- this.worker.postMessage(null);
289
+ window.addEventListener('message', catchIframeMessage);
290
+ this.attachStopCallback(() => {
291
+ window.removeEventListener('message', catchIframeMessage);
292
+ });
293
+ }
294
+ else {
295
+ const catchParentMessage = (event) => {
296
+ const { data } = event;
297
+ if (data.line !== proto.iframeId) {
298
+ return;
214
299
  }
300
+ this.rootId = data.id;
301
+ this.session.setSessionToken(data.token);
302
+ this.frameOderNumber = data.frameOrderNumber;
303
+ this.debug.log('starting iframe tracking', data);
304
+ this.allowAppStart();
215
305
  };
216
- // keep better tactics, discard others?
217
- this.attachEventListener(window, 'beforeunload', alertWorker, false);
218
- this.attachEventListener(document.body, 'mouseleave', alertWorker, false, false);
219
- // TODO: stop session after inactivity timeout (make configurable)
220
- this.attachEventListener(document, 'visibilitychange', alertWorker, false);
221
- }
222
- catch (e) {
223
- this._debug('worker_start', e);
306
+ window.addEventListener('message', catchParentMessage);
307
+ this.attachStopCallback(() => {
308
+ window.removeEventListener('message', catchParentMessage);
309
+ });
310
+ // communicating with parent window,
311
+ // even if its crossdomain is possible via postMessage api
312
+ const domain = this.initialHostName;
313
+ window.parent.postMessage({
314
+ line: proto.iframeSignal,
315
+ source: thisTab,
316
+ context: this.contextId,
317
+ domain,
318
+ }, '*');
224
319
  }
225
- const thisTab = this.session.getTabId();
226
- const proto = {
227
- // ask if there are any tabs alive
228
- ask: 'never-gonna-give-you-up',
229
- // yes, there are someone out there
230
- resp: 'never-gonna-let-you-down',
231
- // you stole someone's identity
232
- reg: 'never-gonna-run-around-and-desert-you',
233
- };
234
- if (this.bc) {
320
+ if (this.bc !== null) {
235
321
  this.bc.postMessage({
236
322
  line: proto.ask,
237
323
  source: thisTab,
238
324
  context: this.contextId,
239
325
  });
240
- }
241
- if (this.bc !== null) {
326
+ this.startTimeout = setTimeout(() => {
327
+ this.allowAppStart();
328
+ }, 500);
242
329
  this.bc.onmessage = (ev) => {
243
330
  if (ev.data.context === this.contextId) {
244
331
  return;
@@ -246,11 +333,13 @@ export default class App {
246
333
  if (ev.data.line === proto.resp) {
247
334
  const sessionToken = ev.data.token;
248
335
  this.session.setSessionToken(sessionToken);
336
+ this.allowAppStart();
249
337
  }
250
338
  if (ev.data.line === proto.reg) {
251
339
  const sessionToken = ev.data.token;
252
340
  this.session.regenerateTabId();
253
341
  this.session.setSessionToken(sessionToken);
342
+ this.allowAppStart();
254
343
  }
255
344
  if (ev.data.line === proto.ask) {
256
345
  const token = this.session.getSessionToken();
@@ -266,6 +355,84 @@ export default class App {
266
355
  };
267
356
  }
268
357
  }
358
+ allowAppStart() {
359
+ this.canStart = true;
360
+ if (this.startTimeout) {
361
+ clearTimeout(this.startTimeout);
362
+ this.startTimeout = null;
363
+ }
364
+ }
365
+ checkNodeId(iframes, domain) {
366
+ for (const iframe of iframes) {
367
+ if (iframe.dataset.domain === domain) {
368
+ // @ts-ignore
369
+ return iframe[this.options.node_id];
370
+ }
371
+ }
372
+ return null;
373
+ }
374
+ initWorker() {
375
+ try {
376
+ this.worker = new Worker(URL.createObjectURL(new Blob(['"use strict";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.ingestURL=t+"/v1/web/i",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t){if(this.busy||!this.token)this.queue.push(t);else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}else this.busy=!1}retry(t,s,i){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout((()=>this.sendBatch(t,s,i)),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,s,i){const e=i?.toString().replace(/^([^_]+)_([^_]+).*/,"$1_$2_$3");this.busy=!0;const n={Authorization:`Bearer ${this.token}`};s&&(n["Content-Encoding"]="gzip"),null!==this.token?fetch(`${this.ingestURL}?batch=${this.pageNo??"noPageNum"}_${e??"noBatchNum"}`,{body:t,method:"POST",headers:n,keepalive:t.length<65536}).then((e=>{if(401===e.status)return this.busy=!1,void this.onUnauthorised();e.status>=400?this.retry(t,s,`${i??"noBatchNum"}_network:${e.status}`):(this.attemptsCount=0,this.sendNext())})).catch((e=>{console.warn("OpenReplay:",e),this.retry(t,s,`${i??"noBatchNum"}_reject:${e.message}`)})):setTimeout((()=>{this.sendBatch(t,s,`${i??"noBatchNum"}_newToken`)}),500)}sendCompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!0,s)}sendUncompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s="function"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class i{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,s){this.data.set(t,s)}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 i=s.encode(t),e=i.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(i,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}}class e extends i{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 38:case 70:case 75:case 76:case 77:case 82: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:case 24:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 50:case 54: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 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 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:case 27:case 30:case 41:case 45:case 46:case 63:case 64:case 79: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 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 37:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3]);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 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.int(t[5]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);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 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120: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 67:case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 68:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83: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])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);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 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 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 122:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 123:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])}}}class n{constructor(t,s,i,n,h,r){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=n,this.tabId=h,this.onOfflineEnd=r,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new e(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,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}prepare(){if(!this.encoder.isEmpty)return;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url],s=[118,this.tabId];this.writeType(t),this.writeFields(t),this.writeWithSize(s),this.isEmpty=!0}writeWithSize(t){const s=this.encoder;if(!this.writeType(t)||!s.skip(3))return!1;const i=s.getCurrentOffset(),e=this.writeFields(t);if(e){const e=s.getCurrentOffset()-i;if(e>16777215)return console.warn("OpenReplay: max message size overflow."),!1;this.writeSizeAt(e,i-3),s.checkpoint(),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}writeMessage(t){if("q_end"===t[0])return this.finaliseBatch(),this.onOfflineEnd();0===t[0]&&(this.timestamp=t[1]),122===t[0]&&(this.url=t[1]),this.writeWithSize(t)||(this.finaliseBatch(),this.writeWithSize(t)||(this.encoder=new e(this.beaconSizeLimit),this.prepare(),this.writeWithSize(t)?this.finaliseBatch():console.warn("OpenReplay: beacon size overflow. Skipping large message.",t,this),this.encoder=new e(this.beaconSize),this.prepare()))}finaliseBatch(){if(this.isEmpty)return;const t=this.encoder.flush();this.onBatch(t),this.prepare()}clean(){this.encoder.reset()}}var h;!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"}(h||(h={}));let r=null,a=null,u=h.NotActive;function o(){a&&a.finaliseBatch()}function c(){return new Promise((t=>{u=h.Stopping,null!==l&&(clearInterval(l),l=null),a&&(a.clean(),a=null),r&&(r.clean(),setTimeout((()=>{r=null}),20)),setTimeout((()=>{u=h.NotActive,t(null)}),100)}))}function g(){u!==h.Stopped&&(postMessage("a_stop"),c().then((()=>{postMessage("a_start")})))}let p,l=null;self.onmessage=({data:s})=>{if(null!=s){if("stop"===s)return o(),void c().then((()=>{u=h.Stopped}));if("forceFlushBatch"!==s){if(!Array.isArray(s)){if("compressed"===s.type){if(!r)return console.debug("OR WebWorker: sender not initialised. Compressed batch."),void g();s.batch&&r.sendCompressed(s.batch)}if("uncompressed"===s.type){if(!r)return console.debug("OR WebWorker: sender not initialised. Uncompressed batch."),void g();s.batch&&r.sendUncompressed(s.batch)}return"start"===s.type?(u=h.Starting,r=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:"failure",reason:t}),c()}(t)}),s.connAttemptCount,s.connAttemptGap,(t=>{postMessage({type:"compress",batch:t},[t.buffer])}),s.pageNo),a=new n(s.pageNo,s.timestamp,s.url,(t=>{r&&r.push(t)}),s.tabId,(()=>postMessage({type:"queue_empty"}))),null===l&&(l=setInterval(o,1e4)),u=h.Active):"auth"===s.type?r?a?(r.authorise(s.token),void(s.beaconSizeLimit&&a.setBeaconSizeLimit(s.beaconSizeLimit))):(console.debug("OR WebWorker: writer not initialised. Received auth."),void g()):(console.debug("OR WebWorker: sender not initialised. Received auth."),void g()):void 0}if(a){const t=a;s.forEach((s=>{55===s[0]&&(s[1]?p=setTimeout((()=>g()),18e5):clearTimeout(p)),t.writeMessage(s)}))}else postMessage("not_init"),g()}else o()}else o()};'], { type: 'text/javascript' })));
377
+ this.worker.onerror = (e) => {
378
+ this._debug('webworker_error', e);
379
+ };
380
+ this.worker.onmessage = ({ data }) => {
381
+ this.handleWorkerMsg(data);
382
+ };
383
+ const alertWorker = () => {
384
+ if (this.worker) {
385
+ this.worker.postMessage(null);
386
+ }
387
+ };
388
+ // keep better tactics, discard others?
389
+ this.attachEventListener(window, 'beforeunload', alertWorker, false);
390
+ this.attachEventListener(document.body, 'mouseleave', alertWorker, false, false);
391
+ // TODO: stop session after inactivity timeout (make configurable)
392
+ this.attachEventListener(document, 'visibilitychange', alertWorker, false);
393
+ }
394
+ catch (e) {
395
+ this._debug('worker_start', e);
396
+ }
397
+ }
398
+ handleWorkerMsg(data) {
399
+ // handling 401 auth restart (new token assignment)
400
+ if (data === 'a_stop') {
401
+ this.stop(false);
402
+ }
403
+ else if (data === 'a_start') {
404
+ void this.start({}, true);
405
+ }
406
+ else if (data === 'not_init') {
407
+ this.debug.warn('OR WebWorker: writer not initialised. Restarting tracker');
408
+ }
409
+ else if (data.type === 'failure') {
410
+ this.stop(false);
411
+ this.debug.error('worker_failed', data.reason);
412
+ this._debug('worker_failed', data.reason);
413
+ }
414
+ else if (data.type === 'compress') {
415
+ const batch = data.batch;
416
+ const batchSize = batch.byteLength;
417
+ if (batchSize > this.compressionThreshold) {
418
+ gzip(data.batch, { mtime: 0 }, (err, result) => {
419
+ if (err) {
420
+ this.debug.error('Openreplay compression error:', err);
421
+ this.worker?.postMessage({ type: 'uncompressed', batch: batch });
422
+ }
423
+ else {
424
+ this.worker?.postMessage({ type: 'compressed', batch: result });
425
+ }
426
+ });
427
+ }
428
+ else {
429
+ this.worker?.postMessage({ type: 'uncompressed', batch: batch });
430
+ }
431
+ }
432
+ else if (data.type === 'queue_empty') {
433
+ this.onSessionSent();
434
+ }
435
+ }
269
436
  _debug(context, e) {
270
437
  if (this.options.__debug_report_edp !== null) {
271
438
  void fetch(this.options.__debug_report_edp, {
@@ -284,15 +451,6 @@ export default class App {
284
451
  if (this.activityState === ActivityState.NotActive) {
285
452
  return;
286
453
  }
287
- // === Back compatibility with Fetch/Axios plugins ===
288
- if (message[0] === 39 /* MType.Fetch */) {
289
- this._usingOldFetchPlugin = true;
290
- deprecationWarn('Fetch plugin', "'network' init option", '/installation/network-options');
291
- deprecationWarn('Axios plugin', "'network' init option", '/installation/network-options');
292
- }
293
- if (this._usingOldFetchPlugin && message[0] === 83 /* MType.NetworkRequest */) {
294
- return;
295
- }
296
454
  // ====================================================
297
455
  if (this.activityState === ActivityState.ColdStart) {
298
456
  this.bufferedMessages1.push(message);
@@ -325,24 +483,35 @@ export default class App {
325
483
  this.messages.length = 0;
326
484
  return;
327
485
  }
328
- if (this.worker !== undefined && this.messages.length) {
329
- try {
330
- requestIdleCb(() => {
331
- this.messages.unshift(TabData(this.session.getTabId()));
332
- this.messages.unshift(Timestamp(this.timestamp()));
333
- // why I need to add opt chaining?
334
- this.worker?.postMessage(this.messages);
335
- this.commitCallbacks.forEach((cb) => cb(this.messages));
336
- this.messages.length = 0;
337
- });
338
- }
339
- catch (e) {
340
- this._debug('worker_commit', e);
341
- this.stop(true);
342
- setTimeout(() => {
343
- void this.start();
344
- }, 500);
345
- }
486
+ if (this.worker === undefined || !this.messages.length) {
487
+ return;
488
+ }
489
+ if (this.insideIframe) {
490
+ window.parent.postMessage({
491
+ line: proto.iframeBatch,
492
+ messages: this.messages,
493
+ domain: this.initialHostName,
494
+ }, '*');
495
+ this.commitCallbacks.forEach((cb) => cb(this.messages));
496
+ this.messages.length = 0;
497
+ return;
498
+ }
499
+ try {
500
+ requestIdleCb(() => {
501
+ this.messages.unshift(TabData(this.session.getTabId()));
502
+ this.messages.unshift(Timestamp(this.timestamp()));
503
+ // why I need to add opt chaining?
504
+ this.worker?.postMessage(this.messages);
505
+ this.commitCallbacks.forEach((cb) => cb(this.messages));
506
+ this.messages.length = 0;
507
+ });
508
+ }
509
+ catch (e) {
510
+ this._debug('worker_commit', e);
511
+ this.stop(true);
512
+ setTimeout(() => {
513
+ void this.start();
514
+ }, 500);
346
515
  }
347
516
  }
348
517
  /**
@@ -412,8 +581,10 @@ export default class App {
412
581
  if (useSafe) {
413
582
  listener = this.safe(listener);
414
583
  }
415
- this.attachStartCallback(() => (target ? createEventListener(target, type, listener, useCapture) : null), useSafe);
416
- this.attachStopCallback(() => (target ? deleteEventListener(target, type, listener, useCapture) : null), useSafe);
584
+ const createListener = () => target ? createEventListener(target, type, listener, useCapture) : null;
585
+ const deleteListener = () => target ? deleteEventListener(target, type, listener, useCapture) : null;
586
+ this.attachStartCallback(createListener, useSafe);
587
+ this.attachStopCallback(deleteListener, useSafe);
417
588
  }
418
589
  // TODO: full correct semantic
419
590
  checkRequiredVersion(version) {
@@ -517,55 +688,15 @@ export default class App {
517
688
  }
518
689
  /**
519
690
  * start buffering messages without starting the actual session, which gives
520
- * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger
691
+ * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger,
521
692
  * and we will then send buffered batch, so it won't get lost
522
693
  * */
523
694
  async coldStart(startOpts = {}, conditional) {
524
695
  this.singleBuffer = false;
525
696
  const second = 1000;
526
- if (conditional) {
527
- this.conditionsManager = new ConditionsManager(this, startOpts);
528
- }
529
697
  const isNewSession = this.checkSessionToken(startOpts.forceNew);
530
698
  if (conditional) {
531
- const r = await fetch(this.options.ingestPoint + '/v1/web/start', {
532
- method: 'POST',
533
- headers: {
534
- 'Content-Type': 'application/json',
535
- },
536
- body: JSON.stringify({
537
- ...this.getTrackerInfo(),
538
- timestamp: now(),
539
- doNotRecord: true,
540
- bufferDiff: 0,
541
- userID: this.session.getInfo().userID,
542
- token: undefined,
543
- deviceMemory,
544
- jsHeapSizeLimit,
545
- timezone: getTimezone(),
546
- width: window.innerWidth,
547
- height: window.innerHeight,
548
- }),
549
- });
550
- const {
551
- // this token is needed to fetch conditions and flags,
552
- // but it can't be used to record a session
553
- token, userBrowser, userCity, userCountry, userDevice, userOS, userState, projectID, } = await r.json();
554
- this.session.assign({ projectID });
555
- this.session.setUserInfo({
556
- userBrowser,
557
- userCity,
558
- userCountry,
559
- userDevice,
560
- userOS,
561
- userState,
562
- });
563
- const onStartInfo = { sessionToken: token, userUUID: '', sessionID: '' };
564
- this.startCallbacks.forEach((cb) => cb(onStartInfo));
565
- await this.conditionsManager?.fetchConditions(projectID, token);
566
- await this.featureFlags.reloadFlags(token);
567
- await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
568
- this.conditionsManager?.processFlags(this.featureFlags.flags);
699
+ await this.setupConditionalStart(startOpts);
569
700
  }
570
701
  const cycle = () => {
571
702
  this.orderNumber += 1;
@@ -606,6 +737,47 @@ export default class App {
606
737
  }, 30 * second);
607
738
  cycle();
608
739
  }
740
+ async setupConditionalStart(startOpts) {
741
+ this.conditionsManager = new ConditionsManager(this, startOpts);
742
+ const r = await fetch(this.options.ingestPoint + '/v1/web/start', {
743
+ method: 'POST',
744
+ headers: {
745
+ 'Content-Type': 'application/json',
746
+ },
747
+ body: JSON.stringify({
748
+ ...this.getTrackerInfo(),
749
+ timestamp: now(),
750
+ doNotRecord: true,
751
+ bufferDiff: 0,
752
+ userID: this.session.getInfo().userID,
753
+ token: undefined,
754
+ deviceMemory,
755
+ jsHeapSizeLimit,
756
+ timezone: getTimezone(),
757
+ width: window.innerWidth,
758
+ height: window.innerHeight,
759
+ }),
760
+ });
761
+ const {
762
+ // this token is needed to fetch conditions and flags,
763
+ // but it can't be used to record a session
764
+ token, userBrowser, userCity, userCountry, userDevice, userOS, userState, projectID, } = await r.json();
765
+ this.session.assign({ projectID });
766
+ this.session.setUserInfo({
767
+ userBrowser,
768
+ userCity,
769
+ userCountry,
770
+ userDevice,
771
+ userOS,
772
+ userState,
773
+ });
774
+ const onStartInfo = { sessionToken: token, userUUID: '', sessionID: '' };
775
+ this.startCallbacks.forEach((cb) => cb(onStartInfo));
776
+ await this.conditionsManager?.fetchConditions(projectID, token);
777
+ await this.featureFlags.reloadFlags(token);
778
+ await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
779
+ this.conditionsManager?.processFlags(this.featureFlags.flags);
780
+ }
609
781
  /**
610
782
  * Starts offline session recording
611
783
  * @param {Object} startOpts - options for session start, same as .start()
@@ -653,7 +825,7 @@ export default class App {
653
825
  /**
654
826
  * Saves the captured messages in localStorage (or whatever is used in its place)
655
827
  *
656
- * Then when this.offlineRecording is called, it will preload this messages and clear the storage item
828
+ * Then, when this.offlineRecording is called, it will preload this messages and clear the storage item
657
829
  *
658
830
  * Keeping the size of local storage reasonable is up to the end users of this library
659
831
  * */
@@ -730,7 +902,7 @@ export default class App {
730
902
  this.postToWorker([['q_end']]);
731
903
  this.clearBuffers();
732
904
  }
733
- _start(startOpts = {}, resetByWorker = false, conditionName) {
905
+ async _start(startOpts = {}, resetByWorker = false, conditionName) {
734
906
  const isColdStart = this.activityState === ActivityState.ColdStart;
735
907
  if (isColdStart && this.coldInterval) {
736
908
  clearInterval(this.coldInterval);
@@ -776,54 +948,41 @@ export default class App {
776
948
  const isNewSession = this.checkSessionToken(startOpts.forceNew);
777
949
  this.sessionStorage.removeItem(this.options.session_reset_key);
778
950
  this.debug.log('OpenReplay: starting session; need new session id?', isNewSession, 'session token: ', sessionToken);
779
- return window
780
- .fetch(this.options.ingestPoint + '/v1/web/start', {
781
- method: 'POST',
782
- headers: {
783
- 'Content-Type': 'application/json',
784
- },
785
- body: JSON.stringify({
786
- ...this.getTrackerInfo(),
787
- timestamp,
788
- doNotRecord: false,
789
- bufferDiff: timestamp - this.coldStartTs,
790
- userID: this.session.getInfo().userID,
791
- token: isNewSession ? undefined : sessionToken,
792
- deviceMemory,
793
- jsHeapSizeLimit,
794
- timezone: getTimezone(),
795
- condition: conditionName,
796
- assistOnly: startOpts.assistOnly ?? this.socketMode,
797
- }),
798
- })
799
- .then((r) => {
800
- if (r.status === 200) {
801
- return r.json();
802
- }
803
- else {
804
- return r
805
- .text()
806
- .then((text) => text === CANCELED
807
- ? Promise.reject(CANCELED)
808
- : Promise.reject(`Server error: ${r.status}. ${text}`));
951
+ try {
952
+ const r = await window.fetch(this.options.ingestPoint + '/v1/web/start', {
953
+ method: 'POST',
954
+ headers: {
955
+ 'Content-Type': 'application/json',
956
+ },
957
+ body: JSON.stringify({
958
+ ...this.getTrackerInfo(),
959
+ timestamp,
960
+ doNotRecord: false,
961
+ bufferDiff: timestamp - this.coldStartTs,
962
+ userID: this.session.getInfo().userID,
963
+ token: isNewSession ? undefined : sessionToken,
964
+ deviceMemory,
965
+ jsHeapSizeLimit,
966
+ timezone: getTimezone(),
967
+ condition: conditionName,
968
+ assistOnly: startOpts.assistOnly ?? this.socketMode,
969
+ }),
970
+ });
971
+ if (r.status !== 200) {
972
+ const error = await r.text();
973
+ const reason = error === CANCELED ? CANCELED : `Server error: ${r.status}. ${error}`;
974
+ return Promise.reject(reason);
809
975
  }
810
- })
811
- .then(async (r) => {
812
976
  if (!this.worker) {
813
977
  const reason = 'no worker found after start request (this might not happen)';
814
978
  this.signalError(reason, []);
815
979
  return Promise.reject(reason);
816
980
  }
817
- if (this.activityState === ActivityState.NotActive) {
818
- const reason = 'Tracker stopped during authorization';
819
- this.signalError(reason, []);
820
- return Promise.reject(reason);
821
- }
822
981
  const { token, userUUID, projectID, beaconSizeLimit, compressionThreshold, // how big the batch should be before we decide to compress it
823
982
  delay, // derived from token
824
983
  sessionID, // derived from token
825
984
  startTimestamp, // real startTS (server time), derived from sessionID
826
- userBrowser, userCity, userCountry, userDevice, userOS, userState, canvasEnabled, canvasQuality, canvasFPS, assistOnly: socketOnly, } = r;
985
+ userBrowser, userCity, userCountry, userDevice, userOS, userState, canvasEnabled, canvasQuality, canvasFPS, assistOnly: socketOnly, } = await r.json();
827
986
  if (typeof token !== 'string' ||
828
987
  typeof userUUID !== 'string' ||
829
988
  (typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
@@ -901,17 +1060,15 @@ export default class App {
901
1060
  /** --------------- COLD START BUFFER ------------------*/
902
1061
  }
903
1062
  else {
904
- this.observer.observe();
1063
+ if (this.insideIframe && this.rootId) {
1064
+ this.observer.crossdomainObserve(this.rootId, this.frameOderNumber);
1065
+ }
1066
+ else {
1067
+ this.observer.observe();
1068
+ }
905
1069
  this.ticker.start();
906
1070
  }
907
- // get rid of onStart ?
908
- if (typeof this.options.onStart === 'function') {
909
- this.options.onStart(onStartInfo);
910
- }
911
- this.restartAttempts = 0;
912
- this.uxtManager = this.uxtManager
913
- ? this.uxtManager
914
- : new UserTestManager(this, uxtStorageKey);
1071
+ this.uxtManager = this.uxtManager ? this.uxtManager : new UserTestManager(this, uxtStorageKey);
915
1072
  let uxtId;
916
1073
  const savedUxtTag = this.localStorage.getItem(uxtStorageKey);
917
1074
  if (savedUxtTag) {
@@ -939,8 +1096,8 @@ export default class App {
939
1096
  }
940
1097
  }
941
1098
  return SuccessfulStart(onStartInfo);
942
- })
943
- .catch((reason) => {
1099
+ }
1100
+ catch (reason) {
944
1101
  this.stop();
945
1102
  this.session.reset();
946
1103
  if (reason === CANCELED) {
@@ -951,7 +1108,7 @@ export default class App {
951
1108
  const errorMessage = reason instanceof Error ? reason.message : reason.toString();
952
1109
  this.signalError(errorMessage, []);
953
1110
  return UnsuccessfulStart(errorMessage);
954
- });
1111
+ }
955
1112
  }
956
1113
  addOnUxtCb(cb) {
957
1114
  // @ts-ignore
@@ -960,33 +1117,57 @@ export default class App {
960
1117
  getUxtId() {
961
1118
  return this.uxtManager?.getTestId();
962
1119
  }
1120
+ async waitStart() {
1121
+ return new Promise((resolve) => {
1122
+ const check = () => {
1123
+ if (this.canStart) {
1124
+ resolve(true);
1125
+ }
1126
+ else {
1127
+ setTimeout(check, 25);
1128
+ }
1129
+ };
1130
+ check();
1131
+ });
1132
+ }
1133
+ async waitStarted() {
1134
+ return new Promise((resolve) => {
1135
+ const check = () => {
1136
+ if (this.activityState === ActivityState.Active) {
1137
+ resolve(true);
1138
+ }
1139
+ else {
1140
+ setTimeout(check, 25);
1141
+ }
1142
+ };
1143
+ check();
1144
+ });
1145
+ }
963
1146
  /**
964
1147
  * basically we ask other tabs during constructor
965
1148
  * and here we just apply 10ms delay just in case
966
1149
  * */
967
- start(...args) {
1150
+ async start(...args) {
968
1151
  if (this.activityState === ActivityState.Active ||
969
1152
  this.activityState === ActivityState.Starting) {
970
1153
  const reason = 'OpenReplay: trying to call `start()` on the instance that has been started already.';
971
1154
  return Promise.resolve(UnsuccessfulStart(reason));
972
1155
  }
973
1156
  if (!document.hidden) {
974
- return new Promise((resolve) => {
975
- setTimeout(() => {
976
- resolve(this._start(...args));
977
- }, 25);
978
- });
1157
+ await this.waitStart();
1158
+ return this._start(...args);
979
1159
  }
980
1160
  else {
981
1161
  return new Promise((resolve) => {
982
- const onVisibilityChange = () => {
1162
+ const onVisibilityChange = async () => {
983
1163
  if (!document.hidden) {
1164
+ await this.waitStart();
1165
+ // eslint-disable-next-line
984
1166
  document.removeEventListener('visibilitychange', onVisibilityChange);
985
- setTimeout(() => {
986
- resolve(this._start(...args));
987
- }, 25);
1167
+ resolve(this._start(...args));
988
1168
  }
989
1169
  };
1170
+ // eslint-disable-next-line
990
1171
  document.addEventListener('visibilitychange', onVisibilityChange);
991
1172
  });
992
1173
  }