@helia/verified-fetch 2.1.2 → 2.2.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 (36) hide show
  1. package/README.md +5 -3
  2. package/dist/index.min.js +56 -17
  3. package/dist/src/index.d.ts +17 -4
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js +44 -21
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/utils/get-peer-id-from-string.d.ts +3 -0
  8. package/dist/src/utils/get-peer-id-from-string.d.ts.map +1 -0
  9. package/dist/src/utils/get-peer-id-from-string.js +10 -0
  10. package/dist/src/utils/get-peer-id-from-string.js.map +1 -0
  11. package/dist/src/utils/libp2p-defaults.browser.d.ts +3 -0
  12. package/dist/src/utils/libp2p-defaults.browser.d.ts.map +1 -0
  13. package/dist/src/utils/libp2p-defaults.browser.js +21 -0
  14. package/dist/src/utils/libp2p-defaults.browser.js.map +1 -0
  15. package/dist/src/utils/libp2p-defaults.d.ts +3 -0
  16. package/dist/src/utils/libp2p-defaults.d.ts.map +1 -0
  17. package/dist/src/utils/libp2p-defaults.js +31 -0
  18. package/dist/src/utils/libp2p-defaults.js.map +1 -0
  19. package/dist/src/utils/libp2p-types.d.ts +8 -0
  20. package/dist/src/utils/libp2p-types.d.ts.map +1 -0
  21. package/dist/src/utils/libp2p-types.js +2 -0
  22. package/dist/src/utils/libp2p-types.js.map +1 -0
  23. package/dist/src/utils/parse-url-string.d.ts.map +1 -1
  24. package/dist/src/utils/parse-url-string.js +2 -8
  25. package/dist/src/utils/parse-url-string.js.map +1 -1
  26. package/dist/src/verified-fetch.d.ts.map +1 -1
  27. package/dist/src/verified-fetch.js +4 -12
  28. package/dist/src/verified-fetch.js.map +1 -1
  29. package/package.json +27 -21
  30. package/src/index.ts +59 -23
  31. package/src/utils/get-peer-id-from-string.ts +12 -0
  32. package/src/utils/libp2p-defaults.browser.ts +28 -0
  33. package/src/utils/libp2p-defaults.ts +38 -0
  34. package/src/utils/libp2p-types.ts +8 -0
  35. package/src/utils/parse-url-string.ts +2 -7
  36. package/src/verified-fetch.ts +4 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helia/verified-fetch",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "A fetch-like API for obtaining verified & trustless IPFS content on the web",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/ipfs/helia-verified-fetch/tree/main/packages/verified-fetch#readme",
@@ -142,52 +142,55 @@
142
142
  "release": "aegir release"
143
143
  },
