accessio 1.3.0 → 1.5.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/cjs/accessio.cjs +98 -97
- package/cjs/accessio.cjs.map +1 -1
- package/cjs/core/accessioError.cjs +51 -1
- package/cjs/core/accessioError.cjs.map +1 -1
- package/cjs/core/buildURL.cjs +10 -4
- package/cjs/core/buildURL.cjs.map +1 -1
- package/cjs/core/fetchAdapter.cjs +125 -105
- package/cjs/core/fetchAdapter.cjs.map +1 -1
- package/cjs/core/request.cjs +73 -23
- package/cjs/core/request.cjs.map +1 -1
- package/cjs/core/retry.cjs +8 -5
- package/cjs/core/retry.cjs.map +1 -1
- package/cjs/helpers/debug.cjs +7 -1
- package/cjs/helpers/debug.cjs.map +1 -1
- package/cjs/helpers/flattenHeaders.cjs +4 -1
- package/cjs/helpers/flattenHeaders.cjs.map +1 -1
- package/cjs/helpers/rateLimiter.cjs +31 -3
- package/cjs/helpers/rateLimiter.cjs.map +1 -1
- package/cjs/helpers/settle.cjs +1 -1
- package/cjs/helpers/settle.cjs.map +1 -1
- package/cjs/helpers/toFormData.cjs +1 -1
- package/cjs/helpers/toFormData.cjs.map +1 -1
- package/cjs/interceptors/interceptorManager.cjs +25 -18
- package/cjs/interceptors/interceptorManager.cjs.map +1 -1
- package/index.d.ts +82 -21
- package/package.json +1 -1
- package/src/accessio.ts +148 -113
- package/src/core/accessioError.ts +57 -1
- package/src/core/buildURL.ts +14 -4
- package/src/core/fetchAdapter.ts +155 -125
- package/src/core/request.ts +85 -27
- package/src/core/retry.ts +8 -5
- package/src/helpers/debug.ts +7 -2
- package/src/helpers/flattenHeaders.ts +4 -1
- package/src/helpers/rateLimiter.ts +35 -3
- package/src/helpers/settle.ts +1 -1
- package/src/helpers/toFormData.ts +5 -1
- package/src/interceptors/interceptorManager.ts +26 -19
- package/src/types.ts +2 -1
package/src/accessio.ts
CHANGED
|
@@ -15,6 +15,66 @@ import type {
|
|
|
15
15
|
} from './types';
|
|
16
16
|
import defaultsConfig from './defaults/index';
|
|
17
17
|
|
|
18
|
+
function runRequestInterceptorsSync(
|
|
19
|
+
startConfig: AccessioRequestConfig,
|
|
20
|
+
interceptors: InterceptorHandler[],
|
|
21
|
+
): Promise<AccessioRequestConfig> {
|
|
22
|
+
let cfg = startConfig;
|
|
23
|
+
let rejectReason: any = null;
|
|
24
|
+
let isRejected = false;
|
|
25
|
+
|
|
26
|
+
for (const interceptor of interceptors) {
|
|
27
|
+
if (!isRejected) {
|
|
28
|
+
try {
|
|
29
|
+
if (interceptor.fulfilled) {
|
|
30
|
+
cfg = (interceptor.fulfilled as any)(cfg) as AccessioRequestConfig;
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
rejectReason = err;
|
|
34
|
+
isRejected = true;
|
|
35
|
+
}
|
|
36
|
+
} else if (interceptor.rejected) {
|
|
37
|
+
try {
|
|
38
|
+
cfg = interceptor.rejected(rejectReason) as AccessioRequestConfig;
|
|
39
|
+
isRejected = false;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
rejectReason = err;
|
|
42
|
+
isRejected = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return isRejected ? Promise.reject(rejectReason) : Promise.resolve(cfg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function runRequestInterceptorsAsync(
|
|
51
|
+
startConfig: AccessioRequestConfig,
|
|
52
|
+
interceptors: InterceptorHandler[],
|
|
53
|
+
): Promise<AccessioRequestConfig> {
|
|
54
|
+
let promise: Promise<any> = Promise.resolve(startConfig);
|
|
55
|
+
for (const interceptor of interceptors) {
|
|
56
|
+
promise = promise.then(
|
|
57
|
+
(value: any) => (interceptor.fulfilled ? (interceptor.fulfilled as any)(value) : value),
|
|
58
|
+
interceptor.rejected ?? undefined,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return promise as Promise<AccessioRequestConfig>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function dispatchAndRetry(cfg: AccessioRequestConfig): Promise<AccessioResponse> {
|
|
65
|
+
const fullUrl = buildURL(cfg.url ?? '', cfg.baseURL, cfg.params, cfg.paramsSerializer);
|
|
66
|
+
logRequest(cfg, fullUrl);
|
|
67
|
+
|
|
68
|
+
const enrichedCfg = fullUrl !== (cfg.url || '') ? { ...cfg, _builtUrl: fullUrl } : cfg;
|
|
69
|
+
|
|
70
|
+
const dispatchFn = cfg.rateLimiter
|
|
71
|
+
? (config: AccessioRequestConfig) =>
|
|
72
|
+
rateLimitedRequest(dispatchRequest, config.rateLimiter!, config)
|
|
73
|
+
: dispatchRequest;
|
|
74
|
+
|
|
75
|
+
return retryRequest(dispatchFn, enrichedCfg);
|
|
76
|
+
}
|
|
77
|
+
|
|
18
78
|
export class Accessio {
|
|
19
79
|
defaults: AccessioRequestConfig;
|
|
20
80
|
interceptors: Interceptors;
|
|
@@ -51,86 +111,17 @@ export class Accessio {
|
|
|
51
111
|
);
|
|
52
112
|
}
|
|
53
113
|
|
|
54
|
-
const requestInterceptors
|
|
55
|
-
|
|
56
|
-
let synchronousRequestInterceptors = true;
|
|
57
|
-
|
|
58
|
-
this.interceptors.request.forEach((interceptor: InterceptorHandler) => {
|
|
59
|
-
if (interceptor.runWhen && !interceptor.runWhen(mergedConfig)) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
|
|
63
|
-
requestInterceptors.unshift(interceptor);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
this.interceptors.response.forEach((interceptor: InterceptorHandler) => {
|
|
67
|
-
responseInterceptors.push(interceptor);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
let promise: Promise<any>;
|
|
71
|
-
|
|
72
|
-
if (synchronousRequestInterceptors) {
|
|
73
|
-
let newConfig = mergedConfig;
|
|
74
|
-
let rejectReason: any = null;
|
|
75
|
-
let isRejected = false;
|
|
76
|
-
|
|
77
|
-
for (const interceptor of requestInterceptors) {
|
|
78
|
-
if (!isRejected) {
|
|
79
|
-
try {
|
|
80
|
-
if (interceptor.fulfilled) {
|
|
81
|
-
newConfig = interceptor.fulfilled(newConfig);
|
|
82
|
-
}
|
|
83
|
-
} catch (err) {
|
|
84
|
-
rejectReason = err;
|
|
85
|
-
isRejected = true;
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
if (interceptor.rejected) {
|
|
89
|
-
try {
|
|
90
|
-
newConfig = interceptor.rejected(rejectReason);
|
|
91
|
-
isRejected = false;
|
|
92
|
-
} catch (err) {
|
|
93
|
-
rejectReason = err;
|
|
94
|
-
isRejected = true;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (isRejected) {
|
|
101
|
-
promise = Promise.reject(rejectReason);
|
|
102
|
-
} else {
|
|
103
|
-
promise = Promise.resolve(newConfig);
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
promise = Promise.resolve(mergedConfig);
|
|
107
|
-
for (const interceptor of requestInterceptors) {
|
|
108
|
-
promise = promise.then((value: any) => {
|
|
109
|
-
if (interceptor.fulfilled) {
|
|
110
|
-
return interceptor.fulfilled(value);
|
|
111
|
-
}
|
|
112
|
-
return value;
|
|
113
|
-
}, interceptor.rejected);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
promise = promise.then((cfg: any) => {
|
|
118
|
-
const fullUrl = buildURL(cfg.url ?? '', cfg.baseURL, cfg.params, cfg.paramsSerializer);
|
|
119
|
-
|
|
120
|
-
logRequest(cfg, fullUrl);
|
|
114
|
+
const { requestInterceptors, responseInterceptors, synchronous } =
|
|
115
|
+
this.collectInterceptors(mergedConfig);
|
|
121
116
|
|
|
122
|
-
|
|
117
|
+
let promise: Promise<any> = synchronous
|
|
118
|
+
? runRequestInterceptorsSync(mergedConfig, requestInterceptors)
|
|
119
|
+
: runRequestInterceptorsAsync(mergedConfig, requestInterceptors);
|
|
123
120
|
|
|
124
|
-
|
|
125
|
-
? (config: AccessioRequestConfig) =>
|
|
126
|
-
rateLimitedRequest(dispatchRequest, config.rateLimiter!, config)
|
|
127
|
-
: dispatchRequest;
|
|
128
|
-
|
|
129
|
-
return retryRequest(dispatchFn, enrichedCfg);
|
|
130
|
-
});
|
|
121
|
+
promise = promise.then((cfg: AccessioRequestConfig) => dispatchAndRetry(cfg));
|
|
131
122
|
|
|
132
123
|
promise = promise.then(
|
|
133
|
-
(value:
|
|
124
|
+
(value: AccessioResponse) => {
|
|
134
125
|
logResponse(value);
|
|
135
126
|
return value;
|
|
136
127
|
},
|
|
@@ -143,15 +134,37 @@ export class Accessio {
|
|
|
143
134
|
for (const interceptor of responseInterceptors) {
|
|
144
135
|
promise = promise.then((value: any) => {
|
|
145
136
|
if (interceptor.fulfilled) {
|
|
146
|
-
return interceptor.fulfilled(value);
|
|
137
|
+
return (interceptor.fulfilled as any)(value);
|
|
147
138
|
}
|
|
148
139
|
return value;
|
|
149
|
-
}, interceptor.rejected);
|
|
140
|
+
}, interceptor.rejected ?? undefined);
|
|
150
141
|
}
|
|
151
142
|
|
|
152
143
|
return promise;
|
|
153
144
|
}
|
|
154
145
|
|
|
146
|
+
private collectInterceptors(mergedConfig: AccessioRequestConfig): {
|
|
147
|
+
requestInterceptors: InterceptorHandler[];
|
|
148
|
+
responseInterceptors: InterceptorHandler[];
|
|
149
|
+
synchronous: boolean;
|
|
150
|
+
} {
|
|
151
|
+
const requestInterceptors: InterceptorHandler[] = [];
|
|
152
|
+
const responseInterceptors: InterceptorHandler[] = [];
|
|
153
|
+
let synchronous = true;
|
|
154
|
+
|
|
155
|
+
this.interceptors.request.forEach((interceptor: InterceptorHandler) => {
|
|
156
|
+
if (interceptor.runWhen && !interceptor.runWhen(mergedConfig)) return;
|
|
157
|
+
synchronous = synchronous && interceptor.synchronous;
|
|
158
|
+
requestInterceptors.unshift(interceptor);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
this.interceptors.response.forEach((interceptor: InterceptorHandler) => {
|
|
162
|
+
responseInterceptors.push(interceptor);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return { requestInterceptors, responseInterceptors, synchronous };
|
|
166
|
+
}
|
|
167
|
+
|
|
155
168
|
getUri(config?: AccessioRequestConfig): string {
|
|
156
169
|
const merged = mergeConfig(this.defaults, config);
|
|
157
170
|
return buildURL(merged.url ?? '', merged.baseURL, merged.params, merged.paramsSerializer);
|
|
@@ -197,7 +210,8 @@ export class Accessio {
|
|
|
197
210
|
return this.request<T>(mergeConfig(config || {}, { method: 'patch', url, data }));
|
|
198
211
|
}
|
|
199
212
|
|
|
200
|
-
|
|
213
|
+
private formRequest<T = any>(
|
|
214
|
+
method: 'post' | 'put' | 'patch',
|
|
201
215
|
url: string,
|
|
202
216
|
data?: any,
|
|
203
217
|
config?: AccessioRequestConfig,
|
|
@@ -205,7 +219,7 @@ export class Accessio {
|
|
|
205
219
|
const formData = data && !(data instanceof FormData) ? toFormData(data) : data;
|
|
206
220
|
return this.request<T>(
|
|
207
221
|
mergeConfig(config || {}, {
|
|
208
|
-
method
|
|
222
|
+
method,
|
|
209
223
|
url,
|
|
210
224
|
data: formData,
|
|
211
225
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
@@ -213,20 +227,20 @@ export class Accessio {
|
|
|
213
227
|
);
|
|
214
228
|
}
|
|
215
229
|
|
|
230
|
+
postForm<T = any>(
|
|
231
|
+
url: string,
|
|
232
|
+
data?: any,
|
|
233
|
+
config?: AccessioRequestConfig,
|
|
234
|
+
): Promise<AccessioResponse<T>> {
|
|
235
|
+
return this.formRequest<T>('post', url, data, config);
|
|
236
|
+
}
|
|
237
|
+
|
|
216
238
|
putForm<T = any>(
|
|
217
239
|
url: string,
|
|
218
240
|
data?: any,
|
|
219
241
|
config?: AccessioRequestConfig,
|
|
220
242
|
): Promise<AccessioResponse<T>> {
|
|
221
|
-
|
|
222
|
-
return this.request<T>(
|
|
223
|
-
mergeConfig(config || {}, {
|
|
224
|
-
method: 'put',
|
|
225
|
-
url,
|
|
226
|
-
data: formData,
|
|
227
|
-
headers: { 'Content-Type': 'multipart/form-data' },
|
|
228
|
-
}),
|
|
229
|
-
);
|
|
243
|
+
return this.formRequest<T>('put', url, data, config);
|
|
230
244
|
}
|
|
231
245
|
|
|
232
246
|
patchForm<T = any>(
|
|
@@ -234,15 +248,7 @@ export class Accessio {
|
|
|
234
248
|
data?: any,
|
|
235
249
|
config?: AccessioRequestConfig,
|
|
236
250
|
): Promise<AccessioResponse<T>> {
|
|
237
|
-
|
|
238
|
-
return this.request<T>(
|
|
239
|
-
mergeConfig(config || {}, {
|
|
240
|
-
method: 'patch',
|
|
241
|
-
url,
|
|
242
|
-
data: formData,
|
|
243
|
-
headers: { 'Content-Type': 'multipart/form-data' },
|
|
244
|
-
}),
|
|
245
|
-
);
|
|
251
|
+
return this.formRequest<T>('patch', url, data, config);
|
|
246
252
|
}
|
|
247
253
|
|
|
248
254
|
async *stream<T = any>(
|
|
@@ -255,19 +261,13 @@ export class Accessio {
|
|
|
255
261
|
if (!response.data) return;
|
|
256
262
|
|
|
257
263
|
const reader = response.data.getReader();
|
|
258
|
-
|
|
259
|
-
|
|
264
|
+
try {
|
|
265
|
+
const decoder = new TextDecoder();
|
|
266
|
+
let buffer = '';
|
|
260
267
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
buffer += decoder.decode(value, { stream: true });
|
|
266
|
-
const lines = buffer.split('\n');
|
|
267
|
-
buffer = lines.pop() || '';
|
|
268
|
-
|
|
269
|
-
for (const line of lines) {
|
|
270
|
-
if (line.trim().startsWith('data:')) {
|
|
268
|
+
const processLine = function* (line: string) {
|
|
269
|
+
const trimmed = line.trim();
|
|
270
|
+
if (trimmed.startsWith('data:')) {
|
|
271
271
|
const dataStr = line.replace(/^data:\s*/, '');
|
|
272
272
|
if (dataStr === '[DONE]') return;
|
|
273
273
|
try {
|
|
@@ -275,14 +275,39 @@ export class Accessio {
|
|
|
275
275
|
} catch (e) {
|
|
276
276
|
yield dataStr as any;
|
|
277
277
|
}
|
|
278
|
-
} else if (
|
|
278
|
+
} else if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
279
279
|
try {
|
|
280
280
|
yield JSON.parse(line);
|
|
281
281
|
} catch (e) {
|
|
282
282
|
// ignore partial json
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
while (true) {
|
|
288
|
+
const { done, value } = await reader.read();
|
|
289
|
+
if (done) break;
|
|
290
|
+
|
|
291
|
+
buffer += decoder.decode(value, { stream: true });
|
|
292
|
+
const lines = buffer.split('\n');
|
|
293
|
+
buffer = lines.pop() || '';
|
|
294
|
+
|
|
295
|
+
for (const line of lines) {
|
|
296
|
+
yield* processLine(line);
|
|
297
|
+
}
|
|
285
298
|
}
|
|
299
|
+
|
|
300
|
+
buffer += decoder.decode(new Uint8Array(), { stream: false });
|
|
301
|
+
if (buffer.trim()) {
|
|
302
|
+
yield* processLine(buffer);
|
|
303
|
+
}
|
|
304
|
+
} finally {
|
|
305
|
+
try {
|
|
306
|
+
await reader.cancel();
|
|
307
|
+
} catch {
|
|
308
|
+
// ignore errors on cancel
|
|
309
|
+
}
|
|
310
|
+
reader.releaseLock();
|
|
286
311
|
}
|
|
287
312
|
}
|
|
288
313
|
|
|
@@ -296,14 +321,24 @@ export class Accessio {
|
|
|
296
321
|
while (nextUrl) {
|
|
297
322
|
const response: AccessioResponse<any> = await this.get(nextUrl, currentConfig);
|
|
298
323
|
|
|
299
|
-
const
|
|
324
|
+
const data = response.data;
|
|
325
|
+
const items = Array.isArray(data)
|
|
326
|
+
? data
|
|
327
|
+
: data && typeof data === 'object'
|
|
328
|
+
? (data as any).data
|
|
329
|
+
: null;
|
|
330
|
+
|
|
300
331
|
if (Array.isArray(items)) {
|
|
301
332
|
for (const item of items) {
|
|
302
333
|
yield item;
|
|
303
334
|
}
|
|
304
335
|
}
|
|
305
336
|
|
|
306
|
-
nextUrl =
|
|
337
|
+
nextUrl =
|
|
338
|
+
data && typeof data === 'object'
|
|
339
|
+
? (data as any).next || (data as any).links?.next || null
|
|
340
|
+
: null;
|
|
341
|
+
|
|
307
342
|
if (nextUrl) {
|
|
308
343
|
currentConfig = mergeConfig(currentConfig, { url: nextUrl, params: {} });
|
|
309
344
|
}
|
|
@@ -6,7 +6,7 @@ function redactHeaders(headers: unknown): unknown {
|
|
|
6
6
|
const out: Record<string, unknown> = {};
|
|
7
7
|
for (const key of Object.keys(headers as Record<string, unknown>)) {
|
|
8
8
|
const value = (headers as Record<string, unknown>)[key];
|
|
9
|
-
if (/^authorization$/i.test(key)) {
|
|
9
|
+
if (/^authorization$/i.test(key) || /^cookie$/i.test(key) || /^set-cookie$/i.test(key)) {
|
|
10
10
|
out[key] = '[REDACTED]';
|
|
11
11
|
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
12
12
|
out[key] = redactHeaders(value);
|
|
@@ -17,11 +17,67 @@ function redactHeaders(headers: unknown): unknown {
|
|
|
17
17
|
return out;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const SENSITIVE_BODY_KEY =
|
|
21
|
+
/^(password|passwd|pwd|token|access_token|refresh_token|id_token|authorization|api[_-]?key|secret|client[_-]?secret|cookie|set[_-]?cookie|private[_-]?key|session)$/i;
|
|
22
|
+
|
|
23
|
+
export function redactBody(value: unknown, seen?: WeakSet<object>): unknown {
|
|
24
|
+
if (value === null || typeof value !== 'object') return value;
|
|
25
|
+
const visited = seen ?? new WeakSet<object>();
|
|
26
|
+
if (visited.has(value as object)) return '[Circular]';
|
|
27
|
+
visited.add(value as object);
|
|
28
|
+
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
return value.map((item) => redactBody(item, visited));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const out: Record<string, unknown> = {};
|
|
34
|
+
for (const key of Object.keys(value as Record<string, unknown>)) {
|
|
35
|
+
const v = (value as Record<string, unknown>)[key];
|
|
36
|
+
if (SENSITIVE_BODY_KEY.test(key)) {
|
|
37
|
+
out[key] = '[REDACTED]';
|
|
38
|
+
} else {
|
|
39
|
+
out[key] = redactBody(v, visited);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function redactParams(params: unknown): unknown {
|
|
46
|
+
if (!params || typeof params !== 'object') return params;
|
|
47
|
+
const out: Record<string, unknown> = {};
|
|
48
|
+
for (const key of Object.keys(params as Record<string, unknown>)) {
|
|
49
|
+
const value = (params as Record<string, unknown>)[key];
|
|
50
|
+
if (SENSITIVE_BODY_KEY.test(key)) {
|
|
51
|
+
out[key] = '[REDACTED]';
|
|
52
|
+
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
53
|
+
out[key] = redactParams(value);
|
|
54
|
+
} else {
|
|
55
|
+
out[key] = value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function redactURL(url: string | undefined): string | undefined {
|
|
62
|
+
if (!url) return url;
|
|
63
|
+
// Match inline credentials: http://user:pass@host
|
|
64
|
+
return url.replace(/^([a-z][a-z\d+\-.]*:\/\/)([^/]+)@/i, (match, protocol, userInfo) => {
|
|
65
|
+
const parts = userInfo.split(':');
|
|
66
|
+
if (parts.length > 1) {
|
|
67
|
+
return `${protocol}${parts[0]}:[REDACTED]@`;
|
|
68
|
+
}
|
|
69
|
+
return `${protocol}[REDACTED]@`;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
20
73
|
export function redactConfig(config: AccessioRequestConfig | null): AccessioRequestConfig | null {
|
|
21
74
|
if (!config) return config;
|
|
22
75
|
const clone = { ...config } as AccessioRequestConfig & { auth?: unknown };
|
|
23
76
|
if ('auth' in clone) delete clone.auth;
|
|
24
77
|
if (clone.headers) clone.headers = redactHeaders(clone.headers) as typeof clone.headers;
|
|
78
|
+
if (clone.params) clone.params = redactParams(clone.params) as typeof clone.params;
|
|
79
|
+
if (clone.url) clone.url = redactURL(clone.url);
|
|
80
|
+
if (clone._builtUrl) clone._builtUrl = redactURL(clone._builtUrl);
|
|
25
81
|
return clone;
|
|
26
82
|
}
|
|
27
83
|
|
package/src/core/buildURL.ts
CHANGED
|
@@ -76,11 +76,19 @@ export default function buildURL(
|
|
|
76
76
|
let fullURL = baseURL && !isAbsoluteURL(url) ? combineURLs(baseURL, url) : url || '';
|
|
77
77
|
|
|
78
78
|
let finalParams = params;
|
|
79
|
-
if (params && typeof params === 'object') {
|
|
80
|
-
const unusedParams = {
|
|
79
|
+
if (params && typeof params === 'object' && !(params instanceof URLSearchParams)) {
|
|
80
|
+
const unusedParams: Record<string, unknown> = {};
|
|
81
|
+
for (const key of Object.keys(params)) {
|
|
82
|
+
if (key === '__proto__' || key === 'prototype' || key === 'constructor') continue;
|
|
83
|
+
unusedParams[key] = (params as Record<string, unknown>)[key];
|
|
84
|
+
}
|
|
81
85
|
fullURL = fullURL.replace(/(?::([a-zA-Z0-9_]+))|(?:{([a-zA-Z0-9_]+)})/g, (match, p1, p2) => {
|
|
82
86
|
const key = p1 || p2;
|
|
83
|
-
if (
|
|
87
|
+
if (
|
|
88
|
+
key &&
|
|
89
|
+
Object.prototype.hasOwnProperty.call(unusedParams, key) &&
|
|
90
|
+
unusedParams[key] !== undefined
|
|
91
|
+
) {
|
|
84
92
|
const val = unusedParams[key];
|
|
85
93
|
delete unusedParams[key];
|
|
86
94
|
return encodeURIComponent(String(val));
|
|
@@ -93,10 +101,12 @@ export default function buildURL(
|
|
|
93
101
|
const serialized = serializeParams(finalParams as Record<string, unknown>, paramsSerializer);
|
|
94
102
|
if (serialized) {
|
|
95
103
|
const hashIndex = fullURL.indexOf('#');
|
|
104
|
+
let fragment = '';
|
|
96
105
|
if (hashIndex !== -1) {
|
|
106
|
+
fragment = fullURL.slice(hashIndex);
|
|
97
107
|
fullURL = fullURL.slice(0, hashIndex);
|
|
98
108
|
}
|
|
99
|
-
fullURL += (fullURL.indexOf('?') === -1 ? '?' : '&') + serialized;
|
|
109
|
+
fullURL += (fullURL.indexOf('?') === -1 ? '?' : '&') + serialized + fragment;
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
return fullURL;
|