@openreplay/tracker 12.0.0-beta.99 → 12.0.1

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 12.0.1
2
+
3
+ - pause canvas snapshotting when its offscreen
4
+
1
5
  # 12.0.0
2
6
 
3
7
  - offline session recording and manual sending
@@ -13,7 +13,8 @@ declare class CanvasRecorder {
13
13
  constructor(app: App, options: Options);
14
14
  startTracking(): void;
15
15
  restartTracking: () => void;
16
- handleCanvasEl: (node: Node) => void;
16
+ captureCanvas: (node: Node) => void;
17
+ recordCanvas: (node: Node, id: number) => void;
17
18
  sendSnaps(images: {
18
19
  data: string;
19
20
  id: number;
package/cjs/app/canvas.js CHANGED
@@ -10,9 +10,9 @@ class CanvasRecorder {
10
10
  this.intervals = [];
11
11
  this.restartTracking = () => {
12
12
  this.clear();
13
- this.app.nodes.scanTree(this.handleCanvasEl);
13
+ this.app.nodes.scanTree(this.captureCanvas);
14
14
  };
15
- this.handleCanvasEl = (node) => {
15
+ this.captureCanvas = (node) => {
16
16
  const id = this.app.nodes.getID(node);
17
17
  if (!id || !(0, guards_js_1.hasTag)(node, 'canvas')) {
18
18
  return;
@@ -21,10 +21,37 @@ class CanvasRecorder {
21
21
  if (isIgnored || !(0, guards_js_1.hasTag)(node, 'canvas') || this.snapshots[id]) {
22
22
  return;
23
23
  }
24
+ const observer = new IntersectionObserver((entries) => {
25
+ entries.forEach((entry) => {
26
+ if (entry.isIntersecting) {
27
+ if (entry.target) {
28
+ if (this.snapshots[id].createdAt) {
29
+ this.snapshots[id].paused = false;
30
+ }
31
+ else {
32
+ this.recordCanvas(entry.target, id);
33
+ }
34
+ /**
35
+ * We can switch this to start observing when element is in the view
36
+ * but otherwise right now we're just pausing when it's not
37
+ * just to save some bandwidth and space on backend
38
+ * */
39
+ // observer.unobserve(entry.target)
40
+ }
41
+ else {
42
+ this.snapshots[id].paused = true;
43
+ }
44
+ }
45
+ });
46
+ });
47
+ observer.observe(node);
48
+ };
49
+ this.recordCanvas = (node, id) => {
24
50
  const ts = this.app.timestamp();
25
51
  this.snapshots[id] = {
26
52
  images: [],
27
53
  createdAt: ts,
54
+ paused: false,
28
55
  };
29
56
  const canvasMsg = (0, messages_gen_js_1.CanvasNode)(id.toString(), ts);
30
57
  this.app.send(canvasMsg);
@@ -36,11 +63,13 @@ class CanvasRecorder {
36
63
  clearInterval(int);
37
64
  }
38
65
  else {
39
- const snapshot = captureSnapshot(canvas, this.options.quality);
40
- this.snapshots[id].images.push({ id: this.app.timestamp(), data: snapshot });
41
- if (this.snapshots[id].images.length > 9) {
42
- this.sendSnaps(this.snapshots[id].images, id, this.snapshots[id].createdAt);
43
- this.snapshots[id].images = [];
66
+ if (!this.snapshots[id].paused) {
67
+ const snapshot = captureSnapshot(canvas, this.options.quality);
68
+ this.snapshots[id].images.push({ id: this.app.timestamp(), data: snapshot });
69
+ if (this.snapshots[id].images.length > 9) {
70
+ this.sendSnaps(this.snapshots[id].images, id, this.snapshots[id].createdAt);
71
+ this.snapshots[id].images = [];
72
+ }
44
73
  }
45
74
  }
46
75
  }, this.interval);
@@ -50,9 +79,9 @@ class CanvasRecorder {
50
79
  }
51
80
  startTracking() {
52
81
  setTimeout(() => {
53
- this.app.nodes.scanTree(this.handleCanvasEl);
82
+ this.app.nodes.scanTree(this.captureCanvas);
54
83
  this.app.nodes.attachNodeCallback((node) => {
55
- this.handleCanvasEl(node);
84
+ this.captureCanvas(node);
56
85
  });
57
86
  }, 500);
58
87
  }
@@ -101,6 +130,8 @@ function captureSnapshot(canvas, quality = 'medium') {
101
130
  }
102
131
  function dataUrlToBlob(dataUrl) {
103
132
  const [header, base64] = dataUrl.split(',');
133
+ if (!header || !base64)
134
+ return null;
104
135
  const encParts = header.match(/:(.*?);/);
105
136
  if (!encParts)
106
137
  return null;
@@ -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, FromWorkerData } from '../common/interaction.js';
14
+ import type { Options as WebworkerOptions } 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 workerManager?;
91
+ private readonly worker?;
92
92
  private compressionThreshold;
93
93
  private restartAttempts;
94
94
  private readonly bc;
@@ -98,10 +98,9 @@ export default class App {
98
98
  private uxtManager;
99
99
  private conditionsManager;
100
100
  featureFlags: FeatureFlags;
101
- private readonly tagWatcher;
101
+ private tagWatcher;
102
102
  constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>, signalError: (error: string, apis: string[]) => void);
103
- handleWorkerMsg(data: FromWorkerData): void;
104
- private readonly _debug;
103
+ private _debug;
105
104
  private _usingOldFetchPlugin;
106
105
  send(message: Message, urgent?: boolean): void;
107
106
  /**
@@ -159,7 +158,7 @@ export default class App {
159
158
  private checkSessionToken;
160
159
  /**
161
160
  * start buffering messages without starting the actual session, which gives
162
- * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger,
161
+ * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger
163
162
  * and we will then send buffered batch, so it won't get lost
164
163
  * */
165
164
  coldStart(startOpts?: StartOptions, conditional?: boolean): Promise<void>;
@@ -177,7 +176,7 @@ export default class App {
177
176
  /**
178
177
  * Saves the captured messages in localStorage (or whatever is used in its place)
179
178
  *
180
- * Then, when this.offlineRecording is called, it will preload this messages and clear the storage item
179
+ * Then when this.offlineRecording is called, it will preload this messages and clear the storage item
181
180
  *
182
181
  * Keeping the size of local storage reasonable is up to the end users of this library
183
182
  * */
package/cjs/app/index.js CHANGED
@@ -44,7 +44,6 @@ 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"));
48
47
  const CANCELED = 'canceled';
49
48
  const uxtStorageKey = 'or_uxt_active';
50
49
  const bufferStorageKey = 'or_buffer_1';
@@ -82,26 +81,12 @@ class App {
82
81
  this.stopCallbacks = [];
83
82
  this.commitCallbacks = [];
84
83
  this.activityState = ActivityState.NotActive;
85
- this.version = '12.0.0-beta.99'; // TODO: version compatability check inside each plugin.
84
+ this.version = '12.0.1'; // TODO: version compatability check inside each plugin.
86
85
  this.compressionThreshold = 24 * 1000;
87
86
  this.restartAttempts = 0;
88
87
  this.bc = null;
89
88
  this.canvasRecorder = null;
90
89
  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
- };
105
90
  this._usingOldFetchPlugin = false;
106
91
  this.coldStartCommitN = 0;
107
92
  this.delay = 0;
@@ -190,11 +175,54 @@ class App {
190
175
  this.session.applySessionHash(sessionToken);
191
176
  }
192
177
  try {
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);
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
+ };
195
223
  const alertWorker = () => {
196
- if (this.workerManager) {
197
- this.workerManager.processMessage(null);
224
+ if (this.worker) {
225
+ this.worker.postMessage(null);
198
226
  }
199
227
  };
200
228
  // keep better tactics, discard others?
@@ -213,7 +241,7 @@ class App {
213
241
  // yes, there are someone out there
214
242
  resp: 'never-gonna-let-you-down',
215
243
  // you stole someone's identity
216
- regen: 'never-gonna-run-around-and-desert-you',
244
+ reg: 'never-gonna-run-around-and-desert-you',
217
245
  };
218
246
  if (this.bc) {
219
247
  this.bc.postMessage({
@@ -231,7 +259,7 @@ class App {
231
259
  const sessionToken = ev.data.token;
232
260
  this.session.setSessionToken(sessionToken);
233
261
  }
234
- if (ev.data.line === proto.regen) {
262
+ if (ev.data.line === proto.reg) {
235
263
  const sessionToken = ev.data.token;
236
264
  this.session.regenerateTabId();
237
265
  this.session.setSessionToken(sessionToken);
@@ -240,7 +268,7 @@ class App {
240
268
  const token = this.session.getSessionToken();
241
269
  if (token && this.bc) {
242
270
  this.bc.postMessage({
243
- line: ev.data.source === thisTab ? proto.regen : proto.resp,
271
+ line: ev.data.source === thisTab ? proto.reg : proto.resp,
244
272
  token,
245
273
  source: thisTab,
246
274
  context: this.contextId,
@@ -250,46 +278,19 @@ class App {
250
278
  };
251
279
  }
252
280
  }
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();
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
+ });
292
292
  }
293
+ this.debug.error('OpenReplay error: ', context, e);
293
294
  }
294
295
  send(message, urgent = false) {
295
296
  var _a;
@@ -330,13 +331,13 @@ class App {
330
331
  * every ~30ms
331
332
  * */
332
333
  _nCommit() {
333
- if (this.workerManager !== undefined && this.messages.length) {
334
+ if (this.worker !== undefined && this.messages.length) {
334
335
  (0, utils_js_1.requestIdleCb)(() => {
335
336
  var _a;
336
337
  this.messages.unshift((0, messages_gen_js_2.TabData)(this.session.getTabId()));
337
338
  this.messages.unshift((0, messages_gen_js_2.Timestamp)(this.timestamp()));
338
339
  // why I need to add opt chaining?
339
- (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.processMessage({ type: 'batch', data: this.messages });
340
+ (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage(this.messages);
340
341
  this.commitCallbacks.forEach((cb) => cb(this.messages));
341
342
  this.messages.length = 0;
342
343
  });
@@ -368,7 +369,7 @@ class App {
368
369
  }
369
370
  postToWorker(messages) {
370
371
  var _a;
371
- (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.processMessage({ type: 'batch', data: messages });
372
+ (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage(messages);
372
373
  this.commitCallbacks.forEach((cb) => cb(messages));
373
374
  messages.length = 0;
374
375
  }
@@ -513,7 +514,7 @@ class App {
513
514
  }
514
515
  /**
515
516
  * start buffering messages without starting the actual session, which gives
516
- * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger,
517
+ * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger
517
518
  * and we will then send buffered batch, so it won't get lost
518
519
  * */
519
520
  async coldStart(startOpts = {}, conditional) {
@@ -639,7 +640,7 @@ class App {
639
640
  /**
640
641
  * Saves the captured messages in localStorage (or whatever is used in its place)
641
642
  *
642
- * Then, when this.offlineRecording is called, it will preload this messages and clear the storage item
643
+ * Then when this.offlineRecording is called, it will preload this messages and clear the storage item
643
644
  *
644
645
  * Keeping the size of local storage reasonable is up to the end users of this library
645
646
  * */
@@ -669,7 +670,7 @@ class App {
669
670
  var _a, _b;
670
671
  this.stop(false);
671
672
  const timestamp = (0, utils_js_1.now)();
672
- (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.processMessage({
673
+ (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage({
673
674
  type: 'start',
674
675
  pageNo: this.session.incPageNo(),
675
676
  ingestPoint: this.options.ingestPoint,
@@ -688,7 +689,8 @@ class App {
688
689
  jsHeapSizeLimit: performance_js_1.jsHeapSizeLimit, timezone: getTimezone() })),
689
690
  });
690
691
  const { token, userBrowser, userCity, userCountry, userDevice, userOS, userState, beaconSizeLimit, projectID, } = await r.json();
691
- (_b = this.workerManager) === null || _b === void 0 ? void 0 : _b.authorizeWorker({
692
+ (_b = this.worker) === null || _b === void 0 ? void 0 : _b.postMessage({
693
+ type: 'auth',
692
694
  token,
693
695
  beaconSizeLimit,
694
696
  });
@@ -708,12 +710,11 @@ class App {
708
710
  this.clearBuffers();
709
711
  }
710
712
  _start(startOpts = {}, resetByWorker = false, conditionName) {
711
- var _a;
712
713
  const isColdStart = this.activityState === ActivityState.ColdStart;
713
714
  if (isColdStart && this.coldInterval) {
714
715
  clearInterval(this.coldInterval);
715
716
  }
716
- if (!this.workerManager) {
717
+ if (!this.worker) {
717
718
  const reason = 'No worker found: perhaps, CSP is not set.';
718
719
  this.signalError(reason, []);
719
720
  return Promise.resolve(UnsuccessfulStart(reason));
@@ -740,7 +741,7 @@ class App {
740
741
  metadata: startOpts.metadata,
741
742
  });
742
743
  const timestamp = (0, utils_js_1.now)();
743
- (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.startWorker({
744
+ this.worker.postMessage({
744
745
  type: 'start',
745
746
  pageNo: this.session.incPageNo(),
746
747
  ingestPoint: this.options.ingestPoint,
@@ -776,8 +777,8 @@ class App {
776
777
  }
777
778
  })
778
779
  .then(async (r) => {
779
- var _a, _b;
780
- if (!this.workerManager) {
780
+ var _a;
781
+ if (!this.worker) {
781
782
  const reason = 'no worker found after start request (this might not happen)';
782
783
  this.signalError(reason, []);
783
784
  return Promise.reject(reason);
@@ -817,7 +818,8 @@ class App {
817
818
  timestamp: startTimestamp || timestamp,
818
819
  projectID,
819
820
  });
820
- (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.authorizeWorker({
821
+ this.worker.postMessage({
822
+ type: 'auth',
821
823
  token,
822
824
  beaconSizeLimit,
823
825
  });
@@ -839,7 +841,7 @@ class App {
839
841
  this.activityState = ActivityState.Active;
840
842
  if (canvasEnabled) {
841
843
  this.canvasRecorder =
842
- (_b = this.canvasRecorder) !== null && _b !== void 0 ? _b : new canvas_js_1.default(this, {
844
+ (_a = this.canvasRecorder) !== null && _a !== void 0 ? _a : new canvas_js_1.default(this, {
843
845
  fps: canvasFPS,
844
846
  quality: canvasQuality,
845
847
  isDebug: this.options.__save_canvas_locally,
@@ -946,7 +948,7 @@ class App {
946
948
  }
947
949
  forceFlushBatch() {
948
950
  var _a;
949
- (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.processMessage({ type: 'forceFlushBatch' });
951
+ (_a = this.worker) === null || _a === void 0 ? void 0 : _a.postMessage('forceFlushBatch');
950
952
  }
951
953
  getTabId() {
952
954
  return this.session.getTabId();
@@ -973,7 +975,7 @@ class App {
973
975
  };
974
976
  }
975
977
  stop(stopWorker = true) {
976
- var _a, _b;
978
+ var _a;
977
979
  if (this.activityState !== ActivityState.NotActive) {
978
980
  try {
979
981
  this.attributeSender.clear();
@@ -984,10 +986,10 @@ class App {
984
986
  this.stopCallbacks.forEach((cb) => cb());
985
987
  this.debug.log('OpenReplay tracking stopped.');
986
988
  this.tagWatcher.clear();
987
- if (this.workerManager && stopWorker) {
988
- (_a = this.workerManager) === null || _a === void 0 ? void 0 : _a.stopWorker();
989
+ if (this.worker && stopWorker) {
990
+ this.worker.postMessage('stop');
989
991
  }
990
- (_b = this.canvasRecorder) === null || _b === void 0 ? void 0 : _b.clear();
992
+ (_a = this.canvasRecorder) === null || _a === void 0 ? void 0 : _a.clear();
991
993
  }
992
994
  finally {
993
995
  this.activityState = ActivityState.NotActive;
@@ -3,61 +3,7 @@ export interface Options {
3
3
  connAttemptCount?: number;
4
4
  connAttemptGap?: number;
5
5
  }
6
- export type ToWorkerData = null | Stop | Batch | WorkerStart | BeaconSizeLimit | ToWriterData | ForceFlushBatch | CheckQueue | ResetWriter | WriterFinalize;
7
- export type FromWorkerData = Restart | Failure | NotInit | Compress | QEmpty | Status | BatchReady;
8
- type BatchReady = {
9
- type: 'batch_ready';
10
- data: Uint8Array;
11
- };
12
- type Status = {
13
- type: 'status';
14
- data: number;
15
- };
16
- type Compress = {
17
- type: 'compress';
18
- batch: Uint8Array;
19
- };
20
- type Restart = {
21
- type: 'restart';
22
- };
23
- type NotInit = {
24
- type: 'not_init';
25
- };
26
- type Stop = {
27
- type: 'stop';
28
- };
29
- type Batch = {
30
- type: 'batch';
31
- data: Array<Message>;
32
- };
33
- type ForceFlushBatch = {
34
- type: 'forceFlushBatch';
35
- };
36
- type CheckQueue = {
37
- type: 'check_queue';
38
- };
39
- type WriterFinalize = {
40
- type: 'writer_finalize';
41
- };
42
- type ResetWriter = {
43
- type: 'reset_writer';
44
- };
45
- type BeaconSizeLimit = {
46
- type: 'beacon_size_limit';
47
- data: number;
48
- };
49
- type ToWriterData = {
50
- type: 'to_writer';
51
- data: Array<Message>;
52
- };
53
- type Failure = {
54
- type: 'failure';
55
- reason: string;
56
- };
57
- type QEmpty = {
58
- type: 'queue_empty';
59
- };
60
- export type WorkerStart = {
6
+ type Start = {
61
7
  type: 'start';
62
8
  ingestPoint: string;
63
9
  pageNo: number;
@@ -65,8 +11,27 @@ export type WorkerStart = {
65
11
  url: string;
66
12
  tabId: string;
67
13
  } & Options;
68
- export type WorkerAuth = {
14
+ type Auth = {
15
+ type: 'auth';
69
16
  token: string;
70
17
  beaconSizeLimit?: number;
71
18
  };
19
+ export type ToWorkerData = null | 'stop' | Start | Auth | Array<Message> | {
20
+ type: 'compressed';
21
+ batch: Uint8Array;
22
+ } | {
23
+ type: 'uncompressed';
24
+ batch: Uint8Array;
25
+ } | 'forceFlushBatch' | 'check_queue';
26
+ type Failure = {
27
+ type: 'failure';
28
+ reason: string;
29
+ };
30
+ type QEmpty = {
31
+ type: 'queue_empty';
32
+ };
33
+ export type FromWorkerData = 'restart' | Failure | 'not_init' | {
34
+ type: 'compress';
35
+ batch: Uint8Array;
36
+ } | QEmpty;
72
37
  export {};
package/cjs/index.js CHANGED
@@ -98,7 +98,7 @@ class API {
98
98
  const orig = this.options.ingestPoint || index_js_1.DEFAULT_INGEST_POINT;
99
99
  req.open('POST', orig + '/v1/web/not-started');
100
100
  req.send(JSON.stringify({
101
- trackerVersion: '12.0.0-beta.99',
101
+ trackerVersion: '12.0.1',
102
102
  projectKey: this.options.projectKey,
103
103
  doNotTrack,
104
104
  reason,
@@ -13,7 +13,8 @@ declare class CanvasRecorder {
13
13
  constructor(app: App, options: Options);
14
14
  startTracking(): void;
15
15
  restartTracking: () => void;
16
- handleCanvasEl: (node: Node) => void;
16
+ captureCanvas: (node: Node) => void;
17
+ recordCanvas: (node: Node, id: number) => void;
17
18
  sendSnaps(images: {
18
19
  data: string;
19
20
  id: number;