@helia/verified-fetch 1.4.1 → 1.4.2

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.
@@ -26,16 +26,16 @@ import { getStreamFromAsyncIterable } from './utils/get-stream-from-async-iterab
26
26
  import { tarStream } from './utils/get-tar-stream.js'
27
27
  import { parseResource } from './utils/parse-resource.js'
28
28
  import { resourceToSessionCacheKey } from './utils/resource-to-cache-key.js'
29
- import { setCacheControlHeader } from './utils/response-headers.js'
30
- import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse, notFoundResponse } from './utils/responses.js'
29
+ import { setCacheControlHeader, setIpfsRoots } from './utils/response-headers.js'
30
+ import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse } from './utils/responses.js'
31
31
  import { selectOutputType } from './utils/select-output-type.js'
32
- import { isObjectNode, walkPath } from './utils/walk-path.js'
32
+ import { handlePathWalking, isObjectNode } from './utils/walk-path.js'
33
33
  import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
34
- import type { RequestFormatShorthand } from './types.js'
34
+ import type { FetchHandlerFunctionArg, RequestFormatShorthand } from './types.js'
35
35
  import type { ParsedUrlStringResults } from './utils/parse-url-string'
36
36
  import type { Helia, SessionBlockstore } from '@helia/interface'
37
37
  import type { Blockstore } from 'interface-blockstore'
38
- import type { ObjectNode, UnixFSEntry } from 'ipfs-unixfs-exporter'
38
+ import type { ObjectNode } from 'ipfs-unixfs-exporter'
39
39
  import type { CID } from 'multiformats/cid'
40
40
 
41
41
  const SESSION_CACHE_MAX_SIZE = 100
@@ -46,36 +46,6 @@ interface VerifiedFetchComponents {
46
46
  ipns?: IPNS
47
47
  }
48
48
 
49
- interface FetchHandlerFunctionArg {
50
- cid: CID
51
- path: string
52
-
53
- /**
54
- * A key for use with the blockstore session cache
55
- */
56
- cacheKey: string
57
-
58
- /**
59
- * Whether to use a session during fetch operations
60
- *
61
- * @default true
62
- */
63
- session: boolean
64
-
65
- options?: Omit<VerifiedFetchOptions, 'signal'> & AbortOptions
66
-
67
- /**
68
- * If present, the user has sent an accept header with this value - if the
69
- * content cannot be represented in this format a 406 should be returned
70
- */
71
- accept?: string
72
-
73
- /**
74
- * The originally requested resource
75
- */
76
- resource: string
77
- }
78
-
79
49
  interface FetchHandlerFunction {
80
50
  (options: FetchHandlerFunctionArg): Promise<Response>
81
51
  }
@@ -156,7 +126,8 @@ export class VerifiedFetch {
156
126
  this.log.trace('created VerifiedFetch instance')
157
127
  }
158
128
 
