@helia/verified-fetch 3.2.3 → 4.0.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.
- package/README.md +5 -5
- package/dist/index.min.js +81 -71
- package/dist/index.min.js.map +4 -4
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +2 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/index.d.ts +57 -13
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -6
- 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 +37 -27
- package/dist/src/plugins/plugin-handle-car.js.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts +1 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js +1 -1
- package/dist/src/plugins/plugin-handle-dag-cbor-html-preview.js.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-cbor.js +5 -5
- package/dist/src/plugins/plugin-handle-dag-cbor.js.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-pb.js +12 -12
- package/dist/src/plugins/plugin-handle-dag-pb.js.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-walk.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-dag-walk.js +5 -4
- package/dist/src/plugins/plugin-handle-dag-walk.js.map +1 -1
- package/dist/src/plugins/plugin-handle-ipns-record.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-ipns-record.js +13 -19
- package/dist/src/plugins/plugin-handle-ipns-record.js.map +1 -1
- package/dist/src/plugins/plugin-handle-json.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-json.js +5 -4
- package/dist/src/plugins/plugin-handle-json.js.map +1 -1
- package/dist/src/plugins/plugin-handle-raw.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-raw.js +18 -5
- package/dist/src/plugins/plugin-handle-raw.js.map +1 -1
- package/dist/src/plugins/plugin-handle-tar.js +1 -1
- package/dist/src/plugins/plugin-handle-tar.js.map +1 -1
- package/dist/src/plugins/types.d.ts +10 -8
- package/dist/src/plugins/types.d.ts.map +1 -1
- package/dist/src/url-resolver.d.ts +21 -0
- package/dist/src/url-resolver.d.ts.map +1 -0
- package/dist/src/url-resolver.js +118 -0
- package/dist/src/url-resolver.js.map +1 -0
- package/dist/src/utils/byte-range-context.d.ts +1 -1
- package/dist/src/utils/get-content-type.d.ts +3 -3
- package/dist/src/utils/get-content-type.d.ts.map +1 -1
- package/dist/src/utils/get-content-type.js +1 -1
- package/dist/src/utils/get-content-type.js.map +1 -1
- package/dist/src/utils/get-e-tag.d.ts +1 -1
- package/dist/src/utils/get-offset-and-length.d.ts +6 -0
- package/dist/src/utils/get-offset-and-length.d.ts.map +1 -0
- package/dist/src/utils/get-offset-and-length.js +46 -0
- package/dist/src/utils/get-offset-and-length.js.map +1 -0
- package/dist/src/utils/get-resolved-accept-header.d.ts +2 -2
- package/dist/src/utils/get-resolved-accept-header.d.ts.map +1 -1
- package/dist/src/utils/handle-redirects.d.ts.map +1 -1
- package/dist/src/utils/handle-redirects.js +3 -3
- package/dist/src/utils/handle-redirects.js.map +1 -1
- package/dist/src/utils/ipfs-path-to-string.d.ts +6 -0
- package/dist/src/utils/ipfs-path-to-string.d.ts.map +1 -0
- package/dist/src/utils/ipfs-path-to-string.js +10 -0
- package/dist/src/utils/ipfs-path-to-string.js.map +1 -0
- package/dist/src/utils/is-accept-explicit.d.ts +6 -4
- package/dist/src/utils/is-accept-explicit.d.ts.map +1 -1
- package/dist/src/utils/is-accept-explicit.js +7 -4
- package/dist/src/utils/is-accept-explicit.js.map +1 -1
- package/dist/src/utils/parse-url-string.d.ts +1 -55
- package/dist/src/utils/parse-url-string.d.ts.map +1 -1
- package/dist/src/utils/parse-url-string.js +16 -217
- package/dist/src/utils/parse-url-string.js.map +1 -1
- package/dist/src/utils/response-headers.d.ts +1 -1
- package/dist/src/utils/response-headers.d.ts.map +1 -1
- package/dist/src/utils/responses.d.ts +1 -1
- package/dist/src/utils/select-output-type.d.ts +6 -2
- package/dist/src/utils/select-output-type.d.ts.map +1 -1
- package/dist/src/utils/select-output-type.js +28 -37
- package/dist/src/utils/select-output-type.js.map +1 -1
- package/dist/src/utils/server-timing.d.ts +5 -11
- package/dist/src/utils/server-timing.d.ts.map +1 -1
- package/dist/src/utils/server-timing.js +17 -15
- package/dist/src/utils/server-timing.js.map +1 -1
- package/dist/src/utils/walk-path.js +1 -1
- package/dist/src/utils/walk-path.js.map +1 -1
- package/dist/src/verified-fetch.d.ts +3 -10
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +68 -57
- package/dist/src/verified-fetch.js.map +1 -1
- package/dist/typedoc-urls.json +13 -2
- package/package.json +35 -36
- package/src/constants.ts +1 -0
- package/src/index.ts +73 -22
- package/src/plugins/plugin-handle-car.ts +54 -30
- package/src/plugins/plugin-handle-dag-cbor-html-preview.ts +2 -2
- package/src/plugins/plugin-handle-dag-cbor.ts +5 -5
- package/src/plugins/plugin-handle-dag-pb.ts +12 -12
- package/src/plugins/plugin-handle-dag-walk.ts +5 -4
- package/src/plugins/plugin-handle-ipns-record.ts +16 -19
- package/src/plugins/plugin-handle-json.ts +5 -4
- package/src/plugins/plugin-handle-raw.ts +21 -6
- package/src/plugins/plugin-handle-tar.ts +1 -1
- package/src/plugins/types.ts +12 -8
- package/src/url-resolver.ts +159 -0
- package/src/utils/byte-range-context.ts +1 -1
- package/src/utils/get-content-type.ts +5 -4
- package/src/utils/get-e-tag.ts +1 -1
- package/src/utils/get-offset-and-length.ts +54 -0
- package/src/utils/get-resolved-accept-header.ts +2 -2
- package/src/utils/handle-redirects.ts +10 -3
- package/src/utils/ipfs-path-to-string.ts +9 -0
- package/src/utils/is-accept-explicit.ts +14 -7
- package/src/utils/parse-url-string.ts +20 -286
- package/src/utils/response-headers.ts +1 -1
- package/src/utils/responses.ts +1 -1
- package/src/utils/select-output-type.ts +38 -44
- package/src/utils/server-timing.ts +17 -30
- package/src/utils/walk-path.ts +1 -1
- package/src/verified-fetch.ts +78 -69
- package/dist/src/types.d.ts +0 -16
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/types.js +0 -2
- package/dist/src/types.js.map +0 -1
- package/dist/src/utils/parse-resource.d.ts +0 -18
- package/dist/src/utils/parse-resource.d.ts.map +0 -1
- package/dist/src/utils/parse-resource.js +0 -27
- package/dist/src/utils/parse-resource.js.map +0 -1
- package/src/types.ts +0 -17
- package/src/utils/parse-resource.ts +0 -42
package/src/verified-fetch.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dnsLink } from '@helia/dnslink'
|
|
2
|
+
import { ipnsResolver } from '@helia/ipns'
|
|
2
3
|
import { AbortError } from '@libp2p/interface'
|
|
3
4
|
import { prefixLogger } from '@libp2p/logger'
|
|
4
5
|
import { CustomProgressEvent } from 'progress-events'
|
|
@@ -12,22 +13,24 @@ import { IpnsRecordPlugin } from './plugins/plugin-handle-ipns-record.js'
|
|
|
12
13
|
import { JsonPlugin } from './plugins/plugin-handle-json.js'
|
|
13
14
|
import { RawPlugin } from './plugins/plugin-handle-raw.js'
|
|
14
15
|
import { TarPlugin } from './plugins/plugin-handle-tar.js'
|
|
16
|
+
import { URLResolver } from './url-resolver.ts'
|
|
15
17
|
import { contentTypeParser } from './utils/content-type-parser.js'
|
|
16
18
|
import { getContentDispositionFilename } from './utils/get-content-disposition-filename.js'
|
|
17
19
|
import { getETag } from './utils/get-e-tag.js'
|
|
18
20
|
import { getResolvedAcceptHeader } from './utils/get-resolved-accept-header.js'
|
|
19
21
|
import { getRedirectResponse } from './utils/handle-redirects.js'
|
|
20
|
-
import {
|
|
22
|
+
import { uriEncodeIPFSPath } from './utils/ipfs-path-to-string.ts'
|
|
21
23
|
import { resourceToSessionCacheKey } from './utils/resource-to-cache-key.js'
|
|
22
24
|
import { setCacheControlHeader } from './utils/response-headers.js'
|
|
23
25
|
import { badRequestResponse, notAcceptableResponse, notSupportedResponse, badGatewayResponse } from './utils/responses.js'
|
|
24
26
|
import { selectOutputType } from './utils/select-output-type.js'
|
|
25
|
-
import {
|
|
26
|
-
import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
|
|
27
|
+
import { ServerTiming } from './utils/server-timing.js'
|
|
28
|
+
import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, ResolveURLResult, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
|
|
27
29
|
import type { VerifiedFetchPlugin, PluginContext, PluginOptions } from './plugins/types.js'
|
|
28
|
-
import type {
|
|
30
|
+
import type { AcceptHeader } from './utils/select-output-type.js'
|
|
31
|
+
import type { DNSLink } from '@helia/dnslink'
|
|
29
32
|
import type { Helia, SessionBlockstore } from '@helia/interface'
|
|
30
|
-
import type {
|
|
33
|
+
import type { IPNSResolver } from '@helia/ipns'
|
|
31
34
|
import type { AbortOptions, Logger } from '@libp2p/interface'
|
|
32
35
|
import type { Blockstore } from 'interface-blockstore'
|
|
33
36
|
import type { CID } from 'multiformats/cid'
|
|
@@ -35,11 +38,6 @@ import type { CID } from 'multiformats/cid'
|
|
|
35
38
|
const SESSION_CACHE_MAX_SIZE = 100
|
|
36
39
|
const SESSION_CACHE_TTL_MS = 60 * 1000
|
|
37
40
|
|
|
38
|
-
interface VerifiedFetchComponents {
|
|
39
|
-
helia: Helia
|
|
40
|
-
ipns?: IPNS
|
|
41
|
-
}
|
|
42
|
-
|
|
43
41
|
function convertOptions (options?: VerifiedFetchOptions): (Omit<VerifiedFetchOptions, 'signal'> & AbortOptions) | undefined {
|
|
44
42
|
if (options == null) {
|
|
45
43
|
return undefined
|
|
@@ -60,19 +58,20 @@ function convertOptions (options?: VerifiedFetchOptions): (Omit<VerifiedFetchOpt
|
|
|
60
58
|
|
|
61
59
|
export class VerifiedFetch {
|
|
62
60
|
private readonly helia: Helia
|
|
63
|
-
private readonly
|
|
61
|
+
private readonly ipnsResolver: IPNSResolver
|
|
62
|
+
private readonly dnsLink: DNSLink
|
|
64
63
|
private readonly log: Logger
|
|
65
64
|
private readonly contentTypeParser: ContentTypeParser | undefined
|
|
66
65
|
private readonly blockstoreSessions: QuickLRU<string, SessionBlockstore>
|
|
67
|
-
private serverTimingHeaders: string[] = []
|
|
68
66
|
private readonly withServerTiming: boolean
|
|
69
67
|
private readonly plugins: VerifiedFetchPlugin[] = []
|
|
70
68
|
|
|
71
|
-
constructor (
|
|
69
|
+
constructor (helia: Helia, init: CreateVerifiedFetchOptions = {}) {
|
|
72
70
|
this.helia = helia
|
|
73
71
|
this.log = helia.logger.forComponent('helia:verified-fetch')
|
|
74
|
-
this.
|
|
75
|
-
this.
|
|
72
|
+
this.ipnsResolver = init.ipnsResolver ?? ipnsResolver(helia)
|
|
73
|
+
this.dnsLink = init.dnsLink ?? dnsLink(helia)
|
|
74
|
+
this.contentTypeParser = init.contentTypeParser ?? contentTypeParser
|
|
76
75
|
this.blockstoreSessions = new QuickLRU({
|
|
77
76
|
maxSize: init?.sessionCacheSize ?? SESSION_CACHE_MAX_SIZE,
|
|
78
77
|
maxAge: init?.sessionTTLms ?? SESSION_CACHE_TTL_MS,
|
|
@@ -86,9 +85,9 @@ export class VerifiedFetch {
|
|
|
86
85
|
...init,
|
|
87
86
|
logger: prefixLogger('helia:verified-fetch'),
|
|
88
87
|
getBlockstore: (cid, resource, useSession, options) => this.getBlockstore(cid, resource, useSession, options),
|
|
89
|
-
handleServerTiming: async (name, description, fn) => this.handleServerTiming(name, description, fn, this.withServerTiming),
|
|
90
88
|
helia,
|
|
91
|
-
contentTypeParser: this.contentTypeParser
|
|
89
|
+
contentTypeParser: this.contentTypeParser,
|
|
90
|
+
ipnsResolver: this.ipnsResolver
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
const defaultPlugins = [
|
|
@@ -103,7 +102,7 @@ export class VerifiedFetch {
|
|
|
103
102
|
new DagPbPlugin(pluginOptions)
|
|
104
103
|
]
|
|
105
104
|
|
|
106
|
-
const customPlugins = init
|
|
105
|
+
const customPlugins = init.plugins?.map((pluginFactory) => pluginFactory(pluginOptions)) ?? []
|
|
107
106
|
|
|
108
107
|
if (customPlugins.length > 0) {
|
|
109
108
|
// allow custom plugins to replace default plugins
|
|
@@ -137,35 +136,20 @@ export class VerifiedFetch {
|
|
|
137
136
|
return session
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
private async handleServerTiming<T> (name: string, description: string, fn: () => Promise<T>, withServerTiming: boolean): Promise<T> {
|
|
141
|
-
if (!withServerTiming) {
|
|
142
|
-
return fn()
|
|
143
|
-
}
|
|
144
|
-
const { error, result, header } = await serverTiming(name, description, fn)
|
|
145
|
-
this.serverTimingHeaders.push(header)
|
|
146
|
-
if (error != null) {
|
|
147
|
-
throw error
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return result
|
|
151
|
-
}
|
|
152
|
-
|
|
153
139
|
/**
|
|
154
140
|
* The last place a Response touches in verified-fetch before being returned to the user. This is where we add the
|
|
155
141
|
* Server-Timing header to the response if it has been collected. It should be used for any final processing of the
|
|
156
142
|
* response before it is returned to the user.
|
|
157
143
|
*/
|
|
158
|
-
private handleFinalResponse (response: Response,
|
|
159
|
-
if (this.
|
|
160
|
-
|
|
161
|
-
response.headers.set('Server-Timing', headerString)
|
|
162
|
-
this.serverTimingHeaders = []
|
|
144
|
+
private handleFinalResponse (response: Response, context?: Partial<PluginContext>): Response {
|
|
145
|
+
if ((this.withServerTiming || context?.withServerTiming === true) && context?.serverTiming != null) {
|
|
146
|
+
response.headers.set('Server-Timing', context?.serverTiming.getHeader())
|
|
163
147
|
}
|
|
164
148
|
|
|
165
149
|
// 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
|
|
166
150
|
if (response.headers.get('Transfer-Encoding') !== 'chunked') {
|
|
167
|
-
if (byteRangeContext != null) {
|
|
168
|
-
const contentLength = byteRangeContext.getLength()
|
|
151
|
+
if (context?.byteRangeContext != null) {
|
|
152
|
+
const contentLength = context.byteRangeContext.getLength()
|
|
169
153
|
if (contentLength != null) {
|
|
170
154
|
this.log.trace('Setting Content-Length from byteRangeContext: %d', contentLength)
|
|
171
155
|
response.headers.set('Content-Length', contentLength.toString())
|
|
@@ -179,19 +163,19 @@ export class VerifiedFetch {
|
|
|
179
163
|
this.log.trace('checking for content disposition')
|
|
180
164
|
|
|
181
165
|
// force download if requested
|
|
182
|
-
if (query?.download === true) {
|
|
166
|
+
if (context?.query?.download === true) {
|
|
183
167
|
contentDisposition = 'attachment'
|
|
184
168
|
} else {
|
|
185
169
|
this.log.trace('download not requested')
|
|
186
170
|
}
|
|
187
171
|
|
|
188
172
|
// override filename if requested
|
|
189
|
-
if (query?.filename != null) {
|
|
173
|
+
if (context?.query?.filename != null) {
|
|
190
174
|
if (contentDisposition == null) {
|
|
191
175
|
contentDisposition = 'inline'
|
|
192
176
|
}
|
|
193
177
|
|
|
194
|
-
contentDisposition = `${contentDisposition}; ${getContentDispositionFilename(query.filename)}`
|
|
178
|
+
contentDisposition = `${contentDisposition}; ${getContentDispositionFilename(context.query.filename)}`
|
|
195
179
|
} else {
|
|
196
180
|
this.log.trace('no filename specified in query')
|
|
197
181
|
}
|
|
@@ -202,24 +186,33 @@ export class VerifiedFetch {
|
|
|
202
186
|
this.log.trace('no content disposition specified')
|
|
203
187
|
}
|
|
204
188
|
|
|
205
|
-
if (cid != null && response.headers.get('etag') == null) {
|
|
206
|
-
response.headers.set('etag', getETag({
|
|
189
|
+
if (context?.cid != null && response.headers.get('etag') == null) {
|
|
190
|
+
response.headers.set('etag', getETag({
|
|
191
|
+
cid: context.pathDetails?.terminalElement.cid ?? context.cid,
|
|
192
|
+
reqFormat: context.reqFormat,
|
|
193
|
+
weak: false
|
|
194
|
+
}))
|
|
207
195
|
}
|
|
208
196
|
|
|
209
|
-
if (protocol != null) {
|
|
210
|
-
setCacheControlHeader({
|
|
197
|
+
if (context?.protocol != null && context.ttl != null) {
|
|
198
|
+
setCacheControlHeader({
|
|
199
|
+
response,
|
|
200
|
+
ttl: context.ttl,
|
|
201
|
+
protocol: context.protocol
|
|
202
|
+
})
|
|
211
203
|
}
|
|
212
|
-
|
|
213
|
-
|
|
204
|
+
|
|
205
|
+
if (context?.ipfsPath != null) {
|
|
206
|
+
response.headers.set('X-Ipfs-Path', uriEncodeIPFSPath(context.ipfsPath))
|
|
214
207
|
}
|
|
215
208
|
|
|
216
209
|
// set CORS headers. If hosting your own gateway with verified-fetch behind the scenes, you can alter these before you send the response to the client.
|
|
217
210
|
response.headers.set('Access-Control-Allow-Origin', '*')
|
|
218
211
|
response.headers.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')
|
|
219
212
|
response.headers.set('Access-Control-Allow-Headers', 'Range, X-Requested-With')
|
|
220
|
-
response.headers.set('Access-Control-Expose-Headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Stream-Output')
|
|
213
|
+
response.headers.set('Access-Control-Expose-Headers', 'Content-Range, Content-Length, X-Ipfs-Path, X-Ipfs-Roots, X-Stream-Output')
|
|
221
214
|
|
|
222
|
-
if (reqFormat !== 'car') {
|
|
215
|
+
if (context?.reqFormat !== 'car') {
|
|
223
216
|
// if we are not doing streaming responses, set the Accept-Ranges header to bytes to enable range requests
|
|
224
217
|
response.headers.set('Accept-Ranges', 'bytes')
|
|
225
218
|
} else {
|
|
@@ -227,10 +220,17 @@ export class VerifiedFetch {
|
|
|
227
220
|
response.headers.set('Accept-Ranges', 'none')
|
|
228
221
|
}
|
|
229
222
|
|
|
230
|
-
if (
|
|
223
|
+
if (response.headers.get('Content-Type')?.includes('application/vnd.ipld.car') === true || response.headers.get('Content-Type')?.includes('application/vnd.ipld.raw') === true) {
|
|
224
|
+
// see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
|
|
225
|
+
response.headers.set('X-Content-Type-Options', 'nosniff')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (context?.options?.method === 'HEAD') {
|
|
231
229
|
// don't send the body for HEAD requests
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
return new Response(null, {
|
|
231
|
+
status: 200,
|
|
232
|
+
headers: response.headers
|
|
233
|
+
})
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
return response
|
|
@@ -248,23 +248,23 @@ export class VerifiedFetch {
|
|
|
248
248
|
let prevModificationId = context.modified
|
|
249
249
|
|
|
250
250
|
while (passCount < maxPasses) {
|
|
251
|
-
this.log(`
|
|
251
|
+
this.log(`starting pipeline pass #${passCount + 1}`)
|
|
252
252
|
passCount++
|
|
253
253
|
|
|
254
254
|
// gather plugins that say they can handle the *current* context, but haven't been used yet
|
|
255
255
|
const readyPlugins = this.plugins.filter(p => !pluginsUsed.has(p.id)).filter(p => p.canHandle(context))
|
|
256
256
|
if (readyPlugins.length === 0) {
|
|
257
|
-
this.log.trace('
|
|
257
|
+
this.log.trace('no plugins can handle the current context.. checking by CID code')
|
|
258
258
|
const plugins = this.plugins.filter(p => p.codes.includes(context.cid.code))
|
|
259
259
|
if (plugins.length > 0) {
|
|
260
260
|
readyPlugins.push(...plugins)
|
|
261
261
|
} else {
|
|
262
|
-
this.log.trace('
|
|
262
|
+
this.log.trace('no plugins found that can handle request by CID code; exiting pipeline.')
|
|
263
263
|
break
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
this.log.trace('
|
|
267
|
+
this.log.trace('plugins ready to handle request: ', readyPlugins.map(p => p.id).join(', '))
|
|
268
268
|
|
|
269
269
|
// track if any plugin changed the context or returned a response
|
|
270
270
|
let contextChanged = false
|
|
@@ -272,7 +272,7 @@ export class VerifiedFetch {
|
|
|
272
272
|
|
|
273
273
|
for (const plugin of readyPlugins) {
|
|
274
274
|
try {
|
|
275
|
-
this.log.trace('
|
|
275
|
+
this.log.trace('invoking plugin:', plugin.id)
|
|
276
276
|
pluginsUsed.add(plugin.id)
|
|
277
277
|
|
|
278
278
|
const maybeResponse = await plugin.handle(context)
|
|
@@ -286,7 +286,7 @@ export class VerifiedFetch {
|
|
|
286
286
|
if (context.options?.signal?.aborted) {
|
|
287
287
|
throw new AbortError(context.options?.signal?.reason)
|
|
288
288
|
}
|
|
289
|
-
this.log.error('
|
|
289
|
+
this.log.error('error in plugin %s - %e', plugin.constructor.name, err)
|
|
290
290
|
// if fatal, short-circuit the pipeline
|
|
291
291
|
if (err.name === 'PluginFatalError') {
|
|
292
292
|
// if plugin provides a custom error response, return it
|
|
@@ -302,7 +302,7 @@ export class VerifiedFetch {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
if (finalResponse != null) {
|
|
305
|
-
this.log.trace('
|
|
305
|
+
this.log.trace('plugin %s produced final response', plugin.id)
|
|
306
306
|
break
|
|
307
307
|
}
|
|
308
308
|
}
|
|
@@ -312,7 +312,7 @@ export class VerifiedFetch {
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
if (!contextChanged) {
|
|
315
|
-
this.log.trace('
|
|
315
|
+
this.log.trace('no context changes and no final response; exiting pipeline.')
|
|
316
316
|
break
|
|
317
317
|
}
|
|
318
318
|
}
|
|
@@ -335,14 +335,20 @@ export class VerifiedFetch {
|
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
const options = convertOptions(opts)
|
|
338
|
-
const
|
|
338
|
+
const serverTiming = new ServerTiming()
|
|
339
|
+
|
|
340
|
+
const urlResolver = new URLResolver({
|
|
341
|
+
ipnsResolver: this.ipnsResolver,
|
|
342
|
+
dnsLink: this.dnsLink,
|
|
343
|
+
timing: serverTiming
|
|
344
|
+
})
|
|
339
345
|
|
|
340
346
|
options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
|
|
341
347
|
|
|
342
|
-
let parsedResult:
|
|
348
|
+
let parsedResult: ResolveURLResult
|
|
349
|
+
|
|
343
350
|
try {
|
|
344
|
-
parsedResult = await
|
|
345
|
-
this.serverTimingHeaders.push(...parsedResult.serverTimings.map(({ header }) => header))
|
|
351
|
+
parsedResult = await urlResolver.resolve(resource, options)
|
|
346
352
|
} catch (err: any) {
|
|
347
353
|
if (options?.signal?.aborted) {
|
|
348
354
|
throw new AbortError(options?.signal?.reason)
|
|
@@ -356,14 +362,15 @@ export class VerifiedFetch {
|
|
|
356
362
|
|
|
357
363
|
const acceptHeader = getResolvedAcceptHeader({ query: parsedResult.query, headers: options?.headers, logger: this.helia.logger })
|
|
358
364
|
|
|
359
|
-
const accept:
|
|
365
|
+
const accept: AcceptHeader | undefined = selectOutputType(parsedResult.cid, acceptHeader)
|
|
360
366
|
this.log('output type %s', accept)
|
|
361
367
|
|
|
362
368
|
if (acceptHeader != null && accept == null) {
|
|
369
|
+
this.log.error('could not fulfil request based on accept header')
|
|
363
370
|
return this.handleFinalResponse(notAcceptableResponse(resource.toString()))
|
|
364
371
|
}
|
|
365
372
|
|
|
366
|
-
const responseContentType: string = accept?.split(';')[0] ?? 'application/octet-stream'
|
|
373
|
+
const responseContentType: string = accept?.mimeType.split(';')[0] ?? 'application/octet-stream'
|
|
367
374
|
|
|
368
375
|
const redirectResponse = await getRedirectResponse({ resource, options, logger: this.helia.logger, cid: parsedResult.cid })
|
|
369
376
|
if (redirectResponse != null) {
|
|
@@ -375,10 +382,12 @@ export class VerifiedFetch {
|
|
|
375
382
|
resource: resource.toString(),
|
|
376
383
|
accept,
|
|
377
384
|
options,
|
|
378
|
-
withServerTiming,
|
|
379
385
|
onProgress: options?.onProgress,
|
|
380
386
|
modified: 0,
|
|
381
|
-
plugins: this.plugins.map(p => p.id)
|
|
387
|
+
plugins: this.plugins.map(p => p.id),
|
|
388
|
+
query: parsedResult.query ?? {},
|
|
389
|
+
withServerTiming: Boolean(options?.withServerTiming) || Boolean(this.withServerTiming),
|
|
390
|
+
serverTiming
|
|
382
391
|
}
|
|
383
392
|
|
|
384
393
|
this.log.trace('finding handler for cid code "%s" and response content type "%s"', parsedResult.cid.code, responseContentType)
|
package/dist/src/types.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type RequestFormatShorthand = 'raw' | 'car' | 'tar' | 'ipns-record' | 'dag-json' | 'dag-cbor' | 'json' | 'cbor';
|
|
2
|
-
export type SupportedBodyTypes = string | Uint8Array | ArrayBuffer | Blob | ReadableStream<Uint8Array> | null;
|
|
3
|
-
/**
|
|
4
|
-
* A ContentTypeParser attempts to return the mime type of a given file. It
|
|
5
|
-
* receives the first chunk of the file data and the file name, if it is
|
|
6
|
-
* available. The function can be sync or async and if it returns/resolves to
|
|
7
|
-
* `undefined`, `application/octet-stream` will be used.
|
|
8
|
-
*/
|
|
9
|
-
export interface ContentTypeParser {
|
|
10
|
-
/**
|
|
11
|
-
* Attempt to determine a mime type, either via of the passed bytes or the
|
|
12
|
-
* filename if it is available.
|
|
13
|
-
*/
|
|
14
|
-
(bytes: Uint8Array, fileName?: string): Promise<string | undefined> | string | undefined;
|
|
15
|
-
}
|
|
16
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/src/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA;AAEtH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,IAAI,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAA;AAE7G;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAAA;CACzF"}
|
package/dist/src/types.js
DELETED
package/dist/src/types.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ParseUrlStringOptions, ParsedUrlStringResults } from './parse-url-string.js';
|
|
2
|
-
import type { Resource } from '../index.js';
|
|
3
|
-
import type { IPNS } from '@helia/ipns';
|
|
4
|
-
import type { ComponentLogger } from '@libp2p/interface';
|
|
5
|
-
export interface ParseResourceComponents {
|
|
6
|
-
ipns: IPNS;
|
|
7
|
-
logger: ComponentLogger;
|
|
8
|
-
}
|
|
9
|
-
export interface ParseResourceOptions extends ParseUrlStringOptions {
|
|
10
|
-
withServerTiming?: boolean;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Handles the different use cases for the `resource` argument.
|
|
14
|
-
* The resource can represent an IPFS path, IPNS path, or CID.
|
|
15
|
-
* If the resource represents an IPNS path, we need to resolve it to a CID.
|
|
16
|
-
*/
|
|
17
|
-
export declare function parseResource(resource: Resource, { ipns, logger }: ParseResourceComponents, { withServerTiming, ...options }?: ParseResourceOptions): Promise<ParsedUrlStringResults>;
|
|
18
|
-
//# sourceMappingURL=parse-resource.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parse-resource.d.ts","sourceRoot":"","sources":["../../../src/utils/parse-resource.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC1F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,eAAe,CAAA;CACxB;AAED,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;IACjE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AACD;;;;GAIG;AACH,wBAAsB,aAAa,CAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,uBAAuB,EAAE,EAAE,gBAAwB,EAAE,GAAG,OAAO,EAAE,GAAE,oBAAkD,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAqBjO"}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { CID } from 'multiformats/cid';
|
|
2
|
-
import { parseUrlString } from './parse-url-string.js';
|
|
3
|
-
/**
|
|
4
|
-
* Handles the different use cases for the `resource` argument.
|
|
5
|
-
* The resource can represent an IPFS path, IPNS path, or CID.
|
|
6
|
-
* If the resource represents an IPNS path, we need to resolve it to a CID.
|
|
7
|
-
*/
|
|
8
|
-
export async function parseResource(resource, { ipns, logger }, { withServerTiming = false, ...options } = { withServerTiming: false }) {
|
|
9
|
-
if (typeof resource === 'string') {
|
|
10
|
-
return parseUrlString({ urlString: resource, ipns, logger, withServerTiming }, options);
|
|
11
|
-
}
|
|
12
|
-
const cid = CID.asCID(resource);
|
|
13
|
-
if (cid != null) {
|
|
14
|
-
// an actual CID
|
|
15
|
-
return {
|
|
16
|
-
cid,
|
|
17
|
-
protocol: 'ipfs',
|
|
18
|
-
path: '',
|
|
19
|
-
query: {},
|
|
20
|
-
ipfsPath: `/ipfs/${cid.toString()}`,
|
|
21
|
-
ttl: 29030400, // 1 year for ipfs content
|
|
22
|
-
serverTimings: []
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
throw new TypeError(`Invalid resource. Cannot determine CID from resource: ${resource}`);
|
|
26
|
-
}
|
|
27
|
-
//# sourceMappingURL=parse-resource.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parse-resource.js","sourceRoot":"","sources":["../../../src/utils/parse-resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AActD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAE,QAAkB,EAAE,EAAE,IAAI,EAAE,MAAM,EAA2B,EAAE,EAAE,gBAAgB,GAAG,KAAK,EAAE,GAAG,OAAO,KAA2B,EAAE,gBAAgB,EAAE,KAAK,EAAE;IAC9L,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,cAAc,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,OAAO,CAAC,CAAA;IACzF,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAE/B,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QAChB,gBAAgB;QAChB,OAAO;YACL,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,EAAE;YACT,QAAQ,EAAE,SAAS,GAAG,CAAC,QAAQ,EAAE,EAAE;YACnC,GAAG,EAAE,QAAQ,EAAE,0BAA0B;YACzC,aAAa,EAAE,EAAE;SACe,CAAA;IACpC,CAAC;IAED,MAAM,IAAI,SAAS,CAAC,yDAAyD,QAAQ,EAAE,CAAC,CAAA;AAC1F,CAAC"}
|
package/src/types.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type RequestFormatShorthand = 'raw' | 'car' | 'tar' | 'ipns-record' | 'dag-json' | 'dag-cbor' | 'json' | 'cbor'
|
|
2
|
-
|
|
3
|
-
export type SupportedBodyTypes = string | Uint8Array | ArrayBuffer | Blob | ReadableStream<Uint8Array> | null
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A ContentTypeParser attempts to return the mime type of a given file. It
|
|
7
|
-
* receives the first chunk of the file data and the file name, if it is
|
|
8
|
-
* available. The function can be sync or async and if it returns/resolves to
|
|
9
|
-
* `undefined`, `application/octet-stream` will be used.
|
|
10
|
-
*/
|
|
11
|
-
export interface ContentTypeParser {
|
|
12
|
-
/**
|
|
13
|
-
* Attempt to determine a mime type, either via of the passed bytes or the
|
|
14
|
-
* filename if it is available.
|
|
15
|
-
*/
|
|
16
|
-
(bytes: Uint8Array, fileName?: string): Promise<string | undefined> | string | undefined
|
|
17
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { CID } from 'multiformats/cid'
|
|
2
|
-
import { parseUrlString } from './parse-url-string.js'
|
|
3
|
-
import type { ParseUrlStringOptions, ParsedUrlStringResults } from './parse-url-string.js'
|
|
4
|
-
import type { Resource } from '../index.js'
|
|
5
|
-
import type { IPNS } from '@helia/ipns'
|
|
6
|
-
import type { ComponentLogger } from '@libp2p/interface'
|
|
7
|
-
|
|
8
|
-
export interface ParseResourceComponents {
|
|
9
|
-
ipns: IPNS
|
|
10
|
-
logger: ComponentLogger
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ParseResourceOptions extends ParseUrlStringOptions {
|
|
14
|
-
withServerTiming?: boolean
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Handles the different use cases for the `resource` argument.
|
|
18
|
-
* The resource can represent an IPFS path, IPNS path, or CID.
|
|
19
|
-
* If the resource represents an IPNS path, we need to resolve it to a CID.
|
|
20
|
-
*/
|
|
21
|
-
export async function parseResource (resource: Resource, { ipns, logger }: ParseResourceComponents, { withServerTiming = false, ...options }: ParseResourceOptions = { withServerTiming: false }): Promise<ParsedUrlStringResults> {
|
|
22
|
-
if (typeof resource === 'string') {
|
|
23
|
-
return parseUrlString({ urlString: resource, ipns, logger, withServerTiming }, options)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const cid = CID.asCID(resource)
|
|
27
|
-
|
|
28
|
-
if (cid != null) {
|
|
29
|
-
// an actual CID
|
|
30
|
-
return {
|
|
31
|
-
cid,
|
|
32
|
-
protocol: 'ipfs',
|
|
33
|
-
path: '',
|
|
34
|
-
query: {},
|
|
35
|
-
ipfsPath: `/ipfs/${cid.toString()}`,
|
|
36
|
-
ttl: 29030400, // 1 year for ipfs content
|
|
37
|
-
serverTimings: []
|
|
38
|
-
} satisfies ParsedUrlStringResults
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
throw new TypeError(`Invalid resource. Cannot determine CID from resource: ${resource}`)
|
|
42
|
-
}
|