@helia/verified-fetch 6.0.0 → 6.1.1
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 +56 -56
- package/dist/index.min.js.map +4 -4
- package/dist/src/index.d.ts +9 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-car.js +7 -3
- package/dist/src/plugins/plugin-handle-car.js.map +1 -1
- package/dist/src/plugins/plugin-handle-ipld.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-ipld.js +4 -15
- package/dist/src/plugins/plugin-handle-ipld.js.map +1 -1
- package/dist/src/plugins/plugin-handle-raw.d.ts +11 -0
- package/dist/src/plugins/plugin-handle-raw.d.ts.map +1 -0
- package/dist/src/plugins/plugin-handle-raw.js +41 -0
- package/dist/src/plugins/plugin-handle-raw.js.map +1 -0
- package/dist/src/plugins/plugin-handle-unixfs.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-unixfs.js +32 -14
- package/dist/src/plugins/plugin-handle-unixfs.js.map +1 -1
- package/dist/src/url-resolver.d.ts +2 -3
- package/dist/src/url-resolver.d.ts.map +1 -1
- package/dist/src/url-resolver.js +19 -55
- package/dist/src/url-resolver.js.map +1 -1
- package/dist/src/utils/error-to-response.js +1 -1
- package/dist/src/utils/error-to-response.js.map +1 -1
- package/dist/src/utils/get-tar-stream.d.ts.map +1 -1
- package/dist/src/utils/get-tar-stream.js +22 -9
- package/dist/src/utils/get-tar-stream.js.map +1 -1
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +28 -23
- package/dist/src/verified-fetch.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +10 -2
- package/src/plugins/plugin-handle-car.ts +8 -3
- package/src/plugins/plugin-handle-ipld.ts +4 -14
- package/src/plugins/plugin-handle-raw.ts +52 -0
- package/src/plugins/plugin-handle-unixfs.ts +36 -14
- package/src/url-resolver.ts +21 -63
- package/src/utils/error-to-response.ts +1 -1
- package/src/utils/get-tar-stream.ts +26 -10
- package/src/verified-fetch.ts +23 -18
package/src/url-resolver.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { DoesNotExistError } from '@helia/unixfs/errors'
|
|
2
|
-
import * as dagCbor from '@ipld/dag-cbor'
|
|
3
|
-
import * as dagJson from '@ipld/dag-json'
|
|
4
|
-
import * as dagPb from '@ipld/dag-pb'
|
|
5
2
|
import { peerIdFromString } from '@libp2p/peer-id'
|
|
6
3
|
import { InvalidParametersError, walkPath } from 'ipfs-unixfs-exporter'
|
|
7
|
-
import toBuffer from 'it-to-buffer'
|
|
8
4
|
import { CID } from 'multiformats/cid'
|
|
9
|
-
import * as json from 'multiformats/codecs/json'
|
|
10
|
-
import * as raw from 'multiformats/codecs/raw'
|
|
11
5
|
import QuickLRU from 'quick-lru'
|
|
12
|
-
import { SESSION_CACHE_MAX_SIZE, SESSION_CACHE_TTL_MS
|
|
6
|
+
import { SESSION_CACHE_MAX_SIZE, SESSION_CACHE_TTL_MS } from './constants.ts'
|
|
13
7
|
import { ServerTiming } from './utils/server-timing.ts'
|
|
14
8
|
import type { ResolveURLResult, URLResolver as URLResolverInterface } from './index.ts'
|
|
15
9
|
import type { DNSLink } from '@helia/dnslink'
|
|
@@ -17,49 +11,30 @@ import type { IPNSResolver } from '@helia/ipns'
|
|
|
17
11
|
import type { AbortOptions } from '@libp2p/interface'
|
|
18
12
|
import type { Helia, SessionBlockstore } from 'helia'
|
|
19
13
|
import type { Blockstore } from 'interface-blockstore'
|
|
20
|
-
import type {
|
|
14
|
+
import type { PathEntry } from 'ipfs-unixfs-exporter'
|
|
21
15
|
|
|
22
16
|
// 1 year in seconds for ipfs content
|
|
23
17
|
const IPFS_CONTENT_TTL = 29030400
|
|
24
18
|
|
|
25
|
-
const ENTITY_CODECS = [
|
|
26
|
-
CODEC_CBOR,
|
|
27
|
-
json.code,
|
|
28
|
-
raw.code
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* These are supported by the UnixFS exporter
|
|
33
|
-
*/
|
|
34
|
-
const EXPORTABLE_CODECS = [
|
|
35
|
-
dagPb.code,
|
|
36
|
-
dagCbor.code,
|
|
37
|
-
dagJson.code,
|
|
38
|
-
raw.code
|
|
39
|
-
]
|
|
40
|
-
|
|
41
19
|
interface GetBlockstoreOptions extends AbortOptions {
|
|
42
20
|
session?: boolean
|
|
43
21
|
}
|
|
44
22
|
|
|
45
23
|
export interface WalkPathResult {
|
|
46
24
|
ipfsRoots: CID[]
|
|
47
|
-
terminalElement:
|
|
25
|
+
terminalElement: PathEntry
|
|
48
26
|
blockstore: Blockstore
|
|
49
27
|
}
|
|
50
28
|
|
|
51
|
-
function basicEntry (
|
|
29
|
+
function basicEntry (cid: CID): PathEntry {
|
|
52
30
|
return {
|
|
31
|
+
cid,
|
|
53
32
|
name: cid.toString(),
|
|
54
33
|
path: cid.toString(),
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
size: BigInt(bytes.byteLength),
|
|
60
|
-
content: async function * () {
|
|
61
|
-
yield bytes
|
|
62
|
-
}
|
|
34
|
+
roots: [
|
|
35
|
+
cid
|
|
36
|
+
],
|
|
37
|
+
remainder: []
|
|
63
38
|
}
|
|
64
39
|
}
|
|
65
40
|
|
|
@@ -77,7 +52,6 @@ export interface URLResolverInit {
|
|
|
77
52
|
export interface ResolveURLOptions extends AbortOptions {
|
|
78
53
|
session?: boolean
|
|
79
54
|
isRawBlockRequest?: boolean
|
|
80
|
-
onlyIfCached?: boolean
|
|
81
55
|
}
|
|
82
56
|
|
|
83
57
|
export class URLResolver implements URLResolverInterface {
|
|
@@ -195,16 +169,14 @@ export class URLResolver implements URLResolverInterface {
|
|
|
195
169
|
const cid = CID.parse(url.hostname)
|
|
196
170
|
const blockstore = this.getBlockstore(cid, options)
|
|
197
171
|
|
|
198
|
-
|
|
172
|
+
try {
|
|
199
173
|
const ipfsRoots: CID[] = []
|
|
200
|
-
let terminalElement:
|
|
174
|
+
let terminalElement: PathEntry | undefined
|
|
201
175
|
const ipfsPath = toIPFSPath(url)
|
|
202
176
|
|
|
203
|
-
// @ts-expect-error offline is a helia option
|
|
204
177
|
for await (const entry of walkPath(ipfsPath, blockstore, {
|
|
205
178
|
...options,
|
|
206
|
-
|
|
207
|
-
extended: options.isRawBlockRequest !== true
|
|
179
|
+
yieldSubShards: true
|
|
208
180
|
})) {
|
|
209
181
|
ipfsRoots.push(entry.cid)
|
|
210
182
|
terminalElement = entry
|
|
@@ -219,31 +191,17 @@ export class URLResolver implements URLResolverInterface {
|
|
|
219
191
|
terminalElement,
|
|
220
192
|
blockstore
|
|
221
193
|
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// entity codecs contain all the bytes for an entity in one block and no
|
|
233
|
-
// path walking outside of that block is possible
|
|
234
|
-
if (ENTITY_CODECS.includes(cid.code)) {
|
|
235
|
-
return {
|
|
236
|
-
ipfsRoots: [cid],
|
|
237
|
-
terminalElement: basicEntry('object', cid, bytes),
|
|
238
|
-
blockstore
|
|
194
|
+
} catch (err: any) {
|
|
195
|
+
if (err.name === 'NoResolverError') {
|
|
196
|
+
// may be an unknown codec
|
|
197
|
+
return {
|
|
198
|
+
ipfsRoots: [cid],
|
|
199
|
+
terminalElement: basicEntry(cid),
|
|
200
|
+
blockstore
|
|
201
|
+
}
|
|
239
202
|
}
|
|
240
|
-
}
|
|
241
203
|
|
|
242
|
-
|
|
243
|
-
return {
|
|
244
|
-
ipfsRoots: [cid],
|
|
245
|
-
terminalElement: basicEntry('raw', cid, bytes),
|
|
246
|
-
blockstore
|
|
204
|
+
throw err
|
|
247
205
|
}
|
|
248
206
|
}
|
|
249
207
|
}
|
|
@@ -26,7 +26,7 @@ export function errorToResponse (resource: Resource | string, err: any, init?: R
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// path was not under DAG root
|
|
29
|
-
if (['
|
|
29
|
+
if (['ERR_BAD_PATH', 'ERR_NO_TERMINAL_ELEMENT', 'ERR_NOT_FOUND'].includes(err.code)) {
|
|
30
30
|
return notFoundResponse(resource)
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NotUnixFSError } from '@helia/unixfs/errors'
|
|
2
|
-
import {
|
|
2
|
+
import { InvalidParametersError } from '@libp2p/interface'
|
|
3
|
+
import { exporter, recursive, walkPath } from 'ipfs-unixfs-exporter'
|
|
4
|
+
import last from 'it-last'
|
|
3
5
|
import map from 'it-map'
|
|
4
6
|
import { pipe } from 'it-pipe'
|
|
5
7
|
import { pack } from 'it-tar'
|
|
@@ -10,9 +12,14 @@ import type { TarEntryHeader, TarImportCandidate } from 'it-tar'
|
|
|
10
12
|
|
|
11
13
|
const EXPORTABLE = ['file', 'raw', 'directory']
|
|
12
14
|
|
|
13
|
-
function toHeader (file: UnixFSEntry): Partial<TarEntryHeader> & { name: string } {
|
|
15
|
+
function toHeader (file: UnixFSEntry, path: string): Partial<TarEntryHeader> & { name: string } {
|
|
14
16
|
let mode: number | undefined
|
|
15
17
|
let mtime: Date | undefined
|
|
18
|
+
let size = 0n
|
|
19
|
+
|
|
20
|
+
if (file.type === 'file' || file.type === 'raw' || file.type === 'identity') {
|
|
21
|
+
size = file.size
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
if (file.type === 'file' || file.type === 'directory') {
|
|
18
25
|
mode = file.unixfs.mode
|
|
@@ -20,21 +27,21 @@ function toHeader (file: UnixFSEntry): Partial<TarEntryHeader> & { name: string
|
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
return {
|
|
23
|
-
name:
|
|
30
|
+
name: path,
|
|
24
31
|
mode,
|
|
25
32
|
mtime,
|
|
26
|
-
size: Number(
|
|
33
|
+
size: Number(size),
|
|
27
34
|
type: file.type === 'directory' ? 'directory' : 'file'
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
function toTarImportCandidate (entry: UnixFSEntry): TarImportCandidate {
|
|
38
|
+
function toTarImportCandidate (entry: UnixFSEntry, path: string): TarImportCandidate {
|
|
32
39
|
if (!EXPORTABLE.includes(entry.type)) {
|
|
33
40
|
throw new NotUnixFSError(`${entry.type} is not a UnixFS node`)
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
const candidate: TarImportCandidate = {
|
|
37
|
-
header: toHeader(entry)
|
|
44
|
+
header: toHeader(entry, path)
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
if (entry.type === 'file' || entry.type === 'raw') {
|
|
@@ -45,11 +52,17 @@ function toTarImportCandidate (entry: UnixFSEntry): TarImportCandidate {
|
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
export async function * tarStream (ipfsPath: string, blockstore: Blockstore, options?: AbortOptions): AsyncGenerator<Uint8Array> {
|
|
48
|
-
const
|
|
55
|
+
const entry = await last(walkPath(ipfsPath, blockstore, options))
|
|
56
|
+
|
|
57
|
+
if (entry == null) {
|
|
58
|
+
throw new InvalidParametersError(`Could not walk path "${ipfsPath}"`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const file = await exporter(entry.cid, blockstore, options)
|
|
49
62
|
|
|
50
63
|
if (file.type === 'file' || file.type === 'raw') {
|
|
51
64
|
yield * pipe(
|
|
52
|
-
[toTarImportCandidate(file)],
|
|
65
|
+
[toTarImportCandidate(file, entry.path)],
|
|
53
66
|
pack()
|
|
54
67
|
)
|
|
55
68
|
|
|
@@ -58,8 +71,11 @@ export async function * tarStream (ipfsPath: string, blockstore: Blockstore, opt
|
|
|
58
71
|
|
|
59
72
|
if (file.type === 'directory') {
|
|
60
73
|
yield * pipe(
|
|
61
|
-
recursive(
|
|
62
|
-
(source) => map(source, (entry) =>
|
|
74
|
+
recursive(file.cid, blockstore, options),
|
|
75
|
+
(source) => map(source, async (entry) => {
|
|
76
|
+
const file = await exporter(entry.cid, blockstore, options)
|
|
77
|
+
return toTarImportCandidate(file, entry.path)
|
|
78
|
+
}),
|
|
63
79
|
pack()
|
|
64
80
|
)
|
|
65
81
|
|
package/src/verified-fetch.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dnsLink } from '@helia/dnslink'
|
|
2
2
|
import { ipnsResolver } from '@helia/ipns'
|
|
3
|
-
import {
|
|
3
|
+
import { isPeerId, isPublicKey } from '@libp2p/interface'
|
|
4
4
|
import { CID } from 'multiformats/cid'
|
|
5
5
|
import { CustomProgressEvent } from 'progress-events'
|
|
6
6
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
@@ -8,6 +8,7 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
|
8
8
|
import { CarPlugin } from './plugins/plugin-handle-car.js'
|
|
9
9
|
import { IpldPlugin } from './plugins/plugin-handle-ipld.js'
|
|
10
10
|
import { IpnsRecordPlugin } from './plugins/plugin-handle-ipns-record.js'
|
|
11
|
+
import { RawPlugin } from './plugins/plugin-handle-raw.ts'
|
|
11
12
|
import { TarPlugin } from './plugins/plugin-handle-tar.js'
|
|
12
13
|
import { UnixFSPlugin } from './plugins/plugin-handle-unixfs.js'
|
|
13
14
|
import { URLResolver } from './url-resolver.ts'
|
|
@@ -19,7 +20,7 @@ import { getETag, ifNoneMatches } from './utils/get-e-tag.js'
|
|
|
19
20
|
import { getRangeHeader } from './utils/get-range-header.ts'
|
|
20
21
|
import { stringToIpfsUrl } from './utils/parse-resource.ts'
|
|
21
22
|
import { setCacheControlHeader } from './utils/response-headers.js'
|
|
22
|
-
import {
|
|
23
|
+
import { notAcceptableResponse, notImplementedResponse, notModifiedResponse } from './utils/responses.js'
|
|
23
24
|
import { ServerTiming } from './utils/server-timing.js'
|
|
24
25
|
import type { AcceptHeader, CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions, VerifiedFetchPlugin, PluginContext, PluginOptions } from './index.js'
|
|
25
26
|
import type { DNSLink } from '@helia/dnslink'
|
|
@@ -108,7 +109,8 @@ export class VerifiedFetch {
|
|
|
108
109
|
new IpldPlugin(pluginOptions),
|
|
109
110
|
new CarPlugin(pluginOptions),
|
|
110
111
|
new TarPlugin(pluginOptions),
|
|
111
|
-
new IpnsRecordPlugin(pluginOptions)
|
|
112
|
+
new IpnsRecordPlugin(pluginOptions),
|
|
113
|
+
new RawPlugin(pluginOptions)
|
|
112
114
|
]
|
|
113
115
|
|
|
114
116
|
const customPlugins = init.plugins?.map((pluginFactory) => pluginFactory(pluginOptions)) ?? []
|
|
@@ -149,6 +151,10 @@ export class VerifiedFetch {
|
|
|
149
151
|
const headers = new Headers(options?.headers)
|
|
150
152
|
const serverTiming = new ServerTiming()
|
|
151
153
|
|
|
154
|
+
if (options != null) {
|
|
155
|
+
options.offline ??= headers.get('cache-control') === 'only-if-cached'
|
|
156
|
+
}
|
|
157
|
+
|
|
152
158
|
options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
|
|
153
159
|
|
|
154
160
|
const range = getRangeHeader(resource, headers)
|
|
@@ -201,8 +207,7 @@ export class VerifiedFetch {
|
|
|
201
207
|
|
|
202
208
|
const resolveResult = await this.urlResolver.resolve(url, serverTiming, {
|
|
203
209
|
...options,
|
|
204
|
-
isRawBlockRequest: isRawBlockRequest(headers)
|
|
205
|
-
onlyIfCached: headers.get('cache-control') === 'only-if-cached'
|
|
210
|
+
isRawBlockRequest: isRawBlockRequest(headers)
|
|
206
211
|
})
|
|
207
212
|
|
|
208
213
|
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', {
|
|
@@ -408,21 +413,21 @@ export class VerifiedFetch {
|
|
|
408
413
|
let pluginHandled = false
|
|
409
414
|
|
|
410
415
|
for (const plugin of plugins) {
|
|
411
|
-
try {
|
|
412
|
-
|
|
413
|
-
|
|
416
|
+
// try {
|
|
417
|
+
this.log('invoking plugin: %s', plugin.id)
|
|
418
|
+
pluginsUsed.add(plugin.id)
|
|
414
419
|
|
|
415
|
-
|
|
420
|
+
const maybeResponse = await plugin.handle(context)
|
|
416
421
|
|
|
417
|
-
|
|
422
|
+
this.log('plugin response %s %o', plugin.id, maybeResponse)
|
|
418
423
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
} catch (err: any) {
|
|
424
|
+
if (maybeResponse != null) {
|
|
425
|
+
// if a plugin returns a final Response, short-circuit
|
|
426
|
+
finalResponse = maybeResponse
|
|
427
|
+
pluginHandled = true
|
|
428
|
+
break
|
|
429
|
+
}
|
|
430
|
+
/* } catch (err: any) {
|
|
426
431
|
if (context.options?.signal?.aborted) {
|
|
427
432
|
throw new AbortError(context.options?.signal?.reason)
|
|
428
433
|
}
|
|
@@ -430,7 +435,7 @@ export class VerifiedFetch {
|
|
|
430
435
|
this.log.error('error in plugin %s - %e', plugin.id, err)
|
|
431
436
|
|
|
432
437
|
return internalServerErrorResponse(context.resource, err)
|
|
433
|
-
}
|
|
438
|
+
} */
|
|
434
439
|
|
|
435
440
|
if (finalResponse != null) {
|
|
436
441
|
this.log.trace('plugin %s produced final response', plugin.id)
|