@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.
Files changed (111) hide show
  1. package/cjs/app/index.d.ts +6 -1
  2. package/cjs/app/index.js +22 -3
  3. package/cjs/app/observer/observer.js +2 -2
  4. package/cjs/app/session.d.ts +10 -0
  5. package/cjs/app/session.js +3 -0
  6. package/cjs/index.d.ts +11 -0
  7. package/cjs/index.js +28 -1
  8. package/cjs/modules/Network/fetchProxy.d.ts +34 -0
  9. package/cjs/modules/Network/fetchProxy.js +240 -0
  10. package/cjs/modules/Network/index.d.ts +3 -0
  11. package/cjs/modules/Network/index.js +9 -0
  12. package/cjs/modules/Network/networkMessage.d.ts +49 -0
  13. package/cjs/modules/Network/networkMessage.js +82 -0
  14. package/cjs/modules/Network/types.d.ts +13 -0
  15. package/cjs/modules/Network/types.js +3 -0
  16. package/cjs/modules/Network/utils.d.ts +11 -0
  17. package/cjs/modules/Network/utils.js +213 -0
  18. package/cjs/modules/Network/xhrProxy.d.ts +47 -0
  19. package/cjs/modules/Network/xhrProxy.js +209 -0
  20. package/cjs/modules/attributeSender.d.ts +14 -0
  21. package/cjs/modules/attributeSender.js +44 -0
  22. package/cjs/modules/console.js +21 -13
  23. package/cjs/modules/featureFlags.d.ts +25 -0
  24. package/cjs/modules/featureFlags.js +100 -0
  25. package/cjs/modules/img.js +4 -4
  26. package/cjs/modules/network.d.ts +3 -4
  27. package/cjs/modules/network.js +13 -3
  28. package/coverage/clover.xml +1412 -900
  29. package/coverage/coverage-final.json +22 -16
  30. package/coverage/lcov-report/index.html +58 -43
  31. package/coverage/lcov-report/main/app/guards.ts.html +42 -42
  32. package/coverage/lcov-report/main/app/index.html +46 -46
  33. package/coverage/lcov-report/main/app/index.ts.html +104 -8
  34. package/coverage/lcov-report/main/app/logger.ts.html +1 -1
  35. package/coverage/lcov-report/main/app/messages.gen.ts.html +146 -146
  36. package/coverage/lcov-report/main/app/observer/iframe_observer.ts.html +1 -1
  37. package/coverage/lcov-report/main/app/observer/iframe_offsets.ts.html +1 -1
  38. package/coverage/lcov-report/main/app/observer/index.html +1 -1
  39. package/coverage/lcov-report/main/app/observer/shadow_root_observer.ts.html +1 -1
  40. package/coverage/lcov-report/main/app/observer/top_observer.ts.html +1 -1
  41. package/coverage/lcov-report/main/app/sanitizer.ts.html +98 -98
  42. package/coverage/lcov-report/main/app/session.ts.html +47 -5
  43. package/coverage/lcov-report/main/app/ticker.ts.html +1 -1
  44. package/coverage/lcov-report/main/index.html +24 -24
  45. package/coverage/lcov-report/main/index.ts.html +138 -6
  46. package/coverage/lcov-report/main/modules/Network/fetchProxy.ts.html +949 -0
  47. package/coverage/lcov-report/main/{vendors/finder → modules/Network}/index.html +72 -12
  48. package/coverage/lcov-report/main/modules/Network/index.ts.html +169 -0
  49. package/coverage/lcov-report/main/{app/nodes.ts.html → modules/Network/networkMessage.ts.html} +130 -115
  50. package/coverage/lcov-report/main/modules/Network/utils.ts.html +700 -0
  51. package/coverage/lcov-report/main/modules/Network/xhrProxy.ts.html +823 -0
  52. package/coverage/lcov-report/main/modules/attributeSender.ts.html +217 -0
  53. package/coverage/lcov-report/main/modules/axiosSpy.ts.html +1 -1
  54. package/coverage/lcov-report/main/modules/connection.ts.html +1 -1
  55. package/coverage/lcov-report/main/modules/console.ts.html +174 -147
  56. package/coverage/lcov-report/main/modules/constructedStyleSheets.ts.html +1 -1
  57. package/coverage/lcov-report/main/modules/cssrules.ts.html +1 -1
  58. package/coverage/lcov-report/main/modules/exception.ts.html +1 -1
  59. package/coverage/lcov-report/main/modules/featureFlags.ts.html +415 -0
  60. package/coverage/lcov-report/main/modules/focus.ts.html +1 -1
  61. package/coverage/lcov-report/main/modules/fonts.ts.html +1 -1
  62. package/coverage/lcov-report/main/modules/img.ts.html +6 -6
  63. package/coverage/lcov-report/main/modules/index.html +54 -24
  64. package/coverage/lcov-report/main/modules/input.ts.html +1 -1
  65. package/coverage/lcov-report/main/modules/mouse.ts.html +1 -1
  66. package/coverage/lcov-report/main/modules/network.ts.html +70 -70
  67. package/coverage/lcov-report/main/modules/performance.ts.html +1 -1
  68. package/coverage/lcov-report/main/modules/scroll.ts.html +1 -1
  69. package/coverage/lcov-report/main/modules/selection.ts.html +1 -1
  70. package/coverage/lcov-report/main/modules/tabs.ts.html +1 -1
  71. package/coverage/lcov-report/main/modules/timing.ts.html +1 -1
  72. package/coverage/lcov-report/main/modules/viewport.ts.html +1 -1
  73. package/coverage/lcov-report/main/utils.ts.html +97 -97
  74. package/coverage/lcov-report/webworker/BatchWriter.ts.html +125 -176
  75. package/coverage/lcov-report/webworker/MessageEncoder.gen.ts.html +88 -88
  76. package/coverage/lcov-report/webworker/PrimitiveEncoder.ts.html +110 -110
  77. package/coverage/lcov-report/webworker/QueueSender.ts.html +140 -110
  78. package/coverage/lcov-report/webworker/index.html +56 -71
  79. package/coverage/lcov-report/webworker/index.ts.html +34 -10
  80. package/coverage/lcov.info +2524 -1552
  81. package/lib/app/index.d.ts +6 -1
  82. package/lib/app/index.js +22 -3
  83. package/lib/app/observer/observer.js +3 -3
  84. package/lib/app/session.d.ts +10 -0
  85. package/lib/app/session.js +3 -0
  86. package/lib/index.d.ts +11 -0
  87. package/lib/index.js +28 -1
  88. package/lib/modules/Network/fetchProxy.d.ts +34 -0
  89. package/lib/modules/Network/fetchProxy.js +234 -0
  90. package/lib/modules/Network/index.d.ts +3 -0
  91. package/lib/modules/Network/index.js +6 -0
  92. package/lib/modules/Network/networkMessage.d.ts +49 -0
  93. package/lib/modules/Network/networkMessage.js +78 -0
  94. package/lib/modules/Network/types.d.ts +13 -0
  95. package/lib/modules/Network/types.js +2 -0
  96. package/lib/modules/Network/utils.d.ts +11 -0
  97. package/lib/modules/Network/utils.js +201 -0
  98. package/lib/modules/Network/xhrProxy.d.ts +47 -0
  99. package/lib/modules/Network/xhrProxy.js +204 -0
  100. package/lib/modules/attributeSender.d.ts +14 -0
  101. package/lib/modules/attributeSender.js +39 -0
  102. package/lib/modules/console.js +21 -13
  103. package/lib/modules/featureFlags.d.ts +25 -0
  104. package/lib/modules/featureFlags.js +97 -0
  105. package/lib/modules/img.js +5 -5
  106. package/lib/modules/network.d.ts +3 -4
  107. package/lib/modules/network.js +13 -3
  108. package/package.json +3 -2
  109. package/coverage/lcov-report/main/app/observer/observer.ts.html +0 -1282
  110. package/coverage/lcov-report/main/vendors/finder/finder.ts.html +0 -1381
  111. package/coverage/lcov-report/webworker/StringDictionary.ts.html +0 -124
