@helia/verified-fetch 1.0.2 → 1.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/README.md +74 -1
- package/dist/index.min.js +7 -7
- package/dist/src/index.d.ts +73 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +73 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/utils/parse-url-string.d.ts.map +1 -1
- package/dist/src/utils/parse-url-string.js +14 -6
- package/dist/src/utils/parse-url-string.js.map +1 -1
- package/dist/src/utils/responses.d.ts +8 -4
- package/dist/src/utils/responses.d.ts.map +1 -1
- package/dist/src/utils/responses.js +56 -7
- package/dist/src/utils/responses.js.map +1 -1
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +35 -19
- package/dist/src/verified-fetch.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +73 -0
- package/src/utils/parse-url-string.ts +17 -8
- package/src/utils/responses.ts +76 -7
- package/src/verified-fetch.ts +36 -19
package/src/index.ts
CHANGED
|
@@ -379,6 +379,79 @@
|
|
|
379
379
|
* const buf = await res.arrayBuffer() // raw bytes, not processed as JSON
|
|
380
380
|
* ```
|
|
381
381
|
*
|
|
382
|
+
* ## Redirects
|
|
383
|
+
*
|
|
384
|
+
* If a requested URL contains a path component, that path component resolves to
|
|
385
|
+
* a UnixFS directory, but the URL does not have a trailing slash, one will be
|
|
386
|
+
* added to form a canonical URL for that resource, otherwise the request will
|
|
387
|
+
* be resolved as normal.
|
|
388
|
+
*
|
|
389
|
+
* ```typescript
|
|
390
|
+
* import { verifiedFetch } from '@helia/verified-fetch'
|
|
391
|
+
*
|
|
392
|
+
* const res = await verifiedFetch('ipfs://bafyfoo/path/to/dir')
|
|
393
|
+
*
|
|
394
|
+
* console.info(res.url) // ipfs://bafyfoo/path/to/dir/
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* It's possible to prevent this behaviour and/or handle a redirect manually
|
|
398
|
+
* through use of the [redirect](https://developer.mozilla.org/en-US/docs/Web/API/fetch#redirect)
|
|
399
|
+
* option.
|
|
400
|
+
*
|
|
401
|
+
* @example Redirect: follow
|
|
402
|
+
*
|
|
403
|
+
* This is the default value and is what happens if no value is specified.
|
|
404
|
+
*
|
|
405
|
+
* ```typescript
|
|
406
|
+
* import { verifiedFetch } from '@helia/verified-fetch'
|
|
407
|
+
*
|
|
408
|
+
* const res = await verifiedFetch('ipfs://bafyfoo/path/to/dir', {
|
|
409
|
+
* redirect: 'follow'
|
|
410
|
+
* })
|
|
411
|
+
*
|
|
412
|
+
* console.info(res.status) // 200
|
|
413
|
+
* console.info(res.url) // ipfs://bafyfoo/path/to/dir/
|
|
414
|
+
* console.info(res.redirected) // true
|
|
415
|
+
* ```
|
|
416
|
+
*
|
|
417
|
+
* @example Redirect: error
|
|
418
|
+
*
|
|
419
|
+
* This causes a `TypeError` to be thrown if a URL would cause a redirect.
|
|
420
|
+
*
|
|
421
|
+
* ```typescript
|
|
422
|
+
*
|
|
423
|
+
* import { verifiedFetch } from '@helia/verified-fetch'
|
|
424
|
+
*
|
|
425
|
+
* const res = await verifiedFetch('ipfs://bafyfoo/path/to/dir', {
|
|
426
|
+
* redirect: 'error'
|
|
427
|
+
* })
|
|
428
|
+
* // throw TypeError('Failed to fetch')
|
|
429
|
+
* ```
|
|
430
|
+
*
|
|
431
|
+
* @example Redirect: manual
|
|
432
|
+
*
|
|
433
|
+
* Manual redirects allow the user to process the redirect. A [301](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301)
|
|
434
|
+
* is returned, and the location to redirect to is available as the "location"
|
|
435
|
+
* response header.
|
|
436
|
+
*
|
|
437
|
+
* This differs slightly from HTTP fetch which returns an opaque response as the
|
|
438
|
+
* browser itself is expected to process the redirect and hide all details from
|
|
439
|
+
* the user.
|
|
440
|
+
*
|
|
441
|
+
* ```typescript
|
|
442
|
+
*
|
|
443
|
+
* import { verifiedFetch } from '@helia/verified-fetch'
|
|
444
|
+
*
|
|
445
|
+
* const res = await verifiedFetch('ipfs://bafyfoo/path/to/dir', {
|
|
446
|
+
* redirect: 'manual'
|
|
447
|
+
* })
|
|
448
|
+
*
|
|
449
|
+
* console.info(res.status) // 301
|
|
450
|
+
* console.info(res.url) // ipfs://bafyfoo/path/to/dir
|
|
451
|
+
* console.info(res.redirected) // false
|
|
452
|
+
* console.info(res.headers.get('location')) // ipfs://bafyfoo/path/to/dir/
|
|
453
|
+
* ```
|
|
454
|
+
*
|
|
382
455
|
* ## Comparison to fetch
|
|
383
456
|
*
|
|
384
457
|
* This module attempts to act as similarly to the `fetch()` API as possible.
|
|
@@ -30,7 +30,22 @@ export interface ParsedUrlStringResults {
|
|
|
30
30
|
query: ParsedUrlQuery
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const URL_REGEX = /^(?<protocol>ip[fn]s):\/\/(?<cidOrPeerIdOrDnsLink>[
|
|
33
|
+
const URL_REGEX = /^(?<protocol>ip[fn]s):\/\/(?<cidOrPeerIdOrDnsLink>[^/?]+)\/?(?<path>[^?]*)\??(?<queryString>.*)$/
|
|
34
|
+
const PATH_REGEX = /^\/(?<protocol>ip[fn]s)\/(?<cidOrPeerIdOrDnsLink>[^/?]+)\/?(?<path>[^?]*)\??(?<queryString>.*)$/
|
|
35
|
+
const PATH_GATEWAY_REGEX = /^https?:\/\/(.*[^/])\/(?<protocol>ip[fn]s)\/(?<cidOrPeerIdOrDnsLink>[^/?]+)\/?(?<path>[^?]*)\??(?<queryString>.*)$/
|
|
36
|
+
const SUBDOMAIN_GATEWAY_REGEX = /^https?:\/\/(?<cidOrPeerIdOrDnsLink>[^/.?]+)\.(?<protocol>ip[fn]s)\.([^/?]+)\/?(?<path>[^?]*)\??(?<queryString>.*)$/
|
|
37
|
+
|
|
38
|
+
function matchURLString (urlString: string): Record<string, string> {
|
|
39
|
+
for (const pattern of [URL_REGEX, PATH_REGEX, PATH_GATEWAY_REGEX, SUBDOMAIN_GATEWAY_REGEX]) {
|
|
40
|
+
const match = urlString.match(pattern)
|
|
41
|
+
|
|
42
|
+
if (match?.groups != null) {
|
|
43
|
+
return match.groups
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new TypeError(`Invalid URL: ${urlString}, please use ipfs://, ipns://, or gateway URLs only`)
|
|
48
|
+
}
|
|
34
49
|
|
|
35
50
|
/**
|
|
36
51
|
* A function that parses ipfs:// and ipns:// URLs, returning an object with easily recognizable properties.
|
|
@@ -41,13 +56,7 @@ const URL_REGEX = /^(?<protocol>ip[fn]s):\/\/(?<cidOrPeerIdOrDnsLink>[^/$?]+)\/?
|
|
|
41
56
|
*/
|
|
42
57
|
export async function parseUrlString ({ urlString, ipns, logger }: ParseUrlStringInput, options?: ParseUrlStringOptions): Promise<ParsedUrlStringResults> {
|
|
43
58
|
const log = logger.forComponent('helia:verified-fetch:parse-url-string')
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
if (match == null || match.groups == null) {
|
|
47
|
-
throw new TypeError(`Invalid URL: ${urlString}, please use ipfs:// or ipns:// URLs only.`)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const { protocol, cidOrPeerIdOrDnsLink, path: urlPath, queryString } = match.groups
|
|
59
|
+
const { protocol, cidOrPeerIdOrDnsLink, path: urlPath, queryString } = matchURLString(urlString)
|
|
51
60
|
|
|
52
61
|
let cid: CID | undefined
|
|
53
62
|
let resolvedPath: string | undefined
|
package/src/utils/responses.ts
CHANGED
|
@@ -1,29 +1,98 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
function setField (response: Response, name: string, value: string | boolean): void {
|
|
2
|
+
Object.defineProperty(response, name, {
|
|
3
|
+
enumerable: true,
|
|
4
|
+
configurable: false,
|
|
5
|
+
set: () => {},
|
|
6
|
+
get: () => value
|
|
7
|
+
})
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function setType (response: Response, value: 'basic' | 'cors' | 'error' | 'opaque' | 'opaqueredirect'): void {
|
|
11
|
+
setField(response, 'type', value)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function setUrl (response: Response, value: string): void {
|
|
15
|
+
setField(response, 'url', value)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function setRedirected (response: Response): void {
|
|
19
|
+
setField(response, 'redirected', true)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ResponseOptions extends ResponseInit {
|
|
23
|
+
redirected?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function okResponse (url: string, body?: BodyInit | null, init?: ResponseOptions): Response {
|
|
27
|
+
const response = new Response(body, {
|
|
28
|
+
...(init ?? {}),
|
|
3
29
|
status: 200,
|
|
4
30
|
statusText: 'OK'
|
|
5
31
|
})
|
|
32
|
+
|
|
33
|
+
if (init?.redirected === true) {
|
|
34
|
+
setRedirected(response)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setType(response, 'basic')
|
|
38
|
+
setUrl(response, url)
|
|
39
|
+
|
|
40
|
+
return response
|
|
6
41
|
}
|
|
7
42
|
|
|
8
|
-
export function notSupportedResponse (body?: BodyInit | null): Response {
|
|
43
|
+
export function notSupportedResponse (url: string, body?: BodyInit | null, init?: ResponseInit): Response {
|
|
9
44
|
const response = new Response(body, {
|
|
45
|
+
...(init ?? {}),
|
|
10
46
|
status: 501,
|
|
11
47
|
statusText: 'Not Implemented'
|
|
12
48
|
})
|
|
13
49
|
response.headers.set('X-Content-Type-Options', 'nosniff') // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
|
|
50
|
+
|
|
51
|
+
setType(response, 'basic')
|
|
52
|
+
setUrl(response, url)
|
|
53
|
+
|
|
14
54
|
return response
|
|
15
55
|
}
|
|
16
56
|
|
|
17
|
-
export function notAcceptableResponse (body?: BodyInit | null): Response {
|
|
18
|
-
|
|
57
|
+
export function notAcceptableResponse (url: string, body?: BodyInit | null, init?: ResponseInit): Response {
|
|
58
|
+
const response = new Response(body, {
|
|
59
|
+
...(init ?? {}),
|
|
19
60
|
status: 406,
|
|
20
61
|
statusText: 'Not Acceptable'
|
|
21
62
|
})
|
|
63
|
+
|
|
64
|
+
setType(response, 'basic')
|
|
65
|
+
setUrl(response, url)
|
|
66
|
+
|
|
67
|
+
return response
|
|
22
68
|
}
|
|
23
69
|
|
|
24
|
-
export function badRequestResponse (body?: BodyInit | null): Response {
|
|
25
|
-
|
|
70
|
+
export function badRequestResponse (url: string, body?: BodyInit | null, init?: ResponseInit): Response {
|
|
71
|
+
const response = new Response(body, {
|
|
72
|
+
...(init ?? {}),
|
|
26
73
|
status: 400,
|
|
27
74
|
statusText: 'Bad Request'
|
|
28
75
|
})
|
|
76
|
+
|
|
77
|
+
setType(response, 'basic')
|
|
78
|
+
setUrl(response, url)
|
|
79
|
+
|
|
80
|
+
return response
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function movedPermanentlyResponse (url: string, location: string, init?: ResponseInit): Response {
|
|
84
|
+
const response = new Response(null, {
|
|
85
|
+
...(init ?? {}),
|
|
86
|
+
status: 301,
|
|
87
|
+
statusText: 'Moved Permanently',
|
|
88
|
+
headers: {
|
|
89
|
+
...(init?.headers ?? {}),
|
|
90
|
+
location
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
setType(response, 'basic')
|
|
95
|
+
setUrl(response, url)
|
|
96
|
+
|
|
97
|
+
return response
|
|
29
98
|
}
|
package/src/verified-fetch.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { getETag } from './utils/get-e-tag.js'
|
|
|
22
22
|
import { getStreamFromAsyncIterable } from './utils/get-stream-from-async-iterable.js'
|
|
23
23
|
import { tarStream } from './utils/get-tar-stream.js'
|
|
24
24
|
import { parseResource } from './utils/parse-resource.js'
|
|
25
|
-
import { badRequestResponse, notAcceptableResponse, notSupportedResponse, okResponse } from './utils/responses.js'
|
|
25
|
+
import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse } from './utils/responses.js'
|
|
26
26
|
import { selectOutputType, queryFormatToAcceptHeader } from './utils/select-output-type.js'
|
|
27
27
|
import { walkPath } from './utils/walk-path.js'
|
|
28
28
|
import type { CIDDetail, ContentTypeParser, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
|
|
@@ -167,7 +167,7 @@ export class VerifiedFetch {
|
|
|
167
167
|
const buf = await this.helia.datastore.get(datastoreKey, options)
|
|
168
168
|
const record = DHTRecord.deserialize(buf)
|
|
169
169
|
|
|
170
|
-
const response = okResponse(record.value)
|
|
170
|
+
const response = okResponse(resource, record.value)
|
|
171
171
|
response.headers.set('content-type', 'application/vnd.ipfs.ipns-record')
|
|
172
172
|
|
|
173
173
|
return response
|
|
@@ -177,11 +177,11 @@ export class VerifiedFetch {
|
|
|
177
177
|
* Accepts a `CID` and returns a `Response` with a body stream that is a CAR
|
|
178
178
|
* of the `DAG` referenced by the `CID`.
|
|
179
179
|
*/
|
|
180
|
-
private async handleCar ({ cid, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
180
|
+
private async handleCar ({ resource, cid, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
181
181
|
const c = car(this.helia)
|
|
182
182
|
const stream = toBrowserReadableStream(c.stream(cid, options))
|
|
183
183
|
|
|
184
|
-
const response = okResponse(stream)
|
|
184
|
+
const response = okResponse(resource, stream)
|
|
185
185
|
response.headers.set('content-type', 'application/vnd.ipld.car; version=1')
|
|
186
186
|
|
|
187
187
|
return response
|
|
@@ -191,20 +191,20 @@ export class VerifiedFetch {
|
|
|
191
191
|
* Accepts a UnixFS `CID` and returns a `.tar` file containing the file or
|
|
192
192
|
* directory structure referenced by the `CID`.
|
|
193
193
|
*/
|
|
194
|
-
private async handleTar ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
194
|
+
private async handleTar ({ resource, cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
195
195
|
if (cid.code !== dagPbCode && cid.code !== rawCode) {
|
|
196
196
|
return notAcceptableResponse('only UnixFS data can be returned in a TAR file')
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
const stream = toBrowserReadableStream<Uint8Array>(tarStream(`/ipfs/${cid}/${path}`, this.helia.blockstore, options))
|
|
200
200
|
|
|
201
|
-
const response = okResponse(stream)
|
|
201
|
+
const response = okResponse(resource, stream)
|
|
202
202
|
response.headers.set('content-type', 'application/x-tar')
|
|
203
203
|
|
|
204
204
|
return response
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
private async handleJson ({ cid, path, accept, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
207
|
+
private async handleJson ({ resource, cid, path, accept, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
208
208
|
this.log.trace('fetching %c/%s', cid, path)
|
|
209
209
|
const block = await this.helia.blockstore.get(cid, options)
|
|
210
210
|
let body: string | Uint8Array
|
|
@@ -218,19 +218,19 @@ export class VerifiedFetch {
|
|
|
218
218
|
body = ipldDagCbor.encode(obj)
|
|
219
219
|
} catch (err) {
|
|
220
220
|
this.log.error('could not transform %c to application/vnd.ipld.dag-cbor', err)
|
|
221
|
-
return notAcceptableResponse()
|
|
221
|
+
return notAcceptableResponse(resource)
|
|
222
222
|
}
|
|
223
223
|
} else {
|
|
224
224
|
// skip decoding
|
|
225
225
|
body = block
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
const response = okResponse(body)
|
|
228
|
+
const response = okResponse(resource, body)
|
|
229
229
|
response.headers.set('content-type', accept ?? 'application/json')
|
|
230
230
|
return response
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
private async handleDagCbor ({ cid, path, accept, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
233
|
+
private async handleDagCbor ({ resource, cid, path, accept, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
234
234
|
this.log.trace('fetching %c/%s', cid, path)
|
|
235
235
|
|
|
236
236
|
const block = await this.helia.blockstore.get(cid, options)
|
|
@@ -248,7 +248,7 @@ export class VerifiedFetch {
|
|
|
248
248
|
body = ipldDagJson.encode(obj)
|
|
249
249
|
} catch (err) {
|
|
250
250
|
this.log.error('could not transform %c to application/vnd.ipld.dag-json', err)
|
|
251
|
-
return notAcceptableResponse()
|
|
251
|
+
return notAcceptableResponse(resource)
|
|
252
252
|
}
|
|
253
253
|
} else {
|
|
254
254
|
try {
|
|
@@ -257,7 +257,7 @@ export class VerifiedFetch {
|
|
|
257
257
|
if (accept === 'application/json') {
|
|
258
258
|
this.log('could not decode DAG-CBOR as JSON-safe, but the client sent "Accept: application/json"', err)
|
|
259
259
|
|
|
260
|
-
return notAcceptableResponse()
|
|
260
|
+
return notAcceptableResponse(resource)
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
this.log('could not decode DAG-CBOR as JSON-safe, falling back to `application/octet-stream`', err)
|
|
@@ -265,7 +265,7 @@ export class VerifiedFetch {
|
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
const response = okResponse(body)
|
|
268
|
+
const response = okResponse(resource, body)
|
|
269
269
|
|
|
270
270
|
if (accept == null) {
|
|
271
271
|
accept = body instanceof Uint8Array ? 'application/octet-stream' : 'application/json'
|
|
@@ -276,9 +276,10 @@ export class VerifiedFetch {
|
|
|
276
276
|
return response
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
private async handleDagPb ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
279
|
+
private async handleDagPb ({ cid, path, resource, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
280
280
|
let terminalElement: UnixFSEntry | undefined
|
|
281
281
|
let ipfsRoots: CID[] | undefined
|
|
282
|
+
let redirected = false
|
|
282
283
|
|
|
283
284
|
try {
|
|
284
285
|
const pathDetails = await walkPath(this.helia.blockstore, `${cid.toString()}/${path}`, options)
|
|
@@ -293,6 +294,21 @@ export class VerifiedFetch {
|
|
|
293
294
|
if (terminalElement?.type === 'directory') {
|
|
294
295
|
const dirCid = terminalElement.cid
|
|
295
296
|
|
|
297
|
+
// https://specs.ipfs.tech/http-gateways/path-gateway/#use-in-directory-url-normalization
|
|
298
|
+
if (path !== '' && !path.endsWith('/')) {
|
|
299
|
+
if (options?.redirect === 'error') {
|
|
300
|
+
this.log('could not redirect to %s/ as redirect option was set to "error"', resource)
|
|
301
|
+
throw new TypeError('Failed to fetch')
|
|
302
|
+
} else if (options?.redirect === 'manual') {
|
|
303
|
+
this.log('returning 301 permanent redirect to %s/', resource)
|
|
304
|
+
return movedPermanentlyResponse(resource, `${resource}/`)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// fall-through simulates following the redirect?
|
|
308
|
+
resource = `${resource}/`
|
|
309
|
+
redirected = true
|
|
310
|
+
}
|
|
311
|
+
|
|
296
312
|
const rootFilePath = 'index.html'
|
|
297
313
|
try {
|
|
298
314
|
this.log.trace('found directory at %c/%s, looking for index.html', cid, path)
|
|
@@ -304,7 +320,6 @@ export class VerifiedFetch {
|
|
|
304
320
|
this.log.trace('found root file at %c/%s with cid %c', dirCid, rootFilePath, stat.cid)
|
|
305
321
|
path = rootFilePath
|
|
306
322
|
resolvedCID = stat.cid
|
|
307
|
-
// terminalElement = stat
|
|
308
323
|
} catch (err: any) {
|
|
309
324
|
this.log('error loading path %c/%s', dirCid, rootFilePath, err)
|
|
310
325
|
return notSupportedResponse('Unable to find index.html for directory at given path. Support for directories with implicit root is not implemented')
|
|
@@ -322,7 +337,9 @@ export class VerifiedFetch {
|
|
|
322
337
|
const { stream, firstChunk } = await getStreamFromAsyncIterable(asyncIter, path ?? '', this.helia.logger, {
|
|
323
338
|
onProgress: options?.onProgress
|
|
324
339
|
})
|
|
325
|
-
const response = okResponse(stream
|
|
340
|
+
const response = okResponse(resource, stream, {
|
|
341
|
+
redirected
|
|
342
|
+
})
|
|
326
343
|
await this.setContentType(firstChunk, path, response)
|
|
327
344
|
|
|
328
345
|
if (ipfsRoots != null) {
|
|
@@ -332,9 +349,9 @@ export class VerifiedFetch {
|
|
|
332
349
|
return response
|
|
333
350
|
}
|
|
334
351
|
|
|
335
|
-
private async handleRaw ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
352
|
+
private async handleRaw ({ resource, cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
336
353
|
const result = await this.helia.blockstore.get(cid, options)
|
|
337
|
-
const response = okResponse(result)
|
|
354
|
+
const response = okResponse(resource, result)
|
|
338
355
|
|
|
339
356
|
// if the user has specified an `Accept` header that corresponds to a raw
|
|
340
357
|
// type, honour that header, so for example they don't request
|
|
@@ -418,7 +435,7 @@ export class VerifiedFetch {
|
|
|
418
435
|
this.log('output type %s', accept)
|
|
419
436
|
|
|
420
437
|
if (acceptHeader != null && accept == null) {
|
|
421
|
-
return notAcceptableResponse()
|
|
438
|
+
return notAcceptableResponse(resource.toString())
|
|
422
439
|
}
|
|
423
440
|
|
|
424
441
|
let response: Response
|