159
- private getBlockstore (root: CID, key: string, useSession: boolean, options?: AbortOptions): Blockstore {
129
+ private getBlockstore (root: CID, resource: string | CID, useSession: boolean, options?: AbortOptions): Blockstore {
130
+ const key = resourceToSessionCacheKey(resource)
160
131
  if (!useSession) {
161
132
  return this.helia.blockstore
162
133
  }
@@ -211,8 +182,8 @@ export class VerifiedFetch {
211
182
  * Accepts a `CID` and returns a `Response` with a body stream that is a CAR
212
183
  * of the `DAG` referenced by the `CID`.
213
184
  */
214
- private async handleCar ({ resource, cid, session, cacheKey, options }: FetchHandlerFunctionArg): Promise<Response> {
215
- const blockstore = this.getBlockstore(cid, cacheKey, session, options)
185
+ private async handleCar ({ resource, cid, session, options }: FetchHandlerFunctionArg): Promise<Response> {
186
+ const blockstore = this.getBlockstore(cid, resource, session, options)
216
187
  const c = car({ blockstore, dagWalkers: this.helia.dagWalkers })
217
188
  const stream = toBrowserReadableStream(c.stream(cid, options))
218
189
 
@@ -226,12 +197,12 @@ export class VerifiedFetch {
226
197
  * Accepts a UnixFS `CID` and returns a `.tar` file containing the file or
227
198
  * directory structure referenced by the `CID`.
228
199
  */
229
- private async handleTar ({ resource, cid, path, session, cacheKey, options }: FetchHandlerFunctionArg): Promise<Response> {
200
+ private async handleTar ({ resource, cid, path, session, options }: FetchHandlerFunctionArg): Promise<Response> {
230
201
  if (cid.code !== dagPbCode && cid.code !== rawCode) {
231
202
  return notAcceptableResponse('only UnixFS data can be returned in a TAR file')
232
203
  }
233
204
 
234
- const blockstore = this.getBlockstore(cid, cacheKey, session, options)
205
+ const blockstore = this.getBlockstore(cid, resource, session, options)
235
206
  const stream = toBrowserReadableStream<Uint8Array>(tarStream(`/ipfs/${cid}/${path}`, blockstore, options))
236
207
 
237
208
  const response = okResponse(resource, stream)
@@ -240,9 +211,9 @@ export class VerifiedFetch {
240
211
  return response
241
212
  }
242
213
 
243
- private async handleJson ({ resource, cid, path, accept, session, cacheKey, options }: FetchHandlerFunctionArg): Promise<Response> {
214
+ private async handleJson ({ resource, cid, path, accept, session, options }: FetchHandlerFunctionArg): Promise<Response> {
244
215
  this.log.trace('fetching %c/%s', cid, path)
245
- const blockstore = this.getBlockstore(cid, cacheKey, session, options)
216
+ const blockstore = this.getBlockstore(cid, resource, session, options)
246
217
  const block = await blockstore.get(cid, options)
247
218
  let body: string | Uint8Array
248
219
 
@@ -267,33 +238,26 @@ export class VerifiedFetch {
267
238
  return response
268
239
  }
269
240
 
270
- private async handleDagCbor ({ resource, cid, path, accept, session, cacheKey, options }: FetchHandlerFunctionArg): Promise<Response> {
241
+ private async handleDagCbor ({ resource, cid, path, accept, session, options }: FetchHandlerFunctionArg): Promise<Response> {
271
242
  this.log.trace('fetching %c/%s', cid, path)
272
- let terminalElement: ObjectNode | undefined
273
- let ipfsRoots: CID[] | undefined
274
- const blockstore = this.getBlockstore(cid, cacheKey, session, options)
243
+ let terminalElement: ObjectNode
244
+ const blockstore = this.getBlockstore(cid, resource, session, options)
275
245
 
276
246
  // need to walk path, if it exists, to get the terminal element
277
- try {
278
- const pathDetails = await walkPath(blockstore, `${cid.toString()}/${path}`, options)
279
- ipfsRoots = pathDetails.ipfsRoots
280
- const potentialTerminalElement = pathDetails.terminalElement
281
- if (potentialTerminalElement == null) {
282
- return notFoundResponse(resource)
283
- }
284
- if (isObjectNode(potentialTerminalElement)) {
285
- terminalElement = potentialTerminalElement
286
- }
287
- } catch (err: any) {
288
- options?.signal?.throwIfAborted()
289
- if (['ERR_NO_PROP', 'ERR_NO_TERMINAL_ELEMENT'].includes(err.code)) {
290
- return notFoundResponse(resource)
291
- }
292
-
293
- this.log.error('error walking path %s', path, err)
294
- return badGatewayResponse(resource, 'Error walking path')
247
+ const pathDetails = await handlePathWalking({ cid, path, resource, options, blockstore, log: this.log })
248
+ if (pathDetails instanceof Response) {
249
+ return pathDetails
250
+ }
251
+ const ipfsRoots = pathDetails.ipfsRoots
252
+ if (isObjectNode(pathDetails.terminalElement)) {
253
+ terminalElement = pathDetails.terminalElement
254
+ } else {
255
+ // this should never happen, but if it does, we should log it and return notSupportedResponse
256
+ this.log.error('terminal element is not a dag-cbor node')
257
+ return notSupportedResponse(resource, 'Terminal element is not a dag-cbor node')
295
258
  }
296
- const block = terminalElement?.node ?? await blockstore.get(cid, options)
259
+
260
+ const block = terminalElement.node
297
261
 
298
262
  let body: string | Uint8Array
299
263
 
@@ -333,36 +297,23 @@ export class VerifiedFetch {
333
297
  }
334
298
 
335
299
  response.headers.set('content-type', accept)
336
-
337
- if (ipfsRoots != null) {
338
- response.headers.set('X-Ipfs-Roots', ipfsRoots.map(cid => cid.toV1().toString()).join(',')) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-roots-response-header
339
- }
300
+ setIpfsRoots(response, ipfsRoots)
340
301
 
341
302
  return response
342
303
  }
343
304
 
344
- private async handleDagPb ({ cid, path, resource, cacheKey, session, options }: FetchHandlerFunctionArg): Promise<Response> {
345
- let terminalElement: UnixFSEntry | undefined
346
- let ipfsRoots: CID[] | undefined
305
+ private async handleDagPb ({ cid, path, resource, session, options }: FetchHandlerFunctionArg): Promise<Response> {
347
306
  let redirected = false
348
307
  const byteRangeContext = new ByteRangeContext(this.helia.logger, options?.headers)
349
- const blockstore = this.getBlockstore(cid, cacheKey, session, options)
350
-
351
- try {
352
- const pathDetails = await walkPath(blockstore, `${cid.toString()}/${path}`, options)
353
- ipfsRoots = pathDetails.ipfsRoots
354
- terminalElement = pathDetails.terminalElement
355
- } catch (err: any) {
356
- options?.signal?.throwIfAborted()
357
- if (['ERR_NO_PROP', 'ERR_NO_TERMINAL_ELEMENT', 'ERR_NOT_FOUND'].includes(err.code)) {
358
- return notFoundResponse(resource.toString())
359
- }
360
- this.log.error('error walking path %s', path, err)
361
-
362
- return badGatewayResponse(resource.toString(), 'Error walking path')
308
+ const blockstore = this.getBlockstore(cid, resource, session, options)
309
+ const pathDetails = await handlePathWalking({ cid, path, resource, options, blockstore, log: this.log })
310
+ if (pathDetails instanceof Response) {
311
+ return pathDetails
363
312
  }
313
+ const ipfsRoots = pathDetails.ipfsRoots
314
+ const terminalElement = pathDetails.terminalElement
315
+ let resolvedCID = terminalElement.cid
364
316
 
365
- let resolvedCID = terminalElement?.cid ?? cid
366
317
  if (terminalElement?.type === 'directory') {
367
318
  const dirCid = terminalElement.cid
368
319
  const redirectCheckNeeded = path === '' ? !resource.toString().endsWith('/') : !path.endsWith('/')
@@ -438,10 +389,8 @@ export class VerifiedFetch {
438
389
  })
439
390
 
440
391
  await this.setContentType(firstChunk, path, response)
392
+ setIpfsRoots(response, ipfsRoots)
441
393
 
442
- if (ipfsRoots != null) {
443
- response.headers.set('X-Ipfs-Roots', ipfsRoots.map(cid => cid.toV1().toString()).join(',')) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-roots-response-header
444
- }
445
394
  return response
446
395
  } catch (err: any) {
447
396
  options?.signal?.throwIfAborted()
@@ -453,9 +402,9 @@ export class VerifiedFetch {
453
402
  }
454
403
  }
455
404
 
456
- private async handleRaw ({ resource, cid, path, session, cacheKey, options, accept }: FetchHandlerFunctionArg): Promise<Response> {
405
+ private async handleRaw ({ resource, cid, path, session, options, accept }: FetchHandlerFunctionArg): Promise<Response> {
457
406
  const byteRangeContext = new ByteRangeContext(this.helia.logger, options?.headers)
458
- const blockstore = this.getBlockstore(cid, cacheKey, session, options)
407
+ const blockstore = this.getBlockstore(cid, resource, session, options)
459
408
  const result = await blockstore.get(cid, options)
460
409
  byteRangeContext.setBody(result)
461
410
  const response = okRangeResponse(resource, byteRangeContext.getBody(), { byteRangeContext, log: this.log }, {
@@ -559,8 +508,7 @@ export class VerifiedFetch {
559
508
  let response: Response
560
509
  let reqFormat: RequestFormatShorthand | undefined
561
510
 
562
- const cacheKey = resourceToSessionCacheKey(resource)
563
- const handlerArgs: FetchHandlerFunctionArg = { resource: resource.toString(), cid, path, accept, cacheKey, session: options?.session ?? true, options }
511
+ const handlerArgs: FetchHandlerFunctionArg = { resource: resource.toString(), cid, path, accept, session: options?.session ?? true, options }
564
512
 
565
513
  if (accept === 'application/vnd.ipfs.ipns-record') {
566
514
  // the user requested a raw IPNS record