@odata2ts/http-client-fetch 0.2.0 → 0.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/CHANGELOG.md +6 -0
- package/lib/FetchClient.d.ts +9 -22
- package/lib/FetchClient.js +24 -94
- package/lib/FetchClient.js.map +1 -1
- package/lib/FetchClientError.d.ts +4 -2
- package/lib/FetchClientError.js +2 -1
- package/lib/FetchClientError.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [0.3.0](https://github.com/odata2ts/http-client/compare/@odata2ts/http-client-fetch@0.2.0...@odata2ts/http-client-fetch@0.3.0) (2023-06-10)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* conventionalize client errors ([#5](https://github.com/odata2ts/http-client/issues/5)) ([a8e8912](https://github.com/odata2ts/http-client/commit/a8e89125eeda47436d48507d6a71efc90953f878))
|
|
11
|
+
|
|
6
12
|
# [0.2.0](https://github.com/odata2ts/http-client/compare/@odata2ts/http-client-fetch@0.1.0...@odata2ts/http-client-fetch@0.2.0) (2023-06-03)
|
|
7
13
|
|
|
8
14
|
### Features
|
package/lib/FetchClient.d.ts
CHANGED
|
@@ -1,28 +1,15 @@
|
|
|
1
|
-
import { HttpResponseModel
|
|
1
|
+
import { HttpResponseModel } from "@odata2ts/http-client-api";
|
|
2
|
+
import { BaseHttpClient, BaseHttpClientOptions, HttpMethods } from "@odata2ts/http-client-base";
|
|
2
3
|
import { FetchRequestConfig } from "./FetchRequestConfig";
|
|
3
|
-
export
|
|
4
|
-
export interface ClientOptions {
|
|
5
|
-
useCsrfProtection?: boolean;
|
|
6
|
-
csrfTokenFetchUrl?: string;
|
|
4
|
+
export interface ClientOptions extends BaseHttpClientOptions {
|
|
7
5
|
}
|
|
8
|
-
export declare const getV2OrV4ErrorMessage: ErrorMessageRetriever;
|
|
9
6
|
export declare const DEFAULT_ERROR_MESSAGE = "No error message!";
|
|
10
|
-
export declare class FetchClient
|
|
7
|
+
export declare class FetchClient extends BaseHttpClient<FetchRequestConfig> {
|
|
11
8
|
private clientOptions?;
|
|
12
|
-
|
|
13
|
-
private csrfToken;
|
|
14
|
-
private retrieveErrorMessage;
|
|
9
|
+
protected readonly config: RequestInit;
|
|
15
10
|
constructor(config?: FetchRequestConfig, clientOptions?: ClientOptions | undefined);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
private getResponseBody;
|
|
21
|
-
private prepareData;
|
|
22
|
-
get<ResponseModel>(url: string, requestConfig?: FetchRequestConfig): Promise<HttpResponseModel<ResponseModel>>;
|
|
23
|
-
post<ResponseModel>(url: string, data: any, requestConfig?: FetchRequestConfig): Promise<HttpResponseModel<ResponseModel>>;
|
|
24
|
-
put<ResponseModel>(url: string, data: any, requestConfig?: FetchRequestConfig): Promise<HttpResponseModel<ResponseModel>>;
|
|
25
|
-
patch<ResponseModel>(url: string, data: any, requestConfig?: FetchRequestConfig): Promise<HttpResponseModel<ResponseModel>>;
|
|
26
|
-
merge<ResponseModel>(url: string, data: any, requestConfig?: FetchRequestConfig): Promise<HttpResponseModel<ResponseModel>>;
|
|
27
|
-
delete(url: string, requestConfig?: FetchRequestConfig): Promise<HttpResponseModel<void>>;
|
|
11
|
+
protected addHeaderToRequestConfig(headers: Record<string, string>, config: FetchRequestConfig | undefined): FetchRequestConfig;
|
|
12
|
+
protected executeRequest<ResponseModel>(method: HttpMethods, url: string, data: any, requestConfig?: FetchRequestConfig | undefined): Promise<HttpResponseModel<ResponseModel>>;
|
|
13
|
+
protected getResponseBody(response: Response, isFailedJsonFatal: boolean): Promise<any>;
|
|
14
|
+
protected mapHeaders(headers: Headers): Record<string, string>;
|
|
28
15
|
}
|
package/lib/FetchClient.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FetchClient = exports.DEFAULT_ERROR_MESSAGE =
|
|
3
|
+
exports.FetchClient = exports.DEFAULT_ERROR_MESSAGE = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
+
const http_client_base_1 = require("@odata2ts/http-client-base");
|
|
5
6
|
const FetchClientError_1 = require("./FetchClientError");
|
|
6
7
|
const FetchRequestConfig_1 = require("./FetchRequestConfig");
|
|
7
|
-
const getV2OrV4ErrorMessage = (errorResponse) => {
|
|
8
|
-
var _a;
|
|
9
|
-
const eMsg = (_a = errorResponse === null || errorResponse === void 0 ? void 0 : errorResponse.error) === null || _a === void 0 ? void 0 : _a.message;
|
|
10
|
-
return typeof (eMsg === null || eMsg === void 0 ? void 0 : eMsg.value) === "string" ? eMsg.value : eMsg;
|
|
11
|
-
};
|
|
12
|
-
exports.getV2OrV4ErrorMessage = getV2OrV4ErrorMessage;
|
|
13
8
|
exports.DEFAULT_ERROR_MESSAGE = "No error message!";
|
|
14
9
|
const FETCH_FAILURE_MESSAGE = "OData request failed entirely: ";
|
|
15
10
|
const JSON_RETRIEVAL_FAILURE_MESSAGE = "Retrieving JSON body from OData response failed: ";
|
|
@@ -18,85 +13,42 @@ function buildErrorMessage(prefix, error) {
|
|
|
18
13
|
const msg = typeof error === "string" ? error : error === null || error === void 0 ? void 0 : error.message;
|
|
19
14
|
return prefix + (msg || exports.DEFAULT_ERROR_MESSAGE);
|
|
20
15
|
}
|
|
21
|
-
class FetchClient {
|
|
16
|
+
class FetchClient extends http_client_base_1.BaseHttpClient {
|
|
22
17
|
constructor(config, clientOptions) {
|
|
23
|
-
|
|
18
|
+
super(clientOptions);
|
|
24
19
|
this.clientOptions = clientOptions;
|
|
25
|
-
this.retrieveErrorMessage = exports.getV2OrV4ErrorMessage;
|
|
26
20
|
this.config = (0, FetchRequestConfig_1.getDefaultConfig)(config);
|
|
27
|
-
if (clientOptions && clientOptions.useCsrfProtection && !((_a = clientOptions.csrfTokenFetchUrl) === null || _a === void 0 ? void 0 : _a.trim())) {
|
|
28
|
-
throw new Error("When automatic CSRF token fetching is activated, the URL must be supplied with attribute [csrfTokenFetchUrl]!");
|
|
29
|
-
}
|
|
30
21
|
}
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
addHeaderToRequestConfig(headers, config) {
|
|
23
|
+
return (0, FetchRequestConfig_1.mergeFetchConfig)(config, { headers });
|
|
33
24
|
}
|
|
34
|
-
|
|
25
|
+
executeRequest(method, url, data, requestConfig = {}) {
|
|
35
26
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
fetchSecurityToken() {
|
|
43
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
44
|
-
const fetchUrl = this.clientOptions.csrfTokenFetchUrl;
|
|
45
|
-
const response = yield this.get(fetchUrl, { headers: { "x-csrf-token": "Fetch" } });
|
|
46
|
-
return response.headers["x-csrf-token"];
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
sendRequest(url, config, requestConfig) {
|
|
50
|
-
var _a, _b;
|
|
51
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
52
|
-
// noinspection SuspiciousTypeOfGuard
|
|
53
|
-
if (typeof url !== "string") {
|
|
54
|
-
throw new Error("Value for URL must be provided!");
|
|
55
|
-
}
|
|
56
|
-
const mergedConfig = (0, FetchRequestConfig_1.mergeFetchConfig)(this.config, requestConfig, config);
|
|
57
|
-
// setup automatic CSRF token handling
|
|
58
|
-
if (((_a = this.clientOptions) === null || _a === void 0 ? void 0 : _a.useCsrfProtection) &&
|
|
59
|
-
mergedConfig.method &&
|
|
60
|
-
["POST", "PUT", "PATCH", "DELETE"].includes(mergedConfig.method.toUpperCase())) {
|
|
61
|
-
const csrfToken = yield this.setupSecurityToken();
|
|
62
|
-
if (typeof csrfToken === "string") {
|
|
63
|
-
mergedConfig.headers.set("x-csrf-token", csrfToken);
|
|
64
|
-
}
|
|
27
|
+
const config = (0, FetchRequestConfig_1.mergeFetchConfig)(this.config, requestConfig);
|
|
28
|
+
config.method = method;
|
|
29
|
+
if (typeof data !== "undefined") {
|
|
30
|
+
config.body = JSON.stringify(data);
|
|
65
31
|
}
|
|
66
32
|
// the actual request
|
|
67
33
|
let response;
|
|
68
34
|
try {
|
|
69
|
-
response = yield fetch(url,
|
|
35
|
+
response = yield fetch(url, config);
|
|
70
36
|
}
|
|
71
37
|
catch (fetchError) {
|
|
72
|
-
throw new FetchClientError_1.FetchClientError(buildErrorMessage(FETCH_FAILURE_MESSAGE, fetchError), undefined, fetchError);
|
|
38
|
+
throw new FetchClientError_1.FetchClientError(buildErrorMessage(FETCH_FAILURE_MESSAGE, fetchError), undefined, undefined, fetchError);
|
|
73
39
|
}
|
|
74
40
|
// error response
|
|
75
41
|
if (!response.ok) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
response.headers.get("x-csrf-token") === "Required") {
|
|
80
|
-
// csrf token expired, let's reset it and perform the original request again
|
|
81
|
-
this.csrfToken = undefined;
|
|
82
|
-
return this.sendRequest(url, config, requestConfig);
|
|
83
|
-
}
|
|
84
|
-
let data = yield this.getResponseBody(response, false);
|
|
85
|
-
const errMsg = this.retrieveErrorMessage(data);
|
|
86
|
-
throw new FetchClientError_1.FetchClientError(buildErrorMessage(RESPONSE_FAILURE_MESSAGE, errMsg), response.status, new Error(errMsg || exports.DEFAULT_ERROR_MESSAGE), response);
|
|
42
|
+
let responseData = yield this.getResponseBody(response, false);
|
|
43
|
+
const errMsg = this.retrieveErrorMessage(responseData);
|
|
44
|
+
throw new FetchClientError_1.FetchClientError(buildErrorMessage(RESPONSE_FAILURE_MESSAGE, errMsg), response.status, this.mapHeaders(response.headers), new Error(errMsg || exports.DEFAULT_ERROR_MESSAGE), response);
|
|
87
45
|
}
|
|
88
|
-
const
|
|
89
|
-
// header
|
|
90
|
-
// Impl Note: No entries prop available, otherwise as one liner Array.from(response.headers.entries)
|
|
91
|
-
const headers = {};
|
|
92
|
-
response.headers.forEach((value, key) => {
|
|
93
|
-
headers[key] = value;
|
|
94
|
-
});
|
|
46
|
+
const responseData = yield this.getResponseBody(response, true);
|
|
95
47
|
return {
|
|
96
48
|
status: response.status,
|
|
97
49
|
statusText: response.statusText,
|
|
98
|
-
headers,
|
|
99
|
-
data,
|
|
50
|
+
headers: this.mapHeaders(response.headers),
|
|
51
|
+
data: responseData,
|
|
100
52
|
};
|
|
101
53
|
});
|
|
102
54
|
}
|
|
@@ -110,38 +62,16 @@ class FetchClient {
|
|
|
110
62
|
}
|
|
111
63
|
catch (error) {
|
|
112
64
|
if (isFailedJsonFatal) {
|
|
113
|
-
throw new FetchClientError_1.FetchClientError(buildErrorMessage(JSON_RETRIEVAL_FAILURE_MESSAGE, error), response.status, error);
|
|
65
|
+
throw new FetchClientError_1.FetchClientError(buildErrorMessage(JSON_RETRIEVAL_FAILURE_MESSAGE, error), response.status, this.mapHeaders(response.headers), error);
|
|
114
66
|
}
|
|
115
67
|
return undefined;
|
|
116
68
|
}
|
|
117
69
|
});
|
|
118
70
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return this.sendRequest(url, { method: "GET" }, requestConfig);
|
|
124
|
-
}
|
|
125
|
-
post(url, data, requestConfig) {
|
|
126
|
-
return this.sendRequest(url, { body: this.prepareData(data), method: "POST" }, requestConfig);
|
|
127
|
-
}
|
|
128
|
-
put(url, data, requestConfig) {
|
|
129
|
-
return this.sendRequest(url, { body: this.prepareData(data), method: "PUT" }, requestConfig);
|
|
130
|
-
}
|
|
131
|
-
patch(url, data, requestConfig) {
|
|
132
|
-
return this.sendRequest(url, { body: this.prepareData(data), method: "PATCH" }, requestConfig);
|
|
133
|
-
}
|
|
134
|
-
merge(url, data, requestConfig) {
|
|
135
|
-
return this.sendRequest(url, {
|
|
136
|
-
body: this.prepareData(data),
|
|
137
|
-
method: "POST",
|
|
138
|
-
headers: {
|
|
139
|
-
"X-Http-Method": "MERGE",
|
|
140
|
-
},
|
|
141
|
-
}, requestConfig);
|
|
142
|
-
}
|
|
143
|
-
delete(url, requestConfig) {
|
|
144
|
-
return this.sendRequest(url, { method: "DELETE" }, requestConfig);
|
|
71
|
+
mapHeaders(headers) {
|
|
72
|
+
const result = {};
|
|
73
|
+
headers.forEach((value, key) => (result[key] = value));
|
|
74
|
+
return result;
|
|
145
75
|
}
|
|
146
76
|
}
|
|
147
77
|
exports.FetchClient = FetchClient;
|
package/lib/FetchClient.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FetchClient.js","sourceRoot":"","sources":["../src/FetchClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { HttpResponseModel, ODataHttpClient } from \"@odata2ts/http-client-api\";\n\nimport { FetchClientError } from \"./FetchClientError\";\nimport { FetchRequestConfig, getDefaultConfig, mergeFetchConfig } from \"./FetchRequestConfig\";\n\nexport type ErrorMessageRetriever = (errorResponse: any) => string | undefined;\n\nexport interface ClientOptions {\n useCsrfProtection?: boolean;\n csrfTokenFetchUrl?: string;\n}\n\nexport const getV2OrV4ErrorMessage: ErrorMessageRetriever = (errorResponse: any): string | undefined => {\n const eMsg = errorResponse?.error?.message;\n return typeof eMsg?.value === \"string\" ? eMsg.value : eMsg;\n};\n\nexport const DEFAULT_ERROR_MESSAGE = \"No error message!\";\nconst FETCH_FAILURE_MESSAGE = \"OData request failed entirely: \";\nconst JSON_RETRIEVAL_FAILURE_MESSAGE = \"Retrieving JSON body from OData response failed: \";\nconst RESPONSE_FAILURE_MESSAGE = \"OData server responded with error: \";\n\nfunction buildErrorMessage(prefix: string, error: any) {\n const msg = typeof error === \"string\" ? error : (error as Error)?.message;\n return prefix + (msg || DEFAULT_ERROR_MESSAGE);\n}\n\nexport class FetchClient implements ODataHttpClient<FetchRequestConfig> {\n private readonly config: RequestInit;\n private csrfToken: string | undefined;\n private retrieveErrorMessage: ErrorMessageRetriever = getV2OrV4ErrorMessage;\n\n constructor(config?: FetchRequestConfig, private clientOptions?: ClientOptions) {\n this.config = getDefaultConfig(config);\n if (clientOptions && clientOptions.useCsrfProtection && !clientOptions.csrfTokenFetchUrl?.trim()) {\n throw new Error(\n \"When automatic CSRF token fetching is activated, the URL must be supplied with attribute [csrfTokenFetchUrl]!\"\n );\n }\n }\n\n public setErrorMessageRetriever(getErrorMsg: ErrorMessageRetriever) {\n this.retrieveErrorMessage = getErrorMsg;\n }\n\n private async setupSecurityToken() {\n if (!this.csrfToken) {\n this.csrfToken = await this.fetchSecurityToken();\n }\n return this.csrfToken;\n }\n\n private async fetchSecurityToken(): Promise<string | undefined> {\n const fetchUrl = this.clientOptions!.csrfTokenFetchUrl!;\n const response = await this.get(fetchUrl, { headers: { \"x-csrf-token\": \"Fetch\" } });\n\n return response.headers[\"x-csrf-token\"];\n }\n\n private async sendRequest<ResponseType>(\n url: string,\n config: RequestInit,\n requestConfig?: FetchRequestConfig\n ): Promise<HttpResponseModel<ResponseType>> {\n // noinspection SuspiciousTypeOfGuard\n if (typeof url !== \"string\") {\n throw new Error(\"Value for URL must be provided!\");\n }\n\n const mergedConfig = mergeFetchConfig(this.config, requestConfig, config);\n\n // setup automatic CSRF token handling\n if (\n this.clientOptions?.useCsrfProtection &&\n mergedConfig.method &&\n [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"].includes(mergedConfig.method.toUpperCase())\n ) {\n const csrfToken = await this.setupSecurityToken();\n if (typeof csrfToken === \"string\") {\n mergedConfig.headers.set(\"x-csrf-token\", csrfToken);\n }\n }\n\n // the actual request\n let response: Response;\n try {\n response = await fetch(url, mergedConfig);\n } catch (fetchError) {\n throw new FetchClientError(buildErrorMessage(FETCH_FAILURE_MESSAGE, fetchError), undefined, fetchError as Error);\n }\n\n // error response\n if (!response.ok) {\n // automatic CSRF token handling\n if (\n this.clientOptions?.useCsrfProtection &&\n response.status === 403 &&\n response.headers.get(\"x-csrf-token\") === \"Required\"\n ) {\n // csrf token expired, let's reset it and perform the original request again\n this.csrfToken = undefined;\n return this.sendRequest<ResponseType>(url, config, requestConfig);\n }\n\n let data = await this.getResponseBody(response, false);\n const errMsg = this.retrieveErrorMessage(data);\n\n throw new FetchClientError(\n buildErrorMessage(RESPONSE_FAILURE_MESSAGE, errMsg),\n response.status,\n new Error(errMsg || DEFAULT_ERROR_MESSAGE),\n response\n );\n }\n\n const data = await this.getResponseBody(response, true);\n\n // header\n // Impl Note: No entries prop available, otherwise as one liner Array.from(response.headers.entries)\n const headers: { [key: string]: string } = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n\n return {\n status: response.status,\n statusText: response.statusText,\n headers,\n data,\n };\n }\n\n private async getResponseBody(response: Response, isFailedJsonFatal: boolean) {\n if (response.status === 204) {\n return undefined;\n }\n try {\n return await response.json();\n } catch (error) {\n if (isFailedJsonFatal) {\n throw new FetchClientError(\n buildErrorMessage(JSON_RETRIEVAL_FAILURE_MESSAGE, error),\n response.status,\n error as Error\n );\n }\n return undefined;\n }\n }\n\n private prepareData(data: any): string {\n return JSON.stringify(data);\n }\n\n public get<ResponseModel>(\n url: string,\n requestConfig?: FetchRequestConfig\n ): Promise<HttpResponseModel<ResponseModel>> {\n return this.sendRequest<ResponseModel>(url, { method: \"GET\" }, requestConfig);\n }\n public post<ResponseModel>(\n url: string,\n data: any,\n requestConfig?: FetchRequestConfig\n ): Promise<HttpResponseModel<ResponseModel>> {\n return this.sendRequest<ResponseModel>(url, { body: this.prepareData(data), method: \"POST\" }, requestConfig);\n }\n public put<ResponseModel>(\n url: string,\n data: any,\n requestConfig?: FetchRequestConfig\n ): Promise<HttpResponseModel<ResponseModel>> {\n return this.sendRequest<ResponseModel>(url, { body: this.prepareData(data), method: \"PUT\" }, requestConfig);\n }\n public patch<ResponseModel>(\n url: string,\n data: any,\n requestConfig?: FetchRequestConfig\n ): Promise<HttpResponseModel<ResponseModel>> {\n return this.sendRequest<ResponseModel>(url, { body: this.prepareData(data), method: \"PATCH\" }, requestConfig);\n }\n public merge<ResponseModel>(\n url: string,\n data: any,\n requestConfig?: FetchRequestConfig\n ): Promise<HttpResponseModel<ResponseModel>> {\n return this.sendRequest<ResponseModel>(\n url,\n {\n body: this.prepareData(data),\n method: \"POST\",\n headers: {\n \"X-Http-Method\": \"MERGE\",\n },\n },\n requestConfig\n );\n }\n public delete(url: string, requestConfig?: FetchRequestConfig): Promise<HttpResponseModel<void>> {\n return this.sendRequest<void>(url, { method: \"DELETE\" }, requestConfig);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"FetchClient.js","sourceRoot":"","sources":["../src/FetchClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { HttpResponseModel } from \"@odata2ts/http-client-api\";\r\nimport { BaseHttpClient, BaseHttpClientOptions, HttpMethods } from \"@odata2ts/http-client-base\";\r\n\r\nimport { FetchClientError } from \"./FetchClientError\";\r\nimport { FetchRequestConfig, getDefaultConfig, mergeFetchConfig } from \"./FetchRequestConfig\";\r\n\r\nexport interface ClientOptions extends BaseHttpClientOptions {}\r\n\r\nexport const DEFAULT_ERROR_MESSAGE = \"No error message!\";\r\nconst FETCH_FAILURE_MESSAGE = \"OData request failed entirely: \";\r\nconst JSON_RETRIEVAL_FAILURE_MESSAGE = \"Retrieving JSON body from OData response failed: \";\r\nconst RESPONSE_FAILURE_MESSAGE = \"OData server responded with error: \";\r\n\r\nfunction buildErrorMessage(prefix: string, error: any) {\r\n const msg = typeof error === \"string\" ? error : (error as Error)?.message;\r\n return prefix + (msg || DEFAULT_ERROR_MESSAGE);\r\n}\r\n\r\nexport class FetchClient extends BaseHttpClient<FetchRequestConfig> {\r\n protected readonly config: RequestInit;\r\n\r\n constructor(config?: FetchRequestConfig, private clientOptions?: ClientOptions) {\r\n super(clientOptions);\r\n this.config = getDefaultConfig(config);\r\n }\r\n\r\n protected addHeaderToRequestConfig(\r\n headers: Record<string, string>,\r\n config: FetchRequestConfig | undefined\r\n ): FetchRequestConfig {\r\n return mergeFetchConfig(config, { headers });\r\n }\r\n\r\n protected async executeRequest<ResponseModel>(\r\n method: HttpMethods,\r\n url: string,\r\n data: any,\r\n requestConfig: FetchRequestConfig | undefined = {}\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n const config = mergeFetchConfig(this.config, requestConfig);\r\n config.method = method;\r\n if (typeof data !== \"undefined\") {\r\n config.body = JSON.stringify(data);\r\n }\r\n\r\n // the actual request\r\n let response: Response;\r\n try {\r\n response = await fetch(url, config);\r\n } catch (fetchError) {\r\n throw new FetchClientError(\r\n buildErrorMessage(FETCH_FAILURE_MESSAGE, fetchError),\r\n undefined,\r\n undefined,\r\n fetchError as Error\r\n );\r\n }\r\n\r\n // error response\r\n if (!response.ok) {\r\n let responseData = await this.getResponseBody(response, false);\r\n const errMsg = this.retrieveErrorMessage(responseData);\r\n\r\n throw new FetchClientError(\r\n buildErrorMessage(RESPONSE_FAILURE_MESSAGE, errMsg),\r\n response.status,\r\n this.mapHeaders(response.headers),\r\n new Error(errMsg || DEFAULT_ERROR_MESSAGE),\r\n response\r\n );\r\n }\r\n\r\n const responseData = await this.getResponseBody(response, true);\r\n\r\n return {\r\n status: response.status,\r\n statusText: response.statusText,\r\n headers: this.mapHeaders(response.headers),\r\n data: responseData,\r\n };\r\n }\r\n\r\n protected async getResponseBody(response: Response, isFailedJsonFatal: boolean) {\r\n if (response.status === 204) {\r\n return undefined;\r\n }\r\n try {\r\n return await response.json();\r\n } catch (error) {\r\n if (isFailedJsonFatal) {\r\n throw new FetchClientError(\r\n buildErrorMessage(JSON_RETRIEVAL_FAILURE_MESSAGE, error),\r\n response.status,\r\n this.mapHeaders(response.headers),\r\n error as Error\r\n );\r\n }\r\n return undefined;\r\n }\r\n }\r\n\r\n protected mapHeaders(headers: Headers): Record<string, string> {\r\n const result: Record<string, string> = {};\r\n headers.forEach((value, key) => (result[key] = value));\r\n\r\n return result;\r\n }\r\n}\r\n"]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { ODataClientError } from "@odata2ts/http-client-api";
|
|
2
|
+
export declare class FetchClientError extends Error implements ODataClientError {
|
|
2
3
|
readonly status?: number | undefined;
|
|
4
|
+
readonly headers?: Record<string, string> | undefined;
|
|
3
5
|
readonly cause?: Error | undefined;
|
|
4
6
|
readonly response?: Response | undefined;
|
|
5
|
-
constructor(message: string, status?: number | undefined, cause?: Error | undefined, response?: Response | undefined);
|
|
7
|
+
constructor(message: string, status?: number | undefined, headers?: Record<string, string> | undefined, cause?: Error | undefined, response?: Response | undefined);
|
|
6
8
|
}
|
package/lib/FetchClientError.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FetchClientError = void 0;
|
|
4
4
|
class FetchClientError extends Error {
|
|
5
|
-
constructor(message, status, cause, response) {
|
|
5
|
+
constructor(message, status, headers, cause, response) {
|
|
6
6
|
// @ts-ignore: fetch requires lib "dom" or "webworker", but then the "cause" property becomes unknown to TS
|
|
7
7
|
super(message, { cause });
|
|
8
8
|
this.status = status;
|
|
9
|
+
this.headers = headers;
|
|
9
10
|
this.cause = cause;
|
|
10
11
|
this.response = response;
|
|
11
12
|
this.name = this.constructor.name;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FetchClientError.js","sourceRoot":"","sources":["../src/FetchClientError.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"FetchClientError.js","sourceRoot":"","sources":["../src/FetchClientError.ts"],"names":[],"mappings":";;;AAEA,MAAa,gBAAiB,SAAQ,KAAK;IACzC,YACE,OAAe,EACC,MAAe,EACf,OAAgC,EAChC,KAAa,EACb,QAAmB;QAEnC,2GAA2G;QAC3G,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QANV,WAAM,GAAN,MAAM,CAAS;QACf,YAAO,GAAP,OAAO,CAAyB;QAChC,UAAK,GAAL,KAAK,CAAQ;QACb,aAAQ,GAAR,QAAQ,CAAW;QAInC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IACpC,CAAC;CACF;AAZD,4CAYC","sourcesContent":["import { ODataClientError } from \"@odata2ts/http-client-api\";\r\n\r\nexport class FetchClientError extends Error implements ODataClientError {\r\n constructor(\r\n message: string,\r\n public readonly status?: number,\r\n public readonly headers?: Record<string, string>,\r\n public readonly cause?: Error,\r\n public readonly response?: Response\r\n ) {\r\n // @ts-ignore: fetch requires lib \"dom\" or \"webworker\", but then the \"cause\" property becomes unknown to TS\r\n super(message, { cause });\r\n this.name = this.constructor.name;\r\n }\r\n}\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@odata2ts/http-client-fetch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"odata2ts"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@odata2ts/http-client-
|
|
34
|
+
"@odata2ts/http-client-base": "^0.1.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@odata2ts/odata-core": "^0.3.7",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"typescript": "5.0.4"
|
|
45
45
|
},
|
|
46
46
|
"types": "./lib/index.d.ts",
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "485559dce9388b480c7d0b3ded2fe2f3f5f98fad"
|
|
48
48
|
}
|