@helia/verified-fetch 2.2.2 → 2.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/README.md +27 -1
- package/dist/index.min.js +35 -32
- package/dist/src/index.d.ts +34 -14
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +28 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/types.d.ts +13 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/set-content-type.d.ts +12 -0
- package/dist/src/utils/set-content-type.d.ts.map +1 -0
- package/dist/src/utils/set-content-type.js +28 -0
- package/dist/src/utils/set-content-type.js.map +1 -0
- package/dist/src/utils/type-guards.d.ts +2 -0
- package/dist/src/utils/type-guards.d.ts.map +1 -0
- package/dist/src/utils/type-guards.js +4 -0
- package/dist/src/utils/type-guards.js.map +1 -0
- package/dist/src/utils/walk-path.d.ts +0 -1
- package/dist/src/utils/walk-path.d.ts.map +1 -1
- package/dist/src/utils/walk-path.js +1 -1
- package/dist/src/utils/walk-path.js.map +1 -1
- package/dist/src/verified-fetch.d.ts +0 -1
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +3 -29
- package/dist/src/verified-fetch.js.map +1 -1
- package/dist/typedoc-urls.json +24 -25
- package/package.json +4 -3
- package/src/index.ts +37 -17
- package/src/types.ts +14 -0
- package/src/utils/set-content-type.ts +38 -0
- package/src/utils/type-guards.ts +3 -0
- package/src/utils/walk-path.ts +1 -1
- package/src/verified-fetch.ts +4 -34
package/src/index.ts
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* All content is retrieved in a [trustless manner](https://www.techopedia.com/definition/trustless), and the integrity of all bytes are verified by comparing hashes of the data.
|
|
7
7
|
*
|
|
8
|
-
* By default, providers for CIDs are found
|
|
8
|
+
* By default, providers for CIDs are found using [delegated routing endpoints](https://docs.ipfs.tech/concepts/public-utilities/#delegated-routing).
|
|
9
|
+
*
|
|
10
|
+
* Data is retrieved using the following strategies:
|
|
11
|
+
* - Directly from providers, using [Bitswap](https://docs.ipfs.tech/concepts/bitswap/) over WebSockets and WebRTC if available.
|
|
12
|
+
* - Directly from providers exposing a [trustless gateway](https://specs.ipfs.tech/http-gateways/trustless-gateway/) over HTTPS.
|
|
13
|
+
* - As a fallback, if no providers reachable from a browser are found, data is retrieved using recursive gateways, e.g. `trustless-gateway.link` which can be configured.
|
|
9
14
|
*
|
|
10
15
|
* This is a marked improvement over `fetch` which offers no such protections and is vulnerable to all sorts of attacks like [Content Spoofing](https://owasp.org/www-community/attacks/Content_Spoofing), [DNS Hijacking](https://en.wikipedia.org/wiki/DNS_hijacking), etc.
|
|
11
16
|
*
|
|
@@ -189,6 +194,25 @@
|
|
|
189
194
|
* }
|
|
190
195
|
* })
|
|
191
196
|
* ```
|
|
197
|
+
* ### Custom Hashers
|
|
198
|
+
*
|
|
199
|
+
* By default, `@helia/verified-fetch` supports `sha256`, `sha512`, and `identity` hashers.
|
|
200
|
+
*
|
|
201
|
+
* If you need to use a different hasher, you can provide a [custom `hasher` function](https://multiformats.github.io/js-multiformats/interfaces/hashes_interface.MultihashHasher.html) as an option to `createVerifiedFetch`.
|
|
202
|
+
*
|
|
203
|
+
* @example Passing a custom hashing function
|
|
204
|
+
*
|
|
205
|
+
* ```typescript
|
|
206
|
+
* import { createVerifiedFetch } from '@helia/verified-fetch'
|
|
207
|
+
* import { blake2b256 } from '@multiformats/blake2/blake2b'
|
|
208
|
+
*
|
|
209
|
+
* const verifiedFetch = await createVerifiedFetch({
|
|
210
|
+
* gateways: ['https://ipfs.io'],
|
|
211
|
+
* hashers: [blake2b256]
|
|
212
|
+
* })
|
|
213
|
+
*
|
|
214
|
+
* const resp = await verifiedFetch('ipfs://cid-using-blake2b256')
|
|
215
|
+
* ```
|
|
192
216
|
*
|
|
193
217
|
* ### IPLD codec handling
|
|
194
218
|
*
|
|
@@ -602,8 +626,9 @@ import { type ResolveDNSLinkProgressEvents } from '@helia/ipns'
|
|
|
602
626
|
import { httpGatewayRouting, libp2pRouting } from '@helia/routers'
|
|
603
627
|
import { type Libp2p, type ServiceMap } from '@libp2p/interface'
|
|
604
628
|
import { dns } from '@multiformats/dns'
|
|
605
|
-
import { createHelia } from 'helia'
|
|
629
|
+
import { createHelia, type HeliaInit } from 'helia'
|
|
606
630
|
import { createLibp2p, type Libp2pOptions } from 'libp2p'
|
|
631
|
+
import { type ContentTypeParser } from './types.js'
|
|
607
632
|
import { getLibp2pConfig } from './utils/libp2p-defaults.js'
|
|
608
633
|
import { VerifiedFetch as VerifiedFetchClass } from './verified-fetch.js'
|
|
609
634
|
import type { GetBlockProgressEvents, Helia, Routing } from '@helia/interface'
|
|
@@ -612,7 +637,6 @@ import type { DNSResolver } from '@multiformats/dns/resolvers'
|
|
|
612
637
|
import type { ExporterProgressEvents } from 'ipfs-unixfs-exporter'
|
|
613
638
|
import type { CID } from 'multiformats/cid'
|
|
614
639
|
import type { ProgressEvent, ProgressOptions } from 'progress-events'
|
|
615
|
-
|
|
616
640
|
/**
|
|
617
641
|
* The types for the first argument of the `verifiedFetch` function.
|
|
618
642
|
*/
|
|
@@ -657,6 +681,13 @@ export interface CreateVerifiedFetchInit {
|
|
|
657
681
|
*/
|
|
658
682
|
dnsResolvers?: DNSResolver[] | DNSResolvers
|
|
659
683
|
|
|
684
|
+
/**
|
|
685
|
+
* By default sha256, sha512 and identity hashes are supported for
|
|
686
|
+
* retrieval operations. To retrieve blocks by CIDs using other hashes
|
|
687
|
+
* pass appropriate MultihashHashers here.
|
|
688
|
+
*/
|
|
689
|
+
hashers?: HeliaInit['hashers']
|
|
690
|
+
|
|
660
691
|
/**
|
|
661
692
|
* By default we will not connect to any HTTP Gateways providers over local or
|
|
662
693
|
* loopback addresses, this is because they are typically running on remote
|
|
@@ -721,19 +752,7 @@ export interface CreateVerifiedFetchOptions {
|
|
|
721
752
|
sessionTTLms?: number
|
|
722
753
|
}
|
|
723
754
|
|
|
724
|
-
|
|
725
|
-
* A ContentTypeParser attempts to return the mime type of a given file. It
|
|
726
|
-
* receives the first chunk of the file data and the file name, if it is
|
|
727
|
-
* available. The function can be sync or async and if it returns/resolves to
|
|
728
|
-
* `undefined`, `application/octet-stream` will be used.
|
|
729
|
-
*/
|
|
730
|
-
export interface ContentTypeParser {
|
|
731
|
-
/**
|
|
732
|
-
* Attempt to determine a mime type, either via of the passed bytes or the
|
|
733
|
-
* filename if it is available.
|
|
734
|
-
*/
|
|
735
|
-
(bytes: Uint8Array, fileName?: string): Promise<string | undefined> | string | undefined
|
|
736
|
-
}
|
|
755
|
+
export type { ContentTypeParser } from './types.js'
|
|
737
756
|
|
|
738
757
|
export type BubbledProgressEvents =
|
|
739
758
|
// unixfs-exporter
|
|
@@ -838,7 +857,8 @@ export async function createVerifiedFetch (init?: Helia | CreateVerifiedFetchIni
|
|
|
838
857
|
libp2p,
|
|
839
858
|
blockBrokers,
|
|
840
859
|
dns,
|
|
841
|
-
routers
|
|
860
|
+
routers,
|
|
861
|
+
hashers: init?.hashers
|
|
842
862
|
})
|
|
843
863
|
init.logger.forComponent('helia:verified-fetch').trace('created verified-fetch with libp2p config: %j', libp2pConfig)
|
|
844
864
|
}
|
package/src/types.ts
CHANGED
|
@@ -30,3 +30,17 @@ export interface FetchHandlerFunctionArg {
|
|
|
30
30
|
*/
|
|
31
31
|
resource: string
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A ContentTypeParser attempts to return the mime type of a given file. It
|
|
36
|
+
* receives the first chunk of the file data and the file name, if it is
|
|
37
|
+
* available. The function can be sync or async and if it returns/resolves to
|
|
38
|
+
* `undefined`, `application/octet-stream` will be used.
|
|
39
|
+
*/
|
|
40
|
+
export interface ContentTypeParser {
|
|
41
|
+
/**
|
|
42
|
+
* Attempt to determine a mime type, either via of the passed bytes or the
|
|
43
|
+
* filename if it is available.
|
|
44
|
+
*/
|
|
45
|
+
(bytes: Uint8Array, fileName?: string): Promise<string | undefined> | string | undefined
|
|
46
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Logger } from '@libp2p/interface'
|
|
2
|
+
import { type ContentTypeParser } from '../types.js'
|
|
3
|
+
import { isPromise } from './type-guards.js'
|
|
4
|
+
|
|
5
|
+
export interface SetContentTypeOptions {
|
|
6
|
+
bytes: Uint8Array
|
|
7
|
+
path: string
|
|
8
|
+
response: Response
|
|
9
|
+
defaultContentType?: string
|
|
10
|
+
contentTypeParser: ContentTypeParser | undefined
|
|
11
|
+
log: Logger
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function setContentType ({ bytes, path, response, contentTypeParser, log, defaultContentType = 'application/octet-stream' }: SetContentTypeOptions): Promise<void> {
|
|
15
|
+
let contentType: string | undefined
|
|
16
|
+
|
|
17
|
+
if (contentTypeParser != null) {
|
|
18
|
+
try {
|
|
19
|
+
let fileName = path.split('/').pop()?.trim()
|
|
20
|
+
fileName = fileName === '' ? undefined : fileName
|
|
21
|
+
const parsed = contentTypeParser(bytes, fileName)
|
|
22
|
+
|
|
23
|
+
if (isPromise(parsed)) {
|
|
24
|
+
const result = await parsed
|
|
25
|
+
|
|
26
|
+
if (result != null) {
|
|
27
|
+
contentType = result
|
|
28
|
+
}
|
|
29
|
+
} else if (parsed != null) {
|
|
30
|
+
contentType = parsed
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
log.error('error parsing content type', err)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
log.trace('setting content type to "%s"', contentType ?? defaultContentType)
|
|
37
|
+
response.headers.set('content-type', contentType ?? defaultContentType)
|
|
38
|
+
}
|
package/src/utils/walk-path.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface PathWalkerFn {
|
|
|
19
19
|
(blockstore: ReadableStorage, path: string, options?: PathWalkerOptions): Promise<PathWalkerResponse>
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
async function walkPath (blockstore: ReadableStorage, path: string, options?: PathWalkerOptions): Promise<PathWalkerResponse> {
|
|
23
23
|
const ipfsRoots: CID[] = []
|
|
24
24
|
let terminalElement: UnixFSEntry | undefined
|
|
25
25
|
|
package/src/verified-fetch.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { code as dagPbCode } from '@ipld/dag-pb'
|
|
|
6
6
|
import { type AbortOptions, type Logger, type PeerId } from '@libp2p/interface'
|
|
7
7
|
import { Record as DHTRecord } from '@libp2p/kad-dht'
|
|
8
8
|
import { Key } from 'interface-datastore'
|
|
9
|
-
import { exporter } from 'ipfs-unixfs-exporter'
|
|
9
|
+
import { exporter, type ObjectNode } from 'ipfs-unixfs-exporter'
|
|
10
10
|
import toBrowserReadableStream from 'it-to-browser-readablestream'
|
|
11
11
|
import { LRUCache } from 'lru-cache'
|
|
12
12
|
import { type CID } from 'multiformats/cid'
|
|
@@ -32,12 +32,12 @@ import { resourceToSessionCacheKey } from './utils/resource-to-cache-key.js'
|
|
|
32
32
|
import { setCacheControlHeader, setIpfsRoots } from './utils/response-headers.js'
|
|
33
33
|
import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse, notFoundResponse } from './utils/responses.js'
|
|
34
34
|
import { selectOutputType } from './utils/select-output-type.js'
|
|
35
|
+
import { setContentType } from './utils/set-content-type.js'
|
|
35
36
|
import { handlePathWalking, isObjectNode } from './utils/walk-path.js'
|
|
36
37
|
import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
|
|
37
38
|
import type { FetchHandlerFunctionArg, RequestFormatShorthand } from './types.js'
|
|
38
39
|
import type { Helia, SessionBlockstore } from '@helia/interface'
|
|
39
40
|
import type { Blockstore } from 'interface-blockstore'
|
|
40
|
-
import type { ObjectNode } from 'ipfs-unixfs-exporter'
|
|
41
41
|
|
|
42
42
|
const SESSION_CACHE_MAX_SIZE = 100
|
|
43
43
|
const SESSION_CACHE_TTL_MS = 60 * 1000
|
|
@@ -398,7 +398,7 @@ export class VerifiedFetch {
|
|
|
398
398
|
redirected
|
|
399
399
|
})
|
|
400
400
|
|
|
401
|
-
await
|
|
401
|
+
await setContentType({ bytes: firstChunk, path, response, contentTypeParser: this.contentTypeParser, log: this.log })
|
|
402
402
|
setIpfsRoots(response, ipfsRoots)
|
|
403
403
|
|
|
404
404
|
return response
|
|
@@ -434,37 +434,11 @@ export class VerifiedFetch {
|
|
|
434
434
|
// if the user has specified an `Accept` header that corresponds to a raw
|
|
435
435
|
// type, honour that header, so for example they don't request
|
|
436
436
|
// `application/vnd.ipld.raw` but get `application/octet-stream`
|
|
437
|
-
await
|
|
437
|
+
await setContentType({ bytes: result, path, response, defaultContentType: getOverridenRawContentType({ headers: options?.headers, accept }), contentTypeParser: this.contentTypeParser, log: this.log })
|
|
438
438
|
|
|
439
439
|
return response
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
-
private async setContentType (bytes: Uint8Array, path: string, response: Response, defaultContentType = 'application/octet-stream'): Promise<void> {
|
|
443
|
-
let contentType: string | undefined
|
|
444
|
-
|
|
445
|
-
if (this.contentTypeParser != null) {
|
|
446
|
-
try {
|
|
447
|
-
let fileName = path.split('/').pop()?.trim()
|
|
448
|
-
fileName = fileName === '' ? undefined : fileName
|
|
449
|
-
const parsed = this.contentTypeParser(bytes, fileName)
|
|
450
|
-
|
|
451
|
-
if (isPromise(parsed)) {
|
|
452
|
-
const result = await parsed
|
|
453
|
-
|
|
454
|
-
if (result != null) {
|
|
455
|
-
contentType = result
|
|
456
|
-
}
|
|
457
|
-
} else if (parsed != null) {
|
|
458
|
-
contentType = parsed
|
|
459
|
-
}
|
|
460
|
-
} catch (err) {
|
|
461
|
-
this.log.error('error parsing content type', err)
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
this.log.trace('setting content type to "%s"', contentType ?? defaultContentType)
|
|
465
|
-
response.headers.set('content-type', contentType ?? defaultContentType)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
442
|
/**
|
|
469
443
|
* If the user has not specified an Accept header or format query string arg,
|
|
470
444
|
* use the CID codec to choose an appropriate handler for the block data.
|
|
@@ -614,7 +588,3 @@ export class VerifiedFetch {
|
|
|
614
588
|
await this.helia.stop()
|
|
615
589
|
}
|
|
616
590
|
}
|
|
617
|
-
|
|
618
|
-
function isPromise <T> (p?: any): p is Promise<T> {
|
|
619
|
-
return p?.then != null
|
|
620
|
-
}
|