@helia/verified-fetch 1.5.0 → 2.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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/index.min.js +17 -18
  3. package/dist/src/errors.d.ts +13 -0
  4. package/dist/src/errors.d.ts.map +1 -0
  5. package/dist/src/errors.js +22 -0
  6. package/dist/src/errors.js.map +1 -0
  7. package/dist/src/index.d.ts +1 -1
  8. package/dist/src/index.js +1 -1
  9. package/dist/src/utils/byte-range-context.d.ts.map +1 -1
  10. package/dist/src/utils/byte-range-context.js +3 -2
  11. package/dist/src/utils/byte-range-context.js.map +1 -1
  12. package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +1 -1
  13. package/dist/src/utils/get-stream-from-async-iterable.js +2 -1
  14. package/dist/src/utils/get-stream-from-async-iterable.js.map +1 -1
  15. package/dist/src/utils/get-tar-stream.js +3 -3
  16. package/dist/src/utils/get-tar-stream.js.map +1 -1
  17. package/dist/src/utils/handle-redirects.d.ts.map +1 -1
  18. package/dist/src/utils/handle-redirects.js +2 -1
  19. package/dist/src/utils/handle-redirects.js.map +1 -1
  20. package/dist/src/utils/parse-url-string.d.ts.map +1 -1
  21. package/dist/src/utils/parse-url-string.js +5 -2
  22. package/dist/src/utils/parse-url-string.js.map +1 -1
  23. package/dist/src/utils/request-headers.d.ts.map +1 -1
  24. package/dist/src/utils/request-headers.js +5 -4
  25. package/dist/src/utils/request-headers.js.map +1 -1
  26. package/dist/src/utils/response-headers.d.ts.map +1 -1
  27. package/dist/src/utils/response-headers.js +3 -2
  28. package/dist/src/utils/response-headers.js.map +1 -1
  29. package/dist/src/utils/select-output-type.js +1 -1
  30. package/dist/src/utils/select-output-type.js.map +1 -1
  31. package/dist/src/utils/walk-path.d.ts.map +1 -1
  32. package/dist/src/utils/walk-path.js +3 -2
  33. package/dist/src/utils/walk-path.js.map +1 -1
  34. package/dist/src/verified-fetch.d.ts.map +1 -1
  35. package/dist/src/verified-fetch.js +2 -2
  36. package/dist/src/verified-fetch.js.map +1 -1
  37. package/package.json +120 -35
  38. package/src/errors.ts +26 -0
  39. package/src/index.ts +1 -1
  40. package/src/utils/byte-range-context.ts +3 -2
  41. package/src/utils/get-stream-from-async-iterable.ts +2 -1
  42. package/src/utils/get-tar-stream.ts +3 -3
  43. package/src/utils/handle-redirects.ts +2 -1
  44. package/src/utils/parse-url-string.ts +6 -3
  45. package/src/utils/request-headers.ts +6 -4
  46. package/src/utils/response-headers.ts +3 -2
  47. package/src/utils/select-output-type.ts +1 -1
  48. package/src/utils/walk-path.ts +3 -2
  49. package/src/verified-fetch.ts +4 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helia/verified-fetch",
3
- "version": "1.5.0",
3
+ "version": "2.0.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",
@@ -41,6 +41,91 @@
41
41
  "sourceType": "module"
42
42
  }
43
43
  },
44
+ "release": {
45
+ "branches": [
46
+ "main"
47
+ ],
48
+ "plugins": [
49
+ [
50
+ "@semantic-release/commit-analyzer",
51
+ {
52
+ "preset": "conventionalcommits",
53
+ "releaseRules": [
54
+ {
55
+ "breaking": true,
56
+ "release": "major"
57
+ },
58
+ {
59
+ "revert": true,
60
+ "release": "patch"
61
+ },
62
+ {
63
+ "type": "feat",
64
+ "release": "minor"
65
+ },
66
+ {
67
+ "type": "fix",
68
+ "release": "patch"
69
+ },
70
+ {
71
+ "type": "docs",
72
+ "release": "patch"
73
+ },
74
+ {
75
+ "type": "test",
76
+ "release": "patch"
77
+ },
78
+ {
79
+ "type": "deps",
80
+ "release": "patch"
81
+ },
82
+ {
83
+ "scope": "no-release",
84
+ "release": false
85
+ }
86
+ ]
87
+ }
88
+ ],
89
+ [
90
+ "@semantic-release/release-notes-generator",
91
+ {
92
+ "preset": "conventionalcommits",
93
+ "presetConfig": {
94
+ "types": [
95
+ {
96
+ "type": "feat",
97
+ "section": "Features"
98
+ },
99
+ {
100
+ "type": "fix",
101
+ "section": "Bug Fixes"
102
+ },
103
+ {
104
+ "type": "chore",
105
+ "section": "Trivial Changes"
106
+ },
107
+ {
108
+ "type": "docs",
109
+ "section": "Documentation"
110
+ },
111
+ {
112
+ "type": "deps",
113
+ "section": "Dependencies"
114
+ },
115
+ {
116
+ "type": "test",
117
+ "section": "Tests"
118
+ }
119
+ ]
120
+ }
121
+ }
122
+ ],
123
+ "@semantic-release/changelog",
124
+ "@semantic-release/npm",
125
+ "@semantic-release/github",
126
+ "@semantic-release/git"
127
+ ]
128
+ },
44
129
  "scripts": {
45
130
  "clean": "aegir clean",
46
131
  "lint": "aegir lint",
@@ -57,52 +142,52 @@
57
142
  "release": "aegir release"
58
143
  },
