@helia/verified-fetch 1.1.2 → 1.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 (45) hide show
  1. package/README.md +24 -1
  2. package/dist/index.min.js +8 -19
  3. package/dist/src/index.d.ts +29 -4
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js +41 -5
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/types.d.ts +1 -0
  8. package/dist/src/types.d.ts.map +1 -1
  9. package/dist/src/utils/byte-range-context.d.ts +82 -0
  10. package/dist/src/utils/byte-range-context.d.ts.map +1 -0
  11. package/dist/src/utils/byte-range-context.js +275 -0
  12. package/dist/src/utils/byte-range-context.js.map +1 -0
  13. package/dist/src/utils/get-stream-from-async-iterable.js +1 -1
  14. package/dist/src/utils/parse-resource.d.ts +2 -2
  15. package/dist/src/utils/parse-url-string.d.ts +2 -2
  16. package/dist/src/utils/parse-url-string.d.ts.map +1 -1
  17. package/dist/src/utils/parse-url-string.js +5 -5
  18. package/dist/src/utils/parse-url-string.js.map +1 -1
  19. package/dist/src/utils/request-headers.d.ts +13 -0
  20. package/dist/src/utils/request-headers.d.ts.map +1 -0
  21. package/dist/src/utils/request-headers.js +50 -0
  22. package/dist/src/utils/request-headers.js.map +1 -0
  23. package/dist/src/utils/response-headers.d.ts +12 -0
  24. package/dist/src/utils/response-headers.d.ts.map +1 -0
  25. package/dist/src/utils/response-headers.js +29 -0
  26. package/dist/src/utils/response-headers.js.map +1 -0
  27. package/dist/src/utils/responses.d.ts +21 -4
  28. package/dist/src/utils/responses.d.ts.map +1 -1
  29. package/dist/src/utils/responses.js +58 -0
  30. package/dist/src/utils/responses.js.map +1 -1
  31. package/dist/src/verified-fetch.d.ts +2 -1
  32. package/dist/src/verified-fetch.d.ts.map +1 -1
  33. package/dist/src/verified-fetch.js +61 -25
  34. package/dist/src/verified-fetch.js.map +1 -1
  35. package/package.json +4 -3
  36. package/src/index.ts +49 -8
  37. package/src/types.ts +2 -0
  38. package/src/utils/byte-range-context.ts +303 -0
  39. package/src/utils/get-stream-from-async-iterable.ts +1 -1
  40. package/src/utils/parse-resource.ts +2 -2
  41. package/src/utils/parse-url-string.ts +7 -7
  42. package/src/utils/request-headers.ts +51 -0
  43. package/src/utils/response-headers.ts +32 -0
  44. package/src/utils/responses.ts +82 -4
  45. package/src/verified-fetch.ts +68 -29
package/src/index.ts CHANGED
@@ -148,7 +148,7 @@
148
148
  *
149
149
  * ```typescript
150
150
  * import { createVerifiedFetch } from '@helia/verified-fetch'
151
- * import { dnsJsonOverHttps, dnsOverHttps } from '@helia/ipns/dns-resolvers'
151
+ * import { dnsJsonOverHttps, dnsOverHttps } from '@multiformats/dns/resolvers'
152
152
  *
153
153
  * const fetch = await createVerifiedFetch({
154
154
  * gateways: ['https://trustless-gateway.link'],
@@ -160,6 +160,29 @@
160
160
  * })
161
161
  * ```
162
162
  *
163
+ * @example Customizing DNS per-TLD resolvers
164
+ *
165
+ * DNS resolvers can be configured to only service DNS queries for specific
166
+ * TLDs:
167
+ *
168
+ * ```typescript
169
+ * import { createVerifiedFetch } from '@helia/verified-fetch'
170
+ * import { dnsJsonOverHttps, dnsOverHttps } from '@multiformats/dns/resolvers'
171
+ *
172
+ * const fetch = await createVerifiedFetch({
173
+ * gateways: ['https://trustless-gateway.link'],
174
+ * routers: ['http://delegated-ipfs.dev'],
175
+ * dnsResolvers: {
176
+ * // this resolver will only be used for `.com` domains (note - this could
177
+ * // also be an array of resolvers)
178
+ * 'com.': dnsJsonOverHttps('https://my-dns-resolver.example.com/dns-json'),
179
+ * // this resolver will be used for everything else (note - this could
180
+ * // also be an array of resolvers)
181
+ * '.': dnsOverHttps('https://my-dns-resolver.example.com/dns-query')
182
+ * }
183
+ * })
184
+ * ```
185
+ *
163
186
  * ### IPLD codec handling
