@helia/verified-fetch 7.0.2 → 7.0.4

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 (39) hide show
  1. package/dist/index.min.js +49 -49
  2. package/dist/index.min.js.map +3 -3
  3. package/dist/src/index.d.ts +6 -6
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/plugins/plugin-handle-car.js +3 -3
  6. package/dist/src/plugins/plugin-handle-car.js.map +1 -1
  7. package/dist/src/plugins/plugin-handle-ipld.js +2 -2
  8. package/dist/src/plugins/plugin-handle-ipld.js.map +1 -1
  9. package/dist/src/plugins/plugin-handle-ipns-record.d.ts +1 -1
  10. package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
  11. package/dist/src/plugins/plugin-handle-ipns-record.js +2 -2
  12. package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
  13. package/dist/src/plugins/plugin-handle-raw.js +2 -2
  14. package/dist/src/plugins/plugin-handle-raw.js.map +1 -1
  15. package/dist/src/plugins/plugin-handle-tar.js +2 -2
  16. package/dist/src/plugins/plugin-handle-tar.js.map +1 -1
  17. package/dist/src/plugins/plugin-handle-unixfs.js +10 -10
  18. package/dist/src/plugins/plugin-handle-unixfs.js.map +1 -1
  19. package/dist/src/url-resolver.d.ts.map +1 -1
  20. package/dist/src/url-resolver.js +1 -1
  21. package/dist/src/url-resolver.js.map +1 -1
  22. package/dist/src/utils/server-timing.d.ts +2 -0
  23. package/dist/src/utils/server-timing.d.ts.map +1 -1
  24. package/dist/src/utils/server-timing.js +7 -2
  25. package/dist/src/utils/server-timing.js.map +1 -1
  26. package/dist/src/verified-fetch.d.ts.map +1 -1
  27. package/dist/src/verified-fetch.js +3 -4
  28. package/dist/src/verified-fetch.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/index.ts +7 -7
  31. package/src/plugins/plugin-handle-car.ts +3 -3
  32. package/src/plugins/plugin-handle-ipld.ts +2 -2
  33. package/src/plugins/plugin-handle-ipns-record.ts +3 -3
  34. package/src/plugins/plugin-handle-raw.ts +2 -2
  35. package/src/plugins/plugin-handle-tar.ts +2 -2
  36. package/src/plugins/plugin-handle-unixfs.ts +10 -10
  37. package/src/url-resolver.ts +3 -2
  38. package/src/utils/server-timing.ts +8 -2
  39. package/src/verified-fetch.ts +3 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helia/verified-fetch",
