@openreplay/tracker 12.0.0-beta.9 → 12.0.0-beta.99

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.
@@ -11,7 +11,7 @@ import type { Options as ObserverOptions } from './observer/top_observer.js';
11
11
  import type { Options as SanitizerOptions } from './sanitizer.js';
12
12
  import type { Options as SessOptions } from './session.js';
13
13
  import type { Options as NetworkOptions } from '../modules/network.js';
14
- import type { Options as WebworkerOptions } from '../common/interaction.js';
14
+ import type { Options as WebworkerOptions, FromWorkerData } from '../common/interaction.js';
15
15
  export interface StartOptions {
16
16
  userID?: string;
17
17
  metadata?: Record<string, string>;
@@ -88,7 +88,7 @@ export default class App {
88
88
  private readonly revID;
89
89
  private activityState;
90
90
  private readonly version;
91
- private readonly worker?;
91
+ private readonly workerManager?;
92
92
  private compressionThreshold;
93
93
  private restartAttempts;
94
94
  private readonly bc;
@@ -98,9 +98,10 @@ export default class App {
98
98
  private uxtManager;
99
99
  private conditionsManager;
100
100
  featureFlags: FeatureFlags;
101
- private tagWatcher;
101
+ private readonly tagWatcher;
102
102
  constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>, signalError: (error: string, apis: string[]) => void);
103
- private _debug;
103
+ handleWorkerMsg(data: FromWorkerData): void;
104
+ private readonly _debug;
104
105
  private _usingOldFetchPlugin;
105
106
  send(message: Message, urgent?: boolean): void;
106
107
  /**
@@ -158,7 +159,7 @@ export default class App {
158
159
  private checkSessionToken;
159
160
  /**
160
161
  * start buffering messages without starting the actual session, which gives
161
- * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger
162
+ * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger,
162
163
  * and we will then send buffered batch, so it won't get lost
163
164
  * */
164
165
  coldStart(startOpts?: StartOptions, conditional?: boolean): Promise<void>;
@@ -176,7 +177,7 @@ export default class App {
176
177
  /**
177
178
  * Saves the captured messages in localStorage (or whatever is used in its place)
178
179
  *
179
- * Then when this.offlineRecording is called, it will preload this messages and clear the storage item
180
+ * Then, when this.offlineRecording is called, it will preload this messages and clear the storage item
180
181
  *
181
182
  * Keeping the size of local storage reasonable is up to the end users of this library
182
183
  * */
@@ -193,7 +194,7 @@ export default class App {
193
194
  * Uploads the stored session buffer to backend
194
195
  * @returns promise that resolves once messages are loaded, it has to be awaited
195
196
  * so the session can be uploaded properly
196
- * @resolve {boolean} - if messages were loaded successfully
197
+ * @resolve - if messages were loaded in service worker successfully
197
198
  * @reject {string} - error message
198
199
  * */
199
200
  uploadOfflineRecording(): Promise<void>;
package/cjs/app/index.js CHANGED
@@ -44,6 +44,7 @@ const attributeSender_js_1 = __importDefault(require("../modules/attributeSender
44
44
  const canvas_js_1 = __importDefault(require("./canvas.js"));
45
45
  const index_js_1 = __importDefault(require("../modules/userTesting/index.js"));
46
46
  const tagWatcher_js_1 = __importDefault(require("../modules/tagWatcher.js"));
47
+ const index_js_2 = __importDefault(require("./workerManager/index.js"));
47
48
  const CANCELED = 'canceled';
48
49
  const uxtStorageKey = 'or_uxt_active';
49
50
  const bufferStorageKey = 'or_buffer_1';
@@ -81,12 +82,26 @@ class App {
81
82
  this.stopCallbacks = [];
82
83
  this.commitCallbacks = [];
83
84
  this.activityState = ActivityState.NotActive;
84
- this.version = '12.0.0-beta.9'; // TODO: version compatability check inside each plugin.
85
+ this.version = '12.0.0-beta.99'; // TODO: version compatability check inside each plugin.
85
86
  this.compressionThreshold = 24 * 1000;
86
87
  this.restartAttempts = 0;
87
88
  this.bc = null;
88
89
  this.canvasRecorder = null;
89
90
  this.conditionsManager = null;
91
+ this._debug = (context, e) => {
92
+ if (this.options.__debug_report_edp !== null) {
93
+ void fetch(this.options.__debug_report_edp, {
94
+ method: 'POST',
95
+ headers: { 'Content-Type': 'application/json' },
96
+ body: JSON.stringify({
97
+ context,
98
+ // @ts-ignore
99
+ error: `${e}`,
100
+ }),
101
+ });
102
+ }
103
+ this.debug.error('OpenReplay error: ', context, e);
104
+ };
90
105
  this._usingOldFetchPlugin = false;
91
106
  this.coldStartCommitN = 0;
92
107
  this.delay = 0;
@@ -175,54 +190,11 @@ class App {
175
190
  this.session.applySessionHash(sessionToken);
176
191
  }
177
192
  try {
178
- this.worker = new Worker(URL.createObjectURL(new Blob(['"use strict";class t{constructor(t,s,i,e=10,n=1e3,h){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,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){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,s){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout((()=>this.sendBatch(t,s)),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,s){this.busy=!0;const i={Authorization:`Bearer ${this.token}`};s&&(i["Content-Encoding"]="gzip"),null!==this.token?fetch(this.ingestURL,{body:t,method:"POST",headers:i,keepalive:t.length<65536}).then((i=>{if(401===i.status)return this.busy=!1,void this.onUnauthorised();i.status>=400?this.retry(t,s):(this.attemptsCount=0,this.sendNext())})).catch((i=>{console.warn("OpenReplay:",i),this.retry(t,s)})):setTimeout((()=>{this.sendBatch(t,s)}),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 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])}}}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,u=null,a=h.NotActive;function o(){u&&u.finaliseBatch()}function c(){a=h.Stopping,null!==g&&(clearInterval(g),g=null),u&&(u.clean(),u=null),r&&(r.clean(),setTimeout((()=>{r=null}),20)),setTimeout((()=>{a=h.NotActive}),100)}function p(){a!==h.Stopped&&(postMessage("restart"),c())}let f,g=null;self.onmessage=({data:s})=>{if(null!=s){if("stop"===s)return o(),c(),a=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?(a=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])})),u=new n(s.pageNo,s.timestamp,s.url,(t=>{r&&r.push(t)}),s.tabId,(()=>postMessage({type:"queue_empty"}))),null===g&&(g=setInterval(o,1e4)),a=h.Active):"auth"===s.type?r?u?(r.authorise(s.token),void(s.beaconSizeLimit&&u.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(u){const t=u;s.forEach((s=>{55===s[0]&&(s[1]?f=setTimeout((()=>p()),18e5):clearTimeout(f)),t.writeMessage(s)}))}else postMessage("not_init"),p()}else o()}else o()};'], { type: 'text/javascript' })));
179
- this.worker.onerror = (e) => {
180
- this._debug('webworker_error', e);
181
- };
182
- this.worker.onmessage = ({ data }) => {
183
- var _a;
184
- if (data === 'restart') {
185
- this.stop(false);
186
- void this.start({}, true);
187
- }
188
- else if (data === 'not_init') {
189
- this.debug.warn('OR WebWorker: writer not initialised. Restarting tracker');
190
- }
191
- else if (data.type === 'failure') {
192
- this.stop(false);
193
- this.debug.error('worker_failed', data.reason);
194
- this._debug('worker_failed', data.reason);
195
- }
196
- else if (data.type === 'compress') {
197
- const batch = data.batch;
198
- const batchSize = batch.byteLength;
199
- if (batchSize > this.compressionThreshold) {
200
- (0, fflate_1.gzip)(data.batch, { mtime: 0 }, (err, result) => {
201
- var _a;
202
- if (err) {
203
- this.debug.error('Openreplay compression error:', err);
204
- this.stop(false);
205
- if (this.restartAttempts < 3) {
206
- this.restartAttempts += 1;
207
- void this.start({}, true);
208
- }
209
- }
210
- else {
211
- (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage({ type: 'compressed', batch: result });
212
- }
213
- });
214
- }
215
- else {
216
- (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage({ type: 'uncompressed', batch: batch });
217
- }
218
- }
219
- else if (data.type === 'queue_empty') {
220
- this.onSessionSent();
221
- }
222
- };
193
+ const webworker = new Worker(URL.createObjectURL(new Blob(['"use strict";const t="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 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,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(i){const s=t.encode(i),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}}class s 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])}}}class e{constructor(t,i,e,n,r,h){this.pageNo=t,this.timestamp=i,this.url=e,this.onBatch=n,this.tabId=r,this.onOfflineEnd=h,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){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 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=n.NotActive;function a(t){postMessage({type:"status",data:t}),h=t}function u(){r&&r.finaliseBatch()}function c(){a(n.Stopping),null!==f&&(clearInterval(f),f=null),r&&(r.clean(),r=null),setTimeout((()=>{a(n.NotActive)}),100)}function o(){h!==n.Stopped&&(postMessage({type:"restart"}),c())}let f=null;self.onmessage=({data:t})=>{if(null!=t){if("writer_finalize"===t.type)return u(),h=n.Stopped;if("reset_writer"!==t.type){if("forceFlushBatch"!==t.type){if("to_writer"===t.type){let i=!1;t.data.forEach((t=>{r?r.writeMessage(t):i||(i=!0,postMessage({type:"not_init"}),o())}))}return"start"===t.type?(h=n.Starting,r=new e(t.pageNo,t.timestamp,t.url,(t=>{postMessage({type:"batch_ready",data:t},[t.buffer])}),t.tabId,(()=>postMessage({type:"queue_empty"}))),null===f&&(f=setInterval(u,1e4)),h=n.Active):"beacon_size_limit"===t.type?r?void(t.beaconSizeLimit&&r.setBeaconSizeLimit(t.beaconSizeLimit)):(console.debug("OR WebWorker: writer not initialised. Received auth."),void o()):void("restart"===t.type&&o())}u()}else c()}else u()};'], { type: 'text/javascript' })));
194
+ this.workerManager = new index_js_2.default(this, webworker, this._debug);
223
195
  const alertWorker = () => {
224
- if (this.worker) {
225
- this.worker.postMessage(null);
196
+ if (this.workerManager) {
197
+ this.workerManager.processMessage(null);
226
198
  }
227
199
  };
228
200
  // keep better tactics, discard others?
@@ -241,7 +213,7 @@ class App {
241
213
  // yes, there are someone out there
242
214
  resp: 'never-gonna-let-you-down',
243
215
  // you stole someone's identity
244
- reg: 'never-gonna-run-around-and-desert-you',
216
+ regen: 'never-gonna-run-around-and-desert-you',
245
217
  };
246
218
  if (this.bc) {
247
219
  this.bc.postMessage({
@@ -259,7 +231,7 @@ class App {
259
231
  const sessionToken = ev.data.token;
260
232
  this.session.setSessionToken(sessionToken);
261
233
  }
262
- if (ev.data.line === proto.reg) {
234
+ if (ev.data.line === proto.regen) {
263
235
  const sessionToken = ev.data.token;
264
236
  this.session.regenerateTabId();
265
237
  this.session.setSessionToken(sessionToken);
@@ -268,7 +240,7 @@ class App {
268
240
  const token = this.session.getSessionToken();
269
241
  if (token && this.bc) {
270
242
  this.bc.postMessage({
271
- line: ev.data.source === thisTab ? proto.reg : proto.resp,
243
+ line: ev.data.source === thisTab ? proto.regen : proto.resp,
272
244
  token,
273
245
  source: thisTab,
274
246
  context: this.contextId,
@@ -278,19 +250,46 @@ class App {
278
250
  };
279
251
  }
280
252
  }
281
- _debug(context, e) {
282
- if (this.options.__debug_report_edp !== null) {
283
- void fetch(this.options.__debug_report_edp, {
284
- method: 'POST',
285
- headers: { 'Content-Type': 'application/json' },
286
- body: JSON.stringify({
287
- context,
288
- // @ts-ignore
289
- error: `${e}`,
290
- }),
291
- });
253
+ handleWorkerMsg(data) {
254
+ var _a;
255
+ if (data.type === 'restart') {
256
+ this.stop(false);
257
+ void this.start({}, true);
258
+ }
259
+ else if (data.type === 'not_init') {
260
+ this.debug.warn('OR WebWorker: writer not initialised; restarting worker');
261
+ }
262
+ else if (data.type === 'failure') {
263
+ this.stop(false);
264
+ this.debug.error('worker_failed', data.reason);
265
+ this._debug('worker_failed', data.reason);
266
+ }
267
+ else if (data.type === 'compress') {
268
+ const batch = data.batch;
269
+ const batchSize = batch.byteLength;
270
+ if (batchSize > this.compressionThreshold) {
271
+ (0, fflate_1.gzip)(data.batch, { mtime: 0 }, (err, result) => {
272
+ var _a;
273
+ if (err) {
274
+ this.debug.error('Openreplay compression error:', err);
275
+ this.stop(false);
276
+ if (this.restartAttempts < 3) {
277
+ this.restartAttempts += 1;
278
+ void this.start({}, true);
279
+ }
280
+ }
281
+ else {
282
+ (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.sendCompressedBatch(result);
283
+ }
284
+ });
285
+ }
286
+ else {
287
+ (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.sendUncompressedBatch(batch);
288
+ }
289
+ }
290
+ else if (data.type === 'queue_empty') {
291
+ this.onSessionSent();
292
292
  }
293
- this.debug.error('OpenReplay error: ', context, e);
294
293
  }
295
294
  send(message, urgent = false) {
296
295
  var _a;
@@ -331,13 +330,13 @@ class App {
331
330
  * every ~30ms
332
331
  * */
333
332
  _nCommit() {
334
- if (this.worker !== undefined && this.messages.length) {
333
+ if (this.workerManager !== undefined && this.messages.length) {
335
334
  (0, utils_js_1.requestIdleCb)(() => {
336
335
  var _a;
337
336
  this.messages.unshift((0, messages_gen_js_2.TabData)(this.session.getTabId()));
338
337
  this.messages.unshift((0, messages_gen_js_2.Timestamp)(this.timestamp()));
339
338
  // why I need to add opt chaining?
340
- (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage(this.messages);
339
+ (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.processMessage({ type: 'batch', data: this.messages });
341
340
  this.commitCallbacks.forEach((cb) => cb(this.messages));
342
341
  this.messages.length = 0;
343
342
  });
@@ -369,7 +368,7 @@ class App {
369
368
  }
370
369
  postToWorker(messages) {
371
370
  var _a;
372
- (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage(messages);
371
+ (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.processMessage({ type: 'batch', data: messages });
373
372
  this.commitCallbacks.forEach((cb) => cb(messages));
374
373
  messages.length = 0;
375
374
  }
@@ -514,7 +513,7 @@ class App {
514
513
  }
515
514
  /**
516
515
  * start buffering messages without starting the actual session, which gives
517
- * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger
516
+ * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger,
518
517
  * and we will then send buffered batch, so it won't get lost
519
518
  * */
520
519
  async coldStart(startOpts = {}, conditional) {
@@ -640,7 +639,7 @@ class App {
640
639
  /**
641
640
  * Saves the captured messages in localStorage (or whatever is used in its place)
642
641
  *
643
- * Then when this.offlineRecording is called, it will preload this messages and clear the storage item
642
+ * Then, when this.offlineRecording is called, it will preload this messages and clear the storage item
644
643
  *
645
644
  * Keeping the size of local storage reasonable is up to the end users of this library
646
645
  * */
@@ -663,14 +662,14 @@ class App {
663
662
  * Uploads the stored session buffer to backend
664
663
  * @returns promise that resolves once messages are loaded, it has to be awaited
665
664
  * so the session can be uploaded properly
666
- * @resolve {boolean} - if messages were loaded successfully
665
+ * @resolve - if messages were loaded in service worker successfully
667
666
  * @reject {string} - error message
668
667
  * */
669
668
  async uploadOfflineRecording() {
670
669
  var _a, _b;
671
670
  this.stop(false);
672
671
  const timestamp = (0, utils_js_1.now)();
673
- (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage({
672
+ (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.processMessage({
674
673
  type: 'start',
675
674
  pageNo: this.session.incPageNo(),
676
675
  ingestPoint: this.options.ingestPoint,
@@ -689,8 +688,7 @@ class App {
689
688
  jsHeapSizeLimit: performance_js_1.jsHeapSizeLimit, timezone: getTimezone() })),
690
689
  });
691
690
  const { token, userBrowser, userCity, userCountry, userDevice, userOS, userState, beaconSizeLimit, projectID, } = await r.json();
692
- (_b = this.worker) === null || _b === void 0 ? void 0 : _b.postMessage({
693
- type: 'auth',
691
+ (_b = this.workerManager) === null || _b === void 0 ? void 0 : _b.authorizeWorker({
694
692
  token,
695
693
  beaconSizeLimit,
696
694
  });
@@ -710,11 +708,12 @@ class App {
710
708
  this.clearBuffers();
711
709
  }
712
710
  _start(startOpts = {}, resetByWorker = false, conditionName) {
711
+ var _a;
713
712
  const isColdStart = this.activityState === ActivityState.ColdStart;
714
713
  if (isColdStart && this.coldInterval) {
715
714
  clearInterval(this.coldInterval);
716
715
  }
717
- if (!this.worker) {
716
+ if (!this.workerManager) {
718
717
  const reason = 'No worker found: perhaps, CSP is not set.';
719
718
  this.signalError(reason, []);
720
719
  return Promise.resolve(UnsuccessfulStart(reason));
@@ -741,7 +740,7 @@ class App {
741
740
  metadata: startOpts.metadata,
742
741
  });
743
742
  const timestamp = (0, utils_js_1.now)();
744
- this.worker.postMessage({
743
+ (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.startWorker({
745
744
  type: 'start',
746
745
  pageNo: this.session.incPageNo(),
747
746
  ingestPoint: this.options.ingestPoint,
@@ -777,8 +776,8 @@ class App {
777
776
  }
778
777
  })
779
778
  .then(async (r) => {
780
- var _a;
781
- if (!this.worker) {
779
+ var _a, _b;
780
+ if (!this.workerManager) {
782
781
  const reason = 'no worker found after start request (this might not happen)';
783
782
  this.signalError(reason, []);
784
783
  return Promise.reject(reason);
@@ -818,8 +817,7 @@ class App {
818
817
  timestamp: startTimestamp || timestamp,
819
818
  projectID,
820
819
  });
821
- this.worker.postMessage({
822
- type: 'auth',
820
+ (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.authorizeWorker({
823
821
  token,
824
822
  beaconSizeLimit,
825
823
  });
@@ -841,7 +839,7 @@ class App {
841
839
  this.activityState = ActivityState.Active;
842
840
  if (canvasEnabled) {
843
841
  this.canvasRecorder =
844
- (_a = this.canvasRecorder) !== null && _a !== void 0 ? _a : new canvas_js_1.default(this, {
842
+ (_b = this.canvasRecorder) !== null && _b !== void 0 ? _b : new canvas_js_1.default(this, {
845
843
  fps: canvasFPS,
846
844
  quality: canvasQuality,
847
845
  isDebug: this.options.__save_canvas_locally,
@@ -948,7 +946,7 @@ class App {
948
946
  }
949
947
  forceFlushBatch() {
950
948
  var _a;
951
- (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage('forceFlushBatch');
949
+ (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.processMessage({ type: 'forceFlushBatch' });
952
950
  }
953
951
  getTabId() {
954
952
  return this.session.getTabId();
@@ -975,7 +973,7 @@ class App {
975
973
  };
976
974
  }
977
975
  stop(stopWorker = true) {
978
- var _a;
976
+ var _a, _b;
979
977
  if (this.activityState !== ActivityState.NotActive) {
980
978
  try {
981
979
  this.attributeSender.clear();
@@ -986,10 +984,10 @@ class App {
986
984
  this.stopCallbacks.forEach((cb) => cb());
987
985
  this.debug.log('OpenReplay tracking stopped.');
988
986
  this.tagWatcher.clear();
989
- if (this.worker && stopWorker) {
990
- this.worker.postMessage('stop');
987
+ if (this.workerManager && stopWorker) {
988
+ (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.stopWorker();
991
989
  }
992
- (_a = this.canvasRecorder) === null || _a === void 0 ? void 0 : _a.clear();
990
+ (_b = this.canvasRecorder) === null || _b === void 0 ? void 0 : _b.clear();
993
991
  }
994
992
  finally {
995
993
  this.activityState = ActivityState.NotActive;
@@ -0,0 +1,23 @@
1
+ export default class QueueSender {
2
+ private readonly onUnauthorised;
3
+ private readonly onFailure;
4
+ private readonly MAX_ATTEMPTS_COUNT;
5
+ private readonly ATTEMPT_TIMEOUT;
6
+ private readonly onCompress?;
7
+ private attemptsCount;
8
+ private busy;
9
+ private readonly queue;
10
+ private readonly ingestURL;
11
+ private token;
12
+ private readonly isCompressing;
13
+ constructor(ingestBaseURL: string, onUnauthorised: () => any, onFailure: (reason: string) => any, MAX_ATTEMPTS_COUNT?: number, ATTEMPT_TIMEOUT?: number, onCompress?: ((batch: Uint8Array) => any) | undefined);
14
+ getQueueStatus(): boolean;
15
+ authorise(token: string): void;
16
+ push(batch: Uint8Array): void;
17
+ private sendNext;
18
+ private retry;
19
+ private sendBatch;
20
+ sendCompressed(batch: Uint8Array): void;
21
+ sendUncompressed(batch: Uint8Array): void;
22
+ clean(): void;
23
+ }
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const INGEST_PATH = '/v1/web/i';
4
+ const KEEPALIVE_SIZE_LIMIT = 64 << 10; // 64 kB
5
+ class QueueSender {
6
+ constructor(ingestBaseURL, onUnauthorised, onFailure, MAX_ATTEMPTS_COUNT = 10, ATTEMPT_TIMEOUT = 1000, onCompress) {
7
+ this.onUnauthorised = onUnauthorised;
8
+ this.onFailure = onFailure;
9
+ this.MAX_ATTEMPTS_COUNT = MAX_ATTEMPTS_COUNT;
10
+ this.ATTEMPT_TIMEOUT = ATTEMPT_TIMEOUT;
11
+ this.onCompress = onCompress;
12
+ this.attemptsCount = 0;
13
+ this.busy = false;
14
+ this.queue = [];
15
+ this.token = null;
16
+ this.ingestURL = ingestBaseURL + INGEST_PATH;
17
+ this.isCompressing = onCompress !== undefined;
18
+ }
19
+ getQueueStatus() {
20
+ return this.queue.length === 0 && !this.busy;
21
+ }
22
+ authorise(token) {
23
+ this.token = token;
24
+ if (!this.busy) {
25
+ // TODO: transparent busy/send logic
26
+ this.sendNext();
27
+ }
28
+ }
29
+ push(batch) {
30
+ if (this.busy || !this.token) {
31
+ this.queue.push(batch);
32
+ }
33
+ else {
34
+ this.busy = true;
35
+ if (this.isCompressing && this.onCompress) {
36
+ this.onCompress(batch);
37
+ }
38
+ else {
39
+ this.sendBatch(batch);
40
+ }
41
+ }
42
+ }
43
+ sendNext() {
44
+ const nextBatch = this.queue.shift();
45
+ if (nextBatch) {
46
+ this.busy = true;
47
+ if (this.isCompressing && this.onCompress) {
48
+ this.onCompress(nextBatch);
49
+ }
50
+ else {
51
+ this.sendBatch(nextBatch);
52
+ }
53
+ }
54
+ else {
55
+ this.busy = false;
56
+ }
57
+ }
58
+ retry(batch, isCompressed) {
59
+ if (this.attemptsCount >= this.MAX_ATTEMPTS_COUNT) {
60
+ this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`);
61
+ // remains this.busy === true
62
+ return;
63
+ }
64
+ this.attemptsCount++;
65
+ setTimeout(() => this.sendBatch(batch, isCompressed), this.ATTEMPT_TIMEOUT * this.attemptsCount);
66
+ }
67
+ // would be nice to use Beacon API, but it is not available in WebWorker
68
+ sendBatch(batch, isCompressed) {
69
+ this.busy = true;
70
+ const headers = {
71
+ Authorization: `Bearer ${this.token}`,
72
+ };
73
+ if (isCompressed) {
74
+ headers['Content-Encoding'] = 'gzip';
75
+ }
76
+ /**
77
+ * sometimes happen during assist connects for some reason
78
+ * */
79
+ if (this.token === null) {
80
+ setTimeout(() => {
81
+ this.sendBatch(batch, isCompressed);
82
+ }, 500);
83
+ return;
84
+ }
85
+ fetch(this.ingestURL, {
86
+ body: batch,
87
+ method: 'POST',
88
+ headers,
89
+ keepalive: batch.length < KEEPALIVE_SIZE_LIMIT,
90
+ })
91
+ .then((r) => {
92
+ if (r.status === 401) {
93
+ // TODO: continuous session ?
94
+ this.busy = false;
95
+ this.onUnauthorised();
96
+ return;
97
+ }
98
+ else if (r.status >= 400) {
99
+ this.retry(batch, isCompressed);
100
+ return;
101
+ }
102
+ // Success
103
+ this.attemptsCount = 0;
104
+ this.sendNext();
105
+ })
106
+ .catch((e) => {
107
+ console.warn('OpenReplay:', e);
108
+ this.retry(batch, isCompressed);
109
+ });
110
+ }
111
+ sendCompressed(batch) {
112
+ this.sendBatch(batch, true);
113
+ }
114
+ sendUncompressed(batch) {
115
+ this.sendBatch(batch, false);
116
+ }
117
+ clean() {
118
+ // sending last batch and closing the shop
119
+ this.sendNext();
120
+ setTimeout(() => {
121
+ this.token = null;
122
+ this.queue.length = 0;
123
+ }, 10);
124
+ }
125
+ }
126
+ exports.default = QueueSender;
@@ -0,0 +1,37 @@
1
+ import { FromWorkerData, ToWorkerData, WorkerAuth, WorkerStart } from '../../common/interaction.js';
2
+ import App from '../index.js';
3
+ import QueueSender from './QueueSender.js';
4
+ declare enum WorkerStatus {
5
+ NotActive = 0,
6
+ Starting = 1,
7
+ Stopping = 2,
8
+ Active = 3,
9
+ Stopped = 4
10
+ }
11
+ interface TypedWorker extends Omit<Worker, 'postMessage'> {
12
+ postMessage(data: ToWorkerData): void;
13
+ }
14
+ declare class WebWorkerManager {
15
+ private readonly app;
16
+ private readonly worker;
17
+ private readonly onError;
18
+ sendIntervalID: ReturnType<typeof setInterval> | null;
19
+ restartTimeoutID: ReturnType<typeof setTimeout> | null;
20
+ workerStatus: WorkerStatus;
21
+ sender: QueueSender | null;
22
+ constructor(app: App, worker: TypedWorker, onError: (ctx: string, e: any) => any);
23
+ finalize: () => void;
24
+ resetWebWorker: () => void;
25
+ resetSender: () => void;
26
+ reset: () => void;
27
+ initiateRestart: () => void;
28
+ initiateFailure: (reason: string) => void;
29
+ processMessage: (data: ToWorkerData | null) => void;
30
+ startWorker: (data: WorkerStart) => void;
31
+ stopWorker: () => void;
32
+ authorizeWorker: (data: WorkerAuth) => void;
33
+ sendCompressedBatch: (data: Uint8Array) => void;
34
+ sendUncompressedBatch: (data: Uint8Array) => void;
35
+ postMessage: (data: FromWorkerData) => void;
36
+ }
37
+ export default WebWorkerManager;