@johntalton/http-util 5.1.6 → 6.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.
Files changed (84) hide show
  1. package/README.md +54 -7
  2. package/package.json +26 -6
  3. package/src/body.js +8 -8
  4. package/src/{response/defs.js → defs.js} +35 -1
  5. package/src/{accept-encoding.js → headers/accept-encoding.js} +11 -14
  6. package/src/{accept-language.js → headers/accept-language.js} +14 -9
  7. package/src/headers/accept.js +86 -0
  8. package/src/{cache-control.js → headers/cache-control.js} +0 -6
  9. package/src/{clear-site-data.js → headers/clear-site-data.js} +4 -10
  10. package/src/headers/client-hints.js +88 -0
  11. package/src/{conditional.js → headers/conditional.js} +190 -117
  12. package/src/headers/content-disposition.js +44 -0
  13. package/src/{content-range.js → headers/content-range.js} +1 -18
  14. package/src/headers/content-type.js +101 -0
  15. package/src/{forwarded.js → headers/forwarded.js} +8 -56
  16. package/src/{index.js → headers/index.js} +4 -2
  17. package/src/headers/link.js +34 -0
  18. package/src/{multipart.js → headers/multipart.js} +22 -13
  19. package/src/{preference.js → headers/preference.js} +3 -58
  20. package/src/{range.js → headers/range.js} +4 -32
  21. package/src/{rate-limit.js → headers/rate-limit.js} +6 -1
  22. package/src/{server-timing.js → headers/server-timing.js} +3 -16
  23. package/src/headers/strict-transport-security.js +39 -0
  24. package/src/{accept-util.js → headers/util/accept-util.js} +8 -14
  25. package/src/headers/util/index.js +7 -0
  26. package/src/headers/util/kvp.js +79 -0
  27. package/src/headers/util/mime.js +77 -0
  28. package/src/headers/util/whitespace.js +8 -0
  29. package/src/{www-authenticate.js → headers/www-authenticate.js} +1 -1
  30. package/src/response/{accepted.js → 2xx/accepted.js} +2 -2
  31. package/src/response/2xx/bytes.js +62 -0
  32. package/src/response/2xx/created.js +49 -0
  33. package/src/response/2xx/json.js +60 -0
  34. package/src/response/2xx/no-content.js +45 -0
  35. package/src/response/2xx/partial-content.js +101 -0
  36. package/src/response/{preflight.js → 2xx/preflight.js} +29 -10
  37. package/src/response/{sse.js → 2xx/sse.js} +2 -2
  38. package/src/response/{trace.js → 2xx/trace.js} +3 -3
  39. package/src/response/3xx/found.js +23 -0
  40. package/src/response/{moved-permanently.js → 3xx/moved-permanently.js} +2 -2
  41. package/src/response/{multiple-choices.js → 3xx/multiple-choices.js} +2 -3
  42. package/src/response/3xx/not-modified.js +59 -0
  43. package/src/response/{permanent-redirect.js → 3xx/permanent-redirect.js} +2 -2
  44. package/src/response/{see-other.js → 3xx/see-other.js} +2 -2
  45. package/src/response/{temporary-redirect.js → 3xx/temporary-redirect.js} +2 -2
  46. package/src/response/4xx/bad-request.js +19 -0
  47. package/src/response/{conflict.js → 4xx/conflict.js} +2 -2
  48. package/src/response/{content-too-large.js → 4xx/content-too-large.js} +2 -2
  49. package/src/response/{forbidden.js → 4xx/forbidden.js} +3 -2
  50. package/src/response/{gone.js → 4xx/gone.js} +2 -2
  51. package/src/response/{im-a-teapot.js → 4xx/im-a-teapot.js} +2 -2
  52. package/src/response/{not-acceptable.js → 4xx/not-acceptable.js} +14 -3
  53. package/src/response/4xx/not-allowed.js +34 -0
  54. package/src/response/{not-found.js → 4xx/not-found.js} +3 -3
  55. package/src/response/4xx/payment-required.js +17 -0
  56. package/src/response/4xx/precondition-failed.js +45 -0
  57. package/src/response/{range-not-satisfiable.js → 4xx/range-not-satisfiable.js} +15 -4
  58. package/src/response/{timeout.js → 4xx/timeout.js} +2 -2
  59. package/src/response/{too-many-requests.js → 4xx/too-many-requests.js} +22 -5
  60. package/src/response/{unauthorized.js → 4xx/unauthorized.js} +5 -5
  61. package/src/response/{unprocessable.js → 4xx/unprocessable.js} +2 -2
  62. package/src/response/{unsupported-media.js → 4xx/unsupported-media.js} +21 -4
  63. package/src/response/{error.js → 5xx/error.js} +3 -3
  64. package/src/response/{insufficient-storage.js → 5xx/insufficient-storage.js} +2 -2
  65. package/src/response/{not-implemented.js → 5xx/not-implemented.js} +4 -4
  66. package/src/response/{unavailable.js → 5xx/unavailable.js} +16 -4
  67. package/src/response/header-util.js +2 -2
  68. package/src/response/index.js +39 -35
  69. package/src/response/response.js +40 -34
  70. package/src/response/send-util.js +32 -21
  71. package/src/accept.js +0 -122
  72. package/src/content-disposition.js +0 -57
  73. package/src/content-type.js +0 -148
  74. package/src/link.js +0 -35
  75. package/src/response/bytes.js +0 -27
  76. package/src/response/created.js +0 -28
  77. package/src/response/json.js +0 -28
  78. package/src/response/no-content.js +0 -25
  79. package/src/response/not-allowed.js +0 -23
  80. package/src/response/not-modified.js +0 -35
  81. package/src/response/partial-content.js +0 -71
  82. package/src/response/precondition-failed.js +0 -16
  83. /package/src/{fetch-metadata.js → headers/fetch-metadata.js} +0 -0
  84. /package/src/{quote.js → headers/util/quote.js} +0 -0
