@helia/verified-fetch 2.6.5 → 2.6.7

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 (62) hide show
  1. package/dist/index.min.js +44 -44
  2. package/dist/src/plugins/plugin-handle-byte-range-context.d.ts +13 -0
  3. package/dist/src/plugins/plugin-handle-byte-range-context.d.ts.map +1 -0
  4. package/dist/src/plugins/plugin-handle-byte-range-context.js +19 -0
  5. package/dist/src/plugins/plugin-handle-byte-range-context.js.map +1 -0
  6. package/dist/src/plugins/plugin-handle-car.d.ts +1 -1
  7. package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
  8. package/dist/src/plugins/plugin-handle-car.js +9 -2
  9. package/dist/src/plugins/plugin-handle-car.js.map +1 -1
  10. package/dist/src/plugins/plugin-handle-dag-cbor.d.ts +2 -2
  11. package/dist/src/plugins/plugin-handle-dag-cbor.d.ts.map +1 -1
  12. package/dist/src/plugins/plugin-handle-dag-cbor.js +7 -6
  13. package/dist/src/plugins/plugin-handle-dag-cbor.js.map +1 -1
  14. package/dist/src/plugins/plugin-handle-dag-pb.d.ts +2 -2
  15. package/dist/src/plugins/plugin-handle-dag-pb.d.ts.map +1 -1
  16. package/dist/src/plugins/plugin-handle-dag-pb.js +5 -6
  17. package/dist/src/plugins/plugin-handle-dag-pb.js.map +1 -1
  18. package/dist/src/plugins/plugin-handle-dir-index-html.d.ts +1 -1
  19. package/dist/src/plugins/plugin-handle-dir-index-html.d.ts.map +1 -1
  20. package/dist/src/plugins/plugin-handle-dir-index-html.js +7 -15
  21. package/dist/src/plugins/plugin-handle-dir-index-html.js.map +1 -1
  22. package/dist/src/plugins/plugin-handle-ipns-record.d.ts +2 -2
  23. package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
  24. package/dist/src/plugins/plugin-handle-ipns-record.js +7 -3
  25. package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
  26. package/dist/src/plugins/plugin-handle-json.d.ts +2 -2
  27. package/dist/src/plugins/plugin-handle-json.d.ts.map +1 -1
  28. package/dist/src/plugins/plugin-handle-json.js +7 -3
  29. package/dist/src/plugins/plugin-handle-json.js.map +1 -1
  30. package/dist/src/plugins/plugin-handle-raw.d.ts +2 -2
  31. package/dist/src/plugins/plugin-handle-raw.d.ts.map +1 -1
  32. package/dist/src/plugins/plugin-handle-raw.js +6 -5
  33. package/dist/src/plugins/plugin-handle-raw.js.map +1 -1
  34. package/dist/src/plugins/plugin-handle-tar.d.ts +2 -2
  35. package/dist/src/plugins/plugin-handle-tar.d.ts.map +1 -1
  36. package/dist/src/plugins/plugin-handle-tar.js +7 -3
  37. package/dist/src/plugins/plugin-handle-tar.js.map +1 -1
  38. package/dist/src/plugins/types.d.ts +10 -1
  39. package/dist/src/plugins/types.d.ts.map +1 -1
  40. package/dist/src/utils/byte-range-context.d.ts.map +1 -1
  41. package/dist/src/utils/byte-range-context.js +1 -0
  42. package/dist/src/utils/byte-range-context.js.map +1 -1
  43. package/dist/src/utils/walk-path.d.ts.map +1 -1
  44. package/dist/src/utils/walk-path.js +3 -1
  45. package/dist/src/utils/walk-path.js.map +1 -1
  46. package/dist/src/verified-fetch.d.ts.map +1 -1
  47. package/dist/src/verified-fetch.js +13 -1
  48. package/dist/src/verified-fetch.js.map +1 -1
  49. package/package.json +2 -1
  50. package/src/plugins/plugin-handle-byte-range-context.ts +22 -0
  51. package/src/plugins/plugin-handle-car.ts +10 -3
  52. package/src/plugins/plugin-handle-dag-cbor.ts +10 -7
  53. package/src/plugins/plugin-handle-dag-pb.ts +6 -7
  54. package/src/plugins/plugin-handle-dir-index-html.ts +11 -16
  55. package/src/plugins/plugin-handle-ipns-record.ts +9 -4
  56. package/src/plugins/plugin-handle-json.ts +9 -4
  57. package/src/plugins/plugin-handle-raw.ts +7 -6
  58. package/src/plugins/plugin-handle-tar.ts +9 -4
  59. package/src/plugins/types.ts +10 -1
  60. package/src/utils/byte-range-context.ts +1 -0
  61. package/src/utils/walk-path.ts +4 -1
  62. package/src/verified-fetch.ts +14 -1
