@helia/verified-fetch 4.1.1 → 5.0.1

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.
Files changed (231) hide show
  1. package/README.md +6 -40
  2. package/dist/index.min.js +73 -534
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/constants.d.ts +2 -0
  5. package/dist/src/constants.d.ts.map +1 -1
  6. package/dist/src/constants.js +2 -0
  7. package/dist/src/constants.js.map +1 -1
  8. package/dist/src/index.d.ts +162 -68
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +7 -40
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/plugins/index.d.ts +0 -5
  13. package/dist/src/plugins/index.d.ts.map +1 -1
  14. package/dist/src/plugins/index.js +0 -4
  15. package/dist/src/plugins/index.js.map +1 -1
  16. package/dist/src/plugins/plugin-base.d.ts +8 -9
  17. package/dist/src/plugins/plugin-base.d.ts.map +1 -1
  18. package/dist/src/plugins/plugin-base.js +5 -6
  19. package/dist/src/plugins/plugin-base.js.map +1 -1
  20. package/dist/src/plugins/plugin-handle-car.d.ts +3 -3
  21. package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
  22. package/dist/src/plugins/plugin-handle-car.js +38 -39
  23. package/dist/src/plugins/plugin-handle-car.js.map +1 -1
  24. package/dist/src/plugins/plugin-handle-ipld.d.ts +12 -0
  25. package/dist/src/plugins/plugin-handle-ipld.d.ts.map +1 -0
  26. package/dist/src/plugins/plugin-handle-ipld.js +83 -0
  27. package/dist/src/plugins/plugin-handle-ipld.js.map +1 -0
  28. package/dist/src/plugins/plugin-handle-ipns-record.d.ts +3 -3
  29. package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
  30. package/dist/src/plugins/plugin-handle-ipns-record.js +25 -34
  31. package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
  32. package/dist/src/plugins/plugin-handle-tar.d.ts +3 -3
  33. package/dist/src/plugins/plugin-handle-tar.d.ts.map +1 -1
  34. package/dist/src/plugins/plugin-handle-tar.js +20 -22
  35. package/dist/src/plugins/plugin-handle-tar.js.map +1 -1
  36. package/dist/src/plugins/plugin-handle-unixfs.d.ts +14 -0
  37. package/dist/src/plugins/plugin-handle-unixfs.d.ts.map +1 -0
  38. package/dist/src/plugins/plugin-handle-unixfs.js +180 -0
  39. package/dist/src/plugins/plugin-handle-unixfs.js.map +1 -0
  40. package/dist/src/plugins/types.d.ts +1 -77
  41. package/dist/src/plugins/types.d.ts.map +1 -1
  42. package/dist/src/url-resolver.d.ts +29 -11
  43. package/dist/src/url-resolver.d.ts.map +1 -1
  44. package/dist/src/url-resolver.js +152 -74
  45. package/dist/src/url-resolver.js.map +1 -1
  46. package/dist/src/utils/content-type-parser.d.ts.map +1 -1
  47. package/dist/src/utils/content-type-parser.js +4 -3
  48. package/dist/src/utils/content-type-parser.js.map +1 -1
  49. package/dist/src/utils/content-types.d.ts +26 -0
  50. package/dist/src/utils/content-types.d.ts.map +1 -0
  51. package/dist/src/utils/content-types.js +137 -0
  52. package/dist/src/utils/content-types.js.map +1 -0
  53. package/dist/src/utils/convert-output.d.ts +17 -0
  54. package/dist/src/utils/convert-output.d.ts.map +1 -0
  55. package/dist/src/utils/convert-output.js +176 -0
  56. package/dist/src/utils/convert-output.js.map +1 -0
  57. package/dist/src/utils/error-to-response.d.ts +3 -0
  58. package/dist/src/utils/error-to-response.d.ts.map +1 -0
  59. package/dist/src/utils/error-to-response.js +40 -0
  60. package/dist/src/utils/error-to-response.js.map +1 -0
  61. package/dist/src/utils/get-content-disposition-filename.d.ts +1 -1
  62. package/dist/src/utils/get-content-disposition-filename.d.ts.map +1 -1
  63. package/dist/src/utils/get-content-disposition-filename.js +4 -0
  64. package/dist/src/utils/get-content-disposition-filename.js.map +1 -1
  65. package/dist/src/utils/get-e-tag.d.ts +20 -15
  66. package/dist/src/utils/get-e-tag.d.ts.map +1 -1
  67. package/dist/src/utils/get-e-tag.js +8 -22
  68. package/dist/src/utils/get-e-tag.js.map +1 -1
  69. package/dist/src/utils/get-offset-and-length.d.ts +12 -2
  70. package/dist/src/utils/get-offset-and-length.d.ts.map +1 -1
  71. package/dist/src/utils/get-offset-and-length.js +63 -21
  72. package/dist/src/utils/get-offset-and-length.js.map +1 -1
  73. package/dist/src/utils/get-range-header.d.ts +22 -0
  74. package/dist/src/utils/get-range-header.d.ts.map +1 -0
  75. package/dist/src/utils/get-range-header.js +69 -0
  76. package/dist/src/utils/get-range-header.js.map +1 -0
  77. package/dist/src/utils/parse-url-string.d.ts +2 -1
  78. package/dist/src/utils/parse-url-string.d.ts.map +1 -1
  79. package/dist/src/utils/parse-url-string.js +46 -71
  80. package/dist/src/utils/parse-url-string.js.map +1 -1
  81. package/dist/src/utils/resource-to-cache-key.d.ts +3 -3
  82. package/dist/src/utils/resource-to-cache-key.js +5 -5
  83. package/dist/src/utils/resource-to-cache-key.js.map +1 -1
  84. package/dist/src/utils/response-headers.d.ts +4 -14
  85. package/dist/src/utils/response-headers.d.ts.map +1 -1
  86. package/dist/src/utils/response-headers.js +36 -36
  87. package/dist/src/utils/response-headers.js.map +1 -1
  88. package/dist/src/utils/responses.d.ts +30 -11
  89. package/dist/src/utils/responses.d.ts.map +1 -1
  90. package/dist/src/utils/responses.js +146 -39
  91. package/dist/src/utils/responses.js.map +1 -1
  92. package/dist/src/verified-fetch.d.ts +16 -15
  93. package/dist/src/verified-fetch.d.ts.map +1 -1
  94. package/dist/src/verified-fetch.js +302 -238
  95. package/dist/src/verified-fetch.js.map +1 -1
  96. package/dist/typedoc-urls.json +64 -45
  97. package/package.json +4 -3
  98. package/src/constants.ts +3 -0
  99. package/src/index.ts +199 -68
  100. package/src/plugins/index.ts +0 -6
  101. package/src/plugins/plugin-base.ts +8 -10
  102. package/src/plugins/plugin-handle-car.ts +48 -46
  103. package/src/plugins/plugin-handle-ipld.ts +93 -0
  104. package/src/plugins/plugin-handle-ipns-record.ts +31 -41
  105. package/src/plugins/plugin-handle-tar.ts +25 -29
  106. package/src/plugins/plugin-handle-unixfs.ts +217 -0
  107. package/src/plugins/types.ts +0 -86
  108. package/src/url-resolver.ts +197 -83
  109. package/src/utils/content-type-parser.ts +4 -3
  110. package/src/utils/content-types.ts +159 -0
  111. package/src/utils/convert-output.ts +187 -0
  112. package/src/utils/error-to-response.ts +49 -0
  113. package/src/utils/get-content-disposition-filename.ts +7 -1
  114. package/src/utils/get-e-tag.ts +26 -35
  115. package/src/utils/get-offset-and-length.ts +75 -21
  116. package/src/utils/get-range-header.ts +107 -0
  117. package/src/utils/parse-url-string.ts +51 -80
  118. package/src/utils/resource-to-cache-key.ts +5 -5
  119. package/src/utils/response-headers.ts +40 -41
  120. package/src/utils/responses.ts +186 -45
  121. package/src/verified-fetch.ts +353 -270
  122. package/dist/src/plugins/plugin-handle-byte-range-context.d.ts +0 -14
  123. package/dist/src/plugins/plugin-handle-byte-range-context.d.ts.map +0 -1
  124. package/dist/src/plugins/plugin-handle-byte-range-context.js +0 -25
  125. package/dist/src/plugins/plugin-handle-byte-range-context.js.map +0 -1
  126. package/dist/src/plugins/plugin-handle-cbor.d.ts +0 -17
  127. package/dist/src/plugins/plugin-handle-cbor.d.ts.map +0 -1
  128. package/dist/src/plugins/plugin-handle-cbor.js +0 -94
  129. package/dist/src/plugins/plugin-handle-cbor.js.map +0 -1
  130. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts +0 -27
  131. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts.map +0 -1
  132. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js +0 -279
  133. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js.map +0 -1
  134. package/dist/src/plugins/plugin-handle-dag-cbor.d.ts +0 -17
  135. package/dist/src/plugins/plugin-handle-dag-cbor.d.ts.map +0 -1
  136. package/dist/src/plugins/plugin-handle-dag-cbor.js +0 -66
  137. package/dist/src/plugins/plugin-handle-dag-cbor.js.map +0 -1
  138. package/dist/src/plugins/plugin-handle-dag-pb.d.ts +0 -17
  139. package/dist/src/plugins/plugin-handle-dag-pb.d.ts.map +0 -1
  140. package/dist/src/plugins/plugin-handle-dag-pb.js +0 -209
  141. package/dist/src/plugins/plugin-handle-dag-pb.js.map +0 -1
  142. package/dist/src/plugins/plugin-handle-dag-walk.d.ts +0 -21
  143. package/dist/src/plugins/plugin-handle-dag-walk.d.ts.map +0 -1
  144. package/dist/src/plugins/plugin-handle-dag-walk.js +0 -95
  145. package/dist/src/plugins/plugin-handle-dag-walk.js.map +0 -1
  146. package/dist/src/plugins/plugin-handle-dir-index-html.d.ts +0 -10
  147. package/dist/src/plugins/plugin-handle-dir-index-html.d.ts.map +0 -1
  148. package/dist/src/plugins/plugin-handle-dir-index-html.js +0 -59
  149. package/dist/src/plugins/plugin-handle-dir-index-html.js.map +0 -1
  150. package/dist/src/plugins/plugin-handle-json.d.ts +0 -12
  151. package/dist/src/plugins/plugin-handle-json.d.ts.map +0 -1
  152. package/dist/src/plugins/plugin-handle-json.js +0 -73
  153. package/dist/src/plugins/plugin-handle-json.js.map +0 -1
  154. package/dist/src/plugins/plugin-handle-raw.d.ts +0 -9
  155. package/dist/src/plugins/plugin-handle-raw.d.ts.map +0 -1
  156. package/dist/src/plugins/plugin-handle-raw.js +0 -92
  157. package/dist/src/plugins/plugin-handle-raw.js.map +0 -1
  158. package/dist/src/plugins/plugins.d.ts +0 -6
  159. package/dist/src/plugins/plugins.d.ts.map +0 -1
  160. package/dist/src/plugins/plugins.js +0 -6
  161. package/dist/src/plugins/plugins.js.map +0 -1
  162. package/dist/src/utils/byte-range-context.d.ts +0 -103
  163. package/dist/src/utils/byte-range-context.d.ts.map +0 -1
  164. package/dist/src/utils/byte-range-context.js +0 -504
  165. package/dist/src/utils/byte-range-context.js.map +0 -1
  166. package/dist/src/utils/dag-cbor-to-safe-json.d.ts +0 -15
  167. package/dist/src/utils/dag-cbor-to-safe-json.d.ts.map +0 -1
  168. package/dist/src/utils/dag-cbor-to-safe-json.js +0 -54
  169. package/dist/src/utils/dag-cbor-to-safe-json.js.map +0 -1
  170. package/dist/src/utils/dir-index-html.d.ts +0 -19
  171. package/dist/src/utils/dir-index-html.d.ts.map +0 -1
  172. package/dist/src/utils/dir-index-html.js +0 -438
  173. package/dist/src/utils/dir-index-html.js.map +0 -1
  174. package/dist/src/utils/get-peer-id-from-string.d.ts +0 -3
  175. package/dist/src/utils/get-peer-id-from-string.d.ts.map +0 -1
  176. package/dist/src/utils/get-peer-id-from-string.js +0 -10
  177. package/dist/src/utils/get-peer-id-from-string.js.map +0 -1
  178. package/dist/src/utils/get-resolved-accept-header.d.ts +0 -9
  179. package/dist/src/utils/get-resolved-accept-header.d.ts.map +0 -1
  180. package/dist/src/utils/get-resolved-accept-header.js +0 -27
  181. package/dist/src/utils/get-resolved-accept-header.js.map +0 -1
  182. package/dist/src/utils/get-stream-from-async-iterable.d.ts +0 -9
  183. package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +0 -1
  184. package/dist/src/utils/get-stream-from-async-iterable.js +0 -43
  185. package/dist/src/utils/get-stream-from-async-iterable.js.map +0 -1
  186. package/dist/src/utils/handle-redirects.d.ts +0 -16
  187. package/dist/src/utils/handle-redirects.d.ts.map +0 -1
  188. package/dist/src/utils/handle-redirects.js +0 -84
  189. package/dist/src/utils/handle-redirects.js.map +0 -1
  190. package/dist/src/utils/is-accept-explicit.d.ts +0 -15
  191. package/dist/src/utils/is-accept-explicit.d.ts.map +0 -1
  192. package/dist/src/utils/is-accept-explicit.js +0 -26
  193. package/dist/src/utils/is-accept-explicit.js.map +0 -1
  194. package/dist/src/utils/request-headers.d.ts +0 -13
  195. package/dist/src/utils/request-headers.d.ts.map +0 -1
  196. package/dist/src/utils/request-headers.js +0 -63
  197. package/dist/src/utils/request-headers.js.map +0 -1
  198. package/dist/src/utils/select-output-type.d.ts +0 -17
  199. package/dist/src/utils/select-output-type.d.ts.map +0 -1
  200. package/dist/src/utils/select-output-type.js +0 -153
  201. package/dist/src/utils/select-output-type.js.map +0 -1
  202. package/dist/src/utils/tlru.d.ts +0 -15
  203. package/dist/src/utils/tlru.d.ts.map +0 -1
  204. package/dist/src/utils/tlru.js +0 -34
  205. package/dist/src/utils/tlru.js.map +0 -1
  206. package/dist/src/utils/walk-path.d.ts +0 -27
  207. package/dist/src/utils/walk-path.d.ts.map +0 -1
  208. package/dist/src/utils/walk-path.js +0 -45
  209. package/dist/src/utils/walk-path.js.map +0 -1
  210. package/src/plugins/plugin-handle-byte-range-context.ts +0 -30
  211. package/src/plugins/plugin-handle-cbor.ts +0 -107
  212. package/src/plugins/plugin-handle-dag-cbor-html-preview.ts +0 -295
  213. package/src/plugins/plugin-handle-dag-cbor.ts +0 -83
  214. package/src/plugins/plugin-handle-dag-pb.ts +0 -248
  215. package/src/plugins/plugin-handle-dag-walk.ts +0 -110
  216. package/src/plugins/plugin-handle-dir-index-html.ts +0 -72
  217. package/src/plugins/plugin-handle-json.ts +0 -80
  218. package/src/plugins/plugin-handle-raw.ts +0 -110
  219. package/src/plugins/plugins.ts +0 -5
  220. package/src/utils/byte-range-context.ts +0 -597
  221. package/src/utils/dag-cbor-to-safe-json.ts +0 -63
  222. package/src/utils/dir-index-html.ts +0 -505
  223. package/src/utils/get-peer-id-from-string.ts +0 -12
  224. package/src/utils/get-resolved-accept-header.ts +0 -42
  225. package/src/utils/get-stream-from-async-iterable.ts +0 -49
  226. package/src/utils/handle-redirects.ts +0 -109
  227. package/src/utils/is-accept-explicit.ts +0 -38
  228. package/src/utils/request-headers.ts +0 -65
  229. package/src/utils/select-output-type.ts +0 -175
  230. package/src/utils/tlru.ts +0 -42
  231. package/src/utils/walk-path.ts +0 -69
