@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.
- package/README.md +54 -7
- package/package.json +26 -6
- package/src/body.js +8 -8
- package/src/{response/defs.js → defs.js} +35 -1
- package/src/{accept-encoding.js → headers/accept-encoding.js} +11 -14
- package/src/{accept-language.js → headers/accept-language.js} +14 -9
- package/src/headers/accept.js +86 -0
- package/src/{cache-control.js → headers/cache-control.js} +0 -6
- package/src/{clear-site-data.js → headers/clear-site-data.js} +4 -10
- package/src/headers/client-hints.js +88 -0
- package/src/{conditional.js → headers/conditional.js} +190 -117
- package/src/headers/content-disposition.js +44 -0
- package/src/{content-range.js → headers/content-range.js} +1 -18
- package/src/headers/content-type.js +101 -0
- package/src/{forwarded.js → headers/forwarded.js} +8 -56
- package/src/{index.js → headers/index.js} +4 -2
- package/src/headers/link.js +34 -0
- package/src/{multipart.js → headers/multipart.js} +22 -13
- package/src/{preference.js → headers/preference.js} +3 -58
- package/src/{range.js → headers/range.js} +4 -32
- package/src/{rate-limit.js → headers/rate-limit.js} +6 -1
- package/src/{server-timing.js → headers/server-timing.js} +3 -16
- package/src/headers/strict-transport-security.js +39 -0
- package/src/{accept-util.js → headers/util/accept-util.js} +8 -14
- package/src/headers/util/index.js +7 -0
- package/src/headers/util/kvp.js +79 -0
- package/src/headers/util/mime.js +77 -0
- package/src/headers/util/whitespace.js +8 -0
- package/src/{www-authenticate.js → headers/www-authenticate.js} +1 -1
- package/src/response/{accepted.js → 2xx/accepted.js} +2 -2
- package/src/response/2xx/bytes.js +62 -0
- package/src/response/2xx/created.js +49 -0
- package/src/response/2xx/json.js +60 -0
- package/src/response/2xx/no-content.js +45 -0
- package/src/response/2xx/partial-content.js +101 -0
- package/src/response/{preflight.js → 2xx/preflight.js} +29 -10
- package/src/response/{sse.js → 2xx/sse.js} +2 -2
- package/src/response/{trace.js → 2xx/trace.js} +3 -3
- package/src/response/3xx/found.js +23 -0
- package/src/response/{moved-permanently.js → 3xx/moved-permanently.js} +2 -2
- package/src/response/{multiple-choices.js → 3xx/multiple-choices.js} +2 -3
- package/src/response/3xx/not-modified.js +59 -0
- package/src/response/{permanent-redirect.js → 3xx/permanent-redirect.js} +2 -2
- package/src/response/{see-other.js → 3xx/see-other.js} +2 -2
- package/src/response/{temporary-redirect.js → 3xx/temporary-redirect.js} +2 -2
- package/src/response/4xx/bad-request.js +19 -0
- package/src/response/{conflict.js → 4xx/conflict.js} +2 -2
- package/src/response/{content-too-large.js → 4xx/content-too-large.js} +2 -2
- package/src/response/{forbidden.js → 4xx/forbidden.js} +3 -2
- package/src/response/{gone.js → 4xx/gone.js} +2 -2
- package/src/response/{im-a-teapot.js → 4xx/im-a-teapot.js} +2 -2
- package/src/response/{not-acceptable.js → 4xx/not-acceptable.js} +14 -3
- package/src/response/4xx/not-allowed.js +34 -0
- package/src/response/{not-found.js → 4xx/not-found.js} +3 -3
- package/src/response/4xx/payment-required.js +17 -0
- package/src/response/4xx/precondition-failed.js +45 -0
- package/src/response/{range-not-satisfiable.js → 4xx/range-not-satisfiable.js} +15 -4
- package/src/response/{timeout.js → 4xx/timeout.js} +2 -2
- package/src/response/{too-many-requests.js → 4xx/too-many-requests.js} +22 -5
- package/src/response/{unauthorized.js → 4xx/unauthorized.js} +5 -5
- package/src/response/{unprocessable.js → 4xx/unprocessable.js} +2 -2
- package/src/response/{unsupported-media.js → 4xx/unsupported-media.js} +21 -4
- package/src/response/{error.js → 5xx/error.js} +3 -3
- package/src/response/{insufficient-storage.js → 5xx/insufficient-storage.js} +2 -2
- package/src/response/{not-implemented.js → 5xx/not-implemented.js} +4 -4
- package/src/response/{unavailable.js → 5xx/unavailable.js} +16 -4
- package/src/response/header-util.js +2 -2
- package/src/response/index.js +39 -35
- package/src/response/response.js +40 -34
- package/src/response/send-util.js +32 -21
- package/src/accept.js +0 -122
- package/src/content-disposition.js +0 -57
- package/src/content-type.js +0 -148
- package/src/link.js +0 -35
- package/src/response/bytes.js +0 -27
- package/src/response/created.js +0 -28
- package/src/response/json.js +0 -28
- package/src/response/no-content.js +0 -25
- package/src/response/not-allowed.js +0 -23
- package/src/response/not-modified.js +0 -35
- package/src/response/partial-content.js +0 -71
- package/src/response/precondition-failed.js +0 -16
- /package/src/{fetch-metadata.js → headers/fetch-metadata.js} +0 -0
- /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
|
-
- [`
|
|
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": "
|
|
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
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
|
|
@@ -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
|
+
|