@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
|
@@ -1,91 +1,95 @@
|
|
|
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.js";
|
|
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.js";
|
|
18
16
|
import { errorToObject } from "./utils/error-to-object.js";
|
|
19
|
-
import {
|
|
17
|
+
import { errorToResponse } from "./utils/error-to-response.js";
|
|
20
18
|
import { getETag } from './utils/get-e-tag.js';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { uriEncodeIPFSPath } from "./utils/ipfs-path-to-string.js";
|
|
24
|
-
import { resourceToSessionCacheKey } from './utils/resource-to-cache-key.js';
|
|
19
|
+
import { getRangeHeader } from "./utils/get-range-header.js";
|
|
20
|
+
import { parseURLString } from "./utils/parse-url-string.js";
|
|
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
|
-
|
|
30
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Retypes the `.signal` property of the options from
|
|
26
|
+
* `AbortSignal | null | undefined` to `AbortSignal | undefined`.
|
|
27
|
+
*/
|
|
31
28
|
function convertOptions(options) {
|
|
32
29
|
if (options == null) {
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
let signal;
|
|
36
|
-
if (options?.signal === null) {
|
|
37
|
-
signal = undefined;
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
signal = options?.signal;
|
|
30
|
+
return;
|
|
41
31
|
}
|
|
42
32
|
return {
|
|
43
33
|
...options,
|
|
44
|
-
signal
|
|
34
|
+
signal: options?.signal == null ? undefined : options?.signal
|
|
45
35
|
};
|
|
46
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns true if the quest is only for an IPNS record
|
|
39
|
+
*/
|
|
40
|
+
function isIPNSRecordRequest(headers) {
|
|
41
|
+
const acceptHeaders = headers.get('accept')?.split(',') ?? [];
|
|
42
|
+
if (acceptHeaders.length !== 1) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const mediaType = acceptHeaders[0].split(';')[0];
|
|
46
|
+
return mediaType === MEDIA_TYPE_IPNS_RECORD;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Returns true if the quest is only for an IPNS record
|
|
50
|
+
*/
|
|
51
|
+
function isRawBlockRequest(headers) {
|
|
52
|
+
const acceptHeaders = headers.get('accept')?.split(',') ?? [];
|
|
53
|
+
if (acceptHeaders.length !== 1) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const mediaType = acceptHeaders[0].split(';')[0];
|
|
57
|
+
return mediaType === MEDIA_TYPE_RAW;
|
|
58
|
+
}
|
|
47
59
|
export class VerifiedFetch {
|
|
48
60
|
helia;
|
|
49
61
|
ipnsResolver;
|
|
50
62
|
dnsLink;
|
|
51
63
|
log;
|
|
52
64
|
contentTypeParser;
|
|
53
|
-
blockstoreSessions;
|
|
54
65
|
withServerTiming;
|
|
55
66
|
plugins = [];
|
|
67
|
+
urlResolver;
|
|
56
68
|
constructor(helia, init = {}) {
|
|
57
69
|
this.helia = helia;
|
|
58
70
|
this.log = helia.logger.forComponent('helia:verified-fetch');
|
|
59
71
|
this.ipnsResolver = init.ipnsResolver ?? ipnsResolver(helia);
|
|
60
72
|
this.dnsLink = init.dnsLink ?? dnsLink(helia);
|
|
61
73
|
this.contentTypeParser = init.contentTypeParser ?? contentTypeParser;
|
|
62
|
-
this.blockstoreSessions = new QuickLRU({
|
|
63
|
-
maxSize: init?.sessionCacheSize ?? SESSION_CACHE_MAX_SIZE,
|
|
64
|
-
maxAge: init?.sessionTTLms ?? SESSION_CACHE_TTL_MS,
|
|
65
|
-
onEviction: (key, store) => {
|
|
66
|
-
store.close();
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
74
|
this.withServerTiming = init?.withServerTiming ?? false;
|
|
75
|
+
this.urlResolver = new URLResolver({
|
|
76
|
+
ipnsResolver: this.ipnsResolver,
|
|
77
|
+
dnsLink: this.dnsLink,
|
|
78
|
+
helia: this.helia
|
|
79
|
+
}, init);
|
|
70
80
|
const pluginOptions = {
|
|
71
81
|
...init,
|
|
72
82
|
logger: helia.logger.forComponent('verified-fetch'),
|
|
73
|
-
getBlockstore: (cid, resource, useSession, options) => this.getBlockstore(cid, resource, useSession, options),
|
|
74
83
|
helia,
|
|
75
84
|
contentTypeParser: this.contentTypeParser,
|
|
76
85
|
ipnsResolver: this.ipnsResolver
|
|
77
86
|
};
|
|
78
87
|
const defaultPlugins = [
|
|
79
|
-
new
|
|
80
|
-
new
|
|
81
|
-
new IpnsRecordPlugin(pluginOptions),
|
|
88
|
+
new UnixFSPlugin(pluginOptions),
|
|
89
|
+
new IpldPlugin(pluginOptions),
|
|
82
90
|
new CarPlugin(pluginOptions),
|
|
83
|
-
new RawPlugin(pluginOptions),
|
|
84
91
|
new TarPlugin(pluginOptions),
|
|
85
|
-
new
|
|
86
|
-
new DagCborPlugin(pluginOptions),
|
|
87
|
-
new DagPbPlugin(pluginOptions),
|
|
88
|
-
new CborPlugin(pluginOptions)
|
|
92
|
+
new IpnsRecordPlugin(pluginOptions)
|
|
89
93
|
];
|
|
90
94
|
const customPlugins = init.plugins?.map((pluginFactory) => pluginFactory(pluginOptions)) ?? [];
|
|
91
95
|
if (customPlugins.length > 0) {
|
|
@@ -93,100 +97,253 @@ export class VerifiedFetch {
|
|
|
93
97
|
const defaultPluginMap = new Map(defaultPlugins.map(plugin => [plugin.id, plugin]));
|
|
94
98
|
const customPluginMap = new Map(customPlugins.map(plugin => [plugin.id, plugin]));
|
|
95
99
|
this.plugins = defaultPlugins.map(plugin => customPluginMap.get(plugin.id) ?? plugin);
|
|
96
|
-
//
|
|
97
|
-
|
|
100
|
+
// add any custom plugins that don't replace default ones with a higher
|
|
101
|
+
// priority than anything built-in
|
|
102
|
+
this.plugins.unshift(...customPlugins.filter(plugin => !defaultPluginMap.has(plugin.id)));
|
|
98
103
|
}
|
|
99
104
|
else {
|
|
100
105
|
this.plugins = defaultPlugins;
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Load a resource from the IPFS network and ensure the retrieved data is the
|
|
110
|
+
* data that was expected to be loaded.
|
|
111
|
+
*
|
|
112
|
+
* Like [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch)
|
|
113
|
+
* but verified.
|
|
114
|
+
*/
|
|
115
|
+
async fetch(resource, opts) {
|
|
116
|
+
this.log('fetch %s %s', opts?.method ?? 'GET', resource);
|
|
117
|
+
if (opts?.method === 'OPTIONS') {
|
|
118
|
+
return this.handleFinalResponse(new Response(null, {
|
|
119
|
+
status: 200
|
|
120
|
+
}));
|
|
107
121
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
const options = convertOptions(opts);
|
|
123
|
+
const headers = new Headers(options?.headers);
|
|
124
|
+
const serverTiming = new ServerTiming();
|
|
125
|
+
options?.onProgress?.(new CustomProgressEvent('verified-fetch:request:start', { resource }));
|
|
126
|
+
const range = getRangeHeader(resource.toString(), headers);
|
|
127
|
+
if (range instanceof Response) {
|
|
128
|
+
// invalid range request
|
|
129
|
+
return this.handleFinalResponse(range);
|
|
130
|
+
}
|
|
131
|
+
let url;
|
|
132
|
+
try {
|
|
133
|
+
url = parseURLString(typeof resource === 'string' ? resource : `ipfs://${resource}`);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
return this.handleFinalResponse(badRequestResponse(resource.toString(), err));
|
|
112
137
|
}
|
|
113
|
-
|
|
138
|
+
let parsedResult;
|
|
139
|
+
// if just an IPNS record has been requested, don't try to load the block
|
|
140
|
+
// the record points to or do any recursive IPNS resolving
|
|
141
|
+
if (isIPNSRecordRequest(headers)) {
|
|
142
|
+
if (url.protocol !== 'ipns:') {
|
|
143
|
+
return notAcceptableResponse(url, []);
|
|
144
|
+
}
|
|
145
|
+
// @ts-expect-error ipnsRecordPlugin may not be of type IpnsRecordPlugin
|
|
146
|
+
const ipnsRecordPlugin = this.plugins.find(plugin => plugin.id === 'ipns-record-plugin');
|
|
147
|
+
if (ipnsRecordPlugin == null) {
|
|
148
|
+
return notAcceptableResponse(url, []);
|
|
149
|
+
}
|
|
150
|
+
return this.handleFinalResponse(await ipnsRecordPlugin.handle({
|
|
151
|
+
range,
|
|
152
|
+
url,
|
|
153
|
+
resource: resource.toString(),
|
|
154
|
+
options
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
try {
|
|
159
|
+
parsedResult = await this.urlResolver.resolve(url, serverTiming, {
|
|
160
|
+
...options,
|
|
161
|
+
isRawBlockRequest: isRawBlockRequest(headers),
|
|
162
|
+
onlyIfCached: headers.get('cache-control') === 'only-if-cached'
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
options?.signal?.throwIfAborted();
|
|
167
|
+
this.log.error('error parsing resource %s - %e', resource, err);
|
|
168
|
+
return this.handleFinalResponse(errorToResponse(resource, err));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
options?.onProgress?.(new CustomProgressEvent('verified-fetch:request:resolve', {
|
|
172
|
+
cid: parsedResult.terminalElement.cid,
|
|
173
|
+
path: parsedResult.url.pathname
|
|
174
|
+
}));
|
|
175
|
+
const accept = this.getAcceptHeader(parsedResult.url, headers.get('accept'), parsedResult.terminalElement.cid);
|
|
176
|
+
if (accept instanceof Response) {
|
|
177
|
+
this.log('allowed media types for requested CID did not contain anything the client can understand');
|
|
178
|
+
// invalid accept header
|
|
179
|
+
return this.handleFinalResponse(accept);
|
|
180
|
+
}
|
|
181
|
+
const context = {
|
|
182
|
+
...parsedResult,
|
|
183
|
+
resource: resource.toString(),
|
|
184
|
+
accept,
|
|
185
|
+
range,
|
|
186
|
+
options,
|
|
187
|
+
onProgress: options?.onProgress,
|
|
188
|
+
serverTiming,
|
|
189
|
+
headers
|
|
190
|
+
};
|
|
191
|
+
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(', '));
|
|
192
|
+
const response = await this.runPluginPipeline(context);
|
|
193
|
+
options?.onProgress?.(new CustomProgressEvent('verified-fetch:request:end', {
|
|
194
|
+
cid: parsedResult.terminalElement.cid,
|
|
195
|
+
path: parsedResult.url.pathname
|
|
196
|
+
}));
|
|
197
|
+
if (response == null) {
|
|
198
|
+
this.log.error('no plugin could handle request for %s', resource);
|
|
199
|
+
}
|
|
200
|
+
return this.handleFinalResponse(response, Boolean(options?.withServerTiming) || Boolean(this.withServerTiming), context);
|
|
114
201
|
}
|
|
115
202
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* response before it is returned to the user.
|
|
203
|
+
* Returns a prioritized list of acceptable content types for the response
|
|
204
|
+
* based on the CID and a passed `Accept` header
|
|
119
205
|
*/
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
206
|
+
getAcceptHeader(url, accept, cid) {
|
|
207
|
+
if (accept == null || accept === '') {
|
|
208
|
+
// if the user has specified CAR options but no Accept header, default to
|
|
209
|
+
// the car content type with the passed options
|
|
210
|
+
try {
|
|
211
|
+
const dagScope = url.searchParams.get('dag-scope');
|
|
212
|
+
const entityBytes = url.searchParams.get('entity-bytes');
|
|
213
|
+
const dups = url.searchParams.get('car-dups');
|
|
214
|
+
const order = url.searchParams.get('car-order');
|
|
215
|
+
const version = url.searchParams.get('car-version');
|
|
216
|
+
if (dagScope != null ||
|
|
217
|
+
entityBytes != null ||
|
|
218
|
+
dups != null ||
|
|
219
|
+
entityBytes != null ||
|
|
220
|
+
order != null) {
|
|
221
|
+
const options = {};
|
|
222
|
+
if (dups != null) {
|
|
223
|
+
options.dups = dups;
|
|
224
|
+
}
|
|
225
|
+
if (order != null) {
|
|
226
|
+
options.order = order;
|
|
227
|
+
}
|
|
228
|
+
if (version != null) {
|
|
229
|
+
options.version = version;
|
|
230
|
+
}
|
|
231
|
+
return [{
|
|
232
|
+
contentType: CONTENT_TYPE_CAR,
|
|
233
|
+
options
|
|
234
|
+
}];
|
|
235
|
+
}
|
|
125
236
|
}
|
|
237
|
+
catch { }
|
|
238
|
+
// yolo content-type
|
|
239
|
+
accept = '*/*';
|
|
240
|
+
// return []
|
|
126
241
|
}
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
242
|
+
// allow user to choose specific output type
|
|
243
|
+
const acceptable = [];
|
|
244
|
+
const requestedMimeTypes = accept
|
|
245
|
+
.split(',')
|
|
246
|
+
.map(s => {
|
|
247
|
+
const parts = s.trim().split(';');
|
|
248
|
+
const options = {
|
|
249
|
+
q: '1'
|
|
250
|
+
};
|
|
251
|
+
for (let i = 1; i < parts.length; i++) {
|
|
252
|
+
const [key, value] = parts[i].split('=').map(s => s.trim());
|
|
253
|
+
options[key] = value;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
mediaType: `${parts[0]}`.trim(),
|
|
257
|
+
options
|
|
258
|
+
};
|
|
259
|
+
})
|
|
260
|
+
.sort((a, b) => {
|
|
261
|
+
if (a.options.q === b.options.q) {
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
if (a.options.q > b.options.q) {
|
|
265
|
+
return -1;
|
|
266
|
+
}
|
|
267
|
+
return 1;
|
|
268
|
+
});
|
|
269
|
+
const supportedContentTypes = getSupportedContentTypes(url.protocol, cid);
|
|
270
|
+
for (const headerFormat of requestedMimeTypes) {
|
|
271
|
+
const [headerFormatType, headerFormatSubType] = headerFormat.mediaType.split('/');
|
|
272
|
+
for (const contentType of supportedContentTypes) {
|
|
273
|
+
const [contentTypeType, contentTypeSubType] = contentType.mediaType.split('/');
|
|
274
|
+
if (headerFormat.mediaType.includes(contentType.mediaType)) {
|
|
275
|
+
acceptable.push({
|
|
276
|
+
contentType,
|
|
277
|
+
options: headerFormat.options
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (headerFormat.mediaType === '*/*') {
|
|
281
|
+
acceptable.push({
|
|
282
|
+
contentType,
|
|
283
|
+
options: headerFormat.options
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (headerFormat.mediaType.startsWith('*/') && contentTypeSubType === headerFormatSubType) {
|
|
287
|
+
acceptable.push({
|
|
288
|
+
contentType,
|
|
289
|
+
options: headerFormat.options
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
if (headerFormat.mediaType.endsWith('/*') && contentTypeType === headerFormatType) {
|
|
293
|
+
acceptable.push({
|
|
294
|
+
contentType,
|
|
295
|
+
options: headerFormat.options
|
|
296
|
+
});
|
|
134
297
|
}
|
|
135
298
|
}
|
|
136
299
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
this.log.trace('download requested');
|
|
142
|
-
contentDisposition = 'attachment';
|
|
300
|
+
if (acceptable.length === 0) {
|
|
301
|
+
this.log('requested %o', requestedMimeTypes.map(({ mediaType }) => mediaType));
|
|
302
|
+
this.log('supported %o', supportedContentTypes.map(({ mediaType }) => mediaType));
|
|
303
|
+
return notAcceptableResponse(url, supportedContentTypes);
|
|
143
304
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
305
|
+
return acceptable;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* The last place a Response touches in verified-fetch before being returned
|
|
309
|
+
* to the user. This is where we add the Server-Timing header to the response
|
|
310
|
+
* if it has been collected. It should be used for any final processing of the
|
|
311
|
+
* response before it is returned to the user.
|
|
312
|
+
*/
|
|
313
|
+
handleFinalResponse(response, withServerTiming, context) {
|
|
314
|
+
const contentType = getContentType(response.headers.get('content-type')) ?? CONTENT_TYPE_OCTET_STREAM;
|
|
315
|
+
if (withServerTiming === true && context?.serverTiming != null) {
|
|
316
|
+
const timingHeader = context?.serverTiming.getHeader();
|
|
317
|
+
if (timingHeader !== '') {
|
|
318
|
+
response.headers.set('server-timing', timingHeader);
|
|
149
319
|
}
|
|
150
|
-
contentDisposition = `${contentDisposition}; ${getContentDispositionFilename(context.query.filename)}`;
|
|
151
|
-
}
|
|
152
|
-
if (contentDisposition != null) {
|
|
153
|
-
this.log.trace('content disposition %s', contentDisposition);
|
|
154
|
-
response.headers.set('Content-Disposition', contentDisposition);
|
|
155
320
|
}
|
|
156
|
-
if (context?.cid != null && response.headers.get('etag') == null) {
|
|
321
|
+
if (context?.terminalElement.cid != null && response.headers.get('etag') == null) {
|
|
157
322
|
response.headers.set('etag', getETag({
|
|
158
|
-
cid: context.
|
|
159
|
-
|
|
160
|
-
weak: false
|
|
323
|
+
cid: context.terminalElement.cid,
|
|
324
|
+
contentType
|
|
161
325
|
}));
|
|
162
326
|
}
|
|
163
|
-
if (context?.protocol != null && context.ttl != null) {
|
|
327
|
+
if (context?.url?.protocol != null && context.ttl != null) {
|
|
164
328
|
setCacheControlHeader({
|
|
165
329
|
response,
|
|
166
330
|
ttl: context.ttl,
|
|
167
|
-
protocol: context.protocol
|
|
331
|
+
protocol: context.url.protocol
|
|
168
332
|
});
|
|
169
333
|
}
|
|
170
|
-
if (context?.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
response.headers.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
|
|
176
|
-
response.headers.set('Access-Control-Allow-Headers', 'Range, X-Requested-With');
|
|
177
|
-
response.headers.set('Access-Control-Expose-Headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Ipfs-Roots, X-Stream-Output');
|
|
178
|
-
if (context?.reqFormat !== 'car') {
|
|
179
|
-
// if we are not doing streaming responses, set the Accept-Ranges header to bytes to enable range requests
|
|
180
|
-
response.headers.set('Accept-Ranges', 'bytes');
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
// set accept-ranges to none to disable range requests for streaming responses
|
|
184
|
-
response.headers.set('Accept-Ranges', 'none');
|
|
185
|
-
}
|
|
186
|
-
if (response.headers.get('Content-Type')?.includes('application/vnd.ipld.car') === true || response.headers.get('Content-Type')?.includes('application/vnd.ipld.raw') === true) {
|
|
187
|
-
// see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
|
|
188
|
-
response.headers.set('X-Content-Type-Options', 'nosniff');
|
|
334
|
+
if (context?.terminalElement.cid != null) {
|
|
335
|
+
// headers can ony contain extended ASCII but IPFS paths can be unicode
|
|
336
|
+
const decodedPath = decodeURI(context?.url.pathname);
|
|
337
|
+
const path = uint8ArrayToString(uint8ArrayFromString(decodedPath), 'ascii');
|
|
338
|
+
response.headers.set('x-ipfs-path', `/${context.url.protocol === 'ipfs:' ? 'ipfs' : 'ipns'}/${context?.url.hostname}${path}`);
|
|
189
339
|
}
|
|
340
|
+
// set CORS headers. If hosting your own gateway with verified-fetch behind
|
|
341
|
+
// the scenes, you can alter these before you send the response to the
|
|
342
|
+
// client.
|
|
343
|
+
response.headers.set('access-control-allow-origin', '*');
|
|
344
|
+
response.headers.set('access-control-allow-methods', 'GET, HEAD, OPTIONS');
|
|
345
|
+
response.headers.set('access-control-allow-headers', 'Range, X-Requested-With');
|
|
346
|
+
response.headers.set('access-control-expose-headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Ipfs-Roots, X-Stream-Output');
|
|
190
347
|
if (context?.options?.method === 'HEAD') {
|
|
191
348
|
// don't send the body for HEAD requests
|
|
192
349
|
return new Response(null, {
|
|
@@ -200,74 +357,48 @@ export class VerifiedFetch {
|
|
|
200
357
|
}
|
|
201
358
|
return response;
|
|
202
359
|
}
|
|
203
|
-
|
|
204
|
-
* Runs plugins in a loop. After each plugin that returns `null` (partial/no final),
|
|
205
|
-
* we re-check `canHandle()` for all plugins in the next iteration if the context changed.
|
|
206
|
-
*/
|
|
207
|
-
async runPluginPipeline(context, maxPasses = 3) {
|
|
360
|
+
async runPluginPipeline(context) {
|
|
208
361
|
let finalResponse;
|
|
209
|
-
let passCount = 0;
|
|
210
362
|
const pluginsUsed = new Set();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
363
|
+
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(', '));
|
|
364
|
+
const plugins = this.plugins.filter(p => !pluginsUsed.has(p.id)).filter(p => p.canHandle(context));
|
|
365
|
+
if (plugins.length === 0) {
|
|
366
|
+
this.log.trace('no plugins found that can handle request; exiting pipeline');
|
|
367
|
+
return notImplementedResponse(context.resource);
|
|
368
|
+
}
|
|
369
|
+
this.log.trace('plugins ready to handle request: %s', plugins.map(p => p.id).join(', '));
|
|
370
|
+
// track if any plugin changed the context or returned a response
|
|
371
|
+
const contextChanged = false;
|
|
372
|
+
let pluginHandled = false;
|
|
373
|
+
for (const plugin of plugins) {
|
|
374
|
+
try {
|
|
375
|
+
this.log('invoking plugin: %s', plugin.id);
|
|
376
|
+
pluginsUsed.add(plugin.id);
|
|
377
|
+
const maybeResponse = await plugin.handle(context);
|
|
378
|
+
this.log('plugin response %s %o', plugin.id, maybeResponse);
|
|
379
|
+
if (maybeResponse != null) {
|
|
380
|
+
// if a plugin returns a final Response, short-circuit
|
|
381
|
+
finalResponse = maybeResponse;
|
|
382
|
+
pluginHandled = true;
|
|
226
383
|
break;
|
|
227
384
|
}
|
|
228
385
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
let pluginHandled = false;
|
|
233
|
-
for (const plugin of readyPlugins) {
|
|
234
|
-
try {
|
|
235
|
-
this.log('invoking plugin: %s', plugin.id);
|
|
236
|
-
pluginsUsed.add(plugin.id);
|
|
237
|
-
const maybeResponse = await plugin.handle(context);
|
|
238
|
-
this.log('plugin response %s %o', plugin.id, maybeResponse);
|
|
239
|
-
if (maybeResponse != null) {
|
|
240
|
-
// if a plugin returns a final Response, short-circuit
|
|
241
|
-
finalResponse = maybeResponse;
|
|
242
|
-
pluginHandled = true;
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
catch (err) {
|
|
247
|
-
if (context.options?.signal?.aborted) {
|
|
248
|
-
throw new AbortError(context.options?.signal?.reason);
|
|
249
|
-
}
|
|
250
|
-
this.log.error('error in plugin %s - %e', plugin.id, err);
|
|
251
|
-
return internalServerErrorResponse(context.resource, JSON.stringify({
|
|
252
|
-
error: errorToObject(err)
|
|
253
|
-
}), {
|
|
254
|
-
headers: {
|
|
255
|
-
'content-type': 'application/json'
|
|
256
|
-
}
|
|
257
|
-
});
|
|
386
|
+
catch (err) {
|
|
387
|
+
if (context.options?.signal?.aborted) {
|
|
388
|
+
throw new AbortError(context.options?.signal?.reason);
|
|
258
389
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
390
|
+
this.log.error('error in plugin %s - %e', plugin.id, err);
|
|
391
|
+
return internalServerErrorResponse(context.resource, JSON.stringify({
|
|
392
|
+
error: errorToObject(err)
|
|
393
|
+
}), {
|
|
394
|
+
headers: {
|
|
395
|
+
'content-type': 'application/json'
|
|
265
396
|
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
if (finalResponse != null) {
|
|
400
|
+
this.log.trace('plugin %s produced final response', plugin.id);
|
|
401
|
+
break;
|
|
271
402
|
}
|
|
272
403
|
if (pluginHandled && finalResponse != null) {
|
|
273
404
|
break;
|
|
@@ -285,73 +416,6 @@ export class VerifiedFetch {
|
|
|
285
416
|
}
|
|
286
417
|
});
|
|
287
418
|
}
|
|
288
|
-
/**
|
|
289
|
-
* We're starting to get to the point where we need a queue or pipeline of
|
|
290
|
-
* operations to perform and a single place to handle errors.
|
|
291
|
-
*
|
|
292
|
-
* TODO: move operations called by fetch to a queue of operations where we can
|
|
293
|
-
* always exit early (and cleanly) if a given signal is aborted
|
|
294
|
-
*/
|
|
295
|
-
async fetch(resource, opts) {
|
|
296
|
-
this.log('fetch %s', resource);
|
|
297
|
-
if (opts?.method === 'OPTIONS') {
|
|
298
|
-
return this.handleFinalResponse(new Response(null, { status: 200 }));
|
|
299
|
-
}
|
|
300
|
-
const options = convertOptions(opts);
|
|
301
|
-
const serverTiming = new ServerTiming();
|
|
302
|
-
const urlResolver = new URLResolver({
|
|
303
|
-
ipnsResolver: this.ipnsResolver,
|
|
304
|
-
dnsLink: this.dnsLink,
|
|
305
|
-
timing: serverTiming
|
|
306
|
-
});
|
|
307
|
-
options?.onProgress?.(new CustomProgressEvent('verified-fetch:request:start', { resource }));
|
|
308
|
-
let parsedResult;
|
|
309
|
-
try {
|
|
310
|
-
parsedResult = await urlResolver.resolve(resource, options);
|
|
311
|
-
}
|
|
312
|
-
catch (err) {
|
|
313
|
-
if (options?.signal?.aborted) {
|
|
314
|
-
throw new AbortError(options?.signal?.reason);
|
|
315
|
-
}
|
|
316
|
-
this.log.error('error parsing resource %s', resource, err);
|
|
317
|
-
return this.handleFinalResponse(badRequestResponse(resource.toString(), err));
|
|
318
|
-
}
|
|
319
|
-
options?.onProgress?.(new CustomProgressEvent('verified-fetch:request:resolve', { cid: parsedResult.cid, path: parsedResult.path }));
|
|
320
|
-
const acceptHeader = getResolvedAcceptHeader({ query: parsedResult.query, headers: options?.headers, logger: this.helia.logger });
|
|
321
|
-
const accept = selectOutputType(parsedResult.cid, acceptHeader);
|
|
322
|
-
this.log('accept %o', accept);
|
|
323
|
-
if (acceptHeader != null && accept == null) {
|
|
324
|
-
this.log.error('could not fulfil request based on accept header');
|
|
325
|
-
return this.handleFinalResponse(notAcceptableResponse(resource.toString()));
|
|
326
|
-
}
|
|
327
|
-
const responseContentType = accept?.mimeType.split(';')[0] ?? 'application/octet-stream';
|
|
328
|
-
const redirectResponse = await getRedirectResponse({ resource, options, logger: this.helia.logger, cid: parsedResult.cid });
|
|
329
|
-
if (redirectResponse != null) {
|
|
330
|
-
return this.handleFinalResponse(redirectResponse);
|
|
331
|
-
}
|
|
332
|
-
const context = {
|
|
333
|
-
...parsedResult,
|
|
334
|
-
resource: resource.toString(),
|
|
335
|
-
accept,
|
|
336
|
-
options,
|
|
337
|
-
onProgress: options?.onProgress,
|
|
338
|
-
modified: 0,
|
|
339
|
-
plugins: this.plugins.map(p => p.id),
|
|
340
|
-
query: parsedResult.query ?? {},
|
|
341
|
-
withServerTiming: Boolean(options?.withServerTiming) || Boolean(this.withServerTiming),
|
|
342
|
-
serverTiming
|
|
343
|
-
};
|
|
344
|
-
this.log.trace('finding handler for cid code "%s" and response content type "%s"', parsedResult.cid.code, responseContentType);
|
|
345
|
-
const response = await this.runPluginPipeline(context);
|
|
346
|
-
options?.onProgress?.(new CustomProgressEvent('verified-fetch:request:end', {
|
|
347
|
-
cid: parsedResult.cid,
|
|
348
|
-
path: parsedResult.path
|
|
349
|
-
}));
|
|
350
|
-
if (response == null) {
|
|
351
|
-
this.log.error('no plugin could handle request for %s', resource);
|
|
352
|
-
}
|
|
353
|
-
return this.handleFinalResponse(response, context);
|
|
354
|
-
}
|
|
355
419
|
/**
|
|
356
420
|
* Start the Helia instance
|
|
357
421
|
*/
|