59
144
  "dependencies": {
60
- "@helia/block-brokers": "^3.0.1",
61
- "@helia/car": "^3.1.5",
62
- "@helia/http": "^1.0.8",
63
- "@helia/interface": "^4.3.0",
64
- "@helia/ipns": "^7.2.2",
65
- "@helia/routers": "^1.1.0",
66
- "@ipld/dag-cbor": "^9.2.0",
67
- "@ipld/dag-json": "^10.2.0",
68
- "@ipld/dag-pb": "^4.1.0",
69
- "@libp2p/interface": "^1.4.0",
70
- "@libp2p/kad-dht": "^12.0.17",
71
- "@libp2p/peer-id": "^4.1.2",
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",
152
+ "@ipld/dag-cbor": "^9.2.1",
153
+ "@ipld/dag-json": "^10.2.2",
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",
72
158
  "@multiformats/dns": "^1.0.6",
73
- "cborg": "^4.2.0",
159
+ "cborg": "^4.2.4",
74
160
  "hashlru": "^2.3.0",
75
- "interface-blockstore": "^5.2.10",
76
- "interface-datastore": "^8.2.11",
77
- "ipfs-unixfs-exporter": "^13.5.0",
78
- "it-map": "^3.1.0",
161
+ "interface-blockstore": "^5.3.1",
162
+ "interface-datastore": "^8.3.1",
163
+ "ipfs-unixfs-exporter": "^13.6.1",
164
+ "it-map": "^3.1.1",
79
165
  "it-pipe": "^3.0.1",
80
166
  "it-tar": "^6.0.5",
81
167
  "it-to-browser-readablestream": "^2.0.9",
82
168
  "lru-cache": "^10.2.2",
83
- "multiformats": "^13.1.0",
84
- "progress-events": "^1.0.0",
169
+ "multiformats": "^13.3.0",
170
+ "progress-events": "^1.0.1",
85
171
  "uint8arrays": "^5.1.0"
86
172
  },