144
144
  "dependencies": {
145
- "@helia/block-brokers": "^4.0.0",
146
- "@helia/car": "^4.0.0",
147
- "@helia/http": "^2.0.0",
148
- "@helia/interface": "^5.0.0",
149
- "@helia/ipns": "^8.0.0",
150
- "@helia/routers": "^2.0.0",
151
- "@helia/unixfs": "^4.0.0",
145
+ "@helia/block-brokers": "^4.0.2",
146
+ "@helia/car": "^4.0.1",
147
+ "@helia/delegated-routing-v1-http-api-client": "^4.2.1",
148
+ "@helia/interface": "^5.1.0",
149
+ "@helia/ipns": "^8.0.1",
150
+ "@helia/routers": "^2.2.0",
151
+ "@helia/unixfs": "^4.0.1",
152
152
  "@ipld/dag-cbor": "^9.2.1",
153
153
  "@ipld/dag-json": "^10.2.2",
154
154
  "@ipld/dag-pb": "^4.1.2",
155
- "@libp2p/interface": "^2.1.3",
156
- "@libp2p/kad-dht": "^14.0.1",
157
- "@libp2p/peer-id": "^5.0.5",
155
+ "@libp2p/interface": "^2.2.1",
156
+ "@libp2p/kad-dht": "^14.1.3",
157
+ "@libp2p/peer-id": "^5.0.8",
158
+ "@libp2p/webrtc": "^5.0.16",
159
+ "@libp2p/websockets": "^9.0.11",
158
160
  "@multiformats/dns": "^1.0.6",
159
161
  "cborg": "^4.2.4",
160
162
  "hashlru": "^2.3.0",
163
+ "helia": "^5.1.0",
161
164
  "interface-blockstore": "^5.3.1",
162
165
  "interface-datastore": "^8.3.1",
163
166
  "ipfs-unixfs-exporter": "^13.6.1",
167
+ "ipns": "^10.0.0",
164
168
  "it-map": "^3.1.1",
165
169
  "it-pipe": "^3.0.1",
166
170
  "it-tar": "^6.0.5",
167
171
  "it-to-browser-readablestream": "^2.0.9",
172
+ "libp2p": "^2.2.1",
168
173
  "lru-cache": "^11.0.2",
169
- "multiformats": "^13.3.0",
174
+ "multiformats": "^13.3.1",
170
175
  "progress-events": "^1.0.1",
171
176
  "uint8arrays": "^5.1.0"
172
177
  },
173
178
  "devDependencies": {
174
- "@helia/dag-cbor": "^4.0.0",
175
- "@helia/dag-json": "^4.0.0",
176
- "@helia/json": "^4.0.0",
177
- "@helia/utils": "^1.0.0",
179
+ "@helia/dag-cbor": "^4.0.1",
180
+ "@helia/dag-json": "^4.0.1",
181
+ "@helia/http": "^2.0.1",
182
+ "@helia/json": "^4.0.1",
178
183
  "@ipld/car": "^5.3.2",
179
- "@libp2p/crypto": "^5.0.5",
180
- "@libp2p/interface-compliance-tests": "^6.1.6",
181
- "@libp2p/logger": "^5.1.1",
184
+ "@libp2p/crypto": "^5.0.7",
185
+ "@libp2p/logger": "^5.1.4",
182
186
  "@sgtpooki/file-type": "^1.0.1",
183
187
  "@types/sinon": "^17.0.3",
184
188
  "aegir": "^45.0.1",
185
189
  "blockstore-core": "^5.0.2",
186
190
  "browser-readablestream-to-it": "^2.0.7",
187
191
  "datastore-core": "^10.0.2",
188
- "helia": "^5.0.0",
192
+ "helia": "^5.1.1",
189
193
  "ipfs-unixfs-importer": "^15.3.1",
190
- "ipns": "^10.0.0",
191
194
  "it-all": "^3.0.6",
192
195
  "it-drain": "^3.0.7",
193
196
  "it-last": "^3.0.6",
@@ -197,5 +200,8 @@
197
200
  "sinon": "^18.0.0",
198
201
  "sinon-ts": "^2.0.0"
199
202
  },
203
+ "browser": {
204
+ "./dist/src/utils/libp2p-defaults.js": "./dist/src/utils/libp2p-defaults.browser.js"
205
+ },
200
206
  "sideEffects": false
201
207
  }
package/src/index.ts CHANGED
@@ -3,11 +3,13 @@
3
3
  *
