accessio 1.1.1 → 1.1.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 +3 -1
- package/cjs/accessio.cjs +43 -7
- package/cjs/accessio.cjs.map +1 -1
- package/cjs/core/buildURL.cjs +1 -1
- package/cjs/core/buildURL.cjs.map +1 -1
- package/cjs/core/fetchAdapter.cjs +187 -0
- package/cjs/core/fetchAdapter.cjs.map +1 -0
- package/cjs/core/mergeConfig.cjs +2 -2
- package/cjs/core/mergeConfig.cjs.map +1 -1
- package/cjs/core/request.cjs +18 -196
- package/cjs/core/request.cjs.map +1 -1
- package/cjs/defaults/transforms.cjs.map +1 -1
- package/cjs/helpers/auth.cjs +45 -0
- package/cjs/helpers/auth.cjs.map +1 -0
- package/cjs/helpers/flattenHeaders.cjs +78 -0
- package/cjs/helpers/flattenHeaders.cjs.map +1 -0
- package/cjs/helpers/parseHeaders.cjs +16 -4
- package/cjs/helpers/parseHeaders.cjs.map +1 -1
- package/cjs/helpers/rateLimiter.cjs +18 -8
- package/cjs/helpers/rateLimiter.cjs.map +1 -1
- package/cjs/helpers/transformData.cjs +2 -2
- package/cjs/helpers/transformData.cjs.map +1 -1
- package/package.json +2 -2
- package/src/accessio.ts +46 -8
- package/src/core/buildURL.ts +1 -1
- package/src/core/fetchAdapter.ts +184 -0
- package/src/core/mergeConfig.ts +2 -2
- package/src/core/request.ts +20 -231
- package/src/defaults/transforms.ts +4 -1
- package/src/helpers/auth.ts +26 -0
- package/src/helpers/flattenHeaders.ts +59 -0
- package/src/helpers/parseHeaders.ts +19 -6
- package/src/helpers/rateLimiter.ts +18 -8
- package/src/helpers/transformData.ts +4 -4
- package/src/types.ts +6 -3
package/src/accessio.ts
CHANGED
|
@@ -52,11 +52,13 @@ export class Accessio {
|
|
|
52
52
|
|
|
53
53
|
const requestInterceptors: any[] = [];
|
|
54
54
|
const responseInterceptors: any[] = [];
|
|
55
|
+
let synchronousRequestInterceptors = true;
|
|
55
56
|
|
|
56
57
|
this.interceptors.request.forEach((interceptor: InterceptorHandler) => {
|
|
57
58
|
if (interceptor.runWhen && !interceptor.runWhen(mergedConfig)) {
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
61
|
+
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
|
|
60
62
|
requestInterceptors.unshift(interceptor);
|
|
61
63
|
});
|
|
62
64
|
|
|
@@ -64,15 +66,51 @@ export class Accessio {
|
|
|
64
66
|
responseInterceptors.push(interceptor);
|
|
65
67
|
});
|
|
66
68
|
|
|
67
|
-
let promise: Promise<any
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
let promise: Promise<any>;
|
|
70
|
+
|
|
71
|
+
if (synchronousRequestInterceptors) {
|
|
72
|
+
let newConfig = mergedConfig;
|
|
73
|
+
let rejectReason: any = null;
|
|
74
|
+
let isRejected = false;
|
|
75
|
+
|
|
76
|
+
for (const interceptor of requestInterceptors) {
|
|
77
|
+
if (!isRejected) {
|
|
78
|
+
try {
|
|
79
|
+
if (interceptor.fulfilled) {
|
|
80
|
+
newConfig = interceptor.fulfilled(newConfig);
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
rejectReason = err;
|
|
84
|
+
isRejected = true;
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
if (interceptor.rejected) {
|
|
88
|
+
try {
|
|
89
|
+
newConfig = interceptor.rejected(rejectReason);
|
|
90
|
+
isRejected = false;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
rejectReason = err;
|
|
93
|
+
isRejected = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
73
96
|
}
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (isRejected) {
|
|
100
|
+
promise = Promise.reject(rejectReason);
|
|
101
|
+
} else {
|
|
102
|
+
promise = Promise.resolve(newConfig);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
promise = Promise.resolve(mergedConfig);
|
|
106
|
+
for (const interceptor of requestInterceptors) {
|
|
107
|
+
promise = promise.then((value: any) => {
|
|
108
|
+
if (interceptor.fulfilled) {
|
|
109
|
+
return interceptor.fulfilled(value);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}, interceptor.rejected);
|
|
113
|
+
}
|
|
76
114
|
}
|
|
77
115
|
|
|
78
116
|
promise = promise.then((cfg: any) => {
|
package/src/core/buildURL.ts
CHANGED
|
@@ -64,7 +64,7 @@ function combineURLs(baseURL: string, relativeURL: string): string {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function isAbsoluteURL(url: string): boolean {
|
|
67
|
-
return /^([a-z][a-z\d+\-.]*:)
|
|
67
|
+
return /^([a-z][a-z\d+\-.]*:)/i.test(url);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export default function buildURL(
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import AccessioError from './accessioError';
|
|
2
|
+
import parseHeaders from '../helpers/parseHeaders';
|
|
3
|
+
import type { AccessioRequestConfig, AccessioResponse } from '../types';
|
|
4
|
+
|
|
5
|
+
async function readResponseData(fetchResponse: Response, responseType: string): Promise<unknown> {
|
|
6
|
+
switch (responseType) {
|
|
7
|
+
case 'arraybuffer':
|
|
8
|
+
return await fetchResponse.arrayBuffer();
|
|
9
|
+
case 'blob':
|
|
10
|
+
return await fetchResponse.blob();
|
|
11
|
+
case 'stream':
|
|
12
|
+
return fetchResponse.body;
|
|
13
|
+
case 'json':
|
|
14
|
+
default: {
|
|
15
|
+
const contentType = fetchResponse.headers.get('content-type') || '';
|
|
16
|
+
if (contentType.includes('application/json')) {
|
|
17
|
+
if (typeof fetchResponse.clone === 'function') {
|
|
18
|
+
const text = await fetchResponse.clone().text();
|
|
19
|
+
return text ? await fetchResponse.json() : '';
|
|
20
|
+
} else {
|
|
21
|
+
const text = await fetchResponse.text();
|
|
22
|
+
return text ? JSON.parse(text) : '';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return await fetchResponse.text();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default async function fetchAdapter(
|
|
31
|
+
config: AccessioRequestConfig,
|
|
32
|
+
fullURL: string,
|
|
33
|
+
fetchOptions: RequestInit,
|
|
34
|
+
requestStartTime: number,
|
|
35
|
+
): Promise<AccessioResponse> {
|
|
36
|
+
let abortController: AbortController | null = null;
|
|
37
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
38
|
+
let isTimedOut = false;
|
|
39
|
+
let onUserAbort: (() => void) | null = null;
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
config.timeout !== undefined &&
|
|
43
|
+
(typeof config.timeout !== 'number' || isNaN(config.timeout) || config.timeout < 0)
|
|
44
|
+
) {
|
|
45
|
+
throw new AccessioError(
|
|
46
|
+
`Invalid timeout value: ${config.timeout}`,
|
|
47
|
+
AccessioError.ERR_BAD_OPTION_VALUE,
|
|
48
|
+
config,
|
|
49
|
+
null,
|
|
50
|
+
null,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const timeoutValue = Number(config.timeout);
|
|
55
|
+
if (!isNaN(timeoutValue) && timeoutValue > 0) {
|
|
56
|
+
abortController = new AbortController();
|
|
57
|
+
|
|
58
|
+
timeoutId = setTimeout(() => {
|
|
59
|
+
isTimedOut = true;
|
|
60
|
+
abortController!.abort(
|
|
61
|
+
new AccessioError(
|
|
62
|
+
`timeout of ${timeoutValue}ms exceeded`,
|
|
63
|
+
AccessioError.ETIMEDOUT,
|
|
64
|
+
config,
|
|
65
|
+
null,
|
|
66
|
+
null,
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
}, timeoutValue);
|
|
70
|
+
|
|
71
|
+
if (config.signal) {
|
|
72
|
+
if (typeof AbortSignal.any === 'function') {
|
|
73
|
+
fetchOptions.signal = AbortSignal.any([config.signal, abortController.signal]);
|
|
74
|
+
} else {
|
|
75
|
+
if (config.signal.aborted) {
|
|
76
|
+
abortController.abort(config.signal.reason);
|
|
77
|
+
} else {
|
|
78
|
+
onUserAbort = () => {
|
|
79
|
+
if (!isTimedOut && abortController) {
|
|
80
|
+
abortController.abort(config.signal!.reason);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
config.signal.addEventListener('abort', onUserAbort, {
|
|
84
|
+
once: true,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
fetchOptions.signal = abortController.signal;
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
fetchOptions.signal = abortController.signal;
|
|
91
|
+
}
|
|
92
|
+
} else if (config.signal) {
|
|
93
|
+
fetchOptions.signal = config.signal;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const fetchResponse = await fetch(fullURL, fetchOptions);
|
|
98
|
+
|
|
99
|
+
let responseData: unknown;
|
|
100
|
+
const responseType = config.responseType || 'json';
|
|
101
|
+
|
|
102
|
+
const contentLength = fetchResponse.headers.get('content-length');
|
|
103
|
+
if (
|
|
104
|
+
contentLength &&
|
|
105
|
+
config.maxContentLength &&
|
|
106
|
+
parseInt(contentLength, 10) > config.maxContentLength
|
|
107
|
+
) {
|
|
108
|
+
throw new AccessioError(
|
|
109
|
+
`maxContentLength size of ${config.maxContentLength} exceeded`,
|
|
110
|
+
AccessioError.ERR_BAD_RESPONSE,
|
|
111
|
+
config,
|
|
112
|
+
fetchResponse,
|
|
113
|
+
null,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
responseData = await readResponseData(fetchResponse, responseType);
|
|
119
|
+
} catch (readError) {
|
|
120
|
+
throw AccessioError.from(
|
|
121
|
+
readError as Error,
|
|
122
|
+
AccessioError.ERR_BAD_RESPONSE,
|
|
123
|
+
config,
|
|
124
|
+
fetchResponse,
|
|
125
|
+
null,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const responseHeaders = parseHeaders(fetchResponse.headers);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
data: responseData,
|
|
133
|
+
status: fetchResponse.status,
|
|
134
|
+
statusText: fetchResponse.statusText,
|
|
135
|
+
headers: responseHeaders,
|
|
136
|
+
config: config,
|
|
137
|
+
request: fetchResponse,
|
|
138
|
+
duration: Date.now() - requestStartTime,
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof AccessioError) {
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
146
|
+
if (isTimedOut) {
|
|
147
|
+
throw new AccessioError(
|
|
148
|
+
`timeout of ${config.timeout}ms exceeded`,
|
|
149
|
+
AccessioError.ETIMEDOUT,
|
|
150
|
+
config,
|
|
151
|
+
null,
|
|
152
|
+
null,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
throw new AccessioError('Request aborted', AccessioError.ERR_CANCELED, config, null, null);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
error instanceof TypeError &&
|
|
160
|
+
(error.message.toLowerCase().includes('url') || error.message.toLowerCase().includes('fetch'))
|
|
161
|
+
) {
|
|
162
|
+
throw new AccessioError(
|
|
163
|
+
`Invalid URL: ${fullURL}`,
|
|
164
|
+
AccessioError.ERR_INVALID_URL,
|
|
165
|
+
config,
|
|
166
|
+
null,
|
|
167
|
+
null,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
throw AccessioError.from(
|
|
172
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
173
|
+
AccessioError.ERR_NETWORK,
|
|
174
|
+
config,
|
|
175
|
+
null,
|
|
176
|
+
null,
|
|
177
|
+
);
|
|
178
|
+
} finally {
|
|
179
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
180
|
+
if (config.signal && onUserAbort) {
|
|
181
|
+
config.signal.removeEventListener('abort', onUserAbort);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
package/src/core/mergeConfig.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AccessioRequestConfig } from '../types';
|
|
2
2
|
|
|
3
3
|
function deepMerge(...sources: any[]): Record<string, any> {
|
|
4
|
-
const result: Record<string, any> =
|
|
4
|
+
const result: Record<string, any> = Object.create(null);
|
|
5
5
|
|
|
6
6
|
for (const source of sources) {
|
|
7
7
|
if (!source || typeof source !== 'object') continue;
|
|
@@ -43,7 +43,7 @@ export default function mergeConfig(
|
|
|
43
43
|
config1: AccessioRequestConfig = {},
|
|
44
44
|
config2: AccessioRequestConfig = {},
|
|
45
45
|
): AccessioRequestConfig {
|
|
46
|
-
const merged: any =
|
|
46
|
+
const merged: any = Object.create(null);
|
|
47
47
|
|
|
48
48
|
const allKeys = new Set<string>([...Object.keys(config1), ...Object.keys(config2)]);
|
|
49
49
|
|
package/src/core/request.ts
CHANGED
|
@@ -1,55 +1,13 @@
|
|
|
1
1
|
import buildURL from './buildURL';
|
|
2
2
|
import AccessioError from './accessioError';
|
|
3
|
-
import parseHeaders from '../helpers/parseHeaders';
|
|
4
3
|
import transformData from '../helpers/transformData';
|
|
5
4
|
import settle from '../helpers/settle';
|
|
5
|
+
import { flattenHeaders, removeContentType, buildFetchHeaders } from '../helpers/flattenHeaders';
|
|
6
|
+
import { setBasicAuth } from '../helpers/auth';
|
|
7
|
+
import fetchAdapter from './fetchAdapter';
|
|
6
8
|
import type { AccessioRequestConfig, AccessioResponse, TransformFunction } from '../types';
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
'common',
|
|
10
|
-
'delete',
|
|
11
|
-
'get',
|
|
12
|
-
'head',
|
|
13
|
-
'options',
|
|
14
|
-
'post',
|
|
15
|
-
'put',
|
|
16
|
-
'patch',
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
type HeadersConfig = Record<string, Record<string, string>>;
|
|
20
|
-
|
|
21
|
-
function flattenHeaders(
|
|
22
|
-
headers: HeadersConfig | undefined,
|
|
23
|
-
method?: string,
|
|
24
|
-
): Record<string, string> {
|
|
25
|
-
if (!headers) return {};
|
|
26
|
-
|
|
27
|
-
const merged: Record<string, string> = {};
|
|
28
|
-
const methodLower = (method || 'get').toLowerCase();
|
|
29
|
-
|
|
30
|
-
if (headers['common']) {
|
|
31
|
-
Object.assign(merged, headers['common']);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (headers[methodLower]) {
|
|
35
|
-
Object.assign(merged, headers[methodLower]);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
for (const key in headers) {
|
|
39
|
-
if (Object.prototype.hasOwnProperty.call(headers, key) && !METHOD_KEYS.has(key)) {
|
|
40
|
-
merged[key] = headers[key] as unknown as string;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return merged;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function removeContentType(headers: Record<string, string>): void {
|
|
48
|
-
const keys = Object.keys(headers).filter((k) => k.toLowerCase() === 'content-type');
|
|
49
|
-
for (const key of keys) {
|
|
50
|
-
delete headers[key];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
10
|
+
type HeadersConfig = Record<string, Record<string, string | string[]>>;
|
|
53
11
|
|
|
54
12
|
function buildTransformArray(
|
|
55
13
|
transform: TransformFunction | TransformFunction[] | undefined,
|
|
@@ -59,42 +17,9 @@ function buildTransformArray(
|
|
|
59
17
|
return [transform];
|
|
60
18
|
}
|
|
61
19
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const password = config.auth.password || '';
|
|
66
|
-
const credentials = `${username}:${password}`;
|
|
67
|
-
|
|
68
|
-
let encoded: string;
|
|
69
|
-
if (typeof Buffer !== 'undefined') {
|
|
70
|
-
encoded = Buffer.from(credentials).toString('base64');
|
|
71
|
-
} else {
|
|
72
|
-
encoded = btoa(
|
|
73
|
-
encodeURIComponent(credentials).replace(/%([0-9A-F]{2})/g, (match, p1) => {
|
|
74
|
-
return String.fromCharCode(parseInt(p1, 16));
|
|
75
|
-
}),
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
headers['Authorization'] = `Basic ${encoded}`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function readResponseData(fetchResponse: Response, responseType: string): Promise<unknown> {
|
|
82
|
-
switch (responseType) {
|
|
83
|
-
case 'arraybuffer':
|
|
84
|
-
return await fetchResponse.arrayBuffer();
|
|
85
|
-
case 'blob':
|
|
86
|
-
return await fetchResponse.blob();
|
|
87
|
-
case 'text':
|
|
88
|
-
return await fetchResponse.text();
|
|
89
|
-
case 'stream':
|
|
90
|
-
return fetchResponse.body;
|
|
91
|
-
case 'json':
|
|
92
|
-
default:
|
|
93
|
-
return await fetchResponse.text();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export default function dispatchRequest(config: AccessioRequestConfig): Promise<AccessioResponse> {
|
|
20
|
+
export default async function dispatchRequest(
|
|
21
|
+
config: AccessioRequestConfig,
|
|
22
|
+
): Promise<AccessioResponse> {
|
|
98
23
|
const fullURL =
|
|
99
24
|
config._builtUrl ||
|
|
100
25
|
buildURL(
|
|
@@ -108,7 +33,7 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
108
33
|
|
|
109
34
|
const requestTransforms = buildTransformArray(config.transformRequest);
|
|
110
35
|
|
|
111
|
-
const requestData = transformData(requestTransforms, config.data, flatHeaders, config);
|
|
36
|
+
const requestData = await transformData(requestTransforms, config.data, flatHeaders, config);
|
|
112
37
|
|
|
113
38
|
if (
|
|
114
39
|
requestData === null ||
|
|
@@ -122,7 +47,7 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
122
47
|
|
|
123
48
|
const fetchOptions: RequestInit = {
|
|
124
49
|
method: (config.method || 'GET').toUpperCase(),
|
|
125
|
-
headers: flatHeaders,
|
|
50
|
+
headers: buildFetchHeaders(flatHeaders),
|
|
126
51
|
};
|
|
127
52
|
|
|
128
53
|
const methodsWithBody = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
|
@@ -145,156 +70,20 @@ export default function dispatchRequest(config: AccessioRequestConfig): Promise<
|
|
|
145
70
|
(fetchOptions as any).agent = config.agent;
|
|
146
71
|
}
|
|
147
72
|
|
|
148
|
-
let abortController: AbortController | null = null;
|
|
149
|
-
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
150
|
-
let isTimedOut = false;
|
|
151
|
-
let onUserAbort: (() => void) | null = null;
|
|
152
|
-
|
|
153
|
-
const timeoutValue = Number(config.timeout);
|
|
154
|
-
if (!isNaN(timeoutValue) && timeoutValue > 0) {
|
|
155
|
-
abortController = new AbortController();
|
|
156
|
-
|
|
157
|
-
timeoutId = setTimeout(() => {
|
|
158
|
-
isTimedOut = true;
|
|
159
|
-
abortController!.abort(
|
|
160
|
-
new AccessioError(
|
|
161
|
-
`timeout of ${timeoutValue}ms exceeded`,
|
|
162
|
-
AccessioError.ETIMEDOUT,
|
|
163
|
-
config,
|
|
164
|
-
null,
|
|
165
|
-
null,
|
|
166
|
-
),
|
|
167
|
-
);
|
|
168
|
-
}, timeoutValue);
|
|
169
|
-
|
|
170
|
-
if (config.signal) {
|
|
171
|
-
if (typeof AbortSignal.any === 'function') {
|
|
172
|
-
fetchOptions.signal = AbortSignal.any([config.signal, abortController.signal]);
|
|
173
|
-
} else {
|
|
174
|
-
if (config.signal.aborted) {
|
|
175
|
-
abortController.abort(config.signal.reason);
|
|
176
|
-
} else {
|
|
177
|
-
onUserAbort = () => {
|
|
178
|
-
if (!isTimedOut && abortController) {
|
|
179
|
-
abortController.abort(config.signal!.reason);
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
config.signal.addEventListener('abort', onUserAbort, {
|
|
183
|
-
once: true,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
fetchOptions.signal = abortController.signal;
|
|
187
|
-
}
|
|
188
|
-
} else {
|
|
189
|
-
fetchOptions.signal = abortController.signal;
|
|
190
|
-
}
|
|
191
|
-
} else if (config.signal) {
|
|
192
|
-
fetchOptions.signal = config.signal;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
73
|
const requestStartTime = Date.now();
|
|
196
74
|
|
|
197
|
-
|
|
198
|
-
.then(async (fetchResponse) => {
|
|
199
|
-
let responseData: unknown;
|
|
200
|
-
const responseType = config.responseType || 'json';
|
|
75
|
+
const response = await fetchAdapter(config, fullURL, fetchOptions, requestStartTime);
|
|
201
76
|
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
contentLength &&
|
|
205
|
-
config.maxContentLength &&
|
|
206
|
-
parseInt(contentLength, 10) > config.maxContentLength
|
|
207
|
-
) {
|
|
208
|
-
throw new AccessioError(
|
|
209
|
-
`maxContentLength size of ${config.maxContentLength} exceeded`,
|
|
210
|
-
AccessioError.ERR_BAD_RESPONSE,
|
|
211
|
-
config,
|
|
212
|
-
fetchResponse,
|
|
213
|
-
null,
|
|
214
|
-
);
|
|
215
|
-
}
|
|
77
|
+
const responseTransforms = buildTransformArray(config.transformResponse);
|
|
216
78
|
|
|
217
|
-
|
|
218
|
-
responseData = await readResponseData(fetchResponse, responseType);
|
|
219
|
-
} catch (readError) {
|
|
220
|
-
throw AccessioError.from(
|
|
221
|
-
readError as Error,
|
|
222
|
-
AccessioError.ERR_BAD_RESPONSE,
|
|
223
|
-
config,
|
|
224
|
-
fetchResponse,
|
|
225
|
-
null,
|
|
226
|
-
);
|
|
227
|
-
}
|
|
79
|
+
response.data = await transformData(responseTransforms, response.data, response.headers, config);
|
|
228
80
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
status: fetchResponse.status,
|
|
238
|
-
statusText: fetchResponse.statusText,
|
|
239
|
-
headers: responseHeaders,
|
|
240
|
-
config: config,
|
|
241
|
-
request: fetchResponse,
|
|
242
|
-
duration: Date.now() - requestStartTime,
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
return new Promise<AccessioResponse>((resolve, reject) => {
|
|
246
|
-
settle(
|
|
247
|
-
resolve as (value: AccessioResponse) => void,
|
|
248
|
-
reject as (reason: AccessioError) => void,
|
|
249
|
-
response,
|
|
250
|
-
config,
|
|
251
|
-
);
|
|
252
|
-
});
|
|
253
|
-
})
|
|
254
|
-
.catch((error) => {
|
|
255
|
-
if (error instanceof AccessioError) {
|
|
256
|
-
throw error;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
260
|
-
if (isTimedOut) {
|
|
261
|
-
throw new AccessioError(
|
|
262
|
-
`timeout of ${config.timeout}ms exceeded`,
|
|
263
|
-
AccessioError.ETIMEDOUT,
|
|
264
|
-
config,
|
|
265
|
-
null,
|
|
266
|
-
null,
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
throw new AccessioError('Request aborted', AccessioError.ERR_CANCELED, config, null, null);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (
|
|
273
|
-
error instanceof TypeError &&
|
|
274
|
-
(error.message.toLowerCase().includes('url') ||
|
|
275
|
-
error.message.toLowerCase().includes('fetch'))
|
|
276
|
-
) {
|
|
277
|
-
throw new AccessioError(
|
|
278
|
-
`Invalid URL: ${fullURL}`,
|
|
279
|
-
AccessioError.ERR_INVALID_URL,
|
|
280
|
-
config,
|
|
281
|
-
null,
|
|
282
|
-
null,
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
throw AccessioError.from(
|
|
287
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
288
|
-
AccessioError.ERR_NETWORK,
|
|
289
|
-
config,
|
|
290
|
-
null,
|
|
291
|
-
null,
|
|
292
|
-
);
|
|
293
|
-
})
|
|
294
|
-
.finally(() => {
|
|
295
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
296
|
-
if (config.signal && onUserAbort) {
|
|
297
|
-
config.signal.removeEventListener('abort', onUserAbort);
|
|
298
|
-
}
|
|
299
|
-
});
|
|
81
|
+
return new Promise<AccessioResponse>((resolve, reject) => {
|
|
82
|
+
settle(
|
|
83
|
+
resolve as (value: AccessioResponse) => void,
|
|
84
|
+
reject as (reason: AccessioError) => void,
|
|
85
|
+
response,
|
|
86
|
+
config,
|
|
87
|
+
);
|
|
88
|
+
});
|
|
300
89
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export function defaultTransformRequest(
|
|
1
|
+
export function defaultTransformRequest(
|
|
2
|
+
data: unknown,
|
|
3
|
+
headers: Record<string, string | string[]>,
|
|
4
|
+
): unknown {
|
|
2
5
|
if (data === null || data === undefined) {
|
|
3
6
|
return data;
|
|
4
7
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AccessioRequestConfig } from '../types';
|
|
2
|
+
|
|
3
|
+
export function setBasicAuth(
|
|
4
|
+
config: AccessioRequestConfig,
|
|
5
|
+
headers: Record<string, string | string[]>,
|
|
6
|
+
): void {
|
|
7
|
+
if (!config.auth) return;
|
|
8
|
+
const username = config.auth.username || '';
|
|
9
|
+
const password = config.auth.password || '';
|
|
10
|
+
const credentials = `${username}:${password}`;
|
|
11
|
+
|
|
12
|
+
let encoded: string;
|
|
13
|
+
if (typeof Buffer !== 'undefined') {
|
|
14
|
+
encoded = Buffer.from(credentials).toString('base64');
|
|
15
|
+
} else {
|
|
16
|
+
// Cryptic but effective UTF-8 to Base64 conversion for browsers lacking Buffer.
|
|
17
|
+
// encodeURIComponent converts non-ASCII to %XX, then we replace %XX with raw bytes
|
|
18
|
+
// before applying btoa.
|
|
19
|
+
encoded = btoa(
|
|
20
|
+
encodeURIComponent(credentials).replace(/%([0-9A-F]{2})/g, (match, p1) => {
|
|
21
|
+
return String.fromCharCode(parseInt(p1, 16));
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
headers['Authorization'] = `Basic ${encoded}`;
|
|
26
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const METHOD_KEYS = new Set<string>([
|
|
2
|
+
'common',
|
|
3
|
+
'delete',
|
|
4
|
+
'get',
|
|
5
|
+
'head',
|
|
6
|
+
'options',
|
|
7
|
+
'post',
|
|
8
|
+
'put',
|
|
9
|
+
'patch',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
type HeadersConfig = Record<string, Record<string, string | string[]>>;
|
|
13
|
+
|
|
14
|
+
export function flattenHeaders(
|
|
15
|
+
headers: HeadersConfig | undefined,
|
|
16
|
+
method?: string,
|
|
17
|
+
): Record<string, string | string[]> {
|
|
18
|
+
if (!headers) return {};
|
|
19
|
+
|
|
20
|
+
const merged: Record<string, string | string[]> = {};
|
|
21
|
+
const methodLower = (method || 'get').toLowerCase();
|
|
22
|
+
|
|
23
|
+
if (headers['common']) {
|
|
24
|
+
Object.assign(merged, headers['common']);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (headers[methodLower]) {
|
|
28
|
+
Object.assign(merged, headers[methodLower]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const key in headers) {
|
|
32
|
+
if (Object.prototype.hasOwnProperty.call(headers, key) && !METHOD_KEYS.has(key)) {
|
|
33
|
+
merged[key] = headers[key] as unknown as string | string[];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return merged;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function removeContentType(headers: Record<string, string | string[]>): void {
|
|
41
|
+
const keys = Object.keys(headers).filter((k) => k.toLowerCase() === 'content-type');
|
|
42
|
+
for (const key of keys) {
|
|
43
|
+
delete headers[key];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildFetchHeaders(headers: Record<string, string | string[]>): Headers {
|
|
48
|
+
const fetchHeaders = new Headers();
|
|
49
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
for (const v of value) {
|
|
52
|
+
fetchHeaders.append(key, v);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
fetchHeaders.set(key, value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return fetchHeaders;
|
|
59
|
+
}
|