@helia/verified-fetch 5.1.1 → 6.1.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/dist/index.min.js +61 -61
- package/dist/index.min.js.map +4 -4
- package/dist/src/index.d.ts +29 -6
- 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 +36 -18
- 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 +20 -57
- package/dist/src/url-resolver.js.map +1 -1
- package/dist/src/utils/error-to-response.d.ts +1 -1
- package/dist/src/utils/error-to-response.d.ts.map +1 -1
- package/dist/src/utils/error-to-response.js +14 -12
- package/dist/src/utils/error-to-response.js.map +1 -1
- package/dist/src/utils/get-range-header.d.ts +2 -1
- package/dist/src/utils/get-range-header.d.ts.map +1 -1
- package/dist/src/utils/get-range-header.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/utils/parse-resource.d.ts +10 -0
- package/dist/src/utils/parse-resource.d.ts.map +1 -0
- package/dist/src/utils/parse-resource.js +52 -0
- package/dist/src/utils/parse-resource.js.map +1 -0
- package/dist/src/utils/responses.d.ts +14 -14
- package/dist/src/utils/responses.d.ts.map +1 -1
- package/dist/src/utils/responses.js +8 -3
- package/dist/src/utils/responses.js.map +1 -1
- package/dist/src/verified-fetch.d.ts +1 -0
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +117 -119
- package/dist/src/verified-fetch.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +30 -6
- 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 +42 -19
- package/src/url-resolver.ts +22 -65
- package/src/utils/error-to-response.ts +15 -12
- package/src/utils/get-range-header.ts +2 -1
- package/src/utils/get-tar-stream.ts +26 -10
- package/src/utils/parse-resource.ts +62 -0
- package/src/utils/responses.ts +24 -19
- package/src/verified-fetch.ts +123 -122
- package/dist/src/utils/ipfs-path-to-url.d.ts +0 -16
- package/dist/src/utils/ipfs-path-to-url.d.ts.map +0 -1
- package/dist/src/utils/ipfs-path-to-url.js +0 -45
- package/dist/src/utils/ipfs-path-to-url.js.map +0 -1
- package/dist/src/utils/parse-url-string.d.ts +0 -23
- package/dist/src/utils/parse-url-string.d.ts.map +0 -1
- package/dist/src/utils/parse-url-string.js +0 -120
- package/dist/src/utils/parse-url-string.js.map +0 -1
- package/dist/src/utils/resource-to-cache-key.d.ts +0 -15
- package/dist/src/utils/resource-to-cache-key.d.ts.map +0 -1
- package/dist/src/utils/resource-to-cache-key.js +0 -27
- package/dist/src/utils/resource-to-cache-key.js.map +0 -1
- package/src/utils/ipfs-path-to-url.ts +0 -54
- package/src/utils/parse-url-string.ts +0 -165
- package/src/utils/resource-to-cache-key.ts +0 -30
package/src/utils/responses.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import itToBrowserReadableStream from 'it-to-browser-readablestream'
|
|
2
2
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
3
|
+
import { errorToObject } from './error-to-object.ts'
|
|
3
4
|
import { rangeToOffsetAndLength } from './get-offset-and-length.ts'
|
|
4
5
|
import { getContentRangeHeader } from './response-headers.ts'
|
|
5
|
-
import type { SupportedBodyTypes, ContentType } from '../index.js'
|
|
6
|
+
import type { SupportedBodyTypes, ContentType, Resource } from '../index.js'
|
|
6
7
|
import type { Range, RangeHeader } from './get-range-header.ts'
|
|
7
8
|
|
|
8
9
|
function setField (response: Response, name: string, value: string | boolean): void {
|
|
@@ -20,7 +21,7 @@ function setType (response: Response, value: 'basic' | 'cors' | 'error' | 'opaqu
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function setUrl (response: Response, value:
|
|
24
|
+
function setUrl (response: Response, value: Resource): void {
|
|
24
25
|
value = value.toString()
|
|
25
26
|
const fragmentStart = value.indexOf('#')
|
|
26
27
|
|
|
@@ -41,7 +42,7 @@ export interface ResponseOptions extends ResponseInit {
|
|
|
41
42
|
redirected?: boolean
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
export function okResponse (url:
|
|
45
|
+
export function okResponse (url: Resource, body?: SupportedBodyTypes, init?: ResponseOptions): Response {
|
|
45
46
|
const response = new Response(body, {
|
|
46
47
|
...(init ?? {}),
|
|
47
48
|
status: 200,
|
|
@@ -58,13 +59,17 @@ export function okResponse (url: string, body?: SupportedBodyTypes, init?: Respo
|
|
|
58
59
|
return response
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
export function internalServerErrorResponse (url:
|
|
62
|
-
const response = new Response(
|
|
62
|
+
export function internalServerErrorResponse (url: Resource, err: Error, init?: ResponseInit): Response {
|
|
63
|
+
const response = new Response(JSON.stringify({
|
|
64
|
+
error: errorToObject(err)
|
|
65
|
+
}), {
|
|
63
66
|
...(init ?? {}),
|
|
64
67
|
status: 500,
|
|
65
68
|
statusText: 'Internal Server Error'
|
|
66
69
|
})
|
|
67
70
|
response.headers.set('X-Content-Type-Options', 'nosniff') // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
|
|
71
|
+
response.headers.set('content-type', 'application/json')
|
|
72
|
+
response.headers.set('x-error-message', btoa(err.message))
|
|
68
73
|
|
|
69
74
|
setType(response, 'basic')
|
|
70
75
|
setUrl(response, url)
|
|
@@ -75,7 +80,7 @@ export function internalServerErrorResponse (url: string, body?: SupportedBodyTy
|
|
|
75
80
|
/**
|
|
76
81
|
* A 504 Gateway Timeout for when a request made to an upstream server timed out
|
|
77
82
|
*/
|
|
78
|
-
export function gatewayTimeoutResponse (url:
|
|
83
|
+
export function gatewayTimeoutResponse (url: Resource, body?: SupportedBodyTypes, init?: ResponseInit): Response {
|
|
79
84
|
const response = new Response(body, {
|
|
80
85
|
...(init ?? {}),
|
|
81
86
|
status: 504,
|
|
@@ -92,7 +97,7 @@ export function gatewayTimeoutResponse (url: string, body?: SupportedBodyTypes,
|
|
|
92
97
|
* A 502 Bad Gateway is for when an invalid response was received from an
|
|
93
98
|
* upstream server.
|
|
94
99
|
*/
|
|
95
|
-
export function badGatewayResponse (url:
|
|
100
|
+
export function badGatewayResponse (url: Resource, body?: SupportedBodyTypes, init?: ResponseInit): Response {
|
|
96
101
|
const response = new Response(body, {
|
|
97
102
|
...(init ?? {}),
|
|
98
103
|
status: 502,
|
|
@@ -105,7 +110,7 @@ export function badGatewayResponse (url: string, body?: SupportedBodyTypes, init
|
|
|
105
110
|
return response
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
export function notImplementedResponse (url:
|
|
113
|
+
export function notImplementedResponse (url: Resource, body?: SupportedBodyTypes, init?: ResponseInit): Response {
|
|
109
114
|
const response = new Response(body, {
|
|
110
115
|
...(init ?? {}),
|
|
111
116
|
status: 501,
|
|
@@ -119,7 +124,7 @@ export function notImplementedResponse (url: string, body?: SupportedBodyTypes,
|
|
|
119
124
|
return response
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
export function notAcceptableResponse (url:
|
|
127
|
+
export function notAcceptableResponse (url: Resource, requested: Array<Pick<ContentType, 'mediaType'>>, acceptable: Array<Pick<ContentType, 'mediaType'>>, init?: ResponseInit): Response {
|
|
123
128
|
const headers = new Headers(init?.headers)
|
|
124
129
|
headers.set('content-type', 'application/json')
|
|
125
130
|
|
|
@@ -139,7 +144,7 @@ export function notAcceptableResponse (url: string | URL, requested: Array<Pick<
|
|
|
139
144
|
return response
|
|
140
145
|
}
|
|
141
146
|
|
|
142
|
-
export function notFoundResponse (url:
|
|
147
|
+
export function notFoundResponse (url: Resource, body?: SupportedBodyTypes, init?: ResponseInit): Response {
|
|
143
148
|
const response = new Response(body, {
|
|
144
149
|
...(init ?? {}),
|
|
145
150
|
status: 404,
|
|
@@ -156,7 +161,7 @@ function isArrayOfErrors (body: unknown | Error | Error[]): body is Error[] {
|
|
|
156
161
|
return Array.isArray(body) && body.every(e => e instanceof Error)
|
|
157
162
|
}
|
|
158
163
|
|
|
159
|
-
export function badRequestResponse (url:
|
|
164
|
+
export function badRequestResponse (url: Resource, errors: Error | Error[], init?: ResponseInit): Response {
|
|
160
165
|
// stacktrace of the single error, or the stacktrace of the last error in the array
|
|
161
166
|
let stack: string | undefined
|
|
162
167
|
let convertedErrors: Array<{ message: string, stack: string }> | undefined
|
|
@@ -190,7 +195,7 @@ export function badRequestResponse (url: string, errors: Error | Error[], init?:
|
|
|
190
195
|
return response
|
|
191
196
|
}
|
|
192
197
|
|
|
193
|
-
export function movedPermanentlyResponse (url:
|
|
198
|
+
export function movedPermanentlyResponse (url: Resource, location: string, init?: ResponseInit): Response {
|
|
194
199
|
const response = new Response(null, {
|
|
195
200
|
...(init ?? {}),
|
|
196
201
|
status: 301,
|
|
@@ -207,7 +212,7 @@ export function movedPermanentlyResponse (url: string, location: string, init?:
|
|
|
207
212
|
return response
|
|
208
213
|
}
|
|
209
214
|
|
|
210
|
-
export function notModifiedResponse (url:
|
|
215
|
+
export function notModifiedResponse (url: Resource, headers: Headers, init?: ResponseInit): Response {
|
|
211
216
|
const response = new Response(null, {
|
|
212
217
|
...(init ?? {}),
|
|
213
218
|
status: 304,
|
|
@@ -248,7 +253,7 @@ export interface PartialContent {
|
|
|
248
253
|
(offset: number, length: number): AsyncGenerator<Uint8Array>
|
|
249
254
|
}
|
|
250
255
|
|
|
251
|
-
export function partialContentResponse (url:
|
|
256
|
+
export function partialContentResponse (url: Resource, getSlice: PartialContent, range: RangeHeader, documentSize: number | bigint, init?: ResponseOptions): Response {
|
|
252
257
|
let response: Response
|
|
253
258
|
|
|
254
259
|
if (range.ranges.length === 1) {
|
|
@@ -269,7 +274,7 @@ export function partialContentResponse (url: string, getSlice: PartialContent, r
|
|
|
269
274
|
return response
|
|
270
275
|
}
|
|
271
276
|
|
|
272
|
-
function singleRangeResponse (url:
|
|
277
|
+
function singleRangeResponse (url: Resource, getSlice: PartialContent, range: Range, documentSize: number | bigint, init?: ResponseOptions): Response {
|
|
273
278
|
try {
|
|
274
279
|
// create headers object with any initial headers from init
|
|
275
280
|
const headers = new Headers(init?.headers)
|
|
@@ -293,14 +298,14 @@ function singleRangeResponse (url: string, getSlice: PartialContent, range: Rang
|
|
|
293
298
|
return notSatisfiableResponse(url, documentSize, init)
|
|
294
299
|
}
|
|
295
300
|
|
|
296
|
-
return internalServerErrorResponse(url,
|
|
301
|
+
return internalServerErrorResponse(url, err, init)
|
|
297
302
|
}
|
|
298
303
|
}
|
|
299
304
|
|
|
300
305
|
/**
|
|
301
306
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Range_requests
|
|
302
307
|
*/
|
|
303
|
-
function multiRangeResponse (url:
|
|
308
|
+
function multiRangeResponse (url: Resource, getSlice: PartialContent, range: RangeHeader, documentSize: number | bigint, init?: ResponseOptions): Response {
|
|
304
309
|
// create headers object with any initial headers from init
|
|
305
310
|
const headers = new Headers(init?.headers)
|
|
306
311
|
|
|
@@ -375,7 +380,7 @@ function multiRangeResponse (url: string, getSlice: PartialContent, range: Range
|
|
|
375
380
|
* - The range is invalid
|
|
376
381
|
* - The range is not supported for the given type
|
|
377
382
|
*/
|
|
378
|
-
export function notSatisfiableResponse (url:
|
|
383
|
+
export function notSatisfiableResponse (url: Resource, documentSize?: number | bigint | string, init?: ResponseInit): Response {
|
|
379
384
|
const headers = new Headers(init?.headers)
|
|
380
385
|
|
|
381
386
|
if (documentSize != null) {
|
|
@@ -402,7 +407,7 @@ export function notSatisfiableResponse (url: string, documentSize?: number | big
|
|
|
402
407
|
*
|
|
403
408
|
* @see https://specs.ipfs.tech/http-gateways/path-gateway/#412-precondition-failed
|
|
404
409
|
*/
|
|
405
|
-
export function preconditionFailedResponse (url:
|
|
410
|
+
export function preconditionFailedResponse (url: Resource, init?: ResponseInit): Response {
|
|
406
411
|
const headers = new Headers(init?.headers)
|
|
407
412
|
|
|
408
413
|
const response = new Response('Precondition Failed', {
|
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'
|
|
@@ -17,11 +18,11 @@ import { errorToObject } from './utils/error-to-object.ts'
|
|
|
17
18
|
import { errorToResponse } from './utils/error-to-response.ts'
|
|
18
19
|
import { getETag, ifNoneMatches } from './utils/get-e-tag.js'
|
|
19
20
|
import { getRangeHeader } from './utils/get-range-header.ts'
|
|
20
|
-
import {
|
|
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
|
-
import type { AcceptHeader, CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions,
|
|
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'
|
|
26
27
|
import type { Helia } from '@helia/interface'
|
|
27
28
|
import type { IPNSResolver } from '@helia/ipns'
|
|
@@ -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)) ?? []
|
|
@@ -138,127 +140,120 @@ export class VerifiedFetch {
|
|
|
138
140
|
async fetch (resource: Resource, opts?: VerifiedFetchOptions): Promise<Response> {
|
|
139
141
|
this.log('fetch %s %s', opts?.method ?? 'GET', resource)
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const options = convertOptions(opts)
|
|
148
|
-
const headers = new Headers(options?.headers)
|
|
149
|
-
const serverTiming = new ServerTiming()
|
|
143
|
+
try {
|
|
144
|
+
if (opts?.method === 'OPTIONS') {
|
|
145
|
+
return this.handleFinalResponse(new Response(null, {
|
|
146
|
+
status: 200
|
|
147
|
+
}))
|
|
148
|
+
}
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
const options = convertOptions(opts)
|
|
151
|
+
const headers = new Headers(options?.headers)
|
|
152
|
+
const serverTiming = new ServerTiming()
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
if (options != null) {
|
|
155
|
+
options.offline ??= headers.get('cache-control') === 'only-if-cached'
|
|
156
|
+
}
|
|
154
157
|
|
|
155
|
-
|
|
156
|
-
// invalid range request
|
|
157
|
-
return this.handleFinalResponse(range)
|
|
158
|
-
}
|
|
158
|
+
options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
const range = getRangeHeader(resource, headers)
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
162
|
+
if (range instanceof Response) {
|
|
163
|
+
// invalid range request
|
|
164
|
+
return this.handleFinalResponse(range)
|
|
165
|
+
}
|
|
167
166
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
167
|
+
const url = this.parseResource(resource)
|
|
168
|
+
|
|
169
|
+
if (url.protocol === 'ipfs:' && url.pathname === '') {
|
|
170
|
+
// if we don't need to resolve an IPNS names or traverse a DAG, we can
|
|
171
|
+
// check the if-none-match header and maybe return a 304 without needing
|
|
172
|
+
// to load any blocks
|
|
173
|
+
if (ifNoneMatches(`"${url.hostname}"`, headers)) {
|
|
174
|
+
return notModifiedResponse(resource, new Headers({
|
|
175
|
+
etag: `"${url.hostname}"`,
|
|
176
|
+
'cache-control': 'public, max-age=29030400, immutable'
|
|
177
|
+
}))
|
|
178
|
+
}
|
|
177
179
|
}
|
|
178
|
-
}
|
|
179
180
|
|
|
180
|
-
|
|
181
|
+
const requestedMimeTypes = getRequestedMimeTypes(url, headers.get('accept'))
|
|
181
182
|
|
|
182
|
-
|
|
183
|
+
// if a raw IPNS record has been requested, don't try to load the block
|
|
184
|
+
// the record points to or do any recursive IPNS resolving
|
|
185
|
+
if (isIPNSRecordRequest(headers)) {
|
|
186
|
+
if (url.protocol !== 'ipns:') {
|
|
187
|
+
return notAcceptableResponse(url, requestedMimeTypes, [
|
|
188
|
+
CONTENT_TYPE_IPNS
|
|
189
|
+
])
|
|
190
|
+
}
|
|
183
191
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (isIPNSRecordRequest(headers)) {
|
|
187
|
-
if (url.protocol !== 'ipns:') {
|
|
188
|
-
return notAcceptableResponse(url, requestedMimeTypes, [
|
|
189
|
-
CONTENT_TYPE_IPNS
|
|
190
|
-
])
|
|
191
|
-
}
|
|
192
|
+
// @ts-expect-error ipnsRecordPlugin may not be of type IpnsRecordPlugin
|
|
193
|
+
const ipnsRecordPlugin: IpnsRecordPlugin | undefined = this.plugins.find(plugin => plugin.id === 'ipns-record-plugin')
|
|
192
194
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
if (ipnsRecordPlugin == null) {
|
|
196
|
+
// IPNS record was requested but no IPNS Record plugin is configured?!
|
|
197
|
+
return notAcceptableResponse(resource, requestedMimeTypes, [])
|
|
198
|
+
}
|
|
195
199
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
return this.handleFinalResponse(await ipnsRecordPlugin.handle({
|
|
201
|
+
range,
|
|
202
|
+
url,
|
|
203
|
+
resource,
|
|
204
|
+
options
|
|
205
|
+
}))
|
|
199
206
|
}
|
|
200
207
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
const resolveResult = await this.urlResolver.resolve(url, serverTiming, {
|
|
209
|
+
...options,
|
|
210
|
+
isRawBlockRequest: isRawBlockRequest(headers)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', {
|
|
214
|
+
cid: resolveResult.terminalElement.cid,
|
|
215
|
+
path: resolveResult.url.pathname
|
|
206
216
|
}))
|
|
207
|
-
} else {
|
|
208
|
-
try {
|
|
209
|
-
parsedResult = await this.urlResolver.resolve(url, serverTiming, {
|
|
210
|
-
...options,
|
|
211
|
-
isRawBlockRequest: isRawBlockRequest(headers),
|
|
212
|
-
onlyIfCached: headers.get('cache-control') === 'only-if-cached'
|
|
213
|
-
})
|
|
214
|
-
} catch (err: any) {
|
|
215
|
-
options?.signal?.throwIfAborted()
|
|
216
|
-
|
|
217
|
-
this.log.error('error parsing resource %s - %e', resource, err)
|
|
218
|
-
return this.handleFinalResponse(errorToResponse(resource, err))
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
217
|
|
|
222
|
-
|
|
223
|
-
cid: parsedResult.terminalElement.cid,
|
|
224
|
-
path: parsedResult.url.pathname
|
|
225
|
-
}))
|
|
218
|
+
const accept = this.getAcceptHeader(resolveResult.url, requestedMimeTypes, resolveResult.terminalElement.cid)
|
|
226
219
|
|
|
227
|
-
|
|
220
|
+
if (accept instanceof Response) {
|
|
221
|
+
this.log('allowed media types for requested CID did not contain anything the client can understand')
|
|
228
222
|
|
|
229
|
-
|
|
230
|
-
|
|
223
|
+
// invalid accept header
|
|
224
|
+
return this.handleFinalResponse(accept)
|
|
225
|
+
}
|
|
231
226
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
227
|
+
const context: PluginContext = {
|
|
228
|
+
...resolveResult,
|
|
229
|
+
resource,
|
|
230
|
+
accept,
|
|
231
|
+
range,
|
|
232
|
+
options,
|
|
233
|
+
onProgress: options?.onProgress,
|
|
234
|
+
serverTiming,
|
|
235
|
+
headers,
|
|
236
|
+
requestedMimeTypes
|
|
237
|
+
}
|
|
235
238
|
|
|
236
|
-
|
|
237
|
-
...parsedResult,
|
|
238
|
-
resource: resource.toString(),
|
|
239
|
-
accept,
|
|
240
|
-
range,
|
|
241
|
-
options,
|
|
242
|
-
onProgress: options?.onProgress,
|
|
243
|
-
serverTiming,
|
|
244
|
-
headers,
|
|
245
|
-
requestedMimeTypes
|
|
246
|
-
}
|
|
239
|
+
this.log.trace('finding handler for cid code "0x%s" and response content types %s', resolveResult.terminalElement.cid.code.toString(16), accept.map(header => header.contentType.mediaType).join(', '))
|
|
247
240
|
|
|
248
|
-
|
|
241
|
+
const response = await this.runPluginPipeline(context)
|
|
249
242
|
|
|
250
|
-
|
|
243
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', {
|
|
244
|
+
cid: resolveResult.terminalElement.cid,
|
|
245
|
+
path: resolveResult.url.pathname
|
|
246
|
+
}))
|
|
251
247
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}))
|
|
248
|
+
if (response == null) {
|
|
249
|
+
this.log.error('no plugin could handle request for %s', resource)
|
|
250
|
+
}
|
|
256
251
|
|
|
257
|
-
|
|
258
|
-
|
|
252
|
+
return this.handleFinalResponse(response, Boolean(options?.withServerTiming) || Boolean(this.withServerTiming), context)
|
|
253
|
+
} catch (err: any) {
|
|
254
|
+
this.log.error('error fetching resource %s - %e', resource, err)
|
|
255
|
+
return this.handleFinalResponse(errorToResponse(resource, err, opts))
|
|
259
256
|
}
|
|
260
|
-
|
|
261
|
-
return this.handleFinalResponse(response, Boolean(options?.withServerTiming) || Boolean(this.withServerTiming), context)
|
|
262
257
|
}
|
|
263
258
|
|
|
264
259
|
/**
|
|
@@ -315,6 +310,18 @@ export class VerifiedFetch {
|
|
|
315
310
|
return acceptable
|
|
316
311
|
}
|
|
317
312
|
|
|
313
|
+
private parseResource (resource: Resource): URL {
|
|
314
|
+
if (isPeerId(resource) || isPublicKey(resource)) {
|
|
315
|
+
resource = `/ipns/${resource}`
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (CID.asCID(resource) === resource || resource instanceof CID) {
|
|
319
|
+
resource = `/ipfs/${resource}`
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return new URL(stringToIpfsUrl(resource.toString()))
|
|
323
|
+
}
|
|
324
|
+
|
|
318
325
|
/**
|
|
319
326
|
* The last place a Response touches in verified-fetch before being returned
|
|
320
327
|
* to the user. This is where we add the Server-Timing header to the response
|
|
@@ -345,7 +352,7 @@ export class VerifiedFetch {
|
|
|
345
352
|
const decodedPath = decodeURI(context?.url.pathname)
|
|
346
353
|
const path = uint8ArrayToString(uint8ArrayFromString(decodedPath), 'ascii')
|
|
347
354
|
|
|
348
|
-
response.headers.set('x-ipfs-path', `/${context.url.protocol === 'ipfs:' ? 'ipfs' : 'ipns'}/${context?.url.hostname}${path}`)
|
|
355
|
+
response.headers.set('x-ipfs-path', `/${context.url.protocol === 'ipfs:' ? 'ipfs' : 'ipns'}/${context?.url.hostname}${path === '/' ? '' : path}`)
|
|
349
356
|
}
|
|
350
357
|
|
|
351
358
|
// set CORS headers. If hosting your own gateway with verified-fetch behind
|
|
@@ -406,35 +413,29 @@ export class VerifiedFetch {
|
|
|
406
413
|
let pluginHandled = false
|
|
407
414
|
|
|
408
415
|
for (const plugin of plugins) {
|
|
409
|
-
try {
|
|
410
|
-
|
|
411
|
-
|
|
416
|
+
// try {
|
|
417
|
+
this.log('invoking plugin: %s', plugin.id)
|
|
418
|
+
pluginsUsed.add(plugin.id)
|
|
412
419
|
|
|
413
|
-
|
|
420
|
+
const maybeResponse = await plugin.handle(context)
|
|
414
421
|
|
|
415
|
-
|
|
422
|
+
this.log('plugin response %s %o', plugin.id, maybeResponse)
|
|
416
423
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
} 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) {
|
|
424
431
|
if (context.options?.signal?.aborted) {
|
|
425
432
|
throw new AbortError(context.options?.signal?.reason)
|
|
426
433
|
}
|
|
427
434
|
|
|
428
435
|
this.log.error('error in plugin %s - %e', plugin.id, err)
|
|
429
436
|
|
|
430
|
-
return internalServerErrorResponse(context.resource,
|
|
431
|
-
|
|
432
|
-
}), {
|
|
433
|
-
headers: {
|
|
434
|
-
'content-type': 'application/json'
|
|
435
|
-
}
|
|
436
|
-
})
|
|
437
|
-
}
|
|
437
|
+
return internalServerErrorResponse(context.resource, err)
|
|
438
|
+
} */
|
|
438
439
|
|
|
439
440
|
if (finalResponse != null) {
|
|
440
441
|
this.log.trace('plugin %s produced final response', plugin.id)
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Turns an IPFS or IPNS path into a HTTP URL. Path gateway syntax is used to
|
|
3
|
-
* preserve any case sensitivity
|
|
4
|
-
*
|
|
5
|
-
* - `/ipfs/cid` -> `https://example.org/ipfs/cid`
|
|
6
|
-
* - `/ipns/name` -> `https://example.org/ipns/name`
|
|
7
|
-
*/
|
|
8
|
-
export declare function ipfsPathToUrl(path: string): string;
|
|
9
|
-
/**
|
|
10
|
-
* Turns an IPFS or IPNS URL into a HTTP URL. Path gateway syntax is used to
|
|
11
|
-
* preserve and case sensitivity
|
|
12
|
-
*
|
|
13
|
-
* `ipfs://cid` -> `https://example.org/ipfs/cid`
|
|
14
|
-
*/
|
|
15
|
-
export declare function ipfsUrlToUrl(url: string): string;
|
|
16
|
-
//# sourceMappingURL=ipfs-path-to-url.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ipfs-path-to-url.d.ts","sourceRoot":"","sources":["../../../src/utils/ipfs-path-to-url.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA2BnD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CASjD"}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { InvalidParametersError } from '@libp2p/interface';
|
|
2
|
-
/**
|
|
3
|
-
* Turns an IPFS or IPNS path into a HTTP URL. Path gateway syntax is used to
|
|
4
|
-
* preserve any case sensitivity
|
|
5
|
-
*
|
|
6
|
-
* - `/ipfs/cid` -> `https://example.org/ipfs/cid`
|
|
7
|
-
* - `/ipns/name` -> `https://example.org/ipns/name`
|
|
8
|
-
*/
|
|
9
|
-
export function ipfsPathToUrl(path) {
|
|
10
|
-
if (!path.startsWith('/ipfs/') && !path.startsWith('/ipns/')) {
|
|
11
|
-
throw new InvalidParametersError(`Path ${path} did not start with /ipfs/ or /ipns/`);
|
|
12
|
-
}
|
|
13
|
-
// trim fragment
|
|
14
|
-
const fragmentIndex = path.indexOf('#');
|
|
15
|
-
let fragment = '';
|
|
16
|
-
if (fragmentIndex > -1) {
|
|
17
|
-
fragment = path.substring(fragmentIndex);
|
|
18
|
-
path = path.substring(0, fragmentIndex);
|
|
19
|
-
}
|
|
20
|
-
// trim query
|
|
21
|
-
const queryIndex = path.indexOf('?');
|
|
22
|
-
let query = '';
|
|
23
|
-
if (queryIndex > -1) {
|
|
24
|
-
query = path.substring(queryIndex);
|
|
25
|
-
path = path.substring(0, queryIndex);
|
|
26
|
-
}
|
|
27
|
-
const type = path.substring(1, 5);
|
|
28
|
-
const rest = path.substring(6);
|
|
29
|
-
return `https://example.org/${type}/${rest}${query}${fragment}`;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Turns an IPFS or IPNS URL into a HTTP URL. Path gateway syntax is used to
|
|
33
|
-
* preserve and case sensitivity
|
|
34
|
-
*
|
|
35
|
-
* `ipfs://cid` -> `https://example.org/ipfs/cid`
|
|
36
|
-
*/
|
|
37
|
-
export function ipfsUrlToUrl(url) {
|
|
38
|
-
if (!url.startsWith('ipfs://') && !url.startsWith('ipns://')) {
|
|
39
|
-
throw new InvalidParametersError(`URL ${url} did not start with ipfs:// or ipns://`);
|
|
40
|
-
}
|
|
41
|
-
const type = url.substring(0, 4);
|
|
42
|
-
const rest = url.substring(7);
|
|
43
|
-
return `https://example.org/${type}/${rest}`;
|
|
44
|
-
}
|
|
45
|
-
//# sourceMappingURL=ipfs-path-to-url.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ipfs-path-to-url.js","sourceRoot":"","sources":["../../../src/utils/ipfs-path-to-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAE1D;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAE,IAAY;IACzC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,sBAAsB,CAAC,QAAQ,IAAI,sCAAsC,CAAC,CAAA;IACtF,CAAC;IAED,gBAAgB;IAChB,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACvC,IAAI,QAAQ,GAAG,EAAE,CAAA;IAEjB,IAAI,aAAa,GAAG,CAAC,CAAC,EAAE,CAAC;QACvB,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAA;QACxC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,aAAa,CAAC,CAAA;IACzC,CAAC;IAED,aAAa;IACb,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACpC,IAAI,KAAK,GAAG,EAAE,CAAA;IAEd,IAAI,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC;QACpB,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QAClC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;IACtC,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IAE9B,OAAO,uBAAuB,IAAI,IAAI,IAAI,GAAG,KAAK,GAAG,QAAQ,EAAE,CAAA;AACjE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAE,GAAW;IACvC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,sBAAsB,CAAC,OAAO,GAAG,wCAAwC,CAAC,CAAA;IACtF,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAChC,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IAE7B,OAAO,uBAAuB,IAAI,IAAI,IAAI,EAAE,CAAA;AAC9C,CAAC"}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export declare const SUBDOMAIN_GATEWAY_REGEX: RegExp;
|
|
2
|
-
export interface ParsedURL {
|
|
3
|
-
url: URL;
|
|
4
|
-
protocol: 'ipfs' | 'ipns';
|
|
5
|
-
cidOrPeerIdOrDnsLink: string;
|
|
6
|
-
path: string[];
|
|
7
|
-
query: Record<string, any>;
|
|
8
|
-
fragment: string;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Accepts the following url strings:
|
|
12
|
-
*
|
|
13
|
-
* - /ipfs/Qmfoo/path
|
|
14
|
-
* - /ipns/Qmfoo/path
|
|
15
|
-
* - ipfs://cid/path
|
|
16
|
-
* - ipns://name/path
|
|
17
|
-
* - http://cid.ipfs.example.com/path
|
|
18
|
-
* - http://name.ipns.example.com/path
|
|
19
|
-
* - http://example.com/ipfs/cid/path
|
|
20
|
-
* - http://example.com/ipns/name/path
|
|
21
|
-
*/
|
|
22
|
-
export declare function parseURLString(urlString: string): URL;
|
|
23
|
-
//# sourceMappingURL=parse-url-string.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parse-url-string.d.ts","sourceRoot":"","sources":["../../../src/utils/parse-url-string.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,uBAAuB,QAAsE,CAAA;AAmB1G,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACjB;AA4FD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAE,SAAS,EAAE,MAAM,GAAG,GAAG,CAuBtD"}
|