87
173
  "devDependencies": {
88
- "@helia/dag-cbor": "^3.0.4",
89
- "@helia/dag-json": "^3.0.4",
90
- "@helia/json": "^3.0.4",
91
- "@helia/unixfs": "^3.0.6",
92
- "@helia/utils": "^0.3.1",
93
- "@ipld/car": "^5.3.0",
94
- "@libp2p/interface-compliance-tests": "^5.4.5",
95
- "@libp2p/logger": "^4.0.13",
96
- "@libp2p/peer-id-factory": "^4.1.2",
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",
178
+ "@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",
97
182
  "@sgtpooki/file-type": "^1.0.1",
98
183
  "@types/sinon": "^17.0.3",
99
- "aegir": "^42.2.11",
100
- "blockstore-core": "^4.4.1",
184
+ "aegir": "^44.1.4",
185
+ "blockstore-core": "^5.0.2",
101
186
  "browser-readablestream-to-it": "^2.0.7",
102
- "datastore-core": "^9.2.9",
103
- "helia": "^4.2.2",
104
- "ipfs-unixfs-importer": "^15.2.5",
105
- "ipns": "^9.1.0",
187
+ "datastore-core": "^10.0.2",
188
+ "helia": "^5.0.0",
189
+ "ipfs-unixfs-importer": "^15.3.1",
190
+ "ipns": "^10.0.0",
106
191
  "it-all": "^3.0.6",
107
192
  "it-drain": "^3.0.7",
108
193
  "it-last": "^3.0.6",
package/src/errors.ts ADDED
@@ -0,0 +1,26 @@
1
+ export class InvalidRangeError extends Error {
2
+ static name = 'InvalidRangeError'
3
+
4
+ constructor (message = 'Invalid range request') {
5
+ super(message)
6
+ this.name = 'InvalidRangeError'
7
+ }
8
+ }
9
+
10
+ export class NoContentError extends Error {
11
+ static name = 'NoContentError'
12
+
13
+ constructor (message = 'No content found') {
14
+ super(message)
15
+ this.name = 'NoContentError'
16
+ }
17
+ }
18
+
19
+ export class SubdomainNotSupportedError extends Error {
20
+ static name = 'SubdomainNotSupportedError'
21
+
22
+ constructor (message = 'Subdomain not supported') {
23
+ super(message)
24
+ this.name = 'SubdomainNotSupportedError'
25
+ }
26
+ }
package/src/index.ts CHANGED
@@ -493,7 +493,7 @@
493
493
  *
494
494
  * This library supports the following methods of fetching web3 content from IPFS:
495
495
  *
496
- * 1. IPFS protocol: `ipfs://<cidv0>` & `ipfs://<cidv0>`
496
+ * 1. IPFS protocol: `ipfs://<cidv0>` & `ipfs://<cidv1>`
497
497
  * 2. IPNS protocol: `ipns://<peerId>` & `ipns://<publicKey>` & `ipns://<hostUri_Supporting_DnsLink_TxtRecords>`
498
498
  * 3. CID instances: An actual CID instance `CID.parse('bafy...')`
499
499
  *
@@ -1,3 +1,4 @@
1
+ import { InvalidRangeError } from '../errors.js'
1
2
  import { calculateByteRangeIndexes, getHeader } from './request-headers.js'
2
3
  import { getContentRangeHeader } from './response-headers.js'
3
4
  import type { SupportedBodyTypes } from '../types.js'
@@ -32,7 +33,7 @@ function getByteRangeFromHeader (rangeHeader: string): { start: string, end: str
32
33
  */
33
34
  const match = rangeHeader.match(/^bytes=(?<start>\d+)?-(?<end>\d+)?$/)
34
35
  if (match?.groups == null) {
35
- throw new Error('Invalid range request')
36
+ throw new InvalidRangeError('Invalid range request')
36
37
  }
37
38
 
38
39
  const { start, end } = match.groups
@@ -289,7 +290,7 @@ export class ByteRangeContext {
289
290
  public get contentRangeHeaderValue (): string {
290
291
  if (!this.isValidRangeRequest) {
291
292
  this.log.error('cannot get contentRangeHeaderValue for invalid range request')
292
- throw new Error('Invalid range request')
293
+ throw new InvalidRangeError('Invalid range request')
293
294
  }
294
295
 
295
296
  return getContentRangeHeader({
@@ -1,5 +1,6 @@
1
1
  import { AbortError, type ComponentLogger } from '@libp2p/interface'
2
2
  import { CustomProgressEvent } from 'progress-events'
3
+ import { NoContentError } from '../errors.js'
3
4
  import type { VerifiedFetchInit } from '../index.js'
4
5
 
5
6
  /**
@@ -12,7 +13,7 @@ export async function getStreamFromAsyncIterable (iterator: AsyncIterable<Uint8A
12
13
 
13
14
  if (done === true) {
14
15
  log.error('no content found for path', path)
15
- throw new Error('No content found')
16
+ throw new NoContentError()
16
17
  }
17
18
 
18
19
  const stream = new ReadableStream({
@@ -1,4 +1,4 @@
1
- import { CodeError } from '@libp2p/interface'
1
+ import { NotUnixFSError } from '@helia/unixfs/errors'
2
2
  import { exporter, recursive, type UnixFSEntry } from 'ipfs-unixfs-exporter'
3
3
  import map from 'it-map'
4
4
  import { pipe } from 'it-pipe'
@@ -28,7 +28,7 @@ function toHeader (file: UnixFSEntry): Partial<TarEntryHeader> & { name: string
28
28
 
29
29
  function toTarImportCandidate (entry: UnixFSEntry): TarImportCandidate {
30
30
  if (!EXPORTABLE.includes(entry.type)) {
31
- throw new CodeError('Not a UnixFS node', 'ERR_NOT_UNIXFS')
31
+ throw new NotUnixFSError(`${entry.type} is not a UnixFS node`)
32
32
  }
33
33
 
34
34
  const candidate: TarImportCandidate = {
@@ -64,5 +64,5 @@ export async function * tarStream (ipfsPath: string, blockstore: Blockstore, opt
64
64
  return
65
65
  }
66
66
 
67
- throw new CodeError('Not a UnixFS node', 'ERR_NOT_UNIXFS')
67
+ throw new NotUnixFSError('Not a UnixFS node')
68
68
  }
@@ -1,4 +1,5 @@
1
1
  import { type AbortOptions, type ComponentLogger } from '@libp2p/interface'
2
+ import { SubdomainNotSupportedError } from '../errors.js'
2
3
  import { type VerifiedFetchInit, type Resource } from '../index.js'
3
4
  import { matchURLString } from './parse-url-string.js'
4
5
  import { movedPermanentlyResponse } from './responses.js'
@@ -82,7 +83,7 @@ export async function getRedirectResponse ({ resource, options, logger, cid, fet
82
83
  return movedPermanentlyResponse(resource.toString(), subdomainUrl.href)
83
84
  } else {
84
85
  log('subdomain not supported, subdomain failed with status %s %s', subdomainTest.status, subdomainTest.statusText)
85
- throw new Error('subdomain not supported')
86
+ throw new SubdomainNotSupportedError('subdomain not supported')
86
87
  }
87
88
  } catch (err: any) {
88
89
  log('subdomain not supported', err)
@@ -3,7 +3,7 @@ import { CID } from 'multiformats/cid'
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'
6
- import type { AbortOptions, ComponentLogger } from '@libp2p/interface'
6
+ import type { AbortOptions, ComponentLogger, PeerId } from '@libp2p/interface'
7
7
  import type { ProgressOptions } from 'progress-events'
8
8
 
9
9
  const ipnsCache = new TLRU<DNSLinkResolveResult | IPNSResolveResult>(1000)
@@ -174,11 +174,14 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
174
174
  log.trace('resolved %s to %c from cache', cidOrPeerIdOrDnsLink, cid)
175
175
  } else {
176
176
  log.trace('Attempting to resolve PeerId for %s', cidOrPeerIdOrDnsLink)
177
- let peerId = null
177
+ let peerId: PeerId | undefined
178
178
  try {
179
179
  // try resolving as an IPNS name
180
180
  peerId = peerIdFromString(cidOrPeerIdOrDnsLink)
181
- resolveResult = await ipns.resolve(peerId, options)
181
+ if (peerId.publicKey == null) {
182
+ throw new TypeError('cidOrPeerIdOrDnsLink contains no public key')
183
+ }
184
+ resolveResult = await ipns.resolve(peerId.publicKey, options)
182
185
  cid = resolveResult?.cid
183
186
  resolvedPath = resolveResult?.path
184
187
  log.trace('resolved %s to %c', cidOrPeerIdOrDnsLink, cid)
@@ -1,3 +1,5 @@
1
+ import { InvalidRangeError } from '../errors.js'
2
+
1
3
  export function getHeader (headers: HeadersInit | undefined, header: string): string | undefined {
2
4
  if (headers == null) {
3
5
  return undefined
@@ -26,16 +28,16 @@ export function getHeader (headers: HeadersInit | undefined, header: string): st
26
28
  // eslint-disable-next-line complexity
27
29
  export function calculateByteRangeIndexes (start: number | undefined, end: number | undefined, fileSize?: number): { byteSize?: number, start?: number, end?: number } {
28
30
  if ((start ?? 0) > (end ?? Infinity)) {
29
- throw new Error('Invalid range: Range-start index is greater than range-end index.')
31
+ throw new InvalidRangeError('Invalid range: Range-start index is greater than range-end index.')
30
32
  }
31
33
  if (start != null && (end ?? 0) >= (fileSize ?? Infinity)) {
32
- throw new Error('Invalid range: Range-end index is greater than or equal to the size of the file.')
34
+ throw new InvalidRangeError('Invalid range: Range-end index is greater than or equal to the size of the file.')
33
35
  }
34
36
  if (start == null && (end ?? 0) > (fileSize ?? Infinity)) {
35
- throw new Error('Invalid range: Range-end index is greater than the size of the file.')
37
+ throw new InvalidRangeError('Invalid range: Range-end index is greater than the size of the file.')
36
38
  }
37
39
  if (start != null && start < 0) {
38
- throw new Error('Invalid range: Range-start index cannot be negative.')
40
+ throw new InvalidRangeError('Invalid range: Range-start index cannot be negative.')
39
41
  }
40
42
 
41
43
  if (start != null && end != null) {
@@ -1,3 +1,4 @@
1
+ import { InvalidRangeError } from '../errors.js'
1
2
  import type { CID } from 'multiformats/cid'
2
3
 
3
4
  interface CacheControlHeaderOptions {
@@ -46,10 +47,10 @@ export function getContentRangeHeader ({ byteStart, byteEnd, byteSize }: { byteS
46
47
  const total = byteSize ?? '*' // if we don't know the total size, we should use *
47
48
 
48
49
  if ((byteEnd ?? 0) >= (byteSize ?? Infinity)) {
49
- throw new Error('Invalid range: Range-end index is greater than or equal to the size of the file.')
50
+ throw new InvalidRangeError('Invalid range: Range-end index is greater than or equal to the size of the file.')
50
51
  }
51
52
  if ((byteStart ?? 0) >= (byteSize ?? Infinity)) {
52
- throw new Error('Invalid range: Range-start index is greater than or equal to the size of the file.')
53
+ throw new InvalidRangeError('Invalid range: Range-start index is greater than or equal to the size of the file.')
53
54
  }
54
55
 
55
56
  if (byteStart != null && byteEnd == null) {
@@ -133,7 +133,7 @@ function parseQFactor (str?: string): number {
133
133
  str = str.trim()
134
134
  }
135
135
 
136
- if (str == null || !str.startsWith('q=')) {
136
+ if (str?.startsWith('q=') !== true) {
137
137
  return 1
138
138
  }
139
139
 
@@ -1,4 +1,5 @@
1
- import { CodeError, type Logger } from '@libp2p/interface'
1
+ import { DoesNotExistError } from '@helia/unixfs/errors'
2
+ import { type Logger } from '@libp2p/interface'
2
3
  import { type Blockstore } from 'interface-blockstore'
3
4
  import { walkPath as exporterWalk, type ExporterOptions, type ReadableStorage, type ObjectNode, type UnixFSEntry } from 'ipfs-unixfs-exporter'
4
5
  import { type FetchHandlerFunctionArg } from '../types.js'
@@ -28,7 +29,7 @@ export async function walkPath (blockstore: ReadableStorage, path: string, optio
28
29
  }
29
30
 
30
31
  if (terminalElement == null) {
31
- throw new CodeError('No terminal element found', 'ERR_NO_TERMINAL_ELEMENT')
32
+ throw new DoesNotExistError('No terminal element found')
32
33
  }
33
34
 
34
35
  return {
@@ -32,7 +32,7 @@ import { setCacheControlHeader, setIpfsRoots } from './utils/response-headers.js
32
32
  import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse, notFoundResponse } from './utils/responses.js'
33
33
  import { selectOutputType } from './utils/select-output-type.js'
34
34
  import { handlePathWalking, isObjectNode } from './utils/walk-path.js'
35
- import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
35
+ import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
36
36
  import type { FetchHandlerFunctionArg, RequestFormatShorthand } from './types.js'
37
37
  import type { Helia, SessionBlockstore } from '@helia/interface'
38
38
  import type { Blockstore } from 'interface-blockstore'
@@ -167,7 +167,7 @@ export class VerifiedFetch {
167
167
  // just read it out..
168
168
  const routingKey = uint8ArrayConcat([
169
169
  uint8ArrayFromString('/ipns/'),
170
- peerId.toBytes()
170
+ peerId.toMultihash().bytes
171
171
  ])
172
172
  const datastoreKey = new Key('/dht/record/' + uint8ArrayToString(routingKey, 'base32'), false)
173
173
  const buf = await this.helia.datastore.get(datastoreKey, options)
@@ -185,7 +185,7 @@ export class VerifiedFetch {
185
185
  */
186
186
  private async handleCar ({ resource, cid, session, options }: FetchHandlerFunctionArg): Promise<Response> {
187
187
  const blockstore = this.getBlockstore(cid, resource, session, options)
188
- const c = car({ blockstore, dagWalkers: this.helia.dagWalkers })
188
+ const c = car({ blockstore, getCodec: this.helia.getCodec })
189
189
  const stream = toBrowserReadableStream(c.stream(cid, options))
190
190
 
191
191
  const response = okResponse(resource, stream)
@@ -481,8 +481,7 @@ export class VerifiedFetch {
481
481
 
482
482
  const options = convertOptions(opts)
483
483
 
484
- options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { resource }))
485
-
484
+ options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
486
485
  // resolve the CID/path from the requested resource
487
486
  let cid: ParsedUrlStringResults['cid']
488
487
  let path: ParsedUrlStringResults['path']