164
187
  *
165
188
  * IPFS supports several data formats (typically referred to as codecs) which are included in the CID. `@helia/verified-fetch` attempts to abstract away some of the details for easier consumption.
@@ -569,10 +592,13 @@
569
592
  import { trustlessGateway } from '@helia/block-brokers'
570
593
  import { createHeliaHTTP } from '@helia/http'
571
594
  import { delegatedHTTPRouting } from '@helia/routers'
595
+ import { dns } from '@multiformats/dns'
572
596
  import { VerifiedFetch as VerifiedFetchClass } from './verified-fetch.js'
573
597
  import type { Helia } from '@helia/interface'
574
- import type { DNSResolver, IPNSRoutingEvents, ResolveDnsLinkProgressEvents, ResolveProgressEvents } from '@helia/ipns'
598
+ import type { ResolveDNSLinkProgressEvents } from '@helia/ipns'
575
599
  import type { GetEvents } from '@helia/unixfs'
600
+ import type { DNSResolvers, DNS } from '@multiformats/dns'
601
+ import type { DNSResolver } from '@multiformats/dns/resolvers'
576
602
  import type { CID } from 'multiformats/cid'
577
603
  import type { ProgressEvent, ProgressOptions } from 'progress-events'
578
604
 
@@ -618,7 +644,7 @@ export interface CreateVerifiedFetchInit {
618
644
  *
619
645
  * @default [dnsJsonOverHttps('https://mozilla.cloudflare-dns.com/dns-query'),dnsJsonOverHttps('https://dns.google/resolve')]
620
646
  */
621
- dnsResolvers?: DNSResolver[]
647
+ dnsResolvers?: DNSResolver[] | DNSResolvers
622
648
  }
623
649
 
