@helia/verified-fetch 5.1.1 → 6.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/dist/index.min.js +52 -52
- package/dist/index.min.js.map +4 -4
- package/dist/src/index.d.ts +20 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/plugins/plugin-handle-unixfs.d.ts.map +1 -1
- package/dist/src/plugins/plugin-handle-unixfs.js +6 -6
- package/dist/src/plugins/plugin-handle-unixfs.js.map +1 -1
- package/dist/src/url-resolver.d.ts.map +1 -1
- package/dist/src/url-resolver.js +1 -2
- 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 +13 -11
- 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/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 +94 -101
- package/dist/src/verified-fetch.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +20 -4
- package/src/plugins/plugin-handle-unixfs.ts +9 -8
- package/src/url-resolver.ts +1 -2
- package/src/utils/error-to-response.ts +14 -11
- package/src/utils/get-range-header.ts +2 -1
- package/src/utils/parse-resource.ts +62 -0
- package/src/utils/responses.ts +24 -19
- package/src/verified-fetch.ts +105 -109
- 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 { AbortError } from '@libp2p/interface'
|
|
3
|
+
import { AbortError, 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'
|
|
@@ -17,11 +17,11 @@ import { errorToObject } from './utils/error-to-object.ts'
|
|
|
17
17
|
import { errorToResponse } from './utils/error-to-response.ts'
|
|
18
18
|
import { getETag, ifNoneMatches } from './utils/get-e-tag.js'
|
|
19
19
|
import { getRangeHeader } from './utils/get-range-header.ts'
|
|
20
|
-
import {
|
|
20
|
+
import { stringToIpfsUrl } from './utils/parse-resource.ts'
|
|
21
21
|
import { setCacheControlHeader } from './utils/response-headers.js'
|
|
22
|
-
import {
|
|
22
|
+
import { internalServerErrorResponse, notAcceptableResponse, notImplementedResponse, notModifiedResponse } from './utils/responses.js'
|
|
23
23
|
import { ServerTiming } from './utils/server-timing.js'
|
|
24
|
-
import type { AcceptHeader, CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions,
|
|
24
|
+
import type { AcceptHeader, CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions, VerifiedFetchPlugin, PluginContext, PluginOptions } from './index.js'
|
|
25
25
|
import type { DNSLink } from '@helia/dnslink'
|
|
26
26
|
import type { Helia } from '@helia/interface'
|
|
27
27
|
import type { IPNSResolver } from '@helia/ipns'
|
|
@@ -138,127 +138,117 @@ export class VerifiedFetch {
|
|
|
138
138
|
async fetch (resource: Resource, opts?: VerifiedFetchOptions): Promise<Response> {
|
|
139
139
|
this.log('fetch %s %s', opts?.method ?? 'GET', resource)
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const options = convertOptions(opts)
|
|
148
|
-
const headers = new Headers(options?.headers)
|
|
149
|
-
const serverTiming = new ServerTiming()
|
|
150
|
-
|
|
151
|
-
options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
|
|
141
|
+
try {
|
|
142
|
+
if (opts?.method === 'OPTIONS') {
|
|
143
|
+
return this.handleFinalResponse(new Response(null, {
|
|
144
|
+
status: 200
|
|
145
|
+
}))
|
|
146
|
+
}
|
|
152
147
|
|
|
153
|
-
|
|
148
|
+
const options = convertOptions(opts)
|
|
149
|
+
const headers = new Headers(options?.headers)
|
|
150
|
+
const serverTiming = new ServerTiming()
|
|
154
151
|
|
|
155
|
-
|
|
156
|
-
// invalid range request
|
|
157
|
-
return this.handleFinalResponse(range)
|
|
158
|
-
}
|
|
152
|
+
options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
|
|
159
153
|
|
|
160
|
-
|
|
154
|
+
const range = getRangeHeader(resource, headers)
|
|
161
155
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
156
|
+
if (range instanceof Response) {
|
|
157
|
+
// invalid range request
|
|
158
|
+
return this.handleFinalResponse(range)
|
|
159
|
+
}
|
|
167
160
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
161
|
+
const url = this.parseResource(resource)
|
|
162
|
+
|
|
163
|
+
if (url.protocol === 'ipfs:' && url.pathname === '') {
|
|
164
|
+
// if we don't need to resolve an IPNS names or traverse a DAG, we can
|
|
165
|
+
// check the if-none-match header and maybe return a 304 without needing
|
|
166
|
+
// to load any blocks
|
|
167
|
+
if (ifNoneMatches(`"${url.hostname}"`, headers)) {
|
|
168
|
+
return notModifiedResponse(resource, new Headers({
|
|
169
|
+
etag: `"${url.hostname}"`,
|
|
170
|
+
'cache-control': 'public, max-age=29030400, immutable'
|
|
171
|
+
}))
|
|
172
|
+
}
|
|
177
173
|
}
|
|
178
|
-
}
|
|
179
174
|
|
|
180
|
-
|
|
175
|
+
const requestedMimeTypes = getRequestedMimeTypes(url, headers.get('accept'))
|
|
181
176
|
|
|
182
|
-
|
|
177
|
+
// if a raw IPNS record has been requested, don't try to load the block
|
|
178
|
+
// the record points to or do any recursive IPNS resolving
|
|
179
|
+
if (isIPNSRecordRequest(headers)) {
|
|
180
|
+
if (url.protocol !== 'ipns:') {
|
|
181
|
+
return notAcceptableResponse(url, requestedMimeTypes, [
|
|
182
|
+
CONTENT_TYPE_IPNS
|
|
183
|
+
])
|
|
184
|
+
}
|
|
183
185
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (isIPNSRecordRequest(headers)) {
|
|
187
|
-
if (url.protocol !== 'ipns:') {
|
|
188
|
-
return notAcceptableResponse(url, requestedMimeTypes, [
|
|
189
|
-
CONTENT_TYPE_IPNS
|
|
190
|
-
])
|
|
191
|
-
}
|
|
186
|
+
// @ts-expect-error ipnsRecordPlugin may not be of type IpnsRecordPlugin
|
|
187
|
+
const ipnsRecordPlugin: IpnsRecordPlugin | undefined = this.plugins.find(plugin => plugin.id === 'ipns-record-plugin')
|
|
192
188
|
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
if (ipnsRecordPlugin == null) {
|
|
190
|
+
// IPNS record was requested but no IPNS Record plugin is configured?!
|
|
191
|
+
return notAcceptableResponse(resource, requestedMimeTypes, [])
|
|
192
|
+
}
|
|
195
193
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
return this.handleFinalResponse(await ipnsRecordPlugin.handle({
|
|
195
|
+
range,
|
|
196
|
+
url,
|
|
197
|
+
resource,
|
|
198
|
+
options
|
|
199
|
+
}))
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}))
|
|
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()
|
|
202
|
+
const resolveResult = await this.urlResolver.resolve(url, serverTiming, {
|
|
203
|
+
...options,
|
|
204
|
+
isRawBlockRequest: isRawBlockRequest(headers),
|
|
205
|
+
onlyIfCached: headers.get('cache-control') === 'only-if-cached'
|
|
206
|
+
})
|
|
216
207
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
208
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', {
|
|
209
|
+
cid: resolveResult.terminalElement.cid,
|
|
210
|
+
path: resolveResult.url.pathname
|
|
211
|
+
}))
|
|
221
212
|
|
|
222
|
-
|
|
223
|
-
cid: parsedResult.terminalElement.cid,
|
|
224
|
-
path: parsedResult.url.pathname
|
|
225
|
-
}))
|
|
213
|
+
const accept = this.getAcceptHeader(resolveResult.url, requestedMimeTypes, resolveResult.terminalElement.cid)
|
|
226
214
|
|
|
227
|
-
|
|
215
|
+
if (accept instanceof Response) {
|
|
216
|
+
this.log('allowed media types for requested CID did not contain anything the client can understand')
|
|
228
217
|
|
|
229
|
-
|
|
230
|
-
|
|
218
|
+
// invalid accept header
|
|
219
|
+
return this.handleFinalResponse(accept)
|
|
220
|
+
}
|
|
231
221
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
222
|
+
const context: PluginContext = {
|
|
223
|
+
...resolveResult,
|
|
224
|
+
resource,
|
|
225
|
+
accept,
|
|
226
|
+
range,
|
|
227
|
+
options,
|
|
228
|
+
onProgress: options?.onProgress,
|
|
229
|
+
serverTiming,
|
|
230
|
+
headers,
|
|
231
|
+
requestedMimeTypes
|
|
232
|
+
}
|
|
235
233
|
|
|
236
|
-
|
|
237
|
-
...parsedResult,
|
|
238
|
-
resource: resource.toString(),
|
|
239
|
-
accept,
|
|
240
|
-
range,
|
|
241
|
-
options,
|
|
242
|
-
onProgress: options?.onProgress,
|
|
243
|
-
serverTiming,
|
|
244
|
-
headers,
|
|
245
|
-
requestedMimeTypes
|
|
246
|
-
}
|
|
234
|
+
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
235
|
|
|
248
|
-
|
|
236
|
+
const response = await this.runPluginPipeline(context)
|
|
249
237
|
|
|
250
|
-
|
|
238
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', {
|
|
239
|
+
cid: resolveResult.terminalElement.cid,
|
|
240
|
+
path: resolveResult.url.pathname
|
|
241
|
+
}))
|
|
251
242
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}))
|
|
243
|
+
if (response == null) {
|
|
244
|
+
this.log.error('no plugin could handle request for %s', resource)
|
|
245
|
+
}
|
|
256
246
|
|
|
257
|
-
|
|
258
|
-
|
|
247
|
+
return this.handleFinalResponse(response, Boolean(options?.withServerTiming) || Boolean(this.withServerTiming), context)
|
|
248
|
+
} catch (err: any) {
|
|
249
|
+
this.log.error('error fetching resource %s - %e', resource, err)
|
|
250
|
+
return this.handleFinalResponse(errorToResponse(resource, err, opts))
|
|
259
251
|
}
|
|
260
|
-
|
|
261
|
-
return this.handleFinalResponse(response, Boolean(options?.withServerTiming) || Boolean(this.withServerTiming), context)
|
|
262
252
|
}
|
|
263
253
|
|
|
264
254
|
/**
|
|
@@ -315,6 +305,18 @@ export class VerifiedFetch {
|
|
|
315
305
|
return acceptable
|
|
316
306
|
}
|
|
317
307
|
|
|
308
|
+
private parseResource (resource: Resource): URL {
|
|
309
|
+
if (isPeerId(resource) || isPublicKey(resource)) {
|
|
310
|
+
resource = `/ipns/${resource}`
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (CID.asCID(resource) === resource || resource instanceof CID) {
|
|
314
|
+
resource = `/ipfs/${resource}`
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return new URL(stringToIpfsUrl(resource.toString()))
|
|
318
|
+
}
|
|
319
|
+
|
|
318
320
|
/**
|
|
319
321
|
* The last place a Response touches in verified-fetch before being returned
|
|
320
322
|
* to the user. This is where we add the Server-Timing header to the response
|
|
@@ -345,7 +347,7 @@ export class VerifiedFetch {
|
|
|
345
347
|
const decodedPath = decodeURI(context?.url.pathname)
|
|
346
348
|
const path = uint8ArrayToString(uint8ArrayFromString(decodedPath), 'ascii')
|
|
347
349
|
|
|
348
|
-
response.headers.set('x-ipfs-path', `/${context.url.protocol === 'ipfs:' ? 'ipfs' : 'ipns'}/${context?.url.hostname}${path}`)
|
|
350
|
+
response.headers.set('x-ipfs-path', `/${context.url.protocol === 'ipfs:' ? 'ipfs' : 'ipns'}/${context?.url.hostname}${path === '/' ? '' : path}`)
|
|
349
351
|
}
|
|
350
352
|
|
|
351
353
|
// set CORS headers. If hosting your own gateway with verified-fetch behind
|
|
@@ -427,13 +429,7 @@ export class VerifiedFetch {
|
|
|
427
429
|
|
|
428
430
|
this.log.error('error in plugin %s - %e', plugin.id, err)
|
|
429
431
|
|
|
430
|
-
return internalServerErrorResponse(context.resource,
|
|
431
|
-
error: errorToObject(err)
|
|
432
|
-
}), {
|
|
433
|
-
headers: {
|
|
434
|
-
'content-type': 'application/json'
|
|
435
|
-
}
|
|
436
|
-
})
|
|
432
|
+
return internalServerErrorResponse(context.resource, err)
|
|
437
433
|
}
|
|
438
434
|
|
|
439
435
|
if (finalResponse != null) {
|
|
@@ -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"}
|