@verii/http-client 1.1.0-pre.1757918680 → 1.1.0-pre.1758002630

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.
Files changed (3) hide show
  1. package/index.js +2 -2
  2. package/package.json +4 -3
  3. package/src/client.js +151 -44
package/index.js CHANGED
@@ -14,6 +14,6 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- const { initHttpClient } = require('./src/client');
17
+ const { initHttpClient, initCache } = require('./src/client');
18
18
 
19
- module.exports = { initHttpClient };
19
+ module.exports = { initHttpClient, initCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verii/http-client",
3
- "version": "1.1.0-pre.1757918680",
3
+ "version": "1.1.0-pre.1758002630",
4
4
  "description": "HTTP client for Verii network",
5
5
  "repository": "https://github.com/LFDT-Verii/core",
6
6
  "main": "index.js",
@@ -20,7 +20,8 @@
20
20
  "http-errors": "^2.0.0",
21
21
  "lodash": "^4.17.21",
22
22
  "nanoid": "~5.1.0",
23
- "undici": "^7.0.0"
23
+ "undici": "^7.0.0",
24
+ "undici-oidc-interceptor": "^0.6.0"
24
25
  },
25
26
  "devDependencies": {
26
27
  "eslint": "8.57.1",
@@ -41,5 +42,5 @@
41
42
  "lib"
42
43
  ]
43
44
  },
44
- "gitHead": "f0bb52fc63b22fadfe65a820a5a4425580a3c8a7"
45
+ "gitHead": "b961d761dc0f0710df506d90222145afe3c35cf6"
45
46
  }
package/src/client.js CHANGED
@@ -15,71 +15,149 @@
15
15
  */
16
16
 
17
17
  const { nanoid } = require('nanoid/non-secure');
18
- const { Agent, interceptors, cacheStores } = require('undici');
18
+ const {
19
+ Agent,
20
+ interceptors,
21
+ cacheStores,
22
+ getGlobalDispatcher,
23
+ } = require('undici');
24
+ const { createOidcInterceptor } = require('undici-oidc-interceptor');
25
+ const { map } = require('lodash/fp');
19
26
  const pkg = require('../package.json');
20
27
 
21
28
  const USER_AGENT_HEADER = `${pkg.name}/${pkg.version}`;
22
29
  const registeredPrefixUrls = new Map();
23
30
 
