@openreplay/tracker 8.0.0 → 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 +4 -1
- package/cjs/app/index.js +18 -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/console.js +21 -13
- package/cjs/modules/featureFlags.d.ts +25 -0
- package/cjs/modules/featureFlags.js +100 -0
- package/cjs/modules/network.d.ts +3 -4
- package/cjs/modules/network.js +13 -3
- package/coverage/clover.xml +898 -449
- package/coverage/coverage-final.json +13 -8
- package/coverage/lcov-report/index.html +50 -35
- package/coverage/lcov-report/main/app/guards.ts.html +1 -1
- package/coverage/lcov-report/main/app/index.html +21 -21
- package/coverage/lcov-report/main/app/index.ts.html +46 -4
- 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 +1 -1
- 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 +20 -20
- package/coverage/lcov-report/main/index.ts.html +38 -26
- package/coverage/lcov-report/main/modules/Network/fetchProxy.ts.html +949 -0
- package/coverage/lcov-report/main/modules/Network/index.html +176 -0
- package/coverage/lcov-report/main/modules/Network/index.ts.html +169 -0
- package/coverage/lcov-report/main/modules/Network/networkMessage.ts.html +382 -0
- 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 +1 -1
- 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 +102 -51
- 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 +1 -1
- package/coverage/lcov-report/main/modules/index.html +33 -33
- 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 +45 -45
- package/coverage/lcov-report/webworker/BatchWriter.ts.html +1 -1
- package/coverage/lcov-report/webworker/MessageEncoder.gen.ts.html +1 -1
- package/coverage/lcov-report/webworker/PrimitiveEncoder.ts.html +1 -1
- package/coverage/lcov-report/webworker/QueueSender.ts.html +1 -1
- package/coverage/lcov-report/webworker/index.html +1 -1
- package/coverage/lcov-report/webworker/index.ts.html +1 -1
- package/coverage/lcov.info +1558 -726
- package/lib/app/index.d.ts +4 -1
- package/lib/app/index.js +18 -2
- 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/console.js +21 -13
- package/lib/modules/featureFlags.d.ts +25 -0
- package/lib/modules/featureFlags.js +97 -0
- package/lib/modules/network.d.ts +3 -4
- package/lib/modules/network.js +13 -3
- package/package.json +1 -1
package/cjs/app/index.d.ts
CHANGED
|
@@ -73,12 +73,13 @@ export default class App {
|
|
|
73
73
|
private readonly startCallbacks;
|
|
74
74
|
private readonly stopCallbacks;
|
|
75
75
|
private readonly commitCallbacks;
|
|
76
|
-
|
|
76
|
+
readonly options: AppOptions;
|
|
77
77
|
readonly networkOptions?: NetworkOptions;
|
|
78
78
|
private readonly revID;
|
|
79
79
|
private activityState;
|
|
80
80
|
private readonly version;
|
|
81
81
|
private readonly worker?;
|
|
82
|
+
private featureFlags;
|
|
82
83
|
private compressionThreshold;
|
|
83
84
|
private restartAttempts;
|
|
84
85
|
private readonly bc;
|
|
@@ -120,6 +121,8 @@ export default class App {
|
|
|
120
121
|
resolveResourceURL(resourceURL: string): string;
|
|
121
122
|
isServiceURL(url: string): boolean;
|
|
122
123
|
active(): boolean;
|
|
124
|
+
isFeatureActive(feature: string): boolean;
|
|
125
|
+
getFeatureFlags(): string[];
|
|
123
126
|
resetNextPageSession(flag: boolean): void;
|
|
124
127
|
private _start;
|
|
125
128
|
/**
|
package/cjs/app/index.js
CHANGED
|
@@ -35,7 +35,8 @@ class App {
|
|
|
35
35
|
this.stopCallbacks = [];
|
|
36
36
|
this.commitCallbacks = [];
|
|
37
37
|
this.activityState = ActivityState.NotActive;
|
|
38
|
-
this.version = '8.0.
|
|
38
|
+
this.version = '8.0.1-beta14'; // TODO: version compatability check inside each plugin.
|
|
39
|
+
this.featureFlags = [];
|
|
39
40
|
this.compressionThreshold = 24 * 1000;
|
|
40
41
|
this.restartAttempts = 0;
|
|
41
42
|
this.bc = new BroadcastChannel('rick');
|
|
@@ -335,6 +336,12 @@ class App {
|
|
|
335
336
|
active() {
|
|
336
337
|
return this.activityState === ActivityState.Active;
|
|
337
338
|
}
|
|
339
|
+
isFeatureActive(feature) {
|
|
340
|
+
return this.featureFlags.includes(feature);
|
|
341
|
+
}
|
|
342
|
+
getFeatureFlags() {
|
|
343
|
+
return this.featureFlags;
|
|
344
|
+
}
|
|
338
345
|
resetNextPageSession(flag) {
|
|
339
346
|
if (flag) {
|
|
340
347
|
this.sessionStorage.setItem(this.options.session_reset_key, 't');
|
|
@@ -413,7 +420,8 @@ class App {
|
|
|
413
420
|
delay, // derived from token
|
|
414
421
|
sessionID, // derived from token
|
|
415
422
|
startTimestamp, // real startTS (server time), derived from sessionID
|
|
416
|
-
|
|
423
|
+
userBrowser, userCity, userCountry, userDevice, userOS, userState, } = r;
|
|
424
|
+
// TODO: insert feature flags here
|
|
417
425
|
if (typeof token !== 'string' ||
|
|
418
426
|
typeof userUUID !== 'string' ||
|
|
419
427
|
(typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
|
|
@@ -424,6 +432,14 @@ class App {
|
|
|
424
432
|
}
|
|
425
433
|
this.delay = delay;
|
|
426
434
|
this.session.setSessionToken(token);
|
|
435
|
+
this.session.setUserInfo({
|
|
436
|
+
userBrowser,
|
|
437
|
+
userCity,
|
|
438
|
+
userCountry,
|
|
439
|
+
userDevice,
|
|
440
|
+
userOS,
|
|
441
|
+
userState,
|
|
442
|
+
});
|
|
427
443
|
this.session.assign({
|
|
428
444
|
sessionID,
|
|
429
445
|
timestamp: startTimestamp || timestamp,
|
package/cjs/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/cjs/app/session.js
CHANGED
|
@@ -50,6 +50,9 @@ class Session {
|
|
|
50
50
|
this.userID = userID;
|
|
51
51
|
this.handleUpdate({ userID });
|
|
52
52
|
}
|
|
53
|
+
setUserInfo(userInfo) {
|
|
54
|
+
this.userInfo = userInfo;
|
|
55
|
+
}
|
|
53
56
|
getPageNumber() {
|
|
54
57
|
const pageNoStr = this.app.sessionStorage.getItem(this.options.session_pageno_key);
|
|
55
58
|
if (pageNoStr == null) {
|
package/cjs/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/cjs/index.js
CHANGED
|
@@ -27,6 +27,7 @@ const constructedStyleSheets_js_1 = require("./modules/constructedStyleSheets.js
|
|
|
27
27
|
const selection_js_1 = require("./modules/selection.js");
|
|
28
28
|
const tabs_js_1 = require("./modules/tabs.js");
|
|
29
29
|
const utils_js_1 = require("./utils.js");
|
|
30
|
+
const featureFlags_js_1 = require("./modules/featureFlags.js");
|
|
30
31
|
const DOCS_SETUP = '/installation/javascript-sdk';
|
|
31
32
|
function processOptions(obj) {
|
|
32
33
|
if (obj == null) {
|
|
@@ -120,7 +121,15 @@ class API {
|
|
|
120
121
|
(0, network_js_1.default)(app, options.network);
|
|
121
122
|
(0, selection_js_1.default)(app);
|
|
122
123
|
(0, tabs_js_1.default)(app);
|
|
124
|
+
this.featureFlags = new featureFlags_js_1.default(app);
|
|
123
125
|
window.__OPENREPLAY__ = this;
|
|
126
|
+
app.attachStartCallback(() => {
|
|
127
|
+
var _a;
|
|
128
|
+
if ((_a = options.flags) === null || _a === void 0 ? void 0 : _a.onFlagsLoad) {
|
|
129
|
+
this.onFlagsLoad(options.flags.onFlagsLoad);
|
|
130
|
+
}
|
|
131
|
+
void this.featureFlags.reloadFlags();
|
|
132
|
+
});
|
|
124
133
|
if (options.autoResetOnWindowOpen) {
|
|
125
134
|
const wOpen = window.open;
|
|
126
135
|
app.attachStartCallback(() => {
|
|
@@ -144,13 +153,31 @@ class API {
|
|
|
144
153
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
145
154
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
146
155
|
req.send(JSON.stringify({
|
|
147
|
-
trackerVersion: '8.0.
|
|
156
|
+
trackerVersion: '8.0.1-beta14',
|
|
148
157
|
projectKey: options.projectKey,
|
|
149
158
|
doNotTrack,
|
|
150
159
|
// TODO: add precise reason (an exact API missing)
|
|
151
160
|
}));
|
|
152
161
|
}
|
|
153
162
|
}
|
|
163
|
+
isFlagEnabled(flagName) {
|
|
164
|
+
return this.featureFlags.isFlagEnabled(flagName);
|
|
165
|
+
}
|
|
166
|
+
onFlagsLoad(callback) {
|
|
167
|
+
this.featureFlags.onFlagsLoad(callback);
|
|
168
|
+
}
|
|
169
|
+
clearPersistFlags() {
|
|
170
|
+
this.featureFlags.clearPersistFlags();
|
|
171
|
+
}
|
|
172
|
+
reloadFlags() {
|
|
173
|
+
return this.featureFlags.reloadFlags();
|
|
174
|
+
}
|
|
175
|
+
getFeatureFlag(flagName) {
|
|
176
|
+
return this.featureFlags.getFeatureFlag(flagName);
|
|
177
|
+
}
|
|
178
|
+
getAllFeatureFlags() {
|
|
179
|
+
return this.featureFlags.flags;
|
|
180
|
+
}
|
|
154
181
|
use(fn) {
|
|
155
182
|
return fn(this.app, this.options);
|
|
156
183
|
}
|
|
@@ -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<ArrayBuffer> | Promise<string>;
|
|
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,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FetchProxyHandler = exports.ResponseProxyHandler = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* I took inspiration in few stack exchange posts
|
|
6
|
+
* and Tencent vConsole library (MIT)
|
|
7
|
+
* by wrapping the XMLHttpRequest object in a Proxy
|
|
8
|
+
* we can intercept the network requests
|
|
9
|
+
* in not-so-hacky way
|
|
10
|
+
* */
|
|
11
|
+
const networkMessage_js_1 = require("./networkMessage.js");
|
|
12
|
+
const utils_js_1 = require("./utils.js");
|
|
13
|
+
class ResponseProxyHandler {
|
|
14
|
+
constructor(resp, item) {
|
|
15
|
+
this.resp = resp;
|
|
16
|
+
this.item = item;
|
|
17
|
+
this.mockReader();
|
|
18
|
+
}
|
|
19
|
+
set(target, key, value) {
|
|
20
|
+
return Reflect.set(target, key, value);
|
|
21
|
+
}
|
|
22
|
+
get(target, key) {
|
|
23
|
+
const value = Reflect.get(target, key);
|
|
24
|
+
switch (key) {
|
|
25
|
+
case 'arrayBuffer':
|
|
26
|
+
case 'blob':
|
|
27
|
+
case 'formData':
|
|
28
|
+
case 'json':
|
|
29
|
+
case 'text':
|
|
30
|
+
return () => {
|
|
31
|
+
this.item.responseType = key.toLowerCase();
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
return value.apply(target).then((resp) => {
|
|
34
|
+
this.item.response = (0, utils_js_1.getStringResponseByType)(this.item.responseType, resp);
|
|
35
|
+
return resp;
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (typeof value === 'function') {
|
|
40
|
+
return value.bind(target);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
mockReader() {
|
|
47
|
+
let readerReceivedValue;
|
|
48
|
+
if (!this.resp.body) {
|
|
49
|
+
// some browsers do not return `body` in some cases, like `OPTIONS` method
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (typeof this.resp.body.getReader !== 'function') {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const _getReader = this.resp.body.getReader;
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
this.resp.body.getReader = () => {
|
|
58
|
+
const reader = _getReader.apply(this.resp.body);
|
|
59
|
+
// when readyState is already 4,
|
|
60
|
+
// it's not a chunked stream, or it had already been read.
|
|
61
|
+
// so should not update status.
|
|
62
|
+
if (this.item.readyState === networkMessage_js_1.RequestState.DONE) {
|
|
63
|
+
return reader;
|
|
64
|
+
}
|
|
65
|
+
const _read = reader.read;
|
|
66
|
+
const _cancel = reader.cancel;
|
|
67
|
+
this.item.responseType = 'arraybuffer';
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
reader.read = () => {
|
|
70
|
+
return _read.apply(reader).then((result) => {
|
|
71
|
+
if (!readerReceivedValue) {
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
readerReceivedValue = new Uint8Array(result.value);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const newValue = new Uint8Array(readerReceivedValue.length + result.value.length);
|
|
77
|
+
newValue.set(readerReceivedValue);
|
|
78
|
+
newValue.set(result.value, readerReceivedValue.length);
|
|
79
|
+
readerReceivedValue = newValue;
|
|
80
|
+
}
|
|
81
|
+
this.item.endTime = performance.now();
|
|
82
|
+
this.item.duration = this.item.endTime - (this.item.startTime || this.item.endTime);
|
|
83
|
+
this.item.readyState = result.done ? 4 : 3;
|
|
84
|
+
this.item.statusText = result.done ? String(this.item.status) : 'Loading';
|
|
85
|
+
this.item.responseSize = readerReceivedValue.length;
|
|
86
|
+
this.item.responseSizeText = (0, utils_js_1.formatByteSize)(this.item.responseSize);
|
|
87
|
+
if (result.done) {
|
|
88
|
+
this.item.response = (0, utils_js_1.getStringResponseByType)(this.item.responseType, readerReceivedValue);
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
reader.cancel = (...args) => {
|
|
94
|
+
this.item.cancelState = 2;
|
|
95
|
+
this.item.statusText = 'Cancel';
|
|
96
|
+
this.item.endTime = performance.now();
|
|
97
|
+
this.item.duration = this.item.endTime - (this.item.startTime || this.item.endTime);
|
|
98
|
+
this.item.response = (0, utils_js_1.getStringResponseByType)(this.item.responseType, readerReceivedValue);
|
|
99
|
+
return _cancel.apply(reader, args);
|
|
100
|
+
};
|
|
101
|
+
return reader;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.ResponseProxyHandler = ResponseProxyHandler;
|
|
106
|
+
class FetchProxyHandler {
|
|
107
|
+
constructor(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
|
|
108
|
+
this.ignoredHeaders = ignoredHeaders;
|
|
109
|
+
this.setSessionTokenHeader = setSessionTokenHeader;
|
|
110
|
+
this.sanitize = sanitize;
|
|
111
|
+
this.sendMessage = sendMessage;
|
|
112
|
+
this.isServiceUrl = isServiceUrl;
|
|
113
|
+
}
|
|
114
|
+
apply(target, thisArg, argsList) {
|
|
115
|
+
const input = argsList[0];
|
|
116
|
+
const init = argsList[1];
|
|
117
|
+
const isORUrl = input instanceof URL || typeof input === 'string'
|
|
118
|
+
? this.isServiceUrl(String(input))
|
|
119
|
+
: this.isServiceUrl(String(input.url));
|
|
120
|
+
if (isORUrl) {
|
|
121
|
+
return target.apply(window, argsList);
|
|
122
|
+
}
|
|
123
|
+
const item = new networkMessage_js_1.default(this.ignoredHeaders, this.setSessionTokenHeader, this.sanitize);
|
|
124
|
+
this.beforeFetch(item, input, init);
|
|
125
|
+
return target.apply(window, argsList)
|
|
126
|
+
.then(this.afterFetch(item))
|
|
127
|
+
.catch((e) => {
|
|
128
|
+
// mock finally
|
|
129
|
+
item.endTime = performance.now();
|
|
130
|
+
item.duration = item.endTime - (item.startTime || item.endTime);
|
|
131
|
+
throw e;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
beforeFetch(item, input, init) {
|
|
135
|
+
let url, method = 'GET', requestHeader = {};
|
|
136
|
+
// handle `input` content
|
|
137
|
+
if (typeof input === 'string') {
|
|
138
|
+
// when `input` is a string
|
|
139
|
+
method = (init === null || init === void 0 ? void 0 : init.method) || 'GET';
|
|
140
|
+
url = (0, utils_js_1.getURL)(input);
|
|
141
|
+
requestHeader = (init === null || init === void 0 ? void 0 : init.headers) || {};
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// when `input` is a `Request` object
|
|
145
|
+
method = input.method || 'GET';
|
|
146
|
+
url = (0, utils_js_1.getURL)(input.url);
|
|
147
|
+
requestHeader = input.headers;
|
|
148
|
+
}
|
|
149
|
+
item.method = method;
|
|
150
|
+
item.requestType = 'fetch';
|
|
151
|
+
item.requestHeader = requestHeader;
|
|
152
|
+
item.url = url.toString();
|
|
153
|
+
item.name = (url.pathname.split('/').pop() || '') + url.search;
|
|
154
|
+
item.status = 0;
|
|
155
|
+
item.statusText = 'Pending';
|
|
156
|
+
item.readyState = 1;
|
|
157
|
+
if (!item.startTime) {
|
|
158
|
+
// UNSENT
|
|
159
|
+
item.startTime = performance.now();
|
|
160
|
+
}
|
|
161
|
+
if (Object.prototype.toString.call(requestHeader) === '[object Headers]') {
|
|
162
|
+
item.requestHeader = {};
|
|
163
|
+
for (const [key, value] of requestHeader) {
|
|
164
|
+
item.requestHeader[key] = value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
item.requestHeader = requestHeader;
|
|
169
|
+
}
|
|
170
|
+
// save GET data
|
|
171
|
+
if (url.search && url.searchParams) {
|
|
172
|
+
item.getData = {};
|
|
173
|
+
for (const [key, value] of url.searchParams) {
|
|
174
|
+
item.getData[key] = value;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// save POST data
|
|
178
|
+
if (init === null || init === void 0 ? void 0 : init.body) {
|
|
179
|
+
item.requestData = (0, utils_js_1.genStringBody)(init.body);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
afterFetch(item) {
|
|
183
|
+
return (resp) => {
|
|
184
|
+
item.endTime = performance.now();
|
|
185
|
+
item.duration = item.endTime - (item.startTime || item.endTime);
|
|
186
|
+
item.status = resp.status;
|
|
187
|
+
item.statusText = String(resp.status);
|
|
188
|
+
let isChunked = false;
|
|
189
|
+
item.header = {};
|
|
190
|
+
for (const [key, value] of resp.headers) {
|
|
191
|
+
item.header[key] = value;
|
|
192
|
+
isChunked = value.toLowerCase().indexOf('chunked') > -1 ? true : isChunked;
|
|
193
|
+
}
|
|
194
|
+
if (isChunked) {
|
|
195
|
+
// when `transfer-encoding` is chunked, the response is a stream which is under loading,
|
|
196
|
+
// so the `readyState` should be 3 (Loading),
|
|
197
|
+
// and the response should NOT be `clone()` which will affect stream reading.
|
|
198
|
+
item.readyState = 3;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// Otherwise, not chunked, the response is not a stream,
|
|
202
|
+
// so it's completed and can be cloned for `text()` calling.
|
|
203
|
+
item.readyState = 4;
|
|
204
|
+
void this.handleResponseBody(resp.clone(), item).then((responseValue) => {
|
|
205
|
+
item.responseSize =
|
|
206
|
+
typeof responseValue === 'string' ? responseValue.length : responseValue.byteLength;
|
|
207
|
+
item.responseSizeText = (0, utils_js_1.formatByteSize)(item.responseSize);
|
|
208
|
+
item.response = (0, utils_js_1.getStringResponseByType)(item.responseType, responseValue);
|
|
209
|
+
this.sendMessage(item.getMessage());
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return new Proxy(resp, new ResponseProxyHandler(resp, item));
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
handleResponseBody(resp, item) {
|
|
216
|
+
// parse response body by Content-Type
|
|
217
|
+
const contentType = resp.headers.get('content-type');
|
|
218
|
+
if (contentType && contentType.includes('application/json')) {
|
|
219
|
+
item.responseType = 'json';
|
|
220
|
+
return resp.text();
|
|
221
|
+
}
|
|
222
|
+
else if (contentType &&
|
|
223
|
+
(contentType.includes('text/html') || contentType.includes('text/plain'))) {
|
|
224
|
+
item.responseType = 'text';
|
|
225
|
+
return resp.text();
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
item.responseType = 'arraybuffer';
|
|
229
|
+
return resp.arrayBuffer();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
exports.FetchProxyHandler = FetchProxyHandler;
|
|
234
|
+
class FetchProxy {
|
|
235
|
+
static create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
|
|
236
|
+
return new Proxy(fetch, new FetchProxyHandler(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
exports.default = FetchProxy;
|
|
240
|
+
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,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fetchProxy_js_1 = require("./fetchProxy.js");
|
|
4
|
+
const xhrProxy_js_1 = require("./xhrProxy.js");
|
|
5
|
+
function setProxy(context, ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
|
|
6
|
+
context.XMLHttpRequest = xhrProxy_js_1.default.create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl);
|
|
7
|
+
context.fetch = fetchProxy_js_1.default.create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl);
|
|
8
|
+
}
|
|
9
|
+
exports.default = setProxy;
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RequestState = void 0;
|
|
4
|
+
const messages_gen_js_1 = require("../../app/messages.gen.js");
|
|
5
|
+
const utils_js_1 = require("../../utils.js");
|
|
6
|
+
var RequestState;
|
|
7
|
+
(function (RequestState) {
|
|
8
|
+
RequestState[RequestState["UNSENT"] = 0] = "UNSENT";
|
|
9
|
+
RequestState[RequestState["OPENED"] = 1] = "OPENED";
|
|
10
|
+
RequestState[RequestState["HEADERS_RECEIVED"] = 2] = "HEADERS_RECEIVED";
|
|
11
|
+
RequestState[RequestState["LOADING"] = 3] = "LOADING";
|
|
12
|
+
RequestState[RequestState["DONE"] = 4] = "DONE";
|
|
13
|
+
})(RequestState = exports.RequestState || (exports.RequestState = {}));
|
|
14
|
+
/**
|
|
15
|
+
* I know we're not using most of the information from this class
|
|
16
|
+
* but it can be useful in the future if we will decide to display more stuff in our ui
|
|
17
|
+
* */
|
|
18
|
+
class NetworkMessage {
|
|
19
|
+
constructor(ignoredHeaders = [], setSessionTokenHeader, sanitize) {
|
|
20
|
+
this.ignoredHeaders = ignoredHeaders;
|
|
21
|
+
this.setSessionTokenHeader = setSessionTokenHeader;
|
|
22
|
+
this.sanitize = sanitize;
|
|
23
|
+
this.id = '';
|
|
24
|
+
this.name = '';
|
|
25
|
+
this.method = '';
|
|
26
|
+
this.url = '';
|
|
27
|
+
this.status = 0;
|
|
28
|
+
this.statusText = '';
|
|
29
|
+
this.cancelState = 0;
|
|
30
|
+
this.readyState = 0;
|
|
31
|
+
this.header = {};
|
|
32
|
+
this.responseType = '';
|
|
33
|
+
this.requestHeader = {};
|
|
34
|
+
this.responseSize = 0; // bytes
|
|
35
|
+
this.responseSizeText = '';
|
|
36
|
+
this.startTime = 0;
|
|
37
|
+
this.endTime = 0;
|
|
38
|
+
this.duration = 0;
|
|
39
|
+
this.getData = {};
|
|
40
|
+
this.requestData = null;
|
|
41
|
+
}
|
|
42
|
+
getMessage() {
|
|
43
|
+
const { reqHs, resHs } = this.writeHeaders();
|
|
44
|
+
const request = {
|
|
45
|
+
headers: reqHs,
|
|
46
|
+
body: this.method === 'GET' ? JSON.stringify(this.getData) : this.requestData,
|
|
47
|
+
};
|
|
48
|
+
const response = { headers: resHs, body: this.response };
|
|
49
|
+
const messageInfo = this.sanitize({
|
|
50
|
+
url: this.url,
|
|
51
|
+
method: this.method,
|
|
52
|
+
status: this.status,
|
|
53
|
+
request,
|
|
54
|
+
response,
|
|
55
|
+
});
|
|
56
|
+
return (0, messages_gen_js_1.NetworkRequest)(this.requestType, messageInfo.method, messageInfo.url, JSON.stringify(messageInfo.request), JSON.stringify(messageInfo.response), messageInfo.status, this.startTime + (0, utils_js_1.getTimeOrigin)(), this.duration);
|
|
57
|
+
}
|
|
58
|
+
writeHeaders() {
|
|
59
|
+
const reqHs = {};
|
|
60
|
+
Object.entries(this.requestHeader).forEach(([key, value]) => {
|
|
61
|
+
if (this.isHeaderIgnored(key))
|
|
62
|
+
return;
|
|
63
|
+
reqHs[key] = value;
|
|
64
|
+
});
|
|
65
|
+
this.setSessionTokenHeader((name, value) => {
|
|
66
|
+
reqHs[name] = value;
|
|
67
|
+
});
|
|
68
|
+
const resHs = {};
|
|
69
|
+
Object.entries(this.header).forEach(([key, value]) => {
|
|
70
|
+
if (this.isHeaderIgnored(key))
|
|
71
|
+
return;
|
|
72
|
+
resHs[key] = value;
|
|
73
|
+
});
|
|
74
|
+
return { reqHs, resHs };
|
|
75
|
+
}
|
|
76
|
+
isHeaderIgnored(key) {
|
|
77
|
+
if (Array.isArray(this.ignoredHeaders))
|
|
78
|
+
return this.ignoredHeaders.includes(key);
|
|
79
|
+
return this.ignoredHeaders;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.default = NetworkMessage;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface RequestResponseData {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly method: string;
|
|
4
|
+
url: string;
|
|
5
|
+
request: {
|
|
6
|
+
body: string | null;
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
};
|
|
9
|
+
response: {
|
|
10
|
+
body: string | null;
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const genResponseByType: (responseType: XMLHttpRequest['responseType'], response: any) => string | Record<string, any>;
|
|
2
|
+
export declare const getStringResponseByType: (responseType: XMLHttpRequest['responseType'], response: any) => string;
|
|
3
|
+
export declare const genStringBody: (body?: BodyInit) => string | null;
|
|
4
|
+
export declare const genGetDataByUrl: (url: string, getData?: Record<string, any>) => Record<string, any>;
|
|
5
|
+
export declare const genFormattedBody: (body?: BodyInit) => string | {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
} | null;
|
|
8
|
+
export declare function isPureObject(input: any): input is Record<any, any>;
|
|
9
|
+
export declare function isIterable(value: any): boolean;
|
|
10
|
+
export declare function formatByteSize(bytes: number): string;
|
|
11
|
+
export declare const getURL: (urlString: string) => URL;
|