@podium/client 5.1.0-beta.1 → 5.1.1
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 +366 -291
- package/README.md +10 -8
- package/lib/client.js +79 -19
- package/lib/http-outgoing.js +106 -32
- package/lib/http.js +22 -17
- package/lib/resolver.cache.js +26 -6
- package/lib/resolver.content.js +54 -31
- package/lib/resolver.fallback.js +21 -2
- package/lib/resolver.js +40 -11
- package/lib/resolver.manifest.js +40 -16
- package/lib/resource.js +70 -12
- package/lib/response.js +17 -3
- package/lib/state.js +14 -1
- package/lib/utils.js +25 -27
- package/package.json +27 -24
- package/types/client.d.ts +120 -0
- package/types/http-outgoing.d.ts +177 -0
- package/types/http.d.ts +29 -0
- package/types/resolver.cache.d.ts +31 -0
- package/types/resolver.content.d.ts +29 -0
- package/types/resolver.d.ts +35 -0
- package/types/resolver.fallback.d.ts +29 -0
- package/types/resolver.manifest.d.ts +27 -0
- package/types/resource.d.ts +109 -0
- package/types/response.d.ts +47 -0
- package/types/state.d.ts +33 -0
- package/types/utils.d.ts +4 -0
- package/client.d.ts +0 -145
- package/dist/package.json +0 -3
package/lib/resolver.content.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-param-reassign */
|
|
2
1
|
import { pipeline } from 'stream';
|
|
3
2
|
import Metrics from '@metrics/client';
|
|
4
3
|
import abslog from 'abslog';
|
|
@@ -21,11 +20,24 @@ const pkg = JSON.parse(pkgJson);
|
|
|
21
20
|
|
|
22
21
|
const UA_STRING = `${pkg.name} ${pkg.version}`;
|
|
23
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {object} PodletClientContentResolverOptions
|
|
25
|
+
* @property {string} clientName
|
|
26
|
+
* @property {import('./http.js').default} [http]
|
|
27
|
+
* @property {import('abslog').AbstractLoggerOptions} [logger]
|
|
28
|
+
*/
|
|
29
|
+
|
|
24
30
|
export default class PodletClientContentResolver {
|
|
25
31
|
#log;
|
|
26
32
|
#metrics;
|
|
27
33
|
#histogram;
|
|
28
34
|
#http;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @constructor
|
|
38
|
+
* @param {PodletClientContentResolverOptions} options
|
|
39
|
+
*/
|
|
40
|
+
// @ts-expect-error Deliberate default empty options for better error messages
|
|
29
41
|
constructor(options = {}) {
|
|
30
42
|
this.#http = options.http || new HTTP();
|
|
31
43
|
const name = options.clientName;
|
|
@@ -54,6 +66,12 @@ export default class PodletClientContentResolver {
|
|
|
54
66
|
return this.#metrics;
|
|
55
67
|
}
|
|
56
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Resolves/fetches the podlet's content.
|
|
71
|
+
*
|
|
72
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
73
|
+
* @returns {Promise<import('./http-outgoing.js').default>}
|
|
74
|
+
*/
|
|
57
75
|
async resolve(outgoing) {
|
|
58
76
|
if (outgoing.kill && outgoing.throwable) {
|
|
59
77
|
this.#log.warn(
|
|
@@ -71,12 +89,12 @@ export default class PodletClientContentResolver {
|
|
|
71
89
|
outgoing.success = true;
|
|
72
90
|
outgoing.pushFallback();
|
|
73
91
|
outgoing.emit(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
'beforeStream',
|
|
93
|
+
new Response({
|
|
94
|
+
js: utils.filterAssets('fallback', outgoing.manifest.js),
|
|
95
|
+
css: utils.filterAssets('fallback', outgoing.manifest.css),
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
80
98
|
return outgoing;
|
|
81
99
|
}
|
|
82
100
|
|
|
@@ -94,12 +112,12 @@ export default class PodletClientContentResolver {
|
|
|
94
112
|
outgoing.success = true;
|
|
95
113
|
outgoing.pushFallback();
|
|
96
114
|
outgoing.emit(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
'beforeStream',
|
|
116
|
+
new Response({
|
|
117
|
+
js: utils.filterAssets('fallback', outgoing.manifest.js),
|
|
118
|
+
css: utils.filterAssets('fallback', outgoing.manifest.css),
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
103
121
|
return outgoing;
|
|
104
122
|
}
|
|
105
123
|
|
|
@@ -113,8 +131,9 @@ export default class PodletClientContentResolver {
|
|
|
113
131
|
const uri = putils.uriBuilder(
|
|
114
132
|
outgoing.reqOptions.pathname,
|
|
115
133
|
outgoing.contentUri,
|
|
116
|
-
)
|
|
134
|
+
);
|
|
117
135
|
|
|
136
|
+
/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
|
|
118
137
|
const reqOptions = {
|
|
119
138
|
rejectUnauthorized: outgoing.rejectUnauthorized,
|
|
120
139
|
bodyTimeout: outgoing.timeout,
|
|
@@ -183,11 +202,17 @@ export default class PodletClientContentResolver {
|
|
|
183
202
|
outgoing.success = true;
|
|
184
203
|
outgoing.pushFallback();
|
|
185
204
|
outgoing.emit(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
205
|
+
'beforeStream',
|
|
206
|
+
new Response({
|
|
207
|
+
js: utils.filterAssets(
|
|
208
|
+
'fallback',
|
|
209
|
+
outgoing.manifest.js,
|
|
210
|
+
),
|
|
211
|
+
css: utils.filterAssets(
|
|
212
|
+
'fallback',
|
|
213
|
+
outgoing.manifest.css,
|
|
214
|
+
),
|
|
215
|
+
}),
|
|
191
216
|
);
|
|
192
217
|
|
|
193
218
|
// Body must be consumed; https://github.com/nodejs/undici/issues/583#issuecomment-855384858
|
|
@@ -227,6 +252,7 @@ export default class PodletClientContentResolver {
|
|
|
227
252
|
if (outgoing.redirectable && statusCode >= 300) {
|
|
228
253
|
outgoing.redirect = {
|
|
229
254
|
statusCode,
|
|
255
|
+
// @ts-expect-error TODO: look into what happens if the podlet returns more than one location header
|
|
230
256
|
location: hdrs && hdrs.location,
|
|
231
257
|
};
|
|
232
258
|
}
|
|
@@ -235,8 +261,8 @@ export default class PodletClientContentResolver {
|
|
|
235
261
|
'beforeStream',
|
|
236
262
|
new Response({
|
|
237
263
|
headers: outgoing.headers,
|
|
238
|
-
js: utils.filterAssets(
|
|
239
|
-
css: utils.filterAssets(
|
|
264
|
+
js: utils.filterAssets('content', outgoing.manifest.js),
|
|
265
|
+
css: utils.filterAssets('content', outgoing.manifest.css),
|
|
240
266
|
redirect: outgoing.redirect,
|
|
241
267
|
}),
|
|
242
268
|
);
|
|
@@ -260,10 +286,7 @@ export default class PodletClientContentResolver {
|
|
|
260
286
|
this.#log.warn(
|
|
261
287
|
`could not create network connection to remote resource when trying to request content - resource: ${outgoing.name} - url: ${uri}`,
|
|
262
288
|
);
|
|
263
|
-
throw badGateway(
|
|
264
|
-
`Error reading content at ${uri}`,
|
|
265
|
-
error,
|
|
266
|
-
);
|
|
289
|
+
throw badGateway(`Error reading content at ${uri}`, error);
|
|
267
290
|
}
|
|
268
291
|
|
|
269
292
|
timer({
|
|
@@ -280,12 +303,12 @@ export default class PodletClientContentResolver {
|
|
|
280
303
|
|
|
281
304
|
outgoing.pushFallback();
|
|
282
305
|
outgoing.emit(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
306
|
+
'beforeStream',
|
|
307
|
+
new Response({
|
|
308
|
+
js: utils.filterAssets('fallback', outgoing.manifest.js),
|
|
309
|
+
css: utils.filterAssets('fallback', outgoing.manifest.css),
|
|
310
|
+
}),
|
|
311
|
+
);
|
|
289
312
|
|
|
290
313
|
return outgoing;
|
|
291
314
|
}
|
package/lib/resolver.fallback.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-param-reassign */
|
|
2
|
-
|
|
3
1
|
import abslog from 'abslog';
|
|
4
2
|
import Metrics from '@metrics/client';
|
|
5
3
|
import { join, dirname } from 'path';
|
|
@@ -17,11 +15,24 @@ const pkg = JSON.parse(pkgJson);
|
|
|
17
15
|
|
|
18
16
|
const UA_STRING = `${pkg.name} ${pkg.version}`;
|
|
19
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} PodletClientFallbackResolverOptions
|
|
20
|
+
* @property {string} clientName
|
|
21
|
+
* @property {import('./http.js').default} [http]
|
|
22
|
+
* @property {import('abslog').AbstractLoggerOptions} [logger]
|
|
23
|
+
*/
|
|
24
|
+
|
|
20
25
|
export default class PodletClientFallbackResolver {
|
|
21
26
|
#log;
|
|
22
27
|
#metrics;
|
|
23
28
|
#histogram;
|
|
24
29
|
#http;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @constructor
|
|
33
|
+
* @param {PodletClientFallbackResolverOptions} options
|
|
34
|
+
*/
|
|
35
|
+
// @ts-expect-error Deliberate default empty options for better error messages
|
|
25
36
|
constructor(options = {}) {
|
|
26
37
|
this.#http = options.http || new HTTP();
|
|
27
38
|
const name = options.clientName;
|
|
@@ -50,6 +61,12 @@ export default class PodletClientFallbackResolver {
|
|
|
50
61
|
return this.#metrics;
|
|
51
62
|
}
|
|
52
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Resolves/fetches the podlet's fallback.
|
|
66
|
+
*
|
|
67
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
68
|
+
* @returns {Promise<import('./http-outgoing.js').default>}
|
|
69
|
+
*/
|
|
53
70
|
async resolve(outgoing) {
|
|
54
71
|
if (outgoing.status === 'cached') {
|
|
55
72
|
return outgoing;
|
|
@@ -77,6 +94,7 @@ export default class PodletClientFallbackResolver {
|
|
|
77
94
|
'User-Agent': UA_STRING,
|
|
78
95
|
};
|
|
79
96
|
|
|
97
|
+
/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
|
|
80
98
|
const reqOptions = {
|
|
81
99
|
rejectUnauthorized: outgoing.rejectUnauthorized,
|
|
82
100
|
timeout: outgoing.timeout,
|
|
@@ -133,6 +151,7 @@ export default class PodletClientFallbackResolver {
|
|
|
133
151
|
`successfully read fallback from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
|
|
134
152
|
);
|
|
135
153
|
return outgoing;
|
|
154
|
+
// eslint-disable-next-line no-unused-vars
|
|
136
155
|
} catch (error) {
|
|
137
156
|
timer({
|
|
138
157
|
labels: {
|
package/lib/resolver.js
CHANGED
|
@@ -7,12 +7,25 @@ import Fallback from './resolver.fallback.js';
|
|
|
7
7
|
import Content from './resolver.content.js';
|
|
8
8
|
import Cache from './resolver.cache.js';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} PodletClientResolverOptions
|
|
12
|
+
* @property {string} clientName
|
|
13
|
+
* @property {import('abslog').AbstractLoggerOptions} [logger]
|
|
14
|
+
*/
|
|
15
|
+
|
|
10
16
|
export default class PodletClientResolver {
|
|
11
17
|
#cache;
|
|
12
18
|
#manifest;
|
|
13
19
|
#fallback;
|
|
14
20
|
#content;
|
|
15
21
|
#metrics;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @constructor
|
|
25
|
+
* @param {import('ttl-mem-cache').default} registry
|
|
26
|
+
* @param {PodletClientResolverOptions} options
|
|
27
|
+
*/
|
|
28
|
+
// @ts-expect-error Deliberate default empty options for better error messages
|
|
16
29
|
constructor(registry, options = {}) {
|
|
17
30
|
assert(
|
|
18
31
|
registry,
|
|
@@ -22,12 +35,16 @@ export default class PodletClientResolver {
|
|
|
22
35
|
const log = abslog(options.logger);
|
|
23
36
|
const http = new HTTP();
|
|
24
37
|
this.#cache = new Cache(registry, options);
|
|
25
|
-
this.#manifest = new Manifest({
|
|
38
|
+
this.#manifest = new Manifest({
|
|
39
|
+
clientName: options.clientName,
|
|
40
|
+
logger: options.logger,
|
|
41
|
+
http,
|
|
42
|
+
});
|
|
26
43
|
this.#fallback = new Fallback({ ...options, http });
|
|
27
44
|
this.#content = new Content({ ...options, http });
|
|
28
45
|
this.#metrics = new Metrics();
|
|
29
46
|
|
|
30
|
-
this.#metrics.on('error', error => {
|
|
47
|
+
this.#metrics.on('error', (error) => {
|
|
31
48
|
log.error(
|
|
32
49
|
'Error emitted by metric stream in @podium/client module',
|
|
33
50
|
error,
|
|
@@ -43,14 +60,20 @@ export default class PodletClientResolver {
|
|
|
43
60
|
return this.#metrics;
|
|
44
61
|
}
|
|
45
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the podlet's manifest, fallback and content
|
|
65
|
+
*
|
|
66
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
67
|
+
* @returns {Promise<import('./http-outgoing.js').default>}
|
|
68
|
+
*/
|
|
46
69
|
resolve(outgoing) {
|
|
47
70
|
return this.#cache
|
|
48
71
|
.load(outgoing)
|
|
49
|
-
.then(obj => this.#manifest.resolve(obj))
|
|
50
|
-
.then(obj => this.#fallback.resolve(obj))
|
|
51
|
-
.then(obj => this.#content.resolve(obj))
|
|
52
|
-
.then(obj => this.#cache.save(obj))
|
|
53
|
-
.then(obj => {
|
|
72
|
+
.then((obj) => this.#manifest.resolve(obj))
|
|
73
|
+
.then((obj) => this.#fallback.resolve(obj))
|
|
74
|
+
.then((obj) => this.#content.resolve(obj))
|
|
75
|
+
.then((obj) => this.#cache.save(obj))
|
|
76
|
+
.then((obj) => {
|
|
54
77
|
if (obj.success) {
|
|
55
78
|
return obj;
|
|
56
79
|
}
|
|
@@ -58,15 +81,21 @@ export default class PodletClientResolver {
|
|
|
58
81
|
});
|
|
59
82
|
}
|
|
60
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Refresh the podlet's cached manifest and fallback
|
|
86
|
+
*
|
|
87
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
88
|
+
* @returns {Promise<boolean>} `true` if successful
|
|
89
|
+
*/
|
|
61
90
|
refresh(outgoing) {
|
|
62
91
|
return this.#manifest
|
|
63
92
|
.resolve(outgoing)
|
|
64
|
-
.then(obj => this.#fallback.resolve(obj))
|
|
65
|
-
.then(obj => this.#cache.save(obj))
|
|
66
|
-
.then(obj => !!obj.manifest.name);
|
|
93
|
+
.then((obj) => this.#fallback.resolve(obj))
|
|
94
|
+
.then((obj) => this.#cache.save(obj))
|
|
95
|
+
.then((obj) => !!obj.manifest.name);
|
|
67
96
|
}
|
|
68
97
|
|
|
69
98
|
get [Symbol.toStringTag]() {
|
|
70
99
|
return 'PodletClientResolver';
|
|
71
100
|
}
|
|
72
|
-
}
|
|
101
|
+
}
|
package/lib/resolver.manifest.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-param-reassign */
|
|
2
|
-
|
|
3
1
|
import CachePolicy from 'http-cache-semantics';
|
|
4
2
|
import { manifest as validateManifest } from '@podium/schemas';
|
|
5
3
|
import Metrics from '@metrics/client';
|
|
@@ -20,11 +18,24 @@ const pkg = JSON.parse(pkgJson);
|
|
|
20
18
|
|
|
21
19
|
const UA_STRING = `${pkg.name} ${pkg.version}`;
|
|
22
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {object} PodletClientManifestResolverOptions
|
|
23
|
+
* @property {string} clientName
|
|
24
|
+
* @property {import('abslog').AbstractLoggerOptions} [logger]
|
|
25
|
+
* @property {import('./http.js').default} [http]
|
|
26
|
+
*/
|
|
27
|
+
|
|
23
28
|
export default class PodletClientManifestResolver {
|
|
24
29
|
#log;
|
|
25
30
|
#metrics;
|
|
26
31
|
#histogram;
|
|
27
32
|
#http;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @constructor
|
|
36
|
+
* @param {PodletClientManifestResolverOptions} options
|
|
37
|
+
*/
|
|
38
|
+
// @ts-expect-error Deliberate default empty options for better error messages
|
|
28
39
|
constructor(options = {}) {
|
|
29
40
|
this.#http = options.http || new HTTP();
|
|
30
41
|
const name = options.clientName;
|
|
@@ -53,6 +64,10 @@ export default class PodletClientManifestResolver {
|
|
|
53
64
|
return this.#metrics;
|
|
54
65
|
}
|
|
55
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
69
|
+
* @returns {Promise<import('./http-outgoing.js').default>}
|
|
70
|
+
*/
|
|
56
71
|
async resolve(outgoing) {
|
|
57
72
|
if (outgoing.status === 'cached') {
|
|
58
73
|
return outgoing;
|
|
@@ -62,6 +77,7 @@ export default class PodletClientManifestResolver {
|
|
|
62
77
|
'User-Agent': UA_STRING,
|
|
63
78
|
};
|
|
64
79
|
|
|
80
|
+
/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
|
|
65
81
|
const reqOptions = {
|
|
66
82
|
rejectUnauthorized: outgoing.rejectUnauthorized,
|
|
67
83
|
timeout: outgoing.timeout,
|
|
@@ -81,11 +97,11 @@ export default class PodletClientManifestResolver {
|
|
|
81
97
|
);
|
|
82
98
|
|
|
83
99
|
try {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
const response = await this.#http.request(
|
|
101
|
+
outgoing.manifestUri,
|
|
102
|
+
reqOptions,
|
|
103
|
+
);
|
|
104
|
+
const { statusCode, headers: hdrs, body } = response;
|
|
89
105
|
|
|
90
106
|
// Remote responds but with an http error code
|
|
91
107
|
const resError = statusCode !== 200;
|
|
@@ -105,7 +121,11 @@ export default class PodletClientManifestResolver {
|
|
|
105
121
|
return outgoing;
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
const manifest = validateManifest(
|
|
124
|
+
const manifest = validateManifest(
|
|
125
|
+
/** @type {import("@podium/schemas").PodletManifestSchema} */ (
|
|
126
|
+
await body.json()
|
|
127
|
+
),
|
|
128
|
+
);
|
|
109
129
|
|
|
110
130
|
// Manifest validation error
|
|
111
131
|
if (manifest.error) {
|
|
@@ -158,6 +178,7 @@ export default class PodletClientManifestResolver {
|
|
|
158
178
|
);
|
|
159
179
|
|
|
160
180
|
// Construct css and js objects with absolute URIs
|
|
181
|
+
// @ts-expect-error We assign here what will end up as PodletManifest as defined in http-outgoing.js
|
|
161
182
|
manifest.value.css = manifest.value.css.map((obj) => {
|
|
162
183
|
obj.value = utils.uriRelativeToAbsolute(
|
|
163
184
|
obj.value,
|
|
@@ -166,6 +187,7 @@ export default class PodletClientManifestResolver {
|
|
|
166
187
|
return new utils.AssetCss(obj);
|
|
167
188
|
});
|
|
168
189
|
|
|
190
|
+
// @ts-expect-error We assign here what will end up as PodletManifest as defined in http-outgoing.js
|
|
169
191
|
manifest.value.js = manifest.value.js.map((obj) => {
|
|
170
192
|
obj.value = utils.uriRelativeToAbsolute(
|
|
171
193
|
obj.value,
|
|
@@ -179,9 +201,9 @@ export default class PodletClientManifestResolver {
|
|
|
179
201
|
|
|
180
202
|
If .proxy is and Array, the podlet are of version 6 or newer. The Array is then an
|
|
181
203
|
Array of proxy Objects ({ name: 'foo', target: '/' }) which is the new structure
|
|
182
|
-
wanted from version 6 and onwards so leave this structure untouched.
|
|
204
|
+
wanted from version 6 and onwards so leave this structure untouched.
|
|
183
205
|
|
|
184
|
-
If .proxy is an Object, the podlet are of version 5 or older where the key of the
|
|
206
|
+
If .proxy is an Object, the podlet are of version 5 or older where the key of the
|
|
185
207
|
Object is the key of the target. If so, convert the structure to the new structure
|
|
186
208
|
consisting of an Array of proxy Objects.
|
|
187
209
|
|
|
@@ -191,12 +213,12 @@ export default class PodletClientManifestResolver {
|
|
|
191
213
|
if (Array.isArray(manifest.value.proxy)) {
|
|
192
214
|
// Build absolute proxy URIs
|
|
193
215
|
manifest.value.proxy = manifest.value.proxy.map((item) => ({
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
216
|
+
target: utils.uriRelativeToAbsolute(
|
|
217
|
+
item.target,
|
|
218
|
+
outgoing.manifestUri,
|
|
219
|
+
),
|
|
220
|
+
name: item.name,
|
|
221
|
+
}));
|
|
200
222
|
} else {
|
|
201
223
|
const proxies = [];
|
|
202
224
|
// Build absolute proxy URIs
|
|
@@ -216,6 +238,7 @@ export default class PodletClientManifestResolver {
|
|
|
216
238
|
END: Proxy backwards compabillity check and handling
|
|
217
239
|
*/
|
|
218
240
|
|
|
241
|
+
// @ts-expect-error We map to AssetCss and AssetJs above
|
|
219
242
|
outgoing.manifest = manifest.value;
|
|
220
243
|
outgoing.status = 'fresh';
|
|
221
244
|
|
|
@@ -223,6 +246,7 @@ export default class PodletClientManifestResolver {
|
|
|
223
246
|
`successfully read manifest from remote resource - resource: ${outgoing.name} - url: ${outgoing.manifestUri}`,
|
|
224
247
|
);
|
|
225
248
|
return outgoing;
|
|
249
|
+
// eslint-disable-next-line no-unused-vars
|
|
226
250
|
} catch (error) {
|
|
227
251
|
timer({
|
|
228
252
|
labels: {
|
package/lib/resource.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-param-reassign */
|
|
2
|
-
|
|
3
1
|
import Metrics from '@metrics/client';
|
|
4
2
|
import abslog from 'abslog';
|
|
5
3
|
import assert from 'assert';
|
|
@@ -11,11 +9,42 @@ import * as utils from './utils.js';
|
|
|
11
9
|
|
|
12
10
|
const inspect = Symbol.for('nodejs.util.inspect.custom');
|
|
13
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} RequestFilterOptions
|
|
14
|
+
* @property {string[]} [deviceType] List of values for the `x-podium-device-type` HTTP request header.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} PodiumClientResourceOptions
|
|
19
|
+
* @property {import('abslog').AbstractLoggerOptions} [logger]
|
|
20
|
+
* @property {string} clientName
|
|
21
|
+
* @property {string} name
|
|
22
|
+
* @property {string} uri To the podlet's `manifest.json`
|
|
23
|
+
* @property {number} timeout In milliseconds
|
|
24
|
+
* @property {number} maxAge
|
|
25
|
+
* @property {number} [retries]
|
|
26
|
+
* @property {boolean} [throwable]
|
|
27
|
+
* @property {boolean} [redirectable]
|
|
28
|
+
* @property {boolean} [rejectUnauthorized]
|
|
29
|
+
* @property {import('http').Agent} [httpAgent]
|
|
30
|
+
* @property {import('https').Agent} [httpsAgent]
|
|
31
|
+
* @property {RequestFilterOptions} [excludeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
|
|
32
|
+
* @property {RequestFilterOptions} [includeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
|
|
33
|
+
*/
|
|
34
|
+
|
|
14
35
|
export default class PodiumClientResource {
|
|
15
36
|
#resolver;
|
|
16
37
|
#options;
|
|
17
38
|
#metrics;
|
|
18
39
|
#state;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @constructor
|
|
43
|
+
* @param {import('ttl-mem-cache').default} registry
|
|
44
|
+
* @param {import('./state.js').default} state
|
|
45
|
+
* @param {PodiumClientResourceOptions} options
|
|
46
|
+
*/
|
|
47
|
+
// @ts-expect-error Deliberate for better error messages
|
|
19
48
|
constructor(registry, state, options = {}) {
|
|
20
49
|
assert(
|
|
21
50
|
registry,
|
|
@@ -56,7 +85,22 @@ export default class PodiumClientResource {
|
|
|
56
85
|
return this.#options.uri;
|
|
57
86
|
}
|
|
58
87
|
|
|
59
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Fetch the podlet's content, or fallback if the podlet is unavailable.
|
|
90
|
+
* The podlet response includes references to its CSS and JS assets which should be included in the final HTML document.
|
|
91
|
+
*
|
|
92
|
+
* @param {import('@podium/utils').HttpIncoming} incoming Instance of HttpIncoming
|
|
93
|
+
* @param {import('./http-outgoing.js').PodiumClientResourceOptions} [reqOptions={}] Optional parameters to the HTTP request, such as query parameters or HTTP request headers.
|
|
94
|
+
* @returns {Promise<import('./response.js').default>}
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```js
|
|
98
|
+
* const incoming = res.locals.podium; // Express server example
|
|
99
|
+
* const header = await headerPodlet.fetch(incoming);
|
|
100
|
+
* incoming.podlets = [header]; // Register the podlet's JS and CSS assets with the layout's HTML template
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
async fetch(incoming, reqOptions = {}) {
|
|
60
104
|
if (!utils.validateIncoming(incoming))
|
|
61
105
|
throw new TypeError(
|
|
62
106
|
'you must pass an instance of "HttpIncoming" as the first argument to the .fetch() method',
|
|
@@ -67,21 +111,21 @@ export default class PodiumClientResource {
|
|
|
67
111
|
/**
|
|
68
112
|
* @type {string[] | undefined}
|
|
69
113
|
*/
|
|
70
|
-
const
|
|
71
|
-
if (Array.isArray(
|
|
114
|
+
const excludedDeviceTypes = this.#options.excludeBy.deviceType;
|
|
115
|
+
if (Array.isArray(excludedDeviceTypes)) {
|
|
72
116
|
const deviceTypeHeader =
|
|
73
117
|
incoming.request.headers['x-podium-device-type'];
|
|
74
118
|
|
|
75
|
-
for (let i = 0; i <
|
|
119
|
+
for (let i = 0; i < excludedDeviceTypes.length; i += 1) {
|
|
76
120
|
const shouldSkip =
|
|
77
|
-
|
|
121
|
+
excludedDeviceTypes[i] === deviceTypeHeader;
|
|
78
122
|
if (shouldSkip) {
|
|
79
123
|
return new Response({
|
|
80
124
|
headers: {},
|
|
81
125
|
content: '',
|
|
82
126
|
css: [],
|
|
83
127
|
js: [],
|
|
84
|
-
redirect:
|
|
128
|
+
redirect: null,
|
|
85
129
|
});
|
|
86
130
|
}
|
|
87
131
|
}
|
|
@@ -107,7 +151,7 @@ export default class PodiumClientResource {
|
|
|
107
151
|
content: '',
|
|
108
152
|
css: [],
|
|
109
153
|
js: [],
|
|
110
|
-
redirect:
|
|
154
|
+
redirect: null,
|
|
111
155
|
});
|
|
112
156
|
}
|
|
113
157
|
}
|
|
@@ -119,7 +163,7 @@ export default class PodiumClientResource {
|
|
|
119
163
|
await this.#resolver.resolve(outgoing);
|
|
120
164
|
|
|
121
165
|
const chunks = [];
|
|
122
|
-
|
|
166
|
+
|
|
123
167
|
for await (const chunk of outgoing) {
|
|
124
168
|
chunks.push(chunk);
|
|
125
169
|
}
|
|
@@ -143,7 +187,14 @@ export default class PodiumClientResource {
|
|
|
143
187
|
});
|
|
144
188
|
}
|
|
145
189
|
|
|
146
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Stream the podlet's content, or fallback if the podlet is unavailable.
|
|
192
|
+
*
|
|
193
|
+
* @param {import('@podium/utils').HttpIncoming} incoming
|
|
194
|
+
* @param {import('./http-outgoing.js').PodiumClientResourceOptions} [reqOptions={}]
|
|
195
|
+
* @returns {import('./http-outgoing.js').default}
|
|
196
|
+
*/
|
|
197
|
+
stream(incoming, reqOptions = {}) {
|
|
147
198
|
if (!utils.validateIncoming(incoming))
|
|
148
199
|
throw new TypeError(
|
|
149
200
|
'you must pass an instance of "HttpIncoming" as the first argument to the .stream() method',
|
|
@@ -154,7 +205,14 @@ export default class PodiumClientResource {
|
|
|
154
205
|
return outgoing;
|
|
155
206
|
}
|
|
156
207
|
|
|
157
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Refresh the podlet's manifest and fallback in the cache.
|
|
210
|
+
*
|
|
211
|
+
* @param {import('@podium/utils').HttpIncoming} [incoming]
|
|
212
|
+
* @param {import('./http-outgoing.js').PodiumClientResourceOptions} [reqOptions={}]
|
|
213
|
+
* @returns {Promise<boolean>} `true` if succesful
|
|
214
|
+
*/
|
|
215
|
+
refresh(incoming, reqOptions = {}) {
|
|
158
216
|
const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming);
|
|
159
217
|
this.#state.setInitializingState();
|
|
160
218
|
return this.#resolver.refresh(outgoing).then((obj) => obj);
|
package/lib/response.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
const inspect = Symbol.for('nodejs.util.inspect.custom');
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {object} PodiumClientResponseOptions
|
|
5
|
+
* @property {string} [content]
|
|
6
|
+
* @property {object} [headers]
|
|
7
|
+
* @property {Array<import('@podium/utils').AssetJs>} [js]
|
|
8
|
+
* @property {Array<import('@podium/utils').AssetCss>} [css]
|
|
9
|
+
* @property {import('./http-outgoing.js').PodiumRedirect | null} [redirect]
|
|
10
|
+
*/
|
|
11
|
+
|
|
3
12
|
export default class PodiumClientResponse {
|
|
4
13
|
#redirect;
|
|
5
14
|
#content;
|
|
6
15
|
#headers;
|
|
7
16
|
#css;
|
|
8
17
|
#js;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @constructor
|
|
21
|
+
* @param {PodiumClientResponseOptions} options
|
|
22
|
+
*/
|
|
9
23
|
constructor({
|
|
10
24
|
content = '',
|
|
11
25
|
headers = {},
|
|
@@ -45,8 +59,8 @@ export default class PodiumClientResponse {
|
|
|
45
59
|
redirect: this.redirect,
|
|
46
60
|
content: this.content,
|
|
47
61
|
headers: this.headers,
|
|
48
|
-
css: this.css,
|
|
49
|
-
js: this.js,
|
|
62
|
+
css: this.css.map((a) => a.toJSON()),
|
|
63
|
+
js: this.js.map((a) => a.toJSON()),
|
|
50
64
|
};
|
|
51
65
|
}
|
|
52
66
|
|
|
@@ -71,4 +85,4 @@ export default class PodiumClientResponse {
|
|
|
71
85
|
get [Symbol.toStringTag]() {
|
|
72
86
|
return 'PodiumClientResponse';
|
|
73
87
|
}
|
|
74
|
-
}
|
|
88
|
+
}
|
package/lib/state.js
CHANGED
|
@@ -2,12 +2,25 @@ import EventEmitter from 'events';
|
|
|
2
2
|
|
|
3
3
|
const inspect = Symbol.for('nodejs.util.inspect.custom');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} PodiumClientStateOptions
|
|
7
|
+
* @property {number} [resolveThreshold=10000]
|
|
8
|
+
* @property {number} [resolveMax=240000]
|
|
9
|
+
*/
|
|
10
|
+
|
|
5
11
|
export default class PodiumClientState extends EventEmitter {
|
|
12
|
+
/** @type {NodeJS.Timeout | undefined} */
|
|
6
13
|
#thresholdTimer;
|
|
7
14
|
#threshold;
|
|
15
|
+
/** @type {NodeJS.Timeout | undefined} */
|
|
8
16
|
#maxTimer;
|
|
17
|
+
/** @type {"instantiated" | "stable" | "initializing" | "unhealthy" | "unstable"} */
|
|
9
18
|
#state;
|
|
10
19
|
#max;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {PodiumClientStateOptions} [options]
|
|
23
|
+
*/
|
|
11
24
|
constructor({
|
|
12
25
|
resolveThreshold = 10 * 1000,
|
|
13
26
|
resolveMax = 4 * 60 * 1000,
|
|
@@ -113,4 +126,4 @@ export default class PodiumClientState extends EventEmitter {
|
|
|
113
126
|
get [Symbol.toStringTag]() {
|
|
114
127
|
return 'PodiumClientState';
|
|
115
128
|
}
|
|
116
|
-
}
|
|
129
|
+
}
|