4
4
  * `@helia/verified-fetch` provides a [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)-like API for retrieving content from the [IPFS](https://ipfs.tech/) network.
5
5
  *
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. By default, CIDs are retrieved over HTTP from [trustless gateways](https://specs.ipfs.tech/http-gateways/trustless-gateway/).
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
+ *
8
+ * By default, providers for CIDs are found with delegated routers and retrieved over HTTP from [trustless gateways](https://specs.ipfs.tech/http-gateways/trustless-gateway/), and WebTransport and WebRTC providers if available.
7
9
  *
8
10
  * 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.
9
11
  *
10
- * A `verifiedFetch` function is exported to get up and running quickly, and a `createVerifiedFetch` function is also available that allows customizing the underlying [Helia](https://helia.io/) node for complete control over how content is retrieved.
12
+ * A `verifiedFetch` function is exported to get up and running quickly, and a `createVerifiedFetch` function is also available that allows customizing the underlying [Helia](https://ipfs.github.io/helia/) node for complete control over how content is retrieved.
11
13
  *
12
14
  * Browser-cache-friendly [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects are returned which should be instantly familiar to web developers.
13
15
  *
@@ -90,7 +92,7 @@
90
92
  *
91
93
  * The [helia](https://www.npmjs.com/package/helia) module is configured with a libp2p node that is suited for decentralized applications, alternatively [@helia/http](https://www.npmjs.com/package/@helia/http) is available which uses HTTP gateways for all network operations.
92
94
  *
93
- * You can see variations of Helia and js-libp2p configuration options at <https://helia.io/interfaces/helia.index.HeliaInit.html>.
95
+ * You can see variations of Helia and js-libp2p configuration options at <https://ipfs.github.io/helia/interfaces/helia.HeliaInit.html>.
94
96
  *
95
97
  * ```typescript
96
98
  * import { trustlessGateway } from '@helia/block-brokers'
@@ -594,13 +596,17 @@
594
596
  * 4. `AbortError` - If the content request is aborted due to user aborting provided AbortSignal. Note that this is a `AbortError` from `@libp2p/interface` and not the standard `AbortError` from the Fetch API.
595
597
  */
596
598
 
597
- import { trustlessGateway } from '@helia/block-brokers'
598
- import { createHeliaHTTP } from '@helia/http'
599
- import { delegatedHTTPRouting, httpGatewayRouting } from '@helia/routers'
599
+ import { bitswap, trustlessGateway } from '@helia/block-brokers'
600
+ import { createDelegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client'
601
+ import { type ResolveDNSLinkProgressEvents } from '@helia/ipns'
602
+ import { httpGatewayRouting, libp2pRouting } from '@helia/routers'
603
+ import { type Libp2p, type ServiceMap } from '@libp2p/interface'
600
604
  import { dns } from '@multiformats/dns'
605
+ import { createHelia } from 'helia'
606
+ import { createLibp2p, type Libp2pOptions } from 'libp2p'
607
+ import { getLibp2pConfig } from './utils/libp2p-defaults.js'
601
608
  import { VerifiedFetch as VerifiedFetchClass } from './verified-fetch.js'
602
- import type { GetBlockProgressEvents, Helia } from '@helia/interface'
603
- import type { ResolveDNSLinkProgressEvents } from '@helia/ipns'
609
+ import type { GetBlockProgressEvents, Helia, Routing } from '@helia/interface'
604
610
  import type { DNSResolvers, DNS } from '@multiformats/dns'
605
611
  import type { DNSResolver } from '@multiformats/dns/resolvers'
606
612
  import type { ExporterProgressEvents } from 'ipfs-unixfs-exporter'
@@ -675,6 +681,16 @@ export interface CreateVerifiedFetchInit {
675
681
  * @default false
676
682
  */
677
683
  allowInsecure?: boolean
684
+
685
+ /**
686
+ * We will instantiate a libp2p node for you, but if you want to override the libp2p configuration,
687
+ * you can pass it here.
688
+ *
689
+ * **WARNING**: We use Object.assign to merge the default libp2p configuration from Helia with the one you pass here,
690
+ * which results in a shallow merge. If you need a deep merge, you should do it yourself before passing the
691
+ * configuration here.
692
+ */
693
+ libp2pConfig?: Partial<Libp2pOptions<ServiceMap>>
678
694
  }
679
695
 
680
696
  export interface CreateVerifiedFetchOptions {
@@ -788,21 +804,41 @@ export interface VerifiedFetchInit extends RequestInit, ProgressOptions<BubbledP
788
804
  * Create and return a Helia node
789
805
  */
790
806
  export async function createVerifiedFetch (init?: Helia | CreateVerifiedFetchInit, options?: CreateVerifiedFetchOptions): Promise<VerifiedFetch> {
807
+ let libp2p: Libp2p<any> | undefined
791
808
  if (!isHelia(init)) {
792
- init = await createHeliaHTTP({
793
- blockBrokers: [
794
- trustlessGateway({
795
- allowInsecure: init?.allowInsecure,
796
- allowLocal: init?.allowLocal
797
- })
798
- ],
799
- routers: [
800
- ...(init?.routers ?? ['https://delegated-ipfs.dev']).map((routerUrl) => delegatedHTTPRouting(routerUrl)),
801
- httpGatewayRouting({
802
- gateways: init?.gateways ?? ['https://trustless-gateway.link']
803
- })
804
- ],
805
- dns: createDns(init?.dnsResolvers)
809
+ const dns = createDns(init?.dnsResolvers)
810
+
811
+ const libp2pConfig = getLibp2pConfig()
812
+ libp2pConfig.dns = dns
813
+
814
+ const delegatedRouters = init?.routers ?? ['https://delegated-ipfs.dev']
815
+ for (let index = 0; index < delegatedRouters.length; index++) {
816
+ const routerUrl = delegatedRouters[index]
817
+ libp2pConfig.services[`delegatedRouting${index}`] = () => createDelegatedRoutingV1HttpApiClient(routerUrl)
818
+ }
819
+ // merge any passed options from init.libp2pConfig into libp2pConfig if it exists
820
+ if (init?.libp2pConfig != null) {
821
+ Object.assign(libp2pConfig, init.libp2pConfig)
822
+ }
823
+ libp2p = await createLibp2p(libp2pConfig)
824
+
825
+ const blockBrokers = [
826
+ bitswap()
827
+ ]
828
+ const routers: Array<Partial<Routing>> = [
829
+ libp2pRouting(libp2p)
830
+ ]
831
+ if (init?.gateways == null || init.gateways.length > 0) {
832
+ // if gateways is null, or set to a non-empty array, use trustless gateways.
833
+ blockBrokers.push(trustlessGateway({ allowInsecure: init?.allowInsecure, allowLocal: init?.allowLocal }))
834
+ routers.push(httpGatewayRouting({ gateways: init?.gateways ?? ['https://trustless-gateway.link'] }))
835
+ }
836
+
837
+ init = await createHelia({
838
+ libp2p,
839
+ blockBrokers,
840
+ dns,
841
+ routers
806
842
  })
807
843
  }
808
844
 
@@ -810,8 +846,8 @@ export async function createVerifiedFetch (init?: Helia | CreateVerifiedFetchIni
810
846
  async function verifiedFetch (resource: Resource, options?: VerifiedFetchInit): Promise<Response> {
811
847
  return verifiedFetchInstance.fetch(resource, options)
812
848
  }
813
- verifiedFetch.stop = verifiedFetchInstance.stop.bind(verifiedFetchInstance)
814
849
  verifiedFetch.start = verifiedFetchInstance.start.bind(verifiedFetchInstance)
850
+ verifiedFetch.stop = verifiedFetchInstance.stop.bind(verifiedFetchInstance)
815
851
 
816
852
  return verifiedFetch
817
853
  }
@@ -0,0 +1,12 @@
1
+ import { peerIdFromCID, peerIdFromString } from '@libp2p/peer-id'
2
+ import { CID } from 'multiformats/cid'
3
+ import type { PeerId } from '@libp2p/interface'
4
+
5
+ export function getPeerIdFromString (peerIdString: string): PeerId {
6
+ if (peerIdString.charAt(0) === '1' || peerIdString.charAt(0) === 'Q') {
7
+ return peerIdFromString(peerIdString)
8
+ }
9
+
10
+ // try resolving as a base36 CID
11
+ return peerIdFromCID(CID.parse(peerIdString))
12
+ }
@@ -0,0 +1,28 @@
1
+ import { webRTCDirect } from '@libp2p/webrtc'
2
+ import { webSockets } from '@libp2p/websockets'
3
+ import { libp2pDefaults, type DefaultLibp2pServices } from 'helia'
4
+ import type { ServiceFactoryMap } from './libp2p-types'
5
+ import type { Libp2pOptions } from 'libp2p'
6
+
7
+ type ServiceMap = Pick<DefaultLibp2pServices, 'dcutr' | 'identify' | 'keychain' | 'ping'>
8
+
9
+ export function getLibp2pConfig (): Libp2pOptions & Required<Pick<Libp2pOptions, 'services'>> {
10
+ const libp2pDefaultOptions = libp2pDefaults()
11
+
12
+ libp2pDefaultOptions.start = false
13
+ libp2pDefaultOptions.addresses = { listen: [] }
14
+ libp2pDefaultOptions.transports = [webRTCDirect(), webSockets()]
15
+
16
+ const services: ServiceFactoryMap<ServiceMap> = {
17
+ dcutr: libp2pDefaultOptions.services.dcutr,
18
+ identify: libp2pDefaultOptions.services.identify,
19
+ keychain: libp2pDefaultOptions.services.keychain,
20
+ ping: libp2pDefaultOptions.services.ping
21
+ }
22
+
23
+ return {
24
+ ...libp2pDefaultOptions,
25
+ start: false,
26
+ services
27
+ }
28
+ }
@@ -0,0 +1,38 @@
1
+ import { kadDHT } from '@libp2p/kad-dht'
2
+ import { libp2pDefaults, type DefaultLibp2pServices } from 'helia'
3
+ import { ipnsSelector } from 'ipns/selector'
4
+ import { ipnsValidator } from 'ipns/validator'
5
+ import type { ServiceFactoryMap } from './libp2p-types'
6
+ import type { Libp2pOptions } from 'libp2p'
7
+
8
+ type ServiceMap = Pick<DefaultLibp2pServices, 'autoNAT' | 'dcutr' | 'dht' | 'identify' | 'keychain' | 'ping' | 'upnp'>
9
+
10
+ export function getLibp2pConfig (): Libp2pOptions & Required<Pick<Libp2pOptions, 'services'>> {
11
+ const libp2pDefaultOptions = libp2pDefaults()
12
+
13
+ libp2pDefaultOptions.start = false
14
+
15
+ const services: ServiceFactoryMap<ServiceMap> = {
16
+ autoNAT: libp2pDefaultOptions.services.autoNAT,
17
+ dcutr: libp2pDefaultOptions.services.dcutr,
18
+ dht: kadDHT({
19
+ clientMode: true,
20
+ validators: {
21
+ ipns: ipnsValidator
22
+ },
23
+ selectors: {
24
+ ipns: ipnsSelector
25
+ }
26
+ }),
27
+ identify: libp2pDefaultOptions.services.identify,
28
+ keychain: libp2pDefaultOptions.services.keychain,
29
+ ping: libp2pDefaultOptions.services.ping,
30
+ upnp: libp2pDefaultOptions.services.upnp
31
+ }
32
+
33
+ return {
34
+ ...libp2pDefaultOptions,
35
+ start: false,
36
+ services
37
+ }
38
+ }
@@ -0,0 +1,8 @@
1
+ import type { DelegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client'
2
+ import type { ServiceMap } from '@libp2p/interface'
3
+
4
+ type DelegatedRoutingServices = Record<`delegatedRouting${number}`, ((components?: unknown) => DelegatedRoutingV1HttpApiClient)>
5
+
6
+ export type ServiceFactoryMap<T extends ServiceMap = ServiceMap> = {
7
+ [Property in keyof T]: (components: any & T) => T[Property]
8
+ } & DelegatedRoutingServices
@@ -1,5 +1,5 @@
1
- import { peerIdFromCID, peerIdFromString } from '@libp2p/peer-id'
2
1
  import { CID } from 'multiformats/cid'
2
+ import { getPeerIdFromString } from './get-peer-id-from-string.js'
3
3
  import { TLRU } from './tlru.js'
4
4
  import type { RequestFormatShorthand } from '../types.js'
5
5
  import type { DNSLinkResolveResult, IPNS, IPNSResolveResult, IPNSRoutingEvents, ResolveDNSLinkProgressEvents, ResolveProgressEvents, ResolveResult } from '@helia/ipns'
@@ -178,12 +178,7 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
178
178
  try {
179
179
  // try resolving as an IPNS name
180
180
 
181
- if (cidOrPeerIdOrDnsLink.charAt(0) === '1' || cidOrPeerIdOrDnsLink.charAt(0) === 'Q') {
182
- peerId = peerIdFromString(cidOrPeerIdOrDnsLink)
183
- } else {
184
- // try resolving as a base36 CID
185
- peerId = peerIdFromCID(CID.parse(cidOrPeerIdOrDnsLink))
186
- }
181
+ peerId = getPeerIdFromString(cidOrPeerIdOrDnsLink)
187
182
  if (peerId.publicKey == null) {
188
183
  throw new TypeError('cidOrPeerIdOrDnsLink contains no public key')
189
184
  }
@@ -5,12 +5,11 @@ import * as ipldDagJson from '@ipld/dag-json'
5
5
  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
- import { peerIdFromCID, peerIdFromString } from '@libp2p/peer-id'
9
8
  import { Key } from 'interface-datastore'
10
9
  import { exporter } from 'ipfs-unixfs-exporter'
11
10
  import toBrowserReadableStream from 'it-to-browser-readablestream'
12
11
  import { LRUCache } from 'lru-cache'
13
- import { CID } from 'multiformats/cid'
12
+ import { type CID } from 'multiformats/cid'
14
13
  import { code as jsonCode } from 'multiformats/codecs/json'
15
14
  import { code as rawCode } from 'multiformats/codecs/raw'
16
15
  import { identity } from 'multiformats/hashes/identity'
@@ -22,6 +21,7 @@ import { ByteRangeContext } from './utils/byte-range-context.js'
22
21
  import { dagCborToSafeJSON } from './utils/dag-cbor-to-safe-json.js'
23
22
  import { getContentDispositionFilename } from './utils/get-content-disposition-filename.js'
24
23
  import { getETag } from './utils/get-e-tag.js'
24
+ import { getPeerIdFromString } from './utils/get-peer-id-from-string.js'
25
25
  import { getResolvedAcceptHeader } from './utils/get-resolved-accept-header.js'
26
26
  import { getStreamFromAsyncIterable } from './utils/get-stream-from-async-iterable.js'
27
27
  import { tarStream } from './utils/get-tar-stream.js'
@@ -159,19 +159,11 @@ export class VerifiedFetch {
159
159
  if (resource.startsWith('ipns://')) {
160
160
  const peerIdString = resource.replace('ipns://', '')
161
161
  this.log.trace('trying to parse peer id from "%s"', peerIdString)
162
- peerId = peerIdFromString(peerIdString)
162
+ peerId = getPeerIdFromString(peerIdString)
163
163
  } else {
164
164
  const peerIdString = resource.split('.ipns.')[0].split('://')[1]
165
165
  this.log.trace('trying to parse peer id from "%s"', peerIdString)
166
- let cid: CID
167
- try {
168
- cid = CID.parse(peerIdString)
169
- } catch (err: any) {
170
- this.log.error('could not construct CID from peerId string "%s"', resource, err)
171
- return badRequestResponse(resource, err)
172
- }
173
-
174
- peerId = peerIdFromCID(cid)
166
+ peerId = getPeerIdFromString(peerIdString)
175
167
  }
176
168
  } catch (err: any) {
177
169
  this.log.error('could not parse peer id from IPNS url %s', resource, err)