3
- "version": "7.0.2",
3
+ "version": "7.0.4",
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",
package/src/index.ts CHANGED
@@ -872,7 +872,7 @@ export interface PluginOptions {
872
872
  * - Shared Data: Allows plugins to communicate partial results, discovered data, or interim errors.
873
873
  * - Ephemeral: Typically discarded once fetch(...) completes.
874
874
  */
875
- export interface PluginContext extends ResolveURLResult {
875
+ export interface PluginContext extends ResolveURLResult, Omit<VerifiedFetchInit, 'signal'>, AbortOptions {
876
876
  /**
877
877
  * The resource that was requested by the user
878
878
  */
@@ -905,11 +905,6 @@ export interface PluginContext extends ResolveURLResult {
905
905
  */
906
906
  onProgress?(evt: ProgressEvent): void
907
907
 
908
- /**
909
- * Onward options to pass to async operations
910
- */
911
- options?: Omit<VerifiedFetchInit, 'signal'> & AbortOptions
912
-
913
908
  /**
914
909
  * Any async operations should be invoked using server timings to allow
915
910
  * introspection by the user
@@ -1234,7 +1229,7 @@ export interface VerifiedFetchInit extends RequestInit, ProgressOptions<Verified
1234
1229
 
1235
1230
  export type URLProtocols = 'ipfs' | 'ipns' | 'dnslink'
1236
1231
 
1237
- export interface ResolveURLOptions extends AbortOptions, ProviderOptions {
1232
+ export interface ResolveURLOptions extends AbortOptions, ProviderOptions, ProgressOptions {
1238
1233
  /**
1239
1234
  * If true, only peers that are found to have the root block of a DAG will be
1240
1235
  * queried for subsequent blocks. If no peers in the session have a subsequent
@@ -1255,6 +1250,11 @@ export interface ResolveURLOptions extends AbortOptions, ProviderOptions {
1255
1250
  * Allows control of how redirects are processed
1256
1251
  */
1257
1252
  redirect?: RequestInit['redirect']
1253
+
1254
+ /**
1255
+ * Record timing information in a response header
1256
+ */
1257
+ serverTiming?: ServerTiming
1258
1258
  }
1259
1259
 
1260
1260
  export interface ResolveURLResult {
@@ -54,7 +54,7 @@ export class CarPlugin extends BasePlugin {
54
54
  }
55
55
 
56
56
  async handle (context: PluginContext): Promise<Response> {
57
- const { options, url, accept, resource, blockstore, range, ipfsRoots, terminalElement, requestedMimeTypes } = context
57
+ const { url, accept, resource, blockstore, range, ipfsRoots, terminalElement, requestedMimeTypes } = context
58
58
 
59
59
  if (range != null) {
60
60
  return badRequestResponse(resource, new Error('Range requests are not supported for CAR files'))
@@ -87,7 +87,7 @@ export class CarPlugin extends BasePlugin {
87
87
  })
88
88
 
89
89
  const carExportOptions: ExportCarOptions = {
90
- ...options,
90
+ ...context,
91
91
  includeTraversalBlocks: true
92
92
  }
93
93
 
@@ -112,7 +112,7 @@ export class CarPlugin extends BasePlugin {
112
112
  listingOnly: true
113
113
  }
114
114
 
115
- const entry = await exporter(terminalElement.cid, blockstore, context.options)
115
+ const entry = await exporter(terminalElement.cid, blockstore, context)
116
116
 
117
117
  if (entry.type === 'file') {
118
118
  const slice = entityBytesToOffsetAndLength(entry.size, url.searchParams.get('entity-bytes'))
@@ -41,10 +41,10 @@ export class IpldPlugin extends BasePlugin {
41
41
  }
42
42
 
43
43
  async handle (context: PluginContext): Promise<Response> {
44
- const { url, resource, accept, ipfsRoots, terminalElement, blockstore, options, requestedMimeTypes } = context
44
+ const { url, resource, accept, ipfsRoots, terminalElement, blockstore, requestedMimeTypes } = context
45
45
 
46
46
  this.log.trace('fetching %c%s', terminalElement.cid, url.pathname)
47
- let block = await toBuffer(blockstore.get(terminalElement.cid, options))
47
+ let block = await toBuffer(blockstore.get(terminalElement.cid, context))
48
48
  let contentType: ContentType
49
49
 
50
50
  try {
@@ -19,8 +19,8 @@ export class IpnsRecordPlugin extends BasePlugin {
19
19
  return accept.some(header => header.contentType.mediaType === MEDIA_TYPE_IPNS_RECORD)
20
20
  }
21
21
 
22
- async handle (context: Pick<PluginContext, 'resource' | 'url' | 'options' | 'range' | 'redirected'>): Promise<Response> {
23
- const { resource, url, options, range } = context
22
+ async handle (context: Pick<PluginContext, 'resource' | 'url' | 'range' | 'redirected' | 'signal' | 'onProgress'>): Promise<Response> {
23
+ const { resource, url, range } = context
24
24
  const { ipnsResolver } = this.pluginOptions
25
25
 
26
26
  if ((url.pathname !== '' && url.pathname !== '/') || url.protocol !== 'ipns:') {
@@ -43,7 +43,7 @@ export class IpnsRecordPlugin extends BasePlugin {
43
43
  return badRequestResponse(resource, err)
44
44
  }
45
45
 
46
- const result = await ipnsResolver.resolve(peerId, options)
46
+ const result = await ipnsResolver.resolve(peerId, context)
47
47
  const block = marshalIPNSRecord(result.record)
48
48
 
49
49
  return okResponse(resource, block, {
@@ -21,10 +21,10 @@ export class RawPlugin extends BasePlugin {
21
21
  }
22
22
 
23
23
  async handle (context: PluginContext): Promise<Response> {
24
- const { url, resource, accept, ipfsRoots, terminalElement, blockstore, options } = context
24
+ const { url, resource, accept, ipfsRoots, terminalElement, blockstore } = context
25
25
 
26
26
  this.log.trace('fetching %c%s', terminalElement.cid, url.pathname)
27
- const block = await toBuffer(blockstore.get(terminalElement.cid, options))
27
+ const block = await toBuffer(blockstore.get(terminalElement.cid, context))
28
28
 
29
29
  const headers = {
30
30
  'content-length': `${block.byteLength}`,
@@ -22,7 +22,7 @@ export class TarPlugin extends BasePlugin {
22
22
  }
23
23
 
24
24
  async handle (context: PluginContext): Promise<Response> {
25
- const { terminalElement, url, resource, options, blockstore, range } = context
25
+ const { terminalElement, url, resource, blockstore, range } = context
26
26
 
27
27
  if (terminalElement.cid.code !== dagPbCode && terminalElement.cid.code !== rawCode) {
28
28
  return badRequestResponse(resource, new NotUnixFSError('Only UnixFS data can be returned in a TAR file'))
@@ -32,7 +32,7 @@ export class TarPlugin extends BasePlugin {
32
32
  return badRequestResponse(resource, new Error('Range requests are not supported for TAR files'))
33
33
  }
34
34
 
35
- const stream = toBrowserReadableStream<Uint8Array>(tarStream(`/ipfs/${terminalElement.cid}${url.pathname}`, blockstore, options))
35
+ const stream = toBrowserReadableStream<Uint8Array>(tarStream(`/ipfs/${terminalElement.cid}${url.pathname}`, blockstore, context))
36
36
 
37
37
  return okResponse(resource, stream, {
38
38
  redirected: context.redirected,
@@ -62,10 +62,10 @@ export class UnixFSPlugin extends BasePlugin {
62
62
  let entry: UnixFSEntry
63
63
 
64
64
  try {
65
- entry = await exporter(terminalElement.cid, blockstore, context.options)
65
+ entry = await exporter(terminalElement.cid, blockstore, context)
66
66
  } catch (err: any) {
67
67
  // throw abort error if signal was aborted
68
- context?.options?.signal?.throwIfAborted()
68
+ context?.signal?.throwIfAborted()
69
69
 
70
70
  if (err.name === 'BlockNotFoundWhileOfflineError') {
71
71
  throw err
@@ -81,10 +81,10 @@ export class UnixFSPlugin extends BasePlugin {
81
81
  if (redirectUrl != null) {
82
82
  this.log.trace('directory url normalization spec requires redirect')
83
83
 
84
- if (context.options?.redirect === 'error') {
84
+ if (context?.redirect === 'error') {
85
85
  this.log('could not redirect to %s as redirect option was set to "error"', redirectUrl)
86
86
  throw new TypeError('Failed to fetch')
87
- } else if (context.options?.redirect === 'manual') {
87
+ } else if (context?.redirect === 'manual') {
88
88
  this.log('returning 301 permanent redirect to %s', redirectUrl)
89
89
  return movedPermanentlyResponse(context.resource, redirectUrl)
90
90
  }
@@ -99,19 +99,19 @@ export class UnixFSPlugin extends BasePlugin {
99
99
  const dirCid = terminalElement.cid
100
100
 
101
101
  // if not disabled, search the directory for an index.html file
102
- if (context.options?.supportDirectoryIndexes !== false) {
102
+ if (context?.supportDirectoryIndexes !== false) {
103
103
  const rootFilePath = 'index.html'
104
104
 
105
105
  try {
106
106
  this.log.trace('found directory at %c%s, looking for index.html', dirCid, url.pathname)
107
107
 
108
- const indexFile = await context.serverTiming.time('exporter-dir', '', last(walkPath(`/ipfs/${dirCid}/${rootFilePath}`, context.blockstore, context.options)))
108
+ const indexFile = await context.serverTiming.time('exporter-dir', '', last(walkPath(`/ipfs/${dirCid}/${rootFilePath}`, context.blockstore, context)))
109
109
 
110
110
  if (indexFile == null) {
111
111
  return badGatewayResponse(resource, 'Unable to stream content')
112
112
  }
113
113
 
114
- const indexFileEntry = await context.serverTiming.time('exporter-dir', '', exporter(indexFile.cid, context.blockstore, context.options))
114
+ const indexFileEntry = await context.serverTiming.time('exporter-dir', '', exporter(indexFile.cid, context.blockstore, context))
115
115
 
116
116
  if (indexFileEntry.type !== 'file' && indexFileEntry.type !== 'raw' && indexFileEntry.type !== 'identity') {
117
117
  return badGatewayResponse(resource, 'Unable to stream content')
@@ -122,7 +122,7 @@ export class UnixFSPlugin extends BasePlugin {
122
122
 
123
123
  this.log.trace('found directory index at %c%s with cid %c', dirCid, rootFilePath, entry.cid)
124
124
 
125
- return await this.streamFile(resource, indexFileEntry, filename, ipfsRoots, redirected, context.range, context.options)
125
+ return await this.streamFile(resource, indexFileEntry, filename, ipfsRoots, redirected, context.range, context)
126
126
  } catch (err: any) {
127
127
  if (err.name !== 'NotFoundError') {
128
128
  this.log.error('error loading path %c/%s - %e', dirCid, rootFilePath, err)
@@ -132,7 +132,7 @@ export class UnixFSPlugin extends BasePlugin {
132
132
  }
133
133
 
134
134
  // no index file found, return the directory listing
135
- const block = await toBuffer(context.blockstore.get(dirCid, context.options))
135
+ const block = await toBuffer(context.blockstore.get(dirCid, context))
136
136
 
137
137
  return okResponse(resource, block, {
138
138
  headers: {
@@ -148,7 +148,7 @@ export class UnixFSPlugin extends BasePlugin {
148
148
  })
149
149
  } else if (entry.type === 'file' || entry.type === 'raw' || entry.type === 'identity') {
150
150
  this.log('streaming file')
151
- return this.streamFile(resource, entry, filename, ipfsRoots, redirected, context.range, context.options)
151
+ return this.streamFile(resource, entry, filename, ipfsRoots, redirected, context.range, context)
152
152
  } else {
153
153
  this.log.error('cannot stream terminal element type %s', entry.type)
154
154
  return badGatewayResponse(resource, 'Unable to stream content')
@@ -15,11 +15,12 @@ import type { AbortOptions, Logger } from '@libp2p/interface'
15
15
  import type { Helia, ProviderOptions, SessionBlockstore } from 'helia'
16
16
  import type { Blockstore } from 'interface-blockstore'
17
17
  import type { PathEntry, UnixFSEntry } from 'ipfs-unixfs-exporter'
18
+ import type { ProgressOptions } from 'progress-events'
18
19
 
19
20
  // 1 year in seconds for ipfs content
20
21
  const IPFS_CONTENT_TTL = 29030400
21
22
 
22
- interface GetBlockstoreOptions extends AbortOptions, ProviderOptions {
23
+ interface GetBlockstoreOptions extends AbortOptions, ProviderOptions, ProgressOptions {
23
24
  session?: boolean
24
25
  }
25
26
 
@@ -144,7 +145,7 @@ export class URLResolver implements URLResolverInterface {
144
145
  } else {
145
146
  // @ts-expect-error @helia/dnslink follows recursive DNSLink records so
146
147
  // result namespace should only be ipns or ipfs
147
- throw new TypeError(`Invalid resource. Unexpected DNSLink namespace ${result.namespace} from domain: ${domain}`)
148
+ throw new TypeError(`Invalid resource. Unexpected DNSLink namespace ${result.namespace} from domain: ${url.hostname}`)
148
149
  }
149
150
 
150
151
  if (resolveResult instanceof Response) {
@@ -1,8 +1,10 @@
1
1
  export class ServerTiming {
2
2
  private headers: string[]
3
+ private precision: number
3
4
 
4
5
  constructor () {
5
6
  this.headers = []
7
+ this.precision = 3
6
8
  }
7
9
 
8
10
  getHeader (): string {
@@ -16,9 +18,13 @@ export class ServerTiming {
16
18
  return await promise // Execute the function
17
19
  } finally {
18
20
  const endTime = performance.now()
19
- const duration = (endTime - startTime).toFixed(1) // Duration in milliseconds
21
+ const duration = (endTime - startTime).toFixed(this.precision) // Duration in milliseconds
20
22
 
21
- this.headers.push(`${name};dur=${duration};desc="${description}"`)
23
+ this.add(name, description, duration)
22
24
  }
23
25
  }
26
+
27
+ add (name: string, description: string, duration: number | string): void {
28
+ this.headers.push(`${name};dur=${Number(duration).toFixed(this.precision)};desc="${description}"`)
29
+ }
24
30
  }
@@ -183,10 +183,10 @@ export class VerifiedFetch {
183
183
  }
184
184
 
185
185
  return this.handleFinalResponse(await ipnsRecordPlugin.handle({
186
+ ...options,
186
187
  range,
187
188
  url,
188
189
  resource,
189
- options,
190
190
  redirected: false
191
191
  }))
192
192
  }
@@ -212,12 +212,11 @@ export class VerifiedFetch {
212
212
  }
213
213
 
214
214
  const context: PluginContext = {
215
+ ...options,
215
216
  ...resolveResult,
216
217
  resource,
217
218
  accept,
218
219
  range,
219
- options,
220
- onProgress: options?.onProgress,
221
220
  serverTiming,
222
221
  headers,
223
222
  requestedMimeTypes
@@ -367,7 +366,7 @@ export class VerifiedFetch {
367
366
  }
368
367
  }
369
368
 
370
- if (context?.options?.method === 'HEAD') {
369
+ if (context?.method === 'HEAD') {
371
370
  // don't send the body for HEAD requests
372
371
  return new Response(null, {
373
372
  status: 200,