@helia/verified-fetch 3.2.3 → 4.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.
Files changed (126) hide show
  1. package/README.md +5 -5
  2. package/dist/index.min.js +81 -71
  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 -0
  6. package/dist/src/constants.js +2 -0
  7. package/dist/src/constants.js.map +1 -0
  8. package/dist/src/index.d.ts +57 -13
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +6 -6
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
  13. package/dist/src/plugins/plugin-handle-car.js +37 -27
  14. package/dist/src/plugins/plugin-handle-car.js.map +1 -1
  15. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts +1 -1
  16. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts.map +1 -1
  17. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js +1 -1
  18. package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js.map +1 -1
  19. package/dist/src/plugins/plugin-handle-dag-cbor.js +5 -5
  20. package/dist/src/plugins/plugin-handle-dag-cbor.js.map +1 -1
  21. package/dist/src/plugins/plugin-handle-dag-pb.js +12 -12
  22. package/dist/src/plugins/plugin-handle-dag-pb.js.map +1 -1
  23. package/dist/src/plugins/plugin-handle-dag-walk.d.ts.map +1 -1
  24. package/dist/src/plugins/plugin-handle-dag-walk.js +5 -4
  25. package/dist/src/plugins/plugin-handle-dag-walk.js.map +1 -1
  26. package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
  27. package/dist/src/plugins/plugin-handle-ipns-record.js +13 -19
  28. package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
  29. package/dist/src/plugins/plugin-handle-json.d.ts.map +1 -1
  30. package/dist/src/plugins/plugin-handle-json.js +5 -4
  31. package/dist/src/plugins/plugin-handle-json.js.map +1 -1
  32. package/dist/src/plugins/plugin-handle-raw.d.ts.map +1 -1
  33. package/dist/src/plugins/plugin-handle-raw.js +18 -5
  34. package/dist/src/plugins/plugin-handle-raw.js.map +1 -1
  35. package/dist/src/plugins/plugin-handle-tar.js +1 -1
  36. package/dist/src/plugins/plugin-handle-tar.js.map +1 -1
  37. package/dist/src/plugins/types.d.ts +10 -8
  38. package/dist/src/plugins/types.d.ts.map +1 -1
  39. package/dist/src/url-resolver.d.ts +21 -0
  40. package/dist/src/url-resolver.d.ts.map +1 -0
  41. package/dist/src/url-resolver.js +118 -0
  42. package/dist/src/url-resolver.js.map +1 -0
  43. package/dist/src/utils/byte-range-context.d.ts +1 -1
  44. package/dist/src/utils/get-content-type.d.ts +3 -3
  45. package/dist/src/utils/get-content-type.d.ts.map +1 -1
  46. package/dist/src/utils/get-content-type.js +1 -1
  47. package/dist/src/utils/get-content-type.js.map +1 -1
  48. package/dist/src/utils/get-e-tag.d.ts +1 -1
  49. package/dist/src/utils/get-offset-and-length.d.ts +6 -0
  50. package/dist/src/utils/get-offset-and-length.d.ts.map +1 -0
  51. package/dist/src/utils/get-offset-and-length.js +46 -0
  52. package/dist/src/utils/get-offset-and-length.js.map +1 -0
  53. package/dist/src/utils/get-resolved-accept-header.d.ts +2 -2
  54. package/dist/src/utils/get-resolved-accept-header.d.ts.map +1 -1
  55. package/dist/src/utils/handle-redirects.d.ts.map +1 -1
  56. package/dist/src/utils/handle-redirects.js +3 -3
  57. package/dist/src/utils/handle-redirects.js.map +1 -1
  58. package/dist/src/utils/ipfs-path-to-string.d.ts +6 -0
  59. package/dist/src/utils/ipfs-path-to-string.d.ts.map +1 -0
  60. package/dist/src/utils/ipfs-path-to-string.js +10 -0
  61. package/dist/src/utils/ipfs-path-to-string.js.map +1 -0
  62. package/dist/src/utils/is-accept-explicit.d.ts +6 -4
  63. package/dist/src/utils/is-accept-explicit.d.ts.map +1 -1
  64. package/dist/src/utils/is-accept-explicit.js +7 -4
  65. package/dist/src/utils/is-accept-explicit.js.map +1 -1
  66. package/dist/src/utils/parse-url-string.d.ts +1 -55
  67. package/dist/src/utils/parse-url-string.d.ts.map +1 -1
  68. package/dist/src/utils/parse-url-string.js +16 -217
  69. package/dist/src/utils/parse-url-string.js.map +1 -1
  70. package/dist/src/utils/response-headers.d.ts +1 -1
  71. package/dist/src/utils/response-headers.d.ts.map +1 -1
  72. package/dist/src/utils/responses.d.ts +1 -1
  73. package/dist/src/utils/select-output-type.d.ts +6 -2
  74. package/dist/src/utils/select-output-type.d.ts.map +1 -1
  75. package/dist/src/utils/select-output-type.js +28 -37
  76. package/dist/src/utils/select-output-type.js.map +1 -1
  77. package/dist/src/utils/server-timing.d.ts +5 -11
  78. package/dist/src/utils/server-timing.d.ts.map +1 -1
  79. package/dist/src/utils/server-timing.js +17 -15
  80. package/dist/src/utils/server-timing.js.map +1 -1
  81. package/dist/src/utils/walk-path.js +1 -1
  82. package/dist/src/utils/walk-path.js.map +1 -1
  83. package/dist/src/verified-fetch.d.ts +3 -10
  84. package/dist/src/verified-fetch.d.ts.map +1 -1
  85. package/dist/src/verified-fetch.js +68 -57
  86. package/dist/src/verified-fetch.js.map +1 -1
  87. package/dist/typedoc-urls.json +13 -2
  88. package/package.json +35 -36
  89. package/src/constants.ts +1 -0
  90. package/src/index.ts +73 -22
  91. package/src/plugins/plugin-handle-car.ts +54 -30
  92. package/src/plugins/plugin-handle-dag-cbor-html-preview.ts +2 -2
  93. package/src/plugins/plugin-handle-dag-cbor.ts +5 -5
  94. package/src/plugins/plugin-handle-dag-pb.ts +12 -12
  95. package/src/plugins/plugin-handle-dag-walk.ts +5 -4
  96. package/src/plugins/plugin-handle-ipns-record.ts +16 -19
  97. package/src/plugins/plugin-handle-json.ts +5 -4
  98. package/src/plugins/plugin-handle-raw.ts +21 -6
  99. package/src/plugins/plugin-handle-tar.ts +1 -1
  100. package/src/plugins/types.ts +12 -8
  101. package/src/url-resolver.ts +159 -0
  102. package/src/utils/byte-range-context.ts +1 -1
  103. package/src/utils/get-content-type.ts +5 -4
  104. package/src/utils/get-e-tag.ts +1 -1
  105. package/src/utils/get-offset-and-length.ts +54 -0
  106. package/src/utils/get-resolved-accept-header.ts +2 -2
  107. package/src/utils/handle-redirects.ts +10 -3
  108. package/src/utils/ipfs-path-to-string.ts +9 -0
  109. package/src/utils/is-accept-explicit.ts +14 -7
  110. package/src/utils/parse-url-string.ts +20 -286
  111. package/src/utils/response-headers.ts +1 -1
  112. package/src/utils/responses.ts +1 -1
  113. package/src/utils/select-output-type.ts +38 -44
  114. package/src/utils/server-timing.ts +17 -30
  115. package/src/utils/walk-path.ts +1 -1
  116. package/src/verified-fetch.ts +78 -69
  117. package/dist/src/types.d.ts +0 -16
  118. package/dist/src/types.d.ts.map +0 -1
  119. package/dist/src/types.js +0 -2
  120. package/dist/src/types.js.map +0 -1
  121. package/dist/src/utils/parse-resource.d.ts +0 -18
  122. package/dist/src/utils/parse-resource.d.ts.map +0 -1
  123. package/dist/src/utils/parse-resource.js +0 -27
  124. package/dist/src/utils/parse-resource.js.map +0 -1
  125. package/src/types.ts +0 -17
  126. package/src/utils/parse-resource.ts +0 -42
