@podium/client 5.0.20 → 5.0.22
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 +15 -0
- package/lib/client.js +68 -17
- package/lib/http-outgoing.js +106 -30
- package/lib/http.js +21 -1
- package/lib/resolver.cache.js +26 -3
- package/lib/resolver.content.js +54 -30
- package/lib/resolver.fallback.js +20 -0
- package/lib/resolver.js +40 -11
- package/lib/resolver.manifest.js +39 -14
- package/lib/resource.js +78 -14
- package/lib/response.js +15 -1
- package/lib/state.js +14 -1
- package/lib/utils.js +25 -27
- package/package.json +10 -8
- package/types/client.d.ts +110 -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 +89 -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 -140
- package/dist/package.json +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [5.0.22](https://github.com/podium-lib/client/compare/v5.0.21...v5.0.22) (2024-05-15)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* generate type definitions before publish ([8189b01](https://github.com/podium-lib/client/commit/8189b01c54c733c760b02f2138803f9b64a6fee5))
|
|
7
|
+
|
|
8
|
+
## [5.0.21](https://github.com/podium-lib/client/compare/v5.0.20...v5.0.21) (2024-05-14)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* export same types as before ([0e3c65a](https://github.com/podium-lib/client/commit/0e3c65afc0fef7e6c53ad544dc80cb7f8dc8596b))
|
|
14
|
+
* generate types from JSDoc ([32cac7e](https://github.com/podium-lib/client/commit/32cac7e10a76494c0d2df602f41d42a67e8e7b6c))
|
|
15
|
+
|
|
1
16
|
## [5.0.20](https://github.com/podium-lib/client/compare/v5.0.19...v5.0.20) (2024-05-14)
|
|
2
17
|
|
|
3
18
|
|
package/lib/client.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
uriStrict as validateUriStrict,
|
|
4
|
+
name as validateName,
|
|
5
|
+
} from '@podium/schemas';
|
|
3
6
|
import Metrics from '@metrics/client';
|
|
4
7
|
import abslog from 'abslog';
|
|
5
8
|
import Cache from 'ttl-mem-cache';
|
|
6
9
|
import http from 'http';
|
|
7
10
|
import https from 'https';
|
|
8
|
-
|
|
9
11
|
import Resource from './resource.js';
|
|
10
12
|
import State from './state.js';
|
|
11
13
|
|
|
12
14
|
const inspect = Symbol.for('nodejs.util.inspect.custom');
|
|
13
|
-
|
|
14
15
|
const HTTP_AGENT_OPTIONS = {
|
|
15
16
|
keepAlive: true,
|
|
16
17
|
maxSockets: 10,
|
|
@@ -18,24 +19,49 @@ const HTTP_AGENT_OPTIONS = {
|
|
|
18
19
|
timeout: 60000,
|
|
19
20
|
keepAliveMsecs: 30000,
|
|
20
21
|
};
|
|
21
|
-
|
|
22
22
|
const HTTPS_AGENT_OPTIONS = {
|
|
23
23
|
...HTTP_AGENT_OPTIONS,
|
|
24
24
|
maxCachedSessions: 10,
|
|
25
25
|
};
|
|
26
|
-
|
|
27
26
|
const REJECT_UNAUTHORIZED = true;
|
|
28
|
-
|
|
29
27
|
const HTTP_AGENT = new http.Agent(HTTP_AGENT_OPTIONS);
|
|
30
|
-
|
|
31
28
|
const HTTPS_AGENT = new https.Agent(HTTPS_AGENT_OPTIONS);
|
|
32
|
-
|
|
33
29
|
const RETRIES = 4;
|
|
34
|
-
|
|
35
30
|
const TIMEOUT = 1000; // 1 seconds
|
|
36
|
-
|
|
37
31
|
const MAX_AGE = Infinity;
|
|
38
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {import('./resource.js').default} PodiumClientResource
|
|
35
|
+
* @typedef {import('./resource.js').PodiumClientResourceOptions} PodiumClientResourceOptions
|
|
36
|
+
* @typedef {import('./response.js').default} PodiumClientResponse
|
|
37
|
+
* @typedef {import('./http-outgoing.js').PodiumRedirect} PodiumRedirect
|
|
38
|
+
* @typedef {import('@podium/schemas').PodletManifestSchema} PodletManifest
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {object} PodiumClientOptions
|
|
43
|
+
* @property {string} name
|
|
44
|
+
* @property {import('abslog').AbstractLoggerOptions} [logger]
|
|
45
|
+
* @property {number} [retries=4]
|
|
46
|
+
* @property {number} [timeout=1000] In milliseconds
|
|
47
|
+
* @property {number} [maxAge=Infinity]
|
|
48
|
+
* @property {boolean} [rejectUnauthorized=true]
|
|
49
|
+
* @property {number} [resolveThreshold]
|
|
50
|
+
* @property {number} [resolveMax]
|
|
51
|
+
* @property {import('http').Agent} [httpAgent]
|
|
52
|
+
* @property {import('https').Agent} [httpsAgent]
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @typedef {object} RegisterOptions
|
|
57
|
+
* @property {string} name A unique name for the podlet
|
|
58
|
+
* @property {string} uri URL to the podlet's `manifest.json`
|
|
59
|
+
* @property {number} [retries=4] Number of retries before serving fallback
|
|
60
|
+
* @property {number} [timeout=1000] In milliseconds, the amount of time to wait before serving fallback.
|
|
61
|
+
* @property {boolean} [throwable=false] Set to `true` and surround `fetch` in `try/catch` to serve different content in case podlet is unavailable. Will not server fallback content.
|
|
62
|
+
* @property {boolean} [redirectable=false] Set to `true` to allow podlet to respond with a redirect. You need to look for the redirect response from the podlet and return a redirect response to the browser yourself.
|
|
63
|
+
*/
|
|
64
|
+
|
|
39
65
|
export default class PodiumClient extends EventEmitter {
|
|
40
66
|
#resources;
|
|
41
67
|
#registry;
|
|
@@ -43,6 +69,12 @@ export default class PodiumClient extends EventEmitter {
|
|
|
43
69
|
#histogram;
|
|
44
70
|
#options;
|
|
45
71
|
#state;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @constructor
|
|
75
|
+
* @param {PodiumClientOptions} options
|
|
76
|
+
*/
|
|
77
|
+
// @ts-expect-error Deliberate default empty options for better error messages
|
|
46
78
|
constructor(options = {}) {
|
|
47
79
|
super();
|
|
48
80
|
const log = abslog(options.logger);
|
|
@@ -69,7 +101,7 @@ export default class PodiumClient extends EventEmitter {
|
|
|
69
101
|
resolveThreshold: options.resolveThreshold,
|
|
70
102
|
resolveMax: options.resolveMax,
|
|
71
103
|
});
|
|
72
|
-
this.#state.on('state', state => {
|
|
104
|
+
this.#state.on('state', (state) => {
|
|
73
105
|
this.emit('state', state);
|
|
74
106
|
});
|
|
75
107
|
|
|
@@ -79,7 +111,7 @@ export default class PodiumClient extends EventEmitter {
|
|
|
79
111
|
changefeed: true,
|
|
80
112
|
ttl: options.maxAge,
|
|
81
113
|
});
|
|
82
|
-
this.#registry.on('error', error => {
|
|
114
|
+
this.#registry.on('error', (error) => {
|
|
83
115
|
log.error(
|
|
84
116
|
'Error emitted by the registry in @podium/client module',
|
|
85
117
|
error,
|
|
@@ -91,7 +123,7 @@ export default class PodiumClient extends EventEmitter {
|
|
|
91
123
|
});
|
|
92
124
|
|
|
93
125
|
this.#metrics = new Metrics();
|
|
94
|
-
this.#metrics.on('error', error => {
|
|
126
|
+
this.#metrics.on('error', (error) => {
|
|
95
127
|
log.error(
|
|
96
128
|
'Error emitted by metric stream in @podium/client module',
|
|
97
129
|
error,
|
|
@@ -99,7 +131,7 @@ export default class PodiumClient extends EventEmitter {
|
|
|
99
131
|
});
|
|
100
132
|
|
|
101
133
|
this[Symbol.iterator] = () => ({
|
|
102
|
-
items: Array.from(this.#resources).map(item => item[1]),
|
|
134
|
+
items: Array.from(this.#resources).map((item) => item[1]),
|
|
103
135
|
next: function next() {
|
|
104
136
|
return {
|
|
105
137
|
done: this.items.length === 0,
|
|
@@ -128,7 +160,21 @@ export default class PodiumClient extends EventEmitter {
|
|
|
128
160
|
return this.#state.status;
|
|
129
161
|
}
|
|
130
162
|
|
|
131
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Register a podlet so you can fetch its contents later with {@link PodiumClientResource.fetch}.
|
|
165
|
+
*
|
|
166
|
+
* @param {RegisterOptions} options
|
|
167
|
+
* @returns {PodiumClientResource}
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```js
|
|
171
|
+
* const headerPodlet = layout.client.register({
|
|
172
|
+
* name: 'header',
|
|
173
|
+
* uri: 'http://header/manifest.json',
|
|
174
|
+
* });
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
register(options) {
|
|
132
178
|
if (validateName(options.name).error)
|
|
133
179
|
throw new Error(
|
|
134
180
|
`The value, "${options.name}", for the required argument "name" on the .register() method is not defined or not valid.`,
|
|
@@ -183,12 +229,17 @@ export default class PodiumClient extends EventEmitter {
|
|
|
183
229
|
return this.#registry.load(dump);
|
|
184
230
|
}
|
|
185
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Refreshes the cached podlet manifest for all {@link register}ed podlets.
|
|
234
|
+
*/
|
|
186
235
|
async refreshManifests() {
|
|
187
236
|
const end = this.#histogram.timer();
|
|
188
237
|
|
|
189
238
|
// Don't return this
|
|
190
239
|
await Promise.all(
|
|
191
|
-
Array.from(this.#resources).map(resource =>
|
|
240
|
+
Array.from(this.#resources).map((resource) =>
|
|
241
|
+
resource[1].refresh(),
|
|
242
|
+
),
|
|
192
243
|
);
|
|
193
244
|
|
|
194
245
|
end();
|
|
@@ -204,4 +255,4 @@ export default class PodiumClient extends EventEmitter {
|
|
|
204
255
|
get [Symbol.toStringTag]() {
|
|
205
256
|
return 'PodiumClient';
|
|
206
257
|
}
|
|
207
|
-
}
|
|
258
|
+
}
|
package/lib/http-outgoing.js
CHANGED
|
@@ -3,26 +3,84 @@
|
|
|
3
3
|
import { PassThrough } from 'stream';
|
|
4
4
|
import assert from 'assert';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} PodiumClientHttpOutgoingOptions
|
|
8
|
+
* @property {string} name
|
|
9
|
+
* @property {string} uri To the podlet's `manifest.json`
|
|
10
|
+
* @property {number} timeout In milliseconds
|
|
11
|
+
* @property {number} maxAge
|
|
12
|
+
* @property {number} [retries=4]
|
|
13
|
+
* @property {boolean} [throwable=false]
|
|
14
|
+
* @property {boolean} [redirectable=false]
|
|
15
|
+
* @property {boolean} [rejectUnauthorized=true]
|
|
16
|
+
* @property {import('http').Agent} [httpAgent]
|
|
17
|
+
* @property {import('https').Agent} [httpsAgent]
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} PodiumClientResourceOptions
|
|
22
|
+
* @property {string} [pathname]
|
|
23
|
+
* @property {import('http').IncomingHttpHeaders} [headers]
|
|
24
|
+
* @property {object} [query]
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} PodiumRedirect
|
|
29
|
+
* @property {number} statusCode;
|
|
30
|
+
* @property {string} location;
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {object} PodletProxySchema
|
|
35
|
+
* @property {string} target
|
|
36
|
+
* @property {string} name
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {object} PodletManifest Similar to the schema's manifest, but with instances of AssetCss and AssetJs from `@podium/utils` and default values.
|
|
41
|
+
* @property {string} name
|
|
42
|
+
* @property {string} version
|
|
43
|
+
* @property {string} content
|
|
44
|
+
* @property {string} fallback
|
|
45
|
+
* @property {Array<import('@podium/utils').AssetJs>} js
|
|
46
|
+
* @property {Array<import('@podium/utils').AssetCss>} css
|
|
47
|
+
* @property {Record<string, string> | Array<PodletProxySchema>} proxy
|
|
48
|
+
* @property {string} team
|
|
49
|
+
*/
|
|
50
|
+
|
|
6
51
|
export default class PodletClientHttpOutgoing extends PassThrough {
|
|
7
52
|
#rejectUnauthorized;
|
|
8
53
|
#killRecursions;
|
|
9
54
|
#killThreshold;
|
|
10
55
|
#redirectable;
|
|
11
56
|
#reqOptions;
|
|
12
|
-
#isFallback;
|
|
57
|
+
#isFallback = false;
|
|
13
58
|
#throwable;
|
|
59
|
+
/** @type {PodletManifest} */
|
|
14
60
|
#manifest;
|
|
15
61
|
#incoming;
|
|
16
|
-
|
|
62
|
+
/** @type {null | PodiumRedirect} */
|
|
63
|
+
#redirect = null;
|
|
17
64
|
#timeout;
|
|
18
65
|
#success;
|
|
19
66
|
#headers;
|
|
20
67
|
#maxAge;
|
|
68
|
+
/** @type {'empty' | 'fresh' | 'cached' | 'stale'} */
|
|
21
69
|
#status;
|
|
22
70
|
#name;
|
|
23
|
-
#uri;
|
|
24
|
-
|
|
25
|
-
|
|
71
|
+
#uri;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @constructor
|
|
75
|
+
* @param {PodiumClientHttpOutgoingOptions} options
|
|
76
|
+
* @param {PodiumClientResourceOptions} [reqOptions]
|
|
77
|
+
* @param {import('@podium/utils').HttpIncoming} [incoming]
|
|
78
|
+
*/
|
|
79
|
+
// @ts-expect-error Deliberate default empty options for better error messages
|
|
80
|
+
constructor(options = {}, reqOptions, incoming) {
|
|
81
|
+
super();
|
|
82
|
+
|
|
83
|
+
const {
|
|
26
84
|
rejectUnauthorized = true,
|
|
27
85
|
throwable = false,
|
|
28
86
|
redirectable = false,
|
|
@@ -31,11 +89,7 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
31
89
|
maxAge,
|
|
32
90
|
name = '',
|
|
33
91
|
uri,
|
|
34
|
-
} =
|
|
35
|
-
reqOptions,
|
|
36
|
-
incoming,
|
|
37
|
-
) {
|
|
38
|
-
super();
|
|
92
|
+
} = options;
|
|
39
93
|
|
|
40
94
|
assert(
|
|
41
95
|
uri,
|
|
@@ -45,7 +99,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
45
99
|
// If requests to https sites should reject on unsigned sertificates
|
|
46
100
|
this.#rejectUnauthorized = rejectUnauthorized;
|
|
47
101
|
|
|
48
|
-
// A HttpIncoming object
|
|
49
102
|
this.#incoming = incoming;
|
|
50
103
|
|
|
51
104
|
// Kill switch for breaking the recursive promise chain
|
|
@@ -67,6 +120,7 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
67
120
|
// Manifest which is either retrieved from the registry or
|
|
68
121
|
// remote podlet (in other words its not saved in registry yet)
|
|
69
122
|
this.#manifest = {
|
|
123
|
+
// @ts-expect-error Internal property
|
|
70
124
|
_fallback: '',
|
|
71
125
|
};
|
|
72
126
|
|
|
@@ -82,14 +136,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
82
136
|
// How long the manifest should be cached before refetched
|
|
83
137
|
this.#maxAge = maxAge;
|
|
84
138
|
|
|
85
|
-
// What status the manifest is in. This is used to tell what actions need to
|
|
86
|
-
// be performed throughout the resolving process to complete a request.
|
|
87
|
-
//
|
|
88
|
-
// The different statuses can be:
|
|
89
|
-
// "empty" - there is no manifest available - we are in process of fetching it
|
|
90
|
-
// "fresh" - the manifest has been fetched but is not stored in cache yet
|
|
91
|
-
// "cached" - the manifest was retrieved from cache
|
|
92
|
-
// "stale" - the manifest is outdated, a new manifest needs to be fetched
|
|
93
139
|
this.#status = 'empty';
|
|
94
140
|
|
|
95
141
|
// Name of the resource (name given to client)
|
|
@@ -100,13 +146,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
100
146
|
|
|
101
147
|
// Whether the user can handle redirects manually.
|
|
102
148
|
this.#redirectable = redirectable;
|
|
103
|
-
|
|
104
|
-
// When redirectable is true, this object should be populated with redirect information
|
|
105
|
-
// such that a user can perform manual redirection
|
|
106
|
-
this.#redirect = null;
|
|
107
|
-
|
|
108
|
-
// When isfallback is true, content fetch has failed and fallback will be served instead
|
|
109
|
-
this.#isFallback = false;
|
|
110
149
|
}
|
|
111
150
|
|
|
112
151
|
get rejectUnauthorized() {
|
|
@@ -130,10 +169,12 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
130
169
|
}
|
|
131
170
|
|
|
132
171
|
get fallback() {
|
|
172
|
+
// @ts-expect-error Internal property
|
|
133
173
|
return this.#manifest._fallback;
|
|
134
174
|
}
|
|
135
175
|
|
|
136
176
|
set fallback(value) {
|
|
177
|
+
// @ts-expect-error Internal property
|
|
137
178
|
this.#manifest._fallback = value;
|
|
138
179
|
}
|
|
139
180
|
|
|
@@ -169,6 +210,16 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
169
210
|
this.#maxAge = value;
|
|
170
211
|
}
|
|
171
212
|
|
|
213
|
+
/**
|
|
214
|
+
* What status the manifest is in. This is used to tell what actions need to
|
|
215
|
+
* be performed throughout the resolving process to complete a request.
|
|
216
|
+
*
|
|
217
|
+
* The different statuses can be:
|
|
218
|
+
* - `"empty"` - there is no manifest available - we are in process of fetching it
|
|
219
|
+
* - `"fresh"` - the manifest has been fetched but is not stored in cache yet
|
|
220
|
+
* - `"cached"` - the manifest was retrieved from cache
|
|
221
|
+
* - `"stale"` - the manifest is outdated, a new manifest needs to be fetched
|
|
222
|
+
*/
|
|
172
223
|
get status() {
|
|
173
224
|
return this.#status;
|
|
174
225
|
}
|
|
@@ -193,18 +244,33 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
193
244
|
return this.#manifest.content;
|
|
194
245
|
}
|
|
195
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Kill switch for breaking the recursive promise chain in case it is never able to completely resolve.
|
|
249
|
+
* This is true if the number of recursions matches the threshold.
|
|
250
|
+
*/
|
|
196
251
|
get kill() {
|
|
197
252
|
return this.#killRecursions === this.#killThreshold;
|
|
198
253
|
}
|
|
199
254
|
|
|
255
|
+
/**
|
|
256
|
+
* The number of recursions before the request should be {@link kill}ed
|
|
257
|
+
*/
|
|
200
258
|
get recursions() {
|
|
201
259
|
return this.#killRecursions;
|
|
202
260
|
}
|
|
203
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Set the number of recursions before the request should be {@link kill}ed
|
|
264
|
+
*/
|
|
204
265
|
set recursions(value) {
|
|
205
266
|
this.#killRecursions = value;
|
|
206
267
|
}
|
|
207
268
|
|
|
269
|
+
/**
|
|
270
|
+
* When {@link redirectable} is `true` this is populated with redirect information so you can send a redirect response to the browser from your layout.
|
|
271
|
+
*
|
|
272
|
+
* @see https://podium-lib.io/docs/layout/handling_redirects
|
|
273
|
+
*/
|
|
208
274
|
get redirect() {
|
|
209
275
|
return this.#redirect;
|
|
210
276
|
}
|
|
@@ -213,6 +279,11 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
213
279
|
this.#redirect = value;
|
|
214
280
|
}
|
|
215
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Whether the podlet can signal redirects to the layout.
|
|
284
|
+
*
|
|
285
|
+
* @see https://podium-lib.io/docs/layout/handling_redirects
|
|
286
|
+
*/
|
|
216
287
|
get redirectable() {
|
|
217
288
|
return this.#redirectable;
|
|
218
289
|
}
|
|
@@ -222,17 +293,22 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
222
293
|
}
|
|
223
294
|
|
|
224
295
|
/**
|
|
225
|
-
*
|
|
296
|
+
* True if the client has returned the podlet's fallback.
|
|
297
|
+
*
|
|
226
298
|
* @example
|
|
227
|
-
*
|
|
299
|
+
*
|
|
300
|
+
* ```js
|
|
228
301
|
* if (outgoing.isFallback) console.log("Fallback!");
|
|
229
302
|
* ```
|
|
303
|
+
*
|
|
304
|
+
* @see https://podium-lib.io/docs/podlet/fallbacks
|
|
230
305
|
*/
|
|
231
306
|
get isFallback() {
|
|
232
|
-
|
|
307
|
+
return this.#isFallback;
|
|
233
308
|
}
|
|
234
309
|
|
|
235
310
|
pushFallback() {
|
|
311
|
+
// @ts-expect-error Internal property
|
|
236
312
|
this.push(this.#manifest._fallback);
|
|
237
313
|
this.push(null);
|
|
238
314
|
this.#isFallback = true;
|
|
@@ -241,4 +317,4 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
241
317
|
get [Symbol.toStringTag]() {
|
|
242
318
|
return 'PodletClientHttpOutgoing';
|
|
243
319
|
}
|
|
244
|
-
}
|
|
320
|
+
}
|
package/lib/http.js
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { request } from 'undici';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {object} PodiumHttpClientRequestOptions
|
|
5
|
+
* @property {'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH'} method
|
|
6
|
+
* @property {boolean} [json]
|
|
7
|
+
* @property {boolean} [rejectUnauthorized]
|
|
8
|
+
* @property {boolean} [follow]
|
|
9
|
+
* @property {number} [timeout]
|
|
10
|
+
* @property {number} [bodyTimeout]
|
|
11
|
+
* @property {object} [query]
|
|
12
|
+
* @property {import('http').IncomingHttpHeaders} [headers]
|
|
13
|
+
*/
|
|
14
|
+
|
|
3
15
|
export default class HTTP {
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} url
|
|
18
|
+
* @param {PodiumHttpClientRequestOptions} options
|
|
19
|
+
* @returns {Promise<Pick<import('undici').Dispatcher.ResponseData, 'statusCode' | 'headers' | 'body'>>}
|
|
20
|
+
*/
|
|
4
21
|
async request(url, options) {
|
|
5
|
-
const { statusCode, headers, body } = await request(
|
|
22
|
+
const { statusCode, headers, body } = await request(
|
|
23
|
+
new URL(url),
|
|
24
|
+
options,
|
|
25
|
+
);
|
|
6
26
|
return { statusCode, headers, body };
|
|
7
27
|
}
|
|
8
28
|
}
|
package/lib/resolver.cache.js
CHANGED
|
@@ -5,9 +5,20 @@ import clonedeep from 'lodash.clonedeep';
|
|
|
5
5
|
import abslog from 'abslog';
|
|
6
6
|
import assert from 'assert';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} PodletClientCacheResolverOptions
|
|
10
|
+
* @property {import('abslog').AbstractLoggerOptions} [logger]
|
|
11
|
+
*/
|
|
12
|
+
|
|
8
13
|
export default class PodletClientCacheResolver {
|
|
9
14
|
#registry;
|
|
10
15
|
#log;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @constructor
|
|
19
|
+
* @param {import('ttl-mem-cache').default} registry
|
|
20
|
+
* @param {PodletClientCacheResolverOptions} options
|
|
21
|
+
*/
|
|
11
22
|
constructor(registry, options = {}) {
|
|
12
23
|
assert(
|
|
13
24
|
registry,
|
|
@@ -17,8 +28,14 @@ export default class PodletClientCacheResolver {
|
|
|
17
28
|
this.#log = abslog(options.logger);
|
|
18
29
|
}
|
|
19
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Loads the podlet's manifest from cache if not stale
|
|
33
|
+
*
|
|
34
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
35
|
+
* @returns {Promise<import('./http-outgoing.js').default>}
|
|
36
|
+
*/
|
|
20
37
|
load(outgoing) {
|
|
21
|
-
return new Promise(resolve => {
|
|
38
|
+
return new Promise((resolve) => {
|
|
22
39
|
if (outgoing.status !== 'stale') {
|
|
23
40
|
const cached = this.#registry.get(outgoing.name);
|
|
24
41
|
if (cached) {
|
|
@@ -33,8 +50,14 @@ export default class PodletClientCacheResolver {
|
|
|
33
50
|
});
|
|
34
51
|
}
|
|
35
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Saves the podlet's manifest to the cache
|
|
55
|
+
*
|
|
56
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
57
|
+
* @returns {Promise<import('./http-outgoing.js').default>}
|
|
58
|
+
*/
|
|
36
59
|
save(outgoing) {
|
|
37
|
-
return new Promise(resolve => {
|
|
60
|
+
return new Promise((resolve) => {
|
|
38
61
|
if (outgoing.status === 'fresh') {
|
|
39
62
|
this.#registry.set(
|
|
40
63
|
outgoing.name,
|
|
@@ -55,4 +78,4 @@ export default class PodletClientCacheResolver {
|
|
|
55
78
|
get [Symbol.toStringTag]() {
|
|
56
79
|
return 'PodletClientCacheResolver';
|
|
57
80
|
}
|
|
58
|
-
}
|
|
81
|
+
}
|
package/lib/resolver.content.js
CHANGED
|
@@ -21,11 +21,24 @@ const pkg = JSON.parse(pkgJson);
|
|
|
21
21
|
|
|
22
22
|
const UA_STRING = `${pkg.name} ${pkg.version}`;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} PodletClientContentResolverOptions
|
|
26
|
+
* @property {string} clientName
|
|
27
|
+
* @property {import('./http.js').default} [http]
|
|
28
|
+
* @property {import('abslog').AbstractLoggerOptions} [logger]
|
|
29
|
+
*/
|
|
30
|
+
|
|
24
31
|
export default class PodletClientContentResolver {
|
|
25
32
|
#log;
|
|
26
33
|
#metrics;
|
|
27
34
|
#histogram;
|
|
28
35
|
#http;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @constructor
|
|
39
|
+
* @param {PodletClientContentResolverOptions} options
|
|
40
|
+
*/
|
|
41
|
+
// @ts-expect-error Deliberate default empty options for better error messages
|
|
29
42
|
constructor(options = {}) {
|
|
30
43
|
this.#http = options.http || new HTTP();
|
|
31
44
|
const name = options.clientName;
|
|
@@ -54,6 +67,12 @@ export default class PodletClientContentResolver {
|
|
|
54
67
|
return this.#metrics;
|
|
55
68
|
}
|
|
56
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Resolves/fetches the podlet's content.
|
|
72
|
+
*
|
|
73
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
74
|
+
* @returns {Promise<import('./http-outgoing.js').default>}
|
|
75
|
+
*/
|
|
57
76
|
async resolve(outgoing) {
|
|
58
77
|
if (outgoing.kill && outgoing.throwable) {
|
|
59
78
|
this.#log.warn(
|
|
@@ -71,12 +90,12 @@ export default class PodletClientContentResolver {
|
|
|
71
90
|
outgoing.success = true;
|
|
72
91
|
outgoing.pushFallback();
|
|
73
92
|
outgoing.emit(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
'beforeStream',
|
|
94
|
+
new Response({
|
|
95
|
+
js: utils.filterAssets('fallback', outgoing.manifest.js),
|
|
96
|
+
css: utils.filterAssets('fallback', outgoing.manifest.css),
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
80
99
|
return outgoing;
|
|
81
100
|
}
|
|
82
101
|
|
|
@@ -94,12 +113,12 @@ export default class PodletClientContentResolver {
|
|
|
94
113
|
outgoing.success = true;
|
|
95
114
|
outgoing.pushFallback();
|
|
96
115
|
outgoing.emit(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
116
|
+
'beforeStream',
|
|
117
|
+
new Response({
|
|
118
|
+
js: utils.filterAssets('fallback', outgoing.manifest.js),
|
|
119
|
+
css: utils.filterAssets('fallback', outgoing.manifest.css),
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
103
122
|
return outgoing;
|
|
104
123
|
}
|
|
105
124
|
|
|
@@ -113,8 +132,9 @@ export default class PodletClientContentResolver {
|
|
|
113
132
|
const uri = putils.uriBuilder(
|
|
114
133
|
outgoing.reqOptions.pathname,
|
|
115
134
|
outgoing.contentUri,
|
|
116
|
-
)
|
|
135
|
+
);
|
|
117
136
|
|
|
137
|
+
/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
|
|
118
138
|
const reqOptions = {
|
|
119
139
|
rejectUnauthorized: outgoing.rejectUnauthorized,
|
|
120
140
|
bodyTimeout: outgoing.timeout,
|
|
@@ -183,11 +203,17 @@ export default class PodletClientContentResolver {
|
|
|
183
203
|
outgoing.success = true;
|
|
184
204
|
outgoing.pushFallback();
|
|
185
205
|
outgoing.emit(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
206
|
+
'beforeStream',
|
|
207
|
+
new Response({
|
|
208
|
+
js: utils.filterAssets(
|
|
209
|
+
'fallback',
|
|
210
|
+
outgoing.manifest.js,
|
|
211
|
+
),
|
|
212
|
+
css: utils.filterAssets(
|
|
213
|
+
'fallback',
|
|
214
|
+
outgoing.manifest.css,
|
|
215
|
+
),
|
|
216
|
+
}),
|
|
191
217
|
);
|
|
192
218
|
|
|
193
219
|
// Body must be consumed; https://github.com/nodejs/undici/issues/583#issuecomment-855384858
|
|
@@ -227,6 +253,7 @@ export default class PodletClientContentResolver {
|
|
|
227
253
|
if (outgoing.redirectable && statusCode >= 300) {
|
|
228
254
|
outgoing.redirect = {
|
|
229
255
|
statusCode,
|
|
256
|
+
// @ts-expect-error TODO: look into what happens if the podlet returns more than one location header
|
|
230
257
|
location: hdrs && hdrs.location,
|
|
231
258
|
};
|
|
232
259
|
}
|
|
@@ -235,8 +262,8 @@ export default class PodletClientContentResolver {
|
|
|
235
262
|
'beforeStream',
|
|
236
263
|
new Response({
|
|
237
264
|
headers: outgoing.headers,
|
|
238
|
-
js: utils.filterAssets(
|
|
239
|
-
css: utils.filterAssets(
|
|
265
|
+
js: utils.filterAssets('content', outgoing.manifest.js),
|
|
266
|
+
css: utils.filterAssets('content', outgoing.manifest.css),
|
|
240
267
|
redirect: outgoing.redirect,
|
|
241
268
|
}),
|
|
242
269
|
);
|
|
@@ -260,10 +287,7 @@ export default class PodletClientContentResolver {
|
|
|
260
287
|
this.#log.warn(
|
|
261
288
|
`could not create network connection to remote resource when trying to request content - resource: ${outgoing.name} - url: ${uri}`,
|
|
262
289
|
);
|
|
263
|
-
throw badGateway(
|
|
264
|
-
`Error reading content at ${uri}`,
|
|
265
|
-
error,
|
|
266
|
-
);
|
|
290
|
+
throw badGateway(`Error reading content at ${uri}`, error);
|
|
267
291
|
}
|
|
268
292
|
|
|
269
293
|
timer({
|
|
@@ -280,12 +304,12 @@ export default class PodletClientContentResolver {
|
|
|
280
304
|
|
|
281
305
|
outgoing.pushFallback();
|
|
282
306
|
outgoing.emit(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
307
|
+
'beforeStream',
|
|
308
|
+
new Response({
|
|
309
|
+
js: utils.filterAssets('fallback', outgoing.manifest.js),
|
|
310
|
+
css: utils.filterAssets('fallback', outgoing.manifest.css),
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
289
313
|
|
|
290
314
|
return outgoing;
|
|
291
315
|
}
|