31
+ const initCache = () => new cacheStores.MemoryCacheStore();
32
+
33
+ const buildInterceptors = ({
34
+ isTest,
35
+ cache,
36
+ tokensEndpoint,
37
+ clientId,
38
+ clientSecret,
39
+ scopes,
40
+ audience,
41
+ }) => {
42
+ const requiredInterceptors = [
43
+ interceptors.responseError(),
44
+ ...addCache(cache),
45
+ ];
46
+
47
+ if (tokensEndpoint) {
48
+ const origins = map(
49
+ (url) => url.origin,
50
+ registeredPrefixUrls.values().toArray()
51
+ );
52
+ const oidcInterceptor = createOidcInterceptor({
53
+ idpTokenUrl: tokensEndpoint,
54
+ clientId,
55
+ clientSecret,
56
+ retryOnStatusCodes: [401],
57
+ scopes,
58
+ audience,
59
+ urls: origins,
60
+ });
61
+ requiredInterceptors.push(oidcInterceptor);
62
+ }
63
+
64
+ if (!isTest) {
65
+ requiredInterceptors.push(
66
+ interceptors.dns({ maxTTL: 300000, maxItems: 2000, dualStack: false })
67
+ );
68
+ }
69
+
70
+ return requiredInterceptors;
71
+ };
72
+
24
73
  const initHttpClient = (options) => {
74
+ const { prefixUrl, isTest, bearerToken } = options;
75
+
25
76
  const { clientOptions, traceIdHeader, customHeaders } = parseOptions(options);
26
77
 
27
- // register prefixUrls
28
- for (const prefixUrl of options.prefixUrls ?? []) {
78
+ // register prefixUrl
79
+ if (prefixUrl) {
29
80
  const parsedPrefixUrl = parsePrefixUrl(prefixUrl);
30
81
  registeredPrefixUrls.set(prefixUrl, parsedPrefixUrl);
31
82
  }
32
83
 
33
- const store = new cacheStores.MemoryCacheStore();
34
- const agent =
35
- options.agent ??
36
- new Agent(clientOptions).compose([
37
- interceptors.dns({ maxTTL: 300000, maxItems: 2000, dualStack: false }),
38
- interceptors.responseError(),
39
- interceptors.cache({ store, methods: ['GET'] }),
40
- ]);
84
+ let agent;
85
+
86
+ if (isTest) {
87
+ const existingAgent = getGlobalDispatcher();
88
+ agent = existingAgent.compose(buildInterceptors(options));
89
+ } else {
90
+ agent = new Agent(clientOptions).compose(buildInterceptors(options));
91
+ }
41
92
 
42
- const request = async (url, reqOptions, method, host, { traceId, log }) => {
93
+ const request = async (
94
+ url,
95
+ reqOptions,
96
+ method,
97
+ host,
98
+ { traceId, log },
99
+ body
100
+ ) => {
43
101
  const reqId = nanoid();
44
102
  const reqHeaders = {
45
103
  'user-agent': USER_AGENT_HEADER,
46
104
  [traceIdHeader]: traceId,
47
105
  ...customHeaders,
106
+ ...reqOptions?.headers,
107
+ ...buildBearerAuthorizationHeader(bearerToken),
48
108
  };
49
- const [origin, path] =
50
- host != null
51
- ? [host.origin, buildRelativePath(host.rootPath, url, reqOptions)]
52
- : parseFullURL(url, clientOptions, reqOptions);
109
+ const [origin, path] = buildUrl(host, url, reqOptions, clientOptions);
53
110
 
54
111
  log.info({ origin, path, url, reqId, reqHeaders }, 'HttpClient request');
55
112
 
56
- const httpResponse = await agent.request({
57
- origin,
58
- path,
59
- method,
60
- headers: reqHeaders,
61
- });
62
- const { statusCode, headers: resHeaders, body } = httpResponse;
63
- return {
64
- statusCode,
65
- resHeaders,
66
- json: async () => {
67
- const bodyJson = await body.json();
68
- log.info(
69
- { origin, url, reqId, statusCode, resHeaders, body: bodyJson },
70
- 'HttpClient response'
71
- );
72
- return bodyJson;
73
- },
74
- text: async () => {
75
- const bodyText = await body.text();
76
- log.info(
77
- { origin, url, reqId, statusCode, resHeaders, body: bodyText },
78
- 'HttpClient response'
79
- );
80
- return bodyText;
81
- },
82
- };
113
+ try {
114
+ const httpRequest = {
115
+ origin,
116
+ path,
117
+ method,
118
+ headers: reqHeaders,
119
+ };
120
+ if (body) {
121
+ httpRequest.body = body;
122
+ }
123
+ const httpResponse = await agent.request(httpRequest);
124
+ const { statusCode, headers: resHeaders, body: rawBody } = httpResponse;
125
+ return {
126
+ rawBody,
127
+ statusCode,
128
+ resHeaders,
129
+ json: async () => {
130
+ try {
131
+ const bodyJson = await rawBody.json();
132
+ log.info(
133
+ { origin, url, reqId, statusCode, resHeaders, body: bodyJson },
134
+ 'HttpClient response'
135
+ );
136
+ return bodyJson;
137
+ } catch (error) {
138
+ log.error(
139
+ { origin, url, reqId, statusCode, resHeaders, error },
140
+ 'JSON parsing error'
141
+ );
142
+
143
+ return {};
144
+ }
145
+ },
146
+ text: async () => {
147
+ const bodyText = await rawBody.text();
148
+ log.info(
149
+ { origin, url, reqId, statusCode, resHeaders, body: bodyText },
150
+ 'HttpClient response'
151
+ );
152
+ return bodyText;
153
+ },
154
+ };
155
+ } catch (error) {
156
+ // eslint-disable-next-line better-mutation/no-mutation
157
+ error.url = `${origin}${path}`;
158
+
159
+ throw error;
160
+ }
83
161
  };
84
162
 
85
163
  return (...args) => {
@@ -93,6 +171,17 @@ const initHttpClient = (options) => {
93
171
  return {
94
172
  get: (url, reqOptions) =>
95
173
  request(url, reqOptions, HTTP_VERBS.GET, host, context),
174
+ post: (url, payload, reqOptions) =>
175
+ request(
176
+ url,
177
+ reqOptions,
178
+ HTTP_VERBS.POST,
179
+ host,
180
+ context,
181
+ JSON.stringify(payload)
182
+ ),
183
+ delete: (url, reqOptions) =>
184
+ request(url, reqOptions, HTTP_VERBS.DELETE, host, context),
96
185
  responseType: 'promise',
97
186
  };
98
187
  };
@@ -124,6 +213,16 @@ const parsePrefixUrl = (prefixUrl) => {
124
213
  };
125
214
  };
126
215
 
216
+ const buildUrl = (host, url, reqOptions, clientOptions) => {
217
+ const fullUrl = reqOptions?.prefixUrl
218
+ ? new URL(url, reqOptions.prefixUrl).toString()
219
+ : url;
220
+
221
+ return host && !reqOptions?.prefixUrl
222
+ ? [host.origin, buildRelativePath(host.rootPath, url, reqOptions)]
223
+ : parseFullURL(fullUrl, clientOptions, reqOptions);
224
+ };
225
+
127
226
  const parseFullURL = (url, clientOptions, reqOptions) => {
128
227
  const { origin, pathname } = new URL(url);
129
228
  return [origin, addSearchParams(pathname, reqOptions?.searchParams)];
@@ -138,8 +237,16 @@ const buildRelativePath = (rootPath, url, reqOptions) =>
138
237
  const addSearchParams = (path, searchParams) =>
139
238
  searchParams != null ? `${path}?${searchParams}` : path;
140
239
 
240
+ const addCache = (store) =>
241
+ store ? [interceptors.cache({ store, methods: ['GET'] })] : [];
242
+
243
+ const buildBearerAuthorizationHeader = (token) =>
244
+ token ? { Authorization: `Bearer ${token}` } : {};
245
+
141
246
  const HTTP_VERBS = {
142
247
  GET: 'GET',
248
+ POST: 'POST',
249
+ DELETE: 'DELETE',
143
250
  };
144
251
 
145
- module.exports = { initHttpClient, parseOptions, parsePrefixUrl };
252
+ module.exports = { initHttpClient, parseOptions, parsePrefixUrl, initCache };