@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
|
@@ -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,97 +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
|
+
}));
|
|
121
|
+
}
|
|
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));
|
|
137
|
+
}
|
|
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
|
+
}
|
|
107
170
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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);
|
|
112
180
|
}
|
|
113
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch { }
|
|
238
|
+
// yolo content-type
|
|
239
|
+
accept = '*/*';
|
|
240
|
+
// return []
|
|
123
241
|
}
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
});
|
|
131
297
|
}
|
|
132
298
|
}
|
|
133
299
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.log.trace('download requested');
|
|
139
|
-
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);
|
|
140
304
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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);
|
|
146
319
|
}
|
|
147
|
-
contentDisposition = `${contentDisposition}; ${getContentDispositionFilename(context.query.filename)}`;
|
|
148
|
-
}
|
|
149
|
-
if (contentDisposition != null) {
|
|
150
|
-
this.log.trace('content disposition %s', contentDisposition);
|
|
151
|
-
response.headers.set('Content-Disposition', contentDisposition);
|
|
152
320
|
}
|
|
153
|
-
if (context?.cid != null && response.headers.get('etag') == null) {
|
|
321
|
+
if (context?.terminalElement.cid != null && response.headers.get('etag') == null) {
|
|
154
322
|
response.headers.set('etag', getETag({
|
|
155
|
-
cid: context.
|
|
156
|
-
|
|
157
|
-
weak: false
|
|
323
|
+
cid: context.terminalElement.cid,
|
|
324
|
+
contentType
|
|
158
325
|
}));
|
|
159
326
|
}
|
|
160
|
-
if (context?.protocol != null && context.ttl != null) {
|
|
327
|
+
if (context?.url?.protocol != null && context.ttl != null) {
|
|
161
328
|
setCacheControlHeader({
|
|
162
329
|
response,
|
|
163
330
|
ttl: context.ttl,
|
|
164
|
-
protocol: context.protocol
|
|
331
|
+
protocol: context.url.protocol
|
|
165
332
|
});
|
|
166
333
|
}
|
|
167
|
-
if (context?.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
response.headers.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');
|
|
173
|
-
response.headers.set('Access-Control-Allow-Headers', 'Range, X-Requested-With');
|
|
174
|
-
response.headers.set('Access-Control-Expose-Headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Ipfs-Roots, X-Stream-Output');
|
|
175
|
-
if (context?.reqFormat !== 'car') {
|
|
176
|
-
// if we are not doing streaming responses, set the Accept-Ranges header to bytes to enable range requests
|
|
177
|
-
response.headers.set('Accept-Ranges', 'bytes');
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
// set accept-ranges to none to disable range requests for streaming responses
|
|
181
|
-
response.headers.set('Accept-Ranges', 'none');
|
|
182
|
-
}
|
|
183
|
-
if (response.headers.get('Content-Type')?.includes('application/vnd.ipld.car') === true || response.headers.get('Content-Type')?.includes('application/vnd.ipld.raw') === true) {
|
|
184
|
-
// see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
|
|
185
|
-
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}`);
|
|
186
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');
|
|
187
347
|
if (context?.options?.method === 'HEAD') {
|
|
188
348
|
// don't send the body for HEAD requests
|
|
189
349
|
return new Response(null, {
|
|
@@ -191,76 +351,54 @@ export class VerifiedFetch {
|
|
|
191
351
|
headers: response.headers
|
|
192
352
|
});
|
|
193
353
|
}
|
|
354
|
+
// make sure users are not expected to "download" error responses
|
|
355
|
+
if (response.status > 399) {
|
|
356
|
+
response.headers.delete('content-disposition');
|
|
357
|
+
}
|
|
194
358
|
return response;
|
|
195
359
|
}
|
|
196
|
-
|
|
197
|
-
* Runs plugins in a loop. After each plugin that returns `null` (partial/no final),
|
|
198
|
-
* we re-check `canHandle()` for all plugins in the next iteration if the context changed.
|
|
199
|
-
*/
|
|
200
|
-
async runPluginPipeline(context, maxPasses = 3) {
|
|
360
|
+
async runPluginPipeline(context) {
|
|
201
361
|
let finalResponse;
|
|
202
|
-
let passCount = 0;
|
|
203
362
|
const pluginsUsed = new Set();
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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;
|
|
219
383
|
break;
|
|
220
384
|
}
|
|
221
385
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
let pluginHandled = false;
|
|
226
|
-
for (const plugin of readyPlugins) {
|
|
227
|
-
try {
|
|
228
|
-
this.log('invoking plugin: %s', plugin.id);
|
|
229
|
-
pluginsUsed.add(plugin.id);
|
|
230
|
-
const maybeResponse = await plugin.handle(context);
|
|
231
|
-
this.log('plugin response %s %o', plugin.id, maybeResponse);
|
|
232
|
-
if (maybeResponse != null) {
|
|
233
|
-
// if a plugin returns a final Response, short-circuit
|
|
234
|
-
finalResponse = maybeResponse;
|
|
235
|
-
pluginHandled = true;
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
catch (err) {
|
|
240
|
-
if (context.options?.signal?.aborted) {
|
|
241
|
-
throw new AbortError(context.options?.signal?.reason);
|
|
242
|
-
}
|
|
243
|
-
this.log.error('error in plugin %s - %e', plugin.id, err);
|
|
244
|
-
return internalServerErrorResponse(context.resource, JSON.stringify({
|
|
245
|
-
error: errorToObject(err)
|
|
246
|
-
}), {
|
|
247
|
-
headers: {
|
|
248
|
-
'content-type': 'application/json'
|
|
249
|
-
}
|
|
250
|
-
});
|
|
386
|
+
catch (err) {
|
|
387
|
+
if (context.options?.signal?.aborted) {
|
|
388
|
+
throw new AbortError(context.options?.signal?.reason);
|
|
251
389
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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'
|
|
258
396
|
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
if (finalResponse != null) {
|
|
400
|
+
this.log.trace('plugin %s produced final response', plugin.id);
|
|
401
|
+
break;
|
|
264
402
|
}
|
|
265
403
|
if (pluginHandled && finalResponse != null) {
|
|
266
404
|
break;
|
|
@@ -278,73 +416,6 @@ export class VerifiedFetch {
|
|
|
278
416
|
}
|
|
279
417
|
});
|
|
280
418
|
}
|
|
281
|
-
/**
|
|
282
|
-
* We're starting to get to the point where we need a queue or pipeline of
|
|
283
|
-
* operations to perform and a single place to handle errors.
|
|
284
|
-
*
|
|
285
|
-
* TODO: move operations called by fetch to a queue of operations where we can
|
|
286
|
-
* always exit early (and cleanly) if a given signal is aborted
|
|
287
|
-
*/
|
|
288
|
-
async fetch(resource, opts) {
|
|
289
|
-
this.log('fetch %s', resource);
|
|
290
|
-
if (opts?.method === 'OPTIONS') {
|
|
291
|
-
return this.handleFinalResponse(new Response(null, { status: 200 }));
|
|
292
|
-
}
|
|
293
|
-
const options = convertOptions(opts);
|
|
294
|
-
const serverTiming = new ServerTiming();
|
|
295
|
-
const urlResolver = new URLResolver({
|
|
296
|
-
ipnsResolver: this.ipnsResolver,
|
|
297
|
-
dnsLink: this.dnsLink,
|
|
298
|
-
timing: serverTiming
|
|
299
|
-
});
|
|
300
|
-
options?.onProgress?.(new CustomProgressEvent('verified-fetch:request:start', { resource }));
|
|
301
|
-
let parsedResult;
|
|
302
|
-
try {
|
|
303
|
-
parsedResult = await urlResolver.resolve(resource, options);
|
|
304
|
-
}
|
|
305
|
-
catch (err) {
|
|
306
|
-
if (options?.signal?.aborted) {
|
|
307
|
-
throw new AbortError(options?.signal?.reason);
|
|
308
|
-
}
|
|
309
|
-
this.log.error('error parsing resource %s', resource, err);
|
|
310
|
-
return this.handleFinalResponse(badRequestResponse(resource.toString(), err));
|
|
311
|
-
}
|
|
312
|
-
options?.onProgress?.(new CustomProgressEvent('verified-fetch:request:resolve', { cid: parsedResult.cid, path: parsedResult.path }));
|
|
313
|
-
const acceptHeader = getResolvedAcceptHeader({ query: parsedResult.query, headers: options?.headers, logger: this.helia.logger });
|
|
314
|
-
const accept = selectOutputType(parsedResult.cid, acceptHeader);
|
|
315
|
-
this.log('accept %o', accept);
|
|
316
|
-
if (acceptHeader != null && accept == null) {
|
|
317
|
-
this.log.error('could not fulfil request based on accept header');
|
|
318
|
-
return this.handleFinalResponse(notAcceptableResponse(resource.toString()));
|
|
319
|
-
}
|
|
320
|
-
const responseContentType = accept?.mimeType.split(';')[0] ?? 'application/octet-stream';
|
|
321
|
-
const redirectResponse = await getRedirectResponse({ resource, options, logger: this.helia.logger, cid: parsedResult.cid });
|
|
322
|
-
if (redirectResponse != null) {
|
|
323
|
-
return this.handleFinalResponse(redirectResponse);
|
|
324
|
-
}
|
|
325
|
-
const context = {
|
|
326
|
-
...parsedResult,
|
|
327
|
-
resource: resource.toString(),
|
|
328
|
-
accept,
|
|
329
|
-
options,
|
|
330
|
-
onProgress: options?.onProgress,
|
|
331
|
-
modified: 0,
|
|
332
|
-
plugins: this.plugins.map(p => p.id),
|
|
333
|
-
query: parsedResult.query ?? {},
|
|
334
|
-
withServerTiming: Boolean(options?.withServerTiming) || Boolean(this.withServerTiming),
|
|
335
|
-
serverTiming
|
|
336
|
-
};
|
|
337
|
-
this.log.trace('finding handler for cid code "%s" and response content type "%s"', parsedResult.cid.code, responseContentType);
|
|
338
|
-
const response = await this.runPluginPipeline(context);
|
|
339
|
-
options?.onProgress?.(new CustomProgressEvent('verified-fetch:request:end', {
|
|
340
|
-
cid: parsedResult.cid,
|
|
341
|
-
path: parsedResult.path
|
|
342
|
-
}));
|
|
343
|
-
if (response == null) {
|
|
344
|
-
this.log.error('no plugin could handle request for %s', resource);
|
|
345
|
-
}
|
|
346
|
-
return this.handleFinalResponse(response, context);
|
|
347
|
-
}
|
|
348
419
|
/**
|
|
349
420
|
* Start the Helia instance
|
|
350
421
|
*/
|