@helia/verified-fetch 4.1.1 → 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 +7 -40
- 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 +302 -238
- 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 +199 -68
- 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 +353 -270
- 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 -69
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,117 +120,306 @@ 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
|
-
|
|
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)
|
|
140
|
+
|
|
141
|
+
if (opts?.method === 'OPTIONS') {
|
|
142
|
+
return this.handleFinalResponse(new Response(null, {
|
|
143
|
+
status: 200
|
|
144
|
+
}))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const options = convertOptions(opts)
|
|
148
|
+
const headers = new Headers(options?.headers)
|
|
149
|
+
const serverTiming = new ServerTiming()
|
|
150
|
+
|
|
151
|
+
options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
|
|
152
|
+
|
|
153
|
+
const range = getRangeHeader(resource.toString(), headers)
|
|
154
|
+
|
|
155
|
+
if (range instanceof Response) {
|
|
156
|
+
// invalid range request
|
|
157
|
+
return this.handleFinalResponse(range)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let url: URL
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
url = parseURLString(typeof resource === 'string' ? resource : `ipfs://${resource}`)
|
|
164
|
+
} catch (err: any) {
|
|
165
|
+
return this.handleFinalResponse(badRequestResponse(resource.toString(), err))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let parsedResult: ResolveURLResult
|
|
169
|
+
|
|
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
|
+
}
|
|
176
|
+
|
|
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
|
+
|
|
180
|
+
if (ipnsRecordPlugin == null) {
|
|
181
|
+
return notAcceptableResponse(url, [])
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return this.handleFinalResponse(await ipnsRecordPlugin.handle({
|
|
185
|
+
range,
|
|
186
|
+
url,
|
|
187
|
+
resource: resource.toString(),
|
|
188
|
+
options
|
|
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
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', {
|
|
206
|
+
cid: parsedResult.terminalElement.cid,
|
|
207
|
+
path: parsedResult.url.pathname
|
|
208
|
+
}))
|
|
209
|
+
|
|
210
|
+
const accept = this.getAcceptHeader(parsedResult.url, headers.get('accept'), parsedResult.terminalElement.cid)
|
|
211
|
+
|
|
212
|
+
if (accept instanceof Response) {
|
|
213
|
+
this.log('allowed media types for requested CID did not contain anything the client can understand')
|
|
214
|
+
|
|
215
|
+
// invalid accept header
|
|
216
|
+
return this.handleFinalResponse(accept)
|
|
127
217
|
}
|
|
128
218
|
|
|
129
|
-
|
|
219
|
+
const context: PluginContext = {
|
|
220
|
+
...parsedResult,
|
|
221
|
+
resource: resource.toString(),
|
|
222
|
+
accept,
|
|
223
|
+
range,
|
|
224
|
+
options,
|
|
225
|
+
onProgress: options?.onProgress,
|
|
226
|
+
serverTiming,
|
|
227
|
+
headers
|
|
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
|
+
}))
|
|
130
238
|
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
this.blockstoreSessions.set(key, session)
|
|
239
|
+
if (response == null) {
|
|
240
|
+
this.log.error('no plugin could handle request for %s', resource)
|
|
134
241
|
}
|
|
135
242
|
|
|
136
|
-
return
|
|
243
|
+
return this.handleFinalResponse(response, Boolean(options?.withServerTiming) || Boolean(this.withServerTiming), context)
|
|
137
244
|
}
|
|
138
245
|
|
|
139
246
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* response before it is returned to the user.
|
|
247
|
+
* Returns a prioritized list of acceptable content types for the response
|
|
248
|
+
* based on the CID and a passed `Accept` header
|
|
143
249
|
*/
|
|
144
|
-
private
|
|
145
|
-
if (
|
|
146
|
-
|
|
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
|
+
}
|
|
147
272
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
273
|
+
if (order != null) {
|
|
274
|
+
options.order = order
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (version != null) {
|
|
278
|
+
options.version = version
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return [{
|
|
282
|
+
contentType: CONTENT_TYPE_CAR,
|
|
283
|
+
options
|
|
284
|
+
}]
|
|
285
|
+
}
|
|
286
|
+
} catch {}
|
|
287
|
+
|
|
288
|
+
// yolo content-type
|
|
289
|
+
accept = '*/*'
|
|
290
|
+
// return []
|
|
151
291
|
}
|
|
152
292
|
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
293
|
+
// allow user to choose specific output type
|
|
294
|
+
const acceptable: AcceptHeader[] = []
|
|
295
|
+
|
|
296
|
+
const requestedMimeTypes = accept
|
|
297
|
+
.split(',')
|
|
298
|
+
.map(s => {
|
|
299
|
+
const parts = s.trim().split(';')
|
|
300
|
+
|
|
301
|
+
const options: Record<string, string> = {
|
|
302
|
+
q: '1'
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
for (let i = 1; i < parts.length; i++) {
|
|
306
|
+
const [key, value] = parts[i].split('=').map(s => s.trim())
|
|
307
|
+
|
|
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
|
+
}
|
|
320
|
+
|
|
321
|
+
if (a.options.q > b.options.q) {
|
|
322
|
+
return -1
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return 1
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
const supportedContentTypes = getSupportedContentTypes(url.protocol, cid)
|
|
329
|
+
|
|
330
|
+
for (const headerFormat of requestedMimeTypes) {
|
|
331
|
+
const [headerFormatType, headerFormatSubType] = headerFormat.mediaType.split('/')
|
|
332
|
+
|
|
333
|
+
for (const contentType of supportedContentTypes) {
|
|
334
|
+
const [contentTypeType, contentTypeSubType] = contentType.mediaType.split('/')
|
|
335
|
+
|
|
336
|
+
if (headerFormat.mediaType.includes(contentType.mediaType)) {
|
|
337
|
+
acceptable.push({
|
|
338
|
+
contentType,
|
|
339
|
+
options: headerFormat.options
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (headerFormat.mediaType === '*/*') {
|
|
344
|
+
acceptable.push({
|
|
345
|
+
contentType,
|
|
346
|
+
options: headerFormat.options
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (headerFormat.mediaType.startsWith('*/') && contentTypeSubType === headerFormatSubType) {
|
|
351
|
+
acceptable.push({
|
|
352
|
+
contentType,
|
|
353
|
+
options: headerFormat.options
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (headerFormat.mediaType.endsWith('/*') && contentTypeType === headerFormatType) {
|
|
358
|
+
acceptable.push({
|
|
359
|
+
contentType,
|
|
360
|
+
options: headerFormat.options
|
|
361
|
+
})
|
|
160
362
|
}
|
|
161
363
|
}
|
|
162
364
|
}
|
|
163
365
|
|
|
164
|
-
|
|
165
|
-
|
|
366
|
+
if (acceptable.length === 0) {
|
|
367
|
+
this.log('requested %o', requestedMimeTypes.map(({ mediaType }) => mediaType))
|
|
368
|
+
this.log('supported %o', supportedContentTypes.map(({ mediaType }) => mediaType))
|
|
166
369
|
|
|
167
|
-
|
|
168
|
-
if (context?.query?.download === true) {
|
|
169
|
-
this.log.trace('download requested')
|
|
170
|
-
contentDisposition = 'attachment'
|
|
370
|
+
return notAcceptableResponse(url, supportedContentTypes)
|
|
171
371
|
}
|
|
172
372
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this.log.trace('specific filename requested')
|
|
373
|
+
return acceptable
|
|
374
|
+
}
|
|
176
375
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
376
|
+
/**
|
|
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.
|
|
381
|
+
*/
|
|
382
|
+
private handleFinalResponse (response: Response, withServerTiming?: boolean, context?: PluginContext): Response {
|
|
383
|
+
const contentType = getContentType(response.headers.get('content-type')) ?? CONTENT_TYPE_OCTET_STREAM
|
|
180
384
|
|
|
181
|
-
|
|
182
|
-
|
|
385
|
+
if (withServerTiming === true && context?.serverTiming != null) {
|
|
386
|
+
const timingHeader = context?.serverTiming.getHeader()
|
|
183
387
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
388
|
+
if (timingHeader !== '') {
|
|
389
|
+
response.headers.set('server-timing', timingHeader)
|
|
390
|
+
}
|
|
187
391
|
}
|
|
188
392
|
|
|
189
|
-
if (context?.cid != null && response.headers.get('etag') == null) {
|
|
393
|
+
if (context?.terminalElement.cid != null && response.headers.get('etag') == null) {
|
|
190
394
|
response.headers.set('etag', getETag({
|
|
191
|
-
cid: context.
|
|
192
|
-
|
|
193
|
-
weak: false
|
|
395
|
+
cid: context.terminalElement.cid,
|
|
396
|
+
contentType
|
|
194
397
|
}))
|
|
195
398
|
}
|
|
196
399
|
|
|
197
|
-
if (context?.protocol != null && context.ttl != null) {
|
|
400
|
+
if (context?.url?.protocol != null && context.ttl != null) {
|
|
198
401
|
setCacheControlHeader({
|
|
199
402
|
response,
|
|
200
403
|
ttl: context.ttl,
|
|
201
|
-
protocol: context.protocol
|
|
404
|
+
protocol: context.url.protocol
|
|
202
405
|
})
|
|
203
406
|
}
|
|
204
407
|
|
|
205
|
-
if (context?.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// set CORS headers. If hosting your own gateway with verified-fetch behind the scenes, you can alter these before you send the response to the client.
|
|
210
|
-
response.headers.set('Access-Control-Allow-Origin', '*')
|
|
211
|
-
response.headers.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')
|
|
212
|
-
response.headers.set('Access-Control-Allow-Headers', 'Range, X-Requested-With')
|
|
213
|
-
response.headers.set('Access-Control-Expose-Headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Ipfs-Roots, X-Stream-Output')
|
|
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')
|
|
214
412
|
|
|
215
|
-
|
|
216
|
-
// if we are not doing streaming responses, set the Accept-Ranges header to bytes to enable range requests
|
|
217
|
-
response.headers.set('Accept-Ranges', 'bytes')
|
|
218
|
-
} else {
|
|
219
|
-
// set accept-ranges to none to disable range requests for streaming responses
|
|
220
|
-
response.headers.set('Accept-Ranges', 'none')
|
|
413
|
+
response.headers.set('x-ipfs-path', `/${context.url.protocol === 'ipfs:' ? 'ipfs' : 'ipns'}/${context?.url.hostname}${path}`)
|
|
221
414
|
}
|
|
222
415
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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')
|
|
227
423
|
|
|
228
424
|
if (context?.options?.method === 'HEAD') {
|
|
229
425
|
// don't send the body for HEAD requests
|
|
@@ -241,86 +437,59 @@ export class VerifiedFetch {
|
|
|
241
437
|
return response
|
|
242
438
|
}
|
|
243
439
|
|
|
244
|
-
|
|
245
|
-
* Runs plugins in a loop. After each plugin that returns `null` (partial/no final),
|
|
246
|
-
* we re-check `canHandle()` for all plugins in the next iteration if the context changed.
|
|
247
|
-
*/
|
|
248
|
-
private async runPluginPipeline (context: PluginContext, maxPasses: number = 3): Promise<Response> {
|
|
440
|
+
private async runPluginPipeline (context: PluginContext): Promise<Response> {
|
|
249
441
|
let finalResponse: Response | undefined
|
|
250
|
-
let passCount = 0
|
|
251
442
|
const pluginsUsed = new Set<string>()
|
|
252
443
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
while (passCount < maxPasses) {
|
|
256
|
-
this.log(`starting pipeline pass #${passCount + 1}`)
|
|
257
|
-
passCount++
|
|
258
|
-
|
|
259
|
-
this.log.trace('checking which plugins can handle %c%s with accept %o', context.cid, context.path.length > 0 ? `/${context.path.join('/')}` : '', context.accept)
|
|
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(', '))
|
|
260
445
|
|
|
261
|
-
|
|
262
|
-
const readyPlugins = this.plugins.filter(p => !pluginsUsed.has(p.id)).filter(p => p.canHandle(context))
|
|
446
|
+
const plugins = this.plugins.filter(p => !pluginsUsed.has(p.id)).filter(p => p.canHandle(context))
|
|
263
447
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (plugins.length > 0) {
|
|
269
|
-
readyPlugins.push(...plugins)
|
|
270
|
-
} else {
|
|
271
|
-
this.log.trace('no plugins found that can handle request by CID code; exiting pipeline')
|
|
272
|
-
break
|
|
273
|
-
}
|
|
274
|
-
}
|
|
448
|
+
if (plugins.length === 0) {
|
|
449
|
+
this.log.trace('no plugins found that can handle request; exiting pipeline')
|
|
450
|
+
return notImplementedResponse(context.resource)
|
|
451
|
+
}
|
|
275
452
|
|
|
276
|
-
|
|
453
|
+
this.log.trace('plugins ready to handle request: %s', plugins.map(p => p.id).join(', '))
|
|
277
454
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
455
|
+
// track if any plugin changed the context or returned a response
|
|
456
|
+
const contextChanged = false
|
|
457
|
+
let pluginHandled = false
|
|
281
458
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
459
|
+
for (const plugin of plugins) {
|
|
460
|
+
try {
|
|
461
|
+
this.log('invoking plugin: %s', plugin.id)
|
|
462
|
+
pluginsUsed.add(plugin.id)
|
|
286
463
|
|
|
287
|
-
|
|
464
|
+
const maybeResponse = await plugin.handle(context)
|
|
288
465
|
|
|
289
|
-
|
|
466
|
+
this.log('plugin response %s %o', plugin.id, maybeResponse)
|
|
290
467
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
+
}
|
|
301
478
|
|
|
302
|
-
|
|
479
|
+
this.log.error('error in plugin %s - %e', plugin.id, err)
|
|
303
480
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
} finally {
|
|
312
|
-
// on each plugin call, check for changes in the context
|
|
313
|
-
const newModificationId = context.modified
|
|
314
|
-
contextChanged = newModificationId !== prevModificationId
|
|
315
|
-
if (contextChanged) {
|
|
316
|
-
prevModificationId = newModificationId
|
|
481
|
+
return internalServerErrorResponse(context.resource, JSON.stringify({
|
|
482
|
+
error: errorToObject(err)
|
|
483
|
+
}), {
|
|
484
|
+
headers: {
|
|
485
|
+
'content-type': 'application/json'
|
|
317
486
|
}
|
|
318
|
-
}
|
|
487
|
+
})
|
|
488
|
+
}
|
|
319
489
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
490
|
+
if (finalResponse != null) {
|
|
491
|
+
this.log.trace('plugin %s produced final response', plugin.id)
|
|
492
|
+
break
|
|
324
493
|
}
|
|
325
494
|
|
|
326
495
|
if (pluginHandled && finalResponse != null) {
|
|
@@ -342,92 +511,6 @@ export class VerifiedFetch {
|
|
|
342
511
|
})
|
|
343
512
|
}
|
|
344
513
|
|
|
345
|
-
/**
|
|
346
|
-
* We're starting to get to the point where we need a queue or pipeline of
|
|
347
|
-
* operations to perform and a single place to handle errors.
|
|
348
|
-
*
|
|
349
|
-
* TODO: move operations called by fetch to a queue of operations where we can
|
|
350
|
-
* always exit early (and cleanly) if a given signal is aborted
|
|
351
|
-
*/
|
|
352
|
-
async fetch (resource: Resource, opts?: VerifiedFetchOptions): Promise<Response> {
|
|
353
|
-
this.log('fetch %s', resource)
|
|
354
|
-
|
|
355
|
-
if (opts?.method === 'OPTIONS') {
|
|
356
|
-
return this.handleFinalResponse(new Response(null, { status: 200 }))
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const options = convertOptions(opts)
|
|
360
|
-
const serverTiming = new ServerTiming()
|
|
361
|
-
|
|
362
|
-
const urlResolver = new URLResolver({
|
|
363
|
-
ipnsResolver: this.ipnsResolver,
|
|
364
|
-
dnsLink: this.dnsLink,
|
|
365
|
-
timing: serverTiming
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
|
|
369
|
-
|
|
370
|
-
let parsedResult: ResolveURLResult
|
|
371
|
-
|
|
372
|
-
try {
|
|
373
|
-
parsedResult = await urlResolver.resolve(resource, options)
|
|
374
|
-
} catch (err: any) {
|
|
375
|
-
if (options?.signal?.aborted) {
|
|
376
|
-
throw new AbortError(options?.signal?.reason)
|
|
377
|
-
}
|
|
378
|
-
this.log.error('error parsing resource %s', resource, err)
|
|
379
|
-
|
|
380
|
-
return this.handleFinalResponse(badRequestResponse(resource.toString(), err))
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', { cid: parsedResult.cid, path: parsedResult.path }))
|
|
384
|
-
|
|
385
|
-
const acceptHeader = getResolvedAcceptHeader({ query: parsedResult.query, headers: options?.headers, logger: this.helia.logger })
|
|
386
|
-
|
|
387
|
-
const accept: AcceptHeader | undefined = selectOutputType(parsedResult.cid, acceptHeader)
|
|
388
|
-
this.log('accept %o', accept)
|
|
389
|
-
|
|
390
|
-
if (acceptHeader != null && accept == null) {
|
|
391
|
-
this.log.error('could not fulfil request based on accept header')
|
|
392
|
-
return this.handleFinalResponse(notAcceptableResponse(resource.toString()))
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const responseContentType: string = accept?.mimeType.split(';')[0] ?? 'application/octet-stream'
|
|
396
|
-
|
|
397
|
-
const redirectResponse = await getRedirectResponse({ resource, options, logger: this.helia.logger, cid: parsedResult.cid })
|
|
398
|
-
if (redirectResponse != null) {
|
|
399
|
-
return this.handleFinalResponse(redirectResponse)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const context: PluginContext = {
|
|
403
|
-
...parsedResult,
|
|
404
|
-
resource: resource.toString(),
|
|
405
|
-
accept,
|
|
406
|
-
options,
|
|
407
|
-
onProgress: options?.onProgress,
|
|
408
|
-
modified: 0,
|
|
409
|
-
plugins: this.plugins.map(p => p.id),
|
|
410
|
-
query: parsedResult.query ?? {},
|
|
411
|
-
withServerTiming: Boolean(options?.withServerTiming) || Boolean(this.withServerTiming),
|
|
412
|
-
serverTiming
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
this.log.trace('finding handler for cid code "%s" and response content type "%s"', parsedResult.cid.code, responseContentType)
|
|
416
|
-
|
|
417
|
-
const response = await this.runPluginPipeline(context)
|
|
418
|
-
|
|
419
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', {
|
|
420
|
-
cid: parsedResult.cid,
|
|
421
|
-
path: parsedResult.path
|
|
422
|
-
}))
|
|
423
|
-
|
|
424
|
-
if (response == null) {
|
|
425
|
-
this.log.error('no plugin could handle request for %s', resource)
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
return this.handleFinalResponse(response, context)
|
|
429
|
-
}
|
|
430
|
-
|
|
431
514
|
/**
|
|
432
515
|
* Start the Helia instance
|
|
433
516
|
*/
|