@wutiange/log-listener-plugin 2.0.1-alpha.2 → 2.0.1-alpha.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,339 +1,339 @@
1
- import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
2
- import BlobFileReader from 'react-native/Libraries/Blob/FileReader';
3
- import {Blob} from 'buffer';
4
- import { createClassWithErrorHandling, formDataToString } from './utils';
5
-
6
- type StartNetworkLoggingOptions = {
7
- /** List of hosts to ignore, e.g. `services.test.com` */
8
- ignoredHosts?: string[];
9
- /** List of urls to ignore, e.g. `https://services.test.com/test` */
10
- ignoredUrls?: string[];
11
- /**
12
- * List of url patterns to ignore, e.g. `/^GET https://test.com\/pages\/.*$/`
13
- *
14
- * Url to match with is in the format: `${method} ${url}`, e.g. `GET https://test.com/pages/123`
15
- */
16
- ignoredPatterns?: RegExp[];
17
- /**
18
- * Force the network logger to start even if another program is using the network interceptor
19
- * e.g. a dev/debuging program
20
- */
21
- forceEnable?: boolean;
22
- };
23
-
24
- interface HttpRequestInfo {
25
- id: string;
26
- method: RequestMethod;
27
- url: string;
28
- timeout: number;
29
- requestHeaders: Record<string, string>;
30
- requestData: any | null;
31
- startTime: number;
32
- endTime: number;
33
- responseHeaders: Headers;
34
- responseData: any | null;
35
- status: number;
36
- duration: number;
37
- responseContentType: string;
38
- responseSize: number;
39
- responseURL: string;
40
- responseType: string;
41
- }
42
-
43
- type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
44
-
45
- type XHR = {
46
- uniqueId: string;
47
- responseHeaders?: Headers;
48
- };
49
-
50
- type EventName =
51
- | 'open'
52
- | 'requestHeader'
53
- | 'headerReceived'
54
- | 'send'
55
- | 'response';
56
-
57
- const extractHost = (url: string) => {
58
- const host = url.split('//')[1]?.split(':')[0]?.split('/')[0] || undefined;
59
-
60
- return host;
61
- };
62
-
63
- const generateUniqueId = () => {
64
- return Date.now().toString(36) + Math.random().toString(36).substr(2);
65
- };
66
-
67
- const parseResponseBlob = async (response: Blob) => {
68
- const blobReader = new BlobFileReader();
69
- blobReader.readAsText(response);
70
-
71
- return await new Promise<string>((resolve, reject) => {
72
- const handleError = () => reject(blobReader.error);
73
-
74
- blobReader.addEventListener('load', () => {
75
- resolve(blobReader.result);
76
- });
77
- blobReader.addEventListener('error', handleError);
78
- blobReader.addEventListener('abort', handleError);
79
- });
80
- };
81
-
82
- const getResponseBody = async (responseType: string, response: any) => {
83
- try {
84
- if (responseType === 'blob' && response) {
85
- return await parseResponseBlob(response as unknown as Blob);
86
- }
87
- return response ?? null;
88
- } catch (error) {
89
- console.warn("getResponseBody---error---", error)
90
- return null;
91
- }
92
- };
93
-
94
- class HTTPInterceptor {
95
- private static _index = 0;
96
- private ignoredHosts: Set<string> | undefined;
97
- private ignoredUrls: Set<string> | undefined;
98
- private ignoredPatterns: RegExp[] | undefined;
99
- // 只保存正在请求中的
100
- private allRequests = new Map<string, Partial<HttpRequestInfo>>();
101
-
102
- private userListeners: [
103
- EventName,
104
- (data: Partial<HttpRequestInfo>) => Promise<void> | void,
105
- ][] = [];
106
-
107
- private enabled = false;
108
-
109
- addListener = (
110
- eventName: EventName,
111
- listener: (data: Partial<HttpRequestInfo>) => Promise<void> | void,
112
- ) => {
113
- // 如果之前已经订阅过了就过滤掉
114
- if (
115
- this.userListeners.find(
116
- ([name, tempListener]) =>
117
- name === eventName && tempListener === listener,
118
- )
119
- ) {
120
- return;
121
- }
122
- this.userListeners.push([eventName, listener]);
123
-
124
- return () => {
125
- this.userListeners = this.userListeners.filter(
126
- ([name, tempListener]) =>
127
- name !== eventName || tempListener !== listener,
128
- );
129
- };
130
- };
131
-
132
- removeListener = (
133
- eventName: EventName,
134
- listener: (data: Partial<HttpRequestInfo>) => Promise<void> | void,
135
- ) => {
136
- this.userListeners = this.userListeners.filter(
137
- ([name, tempListener]) => name !== eventName || tempListener !== listener,
138
- );
139
- };
140
-
141
- removeAllListener() {
142
- this.userListeners = [];
143
- }
144
-
145
- private listenerHandle = (
146
- eventName: EventName,
147
- data: Partial<HttpRequestInfo>,
148
- ) => {
149
- this.userListeners.forEach(async ([name, listener]) => {
150
- try {
151
- if (name === eventName) {
152
- await listener(data);
153
- }
154
- } catch (error: any) {
155
- console.warn(`eventName=${eventName}, error=${error?.message}`)
156
- }
157
- });
158
- };
159
-
160
- private openHandle = (method: RequestMethod, url: string, xhr: XHR) => {
161
- if (this.ignoredHosts) {
162
- const host = extractHost(url);
163
- if (host && this.ignoredHosts.has(host)) {
164
- return;
165
- }
166
- }
167
- if (this.ignoredUrls && this.ignoredUrls.has(url)) {
168
- return;
169
- }
170
-
171
- if (this.ignoredPatterns) {
172
- if (
173
- this.ignoredPatterns.some(pattern => pattern.test(`${method} ${url}`))
174
- ) {
175
- return;
176
- }
177
- }
178
- xhr.uniqueId = HTTPInterceptor._index + generateUniqueId();
179
- const newRequest = {
180
- id: xhr.uniqueId,
181
- method,
182
- url,
183
- };
184
- this.allRequests.set(xhr.uniqueId, newRequest);
185
- this.listenerHandle('open', newRequest);
186
- };
187
-
188
- private requestHeaderHandle = (header: string, value: string, xhr: XHR) => {
189
- const currentRequest = this.allRequests.get(xhr.uniqueId);
190
- if (!currentRequest) {
191
- return;
192
- }
193
- if (!currentRequest.requestHeaders) {
194
- currentRequest.requestHeaders = {};
195
- }
196
- currentRequest.requestHeaders[header] = value;
197
- this.listenerHandle('requestHeader', currentRequest);
198
- };
199
-
200
- private headerReceivedHandle = (
201
- responseContentType: string,
202
- responseSize: number,
203
- responseHeaders: Headers,
204
- xhr: XHR,
205
- ) => {
206
- const currentRequest = this.allRequests.get(xhr.uniqueId);
207
- if (!currentRequest) {
208
- return;
209
- }
210
- currentRequest.responseContentType = responseContentType;
211
- currentRequest.responseSize = responseSize;
212
- currentRequest.responseHeaders = xhr.responseHeaders;
213
- this.listenerHandle('headerReceived', currentRequest);
214
- };
215
-
216
- private responseHandle = async (
217
- status: number,
218
- timeout: number,
219
- response: any,
220
- responseURL: string,
221
- responseType: string,
222
- xhr: XHR,
223
- ) => {
224
- const currentRequest = this.allRequests.get(xhr.uniqueId);
225
- if (!currentRequest) {
226
- return;
227
- }
228
- currentRequest.endTime = Date.now();
229
- currentRequest.status = status;
230
- currentRequest.timeout = timeout;
231
- currentRequest.responseData = await getResponseBody(responseType, response);
232
- currentRequest.responseURL = responseURL;
233
- currentRequest.responseType = responseType;
234
- currentRequest.duration =
235
- currentRequest.endTime - (currentRequest.startTime ?? 0);
236
- this.listenerHandle('response', currentRequest);
237
- this.allRequests.delete(xhr.uniqueId);
238
- };
239
-
240
- private sendHandle = (data: any, xhr: XHR) => {
241
- const currentRequest = this.allRequests.get(xhr.uniqueId);
242
- if (!currentRequest) {
243
- return;
244
- }
245
- try {
246
- if (data && typeof data === 'object' && data instanceof FormData) {
247
- currentRequest.requestData = formDataToString(data);
248
- } else {
249
- currentRequest.requestData = JSON.parse(data);
250
- }
251
- } catch (error) {
252
- currentRequest.requestData = null;
253
- }
254
- currentRequest.startTime = Date.now();
255
- this.listenerHandle('send', currentRequest);
256
- };
257
-
258
- setIgnoredUrls = (ignoredUrls: string[]) => {
259
- if (ignoredUrls?.length) {
260
- if (
261
- !Array.isArray(ignoredUrls) ||
262
- typeof ignoredUrls[0] !== 'string'
263
- ) {
264
- console.warn(
265
- 'ignoredUrls must be an array of strings. The logger has not been started.',
266
- );
267
- return;
268
- }
269
- this.ignoredUrls = new Set(ignoredUrls);
270
- }
271
- }
272
-
273
- enable = (options?: StartNetworkLoggingOptions) => {
274
- try {
275
- if (
276
- this.enabled ||
277
- (XHRInterceptor.isInterceptorEnabled() && !options?.forceEnable)
278
- ) {
279
- if (!this.enabled) {
280
- console.warn(
281
- 'network interceptor has not been enabled as another interceptor is already running (e.g. another debugging program). Use option `forceEnable: true` to override this behaviour.',
282
- );
283
- }
284
- return;
285
- }
286
-
287
- if (options?.ignoredHosts) {
288
- if (
289
- !Array.isArray(options.ignoredHosts) ||
290
- typeof options.ignoredHosts[0] !== 'string'
291
- ) {
292
- console.warn(
293
- 'ignoredHosts must be an array of strings. The logger has not been started.',
294
- );
295
- return;
296
- }
297
- this.ignoredHosts = new Set(options.ignoredHosts);
298
- }
299
-
300
- if (options?.ignoredPatterns) {
301
- this.ignoredPatterns = options.ignoredPatterns;
302
- }
303
- this.setIgnoredUrls(options?.ignoredUrls)
304
- XHRInterceptor.setOpenCallback(this.openHandle);
305
- XHRInterceptor.setRequestHeaderCallback(this.requestHeaderHandle);
306
- XHRInterceptor.setHeaderReceivedCallback(this.headerReceivedHandle);
307
- XHRInterceptor.setSendCallback(this.sendHandle);
308
- XHRInterceptor.setResponseCallback(this.responseHandle);
309
- XHRInterceptor.enableInterception();
310
- this.enabled = true;
311
- } catch (error) {}
312
- };
313
-
314
- disable = () => {
315
- if (!this.enabled) {
316
- return;
317
- }
318
- XHRInterceptor.disableInterception();
319
- this.enabled = false;
320
- };
321
-
322
- reset = () => {
323
- this.disable();
324
- this.removeAllListener();
325
- this.ignoredHosts = undefined;
326
- this.ignoredUrls = undefined;
327
- this.ignoredPatterns = undefined;
328
- this.allRequests.clear();
329
- };
330
- }
331
-
332
- const SafeHTTPInterceptor = createClassWithErrorHandling(HTTPInterceptor)
333
- const httpInterceptor = new SafeHTTPInterceptor();
334
- export {
335
- type StartNetworkLoggingOptions,
336
- httpInterceptor,
337
- type EventName,
338
- type RequestMethod,
339
- };
1
+ import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
2
+ import BlobFileReader from 'react-native/Libraries/Blob/FileReader';
3
+ import {Blob} from 'buffer';
4
+ import { createClassWithErrorHandling, formDataToString } from './utils';
5
+
6
+ type StartNetworkLoggingOptions = {
7
+ /** List of hosts to ignore, e.g. `services.test.com` */
8
+ ignoredHosts?: string[];
9
+ /** List of urls to ignore, e.g. `https://services.test.com/test` */
10
+ ignoredUrls?: string[];
11
+ /**
12
+ * List of url patterns to ignore, e.g. `/^GET https://test.com\/pages\/.*$/`
13
+ *
14
+ * Url to match with is in the format: `${method} ${url}`, e.g. `GET https://test.com/pages/123`
15
+ */
16
+ ignoredPatterns?: RegExp[];
17
+ /**
18
+ * Force the network logger to start even if another program is using the network interceptor
19
+ * e.g. a dev/debuging program
20
+ */
21
+ forceEnable?: boolean;
22
+ };
23
+
24
+ interface HttpRequestInfo {
25
+ id: string;
26
+ method: RequestMethod;
27
+ url: string;
28
+ timeout: number;
29
+ requestHeaders: Record<string, string>;
30
+ requestData: any | null;
31
+ startTime: number;
32
+ endTime: number;
33
+ responseHeaders: Headers;
34
+ responseData: any | null;
35
+ status: number;
36
+ duration: number;
37
+ responseContentType: string;
38
+ responseSize: number;
39
+ responseURL: string;
40
+ responseType: string;
41
+ }
42
+
43
+ type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
44
+
45
+ type XHR = {
46
+ uniqueId: string;
47
+ responseHeaders?: Headers;
48
+ };
49
+
50
+ type EventName =
51
+ | 'open'
52
+ | 'requestHeader'
53
+ | 'headerReceived'
54
+ | 'send'
55
+ | 'response';
56
+
57
+ const extractHost = (url: string) => {
58
+ const host = url.split('//')[1]?.split(':')[0]?.split('/')[0] || undefined;
59
+
60
+ return host;
61
+ };
62
+
63
+ const generateUniqueId = () => {
64
+ return Date.now().toString(36) + Math.random().toString(36).substr(2);
65
+ };
66
+
67
+ const parseResponseBlob = async (response: Blob) => {
68
+ const blobReader = new BlobFileReader();
69
+ blobReader.readAsText(response);
70
+
71
+ return await new Promise<string>((resolve, reject) => {
72
+ const handleError = () => reject(blobReader.error);
73
+
74
+ blobReader.addEventListener('load', () => {
75
+ resolve(blobReader.result);
76
+ });
77
+ blobReader.addEventListener('error', handleError);
78
+ blobReader.addEventListener('abort', handleError);
79
+ });
80
+ };
81
+
82
+ const getResponseBody = async (responseType: string, response: any) => {
83
+ try {
84
+ if (responseType === 'blob' && response) {
85
+ return await parseResponseBlob(response as unknown as Blob);
86
+ }
87
+ return response ?? null;
88
+ } catch (error) {
89
+ console.warn("getResponseBody---error---", error)
90
+ return null;
91
+ }
92
+ };
93
+
94
+ class HTTPInterceptor {
95
+ private static _index = 0;
96
+ private ignoredHosts: Set<string> | undefined;
97
+ private ignoredUrls: Set<string> | undefined;
98
+ private ignoredPatterns: RegExp[] | undefined;
99
+ // 只保存正在请求中的
100
+ private allRequests = new Map<string, Partial<HttpRequestInfo>>();
101
+
102
+ private userListeners: [
103
+ EventName,
104
+ (data: Partial<HttpRequestInfo>) => Promise<void> | void,
105
+ ][] = [];
106
+
107
+ private enabled = false;
108
+
109
+ addListener = (
110
+ eventName: EventName,
111
+ listener: (data: Partial<HttpRequestInfo>) => Promise<void> | void,
112
+ ) => {
113
+ // 如果之前已经订阅过了就过滤掉
114
+ if (
115
+ this.userListeners.find(
116
+ ([name, tempListener]) =>
117
+ name === eventName && tempListener === listener,
118
+ )
119
+ ) {
120
+ return;
121
+ }
122
+ this.userListeners.push([eventName, listener]);
123
+
124
+ return () => {
125
+ this.userListeners = this.userListeners.filter(
126
+ ([name, tempListener]) =>
127
+ name !== eventName || tempListener !== listener,
128
+ );
129
+ };
130
+ };
131
+
132
+ removeListener = (
133
+ eventName: EventName,
134
+ listener: (data: Partial<HttpRequestInfo>) => Promise<void> | void,
135
+ ) => {
136
+ this.userListeners = this.userListeners.filter(
137
+ ([name, tempListener]) => name !== eventName || tempListener !== listener,
138
+ );
139
+ };
140
+
141
+ removeAllListener() {
142
+ this.userListeners = [];
143
+ }
144
+
145
+ private listenerHandle = (
146
+ eventName: EventName,
147
+ data: Partial<HttpRequestInfo>,
148
+ ) => {
149
+ this.userListeners.forEach(async ([name, listener]) => {
150
+ try {
151
+ if (name === eventName) {
152
+ await listener(data);
153
+ }
154
+ } catch (error: any) {
155
+ console.warn(`eventName=${eventName}, error=${error?.message}`)
156
+ }
157
+ });
158
+ };
159
+
160
+ private openHandle = (method: RequestMethod, url: string, xhr: XHR) => {
161
+ if (this.ignoredHosts) {
162
+ const host = extractHost(url);
163
+ if (host && this.ignoredHosts.has(host)) {
164
+ return;
165
+ }
166
+ }
167
+ if (this.ignoredUrls && this.ignoredUrls.has(url)) {
168
+ return;
169
+ }
170
+
171
+ if (this.ignoredPatterns) {
172
+ if (
173
+ this.ignoredPatterns.some(pattern => pattern.test(`${method} ${url}`))
174
+ ) {
175
+ return;
176
+ }
177
+ }
178
+ xhr.uniqueId = HTTPInterceptor._index + generateUniqueId();
179
+ const newRequest = {
180
+ id: xhr.uniqueId,
181
+ method,
182
+ url,
183
+ };
184
+ this.allRequests.set(xhr.uniqueId, newRequest);
185
+ this.listenerHandle('open', newRequest);
186
+ };
187
+
188
+ private requestHeaderHandle = (header: string, value: string, xhr: XHR) => {
189
+ const currentRequest = this.allRequests.get(xhr.uniqueId);
190
+ if (!currentRequest) {
191
+ return;
192
+ }
193
+ if (!currentRequest.requestHeaders) {
194
+ currentRequest.requestHeaders = {};
195
+ }
196
+ currentRequest.requestHeaders[header] = value;
197
+ this.listenerHandle('requestHeader', currentRequest);
198
+ };
199
+
200
+ private headerReceivedHandle = (
201
+ responseContentType: string,
202
+ responseSize: number,
203
+ responseHeaders: Headers,
204
+ xhr: XHR,
205
+ ) => {
206
+ const currentRequest = this.allRequests.get(xhr.uniqueId);
207
+ if (!currentRequest) {
208
+ return;
209
+ }
210
+ currentRequest.responseContentType = responseContentType;
211
+ currentRequest.responseSize = responseSize;
212
+ currentRequest.responseHeaders = xhr.responseHeaders;
213
+ this.listenerHandle('headerReceived', currentRequest);
214
+ };
215
+
216
+ private responseHandle = async (
217
+ status: number,
218
+ timeout: number,
219
+ response: any,
220
+ responseURL: string,
221
+ responseType: string,
222
+ xhr: XHR,
223
+ ) => {
224
+ const currentRequest = this.allRequests.get(xhr.uniqueId);
225
+ if (!currentRequest) {
226
+ return;
227
+ }
228
+ currentRequest.endTime = Date.now();
229
+ currentRequest.status = status;
230
+ currentRequest.timeout = timeout;
231
+ currentRequest.responseData = await getResponseBody(responseType, response);
232
+ currentRequest.responseURL = responseURL;
233
+ currentRequest.responseType = responseType;
234
+ currentRequest.duration =
235
+ currentRequest.endTime - (currentRequest.startTime ?? 0);
236
+ this.listenerHandle('response', currentRequest);
237
+ this.allRequests.delete(xhr.uniqueId);
238
+ };
239
+
240
+ private sendHandle = (data: any, xhr: XHR) => {
241
+ const currentRequest = this.allRequests.get(xhr.uniqueId);
242
+ if (!currentRequest) {
243
+ return;
244
+ }
245
+ try {
246
+ if (data && typeof data === 'object' && data instanceof FormData) {
247
+ currentRequest.requestData = formDataToString(data);
248
+ } else {
249
+ currentRequest.requestData = JSON.parse(data);
250
+ }
251
+ } catch (error) {
252
+ currentRequest.requestData = null;
253
+ }
254
+ currentRequest.startTime = Date.now();
255
+ this.listenerHandle('send', currentRequest);
256
+ };
257
+
258
+ setIgnoredUrls = (ignoredUrls: string[]) => {
259
+ if (ignoredUrls?.length) {
260
+ if (
261
+ !Array.isArray(ignoredUrls) ||
262
+ typeof ignoredUrls[0] !== 'string'
263
+ ) {
264
+ console.warn(
265
+ 'ignoredUrls must be an array of strings. The logger has not been started.',
266
+ );
267
+ return;
268
+ }
269
+ this.ignoredUrls = new Set(ignoredUrls);
270
+ }
271
+ }
272
+
273
+ enable = (options?: StartNetworkLoggingOptions) => {
274
+ try {
275
+ if (
276
+ this.enabled ||
277
+ (XHRInterceptor.isInterceptorEnabled() && !options?.forceEnable)
278
+ ) {
279
+ if (!this.enabled) {
280
+ console.warn(
281
+ 'network interceptor has not been enabled as another interceptor is already running (e.g. another debugging program). Use option `forceEnable: true` to override this behaviour.',
282
+ );
283
+ }
284
+ return;
285
+ }
286
+
287
+ if (options?.ignoredHosts) {
288
+ if (
289
+ !Array.isArray(options.ignoredHosts) ||
290
+ typeof options.ignoredHosts[0] !== 'string'
291
+ ) {
292
+ console.warn(
293
+ 'ignoredHosts must be an array of strings. The logger has not been started.',
294
+ );
295
+ return;
296
+ }
297
+ this.ignoredHosts = new Set(options.ignoredHosts);
298
+ }
299
+
300
+ if (options?.ignoredPatterns) {
301
+ this.ignoredPatterns = options.ignoredPatterns;
302
+ }
303
+ this.setIgnoredUrls(options?.ignoredUrls)
304
+ XHRInterceptor.setOpenCallback(this.openHandle);
305
+ XHRInterceptor.setRequestHeaderCallback(this.requestHeaderHandle);
306
+ XHRInterceptor.setHeaderReceivedCallback(this.headerReceivedHandle);
307
+ XHRInterceptor.setSendCallback(this.sendHandle);
308
+ XHRInterceptor.setResponseCallback(this.responseHandle);
309
+ XHRInterceptor.enableInterception();
310
+ this.enabled = true;
311
+ } catch (error) {}
312
+ };
313
+
314
+ disable = () => {
315
+ if (!this.enabled) {
316
+ return;
317
+ }
318
+ XHRInterceptor.disableInterception();
319
+ this.enabled = false;
320
+ };
321
+
322
+ reset = () => {
323
+ this.disable();
324
+ this.removeAllListener();
325
+ this.ignoredHosts = undefined;
326
+ this.ignoredUrls = undefined;
327
+ this.ignoredPatterns = undefined;
328
+ this.allRequests.clear();
329
+ };
330
+ }
331
+
332
+ const SafeHTTPInterceptor = createClassWithErrorHandling(HTTPInterceptor)
333
+ const httpInterceptor = new SafeHTTPInterceptor();
334
+ export {
335
+ type StartNetworkLoggingOptions,
336
+ httpInterceptor,
337
+ type EventName,
338
+ type RequestMethod,
339
+ };