@@ -1,4 +1,5 @@
1
- import { ipns as heliaIpns } from '@helia/ipns'
1
+ import { dnsLink } from '@helia/dnslink'
2
+ import { ipnsResolver } from '@helia/ipns'
2
3
  import { AbortError } from '@libp2p/interface'
3
4
  import { prefixLogger } from '@libp2p/logger'
4
5
  import { CustomProgressEvent } from 'progress-events'
@@ -12,22 +13,24 @@ import { IpnsRecordPlugin } from './plugins/plugin-handle-ipns-record.js'
12
13
  import { JsonPlugin } from './plugins/plugin-handle-json.js'
13
14
  import { RawPlugin } from './plugins/plugin-handle-raw.js'
14
15
  import { TarPlugin } from './plugins/plugin-handle-tar.js'
16
+ import { URLResolver } from './url-resolver.ts'
15
17
  import { contentTypeParser } from './utils/content-type-parser.js'
16
18
  import { getContentDispositionFilename } from './utils/get-content-disposition-filename.js'
17
19
  import { getETag } from './utils/get-e-tag.js'
18
20
  import { getResolvedAcceptHeader } from './utils/get-resolved-accept-header.js'
19
21
  import { getRedirectResponse } from './utils/handle-redirects.js'
20
- import { parseResource } from './utils/parse-resource.js'
22
+ import { uriEncodeIPFSPath } from './utils/ipfs-path-to-string.ts'
21
23
  import { resourceToSessionCacheKey } from './utils/resource-to-cache-key.js'
