@helia/verified-fetch 1.2.1 → 1.3.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.
- package/dist/index.min.js +5 -5
- package/dist/src/utils/get-stream-from-async-iterable.d.ts +2 -2
- package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +1 -1
- package/dist/src/utils/get-stream-from-async-iterable.js +6 -0
- package/dist/src/utils/get-stream-from-async-iterable.js.map +1 -1
- package/dist/src/utils/parse-resource.d.ts +3 -4
- package/dist/src/utils/parse-resource.d.ts.map +1 -1
- package/dist/src/utils/parse-resource.js +3 -2
- package/dist/src/utils/parse-resource.js.map +1 -1
- package/dist/src/utils/parse-url-string.d.ts +13 -8
- package/dist/src/utils/parse-url-string.d.ts.map +1 -1
- package/dist/src/utils/parse-url-string.js +62 -10
- package/dist/src/utils/parse-url-string.js.map +1 -1
- package/dist/src/utils/response-headers.d.ts +19 -0
- package/dist/src/utils/response-headers.d.ts.map +1 -1
- package/dist/src/utils/response-headers.js +25 -0
- package/dist/src/utils/response-headers.js.map +1 -1
- package/dist/src/utils/responses.d.ts +4 -1
- package/dist/src/utils/responses.d.ts.map +1 -1
- package/dist/src/utils/responses.js +6 -0
- package/dist/src/utils/responses.js.map +1 -1
- package/dist/src/verified-fetch.d.ts +12 -0
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +49 -6
- package/dist/src/verified-fetch.js.map +1 -1
- package/package.json +1 -1
- package/src/utils/get-stream-from-async-iterable.ts +7 -2
- package/src/utils/parse-resource.ts +7 -7
- package/src/utils/parse-url-string.ts +88 -21
- package/src/utils/response-headers.ts +36 -0
- package/src/utils/responses.ts +7 -1
- package/src/verified-fetch.ts +54 -9
package/src/verified-fetch.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { unixfs as heliaUnixFs, type UnixFS as HeliaUnixFs } from '@helia/unixfs
|
|
|
4
4
|
import * as ipldDagCbor from '@ipld/dag-cbor'
|
|
5
5
|
import * as ipldDagJson from '@ipld/dag-json'
|
|
6
6
|
import { code as dagPbCode } from '@ipld/dag-pb'
|
|
7
|
+
import { AbortError, type AbortOptions, type Logger, type PeerId } from '@libp2p/interface'
|
|
7
8
|
import { Record as DHTRecord } from '@libp2p/kad-dht'
|
|
8
9
|
import { peerIdFromString } from '@libp2p/peer-id'
|
|
9
10
|
import { Key } from 'interface-datastore'
|
|
@@ -22,6 +23,7 @@ import { getETag } from './utils/get-e-tag.js'
|
|
|
22
23
|
import { getStreamFromAsyncIterable } from './utils/get-stream-from-async-iterable.js'
|
|
23
24
|
import { tarStream } from './utils/get-tar-stream.js'
|
|
24
25
|
import { parseResource } from './utils/parse-resource.js'
|
|
26
|
+
import { setCacheControlHeader } from './utils/response-headers.js'
|
|
25
27
|
import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse } from './utils/responses.js'
|
|
26
28
|
import { selectOutputType, queryFormatToAcceptHeader } from './utils/select-output-type.js'
|
|
27
29
|
import { walkPath } from './utils/walk-path.js'
|
|
@@ -29,7 +31,6 @@ import type { CIDDetail, ContentTypeParser, Resource, VerifiedFetchInit as Verif
|
|
|
29
31
|
import type { RequestFormatShorthand } from './types.js'
|
|
30
32
|
import type { ParsedUrlStringResults } from './utils/parse-url-string'
|
|
31
33
|
import type { Helia } from '@helia/interface'
|
|
32
|
-
import type { AbortOptions, Logger, PeerId } from '@libp2p/interface'
|
|
33
34
|
import type { DNSResolver } from '@multiformats/dns/resolvers'
|
|
34
35
|
import type { UnixFSEntry } from 'ipfs-unixfs-exporter'
|
|
35
36
|
import type { CID } from 'multiformats/cid'
|
|
@@ -77,7 +78,10 @@ function convertOptions (options?: VerifiedFetchOptions): (Omit<VerifiedFetchOpt
|
|
|
77
78
|
let signal: AbortSignal | undefined
|
|
78
79
|
if (options?.signal === null) {
|
|
79
80
|
signal = undefined
|
|
81
|
+
} else {
|
|
82
|
+
signal = options?.signal
|
|
80
83
|
}
|
|
84
|
+
|
|
81
85
|
return {
|
|
82
86
|
...options,
|
|
83
87
|
signal
|
|
@@ -283,10 +287,13 @@ export class VerifiedFetch {
|
|
|
283
287
|
const pathDetails = await walkPath(this.helia.blockstore, `${cid.toString()}/${path}`, options)
|
|
284
288
|
ipfsRoots = pathDetails.ipfsRoots
|
|
285
289
|
terminalElement = pathDetails.terminalElement
|
|
286
|
-
} catch (err) {
|
|
290
|
+
} catch (err: any) {
|
|
291
|
+
if (options?.signal?.aborted === true) {
|
|
292
|
+
throw new AbortError('signal aborted by user')
|
|
293
|
+
}
|
|
287
294
|
this.log.error('error walking path %s', path, err)
|
|
288
295
|
|
|
289
|
-
return badGatewayResponse('Error walking path')
|
|
296
|
+
return badGatewayResponse(resource.toString(), 'Error walking path')
|
|
290
297
|
}
|
|
291
298
|
|
|
292
299
|
let resolvedCID = terminalElement?.cid ?? cid
|
|
@@ -320,6 +327,9 @@ export class VerifiedFetch {
|
|
|
320
327
|
path = rootFilePath
|
|
321
328
|
resolvedCID = stat.cid
|
|
322
329
|
} catch (err: any) {
|
|
330
|
+
if (options?.signal?.aborted === true) {
|
|
331
|
+
throw new AbortError('signal aborted by user')
|
|
332
|
+
}
|
|
323
333
|
this.log('error loading path %c/%s', dirCid, rootFilePath, err)
|
|
324
334
|
return notSupportedResponse('Unable to find index.html for directory at given path. Support for directories with implicit root is not implemented')
|
|
325
335
|
} finally {
|
|
@@ -346,7 +356,8 @@ export class VerifiedFetch {
|
|
|
346
356
|
|
|
347
357
|
try {
|
|
348
358
|
const { stream, firstChunk } = await getStreamFromAsyncIterable(asyncIter, path ?? '', this.helia.logger, {
|
|
349
|
-
onProgress: options?.onProgress
|
|
359
|
+
onProgress: options?.onProgress,
|
|
360
|
+
signal: options?.signal
|
|
350
361
|
})
|
|
351
362
|
byteRangeContext.setBody(stream)
|
|
352
363
|
// if not a valid range request, okRangeRequest will call okResponse
|
|
@@ -362,11 +373,14 @@ export class VerifiedFetch {
|
|
|
362
373
|
|
|
363
374
|
return response
|
|
364
375
|
} catch (err: any) {
|
|
376
|
+
if (options?.signal?.aborted === true) {
|
|
377
|
+
throw new AbortError('signal aborted by user')
|
|
378
|
+
}
|
|
365
379
|
this.log.error('error streaming %c/%s', cid, path, err)
|
|
366
380
|
if (byteRangeContext.isRangeRequest && err.code === 'ERR_INVALID_PARAMS') {
|
|
367
381
|
return badRangeResponse(resource)
|
|
368
382
|
}
|
|
369
|
-
return badGatewayResponse('Unable to stream content')
|
|
383
|
+
return badGatewayResponse(resource.toString(), 'Unable to stream content')
|
|
370
384
|
}
|
|
371
385
|
}
|
|
372
386
|
|
|
@@ -430,26 +444,56 @@ export class VerifiedFetch {
|
|
|
430
444
|
[identity.code]: this.handleRaw
|
|
431
445
|
}
|
|
432
446
|
|
|
447
|
+
/**
|
|
448
|
+
*
|
|
449
|
+
* TODO: Should we use 400, 408, 418, or 425, or throw and not even return a response?
|
|
450
|
+
*/
|
|
451
|
+
private async abortHandler (opController: AbortController): Promise<void> {
|
|
452
|
+
this.log.error('signal aborted by user')
|
|
453
|
+
opController.abort('signal aborted by user')
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* We're starting to get to the point where we need a queue or pipeline of
|
|
458
|
+
* operations to perform and a single place to handle errors.
|
|
459
|
+
*
|
|
460
|
+
* TODO: move operations called by fetch to a queue of operations where we can
|
|
461
|
+
* always exit early (and cleanly) if a given signal is aborted
|
|
462
|
+
*/
|
|
463
|
+
// eslint-disable-next-line complexity
|
|
433
464
|
async fetch (resource: Resource, opts?: VerifiedFetchOptions): Promise<Response> {
|
|
434
465
|
this.log('fetch %s', resource)
|
|
435
466
|
|
|
436
467
|
const options = convertOptions(opts)
|
|
437
468
|
|
|
469
|
+
const opController = new AbortController()
|
|
470
|
+
if (options?.signal != null) {
|
|
471
|
+
options.signal.onabort = this.abortHandler.bind(this, opController)
|
|
472
|
+
options.signal = opController.signal
|
|
473
|
+
}
|
|
474
|
+
|
|
438
475
|
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { resource }))
|
|
439
476
|
|
|
440
477
|
// resolve the CID/path from the requested resource
|
|
441
478
|
let cid: ParsedUrlStringResults['cid']
|
|
442
479
|
let path: ParsedUrlStringResults['path']
|
|
443
480
|
let query: ParsedUrlStringResults['query']
|
|
481
|
+
let ttl: ParsedUrlStringResults['ttl']
|
|
482
|
+
let protocol: ParsedUrlStringResults['protocol']
|
|
444
483
|
try {
|
|
445
484
|
const result = await parseResource(resource, { ipns: this.ipns, logger: this.helia.logger }, options)
|
|
446
485
|
cid = result.cid
|
|
447
486
|
path = result.path
|
|
448
487
|
query = result.query
|
|
449
|
-
|
|
488
|
+
ttl = result.ttl
|
|
489
|
+
protocol = result.protocol
|
|
490
|
+
} catch (err: any) {
|
|
491
|
+
if (options?.signal?.aborted === true) {
|
|
492
|
+
throw new AbortError('signal aborted by user')
|
|
493
|
+
}
|
|
450
494
|
this.log.error('error parsing resource %s', resource, err)
|
|
451
495
|
|
|
452
|
-
return badRequestResponse(
|
|
496
|
+
return badRequestResponse(resource.toString(), err)
|
|
453
497
|
}
|
|
454
498
|
|
|
455
499
|
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', { cid, path }))
|
|
@@ -508,7 +552,7 @@ export class VerifiedFetch {
|
|
|
508
552
|
const codecHandler = this.codecHandlers[cid.code]
|
|
509
553
|
|
|
510
554
|
if (codecHandler == null) {
|
|
511
|
-
return notSupportedResponse(`Support for codec with code ${cid.code} is not yet implemented. Please open an issue at https://github.com/ipfs/helia/issues/new`)
|
|
555
|
+
return notSupportedResponse(`Support for codec with code ${cid.code} is not yet implemented. Please open an issue at https://github.com/ipfs/helia-verified-fetch/issues/new`)
|
|
512
556
|
}
|
|
513
557
|
this.log.trace('calling handler "%s"', codecHandler.name)
|
|
514
558
|
|
|
@@ -516,7 +560,8 @@ export class VerifiedFetch {
|
|
|
516
560
|
}
|
|
517
561
|
|
|
518
562
|
response.headers.set('etag', getETag({ cid, reqFormat, weak: false }))
|
|
519
|
-
|
|
563
|
+
|
|
564
|
+
setCacheControlHeader({ response, ttl, protocol })
|
|
520
565
|
// https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-path-response-header
|
|
521
566
|
response.headers.set('X-Ipfs-Path', resource.toString())
|
|
522
567
|
|