624
650
  export interface CreateVerifiedFetchOptions {
@@ -651,7 +677,7 @@ export type BubbledProgressEvents =
651
677
  // unixfs
652
678
  GetEvents |
653
679
  // ipns
654
- ResolveProgressEvents | ResolveDnsLinkProgressEvents | IPNSRoutingEvents
680
+ ResolveDNSLinkProgressEvents
655
681
 
656
682
  export type VerifiedFetchProgressEvents =
657
683
  ProgressEvent<'verified-fetch:request:start', CIDDetail> |
@@ -674,20 +700,19 @@ export interface VerifiedFetchInit extends RequestInit, ProgressOptions<BubbledP
674
700
  * Create and return a Helia node
675
701
  */
676
702
  export async function createVerifiedFetch (init?: Helia | CreateVerifiedFetchInit, options?: CreateVerifiedFetchOptions): Promise<VerifiedFetch> {
677
- let dnsResolvers: DNSResolver[] | undefined
678
703
  if (!isHelia(init)) {
679
- dnsResolvers = init?.dnsResolvers
680
704
  init = await createHeliaHTTP({
681
705
  blockBrokers: [
682
706
  trustlessGateway({
683
707
  gateways: init?.gateways
684
708
  })
685
709
  ],
686
- routers: (init?.routers ?? ['https://delegated-ipfs.dev']).map((routerUrl) => delegatedHTTPRouting(routerUrl))
710
+ routers: (init?.routers ?? ['https://delegated-ipfs.dev']).map((routerUrl) => delegatedHTTPRouting(routerUrl)),
711
+ dns: createDns(init?.dnsResolvers)
687
712
  })
688
713
  }
689
714
 
690
- const verifiedFetchInstance = new VerifiedFetchClass({ helia: init }, { dnsResolvers, ...options })
715
+ const verifiedFetchInstance = new VerifiedFetchClass({ helia: init }, options)
691
716
  async function verifiedFetch (resource: Resource, options?: VerifiedFetchInit): Promise<Response> {
692
717
  return verifiedFetchInstance.fetch(resource, options)
693
718
  }
@@ -707,3 +732,19 @@ function isHelia (obj: any): obj is Helia {
707
732
  obj?.stop != null &&
708
733
  obj?.start != null
709
734
  }
735
+
736
+ function createDns (resolvers?: DNSResolver[] | DNSResolvers): DNS | undefined {
737
+ if (resolvers == null) {
738
+ return
739
+ }
740
+
741
+ if (Array.isArray(resolvers)) {
742
+ return dns({
743
+ resolvers: {
744
+ '.': resolvers
745
+ }
746
+ })
747
+ }
748
+
749
+ return dns({ resolvers })
750
+ }
package/src/types.ts CHANGED
@@ -1 +1,3 @@
1
1
  export type RequestFormatShorthand = 'raw' | 'car' | 'tar' | 'ipns-record' | 'dag-json' | 'dag-cbor' | 'json' | 'cbor'
2
+
3
+ export type SupportedBodyTypes = string | ArrayBuffer | Blob | ReadableStream<Uint8Array> | null
@@ -0,0 +1,303 @@
1
+ import { calculateByteRangeIndexes, getHeader } from './request-headers.js'
2
+ import { getContentRangeHeader } from './response-headers.js'
3
+ import type { SupportedBodyTypes } from '../types.js'
4
+ import type { ComponentLogger, Logger } from '@libp2p/interface'
5
+
6
+ type SliceableBody = Exclude<SupportedBodyTypes, ReadableStream<Uint8Array> | null>
7
+
8
+ /**
9
+ * Gets the body size of a given body if it's possible to calculate it synchronously.
10
+ */
11
+ function getBodySizeSync (body: SupportedBodyTypes): number | null {
12
+ if (typeof body === 'string') {
13
+ return body.length
14
+ }
15
+ if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
16
+ return body.byteLength
17
+ }
18
+ if (body instanceof Blob) {
19
+ return body.size
20
+ }
21
+
22
+ if (body instanceof ReadableStream) {
23
+ return null
24
+ }
25
+
26
+ return null
27
+ }
28
+
29
+ function getByteRangeFromHeader (rangeHeader: string): { start: string, end: string } {
30
+ /**
31
+ * Range: bytes=<start>-<end> | bytes=<start2>- | bytes=-<end2>
32
+ */
33
+ const match = rangeHeader.match(/^bytes=(?<start>\d+)?-(?<end>\d+)?$/)
34
+ if (match?.groups == null) {
35
+ throw new Error('Invalid range request')
36
+ }
37
+
38
+ const { start, end } = match.groups
39
+
40
+ return { start, end }
41
+ }
42
+
43
+ export class ByteRangeContext {
44
+ public readonly isRangeRequest: boolean
45
+
46
+ /**
47
+ * This property is purposefully only set in `set fileSize` and should not be set directly.
48
+ */
49
+ private _fileSize: number | null | undefined
50
+ private _body: SupportedBodyTypes = null
51
+ private readonly rangeRequestHeader: string | undefined
52
+ private readonly log: Logger
53
+ private readonly requestRangeStart: number | null
54
+ private readonly requestRangeEnd: number | null
55
+ private byteStart: number | undefined
56
+ private byteEnd: number | undefined
57
+ private byteSize: number | undefined
58
+
59
+ constructor (logger: ComponentLogger, private readonly headers?: HeadersInit) {
60
+ this.log = logger.forComponent('helia:verified-fetch:byte-range-context')
61
+ this.rangeRequestHeader = getHeader(this.headers, 'Range')
62
+ if (this.rangeRequestHeader != null) {
63
+ this.isRangeRequest = true
64
+ this.log.trace('range request detected')
65
+ try {
66
+ const { start, end } = getByteRangeFromHeader(this.rangeRequestHeader)
67
+ this.requestRangeStart = start != null ? parseInt(start) : null
68
+ this.requestRangeEnd = end != null ? parseInt(end) : null
69
+ } catch (e) {
70
+ this.log.error('error parsing range request header: %o', e)
71
+ this.requestRangeStart = null
72
+ this.requestRangeEnd = null
73
+ }
74
+
75
+ this.setOffsetDetails()
76
+ } else {
77
+ this.log.trace('no range request detected')
78
+ this.isRangeRequest = false
79
+ this.requestRangeStart = null
80
+ this.requestRangeEnd = null
81
+ }
82
+ }
83
+
84
+ public setBody (body: SupportedBodyTypes): void {
85
+ this._body = body
86
+ // if fileSize was already set, don't recalculate it
87
+ this.setFileSize(this._fileSize ?? getBodySizeSync(body))
88
+
89
+ this.log.trace('set request body with fileSize %o', this._fileSize)
90
+ }
91
+
92
+ public getBody (): SupportedBodyTypes {
93
+ const body = this._body
94
+ if (body == null) {
95
+ this.log.trace('body is null')
96
+ return body
97
+ }
98
+ if (!this.isRangeRequest || !this.isValidRangeRequest) {
99
+ this.log.trace('returning body unmodified for non-range, or invalid range, request')
100
+ return body
101
+ }
102
+ const byteStart = this.byteStart
103
+ const byteEnd = this.byteEnd
104
+ const byteSize = this.byteSize
105
+ if (byteStart != null || byteEnd != null) {
106
+ this.log.trace('returning body with byteStart=%o, byteEnd=%o, byteSize=%o', byteStart, byteEnd, byteSize)
107
+ if (body instanceof ReadableStream) {
108
+ // stream should already be spliced by `unixfs.cat`
109
+ return body
110
+ }
111
+ return this.getSlicedBody(body)
112
+ }
113
+
114
+ // we should not reach this point, but return body untouched.
115
+ this.log.error('returning unmodified body for valid range request')
116
+ return body
117
+ }
118
+
119
+ private getSlicedBody <T extends SliceableBody>(body: T): SliceableBody {
120
+ if (this.isPrefixLengthRequest) {
121
+ this.log.trace('sliced body with byteStart %o', this.byteStart)
122
+ return body.slice(this.offset) satisfies SliceableBody
123
+ }
124
+ if (this.isSuffixLengthRequest && this.length != null) {
125
+ this.log.trace('sliced body with length %o', -this.length)
126
+ return body.slice(-this.length) satisfies SliceableBody
127
+ }
128
+ const offset = this.byteStart ?? 0
129
+ const length = this.byteEnd == null ? undefined : this.byteEnd + 1
130
+ this.log.trace('returning body with offset %o and length %o', offset, length)
131
+
132
+ return body.slice(offset, length) satisfies SliceableBody
133
+ }
134
+
135
+ private get isSuffixLengthRequest (): boolean {
136
+ return this.requestRangeStart == null && this.requestRangeEnd != null
137
+ }
138
+
139
+ private get isPrefixLengthRequest (): boolean {
140
+ return this.requestRangeStart != null && this.requestRangeEnd == null
141
+ }
142
+
143
+ /**
144
+ * Sometimes, we need to set the fileSize explicitly because we can't calculate
145
+ * the size of the body (e.g. for unixfs content where we call .stat).
146
+ *
147
+ * This fileSize should otherwise only be called from `setBody`.
148
+ */
149
+ public setFileSize (size: number | bigint | null): void {
150
+ this._fileSize = size != null ? Number(size) : null
151
+ this.log.trace('set _fileSize to %o', this._fileSize)
152
+ // when fileSize changes, we need to recalculate the offset details
153
+ this.setOffsetDetails()
154
+ }
155
+
156
+ public getFileSize (): number | null | undefined {
157
+ return this._fileSize
158
+ }
159
+
160
+ private isValidByteStart (): boolean {
161
+ if (this.byteStart != null) {
162
+ if (this.byteStart < 0) {
163
+ return false
164
+ }
165
+ if (this._fileSize != null && this.byteStart > this._fileSize) {
166
+ return false
167
+ }
168
+ }
169
+ return true
170
+ }
171
+
172
+ private isValidByteEnd (): boolean {
173
+ if (this.byteEnd != null) {
174
+ if (this.byteEnd < 0) {
175
+ return false
176
+ }
177
+ if (this._fileSize != null && this.byteEnd > this._fileSize) {
178
+ return false
179
+ }
180
+ }
181
+ return true
182
+ }
183
+
184
+ /**
185
+ * We may get the values required to determine if this is a valid range request at different times
186
+ * so we need to calculate it when asked.
187
+ */
188
+ public get isValidRangeRequest (): boolean {
189
+ if (!this.isRangeRequest) {
190
+ return false
191
+ }
192
+ if (this.requestRangeStart == null && this.requestRangeEnd == null) {
193
+ this.log.trace('invalid range request, range request values not provided')
194
+ return false
195
+ }
196
+ if (!this.isValidByteStart()) {
197
+ this.log.trace('invalid range request, byteStart is less than 0 or greater than fileSize')
198
+ return false
199
+ }
200
+ if (!this.isValidByteEnd()) {
201
+ this.log.trace('invalid range request, byteEnd is less than 0 or greater than fileSize')
202
+ return false
203
+ }
204
+ if (this.requestRangeEnd != null && this.requestRangeStart != null) {
205
+ // we may not have enough info.. base check on requested bytes
206
+ if (this.requestRangeStart > this.requestRangeEnd) {
207
+ this.log.trace('invalid range request, start is greater than end')
208
+ return false
209
+ } else if (this.requestRangeStart < 0) {
210
+ this.log.trace('invalid range request, start is less than 0')
211
+ return false
212
+ } else if (this.requestRangeEnd < 0) {
213
+ this.log.trace('invalid range request, end is less than 0')
214
+ return false
215
+ }
216
+ }
217
+
218
+ return true
219
+ }
220
+
221
+ /**
222
+ * Given all the information we have, this function returns the offset that will be used when:
223
+ * 1. calling unixfs.cat
224
+ * 2. slicing the body
225
+ */
226
+ public get offset (): number {
227
+ if (this.byteStart === 0) {
228
+ return 0
229
+ }
230
+ if (this.isPrefixLengthRequest || this.isSuffixLengthRequest) {
231
+ if (this.byteStart != null) {
232
+ // we have to subtract by 1 because the offset is inclusive
233
+ return this.byteStart - 1
234
+ }
235
+ }
236
+
237
+ return this.byteStart ?? 0
238
+ }
239
+
240
+ /**
241
+ * Given all the information we have, this function returns the length that will be used when:
242
+ * 1. calling unixfs.cat
243
+ * 2. slicing the body
244
+ */
245
+ public get length (): number | undefined {
246
+ return this.byteSize ?? undefined
247
+ }
248
+
249
+ /**
250
+ * Converts a range request header into helia/unixfs supported range options
251
+ * Note that the gateway specification says we "MAY" support multiple ranges (https://specs.ipfs.tech/http-gateways/path-gateway/#range-request-header) but we don't
252
+ *
253
+ * Also note that @helia/unixfs and ipfs-unixfs-exporter expect length and offset to be numbers, the range header is a string, and the size of the resource is likely a bigint.
254
+ *
255
+ * SUPPORTED:
256
+ * Range: bytes=<range-start>-<range-end>
257
+ * Range: bytes=<range-start>-
258
+ * Range: bytes=-<suffix-length> // must pass size so we can calculate the offset. suffix-length is the number of bytes from the end of the file.
259
+ *
260
+ * NOT SUPPORTED:
261
+ * Range: bytes=<range-start>-<range-end>, <range-start>-<range-end>
262
+ * Range: bytes=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>
263
+ *
264
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range#directives
265
+ */
266
+ private setOffsetDetails (): void {
267
+ if (this.requestRangeStart == null && this.requestRangeEnd == null) {
268
+ this.log.trace('requestRangeStart and requestRangeEnd are null')
269
+ return
270
+ }
271
+
272
+ const { start, end, byteSize } = calculateByteRangeIndexes(this.requestRangeStart ?? undefined, this.requestRangeEnd ?? undefined, this._fileSize ?? undefined)
273
+ this.log.trace('set byteStart to %o, byteEnd to %o, byteSize to %o', start, end, byteSize)
274
+ this.byteStart = start
275
+ this.byteEnd = end
276
+ this.byteSize = byteSize
277
+ }
278
+
279
+ /**
280
+ * This function returns the value of the "content-range" header.
281
+ *
282
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range
283
+ *
284
+ * Returns a string representing the following content ranges:
285
+ *
286
+ * @example
287
+ * - Content-Range: <unit> <byteStart>-<byteEnd>/<byteSize>
288
+ * - Content-Range: <unit> <byteStart>-<byteEnd>/*
289
+ */
290
+ // - Content-Range: <unit> */<byteSize> // this is purposefully not in jsdoc block
291
+ public get contentRangeHeaderValue (): string {
292
+ if (!this.isValidRangeRequest) {
293
+ this.log.error('cannot get contentRangeHeaderValue for invalid range request')
294
+ throw new Error('Invalid range request')
295
+ }
296
+
297
+ return getContentRangeHeader({
298
+ byteStart: this.byteStart,
299
+ byteEnd: this.byteEnd,
300
+ byteSize: this._fileSize ?? undefined
301
+ })
302
+ }
303
+ }
@@ -11,7 +11,7 @@ export async function getStreamFromAsyncIterable (iterator: AsyncIterable<Uint8A
11
11
  const { value: firstChunk, done } = await reader.next()
12
12
 
13
13
  if (done === true) {
14
- log.error('No content found for path', path)
14
+ log.error('no content found for path', path)
15
15
  throw new Error('No content found')
16
16
  }
17
17
 
@@ -2,7 +2,7 @@ import { CID } from 'multiformats/cid'
2
2
  import { parseUrlString } from './parse-url-string.js'
3
3
  import type { ParsedUrlStringResults } from './parse-url-string.js'
4
4
  import type { Resource } from '../index.js'
5
- import type { IPNS, IPNSRoutingEvents, ResolveDnsLinkProgressEvents, ResolveProgressEvents } from '@helia/ipns'
5
+ import type { IPNS, IPNSRoutingEvents, ResolveDNSLinkProgressEvents, ResolveProgressEvents } from '@helia/ipns'
6
6
  import type { ComponentLogger } from '@libp2p/interface'
7
7
  import type { ProgressOptions } from 'progress-events'
8
8
 
@@ -11,7 +11,7 @@ export interface ParseResourceComponents {
11
11
  logger: ComponentLogger
12
12
  }
13
13
 
14
- export interface ParseResourceOptions extends ProgressOptions<ResolveProgressEvents | IPNSRoutingEvents | ResolveDnsLinkProgressEvents> {
14
+ export interface ParseResourceOptions extends ProgressOptions<ResolveProgressEvents | IPNSRoutingEvents | ResolveDNSLinkProgressEvents> {
15
15
 
16
16
  }
17
17
  /**
@@ -2,7 +2,7 @@ import { peerIdFromString } from '@libp2p/peer-id'
2
2
  import { CID } from 'multiformats/cid'
3
3
  import { TLRU } from './tlru.js'
4
4
  import type { RequestFormatShorthand } from '../types.js'
5
- import type { IPNS, IPNSRoutingEvents, ResolveDnsLinkProgressEvents, ResolveProgressEvents, ResolveResult } from '@helia/ipns'
5
+ import type { IPNS, ResolveDNSLinkProgressEvents, ResolveResult } from '@helia/ipns'
6
6
  import type { ComponentLogger } from '@libp2p/interface'
7
7
  import type { ProgressOptions } from 'progress-events'
8
8
 
@@ -13,7 +13,7 @@ export interface ParseUrlStringInput {
13
13
  ipns: IPNS
14
14
  logger: ComponentLogger
15
15
  }
16
- export interface ParseUrlStringOptions extends ProgressOptions<ResolveProgressEvents | IPNSRoutingEvents | ResolveDnsLinkProgressEvents> {
16
+ export interface ParseUrlStringOptions extends ProgressOptions<ResolveDNSLinkProgressEvents> {
17
17
 
18
18
  }
19
19
 
@@ -106,7 +106,7 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
106
106
  log.trace('resolved %s to %c from cache', cidOrPeerIdOrDnsLink, cid)
107
107
  } else {
108
108
  // protocol is ipns
109
- log.trace('Attempting to resolve PeerId for %s', cidOrPeerIdOrDnsLink)
109
+ log.trace('attempting to resolve PeerId for %s', cidOrPeerIdOrDnsLink)
110
110
  let peerId = null
111
111
  try {
112
112
  peerId = peerIdFromString(cidOrPeerIdOrDnsLink)
@@ -117,10 +117,10 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
117
117
  ipnsCache.set(cidOrPeerIdOrDnsLink, resolveResult, 60 * 1000 * 2)
118
118
  } catch (err) {
119
119
  if (peerId == null) {
120
- log.error('Could not parse PeerId string "%s"', cidOrPeerIdOrDnsLink, err)
120
+ log.error('could not parse PeerId string "%s"', cidOrPeerIdOrDnsLink, err)
121
121
  errors.push(new TypeError(`Could not parse PeerId in ipns url "${cidOrPeerIdOrDnsLink}", ${(err as Error).message}`))
122
122
  } else {
123
- log.error('Could not resolve PeerId %c', peerId, err)
123
+ log.error('could not resolve PeerId %c', peerId, err)
124
124
  errors.push(new TypeError(`Could not resolve PeerId "${cidOrPeerIdOrDnsLink}", ${(err as Error).message}`))
125
125
  }
126
126
  }
@@ -134,13 +134,13 @@ export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStrin
134
134
  log.trace('Attempting to resolve DNSLink for %s', decodedDnsLinkLabel)
135
135
 
136
136
  try {
137
- resolveResult = await ipns.resolveDns(decodedDnsLinkLabel, { onProgress: options?.onProgress })
137
+ resolveResult = await ipns.resolveDNSLink(decodedDnsLinkLabel, { onProgress: options?.onProgress })
138
138
  cid = resolveResult?.cid
139
139
  resolvedPath = resolveResult?.path
140
140
  log.trace('resolved %s to %c', decodedDnsLinkLabel, cid)
141
141
  ipnsCache.set(cidOrPeerIdOrDnsLink, resolveResult, 60 * 1000 * 2)
142
142
  } catch (err: any) {
143
- log.error('Could not resolve DnsLink for "%s"', cidOrPeerIdOrDnsLink, err)
143
+ log.error('could not resolve DnsLink for "%s"', cidOrPeerIdOrDnsLink, err)
144
144
  errors.push(err)
145
145
  }
146
146
  }
@@ -0,0 +1,51 @@
1
+ export function getHeader (headers: HeadersInit | undefined, header: string): string | undefined {
2
+ if (headers == null) {
3
+ return undefined
4
+ }
5
+ if (headers instanceof Headers) {
6
+ return headers.get(header) ?? undefined
7
+ }
8
+ if (Array.isArray(headers)) {
9
+ const entry = headers.find(([key]) => key.toLowerCase() === header.toLowerCase())
10
+ return entry?.[1]
11
+ }
12
+ const key = Object.keys(headers).find(k => k.toLowerCase() === header.toLowerCase())
13
+ if (key == null) {
14
+ return undefined
15
+ }
16
+
17
+ return headers[key]
18
+ }
19
+
20
+ /**
21
+ * Given two ints from a Range header, and potential fileSize, returns:
22
+ * 1. number of bytes the response should contain.
23
+ * 2. the start index of the range. // inclusive
24
+ * 3. the end index of the range. // inclusive
25
+ */
26
+ export function calculateByteRangeIndexes (start: number | undefined, end: number | undefined, fileSize?: number): { byteSize?: number, start?: number, end?: number } {
27
+ if (start != null && end != null) {
28
+ if (start > end) {
29
+ throw new Error('Invalid range')
30
+ }
31
+
32
+ return { byteSize: end - start + 1, start, end }
33
+ } else if (start == null && end != null) {
34
+ // suffix byte range requested
35
+ if (fileSize == null) {
36
+ return { end }
37
+ }
38
+ const result = { byteSize: end, start: fileSize - end + 1, end: fileSize }
39
+ return result
40
+ } else if (start != null && end == null) {
41
+ if (fileSize == null) {
42
+ return { start }
43
+ }
44
+ const byteSize = fileSize - start + 1
45
+ const end = fileSize
46
+ return { byteSize, start, end }
47
+ }
48
+
49
+ // both start and end are undefined
50
+ return { byteSize: fileSize }
51
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * This function returns the value of the `Content-Range` header for a given range.
3
+ * If you know the total size of the body, pass it as `byteSize`
4
+ *
5
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range
6
+ */
7
+ export function getContentRangeHeader ({ byteStart, byteEnd, byteSize }: { byteStart: number | undefined, byteEnd: number | undefined, byteSize: number | undefined }): string {
8
+ const total = byteSize ?? '*' // if we don't know the total size, we should use *
9
+
10
+ if (byteStart != null && byteEnd == null) {
11
+ // only byteStart in range
12
+ if (byteSize == null) {
13
+ return `bytes */${total}`
14
+ }
15
+ return `bytes ${byteStart}-${byteSize}/${byteSize}`
16
+ }
17
+
18
+ if (byteStart == null && byteEnd != null) {
19
+ // only byteEnd in range
20
+ if (byteSize == null) {
21
+ return `bytes */${total}`
22
+ }
23
+ return `bytes ${byteSize - byteEnd + 1}-${byteSize}/${byteSize}`
24
+ }
25
+
26
+ if (byteStart == null && byteEnd == null) {
27
+ // neither are provided, we can't return a valid range.
28
+ return `bytes */${total}`
29
+ }
30
+
31
+ return `bytes ${byteStart}-${byteEnd}/${total}`
32
+ }