@openreplay/tracker 8.0.0-beta.1 → 8.0.1-beta14
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/cjs/app/index.d.ts +6 -1
- package/cjs/app/index.js +22 -3
- package/cjs/app/observer/observer.js +2 -2
- package/cjs/app/session.d.ts +10 -0
- package/cjs/app/session.js +3 -0
- package/cjs/index.d.ts +11 -0
- package/cjs/index.js +28 -1
- package/cjs/modules/Network/fetchProxy.d.ts +34 -0
- package/cjs/modules/Network/fetchProxy.js +240 -0
- package/cjs/modules/Network/index.d.ts +3 -0
- package/cjs/modules/Network/index.js +9 -0
- package/cjs/modules/Network/networkMessage.d.ts +49 -0
- package/cjs/modules/Network/networkMessage.js +82 -0
- package/cjs/modules/Network/types.d.ts +13 -0
- package/cjs/modules/Network/types.js +3 -0
- package/cjs/modules/Network/utils.d.ts +11 -0
- package/cjs/modules/Network/utils.js +213 -0
- package/cjs/modules/Network/xhrProxy.d.ts +47 -0
- package/cjs/modules/Network/xhrProxy.js +209 -0
- package/cjs/modules/attributeSender.d.ts +14 -0
- package/cjs/modules/attributeSender.js +44 -0
- package/cjs/modules/console.js +21 -13
- package/cjs/modules/featureFlags.d.ts +25 -0
- package/cjs/modules/featureFlags.js +100 -0
- package/cjs/modules/img.js +4 -4
- package/cjs/modules/network.d.ts +3 -4
- package/cjs/modules/network.js +13 -3
- package/coverage/clover.xml +1412 -900
- package/coverage/coverage-final.json +22 -16
- package/coverage/lcov-report/index.html +58 -43
- package/coverage/lcov-report/main/app/guards.ts.html +42 -42
- package/coverage/lcov-report/main/app/index.html +46 -46
- package/coverage/lcov-report/main/app/index.ts.html +104 -8
- package/coverage/lcov-report/main/app/logger.ts.html +1 -1
- package/coverage/lcov-report/main/app/messages.gen.ts.html +146 -146
- package/coverage/lcov-report/main/app/observer/iframe_observer.ts.html +1 -1
- package/coverage/lcov-report/main/app/observer/iframe_offsets.ts.html +1 -1
- package/coverage/lcov-report/main/app/observer/index.html +1 -1
- package/coverage/lcov-report/main/app/observer/shadow_root_observer.ts.html +1 -1
- package/coverage/lcov-report/main/app/observer/top_observer.ts.html +1 -1
- package/coverage/lcov-report/main/app/sanitizer.ts.html +98 -98
- package/coverage/lcov-report/main/app/session.ts.html +47 -5
- package/coverage/lcov-report/main/app/ticker.ts.html +1 -1
- package/coverage/lcov-report/main/index.html +24 -24
- package/coverage/lcov-report/main/index.ts.html +138 -6
- package/coverage/lcov-report/main/modules/Network/fetchProxy.ts.html +949 -0
- package/coverage/lcov-report/main/{vendors/finder → modules/Network}/index.html +72 -12
- package/coverage/lcov-report/main/modules/Network/index.ts.html +169 -0
- package/coverage/lcov-report/main/{app/nodes.ts.html → modules/Network/networkMessage.ts.html} +130 -115
- package/coverage/lcov-report/main/modules/Network/utils.ts.html +700 -0
- package/coverage/lcov-report/main/modules/Network/xhrProxy.ts.html +823 -0
- package/coverage/lcov-report/main/modules/attributeSender.ts.html +217 -0
- package/coverage/lcov-report/main/modules/axiosSpy.ts.html +1 -1
- package/coverage/lcov-report/main/modules/connection.ts.html +1 -1
- package/coverage/lcov-report/main/modules/console.ts.html +174 -147
- package/coverage/lcov-report/main/modules/constructedStyleSheets.ts.html +1 -1
- package/coverage/lcov-report/main/modules/cssrules.ts.html +1 -1
- package/coverage/lcov-report/main/modules/exception.ts.html +1 -1
- package/coverage/lcov-report/main/modules/featureFlags.ts.html +415 -0
- package/coverage/lcov-report/main/modules/focus.ts.html +1 -1
- package/coverage/lcov-report/main/modules/fonts.ts.html +1 -1
- package/coverage/lcov-report/main/modules/img.ts.html +6 -6
- package/coverage/lcov-report/main/modules/index.html +54 -24
- package/coverage/lcov-report/main/modules/input.ts.html +1 -1
- package/coverage/lcov-report/main/modules/mouse.ts.html +1 -1
- package/coverage/lcov-report/main/modules/network.ts.html +70 -70
- package/coverage/lcov-report/main/modules/performance.ts.html +1 -1
- package/coverage/lcov-report/main/modules/scroll.ts.html +1 -1
- package/coverage/lcov-report/main/modules/selection.ts.html +1 -1
- package/coverage/lcov-report/main/modules/tabs.ts.html +1 -1
- package/coverage/lcov-report/main/modules/timing.ts.html +1 -1
- package/coverage/lcov-report/main/modules/viewport.ts.html +1 -1
- package/coverage/lcov-report/main/utils.ts.html +97 -97
- package/coverage/lcov-report/webworker/BatchWriter.ts.html +125 -176
- package/coverage/lcov-report/webworker/MessageEncoder.gen.ts.html +88 -88
- package/coverage/lcov-report/webworker/PrimitiveEncoder.ts.html +110 -110
- package/coverage/lcov-report/webworker/QueueSender.ts.html +140 -110
- package/coverage/lcov-report/webworker/index.html +56 -71
- package/coverage/lcov-report/webworker/index.ts.html +34 -10
- package/coverage/lcov.info +2524 -1552
- package/lib/app/index.d.ts +6 -1
- package/lib/app/index.js +22 -3
- package/lib/app/observer/observer.js +3 -3
- package/lib/app/session.d.ts +10 -0
- package/lib/app/session.js +3 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +28 -1
- package/lib/modules/Network/fetchProxy.d.ts +34 -0
- package/lib/modules/Network/fetchProxy.js +234 -0
- package/lib/modules/Network/index.d.ts +3 -0
- package/lib/modules/Network/index.js +6 -0
- package/lib/modules/Network/networkMessage.d.ts +49 -0
- package/lib/modules/Network/networkMessage.js +78 -0
- package/lib/modules/Network/types.d.ts +13 -0
- package/lib/modules/Network/types.js +2 -0
- package/lib/modules/Network/utils.d.ts +11 -0
- package/lib/modules/Network/utils.js +201 -0
- package/lib/modules/Network/xhrProxy.d.ts +47 -0
- package/lib/modules/Network/xhrProxy.js +204 -0
- package/lib/modules/attributeSender.d.ts +14 -0
- package/lib/modules/attributeSender.js +39 -0
- package/lib/modules/console.js +21 -13
- package/lib/modules/featureFlags.d.ts +25 -0
- package/lib/modules/featureFlags.js +97 -0
- package/lib/modules/img.js +5 -5
- package/lib/modules/network.d.ts +3 -4
- package/lib/modules/network.js +13 -3
- package/package.json +3 -2
- package/coverage/lcov-report/main/app/observer/observer.ts.html +0 -1282
- package/coverage/lcov-report/main/vendors/finder/finder.ts.html +0 -1381
- package/coverage/lcov-report/webworker/StringDictionary.ts.html +0 -124
package/lib/app/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import Sanitizer from './sanitizer.js';
|
|
|
5
5
|
import Ticker from './ticker.js';
|
|
6
6
|
import Logger from './logger.js';
|
|
7
7
|
import Session from './session.js';
|
|
8
|
+
import AttributeSender from '../modules/attributeSender.js';
|
|
8
9
|
import type { Options as ObserverOptions } from './observer/top_observer.js';
|
|
9
10
|
import type { Options as SanitizerOptions } from './sanitizer.js';
|
|
10
11
|
import type { Options as LoggerOptions } from './logger.js';
|
|
@@ -72,15 +73,17 @@ export default class App {
|
|
|
72
73
|
private readonly startCallbacks;
|
|
73
74
|
private readonly stopCallbacks;
|
|
74
75
|
private readonly commitCallbacks;
|
|
75
|
-
|
|
76
|
+
readonly options: AppOptions;
|
|
76
77
|
readonly networkOptions?: NetworkOptions;
|
|
77
78
|
private readonly revID;
|
|
78
79
|
private activityState;
|
|
79
80
|
private readonly version;
|
|
80
81
|
private readonly worker?;
|
|
82
|
+
private featureFlags;
|
|
81
83
|
private compressionThreshold;
|
|
82
84
|
private restartAttempts;
|
|
83
85
|
private readonly bc;
|
|
86
|
+
attributeSender: AttributeSender;
|
|
84
87
|
constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>);
|
|
85
88
|
private _debug;
|
|
86
89
|
private _usingOldFetchPlugin;
|
|
@@ -118,6 +121,8 @@ export default class App {
|
|
|
118
121
|
resolveResourceURL(resourceURL: string): string;
|
|
119
122
|
isServiceURL(url: string): boolean;
|
|
120
123
|
active(): boolean;
|
|
124
|
+
isFeatureActive(feature: string): boolean;
|
|
125
|
+
getFeatureFlags(): string[];
|
|
121
126
|
resetNextPageSession(flag: boolean): void;
|
|
122
127
|
private _start;
|
|
123
128
|
/**
|
package/lib/app/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import Logger, { LogLevel } from './logger.js';
|
|
|
8
8
|
import Session from './session.js';
|
|
9
9
|
import { gzip } from 'fflate';
|
|
10
10
|
import { deviceMemory, jsHeapSizeLimit } from '../modules/performance.js';
|
|
11
|
+
import AttributeSender from '../modules/attributeSender.js';
|
|
11
12
|
const CANCELED = 'canceled';
|
|
12
13
|
const START_ERROR = ':(';
|
|
13
14
|
const UnsuccessfulStart = (reason) => ({ reason, success: false });
|
|
@@ -31,7 +32,8 @@ export default class App {
|
|
|
31
32
|
this.stopCallbacks = [];
|
|
32
33
|
this.commitCallbacks = [];
|
|
33
34
|
this.activityState = ActivityState.NotActive;
|
|
34
|
-
this.version = '8.0.
|
|
35
|
+
this.version = '8.0.1-beta14'; // TODO: version compatability check inside each plugin.
|
|
36
|
+
this.featureFlags = [];
|
|
35
37
|
this.compressionThreshold = 24 * 1000;
|
|
36
38
|
this.restartAttempts = 0;
|
|
37
39
|
this.bc = new BroadcastChannel('rick');
|
|
@@ -67,6 +69,7 @@ export default class App {
|
|
|
67
69
|
this.debug = new Logger(this.options.__debug__);
|
|
68
70
|
this.notify = new Logger(this.options.verbose ? LogLevel.Warnings : LogLevel.Silent);
|
|
69
71
|
this.session = new Session(this, this.options);
|
|
72
|
+
this.attributeSender = new AttributeSender(this);
|
|
70
73
|
this.session.attachUpdateCallback(({ userID, metadata }) => {
|
|
71
74
|
if (userID != null) {
|
|
72
75
|
// TODO: nullable userID
|
|
@@ -81,7 +84,7 @@ export default class App {
|
|
|
81
84
|
this.session.applySessionHash(sessionToken);
|
|
82
85
|
}
|
|
83
86
|
try {
|
|
84
|
-
this.worker = new Worker(URL.createObjectURL(new Blob(['"use strict";class t{constructor(t,i,s,e=10,n=1e3,h){this.onUnauthorised=i,this.onFailure=s,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}authorise(t){this.token=t,this.busy||this.sendNext()}push(t){this.busy||!this.token?this.queue.push(t):(this.busy=!0,this.isCompressing&&this.onCompress?this.onCompress(t):this.sendBatch(t))}sendNext(){const t=this.queue.shift();t?(this.busy=!0,this.isCompressing&&this.onCompress?this.onCompress(t):this.sendBatch(t)):this.busy=!1}retry(t,i){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout(()=>this.sendBatch(t,i),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,i){this.busy=!0;const s={Authorization:"Bearer "+this.token};i&&(s["Content-Encoding"]="gzip"),null!==this.token?fetch(this.ingestURL,{body:t,method:"POST",headers:s,keepalive:t.length<65536}).then(s=>{if(401===s.status)return this.busy=!1,void this.onUnauthorised();s.status>=400?this.retry(t,i):(this.attemptsCount=0,this.sendNext())}).catch(s=>{console.warn("OpenReplay:",s),this.retry(t,i)}):setTimeout(()=>{this.sendBatch(t,i)},500)}sendCompressed(t){this.sendBatch(t,!0)}sendUncompressed(t){this.sendBatch(t,!1)}clean(){this.sendNext(),setTimeout(()=>{this.token=null,this.queue.length=0},10)}}const i="function"==typeof TextEncoder?new TextEncoder:{encode(t){const i=t.length,s=new Uint8Array(3*i);let e=-1;for(let n=0,h=0,r=0;r!==i;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===i){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){s[e+=1]=240|n>>>18,s[e+=1]=128|n>>>12&63,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n;continue}}n<=127?s[e+=1]=0|n:n<=2047?(s[e+=1]=192|n>>>6,s[e+=1]=128|63&n):(s[e+=1]=224|n>>>12,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n)}return s.subarray(0,e+1)}};class s extends class{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,i){this.data.set(t,i)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=i.encode(t),e=s.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(s,this.offset),this.offset+=e,!0)}reset(){this.offset=0,this.checkpointOffset=0}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}{encode(t){switch(t[0]){case 0:return this.uint(t[1]);case 4:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 11:return this.uint(t[1]);case 12:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 17:return this.uint(t[1])&&this.string(t[2]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 20:return this.uint(t[1])&&this.uint(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 24:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 27:return this.string(t[1])&&this.string(t[2]);case 28:case 29:return this.string(t[1]);case 30:return this.string(t[1])&&this.string(t[2]);case 37:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3]);case 38:return this.uint(t[1])&&this.uint(t[2]);case 39:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 41:return this.string(t[1])&&this.string(t[2]);case 42:return this.string(t[1]);case 44:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 45:case 46:return this.string(t[1])&&this.string(t[2]);case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 50:return this.uint(t[1])&&this.string(t[2]);case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 54:return this.uint(t[1])&&this.string(t[2]);case 55:return this.boolean(t[1]);case 57:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:return this.int(t[1]);case 59:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6])&&this.string(t[7]);case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 61:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 63:case 64:return this.string(t[1])&&this.string(t[2]);case 67:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 70:return this.uint(t[1])&&this.uint(t[2]);case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 79:return this.string(t[1])&&this.string(t[2]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 82:return this.uint(t[1])&&this.uint(t[2]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 114:case 115:return this.uint(t[1]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 117:case 118:return this.string(t[1])}}}class e{constructor(){this.idx=1,this.backDict={}}getKey(t){let i=!1;return this.backDict[t]||(i=!0,this.backDict[t]=this.idx++),[this.backDict[t],i]}}class n{constructor(t,i,n,h,r){this.pageNo=t,this.timestamp=i,this.url=n,this.onBatch=h,this.tabId=r,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new s(this.beaconSize),this.strDict=new e,this.sizeBuffer=new Uint8Array(3),this.isEmpty=!0,this.beaconSizeLimit=1e6,this.prepare()}writeType(t){return this.encoder.uint(t[0])}writeFields(t){return this.encoder.encode(t)}writeSizeAt(t,i){for(let i=0;i<3;i++)this.sizeBuffer[i]=t>>8*i;this.encoder.set(this.sizeBuffer,i)}prepare(){if(!this.encoder.isEmpty)return;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url],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}applyDict(t){const[i,s]=this.strDict.getKey(t);return s&&this.writeMessage([50,i,t]),i}writeMessage(t){0===t[0]&&(this.timestamp=t[1]),4===t[0]&&(this.url=t[1]),12===t[0]&&(t=[51,t[1],this.applyDict(t[2]),this.applyDict(t[3])]),this.writeWithSize(t)||(this.finaliseBatch(),this.writeWithSize(t)||(this.encoder=new s(this.beaconSizeLimit),this.prepare(),this.writeWithSize(t)?this.finaliseBatch():console.warn("OpenReplay: beacon size overflow. Skipping large message.",t,this),this.encoder=new s(this.beaconSize),this.prepare()))}finaliseBatch(){if(this.isEmpty)return;const t=this.encoder.flush();this.onBatch(t),this.prepare()}clean(){this.encoder.reset()}}var 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!==l&&(clearInterval(l),l=null),u&&(u.clean(),u=null),r&&(r.clean(),setTimeout(()=>{r=null},20)),setTimeout(()=>{a=h.NotActive},100)}function g(){a!==h.Stopped&&(postMessage("restart"),c())}let p,l=null;self.onmessage=({data:i})=>{if(null!=i){if("stop"===i)return o(),c(),a=h.Stopped;if(!Array.isArray(i)){if("compressed"===i.type){if(!r)return console.debug("WebWorker: sender not initialised. Compressed batch."),void g();r.sendCompressed(i.batch)}if("uncompressed"===i.type){if(!r)return console.debug("WebWorker: sender not initialised. Uncompressed batch."),void g();r.sendUncompressed(i.batch)}return"start"===i.type?(a=h.Starting,r=new t(i.ingestPoint,()=>{g()},t=>{!function(t){postMessage({type:"failure",reason:t}),c()}(t)},i.connAttemptCount,i.connAttemptGap,t=>{postMessage({type:"compress",batch:t},[t.buffer])}),u=new n(i.pageNo,i.timestamp,i.url,t=>r&&r.push(t),i.tabId),null===l&&(l=setInterval(o,1e4)),a=h.Active):"auth"===i.type?r?u?(r.authorise(i.token),void(i.beaconSizeLimit&&u.setBeaconSizeLimit(i.beaconSizeLimit))):(console.debug("WebWorker: writer not initialised. Received auth."),void g()):(console.debug("WebWorker: sender not initialised. Received auth."),void g()):void 0}if(null!==u){const t=u;i.forEach(i=>{55===i[0]&&(i[1]?p=setTimeout(()=>g(),18e5):clearTimeout(p)),t.writeMessage(i)})}u||(postMessage("not_init"),g())}else o()};'], { type: 'text/javascript' })));
|
|
87
|
+
this.worker = new Worker(URL.createObjectURL(new Blob(['"use strict";class t{constructor(t,i,s,e=10,n=1e3,r){this.onUnauthorised=i,this.onFailure=s,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.ingestURL=t+"/v1/web/i",this.isCompressing=void 0!==r}authorise(t){this.token=t,this.busy||this.sendNext()}push(t){this.busy||!this.token?this.queue.push(t):(this.busy=!0,this.isCompressing&&this.onCompress?this.onCompress(t):this.sendBatch(t))}sendNext(){const t=this.queue.shift();t?(this.busy=!0,this.isCompressing&&this.onCompress?this.onCompress(t):this.sendBatch(t)):this.busy=!1}retry(t,i){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout(()=>this.sendBatch(t,i),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,i){this.busy=!0;const s={Authorization:"Bearer "+this.token};i&&(s["Content-Encoding"]="gzip"),null!==this.token?fetch(this.ingestURL,{body:t,method:"POST",headers:s,keepalive:t.length<65536}).then(s=>{if(401===s.status)return this.busy=!1,void this.onUnauthorised();s.status>=400?this.retry(t,i):(this.attemptsCount=0,this.sendNext())}).catch(s=>{console.warn("OpenReplay:",s),this.retry(t,i)}):setTimeout(()=>{this.sendBatch(t,i)},500)}sendCompressed(t){this.sendBatch(t,!0)}sendUncompressed(t){this.sendBatch(t,!1)}clean(){this.sendNext(),setTimeout(()=>{this.token=null,this.queue.length=0},10)}}const i="function"==typeof TextEncoder?new TextEncoder:{encode(t){const i=t.length,s=new Uint8Array(3*i);let e=-1;for(let n=0,r=0,h=0;h!==i;){if(n=t.charCodeAt(h),h+=1,n>=55296&&n<=56319){if(h===i){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;break}if(r=t.charCodeAt(h),!(r>=56320&&r<=57343)){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;continue}if(n=1024*(n-55296)+r-56320+65536,h+=1,n>65535){s[e+=1]=240|n>>>18,s[e+=1]=128|n>>>12&63,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n;continue}}n<=127?s[e+=1]=0|n:n<=2047?(s[e+=1]=192|n>>>6,s[e+=1]=128|63&n):(s[e+=1]=224|n>>>12,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n)}return s.subarray(0,e+1)}};class s extends class{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,i){this.data.set(t,i)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=i.encode(t),e=s.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(s,this.offset),this.offset+=e,!0)}reset(){this.offset=0,this.checkpointOffset=0}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}{encode(t){switch(t[0]){case 0:return this.uint(t[1]);case 4:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 11:return this.uint(t[1]);case 12:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 17:return this.uint(t[1])&&this.string(t[2]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 20:return this.uint(t[1])&&this.uint(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 24:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 27:return this.string(t[1])&&this.string(t[2]);case 28:case 29:return this.string(t[1]);case 30:return this.string(t[1])&&this.string(t[2]);case 37:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3]);case 38:return this.uint(t[1])&&this.uint(t[2]);case 39:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 41:return this.string(t[1])&&this.string(t[2]);case 42:return this.string(t[1]);case 44:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 45:case 46:return this.string(t[1])&&this.string(t[2]);case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 50:return this.uint(t[1])&&this.string(t[2]);case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 54:return this.uint(t[1])&&this.string(t[2]);case 55:return this.boolean(t[1]);case 57:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:return this.int(t[1]);case 59:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6])&&this.string(t[7]);case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 61:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 63:case 64:return this.string(t[1])&&this.string(t[2]);case 67:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 70:return this.uint(t[1])&&this.uint(t[2]);case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 79:return this.string(t[1])&&this.string(t[2]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 82:return this.uint(t[1])&&this.uint(t[2]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 114:case 115:return this.uint(t[1]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 117:case 118:return this.string(t[1])}}}class e{constructor(t,i,e,n,r){this.pageNo=t,this.timestamp=i,this.url=e,this.onBatch=n,this.tabId=r,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new s(this.beaconSize),this.sizeBuffer=new Uint8Array(3),this.isEmpty=!0,this.beaconSizeLimit=1e6,this.prepare()}writeType(t){return this.encoder.uint(t[0])}writeFields(t){return this.encoder.encode(t)}writeSizeAt(t,i){for(let i=0;i<3;i++)this.sizeBuffer[i]=t>>8*i;this.encoder.set(this.sizeBuffer,i)}prepare(){if(!this.encoder.isEmpty)return;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url],i=[118,this.tabId];this.writeType(t),this.writeFields(t),this.writeWithSize(i),this.isEmpty=!0}writeWithSize(t){const i=this.encoder;if(!this.writeType(t)||!i.skip(3))return!1;const s=i.getCurrentOffset(),e=this.writeFields(t);if(e){const e=i.getCurrentOffset()-s;if(e>16777215)return console.warn("OpenReplay: max message size overflow."),!1;this.writeSizeAt(e,s-3),i.checkpoint(),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}writeMessage(t){0===t[0]&&(this.timestamp=t[1]),4===t[0]&&(this.url=t[1]),this.writeWithSize(t)||(this.finaliseBatch(),this.writeWithSize(t)||(this.encoder=new s(this.beaconSizeLimit),this.prepare(),this.writeWithSize(t)?this.finaliseBatch():console.warn("OpenReplay: beacon size overflow. Skipping large message.",t,this),this.encoder=new s(this.beaconSize),this.prepare()))}finaliseBatch(){if(this.isEmpty)return;const t=this.encoder.flush();this.onBatch(t),this.prepare()}clean(){this.encoder.reset()}}var n;!function(t){t[t.NotActive=0]="NotActive",t[t.Starting=1]="Starting",t[t.Stopping=2]="Stopping",t[t.Active=3]="Active",t[t.Stopped=4]="Stopped"}(n||(n={}));let r=null,h=null,u=n.NotActive;function a(){h&&h.finaliseBatch()}function o(){u=n.Stopping,null!==p&&(clearInterval(p),p=null),h&&(h.clean(),h=null),r&&(r.clean(),setTimeout(()=>{r=null},20)),setTimeout(()=>{u=n.NotActive},100)}function c(){u!==n.Stopped&&(postMessage("restart"),o())}let g,p=null;self.onmessage=({data:i})=>{if(null!=i){if("stop"===i)return a(),o(),u=n.Stopped;if(!Array.isArray(i)){if("compressed"===i.type){if(!r)return console.debug("WebWorker: sender not initialised. Compressed batch."),void c();r.sendCompressed(i.batch)}if("uncompressed"===i.type){if(!r)return console.debug("WebWorker: sender not initialised. Uncompressed batch."),void c();r.sendUncompressed(i.batch)}return"start"===i.type?(u=n.Starting,r=new t(i.ingestPoint,()=>{c()},t=>{!function(t){postMessage({type:"failure",reason:t}),o()}(t)},i.connAttemptCount,i.connAttemptGap,t=>{postMessage({type:"compress",batch:t},[t.buffer])}),h=new e(i.pageNo,i.timestamp,i.url,t=>r&&r.push(t),i.tabId),null===p&&(p=setInterval(a,1e4)),u=n.Active):"auth"===i.type?r?h?(r.authorise(i.token),void(i.beaconSizeLimit&&h.setBeaconSizeLimit(i.beaconSizeLimit))):(console.debug("WebWorker: writer not initialised. Received auth."),void c()):(console.debug("WebWorker: sender not initialised. Received auth."),void c()):void 0}if(null!==h){const t=h;i.forEach(i=>{55===i[0]&&(i[1]?g=setTimeout(()=>c(),18e5):clearTimeout(g)),t.writeMessage(i)})}h||(postMessage("not_init"),c())}else a()};'], { type: 'text/javascript' })));
|
|
85
88
|
this.worker.onerror = (e) => {
|
|
86
89
|
this._debug('webworker_error', e);
|
|
87
90
|
};
|
|
@@ -330,6 +333,12 @@ export default class App {
|
|
|
330
333
|
active() {
|
|
331
334
|
return this.activityState === ActivityState.Active;
|
|
332
335
|
}
|
|
336
|
+
isFeatureActive(feature) {
|
|
337
|
+
return this.featureFlags.includes(feature);
|
|
338
|
+
}
|
|
339
|
+
getFeatureFlags() {
|
|
340
|
+
return this.featureFlags;
|
|
341
|
+
}
|
|
333
342
|
resetNextPageSession(flag) {
|
|
334
343
|
if (flag) {
|
|
335
344
|
this.sessionStorage.setItem(this.options.session_reset_key, 't');
|
|
@@ -408,7 +417,8 @@ export default class App {
|
|
|
408
417
|
delay, // derived from token
|
|
409
418
|
sessionID, // derived from token
|
|
410
419
|
startTimestamp, // real startTS (server time), derived from sessionID
|
|
411
|
-
|
|
420
|
+
userBrowser, userCity, userCountry, userDevice, userOS, userState, } = r;
|
|
421
|
+
// TODO: insert feature flags here
|
|
412
422
|
if (typeof token !== 'string' ||
|
|
413
423
|
typeof userUUID !== 'string' ||
|
|
414
424
|
(typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
|
|
@@ -419,6 +429,14 @@ export default class App {
|
|
|
419
429
|
}
|
|
420
430
|
this.delay = delay;
|
|
421
431
|
this.session.setSessionToken(token);
|
|
432
|
+
this.session.setUserInfo({
|
|
433
|
+
userBrowser,
|
|
434
|
+
userCity,
|
|
435
|
+
userCountry,
|
|
436
|
+
userDevice,
|
|
437
|
+
userOS,
|
|
438
|
+
userState,
|
|
439
|
+
});
|
|
422
440
|
this.session.assign({
|
|
423
441
|
sessionID,
|
|
424
442
|
timestamp: startTimestamp || timestamp,
|
|
@@ -495,6 +513,7 @@ export default class App {
|
|
|
495
513
|
stop(stopWorker = true) {
|
|
496
514
|
if (this.activityState !== ActivityState.NotActive) {
|
|
497
515
|
try {
|
|
516
|
+
this.attributeSender.clear();
|
|
498
517
|
this.sanitizer.clear();
|
|
499
518
|
this.observer.disconnect();
|
|
500
519
|
this.nodes.clear();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RemoveNodeAttribute,
|
|
1
|
+
import { RemoveNodeAttribute, SetNodeAttributeURLBased, SetCSSDataURLBased, SetNodeData, CreateTextNode, CreateElementNode, MoveNode, RemoveNode, UnbindNodes, } from '../messages.gen.js';
|
|
2
2
|
import { isRootNode, isTextNode, isElementNode, isSVGElement, hasTag, isCommentNode, } from '../guards.js';
|
|
3
3
|
function isIgnored(node) {
|
|
4
4
|
if (isCommentNode(node)) {
|
|
@@ -113,7 +113,7 @@ export default class Observer {
|
|
|
113
113
|
this.app.send(SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
|
114
114
|
}
|
|
115
115
|
else {
|
|
116
|
-
this.app.
|
|
116
|
+
this.app.attributeSender.sendSetAttribute(id, name, value);
|
|
117
117
|
}
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
@@ -143,7 +143,7 @@ export default class Observer {
|
|
|
143
143
|
if (name === 'href' || value.length > 1e5) {
|
|
144
144
|
value = '';
|
|
145
145
|
}
|
|
146
|
-
this.app.
|
|
146
|
+
this.app.attributeSender.sendSetAttribute(id, name, value);
|
|
147
147
|
}
|
|
148
148
|
sendNodeData(id, parentElement, data) {
|
|
149
149
|
if (hasTag(parentElement, 'style')) {
|
package/lib/app/session.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type App from './index.js';
|
|
2
|
+
interface UserInfo {
|
|
3
|
+
userBrowser: string;
|
|
4
|
+
userCity: string;
|
|
5
|
+
userCountry: string;
|
|
6
|
+
userDevice: string;
|
|
7
|
+
userOS: string;
|
|
8
|
+
userState: string;
|
|
9
|
+
}
|
|
2
10
|
interface SessionInfo {
|
|
3
11
|
sessionID: string | undefined;
|
|
4
12
|
metadata: Record<string, string>;
|
|
@@ -22,12 +30,14 @@ export default class Session {
|
|
|
22
30
|
private timestamp;
|
|
23
31
|
private projectID;
|
|
24
32
|
private tabId;
|
|
33
|
+
userInfo: UserInfo;
|
|
25
34
|
constructor(app: App, options: Options);
|
|
26
35
|
attachUpdateCallback(cb: OnUpdateCallback): void;
|
|
27
36
|
private handleUpdate;
|
|
28
37
|
assign(newInfo: Partial<SessionInfo>): void;
|
|
29
38
|
setMetadata(key: string, value: string): void;
|
|
30
39
|
setUserID(userID: string): void;
|
|
40
|
+
setUserInfo(userInfo: UserInfo): void;
|
|
31
41
|
private getPageNumber;
|
|
32
42
|
incPageNo(): number;
|
|
33
43
|
getSessionToken(): string | undefined;
|
package/lib/app/session.js
CHANGED
|
@@ -48,6 +48,9 @@ export default class Session {
|
|
|
48
48
|
this.userID = userID;
|
|
49
49
|
this.handleUpdate({ userID });
|
|
50
50
|
}
|
|
51
|
+
setUserInfo(userInfo) {
|
|
52
|
+
this.userInfo = userInfo;
|
|
53
|
+
}
|
|
51
54
|
getPageNumber() {
|
|
52
55
|
const pageNoStr = this.app.sessionStorage.getItem(this.options.session_pageno_key);
|
|
53
56
|
if (pageNoStr == null) {
|
package/lib/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { default as App } from './app/index.js';
|
|
|
3
3
|
import * as _Messages from './app/messages.gen.js';
|
|
4
4
|
export declare const Messages: typeof _Messages;
|
|
5
5
|
export { SanitizeLevel } from './app/sanitizer.js';
|
|
6
|
+
import FeatureFlags, { IFeatureFlag } from './modules/featureFlags.js';
|
|
6
7
|
import type { Options as AppOptions } from './app/index.js';
|
|
7
8
|
import type { Options as ConsoleOptions } from './modules/console.js';
|
|
8
9
|
import type { Options as ExceptionOptions } from './modules/exception.js';
|
|
@@ -21,12 +22,22 @@ export type Options = Partial<AppOptions & ConsoleOptions & ExceptionOptions & I
|
|
|
21
22
|
autoResetOnWindowOpen?: boolean;
|
|
22
23
|
network?: NetworkOptions;
|
|
23
24
|
mouse?: MouseHandlerOptions;
|
|
25
|
+
flags?: {
|
|
26
|
+
onFlagsLoad?: (flags: IFeatureFlag[]) => void;
|
|
27
|
+
};
|
|
24
28
|
__DISABLE_SECURE_MODE?: boolean;
|
|
25
29
|
};
|
|
26
30
|
export default class API {
|
|
27
31
|
private readonly options;
|
|
32
|
+
featureFlags: FeatureFlags;
|
|
28
33
|
private readonly app;
|
|
29
34
|
constructor(options: Options);
|
|
35
|
+
isFlagEnabled(flagName: string): boolean;
|
|
36
|
+
onFlagsLoad(callback: (flags: IFeatureFlag[]) => void): void;
|
|
37
|
+
clearPersistFlags(): void;
|
|
38
|
+
reloadFlags(): Promise<void>;
|
|
39
|
+
getFeatureFlag(flagName: string): IFeatureFlag | undefined;
|
|
40
|
+
getAllFeatureFlags(): IFeatureFlag[];
|
|
30
41
|
use<T>(fn: (app: App | null, options?: Options) => T): T;
|
|
31
42
|
isActive(): boolean;
|
|
32
43
|
start(startOpts?: Partial<StartOptions>): Promise<StartPromiseReturn>;
|
package/lib/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import ConstructedStyleSheets from './modules/constructedStyleSheets.js';
|
|
|
22
22
|
import Selection from './modules/selection.js';
|
|
23
23
|
import Tabs from './modules/tabs.js';
|
|
24
24
|
import { IN_BROWSER, deprecationWarn, DOCS_HOST } from './utils.js';
|
|
25
|
+
import FeatureFlags from './modules/featureFlags.js';
|
|
25
26
|
const DOCS_SETUP = '/installation/javascript-sdk';
|
|
26
27
|
function processOptions(obj) {
|
|
27
28
|
if (obj == null) {
|
|
@@ -115,7 +116,15 @@ export default class API {
|
|
|
115
116
|
Network(app, options.network);
|
|
116
117
|
Selection(app);
|
|
117
118
|
Tabs(app);
|
|
119
|
+
this.featureFlags = new FeatureFlags(app);
|
|
118
120
|
window.__OPENREPLAY__ = this;
|
|
121
|
+
app.attachStartCallback(() => {
|
|
122
|
+
var _a;
|
|
123
|
+
if ((_a = options.flags) === null || _a === void 0 ? void 0 : _a.onFlagsLoad) {
|
|
124
|
+
this.onFlagsLoad(options.flags.onFlagsLoad);
|
|
125
|
+
}
|
|
126
|
+
void this.featureFlags.reloadFlags();
|
|
127
|
+
});
|
|
119
128
|
if (options.autoResetOnWindowOpen) {
|
|
120
129
|
const wOpen = window.open;
|
|
121
130
|
app.attachStartCallback(() => {
|
|
@@ -139,13 +148,31 @@ export default class API {
|
|
|
139
148
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
140
149
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
141
150
|
req.send(JSON.stringify({
|
|
142
|
-
trackerVersion: '8.0.
|
|
151
|
+
trackerVersion: '8.0.1-beta14',
|
|
143
152
|
projectKey: options.projectKey,
|
|
144
153
|
doNotTrack,
|
|
145
154
|
// TODO: add precise reason (an exact API missing)
|
|
146
155
|
}));
|
|
147
156
|
}
|
|
148
157
|
}
|
|
158
|
+
isFlagEnabled(flagName) {
|
|
159
|
+
return this.featureFlags.isFlagEnabled(flagName);
|
|
160
|
+
}
|
|
161
|
+
onFlagsLoad(callback) {
|
|
162
|
+
this.featureFlags.onFlagsLoad(callback);
|
|
163
|
+
}
|
|
164
|
+
clearPersistFlags() {
|
|
165
|
+
this.featureFlags.clearPersistFlags();
|
|
166
|
+
}
|
|
167
|
+
reloadFlags() {
|
|
168
|
+
return this.featureFlags.reloadFlags();
|
|
169
|
+
}
|
|
170
|
+
getFeatureFlag(flagName) {
|
|
171
|
+
return this.featureFlags.getFeatureFlag(flagName);
|
|
172
|
+
}
|
|
173
|
+
getAllFeatureFlags() {
|
|
174
|
+
return this.featureFlags.flags;
|
|
175
|
+
}
|
|
149
176
|
use(fn) {
|
|
150
177
|
return fn(this.app, this.options);
|
|
151
178
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I took inspiration in few stack exchange posts
|
|
3
|
+
* and Tencent vConsole library (MIT)
|
|
4
|
+
* by wrapping the XMLHttpRequest object in a Proxy
|
|
5
|
+
* we can intercept the network requests
|
|
6
|
+
* in not-so-hacky way
|
|
7
|
+
* */
|
|
8
|
+
import NetworkMessage from './networkMessage.js';
|
|
9
|
+
import { RequestResponseData } from './types.js';
|
|
10
|
+
import { NetworkRequest } from '../../common/messages.gen.js';
|
|
11
|
+
export declare class ResponseProxyHandler<T extends Response> implements ProxyHandler<T> {
|
|
12
|
+
resp: Response;
|
|
13
|
+
item: NetworkMessage;
|
|
14
|
+
constructor(resp: T, item: NetworkMessage);
|
|
15
|
+
set(target: T, key: string, value: (args: any[]) => any): boolean;
|
|
16
|
+
get(target: T, key: string): any;
|
|
17
|
+
protected mockReader(): void;
|
|
18
|
+
}
|
|
19
|
+
export declare class FetchProxyHandler<T extends typeof fetch> implements ProxyHandler<T> {
|
|
20
|
+
private readonly ignoredHeaders;
|
|
21
|
+
private readonly setSessionTokenHeader;
|
|
22
|
+
private readonly sanitize;
|
|
23
|
+
private readonly sendMessage;
|
|
24
|
+
private readonly isServiceUrl;
|
|
25
|
+
constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean);
|
|
26
|
+
apply(target: T, thisArg: typeof window, argsList: [RequestInfo | URL, RequestInit]): any;
|
|
27
|
+
protected beforeFetch(item: NetworkMessage, input: RequestInfo, init?: RequestInit): void;
|
|
28
|
+
protected afterFetch(item: NetworkMessage): (resp: Response) => Response;
|
|
29
|
+
protected handleResponseBody(resp: Response, item: NetworkMessage): Promise<string> | Promise<ArrayBuffer>;
|
|
30
|
+
}
|
|
31
|
+
export default class FetchProxy {
|
|
32
|
+
static origFetch: typeof fetch;
|
|
33
|
+
static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean): typeof fetch;
|
|
34
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I took inspiration in few stack exchange posts
|
|
3
|
+
* and Tencent vConsole library (MIT)
|
|
4
|
+
* by wrapping the XMLHttpRequest object in a Proxy
|
|
5
|
+
* we can intercept the network requests
|
|
6
|
+
* in not-so-hacky way
|
|
7
|
+
* */
|
|
8
|
+
import NetworkMessage, { RequestState } from './networkMessage.js';
|
|
9
|
+
import { formatByteSize, genStringBody, getStringResponseByType, getURL } from './utils.js';
|
|
10
|
+
export class ResponseProxyHandler {
|
|
11
|
+
constructor(resp, item) {
|
|
12
|
+
this.resp = resp;
|
|
13
|
+
this.item = item;
|
|
14
|
+
this.mockReader();
|
|
15
|
+
}
|
|
16
|
+
set(target, key, value) {
|
|
17
|
+
return Reflect.set(target, key, value);
|
|
18
|
+
}
|
|
19
|
+
get(target, key) {
|
|
20
|
+
const value = Reflect.get(target, key);
|
|
21
|
+
switch (key) {
|
|
22
|
+
case 'arrayBuffer':
|
|
23
|
+
case 'blob':
|
|
24
|
+
case 'formData':
|
|
25
|
+
case 'json':
|
|
26
|
+
case 'text':
|
|
27
|
+
return () => {
|
|
28
|
+
this.item.responseType = key.toLowerCase();
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
return value.apply(target).then((resp) => {
|
|
31
|
+
this.item.response = getStringResponseByType(this.item.responseType, resp);
|
|
32
|
+
return resp;
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (typeof value === 'function') {
|
|
37
|
+
return value.bind(target);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
mockReader() {
|
|
44
|
+
let readerReceivedValue;
|
|
45
|
+
if (!this.resp.body) {
|
|
46
|
+
// some browsers do not return `body` in some cases, like `OPTIONS` method
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (typeof this.resp.body.getReader !== 'function') {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const _getReader = this.resp.body.getReader;
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
this.resp.body.getReader = () => {
|
|
55
|
+
const reader = _getReader.apply(this.resp.body);
|
|
56
|
+
// when readyState is already 4,
|
|
57
|
+
// it's not a chunked stream, or it had already been read.
|
|
58
|
+
// so should not update status.
|
|
59
|
+
if (this.item.readyState === RequestState.DONE) {
|
|
60
|
+
return reader;
|
|
61
|
+
}
|
|
62
|
+
const _read = reader.read;
|
|
63
|
+
const _cancel = reader.cancel;
|
|
64
|
+
this.item.responseType = 'arraybuffer';
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
reader.read = () => {
|
|
67
|
+
return _read.apply(reader).then((result) => {
|
|
68
|
+
if (!readerReceivedValue) {
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
readerReceivedValue = new Uint8Array(result.value);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const newValue = new Uint8Array(readerReceivedValue.length + result.value.length);
|
|
74
|
+
newValue.set(readerReceivedValue);
|
|
75
|
+
newValue.set(result.value, readerReceivedValue.length);
|
|
76
|
+
readerReceivedValue = newValue;
|
|
77
|
+
}
|
|
78
|
+
this.item.endTime = performance.now();
|
|
79
|
+
this.item.duration = this.item.endTime - (this.item.startTime || this.item.endTime);
|
|
80
|
+
this.item.readyState = result.done ? 4 : 3;
|
|
81
|
+
this.item.statusText = result.done ? String(this.item.status) : 'Loading';
|
|
82
|
+
this.item.responseSize = readerReceivedValue.length;
|
|
83
|
+
this.item.responseSizeText = formatByteSize(this.item.responseSize);
|
|
84
|
+
if (result.done) {
|
|
85
|
+
this.item.response = getStringResponseByType(this.item.responseType, readerReceivedValue);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
reader.cancel = (...args) => {
|
|
91
|
+
this.item.cancelState = 2;
|
|
92
|
+
this.item.statusText = 'Cancel';
|
|
93
|
+
this.item.endTime = performance.now();
|
|
94
|
+
this.item.duration = this.item.endTime - (this.item.startTime || this.item.endTime);
|
|
95
|
+
this.item.response = getStringResponseByType(this.item.responseType, readerReceivedValue);
|
|
96
|
+
return _cancel.apply(reader, args);
|
|
97
|
+
};
|
|
98
|
+
return reader;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export class FetchProxyHandler {
|
|
103
|
+
constructor(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
|
|
104
|
+
this.ignoredHeaders = ignoredHeaders;
|
|
105
|
+
this.setSessionTokenHeader = setSessionTokenHeader;
|
|
106
|
+
this.sanitize = sanitize;
|
|
107
|
+
this.sendMessage = sendMessage;
|
|
108
|
+
this.isServiceUrl = isServiceUrl;
|
|
109
|
+
}
|
|
110
|
+
apply(target, thisArg, argsList) {
|
|
111
|
+
const input = argsList[0];
|
|
112
|
+
const init = argsList[1];
|
|
113
|
+
const isORUrl = input instanceof URL || typeof input === 'string'
|
|
114
|
+
? this.isServiceUrl(String(input))
|
|
115
|
+
: this.isServiceUrl(String(input.url));
|
|
116
|
+
if (isORUrl) {
|
|
117
|
+
return target.apply(window, argsList);
|
|
118
|
+
}
|
|
119
|
+
const item = new NetworkMessage(this.ignoredHeaders, this.setSessionTokenHeader, this.sanitize);
|
|
120
|
+
this.beforeFetch(item, input, init);
|
|
121
|
+
return target.apply(window, argsList)
|
|
122
|
+
.then(this.afterFetch(item))
|
|
123
|
+
.catch((e) => {
|
|
124
|
+
// mock finally
|
|
125
|
+
item.endTime = performance.now();
|
|
126
|
+
item.duration = item.endTime - (item.startTime || item.endTime);
|
|
127
|
+
throw e;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
beforeFetch(item, input, init) {
|
|
131
|
+
let url, method = 'GET', requestHeader = {};
|
|
132
|
+
// handle `input` content
|
|
133
|
+
if (typeof input === 'string') {
|
|
134
|
+
// when `input` is a string
|
|
135
|
+
method = (init === null || init === void 0 ? void 0 : init.method) || 'GET';
|
|
136
|
+
url = getURL(input);
|
|
137
|
+
requestHeader = (init === null || init === void 0 ? void 0 : init.headers) || {};
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// when `input` is a `Request` object
|
|
141
|
+
method = input.method || 'GET';
|
|
142
|
+
url = getURL(input.url);
|
|
143
|
+
requestHeader = input.headers;
|
|
144
|
+
}
|
|
145
|
+
item.method = method;
|
|
146
|
+
item.requestType = 'fetch';
|
|
147
|
+
item.requestHeader = requestHeader;
|
|
148
|
+
item.url = url.toString();
|
|
149
|
+
item.name = (url.pathname.split('/').pop() || '') + url.search;
|
|
150
|
+
item.status = 0;
|
|
151
|
+
item.statusText = 'Pending';
|
|
152
|
+
item.readyState = 1;
|
|
153
|
+
if (!item.startTime) {
|
|
154
|
+
// UNSENT
|
|
155
|
+
item.startTime = performance.now();
|
|
156
|
+
}
|
|
157
|
+
if (Object.prototype.toString.call(requestHeader) === '[object Headers]') {
|
|
158
|
+
item.requestHeader = {};
|
|
159
|
+
for (const [key, value] of requestHeader) {
|
|
160
|
+
item.requestHeader[key] = value;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
item.requestHeader = requestHeader;
|
|
165
|
+
}
|
|
166
|
+
// save GET data
|
|
167
|
+
if (url.search && url.searchParams) {
|
|
168
|
+
item.getData = {};
|
|
169
|
+
for (const [key, value] of url.searchParams) {
|
|
170
|
+
item.getData[key] = value;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// save POST data
|
|
174
|
+
if (init === null || init === void 0 ? void 0 : init.body) {
|
|
175
|
+
item.requestData = genStringBody(init.body);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
afterFetch(item) {
|
|
179
|
+
return (resp) => {
|
|
180
|
+
item.endTime = performance.now();
|
|
181
|
+
item.duration = item.endTime - (item.startTime || item.endTime);
|
|
182
|
+
item.status = resp.status;
|
|
183
|
+
item.statusText = String(resp.status);
|
|
184
|
+
let isChunked = false;
|
|
185
|
+
item.header = {};
|
|
186
|
+
for (const [key, value] of resp.headers) {
|
|
187
|
+
item.header[key] = value;
|
|
188
|
+
isChunked = value.toLowerCase().indexOf('chunked') > -1 ? true : isChunked;
|
|
189
|
+
}
|
|
190
|
+
if (isChunked) {
|
|
191
|
+
// when `transfer-encoding` is chunked, the response is a stream which is under loading,
|
|
192
|
+
// so the `readyState` should be 3 (Loading),
|
|
193
|
+
// and the response should NOT be `clone()` which will affect stream reading.
|
|
194
|
+
item.readyState = 3;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// Otherwise, not chunked, the response is not a stream,
|
|
198
|
+
// so it's completed and can be cloned for `text()` calling.
|
|
199
|
+
item.readyState = 4;
|
|
200
|
+
void this.handleResponseBody(resp.clone(), item).then((responseValue) => {
|
|
201
|
+
item.responseSize =
|
|
202
|
+
typeof responseValue === 'string' ? responseValue.length : responseValue.byteLength;
|
|
203
|
+
item.responseSizeText = formatByteSize(item.responseSize);
|
|
204
|
+
item.response = getStringResponseByType(item.responseType, responseValue);
|
|
205
|
+
this.sendMessage(item.getMessage());
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return new Proxy(resp, new ResponseProxyHandler(resp, item));
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
handleResponseBody(resp, item) {
|
|
212
|
+
// parse response body by Content-Type
|
|
213
|
+
const contentType = resp.headers.get('content-type');
|
|
214
|
+
if (contentType && contentType.includes('application/json')) {
|
|
215
|
+
item.responseType = 'json';
|
|
216
|
+
return resp.text();
|
|
217
|
+
}
|
|
218
|
+
else if (contentType &&
|
|
219
|
+
(contentType.includes('text/html') || contentType.includes('text/plain'))) {
|
|
220
|
+
item.responseType = 'text';
|
|
221
|
+
return resp.text();
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
item.responseType = 'arraybuffer';
|
|
225
|
+
return resp.arrayBuffer();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export default class FetchProxy {
|
|
230
|
+
static create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
|
|
231
|
+
return new Proxy(fetch, new FetchProxyHandler(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
FetchProxy.origFetch = fetch;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { RequestResponseData } from './types.js';
|
|
2
|
+
import { NetworkRequest } from '../../common/messages.gen.js';
|
|
3
|
+
export default function setProxy(context: typeof globalThis, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean): void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import FetchProxy from './fetchProxy.js';
|
|
2
|
+
import XHRProxy from './xhrProxy.js';
|
|
3
|
+
export default function setProxy(context, ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
|
|
4
|
+
context.XMLHttpRequest = XHRProxy.create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl);
|
|
5
|
+
context.fetch = FetchProxy.create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl);
|
|
6
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RequestResponseData } from './types.js';
|
|
2
|
+
export type httpMethod = '' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH';
|
|
3
|
+
export declare enum RequestState {
|
|
4
|
+
UNSENT = 0,
|
|
5
|
+
OPENED = 1,
|
|
6
|
+
HEADERS_RECEIVED = 2,
|
|
7
|
+
LOADING = 3,
|
|
8
|
+
DONE = 4
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* I know we're not using most of the information from this class
|
|
12
|
+
* but it can be useful in the future if we will decide to display more stuff in our ui
|
|
13
|
+
* */
|
|
14
|
+
export default class NetworkMessage {
|
|
15
|
+
private readonly ignoredHeaders;
|
|
16
|
+
private readonly setSessionTokenHeader;
|
|
17
|
+
private readonly sanitize;
|
|
18
|
+
id: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
method: httpMethod;
|
|
21
|
+
url: string;
|
|
22
|
+
status: number;
|
|
23
|
+
statusText?: string;
|
|
24
|
+
cancelState?: 0 | 1 | 2 | 3;
|
|
25
|
+
readyState?: RequestState;
|
|
26
|
+
header: {
|
|
27
|
+
[key: string]: string;
|
|
28
|
+
};
|
|
29
|
+
responseType: XMLHttpRequest['responseType'];
|
|
30
|
+
requestType: 'xhr' | 'fetch' | 'ping' | 'custom';
|
|
31
|
+
requestHeader: HeadersInit;
|
|
32
|
+
response: any;
|
|
33
|
+
responseSize: number;
|
|
34
|
+
responseSizeText: string;
|
|
35
|
+
startTime: number;
|
|
36
|
+
endTime: number;
|
|
37
|
+
duration: number;
|
|
38
|
+
getData: {
|
|
39
|
+
[key: string]: string;
|
|
40
|
+
};
|
|
41
|
+
requestData: string | null;
|
|
42
|
+
constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData);
|
|
43
|
+
getMessage(): import("../../common/messages.gen.js").NetworkRequest;
|
|
44
|
+
writeHeaders(): {
|
|
45
|
+
reqHs: Record<string, string>;
|
|
46
|
+
resHs: Record<string, string>;
|
|
47
|
+
};
|
|
48
|
+
isHeaderIgnored(key: string): boolean;
|
|
49
|
+
}
|