@@ -3,7 +3,7 @@ import toBrowserReadableStream from 'it-to-browser-readablestream'
3
3
  import { code as rawCode } from 'multiformats/codecs/raw'
4
4
  import { getETag } from '../utils/get-e-tag.js'
5
5
  import { tarStream } from '../utils/get-tar-stream.js'
6
- import { notAcceptableResponse, okResponse } from '../utils/responses.js'
6
+ import { notAcceptableResponse, okRangeResponse } from '../utils/responses.js'
7
7
  import { BasePlugin } from './plugin-base.js'
8
8
  import type { PluginContext } from './types.js'
9
9
 
@@ -13,12 +13,15 @@ import type { PluginContext } from './types.js'
13
13
  */
14
14
  export class TarPlugin extends BasePlugin {
15
15
  readonly codes = []
16
- canHandle ({ cid, accept, query }: PluginContext): boolean {
16
+ canHandle ({ cid, accept, query, byteRangeContext }: PluginContext): boolean {
17
17
  this.log('checking if we can handle %c with accept %s', cid, accept)
18
+ if (byteRangeContext == null) {
19
+ return false
20
+ }
18
21
  return accept === 'application/x-tar' || query.format === 'tar'
19
22
  }
20
23
 
21
- async handle (context: PluginContext): Promise<Response> {
24
+ async handle (context: PluginContext & Required<Pick<PluginContext, 'byteRangeContext'>>): Promise<Response> {
22
25
  const { cid, path, resource, options, pathDetails } = context
23
26
  const { getBlockstore } = this.pluginOptions
24
27
 
@@ -34,7 +37,9 @@ export class TarPlugin extends BasePlugin {
34
37
  const blockstore = getBlockstore(terminusElement, resource, options?.session, options)
35
38
  const stream = toBrowserReadableStream<Uint8Array>(tarStream(`/ipfs/${cid}/${path}`, blockstore, options))
36
39
 
37
- const response = okResponse(resource, stream)
40
+ context.byteRangeContext.setBody(stream)
41
+
42
+ const response = okRangeResponse(resource, context.byteRangeContext.getBody(), { byteRangeContext: context.byteRangeContext, log: this.log })
38
43
  response.headers.set('content-type', 'application/x-tar')
39
44
 
40
45
  response.headers.set('etag', getETag({ cid: terminusElement, reqFormat: context.reqFormat, weak: true }))
@@ -1,6 +1,7 @@
1
1
  import type { PluginError } from './errors.js'
2
2
  import type { VerifiedFetchInit } from '../index.js'
3
3
  import type { ContentTypeParser, RequestFormatShorthand } from '../types.js'
4
+ import type { ByteRangeContext } from '../utils/byte-range-context.js'
4
5
  import type { ParsedUrlStringResults } from '../utils/parse-url-string.js'
5
6
  import type { PathWalkerResponse } from '../utils/walk-path.js'
6
7
  import type { AbortOptions, ComponentLogger, Logger } from '@libp2p/interface'
@@ -12,7 +13,7 @@ import type { CustomProgressEvent } from 'progress-events'
12
13
 
13
14
  /**
14
15
  * Contains common components and functions required by plugins to handle a request.
15
- * - Read-Only: Plugins can read but shouldnt rewrite them.
16
+ * - Read-Only: Plugins can read but shouldn't rewrite them.
16
17
  * - Persistent: Relevant even after the request completes (e.g., logging or metrics).
17
18
  */
18
19
  export interface PluginOptions {
@@ -47,6 +48,14 @@ export interface PluginContext extends ParsedUrlStringResults {
47
48
  errors?: PluginError[]
48
49
  reqFormat?: RequestFormatShorthand
49
50
  pathDetails?: PathWalkerResponse
51
+ query: ParsedUrlStringResults['query']
52
+ /**
53
+ * ByteRangeContext contains information about the size of the content and range requests.
54
+ * This can be used to set the Content-Length header without loading the entire body.
55
+ *
56
+ * This is set by the ByteRangeContextPlugin
57
+ */
58
+ byteRangeContext?: ByteRangeContext
50
59
  [key: string]: unknown
51
60
  }
52
61
 
@@ -107,6 +107,7 @@ export class ByteRangeContext {
107
107
  this.log.trace('returning body with byteStart=%o, byteEnd=%o, byteSize=%o', byteStart, byteEnd, byteSize)
108
108
  if (body instanceof ReadableStream) {
109
109
  // stream should already be spliced by `unixfs.cat`
110
+ // TODO: if the content is not unixfs and unixfs.cat was not called, we need to slice the body here.
110
111
  return body
111
112
  }
112
113
  return this.getSlicedBody(body)
@@ -1,5 +1,6 @@
1
1
  import { DoesNotExistError } from '@helia/unixfs/errors'
2
2
  import { type Logger } from '@libp2p/interface'
3
+ import { abortableSource } from 'abortable-iterator'
3
4
  import { type Blockstore } from 'interface-blockstore'
4
5
  import { walkPath as exporterWalk, type ExporterOptions, type ReadableStorage, type ObjectNode, type UnixFSEntry } from 'ipfs-unixfs-exporter'
5
6
  import { badGatewayResponse, notFoundResponse } from './responses.js'
@@ -22,7 +23,9 @@ async function walkPath (blockstore: ReadableStorage, path: string, options?: Pa
22
23
  const ipfsRoots: CID[] = []
23
24
  let terminalElement: UnixFSEntry | undefined
24
25
 
25
- for await (const entry of exporterWalk(path, blockstore, options)) {
26
+ const iterator = options?.signal != null ? abortableSource(exporterWalk(path, blockstore, options), options.signal) : exporterWalk(path, blockstore, options)
27
+
28
+ for await (const entry of iterator) {
26
29
  ipfsRoots.push(entry.cid)
27
30
  terminalElement = entry
28
31
  }
@@ -4,6 +4,7 @@ import { prefixLogger } from '@libp2p/logger'
4
4
  import { LRUCache } from 'lru-cache'
5
5
  import { type CID } from 'multiformats/cid'
6
6
  import { CustomProgressEvent } from 'progress-events'
7
+ import { ByteRangeContextPlugin } from './plugins/plugin-handle-byte-range-context.js'
7
8
  import { CarPlugin } from './plugins/plugin-handle-car.js'
8
9
  import { DagCborPlugin } from './plugins/plugin-handle-dag-cbor.js'
9
10
  import { DagPbPlugin } from './plugins/plugin-handle-dag-pb.js'
@@ -90,6 +91,7 @@ export class VerifiedFetch {
90
91
 
91
92
  const defaultPlugins = [
92
93
  new DagWalkPlugin(pluginOptions),
94
+ new ByteRangeContextPlugin(pluginOptions),
93
95
  new IpnsRecordPlugin(pluginOptions),
94
96
  new CarPlugin(pluginOptions),
95
97
  new RawPlugin(pluginOptions),
@@ -151,13 +153,24 @@ export class VerifiedFetch {
151
153
  * Server-Timing header to the response if it has been collected. It should be used for any final processing of the
152
154
  * response before it is returned to the user.
153
155
  */
154
- private handleFinalResponse (response: Response, { query, cid, reqFormat, ttl, protocol, ipfsPath, pathDetails }: Partial<PluginContext> = {}): Response {
156
+ private handleFinalResponse (response: Response, { query, cid, reqFormat, ttl, protocol, ipfsPath, pathDetails, byteRangeContext }: Partial<PluginContext> = {}): Response {
155
157
  if (this.serverTimingHeaders.length > 0) {
156
158
  const headerString = this.serverTimingHeaders.join(', ')
157
159
  response.headers.set('Server-Timing', headerString)
158
160
  this.serverTimingHeaders = []
159
161
  }
160
162
 
163
+ // if there are multiple ranges, we should omit the content-length header. see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding
164
+ if (response.headers.get('Transfer-Encoding') !== 'chunked') {
165
+ if (byteRangeContext != null) {
166
+ const contentLength = byteRangeContext.length
167
+ if (contentLength != null) {
168
+ this.log.trace('Setting Content-Length from byteRangeContext: %d', contentLength)
169
+ response.headers.set('Content-Length', contentLength.toString())
170
+ }
171
+ }
172
+ }
173
+
161
174
  // set Content-Disposition header
162
175
  let contentDisposition: string | undefined
163
176