@podium/client 5.0.0-next.9 → 5.0.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.
@@ -1,16 +1,18 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
 
3
- import request from 'request';
4
3
  import abslog from 'abslog';
5
4
  import Metrics from '@metrics/client';
6
-
7
5
  import { join, dirname } from 'path';
8
6
  import { fileURLToPath } from 'url';
9
7
  import fs from 'fs';
8
+ import HTTP from './http.js';
10
9
 
11
10
  const currentDirectory = dirname(fileURLToPath(import.meta.url));
12
11
 
13
- const pkgJson = fs.readFileSync(join(currentDirectory, '../package.json'), 'utf-8');
12
+ const pkgJson = fs.readFileSync(
13
+ join(currentDirectory, '../package.json'),
14
+ 'utf-8',
15
+ );
14
16
  const pkg = JSON.parse(pkgJson);
15
17
 
16
18
  const UA_STRING = `${pkg.name} ${pkg.version}`;
@@ -19,14 +21,12 @@ export default class PodletClientFallbackResolver {
19
21
  #log;
20
22
  #metrics;
21
23
  #histogram;
22
- #httpAgent;
23
- #httpsAgent;
24
+ #http;
24
25
  constructor(options = {}) {
26
+ this.#http = options.http || new HTTP();
25
27
  const name = options.clientName;
26
28
  this.#log = abslog(options.logger);
27
29
  this.#metrics = new Metrics();
28
- this.#httpAgent = options.httpAgent;
29
- this.#httpsAgent = options.httpsAgent;
30
30
  this.#histogram = this.#metrics.histogram({
31
31
  name: 'podium_client_resolver_fallback_resolve',
32
32
  description: 'Time taken for success/failure of fallback request',
@@ -38,7 +38,7 @@ export default class PodletClientFallbackResolver {
38
38
  buckets: [0.001, 0.01, 0.1, 0.5, 1, 2, 10],
39
39
  });
40
40
 
41
- this.#metrics.on('error', error => {
41
+ this.#metrics.on('error', (error) => {
42
42
  this.#log.error(
43
43
  'Error emitted by metric stream in @podium/client module',
44
44
  error,
@@ -50,113 +50,103 @@ export default class PodletClientFallbackResolver {
50
50
  return this.#metrics;
51
51
  }
52
52
 
53
- resolve(outgoing) {
54
- return new Promise(resolve => {
55
- if (outgoing.status === 'cached') {
56
- resolve(outgoing);
57
- return;
58
- }
53
+ async resolve(outgoing) {
54
+ if (outgoing.status === 'cached') {
55
+ return outgoing;
56
+ }
57
+
58
+ // Manifest has no fallback, fetching of manifest likely failed.
59
+ // Its not possible to fetch anything
60
+ // Do not set fallback so we can serve any previous fallback we might have
61
+ if (outgoing.manifest.fallback === undefined) {
62
+ return outgoing;
63
+ }
64
+
65
+ // If manifest fallback is empty, there is no fallback content to fetch
66
+ // Set fallback to empty string
67
+ if (outgoing.manifest.fallback === '') {
68
+ this.#log.debug(
69
+ `no fallback defined in manifest - resource: ${outgoing.name}`,
70
+ );
71
+ outgoing.fallback = '';
72
+ return outgoing;
73
+ }
74
+
75
+ // The manifest fallback holds a URI, fetch its content
76
+ const headers = {
77
+ 'User-Agent': UA_STRING,
78
+ };
79
+
80
+ const reqOptions = {
81
+ rejectUnauthorized: outgoing.rejectUnauthorized,
82
+ timeout: outgoing.timeout,
83
+ method: 'GET',
84
+ headers,
85
+ };
86
+
87
+ const timer = this.#histogram.timer({
88
+ labels: {
89
+ podlet: outgoing.name,
90
+ },
91
+ });
59
92
 
60
- // Manifest has no fallback, fetching of manifest likely failed.
61
- // Its not possible to fetch anything
62
- // Do not set fallback so we can serve any previous fallback we might have
63
- if (outgoing.manifest.fallback === undefined) {
64
- resolve(outgoing);
65
- return;
66
- }
93
+ try {
94
+ this.#log.debug(
95
+ `start reading fallback content from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
96
+ );
97
+ const { statusCode, body } = await this.#http.request(
98
+ outgoing.fallbackUri,
99
+ reqOptions,
100
+ );
101
+
102
+ // Remote responds but with an http error code
103
+ const resError = statusCode !== 200;
104
+ if (resError) {
105
+ timer({
106
+ labels: {
107
+ status: 'failure',
108
+ },
109
+ });
67
110
 
68
- // If manifest fallback is empty, there is no fallback content to fetch
69
- // Set fallback to empty string
70
- if (outgoing.manifest.fallback === '') {
71
- this.#log.debug(
72
- `no fallback defined in manifest - resource: ${outgoing.name}`,
111
+ this.#log.warn(
112
+ `remote resource responded with non 200 http status code for fallback content - code: ${statusCode} - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
73
113
  );
114
+
74
115
  outgoing.fallback = '';
75
- resolve(outgoing);
76
- return;
116
+ return outgoing;
77
117
  }
78
118
 
79
- // The manifest fallback holds a URI, fetch its content
80
- const headers = {
81
- 'User-Agent': UA_STRING,
82
- };
83
-
84
- const reqOptions = {
85
- rejectUnauthorized: outgoing.rejectUnauthorized,
86
- timeout: outgoing.timeout,
87
- method: 'GET',
88
- agent: outgoing.fallbackUri.startsWith('https://')
89
- ? this.#httpsAgent
90
- : this.#httpAgent,
91
- uri: outgoing.fallbackUri,
92
- headers,
93
- };
94
-
95
- const timer = this.#histogram.timer({
119
+ // Response is OK. Store response body as fallback html for caching
120
+ timer({
96
121
  labels: {
97
- podlet: outgoing.name,
122
+ status: 'success',
98
123
  },
99
124
  });
100
125
 
101
- request(reqOptions, (error, res, body) => {
102
- this.#log.debug(
103
- `start reading fallback content from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
104
- );
126
+ // Set fallback to the fetched content
127
+ outgoing.fallback = await body.text();
105
128
 
106
- // Network error
107
- if (error) {
108
- timer({
109
- labels: {
110
- status: 'failure',
111
- },
112
- });
113
-
114
- this.#log.warn(
115
- `could not create network connection to remote resource for fallback content - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
116
- );
117
-
118
- outgoing.fallback = '';
119
- resolve(outgoing);
120
- return;
121
- }
122
-
123
- // Remote responds but with an http error code
124
- const resError = res.statusCode !== 200;
125
- if (resError) {
126
- timer({
127
- labels: {
128
- status: 'failure',
129
- },
130
- });
131
-
132
- this.#log.warn(
133
- `remote resource responded with non 200 http status code for fallback content - code: ${res.statusCode} - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
134
- );
135
-
136
- outgoing.fallback = '';
137
- resolve(outgoing);
138
- return;
139
- }
140
-
141
- // Response is OK. Store response body as fallback html for caching
142
- timer({
143
- labels: {
144
- status: 'success',
145
- },
146
- });
129
+ this.#log.debug(
130
+ `successfully read fallback from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
131
+ );
132
+ return outgoing;
133
+ } catch (error) {
134
+ timer({
135
+ labels: {
136
+ status: 'failure',
137
+ },
138
+ });
147
139
 
148
- // Set fallback to the fetched content
149
- outgoing.fallback = body;
140
+ this.#log.warn(
141
+ `could not create network connection to remote resource for fallback content - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
142
+ );
150
143
 
151
- this.#log.debug(
152
- `successfully read fallback from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
153
- );
154
- resolve(outgoing);
155
- });
156
- });
144
+ outgoing.fallback = '';
145
+ return outgoing;
146
+ }
157
147
  }
