@openreplay/tracker 8.1.0 → 8.1.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/cjs/app/index.js +1 -1
  3. package/cjs/index.js +1 -1
  4. package/cjs/modules/Network/fetchProxy.d.ts +34 -0
  5. package/cjs/modules/Network/fetchProxy.js +240 -0
  6. package/cjs/modules/Network/index.d.ts +3 -0
  7. package/cjs/modules/Network/index.js +9 -0
  8. package/cjs/modules/Network/networkMessage.d.ts +49 -0
  9. package/cjs/modules/Network/networkMessage.js +82 -0
  10. package/cjs/modules/Network/types.d.ts +13 -0
  11. package/cjs/modules/Network/types.js +3 -0
  12. package/cjs/modules/Network/utils.d.ts +11 -0
  13. package/cjs/modules/Network/utils.js +213 -0
  14. package/cjs/modules/Network/xhrProxy.d.ts +47 -0
  15. package/cjs/modules/Network/xhrProxy.js +209 -0
  16. package/cjs/modules/console.js +22 -14
  17. package/cjs/modules/input.d.ts +1 -0
  18. package/cjs/modules/input.js +4 -0
  19. package/cjs/modules/network.d.ts +3 -4
  20. package/cjs/modules/network.js +15 -3
  21. package/lib/app/index.js +1 -1
  22. package/lib/index.js +1 -1
  23. package/lib/modules/Network/fetchProxy.d.ts +34 -0
  24. package/lib/modules/Network/fetchProxy.js +234 -0
  25. package/lib/modules/Network/index.d.ts +3 -0
  26. package/lib/modules/Network/index.js +6 -0
  27. package/lib/modules/Network/networkMessage.d.ts +49 -0
  28. package/lib/modules/Network/networkMessage.js +78 -0
  29. package/lib/modules/Network/types.d.ts +13 -0
  30. package/lib/modules/Network/types.js +2 -0
  31. package/lib/modules/Network/utils.d.ts +11 -0
  32. package/lib/modules/Network/utils.js +201 -0
  33. package/lib/modules/Network/xhrProxy.d.ts +47 -0
  34. package/lib/modules/Network/xhrProxy.js +204 -0
  35. package/lib/modules/console.js +22 -14
  36. package/lib/modules/input.d.ts +1 -0
  37. package/lib/modules/input.js +4 -0
  38. package/lib/modules/network.d.ts +3 -4
  39. package/lib/modules/network.js +15 -3
  40. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,4 +1,6 @@
1
- # 8.1.0
1
+ # 8.1.1
2
+
3
+ [collective patch]
2
4
 
3
5
  - Console and network are now using proxy objects to capture calls (opt in for network), use ` { network: { useProxy: true } }` to enable it
4
6
  - Force disable Multitab feature for old browsers (2016 and older + safari 14)
package/cjs/app/index.js CHANGED
@@ -35,7 +35,7 @@ class App {
35
35
  this.stopCallbacks = [];
36
36
  this.commitCallbacks = [];
37
37
  this.activityState = ActivityState.NotActive;
38
- this.version = '8.1.0'; // TODO: version compatability check inside each plugin.
38
+ this.version = '8.1.2-beta.1'; // TODO: version compatability check inside each plugin.
39
39
  this.compressionThreshold = 24 * 1000;
40
40
  this.restartAttempts = 0;
41
41
  this.bc = null;
package/cjs/index.js CHANGED
@@ -151,7 +151,7 @@ class API {
151
151
  // no-cors issue only with text/plain or not-set Content-Type
152
152
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
153
153
  req.send(JSON.stringify({
154
- trackerVersion: '8.1.0',
154
+ trackerVersion: '8.1.2-beta.1',
155
155
  projectKey: options.projectKey,
156
156
  doNotTrack,
157
157
  // TODO: add precise reason (an exact API missing)
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // we only support sanitizing for json/string data because how you're gonna sanitize binary data?
@@ -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;