@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.
Files changed (72) hide show
  1. package/dist/index.min.js +61 -61
  2. package/dist/index.min.js.map +4 -4
  3. package/dist/src/index.d.ts +29 -6
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js.map +1 -1
  6. package/dist/src/plugins/plugin-handle-car.d.ts.map +1 -1
  7. package/dist/src/plugins/plugin-handle-car.js +7 -3
  8. package/dist/src/plugins/plugin-handle-car.js.map +1 -1
  9. package/dist/src/plugins/plugin-handle-ipld.d.ts.map +1 -1
  10. package/dist/src/plugins/plugin-handle-ipld.js +4 -15
  11. package/dist/src/plugins/plugin-handle-ipld.js.map +1 -1
  12. package/dist/src/plugins/plugin-handle-raw.d.ts +11 -0
  13. package/dist/src/plugins/plugin-handle-raw.d.ts.map +1 -0
  14. package/dist/src/plugins/plugin-handle-raw.js +41 -0
  15. package/dist/src/plugins/plugin-handle-raw.js.map +1 -0
  16. package/dist/src/plugins/plugin-handle-unixfs.d.ts.map +1 -1
  17. package/dist/src/plugins/plugin-handle-unixfs.js +36 -18
  18. package/dist/src/plugins/plugin-handle-unixfs.js.map +1 -1
  19. package/dist/src/url-resolver.d.ts +2 -3
  20. package/dist/src/url-resolver.d.ts.map +1 -1
  21. package/dist/src/url-resolver.js +20 -57
  22. package/dist/src/url-resolver.js.map +1 -1
  23. package/dist/src/utils/error-to-response.d.ts +1 -1
  24. package/dist/src/utils/error-to-response.d.ts.map +1 -1
  25. package/dist/src/utils/error-to-response.js +14 -12
  26. package/dist/src/utils/error-to-response.js.map +1 -1
  27. package/dist/src/utils/get-range-header.d.ts +2 -1
  28. package/dist/src/utils/get-range-header.d.ts.map +1 -1
  29. package/dist/src/utils/get-range-header.js.map +1 -1
  30. package/dist/src/utils/get-tar-stream.d.ts.map +1 -1
  31. package/dist/src/utils/get-tar-stream.js +22 -9
  32. package/dist/src/utils/get-tar-stream.js.map +1 -1
  33. package/dist/src/utils/parse-resource.d.ts +10 -0
  34. package/dist/src/utils/parse-resource.d.ts.map +1 -0
  35. package/dist/src/utils/parse-resource.js +52 -0
  36. package/dist/src/utils/parse-resource.js.map +1 -0
  37. package/dist/src/utils/responses.d.ts +14 -14
  38. package/dist/src/utils/responses.d.ts.map +1 -1
  39. package/dist/src/utils/responses.js +8 -3
  40. package/dist/src/utils/responses.js.map +1 -1
  41. package/dist/src/verified-fetch.d.ts +1 -0
  42. package/dist/src/verified-fetch.d.ts.map +1 -1
  43. package/dist/src/verified-fetch.js +117 -119
  44. package/dist/src/verified-fetch.js.map +1 -1
  45. package/package.json +3 -3
  46. package/src/index.ts +30 -6
  47. package/src/plugins/plugin-handle-car.ts +8 -3
  48. package/src/plugins/plugin-handle-ipld.ts +4 -14
  49. package/src/plugins/plugin-handle-raw.ts +52 -0
  50. package/src/plugins/plugin-handle-unixfs.ts +42 -19
  51. package/src/url-resolver.ts +22 -65
  52. package/src/utils/error-to-response.ts +15 -12
  53. package/src/utils/get-range-header.ts +2 -1
  54. package/src/utils/get-tar-stream.ts +26 -10
  55. package/src/utils/parse-resource.ts +62 -0
  56. package/src/utils/responses.ts +24 -19
  57. package/src/verified-fetch.ts +123 -122
  58. package/dist/src/utils/ipfs-path-to-url.d.ts +0 -16
  59. package/dist/src/utils/ipfs-path-to-url.d.ts.map +0 -1
  60. package/dist/src/utils/ipfs-path-to-url.js +0 -45
  61. package/dist/src/utils/ipfs-path-to-url.js.map +0 -1
  62. package/dist/src/utils/parse-url-string.d.ts +0 -23
  63. package/dist/src/utils/parse-url-string.d.ts.map +0 -1
  64. package/dist/src/utils/parse-url-string.js +0 -120
  65. package/dist/src/utils/parse-url-string.js.map +0 -1
  66. package/dist/src/utils/resource-to-cache-key.d.ts +0 -15
  67. package/dist/src/utils/resource-to-cache-key.d.ts.map +0 -1
  68. package/dist/src/utils/resource-to-cache-key.js +0 -27
  69. package/dist/src/utils/resource-to-cache-key.js.map +0 -1
  70. package/src/utils/ipfs-path-to-url.ts +0 -54
  71. package/src/utils/parse-url-string.ts +0 -165
  72. package/src/utils/resource-to-cache-key.ts +0 -30
