@podium/client 5.0.34 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [5.1.0](https://github.com/podium-lib/client/compare/v5.0.34...v5.1.0) (2024-07-02)
2
+
3
+
4
+ ### Features
5
+
6
+ * conditional fetch by deviceType header ([de4e9c4](https://github.com/podium-lib/client/commit/de4e9c47eea6baf07f2dc72465699a080b6aace1))
7
+
1
8
  ## [5.0.34](https://github.com/podium-lib/client/compare/v5.0.33...v5.0.34) (2024-06-18)
2
9
 
3
10
 
package/README.md CHANGED
@@ -150,8 +150,31 @@ 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.
153
+ - `excludeBy` - {Object} - Lets you define a set of rules where a `fetch` call will not be resolved if it matches. - Optional.
154
+ - `includeBy` - {Object} - Inverse of `excludeBy`. Setting both at the same time will throw. - Optional.
155
+
156
+ ##### `excludeBy` and `includeBy`
157
+
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.
159
+
160
+ Allowed options:
161
+
162
+ - `deviceType` - {Array<String>} - List of values for the `x-podium-device-type` header. - Optional.
163
+
164
+ Example: exclude a header and footer in a hybrid web view.
165
+
166
+ ```js
167
+ import Client from '@podium/client';
168
+ const client = new Client();
169
+
170
+ const footer = client.register({
171
+ uri: 'http://footer.site.com/manifest.json',
172
+ name: 'footer',
173
+ excludeBy: {
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.
175
+ },
176
+ });
177
+ ```
155
178
 
156
179
  ### .js()
157
180
 
@@ -437,15 +460,19 @@ stream.once('beforeStream', (data) => {
437
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).
438
461
 
439
462
  For example, if the podlet manifest contains a JavaScript asset definition of the form:
463
+
440
464
  ```
441
465
  {
442
466
  js: [{ value: "https://assets.com/path/to/file.js", scope: "content" }],
443
467
  }
444
468
  ```
469
+
445
470
  And the client performs a fetch like so:
471
+
446
472
  ```js
447
473
  const result = await component.fetch();
448
474
  ```
475
+
449
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.
450
477
 
451
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
@@ -60,6 +60,8 @@ const MAX_AGE = Infinity;
60
60
  * @property {number} [timeout=1000] In milliseconds, the amount of time to wait before serving fallback.
61
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
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.
63
65
  */
64
66
 
65
67
  export default class PodiumClient extends EventEmitter {
@@ -94,6 +96,8 @@ export default class PodiumClient extends EventEmitter {
94
96
  rejectUnauthorized: REJECT_UNAUTHORIZED,
95
97
  httpAgent: HTTP_AGENT,
96
98
  httpsAgent: HTTPS_AGENT,
99
+ includeBy: undefined,
100
+ excludeBy: undefined,
97
101
  ...options,
98
102
  };
99
103
 
@@ -191,6 +195,12 @@ export default class PodiumClient extends EventEmitter {
191
195
  );
192
196
  }
193
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
+
194
204
  const resourceOptions = {
195
205
  rejectUnauthorized: this.#options.rejectUnauthorized,
196
206
  clientName: this.#options.name,
@@ -200,8 +210,11 @@ export default class PodiumClient extends EventEmitter {
200
210
  maxAge: this.#options.maxAge,
201
211
  httpsAgent: this.#options.httpsAgent,
202
212
  httpAgent: this.#options.httpAgent,
213
+ includeBy: this.#options.includeBy,
214
+ excludeBy: this.#options.excludeBy,
203
215
  ...options,
204
216
  };
217
+
205
218
  const resource = new Resource(
206
219
  this.#registry,
207
220
  this.#state,
package/lib/resource.js CHANGED
@@ -11,6 +11,11 @@ import * as utils from './utils.js';
11
11
 
12
12
  const inspect = Symbol.for('nodejs.util.inspect.custom');
13
13
 
14
+ /**
15
+ * @typedef {object} RequestFilterOptions
16
+ * @property {string[]} [deviceType] List of values for the `x-podium-device-type` HTTP request header.
17
+ */
18
+
14
19
  /**
15
20
  * @typedef {object} PodiumClientResourceOptions
16
21
  * @property {import('abslog').AbstractLoggerOptions} [logger]
@@ -25,6 +30,8 @@ const inspect = Symbol.for('nodejs.util.inspect.custom');
25
30
  * @property {boolean} [rejectUnauthorized]
26
31
  * @property {import('http').Agent} [httpAgent]
27
32
  * @property {import('https').Agent} [httpsAgent]
33
+ * @property {RequestFilterOptions} [excludeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
34
+ * @property {RequestFilterOptions} [includeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
28
35
  */
29
36
 
30
37
  export default class PodiumClientResource {
@@ -102,6 +109,56 @@ export default class PodiumClientResource {
102
109
  );
103
110
  const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming);
104
111
 
112
+ if (this.#options.excludeBy) {
113
+ /**
114
+ * @type {string[] | undefined}
115
+ */
116
+ const excludedDeviceTypes = this.#options.excludeBy.deviceType;
117
+ if (Array.isArray(excludedDeviceTypes)) {
118
+ const deviceTypeHeader =
119
+ incoming.request.headers['x-podium-device-type'];
120
+
121
+ for (let i = 0; i < excludedDeviceTypes.length; i += 1) {
122
+ const shouldSkip =
123
+ excludedDeviceTypes[i] === deviceTypeHeader;
124
+ if (shouldSkip) {
125
+ return new Response({
126
+ headers: {},
127
+ content: '',
128
+ css: [],
129
+ js: [],
130
+ redirect: null,
131
+ });
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ if (this.#options.includeBy) {
138
+ /**
139
+ * @type {string[] | undefined}
140
+ */
141
+ const includeDeviceTypes = this.#options.includeBy.deviceType;
142
+ if (Array.isArray(includeDeviceTypes)) {
143
+ const deviceTypeHeader =
144
+ incoming.request.headers['x-podium-device-type'];
145
+
146
+ const shouldRequest =
147
+ !deviceTypeHeader ||
148
+ includeDeviceTypes.includes(deviceTypeHeader);
149
+
150
+ if (!shouldRequest) {
151
+ return new Response({
152
+ headers: {},
153
+ content: '',
154
+ css: [],
155
+ js: [],
156
+ redirect: null,
157
+ });
158
+ }
159
+ }
160
+ }
161
+
105
162
  this.#state.setInitializingState();
106
163
 
107
164
  const { manifest, headers, redirect, isFallback } =
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@podium/client",
3
- "version": "5.0.34",
3
+ "version": "5.1.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -46,11 +46,11 @@
46
46
  "undici": "6.19.2"
47
47
  },
48
48
  "devDependencies": {
49
- "@babel/eslint-parser": "7.24.6",
49
+ "@babel/eslint-parser": "7.24.7",
50
50
  "@podium/test-utils": "2.5.2",
51
51
  "@semantic-release/changelog": "6.0.3",
52
52
  "@semantic-release/git": "10.0.1",
53
- "@semantic-release/github": "10.0.5",
53
+ "@semantic-release/github": "10.0.6",
54
54
  "@semantic-release/npm": "12.0.1",
55
55
  "@semantic-release/release-notes-generator": "13.0.0",
56
56
  "@sinonjs/fake-timers": "11.2.2",
@@ -65,7 +65,7 @@
65
65
  "get-stream": "9.0.1",
66
66
  "http-proxy": "1.18.1",
67
67
  "is-stream": "4.0.1",
68
- "prettier": "3.3.0",
68
+ "prettier": "3.3.2",
69
69
  "semantic-release": "23.1.1",
70
70
  "tap": "18.7.2",
71
71
  "typescript": "5.4.5"
package/types/client.d.ts CHANGED
@@ -27,6 +27,8 @@
27
27
  * @property {number} [timeout=1000] In milliseconds, the amount of time to wait before serving fallback.
28
28
  * @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.
29
29
  * @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.
30
+ * @property {import('./resource.js').RequestFilterOptions} [excludeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
31
+ * @property {import('./resource.js').RequestFilterOptions} [includeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
30
32
  */
31
33
  export default class PodiumClient extends EventEmitter<[never]> {
32
34
  /**
@@ -105,6 +107,14 @@ export type RegisterOptions = {
105
107
  * 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.
106
108
  */
107
109
  redirectable?: boolean;
110
+ /**
111
+ * Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
112
+ */
113
+ excludeBy?: import('./resource.js').RequestFilterOptions;
114
+ /**
115
+ * Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
116
+ */
117
+ includeBy?: import('./resource.js').RequestFilterOptions;
108
118
  };
109
119
  import EventEmitter from 'events';
110
120
  import Metrics from '@metrics/client';
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @typedef {object} RequestFilterOptions
3
+ * @property {string[]} [deviceType] List of values for the `x-podium-device-type` HTTP request header.
4
+ */
1
5
  /**
2
6
  * @typedef {object} PodiumClientResourceOptions
3
7
  * @property {import('abslog').AbstractLoggerOptions} [logger]
@@ -12,6 +16,8 @@
12
16
  * @property {boolean} [rejectUnauthorized]
13
17
  * @property {import('http').Agent} [httpAgent]
14
18
  * @property {import('https').Agent} [httpsAgent]
19
+ * @property {RequestFilterOptions} [excludeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
20
+ * @property {RequestFilterOptions} [includeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
15
21
  */
16
22
  export default class PodiumClientResource {
17
23
  /**
@@ -64,6 +70,12 @@ export default class PodiumClientResource {
64
70
  get [Symbol.toStringTag](): string;
65
71
  #private;
66
72
  }
73
+ export type RequestFilterOptions = {
74
+ /**
75
+ * List of values for the `x-podium-device-type` HTTP request header.
76
+ */
77
+ deviceType?: string[];
78
+ };
67
79
  export type PodiumClientResourceOptions = {
68
80
  logger?: import('abslog').AbstractLoggerOptions;
69
81
  clientName: string;
@@ -83,6 +95,14 @@ export type PodiumClientResourceOptions = {
83
95
  rejectUnauthorized?: boolean;
84
96
  httpAgent?: import('http').Agent;
85
97
  httpsAgent?: import('https').Agent;
98
+ /**
99
+ * Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
100
+ */
101
+ excludeBy?: RequestFilterOptions;
102
+ /**
103
+ * Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
104
+ */
105
+ includeBy?: RequestFilterOptions;
86
106
  };
87
107
  import Metrics from '@metrics/client';
88
108
  declare const inspect: unique symbol;