package/README.md CHANGED
@@ -6,7 +6,7 @@ Set of utilities to aid in building from-scratch [node:http2](https://nodejs.org
6
6
  - [Stream Response methods](#response)
7
7
  - [Body parser](#body)
8
8
 
9
- ## Header Parsers
9
+ ## Header Parsers / Encoders
10
10
  ### From Client:
11
11
  - Accept Encoding - `parse` and `select` based on server/client values
12
12
  - Accept Language - `parse` and `select`
@@ -17,10 +17,21 @@ Set of utilities to aid in building from-scratch [node:http2](https://nodejs.org
17
17
  - Multipart - parse into `FormData`
18
18
  - Content Disposition - for use inside of Multipart
19
19
  - Conditionals - Etag / FixDate for IfMatch, IfModifiedSince etc
20
+ - Client Hints
21
+ - Forwarded
22
+ - Applied Preferences
20
23
 
21
24
  ### Server Sent:
22
25
  - Rate Limit
23
26
  - Server Timing
27
+ - Cache Control
28
+ - Clear Site Data
29
+ - Conditional (Etag / IMF Date)
30
+ - Content Range
31
+ - Preferences
32
+ - WWW Auth
33
+ - STS
34
+
24
35
 
25
36
 
26
37
  ```javascript
@@ -49,11 +60,12 @@ All responders take in a `stream` as well as a metadata object to hint on server
49
60
  - [`sendContentTooLarge`](#)
50
61
  - [`sendCreated`](#responsecreated)
51
62
  - [`sendError`](#responseerror) - 500
52
- - [`sendGone`](#)
63
+ - [`sendForbidden`](#responseforbidden)
64
+ - [`sendGone`](#responsegone)
53
65
  - [`sendImATeapot`](#)
54
66
  - [`sendInsufficientStorage`](#)
55
67
  - [`sendJSON_Encoded`](#responsejson) - Standard Ok response with encoding
56
- - [`sendMovedPermanently`](#)
68
+ - [`sendMovedPermanently`](#responsemovedpermanently)
57
69
  - [`sendMultipleChoices`](#)
58
70
  - [`sendNoContent`](#responsenocontent)
59
71
  - [`sendNotAcceptable`](#responsenotacceptable)
@@ -62,9 +74,10 @@ All responders take in a `stream` as well as a metadata object to hint on server
62
74
  - [`sendNotImplemented`](#responsenotimplemented)
63
75
  - [`sendNotModified`](#responsenotmodified)
64
76
  - [`sendPartialContent`](#)
77
+ - [`sendPermanentRedirect`](#)
65
78
  - [`sendPreconditionFailed`](#responsepreconditionfailed)
66
79
  - [`sendPreflight`](#responsepreflight) - Response to OPTIONS with CORS headers
67
- - [`sendRangeNotSatisfiable`](#)
80
+ - [`sendRangeNotSatisfiable`](#responserangenotsatisfiable)
68
81
  - [`sendSeeOther`](#)
69
82
  - [`sendTemporaryRedirect`](#)
70
83
  - [`sendTimeout`](#responsetimeout)
@@ -145,8 +158,8 @@ Parameters:
145
158
 
146
159
  Parameters:
147
160
  - stream
148
- - location
149
- - etag
161
+ - location - URL to newly create object
162
+ - etag - etag of the related object
150
163
  - meta
151
164
 
152
165
  Additional Exposed Headers:
@@ -158,6 +171,27 @@ Parameters:
158
171
  - stream
159
172
  - meta
160
173
 
174
+ ### Response.forbidden
175
+
176
+ Parameters:
177
+ - stream
178
+ - meta
179
+
180
+ ### Response.gone
181
+
182
+ Parameters:
183
+ - stream
184
+ - meta
185
+
186
+ ### Response.movedPermanently
187
+
188
+ Parameters:
189
+ - steam
190
+ - location
191
+ - meta
192
+
193
+ Additional Exposed Headers:
194
+ - location
161
195
 
162
196
  ### Response.json
163
197
 
@@ -186,7 +220,7 @@ Parameters:
186
220
 
187
221
  Parameters:
188
222
  - stream
189
- - supportedTypes
223
+ - supportedTypes - array of supported types (mime/lang/encodings etc)
190
224
  - meta
191
225
 
192
226
  ### Response.notAllowed
@@ -237,10 +271,22 @@ Parameters:
237
271
  - stream
238
272
  - methods
239
273
  - supportedQueryTypes - undefined | Array of supported types
274
+ - acceptRanges
240
275
  - meta
241
276
 
242
277
  Additional Exposed Headers:
243
278
  - accept-query
279
+ - accept-ranges
280
+
281
+ ### Response.rangeNotSatisfiable
282
+
283
+ Parameters:
284
+ - stream
285
+ - directive - specify size of acceptable range of bytes
286
+ - meta
287
+
288
+ Additional Exposed Headers:
289
+ - content-range
244
290
 
245
291
  ### Response.sse
246
292
 
@@ -282,6 +328,7 @@ Parameters:
282
328
 
283
329
  Parameters:
284
330
  - stream
331
+ - challenge
285
332
  - meta
286
333
 
287
334
 
package/package.json CHANGED
@@ -1,23 +1,43 @@
1
1
  {
2
2
  "name": "@johntalton/http-util",
3
- "version": "5.1.6",
3
+ "version": "6.1.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
+ "imports": {
7
+ "#util": "./src/headers/util/index.js"
8
+ },
6
9
  "exports": {
7
- ".": "./src/index.js",
8
- "./headers": "./src/index.js",
10
+ ".": "./src/headers/index.js",
11
+ "./headers": "./src/headers/index.js",
9
12
  "./body": "./src/body.js",
10
13
  "./response": "./src/response/index.js",
11
- "./response/object": "./src/response/response.js"
14
+ "./response/object": "./src/response/response.js",
15
+ "./util": "./src/headers/util/index.js"
12
16
  },
13
17
  "files": [
14
18
  "src/*.js",
15
- "src/response/*.js"
19
+ "src/response/**/*.js",
20
+ "src/headers/*.js",
21
+ "src/headers/util/*.js"
16
22
  ],
23
+ "scripts": {
24
+ "lint": "biome check",
25
+ "test": "node --test test/**/*.test.js",
26
+ "coverage": "c8 --src src --check-coverage --statements 90 --branches 90 --functions 90 --lines 90 -r lcovonly -r text node --test test/**/*.test.js"
27
+ },
17
28
  "repository": {
18
29
  "url": "https://github.com/johntalton/http-util"
19
30
  },
20
31
  "dependencies": {
21
32
  "@johntalton/sse-util": "^1.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@biomejs/biome": "^2.4.15",
36
+ "c8": "^11.0.0"
37
+ },
38
+ "overrides": {
39
+ "c8": {
40
+ "yargs": "^18.0.0"
41
+ }
22
42
  }
23
- }
43
+ }
package/src/body.js CHANGED
@@ -2,28 +2,28 @@ import {
2
2
  CHARSET_UTF8,
3
3
  MIME_TYPE_MULTIPART_FORM_DATA,
4
4
  MIME_TYPE_URL_FORM_DATA
5
- } from './content-type.js'
6
- import { Multipart } from './multipart.js'
5
+ } from './headers/content-type.js'
6
+ import { Multipart } from './headers/multipart.js'
7
7
 
8
8
  export const BYTE_PER_K = 1024
9
9
  export const DEFAULT_BYTE_LIMIT = BYTE_PER_K * BYTE_PER_K //
10
10
 
11
11
  /** @import { Readable } from 'node:stream' */
12
- /** @import { ContentType } from './content-type.js' */
12
+ /** @import { ContentTypeItem } from './headers/content-type.js' */
13
13
 
14
14
  /**
15
15
  * @typedef {Object} BodyOptions
16
16
  * @property {AbortSignal|undefined} [signal]
17
17
  * @property {number|undefined} [byteLimit]
18
18
  * @property {number|undefined} [contentLength]
19
- * @property {ContentType|undefined} [contentType]
19
+ * @property {ContentTypeItem|undefined} [contentType]
20
20
  */
21
21
 
22
22
  /**
23
23
  * @typedef {Object} BodyFuture
24
24
  * @property {number} duration
25
25
  * @property {ReadableStream} body
26
- * @property {ContentType|undefined} contentType
26
+ * @property {ContentTypeItem|undefined} contentType
27
27
  * @property { (mimetype: string) => Promise<Blob> } blob
28
28
  * @property { () => Promise<ArrayBufferLike> } arrayBuffer
29
29
  * @property { () => Promise<Uint8Array> } bytes
@@ -277,7 +277,7 @@ export async function bodyJSON(reader, charset) {
277
277
 
278
278
  /**
279
279
  * @param {ReadableStream} reader
280
- * @param {ContentType} contentType
280
+ * @param {ContentTypeItem} contentType
281
281
  */
282
282
  export async function _bodyFormData_Multipart(reader, contentType) {
283
283
  const MULTIPART_FORM_DATA_BOUNDARY_PARAMETER = 'boundary'
@@ -291,7 +291,7 @@ export async function _bodyFormData_Multipart(reader, contentType) {
291
291
 
292
292
  /**
293
293
  * @param {ReadableStream} reader
294
- * @param {ContentType} contentType
294
+ * @param {ContentTypeItem} contentType
295
295
  */
296
296
  export async function _bodyFormData_URL(reader, contentType) {
297
297
  const text = await bodyText(reader, contentType.charset)
@@ -307,7 +307,7 @@ export async function _bodyFormData_URL(reader, contentType) {
307
307
 
308
308
  /**
309
309
  * @param {ReadableStream} reader
310
- * @param {ContentType|undefined} contentType
310
+ * @param {ContentTypeItem|undefined} contentType
311
311
  */
312
312
  export async function bodyFormData(reader, contentType) {
313
313
  if(contentType === undefined) { throw new Error('undefined content type for form data') }
@@ -26,7 +26,14 @@ export const RANGE_UNITS_BYTES = 'bytes'
26
26
  /** @type {'none'} */
27
27
  export const RANGE_UNITS_NONE = 'none'
28
28
 
29
- /** @import { TimingsInfo } from '../server-timing.js' */
29
+
30
+ /** @import { TimingsInfo } from './headers/server-timing.js' */
31
+ /** @import { EtagItem, IMFFixDateInput, } from './headers/conditional.js' */
32
+ /** @import { CacheControlOptions } from './headers/cache-control.js' */
33
+ /** @import { ContentRangeDirective } from './headers/content-range.js' */
34
+ /** @import { RateLimitPolicyInfo, RateLimitInfo } from './headers/rate-limit.js' */
35
+
36
+ /** @typedef {RANGE_UNITS_BYTES | RANGE_UNITS_NONE} AcceptRangeUnits */
30
37
 
31
38
  /**
32
39
  * @typedef {`X-${string}`} CustomHeaderKey
@@ -46,3 +53,30 @@ export const RANGE_UNITS_NONE = 'none'
46
53
  * @property {boolean} [bom]
47
54
  */
48
55
 
56
+ /** @typedef {ArrayBufferLike|ArrayBufferView|ReadableStream|string} SendBody */
57
+
58
+ /**
59
+ * @typedef {Object} SendContent
60
+ * @property {string|undefined} contentType
61
+ * @property {number|undefined} contentLength
62
+ * @property {string|undefined} encoding
63
+ * @property {EtagItem|undefined} etag
64
+ * @property {IMFFixDateInput|string|undefined} lastModified
65
+ * @property {number|undefined} age
66
+ * @property {CacheControlOptions} cacheControl
67
+ * @property {ContentRangeDirective} rangeDirective
68
+ */
69
+
70
+ /**
71
+ * @typedef {Object} SendInfo
72
+ * @property {Array<string>} supportedMethods
73
+ * @property {Array<string>|string} supportedTypes
74
+ * @property {Array<string>|string} acceptableMediaType
75
+ * @property {AcceptRangeUnits|undefined} acceptRanges
76
+ * @property {Array<string>|undefined} supportedQueryTypes
77
+ * @property {RateLimitInfo} limitInfo
78
+ * @property {Array<RateLimitPolicyInfo>} policies
79
+ * @property {number|undefined} retryAfter
80
+ */
81
+
82
+
@@ -1,6 +1,8 @@
1
- import { parseAcceptStyleHeader } from './accept-util.js'
1
+ import { parseAcceptStyleHeader } from './util/accept-util.js'
2
2
 
3
- /** @import { AcceptStyleItem } from './accept-util.js' */
3
+ /** @import { AcceptStyleItem } from './util/accept-util.js' */
4
+
5
+ export const ENCODING_ANY = '*'
4
6
 
5
7
  export const WELL_KNOWN_ENCODINGS = new Map([
6
8
  [ 'gzip, deflate, br, zstd', [ { name: 'gzip' }, { name: 'deflate' }, { name: 'br' }, { name: 'zstd' } ] ],
@@ -29,6 +31,8 @@ export class AcceptEncoding {
29
31
  * @param {Array<string>} supportedTypes
30
32
  */
31
33
  static selectFrom(acceptEncodings, supportedTypes) {
34
+ if(supportedTypes === undefined) { return undefined }
35
+
32
36
  for(const acceptEncoding of acceptEncodings) {
33
37
  const { name } = acceptEncoding
34
38
  if(supportedTypes.includes(name)) {
@@ -36,18 +40,11 @@ export class AcceptEncoding {
36
40
  }
37
41
  }
38
42
 
43
+ //
44
+ if(acceptEncodings.some(item => item.name === ENCODING_ANY)) {
45
+ return supportedTypes.at(0)
46
+ }
47
+
39
48
  return undefined
40
49
  }
41
50
  }
42
-
43
-
44
- // console.log(AcceptEncoding.parse(''))
45
- // console.log(AcceptEncoding.parse(' '))
46
- // console.log(AcceptEncoding.parse('zstd'))
47
- // console.log(AcceptEncoding.parse('identity'))
48
- // console.log(AcceptEncoding.parse('*'))
49
- // console.log(AcceptEncoding.parse('gzip, deflate, br, zstd'))
50
- // console.log(AcceptEncoding.parse('br;q=1.0, gzip;q=0.8, *;q=0.1'))
51
- // console.log(AcceptEncoding.parse('deflate, gzip;q=1.0, *;q=0.5'))
52
- // console.log(AcceptEncoding.parse('identity;q=0'))
53
- // console.log(AcceptEncoding.parse('*;q=0'))
@@ -1,12 +1,13 @@
1
- import { parseAcceptStyleHeader } from './accept-util.js'
1
+ import { parseAcceptStyleHeader } from './util/accept-util.js'
2
2
 
3
- /**
4
- * @import { AcceptStyleItem } from './accept-util.js'
5
- */
3
+ /** @import { AcceptStyleItem } from './util/accept-util.js' */
4
+
5
+ export const LANGUAGE_ANY = '*'
6
6
 
7
7
  export const WELL_KNOWN_LANGUAGES = new Map([
8
8
  [ 'en-US,en;q=0.5', [ { name: 'en-US', quality: 1 }, { name: 'en', quality: 0.5 } ] ],
9
- [ 'en-US,en;q=0.9', [ { name: 'en-US', quality: 1 }, { name: 'en', quality: 0.9 } ] ]
9
+ [ 'en-US,en;q=0.9', [ { name: 'en-US', quality: 1 }, { name: 'en', quality: 0.9 } ] ],
10
+ // [ '*', [ { name: '*', quality: 1 } ]]
10
11
  ])
11
12
 
12
13
  export class AcceptLanguage {
@@ -31,16 +32,20 @@ export class AcceptLanguage {
31
32
  * @param {Array<string>} supportedTypes
32
33
  */
33
34
  static selectFrom(acceptLanguages, supportedTypes) {
35
+ if(supportedTypes === undefined) { return undefined }
36
+
34
37
  for(const acceptLanguage of acceptLanguages) {
35
38
  const { name } = acceptLanguage
36
39
  if(supportedTypes.includes(name)) {
37
40
  return name
38
- }
41
+ }
42
+ }
43
+
44
+ //
45
+ if(acceptLanguages.some(item => item.name === LANGUAGE_ANY)) {
46
+ return supportedTypes.at(0)
39
47
  }
40
48
 
41
49
  return undefined
42
50
  }
43
51
  }
44
-
45
- // console.log(AcceptLanguage.parse('en-US,en;q=0.9'))
46
- // console.log(AcceptLanguage.select('foo;q=0.2, bar-BZ', [ 'bang', 'foo' ]))
@@ -0,0 +1,86 @@
1
+ import { parseAcceptStyleHeader } from './util/accept-util.js'
2
+ import { MIME_ANY, Mime } from './util/mime.js'
3
+
4
+ /** @import { AcceptStyleItem } from './util/accept-util.js' */
5
+ /** @import { MimeItem } from './util/mime.js' */
6
+
7
+ /** @typedef {AcceptStyleItem & MimeItem} AcceptItem */
8
+
9
+ export const WELL_KNOWN = new Map([
10
+ [ '*/*', [ { name: '*/*', quality: 1 } ] ],
11
+ [ 'application/json', [ { name: 'application/json', quality: 1 } ] ]
12
+ ])
13
+
14
+ export class Accept {
15
+ /**
16
+ * @param {string|undefined} acceptHeader
17
+ * @returns {Array<AcceptItem>}
18
+ */
19
+ static parse(acceptHeader) {
20
+ return parseAcceptStyleHeader(acceptHeader, WELL_KNOWN)
21
+ .map(({ name, quality, parameters }) => {
22
+
23
+ const mime = Mime.parse(name)
24
+ if(mime === undefined) { return undefined }
25
+
26
+ return {
27
+ name,
28
+ ...mime,
29
+ quality,
30
+ parameters
31
+ }
32
+ })
33
+ .filter(entry => entry !== undefined)
34
+ .sort((entryA, entryB) => {
35
+ if(entryA.quality === entryB.quality) {
36
+ // prefer things with less ANY
37
+ const specificityA = (entryA.type === MIME_ANY ? 1 : 0) + (entryA.subtype === MIME_ANY ? 1 : 0)
38
+ const specificityB = (entryB.type === MIME_ANY ? 1 : 0) + (entryB.subtype === MIME_ANY ? 1 : 0)
39
+ return specificityA - specificityB
40
+ }
41
+
42
+ // B - A descending order
43
+ const qualityB = entryB.quality ?? 0
44
+ const qualityA = entryA.quality ?? 0
45
+ return qualityB - qualityA
46
+ })
47
+ }
48
+
49
+ /**
50
+ * @param {string|undefined} acceptHeader
51
+ * @param {Array<string>} supportedTypes
52
+ */
53
+ static select(acceptHeader, supportedTypes) {
54
+ const accepts = Accept.parse(acceptHeader)
55
+ return Accept.selectFrom(accepts, supportedTypes)
56
+ }
57
+
58
+ /**
59
+ * @param {Array<AcceptItem>} accepts
60
+ * @param {Array<string>} supportedTypes
61
+ */
62
+ static selectFrom(accepts, supportedTypes) {
63
+ const bests = accepts.map(accept => {
64
+ const { type, subtype, quality } = accept
65
+ const st = supportedTypes.filter(supportedType => {
66
+ const supportedMime = Mime.parse(supportedType)
67
+ if(supportedMime === undefined) { return false }
68
+ return ((supportedMime.type === type || type === MIME_ANY) && (supportedMime.subtype === subtype || subtype === MIME_ANY))
69
+ })
70
+
71
+ return {
72
+ supportedTypes: st,
73
+ quality
74
+ }
75
+ })
76
+ .filter(best => best.supportedTypes.length > 0)
77
+
78
+ if(bests.length === 0) { return undefined }
79
+ const [ first ] = bests
80
+ if(first === undefined) { return undefined }
81
+ const [ firstSt ] = first.supportedTypes
82
+ return firstSt
83
+ }
84
+ }
85
+
86
+
@@ -54,9 +54,3 @@ export class CacheControl {
54
54
  return result.join(', ')
55
55
  }
56
56
  }
57
-
58
- // console.log(CacheControl.encode({
59
- // priv: true,
60
- // maxAge: 60,
61
- // directives: ['must-revalidate', 'no-transform']
62
- // }))
@@ -42,18 +42,12 @@ export class SiteData {
42
42
  if(directives.prerenderCache === true) { result.push(CSD_DIRECTIVE_PRERENDER_CACHE) }
43
43
  if(directives.storage === true) { result.push(CSD_DIRECTIVE_STORAGE) }
44
44
 
45
+ if(result.length === 0) {
46
+ return undefined
47
+ }
48
+
45
49
  return result
46
50
  .map(item => `${CSD_QUOTE}${item}${CSD_QUOTE}`)
47
51
  .join(CSD_DIRECTIVE_SEPARATOR)
48
52
  }
49
53
  }
50
-
51
- // console.log(SiteData.encode())
52
- // console.log(SiteData.encode({}))
53
- // console.log(SiteData.encode(false))
54
- // console.log(SiteData.encode('true'))
55
- // console.log(SiteData.encode(true))
56
- // console.log(SiteData.encode('*'))
57
- // console.log(SiteData.encode({ storage: false }))
58
- // console.log(SiteData.encode({ storage: true }))
59
- // console.log(SiteData.encode({ storage: true, cookies: true }))
@@ -0,0 +1,88 @@
1
+
2
+
3
+ export const CLIENT_HINT_USER_AGENT = 'Sec-CH-UA'
4
+ export const CLIENT_HINT_ARCHITECTURE = 'Sec-CH-UA-Arch'
5
+ export const CLIENT_HINT_BITNESS = 'Sec-CH-UA-Bitness'
6
+ export const CLIENT_HINT_FORM_FACTORS = 'Sec-CH-UA-Form-Factors'
7
+ // export const CLIENT_HINT_ = 'Sec-CH-UA-Full-Version'
8
+ export const CLIENT_HINT_VERSION_LIST = 'Sec-CH-UA-Full-Version-List'
9
+ export const CLIENT_HINT_MOBILE = 'Sec-CH-UA-Mobile'
10
+ export const CLIENT_HINT_MODEL = 'Sec-CH-UA-Model'
11
+ export const CLIENT_HINT_PLATFORM = 'Sec-CH-UA-Platform'
12
+ export const CLIENT_HINT_PLATFORM_VERSION = 'Sec-CH-UA-Platform-Version'
13
+ export const CLIENT_HINT_WOW64 = 'Sec-CH-UA-WoW64'
14
+ export const CLIENT_HINT_PREFERS_COLOR_SCHEME = 'Sec-CH-Prefers-Color-Scheme'
15
+ export const CLIENT_HINT_PREFERS_REDUCED_MOTION = 'Sec-CH-Prefers-Reduced-Motion'
16
+ export const CLIENT_HINT_PREFERS_REDUCED_TRANSPARENCY = 'Sec-CH-Prefers-Reduced-Transparency'
17
+ export const CLIENT_HINT_DEVICE_MEMORY = 'Sec-CH-Device-Memory'
18
+ export const CLIENT_HINT_DEVICE_PIXEL_RATIO = 'Sec-CH-DPR'
19
+ export const CLIENT_HINT_VIEWPORT_HEIGHT = 'Sec-CH-Viewport-Height'
20
+ export const CLIENT_HINT_VIEWPORT_WIDTH = 'Sec-CH-Viewport-Width'
21
+ export const CLIENT_HINT_IMAGE_WIDTH = 'Sec-CH-Width'
22
+ export const CLIENT_HINT_DOWNLINK = 'Downlink'
23
+ export const CLIENT_HINT_EFFECTIVE_CONNECTION_TYPE = 'ECT'
24
+ export const CLIENT_HINT_ROUND_TRIP_TIME = 'RTT'
25
+ export const CLIENT_HINT_SAVE_DATA = 'Save-Data'
26
+ export const CLIENT_HINT_GLOBAL_PRIVACY_CONTROL = 'Sec-GPC'
27
+
28
+ export const KNOWN_CLIENT_HINTS = [
29
+ CLIENT_HINT_USER_AGENT,
30
+ CLIENT_HINT_ARCHITECTURE,
31
+ CLIENT_HINT_BITNESS,
32
+ CLIENT_HINT_FORM_FACTORS,
33
+ CLIENT_HINT_VERSION_LIST,
34
+ CLIENT_HINT_MOBILE,
35
+ CLIENT_HINT_MODEL,
36
+ CLIENT_HINT_PLATFORM,
37
+ CLIENT_HINT_PLATFORM_VERSION,
38
+ CLIENT_HINT_WOW64,
39
+ CLIENT_HINT_PREFERS_COLOR_SCHEME,
40
+ CLIENT_HINT_PREFERS_REDUCED_MOTION,
41
+ CLIENT_HINT_PREFERS_REDUCED_TRANSPARENCY,
42
+ CLIENT_HINT_DEVICE_MEMORY,
43
+ CLIENT_HINT_DEVICE_PIXEL_RATIO,
44
+ CLIENT_HINT_VIEWPORT_HEIGHT,
45
+ CLIENT_HINT_VIEWPORT_WIDTH,
46
+ CLIENT_HINT_IMAGE_WIDTH,
47
+ CLIENT_HINT_DOWNLINK,
48
+ CLIENT_HINT_EFFECTIVE_CONNECTION_TYPE,
49
+ CLIENT_HINT_ROUND_TRIP_TIME,
50
+ CLIENT_HINT_SAVE_DATA,
51
+ CLIENT_HINT_GLOBAL_PRIVACY_CONTROL
52
+ ]
53
+
54
+ export const KNOWN_FORM_FACTORS = [
55
+ 'Desktop',
56
+ 'Automotive',
57
+ 'Mobile',
58
+ 'Tablet',
59
+ 'XR',
60
+ 'EInk',
61
+ 'Watch'
62
+ ]
63
+
64
+ export const CLIENT_HINT_TRUE = '?1'
65
+ export const CLIENT_HINT_FALSE = '?0'
66
+
67
+
68
+ export class ClientHints {
69
+
70
+ /**
71
+ * @param {Array<String>} hints
72
+ */
73
+ static encode(hints) {
74
+ if(hints === undefined) { return undefined }
75
+ if(!Array.isArray(hints)) { return undefined }
76
+ if(hints.length === 0) { return undefined }
77
+
78
+ const remaining = hints
79
+ .filter(hint => KNOWN_CLIENT_HINTS.includes(hint))
80
+
81
+ if(remaining.length === 0) { return undefined }
82
+
83
+ return remaining.join(', ')
84
+ }
85
+ }
86
+
87
+
88
+