@helia/verified-fetch 4.1.0 → 5.0.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 +6 -40
- package/dist/index.min.js +73 -534
- package/dist/index.min.js.map +4 -4
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +2 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +162 -68
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +11 -43
- package/dist/src/index.js.map +1 -1
- package/dist/src/plugins/index.d.ts +0 -5
- package/dist/src/plugins/index.d.ts.map +1 -1
- package/dist/src/plugins/index.js +0 -4
- package/dist/src/plugins/index.js.map +1 -1
- package/dist/src/plugins/plugin-base.d.ts +8 -9
- package/dist/src/plugins/plugin-base.d.ts.map +1 -1
- package/dist/src/plugins/plugin-base.js +5 -6
- package/dist/src/plugins/plugin-base.js.map +1 -1
- package/dist/src/plugins/plugin-handle-car.d.ts +3 -3
- package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-car.js +38 -39
- package/dist/src/plugins/plugin-handle-car.js.map +1 -1
- package/dist/src/plugins/plugin-handle-ipld.d.ts +12 -0
- package/dist/src/plugins/plugin-handle-ipld.d.ts.map +1 -0
- package/dist/src/plugins/plugin-handle-ipld.js +83 -0
- package/dist/src/plugins/plugin-handle-ipld.js.map +1 -0
- package/dist/src/plugins/plugin-handle-ipns-record.d.ts +3 -3
- package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-ipns-record.js +25 -34
- package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
- package/dist/src/plugins/plugin-handle-tar.d.ts +3 -3
- package/dist/src/plugins/plugin-handle-tar.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-tar.js +20 -22
- package/dist/src/plugins/plugin-handle-tar.js.map +1 -1
- package/dist/src/plugins/plugin-handle-unixfs.d.ts +14 -0
- package/dist/src/plugins/plugin-handle-unixfs.d.ts.map +1 -0
- package/dist/src/plugins/plugin-handle-unixfs.js +180 -0
- package/dist/src/plugins/plugin-handle-unixfs.js.map +1 -0
- package/dist/src/plugins/types.d.ts +1 -77
- package/dist/src/plugins/types.d.ts.map +1 -1
- package/dist/src/url-resolver.d.ts +29 -11
- package/dist/src/url-resolver.d.ts.map +1 -1
- package/dist/src/url-resolver.js +152 -74
- package/dist/src/url-resolver.js.map +1 -1
- package/dist/src/utils/content-type-parser.d.ts.map +1 -1
- package/dist/src/utils/content-type-parser.js +4 -3
- package/dist/src/utils/content-type-parser.js.map +1 -1
- package/dist/src/utils/content-types.d.ts +26 -0
- package/dist/src/utils/content-types.d.ts.map +1 -0
- package/dist/src/utils/content-types.js +137 -0
- package/dist/src/utils/content-types.js.map +1 -0
- package/dist/src/utils/convert-output.d.ts +17 -0
- package/dist/src/utils/convert-output.d.ts.map +1 -0
- package/dist/src/utils/convert-output.js +176 -0
- package/dist/src/utils/convert-output.js.map +1 -0
- package/dist/src/utils/error-to-response.d.ts +3 -0
- package/dist/src/utils/error-to-response.d.ts.map +1 -0
- package/dist/src/utils/error-to-response.js +40 -0
- package/dist/src/utils/error-to-response.js.map +1 -0
- package/dist/src/utils/get-content-disposition-filename.d.ts +1 -1
- package/dist/src/utils/get-content-disposition-filename.d.ts.map +1 -1
- package/dist/src/utils/get-content-disposition-filename.js +4 -0
- package/dist/src/utils/get-content-disposition-filename.js.map +1 -1
- package/dist/src/utils/get-e-tag.d.ts +20 -15
- package/dist/src/utils/get-e-tag.d.ts.map +1 -1
- package/dist/src/utils/get-e-tag.js +8 -22
- package/dist/src/utils/get-e-tag.js.map +1 -1
- package/dist/src/utils/get-offset-and-length.d.ts +12 -2
- package/dist/src/utils/get-offset-and-length.d.ts.map +1 -1
- package/dist/src/utils/get-offset-and-length.js +63 -21
- package/dist/src/utils/get-offset-and-length.js.map +1 -1
- package/dist/src/utils/get-range-header.d.ts +22 -0
- package/dist/src/utils/get-range-header.d.ts.map +1 -0
- package/dist/src/utils/get-range-header.js +69 -0
- package/dist/src/utils/get-range-header.js.map +1 -0
- package/dist/src/utils/parse-url-string.d.ts +2 -1
- package/dist/src/utils/parse-url-string.d.ts.map +1 -1
- package/dist/src/utils/parse-url-string.js +46 -71
- package/dist/src/utils/parse-url-string.js.map +1 -1
- package/dist/src/utils/resource-to-cache-key.d.ts +3 -3
- package/dist/src/utils/resource-to-cache-key.js +5 -5
- package/dist/src/utils/resource-to-cache-key.js.map +1 -1
- package/dist/src/utils/response-headers.d.ts +4 -14
- package/dist/src/utils/response-headers.d.ts.map +1 -1
- package/dist/src/utils/response-headers.js +36 -36
- package/dist/src/utils/response-headers.js.map +1 -1
- package/dist/src/utils/responses.d.ts +30 -11
- package/dist/src/utils/responses.d.ts.map +1 -1
- package/dist/src/utils/responses.js +146 -39
- package/dist/src/utils/responses.js.map +1 -1
- package/dist/src/verified-fetch.d.ts +16 -15
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +307 -236
- package/dist/src/verified-fetch.js.map +1 -1
- package/dist/typedoc-urls.json +64 -45
- package/package.json +4 -3
- package/src/constants.ts +3 -0
- package/src/index.ts +203 -71
- package/src/plugins/index.ts +0 -6
- package/src/plugins/plugin-base.ts +8 -10
- package/src/plugins/plugin-handle-car.ts +48 -46
- package/src/plugins/plugin-handle-ipld.ts +93 -0
- package/src/plugins/plugin-handle-ipns-record.ts +31 -41
- package/src/plugins/plugin-handle-tar.ts +25 -29
- package/src/plugins/plugin-handle-unixfs.ts +217 -0
- package/src/plugins/types.ts +0 -86
- package/src/url-resolver.ts +197 -83
- package/src/utils/content-type-parser.ts +4 -3
- package/src/utils/content-types.ts +159 -0
- package/src/utils/convert-output.ts +187 -0
- package/src/utils/error-to-response.ts +49 -0
- package/src/utils/get-content-disposition-filename.ts +7 -1
- package/src/utils/get-e-tag.ts +26 -35
- package/src/utils/get-offset-and-length.ts +75 -21
- package/src/utils/get-range-header.ts +107 -0
- package/src/utils/parse-url-string.ts +51 -80
- package/src/utils/resource-to-cache-key.ts +5 -5
- package/src/utils/response-headers.ts +40 -41
- package/src/utils/responses.ts +186 -45
- package/src/verified-fetch.ts +359 -267
- package/dist/src/plugins/plugin-handle-byte-range-context.d.ts +0 -14
- package/dist/src/plugins/plugin-handle-byte-range-context.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-byte-range-context.js +0 -25
- package/dist/src/plugins/plugin-handle-byte-range-context.js.map +0 -1
- package/dist/src/plugins/plugin-handle-cbor.d.ts +0 -17
- package/dist/src/plugins/plugin-handle-cbor.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-cbor.js +0 -94
- package/dist/src/plugins/plugin-handle-cbor.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts +0 -27
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js +0 -279
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-cbor.d.ts +0 -17
- package/dist/src/plugins/plugin-handle-dag-cbor.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-cbor.js +0 -66
- package/dist/src/plugins/plugin-handle-dag-cbor.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-pb.d.ts +0 -17
- package/dist/src/plugins/plugin-handle-dag-pb.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-pb.js +0 -209
- package/dist/src/plugins/plugin-handle-dag-pb.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-walk.d.ts +0 -21
- package/dist/src/plugins/plugin-handle-dag-walk.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dag-walk.js +0 -95
- package/dist/src/plugins/plugin-handle-dag-walk.js.map +0 -1
- package/dist/src/plugins/plugin-handle-dir-index-html.d.ts +0 -10
- package/dist/src/plugins/plugin-handle-dir-index-html.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-dir-index-html.js +0 -59
- package/dist/src/plugins/plugin-handle-dir-index-html.js.map +0 -1
- package/dist/src/plugins/plugin-handle-json.d.ts +0 -12
- package/dist/src/plugins/plugin-handle-json.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-json.js +0 -73
- package/dist/src/plugins/plugin-handle-json.js.map +0 -1
- package/dist/src/plugins/plugin-handle-raw.d.ts +0 -9
- package/dist/src/plugins/plugin-handle-raw.d.ts.map +0 -1
- package/dist/src/plugins/plugin-handle-raw.js +0 -92
- package/dist/src/plugins/plugin-handle-raw.js.map +0 -1
- package/dist/src/plugins/plugins.d.ts +0 -6
- package/dist/src/plugins/plugins.d.ts.map +0 -1
- package/dist/src/plugins/plugins.js +0 -6
- package/dist/src/plugins/plugins.js.map +0 -1
- package/dist/src/utils/byte-range-context.d.ts +0 -103
- package/dist/src/utils/byte-range-context.d.ts.map +0 -1
- package/dist/src/utils/byte-range-context.js +0 -504
- package/dist/src/utils/byte-range-context.js.map +0 -1
- package/dist/src/utils/dag-cbor-to-safe-json.d.ts +0 -15
- package/dist/src/utils/dag-cbor-to-safe-json.d.ts.map +0 -1
- package/dist/src/utils/dag-cbor-to-safe-json.js +0 -54
- package/dist/src/utils/dag-cbor-to-safe-json.js.map +0 -1
- package/dist/src/utils/dir-index-html.d.ts +0 -19
- package/dist/src/utils/dir-index-html.d.ts.map +0 -1
- package/dist/src/utils/dir-index-html.js +0 -438
- package/dist/src/utils/dir-index-html.js.map +0 -1
- package/dist/src/utils/get-peer-id-from-string.d.ts +0 -3
- package/dist/src/utils/get-peer-id-from-string.d.ts.map +0 -1
- package/dist/src/utils/get-peer-id-from-string.js +0 -10
- package/dist/src/utils/get-peer-id-from-string.js.map +0 -1
- package/dist/src/utils/get-resolved-accept-header.d.ts +0 -9
- package/dist/src/utils/get-resolved-accept-header.d.ts.map +0 -1
- package/dist/src/utils/get-resolved-accept-header.js +0 -27
- package/dist/src/utils/get-resolved-accept-header.js.map +0 -1
- package/dist/src/utils/get-stream-from-async-iterable.d.ts +0 -9
- package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +0 -1
- package/dist/src/utils/get-stream-from-async-iterable.js +0 -43
- package/dist/src/utils/get-stream-from-async-iterable.js.map +0 -1
- package/dist/src/utils/handle-redirects.d.ts +0 -16
- package/dist/src/utils/handle-redirects.d.ts.map +0 -1
- package/dist/src/utils/handle-redirects.js +0 -84
- package/dist/src/utils/handle-redirects.js.map +0 -1
- package/dist/src/utils/is-accept-explicit.d.ts +0 -15
- package/dist/src/utils/is-accept-explicit.d.ts.map +0 -1
- package/dist/src/utils/is-accept-explicit.js +0 -26
- package/dist/src/utils/is-accept-explicit.js.map +0 -1
- package/dist/src/utils/request-headers.d.ts +0 -13
- package/dist/src/utils/request-headers.d.ts.map +0 -1
- package/dist/src/utils/request-headers.js +0 -63
- package/dist/src/utils/request-headers.js.map +0 -1
- package/dist/src/utils/select-output-type.d.ts +0 -17
- package/dist/src/utils/select-output-type.d.ts.map +0 -1
- package/dist/src/utils/select-output-type.js +0 -153
- package/dist/src/utils/select-output-type.js.map +0 -1
- package/dist/src/utils/tlru.d.ts +0 -15
- package/dist/src/utils/tlru.d.ts.map +0 -1
- package/dist/src/utils/tlru.js +0 -34
- package/dist/src/utils/tlru.js.map +0 -1
- package/dist/src/utils/walk-path.d.ts +0 -27
- package/dist/src/utils/walk-path.d.ts.map +0 -1
- package/dist/src/utils/walk-path.js +0 -45
- package/dist/src/utils/walk-path.js.map +0 -1
- package/src/plugins/plugin-handle-byte-range-context.ts +0 -30
- package/src/plugins/plugin-handle-cbor.ts +0 -107
- package/src/plugins/plugin-handle-dag-cbor-html-preview.ts +0 -295
- package/src/plugins/plugin-handle-dag-cbor.ts +0 -83
- package/src/plugins/plugin-handle-dag-pb.ts +0 -248
- package/src/plugins/plugin-handle-dag-walk.ts +0 -110
- package/src/plugins/plugin-handle-dir-index-html.ts +0 -72
- package/src/plugins/plugin-handle-json.ts +0 -80
- package/src/plugins/plugin-handle-raw.ts +0 -110
- package/src/plugins/plugins.ts +0 -5
- package/src/utils/byte-range-context.ts +0 -597
- package/src/utils/dag-cbor-to-safe-json.ts +0 -63
- package/src/utils/dir-index-html.ts +0 -505
- package/src/utils/get-peer-id-from-string.ts +0 -12
- package/src/utils/get-resolved-accept-header.ts +0 -42
- package/src/utils/get-stream-from-async-iterable.ts +0 -49
- package/src/utils/handle-redirects.ts +0 -109
- package/src/utils/is-accept-explicit.ts +0 -38
- package/src/utils/request-headers.ts +0 -65
- package/src/utils/select-output-type.ts +0 -175
- package/src/utils/tlru.ts +0 -42
- package/src/utils/walk-path.ts +0 -68
package/src/verified-fetch.ts
CHANGED
|
@@ -1,71 +1,86 @@
|
|
|
1
1
|
import { dnsLink } from '@helia/dnslink'
|
|
2
2
|
import { ipnsResolver } from '@helia/ipns'
|
|
3
3
|
import { AbortError } from '@libp2p/interface'
|
|
4
|
+
import { CID } from 'multiformats/cid'
|
|
4
5
|
import { CustomProgressEvent } from 'progress-events'
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
6
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
7
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
7
8
|
import { CarPlugin } from './plugins/plugin-handle-car.js'
|
|
8
|
-
import {
|
|
9
|
-
import { DagCborPlugin } from './plugins/plugin-handle-dag-cbor.js'
|
|
10
|
-
import { DagPbPlugin } from './plugins/plugin-handle-dag-pb.js'
|
|
11
|
-
import { DagWalkPlugin } from './plugins/plugin-handle-dag-walk.js'
|
|
9
|
+
import { IpldPlugin } from './plugins/plugin-handle-ipld.js'
|
|
12
10
|
import { IpnsRecordPlugin } from './plugins/plugin-handle-ipns-record.js'
|
|
13
|
-
import { JsonPlugin } from './plugins/plugin-handle-json.js'
|
|
14
|
-
import { RawPlugin } from './plugins/plugin-handle-raw.js'
|
|
15
11
|
import { TarPlugin } from './plugins/plugin-handle-tar.js'
|
|
12
|
+
import { UnixFSPlugin } from './plugins/plugin-handle-unixfs.js'
|
|
16
13
|
import { URLResolver } from './url-resolver.ts'
|
|
17
14
|
import { contentTypeParser } from './utils/content-type-parser.js'
|
|
15
|
+
import { getContentType, getSupportedContentTypes, CONTENT_TYPE_OCTET_STREAM, CONTENT_TYPE_CAR, MEDIA_TYPE_IPNS_RECORD, MEDIA_TYPE_RAW } from './utils/content-types.ts'
|
|
18
16
|
import { errorToObject } from './utils/error-to-object.ts'
|
|
19
|
-
import {
|
|
17
|
+
import { errorToResponse } from './utils/error-to-response.ts'
|
|
20
18
|
import { getETag } from './utils/get-e-tag.js'
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { uriEncodeIPFSPath } from './utils/ipfs-path-to-string.ts'
|
|
24
|
-
import { resourceToSessionCacheKey } from './utils/resource-to-cache-key.js'
|
|
19
|
+
import { getRangeHeader } from './utils/get-range-header.ts'
|
|
20
|
+
import { parseURLString } from './utils/parse-url-string.ts'
|
|
25
21
|
import { setCacheControlHeader } from './utils/response-headers.js'
|
|
26
|
-
import { badRequestResponse,
|
|
27
|
-
import { selectOutputType } from './utils/select-output-type.js'
|
|
22
|
+
import { badRequestResponse, internalServerErrorResponse, notAcceptableResponse, notImplementedResponse } from './utils/responses.js'
|
|
28
23
|
import { ServerTiming } from './utils/server-timing.js'
|
|
29
|
-
import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, ResolveURLResult, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
|
|
30
|
-
import type { VerifiedFetchPlugin, PluginContext, PluginOptions } from './plugins/types.js'
|
|
31
|
-
import type { AcceptHeader } from './utils/select-output-type.js'
|
|
24
|
+
import type { AcceptHeader, CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, ResolveURLResult, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions, VerifiedFetchPlugin, PluginContext, PluginOptions } from './index.js'
|
|
32
25
|
import type { DNSLink } from '@helia/dnslink'
|
|
33
|
-
import type { Helia
|
|
26
|
+
import type { Helia } from '@helia/interface'
|
|
34
27
|
import type { IPNSResolver } from '@helia/ipns'
|
|
35
28
|
import type { AbortOptions, Logger } from '@libp2p/interface'
|
|
36
|
-
import type { Blockstore } from 'interface-blockstore'
|
|
37
|
-
import type { CID } from 'multiformats/cid'
|
|
38
|
-
|
|
39
|
-
const SESSION_CACHE_MAX_SIZE = 100
|
|
40
|
-
const SESSION_CACHE_TTL_MS = 60 * 1000
|
|
41
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Retypes the `.signal` property of the options from
|
|
32
|
+
* `AbortSignal | null | undefined` to `AbortSignal | undefined`.
|
|
33
|
+
*/
|
|
42
34
|
function convertOptions (options?: VerifiedFetchOptions): (Omit<VerifiedFetchOptions, 'signal'> & AbortOptions) | undefined {
|
|
43
35
|
if (options == null) {
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let signal: AbortSignal | undefined
|
|
48
|
-
if (options?.signal === null) {
|
|
49
|
-
signal = undefined
|
|
50
|
-
} else {
|
|
51
|
-
signal = options?.signal
|
|
36
|
+
return
|
|
52
37
|
}
|
|
53
38
|
|
|
54
39
|
return {
|
|
55
40
|
...options,
|
|
56
|
-
signal
|
|
41
|
+
signal: options?.signal == null ? undefined : options?.signal
|
|
57
42
|
}
|
|
58
43
|
}
|
|
59
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Returns true if the quest is only for an IPNS record
|
|
47
|
+
*/
|
|
48
|
+
function isIPNSRecordRequest (headers: Headers): boolean {
|
|
49
|
+
const acceptHeaders = headers.get('accept')?.split(',') ?? []
|
|
50
|
+
|
|
51
|
+
if (acceptHeaders.length !== 1) {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const mediaType = acceptHeaders[0].split(';')[0]
|
|
56
|
+
|
|
57
|
+
return mediaType === MEDIA_TYPE_IPNS_RECORD
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns true if the quest is only for an IPNS record
|
|
62
|
+
*/
|
|
63
|
+
function isRawBlockRequest (headers: Headers): boolean {
|
|
64
|
+
const acceptHeaders = headers.get('accept')?.split(',') ?? []
|
|
65
|
+
|
|
66
|
+
if (acceptHeaders.length !== 1) {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const mediaType = acceptHeaders[0].split(';')[0]
|
|
71
|
+
|
|
72
|
+
return mediaType === MEDIA_TYPE_RAW
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
export class VerifiedFetch {
|
|
61
76
|
private readonly helia: Helia
|
|
62
77
|
private readonly ipnsResolver: IPNSResolver
|
|
63
78
|
private readonly dnsLink: DNSLink
|
|
64
79
|
private readonly log: Logger
|
|
65
80
|
private readonly contentTypeParser: ContentTypeParser | undefined
|
|
66
|
-
private readonly blockstoreSessions: QuickLRU<string, SessionBlockstore>
|
|
67
81
|
private readonly withServerTiming: boolean
|
|
68
82
|
private readonly plugins: VerifiedFetchPlugin[] = []
|
|
83
|
+
private readonly urlResolver: URLResolver
|
|
69
84
|
|
|
70
85
|
constructor (helia: Helia, init: CreateVerifiedFetchOptions = {}) {
|
|
71
86
|
this.helia = helia
|
|
@@ -73,35 +88,27 @@ export class VerifiedFetch {
|
|
|
73
88
|
this.ipnsResolver = init.ipnsResolver ?? ipnsResolver(helia)
|
|
74
89
|
this.dnsLink = init.dnsLink ?? dnsLink(helia)
|
|
75
90
|
this.contentTypeParser = init.contentTypeParser ?? contentTypeParser
|
|
76
|
-
this.blockstoreSessions = new QuickLRU({
|
|
77
|
-
maxSize: init?.sessionCacheSize ?? SESSION_CACHE_MAX_SIZE,
|
|
78
|
-
maxAge: init?.sessionTTLms ?? SESSION_CACHE_TTL_MS,
|
|
79
|
-
onEviction: (key, store) => {
|
|
80
|
-
store.close()
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
91
|
this.withServerTiming = init?.withServerTiming ?? false
|
|
92
|
+
this.urlResolver = new URLResolver({
|
|
93
|
+
ipnsResolver: this.ipnsResolver,
|
|
94
|
+
dnsLink: this.dnsLink,
|
|
95
|
+
helia: this.helia
|
|
96
|
+
}, init)
|
|
84
97
|
|
|
85
98
|
const pluginOptions: PluginOptions = {
|
|
86
99
|
...init,
|
|
87
100
|
logger: helia.logger.forComponent('verified-fetch'),
|
|
88
|
-
getBlockstore: (cid, resource, useSession, options) => this.getBlockstore(cid, resource, useSession, options),
|
|
89
101
|
helia,
|
|
90
102
|
contentTypeParser: this.contentTypeParser,
|
|
91
103
|
ipnsResolver: this.ipnsResolver
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
const defaultPlugins = [
|
|
95
|
-
new
|
|
96
|
-
new
|
|
97
|
-
new IpnsRecordPlugin(pluginOptions),
|
|
107
|
+
new UnixFSPlugin(pluginOptions),
|
|
108
|
+
new IpldPlugin(pluginOptions),
|
|
98
109
|
new CarPlugin(pluginOptions),
|
|
99
|
-
new RawPlugin(pluginOptions),
|
|
100
110
|
new TarPlugin(pluginOptions),
|
|
101
|
-
new
|
|
102
|
-
new DagCborPlugin(pluginOptions),
|
|
103
|
-
new DagPbPlugin(pluginOptions),
|
|
104
|
-
new CborPlugin(pluginOptions)
|
|
111
|
+
new IpnsRecordPlugin(pluginOptions)
|
|
105
112
|
]
|
|
106
113
|
|
|
107
114
|
const customPlugins = init.plugins?.map((pluginFactory) => pluginFactory(pluginOptions)) ?? []
|
|
@@ -113,310 +120,395 @@ export class VerifiedFetch {
|
|
|
113
120
|
|
|
114
121
|
this.plugins = defaultPlugins.map(plugin => customPluginMap.get(plugin.id) ?? plugin)
|
|
115
122
|
|
|
116
|
-
//
|
|
117
|
-
|
|
123
|
+
// add any custom plugins that don't replace default ones with a higher
|
|
124
|
+
// priority than anything built-in
|
|
125
|
+
this.plugins.unshift(...customPlugins.filter(plugin => !defaultPluginMap.has(plugin.id)))
|
|
118
126
|
} else {
|
|
119
127
|
this.plugins = defaultPlugins
|
|
120
128
|
}
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Load a resource from the IPFS network and ensure the retrieved data is the
|
|
133
|
+
* data that was expected to be loaded.
|
|
134
|
+
*
|
|
135
|
+
* Like [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch)
|
|
136
|
+
* but verified.
|
|
137
|
+
*/
|
|
138
|
+
async fetch (resource: Resource, opts?: VerifiedFetchOptions): Promise<Response> {
|
|
139
|
+
this.log('fetch %s %s', opts?.method ?? 'GET', resource)
|
|
130
140
|
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
141
|
+
if (opts?.method === 'OPTIONS') {
|
|
142
|
+
return this.handleFinalResponse(new Response(null, {
|
|
143
|
+
status: 200
|
|
144
|
+
}))
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
const options = convertOptions(opts)
|
|
148
|
+
const headers = new Headers(options?.headers)
|
|
149
|
+
const serverTiming = new ServerTiming()
|
|
138
150
|
|
|
139
|
-
|
|
140
|
-
* The last place a Response touches in verified-fetch before being returned to the user. This is where we add the
|
|
141
|
-
* Server-Timing header to the response if it has been collected. It should be used for any final processing of the
|
|
142
|
-
* response before it is returned to the user.
|
|
143
|
-
*/
|
|
144
|
-
private handleFinalResponse (response: Response, context?: Partial<PluginContext>): Response {
|
|
145
|
-
if ((this.withServerTiming || context?.withServerTiming === true) && context?.serverTiming != null) {
|
|
146
|
-
response.headers.set('Server-Timing', context?.serverTiming.getHeader())
|
|
147
|
-
}
|
|
151
|
+
options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
|
|
148
152
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this.log.trace('Setting Content-Length from byteRangeContext: %d', contentLength)
|
|
155
|
-
response.headers.set('Content-Length', contentLength.toString())
|
|
156
|
-
}
|
|
157
|
-
}
|
|
153
|
+
const range = getRangeHeader(resource.toString(), headers)
|
|
154
|
+
|
|
155
|
+
if (range instanceof Response) {
|
|
156
|
+
// invalid range request
|
|
157
|
+
return this.handleFinalResponse(range)
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
let contentDisposition: string | undefined
|
|
160
|
+
let url: URL
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
try {
|
|
163
|
+
url = parseURLString(typeof resource === 'string' ? resource : `ipfs://${resource}`)
|
|
164
|
+
} catch (err: any) {
|
|
165
|
+
return this.handleFinalResponse(badRequestResponse(resource.toString(), err))
|
|
167
166
|
}
|
|
168
167
|
|
|
169
|
-
|
|
170
|
-
if (context?.query?.filename != null) {
|
|
171
|
-
this.log.trace('specific filename requested')
|
|
168
|
+
let parsedResult: ResolveURLResult
|
|
172
169
|
|
|
173
|
-
|
|
174
|
-
|
|
170
|
+
// if just an IPNS record has been requested, don't try to load the block
|
|
171
|
+
// the record points to or do any recursive IPNS resolving
|
|
172
|
+
if (isIPNSRecordRequest(headers)) {
|
|
173
|
+
if (url.protocol !== 'ipns:') {
|
|
174
|
+
return notAcceptableResponse(url, [])
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
// @ts-expect-error ipnsRecordPlugin may not be of type IpnsRecordPlugin
|
|
178
|
+
const ipnsRecordPlugin: IpnsRecordPlugin | undefined = this.plugins.find(plugin => plugin.id === 'ipns-record-plugin')
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
180
|
+
if (ipnsRecordPlugin == null) {
|
|
181
|
+
return notAcceptableResponse(url, [])
|
|
182
|
+
}
|
|
184
183
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
return this.handleFinalResponse(await ipnsRecordPlugin.handle({
|
|
185
|
+
range,
|
|
186
|
+
url,
|
|
187
|
+
resource: resource.toString(),
|
|
188
|
+
options
|
|
190
189
|
}))
|
|
190
|
+
} else {
|
|
191
|
+
try {
|
|
192
|
+
parsedResult = await this.urlResolver.resolve(url, serverTiming, {
|
|
193
|
+
...options,
|
|
194
|
+
isRawBlockRequest: isRawBlockRequest(headers),
|
|
195
|
+
onlyIfCached: headers.get('cache-control') === 'only-if-cached'
|
|
196
|
+
})
|
|
197
|
+
} catch (err: any) {
|
|
198
|
+
options?.signal?.throwIfAborted()
|
|
199
|
+
|
|
200
|
+
this.log.error('error parsing resource %s - %e', resource, err)
|
|
201
|
+
return this.handleFinalResponse(errorToResponse(resource, err))
|
|
202
|
+
}
|
|
191
203
|
}
|
|
192
204
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
protocol: context.protocol
|
|
198
|
-
})
|
|
199
|
-
}
|
|
205
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', {
|
|
206
|
+
cid: parsedResult.terminalElement.cid,
|
|
207
|
+
path: parsedResult.url.pathname
|
|
208
|
+
}))
|
|
200
209
|
|
|
201
|
-
|
|
202
|
-
response.headers.set('X-Ipfs-Path', uriEncodeIPFSPath(context.ipfsPath))
|
|
203
|
-
}
|
|
210
|
+
const accept = this.getAcceptHeader(parsedResult.url, headers.get('accept'), parsedResult.terminalElement.cid)
|
|
204
211
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
response.headers.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')
|
|
208
|
-
response.headers.set('Access-Control-Allow-Headers', 'Range, X-Requested-With')
|
|
209
|
-
response.headers.set('Access-Control-Expose-Headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Ipfs-Roots, X-Stream-Output')
|
|
212
|
+
if (accept instanceof Response) {
|
|
213
|
+
this.log('allowed media types for requested CID did not contain anything the client can understand')
|
|
210
214
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
response.headers.set('Accept-Ranges', 'bytes')
|
|
214
|
-
} else {
|
|
215
|
-
// set accept-ranges to none to disable range requests for streaming responses
|
|
216
|
-
response.headers.set('Accept-Ranges', 'none')
|
|
215
|
+
// invalid accept header
|
|
216
|
+
return this.handleFinalResponse(accept)
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
const context: PluginContext = {
|
|
220
|
+
...parsedResult,
|
|
221
|
+
resource: resource.toString(),
|
|
222
|
+
accept,
|
|
223
|
+
range,
|
|
224
|
+
options,
|
|
225
|
+
onProgress: options?.onProgress,
|
|
226
|
+
serverTiming,
|
|
227
|
+
headers
|
|
222
228
|
}
|
|
223
229
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
this.log.trace('finding handler for cid code "0x%s" and response content types %s', parsedResult.terminalElement.cid.code.toString(16), accept.map(header => header.contentType.mediaType).join(', '))
|
|
231
|
+
|
|
232
|
+
const response = await this.runPluginPipeline(context)
|
|
233
|
+
|
|
234
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', {
|
|
235
|
+
cid: parsedResult.terminalElement.cid,
|
|
236
|
+
path: parsedResult.url.pathname
|
|
237
|
+
}))
|
|
238
|
+
|
|
239
|
+
if (response == null) {
|
|
240
|
+
this.log.error('no plugin could handle request for %s', resource)
|
|
230
241
|
}
|
|
231
242
|
|
|
232
|
-
return response
|
|
243
|
+
return this.handleFinalResponse(response, Boolean(options?.withServerTiming) || Boolean(this.withServerTiming), context)
|
|
233
244
|
}
|
|
234
245
|
|
|
235
246
|
/**
|
|
236
|
-
*
|
|
237
|
-
*
|
|
247
|
+
* Returns a prioritized list of acceptable content types for the response
|
|
248
|
+
* based on the CID and a passed `Accept` header
|
|
238
249
|
*/
|
|
239
|
-
private
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
250
|
+
private getAcceptHeader (url: URL, accept?: string | null, cid?: CID): AcceptHeader[] | Response {
|
|
251
|
+
if (accept == null || accept === '') {
|
|
252
|
+
// if the user has specified CAR options but no Accept header, default to
|
|
253
|
+
// the car content type with the passed options
|
|
254
|
+
try {
|
|
255
|
+
const dagScope = url.searchParams.get('dag-scope')
|
|
256
|
+
const entityBytes = url.searchParams.get('entity-bytes')
|
|
257
|
+
const dups = url.searchParams.get('car-dups')
|
|
258
|
+
const order = url.searchParams.get('car-order')
|
|
259
|
+
const version = url.searchParams.get('car-version')
|
|
260
|
+
|
|
261
|
+
if (dagScope != null ||
|
|
262
|
+
entityBytes != null ||
|
|
263
|
+
dups != null ||
|
|
264
|
+
entityBytes != null ||
|
|
265
|
+
order != null
|
|
266
|
+
) {
|
|
267
|
+
const options: Record<string, string> = {}
|
|
268
|
+
|
|
269
|
+
if (dups != null) {
|
|
270
|
+
options.dups = dups
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (order != null) {
|
|
274
|
+
options.order = order
|
|
275
|
+
}
|
|
243
276
|
|
|
244
|
-
|
|
277
|
+
if (version != null) {
|
|
278
|
+
options.version = version
|
|
279
|
+
}
|
|
245
280
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
281
|
+
return [{
|
|
282
|
+
contentType: CONTENT_TYPE_CAR,
|
|
283
|
+
options
|
|
284
|
+
}]
|
|
285
|
+
}
|
|
286
|
+
} catch {}
|
|
249
287
|
|
|
250
|
-
|
|
288
|
+
// yolo content-type
|
|
289
|
+
accept = '*/*'
|
|
290
|
+
// return []
|
|
291
|
+
}
|
|
251
292
|
|
|
252
|
-
|
|
253
|
-
|
|
293
|
+
// allow user to choose specific output type
|
|
294
|
+
const acceptable: AcceptHeader[] = []
|
|
254
295
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
296
|
+
const requestedMimeTypes = accept
|
|
297
|
+
.split(',')
|
|
298
|
+
.map(s => {
|
|
299
|
+
const parts = s.trim().split(';')
|
|
258
300
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
} else {
|
|
262
|
-
this.log.trace('no plugins found that can handle request by CID code; exiting pipeline')
|
|
263
|
-
break
|
|
301
|
+
const options: Record<string, string> = {
|
|
302
|
+
q: '1'
|
|
264
303
|
}
|
|
265
|
-
}
|
|
266
304
|
|
|
267
|
-
|
|
305
|
+
for (let i = 1; i < parts.length; i++) {
|
|
306
|
+
const [key, value] = parts[i].split('=').map(s => s.trim())
|
|
268
307
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
308
|
+
options[key] = value
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
mediaType: `${parts[0]}`.trim(),
|
|
313
|
+
options
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
.sort((a, b) => {
|
|
317
|
+
if (a.options.q === b.options.q) {
|
|
318
|
+
return 0
|
|
319
|
+
}
|
|
272
320
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
pluginsUsed.add(plugin.id)
|
|
321
|
+
if (a.options.q > b.options.q) {
|
|
322
|
+
return -1
|
|
323
|
+
}
|
|
277
324
|
|
|
278
|
-
|
|
325
|
+
return 1
|
|
326
|
+
})
|
|
279
327
|
|
|
280
|
-
|
|
328
|
+
const supportedContentTypes = getSupportedContentTypes(url.protocol, cid)
|
|
281
329
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
finalResponse = maybeResponse
|
|
285
|
-
pluginHandled = true
|
|
286
|
-
break
|
|
287
|
-
}
|
|
288
|
-
} catch (err: any) {
|
|
289
|
-
if (context.options?.signal?.aborted) {
|
|
290
|
-
throw new AbortError(context.options?.signal?.reason)
|
|
291
|
-
}
|
|
330
|
+
for (const headerFormat of requestedMimeTypes) {
|
|
331
|
+
const [headerFormatType, headerFormatSubType] = headerFormat.mediaType.split('/')
|
|
292
332
|
|
|
293
|
-
|
|
333
|
+
for (const contentType of supportedContentTypes) {
|
|
334
|
+
const [contentTypeType, contentTypeSubType] = contentType.mediaType.split('/')
|
|
294
335
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
'content-type': 'application/json'
|
|
300
|
-
}
|
|
336
|
+
if (headerFormat.mediaType.includes(contentType.mediaType)) {
|
|
337
|
+
acceptable.push({
|
|
338
|
+
contentType,
|
|
339
|
+
options: headerFormat.options
|
|
301
340
|
})
|
|
302
|
-
} finally {
|
|
303
|
-
// on each plugin call, check for changes in the context
|
|
304
|
-
const newModificationId = context.modified
|
|
305
|
-
contextChanged = newModificationId !== prevModificationId
|
|
306
|
-
if (contextChanged) {
|
|
307
|
-
prevModificationId = newModificationId
|
|
308
|
-
}
|
|
309
341
|
}
|
|
310
342
|
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
343
|
+
if (headerFormat.mediaType === '*/*') {
|
|
344
|
+
acceptable.push({
|
|
345
|
+
contentType,
|
|
346
|
+
options: headerFormat.options
|
|
347
|
+
})
|
|
314
348
|
}
|
|
315
|
-
}
|
|
316
349
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
350
|
+
if (headerFormat.mediaType.startsWith('*/') && contentTypeSubType === headerFormatSubType) {
|
|
351
|
+
acceptable.push({
|
|
352
|
+
contentType,
|
|
353
|
+
options: headerFormat.options
|
|
354
|
+
})
|
|
355
|
+
}
|
|
320
356
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
357
|
+
if (headerFormat.mediaType.endsWith('/*') && contentTypeType === headerFormatType) {
|
|
358
|
+
acceptable.push({
|
|
359
|
+
contentType,
|
|
360
|
+
options: headerFormat.options
|
|
361
|
+
})
|
|
362
|
+
}
|
|
324
363
|
}
|
|
325
364
|
}
|
|
326
365
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
366
|
+
if (acceptable.length === 0) {
|
|
367
|
+
this.log('requested %o', requestedMimeTypes.map(({ mediaType }) => mediaType))
|
|
368
|
+
this.log('supported %o', supportedContentTypes.map(({ mediaType }) => mediaType))
|
|
369
|
+
|
|
370
|
+
return notAcceptableResponse(url, supportedContentTypes)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return acceptable
|
|
334
374
|
}
|
|
335
375
|
|
|
336
376
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
* always exit early (and cleanly) if a given signal is aborted
|
|
377
|
+
* The last place a Response touches in verified-fetch before being returned
|
|
378
|
+
* to the user. This is where we add the Server-Timing header to the response
|
|
379
|
+
* if it has been collected. It should be used for any final processing of the
|
|
380
|
+
* response before it is returned to the user.
|
|
342
381
|
*/
|
|
343
|
-
|
|
344
|
-
|
|
382
|
+
private handleFinalResponse (response: Response, withServerTiming?: boolean, context?: PluginContext): Response {
|
|
383
|
+
const contentType = getContentType(response.headers.get('content-type')) ?? CONTENT_TYPE_OCTET_STREAM
|
|
345
384
|
|
|
346
|
-
if (
|
|
347
|
-
|
|
385
|
+
if (withServerTiming === true && context?.serverTiming != null) {
|
|
386
|
+
const timingHeader = context?.serverTiming.getHeader()
|
|
387
|
+
|
|
388
|
+
if (timingHeader !== '') {
|
|
389
|
+
response.headers.set('server-timing', timingHeader)
|
|
390
|
+
}
|
|
348
391
|
}
|
|
349
392
|
|
|
350
|
-
|
|
351
|
-
|
|
393
|
+
if (context?.terminalElement.cid != null && response.headers.get('etag') == null) {
|
|
394
|
+
response.headers.set('etag', getETag({
|
|
395
|
+
cid: context.terminalElement.cid,
|
|
396
|
+
contentType
|
|
397
|
+
}))
|
|
398
|
+
}
|
|
352
399
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
400
|
+
if (context?.url?.protocol != null && context.ttl != null) {
|
|
401
|
+
setCacheControlHeader({
|
|
402
|
+
response,
|
|
403
|
+
ttl: context.ttl,
|
|
404
|
+
protocol: context.url.protocol
|
|
405
|
+
})
|
|
406
|
+
}
|
|
358
407
|
|
|
359
|
-
|
|
408
|
+
if (context?.terminalElement.cid != null) {
|
|
409
|
+
// headers can ony contain extended ASCII but IPFS paths can be unicode
|
|
410
|
+
const decodedPath = decodeURI(context?.url.pathname)
|
|
411
|
+
const path = uint8ArrayToString(uint8ArrayFromString(decodedPath), 'ascii')
|
|
360
412
|
|
|
361
|
-
|
|
413
|
+
response.headers.set('x-ipfs-path', `/${context.url.protocol === 'ipfs:' ? 'ipfs' : 'ipns'}/${context?.url.hostname}${path}`)
|
|
414
|
+
}
|
|
362
415
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
416
|
+
// set CORS headers. If hosting your own gateway with verified-fetch behind
|
|
417
|
+
// the scenes, you can alter these before you send the response to the
|
|
418
|
+
// client.
|
|
419
|
+
response.headers.set('access-control-allow-origin', '*')
|
|
420
|
+
response.headers.set('access-control-allow-methods', 'GET, HEAD, OPTIONS')
|
|
421
|
+
response.headers.set('access-control-allow-headers', 'Range, X-Requested-With')
|
|
422
|
+
response.headers.set('access-control-expose-headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Ipfs-Roots, X-Stream-Output')
|
|
370
423
|
|
|
371
|
-
|
|
424
|
+
if (context?.options?.method === 'HEAD') {
|
|
425
|
+
// don't send the body for HEAD requests
|
|
426
|
+
return new Response(null, {
|
|
427
|
+
status: 200,
|
|
428
|
+
headers: response.headers
|
|
429
|
+
})
|
|
372
430
|
}
|
|
373
431
|
|
|
374
|
-
|
|
432
|
+
// make sure users are not expected to "download" error responses
|
|
433
|
+
if (response.status > 399) {
|
|
434
|
+
response.headers.delete('content-disposition')
|
|
435
|
+
}
|
|
375
436
|
|
|
376
|
-
|
|
437
|
+
return response
|
|
438
|
+
}
|
|
377
439
|
|
|
378
|
-
|
|
379
|
-
|
|
440
|
+
private async runPluginPipeline (context: PluginContext): Promise<Response> {
|
|
441
|
+
let finalResponse: Response | undefined
|
|
442
|
+
const pluginsUsed = new Set<string>()
|
|
380
443
|
|
|
381
|
-
|
|
382
|
-
this.log.error('could not fulfil request based on accept header')
|
|
383
|
-
return this.handleFinalResponse(notAcceptableResponse(resource.toString()))
|
|
384
|
-
}
|
|
444
|
+
this.log.trace('checking which plugins can handle %c%s with accept %s', context.terminalElement.cid, context.url.pathname, context.accept.map(contentType => contentType.contentType.mediaType).join(', '))
|
|
385
445
|
|
|
386
|
-
const
|
|
446
|
+
const plugins = this.plugins.filter(p => !pluginsUsed.has(p.id)).filter(p => p.canHandle(context))
|
|
387
447
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return
|
|
448
|
+
if (plugins.length === 0) {
|
|
449
|
+
this.log.trace('no plugins found that can handle request; exiting pipeline')
|
|
450
|
+
return notImplementedResponse(context.resource)
|
|
391
451
|
}
|
|
392
452
|
|
|
393
|
-
|
|
394
|
-
...parsedResult,
|
|
395
|
-
resource: resource.toString(),
|
|
396
|
-
accept,
|
|
397
|
-
options,
|
|
398
|
-
onProgress: options?.onProgress,
|
|
399
|
-
modified: 0,
|
|
400
|
-
plugins: this.plugins.map(p => p.id),
|
|
401
|
-
query: parsedResult.query ?? {},
|
|
402
|
-
withServerTiming: Boolean(options?.withServerTiming) || Boolean(this.withServerTiming),
|
|
403
|
-
serverTiming
|
|
404
|
-
}
|
|
453
|
+
this.log.trace('plugins ready to handle request: %s', plugins.map(p => p.id).join(', '))
|
|
405
454
|
|
|
406
|
-
|
|
455
|
+
// track if any plugin changed the context or returned a response
|
|
456
|
+
const contextChanged = false
|
|
457
|
+
let pluginHandled = false
|
|
407
458
|
|
|
408
|
-
const
|
|
459
|
+
for (const plugin of plugins) {
|
|
460
|
+
try {
|
|
461
|
+
this.log('invoking plugin: %s', plugin.id)
|
|
462
|
+
pluginsUsed.add(plugin.id)
|
|
409
463
|
|
|
410
|
-
|
|
411
|
-
cid: parsedResult.cid,
|
|
412
|
-
path: parsedResult.path
|
|
413
|
-
}))
|
|
464
|
+
const maybeResponse = await plugin.handle(context)
|
|
414
465
|
|
|
415
|
-
|
|
416
|
-
|
|
466
|
+
this.log('plugin response %s %o', plugin.id, maybeResponse)
|
|
467
|
+
|
|
468
|
+
if (maybeResponse != null) {
|
|
469
|
+
// if a plugin returns a final Response, short-circuit
|
|
470
|
+
finalResponse = maybeResponse
|
|
471
|
+
pluginHandled = true
|
|
472
|
+
break
|
|
473
|
+
}
|
|
474
|
+
} catch (err: any) {
|
|
475
|
+
if (context.options?.signal?.aborted) {
|
|
476
|
+
throw new AbortError(context.options?.signal?.reason)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
this.log.error('error in plugin %s - %e', plugin.id, err)
|
|
480
|
+
|
|
481
|
+
return internalServerErrorResponse(context.resource, JSON.stringify({
|
|
482
|
+
error: errorToObject(err)
|
|
483
|
+
}), {
|
|
484
|
+
headers: {
|
|
485
|
+
'content-type': 'application/json'
|
|
486
|
+
}
|
|
487
|
+
})
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (finalResponse != null) {
|
|
491
|
+
this.log.trace('plugin %s produced final response', plugin.id)
|
|
492
|
+
break
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (pluginHandled && finalResponse != null) {
|
|
496
|
+
break
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (!contextChanged) {
|
|
500
|
+
this.log.trace('no context changes and no final response; exiting pipeline.')
|
|
501
|
+
break
|
|
502
|
+
}
|
|
417
503
|
}
|
|
418
504
|
|
|
419
|
-
return
|
|
505
|
+
return finalResponse ?? notImplementedResponse(context.resource, JSON.stringify({
|
|
506
|
+
error: errorToObject(new Error('No verified fetch plugin could handle the request'))
|
|
507
|
+
}), {
|
|
508
|
+
headers: {
|
|
509
|
+
'content-type': 'application/json'
|
|
510
|
+
}
|
|
511
|
+
})
|
|
420
512
|
}
|
|
421
513
|
|
|
422
514
|
/**
|