Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@podium/client

Package Overview
Dependencies
Maintainers
0
Versions
196
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@podium/client - npm Package Compare versions

Comparing version 5.1.0-beta.1 to 5.1.0

types/client.d.ts

195

CHANGELOG.md

@@ -1,2 +0,2 @@

# [5.1.0-beta.1](https://github.com/podium-lib/client/compare/v5.0.7...v5.1.0-beta.1) (2024-03-20)
# [5.1.0](https://github.com/podium-lib/client/compare/v5.0.34...v5.1.0) (2024-07-02)

@@ -6,5 +6,194 @@

* add new options to register and use them in fetch ([5aed9d6](https://github.com/podium-lib/client/commit/5aed9d6303681910fc95413535345643bbfa8d62))
* add support for includeBy and excludeBy options in Resource ([51eb442](https://github.com/podium-lib/client/commit/51eb442398a15b487970ebabe51a5c33f3aede29))
* conditional fetch by deviceType header ([de4e9c4](https://github.com/podium-lib/client/commit/de4e9c47eea6baf07f2dc72465699a080b6aace1))
## [5.0.34](https://github.com/podium-lib/client/compare/v5.0.33...v5.0.34) (2024-06-18)
### Bug Fixes
* **deps:** update dependency undici to v6.19.2 ([ac66051](https://github.com/podium-lib/client/commit/ac66051d0600273f5d3e7c0b57be6fccf222cb83))
## [5.0.33](https://github.com/podium-lib/client/compare/v5.0.32...v5.0.33) (2024-06-17)
### Bug Fixes
* **deps:** update dependency undici to v6.19.1 ([c64b634](https://github.com/podium-lib/client/commit/c64b6343c87630fc88a2dbf37ca26044c6a81c75))
## [5.0.32](https://github.com/podium-lib/client/compare/v5.0.31...v5.0.32) (2024-06-14)
### Bug Fixes
* **deps:** update dependency undici to v6.19.0 ([ae67694](https://github.com/podium-lib/client/commit/ae6769416e368071a7a7ff8776856cf2307877c0))
## [5.0.31](https://github.com/podium-lib/client/compare/v5.0.30...v5.0.31) (2024-06-05)
### Bug Fixes
* **deps:** update dependency @podium/schemas to v5.0.5 ([1fd5c68](https://github.com/podium-lib/client/commit/1fd5c68bf97de53cdffdb26f3510f569fd8a110f))
## [5.0.30](https://github.com/podium-lib/client/compare/v5.0.29...v5.0.30) (2024-06-04)
### Bug Fixes
* **deps:** update dependency @podium/schemas to v5.0.4 ([d3574df](https://github.com/podium-lib/client/commit/d3574dfa36e0aa0838ed1410e8b7c0cf15559c0b))
## [5.0.29](https://github.com/podium-lib/client/compare/v5.0.28...v5.0.29) (2024-05-29)
### Bug Fixes
* **deps:** update dependency undici to v6.18.2 ([68b44d0](https://github.com/podium-lib/client/commit/68b44d06037a5e8323ff2b7c913838b55e44f59e))
## [5.0.28](https://github.com/podium-lib/client/compare/v5.0.27...v5.0.28) (2024-05-26)
### Bug Fixes
* **deps:** update dependency @podium/schemas to v5.0.3 ([089f50a](https://github.com/podium-lib/client/commit/089f50ad1bf06c7bf6ae16fcb24cec3d243dd271))
## [5.0.27](https://github.com/podium-lib/client/compare/v5.0.26...v5.0.27) (2024-05-22)
### Bug Fixes
* **deps:** update dependency undici to v6.18.1 ([f1d0705](https://github.com/podium-lib/client/commit/f1d0705a269aee4ddb92b75ff4d791ba02e305e9))
## [5.0.26](https://github.com/podium-lib/client/compare/v5.0.25...v5.0.26) (2024-05-20)
### Bug Fixes
* **deps:** update dependency undici to v6.18.0 ([3c6585f](https://github.com/podium-lib/client/commit/3c6585f0d4ce5289f63034384243892557ec67c3))
## [5.0.25](https://github.com/podium-lib/client/compare/v5.0.24...v5.0.25) (2024-05-17)
### Bug Fixes
* **deps:** update dependency undici to v6.17.0 ([83f8fba](https://github.com/podium-lib/client/commit/83f8fbaa6c1c417f29f76152510d14cba673abe3))
## [5.0.24](https://github.com/podium-lib/client/compare/v5.0.23...v5.0.24) (2024-05-15)
### Bug Fixes
* **deps:** update dependency @podium/utils to v5.0.7 ([ee4ee5b](https://github.com/podium-lib/client/commit/ee4ee5b160ff2aab130e17a3e86abdfec08d1864))
## [5.0.23](https://github.com/podium-lib/client/compare/v5.0.22...v5.0.23) (2024-05-15)
### Bug Fixes
* make response toJSON return type serializable ([026a723](https://github.com/podium-lib/client/commit/026a723b545c6b3b0c622851b7d164be2674e8a2))
## [5.0.22](https://github.com/podium-lib/client/compare/v5.0.21...v5.0.22) (2024-05-15)
### Bug Fixes
* generate type definitions before publish ([8189b01](https://github.com/podium-lib/client/commit/8189b01c54c733c760b02f2138803f9b64a6fee5))
## [5.0.21](https://github.com/podium-lib/client/compare/v5.0.20...v5.0.21) (2024-05-14)
### Bug Fixes
* export same types as before ([0e3c65a](https://github.com/podium-lib/client/commit/0e3c65afc0fef7e6c53ad544dc80cb7f8dc8596b))
* generate types from JSDoc ([32cac7e](https://github.com/podium-lib/client/commit/32cac7e10a76494c0d2df602f41d42a67e8e7b6c))
## [5.0.20](https://github.com/podium-lib/client/compare/v5.0.19...v5.0.20) (2024-05-14)
### Bug Fixes
* **deps:** update dependency @podium/schemas to v5.0.2 ([e8e2fb4](https://github.com/podium-lib/client/commit/e8e2fb45e00be74222fa19e1faba1f29829976e8))
## [5.0.19](https://github.com/podium-lib/client/compare/v5.0.18...v5.0.19) (2024-05-13)
### Bug Fixes
* **deps:** update dependency @podium/utils to v5.0.6 ([fb1e29f](https://github.com/podium-lib/client/commit/fb1e29f6734fc04f907c64904c535bb02faca334))
## [5.0.18](https://github.com/podium-lib/client/compare/v5.0.17...v5.0.18) (2024-05-10)
### Bug Fixes
* **deps:** update dependency undici to v6.16.1 ([16edd1c](https://github.com/podium-lib/client/commit/16edd1c20a34686182868f0432e3280952805450))
## [5.0.17](https://github.com/podium-lib/client/compare/v5.0.16...v5.0.17) (2024-05-07)
### Bug Fixes
* **deps:** update dependency undici to v6.16.0 ([8884811](https://github.com/podium-lib/client/commit/88848113650e00183f0368aa7c8642a33aaae3ec))
## [5.0.16](https://github.com/podium-lib/client/compare/v5.0.15...v5.0.16) (2024-05-07)
### Bug Fixes
* **deps:** update dependency @podium/utils to v5.0.5 ([4059526](https://github.com/podium-lib/client/commit/40595261f2d5b644e7cda15e7d1998cdbbb8b3c3))
## [5.0.15](https://github.com/podium-lib/client/compare/v5.0.14...v5.0.15) (2024-05-07)
### Bug Fixes
* Requests hang on http errors ([a9c82b6](https://github.com/podium-lib/client/commit/a9c82b6a931d92540c69c2cbf911184710dc54ee))
## [5.0.14](https://github.com/podium-lib/client/compare/v5.0.13...v5.0.14) (2024-05-02)
### Bug Fixes
* **deps:** update dependency abslog to v2.4.4 ([0e21e41](https://github.com/podium-lib/client/commit/0e21e41d2503387dc1d796f857a41048f5222209))
## [5.0.13](https://github.com/podium-lib/client/compare/v5.0.12...v5.0.13) (2024-04-30)
### Bug Fixes
* **deps:** update dependency abslog to v2.4.3 ([ea2a8ff](https://github.com/podium-lib/client/commit/ea2a8ff0cc43c97cdd853551959928d4fcc53992))
## [5.0.12](https://github.com/podium-lib/client/compare/v5.0.11...v5.0.12) (2024-04-30)
### Bug Fixes
* **deps:** update dependency @podium/schemas to v5.0.1 ([f6b3f56](https://github.com/podium-lib/client/commit/f6b3f56e1be12b2e5a3d5296a00488e61d354662))
## [5.0.11](https://github.com/podium-lib/client/compare/v5.0.10...v5.0.11) (2024-04-23)
### Bug Fixes
* **deps:** update dependency @podium/utils to v5.0.4 ([b622774](https://github.com/podium-lib/client/commit/b62277448a0fe326fdd11504a0d7787132911908))
## [5.0.10](https://github.com/podium-lib/client/compare/v5.0.9...v5.0.10) (2024-04-12)
### Bug Fixes
* **deps:** update dependency abslog to v2.4.2 ([f36069e](https://github.com/podium-lib/client/commit/f36069ecc3022b3e02a7b4630511fee8b8c8e21d))
## [5.0.9](https://github.com/podium-lib/client/compare/v5.0.8...v5.0.9) (2024-04-12)
### Bug Fixes
* **deps:** update dependency @podium/utils to v5.0.3 ([9412fa6](https://github.com/podium-lib/client/commit/9412fa6d3dd847f595fc8818a2f74374f047feeb))
## [5.0.8](https://github.com/podium-lib/client/compare/v5.0.7...v5.0.8) (2024-04-10)
### Bug Fixes
* **deps:** update dependency abslog to v2.4.1 ([105a16c](https://github.com/podium-lib/client/commit/105a16c0ff0215cd68984d7aad7351b4b5b623b2))
## [5.0.7](https://github.com/podium-lib/client/compare/v5.0.6...v5.0.7) (2024-02-01)

@@ -11,0 +200,0 @@

import EventEmitter from 'events';
import {uriStrict as validateUriStrict, name as validateName } from'@podium/schemas';
import {
uriStrict as validateUriStrict,
name as validateName,
} from '@podium/schemas';
import Metrics from '@metrics/client';

@@ -8,3 +11,2 @@ import abslog from 'abslog';

import https from 'https';
import Resource from './resource.js';

@@ -14,3 +16,2 @@ import State from './state.js';

const inspect = Symbol.for('nodejs.util.inspect.custom');
const HTTP_AGENT_OPTIONS = {

@@ -23,3 +24,2 @@ keepAlive: true,

};
const HTTPS_AGENT_OPTIONS = {

@@ -29,15 +29,43 @@ ...HTTP_AGENT_OPTIONS,

};
const REJECT_UNAUTHORIZED = true;
const HTTP_AGENT = new http.Agent(HTTP_AGENT_OPTIONS);
const HTTPS_AGENT = new https.Agent(HTTPS_AGENT_OPTIONS);
const RETRIES = 4;
const TIMEOUT = 1000; // 1 seconds
const MAX_AGE = Infinity;
/**
* @typedef {import('./resource.js').default} PodiumClientResource
* @typedef {import('./resource.js').PodiumClientResourceOptions} PodiumClientResourceOptions
* @typedef {import('./response.js').default} PodiumClientResponse
* @typedef {import('./http-outgoing.js').PodiumRedirect} PodiumRedirect
* @typedef {import('@podium/schemas').PodletManifestSchema} PodletManifest
*/
/**
* @typedef {object} PodiumClientOptions
* @property {string} name
* @property {import('abslog').AbstractLoggerOptions} [logger]
* @property {number} [retries=4]
* @property {number} [timeout=1000] In milliseconds
* @property {number} [maxAge=Infinity]
* @property {boolean} [rejectUnauthorized=true]
* @property {number} [resolveThreshold]
* @property {number} [resolveMax]
* @property {import('http').Agent} [httpAgent]
* @property {import('https').Agent} [httpsAgent]
*/
/**
* @typedef {object} RegisterOptions
* @property {string} name A unique name for the podlet
* @property {string} uri URL to the podlet's `manifest.json`
* @property {number} [retries=4] Number of retries before serving fallback
* @property {number} [timeout=1000] In milliseconds, the amount of time to wait before serving fallback.
* @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.
* @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.
* @property {import('./resource.js').RequestFilterOptions} [excludeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
* @property {import('./resource.js').RequestFilterOptions} [includeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
*/
export default class PodiumClient extends EventEmitter {

@@ -50,2 +78,8 @@ #resources;

#state;
/**
* @constructor
* @param {PodiumClientOptions} options
*/
// @ts-expect-error Deliberate default empty options for better error messages
constructor(options = {}) {

@@ -79,3 +113,3 @@ super();

});
this.#state.on('state', state => {
this.#state.on('state', (state) => {
this.emit('state', state);

@@ -90,3 +124,3 @@ });

});
this.#registry.on('error', error => {
this.#registry.on('error', (error) => {
log.error(

@@ -103,3 +137,3 @@ 'Error emitted by the registry in @podium/client module',

this.#metrics = new Metrics();
this.#metrics.on('error', error => {
this.#metrics.on('error', (error) => {
log.error(

@@ -112,3 +146,3 @@ 'Error emitted by metric stream in @podium/client module',

this[Symbol.iterator] = () => ({
items: Array.from(this.#resources).map(item => item[1]),
items: Array.from(this.#resources).map((item) => item[1]),
next: function next() {

@@ -142,3 +176,17 @@ return {

register(options = {}) {
/**
* Register a podlet so you can fetch its contents later with {@link PodiumClientResource.fetch}.
*
* @param {RegisterOptions} options
* @returns {PodiumClientResource}
*
* @example
* ```js
* const headerPodlet = layout.client.register({
* name: 'header',
* uri: 'http://header/manifest.json',
* });
* ```
*/
register(options) {
if (validateName(options.name).error)

@@ -160,2 +208,8 @@ throw new Error(

if (options.includeBy && options.excludeBy) {
throw new Error(
'A podlet can only be registered with either includeBy or excludeBy, not both.',
);
}
const resourceOptions = {

@@ -168,8 +222,9 @@ rejectUnauthorized: this.#options.rejectUnauthorized,

maxAge: this.#options.maxAge,
httpsAgent: this.#options.httpsAgent,
httpAgent: this.#options.httpAgent,
includeBy: this.#options.includeBy,
excludeBy: this.#options.excludeBy,
httpsAgent: this.#options.httpsAgent,
httpAgent: this.#options.httpAgent,
...options,
};
const resource = new Resource(

@@ -202,2 +257,5 @@ this.#registry,

/**
* Refreshes the cached podlet manifest for all {@link register}ed podlets.
*/
async refreshManifests() {

@@ -208,3 +266,5 @@ const end = this.#histogram.timer();

await Promise.all(
Array.from(this.#resources).map(resource => resource[1].refresh()),
Array.from(this.#resources).map((resource) =>
resource[1].refresh(),
),
);

@@ -225,2 +285,2 @@

}
};
}

136

lib/http-outgoing.js

@@ -6,2 +6,47 @@ /* eslint-disable no-underscore-dangle */

/**
* @typedef {object} PodiumClientHttpOutgoingOptions
* @property {string} name
* @property {string} uri To the podlet's `manifest.json`
* @property {number} timeout In milliseconds
* @property {number} maxAge
* @property {number} [retries=4]
* @property {boolean} [throwable=false]
* @property {boolean} [redirectable=false]
* @property {boolean} [rejectUnauthorized=true]
* @property {import('http').Agent} [httpAgent]
* @property {import('https').Agent} [httpsAgent]
*/
/**
* @typedef {object} PodiumClientResourceOptions
* @property {string} [pathname]
* @property {import('http').IncomingHttpHeaders} [headers]
* @property {object} [query]
*/
/**
* @typedef {object} PodiumRedirect
* @property {number} statusCode;
* @property {string} location;
*/
/**
* @typedef {object} PodletProxySchema
* @property {string} target
* @property {string} name
*/
/**
* @typedef {object} PodletManifest Similar to the schema's manifest, but with instances of AssetCss and AssetJs from `@podium/utils` and default values.
* @property {string} name
* @property {string} version
* @property {string} content
* @property {string} fallback
* @property {Array<import('@podium/utils').AssetJs>} js
* @property {Array<import('@podium/utils').AssetCss>} css
* @property {Record<string, string> | Array<PodletProxySchema>} proxy
* @property {string} team
*/
export default class PodletClientHttpOutgoing extends PassThrough {

@@ -13,7 +58,9 @@ #rejectUnauthorized;

#reqOptions;
#isFallback;
#isFallback = false;
#throwable;
/** @type {PodletManifest} */
#manifest;
#incoming;
#redirect;
/** @type {null | PodiumRedirect} */
#redirect = null;
#timeout;

@@ -23,7 +70,18 @@ #success;

#maxAge;
/** @type {'empty' | 'fresh' | 'cached' | 'stale'} */
#status;
#name;
#uri;
constructor(
{
#uri;
/**
* @constructor
* @param {PodiumClientHttpOutgoingOptions} options
* @param {PodiumClientResourceOptions} [reqOptions]
* @param {import('@podium/utils').HttpIncoming} [incoming]
*/
// @ts-expect-error Deliberate default empty options for better error messages
constructor(options = {}, reqOptions, incoming) {
super();
const {
rejectUnauthorized = true,

@@ -37,7 +95,3 @@ throwable = false,

uri,
} = {},
reqOptions,
incoming,
) {
super();
} = options;

@@ -52,3 +106,2 @@ assert(

// A HttpIncoming object
this.#incoming = incoming;

@@ -75,2 +128,3 @@

this.#manifest = {
// @ts-expect-error Internal property
_fallback: '',

@@ -91,10 +145,2 @@ };

// What status the manifest is in. This is used to tell what actions need to
// be performed throughout the resolving process to complete a request.
//
// The different statuses can be:
// "empty" - there is no manifest available - we are in process of fetching it
// "fresh" - the manifest has been fetched but is not stored in cache yet
// "cached" - the manifest was retrieved from cache
// "stale" - the manifest is outdated, a new manifest needs to be fetched
this.#status = 'empty';

@@ -110,9 +156,2 @@

this.#redirectable = redirectable;
// When redirectable is true, this object should be populated with redirect information
// such that a user can perform manual redirection
this.#redirect = null;
// When isfallback is true, content fetch has failed and fallback will be served instead
this.#isFallback = false;
}

@@ -141,2 +180,3 @@

get fallback() {
// @ts-expect-error Internal property
return this.#manifest._fallback;

@@ -146,2 +186,3 @@ }

set fallback(value) {
// @ts-expect-error Internal property
this.#manifest._fallback = value;

@@ -182,2 +223,12 @@ }

/**
* What status the manifest is in. This is used to tell what actions need to
* be performed throughout the resolving process to complete a request.
*
* The different statuses can be:
* - `"empty"` - there is no manifest available - we are in process of fetching it
* - `"fresh"` - the manifest has been fetched but is not stored in cache yet
* - `"cached"` - the manifest was retrieved from cache
* - `"stale"` - the manifest is outdated, a new manifest needs to be fetched
*/
get status() {

@@ -207,2 +258,6 @@ return this.#status;

/**
* Kill switch for breaking the recursive promise chain in case it is never able to completely resolve.
* This is true if the number of recursions matches the threshold.
*/
get kill() {

@@ -212,2 +267,5 @@ return this.#killRecursions === this.#killThreshold;

/**
* The number of recursions before the request should be {@link kill}ed
*/
get recursions() {

@@ -217,2 +275,5 @@ return this.#killRecursions;

/**
* Set the number of recursions before the request should be {@link kill}ed
*/
set recursions(value) {

@@ -222,2 +283,7 @@ this.#killRecursions = value;

/**
* When {@link redirectable} is `true` this is populated with redirect information so you can send a redirect response to the browser from your layout.
*
* @see https://podium-lib.io/docs/layout/handling_redirects
*/
get redirect() {

@@ -231,2 +297,7 @@ return this.#redirect;

/**
* Whether the podlet can signal redirects to the layout.
*
* @see https://podium-lib.io/docs/layout/handling_redirects
*/
get redirectable() {

@@ -241,13 +312,18 @@ return this.#redirectable;

/**
* Boolean getter that indicates whether the client is responding with a content or fallback payload.
* True if the client has returned the podlet's fallback.
*
* @example
* ```
*
* ```js
* if (outgoing.isFallback) console.log("Fallback!");
* ```
*
* @see https://podium-lib.io/docs/podlet/fallbacks
*/
get isFallback() {
return this.#isFallback;
return this.#isFallback;
}
pushFallback() {
// @ts-expect-error Internal property
this.push(this.#manifest._fallback);

@@ -261,2 +337,2 @@ this.push(null);

}
};
}

@@ -1,23 +0,28 @@

import { Client } from 'undici';
import { request } from 'undici';
/**
* @typedef {object} PodiumHttpClientRequestOptions
* @property {'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH'} method
* @property {boolean} [json]
* @property {boolean} [rejectUnauthorized]
* @property {boolean} [follow]
* @property {number} [timeout]
* @property {number} [bodyTimeout]
* @property {object} [query]
* @property {import('http').IncomingHttpHeaders} [headers]
*/
export default class HTTP {
// #client;
// constructor() {
// // this.#client = { request };
// }
/**
* @param {string} url
* @param {PodiumHttpClientRequestOptions} options
* @returns {Promise<Pick<import('undici').Dispatcher.ResponseData, 'statusCode' | 'headers' | 'body'>>}
*/
async request(url, options) {
const u = new URL(url);
const client = new Client(u.origin, {
...options,
connect: { rejectUnauthorized: options.rejectUnauthorized },
});
const { statusCode, headers, body } = await client.request({
...options,
path: u.pathname,
});
const { statusCode, headers, body } = await request(
new URL(url),
options,
);
return { statusCode, headers, body };
}
}

@@ -8,5 +8,16 @@ /* eslint-disable no-plusplus */

/**
* @typedef {object} PodletClientCacheResolverOptions
* @property {import('abslog').AbstractLoggerOptions} [logger]
*/
export default class PodletClientCacheResolver {
#registry;
#log;
/**
* @constructor
* @param {import('ttl-mem-cache').default} registry
* @param {PodletClientCacheResolverOptions} options
*/
constructor(registry, options = {}) {

@@ -21,4 +32,10 @@ assert(

/**
* Loads the podlet's manifest from cache if not stale
*
* @param {import('./http-outgoing.js').default} outgoing
* @returns {Promise<import('./http-outgoing.js').default>}
*/
load(outgoing) {
return new Promise(resolve => {
return new Promise((resolve) => {
if (outgoing.status !== 'stale') {

@@ -38,4 +55,10 @@ const cached = this.#registry.get(outgoing.name);

/**
* Saves the podlet's manifest to the cache
*
* @param {import('./http-outgoing.js').default} outgoing
* @returns {Promise<import('./http-outgoing.js').default>}
*/
save(outgoing) {
return new Promise(resolve => {
return new Promise((resolve) => {
if (outgoing.status === 'fresh') {

@@ -61,2 +84,2 @@ this.#registry.set(

}
};
}

@@ -24,2 +24,9 @@ /* eslint-disable no-param-reassign */

/**
* @typedef {object} PodletClientContentResolverOptions
* @property {string} clientName
* @property {import('./http.js').default} [http]
* @property {import('abslog').AbstractLoggerOptions} [logger]
*/
export default class PodletClientContentResolver {

@@ -30,2 +37,8 @@ #log;

#http;
/**
* @constructor
* @param {PodletClientContentResolverOptions} options
*/
// @ts-expect-error Deliberate default empty options for better error messages
constructor(options = {}) {

@@ -59,2 +72,8 @@ this.#http = options.http || new HTTP();

/**
* Resolves/fetches the podlet's content.
*
* @param {import('./http-outgoing.js').default} outgoing
* @returns {Promise<import('./http-outgoing.js').default>}
*/
async resolve(outgoing) {

@@ -77,8 +96,8 @@ if (outgoing.kill && outgoing.throwable) {

outgoing.emit(
'beforeStream',
new Response({
js: utils.filterAssets("fallback", outgoing.manifest.js),
css: utils.filterAssets("fallback", outgoing.manifest.css),
}),
);
'beforeStream',
new Response({
js: utils.filterAssets('fallback', outgoing.manifest.js),
css: utils.filterAssets('fallback', outgoing.manifest.css),
}),
);
return outgoing;

@@ -101,8 +120,8 @@ }

outgoing.emit(
'beforeStream',
new Response({
js: utils.filterAssets("fallback", outgoing.manifest.js),
css: utils.filterAssets("fallback", outgoing.manifest.css),
}),
);
'beforeStream',
new Response({
js: utils.filterAssets('fallback', outgoing.manifest.js),
css: utils.filterAssets('fallback', outgoing.manifest.css),
}),
);
return outgoing;

@@ -121,4 +140,5 @@ }

outgoing.contentUri,
)
);
/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
const reqOptions = {

@@ -192,7 +212,13 @@ rejectUnauthorized: outgoing.rejectUnauthorized,

outgoing.emit(
'beforeStream',
new Response({
js: utils.filterAssets("fallback", outgoing.manifest.js),
css: utils.filterAssets("fallback", outgoing.manifest.css),
}),
'beforeStream',
new Response({
js: utils.filterAssets(
'fallback',
outgoing.manifest.js,
),
css: utils.filterAssets(
'fallback',
outgoing.manifest.css,
),
}),
);

@@ -237,2 +263,3 @@

statusCode,
// @ts-expect-error TODO: look into what happens if the podlet returns more than one location header
location: hdrs && hdrs.location,

@@ -246,4 +273,4 @@ };

headers: outgoing.headers,
js: utils.filterAssets("content", outgoing.manifest.js),
css: utils.filterAssets("content", outgoing.manifest.css),
js: utils.filterAssets('content', outgoing.manifest.js),
css: utils.filterAssets('content', outgoing.manifest.css),
redirect: outgoing.redirect,

@@ -272,6 +299,3 @@ }),

);
throw badGateway(
`Error reading content at ${uri}`,
error,
);
throw badGateway(`Error reading content at ${uri}`, error);
}

@@ -293,8 +317,8 @@

outgoing.emit(
'beforeStream',
new Response({
js: utils.filterAssets("fallback", outgoing.manifest.js),
css: utils.filterAssets("fallback", outgoing.manifest.css),
}),
);
'beforeStream',
new Response({
js: utils.filterAssets('fallback', outgoing.manifest.js),
css: utils.filterAssets('fallback', outgoing.manifest.css),
}),
);

@@ -301,0 +325,0 @@ return outgoing;

@@ -20,2 +20,9 @@ /* eslint-disable no-param-reassign */

/**
* @typedef {object} PodletClientFallbackResolverOptions
* @property {string} clientName
* @property {import('./http.js').default} [http]
* @property {import('abslog').AbstractLoggerOptions} [logger]
*/
export default class PodletClientFallbackResolver {

@@ -26,2 +33,8 @@ #log;

#http;
/**
* @constructor
* @param {PodletClientFallbackResolverOptions} options
*/
// @ts-expect-error Deliberate default empty options for better error messages
constructor(options = {}) {

@@ -55,2 +68,8 @@ this.#http = options.http || new HTTP();

/**
* Resolves/fetches the podlet's fallback.
*
* @param {import('./http-outgoing.js').default} outgoing
* @returns {Promise<import('./http-outgoing.js').default>}
*/
async resolve(outgoing) {

@@ -83,2 +102,3 @@ if (outgoing.status === 'cached') {

/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
const reqOptions = {

@@ -85,0 +105,0 @@ rejectUnauthorized: outgoing.rejectUnauthorized,

@@ -10,2 +10,8 @@ import Metrics from '@metrics/client';

/**
* @typedef {object} PodletClientResolverOptions
* @property {string} clientName
* @property {import('abslog').AbstractLoggerOptions} [logger]
*/
export default class PodletClientResolver {

@@ -17,2 +23,9 @@ #cache;

#metrics;
/**
* @constructor
* @param {import('ttl-mem-cache').default} registry
* @param {PodletClientResolverOptions} options
*/
// @ts-expect-error Deliberate default empty options for better error messages
constructor(registry, options = {}) {

@@ -27,3 +40,7 @@ assert(

this.#cache = new Cache(registry, options);
this.#manifest = new Manifest({ ...options, http });
this.#manifest = new Manifest({
clientName: options.clientName,
logger: options.logger,
http,
});
this.#fallback = new Fallback({ ...options, http });

@@ -33,3 +50,3 @@ this.#content = new Content({ ...options, http });

this.#metrics.on('error', error => {
this.#metrics.on('error', (error) => {
log.error(

@@ -50,10 +67,16 @@ 'Error emitted by metric stream in @podium/client module',

/**
* Resolve the podlet's manifest, fallback and content
*
* @param {import('./http-outgoing.js').default} outgoing
* @returns {Promise<import('./http-outgoing.js').default>}
*/
resolve(outgoing) {
return this.#cache
.load(outgoing)
.then(obj => this.#manifest.resolve(obj))
.then(obj => this.#fallback.resolve(obj))
.then(obj => this.#content.resolve(obj))
.then(obj => this.#cache.save(obj))
.then(obj => {
.then((obj) => this.#manifest.resolve(obj))
.then((obj) => this.#fallback.resolve(obj))
.then((obj) => this.#content.resolve(obj))
.then((obj) => this.#cache.save(obj))
.then((obj) => {
if (obj.success) {

@@ -66,8 +89,14 @@ return obj;

/**
* Refresh the podlet's cached manifest and fallback
*
* @param {import('./http-outgoing.js').default} outgoing
* @returns {Promise<boolean>} `true` if successful
*/
refresh(outgoing) {
return this.#manifest
.resolve(outgoing)
.then(obj => this.#fallback.resolve(obj))
.then(obj => this.#cache.save(obj))
.then(obj => !!obj.manifest.name);
.then((obj) => this.#fallback.resolve(obj))
.then((obj) => this.#cache.save(obj))
.then((obj) => !!obj.manifest.name);
}

@@ -78,2 +107,2 @@

}
};
}

@@ -23,2 +23,9 @@ /* eslint-disable no-param-reassign */

/**
* @typedef {object} PodletClientManifestResolverOptions
* @property {string} clientName
* @property {import('abslog').AbstractLoggerOptions} [logger]
* @property {import('./http.js').default} [http]
*/
export default class PodletClientManifestResolver {

@@ -29,2 +36,8 @@ #log;

#http;
/**
* @constructor
* @param {PodletClientManifestResolverOptions} options
*/
// @ts-expect-error Deliberate default empty options for better error messages
constructor(options = {}) {

@@ -58,2 +71,6 @@ this.#http = options.http || new HTTP();

/**
* @param {import('./http-outgoing.js').default} outgoing
* @returns {Promise<import('./http-outgoing.js').default>}
*/
async resolve(outgoing) {

@@ -68,2 +85,3 @@ if (outgoing.status === 'cached') {

/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
const reqOptions = {

@@ -88,7 +106,7 @@ rejectUnauthorized: outgoing.rejectUnauthorized,

try {
const {
statusCode,
headers: hdrs,
body,
} = await this.#http.request(outgoing.manifestUri, reqOptions);
const response = await this.#http.request(
outgoing.manifestUri,
reqOptions,
);
const { statusCode, headers: hdrs, body } = response;

@@ -113,3 +131,7 @@ // Remote responds but with an http error code

const manifest = validateManifest(await body.json());
const manifest = validateManifest(
/** @type {import("@podium/schemas").PodletManifestSchema} */ (
await body.json()
),
);

@@ -167,2 +189,3 @@ // Manifest validation error

// Construct css and js objects with absolute URIs
// @ts-expect-error We assign here what will end up as PodletManifest as defined in http-outgoing.js
manifest.value.css = manifest.value.css.map((obj) => {

@@ -176,2 +199,3 @@ obj.value = utils.uriRelativeToAbsolute(

// @ts-expect-error We assign here what will end up as PodletManifest as defined in http-outgoing.js
manifest.value.js = manifest.value.js.map((obj) => {

@@ -190,5 +214,5 @@ obj.value = utils.uriRelativeToAbsolute(

Array of proxy Objects ({ name: 'foo', target: '/' }) which is the new structure
wanted from version 6 and onwards so leave this structure untouched.
wanted from version 6 and onwards so leave this structure untouched.
If .proxy is an Object, the podlet are of version 5 or older where the key of the
If .proxy is an Object, the podlet are of version 5 or older where the key of the
Object is the key of the target. If so, convert the structure to the new structure

@@ -203,8 +227,8 @@ consisting of an Array of proxy Objects.

manifest.value.proxy = manifest.value.proxy.map((item) => ({
target: utils.uriRelativeToAbsolute(
item.target,
outgoing.manifestUri,
),
name: item.name,
}));
target: utils.uriRelativeToAbsolute(
item.target,
outgoing.manifestUri,
),
name: item.name,
}));
} else {

@@ -229,2 +253,3 @@ const proxies = [];

// @ts-expect-error We map to AssetCss and AssetJs above
outgoing.manifest = manifest.value;

@@ -231,0 +256,0 @@ outgoing.status = 'fresh';

@@ -14,2 +14,25 @@ /* eslint-disable no-param-reassign */

/**
* @typedef {object} RequestFilterOptions
* @property {string[]} [deviceType] List of values for the `x-podium-device-type` HTTP request header.
*/
/**
* @typedef {object} PodiumClientResourceOptions
* @property {import('abslog').AbstractLoggerOptions} [logger]
* @property {string} clientName
* @property {string} name
* @property {string} uri To the podlet's `manifest.json`
* @property {number} timeout In milliseconds
* @property {number} maxAge
* @property {number} [retries]
* @property {boolean} [throwable]
* @property {boolean} [redirectable]
* @property {boolean} [rejectUnauthorized]
* @property {import('http').Agent} [httpAgent]
* @property {import('https').Agent} [httpsAgent]
* @property {RequestFilterOptions} [excludeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
* @property {RequestFilterOptions} [includeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
*/
export default class PodiumClientResource {

@@ -20,2 +43,10 @@ #resolver;

#state;
/**
* @constructor
* @param {import('ttl-mem-cache').default} registry
* @param {import('./state.js').default} state
* @param {PodiumClientResourceOptions} options
*/
// @ts-expect-error Deliberate for better error messages
constructor(registry, state, options = {}) {

@@ -61,3 +92,18 @@ assert(

async fetch(incoming = {}, reqOptions = {}) {
/**
* Fetch the podlet's content, or fallback if the podlet is unavailable.
* The podlet response includes references to its CSS and JS assets which should be included in the final HTML document.
*
* @param {import('@podium/utils').HttpIncoming} incoming Instance of HttpIncoming
* @param {import('./http-outgoing.js').PodiumClientResourceOptions} [reqOptions={}] Optional parameters to the HTTP request, such as query parameters or HTTP request headers.
* @returns {Promise<import('./response.js').default>}
*
* @example
* ```js
* const incoming = res.locals.podium; // Express server example
* const header = await headerPodlet.fetch(incoming);
* incoming.podlets = [header]; // Register the podlet's JS and CSS assets with the layout's HTML template
* ```
*/
async fetch(incoming, reqOptions = {}) {
if (!utils.validateIncoming(incoming))

@@ -73,10 +119,10 @@ throw new TypeError(

*/
const exclucedDeviceTypes = this.#options.excludeBy.deviceType;
if (Array.isArray(exclucedDeviceTypes)) {
const excludedDeviceTypes = this.#options.excludeBy.deviceType;
if (Array.isArray(excludedDeviceTypes)) {
const deviceTypeHeader =
incoming.request.headers['x-podium-device-type'];
for (let i = 0; i < exclucedDeviceTypes.length; i += 1) {
for (let i = 0; i < excludedDeviceTypes.length; i += 1) {
const shouldSkip =
exclucedDeviceTypes[i] === deviceTypeHeader;
excludedDeviceTypes[i] === deviceTypeHeader;
if (shouldSkip) {

@@ -88,3 +134,3 @@ return new Response({

js: [],
redirect: '',
redirect: null,
});

@@ -115,3 +161,3 @@ }

js: [],
redirect: '',
redirect: null,
});

@@ -152,3 +198,10 @@ }

stream(incoming = {}, reqOptions = {}) {
/**
* Stream the podlet's content, or fallback if the podlet is unavailable.
*
* @param {import('@podium/utils').HttpIncoming} incoming
* @param {import('./http-outgoing.js').PodiumClientResourceOptions} [reqOptions={}]
* @returns {import('./http-outgoing.js').default}
*/
stream(incoming, reqOptions = {}) {
if (!utils.validateIncoming(incoming))

@@ -164,3 +217,10 @@ throw new TypeError(

refresh(incoming = {}, reqOptions = {}) {
/**
* Refresh the podlet's manifest and fallback in the cache.
*
* @param {import('@podium/utils').HttpIncoming} [incoming]
* @param {import('./http-outgoing.js').PodiumClientResourceOptions} [reqOptions={}]
* @returns {Promise<boolean>} `true` if succesful
*/
refresh(incoming, reqOptions = {}) {
const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming);

@@ -167,0 +227,0 @@ this.#state.setInitializingState();

const inspect = Symbol.for('nodejs.util.inspect.custom');
/**
* @typedef {object} PodiumClientResponseOptions
* @property {string} [content]
* @property {object} [headers]
* @property {Array<import('@podium/utils').AssetJs>} [js]
* @property {Array<import('@podium/utils').AssetCss>} [css]
* @property {import('./http-outgoing.js').PodiumRedirect | null} [redirect]
*/
export default class PodiumClientResponse {

@@ -9,2 +18,7 @@ #redirect;

#js;
/**
* @constructor
* @param {PodiumClientResponseOptions} options
*/
constructor({

@@ -49,4 +63,4 @@ content = '',

headers: this.headers,
css: this.css,
js: this.js,
css: this.css.map((a) => a.toJSON()),
js: this.js.map((a) => a.toJSON()),
};

@@ -76,2 +90,2 @@ }

}
};
}

@@ -5,8 +5,21 @@ import EventEmitter from 'events';

/**
* @typedef {object} PodiumClientStateOptions
* @property {number} [resolveThreshold=10000]
* @property {number} [resolveMax=240000]
*/
export default class PodiumClientState extends EventEmitter {
/** @type {NodeJS.Timeout | undefined} */
#thresholdTimer;
#threshold;
/** @type {NodeJS.Timeout | undefined} */
#maxTimer;
/** @type {"instantiated" | "stable" | "initializing" | "unhealthy" | "unstable"} */
#state;
#max;
/**
* @param {PodiumClientStateOptions} [options]
*/
constructor({

@@ -117,2 +130,2 @@ resolveThreshold = 10 * 1000,

}
};
}
/**
* Checks if a header Oject has a header.
* Will return true if the header exist and are not an empty
* String or a String of whitespace.
* Checks if a header object has a header.
* Will return true if the header exist and is not an empty
* string or a string of whitespace.
*
* @param {Object} headers A headers object
* @param {String} header A header value
*
* @returns {Boolean}
* @param {object} headers
* @param {string} header
* @returns {boolean}
*/
export const isHeaderDefined = (headers, header) => {

@@ -24,8 +22,6 @@ if (headers[header] === undefined || headers[header].trim() === '') {

*
* @param {Object} item A changelog event object
*
* @returns {Boolean}
* @param {object} item A changelog event object
* @returns {boolean}
*/
export const hasManifestChange = item => {
export const hasManifestChange = (item) => {
const oldVersion = item.oldVal ? item.oldVal.version : '';

@@ -36,18 +32,13 @@ const newVersion = item.newVal ? item.newVal.version : '';

/**
* Check if a value is a HttpIncoming object or not. If not, it
* Check if a value is a Podium HttpIncoming object or not. If not, it
* assume the incoming value is a context
*
* @param {Object} incoming A object
*
* @returns {HttpIncoming}
* @param {object} incoming
* @returns {boolean}
*/
export const validateIncoming = (incoming = {}) => (Object.prototype.toString.call(incoming) === '[object PodiumHttpIncoming]');
export const validateIncoming = (incoming = {}) =>
Object.prototype.toString.call(incoming) === '[object PodiumHttpIncoming]';
/**
* @typedef {import("@podium/utils").AssetCss | import("@podium/utils").AssetJs} Asset
*/
/**
* Filter assets array based on scope.

@@ -58,5 +49,6 @@ * If scope property is not present, asset will be included (backwards compatibility)

* If scope is set to "fallback" and asset scope property is set to "content", asset will not be included
* @template {import("@podium/utils").AssetCss | import("@podium/utils").AssetJs} T[]
* @param {"content" | "fallback" | "all"} scope
* @param {Asset[]} assets
* @returns {Asset[]}
* @param {T[]} assets
* @returns {T[]}
*

@@ -75,5 +67,11 @@ * @example

// if a non array value is given, throw
if (!Array.isArray(assets)) throw new TypeError(`Asset definition must be of type array. Got ${typeof assets}`);
if (!Array.isArray(assets))
throw new TypeError(
`Asset definition must be of type array. Got ${typeof assets}`,
);
// filter the array of asset definitions to matchin scope or anything with all. Treat no scope the same as "all" for backwards compatibility.
return assets.filter(asset => !asset.scope || asset.scope === scope || asset.scope === "all");
return assets.filter(
(asset) =>
!asset.scope || asset.scope === scope || asset.scope === 'all',
);
};
{
"name": "@podium/client",
"version": "5.1.0-beta.1",
"version": "5.1.0",
"type": "module",

@@ -24,35 +24,36 @@ "license": "MIT",

"CHANGELOG.md",
"client.d.ts",
"README.md",
"LICENSE",
"dist",
"lib"
"lib",
"types"
],
"main": "./lib/client.js",
"types": "client.d.ts",
"types": "./types/client.d.ts",
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"test": "tap --disable-coverage --allow-empty-coverage"
"test": "tap tests/*.js --disable-coverage --allow-empty-coverage && tsc --project tsconfig.test.json",
"types": "tsc --declaration --emitDeclarationOnly"
},
"dependencies": {
"@hapi/boom": "^10.0.0",
"@hapi/boom": "10.0.1",
"@metrics/client": "2.5.2",
"@podium/schemas": "5.0.0",
"@podium/utils": "5.0.2",
"abslog": "2.4.0",
"@podium/schemas": "5.0.5",
"@podium/utils": "5.0.7",
"abslog": "2.4.4",
"http-cache-semantics": "^4.0.3",
"lodash.clonedeep": "^4.5.0",
"ttl-mem-cache": "4.1.0",
"undici": "^6.0.0"
"undici": "6.19.2"
},
"devDependencies": {
"@babel/eslint-parser": "7.24.7",
"@podium/test-utils": "2.5.2",
"@semantic-release/changelog": "6.0.3",
"@semantic-release/git": "10.0.1",
"@babel/eslint-parser": "7.23.10",
"@semantic-release/github": "9.2.6",
"@semantic-release/npm": "11.0.2",
"@semantic-release/release-notes-generator": "12.1.0",
"@semantic-release/github": "10.0.6",
"@semantic-release/npm": "12.0.1",
"@semantic-release/release-notes-generator": "13.0.0",
"@sinonjs/fake-timers": "11.2.2",
"@types/readable-stream": "4.0.14",
"benchmark": "2.1.4",

@@ -64,10 +65,11 @@ "eslint": "8.57.0",

"eslint-plugin-prettier": "5.1.3",
"express": "4.18.3",
"get-stream": "8.0.1",
"express": "4.19.2",
"get-stream": "9.0.1",
"http-proxy": "1.18.1",
"is-stream": "3.0.0",
"prettier": "3.2.5",
"semantic-release": "23.0.2",
"tap": "18.7.0"
"is-stream": "4.0.1",
"prettier": "3.3.2",
"semantic-release": "23.1.1",
"tap": "18.7.2",
"typescript": "5.4.5"
}
}

@@ -153,14 +153,12 @@ # @podium/client v5

- `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.
- `resolveJs` - {Boolean} - Defines whether to resolve a relative JS uri for a component to be an absolute uri. Defaults to `false` - Optional.
- `resolveCss` - {Boolean} - Defines whether to resolve a relative CSS uri for a component to be an absolute uri. Defaults to `false` - Optional.
- `excludeBy` - {Object} - Lets you define a set of rules where a `fetch` call will not be resolved if it matches. - Optional.
- `includeBy` - {Object} - Inverse of `excludeBy`. - Optional.
- `includeBy` - {Object} - Inverse of `excludeBy`. Setting both at the same time will throw. - Optional.
##### `excludeBy` and `includeBy`
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.
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.
Allowed options:
- `deviceType` - {Array<String>} - List of values for the `x-podium-device-type` header. - Optional.
- `deviceType` - {Array<String>} - List of values for the `x-podium-device-type` header. - Optional.

@@ -177,3 +175,3 @@ Example: exclude a header and footer in a hybrid web view.

excludeBy: {
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.
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.
},

@@ -467,2 +465,3 @@ });

For example, if the podlet manifest contains a JavaScript asset definition of the form:
```

@@ -473,6 +472,9 @@ {

```
And the client performs a fetch like so:
```js
const result = await component.fetch();
```
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.

@@ -479,0 +481,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc