@odata2ts/http-client-base 0.3.0 → 0.4.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 CHANGED
@@ -3,6 +3,22 @@
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.4.0](https://github.com/odata2ts/http-client/compare/@odata2ts/http-client-base@0.3.0...@odata2ts/http-client-base@0.4.0) (2023-09-13)
7
+
8
+ ### Code Refactoring
9
+
10
+ * expand additionalHeaders param to internalConfig ([#15](https://github.com/odata2ts/http-client/issues/15)) ([7fe1d73](https://github.com/odata2ts/http-client/commit/7fe1d73a7436f64b84a060bd1dbf9e121ef901ce))
11
+
12
+ ### BREAKING CHANGES
13
+
14
+ * additional headers are now part of the config parameter
15
+
16
+ * fix: don't lose configuration when CSRF token is active
17
+
18
+ * add new option which prevents FetchClient from evaluating response body (not needed for fetching csrf tokens & makes trouble with SAP's mockServer)
19
+
20
+ * fix: add headers for content-type and accept for main methods
21
+
6
22
  # [0.3.0](https://github.com/odata2ts/http-client/compare/@odata2ts/http-client-base@0.2.0...@odata2ts/http-client-base@0.3.0) (2023-08-03)
7
23
 
8
24
  ### Code Refactoring
package/README.md CHANGED
@@ -1,80 +1,74 @@
1
- [![npm (scoped)](https://img.shields.io/npm/v/@odata2ts/http-client-base?style=for-the-badge)](https://www.npmjs.com/package/@odata2ts/http-client-base)
2
-
3
- # OData HTTP Client Base
4
-
5
- Base implementation for [odata2ts](https://github.com/odata2ts/odata2ts) compatible HTTP clients:
6
-
7
- - implements automatic CSRF token handling
8
- - allows user to set custom CSRF token header key (default: `x-csrf-token`)
9
- - implements standard error message retrieval method for OData error responses
10
- - works for V2 & V4
11
- - allows user to set custom retrieval method
12
- - streamlines all HTTP calls (POST, GET, ...) into one method
13
-
14
- ## Installation
15
-
16
- Install package `@odata2ts/http-client-base` as dependency:
17
-
18
- ```bash
19
- npm install --save @odata2ts/http-client-base
20
- ```
21
-
22
- ## Usage
23
-
24
- Extend the class `BaseHttpClient` to simplify your HttpClient implementation.
25
- You need to implement two abstract methods:
26
-
27
- ```ts
28
- import { BaseHttpClient, BaseHttpClientOptions } from "@odata2ts/http-client-base";
29
-
30
- export interface MyRequestConfig {}
31
-
32
- export class MyHttpClient extends BaseHttpClient<MyRequestConfig> {
33
- constructor(clientOptions: BaseHttpClientOptions) {
34
- super(clientOptions);
35
- }
36
-
37
- protected addHeaderToRequestConfig(
38
- headers: Record<string, string>,
39
- config: BaseHttpClient | undefined
40
- ): MyRequestConfig {
41
- // your implementation
42
- }
43
-
44
- protected async executeRequest<ResponseModel>(
45
- method: HttpMethods,
46
- url: string,
47
- data: any,
48
- requestConfig: AxiosRequestConfig | undefined = {}
49
- ): Promise<HttpResponseModel<ResponseModel>> {
50
- // your implementation
51
- }
52
- }
53
- ```
54
-
55
- Compare any of the existing clients.
56
-
57
- ## Documentation
58
-
59
- [HTTP Client Documentation](https://odata2ts.github.io/docs/http-client)
60
-
61
- Main documentation for the odata2ts eco system:
62
- [https://odata2ts.github.io](https://odata2ts.github.io/)
63
-
64
- ## Tests
65
-
66
- See folder [test](https://github.com/odata2ts/http-client/tree/main/packages/core/test)
67
- for unit tests.
68
-
69
- ## Support, Feedback, Contributing
70
-
71
- This project is open to feature requests, suggestions, bug reports, usage questions etc.
72
- via [GitHub issues](https://github.com/odata2ts/http-client/issues).
73
-
74
- Contributions and feedback are encouraged and always welcome.
75
-
76
- See the [contribution guidelines](https://github.com/odata2ts/http-client/blob/main/CONTRIBUTING.md) for further information.
77
-
78
- ## License
79
-
80
- MIT - see [License](./LICENSE).
1
+ [![npm (scoped)](https://img.shields.io/npm/v/@odata2ts/http-client-base?style=for-the-badge)](https://www.npmjs.com/package/@odata2ts/http-client-base)
2
+
3
+ # OData HTTP Client Base
4
+
5
+ Base implementation for [odata2ts](https://github.com/odata2ts/odata2ts) compatible HTTP clients:
6
+
7
+ - implements automatic CSRF token handling
8
+ - allows user to set custom CSRF token header key (default: `x-csrf-token`)
9
+ - implements standard error message retrieval method for OData error responses
10
+ - works for V2 & V4
11
+ - allows user to set custom retrieval method
12
+ - streamlines all HTTP calls (POST, GET, ...) into one method
13
+
14
+ ## Installation
15
+
16
+ Install package `@odata2ts/http-client-base` as dependency:
17
+
18
+ ```bash
19
+ npm install --save @odata2ts/http-client-base
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ Extend the class `BaseHttpClient` to simplify your HttpClient implementation.
25
+ You need to implement two abstract methods:
26
+
27
+ ```ts
28
+ import { BaseHttpClient, BaseHttpClientOptions, InternalHttpClientConfig } from "@odata2ts/http-client-base";
29
+
30
+ export interface MyRequestConfig {}
31
+
32
+ export class MyHttpClient extends BaseHttpClient<MyRequestConfig> {
33
+ constructor(clientOptions: BaseHttpClientOptions) {
34
+ super(clientOptions);
35
+ }
36
+
37
+ protected async executeRequest<ResponseModel>(
38
+ method: HttpMethods,
39
+ url: string,
40
+ data: any,
41
+ requestConfig: AxiosRequestConfig | undefined = {},
42
+ internalConfig?: InternalHttpClientConfig
43
+ ): Promise<HttpResponseModel<ResponseModel>> {
44
+ // your implementation
45
+ }
46
+ }
47
+ ```
48
+
49
+ Compare any of the existing clients.
50
+
51
+ ## Documentation
52
+
53
+ [HTTP Client Documentation](https://odata2ts.github.io/docs/http-client)
54
+
55
+ Main documentation for the odata2ts eco system:
56
+ [https://odata2ts.github.io](https://odata2ts.github.io/)
57
+
58
+ ## Tests
59
+
60
+ See folder [test](https://github.com/odata2ts/http-client/tree/main/packages/core/test)
61
+ for unit tests.
62
+
63
+ ## Support, Feedback, Contributing
64
+
65
+ This project is open to feature requests, suggestions, bug reports, usage questions etc.
66
+ via [GitHub issues](https://github.com/odata2ts/http-client/issues).
67
+
68
+ Contributions and feedback are encouraged and always welcome.
69
+
70
+ See the [contribution guidelines](https://github.com/odata2ts/http-client/blob/main/CONTRIBUTING.md) for further information.
71
+
72
+ ## License
73
+
74
+ MIT - see [License](./LICENSE).
@@ -1,4 +1,4 @@
1
- import { HttpResponseModel, ODataHttpClient } from "@odata2ts/http-client-api";
1
+ import { HttpResponseModel, InternalHttpClientConfig, ODataHttpClient } from "@odata2ts/http-client-api";
2
2
  import { ErrorMessageRetriever } from "./ErrorMessageRetriever";
3
3
  import { HttpMethods } from "./HttpMethods";
4
4
  export interface BaseHttpClientOptions {
@@ -20,15 +20,6 @@ export declare abstract class BaseHttpClient<RequestConfigType> implements OData
20
20
  private csrfTokenKey;
21
21
  protected retrieveErrorMessage: ErrorMessageRetriever;
22
22
  protected constructor(baseOptions?: BaseHttpClientOptions);
23
- /**
24
- * Use the given headers to either create an entire new request config or merge them into the given
25
- * request config.
26
- *
27
- * @param headers
28
- * @param config
29
- * @returns request configuration
30
- */
31
- protected abstract addHeaderToRequestConfig(headers: Record<string, string>, config?: RequestConfigType): RequestConfigType;
32
23
  /**
33
24
  * Main function to implement by any extending http client.
34
25
  * As it name suggests, the request gets executed in this method.
@@ -38,8 +29,9 @@ export declare abstract class BaseHttpClient<RequestConfigType> implements OData
38
29
  * @param url
39
30
  * @param data
40
31
  * @param config
32
+ * @param internalConfig
41
33
  */
42
- protected abstract executeRequest<ResponseModel>(method: HttpMethods, url: string, data: any, config?: RequestConfigType): Promise<HttpResponseModel<ResponseModel>>;
34
+ protected abstract executeRequest<ResponseModel>(method: HttpMethods, url: string, data: any, config?: RequestConfigType, internalConfig?: InternalHttpClientConfig): Promise<HttpResponseModel<ResponseModel>>;
43
35
  getCsrfTokenKey(): string;
44
36
  setCsrfTokenKey(newKey: string): void;
45
37
  setErrorMessageRetriever(getErrorMsg: ErrorMessageRetriever): void;
@@ -52,13 +44,13 @@ export declare abstract class BaseHttpClient<RequestConfigType> implements OData
52
44
  * @param url
53
45
  * @param data
54
46
  * @param requestConfig
55
- * @param additionalHeaders
47
+ * @param internalConfig
56
48
  * @private
57
49
  */
58
50
  private sendRequest;
59
- get<ResponseModel>(url: string, requestConfig?: RequestConfigType, additionalHeaders?: Record<string, string>): Promise<HttpResponseModel<ResponseModel>>;
60
- post<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType, additionalHeaders?: Record<string, string>): Promise<HttpResponseModel<ResponseModel>>;
61
- put<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType, additionalHeaders?: Record<string, string>): Promise<HttpResponseModel<ResponseModel>>;
62
- patch<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType, additionalHeaders?: Record<string, string>): Promise<HttpResponseModel<ResponseModel>>;
63
- delete(url: string, requestConfig?: RequestConfigType, additionalHeaders?: Record<string, string>): Promise<HttpResponseModel<void>>;
51
+ get<ResponseModel>(url: string, requestConfig?: RequestConfigType, config?: InternalHttpClientConfig): Promise<HttpResponseModel<ResponseModel>>;
52
+ post<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType, config?: InternalHttpClientConfig): Promise<HttpResponseModel<ResponseModel>>;
53
+ put<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType, config?: InternalHttpClientConfig): Promise<HttpResponseModel<ResponseModel>>;
54
+ patch<ResponseModel>(url: string, data: any, requestConfig?: RequestConfigType, config?: InternalHttpClientConfig): Promise<HttpResponseModel<ResponseModel>>;
55
+ delete(url: string, requestConfig?: RequestConfigType, config?: InternalHttpClientConfig): Promise<HttpResponseModel<void>>;
64
56
  }
@@ -5,9 +5,13 @@ const tslib_1 = require("tslib");
5
5
  const ErrorMessageRetriever_1 = require("./ErrorMessageRetriever");
6
6
  const HttpMethods_1 = require("./HttpMethods");
7
7
  exports.DEFAULT_CSRF_TOKEN_KEY = "x-csrf-token";
8
- const EDIT_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
8
+ const EDIT_METHODS = [HttpMethods_1.HttpMethods.Post, HttpMethods_1.HttpMethods.Put, HttpMethods_1.HttpMethods.Patch, HttpMethods_1.HttpMethods.Delete];
9
9
  const FAILURE_MISSING_CSRF_URL = "When automatic CSRF token handling is activated, the URL must be supplied via attribute [csrfTokenFetchUrl]!";
10
10
  const FAILURE_MISSING_URL = "Value for URL must be provided!";
11
+ const JSON_VALUE = "application/json";
12
+ function addJsonHeaders(config, setContentType = true) {
13
+ config.headers = Object.assign(Object.assign({ Accept: JSON_VALUE }, (setContentType ? { "Content-Type": JSON_VALUE } : undefined)), config.headers);
14
+ }
11
15
  class BaseHttpClient {
12
16
  constructor(baseOptions = { useCsrfProtection: false }) {
13
17
  var _a;
@@ -38,7 +42,10 @@ class BaseHttpClient {
38
42
  fetchSecurityToken() {
39
43
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
40
44
  const fetchUrl = this.baseOptions.csrfTokenFetchUrl;
41
- const response = yield this.get(fetchUrl, this.addHeaderToRequestConfig({ [this.csrfTokenKey]: "Fetch" }));
45
+ const response = yield this.get(fetchUrl, undefined, {
46
+ noBodyEvaluation: true,
47
+ headers: { [this.csrfTokenKey]: "Fetch", Accept: "application/json" },
48
+ });
42
49
  return response.headers[this.csrfTokenKey];
43
50
  });
44
51
  }
@@ -49,30 +56,27 @@ class BaseHttpClient {
49
56
  * @param url
50
57
  * @param data
51
58
  * @param requestConfig
52
- * @param additionalHeaders
59
+ * @param internalConfig
53
60
  * @private
54
61
  */
55
- sendRequest(method, url, data, requestConfig, additionalHeaders) {
62
+ sendRequest(method, url, data, requestConfig, internalConfig = {}) {
56
63
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
57
64
  // noinspection SuspiciousTypeOfGuard
58
65
  if (typeof url !== "string") {
59
66
  throw new Error(FAILURE_MISSING_URL);
60
67
  }
61
- // use big numbers
62
- let config = requestConfig;
63
- // use additional headers
64
- if (additionalHeaders) {
65
- config = this.addHeaderToRequestConfig(additionalHeaders, config);
66
- }
67
68
  // setup automatic CSRF token handling
68
69
  if (this.baseOptions.useCsrfProtection && EDIT_METHODS.includes(method)) {
69
70
  const [tokenKey, tokenValue] = yield this.setupSecurityToken();
70
71
  if (tokenValue) {
71
- config = this.addHeaderToRequestConfig({ [tokenKey]: tokenValue });
72
+ if (!internalConfig.headers) {
73
+ internalConfig.headers = {};
74
+ }
75
+ internalConfig.headers[tokenKey] = tokenValue;
72
76
  }
73
77
  }
74
78
  try {
75
- return yield this.executeRequest(method, url, data, config);
79
+ return yield this.executeRequest(method, url, data, requestConfig, internalConfig);
76
80
  }
77
81
  catch (e) {
78
82
  const clientError = e;
@@ -89,20 +93,24 @@ class BaseHttpClient {
89
93
  }
90
94
  });
91
95
  }
92
- get(url, requestConfig, additionalHeaders) {
93
- return this.sendRequest(HttpMethods_1.HttpMethods.Get, url, undefined, requestConfig, additionalHeaders);
96
+ get(url, requestConfig, config = {}) {
97
+ addJsonHeaders(config, false);
98
+ return this.sendRequest(HttpMethods_1.HttpMethods.Get, url, undefined, requestConfig, config);
94
99
  }
95
- post(url, data, requestConfig, additionalHeaders) {
96
- return this.sendRequest(HttpMethods_1.HttpMethods.Post, url, data, requestConfig, additionalHeaders);
100
+ post(url, data, requestConfig, config = {}) {
101
+ addJsonHeaders(config);
102
+ return this.sendRequest(HttpMethods_1.HttpMethods.Post, url, data, requestConfig, config);
97
103
  }
98
- put(url, data, requestConfig, additionalHeaders) {
99
- return this.sendRequest(HttpMethods_1.HttpMethods.Put, url, data, requestConfig, additionalHeaders);
104
+ put(url, data, requestConfig, config = {}) {
105
+ addJsonHeaders(config);
106
+ return this.sendRequest(HttpMethods_1.HttpMethods.Put, url, data, requestConfig, config);
100
107
  }
101
- patch(url, data, requestConfig, additionalHeaders) {
102
- return this.sendRequest(HttpMethods_1.HttpMethods.Patch, url, data, requestConfig, additionalHeaders);
108
+ patch(url, data, requestConfig, config = {}) {
109
+ addJsonHeaders(config);
110
+ return this.sendRequest(HttpMethods_1.HttpMethods.Patch, url, data, requestConfig, config);
103
111
  }
104
- delete(url, requestConfig, additionalHeaders) {
105
- return this.sendRequest(HttpMethods_1.HttpMethods.Delete, url, undefined, requestConfig, additionalHeaders);
112
+ delete(url, requestConfig, config = {}) {
113
+ return this.sendRequest(HttpMethods_1.HttpMethods.Delete, url, undefined, requestConfig, config);
106
114
  }
107
115
  }
108
116
  exports.BaseHttpClient = BaseHttpClient;
@@ -1 +1 @@
1
- {"version":3,"file":"BaseHttpClient.js","sourceRoot":"","sources":["../src/BaseHttpClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { HttpResponseModel, ODataClientError, ODataHttpClient } from \"@odata2ts/http-client-api\";\n\nimport { ErrorMessageRetriever, retrieveErrorMessage } from \"./ErrorMessageRetriever\";\nimport { HttpMethods } from \"./HttpMethods\";\n\nexport interface BaseHttpClientOptions {\n /**\n * Enable automatic CSRF token handling.\n */\n useCsrfProtection?: boolean;\n /**\n * Specify the URL from which the token is fetched.\n * This could be any path to your OData service, since the token is exchanged via HTTP request headers.\n * However, it should be a fast response and usually the root URL to the OData service is a good choice.\n */\n csrfTokenFetchUrl?: string;\n}\n\nexport const DEFAULT_CSRF_TOKEN_KEY = \"x-csrf-token\";\n\nconst EDIT_METHODS = [\"POST\", \"PUT\", \"PATCH\", \"DELETE\"];\nconst FAILURE_MISSING_CSRF_URL =\n \"When automatic CSRF token handling is activated, the URL must be supplied via attribute [csrfTokenFetchUrl]!\";\nconst FAILURE_MISSING_URL = \"Value for URL must be provided!\";\n\nexport abstract class BaseHttpClient<RequestConfigType> implements ODataHttpClient<RequestConfigType> {\n private csrfToken: string | undefined;\n private csrfTokenKey = DEFAULT_CSRF_TOKEN_KEY;\n\n protected retrieveErrorMessage: ErrorMessageRetriever = retrieveErrorMessage;\n\n protected constructor(private baseOptions: BaseHttpClientOptions = { useCsrfProtection: false }) {\n if (baseOptions.useCsrfProtection && !baseOptions.csrfTokenFetchUrl?.trim()) {\n throw new Error(FAILURE_MISSING_CSRF_URL);\n }\n }\n\n /**\n * Use the given headers to either create an entire new request config or merge them into the given\n * request config.\n *\n * @param headers\n * @param config\n * @returns request configuration\n */\n protected abstract addHeaderToRequestConfig(\n headers: Record<string, string>,\n config?: RequestConfigType\n ): RequestConfigType;\n\n /**\n * Main function to implement by any extending http client.\n * As it name suggests, the request gets executed in this method.\n * Additionally, failures should be handled and errors of type <code>HttpClientError</code> should be thrown.\n *\n * @param method\n * @param url\n * @param data\n * @param config\n */\n protected abstract executeRequest<ResponseModel>(\n method: HttpMethods,\n url: string,\n data: any,\n config?: RequestConfigType\n ): Promise<HttpResponseModel<ResponseModel>>;\n\n public getCsrfTokenKey() {\n return this.csrfTokenKey;\n }\n\n public setCsrfTokenKey(newKey: string) {\n this.csrfTokenKey = newKey || DEFAULT_CSRF_TOKEN_KEY;\n }\n\n public setErrorMessageRetriever(getErrorMsg: ErrorMessageRetriever) {\n this.retrieveErrorMessage = getErrorMsg;\n }\n\n protected async setupSecurityToken(): Promise<[string, string | undefined]> {\n if (!this.csrfToken) {\n this.csrfToken = await this.fetchSecurityToken();\n }\n return [this.csrfTokenKey, this.csrfToken];\n }\n\n protected async fetchSecurityToken(): Promise<string | undefined> {\n const fetchUrl = this.baseOptions!.csrfTokenFetchUrl!;\n const response = await this.get(fetchUrl, this.addHeaderToRequestConfig({ [this.csrfTokenKey]: \"Fetch\" }));\n\n return response.headers[this.csrfTokenKey];\n }\n\n /**\n * Follows the template pattern.\n *\n * @param method\n * @param url\n * @param data\n * @param requestConfig\n * @param additionalHeaders\n * @private\n */\n private async sendRequest<ResponseModel>(\n method: HttpMethods,\n url: string,\n data: any,\n requestConfig?: RequestConfigType,\n additionalHeaders?: Record<string, string>\n ): Promise<HttpResponseModel<ResponseModel>> {\n // noinspection SuspiciousTypeOfGuard\n if (typeof url !== \"string\") {\n throw new Error(FAILURE_MISSING_URL);\n }\n\n // use big numbers\n let config = requestConfig;\n\n // use additional headers\n if (additionalHeaders) {\n config = this.addHeaderToRequestConfig(additionalHeaders, config);\n }\n\n // setup automatic CSRF token handling\n if (this.baseOptions.useCsrfProtection && EDIT_METHODS.includes(method)) {\n const [tokenKey, tokenValue] = await this.setupSecurityToken();\n if (tokenValue) {\n config = this.addHeaderToRequestConfig({ [tokenKey]: tokenValue });\n }\n }\n\n try {\n return await this.executeRequest<ResponseModel>(method, url, data, config);\n } catch (e) {\n const clientError = e as ODataClientError;\n\n // automatic CSRF token handling\n if (\n !!this.baseOptions.useCsrfProtection &&\n clientError.status === 403 &&\n !!clientError.headers &&\n clientError.headers[\"x-csrf-token\"] === \"Required\"\n ) {\n // token has expired: reset csrf token & perform the original request again\n this.csrfToken = undefined;\n return this.sendRequest<ResponseModel>(method, url, data, requestConfig);\n }\n\n throw e;\n }\n }\n\n public get<ResponseModel>(\n url: string,\n requestConfig?: RequestConfigType,\n additionalHeaders?: Record<string, string>\n ): Promise<HttpResponseModel<ResponseModel>> {\n return this.sendRequest<ResponseModel>(HttpMethods.Get, url, undefined, requestConfig, additionalHeaders);\n }\n public post<ResponseModel>(\n url: string,\n data: any,\n requestConfig?: RequestConfigType,\n additionalHeaders?: Record<string, string>\n ): Promise<HttpResponseModel<ResponseModel>> {\n return this.sendRequest<ResponseModel>(HttpMethods.Post, url, data, requestConfig, additionalHeaders);\n }\n public put<ResponseModel>(\n url: string,\n data: any,\n requestConfig?: RequestConfigType,\n additionalHeaders?: Record<string, string>\n ): Promise<HttpResponseModel<ResponseModel>> {\n return this.sendRequest<ResponseModel>(HttpMethods.Put, url, data, requestConfig, additionalHeaders);\n }\n public patch<ResponseModel>(\n url: string,\n data: any,\n requestConfig?: RequestConfigType,\n additionalHeaders?: Record<string, string>\n ): Promise<HttpResponseModel<ResponseModel>> {\n return this.sendRequest<ResponseModel>(HttpMethods.Patch, url, data, requestConfig, additionalHeaders);\n }\n\n public delete(\n url: string,\n requestConfig?: RequestConfigType,\n additionalHeaders?: Record<string, string>\n ): Promise<HttpResponseModel<void>> {\n return this.sendRequest<void>(HttpMethods.Delete, url, undefined, requestConfig, additionalHeaders);\n }\n}\n"]}
1
+ {"version":3,"file":"BaseHttpClient.js","sourceRoot":"","sources":["../src/BaseHttpClient.ts"],"names":[],"mappings":"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","sourcesContent":["import {\r\n HttpResponseModel,\r\n InternalHttpClientConfig,\r\n ODataClientError,\r\n ODataHttpClient,\r\n} from \"@odata2ts/http-client-api\";\r\n\r\nimport { ErrorMessageRetriever, retrieveErrorMessage } from \"./ErrorMessageRetriever\";\r\nimport { HttpMethods } from \"./HttpMethods\";\r\n\r\nexport interface BaseHttpClientOptions {\r\n /**\r\n * Enable automatic CSRF token handling.\r\n */\r\n useCsrfProtection?: boolean;\r\n /**\r\n * Specify the URL from which the token is fetched.\r\n * This could be any path to your OData service, since the token is exchanged via HTTP request headers.\r\n * However, it should be a fast response and usually the root URL to the OData service is a good choice.\r\n */\r\n csrfTokenFetchUrl?: string;\r\n}\r\n\r\nexport const DEFAULT_CSRF_TOKEN_KEY = \"x-csrf-token\";\r\n\r\nconst EDIT_METHODS = [HttpMethods.Post, HttpMethods.Put, HttpMethods.Patch, HttpMethods.Delete];\r\nconst FAILURE_MISSING_CSRF_URL =\r\n \"When automatic CSRF token handling is activated, the URL must be supplied via attribute [csrfTokenFetchUrl]!\";\r\nconst FAILURE_MISSING_URL = \"Value for URL must be provided!\";\r\nconst JSON_VALUE = \"application/json\";\r\n\r\nfunction addJsonHeaders(config: InternalHttpClientConfig, setContentType: boolean = true) {\r\n config.headers = {\r\n Accept: JSON_VALUE,\r\n ...(setContentType ? { \"Content-Type\": JSON_VALUE } : undefined),\r\n ...config.headers,\r\n };\r\n}\r\n\r\nexport abstract class BaseHttpClient<RequestConfigType> implements ODataHttpClient<RequestConfigType> {\r\n private csrfToken: string | undefined;\r\n private csrfTokenKey = DEFAULT_CSRF_TOKEN_KEY;\r\n\r\n protected retrieveErrorMessage: ErrorMessageRetriever = retrieveErrorMessage;\r\n\r\n protected constructor(private baseOptions: BaseHttpClientOptions = { useCsrfProtection: false }) {\r\n if (baseOptions.useCsrfProtection && !baseOptions.csrfTokenFetchUrl?.trim()) {\r\n throw new Error(FAILURE_MISSING_CSRF_URL);\r\n }\r\n }\r\n\r\n /**\r\n * Main function to implement by any extending http client.\r\n * As it name suggests, the request gets executed in this method.\r\n * Additionally, failures should be handled and errors of type <code>HttpClientError</code> should be thrown.\r\n *\r\n * @param method\r\n * @param url\r\n * @param data\r\n * @param config\r\n * @param internalConfig\r\n */\r\n protected abstract executeRequest<ResponseModel>(\r\n method: HttpMethods,\r\n url: string,\r\n data: any,\r\n config?: RequestConfigType,\r\n internalConfig?: InternalHttpClientConfig\r\n ): Promise<HttpResponseModel<ResponseModel>>;\r\n\r\n public getCsrfTokenKey() {\r\n return this.csrfTokenKey;\r\n }\r\n\r\n public setCsrfTokenKey(newKey: string) {\r\n this.csrfTokenKey = newKey || DEFAULT_CSRF_TOKEN_KEY;\r\n }\r\n\r\n public setErrorMessageRetriever(getErrorMsg: ErrorMessageRetriever) {\r\n this.retrieveErrorMessage = getErrorMsg;\r\n }\r\n\r\n protected async setupSecurityToken(): Promise<[string, string | undefined]> {\r\n if (!this.csrfToken) {\r\n this.csrfToken = await this.fetchSecurityToken();\r\n }\r\n return [this.csrfTokenKey, this.csrfToken];\r\n }\r\n\r\n protected async fetchSecurityToken(): Promise<string | undefined> {\r\n const fetchUrl = this.baseOptions!.csrfTokenFetchUrl!;\r\n const response = await this.get(fetchUrl, undefined, {\r\n noBodyEvaluation: true,\r\n headers: { [this.csrfTokenKey]: \"Fetch\", Accept: \"application/json\" },\r\n });\r\n\r\n return response.headers[this.csrfTokenKey];\r\n }\r\n\r\n /**\r\n * Follows the template pattern.\r\n *\r\n * @param method\r\n * @param url\r\n * @param data\r\n * @param requestConfig\r\n * @param internalConfig\r\n * @private\r\n */\r\n private async sendRequest<ResponseModel>(\r\n method: HttpMethods,\r\n url: string,\r\n data: any,\r\n requestConfig?: RequestConfigType,\r\n internalConfig: InternalHttpClientConfig = {}\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n // noinspection SuspiciousTypeOfGuard\r\n if (typeof url !== \"string\") {\r\n throw new Error(FAILURE_MISSING_URL);\r\n }\r\n\r\n // setup automatic CSRF token handling\r\n if (this.baseOptions.useCsrfProtection && EDIT_METHODS.includes(method)) {\r\n const [tokenKey, tokenValue] = await this.setupSecurityToken();\r\n if (tokenValue) {\r\n if (!internalConfig.headers) {\r\n internalConfig.headers = {};\r\n }\r\n internalConfig.headers[tokenKey] = tokenValue;\r\n }\r\n }\r\n\r\n try {\r\n return await this.executeRequest<ResponseModel>(method, url, data, requestConfig, internalConfig);\r\n } catch (e) {\r\n const clientError = e as ODataClientError;\r\n\r\n // automatic CSRF token handling\r\n if (\r\n !!this.baseOptions.useCsrfProtection &&\r\n clientError.status === 403 &&\r\n !!clientError.headers &&\r\n clientError.headers[\"x-csrf-token\"] === \"Required\"\r\n ) {\r\n // token has expired: reset csrf token & perform the original request again\r\n this.csrfToken = undefined;\r\n return this.sendRequest<ResponseModel>(method, url, data, requestConfig);\r\n }\r\n\r\n throw e;\r\n }\r\n }\r\n\r\n public get<ResponseModel>(\r\n url: string,\r\n requestConfig?: RequestConfigType,\r\n config: InternalHttpClientConfig = {}\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n addJsonHeaders(config, false);\r\n return this.sendRequest<ResponseModel>(HttpMethods.Get, url, undefined, requestConfig, config);\r\n }\r\n\r\n public post<ResponseModel>(\r\n url: string,\r\n data: any,\r\n requestConfig?: RequestConfigType,\r\n config: InternalHttpClientConfig = {}\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n addJsonHeaders(config);\r\n return this.sendRequest<ResponseModel>(HttpMethods.Post, url, data, requestConfig, config);\r\n }\r\n\r\n public put<ResponseModel>(\r\n url: string,\r\n data: any,\r\n requestConfig?: RequestConfigType,\r\n config: InternalHttpClientConfig = {}\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n addJsonHeaders(config);\r\n return this.sendRequest<ResponseModel>(HttpMethods.Put, url, data, requestConfig, config);\r\n }\r\n\r\n public patch<ResponseModel>(\r\n url: string,\r\n data: any,\r\n requestConfig?: RequestConfigType,\r\n config: InternalHttpClientConfig = {}\r\n ): Promise<HttpResponseModel<ResponseModel>> {\r\n addJsonHeaders(config);\r\n return this.sendRequest<ResponseModel>(HttpMethods.Patch, url, data, requestConfig, config);\r\n }\r\n\r\n public delete(\r\n url: string,\r\n requestConfig?: RequestConfigType,\r\n config: InternalHttpClientConfig = {}\r\n ): Promise<HttpResponseModel<void>> {\r\n return this.sendRequest<void>(HttpMethods.Delete, url, undefined, requestConfig, config);\r\n }\r\n}\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odata2ts/http-client-base",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -29,7 +29,7 @@
29
29
  "odata2ts"
30
30
  ],
31
31
  "dependencies": {
32
- "@odata2ts/http-client-api": "^0.4.0"
32
+ "@odata2ts/http-client-api": "^0.5.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@odata2ts/odata-core": "^0.3.7",
@@ -42,5 +42,5 @@
42
42
  "typescript": "5.0.4"
43
43
  },
44
44
  "types": "./lib/index.d.ts",
45
- "gitHead": "dc6e29cef27b2f5fca6e838e4a22766b90249e8e"
45
+ "gitHead": "67c7fc10f25461c33baf4e35694a457a03b81b78"
46
46
  }