@openreplay/tracker 13.0.2 → 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.
- package/CHANGELOG.md +10 -0
- package/bun.lockb +0 -0
- package/cjs/app/index.d.ts +38 -16
- package/cjs/app/index.js +417 -236
- package/cjs/app/messages.gen.d.ts +6 -3
- package/cjs/app/messages.gen.js +44 -10
- package/cjs/app/nodes.d.ts +2 -0
- package/cjs/app/nodes.js +15 -1
- package/cjs/app/observer/iframe_observer.d.ts +1 -0
- package/cjs/app/observer/iframe_observer.js +9 -0
- package/cjs/app/observer/iframe_offsets.js +0 -1
- package/cjs/app/observer/top_observer.d.ts +1 -0
- package/cjs/app/observer/top_observer.js +14 -0
- package/cjs/common/messages.gen.d.ts +38 -10
- package/cjs/index.d.ts +1 -0
- package/cjs/index.js +17 -8
- package/cjs/modules/conditionsManager.js +2 -2
- package/cjs/modules/mouse.js +14 -1
- package/cjs/modules/scroll.d.ts +1 -1
- package/cjs/modules/scroll.js +9 -4
- package/cjs/modules/viewport.js +2 -2
- package/cjs/utils.d.ts +2 -1
- package/cjs/utils.js +33 -6
- package/lib/app/index.d.ts +38 -16
- package/lib/app/index.js +399 -218
- package/lib/app/messages.gen.d.ts +6 -3
- package/lib/app/messages.gen.js +37 -6
- package/lib/app/nodes.d.ts +2 -0
- package/lib/app/nodes.js +15 -1
- package/lib/app/observer/iframe_observer.d.ts +1 -0
- package/lib/app/observer/iframe_observer.js +9 -0
- package/lib/app/observer/iframe_offsets.js +0 -1
- package/lib/app/observer/top_observer.d.ts +1 -0
- package/lib/app/observer/top_observer.js +14 -0
- package/lib/common/messages.gen.d.ts +38 -10
- package/lib/common/tsconfig.tsbuildinfo +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +18 -9
- package/lib/modules/conditionsManager.js +2 -2
- package/lib/modules/mouse.js +14 -1
- package/lib/modules/scroll.d.ts +1 -1
- package/lib/modules/scroll.js +9 -4
- package/lib/modules/viewport.js +2 -2
- package/lib/utils.d.ts +2 -1
- package/lib/utils.js +31 -5
- 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 {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
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
|
|
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 = '
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
136
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
234
|
+
tries++;
|
|
235
|
+
await delay(100);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
void signalId();
|
|
206
239
|
}
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
this.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
416
|
-
|
|
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
|
-
|
|
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
|
-
|
|
780
|
-
.fetch(this.options.ingestPoint + '/v1/web/start', {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
975
|
-
|
|
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
|
-
|
|
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
|
}
|