22
24
  import { setCacheControlHeader } from './utils/response-headers.js'
23
25
  import { badRequestResponse, notAcceptableResponse, notSupportedResponse, badGatewayResponse } from './utils/responses.js'
24
26
  import { selectOutputType } from './utils/select-output-type.js'
25
- import { serverTiming } from './utils/server-timing.js'
26
- import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
27
+ import { ServerTiming } from './utils/server-timing.js'
28
+ import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, ResolveURLResult, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
27
29
  import type { VerifiedFetchPlugin, PluginContext, PluginOptions } from './plugins/types.js'
28
- import type { ParsedUrlStringResults } from './utils/parse-url-string.js'
30
+ import type { AcceptHeader } from './utils/select-output-type.js'
31
+ import type { DNSLink } from '@helia/dnslink'
29
32
  import type { Helia, SessionBlockstore } from '@helia/interface'
30
- import type { IPNS } from '@helia/ipns'
33
+ import type { IPNSResolver } from '@helia/ipns'
31
34
  import type { AbortOptions, Logger } from '@libp2p/interface'
32
35
  import type { Blockstore } from 'interface-blockstore'
33
36
  import type { CID } from 'multiformats/cid'
@@ -35,11 +38,6 @@ import type { CID } from 'multiformats/cid'
35
38
  const SESSION_CACHE_MAX_SIZE = 100
36
39
  const SESSION_CACHE_TTL_MS = 60 * 1000
37
40
 