@@ -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: string | URL): void {
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: string, body?: SupportedBodyTypes, init?: ResponseOptions): Response {
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: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
62
- const response = new Response(body, {
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: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
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: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
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: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
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: string | URL, requested: Array<Pick<ContentType, 'mediaType'>>, acceptable: Array<Pick<ContentType, 'mediaType'>>, init?: ResponseInit): Response {
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: string, body?: SupportedBodyTypes, init?: ResponseInit): Response {
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: string, errors: Error | Error[], init?: ResponseInit): Response {
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: string, location: string, init?: ResponseInit): Response {
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: string, headers: Headers, init?: ResponseInit): Response {
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: string, getSlice: PartialContent, range: RangeHeader, documentSize: number | bigint, init?: ResponseOptions): Response {
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: string, getSlice: PartialContent, range: Range, documentSize: number | bigint, init?: ResponseOptions): Response {
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, '', init)
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: string, getSlice: PartialContent, range: RangeHeader, documentSize: number | bigint, init?: ResponseOptions): Response {
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: string, documentSize?: number | bigint | string, init?: ResponseInit): Response {
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: string, init?: ResponseInit): Response {
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', {
@@ -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 { 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 { parseURLString } from './utils/parse-url-string.ts'
21
+ import { stringToIpfsUrl } from './utils/parse-resource.ts'
21
22
  import { setCacheControlHeader } from './utils/response-headers.js'
22
- import { badRequestResponse, internalServerErrorResponse, notAcceptableResponse, notImplementedResponse, notModifiedResponse } from './utils/responses.js'
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, ResolveURLResult, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions, VerifiedFetchPlugin, PluginContext, PluginOptions } from './index.js'
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
- if (opts?.method === 'OPTIONS') {
142
- return this.handleFinalResponse(new Response(null, {
143
- status: 200
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
- options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
150
+ const options = convertOptions(opts)
151
+ const headers = new Headers(options?.headers)
152
+ const serverTiming = new ServerTiming()
152
153
 
153
- const range = getRangeHeader(resource.toString(), headers)
154
+ if (options != null) {
155
+ options.offline ??= headers.get('cache-control') === 'only-if-cached'
156
+ }
154
157
 
155
- if (range instanceof Response) {
156
- // invalid range request
157
- return this.handleFinalResponse(range)
158
- }
158
+ options?.onProgress?.(new CustomProgressEvent<ResourceDetail>('verified-fetch:request:start', { resource }))
159
159
 
160
- let url: URL
160
+ const range = getRangeHeader(resource, headers)
161
161
 
162
- try {
163
- url = parseURLString(typeof resource === 'string' ? resource : `ipfs://${resource}`)
164
- } catch (err: any) {
165
- return this.handleFinalResponse(badRequestResponse(resource.toString(), err))
166
- }
162
+ if (range instanceof Response) {
163
+ // invalid range request
164
+ return this.handleFinalResponse(range)
165
+ }
167
166
 
168
- if (url.protocol === 'ipfs:' && url.pathname === '') {
169
- // if we don't need to resolve an IPNS names or traverse a DAG, we can
170
- // check the if-none-match header and maybe return a 304 without needing
171
- // to load any blocks
172
- if (ifNoneMatches(`"${url.hostname}"`, headers)) {
173
- return notModifiedResponse(resource.toString(), new Headers({
174
- etag: `"${url.hostname}"`,
175
- 'cache-control': 'public, max-age=29030400, immutable'
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
- const requestedMimeTypes = getRequestedMimeTypes(url, headers.get('accept'))
181
+ const requestedMimeTypes = getRequestedMimeTypes(url, headers.get('accept'))
181
182
 
182
- let parsedResult: ResolveURLResult
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
- // if just an IPNS record has been requested, don't try to load the block
185
- // the record points to or do any recursive IPNS resolving
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
- // @ts-expect-error ipnsRecordPlugin may not be of type IpnsRecordPlugin
194
- const ipnsRecordPlugin: IpnsRecordPlugin | undefined = this.plugins.find(plugin => plugin.id === 'ipns-record-plugin')
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
- if (ipnsRecordPlugin == null) {
197
- // IPNS record was requested but no IPNS Record plugin is configured?!
198
- return notAcceptableResponse(url, requestedMimeTypes, [])
200
+ return this.handleFinalResponse(await ipnsRecordPlugin.handle({
201
+ range,
202
+ url,
203
+ resource,
204
+ options
205
+ }))
199
206
  }
200
207
 
201
- return this.handleFinalResponse(await ipnsRecordPlugin.handle({
202
- range,
203
- url,
204
- resource: resource.toString(),
205
- options
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
- options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:resolve', {
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
- const accept = this.getAcceptHeader(parsedResult.url, requestedMimeTypes, parsedResult.terminalElement.cid)
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
- if (accept instanceof Response) {
230
- this.log('allowed media types for requested CID did not contain anything the client can understand')
223
+ // invalid accept header
224
+ return this.handleFinalResponse(accept)
225
+ }
231
226
 
232
- // invalid accept header
233
- return this.handleFinalResponse(accept)
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
- const context: PluginContext = {
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
- this.log.trace('finding handler for cid code "0x%s" and response content types %s', parsedResult.terminalElement.cid.code.toString(16), accept.map(header => header.contentType.mediaType).join(', '))
241
+ const response = await this.runPluginPipeline(context)
249
242
 
250
- const response = await this.runPluginPipeline(context)
243
+ options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', {
244
+ cid: resolveResult.terminalElement.cid,
245
+ path: resolveResult.url.pathname
246
+ }))
251
247
 
252
- options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', {
253
- cid: parsedResult.terminalElement.cid,
254
- path: parsedResult.url.pathname
255
- }))
248
+ if (response == null) {
249
+ this.log.error('no plugin could handle request for %s', resource)
250
+ }
256
251
 
257
- if (response == null) {
258
- this.log.error('no plugin could handle request for %s', resource)
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
- this.log('invoking plugin: %s', plugin.id)
411
- pluginsUsed.add(plugin.id)
416
+ // try {
417
+ this.log('invoking plugin: %s', plugin.id)
418
+ pluginsUsed.add(plugin.id)
412
419
 
413
- const maybeResponse = await plugin.handle(context)
420
+ const maybeResponse = await plugin.handle(context)
414
421
 
415
- this.log('plugin response %s %o', plugin.id, maybeResponse)
422
+ this.log('plugin response %s %o', plugin.id, maybeResponse)
416
423
 
417
- if (maybeResponse != null) {
418
- // if a plugin returns a final Response, short-circuit
419
- finalResponse = maybeResponse
420
- pluginHandled = true
421
- break
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, JSON.stringify({
431
- error: errorToObject(err)
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"}