@jetlinks-web/core 2.2.18 → 2.3.0
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/dist/index.d.ts +213 -51
- package/dist/index.mjs +2 -3
- package/package.json +3 -3
- package/src/axios.ts +528 -267
- package/src/fetch.ts +225 -195
- package/src/type.ts +14 -5
package/src/fetch.ts
CHANGED
|
@@ -1,241 +1,271 @@
|
|
|
1
|
-
import {getToken} from "@jetlinks-web/utils";
|
|
2
|
-
import {BASE_API, TOKEN_KEY} from "@jetlinks-web/constants";
|
|
3
|
-
import {isFunction, isObject} from "lodash-es";
|
|
4
|
-
import { Observable, } from 'rxjs'
|
|
1
|
+
import { getToken } from "@jetlinks-web/utils";
|
|
2
|
+
import { BASE_API, TOKEN_KEY } from "@jetlinks-web/constants";
|
|
3
|
+
import { isFunction, isObject } from "lodash-es";
|
|
4
|
+
import { Observable, Subscriber } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* NdJson 配置选项
|
|
8
|
+
*/
|
|
9
|
+
export interface NdJsonOptions {
|
|
10
|
+
/** 成功状态码 */
|
|
11
|
+
code?: number;
|
|
12
|
+
/** 状态码字段名 */
|
|
13
|
+
codeKey?: string;
|
|
14
|
+
/** 不需要 token 的 URL 列表 */
|
|
15
|
+
filter_url?: string[];
|
|
16
|
+
/** token 过期回调 */
|
|
17
|
+
tokenExpiration?: () => void;
|
|
18
|
+
/** 自定义请求配置 */
|
|
19
|
+
requestOptions?: (config: RequestInit) => Record<string, unknown>;
|
|
20
|
+
/** 自定义响应处理 */
|
|
21
|
+
handleResponse?: <T>(response: T) => T;
|
|
22
|
+
/** 基础 API 地址,默认使用 BASE_API 常量 */
|
|
23
|
+
baseURL?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface RequestContext {
|
|
27
|
+
controller: AbortController;
|
|
28
|
+
isActive: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type HttpMethod = 'GET' | 'POST';
|
|
5
32
|
|
|
6
33
|
export class NdJson {
|
|
7
|
-
options:
|
|
34
|
+
private options: NdJsonOptions = {
|
|
8
35
|
code: 200,
|
|
9
36
|
codeKey: 'status'
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
constructor() {}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
private activeRequests = new Set<RequestContext>();
|
|
14
40
|
|
|
15
|
-
|
|
16
|
-
|
|
41
|
+
constructor(options?: NdJsonOptions) {
|
|
42
|
+
if (options) {
|
|
43
|
+
this.options = { ...this.options, ...options };
|
|
44
|
+
}
|
|
17
45
|
}
|
|
18
46
|
|
|
19
|
-
|
|
20
|
-
|
|
47
|
+
/**
|
|
48
|
+
* 初始化/更新配置
|
|
49
|
+
*/
|
|
50
|
+
create(options: NdJsonOptions): void {
|
|
51
|
+
this.options = { ...this.options, ...options };
|
|
21
52
|
}
|
|
22
53
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
fetch(
|
|
31
|
-
_url,
|
|
32
|
-
{
|
|
33
|
-
method: 'GET',
|
|
34
|
-
signal: controller.signal,
|
|
35
|
-
keepalive: true,
|
|
36
|
-
...extra,
|
|
37
|
-
...this.handleRequest(_url)
|
|
38
|
-
}
|
|
39
|
-
).then(resp => {
|
|
40
|
-
reader = resp.body?.getReader();
|
|
41
|
-
const decoder = new TextDecoder();
|
|
42
|
-
let data_buf = "";
|
|
43
|
-
|
|
44
|
-
if (!reader) {
|
|
45
|
-
observer.error(new Error('No readable stream available'));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
54
|
+
/**
|
|
55
|
+
* 获取完整 URL
|
|
56
|
+
*/
|
|
57
|
+
private getUrl(url: string): string {
|
|
58
|
+
const baseURL = this.options.baseURL ?? BASE_API;
|
|
59
|
+
return baseURL + url;
|
|
60
|
+
}
|
|
48
61
|
|
|
49
|
-
|
|
62
|
+
/**
|
|
63
|
+
* 处理 NDJSON 流的核心逻辑
|
|
64
|
+
*/
|
|
65
|
+
private processStream<T>(
|
|
66
|
+
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
67
|
+
observer: Subscriber<T>,
|
|
68
|
+
context: RequestContext
|
|
69
|
+
): void {
|
|
70
|
+
const decoder = new TextDecoder();
|
|
71
|
+
let buffer = '';
|
|
72
|
+
|
|
73
|
+
const read = (): void => {
|
|
74
|
+
if (!context.isActive) {
|
|
75
|
+
reader.cancel();
|
|
76
|
+
observer.complete();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
50
79
|
|
|
51
|
-
|
|
52
|
-
|
|
80
|
+
reader.read()
|
|
81
|
+
.then(({ done, value }) => {
|
|
82
|
+
if (done) {
|
|
83
|
+
this.flushBuffer(buffer, observer);
|
|
53
84
|
observer.complete();
|
|
54
|
-
return
|
|
85
|
+
return;
|
|
55
86
|
}
|
|
56
87
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const data = decoder.decode(value, { stream: true });
|
|
71
|
-
data_buf += data;
|
|
72
|
-
|
|
73
|
-
let lines = data_buf.split('\n');
|
|
74
|
-
for (let i = 0; i < lines.length - 1; ++i) {
|
|
75
|
-
const line = lines[i].trim();
|
|
76
|
-
if (line.length > 0) {
|
|
77
|
-
try {
|
|
78
|
-
observer.next(JSON.parse(line.startsWith('data:') ? line.slice(5) : line));
|
|
79
|
-
} catch (e) {
|
|
80
|
-
observer.error(e);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
data_buf = lines[lines.length - 1];
|
|
86
|
-
read();
|
|
87
|
-
}).catch(err => {
|
|
88
|
-
if (err.name !== 'AbortError') {
|
|
89
|
-
observer.error(err);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
};
|
|
93
|
-
that.isRead = true
|
|
94
|
-
read();
|
|
95
|
-
}).catch(e => {
|
|
96
|
-
observer.error(e)
|
|
97
|
-
})
|
|
88
|
+
buffer += decoder.decode(value, { stream: true });
|
|
89
|
+
buffer = this.parseLines(buffer, observer);
|
|
90
|
+
read();
|
|
91
|
+
})
|
|
92
|
+
.catch(err => {
|
|
93
|
+
if (err.name !== 'AbortError') {
|
|
94
|
+
observer.error(err);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
that.cancel()
|
|
101
|
-
}
|
|
102
|
-
})
|
|
99
|
+
read();
|
|
103
100
|
}
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
...this.handleRequest(_url)
|
|
121
|
-
}
|
|
122
|
-
).then(async resp => {
|
|
123
|
-
reader = resp.body?.getReader();
|
|
124
|
-
const decoder = new TextDecoder();
|
|
125
|
-
let data_buf = "";
|
|
126
|
-
|
|
127
|
-
if (!reader) {
|
|
128
|
-
observer.error(new Error('No readable stream available'));
|
|
129
|
-
return;
|
|
102
|
+
/**
|
|
103
|
+
* 解析缓冲区中的完整行
|
|
104
|
+
*/
|
|
105
|
+
private parseLines<T>(buffer: string, observer: Subscriber<T>): string {
|
|
106
|
+
const lines = buffer.split('\n');
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
109
|
+
const line = lines[i].trim();
|
|
110
|
+
if (line.length > 0) {
|
|
111
|
+
try {
|
|
112
|
+
const data = line.startsWith('data:') ? line.slice(5) : line;
|
|
113
|
+
observer.next(JSON.parse(data));
|
|
114
|
+
} catch (e) {
|
|
115
|
+
observer.error(e);
|
|
116
|
+
return '';
|
|
130
117
|
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
131
120
|
|
|
132
|
-
|
|
121
|
+
return lines[lines.length - 1];
|
|
122
|
+
}
|
|
133
123
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
124
|
+
/**
|
|
125
|
+
* 刷新剩余缓冲区
|
|
126
|
+
*/
|
|
127
|
+
private flushBuffer<T>(buffer: string, observer: Subscriber<T>): void {
|
|
128
|
+
const trimmed = buffer.trim();
|
|
129
|
+
if (trimmed.length > 0) {
|
|
130
|
+
try {
|
|
131
|
+
observer.next(JSON.parse(trimmed));
|
|
132
|
+
} catch (e) {
|
|
133
|
+
observer.error(e);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 创建请求的 Observable
|
|
140
|
+
*/
|
|
141
|
+
private request<T>(
|
|
142
|
+
method: HttpMethod,
|
|
143
|
+
url: string,
|
|
144
|
+
data?: BodyInit | Record<string, unknown>,
|
|
145
|
+
extra: RequestInit = {}
|
|
146
|
+
): Observable<T> {
|
|
147
|
+
const fullUrl = this.getUrl(url);
|
|
148
|
+
|
|
149
|
+
return new Observable<T>(observer => {
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
const context: RequestContext = {
|
|
152
|
+
controller,
|
|
153
|
+
isActive: true
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
this.activeRequests.add(context);
|
|
157
|
+
|
|
158
|
+
const requestInit: RequestInit = {
|
|
159
|
+
method,
|
|
160
|
+
signal: controller.signal,
|
|
161
|
+
keepalive: true,
|
|
162
|
+
...extra,
|
|
163
|
+
...this.handleRequest(fullUrl, method)
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// POST 请求添加 body
|
|
167
|
+
if (method === 'POST' && data !== undefined) {
|
|
168
|
+
requestInit.body = isObject(data) ? JSON.stringify(data) : data as BodyInit;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fetch(fullUrl, requestInit)
|
|
172
|
+
.then(resp => {
|
|
173
|
+
const reader = resp.body?.getReader();
|
|
174
|
+
|
|
175
|
+
if (!reader) {
|
|
176
|
+
observer.error(new Error('No readable stream available'));
|
|
177
|
+
return;
|
|
138
178
|
}
|
|
139
179
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
observer.error(e);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
observer.complete();
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const data = decoder.decode(value, { stream: true });
|
|
154
|
-
data_buf += data;
|
|
155
|
-
|
|
156
|
-
let lines = data_buf.split('\n');
|
|
157
|
-
for (let i = 0; i < lines.length - 1; ++i) {
|
|
158
|
-
const line = lines[i].trim();
|
|
159
|
-
if (line.length > 0) {
|
|
160
|
-
try {
|
|
161
|
-
observer.next(JSON.parse(line.startsWith('data:') ? line.slice(5) : line));
|
|
162
|
-
} catch (e) {
|
|
163
|
-
observer.error(e);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
data_buf = lines[lines.length - 1];
|
|
169
|
-
read();
|
|
170
|
-
}).catch(err => {
|
|
171
|
-
if (err.name !== 'AbortError') {
|
|
172
|
-
observer.error(err);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
};
|
|
176
|
-
that.isRead = true
|
|
177
|
-
read();
|
|
178
|
-
}).catch(e => {
|
|
179
|
-
observer.error(e)
|
|
180
|
-
})
|
|
180
|
+
context.isActive = true;
|
|
181
|
+
this.processStream(reader, observer, context);
|
|
182
|
+
})
|
|
183
|
+
.catch(e => {
|
|
184
|
+
observer.error(e);
|
|
185
|
+
});
|
|
181
186
|
|
|
187
|
+
// 返回清理函数
|
|
182
188
|
return () => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
context.isActive = false;
|
|
190
|
+
controller.abort();
|
|
191
|
+
this.activeRequests.delete(context);
|
|
192
|
+
};
|
|
193
|
+
});
|
|
186
194
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
195
|
+
|
|
196
|
+
get<T = unknown>(url: string, _data = '{}', extra: RequestInit = {}): Observable<T> {
|
|
197
|
+
return this.request<T>('GET', url, undefined, extra);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
post<T = unknown>(url: string, data: BodyInit | Record<string, unknown> = {}, extra: RequestInit = {}): Observable<T> {
|
|
201
|
+
return this.request<T>('POST', url, data, extra);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private handleRequest(url: string, method: HttpMethod): RequestInit {
|
|
205
|
+
const headers: Record<string, string> = {};
|
|
206
|
+
|
|
207
|
+
// 只有 POST 请求才设置 Content-Type
|
|
208
|
+
if (method === 'POST') {
|
|
209
|
+
headers['Content-Type'] = 'application/x-ndjson';
|
|
192
210
|
}
|
|
193
211
|
|
|
194
|
-
const
|
|
212
|
+
const config: RequestInit = { headers };
|
|
213
|
+
const token = getToken();
|
|
195
214
|
|
|
196
|
-
if (!token && this.options.filter_url?.some(_url =>
|
|
197
|
-
this.options.tokenExpiration?.()
|
|
198
|
-
return config
|
|
215
|
+
if (!token && this.options.filter_url?.some(_url => url.includes(_url))) {
|
|
216
|
+
this.options.tokenExpiration?.();
|
|
217
|
+
return config;
|
|
199
218
|
}
|
|
200
219
|
|
|
201
|
-
if (
|
|
202
|
-
|
|
220
|
+
if (token) {
|
|
221
|
+
headers[TOKEN_KEY] = token;
|
|
203
222
|
}
|
|
204
223
|
|
|
205
224
|
if (this.options.requestOptions && isFunction(this.options.requestOptions)) {
|
|
206
|
-
const extraOptions = this.options.requestOptions(config)
|
|
225
|
+
const extraOptions = this.options.requestOptions(config);
|
|
207
226
|
if (extraOptions && isObject(extraOptions)) {
|
|
208
|
-
|
|
209
|
-
config[key] = extraOptions[key]
|
|
210
|
-
}
|
|
227
|
+
Object.assign(config, extraOptions);
|
|
211
228
|
}
|
|
212
229
|
}
|
|
213
230
|
|
|
214
|
-
return config
|
|
231
|
+
return config;
|
|
215
232
|
}
|
|
216
233
|
|
|
217
|
-
handleResponse(response) {
|
|
218
|
-
|
|
234
|
+
handleResponse<T>(response: T): T {
|
|
219
235
|
if (this.options.handleResponse && isFunction(this.options.handleResponse)) {
|
|
220
|
-
return this.options.handleResponse(response)
|
|
236
|
+
return this.options.handleResponse(response);
|
|
221
237
|
}
|
|
222
|
-
|
|
223
|
-
// const status = response[this.options.codeKey || 'status']
|
|
224
|
-
// response.success = status === this.options.code
|
|
225
|
-
|
|
226
|
-
return response
|
|
238
|
+
return response;
|
|
227
239
|
}
|
|
228
240
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
241
|
+
/**
|
|
242
|
+
* 取消所有活跃的请求
|
|
243
|
+
*/
|
|
244
|
+
cancelAll(): void {
|
|
245
|
+
this.activeRequests.forEach(context => {
|
|
246
|
+
context.isActive = false;
|
|
247
|
+
context.controller.abort();
|
|
248
|
+
});
|
|
249
|
+
this.activeRequests.clear();
|
|
237
250
|
}
|
|
238
251
|
}
|
|
239
252
|
|
|
240
|
-
|
|
241
|
-
|
|
253
|
+
// 默认实例
|
|
254
|
+
const defaultNdJson = new NdJson();
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 创建新的 NdJson 实例
|
|
258
|
+
*/
|
|
259
|
+
export const createNdJson = (options?: NdJsonOptions): NdJson => {
|
|
260
|
+
return new NdJson(options);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 初始化默认实例
|
|
265
|
+
*/
|
|
266
|
+
export const createNdJsonService = (options: NdJsonOptions): void => {
|
|
267
|
+
defaultNdJson.create(options);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// 导出默认实例 (保持向后兼容)
|
|
271
|
+
export const ndJson = defaultNdJson;
|
package/src/type.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type {AxiosError, AxiosResponse, InternalAxiosRequestConfig} from "axios";
|
|
2
2
|
|
|
3
|
+
export interface AxiosResponseRewrite<T = any>
|
|
4
|
+
extends AxiosResponse<T, any> {
|
|
5
|
+
result: T
|
|
6
|
+
success: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
3
10
|
export interface Options {
|
|
4
|
-
tokenExpiration: (err?: AxiosError<any>, response?:
|
|
11
|
+
tokenExpiration: (err?: AxiosError<any>, response?: AxiosResponseRewrite) => void
|
|
5
12
|
handleReconnect: () => Promise<any>
|
|
6
13
|
filter_url?: Array<string>
|
|
7
14
|
code?: number
|
|
@@ -16,7 +23,7 @@ export interface Options {
|
|
|
16
23
|
* response处理函数
|
|
17
24
|
* @param response AxiosResponse实例
|
|
18
25
|
*/
|
|
19
|
-
handleResponse?: (response:
|
|
26
|
+
handleResponse?: (response: AxiosResponseRewrite) => void
|
|
20
27
|
/**
|
|
21
28
|
* 错误处理函数
|
|
22
29
|
* @param msg 错误消息
|
|
@@ -30,15 +37,17 @@ export interface Options {
|
|
|
30
37
|
|
|
31
38
|
export interface ExpandRequestConfig extends InternalAxiosRequestConfig {
|
|
32
39
|
__requestKey?: string
|
|
40
|
+
_retry?: boolean
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
export interface ExpandAxiosResponse extends
|
|
43
|
+
export interface ExpandAxiosResponse<T = any> extends AxiosResponseRewrite<T> {
|
|
36
44
|
config: ExpandRequestConfig
|
|
37
45
|
message: string
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
export interface ExpandAxiosError<T> extends AxiosError<T> {
|
|
41
|
-
|
|
48
|
+
export interface ExpandAxiosError<T = any> extends Omit<AxiosError<T>, 'config'> {
|
|
49
|
+
config?: ExpandRequestConfig
|
|
50
|
+
response?: ExpandAxiosResponse<T>
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
export interface PageResult<T> {
|