158
148
 
159
149
  get [Symbol.toStringTag]() {
160
150
  return 'PodletClientFallbackResolver';
161
151
  }
162
- };
152
+ }
package/lib/resolver.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import Metrics from '@metrics/client';
2
2
  import abslog from 'abslog';
3
3
  import assert from 'assert';
4
+ import HTTP from './http.js';
4
5
  import Manifest from './resolver.manifest.js';
5
6
  import Fallback from './resolver.fallback.js';
6
7
  import Content from './resolver.content.js';
@@ -19,10 +20,11 @@ export default class PodletClientResolver {
19
20
  );
20
21
 
21
22
  const log = abslog(options.logger);
23
+ const http = new HTTP();
22
24
  this.#cache = new Cache(registry, options);
23
- this.#manifest = new Manifest(options);
24
- this.#fallback = new Fallback(options);
25
- this.#content = new Content(options);
25
+ this.#manifest = new Manifest({ ...options, http });
26
+ this.#fallback = new Fallback({ ...options, http });
27
+ this.#content = new Content({ ...options, http });
26
28
  this.#metrics = new Metrics();
27
29
 
28
30
  this.#metrics.on('error', error => {
@@ -1,19 +1,21 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
 
3
3
  import CachePolicy from 'http-cache-semantics';