38
- interface VerifiedFetchComponents {
39
- helia: Helia
40
- ipns?: IPNS
41
- }
42
-
43
41
  function convertOptions (options?: VerifiedFetchOptions): (Omit<VerifiedFetchOptions, 'signal'> & AbortOptions) | undefined {
44
42
  if (options == null) {
45
43
  return undefined
@@ -60,19 +58,20 @@ function convertOptions (options?: VerifiedFetchOptions): (Omit<VerifiedFetchOpt
60
58
 
61
59
  export class VerifiedFetch {
62
60
  private readonly helia: Helia
63
- private readonly ipns: IPNS
61
+ private readonly ipnsResolver: IPNSResolver
62
+ private readonly dnsLink: DNSLink
64
63
  private readonly log: Logger
65
64
  private readonly contentTypeParser: ContentTypeParser | undefined
66
65
  private readonly blockstoreSessions: QuickLRU<string, SessionBlockstore>
67
- private serverTimingHeaders: string[] = []
68
66
  private readonly withServerTiming: boolean
69
67
  private readonly plugins: VerifiedFetchPlugin[] = []
70
68
 
71
- constructor ({ helia, ipns }: VerifiedFetchComponents, init?: CreateVerifiedFetchOptions) {
69
+ constructor (helia: Helia, init: CreateVerifiedFetchOptions = {}) {
72
70
  this.helia = helia
73
71
  this.log = helia.logger.forComponent('helia:verified-fetch')
74
- this.ipns = ipns ?? heliaIpns(helia)
75
- this.contentTypeParser = init?.contentTypeParser ?? contentTypeParser
72
+ this.ipnsResolver = init.ipnsResolver ?? ipnsResolver(helia)
73
+ this.dnsLink = init.dnsLink ?? dnsLink(helia)
74
+ this.contentTypeParser = init.contentTypeParser ?? contentTypeParser
76
75
  this.blockstoreSessions = new QuickLRU({
77
76
  maxSize: init?.sessionCacheSize ?? SESSION_CACHE_MAX_SIZE,
78
77
  maxAge: init?.sessionTTLms ?? SESSION_CACHE_TTL_MS,
@@ -86,9 +85,9 @@ export class VerifiedFetch {
86
85
  ...init,
87
86
  logger: prefixLogger('helia:verified-fetch'),
88
87
  getBlockstore: (cid, resource, useSession, options) => this.getBlockstore(cid, resource, useSession, options),
89
- handleServerTiming: async (name, description, fn) => this.handleServerTiming(name, description, fn, this.withServerTiming),
90
88
  helia,
91
- contentTypeParser: this.contentTypeParser
89
+ contentTypeParser: this.contentTypeParser,
90
+ ipnsResolver: this.ipnsResolver
92
91
  }
93
92
 
94
93
  const defaultPlugins = [
@@ -103,7 +102,7 @@ export class VerifiedFetch {
103
102
  new DagPbPlugin(pluginOptions)
104
103
  ]
105
104
 
106
- const customPlugins = init?.plugins?.map((pluginFactory) => pluginFactory(pluginOptions)) ?? []
105
+ const customPlugins = init.plugins?.map((pluginFactory) => pluginFactory(pluginOptions)) ?? []
107
106
 
108
107
  if (customPlugins.length > 0) {
109
108
  // allow custom plugins to replace default plugins
@@ -137,35 +136,20 @@ export class VerifiedFetch {
137
136
  return session
138
137
  }
139
138
 
140
- private async handleServerTiming<T> (name: string, description: string, fn: () => Promise<T>, withServerTiming: boolean): Promise<T> {
141
- if (!withServerTiming) {
142
- return fn()
143
- }
144
- const { error, result, header } = await serverTiming(name, description, fn)
145
- this.serverTimingHeaders.push(header)
146
- if (error != null) {
147
- throw error
148
- }
149
-
150
- return result
151
- }
152
-
153
139
  /**
154
140
  * The last place a Response touches in verified-fetch before being returned to the user. This is where we add the
155
141
  * Server-Timing header to the response if it has been collected. It should be used for any final processing of the
156
142
  * response before it is returned to the user.
157
143
  */
158
- private handleFinalResponse (response: Response, { query, cid, reqFormat, ttl, protocol, ipfsPath, pathDetails, byteRangeContext, options }: Partial<PluginContext> = {}): Response {
159
- if (this.serverTimingHeaders.length > 0) {
160
- const headerString = this.serverTimingHeaders.join(', ')
161
- response.headers.set('Server-Timing', headerString)
162
- this.serverTimingHeaders = []
144
+ private handleFinalResponse (response: Response, context?: Partial<PluginContext>): Response {
145
+ if ((this.withServerTiming || context?.withServerTiming === true) && context?.serverTiming != null) {
146
+ response.headers.set('Server-Timing', context?.serverTiming.getHeader())
163
147
  }
164
148
 
165
149
  // 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
166
150
  if (response.headers.get('Transfer-Encoding') !== 'chunked') {
167
- if (byteRangeContext != null) {
168
- const contentLength = byteRangeContext.getLength()
151
+ if (context?.byteRangeContext != null) {
152
+ const contentLength = context.byteRangeContext.getLength()
169
153
  if (contentLength != null) {
170
154
  this.log.trace('Setting Content-Length from byteRangeContext: %d', contentLength)
171
155
  response.headers.set('Content-Length', contentLength.toString())
@@ -179,19 +163,19 @@ export class VerifiedFetch {
179
163
  this.log.trace('checking for content disposition')
180
164
 
181
165
  // force download if requested
182
- if (query?.download === true) {
166
+ if (context?.query?.download === true) {
183
167
  contentDisposition = 'attachment'
184
168
  } else {
185
169
  this.log.trace('download not requested')
186
170
  }
187
171
 
188
172
  // override filename if requested
189
- if (query?.filename != null) {
173
+ if (context?.query?.filename != null) {
190
174
  if (contentDisposition == null) {
191
175
  contentDisposition = 'inline'
192
176
  }
193
177
 
194
- contentDisposition = `${contentDisposition}; ${getContentDispositionFilename(query.filename)}`
178
+ contentDisposition = `${contentDisposition}; ${getContentDispositionFilename(context.query.filename)}`
195
179
  } else {
196
180
  this.log.trace('no filename specified in query')
197
181
  }
@@ -202,24 +186,33 @@ export class VerifiedFetch {
202
186
  this.log.trace('no content disposition specified')
203
187
  }
204
188
 
205
- if (cid != null && response.headers.get('etag') == null) {
206
- response.headers.set('etag', getETag({ cid: pathDetails?.terminalElement.cid ?? cid, reqFormat, weak: false }))
189
+ if (context?.cid != null && response.headers.get('etag') == null) {
190
+ response.headers.set('etag', getETag({
191
+ cid: context.pathDetails?.terminalElement.cid ?? context.cid,
192
+ reqFormat: context.reqFormat,
193
+ weak: false
194
+ }))
207
195
  }
208
196
 
209
- if (protocol != null) {
210
- setCacheControlHeader({ response, ttl, protocol })
197
+ if (context?.protocol != null && context.ttl != null) {
198
+ setCacheControlHeader({
199
+ response,
200
+ ttl: context.ttl,
201
+ protocol: context.protocol
202
+ })
211
203
  }
212
- if (ipfsPath != null) {
213
- response.headers.set('X-Ipfs-Path', ipfsPath)
204
+
205
+ if (context?.ipfsPath != null) {
206
+ response.headers.set('X-Ipfs-Path', uriEncodeIPFSPath(context.ipfsPath))
214
207
  }
215
208
 
216
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.
217
210
  response.headers.set('Access-Control-Allow-Origin', '*')
218
211
  response.headers.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')
219
212
  response.headers.set('Access-Control-Allow-Headers', 'Range, X-Requested-With')
220
- response.headers.set('Access-Control-Expose-Headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Stream-Output')
213
+ response.headers.set('Access-Control-Expose-Headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Ipfs-Roots, X-Stream-Output')
221
214
 
222
- if (reqFormat !== 'car') {
215
+ if (context?.reqFormat !== 'car') {
223
216
  // if we are not doing streaming responses, set the Accept-Ranges header to bytes to enable range requests
224
217
  response.headers.set('Accept-Ranges', 'bytes')
225
218
  } else {
@@ -227,10 +220,17 @@ export class VerifiedFetch {
227
220
  response.headers.set('Accept-Ranges', 'none')
228
221
  }
229
222
 
230
- if (options?.method === 'HEAD') {
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
+ }
227
+
228
+ if (context?.options?.method === 'HEAD') {
231
229
  // don't send the body for HEAD requests
232
- const headers = response?.headers
233
- return new Response(null, { status: 200, headers })
230
+ return new Response(null, {
231
+ status: 200,
232
+ headers: response.headers
233
+ })
234
234
  }
235
235
 
236
236
  return response
@@ -248,23 +248,23 @@ export class VerifiedFetch {
248
248
  let prevModificationId = context.modified
249
249
 
250
250
  while (passCount < maxPasses) {
251
- this.log(`Starting pipeline pass #${passCount + 1}`)
251
+ this.log(`starting pipeline pass #${passCount + 1}`)
252
252
  passCount++
253
253
 
254
254
  // gather plugins that say they can handle the *current* context, but haven't been used yet
255
255
  const readyPlugins = this.plugins.filter(p => !pluginsUsed.has(p.id)).filter(p => p.canHandle(context))
256
256
  if (readyPlugins.length === 0) {
257
- this.log.trace('No plugins can handle the current context.. checking by CID code')
257
+ this.log.trace('no plugins can handle the current context.. checking by CID code')
258
258
  const plugins = this.plugins.filter(p => p.codes.includes(context.cid.code))
259
259
  if (plugins.length > 0) {
260
260
  readyPlugins.push(...plugins)
261
261
  } else {
262
- this.log.trace('No plugins found that can handle request by CID code; exiting pipeline.')
262
+ this.log.trace('no plugins found that can handle request by CID code; exiting pipeline.')
263
263
  break
264
264
  }
265
265
  }
266
266
 
267
- this.log.trace('Plugins ready to handle request: ', readyPlugins.map(p => p.id).join(', '))
267
+ this.log.trace('plugins ready to handle request: ', readyPlugins.map(p => p.id).join(', '))
268
268
 
269
269
  // track if any plugin changed the context or returned a response
270
270
  let contextChanged = false
@@ -272,7 +272,7 @@ export class VerifiedFetch {
272
272
 
273
273
  for (const plugin of readyPlugins) {
274
274
  try {
275
- this.log.trace('Invoking plugin:', plugin.id)
275
+ this.log.trace('invoking plugin:', plugin.id)
276
276
  pluginsUsed.add(plugin.id)
277
277
 
278
278
  const maybeResponse = await plugin.handle(context)
@@ -286,7 +286,7 @@ export class VerifiedFetch {
286
286
  if (context.options?.signal?.aborted) {
287
287
  throw new AbortError(context.options?.signal?.reason)
288
288
  }
289
- this.log.error('Error in plugin:', plugin.constructor.name, err)
289
+ this.log.error('error in plugin %s - %e', plugin.constructor.name, err)
290
290
  // if fatal, short-circuit the pipeline
291
291
  if (err.name === 'PluginFatalError') {
292
292
  // if plugin provides a custom error response, return it
@@ -302,7 +302,7 @@ export class VerifiedFetch {
302
302
  }
303
303
 
304
304
  if (finalResponse != null) {
305
- this.log.trace('Plugin produced final response:', plugin.id)
305
+ this.log.trace('plugin %s produced final response', plugin.id)
306
306
  break
307
307
  }
308
308
  }
@@ -312,7 +312,7 @@ export class VerifiedFetch {
312
312
  }
313
313
 
314
314
  if (!contextChanged) {
315
- this.log.trace('No context changes and no final response; exiting pipeline.')
315
+ this.log.trace('no context changes and no final response; exiting pipeline.')
316
316
  break
317
317
  }
318
318
  }
@@ -335,14 +335,20 @@ export class VerifiedFetch {
335
335
  }
336
336
 
337
337
  const options = convertOptions(opts)
338
- const withServerTiming = options?.withServerTiming ?? this.withServerTiming
338
+ const serverTiming = new ServerTiming()
339
+
340
+ const urlResolver = new URLResolver({
341
+ ipnsResolver: this.ipnsResolver,
342
+ dnsLink: this.dnsLink,
343
+ timing: serverTiming
344
+ })
339
345
 
340
346
  options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
341
347
 
342
- let parsedResult: ParsedUrlStringResults
348
+ let parsedResult: ResolveURLResult
349
+
343
350
  try {
344
- parsedResult = await this.handleServerTiming('parse-resource', '', async () => parseResource(resource, { ipns: this.ipns, logger: this.helia.logger }, { withServerTiming, ...options }), withServerTiming)
345
- this.serverTimingHeaders.push(...parsedResult.serverTimings.map(({ header }) => header))
351
+ parsedResult = await urlResolver.resolve(resource, options)
346
352
  } catch (err: any) {
347
353
  if (options?.signal?.aborted) {
348
354
  throw new AbortError(options?.signal?.reason)
@@ -356,14 +362,15 @@ export class VerifiedFetch {
356
362
 
357
363
  const acceptHeader = getResolvedAcceptHeader({ query: parsedResult.query, headers: options?.headers, logger: this.helia.logger })
358
364
 
359
- const accept: string | undefined = selectOutputType(parsedResult.cid, acceptHeader)
365
+ const accept: AcceptHeader | undefined = selectOutputType(parsedResult.cid, acceptHeader)
360
366
  this.log('output type %s', accept)
361
367
 
362
368
  if (acceptHeader != null && accept == null) {
369
+ this.log.error('could not fulfil request based on accept header')
363
370
  return this.handleFinalResponse(notAcceptableResponse(resource.toString()))
364
371
  }
365
372
 
366
- const responseContentType: string = accept?.split(';')[0] ?? 'application/octet-stream'
373
+ const responseContentType: string = accept?.mimeType.split(';')[0] ?? 'application/octet-stream'
367
374
 
368
375
  const redirectResponse = await getRedirectResponse({ resource, options, logger: this.helia.logger, cid: parsedResult.cid })
369
376
  if (redirectResponse != null) {
@@ -375,10 +382,12 @@ export class VerifiedFetch {
375
382
  resource: resource.toString(),
376
383
  accept,
377
384
  options,
378
- withServerTiming,
379
385
  onProgress: options?.onProgress,
380
386
  modified: 0,
381
- plugins: this.plugins.map(p => p.id)
387
+ plugins: this.plugins.map(p => p.id),
388
+ query: parsedResult.query ?? {},
389
+ withServerTiming: Boolean(options?.withServerTiming) || Boolean(this.withServerTiming),
390
+ serverTiming
382
391
  }
383
392
 
384
393
  this.log.trace('finding handler for cid code "%s" and response content type "%s"', parsedResult.cid.code, responseContentType)
@@ -1,16 +0,0 @@
1
- export type RequestFormatShorthand = 'raw' | 'car' | 'tar' | 'ipns-record' | 'dag-json' | 'dag-cbor' | 'json' | 'cbor';
2
- export type SupportedBodyTypes = string | Uint8Array | ArrayBuffer | Blob | ReadableStream<Uint8Array> | null;
3
- /**
4
- * A ContentTypeParser attempts to return the mime type of a given file. It
5
- * receives the first chunk of the file data and the file name, if it is
6
- * available. The function can be sync or async and if it returns/resolves to
7
- * `undefined`, `application/octet-stream` will be used.
8
- */
9
- export interface ContentTypeParser {
10
- /**
11
- * Attempt to determine a mime type, either via of the passed bytes or the
12
- * filename if it is available.
13
- */
14
- (bytes: Uint8Array, fileName?: string): Promise<string | undefined> | string | undefined;
15
- }
16
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;AAEtH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,IAAI,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAA;AAE7G;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAAA;CACzF"}
package/dist/src/types.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
@@ -1,18 +0,0 @@
1
- import type { ParseUrlStringOptions, ParsedUrlStringResults } from './parse-url-string.js';
2
- import type { Resource } from '../index.js';
3
- import type { IPNS } from '@helia/ipns';
4
- import type { ComponentLogger } from '@libp2p/interface';
5
- export interface ParseResourceComponents {
6
- ipns: IPNS;
7
- logger: ComponentLogger;
8
- }
9
- export interface ParseResourceOptions extends ParseUrlStringOptions {
10
- withServerTiming?: boolean;
11
- }
12
- /**
13
- * Handles the different use cases for the `resource` argument.
14
- * The resource can represent an IPFS path, IPNS path, or CID.
15
- * If the resource represents an IPNS path, we need to resolve it to a CID.
16
- */
17
- export declare function parseResource(resource: Resource, { ipns, logger }: ParseResourceComponents, { withServerTiming, ...options }?: ParseResourceOptions): Promise<ParsedUrlStringResults>;
18
- //# sourceMappingURL=parse-resource.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"parse-resource.d.ts","sourceRoot":"","sources":["../../../src/utils/parse-resource.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC1F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,eAAe,CAAA;CACxB;AAED,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;IACjE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AACD;;;;GAIG;AACH,wBAAsB,aAAa,CAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,uBAAuB,EAAE,EAAE,gBAAwB,EAAE,GAAG,OAAO,EAAE,GAAE,oBAAkD,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAqBjO"}
@@ -1,27 +0,0 @@
1
- import { CID } from 'multiformats/cid';
2
- import { parseUrlString } from './parse-url-string.js';
3
- /**
4
- * Handles the different use cases for the `resource` argument.
5
- * The resource can represent an IPFS path, IPNS path, or CID.
6
- * If the resource represents an IPNS path, we need to resolve it to a CID.
7
- */
8
- export async function parseResource(resource, { ipns, logger }, { withServerTiming = false, ...options } = { withServerTiming: false }) {
9
- if (typeof resource === 'string') {
10
- return parseUrlString({ urlString: resource, ipns, logger, withServerTiming }, options);
11
- }
12
- const cid = CID.asCID(resource);
13
- if (cid != null) {
14
- // an actual CID
15
- return {
16
- cid,
17
- protocol: 'ipfs',
18
- path: '',
19
- query: {},
20
- ipfsPath: `/ipfs/${cid.toString()}`,
21
- ttl: 29030400, // 1 year for ipfs content
22
- serverTimings: []
23
- };
24
- }
25
- throw new TypeError(`Invalid resource. Cannot determine CID from resource: ${resource}`);
26
- }
27
- //# sourceMappingURL=parse-resource.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"parse-resource.js","sourceRoot":"","sources":["../../../src/utils/parse-resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AActD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAE,QAAkB,EAAE,EAAE,IAAI,EAAE,MAAM,EAA2B,EAAE,EAAE,gBAAgB,GAAG,KAAK,EAAE,GAAG,OAAO,KAA2B,EAAE,gBAAgB,EAAE,KAAK,EAAE;IAC9L,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,cAAc,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,OAAO,CAAC,CAAA;IACzF,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAE/B,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QAChB,gBAAgB;QAChB,OAAO;YACL,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,EAAE;YACT,QAAQ,EAAE,SAAS,GAAG,CAAC,QAAQ,EAAE,EAAE;YACnC,GAAG,EAAE,QAAQ,EAAE,0BAA0B;YACzC,aAAa,EAAE,EAAE;SACe,CAAA;IACpC,CAAC;IAED,MAAM,IAAI,SAAS,CAAC,yDAAyD,QAAQ,EAAE,CAAC,CAAA;AAC1F,CAAC"}
package/src/types.ts DELETED
@@ -1,17 +0,0 @@
1
- export type RequestFormatShorthand = 'raw' | 'car' | 'tar' | 'ipns-record' | 'dag-json' | 'dag-cbor' | 'json' | 'cbor'
2
-
3
- export type SupportedBodyTypes = string | Uint8Array | ArrayBuffer | Blob | ReadableStream<Uint8Array> | null
4
-
5
- /**
6
- * A ContentTypeParser attempts to return the mime type of a given file. It
7
- * receives the first chunk of the file data and the file name, if it is
8
- * available. The function can be sync or async and if it returns/resolves to
9
- * `undefined`, `application/octet-stream` will be used.
10
- */
11
- export interface ContentTypeParser {
12
- /**
13
- * Attempt to determine a mime type, either via of the passed bytes or the
14
- * filename if it is available.
15
- */
16
- (bytes: Uint8Array, fileName?: string): Promise<string | undefined> | string | undefined
17
- }
@@ -1,42 +0,0 @@
1
- import { CID } from 'multiformats/cid'
2
- import { parseUrlString } from './parse-url-string.js'
3
- import type { ParseUrlStringOptions, ParsedUrlStringResults } from './parse-url-string.js'
4
- import type { Resource } from '../index.js'
5
- import type { IPNS } from '@helia/ipns'
6
- import type { ComponentLogger } from '@libp2p/interface'
7
-
8
- export interface ParseResourceComponents {
9
- ipns: IPNS
10
- logger: ComponentLogger
11
- }
12
-
13
- export interface ParseResourceOptions extends ParseUrlStringOptions {
14
- withServerTiming?: boolean
15
- }
16
- /**
17
- * Handles the different use cases for the `resource` argument.
18
- * The resource can represent an IPFS path, IPNS path, or CID.
19
- * If the resource represents an IPNS path, we need to resolve it to a CID.
20
- */
21
- export async function parseResource (resource: Resource, { ipns, logger }: ParseResourceComponents, { withServerTiming = false, ...options }: ParseResourceOptions = { withServerTiming: false }): Promise<ParsedUrlStringResults> {
22
- if (typeof resource === 'string') {
23
- return parseUrlString({ urlString: resource, ipns, logger, withServerTiming }, options)
24
- }
25
-
26
- const cid = CID.asCID(resource)
27
-
28
- if (cid != null) {
29
- // an actual CID
30
- return {
31
- cid,
32
- protocol: 'ipfs',
33
- path: '',
34
- query: {},
35
- ipfsPath: `/ipfs/${cid.toString()}`,
36
- ttl: 29030400, // 1 year for ipfs content
37
- serverTimings: []
38
- } satisfies ParsedUrlStringResults
39
- }
40
-
41
- throw new TypeError(`Invalid resource. Cannot determine CID from resource: ${resource}`)
42
- }