@@ -0,0 +1,78 @@
1
+ import { NetworkRequest } from '../../app/messages.gen.js';
2
+ import { getTimeOrigin } from '../../utils.js';
3
+ export var RequestState;
4
+ (function (RequestState) {
5
+ RequestState[RequestState["UNSENT"] = 0] = "UNSENT";
6
+ RequestState[RequestState["OPENED"] = 1] = "OPENED";
7
+ RequestState[RequestState["HEADERS_RECEIVED"] = 2] = "HEADERS_RECEIVED";
8
+ RequestState[RequestState["LOADING"] = 3] = "LOADING";
9
+ RequestState[RequestState["DONE"] = 4] = "DONE";
10
+ })(RequestState || (RequestState = {}));
11
+ /**
12
+ * I know we're not using most of the information from this class
13
+ * but it can be useful in the future if we will decide to display more stuff in our ui
14
+ * */
15
+ export default class NetworkMessage {
16
+ constructor(ignoredHeaders = [], setSessionTokenHeader, sanitize) {
17
+ this.ignoredHeaders = ignoredHeaders;
18
+ this.setSessionTokenHeader = setSessionTokenHeader;
19
+ this.sanitize = sanitize;
20
+ this.id = '';
21
+ this.name = '';
22
+ this.method = '';
23
+ this.url = '';
24
+ this.status = 0;
25
+ this.statusText = '';
26
+ this.cancelState = 0;
27
+ this.readyState = 0;
28
+ this.header = {};
29
+ this.responseType = '';
30
+ this.requestHeader = {};
31
+ this.responseSize = 0; // bytes
32
+ this.responseSizeText = '';
33
+ this.startTime = 0;
34
+ this.endTime = 0;
35
+ this.duration = 0;
36
+ this.getData = {};
37
+ this.requestData = null;
38
+ }
39
+ getMessage() {
40
+ const { reqHs, resHs } = this.writeHeaders();
41
+ const request = {
42
+ headers: reqHs,
43
+ body: this.method === 'GET' ? JSON.stringify(this.getData) : this.requestData,
44
+ };
45
+ const response = { headers: resHs, body: this.response };
46
+ const messageInfo = this.sanitize({
47
+ url: this.url,
48
+ method: this.method,
49
+ status: this.status,
50
+ request,
51
+ response,
52
+ });
53
+ return NetworkRequest(this.requestType, messageInfo.method, messageInfo.url, JSON.stringify(messageInfo.request), JSON.stringify(messageInfo.response), messageInfo.status, this.startTime + getTimeOrigin(), this.duration);
54
+ }
55
+ writeHeaders() {
56
+ const reqHs = {};
57
+ Object.entries(this.requestHeader).forEach(([key, value]) => {
58
+ if (this.isHeaderIgnored(key))
59
+ return;
60
+ reqHs[key] = value;
61
+ });
62
+ this.setSessionTokenHeader((name, value) => {
63
+ reqHs[name] = value;
64
+ });
65
+ const resHs = {};
66
+ Object.entries(this.header).forEach(([key, value]) => {
67
+ if (this.isHeaderIgnored(key))
68
+ return;
69
+ resHs[key] = value;
70
+ });
71
+ return { reqHs, resHs };
72
+ }
73
+ isHeaderIgnored(key) {
74
+ if (Array.isArray(this.ignoredHeaders))
75
+ return this.ignoredHeaders.includes(key);
76
+ return this.ignoredHeaders;
77
+ }
78
+ }
@@ -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,2 @@
1
+ export {};
2
+ // 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;
@@ -0,0 +1,201 @@
1
+ export const genResponseByType = (responseType, response) => {
2
+ let result = '';
3
+ switch (responseType) {
4
+ case '':
5
+ case 'text':
6
+ case 'json':
7
+ if (typeof response == 'string') {
8
+ try {
9
+ result = JSON.parse(response);
10
+ }
11
+ catch (e) {
12
+ // not a JSON string
13
+ result = response.slice(0, 10000);
14
+ }
15
+ }
16
+ else if (isPureObject(response) || Array.isArray(response)) {
17
+ result = JSON.stringify(response);
18
+ }
19
+ else if (typeof response !== 'undefined') {
20
+ result = Object.prototype.toString.call(response);
21
+ }
22
+ break;
23
+ case 'blob':
24
+ case 'document':
25
+ case 'arraybuffer':
26
+ default:
27
+ if (typeof response !== 'undefined') {
28
+ result = Object.prototype.toString.call(response);
29
+ }
30
+ break;
31
+ }
32
+ return result;
33
+ };
34
+ export const getStringResponseByType = (responseType, response) => {
35
+ let result = '';
36
+ switch (responseType) {
37
+ case '':
38
+ case 'text':
39
+ case 'json':
40
+ if (typeof response == 'string') {
41
+ result = response;
42
+ }
43
+ else if (isPureObject(response) || Array.isArray(response)) {
44
+ result = JSON.stringify(response);
45
+ }
46
+ else if (typeof response !== 'undefined') {
47
+ result = Object.prototype.toString.call(response);
48
+ }
49
+ break;
50
+ case 'blob':
51
+ case 'document':
52
+ case 'arraybuffer':
53
+ default:
54
+ if (typeof response !== 'undefined') {
55
+ result = Object.prototype.toString.call(response);
56
+ }
57
+ break;
58
+ }
59
+ return result;
60
+ };
61
+ export const genStringBody = (body) => {
62
+ if (!body) {
63
+ return null;
64
+ }
65
+ let result;
66
+ if (typeof body === 'string') {
67
+ if (body[0] === '{' || body[0] === '[') {
68
+ result = body;
69
+ }
70
+ // 'a=1&b=2' => try to parse as query
71
+ const arr = body.split('&');
72
+ if (arr.length === 1) {
73
+ // not a query, parse as original string
74
+ result = body;
75
+ }
76
+ else {
77
+ // 'a=1&b=2&c' => parse as query
78
+ result = arr.join(',');
79
+ }
80
+ }
81
+ else if (isIterable(body)) {
82
+ // FormData or URLSearchParams or Array
83
+ const arr = [];
84
+ for (const [key, value] of body) {
85
+ arr.push(`${key}=${typeof value === 'string' ? value : '[object Object]'}`);
86
+ }
87
+ result = arr.join(',');
88
+ }
89
+ else if (body instanceof Blob ||
90
+ body instanceof ReadableStream ||
91
+ body instanceof ArrayBuffer) {
92
+ result = 'byte data';
93
+ }
94
+ else if (isPureObject(body)) {
95
+ // overriding ArrayBufferView which is not convertable to string
96
+ result = body;
97
+ }
98
+ else {
99
+ result = `can't parse body ${typeof body}`;
100
+ }
101
+ return result;
102
+ };
103
+ export const genGetDataByUrl = (url, getData = {}) => {
104
+ if (!isPureObject(getData)) {
105
+ getData = {};
106
+ }
107
+ let query = url ? url.split('?') : []; // a.php?b=c&d=?e => ['a.php', 'b=c&d=', 'e']
108
+ query.shift(); // => ['b=c&d=', 'e']
109
+ if (query.length > 0) {
110
+ query = query.join('?').split('&'); // => 'b=c&d=?e' => ['b=c', 'd=?e']
111
+ for (const q of query) {
112
+ const kv = q.split('=');
113
+ try {
114
+ getData[kv[0]] = decodeURIComponent(kv[1]);
115
+ }
116
+ catch (e) {
117
+ // "URIError: URI malformed" will be thrown when `kv[1]` contains "%", so just use raw data
118
+ // @issue #470
119
+ // @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Malformed_URI
120
+ getData[kv[0]] = kv[1];
121
+ }
122
+ }
123
+ }
124
+ return getData;
125
+ };
126
+ export const genFormattedBody = (body) => {
127
+ if (!body) {
128
+ return null;
129
+ }
130
+ let result;
131
+ if (typeof body === 'string') {
132
+ try {
133
+ // '{a:1}' =>
134
+ result = JSON.parse(body);
135
+ }
136
+ catch (e) {
137
+ // 'a=1&b=2' => try to parse as query
138
+ const arr = body.split('&');
139
+ result = {};
140
+ // eslint-disable-next-line
141
+ for (let q of arr) {
142
+ const kv = q.split('=');
143
+ result[kv[0]] = kv[1] === undefined ? 'undefined' : kv[1];
144
+ }
145
+ }
146
+ }
147
+ else if (isIterable(body)) {
148
+ // FormData or URLSearchParams or Array
149
+ result = {};
150
+ for (const [key, value] of body) {
151
+ result[key] = typeof value === 'string' ? value : '[object Object]';
152
+ }
153
+ }
154
+ else if (body instanceof Blob ||
155
+ body instanceof ReadableStream ||
156
+ body instanceof ArrayBuffer) {
157
+ result = 'byte data';
158
+ }
159
+ else if (isPureObject(body)) {
160
+ // overriding ArrayBufferView which is not convertable to string
161
+ result = body;
162
+ }
163
+ else {
164
+ result = `can't parse body ${typeof body}`;
165
+ }
166
+ return result;
167
+ };
168
+ export function isPureObject(input) {
169
+ return null !== input && typeof input === 'object';
170
+ }
171
+ export function isIterable(value) {
172
+ if (value === null || value === undefined) {
173
+ return false;
174
+ }
175
+ return typeof Symbol !== 'undefined' && typeof value[Symbol.iterator] === 'function';
176
+ }
177
+ export function formatByteSize(bytes) {
178
+ if (bytes <= 0) {
179
+ // shouldn't happen?
180
+ return '';
181
+ }
182
+ if (bytes >= 1000 * 1000) {
183
+ return (bytes / 1000 / 1000).toFixed(1) + ' MB';
184
+ }
185
+ if (bytes >= 1000) {
186
+ return (bytes / 1000).toFixed(1) + ' KB';
187
+ }
188
+ return `${bytes}B`;
189
+ }
190
+ export const getURL = (urlString) => {
191
+ if (urlString.startsWith('//')) {
192
+ const baseUrl = new URL(window.location.href);
193
+ urlString = `${baseUrl.protocol}${urlString}`;
194
+ }
195
+ if (urlString.startsWith('http')) {
196
+ return new URL(urlString);
197
+ }
198
+ else {
199
+ return new URL(urlString, window.location.href);
200
+ }
201
+ };
@@ -0,0 +1,47 @@
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 XHRProxyHandler<T extends XMLHttpRequest> implements ProxyHandler<T> {
12
+ private readonly ignoredHeaders;
13
+ private readonly setSessionTokenHeader;
14
+ private readonly sanitize;
15
+ private readonly sendMessage;
16
+ private readonly isServiceUrl;
17
+ XMLReq: XMLHttpRequest;
18
+ item: NetworkMessage;
19
+ constructor(XMLReq: XMLHttpRequest, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean);
20
+ get(target: T, key: string): any;
21
+ set(target: T, key: string, value: (args: any[]) => any): boolean;
22
+ onReadyStateChange(): void;
23
+ onAbort(): void;
24
+ onTimeout(): void;
25
+ protected getOpen(target: T): (...args: any[]) => any;
26
+ protected getSend(target: T): (...args: any[]) => any;
27
+ protected getSetRequestHeader(target: T): (...args: any[]) => any;
28
+ protected setOnReadyStateChange(target: T, key: string, orscFunction: (args: any[]) => any): boolean;
29
+ protected setOnAbort(target: T, key: string, oaFunction: (args: any[]) => any): boolean;
30
+ protected setOnTimeout(target: T, key: string, otFunction: (args: any[]) => any): boolean;
31
+ /**
32
+ * Update item's properties according to readyState.
33
+ */
34
+ protected updateItemByReadyState(): void;
35
+ }
36
+ export default class XHRProxy {
37
+ static origXMLHttpRequest: {
38
+ new (): XMLHttpRequest;
39
+ prototype: XMLHttpRequest;
40
+ readonly DONE: number;
41
+ readonly HEADERS_RECEIVED: number;
42
+ readonly LOADING: number;
43
+ readonly OPENED: number;
44
+ readonly UNSENT: number;
45
+ };
46
+ static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (data: NetworkRequest) => void, isServiceUrl: (url: string) => boolean): any;
47
+ }
@@ -0,0 +1,204 @@
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 { genGetDataByUrl, formatByteSize, genStringBody, getStringResponseByType } from './utils.js';
10
+ export class XHRProxyHandler {
11
+ constructor(XMLReq, ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
12
+ this.ignoredHeaders = ignoredHeaders;
13
+ this.setSessionTokenHeader = setSessionTokenHeader;
14
+ this.sanitize = sanitize;
15
+ this.sendMessage = sendMessage;
16
+ this.isServiceUrl = isServiceUrl;
17
+ this.XMLReq = XMLReq;
18
+ this.XMLReq.onreadystatechange = () => {
19
+ this.onReadyStateChange();
20
+ };
21
+ this.XMLReq.onabort = () => {
22
+ this.onAbort();
23
+ };
24
+ this.XMLReq.ontimeout = () => {
25
+ this.onTimeout();
26
+ };
27
+ this.item = new NetworkMessage(ignoredHeaders, setSessionTokenHeader, sanitize);
28
+ this.item.requestType = 'xhr';
29
+ }
30
+ get(target, key) {
31
+ switch (key) {
32
+ case 'open':
33
+ return this.getOpen(target);
34
+ case 'send':
35
+ return this.getSend(target);
36
+ case 'setRequestHeader':
37
+ return this.getSetRequestHeader(target);
38
+ default:
39
+ // eslint-disable-next-line no-case-declarations
40
+ const value = Reflect.get(target, key);
41
+ if (typeof value === 'function') {
42
+ return value.bind(target);
43
+ }
44
+ else {
45
+ return value;
46
+ }
47
+ }
48
+ }
49
+ set(target, key, value) {
50
+ switch (key) {
51
+ case 'onreadystatechange':
52
+ return this.setOnReadyStateChange(target, key, value);
53
+ case 'onabort':
54
+ return this.setOnAbort(target, key, value);
55
+ case 'ontimeout':
56
+ return this.setOnTimeout(target, key, value);
57
+ default:
58
+ // not tracked methods
59
+ }
60
+ return Reflect.set(target, key, value);
61
+ }
62
+ onReadyStateChange() {
63
+ if (this.item.url && this.isServiceUrl(this.item.url))
64
+ return;
65
+ this.item.readyState = this.XMLReq.readyState;
66
+ this.item.responseType = this.XMLReq.responseType;
67
+ this.item.endTime = performance.now();
68
+ this.item.duration = this.item.endTime - this.item.startTime;
69
+ this.updateItemByReadyState();
70
+ setTimeout(() => {
71
+ this.item.response = getStringResponseByType(this.item.responseType, this.item.response);
72
+ }, 0);
73
+ if (this.XMLReq.readyState === RequestState.DONE) {
74
+ this.sendMessage(this.item.getMessage());
75
+ }
76
+ }
77
+ onAbort() {
78
+ this.item.cancelState = 1;
79
+ this.item.statusText = 'Abort';
80
+ this.sendMessage(this.item.getMessage());
81
+ }
82
+ onTimeout() {
83
+ this.item.cancelState = 3;
84
+ this.item.statusText = 'Timeout';
85
+ this.sendMessage(this.item.getMessage());
86
+ }
87
+ getOpen(target) {
88
+ const targetFunction = Reflect.get(target, 'open');
89
+ return (...args) => {
90
+ const method = args[0];
91
+ const url = args[1];
92
+ this.item.method = method ? method.toUpperCase() : 'GET';
93
+ this.item.url = url || '';
94
+ this.item.name = this.item.url.replace(new RegExp('/*$'), '').split('/').pop() || '';
95
+ this.item.getData = genGetDataByUrl(this.item.url, {});
96
+ return targetFunction.apply(target, args);
97
+ };
98
+ }
99
+ getSend(target) {
100
+ const targetFunction = Reflect.get(target, 'send');
101
+ return (...args) => {
102
+ const data = args[0];
103
+ this.item.requestData = genStringBody(data);
104
+ return targetFunction.apply(target, args);
105
+ };
106
+ }
107
+ getSetRequestHeader(target) {
108
+ const targetFunction = Reflect.get(target, 'setRequestHeader');
109
+ return (...args) => {
110
+ if (!this.item.requestHeader) {
111
+ this.item.requestHeader = {};
112
+ }
113
+ // @ts-ignore
114
+ this.item.requestHeader[args[0]] = args[1];
115
+ return targetFunction.apply(target, args);
116
+ };
117
+ }
118
+ setOnReadyStateChange(target, key, orscFunction) {
119
+ return Reflect.set(target, key, (...args) => {
120
+ this.onReadyStateChange();
121
+ orscFunction.apply(target, args);
122
+ });
123
+ }
124
+ setOnAbort(target, key, oaFunction) {
125
+ return Reflect.set(target, key, (...args) => {
126
+ this.onAbort();
127
+ oaFunction.apply(target, args);
128
+ });
129
+ }
130
+ setOnTimeout(target, key, otFunction) {
131
+ return Reflect.set(target, key, (...args) => {
132
+ this.onTimeout();
133
+ otFunction.apply(target, args);
134
+ });
135
+ }
136
+ /**
137
+ * Update item's properties according to readyState.
138
+ */
139
+ updateItemByReadyState() {
140
+ switch (this.XMLReq.readyState) {
141
+ case RequestState.UNSENT:
142
+ case RequestState.OPENED:
143
+ this.item.status = RequestState.UNSENT;
144
+ this.item.statusText = 'Pending';
145
+ if (!this.item.startTime) {
146
+ this.item.startTime = performance.now();
147
+ }
148
+ break;
149
+ case RequestState.HEADERS_RECEIVED:
150
+ this.item.status = this.XMLReq.status;
151
+ this.item.statusText = 'Loading';
152
+ this.item.header = {};
153
+ // eslint-disable-next-line no-case-declarations
154
+ const header = this.XMLReq.getAllResponseHeaders() || '', headerArr = header.split('\n');
155
+ // extract plain text to key-value format
156
+ for (let i = 0; i < headerArr.length; i++) {
157
+ const line = headerArr[i];
158
+ if (!line) {
159
+ continue;
160
+ }
161
+ const arr = line.split(': ');
162
+ const key = arr[0];
163
+ this.item.header[key] = arr.slice(1).join(': ');
164
+ }
165
+ break;
166
+ case RequestState.LOADING:
167
+ this.item.status = this.XMLReq.status;
168
+ this.item.statusText = 'Loading';
169
+ if (!!this.XMLReq.response && this.XMLReq.response.length) {
170
+ this.item.responseSize = this.XMLReq.response.length;
171
+ this.item.responseSizeText = formatByteSize(this.item.responseSize);
172
+ }
173
+ break;
174
+ case RequestState.DONE:
175
+ // `XMLReq.abort()` will change `status` from 200 to 0, so use previous value in this case
176
+ this.item.status = this.XMLReq.status || this.item.status || 0;
177
+ // show status code when request completed
178
+ this.item.statusText = String(this.item.status);
179
+ this.item.endTime = performance.now();
180
+ this.item.duration = this.item.endTime - (this.item.startTime || this.item.endTime);
181
+ this.item.response = this.XMLReq.response;
182
+ if (!!this.XMLReq.response && this.XMLReq.response.length) {
183
+ this.item.responseSize = this.XMLReq.response.length;
184
+ this.item.responseSizeText = formatByteSize(this.item.responseSize);
185
+ }
186
+ break;
187
+ default:
188
+ this.item.status = this.XMLReq.status;
189
+ this.item.statusText = 'Unknown';
190
+ break;
191
+ }
192
+ }
193
+ }
194
+ export default class XHRProxy {
195
+ static create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
196
+ return new Proxy(XMLHttpRequest, {
197
+ construct(original) {
198
+ const XMLReq = new original();
199
+ return new Proxy(XMLReq, new XHRProxyHandler(XMLReq, ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl));
200
+ },
201
+ });
202
+ }
203
+ }
204
+ XHRProxy.origXMLHttpRequest = XMLHttpRequest;
@@ -0,0 +1,14 @@
1
+ import App from '../app/index.js';
2
+ export declare class StringDictionary {
3
+ private idx;
4
+ private backDict;
5
+ getKey(str: string): [number, boolean];
6
+ }
7
+ export default class AttributeSender {
8
+ private readonly app;
9
+ private dict;
10
+ constructor(app: App);
11
+ sendSetAttribute(id: number, name: string, value: string): void;
12
+ private applyDict;
13
+ clear(): void;
14
+ }
@@ -0,0 +1,39 @@
1
+ export class StringDictionary {
2
+ constructor() {
3
+ this.idx = 1;
4
+ this.backDict = {};
5
+ }
6
+ getKey(str) {
7
+ let isNew = false;
8
+ if (!this.backDict[str]) {
9
+ isNew = true;
10
+ this.backDict[str] = this.idx++;
11
+ }
12
+ return [this.backDict[str], isNew];
13
+ }
14
+ }
15
+ export default class AttributeSender {
16
+ constructor(app) {
17
+ this.app = app;
18
+ this.dict = new StringDictionary();
19
+ }
20
+ sendSetAttribute(id, name, value) {
21
+ const message = [
22
+ 51 /* Type.SetNodeAttributeDict */,
23
+ id,
24
+ this.applyDict(name),
25
+ this.applyDict(value),
26
+ ];
27
+ this.app.send(message);
28
+ }
29
+ applyDict(str) {
30
+ const [key, isNew] = this.dict.getKey(str);
31
+ if (isNew) {
32
+ this.app.send([50 /* Type.StringDict */, key, str]);
33
+ }
34
+ return key;
35
+ }
36
+ clear() {
37
+ this.dict = new StringDictionary();
38
+ }
39
+ }
@@ -90,26 +90,34 @@ export default function (app, opts) {
90
90
  return;
91
91
  }
