@wutiange/log-listener-plugin 1.3.2 → 2.0.1-alpha.2
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/README.md +130 -24
- package/dist/src/HTTPInterceptor.d.ts +1 -0
- package/dist/src/HTTPInterceptor.js +12 -9
- package/dist/src/HTTPInterceptor.js.map +1 -1
- package/dist/src/Server.d.ts +13 -6
- package/dist/src/Server.js +119 -41
- package/dist/src/Server.js.map +1 -1
- package/dist/src/__mocks__/react-native/Libraries/Blob/FileReader.js +0 -1
- package/dist/src/__mocks__/react-native/Libraries/Blob/FileReader.js.map +1 -1
- package/dist/src/__mocks__/react-native/Libraries/Network/XHRInterceptor.js +0 -1
- package/dist/src/__mocks__/react-native/Libraries/Network/XHRInterceptor.js.map +1 -1
- package/dist/src/__tests__/Server.test.js +76 -115
- package/dist/src/__tests__/Server.test.js.map +1 -1
- package/dist/src/common.d.ts +19 -10
- package/dist/src/common.js +63 -4
- package/dist/src/common.js.map +1 -1
- package/dist/src/logPlugin.d.ts +12 -9
- package/dist/src/logPlugin.js +87 -82
- package/dist/src/logPlugin.js.map +1 -1
- package/dist/src/logger.d.ts +6 -0
- package/dist/src/logger.js +16 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/utils.js +12 -7
- package/dist/src/utils.js.map +1 -1
- package/index.ts +3 -0
- package/package.json +18 -12
- package/src/HTTPInterceptor.ts +339 -0
- package/src/Server.ts +164 -0
- package/src/__mocks__/react-native/Libraries/Blob/FileReader.js +45 -0
- package/src/__mocks__/react-native/Libraries/Network/XHRInterceptor.js +39 -0
- package/src/__tests__/HTTPInterceptor.test.ts +322 -0
- package/src/__tests__/Server.test.ts +150 -0
- package/src/__tests__/utils.test.ts +113 -0
- package/src/common.ts +70 -0
- package/src/logPlugin.ts +224 -0
- package/src/logger.ts +15 -0
- package/src/utils.ts +112 -0
- package/tsconfig.json +27 -0
package/src/Server.ts
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
import { hasPort, sleep } from "./utils";
|
2
|
+
import Zeroconf from "react-native-zeroconf";
|
3
|
+
import { getBaseData, getErrMsg, LOG_KEY } from "./common";
|
4
|
+
import logger from "./logger";
|
5
|
+
import md5 from 'crypto-js/md5';
|
6
|
+
|
7
|
+
|
8
|
+
const DEFAULT_PORT = 27751;
|
9
|
+
class Server {
|
10
|
+
private baseUrlObj: Record<string, string> = {};
|
11
|
+
private timeout: number;
|
12
|
+
private baseData: Record<string, any> = {};
|
13
|
+
private urlsListener: (
|
14
|
+
urls: string[],
|
15
|
+
urlsObj: Record<string, string>
|
16
|
+
) => void;
|
17
|
+
private innerBaseData: Record<string, string> = {};
|
18
|
+
|
19
|
+
constructor(url?: string | Record<string, string>, timeout: number = 30000) {
|
20
|
+
if (typeof url === "string") {
|
21
|
+
this.updateUrl(url);
|
22
|
+
} else {
|
23
|
+
this.setBaseUrlObj(url ?? {});
|
24
|
+
}
|
25
|
+
this.timeout = timeout;
|
26
|
+
this.innerBaseData = getBaseData();
|
27
|
+
this.handleZeroConf();
|
28
|
+
}
|
29
|
+
|
30
|
+
addUrlsListener = (
|
31
|
+
onNewUrlCallback: (urls: string[], urlsObj: Record<string, string>) => void
|
32
|
+
) => {
|
33
|
+
this.urlsListener = onNewUrlCallback;
|
34
|
+
};
|
35
|
+
|
36
|
+
private requestJoin = async (url: string, token: string) => {
|
37
|
+
const response = await fetch(url, {
|
38
|
+
method: "POST",
|
39
|
+
headers: {
|
40
|
+
"Content-Type": "application/json;charset=utf-8",
|
41
|
+
},
|
42
|
+
body: JSON.stringify({
|
43
|
+
token,
|
44
|
+
model: this.innerBaseData.Model,
|
45
|
+
id: md5(JSON.stringify(this.innerBaseData)).toString(),
|
46
|
+
}),
|
47
|
+
});
|
48
|
+
if (response.status !== 200) {
|
49
|
+
return false;
|
50
|
+
}
|
51
|
+
const json = await response.json();
|
52
|
+
|
53
|
+
if (json.code !== 0) {
|
54
|
+
return false;
|
55
|
+
}
|
56
|
+
return true
|
57
|
+
}
|
58
|
+
|
59
|
+
private async handleZeroConf() {
|
60
|
+
try {
|
61
|
+
const Zeroconf = require("react-native-zeroconf")?.default;
|
62
|
+
if (!Zeroconf) {
|
63
|
+
return;
|
64
|
+
}
|
65
|
+
// @ts-ignore
|
66
|
+
const zeroconf: Zeroconf = new Zeroconf();
|
67
|
+
zeroconf.on("resolved", async (service) => {
|
68
|
+
try {
|
69
|
+
const { path, token } = service.txt ?? {};
|
70
|
+
if (!(path && token) || this.baseUrlObj[token]) {
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
const url = `http://${service.host}:${service.port}`;
|
74
|
+
if (!(await this.requestJoin(`${url}${path}`, token))) {
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
this.baseUrlObj[token] = url;
|
78
|
+
if (this.urlsListener) {
|
79
|
+
this.urlsListener(this.getUrls(), this.baseUrlObj);
|
80
|
+
}
|
81
|
+
} catch (error) {
|
82
|
+
logger.warn(LOG_KEY, "加入日志系统失败---", error);
|
83
|
+
}
|
84
|
+
});
|
85
|
+
zeroconf.scan("http", "tcp");
|
86
|
+
} catch (error: any) {
|
87
|
+
logger.warn(LOG_KEY, "zeroconf扫描或处理相关逻辑失败或者您根本就没有安装 react-native-zeroconf ,如果您没有安装,那么您将无法使用发现功能", error);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
updateTimeout(timeout = 3000) {
|
92
|
+
this.timeout = timeout;
|
93
|
+
}
|
94
|
+
|
95
|
+
getUrls() {
|
96
|
+
return Object.values(this.baseUrlObj).map((e) => {
|
97
|
+
if (hasPort(e)) {
|
98
|
+
return e;
|
99
|
+
}
|
100
|
+
return `${e}:${DEFAULT_PORT}`;
|
101
|
+
});
|
102
|
+
}
|
103
|
+
|
104
|
+
private send = async (
|
105
|
+
path: string,
|
106
|
+
data: Record<string, any>
|
107
|
+
): Promise<void> => {
|
108
|
+
const request = async (url: string, _data: Record<string, any>) => {
|
109
|
+
await Promise.race([
|
110
|
+
fetch(`${url}/${path}`, {
|
111
|
+
method: "POST",
|
112
|
+
headers: {
|
113
|
+
"Content-Type": "application/json;charset=utf-8",
|
114
|
+
},
|
115
|
+
body: JSON.stringify(
|
116
|
+
{ ...this.innerBaseData, ...this.baseData, ..._data },
|
117
|
+
(_, val) => {
|
118
|
+
if (val instanceof Error) {
|
119
|
+
return val.toString();
|
120
|
+
}
|
121
|
+
return val;
|
122
|
+
}
|
123
|
+
),
|
124
|
+
}),
|
125
|
+
sleep(this.timeout, true),
|
126
|
+
]);
|
127
|
+
};
|
128
|
+
try {
|
129
|
+
if (Object.keys(this.baseUrlObj).length === 0) {
|
130
|
+
return;
|
131
|
+
}
|
132
|
+
await Promise.all(this.getUrls().map(async (e) => request(e, data)));
|
133
|
+
} catch (error: any) {
|
134
|
+
logger.warn(LOG_KEY, "上报日志失败", error)
|
135
|
+
}
|
136
|
+
};
|
137
|
+
|
138
|
+
updateUrl(url: string) {
|
139
|
+
const tempUrl = url.includes("http") ? url : `http://${url}`;
|
140
|
+
if (!url) {
|
141
|
+
delete this.baseUrlObj["Default"];
|
142
|
+
} else {
|
143
|
+
this.baseUrlObj["Default"] = tempUrl;
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
setBaseUrlObj(urlObj: Record<string, string>) {
|
148
|
+
this.baseUrlObj = urlObj;
|
149
|
+
}
|
150
|
+
|
151
|
+
updateBaseData(data: Record<string, any>) {
|
152
|
+
this.baseData = data;
|
153
|
+
}
|
154
|
+
|
155
|
+
log = async (data: Record<string, any>) => {
|
156
|
+
return this.send("log", data);
|
157
|
+
};
|
158
|
+
|
159
|
+
network = async (data: Record<string, any>) => {
|
160
|
+
return this.send("network", data);
|
161
|
+
};
|
162
|
+
}
|
163
|
+
|
164
|
+
export default Server;
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class FileReader {
|
2
|
+
constructor() {
|
3
|
+
this.result = null;
|
4
|
+
this.error = null;
|
5
|
+
this.readyState = FileReader.EMPTY;
|
6
|
+
}
|
7
|
+
|
8
|
+
static EMPTY = 0;
|
9
|
+
static LOADING = 1;
|
10
|
+
static DONE = 2;
|
11
|
+
|
12
|
+
addEventListener(event, callback) {
|
13
|
+
this[`on${event}`] = callback;
|
14
|
+
}
|
15
|
+
|
16
|
+
removeEventListener(event, callback) {
|
17
|
+
if (this[`on${event}`] === callback) {
|
18
|
+
this[`on${event}`] = null;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
readAsText(blob) {
|
23
|
+
this._read(blob, 'text');
|
24
|
+
}
|
25
|
+
|
26
|
+
readAsArrayBuffer(blob) {
|
27
|
+
this._read(blob, 'arraybuffer');
|
28
|
+
}
|
29
|
+
|
30
|
+
_read(blob, resultType) {
|
31
|
+
this.readyState = FileReader.LOADING;
|
32
|
+
setTimeout(() => {
|
33
|
+
this.readyState = FileReader.DONE;
|
34
|
+
if (resultType === 'text') {
|
35
|
+
this.result = blob.text();
|
36
|
+
} else if (resultType === 'arraybuffer') {
|
37
|
+
// 这里我们简单地返回一个空的 ArrayBuffer
|
38
|
+
this.result = new ArrayBuffer(0);
|
39
|
+
}
|
40
|
+
if (this.onload) this.onload({target: this});
|
41
|
+
}, 0);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
module.exports = FileReader;
|
@@ -0,0 +1,39 @@
|
|
1
|
+
// __mocks__/react-native/Libraries/XHRInterceptor.js
|
2
|
+
|
3
|
+
class XHRInterceptor {
|
4
|
+
static _isInterceptorEnabled = false
|
5
|
+
static openCallback = null
|
6
|
+
static requestHeaderCallback = null
|
7
|
+
static headerReceivedCallback = null
|
8
|
+
static sendCallback = null
|
9
|
+
static responseCallback = null
|
10
|
+
|
11
|
+
static setOpenCallback = jest.fn((callback) => {
|
12
|
+
XHRInterceptor.openCallback = callback;
|
13
|
+
})
|
14
|
+
static setRequestHeaderCallback = jest.fn((callback) => {
|
15
|
+
XHRInterceptor.requestHeaderCallback = callback;
|
16
|
+
})
|
17
|
+
static setHeaderReceivedCallback = jest.fn((callback) => {
|
18
|
+
XHRInterceptor.headerReceivedCallback = callback;
|
19
|
+
})
|
20
|
+
static setSendCallback = jest.fn((callback) => {
|
21
|
+
XHRInterceptor.sendCallback = callback;
|
22
|
+
})
|
23
|
+
static setResponseCallback = jest.fn((callback) => {
|
24
|
+
XHRInterceptor.responseCallback = callback;
|
25
|
+
})
|
26
|
+
|
27
|
+
static enableInterception = jest.fn(() => {
|
28
|
+
XHRInterceptor._isInterceptorEnabled = true;
|
29
|
+
})
|
30
|
+
|
31
|
+
static disableInterception = jest.fn(() => {
|
32
|
+
XHRInterceptor._isInterceptorEnabled = false;
|
33
|
+
})
|
34
|
+
|
35
|
+
static isInterceptorEnabled = jest.fn(() => XHRInterceptor._isInterceptorEnabled)
|
36
|
+
};
|
37
|
+
|
38
|
+
module.exports = XHRInterceptor;
|
39
|
+
|
@@ -0,0 +1,322 @@
|
|
1
|
+
import { httpInterceptor } from '../HTTPInterceptor';
|
2
|
+
import XHRInterceptor from 'react-native/Libraries/Network/XHRInterceptor';
|
3
|
+
import { formDataToString } from '../utils';
|
4
|
+
|
5
|
+
// Mock dependencies
|
6
|
+
jest.mock('buffer', () => ({
|
7
|
+
Blob: jest.fn(),
|
8
|
+
}));
|
9
|
+
jest.mock('../utils', () => ({
|
10
|
+
createClassWithErrorHandling: jest.fn(Class => Class),
|
11
|
+
formDataToString: jest.fn(),
|
12
|
+
}));
|
13
|
+
|
14
|
+
// 在文件顶部添加这个模拟
|
15
|
+
class MockFormData {
|
16
|
+
private data: Record<string, string> = {};
|
17
|
+
|
18
|
+
append(key: string, value: string) {
|
19
|
+
this.data[key] = value;
|
20
|
+
}
|
21
|
+
|
22
|
+
get(key: string) {
|
23
|
+
return this.data[key];
|
24
|
+
}
|
25
|
+
|
26
|
+
// 可以根据需要添加更多方法
|
27
|
+
}
|
28
|
+
|
29
|
+
class MockBlob {
|
30
|
+
private content: string;
|
31
|
+
type: any
|
32
|
+
constructor(parts: any, options: any = {}) {
|
33
|
+
this.content = parts ? parts.join('') : '';
|
34
|
+
this.type = options.type || '';
|
35
|
+
}
|
36
|
+
|
37
|
+
text(): Promise<string> {
|
38
|
+
return Promise.resolve(this.content);
|
39
|
+
}
|
40
|
+
|
41
|
+
arrayBuffer() {
|
42
|
+
return Promise.resolve(new ArrayBuffer(0));
|
43
|
+
}
|
44
|
+
}
|
45
|
+
// 全局声明,以避免 TypeScript 错误
|
46
|
+
declare global {
|
47
|
+
// @ts-ignore
|
48
|
+
var FormData: typeof MockFormData;
|
49
|
+
}
|
50
|
+
|
51
|
+
// 在测试套件开始前设置全局 FormData
|
52
|
+
beforeAll(() => {
|
53
|
+
// @ts-ignore
|
54
|
+
global.FormData = MockFormData;
|
55
|
+
});
|
56
|
+
|
57
|
+
// 在测试套件结束后清理
|
58
|
+
afterAll(() => {
|
59
|
+
// @ts-ignore
|
60
|
+
delete global.FormData;
|
61
|
+
});
|
62
|
+
|
63
|
+
describe('HTTPInterceptor', () => {
|
64
|
+
beforeEach(() => {
|
65
|
+
jest.clearAllMocks();
|
66
|
+
console.warn = jest.fn();
|
67
|
+
httpInterceptor.reset();
|
68
|
+
(XHRInterceptor.isInterceptorEnabled as jest.Mock).mockReturnValue(false);
|
69
|
+
});
|
70
|
+
|
71
|
+
describe('enable', () => {
|
72
|
+
it('should enable interception', () => {
|
73
|
+
httpInterceptor.enable();
|
74
|
+
expect(XHRInterceptor.enableInterception).toHaveBeenCalled();
|
75
|
+
});
|
76
|
+
|
77
|
+
it('should not enable if already enabled', () => {
|
78
|
+
httpInterceptor.enable();
|
79
|
+
httpInterceptor.enable();
|
80
|
+
expect(XHRInterceptor.enableInterception).toHaveBeenCalledTimes(1);
|
81
|
+
});
|
82
|
+
|
83
|
+
it('should handle ignored hosts', () => {
|
84
|
+
httpInterceptor.enable({ ignoredHosts: ['example.com'] });
|
85
|
+
expect(XHRInterceptor.enableInterception).toHaveBeenCalled();
|
86
|
+
});
|
87
|
+
|
88
|
+
it('should handle ignored patterns', () => {
|
89
|
+
httpInterceptor.enable({ ignoredPatterns: [/^GET https:\/\/test\.com/] });
|
90
|
+
expect(XHRInterceptor.enableInterception).toHaveBeenCalled();
|
91
|
+
});
|
92
|
+
|
93
|
+
it('should handle ignored URLs', () => {
|
94
|
+
httpInterceptor.enable({ ignoredUrls: ['https://example.com/api'] });
|
95
|
+
expect(XHRInterceptor.enableInterception).toHaveBeenCalled();
|
96
|
+
});
|
97
|
+
|
98
|
+
it('should warn if another interceptor is running', () => {
|
99
|
+
(XHRInterceptor.isInterceptorEnabled as jest.Mock).mockReturnValue(true);
|
100
|
+
console.warn = jest.fn();
|
101
|
+
httpInterceptor.enable();
|
102
|
+
expect(console.warn).toHaveBeenCalled();
|
103
|
+
});
|
104
|
+
|
105
|
+
it('should force enable if specified', () => {
|
106
|
+
(XHRInterceptor.isInterceptorEnabled as jest.Mock).mockReturnValue(true);
|
107
|
+
httpInterceptor.enable({ forceEnable: true });
|
108
|
+
expect(XHRInterceptor.enableInterception).toHaveBeenCalled();
|
109
|
+
});
|
110
|
+
});
|
111
|
+
|
112
|
+
describe('disable', () => {
|
113
|
+
it('should disable interception', () => {
|
114
|
+
httpInterceptor.enable();
|
115
|
+
httpInterceptor.disable();
|
116
|
+
expect(XHRInterceptor.disableInterception).toHaveBeenCalled();
|
117
|
+
});
|
118
|
+
|
119
|
+
it('should not disable if not enabled', () => {
|
120
|
+
httpInterceptor.disable();
|
121
|
+
expect(XHRInterceptor.disableInterception).not.toHaveBeenCalled();
|
122
|
+
});
|
123
|
+
});
|
124
|
+
|
125
|
+
describe('listeners', () => {
|
126
|
+
it('should add and remove listeners', () => {
|
127
|
+
const listener = jest.fn();
|
128
|
+
const removeListener = httpInterceptor.addListener('open', listener);
|
129
|
+
expect(httpInterceptor['userListeners'].length).toBe(1);
|
130
|
+
removeListener();
|
131
|
+
expect(httpInterceptor['userListeners'].length).toBe(0);
|
132
|
+
});
|
133
|
+
|
134
|
+
it('should not add duplicate listeners', () => {
|
135
|
+
const listener = jest.fn();
|
136
|
+
httpInterceptor.addListener('open', listener);
|
137
|
+
httpInterceptor.addListener('open', listener);
|
138
|
+
expect(httpInterceptor['userListeners'].length).toBe(1);
|
139
|
+
});
|
140
|
+
|
141
|
+
it('should remove specific listener', () => {
|
142
|
+
const listener1 = jest.fn();
|
143
|
+
const listener2 = jest.fn();
|
144
|
+
httpInterceptor.addListener('open', listener1);
|
145
|
+
httpInterceptor.addListener('open', listener2);
|
146
|
+
httpInterceptor.removeListener('open', listener1);
|
147
|
+
expect(httpInterceptor['userListeners'].length).toBe(1);
|
148
|
+
expect(httpInterceptor['userListeners'][0][1]).toBe(listener2);
|
149
|
+
});
|
150
|
+
|
151
|
+
it('should remove all listeners', () => {
|
152
|
+
httpInterceptor.addListener('open', jest.fn());
|
153
|
+
httpInterceptor.addListener('send', jest.fn());
|
154
|
+
httpInterceptor.removeAllListener();
|
155
|
+
expect(httpInterceptor['userListeners'].length).toBe(0);
|
156
|
+
});
|
157
|
+
});
|
158
|
+
|
159
|
+
describe('request handling', () => {
|
160
|
+
let openCallback: Function;
|
161
|
+
let requestHeaderCallback: Function;
|
162
|
+
let headerReceivedCallback: Function;
|
163
|
+
let sendCallback: Function;
|
164
|
+
let responseCallback: Function;
|
165
|
+
|
166
|
+
beforeEach(() => {
|
167
|
+
httpInterceptor.enable();
|
168
|
+
openCallback = (XHRInterceptor.setOpenCallback as jest.Mock).mock.calls[0][0];
|
169
|
+
requestHeaderCallback = (XHRInterceptor.setRequestHeaderCallback as jest.Mock).mock.calls[0][0];
|
170
|
+
headerReceivedCallback = (XHRInterceptor.setHeaderReceivedCallback as jest.Mock).mock.calls[0][0];
|
171
|
+
sendCallback = (XHRInterceptor.setSendCallback as jest.Mock).mock.calls[0][0];
|
172
|
+
responseCallback = (XHRInterceptor.setResponseCallback as jest.Mock).mock.calls[0][0];
|
173
|
+
});
|
174
|
+
|
175
|
+
it('should handle open event', () => {
|
176
|
+
const listener = jest.fn();
|
177
|
+
httpInterceptor.addListener('open', listener);
|
178
|
+
const xhr = {}
|
179
|
+
openCallback('GET', 'https://example.com', xhr);
|
180
|
+
expect(listener).toHaveBeenCalledWith(expect.objectContaining({
|
181
|
+
method: 'GET',
|
182
|
+
url: 'https://example.com',
|
183
|
+
}));
|
184
|
+
});
|
185
|
+
|
186
|
+
it('should handle request header event', () => {
|
187
|
+
const listener = jest.fn();
|
188
|
+
httpInterceptor.addListener('requestHeader', listener);
|
189
|
+
const xhr = {}
|
190
|
+
openCallback('GET', 'https://example.com', xhr);
|
191
|
+
requestHeaderCallback('Content-Type', 'application/json', xhr);
|
192
|
+
expect(listener).toHaveBeenCalledWith(expect.objectContaining({
|
193
|
+
requestHeaders: { 'Content-Type': 'application/json' },
|
194
|
+
}));
|
195
|
+
});
|
196
|
+
|
197
|
+
it('should handle header received event', () => {
|
198
|
+
const listener = jest.fn();
|
199
|
+
httpInterceptor.addListener('headerReceived', listener);
|
200
|
+
const xhr: {[key in string]: any} = {}
|
201
|
+
openCallback('GET', 'https://example.com', xhr);
|
202
|
+
xhr.responseHeaders = { 'Content-Type': 'application/json' }
|
203
|
+
headerReceivedCallback('application/json', 100, {}, xhr);
|
204
|
+
expect(listener).toHaveBeenCalledWith(expect.objectContaining({
|
205
|
+
responseContentType: 'application/json',
|
206
|
+
responseSize: 100,
|
207
|
+
responseHeaders: { 'Content-Type': 'application/json' },
|
208
|
+
}));
|
209
|
+
});
|
210
|
+
|
211
|
+
it('should handle send event with JSON data', () => {
|
212
|
+
const listener = jest.fn();
|
213
|
+
httpInterceptor.addListener('send', listener);
|
214
|
+
const xhr = {}
|
215
|
+
openCallback('POST', 'https://example.com', xhr);
|
216
|
+
sendCallback(JSON.stringify({ key: 'value' }), xhr);
|
217
|
+
expect(listener).toHaveBeenCalledWith(expect.objectContaining({
|
218
|
+
requestData: { key: 'value' },
|
219
|
+
}));
|
220
|
+
});
|
221
|
+
|
222
|
+
it('should handle send event with FormData', () => {
|
223
|
+
const listener = jest.fn();
|
224
|
+
httpInterceptor.addListener('send', listener);
|
225
|
+
const xhr = {}
|
226
|
+
openCallback('POST', 'https://example.com', xhr);
|
227
|
+
const formData = new FormData();
|
228
|
+
formData.append('key', 'value');
|
229
|
+
(formDataToString as jest.Mock).mockReturnValue('key=value');
|
230
|
+
sendCallback(formData, xhr);
|
231
|
+
expect(listener).toHaveBeenCalledWith(expect.objectContaining({
|
232
|
+
requestData: 'key=value',
|
233
|
+
}));
|
234
|
+
});
|
235
|
+
|
236
|
+
it('should handle response event', async () => {
|
237
|
+
const listener = jest.fn();
|
238
|
+
httpInterceptor.addListener('response', listener);
|
239
|
+
const xhr = {}
|
240
|
+
openCallback('GET', 'https://example.com', xhr);
|
241
|
+
await responseCallback(200, 1000, { data: 'response' }, 'https://example.com', 'json', xhr);
|
242
|
+
expect(listener).toHaveBeenCalledWith(expect.objectContaining({
|
243
|
+
status: 200,
|
244
|
+
timeout: 1000,
|
245
|
+
responseData: { data: 'response' },
|
246
|
+
responseURL: 'https://example.com',
|
247
|
+
responseType: 'json',
|
248
|
+
}));
|
249
|
+
});
|
250
|
+
|
251
|
+
it('should handle response event with blob data', async () => {
|
252
|
+
const listener = jest.fn();
|
253
|
+
httpInterceptor.addListener('response', listener);
|
254
|
+
const xhr = {}
|
255
|
+
openCallback('GET', 'https://example.com', xhr);
|
256
|
+
const mockBlob = new MockBlob(['blob content']);
|
257
|
+
await responseCallback(200, 1000, mockBlob, 'https://example.com', 'blob', xhr);
|
258
|
+
expect(listener).toHaveBeenCalledWith(expect.objectContaining({
|
259
|
+
responseData: 'blob content',
|
260
|
+
}));
|
261
|
+
});
|
262
|
+
});
|
263
|
+
|
264
|
+
describe('error handling', () => {
|
265
|
+
beforeEach(() => {
|
266
|
+
httpInterceptor.enable();
|
267
|
+
})
|
268
|
+
it('should handle errors in listeners', async () => {
|
269
|
+
const errorListener = jest.fn(() => {
|
270
|
+
throw new Error('Listener error');
|
271
|
+
});
|
272
|
+
httpInterceptor.addListener('open', errorListener);
|
273
|
+
console.warn = jest.fn();
|
274
|
+
const xhr = {}
|
275
|
+
const openCallback = (XHRInterceptor.setOpenCallback as jest.Mock).mock.calls[0][0];
|
276
|
+
openCallback('GET', 'https://example.com', xhr);
|
277
|
+
|
278
|
+
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Listener error'));
|
279
|
+
});
|
280
|
+
});
|
281
|
+
|
282
|
+
describe('ignored requests', () => {
|
283
|
+
it('should ignore requests to ignored hosts', () => {
|
284
|
+
httpInterceptor.enable({ ignoredHosts: ['ignored.com'] });
|
285
|
+
const openCallback = (XHRInterceptor.setOpenCallback as jest.Mock).mock.calls[0][0];
|
286
|
+
const listener = jest.fn();
|
287
|
+
httpInterceptor.addListener('open', listener);
|
288
|
+
|
289
|
+
openCallback('GET', 'https://ignored.com', { uniqueId: '123' });
|
290
|
+
expect(listener).not.toHaveBeenCalled();
|
291
|
+
|
292
|
+
openCallback('GET', 'https://example.com', { uniqueId: '124' });
|
293
|
+
expect(listener).toHaveBeenCalled();
|
294
|
+
});
|
295
|
+
|
296
|
+
it('should ignore requests to ignored URLs', () => {
|
297
|
+
httpInterceptor.enable({ ignoredUrls: ['https://example.com/ignored'] });
|
298
|
+
const openCallback = (XHRInterceptor.setOpenCallback as jest.Mock).mock.calls[0][0];
|
299
|
+
const listener = jest.fn();
|
300
|
+
httpInterceptor.addListener('open', listener);
|
301
|
+
|
302
|
+
openCallback('GET', 'https://example.com/ignored', { uniqueId: '123' });
|
303
|
+
expect(listener).not.toHaveBeenCalled();
|
304
|
+
|
305
|
+
openCallback('GET', 'https://example.com/api', { uniqueId: '124' });
|
306
|
+
expect(listener).toHaveBeenCalled();
|
307
|
+
});
|
308
|
+
|
309
|
+
it('should ignore requests matching ignored patterns', () => {
|
310
|
+
httpInterceptor.enable({ ignoredPatterns: [/^GET https:\/\/test\.com/] });
|
311
|
+
const openCallback = (XHRInterceptor.setOpenCallback as jest.Mock).mock.calls[0][0];
|
312
|
+
const listener = jest.fn();
|
313
|
+
httpInterceptor.addListener('open', listener);
|
314
|
+
|
315
|
+
openCallback('GET', 'https://test.com/api', { uniqueId: '123' });
|
316
|
+
expect(listener).not.toHaveBeenCalled();
|
317
|
+
|
318
|
+
openCallback('POST', 'https://test.com/api', { uniqueId: '124' });
|
319
|
+
expect(listener).toHaveBeenCalled();
|
320
|
+
});
|
321
|
+
});
|
322
|
+
});
|