@@ -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 QuickLRU from 'quick-lru'
6
- import { ByteRangeContextPlugin } from './plugins/plugin-handle-byte-range-context.js'
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 { CborPlugin } from './plugins/plugin-handle-cbor.js'
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 { getContentDispositionFilename } from './utils/get-content-disposition-filename.js'
17
+ import { errorToResponse } from './utils/error-to-response.ts'
20
18
  import { getETag } from './utils/get-e-tag.js'
21
- import { getResolvedAcceptHeader } from './utils/get-resolved-accept-header.js'
22
- import { getRedirectResponse } from './utils/handle-redirects.js'
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, notAcceptableResponse, internalServerErrorResponse, notImplementedResponse } from './utils/responses.js'
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, SessionBlockstore } from '@helia/interface'
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 undefined
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 DagWalkPlugin(pluginOptions),
96
- new ByteRangeContextPlugin(pluginOptions),
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 JsonPlugin(pluginOptions),
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
- // Add any remaining custom plugins that don't replace a default plugin
117
- this.plugins.push(...customPlugins.filter(plugin => !defaultPluginMap.has(plugin.id)))
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
- private getBlockstore (root: CID, resource: string | CID, useSession: boolean = true, options: AbortOptions = {}): Blockstore {
124
- const key = resourceToSessionCacheKey(resource)
125
- if (!useSession) {
126
- return this.helia.blockstore
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
- let session = this.blockstoreSessions.get(key)
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 (session == null) {
132
- session = this.helia.blockstore.createSession(root, options)
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 session
243
+ return this.handleFinalResponse(response, Boolean(options?.withServerTiming) || Boolean(this.withServerTiming), context)
137
244
  }
138
245
 
139
246
  /**
140
- * The last place a Response touches in verified-fetch before being returned to the user. This is where we add the
141
- * Server-Timing header to the response if it has been collected. It should be used for any final processing of the
142
- * response before it is returned to the user.
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 handleFinalResponse (response: Response, context?: Partial<PluginContext>): Response {
145
- if ((this.withServerTiming || context?.withServerTiming === true) && context?.serverTiming != null) {
146
- const timingHeader = context?.serverTiming.getHeader()
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
- if (timingHeader !== '') {
149
- response.headers.set('Server-Timing', timingHeader)
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
- // if there are multiple ranges, we should omit the content-length header. see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding
154
- if (response.headers.get('Transfer-Encoding') !== 'chunked') {
155
- if (context?.byteRangeContext != null) {
156
- const contentLength = context.byteRangeContext.getLength()
157
- if (contentLength != null) {
158
- this.log.trace('Setting Content-Length from byteRangeContext: %d', contentLength)
159
- response.headers.set('Content-Length', contentLength.toString())
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
- // set Content-Disposition header
165
- let contentDisposition: string | undefined
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
- // force download if requested
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
- // override filename if requested
174
- if (context?.query?.filename != null) {
175
- this.log.trace('specific filename requested')
373
+ return acceptable
374
+ }
176
375
 
177
- if (contentDisposition == null) {
178
- contentDisposition = 'inline'
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
- contentDisposition = `${contentDisposition}; ${getContentDispositionFilename(context.query.filename)}`
182
- }
385
+ if (withServerTiming === true && context?.serverTiming != null) {
386
+ const timingHeader = context?.serverTiming.getHeader()
183
387
 
184
- if (contentDisposition != null) {
185
- this.log.trace('content disposition %s', contentDisposition)
186
- response.headers.set('Content-Disposition', contentDisposition)
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.pathDetails?.terminalElement.cid ?? context.cid,
192
- reqFormat: context.reqFormat,
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?.ipfsPath != null) {
206
- response.headers.set('X-Ipfs-Path', uriEncodeIPFSPath(context.ipfsPath))
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
- if (context?.reqFormat !== 'car') {
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
- if (response.headers.get('Content-Type')?.includes('application/vnd.ipld.car') === true || response.headers.get('Content-Type')?.includes('application/vnd.ipld.raw') === true) {
224
- // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
225
- response.headers.set('X-Content-Type-Options', 'nosniff')
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
- let prevModificationId = context.modified
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
- // gather plugins that say they can handle the *current* context, but haven't been used yet
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
- if (readyPlugins.length === 0) {
265
- this.log.trace('no plugins can handle the current context, checking by CID code')
266
- const plugins = this.plugins.filter(p => p.codes.includes(context.cid.code))
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
- this.log.trace('plugins ready to handle request: %s', readyPlugins.map(p => p.id).join(', '))
453
+ this.log.trace('plugins ready to handle request: %s', plugins.map(p => p.id).join(', '))
277
454
 
278
- // track if any plugin changed the context or returned a response
279
- let contextChanged = false
280
- let pluginHandled = false
455
+ // track if any plugin changed the context or returned a response
456
+ const contextChanged = false
457
+ let pluginHandled = false
281
458
 
282
- for (const plugin of readyPlugins) {
283
- try {
284
- this.log('invoking plugin: %s', plugin.id)
285
- pluginsUsed.add(plugin.id)
459
+ for (const plugin of plugins) {
460
+ try {
461
+ this.log('invoking plugin: %s', plugin.id)
462
+ pluginsUsed.add(plugin.id)
286
463
 
287
- const maybeResponse = await plugin.handle(context)
464
+ const maybeResponse = await plugin.handle(context)
288
465
 
289
- this.log('plugin response %s %o', plugin.id, maybeResponse)
466
+ this.log('plugin response %s %o', plugin.id, maybeResponse)
290
467
 
291
- if (maybeResponse != null) {
292
- // if a plugin returns a final Response, short-circuit
293
- finalResponse = maybeResponse
294
- pluginHandled = true
295
- break
296
- }
297
- } catch (err: any) {
298
- if (context.options?.signal?.aborted) {
299
- throw new AbortError(context.options?.signal?.reason)
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
- this.log.error('error in plugin %s - %e', plugin.id, err)
479
+ this.log.error('error in plugin %s - %e', plugin.id, err)
303
480
 
304
- return internalServerErrorResponse(context.resource, JSON.stringify({
305
- error: errorToObject(err)
306
- }), {
307
- headers: {
308
- 'content-type': 'application/json'
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
- if (finalResponse != null) {
321
- this.log.trace('plugin %s produced final response', plugin.id)
322
- break
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
  */