@naturalcycles/js-lib 14.134.0 → 14.135.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/env.d.ts +14 -0
- package/dist/env.js +23 -0
- package/dist/http/fetcher.d.ts +2 -0
- package/dist/http/fetcher.js +104 -92
- package/dist/http/fetcher.model.d.ts +14 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/vendor/is.d.ts +2 -2
- package/dist-esm/env.js +18 -0
- package/dist-esm/http/fetcher.js +111 -98
- package/dist-esm/index.js +1 -0
- package/package.json +1 -1
- package/src/env.ts +19 -0
- package/src/http/fetcher.model.ts +14 -3
- package/src/http/fetcher.ts +117 -95
- package/src/index.ts +1 -0
- package/src/vendor/is.ts +3 -3
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use it to detect SSR/Node.js environment.
|
|
3
|
+
*
|
|
4
|
+
* Will return `true` in Node.js.
|
|
5
|
+
* Will return `false` in the Browser.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isServerSide(): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Use it to detect Browser (not SSR/Node) environment.
|
|
10
|
+
*
|
|
11
|
+
* Will return `true` in the Browser.
|
|
12
|
+
* Will return `false` in Node.js.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isClientSide(): boolean;
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isClientSide = exports.isServerSide = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Use it to detect SSR/Node.js environment.
|
|
6
|
+
*
|
|
7
|
+
* Will return `true` in Node.js.
|
|
8
|
+
* Will return `false` in the Browser.
|
|
9
|
+
*/
|
|
10
|
+
function isServerSide() {
|
|
11
|
+
return typeof window === 'undefined';
|
|
12
|
+
}
|
|
13
|
+
exports.isServerSide = isServerSide;
|
|
14
|
+
/**
|
|
15
|
+
* Use it to detect Browser (not SSR/Node) environment.
|
|
16
|
+
*
|
|
17
|
+
* Will return `true` in the Browser.
|
|
18
|
+
* Will return `false` in Node.js.
|
|
19
|
+
*/
|
|
20
|
+
function isClientSide() {
|
|
21
|
+
return typeof window !== 'undefined';
|
|
22
|
+
}
|
|
23
|
+
exports.isClientSide = isClientSide;
|
package/dist/http/fetcher.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export declare class Fetcher {
|
|
|
38
38
|
* Never throws, returns `err` property in the response instead.
|
|
39
39
|
*/
|
|
40
40
|
rawFetch<T = unknown>(url: string, rawOpt?: FetcherOptions): Promise<FetcherResponse<T>>;
|
|
41
|
+
private onOkResponse;
|
|
42
|
+
private onNotOkResponse;
|
|
41
43
|
private processRetry;
|
|
42
44
|
/**
|
|
43
45
|
* Default is yes,
|
package/dist/http/fetcher.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/// <reference lib="dom"/>
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.getFetcher = exports.Fetcher = void 0;
|
|
5
|
+
const env_1 = require("../env");
|
|
5
6
|
const error_util_1 = require("../error/error.util");
|
|
6
7
|
const http_error_1 = require("../error/http.error");
|
|
7
8
|
const number_util_1 = require("../number/number.util");
|
|
@@ -94,7 +95,7 @@ class Fetcher {
|
|
|
94
95
|
async rawFetch(url, rawOpt = {}) {
|
|
95
96
|
const { logger } = this.cfg;
|
|
96
97
|
const req = this.normalizeOptions(url, rawOpt);
|
|
97
|
-
const { timeoutSeconds,
|
|
98
|
+
const { timeoutSeconds, init: { method }, } = req;
|
|
98
99
|
// setup timeout
|
|
99
100
|
let timeout;
|
|
100
101
|
if (timeoutSeconds) {
|
|
@@ -107,6 +108,10 @@ class Fetcher {
|
|
|
107
108
|
for await (const hook of this.cfg.hooks.beforeRequest || []) {
|
|
108
109
|
await hook(req);
|
|
109
110
|
}
|
|
111
|
+
const isFullUrl = req.url.includes('://');
|
|
112
|
+
const fullUrl = isFullUrl ? new URL(req.url) : undefined;
|
|
113
|
+
const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url;
|
|
114
|
+
const signature = [method, shortUrl].join(' ');
|
|
110
115
|
const res = {
|
|
111
116
|
req,
|
|
112
117
|
retryStatus: {
|
|
@@ -114,10 +119,8 @@ class Fetcher {
|
|
|
114
119
|
retryStopped: false,
|
|
115
120
|
retryTimeout: req.retry.timeout,
|
|
116
121
|
},
|
|
122
|
+
signature,
|
|
117
123
|
};
|
|
118
|
-
const fullUrl = new URL(req.url);
|
|
119
|
-
const shortUrl = this.getShortUrl(fullUrl);
|
|
120
|
-
const signature = [method, shortUrl].join(' ');
|
|
121
124
|
/* eslint-disable no-await-in-loop */
|
|
122
125
|
while (!res.retryStatus.retryStopped) {
|
|
123
126
|
const started = Date.now();
|
|
@@ -141,95 +144,11 @@ class Fetcher {
|
|
|
141
144
|
}
|
|
142
145
|
res.statusFamily = this.getStatusFamily(res);
|
|
143
146
|
if (res.fetchResponse?.ok) {
|
|
144
|
-
|
|
145
|
-
if (res.fetchResponse.body) {
|
|
146
|
-
const text = await res.fetchResponse.text();
|
|
147
|
-
if (text) {
|
|
148
|
-
try {
|
|
149
|
-
res.body = text;
|
|
150
|
-
res.body = JSON.parse(text, req.jsonReviver);
|
|
151
|
-
}
|
|
152
|
-
catch (err) {
|
|
153
|
-
const { message } = (0, error_util_1._anyToError)(err);
|
|
154
|
-
res.err = new http_error_1.HttpError([signature, message].join('\n'), {
|
|
155
|
-
httpStatusCode: 0,
|
|
156
|
-
url: req.url,
|
|
157
|
-
});
|
|
158
|
-
res.ok = false;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
// Body had a '' (empty string)
|
|
163
|
-
res.body = {};
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
// if no body: set responseBody as {}
|
|
168
|
-
// do not throw a "cannot parse null as Json" error
|
|
169
|
-
res.body = {};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else if (mode === 'text') {
|
|
173
|
-
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
|
|
174
|
-
}
|
|
175
|
-
else if (mode === 'arrayBuffer') {
|
|
176
|
-
res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
|
|
177
|
-
}
|
|
178
|
-
else if (mode === 'blob') {
|
|
179
|
-
res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
|
|
180
|
-
}
|
|
181
|
-
clearTimeout(timeout);
|
|
182
|
-
res.retryStatus.retryStopped = true;
|
|
183
|
-
// res.err can happen on JSON.parse error
|
|
184
|
-
if (!res.err && this.cfg.logResponse) {
|
|
185
|
-
const { retryAttempt } = res.retryStatus;
|
|
186
|
-
logger.log([
|
|
187
|
-
' <<',
|
|
188
|
-
res.fetchResponse.status,
|
|
189
|
-
signature,
|
|
190
|
-
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
|
|
191
|
-
(0, time_util_1._since)(started),
|
|
192
|
-
]
|
|
193
|
-
.filter(Boolean)
|
|
194
|
-
.join(' '));
|
|
195
|
-
if (this.cfg.logResponseBody) {
|
|
196
|
-
logger.log(res.body);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
147
|
+
await this.onOkResponse(res, started, timeout);
|
|
199
148
|
}
|
|
200
149
|
else {
|
|
201
150
|
// !res.ok
|
|
202
|
-
|
|
203
|
-
let errObj;
|
|
204
|
-
if (res.fetchResponse) {
|
|
205
|
-
const body = (0, json_util_1._jsonParseIfPossible)(await res.fetchResponse.text());
|
|
206
|
-
errObj = (0, error_util_1._anyToErrorObject)(body);
|
|
207
|
-
}
|
|
208
|
-
else if (res.err) {
|
|
209
|
-
errObj = (0, error_util_1._errorToErrorObject)(res.err);
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
errObj = {};
|
|
213
|
-
}
|
|
214
|
-
const originalMessage = errObj.message;
|
|
215
|
-
errObj.message = [
|
|
216
|
-
[res.fetchResponse?.status, signature].filter(Boolean).join(' '),
|
|
217
|
-
originalMessage,
|
|
218
|
-
]
|
|
219
|
-
.filter(Boolean)
|
|
220
|
-
.join('\n');
|
|
221
|
-
res.err = new http_error_1.HttpError(errObj.message, (0, object_util_1._filterNullishValues)({
|
|
222
|
-
...errObj.data,
|
|
223
|
-
originalMessage,
|
|
224
|
-
httpStatusCode: res.fetchResponse?.status || 0,
|
|
225
|
-
// These properties are provided to be used in e.g custom Sentry error grouping
|
|
226
|
-
// Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
|
|
227
|
-
// Enabled, cause `data` is not printed by default when error is HttpError
|
|
228
|
-
// method: req.method,
|
|
229
|
-
url: req.url,
|
|
230
|
-
// tryCount: req.tryCount,
|
|
231
|
-
}));
|
|
232
|
-
await this.processRetry(res);
|
|
151
|
+
await this.onNotOkResponse(res, timeout);
|
|
233
152
|
}
|
|
234
153
|
}
|
|
235
154
|
for await (const hook of this.cfg.hooks.afterResponse || []) {
|
|
@@ -237,6 +156,99 @@ class Fetcher {
|
|
|
237
156
|
}
|
|
238
157
|
return res;
|
|
239
158
|
}
|
|
159
|
+
async onOkResponse(res, started, timeout) {
|
|
160
|
+
const { req } = res;
|
|
161
|
+
const { mode } = res.req;
|
|
162
|
+
if (mode === 'json') {
|
|
163
|
+
if (res.fetchResponse.body) {
|
|
164
|
+
const text = await res.fetchResponse.text();
|
|
165
|
+
if (text) {
|
|
166
|
+
try {
|
|
167
|
+
res.body = text;
|
|
168
|
+
res.body = JSON.parse(text, req.jsonReviver);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
const { message } = (0, error_util_1._anyToError)(err);
|
|
172
|
+
res.err = new http_error_1.HttpError([res.signature, message].join('\n'), {
|
|
173
|
+
httpStatusCode: 0,
|
|
174
|
+
url: req.url,
|
|
175
|
+
});
|
|
176
|
+
res.ok = false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// Body had a '' (empty string)
|
|
181
|
+
res.body = {};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// if no body: set responseBody as {}
|
|
186
|
+
// do not throw a "cannot parse null as Json" error
|
|
187
|
+
res.body = {};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (mode === 'text') {
|
|
191
|
+
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
|
|
192
|
+
}
|
|
193
|
+
else if (mode === 'arrayBuffer') {
|
|
194
|
+
res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
|
|
195
|
+
}
|
|
196
|
+
else if (mode === 'blob') {
|
|
197
|
+
res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
|
|
198
|
+
}
|
|
199
|
+
clearTimeout(timeout);
|
|
200
|
+
res.retryStatus.retryStopped = true;
|
|
201
|
+
// res.err can happen on JSON.parse error
|
|
202
|
+
if (!res.err && this.cfg.logResponse) {
|
|
203
|
+
const { retryAttempt } = res.retryStatus;
|
|
204
|
+
const { logger } = this.cfg;
|
|
205
|
+
logger.log([
|
|
206
|
+
' <<',
|
|
207
|
+
res.fetchResponse.status,
|
|
208
|
+
res.signature,
|
|
209
|
+
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
|
|
210
|
+
(0, time_util_1._since)(started),
|
|
211
|
+
]
|
|
212
|
+
.filter(Boolean)
|
|
213
|
+
.join(' '));
|
|
214
|
+
if (this.cfg.logResponseBody) {
|
|
215
|
+
logger.log(res.body);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async onNotOkResponse(res, timeout) {
|
|
220
|
+
clearTimeout(timeout);
|
|
221
|
+
let errObj;
|
|
222
|
+
if (res.fetchResponse) {
|
|
223
|
+
const body = (0, json_util_1._jsonParseIfPossible)(await res.fetchResponse.text());
|
|
224
|
+
errObj = (0, error_util_1._anyToErrorObject)(body);
|
|
225
|
+
}
|
|
226
|
+
else if (res.err) {
|
|
227
|
+
errObj = (0, error_util_1._errorToErrorObject)(res.err);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
errObj = {};
|
|
231
|
+
}
|
|
232
|
+
const originalMessage = errObj.message;
|
|
233
|
+
errObj.message = [
|
|
234
|
+
[res.fetchResponse?.status, res.signature].filter(Boolean).join(' '),
|
|
235
|
+
originalMessage,
|
|
236
|
+
]
|
|
237
|
+
.filter(Boolean)
|
|
238
|
+
.join('\n');
|
|
239
|
+
res.err = new http_error_1.HttpError(errObj.message, (0, object_util_1._filterNullishValues)({
|
|
240
|
+
...errObj.data,
|
|
241
|
+
originalMessage,
|
|
242
|
+
httpStatusCode: res.fetchResponse?.status || 0,
|
|
243
|
+
// These properties are provided to be used in e.g custom Sentry error grouping
|
|
244
|
+
// Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
|
|
245
|
+
// Enabled, cause `data` is not printed by default when error is HttpError
|
|
246
|
+
// method: req.method,
|
|
247
|
+
url: res.req.url,
|
|
248
|
+
// tryCount: req.tryCount,
|
|
249
|
+
}));
|
|
250
|
+
await this.processRetry(res);
|
|
251
|
+
}
|
|
240
252
|
async processRetry(res) {
|
|
241
253
|
const { retryStatus } = res;
|
|
242
254
|
if (!this.shouldRetry(res)) {
|
|
@@ -307,7 +319,7 @@ class Fetcher {
|
|
|
307
319
|
if (!this.cfg.logWithSearchParams) {
|
|
308
320
|
shortUrl = shortUrl.split('?')[0];
|
|
309
321
|
}
|
|
310
|
-
if (!this.cfg.
|
|
322
|
+
if (!this.cfg.logWithBaseUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
|
|
311
323
|
shortUrl = shortUrl.slice(baseUrl.length);
|
|
312
324
|
}
|
|
313
325
|
return shortUrl;
|
|
@@ -334,7 +346,7 @@ class Fetcher {
|
|
|
334
346
|
logRequestBody: debug,
|
|
335
347
|
logResponse: debug,
|
|
336
348
|
logResponseBody: debug,
|
|
337
|
-
|
|
349
|
+
logWithBaseUrl: (0, env_1.isServerSide)(),
|
|
338
350
|
logWithSearchParams: true,
|
|
339
351
|
retry: { ...defRetryOptions },
|
|
340
352
|
init: {
|
|
@@ -43,10 +43,19 @@ export interface FetcherCfg {
|
|
|
43
43
|
logResponse?: boolean;
|
|
44
44
|
logResponseBody?: boolean;
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* Controls if `baseUrl` should be included in logs (both success and error).
|
|
47
|
+
*
|
|
48
|
+
* Defaults to `true` on ServerSide and `false` on ClientSide.
|
|
49
|
+
*
|
|
50
|
+
* Reasoning.
|
|
51
|
+
*
|
|
52
|
+
* ClientSide often uses one main "backend host".
|
|
53
|
+
* Not including baseUrl improves Sentry error grouping.
|
|
54
|
+
*
|
|
55
|
+
* ServerSide often uses one Fetcher instance per 3rd-party API.
|
|
56
|
+
* Not including baseUrl can introduce confusion of "which API is it?".
|
|
48
57
|
*/
|
|
49
|
-
|
|
58
|
+
logWithBaseUrl?: boolean;
|
|
50
59
|
/**
|
|
51
60
|
* Default to true.
|
|
52
61
|
* Set to false to strip searchParams from url when logging (both success and error)
|
|
@@ -138,6 +147,7 @@ export interface FetcherSuccessResponse<BODY = unknown> {
|
|
|
138
147
|
req: FetcherRequest;
|
|
139
148
|
statusFamily?: HttpStatusFamily;
|
|
140
149
|
retryStatus: FetcherRetryStatus;
|
|
150
|
+
signature: string;
|
|
141
151
|
}
|
|
142
152
|
export interface FetcherErrorResponse<BODY = unknown> {
|
|
143
153
|
ok: false;
|
|
@@ -147,6 +157,7 @@ export interface FetcherErrorResponse<BODY = unknown> {
|
|
|
147
157
|
req: FetcherRequest;
|
|
148
158
|
statusFamily?: HttpStatusFamily;
|
|
149
159
|
retryStatus: FetcherRetryStatus;
|
|
160
|
+
signature: string;
|
|
150
161
|
}
|
|
151
162
|
export type FetcherResponse<BODY = unknown> = FetcherSuccessResponse<BODY> | FetcherErrorResponse<BODY>;
|
|
152
163
|
export type FetcherMode = 'json' | 'text' | 'void' | 'arrayBuffer' | 'blob';
|
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,7 @@ export * from './datetime/localDate';
|
|
|
72
72
|
export * from './datetime/localTime';
|
|
73
73
|
export * from './datetime/dateInterval';
|
|
74
74
|
export * from './datetime/timeInterval';
|
|
75
|
+
export * from './env';
|
|
75
76
|
export * from './http/http.model';
|
|
76
77
|
export * from './http/fetcher';
|
|
77
78
|
export * from './http/fetcher.model';
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ tslib_1.__exportStar(require("./datetime/localDate"), exports);
|
|
|
76
76
|
tslib_1.__exportStar(require("./datetime/localTime"), exports);
|
|
77
77
|
tslib_1.__exportStar(require("./datetime/dateInterval"), exports);
|
|
78
78
|
tslib_1.__exportStar(require("./datetime/timeInterval"), exports);
|
|
79
|
+
tslib_1.__exportStar(require("./env"), exports);
|
|
79
80
|
tslib_1.__exportStar(require("./http/http.model"), exports);
|
|
80
81
|
tslib_1.__exportStar(require("./http/fetcher"), exports);
|
|
81
82
|
tslib_1.__exportStar(require("./http/fetcher.model"), exports);
|
package/dist/vendor/is.d.ts
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
/// <reference lib="dom" />
|
|
6
6
|
import { Class, ObservableLike, Primitive, TypedArray } from '../typeFest';
|
|
7
7
|
declare const objectTypeNames: readonly ["Function", "Generator", "AsyncGenerator", "GeneratorFunction", "AsyncGeneratorFunction", "AsyncFunction", "Observable", "Array", "Buffer", "Object", "RegExp", "Date", "Error", "Map", "Set", "WeakMap", "WeakSet", "ArrayBuffer", "SharedArrayBuffer", "DataView", "Promise", "URL", "FormData", "URLSearchParams", "HTMLElement", "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "BigInt64Array", "BigUint64Array"];
|
|
8
|
-
type ObjectTypeName = typeof objectTypeNames[number];
|
|
8
|
+
type ObjectTypeName = (typeof objectTypeNames)[number];
|
|
9
9
|
declare const primitiveTypeNames: readonly ["null", "undefined", "string", "number", "bigint", "boolean", "symbol"];
|
|
10
|
-
type PrimitiveTypeName = typeof primitiveTypeNames[number];
|
|
10
|
+
type PrimitiveTypeName = (typeof primitiveTypeNames)[number];
|
|
11
11
|
export type TypeName = ObjectTypeName | PrimitiveTypeName;
|
|
12
12
|
export declare function is(value: unknown): TypeName;
|
|
13
13
|
export declare namespace is {
|
package/dist-esm/env.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use it to detect SSR/Node.js environment.
|
|
3
|
+
*
|
|
4
|
+
* Will return `true` in Node.js.
|
|
5
|
+
* Will return `false` in the Browser.
|
|
6
|
+
*/
|
|
7
|
+
export function isServerSide() {
|
|
8
|
+
return typeof window === 'undefined';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Use it to detect Browser (not SSR/Node) environment.
|
|
12
|
+
*
|
|
13
|
+
* Will return `true` in the Browser.
|
|
14
|
+
* Will return `false` in Node.js.
|
|
15
|
+
*/
|
|
16
|
+
export function isClientSide() {
|
|
17
|
+
return typeof window !== 'undefined';
|
|
18
|
+
}
|
package/dist-esm/http/fetcher.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference lib="dom"/>
|
|
2
2
|
import { __asyncValues } from "tslib";
|
|
3
|
+
import { isServerSide } from '../env';
|
|
3
4
|
import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util';
|
|
4
5
|
import { HttpError } from '../error/http.error';
|
|
5
6
|
import { _clamp } from '../number/number.util';
|
|
@@ -82,10 +83,10 @@ export class Fetcher {
|
|
|
82
83
|
*/
|
|
83
84
|
async rawFetch(url, rawOpt = {}) {
|
|
84
85
|
var _a, e_1, _b, _c, _d, e_2, _e, _f;
|
|
85
|
-
var _g
|
|
86
|
+
var _g;
|
|
86
87
|
const { logger } = this.cfg;
|
|
87
88
|
const req = this.normalizeOptions(url, rawOpt);
|
|
88
|
-
const { timeoutSeconds,
|
|
89
|
+
const { timeoutSeconds, init: { method }, } = req;
|
|
89
90
|
// setup timeout
|
|
90
91
|
let timeout;
|
|
91
92
|
if (timeoutSeconds) {
|
|
@@ -96,25 +97,29 @@ export class Fetcher {
|
|
|
96
97
|
}, timeoutSeconds * 1000);
|
|
97
98
|
}
|
|
98
99
|
try {
|
|
99
|
-
for (var
|
|
100
|
-
_c =
|
|
101
|
-
|
|
100
|
+
for (var _h = true, _j = __asyncValues(this.cfg.hooks.beforeRequest || []), _k; _k = await _j.next(), _a = _k.done, !_a;) {
|
|
101
|
+
_c = _k.value;
|
|
102
|
+
_h = false;
|
|
102
103
|
try {
|
|
103
104
|
const hook = _c;
|
|
104
105
|
await hook(req);
|
|
105
106
|
}
|
|
106
107
|
finally {
|
|
107
|
-
|
|
108
|
+
_h = true;
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
112
113
|
finally {
|
|
113
114
|
try {
|
|
114
|
-
if (!
|
|
115
|
+
if (!_h && !_a && (_b = _j.return)) await _b.call(_j);
|
|
115
116
|
}
|
|
116
117
|
finally { if (e_1) throw e_1.error; }
|
|
117
118
|
}
|
|
119
|
+
const isFullUrl = req.url.includes('://');
|
|
120
|
+
const fullUrl = isFullUrl ? new URL(req.url) : undefined;
|
|
121
|
+
const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url;
|
|
122
|
+
const signature = [method, shortUrl].join(' ');
|
|
118
123
|
const res = {
|
|
119
124
|
req,
|
|
120
125
|
retryStatus: {
|
|
@@ -122,10 +127,8 @@ export class Fetcher {
|
|
|
122
127
|
retryStopped: false,
|
|
123
128
|
retryTimeout: req.retry.timeout,
|
|
124
129
|
},
|
|
130
|
+
signature,
|
|
125
131
|
};
|
|
126
|
-
const fullUrl = new URL(req.url);
|
|
127
|
-
const shortUrl = this.getShortUrl(fullUrl);
|
|
128
|
-
const signature = [method, shortUrl].join(' ');
|
|
129
132
|
/* eslint-disable no-await-in-loop */
|
|
130
133
|
while (!res.retryStatus.retryStopped) {
|
|
131
134
|
const started = Date.now();
|
|
@@ -149,114 +152,124 @@ export class Fetcher {
|
|
|
149
152
|
}
|
|
150
153
|
res.statusFamily = this.getStatusFamily(res);
|
|
151
154
|
if ((_g = res.fetchResponse) === null || _g === void 0 ? void 0 : _g.ok) {
|
|
152
|
-
|
|
153
|
-
if (res.fetchResponse.body) {
|
|
154
|
-
const text = await res.fetchResponse.text();
|
|
155
|
-
if (text) {
|
|
156
|
-
try {
|
|
157
|
-
res.body = text;
|
|
158
|
-
res.body = JSON.parse(text, req.jsonReviver);
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
const { message } = _anyToError(err);
|
|
162
|
-
res.err = new HttpError([signature, message].join('\n'), {
|
|
163
|
-
httpStatusCode: 0,
|
|
164
|
-
url: req.url,
|
|
165
|
-
});
|
|
166
|
-
res.ok = false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
// Body had a '' (empty string)
|
|
171
|
-
res.body = {};
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
// if no body: set responseBody as {}
|
|
176
|
-
// do not throw a "cannot parse null as Json" error
|
|
177
|
-
res.body = {};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
else if (mode === 'text') {
|
|
181
|
-
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
|
|
182
|
-
}
|
|
183
|
-
else if (mode === 'arrayBuffer') {
|
|
184
|
-
res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
|
|
185
|
-
}
|
|
186
|
-
else if (mode === 'blob') {
|
|
187
|
-
res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
|
|
188
|
-
}
|
|
189
|
-
clearTimeout(timeout);
|
|
190
|
-
res.retryStatus.retryStopped = true;
|
|
191
|
-
// res.err can happen on JSON.parse error
|
|
192
|
-
if (!res.err && this.cfg.logResponse) {
|
|
193
|
-
const { retryAttempt } = res.retryStatus;
|
|
194
|
-
logger.log([
|
|
195
|
-
' <<',
|
|
196
|
-
res.fetchResponse.status,
|
|
197
|
-
signature,
|
|
198
|
-
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
|
|
199
|
-
_since(started),
|
|
200
|
-
]
|
|
201
|
-
.filter(Boolean)
|
|
202
|
-
.join(' '));
|
|
203
|
-
if (this.cfg.logResponseBody) {
|
|
204
|
-
logger.log(res.body);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
155
|
+
await this.onOkResponse(res, started, timeout);
|
|
207
156
|
}
|
|
208
157
|
else {
|
|
209
158
|
// !res.ok
|
|
210
|
-
|
|
211
|
-
let errObj;
|
|
212
|
-
if (res.fetchResponse) {
|
|
213
|
-
const body = _jsonParseIfPossible(await res.fetchResponse.text());
|
|
214
|
-
errObj = _anyToErrorObject(body);
|
|
215
|
-
}
|
|
216
|
-
else if (res.err) {
|
|
217
|
-
errObj = _errorToErrorObject(res.err);
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
errObj = {};
|
|
221
|
-
}
|
|
222
|
-
const originalMessage = errObj.message;
|
|
223
|
-
errObj.message = [
|
|
224
|
-
[(_h = res.fetchResponse) === null || _h === void 0 ? void 0 : _h.status, signature].filter(Boolean).join(' '),
|
|
225
|
-
originalMessage,
|
|
226
|
-
]
|
|
227
|
-
.filter(Boolean)
|
|
228
|
-
.join('\n');
|
|
229
|
-
res.err = new HttpError(errObj.message, _filterNullishValues(Object.assign(Object.assign({}, errObj.data), { originalMessage, httpStatusCode: ((_j = res.fetchResponse) === null || _j === void 0 ? void 0 : _j.status) || 0,
|
|
230
|
-
// These properties are provided to be used in e.g custom Sentry error grouping
|
|
231
|
-
// Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
|
|
232
|
-
// Enabled, cause `data` is not printed by default when error is HttpError
|
|
233
|
-
// method: req.method,
|
|
234
|
-
url: req.url })));
|
|
235
|
-
await this.processRetry(res);
|
|
159
|
+
await this.onNotOkResponse(res, timeout);
|
|
236
160
|
}
|
|
237
161
|
}
|
|
238
162
|
try {
|
|
239
|
-
for (var
|
|
240
|
-
_f =
|
|
241
|
-
|
|
163
|
+
for (var _l = true, _m = __asyncValues(this.cfg.hooks.afterResponse || []), _o; _o = await _m.next(), _d = _o.done, !_d;) {
|
|
164
|
+
_f = _o.value;
|
|
165
|
+
_l = false;
|
|
242
166
|
try {
|
|
243
167
|
const hook = _f;
|
|
244
168
|
await hook(res);
|
|
245
169
|
}
|
|
246
170
|
finally {
|
|
247
|
-
|
|
171
|
+
_l = true;
|
|
248
172
|
}
|
|
249
173
|
}
|
|
250
174
|
}
|
|
251
175
|
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
252
176
|
finally {
|
|
253
177
|
try {
|
|
254
|
-
if (!
|
|
178
|
+
if (!_l && !_d && (_e = _m.return)) await _e.call(_m);
|
|
255
179
|
}
|
|
256
180
|
finally { if (e_2) throw e_2.error; }
|
|
257
181
|
}
|
|
258
182
|
return res;
|
|
259
183
|
}
|
|
184
|
+
async onOkResponse(res, started, timeout) {
|
|
185
|
+
const { req } = res;
|
|
186
|
+
const { mode } = res.req;
|
|
187
|
+
if (mode === 'json') {
|
|
188
|
+
if (res.fetchResponse.body) {
|
|
189
|
+
const text = await res.fetchResponse.text();
|
|
190
|
+
if (text) {
|
|
191
|
+
try {
|
|
192
|
+
res.body = text;
|
|
193
|
+
res.body = JSON.parse(text, req.jsonReviver);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
const { message } = _anyToError(err);
|
|
197
|
+
res.err = new HttpError([res.signature, message].join('\n'), {
|
|
198
|
+
httpStatusCode: 0,
|
|
199
|
+
url: req.url,
|
|
200
|
+
});
|
|
201
|
+
res.ok = false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// Body had a '' (empty string)
|
|
206
|
+
res.body = {};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// if no body: set responseBody as {}
|
|
211
|
+
// do not throw a "cannot parse null as Json" error
|
|
212
|
+
res.body = {};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else if (mode === 'text') {
|
|
216
|
+
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : '';
|
|
217
|
+
}
|
|
218
|
+
else if (mode === 'arrayBuffer') {
|
|
219
|
+
res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {};
|
|
220
|
+
}
|
|
221
|
+
else if (mode === 'blob') {
|
|
222
|
+
res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {};
|
|
223
|
+
}
|
|
224
|
+
clearTimeout(timeout);
|
|
225
|
+
res.retryStatus.retryStopped = true;
|
|
226
|
+
// res.err can happen on JSON.parse error
|
|
227
|
+
if (!res.err && this.cfg.logResponse) {
|
|
228
|
+
const { retryAttempt } = res.retryStatus;
|
|
229
|
+
const { logger } = this.cfg;
|
|
230
|
+
logger.log([
|
|
231
|
+
' <<',
|
|
232
|
+
res.fetchResponse.status,
|
|
233
|
+
res.signature,
|
|
234
|
+
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
|
|
235
|
+
_since(started),
|
|
236
|
+
]
|
|
237
|
+
.filter(Boolean)
|
|
238
|
+
.join(' '));
|
|
239
|
+
if (this.cfg.logResponseBody) {
|
|
240
|
+
logger.log(res.body);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async onNotOkResponse(res, timeout) {
|
|
245
|
+
var _a, _b;
|
|
246
|
+
clearTimeout(timeout);
|
|
247
|
+
let errObj;
|
|
248
|
+
if (res.fetchResponse) {
|
|
249
|
+
const body = _jsonParseIfPossible(await res.fetchResponse.text());
|
|
250
|
+
errObj = _anyToErrorObject(body);
|
|
251
|
+
}
|
|
252
|
+
else if (res.err) {
|
|
253
|
+
errObj = _errorToErrorObject(res.err);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
errObj = {};
|
|
257
|
+
}
|
|
258
|
+
const originalMessage = errObj.message;
|
|
259
|
+
errObj.message = [
|
|
260
|
+
[(_a = res.fetchResponse) === null || _a === void 0 ? void 0 : _a.status, res.signature].filter(Boolean).join(' '),
|
|
261
|
+
originalMessage,
|
|
262
|
+
]
|
|
263
|
+
.filter(Boolean)
|
|
264
|
+
.join('\n');
|
|
265
|
+
res.err = new HttpError(errObj.message, _filterNullishValues(Object.assign(Object.assign({}, errObj.data), { originalMessage, httpStatusCode: ((_b = res.fetchResponse) === null || _b === void 0 ? void 0 : _b.status) || 0,
|
|
266
|
+
// These properties are provided to be used in e.g custom Sentry error grouping
|
|
267
|
+
// Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
|
|
268
|
+
// Enabled, cause `data` is not printed by default when error is HttpError
|
|
269
|
+
// method: req.method,
|
|
270
|
+
url: res.req.url })));
|
|
271
|
+
await this.processRetry(res);
|
|
272
|
+
}
|
|
260
273
|
async processRetry(res) {
|
|
261
274
|
var _a, e_3, _b, _c;
|
|
262
275
|
const { retryStatus } = res;
|
|
@@ -347,7 +360,7 @@ export class Fetcher {
|
|
|
347
360
|
if (!this.cfg.logWithSearchParams) {
|
|
348
361
|
shortUrl = shortUrl.split('?')[0];
|
|
349
362
|
}
|
|
350
|
-
if (!this.cfg.
|
|
363
|
+
if (!this.cfg.logWithBaseUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
|
|
351
364
|
shortUrl = shortUrl.slice(baseUrl.length);
|
|
352
365
|
}
|
|
353
366
|
return shortUrl;
|
|
@@ -375,7 +388,7 @@ export class Fetcher {
|
|
|
375
388
|
logRequestBody: debug,
|
|
376
389
|
logResponse: debug,
|
|
377
390
|
logResponseBody: debug,
|
|
378
|
-
|
|
391
|
+
logWithBaseUrl: isServerSide(),
|
|
379
392
|
logWithSearchParams: true,
|
|
380
393
|
retry: Object.assign({}, defRetryOptions),
|
|
381
394
|
init: {
|
package/dist-esm/index.js
CHANGED
|
@@ -72,6 +72,7 @@ export * from './datetime/localDate';
|
|
|
72
72
|
export * from './datetime/localTime';
|
|
73
73
|
export * from './datetime/dateInterval';
|
|
74
74
|
export * from './datetime/timeInterval';
|
|
75
|
+
export * from './env';
|
|
75
76
|
export * from './http/http.model';
|
|
76
77
|
export * from './http/fetcher';
|
|
77
78
|
export * from './http/fetcher.model';
|
package/package.json
CHANGED
package/src/env.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use it to detect SSR/Node.js environment.
|
|
3
|
+
*
|
|
4
|
+
* Will return `true` in Node.js.
|
|
5
|
+
* Will return `false` in the Browser.
|
|
6
|
+
*/
|
|
7
|
+
export function isServerSide(): boolean {
|
|
8
|
+
return typeof window === 'undefined'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Use it to detect Browser (not SSR/Node) environment.
|
|
13
|
+
*
|
|
14
|
+
* Will return `true` in the Browser.
|
|
15
|
+
* Will return `false` in Node.js.
|
|
16
|
+
*/
|
|
17
|
+
export function isClientSide(): boolean {
|
|
18
|
+
return typeof window !== 'undefined'
|
|
19
|
+
}
|
|
@@ -49,10 +49,19 @@ export interface FetcherCfg {
|
|
|
49
49
|
logResponseBody?: boolean
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
52
|
+
* Controls if `baseUrl` should be included in logs (both success and error).
|
|
53
|
+
*
|
|
54
|
+
* Defaults to `true` on ServerSide and `false` on ClientSide.
|
|
55
|
+
*
|
|
56
|
+
* Reasoning.
|
|
57
|
+
*
|
|
58
|
+
* ClientSide often uses one main "backend host".
|
|
59
|
+
* Not including baseUrl improves Sentry error grouping.
|
|
60
|
+
*
|
|
61
|
+
* ServerSide often uses one Fetcher instance per 3rd-party API.
|
|
62
|
+
* Not including baseUrl can introduce confusion of "which API is it?".
|
|
54
63
|
*/
|
|
55
|
-
|
|
64
|
+
logWithBaseUrl?: boolean
|
|
56
65
|
|
|
57
66
|
/**
|
|
58
67
|
* Default to true.
|
|
@@ -165,6 +174,7 @@ export interface FetcherSuccessResponse<BODY = unknown> {
|
|
|
165
174
|
req: FetcherRequest
|
|
166
175
|
statusFamily?: HttpStatusFamily
|
|
167
176
|
retryStatus: FetcherRetryStatus
|
|
177
|
+
signature: string
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
export interface FetcherErrorResponse<BODY = unknown> {
|
|
@@ -175,6 +185,7 @@ export interface FetcherErrorResponse<BODY = unknown> {
|
|
|
175
185
|
req: FetcherRequest
|
|
176
186
|
statusFamily?: HttpStatusFamily
|
|
177
187
|
retryStatus: FetcherRetryStatus
|
|
188
|
+
signature: string
|
|
178
189
|
}
|
|
179
190
|
|
|
180
191
|
export type FetcherResponse<BODY = unknown> =
|
package/src/http/fetcher.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference lib="dom"/>
|
|
2
2
|
|
|
3
|
+
import { isServerSide } from '../env'
|
|
3
4
|
import { ErrorObject } from '../error/error.model'
|
|
4
5
|
import { _anyToError, _anyToErrorObject, _errorToErrorObject } from '../error/error.util'
|
|
5
6
|
import { HttpError } from '../error/http.error'
|
|
@@ -151,7 +152,6 @@ export class Fetcher {
|
|
|
151
152
|
const req = this.normalizeOptions(url, rawOpt)
|
|
152
153
|
const {
|
|
153
154
|
timeoutSeconds,
|
|
154
|
-
mode,
|
|
155
155
|
init: { method },
|
|
156
156
|
} = req
|
|
157
157
|
|
|
@@ -169,6 +169,11 @@ export class Fetcher {
|
|
|
169
169
|
await hook(req)
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
const isFullUrl = req.url.includes('://')
|
|
173
|
+
const fullUrl = isFullUrl ? new URL(req.url) : undefined
|
|
174
|
+
const shortUrl = fullUrl ? this.getShortUrl(fullUrl) : req.url
|
|
175
|
+
const signature = [method, shortUrl].join(' ')
|
|
176
|
+
|
|
172
177
|
const res = {
|
|
173
178
|
req,
|
|
174
179
|
retryStatus: {
|
|
@@ -176,12 +181,9 @@ export class Fetcher {
|
|
|
176
181
|
retryStopped: false,
|
|
177
182
|
retryTimeout: req.retry.timeout,
|
|
178
183
|
},
|
|
184
|
+
signature,
|
|
179
185
|
} as FetcherResponse<any>
|
|
180
186
|
|
|
181
|
-
const fullUrl = new URL(req.url)
|
|
182
|
-
const shortUrl = this.getShortUrl(fullUrl)
|
|
183
|
-
const signature = [method, shortUrl].join(' ')
|
|
184
|
-
|
|
185
187
|
/* eslint-disable no-await-in-loop */
|
|
186
188
|
while (!res.retryStatus.retryStopped) {
|
|
187
189
|
const started = Date.now()
|
|
@@ -209,109 +211,129 @@ export class Fetcher {
|
|
|
209
211
|
res.statusFamily = this.getStatusFamily(res)
|
|
210
212
|
|
|
211
213
|
if (res.fetchResponse?.ok) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
res.body = text
|
|
219
|
-
res.body = JSON.parse(text, req.jsonReviver)
|
|
220
|
-
} catch (err) {
|
|
221
|
-
const { message } = _anyToError(err)
|
|
222
|
-
res.err = new HttpError([signature, message].join('\n'), {
|
|
223
|
-
httpStatusCode: 0,
|
|
224
|
-
url: req.url,
|
|
225
|
-
})
|
|
226
|
-
res.ok = false
|
|
227
|
-
}
|
|
228
|
-
} else {
|
|
229
|
-
// Body had a '' (empty string)
|
|
230
|
-
res.body = {}
|
|
231
|
-
}
|
|
232
|
-
} else {
|
|
233
|
-
// if no body: set responseBody as {}
|
|
234
|
-
// do not throw a "cannot parse null as Json" error
|
|
235
|
-
res.body = {}
|
|
236
|
-
}
|
|
237
|
-
} else if (mode === 'text') {
|
|
238
|
-
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : ''
|
|
239
|
-
} else if (mode === 'arrayBuffer') {
|
|
240
|
-
res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {}
|
|
241
|
-
} else if (mode === 'blob') {
|
|
242
|
-
res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
clearTimeout(timeout)
|
|
246
|
-
res.retryStatus.retryStopped = true
|
|
247
|
-
|
|
248
|
-
// res.err can happen on JSON.parse error
|
|
249
|
-
if (!res.err && this.cfg.logResponse) {
|
|
250
|
-
const { retryAttempt } = res.retryStatus
|
|
251
|
-
logger.log(
|
|
252
|
-
[
|
|
253
|
-
' <<',
|
|
254
|
-
res.fetchResponse.status,
|
|
255
|
-
signature,
|
|
256
|
-
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
|
|
257
|
-
_since(started),
|
|
258
|
-
]
|
|
259
|
-
.filter(Boolean)
|
|
260
|
-
.join(' '),
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
if (this.cfg.logResponseBody) {
|
|
264
|
-
logger.log(res.body)
|
|
265
|
-
}
|
|
266
|
-
}
|
|
214
|
+
await this.onOkResponse(
|
|
215
|
+
res as FetcherResponse<T> & { fetchResponse: Response },
|
|
216
|
+
started,
|
|
217
|
+
timeout,
|
|
218
|
+
)
|
|
267
219
|
} else {
|
|
268
220
|
// !res.ok
|
|
269
|
-
|
|
221
|
+
await this.onNotOkResponse(res, timeout)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for await (const hook of this.cfg.hooks.afterResponse || []) {
|
|
226
|
+
await hook(res)
|
|
227
|
+
}
|
|
270
228
|
|
|
271
|
-
|
|
229
|
+
return res
|
|
230
|
+
}
|
|
272
231
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
232
|
+
private async onOkResponse(
|
|
233
|
+
res: FetcherResponse<any> & { fetchResponse: Response },
|
|
234
|
+
started: number,
|
|
235
|
+
timeout?: number,
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
const { req } = res
|
|
238
|
+
const { mode } = res.req
|
|
239
|
+
|
|
240
|
+
if (mode === 'json') {
|
|
241
|
+
if (res.fetchResponse.body) {
|
|
242
|
+
const text = await res.fetchResponse.text()
|
|
243
|
+
|
|
244
|
+
if (text) {
|
|
245
|
+
try {
|
|
246
|
+
res.body = text
|
|
247
|
+
res.body = JSON.parse(text, req.jsonReviver)
|
|
248
|
+
} catch (err) {
|
|
249
|
+
const { message } = _anyToError(err)
|
|
250
|
+
res.err = new HttpError([res.signature, message].join('\n'), {
|
|
251
|
+
httpStatusCode: 0,
|
|
252
|
+
url: req.url,
|
|
253
|
+
})
|
|
254
|
+
res.ok = false
|
|
255
|
+
}
|
|
278
256
|
} else {
|
|
279
|
-
|
|
257
|
+
// Body had a '' (empty string)
|
|
258
|
+
res.body = {}
|
|
280
259
|
}
|
|
260
|
+
} else {
|
|
261
|
+
// if no body: set responseBody as {}
|
|
262
|
+
// do not throw a "cannot parse null as Json" error
|
|
263
|
+
res.body = {}
|
|
264
|
+
}
|
|
265
|
+
} else if (mode === 'text') {
|
|
266
|
+
res.body = res.fetchResponse.body ? await res.fetchResponse.text() : ''
|
|
267
|
+
} else if (mode === 'arrayBuffer') {
|
|
268
|
+
res.body = res.fetchResponse.body ? await res.fetchResponse.arrayBuffer() : {}
|
|
269
|
+
} else if (mode === 'blob') {
|
|
270
|
+
res.body = res.fetchResponse.body ? await res.fetchResponse.blob() : {}
|
|
271
|
+
}
|
|
281
272
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
273
|
+
clearTimeout(timeout)
|
|
274
|
+
res.retryStatus.retryStopped = true
|
|
275
|
+
|
|
276
|
+
// res.err can happen on JSON.parse error
|
|
277
|
+
if (!res.err && this.cfg.logResponse) {
|
|
278
|
+
const { retryAttempt } = res.retryStatus
|
|
279
|
+
const { logger } = this.cfg
|
|
280
|
+
logger.log(
|
|
281
|
+
[
|
|
282
|
+
' <<',
|
|
283
|
+
res.fetchResponse.status,
|
|
284
|
+
res.signature,
|
|
285
|
+
retryAttempt && `try#${retryAttempt + 1}/${req.retry.count + 1}`,
|
|
286
|
+
_since(started),
|
|
286
287
|
]
|
|
287
288
|
.filter(Boolean)
|
|
288
|
-
.join('
|
|
289
|
-
|
|
290
|
-
res.err = new HttpError(
|
|
291
|
-
errObj.message,
|
|
292
|
-
|
|
293
|
-
_filterNullishValues({
|
|
294
|
-
...errObj.data,
|
|
295
|
-
originalMessage,
|
|
296
|
-
httpStatusCode: res.fetchResponse?.status || 0,
|
|
297
|
-
// These properties are provided to be used in e.g custom Sentry error grouping
|
|
298
|
-
// Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
|
|
299
|
-
// Enabled, cause `data` is not printed by default when error is HttpError
|
|
300
|
-
// method: req.method,
|
|
301
|
-
url: req.url,
|
|
302
|
-
// tryCount: req.tryCount,
|
|
303
|
-
}),
|
|
304
|
-
)
|
|
289
|
+
.join(' '),
|
|
290
|
+
)
|
|
305
291
|
|
|
306
|
-
|
|
292
|
+
if (this.cfg.logResponseBody) {
|
|
293
|
+
logger.log(res.body)
|
|
307
294
|
}
|
|
308
295
|
}
|
|
296
|
+
}
|
|
309
297
|
|
|
310
|
-
|
|
311
|
-
|
|
298
|
+
private async onNotOkResponse(res: FetcherResponse, timeout?: number): Promise<void> {
|
|
299
|
+
clearTimeout(timeout)
|
|
300
|
+
|
|
301
|
+
let errObj: ErrorObject
|
|
302
|
+
|
|
303
|
+
if (res.fetchResponse) {
|
|
304
|
+
const body = _jsonParseIfPossible(await res.fetchResponse.text())
|
|
305
|
+
errObj = _anyToErrorObject(body)
|
|
306
|
+
} else if (res.err) {
|
|
307
|
+
errObj = _errorToErrorObject(res.err)
|
|
308
|
+
} else {
|
|
309
|
+
errObj = {} as ErrorObject
|
|
312
310
|
}
|
|
313
311
|
|
|
314
|
-
|
|
312
|
+
const originalMessage = errObj.message
|
|
313
|
+
errObj.message = [
|
|
314
|
+
[res.fetchResponse?.status, res.signature].filter(Boolean).join(' '),
|
|
315
|
+
originalMessage,
|
|
316
|
+
]
|
|
317
|
+
.filter(Boolean)
|
|
318
|
+
.join('\n')
|
|
319
|
+
|
|
320
|
+
res.err = new HttpError(
|
|
321
|
+
errObj.message,
|
|
322
|
+
|
|
323
|
+
_filterNullishValues({
|
|
324
|
+
...errObj.data,
|
|
325
|
+
originalMessage,
|
|
326
|
+
httpStatusCode: res.fetchResponse?.status || 0,
|
|
327
|
+
// These properties are provided to be used in e.g custom Sentry error grouping
|
|
328
|
+
// Actually, disabled now, to avoid unnecessary error printing when both msg and data are printed
|
|
329
|
+
// Enabled, cause `data` is not printed by default when error is HttpError
|
|
330
|
+
// method: req.method,
|
|
331
|
+
url: res.req.url,
|
|
332
|
+
// tryCount: req.tryCount,
|
|
333
|
+
}),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
await this.processRetry(res)
|
|
315
337
|
}
|
|
316
338
|
|
|
317
339
|
private async processRetry(res: FetcherResponse): Promise<void> {
|
|
@@ -388,7 +410,7 @@ export class Fetcher {
|
|
|
388
410
|
shortUrl = shortUrl.split('?')[0]!
|
|
389
411
|
}
|
|
390
412
|
|
|
391
|
-
if (!this.cfg.
|
|
413
|
+
if (!this.cfg.logWithBaseUrl && baseUrl && shortUrl.startsWith(baseUrl)) {
|
|
392
414
|
shortUrl = shortUrl.slice(baseUrl.length)
|
|
393
415
|
}
|
|
394
416
|
|
|
@@ -419,7 +441,7 @@ export class Fetcher {
|
|
|
419
441
|
logRequestBody: debug,
|
|
420
442
|
logResponse: debug,
|
|
421
443
|
logResponseBody: debug,
|
|
422
|
-
|
|
444
|
+
logWithBaseUrl: isServerSide(),
|
|
423
445
|
logWithSearchParams: true,
|
|
424
446
|
retry: { ...defRetryOptions },
|
|
425
447
|
init: {
|
package/src/index.ts
CHANGED
|
@@ -72,6 +72,7 @@ export * from './datetime/localDate'
|
|
|
72
72
|
export * from './datetime/localTime'
|
|
73
73
|
export * from './datetime/dateInterval'
|
|
74
74
|
export * from './datetime/timeInterval'
|
|
75
|
+
export * from './env'
|
|
75
76
|
export * from './http/http.model'
|
|
76
77
|
export * from './http/fetcher'
|
|
77
78
|
export * from './http/fetcher.model'
|
package/src/vendor/is.ts
CHANGED
|
@@ -25,7 +25,7 @@ const typedArrayTypeNames = [
|
|
|
25
25
|
'BigUint64Array',
|
|
26
26
|
] as const
|
|
27
27
|
|
|
28
|
-
type TypedArrayTypeName = typeof typedArrayTypeNames[number]
|
|
28
|
+
type TypedArrayTypeName = (typeof typedArrayTypeNames)[number]
|
|
29
29
|
|
|
30
30
|
function isTypedArrayName(name: unknown): name is TypedArrayTypeName {
|
|
31
31
|
return typedArrayTypeNames.includes(name as TypedArrayTypeName)
|
|
@@ -60,7 +60,7 @@ const objectTypeNames = [
|
|
|
60
60
|
...typedArrayTypeNames,
|
|
61
61
|
] as const
|
|
62
62
|
|
|
63
|
-
type ObjectTypeName = typeof objectTypeNames[number]
|
|
63
|
+
type ObjectTypeName = (typeof objectTypeNames)[number]
|
|
64
64
|
|
|
65
65
|
function isObjectTypeName(name: unknown): name is ObjectTypeName {
|
|
66
66
|
return objectTypeNames.includes(name as ObjectTypeName)
|
|
@@ -76,7 +76,7 @@ const primitiveTypeNames = [
|
|
|
76
76
|
'symbol',
|
|
77
77
|
] as const
|
|
78
78
|
|
|
79
|
-
type PrimitiveTypeName = typeof primitiveTypeNames[number]
|
|
79
|
+
type PrimitiveTypeName = (typeof primitiveTypeNames)[number]
|
|
80
80
|
|
|
81
81
|
function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName {
|
|
82
82
|
return primitiveTypeNames.includes(name as PrimitiveTypeName)
|