92
92
  const sendConsoleLog = app.safe((level, args) => app.send(ConsoleLog(level, printf(args))));
93
- let n;
93
+ let n = 0;
94
94
  const reset = () => {
95
95
  n = 0;
96
96
  };
97
97
  app.attachStartCallback(reset);
98
98
  app.ticker.attach(reset, 33, false);
99
- const patchConsole = (console) => options.consoleMethods.forEach((method) => {
100
- if (consoleMethods.indexOf(method) === -1) {
101
- app.debug.error(`OpenReplay: unsupported console method "${method}"`);
102
- return;
103
- }
104
- const fn = console[method];
105
- console[method] = function (...args) {
106
- fn.apply(this, args);
107
- if (n++ > options.consoleThrottling) {
99
+ const patchConsole = (console) => {
100
+ const handler = {
101
+ apply: function (target, thisArg, argumentsList) {
102
+ target.apply(console, argumentsList);
103
+ n = n + 1;
104
+ if (n > options.consoleThrottling) {
105
+ return;
106
+ }
107
+ else {
108
+ sendConsoleLog(target.name, argumentsList);
109
+ }
110
+ },
111
+ };
112
+ options.consoleMethods.forEach((method) => {
113
+ if (consoleMethods.indexOf(method) === -1) {
114
+ app.debug.error(`OpenReplay: unsupported console method "${method}"`);
108
115
  return;
109
116
  }
110
- sendConsoleLog(method, args);
111
- };
112
- });
117
+ const fn = console[method];
118
+ console[method] = new Proxy(fn, handler);
119
+ });
120
+ };
113
121
  const patchContext = app.safe((context) => patchConsole(context.console));
114
122
  patchContext(window);
115
123
  app.observer.attachContextCallback(patchContext);