@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/README.md
CHANGED
|
@@ -150,18 +150,16 @@ The following values can be provided:
|
|
|
150
150
|
- `retries` - {Number} - The number of times the client should retry to settle a version number conflict before terminating. See the section "[on retrying](#on-retrying)" for more information. Default: 4 - Optional.
|
|
151
151
|
- `timeout` - {Number} - Defines how long, in milliseconds, a request should wait before the connection is terminated. Overrides the global default. Default: 1000 - Optional.
|
|
152
152
|
- `throwable` - {Boolean} - Defines whether an error should be thrown if a failure occurs during the process of fetching a podium component. Defaults to `false` - Optional.
|
|
153
|
-
- `resolveJs` - {Boolean} - Defines whether to resolve a relative JS uri for a component to be an absolute uri. Defaults to `false` - Optional.
|
|
154
|
-
- `resolveCss` - {Boolean} - Defines whether to resolve a relative CSS uri for a component to be an absolute uri. Defaults to `false` - Optional.
|
|
155
153
|
- `excludeBy` - {Object} - Lets you define a set of rules where a `fetch` call will not be resolved if it matches. - Optional.
|
|
156
|
-
- `includeBy` - {Object} - Inverse of `excludeBy`. - Optional.
|
|
154
|
+
- `includeBy` - {Object} - Inverse of `excludeBy`. Setting both at the same time will throw. - Optional.
|
|
157
155
|
|
|
158
156
|
##### `excludeBy` and `includeBy`
|
|
159
157
|
|
|
160
|
-
These options are used by `fetch` to conditionally skip fetching the podlet content based on values on the request. It's an alternative to conditionally fetching podlets in your request handler.
|
|
158
|
+
These options are used by `fetch` to conditionally skip fetching the podlet content based on values on the request. It's an alternative to conditionally fetching podlets in your request handler. Setting both at the same time will throw.
|
|
161
159
|
|
|
162
160
|
Allowed options:
|
|
163
161
|
|
|
164
|
-
-
|
|
162
|
+
- `deviceType` - {Array<String>} - List of values for the `x-podium-device-type` header. - Optional.
|
|
165
163
|
|
|
166
164
|
Example: exclude a header and footer in a hybrid web view.
|
|
167
165
|
|
|
@@ -173,7 +171,7 @@ const footer = client.register({
|
|
|
173
171
|
uri: 'http://footer.site.com/manifest.json',
|
|
174
172
|
name: 'footer',
|
|
175
173
|
excludeBy: {
|
|
176
|
-
deviceType: [
|
|
174
|
+
deviceType: ['hybrid-ios', 'hybrid-android'], // when footer.fetch(incoming) is called, if the incoming request has the header `x-podium-device-type: hybrid-ios`, `fetch` will return an empty response.
|
|
177
175
|
},
|
|
178
176
|
});
|
|
179
177
|
```
|
|
@@ -382,7 +380,7 @@ Response object containing the keys `content`, `headers`, `css` and `js`.
|
|
|
382
380
|
|
|
383
381
|
#### incoming (required)
|
|
384
382
|
|
|
385
|
-
A HttpIncoming object. See https://github.com/podium-lib/utils/blob/
|
|
383
|
+
A HttpIncoming object. See https://github.com/podium-lib/utils/blob/main/lib/http-incoming.js
|
|
386
384
|
|
|
387
385
|
#### options (optional)
|
|
388
386
|
|
|
@@ -413,7 +411,7 @@ emitted.
|
|
|
413
411
|
|
|
414
412
|
#### incoming (required)
|
|
415
413
|
|
|
416
|
-
A HttpIncoming object. See https://github.com/podium-lib/utils/blob/
|
|
414
|
+
A HttpIncoming object. See https://github.com/podium-lib/utils/blob/main/lib/http-incoming.js
|
|
417
415
|
|
|
418
416
|
#### options (optional)
|
|
419
417
|
|
|
@@ -462,15 +460,19 @@ stream.once('beforeStream', (data) => {
|
|
|
462
460
|
Both the .fetch() method and the .stream() method give you access to podlet asset objects and these CSS and JS asset objects will be filtered if the asset objects contain a `scope` property and that `scope` property matches the current response type (either content or fallback).
|
|
463
461
|
|
|
464
462
|
For example, if the podlet manifest contains a JavaScript asset definition of the form:
|
|
463
|
+
|
|
465
464
|
```
|
|
466
465
|
{
|
|
467
466
|
js: [{ value: "https://assets.com/path/to/file.js", scope: "content" }],
|
|
468
467
|
}
|
|
469
468
|
```
|
|
469
|
+
|
|
470
470
|
And the client performs a fetch like so:
|
|
471
|
+
|
|
471
472
|
```js
|
|
472
473
|
const result = await component.fetch();
|
|
473
474
|
```
|
|
475
|
+
|
|
474
476
|
Then, if the podlet successfully responds from its content route, the `result.js` property will contain the asset defined above. If, however, the podlet's content route errors and the client is forced to use the podlet's fallback content, then `result.js` property will be an empty array.
|
|
475
477
|
|
|
476
478
|
Possible `scope` values are `content`, `fallback` and `all`. For backwards compatibility reasons, when assets do not provide a `scope` property, they will always be included in both `content` and `fallback` responses.
|
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,51 @@ 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
|
+
* @property {import('./resource.js').RequestFilterOptions} [excludeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
|
|
64
|
+
* @property {import('./resource.js').RequestFilterOptions} [includeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
|
|
65
|
+
*/
|
|
66
|
+
|
|
39
67
|
export default class PodiumClient extends EventEmitter {
|
|
40
68
|
#resources;
|
|
41
69
|
#registry;
|
|
@@ -43,6 +71,12 @@ export default class PodiumClient extends EventEmitter {
|
|
|
43
71
|
#histogram;
|
|
44
72
|
#options;
|
|
45
73
|
#state;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @constructor
|
|
77
|
+
* @param {PodiumClientOptions} options
|
|
78
|
+
*/
|
|
79
|
+
// @ts-expect-error Deliberate default empty options for better error messages
|
|
46
80
|
constructor(options = {}) {
|
|
47
81
|
super();
|
|
48
82
|
const log = abslog(options.logger);
|
|
@@ -71,7 +105,7 @@ export default class PodiumClient extends EventEmitter {
|
|
|
71
105
|
resolveThreshold: options.resolveThreshold,
|
|
72
106
|
resolveMax: options.resolveMax,
|
|
73
107
|
});
|
|
74
|
-
this.#state.on('state', state => {
|
|
108
|
+
this.#state.on('state', (state) => {
|
|
75
109
|
this.emit('state', state);
|
|
76
110
|
});
|
|
77
111
|
|
|
@@ -81,7 +115,7 @@ export default class PodiumClient extends EventEmitter {
|
|
|
81
115
|
changefeed: true,
|
|
82
116
|
ttl: options.maxAge,
|
|
83
117
|
});
|
|
84
|
-
this.#registry.on('error', error => {
|
|
118
|
+
this.#registry.on('error', (error) => {
|
|
85
119
|
log.error(
|
|
86
120
|
'Error emitted by the registry in @podium/client module',
|
|
87
121
|
error,
|
|
@@ -93,7 +127,7 @@ export default class PodiumClient extends EventEmitter {
|
|
|
93
127
|
});
|
|
94
128
|
|
|
95
129
|
this.#metrics = new Metrics();
|
|
96
|
-
this.#metrics.on('error', error => {
|
|
130
|
+
this.#metrics.on('error', (error) => {
|
|
97
131
|
log.error(
|
|
98
132
|
'Error emitted by metric stream in @podium/client module',
|
|
99
133
|
error,
|
|
@@ -101,7 +135,7 @@ export default class PodiumClient extends EventEmitter {
|
|
|
101
135
|
});
|
|
102
136
|
|
|
103
137
|
this[Symbol.iterator] = () => ({
|
|
104
|
-
items: Array.from(this.#resources).map(item => item[1]),
|
|
138
|
+
items: Array.from(this.#resources).map((item) => item[1]),
|
|
105
139
|
next: function next() {
|
|
106
140
|
return {
|
|
107
141
|
done: this.items.length === 0,
|
|
@@ -130,7 +164,21 @@ export default class PodiumClient extends EventEmitter {
|
|
|
130
164
|
return this.#state.status;
|
|
131
165
|
}
|
|
132
166
|
|
|
133
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Register a podlet so you can fetch its contents later with {@link PodiumClientResource.fetch}.
|
|
169
|
+
*
|
|
170
|
+
* @param {RegisterOptions} options
|
|
171
|
+
* @returns {PodiumClientResource}
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```js
|
|
175
|
+
* const headerPodlet = layout.client.register({
|
|
176
|
+
* name: 'header',
|
|
177
|
+
* uri: 'http://header/manifest.json',
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
register(options) {
|
|
134
182
|
if (validateName(options.name).error)
|
|
135
183
|
throw new Error(
|
|
136
184
|
`The value, "${options.name}", for the required argument "name" on the .register() method is not defined or not valid.`,
|
|
@@ -147,6 +195,12 @@ export default class PodiumClient extends EventEmitter {
|
|
|
147
195
|
);
|
|
148
196
|
}
|
|
149
197
|
|
|
198
|
+
if (options.includeBy && options.excludeBy) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
'A podlet can only be registered with either includeBy or excludeBy, not both.',
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
150
204
|
const resourceOptions = {
|
|
151
205
|
rejectUnauthorized: this.#options.rejectUnauthorized,
|
|
152
206
|
clientName: this.#options.name,
|
|
@@ -154,12 +208,13 @@ export default class PodiumClient extends EventEmitter {
|
|
|
154
208
|
timeout: this.#options.timeout,
|
|
155
209
|
logger: this.#options.logger,
|
|
156
210
|
maxAge: this.#options.maxAge,
|
|
157
|
-
includeBy: this.#options.includeBy,
|
|
158
|
-
excludeBy: this.#options.excludeBy,
|
|
159
211
|
httpsAgent: this.#options.httpsAgent,
|
|
160
212
|
httpAgent: this.#options.httpAgent,
|
|
213
|
+
includeBy: this.#options.includeBy,
|
|
214
|
+
excludeBy: this.#options.excludeBy,
|
|
161
215
|
...options,
|
|
162
216
|
};
|
|
217
|
+
|
|
163
218
|
const resource = new Resource(
|
|
164
219
|
this.#registry,
|
|
165
220
|
this.#state,
|
|
@@ -187,12 +242,17 @@ export default class PodiumClient extends EventEmitter {
|
|
|
187
242
|
return this.#registry.load(dump);
|
|
188
243
|
}
|
|
189
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Refreshes the cached podlet manifest for all {@link register}ed podlets.
|
|
247
|
+
*/
|
|
190
248
|
async refreshManifests() {
|
|
191
249
|
const end = this.#histogram.timer();
|
|
192
250
|
|
|
193
251
|
// Don't return this
|
|
194
252
|
await Promise.all(
|
|
195
|
-
Array.from(this.#resources).map(resource =>
|
|
253
|
+
Array.from(this.#resources).map((resource) =>
|
|
254
|
+
resource[1].refresh(),
|
|
255
|
+
),
|
|
196
256
|
);
|
|
197
257
|
|
|
198
258
|
end();
|
|
@@ -208,4 +268,4 @@ export default class PodiumClient extends EventEmitter {
|
|
|
208
268
|
get [Symbol.toStringTag]() {
|
|
209
269
|
return 'PodiumClient';
|
|
210
270
|
}
|
|
211
|
-
}
|
|
271
|
+
}
|
package/lib/http-outgoing.js
CHANGED
|
@@ -1,28 +1,84 @@
|
|
|
1
|
-
/* eslint-disable no-underscore-dangle */
|
|
2
|
-
|
|
3
1
|
import { PassThrough } from 'stream';
|
|
4
2
|
import assert from 'assert';
|
|
5
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} PodiumClientHttpOutgoingOptions
|
|
6
|
+
* @property {string} name
|
|
7
|
+
* @property {string} uri To the podlet's `manifest.json`
|
|
8
|
+
* @property {number} timeout In milliseconds
|
|
9
|
+
* @property {number} maxAge
|
|
10
|
+
* @property {number} [retries=4]
|
|
11
|
+
* @property {boolean} [throwable=false]
|
|
12
|
+
* @property {boolean} [redirectable=false]
|
|
13
|
+
* @property {boolean} [rejectUnauthorized=true]
|
|
14
|
+
* @property {import('http').Agent} [httpAgent]
|
|
15
|
+
* @property {import('https').Agent} [httpsAgent]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} PodiumClientResourceOptions
|
|
20
|
+
* @property {string} [pathname]
|
|
21
|
+
* @property {import('http').IncomingHttpHeaders} [headers]
|
|
22
|
+
* @property {object} [query]
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {object} PodiumRedirect
|
|
27
|
+
* @property {number} statusCode;
|
|
28
|
+
* @property {string} location;
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} PodletProxySchema
|
|
33
|
+
* @property {string} target
|
|
34
|
+
* @property {string} name
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} PodletManifest Similar to the schema's manifest, but with instances of AssetCss and AssetJs from `@podium/utils` and default values.
|
|
39
|
+
* @property {string} name
|
|
40
|
+
* @property {string} version
|
|
41
|
+
* @property {string} content
|
|
42
|
+
* @property {string} fallback
|
|
43
|
+
* @property {Array<import('@podium/utils').AssetJs>} js
|
|
44
|
+
* @property {Array<import('@podium/utils').AssetCss>} css
|
|
45
|
+
* @property {Record<string, string> | Array<PodletProxySchema>} proxy
|
|
46
|
+
* @property {string} team
|
|
47
|
+
*/
|
|
48
|
+
|
|
6
49
|
export default class PodletClientHttpOutgoing extends PassThrough {
|
|
7
50
|
#rejectUnauthorized;
|
|
8
51
|
#killRecursions;
|
|
9
52
|
#killThreshold;
|
|
10
53
|
#redirectable;
|
|
11
54
|
#reqOptions;
|
|
12
|
-
#isFallback;
|
|
55
|
+
#isFallback = false;
|
|
13
56
|
#throwable;
|
|
57
|
+
/** @type {PodletManifest} */
|
|
14
58
|
#manifest;
|
|
15
59
|
#incoming;
|
|
16
|
-
|
|
60
|
+
/** @type {null | PodiumRedirect} */
|
|
61
|
+
#redirect = null;
|
|
17
62
|
#timeout;
|
|
18
63
|
#success;
|
|
19
64
|
#headers;
|
|
20
65
|
#maxAge;
|
|
66
|
+
/** @type {'empty' | 'fresh' | 'cached' | 'stale'} */
|
|
21
67
|
#status;
|
|
22
68
|
#name;
|
|
23
|
-
#uri;
|
|
24
|
-
|
|
25
|
-
|
|
69
|
+
#uri;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @constructor
|
|
73
|
+
* @param {PodiumClientHttpOutgoingOptions} options
|
|
74
|
+
* @param {PodiumClientResourceOptions} [reqOptions]
|
|
75
|
+
* @param {import('@podium/utils').HttpIncoming} [incoming]
|
|
76
|
+
*/
|
|
77
|
+
// @ts-expect-error Deliberate default empty options for better error messages
|
|
78
|
+
constructor(options = {}, reqOptions, incoming) {
|
|
79
|
+
super();
|
|
80
|
+
|
|
81
|
+
const {
|
|
26
82
|
rejectUnauthorized = true,
|
|
27
83
|
throwable = false,
|
|
28
84
|
redirectable = false,
|
|
@@ -31,11 +87,7 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
31
87
|
maxAge,
|
|
32
88
|
name = '',
|
|
33
89
|
uri,
|
|
34
|
-
} =
|
|
35
|
-
reqOptions,
|
|
36
|
-
incoming,
|
|
37
|
-
) {
|
|
38
|
-
super();
|
|
90
|
+
} = options;
|
|
39
91
|
|
|
40
92
|
assert(
|
|
41
93
|
uri,
|
|
@@ -45,7 +97,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
45
97
|
// If requests to https sites should reject on unsigned sertificates
|
|
46
98
|
this.#rejectUnauthorized = rejectUnauthorized;
|
|
47
99
|
|
|
48
|
-
// A HttpIncoming object
|
|
49
100
|
this.#incoming = incoming;
|
|
50
101
|
|
|
51
102
|
// Kill switch for breaking the recursive promise chain
|
|
@@ -67,6 +118,7 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
67
118
|
// Manifest which is either retrieved from the registry or
|
|
68
119
|
// remote podlet (in other words its not saved in registry yet)
|
|
69
120
|
this.#manifest = {
|
|
121
|
+
// @ts-expect-error Internal property
|
|
70
122
|
_fallback: '',
|
|
71
123
|
};
|
|
72
124
|
|
|
@@ -82,14 +134,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
82
134
|
// How long the manifest should be cached before refetched
|
|
83
135
|
this.#maxAge = maxAge;
|
|
84
136
|
|
|
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
137
|
this.#status = 'empty';
|
|
94
138
|
|
|
95
139
|
// Name of the resource (name given to client)
|
|
@@ -100,13 +144,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
100
144
|
|
|
101
145
|
// Whether the user can handle redirects manually.
|
|
102
146
|
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
147
|
}
|
|
111
148
|
|
|
112
149
|
get rejectUnauthorized() {
|
|
@@ -130,10 +167,12 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
130
167
|
}
|
|
131
168
|
|
|
132
169
|
get fallback() {
|
|
170
|
+
// @ts-expect-error Internal property
|
|
133
171
|
return this.#manifest._fallback;
|
|
134
172
|
}
|
|
135
173
|
|
|
136
174
|
set fallback(value) {
|
|
175
|
+
// @ts-expect-error Internal property
|
|
137
176
|
this.#manifest._fallback = value;
|
|
138
177
|
}
|
|
139
178
|
|
|
@@ -169,6 +208,16 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
169
208
|
this.#maxAge = value;
|
|
170
209
|
}
|
|
171
210
|
|
|
211
|
+
/**
|
|
212
|
+
* What status the manifest is in. This is used to tell what actions need to
|
|
213
|
+
* be performed throughout the resolving process to complete a request.
|
|
214
|
+
*
|
|
215
|
+
* The different statuses can be:
|
|
216
|
+
* - `"empty"` - there is no manifest available - we are in process of fetching it
|
|
217
|
+
* - `"fresh"` - the manifest has been fetched but is not stored in cache yet
|
|
218
|
+
* - `"cached"` - the manifest was retrieved from cache
|
|
219
|
+
* - `"stale"` - the manifest is outdated, a new manifest needs to be fetched
|
|
220
|
+
*/
|
|
172
221
|
get status() {
|
|
173
222
|
return this.#status;
|
|
174
223
|
}
|
|
@@ -193,18 +242,33 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
193
242
|
return this.#manifest.content;
|
|
194
243
|
}
|
|
195
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Kill switch for breaking the recursive promise chain in case it is never able to completely resolve.
|
|
247
|
+
* This is true if the number of recursions matches the threshold.
|
|
248
|
+
*/
|
|
196
249
|
get kill() {
|
|
197
250
|
return this.#killRecursions === this.#killThreshold;
|
|
198
251
|
}
|
|
199
252
|
|
|
253
|
+
/**
|
|
254
|
+
* The number of recursions before the request should be {@link kill}ed
|
|
255
|
+
*/
|
|
200
256
|
get recursions() {
|
|
201
257
|
return this.#killRecursions;
|
|
202
258
|
}
|
|
203
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Set the number of recursions before the request should be {@link kill}ed
|
|
262
|
+
*/
|
|
204
263
|
set recursions(value) {
|
|
205
264
|
this.#killRecursions = value;
|
|
206
265
|
}
|
|
207
266
|
|
|
267
|
+
/**
|
|
268
|
+
* When {@link redirectable} is `true` this is populated with redirect information so you can send a redirect response to the browser from your layout.
|
|
269
|
+
*
|
|
270
|
+
* @see https://podium-lib.io/docs/layout/handling_redirects
|
|
271
|
+
*/
|
|
208
272
|
get redirect() {
|
|
209
273
|
return this.#redirect;
|
|
210
274
|
}
|
|
@@ -213,6 +277,11 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
213
277
|
this.#redirect = value;
|
|
214
278
|
}
|
|
215
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Whether the podlet can signal redirects to the layout.
|
|
282
|
+
*
|
|
283
|
+
* @see https://podium-lib.io/docs/layout/handling_redirects
|
|
284
|
+
*/
|
|
216
285
|
get redirectable() {
|
|
217
286
|
return this.#redirectable;
|
|
218
287
|
}
|
|
@@ -222,17 +291,22 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
222
291
|
}
|
|
223
292
|
|
|
224
293
|
/**
|
|
225
|
-
*
|
|
294
|
+
* True if the client has returned the podlet's fallback.
|
|
295
|
+
*
|
|
226
296
|
* @example
|
|
227
|
-
*
|
|
297
|
+
*
|
|
298
|
+
* ```js
|
|
228
299
|
* if (outgoing.isFallback) console.log("Fallback!");
|
|
229
300
|
* ```
|
|
301
|
+
*
|
|
302
|
+
* @see https://podium-lib.io/docs/podlet/fallbacks
|
|
230
303
|
*/
|
|
231
304
|
get isFallback() {
|
|
232
|
-
|
|
305
|
+
return this.#isFallback;
|
|
233
306
|
}
|
|
234
307
|
|
|
235
308
|
pushFallback() {
|
|
309
|
+
// @ts-expect-error Internal property
|
|
236
310
|
this.push(this.#manifest._fallback);
|
|
237
311
|
this.push(null);
|
|
238
312
|
this.#isFallback = true;
|
|
@@ -241,4 +315,4 @@ export default class PodletClientHttpOutgoing extends PassThrough {
|
|
|
241
315
|
get [Symbol.toStringTag]() {
|
|
242
316
|
return 'PodletClientHttpOutgoing';
|
|
243
317
|
}
|
|
244
|
-
}
|
|
318
|
+
}
|
package/lib/http.js
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { request } from 'undici';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
*/
|
|
9
14
|
|
|
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
|
+
*/
|
|
10
21
|
async request(url, options) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const { statusCode, headers, body } = await client.request({
|
|
18
|
-
...options,
|
|
19
|
-
path: u.pathname,
|
|
20
|
-
});
|
|
22
|
+
const { statusCode, headers, body } = await request(
|
|
23
|
+
new URL(url),
|
|
24
|
+
options,
|
|
25
|
+
);
|
|
21
26
|
return { statusCode, headers, body };
|
|
22
27
|
}
|
|
23
28
|
}
|
package/lib/resolver.cache.js
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
/* eslint-disable no-plusplus */
|
|
2
|
-
/* eslint-disable no-param-reassign */
|
|
3
|
-
|
|
4
1
|
import clonedeep from 'lodash.clonedeep';
|
|
5
2
|
import abslog from 'abslog';
|
|
6
3
|
import assert from 'assert';
|
|
7
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} PodletClientCacheResolverOptions
|
|
7
|
+
* @property {import('abslog').AbstractLoggerOptions} [logger]
|
|
8
|
+
*/
|
|
9
|
+
|
|
8
10
|
export default class PodletClientCacheResolver {
|
|
9
11
|
#registry;
|
|
10
12
|
#log;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @constructor
|
|
16
|
+
* @param {import('ttl-mem-cache').default} registry
|
|
17
|
+
* @param {PodletClientCacheResolverOptions} options
|
|
18
|
+
*/
|
|
11
19
|
constructor(registry, options = {}) {
|
|
12
20
|
assert(
|
|
13
21
|
registry,
|
|
@@ -17,8 +25,14 @@ export default class PodletClientCacheResolver {
|
|
|
17
25
|
this.#log = abslog(options.logger);
|
|
18
26
|
}
|
|
19
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Loads the podlet's manifest from cache if not stale
|
|
30
|
+
*
|
|
31
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
32
|
+
* @returns {Promise<import('./http-outgoing.js').default>}
|
|
33
|
+
*/
|
|
20
34
|
load(outgoing) {
|
|
21
|
-
return new Promise(resolve => {
|
|
35
|
+
return new Promise((resolve) => {
|
|
22
36
|
if (outgoing.status !== 'stale') {
|
|
23
37
|
const cached = this.#registry.get(outgoing.name);
|
|
24
38
|
if (cached) {
|
|
@@ -33,8 +47,14 @@ export default class PodletClientCacheResolver {
|
|
|
33
47
|
});
|
|
34
48
|
}
|
|
35
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Saves the podlet's manifest to the cache
|
|
52
|
+
*
|
|
53
|
+
* @param {import('./http-outgoing.js').default} outgoing
|
|
54
|
+
* @returns {Promise<import('./http-outgoing.js').default>}
|
|
55
|
+
*/
|
|
36
56
|
save(outgoing) {
|
|
37
|
-
return new Promise(resolve => {
|
|
57
|
+
return new Promise((resolve) => {
|
|
38
58
|
if (outgoing.status === 'fresh') {
|
|
39
59
|
this.#registry.set(
|
|
40
60
|
outgoing.name,
|
|
@@ -55,4 +75,4 @@ export default class PodletClientCacheResolver {
|
|
|
55
75
|
get [Symbol.toStringTag]() {
|
|
56
76
|
return 'PodletClientCacheResolver';
|
|
57
77
|
}
|
|
58
|
-
}
|
|
78
|
+
}
|