@verii/http-client 1.1.0-pre.1764065131 → 1.1.0-pre.1765262457

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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/client.js +118 -98
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verii/http-client",
3
- "version": "1.1.0-pre.1764065131",
3
+ "version": "1.1.0-pre.1765262457",
4
4
  "description": "HTTP client for Verii network",
5
5
  "repository": "https://github.com/LFDT-Verii/core",
6
6
  "main": "index.js",
@@ -42,5 +42,5 @@
42
42
  "lib"
43
43
  ]
44
44
  },
45
- "gitHead": "22f75261bf2f5fca4d48f4a5473e243a3f5e68e8"
45
+ "gitHead": "6a5515772ad290ae6cbc6884b85ef2ad86afa4c3"
46
46
  }
package/src/client.js CHANGED
@@ -22,74 +22,28 @@ const {
22
22
  getGlobalDispatcher,
23
23
  } = require('undici');
24
24
  const { createOidcInterceptor } = require('undici-oidc-interceptor');
25
- const { map } = require('lodash/fp');
26
25
  const { isObject } = require('lodash');
27
26
  const pkg = require('../package.json');
28
27
 
29
28
  const USER_AGENT_HEADER = `${pkg.name}/${pkg.version}`;
30
- const registeredPrefixUrls = new Map();
31
29
 
32
30
  const initCache = () => new cacheStores.MemoryCacheStore();
33
31
 