4
- import {manifest as validateManifest } from'@podium/schemas';
5
- import request from 'request';
4
+ import { manifest as validateManifest } from '@podium/schemas';
6
5
  import Metrics from '@metrics/client';
7
6
  import abslog from 'abslog';
8
7
  import * as utils from '@podium/utils';
9
-
10
8
  import { join, dirname } from 'path';
11
9
  import { fileURLToPath } from 'url';
12
10
  import fs from 'fs';
11
+ import HTTP from './http.js';
13
12
 
14
13
  const currentDirectory = dirname(fileURLToPath(import.meta.url));
15
14
 
16
- const pkgJson = fs.readFileSync(join(currentDirectory, '../package.json'), 'utf-8');
15
+ const pkgJson = fs.readFileSync(
16
+ join(currentDirectory, '../package.json'),
17
+ 'utf-8',
18
+ );
17
19
  const pkg = JSON.parse(pkgJson);
18
20
 
19
21
  const UA_STRING = `${pkg.name} ${pkg.version}`;
@@ -21,15 +23,13 @@ const UA_STRING = `${pkg.name} ${pkg.version}`;
21
23
  export default class PodletClientManifestResolver {
22
24
  #log;
23
25
  #metrics;
24
- #httpAgent;
25
- #httpsAgent;
26
26
  #histogram;
27
+ #http;
27
28
  constructor(options = {}) {
29
+ this.#http = options.http || new HTTP();
28
30
  const name = options.clientName;
29
31
  this.#log = abslog(options.logger);
30
32
  this.#metrics = new Metrics();
31
- this.#httpAgent = options.httpAgent;
32
- this.#httpsAgent = options.httpsAgent;
33
33
  this.#histogram = this.#metrics.histogram({
34
34
  name: 'podium_client_resolver_manifest_resolve',
35
35
  description: 'Time taken for success/failure of manifest request',
@@ -41,7 +41,7 @@ export default class PodletClientManifestResolver {
41
41
  buckets: [0.001, 0.01, 0.1, 0.5, 1, 2, 10],
42
42
  });
43
43
 
44
- this.#metrics.on('error', error => {
44
+ this.#metrics.on('error', (error) => {
45
45
  this.#log.error(
46
46
  'Error emitted by metric stream in @podium/client module',
47
47
  error,
@@ -53,161 +53,188 @@ export default class PodletClientManifestResolver {
53
53
  return this.#metrics;
54
54
  }
55
55
 
56
- resolve(outgoing) {
57
- return new Promise(resolve => {
58
- if (outgoing.status === 'cached') {
59
- resolve(outgoing);
60
- return;
61
- }
56
+ async resolve(outgoing) {
57
+ if (outgoing.status === 'cached') {
58
+ return outgoing;
59
+ }
62
60
 
63
- const headers = {
64
- 'User-Agent': UA_STRING,
65
- };
61
+ const headers = {
62
+ 'User-Agent': UA_STRING,
63
+ };
66
64
 
67
- const reqOptions = {
68
- rejectUnauthorized: outgoing.rejectUnauthorized,
69
- timeout: outgoing.timeout,
70
- method: 'GET',
71
- agent: outgoing.manifestUri.startsWith('https://')
72
- ? this.#httpsAgent
73
- : this.#httpAgent,
74
- json: true,
75
- uri: outgoing.manifestUri,
76
- headers,
77
- };
65
+ const reqOptions = {
66
+ rejectUnauthorized: outgoing.rejectUnauthorized,
67
+ timeout: outgoing.timeout,
68
+ method: 'GET',
69
+ json: true,
70
+ headers,
71
+ };
78
72
 
79
- const timer = this.#histogram.timer({
80
- labels: {
81
- podlet: outgoing.name,
82
- },
83
- });
73
+ const timer = this.#histogram.timer({
74
+ labels: {
75
+ podlet: outgoing.name,
76
+ },
77
+ });
84
78
 
85
- request(reqOptions, (error, res, body) => {
86
- this.#log.debug(
87
- `start reading manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
88
- );
79
+ this.#log.debug(
80
+ `start reading manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
81
+ );
89
82
 
90
- // Network error or JSON parsing error
91
- if (error) {
92
- timer({
93
- labels: {
94
- status: 'failure',
95
- },
96
- });
83
+ try {
84
+ const {
85
+ statusCode,
86
+ headers: hdrs,
87
+ body,
88
+ } = await this.#http.request(outgoing.manifestUri, reqOptions);
97
89
 
98
- this.#log.warn(
99
- `could not create network connection to remote manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
100
- );
101
- resolve(outgoing);
102
- return;
103
- }
104
-
105
- // Remote responds but with an http error code
106
- const resError = res.statusCode !== 200;
107
- if (resError) {
108
- timer({
109
- labels: {
110
- status: 'failure',
111
- },
112
- });
90
+ // Remote responds but with an http error code
91
+ const resError = statusCode !== 200;
92
+ if (resError) {
93
+ timer({
94
+ labels: {
95
+ status: 'failure',
96
+ },
97
+ });
113
98
 
114
- this.#log.warn(
115
- `remote resource responded with non 200 http status code for manifest - code: ${res.statusCode} - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
116
- );
117
- resolve(outgoing);
118
- return;
119
- }
120
-
121
- const manifest = validateManifest(body);
122
-
123
- // Manifest validation error
124
- if (manifest.error) {
125
- timer({
126
- labels: {
127
- status: 'failure',
128
- },
129
- });
99
+ this.#log.warn(
100
+ `remote resource responded with non 200 http status code for manifest - code: ${statusCode} - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
101
+ );
102
+ return outgoing;
103
+ }
130
104
 
131
- this.#log.warn(
132
- `could not parse manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
133
- );
134
- resolve(outgoing);
135
- return;
136
- }
105
+ const manifest = validateManifest(await body.json());
137
106
 
138
- // Manifest is valid, calculate maxAge for caching and continue
107
+ // Manifest validation error
108
+ if (manifest.error) {
139
109
  timer({
140
110
  labels: {
141
- status: 'success',
111
+ status: 'failure',
142
112
  },
143
113
  });
144
114
 
145
- const resValues = {
146
- status: res.statusCode,
147
- headers: res.headers,
148
- };
115
+ this.#log.warn(
116
+ `could not parse manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
117
+ );
118
+ return outgoing;
119
+ }
149
120
 
150
- const cachePolicy = new CachePolicy(reqOptions, resValues, {
151
- ignoreCargoCult: true,
152
- });
153
- const maxAge = cachePolicy.timeToLive();
154
- if (maxAge !== 0) {
155
- this.#log.debug(
156
- `remote resource has cache header which yelds a max age of ${maxAge}ms, using this as cache ttl - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
157
- );
158
- outgoing.maxAge = maxAge;
159
- }
160
-
161
- // Build absolute content and fallback URIs
162
- if (manifest.value.fallback !== '') {
163
- manifest.value.fallback = utils.uriRelativeToAbsolute(
164
- manifest.value.fallback,
165
- outgoing.manifestUri,
166
- );
167
- }
168
-
169
- manifest.value.content = utils.uriRelativeToAbsolute(
170
- manifest.value.content,
121
+ // Manifest is valid, calculate maxAge for caching and continue
122
+ timer({
123
+ labels: {
124
+ status: 'success',
125
+ },
126
+ });
127
+
128
+ const resValues = {
129
+ status: statusCode,
130
+ headers: hdrs,
131
+ };
132
+
133
+ const cachePolicy = new CachePolicy(reqOptions, resValues, {
134
+ ignoreCargoCult: true,
135
+ });
136
+ const maxAge = cachePolicy.timeToLive();
137
+ if (maxAge !== 0) {
138
+ this.#log.debug(
139
+ `remote resource has cache header which yelds a max age of ${maxAge}ms, using this as cache ttl - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
140
+ );
141
+ outgoing.maxAge = maxAge;
142
+ }
143
+
144
+ // Build absolute content and fallback URIs
145
+ if (manifest.value.fallback !== '') {
146
+ manifest.value.fallback = utils.uriRelativeToAbsolute(
147
+ manifest.value.fallback,
171
148
  outgoing.manifestUri,
172
149
  );
150
+ }
173
151
 
174
- // Construct css and js objects with absolute URIs
175
- manifest.value.css = manifest.value.css.map(obj => {
176
- obj.value = utils.uriRelativeToAbsolute(
177
- obj.value,
178
- outgoing.manifestUri,
179
- );
180
- return new utils.AssetCss(obj);
181
- });
152
+ manifest.value.content = utils.uriRelativeToAbsolute(
153
+ manifest.value.content,
154
+ outgoing.manifestUri,
155
+ );
182
156
 
183
- manifest.value.js = manifest.value.js.map(obj => {
184
- obj.value = utils.uriRelativeToAbsolute(
185
- obj.value,
186
- outgoing.manifestUri,
187
- );
188
- return new utils.AssetJs(obj);
189
- });
157
+ // Construct css and js objects with absolute URIs
158
+ manifest.value.css = manifest.value.css.map((obj) => {
159
+ obj.value = utils.uriRelativeToAbsolute(
160
+ obj.value,
161
+ outgoing.manifestUri,
162
+ );
163
+ return new utils.AssetCss(obj);
164
+ });
165
+
166
+ manifest.value.js = manifest.value.js.map((obj) => {
167
+ obj.value = utils.uriRelativeToAbsolute(
168
+ obj.value,
169
+ outgoing.manifestUri,
170
+ );
171
+ return new utils.AssetJs(obj);
172
+ });
173
+
174
+ /*
175
+ START: Proxy backwards compabillity check and handling
190
176
 
177
+ If .proxy is and Array, the podlet are of version 6 or newer. The Array is then an
178
+ Array of proxy Objects ({ name: 'foo', target: '/' }) which is the new structure
179
+ wanted from version 6 and onwards so leave this structure untouched.
180
+
181
+ If .proxy is an Object, the podlet are of version 5 or older where the key of the
182
+ Object is the key of the target. If so, convert the structure to the new structure
183
+ consisting of an Array of proxy Objects.
184
+
185
+ This check can be removed in version 7 (requires all podlets to be on version 6)
186
+ */
187
+
188
+ if (Array.isArray(manifest.value.proxy)) {
189
+ // Build absolute proxy URIs
190
+ manifest.value.proxy = manifest.value.proxy.map((item) => ({
191
+ target: utils.uriRelativeToAbsolute(
192
+ item.target,
193
+ outgoing.manifestUri,
194
+ ),
195
+ name: item.name,
196
+ }));
197
+ } else {
198
+ const proxies = [];
191
199
  // Build absolute proxy URIs
192
- Object.keys(manifest.value.proxy).forEach(key => {
193
- manifest.value.proxy[key] = utils.uriRelativeToAbsolute(
194
- manifest.value.proxy[key],
195
- outgoing.manifestUri,
196
- );
200
+ Object.keys(manifest.value.proxy).forEach((key) => {
201
+ proxies.push({
202
+ target: utils.uriRelativeToAbsolute(
203
+ manifest.value.proxy[key],
204
+ outgoing.manifestUri,
205
+ ),
206
+ name: key,
207
+ });
197
208
  });
209
+ manifest.value.proxy = proxies;
210
+ }
198
211
 
199
- outgoing.manifest = manifest.value;
200
- outgoing.status = 'fresh';
212
+ /*
213
+ END: Proxy backwards compabillity check and handling
214
+ */
201
215
 
202
- this.#log.debug(
203
- `successfully read manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
204
- );
205
- resolve(outgoing);
216
+ outgoing.manifest = manifest.value;
217
+ outgoing.status = 'fresh';
218
+
219
+ this.#log.debug(
220
+ `successfully read manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
221
+ );
222
+ return outgoing;
223
+ } catch (error) {
224
+ timer({
225
+ labels: {
226
+ status: 'failure',
227
+ },
206
228
  });
207
- });
229
+
230
+ this.#log.warn(
231
+ `could not create network connection to remote manifest - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
232
+ );
233
+ return outgoing;
234
+ }
208
235
  }
209
236
 
210
237
  get [Symbol.toStringTag]() {
211
238
  return 'PodletClientManifestResolver';
212
239
  }
213
- };
240
+ }