@helia/verified-fetch 1.4.1 → 1.4.2
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.
- package/dist/index.min.js +8 -8
- package/dist/src/types.d.ts +23 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +2 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/response-headers.d.ts +7 -0
- package/dist/src/utils/response-headers.d.ts.map +1 -1
- package/dist/src/utils/response-headers.js +10 -0
- package/dist/src/utils/response-headers.js.map +1 -1
- package/dist/src/utils/walk-path.d.ts +13 -0
- package/dist/src/utils/walk-path.d.ts.map +1 -1
- package/dist/src/utils/walk-path.js +22 -0
- package/dist/src/utils/walk-path.js.map +1 -1
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +39 -59
- package/dist/src/verified-fetch.js.map +1 -1
- package/dist/typedoc-urls.json +25 -25
- package/package.json +1 -1
- package/src/types.ts +29 -0
- package/src/utils/response-headers.ts +13 -0
- package/src/utils/walk-path.ts +24 -1
- package/src/verified-fetch.ts +42 -94
package/src/verified-fetch.ts
CHANGED
|
@@ -26,16 +26,16 @@ import { getStreamFromAsyncIterable } from './utils/get-stream-from-async-iterab
|
|
|
26
26
|
import { tarStream } from './utils/get-tar-stream.js'
|
|
27
27
|
import { parseResource } from './utils/parse-resource.js'
|
|
28
28
|
import { resourceToSessionCacheKey } from './utils/resource-to-cache-key.js'
|
|
29
|
-
import { setCacheControlHeader } from './utils/response-headers.js'
|
|
30
|
-
import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse
|
|
29
|
+
import { setCacheControlHeader, setIpfsRoots } from './utils/response-headers.js'
|
|
30
|
+
import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse } from './utils/responses.js'
|
|
31
31
|
import { selectOutputType } from './utils/select-output-type.js'
|
|
32
|
-
import {
|
|
32
|
+
import { handlePathWalking, isObjectNode } from './utils/walk-path.js'
|
|
33
33
|
import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
|
|
34
|
-
import type { RequestFormatShorthand } from './types.js'
|
|
34
|
+
import type { FetchHandlerFunctionArg, RequestFormatShorthand } from './types.js'
|
|
35
35
|
import type { ParsedUrlStringResults } from './utils/parse-url-string'
|
|
36
36
|
import type { Helia, SessionBlockstore } from '@helia/interface'
|
|
37
37
|
import type { Blockstore } from 'interface-blockstore'
|
|
38
|
-
import type { ObjectNode
|
|
38
|
+
import type { ObjectNode } from 'ipfs-unixfs-exporter'
|
|
39
39
|
import type { CID } from 'multiformats/cid'
|
|
40
40
|
|
|
41
41
|
const SESSION_CACHE_MAX_SIZE = 100
|
|
@@ -46,36 +46,6 @@ interface VerifiedFetchComponents {
|
|
|
46
46
|
ipns?: IPNS
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
interface FetchHandlerFunctionArg {
|
|
50
|
-
cid: CID
|
|
51
|
-
path: string
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* A key for use with the blockstore session cache
|
|
55
|
-
*/
|
|
56
|
-
cacheKey: string
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Whether to use a session during fetch operations
|
|
60
|
-
*
|
|
61
|
-
* @default true
|
|
62
|
-
*/
|
|
63
|
-
session: boolean
|
|
64
|
-
|
|
65
|
-
options?: Omit<VerifiedFetchOptions, 'signal'> & AbortOptions
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* If present, the user has sent an accept header with this value - if the
|
|
69
|
-
* content cannot be represented in this format a 406 should be returned
|
|
70
|
-
*/
|
|
71
|
-
accept?: string
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* The originally requested resource
|
|
75
|
-
*/
|
|
76
|
-
resource: string
|
|
77
|
-
}
|
|
78
|
-
|
|
79
49
|
interface FetchHandlerFunction {
|
|
80
50
|
(options: FetchHandlerFunctionArg): Promise<Response>
|
|
81
51
|
}
|
|
@@ -156,7 +126,8 @@ export class VerifiedFetch {
|
|
|
156
126
|
this.log.trace('created VerifiedFetch instance')
|
|
157
127
|
}
|
|
158
128
|
|
|
159
|
-
private getBlockstore (root: CID,
|
|
129
|
+
private getBlockstore (root: CID, resource: string | CID, useSession: boolean, options?: AbortOptions): Blockstore {
|
|
130
|
+
const key = resourceToSessionCacheKey(resource)
|
|
160
131
|
if (!useSession) {
|
|
161
132
|
return this.helia.blockstore
|
|
162
133
|
}
|
|
@@ -211,8 +182,8 @@ export class VerifiedFetch {
|
|
|
211
182
|
* Accepts a `CID` and returns a `Response` with a body stream that is a CAR
|
|
212
183
|
* of the `DAG` referenced by the `CID`.
|
|
213
184
|
*/
|
|
214
|
-
private async handleCar ({ resource, cid, session,
|
|
215
|
-
const blockstore = this.getBlockstore(cid,
|
|
185
|
+
private async handleCar ({ resource, cid, session, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
186
|
+
const blockstore = this.getBlockstore(cid, resource, session, options)
|
|
216
187
|
const c = car({ blockstore, dagWalkers: this.helia.dagWalkers })
|
|
217
188
|
const stream = toBrowserReadableStream(c.stream(cid, options))
|
|
218
189
|
|
|
@@ -226,12 +197,12 @@ export class VerifiedFetch {
|
|
|
226
197
|
* Accepts a UnixFS `CID` and returns a `.tar` file containing the file or
|
|
227
198
|
* directory structure referenced by the `CID`.
|
|
228
199
|
*/
|
|
229
|
-
private async handleTar ({ resource, cid, path, session,
|
|
200
|
+
private async handleTar ({ resource, cid, path, session, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
230
201
|
if (cid.code !== dagPbCode && cid.code !== rawCode) {
|
|
231
202
|
return notAcceptableResponse('only UnixFS data can be returned in a TAR file')
|
|
232
203
|
}
|
|
233
204
|
|
|
234
|
-
const blockstore = this.getBlockstore(cid,
|
|
205
|
+
const blockstore = this.getBlockstore(cid, resource, session, options)
|
|
235
206
|
const stream = toBrowserReadableStream<Uint8Array>(tarStream(`/ipfs/${cid}/${path}`, blockstore, options))
|
|
236
207
|
|
|
237
208
|
const response = okResponse(resource, stream)
|
|
@@ -240,9 +211,9 @@ export class VerifiedFetch {
|
|
|
240
211
|
return response
|
|
241
212
|
}
|
|
242
213
|
|
|
243
|
-
private async handleJson ({ resource, cid, path, accept, session,
|
|
214
|
+
private async handleJson ({ resource, cid, path, accept, session, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
244
215
|
this.log.trace('fetching %c/%s', cid, path)
|
|
245
|
-
const blockstore = this.getBlockstore(cid,
|
|
216
|
+
const blockstore = this.getBlockstore(cid, resource, session, options)
|
|
246
217
|
const block = await blockstore.get(cid, options)
|
|
247
218
|
let body: string | Uint8Array
|
|
248
219
|
|
|
@@ -267,33 +238,26 @@ export class VerifiedFetch {
|
|
|
267
238
|
return response
|
|
268
239
|
}
|
|
269
240
|
|
|
270
|
-
private async handleDagCbor ({ resource, cid, path, accept, session,
|
|
241
|
+
private async handleDagCbor ({ resource, cid, path, accept, session, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
271
242
|
this.log.trace('fetching %c/%s', cid, path)
|
|
272
|
-
let terminalElement: ObjectNode
|
|
273
|
-
|
|
274
|
-
const blockstore = this.getBlockstore(cid, cacheKey, session, options)
|
|
243
|
+
let terminalElement: ObjectNode
|
|
244
|
+
const blockstore = this.getBlockstore(cid, resource, session, options)
|
|
275
245
|
|
|
276
246
|
// need to walk path, if it exists, to get the terminal element
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
options?.signal?.throwIfAborted()
|
|
289
|
-
if (['ERR_NO_PROP', 'ERR_NO_TERMINAL_ELEMENT'].includes(err.code)) {
|
|
290
|
-
return notFoundResponse(resource)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
this.log.error('error walking path %s', path, err)
|
|
294
|
-
return badGatewayResponse(resource, 'Error walking path')
|
|
247
|
+
const pathDetails = await handlePathWalking({ cid, path, resource, options, blockstore, log: this.log })
|
|
248
|
+
if (pathDetails instanceof Response) {
|
|
249
|
+
return pathDetails
|
|
250
|
+
}
|
|
251
|
+
const ipfsRoots = pathDetails.ipfsRoots
|
|
252
|
+
if (isObjectNode(pathDetails.terminalElement)) {
|
|
253
|
+
terminalElement = pathDetails.terminalElement
|
|
254
|
+
} else {
|
|
255
|
+
// this should never happen, but if it does, we should log it and return notSupportedResponse
|
|
256
|
+
this.log.error('terminal element is not a dag-cbor node')
|
|
257
|
+
return notSupportedResponse(resource, 'Terminal element is not a dag-cbor node')
|
|
295
258
|
}
|
|
296
|
-
|
|
259
|
+
|
|
260
|
+
const block = terminalElement.node
|
|
297
261
|
|
|
298
262
|
let body: string | Uint8Array
|
|
299
263
|
|
|
@@ -333,36 +297,23 @@ export class VerifiedFetch {
|
|
|
333
297
|
}
|
|
334
298
|
|
|
335
299
|
response.headers.set('content-type', accept)
|
|
336
|
-
|
|
337
|
-
if (ipfsRoots != null) {
|
|
338
|
-
response.headers.set('X-Ipfs-Roots', ipfsRoots.map(cid => cid.toV1().toString()).join(',')) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-roots-response-header
|
|
339
|
-
}
|
|
300
|
+
setIpfsRoots(response, ipfsRoots)
|
|
340
301
|
|
|
341
302
|
return response
|
|
342
303
|
}
|
|
343
304
|
|
|
344
|
-
private async handleDagPb ({ cid, path, resource,
|
|
345
|
-
let terminalElement: UnixFSEntry | undefined
|
|
346
|
-
let ipfsRoots: CID[] | undefined
|
|
305
|
+
private async handleDagPb ({ cid, path, resource, session, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
347
306
|
let redirected = false
|
|
348
307
|
const byteRangeContext = new ByteRangeContext(this.helia.logger, options?.headers)
|
|
349
|
-
const blockstore = this.getBlockstore(cid,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
ipfsRoots = pathDetails.ipfsRoots
|
|
354
|
-
terminalElement = pathDetails.terminalElement
|
|
355
|
-
} catch (err: any) {
|
|
356
|
-
options?.signal?.throwIfAborted()
|
|
357
|
-
if (['ERR_NO_PROP', 'ERR_NO_TERMINAL_ELEMENT', 'ERR_NOT_FOUND'].includes(err.code)) {
|
|
358
|
-
return notFoundResponse(resource.toString())
|
|
359
|
-
}
|
|
360
|
-
this.log.error('error walking path %s', path, err)
|
|
361
|
-
|
|
362
|
-
return badGatewayResponse(resource.toString(), 'Error walking path')
|
|
308
|
+
const blockstore = this.getBlockstore(cid, resource, session, options)
|
|
309
|
+
const pathDetails = await handlePathWalking({ cid, path, resource, options, blockstore, log: this.log })
|
|
310
|
+
if (pathDetails instanceof Response) {
|
|
311
|
+
return pathDetails
|
|
363
312
|
}
|
|
313
|
+
const ipfsRoots = pathDetails.ipfsRoots
|
|
314
|
+
const terminalElement = pathDetails.terminalElement
|
|
315
|
+
let resolvedCID = terminalElement.cid
|
|
364
316
|
|
|
365
|
-
let resolvedCID = terminalElement?.cid ?? cid
|
|
366
317
|
if (terminalElement?.type === 'directory') {
|
|
367
318
|
const dirCid = terminalElement.cid
|
|
368
319
|
const redirectCheckNeeded = path === '' ? !resource.toString().endsWith('/') : !path.endsWith('/')
|
|
@@ -438,10 +389,8 @@ export class VerifiedFetch {
|
|
|
438
389
|
})
|
|
439
390
|
|
|
440
391
|
await this.setContentType(firstChunk, path, response)
|
|
392
|
+
setIpfsRoots(response, ipfsRoots)
|
|
441
393
|
|
|
442
|
-
if (ipfsRoots != null) {
|
|
443
|
-
response.headers.set('X-Ipfs-Roots', ipfsRoots.map(cid => cid.toV1().toString()).join(',')) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-roots-response-header
|
|
444
|
-
}
|
|
445
394
|
return response
|
|
446
395
|
} catch (err: any) {
|
|
447
396
|
options?.signal?.throwIfAborted()
|
|
@@ -453,9 +402,9 @@ export class VerifiedFetch {
|
|
|
453
402
|
}
|
|
454
403
|
}
|
|
455
404
|
|
|
456
|
-
private async handleRaw ({ resource, cid, path, session,
|
|
405
|
+
private async handleRaw ({ resource, cid, path, session, options, accept }: FetchHandlerFunctionArg): Promise<Response> {
|
|
457
406
|
const byteRangeContext = new ByteRangeContext(this.helia.logger, options?.headers)
|
|
458
|
-
const blockstore = this.getBlockstore(cid,
|
|
407
|
+
const blockstore = this.getBlockstore(cid, resource, session, options)
|
|
459
408
|
const result = await blockstore.get(cid, options)
|
|
460
409
|
byteRangeContext.setBody(result)
|
|
461
410
|
const response = okRangeResponse(resource, byteRangeContext.getBody(), { byteRangeContext, log: this.log }, {
|
|
@@ -559,8 +508,7 @@ export class VerifiedFetch {
|
|
|
559
508
|
let response: Response
|
|
560
509
|
let reqFormat: RequestFormatShorthand | undefined
|
|
561
510
|
|
|
562
|
-
const
|
|
563
|
-
const handlerArgs: FetchHandlerFunctionArg = { resource: resource.toString(), cid, path, accept, cacheKey, session: options?.session ?? true, options }
|
|
511
|
+
const handlerArgs: FetchHandlerFunctionArg = { resource: resource.toString(), cid, path, accept, session: options?.session ?? true, options }
|
|
564
512
|
|
|
565
513
|
if (accept === 'application/vnd.ipfs.ipns-record') {
|
|
566
514
|
// the user requested a raw IPNS record
|