34
- const buildInterceptors = ({
35
- isTest,
36
- cache,
37
- tokensEndpoint,
38
- clientId,
39
- clientSecret,
40
- scopes,
41
- audience,
42
- }) => {
43
- const requiredInterceptors = [
44
- interceptors.responseError(),
45
- ...addCache(cache),
46
- ];
47
-
48
- if (tokensEndpoint) {
49
- const origins = map(
50
- (url) => url.origin,
51
- registeredPrefixUrls.values().toArray()
52
- );
53
- const oidcInterceptor = createOidcInterceptor({
54
- idpTokenUrl: tokensEndpoint,
55
- clientId,
56
- clientSecret,
57
- retryOnStatusCodes: [401],
58
- scope: scopes,
59
- audience,
60
- urls: origins,
61
- });
62
- requiredInterceptors.push(oidcInterceptor);
63
- }
64
-
65
- if (!isTest) {
66
- requiredInterceptors.push(
67
- interceptors.dns({ maxTTL: 300000, maxItems: 2000, dualStack: false })
68
- );
69
- }
70
-
71
- return requiredInterceptors;
72
- };
73
-
74
32
  const initHttpClient = (options) => {
75
33
  const { prefixUrl, isTest, bearerToken } = options;
76
34
 
77
35
  const { clientOptions, traceIdHeader, customHeaders } = parseOptions(options);
78
36
 
79
- // register prefixUrl
80
- if (prefixUrl) {
81
- const parsedPrefixUrl = parsePrefixUrl(prefixUrl);
82
- registeredPrefixUrls.set(prefixUrl, parsedPrefixUrl);
83
- }
37
+ const presetHost = prefixUrl != null ? parsePrefixUrl(prefixUrl) : undefined;
84
38
 
85
- let agent;
39
+ const baseAgent = isTest ? getGlobalDispatcher() : new Agent(clientOptions);
40
+ const agent = baseAgent.compose(buildInterceptorChain(presetHost, options));
86
41
 
87
- if (isTest) {
88
- const existingAgent = getGlobalDispatcher();
89
- agent = existingAgent.compose(buildInterceptors(options));
90
- } else {
91
- agent = new Agent(clientOptions).compose(buildInterceptors(options));
92
- }
42
+ const defaultReqHeaders = {
43
+ 'user-agent': USER_AGENT_HEADER,
44
+ ...customHeaders,
45
+ ...buildBearerAuthorizationHeader(bearerToken),
46
+ };
93
47
 
94
48
  const request = async (
95
49
  url,
@@ -97,16 +51,11 @@ const initHttpClient = (options) => {
97
51
  method,
98
52
  host,
99
53
  { traceId, log },
100
- body
54
+ body,
55
+ contentType
101
56
  ) => {
102
57
  const reqId = nanoid();
103
- const reqHeaders = {
104
- 'user-agent': USER_AGENT_HEADER,
105
- [traceIdHeader]: traceId,
106
- ...customHeaders,
107
- ...reqOptions?.headers,
108
- ...buildBearerAuthorizationHeader(bearerToken),
109
- };
58
+ const reqHeaders = buildReqHeaders(reqOptions, contentType, traceId);
110
59
  const [origin, path] = buildUrl(host, url, reqOptions);
111
60
 
112
61
  log.info({ origin, path, url, reqId, reqHeaders }, 'HttpClient request');
@@ -161,42 +110,55 @@ const initHttpClient = (options) => {
161
110
  }
162
111
  };
163
112
 
164
- return (...args) => {
165
- let host;
166
- let context = args[0];
167
- if (args.length === 2) {
168
- host = registeredPrefixUrls.get(args[0]) ?? parsePrefixUrl(args[0]);
169
- context = args[1];
113
+ const buildReqHeaders = (reqOptions, contentType, traceId) => {
114
+ const reqHeaders = {
115
+ ...defaultReqHeaders,
116
+ ...(reqOptions.headers ?? {}),
117
+ [traceIdHeader]: traceId,
118
+ };
119
+ if (contentType) {
120
+ reqHeaders['content-type'] = contentType;
170
121
  }
122
+ return reqHeaders;
123
+ };
171
124
 
125
+ return (...args) => {
126
+ const { host, context } = parseArgs(presetHost, args);
172
127
  return {
173
- get: (url, reqOptions) =>
128
+ get: (url, reqOptions = {}) =>
174
129
  request(url, reqOptions, HTTP_VERBS.GET, host, context),
175
- post: (url, payload, reqOptions) =>
130
+ post: (url, payload, reqOptions = {}) =>
176
131
  request(
177
132
  url,
178
- {
179
- ...reqOptions,
180
- headers: setContentType(reqOptions?.headers || {}, payload),
181
- },
133
+ reqOptions,
182
134
  HTTP_VERBS.POST,
183
135
  host,
184
136
  context,
185
- isObject(payload) ? JSON.stringify(payload) : payload
137
+ isObject(payload) ? JSON.stringify(payload) : payload,
138
+ calcContentType(payload, reqOptions?.headers)
186
139
  ),
187
- delete: (url, reqOptions) =>
140
+ delete: (url, reqOptions = {}) =>
188
141
  request(url, reqOptions, HTTP_VERBS.DELETE, host, context),
189
142
  responseType: 'promise',
190
143
  };
191
144
  };
192
145
  };
193
146
 
194
- const setContentType = (headers, payload) => {
195
- if (isObject(payload) && !headers['content-type']) {
196
- return { ...headers, 'content-type': 'application/json' };
197
- }
147
+ const calcContentType = (payload, headers = {}) =>
148
+ isObject(payload) && !headers['content-type']
149
+ ? 'application/json'
150
+ : headers['content-type'];
198
151
 
199
- return headers;
152
+ const parseArgs = (presetHost, args) => {
153
+ if (args.length === 1) {
154
+ return { host: presetHost, context: args[0] };
155
+ }
156
+ if (args.length === 2) {
157
+ return { host: parsePrefixUrl(args[0]), context: args[1] };
158
+ }
159
+ throw new Error(
160
+ `HttpClient: Expected 1 or 2 arguments, received ${args.length}`
161
+ );
200
162
  };
201
163
 
202
164
  const parseOptions = (options) => {
@@ -216,6 +178,47 @@ const parseOptions = (options) => {
216
178
  };
217
179
  };
218
180
 
181
+ const buildInterceptorChain = (
182
+ host,
183
+ {
184
+ isTest,
185
+ cache: cacheStore,
186
+ tokensEndpoint,
187
+ clientId,
188
+ clientSecret,
189
+ scopes,
190
+ audience,
191
+ }
192
+ ) => {
193
+ const chain = [];
194
+ if (!isTest) {
195
+ chain.push(
196
+ interceptors.dns({ maxTTL: 300000, maxItems: 2000, dualStack: false })
197
+ );
198
+ }
199
+
200
+ chain.push(interceptors.responseError());
201
+
202
+ if (cacheStore != null) {
203
+ chain.push(interceptors.cache({ store: cacheStore, methods: ['GET'] }));
204
+ }
205
+
206
+ if (tokensEndpoint && host != null) {
207
+ const oidcInterceptor = createOidcInterceptor({
208
+ idpTokenUrl: tokensEndpoint,
209
+ clientId,
210
+ clientSecret,
211
+ retryOnStatusCodes: [401],
212
+ scope: scopes,
213
+ audience,
214
+ urls: [host.origin],
215
+ });
216
+ chain.push(oidcInterceptor);
217
+ }
218
+
219
+ return chain;
220
+ };
221
+
219
222
  const parsePrefixUrl = (prefixUrl) => {
220
223
  const url = new URL(prefixUrl);
221
224
  return {
@@ -226,34 +229,45 @@ const parsePrefixUrl = (prefixUrl) => {
226
229
  };
227
230
 
228
231
  const buildUrl = (host, url, reqOptions) => {
229
- const fullUrl = reqOptions?.prefixUrl
230
- ? new URL(url, reqOptions.prefixUrl).toString()
231
- : url;
232
-
233
- return host && !reqOptions?.prefixUrl
234
- ? [host.origin, buildRelativePath(host.rootPath, url, reqOptions)]
235
- : parseFullURL(fullUrl, reqOptions);
232
+ if (/https?:\/\//.test(url)) {
233
+ return parseFullURL(url, reqOptions);
234
+ }
235
+ if (!host) {
236
+ throw new Error(
237
+ 'HttpClient: Cannot build URL without prefixUrl or full url'
238
+ );
239
+ }
240
+ return [host.origin, buildRelativePath(host.rootPath ?? '', url, reqOptions)];
236
241
  };
237
242
 
238
243
  const parseFullURL = (url, reqOptions) => {
239
244
  const { origin, pathname, searchParams } = new URL(url);
240
245
  return [
241
246
  origin,
242
- addSearchParams(pathname, reqOptions?.searchParams || `${searchParams}`),
247
+ addSearchParams(pathname, searchParams, reqOptions?.searchParams),
243
248
  ];
244
249
  };
245
250
 
246
- const buildRelativePath = (rootPath, url, reqOptions) =>
247
- addSearchParams(
248
- `${rootPath}${url.charAt[0] === '/' ? '' : '/'}${url}`,
251
+ const buildRelativePath = (rootPath, url, reqOptions) => {
252
+ const relativePath = `${rootPath}${url.charAt(0) === '/' ? '' : '/'}${url}`;
253
+ const [pathname, searchParamsString] = relativePath.split('?');
254
+
255
+ return addSearchParams(
256
+ pathname,
257
+ new URLSearchParams(searchParamsString),
249
258
  reqOptions?.searchParams
250
259
  );
260
+ };
251
261
 
252
- const addSearchParams = (path, searchParams) =>
253
- searchParams?.size || searchParams?.length ? `${path}?${searchParams}` : path;
254
-
255
- const addCache = (store) =>
256
- store ? [interceptors.cache({ store, methods: ['GET'] })] : [];
262
+ const addSearchParams = (path, searchParams, additionalParams) => {
263
+ const params = additionalParams?.size
264
+ ? new URLSearchParams([
265
+ ...Array.from(searchParams.entries()),
266
+ ...Array.from(additionalParams.entries()),
267
+ ])
268
+ : searchParams;
269
+ return params?.size ? `${path}?${params}` : path;
270
+ };
257
271
 
258
272
  const buildBearerAuthorizationHeader = (token) =>
259
273
  token ? { Authorization: `Bearer ${token}` } : {};
@@ -264,4 +278,10 @@ const HTTP_VERBS = {
264
278
  DELETE: 'DELETE',
265
279
  };
266
280
 
267
- module.exports = { initHttpClient, parseOptions, parsePrefixUrl, initCache };
281
+ module.exports = {
282
+ initHttpClient,
283
+ parseOptions,
284
+ parsePrefixUrl,
285
+ initCache,
286
+ buildUrl,
287
+ };