@helia/verified-fetch 0.0.0-8a5bc6f → 0.0.0-8db7792
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -44
- package/dist/index.min.js +4 -29
- package/dist/src/index.d.ts +297 -53
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +270 -49
- package/dist/src/index.js.map +1 -1
- package/dist/src/singleton.d.ts +3 -0
- package/dist/src/singleton.d.ts.map +1 -0
- package/dist/src/singleton.js +15 -0
- package/dist/src/singleton.js.map +1 -0
- package/dist/src/types.d.ts +2 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/dag-cbor-to-safe-json.d.ts +7 -0
- package/dist/src/utils/dag-cbor-to-safe-json.d.ts.map +1 -0
- package/dist/src/utils/dag-cbor-to-safe-json.js +37 -0
- package/dist/src/utils/dag-cbor-to-safe-json.js.map +1 -0
- package/dist/src/utils/get-e-tag.d.ts +28 -0
- package/dist/src/utils/get-e-tag.d.ts.map +1 -0
- package/dist/src/utils/get-e-tag.js +18 -0
- package/dist/src/utils/get-e-tag.js.map +1 -0
- package/dist/src/utils/get-stream-from-async-iterable.d.ts +10 -0
- package/dist/src/utils/get-stream-from-async-iterable.d.ts.map +1 -0
- package/dist/src/utils/{get-stream-and-content-type.js → get-stream-from-async-iterable.js} +10 -9
- package/dist/src/utils/get-stream-from-async-iterable.js.map +1 -0
- package/dist/src/utils/parse-url-string.d.ts +5 -1
- package/dist/src/utils/parse-url-string.d.ts.map +1 -1
- package/dist/src/utils/parse-url-string.js.map +1 -1
- package/dist/src/verified-fetch.d.ts +5 -12
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +90 -61
- package/dist/src/verified-fetch.js.map +1 -1
- package/package.json +28 -22
- package/src/index.ts +303 -54
- package/src/singleton.ts +20 -0
- package/src/types.ts +1 -0
- package/src/utils/dag-cbor-to-safe-json.ts +44 -0
- package/src/utils/get-e-tag.ts +36 -0
- package/src/utils/{get-stream-and-content-type.ts → get-stream-from-async-iterable.ts} +9 -8
- package/src/utils/parse-url-string.ts +6 -1
- package/src/verified-fetch.ts +104 -70
- package/dist/src/utils/get-content-type.d.ts +0 -11
- package/dist/src/utils/get-content-type.d.ts.map +0 -1
- package/dist/src/utils/get-content-type.js +0 -43
- package/dist/src/utils/get-content-type.js.map +0 -1
- package/dist/src/utils/get-stream-and-content-type.d.ts +0 -10
- package/dist/src/utils/get-stream-and-content-type.d.ts.map +0 -1
- package/dist/src/utils/get-stream-and-content-type.js.map +0 -1
- package/src/utils/get-content-type.ts +0 -55
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { peerIdFromString } from '@libp2p/peer-id'
|
|
2
2
|
import { CID } from 'multiformats/cid'
|
|
3
3
|
import { TLRU } from './tlru.js'
|
|
4
|
+
import type { RequestFormatShorthand } from '../types.js'
|
|
4
5
|
import type { IPNS, IPNSRoutingEvents, ResolveDnsLinkProgressEvents, ResolveProgressEvents, ResolveResult } from '@helia/ipns'
|
|
5
6
|
import type { ComponentLogger } from '@libp2p/interface'
|
|
6
7
|
import type { ProgressOptions } from 'progress-events'
|
|
@@ -16,11 +17,15 @@ export interface ParseUrlStringOptions extends ProgressOptions<ResolveProgressEv
|
|
|
16
17
|
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
export interface ParsedUrlQuery extends Record<string, string | unknown> {
|
|
21
|
+
format?: RequestFormatShorthand
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
export interface ParsedUrlStringResults {
|
|
20
25
|
protocol: string
|
|
21
26
|
path: string
|
|
22
27
|
cid: CID
|
|
23
|
-
query:
|
|
28
|
+
query: ParsedUrlQuery
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
const URL_REGEX = /^(?<protocol>ip[fn]s):\/\/(?<cidOrPeerIdOrDnsLink>[^/$?]+)\/?(?<path>[^$?]*)\??(?<queryString>.*)$/
|
package/src/verified-fetch.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { dagCbor as heliaDagCbor, type DAGCBOR } from '@helia/dag-cbor'
|
|
2
|
-
import { dagJson as heliaDagJson, type DAGJSON } from '@helia/dag-json'
|
|
3
1
|
import { ipns as heliaIpns, type IPNS } from '@helia/ipns'
|
|
4
2
|
import { dnsJsonOverHttps } from '@helia/ipns/dns-resolvers'
|
|
5
|
-
import { json as heliaJson, type JSON } from '@helia/json'
|
|
6
3
|
import { unixfs as heliaUnixFs, type UnixFS as HeliaUnixFs, type UnixFSStats } from '@helia/unixfs'
|
|
7
4
|
import { code as dagCborCode } from '@ipld/dag-cbor'
|
|
8
5
|
import { code as dagJsonCode } from '@ipld/dag-json'
|
|
9
6
|
import { code as dagPbCode } from '@ipld/dag-pb'
|
|
10
7
|
import { code as jsonCode } from 'multiformats/codecs/json'
|
|
11
|
-
import {
|
|
8
|
+
import { code as rawCode } from 'multiformats/codecs/raw'
|
|
9
|
+
import { identity } from 'multiformats/hashes/identity'
|
|
12
10
|
import { CustomProgressEvent } from 'progress-events'
|
|
13
|
-
import {
|
|
11
|
+
import { dagCborToSafeJSON } from './utils/dag-cbor-to-safe-json.js'
|
|
12
|
+
import { getETag } from './utils/get-e-tag.js'
|
|
13
|
+
import { getStreamFromAsyncIterable } from './utils/get-stream-from-async-iterable.js'
|
|
14
14
|
import { parseResource } from './utils/parse-resource.js'
|
|
15
15
|
import { walkPath, type PathWalkerFn } from './utils/walk-path.js'
|
|
16
|
-
import type { CIDDetail, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
|
|
16
|
+
import type { CIDDetail, ContentTypeParser, Resource, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
|
|
17
|
+
import type { RequestFormatShorthand } from './types.js'
|
|
17
18
|
import type { Helia } from '@helia/interface'
|
|
18
19
|
import type { AbortOptions, Logger } from '@libp2p/interface'
|
|
19
20
|
import type { UnixFSEntry } from 'ipfs-unixfs-exporter'
|
|
@@ -23,18 +24,14 @@ interface VerifiedFetchComponents {
|
|
|
23
24
|
helia: Helia
|
|
24
25
|
ipns?: IPNS
|
|
25
26
|
unixfs?: HeliaUnixFs
|
|
26
|
-
dagJson?: DAGJSON
|
|
27
|
-
json?: JSON
|
|
28
|
-
dagCbor?: DAGCBOR
|
|
29
27
|
pathWalker?: PathWalkerFn
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
/**
|
|
33
31
|
* Potential future options for the VerifiedFetch constructor.
|
|
34
32
|
*/
|
|
35
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
36
33
|
interface VerifiedFetchInit {
|
|
37
|
-
|
|
34
|
+
contentTypeParser?: ContentTypeParser
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
interface FetchHandlerFunctionArg {
|
|
@@ -63,17 +60,29 @@ function convertOptions (options?: VerifiedFetchOptions): (Omit<VerifiedFetchOpt
|
|
|
63
60
|
}
|
|
64
61
|
}
|
|
65
62
|
|
|
63
|
+
function okResponse (body?: BodyInit | null): Response {
|
|
64
|
+
return new Response(body, {
|
|
65
|
+
status: 200,
|
|
66
|
+
statusText: 'OK'
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function notSupportedResponse (body?: BodyInit | null): Response {
|
|
71
|
+
return new Response(body, {
|
|
72
|
+
status: 501,
|
|
73
|
+
statusText: 'Not Implemented'
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
export class VerifiedFetch {
|
|
67
78
|
private readonly helia: Helia
|
|
68
79
|
private readonly ipns: IPNS
|
|
69
80
|
private readonly unixfs: HeliaUnixFs
|
|
70
|
-
private readonly dagJson: DAGJSON
|
|
71
|
-
private readonly dagCbor: DAGCBOR
|
|
72
|
-
private readonly json: JSON
|
|
73
81
|
private readonly pathWalker: PathWalkerFn
|
|
74
82
|
private readonly log: Logger
|
|
83
|
+
private readonly contentTypeParser: ContentTypeParser | undefined
|
|
75
84
|
|
|
76
|
-
constructor ({ helia, ipns, unixfs,
|
|
85
|
+
constructor ({ helia, ipns, unixfs, pathWalker }: VerifiedFetchComponents, init?: VerifiedFetchInit) {
|
|
77
86
|
this.helia = helia
|
|
78
87
|
this.log = helia.logger.forComponent('helia:verified-fetch')
|
|
79
88
|
this.ipns = ipns ?? heliaIpns(helia, {
|
|
@@ -83,63 +92,55 @@ export class VerifiedFetch {
|
|
|
83
92
|
]
|
|
84
93
|
})
|
|
85
94
|
this.unixfs = unixfs ?? heliaUnixFs(helia)
|
|
86
|
-
this.dagJson = dagJson ?? heliaDagJson(helia)
|
|
87
|
-
this.json = json ?? heliaJson(helia)
|
|
88
|
-
this.dagCbor = dagCbor ?? heliaDagCbor(helia)
|
|
89
95
|
this.pathWalker = pathWalker ?? walkPath
|
|
96
|
+
this.contentTypeParser = init?.contentTypeParser
|
|
90
97
|
this.log.trace('created VerifiedFetch instance')
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
// handle vnd.ipfs.ipns-record
|
|
94
101
|
private async handleIPNSRecord ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
95
|
-
const response =
|
|
102
|
+
const response = notSupportedResponse('vnd.ipfs.ipns-record support is not implemented')
|
|
96
103
|
response.headers.set('X-Content-Type-Options', 'nosniff') // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
|
|
97
104
|
return response
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
// handle vnd.ipld.car
|
|
101
108
|
private async handleIPLDCar ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
102
|
-
const response =
|
|
109
|
+
const response = notSupportedResponse('vnd.ipld.car support is not implemented')
|
|
103
110
|
response.headers.set('X-Content-Type-Options', 'nosniff') // see https://specs.ipfs.tech/http-gateways/path-gateway/#x-content-type-options-response-header
|
|
104
111
|
return response
|
|
105
112
|
}
|
|
106
113
|
|
|
107
|
-
private async handleDagJson ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
108
|
-
this.log.trace('fetching %c/%s', cid, path)
|
|
109
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid: cid.toString(), path }))
|
|
110
|
-
const result = await this.dagJson.get(cid, {
|
|
111
|
-
signal: options?.signal,
|
|
112
|
-
onProgress: options?.onProgress
|
|
113
|
-
})
|
|
114
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid: cid.toString(), path }))
|
|
115
|
-
const response = new Response(JSON.stringify(result), { status: 200 })
|
|
116
|
-
response.headers.set('content-type', 'application/json')
|
|
117
|
-
return response
|
|
118
|
-
}
|
|
119
|
-
|
|
120
114
|
private async handleJson ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
121
115
|
this.log.trace('fetching %c/%s', cid, path)
|
|
122
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid
|
|
123
|
-
const result
|
|
116
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid, path }))
|
|
117
|
+
const result = await this.helia.blockstore.get(cid, {
|
|
124
118
|
signal: options?.signal,
|
|
125
119
|
onProgress: options?.onProgress
|
|
126
120
|
})
|
|
127
|
-
|
|
128
|
-
const response = new Response(JSON.stringify(result), { status: 200 })
|
|
121
|
+
const response = okResponse(result)
|
|
129
122
|
response.headers.set('content-type', 'application/json')
|
|
123
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid, path }))
|
|
130
124
|
return response
|
|
131
125
|
}
|
|
132
126
|
|
|
133
127
|
private async handleDagCbor ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
134
128
|
this.log.trace('fetching %c/%s', cid, path)
|
|
135
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
129
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid, path }))
|
|
130
|
+
// return body as binary
|
|
131
|
+
const block = await this.helia.blockstore.get(cid)
|
|
132
|
+
let body: string | Uint8Array
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
body = dagCborToSafeJSON(block)
|
|
136
|
+
} catch (err) {
|
|
137
|
+
this.log('could not decode DAG-CBOR as JSON-safe, falling back to `application/octet-stream`', err)
|
|
138
|
+
body = block
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const response = okResponse(body)
|
|
142
|
+
response.headers.set('content-type', body instanceof Uint8Array ? 'application/octet-stream' : 'application/json')
|
|
143
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid, path }))
|
|
143
144
|
return response
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -153,7 +154,7 @@ export class VerifiedFetch {
|
|
|
153
154
|
const rootFilePath = 'index.html'
|
|
154
155
|
try {
|
|
155
156
|
this.log.trace('found directory at %c/%s, looking for index.html', cid, path)
|
|
156
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid: dirCid
|
|
157
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid: dirCid, path: rootFilePath }))
|
|
157
158
|
stat = await this.unixfs.stat(dirCid, {
|
|
158
159
|
path: rootFilePath,
|
|
159
160
|
signal: options?.signal,
|
|
@@ -165,47 +166,75 @@ export class VerifiedFetch {
|
|
|
165
166
|
// terminalElement = stat
|
|
166
167
|
} catch (err: any) {
|
|
167
168
|
this.log('error loading path %c/%s', dirCid, rootFilePath, err)
|
|
168
|
-
return
|
|
169
|
+
return notSupportedResponse('Unable to find index.html for directory at given path. Support for directories with implicit root is not implemented')
|
|
169
170
|
} finally {
|
|
170
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid: dirCid
|
|
171
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid: dirCid, path: rootFilePath }))
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid: resolvedCID
|
|
175
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid: resolvedCID, path: '' }))
|
|
175
176
|
const asyncIter = this.unixfs.cat(resolvedCID, {
|
|
176
177
|
signal: options?.signal,
|
|
177
178
|
onProgress: options?.onProgress
|
|
178
179
|
})
|
|
179
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid: resolvedCID.toString(), path: '' }))
|
|
180
180
|
this.log('got async iterator for %c/%s', cid, path)
|
|
181
181
|
|
|
182
|
-
const {
|
|
182
|
+
const { stream, firstChunk } = await getStreamFromAsyncIterable(asyncIter, path ?? '', this.helia.logger, {
|
|
183
183
|
onProgress: options?.onProgress
|
|
184
184
|
})
|
|
185
|
-
const response =
|
|
186
|
-
|
|
185
|
+
const response = okResponse(stream)
|
|
186
|
+
await this.setContentType(firstChunk, path, response)
|
|
187
|
+
|
|
188
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid: resolvedCID, path: '' }))
|
|
187
189
|
|
|
188
190
|
return response
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
private async handleRaw ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
192
194
|
this.log.trace('fetching %c/%s', cid, path)
|
|
193
|
-
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid
|
|
195
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid, path }))
|
|
194
196
|
const result = await this.helia.blockstore.get(cid)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
const response = okResponse(result)
|
|
198
|
+
await this.setContentType(result, path, response)
|
|
199
|
+
|
|
200
|
+
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid, path }))
|
|
198
201
|
return response
|
|
199
202
|
}
|
|
200
203
|
|
|
204
|
+
private async setContentType (bytes: Uint8Array, path: string, response: Response): Promise<void> {
|
|
205
|
+
let contentType = 'application/octet-stream'
|
|
206
|
+
|
|
207
|
+
if (this.contentTypeParser != null) {
|
|
208
|
+
try {
|
|
209
|
+
let fileName = path.split('/').pop()?.trim()
|
|
210
|
+
fileName = fileName === '' ? undefined : fileName
|
|
211
|
+
const parsed = this.contentTypeParser(bytes, fileName)
|
|
212
|
+
|
|
213
|
+
if (isPromise(parsed)) {
|
|
214
|
+
const result = await parsed
|
|
215
|
+
|
|
216
|
+
if (result != null) {
|
|
217
|
+
contentType = result
|
|
218
|
+
}
|
|
219
|
+
} else if (parsed != null) {
|
|
220
|
+
contentType = parsed
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
this.log.error('Error parsing content type', err)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
response.headers.set('content-type', contentType)
|
|
228
|
+
}
|
|
229
|
+
|
|
201
230
|
/**
|
|
202
231
|
* Determines the format requested by the client, defaults to `null` if no format is requested.
|
|
203
232
|
*
|
|
204
233
|
* @see https://specs.ipfs.tech/http-gateways/path-gateway/#format-request-query-parameter
|
|
205
234
|
* @default 'raw'
|
|
206
235
|
*/
|
|
207
|
-
private getFormat ({ headerFormat, queryFormat }: { headerFormat: string | null, queryFormat:
|
|
208
|
-
const formatMap: Record<string,
|
|
236
|
+
private getFormat ({ headerFormat, queryFormat }: { headerFormat: string | null, queryFormat: RequestFormatShorthand | null }): RequestFormatShorthand | null {
|
|
237
|
+
const formatMap: Record<string, RequestFormatShorthand> = {
|
|
209
238
|
'vnd.ipld.raw': 'raw',
|
|
210
239
|
'vnd.ipld.car': 'car',
|
|
211
240
|
'application/x-tar': 'tar',
|
|
@@ -234,22 +263,23 @@ export class VerifiedFetch {
|
|
|
234
263
|
* These format handlers should adjust the response headers as specified in https://specs.ipfs.tech/http-gateways/path-gateway/#response-headers
|
|
235
264
|
*/
|
|
236
265
|
private readonly formatHandlers: Record<string, FetchHandlerFunction> = {
|
|
237
|
-
raw: async () =>
|
|
266
|
+
raw: async () => notSupportedResponse('application/vnd.ipld.raw support is not implemented'),
|
|
238
267
|
car: this.handleIPLDCar,
|
|
239
268
|
'ipns-record': this.handleIPNSRecord,
|
|
240
|
-
tar: async () =>
|
|
241
|
-
'dag-json': async () =>
|
|
242
|
-
'dag-cbor': async () =>
|
|
243
|
-
json: async () =>
|
|
244
|
-
cbor: async () =>
|
|
269
|
+
tar: async () => notSupportedResponse('application/x-tar support is not implemented'),
|
|
270
|
+
'dag-json': async () => notSupportedResponse('application/vnd.ipld.dag-json support is not implemented'),
|
|
271
|
+
'dag-cbor': async () => notSupportedResponse('application/vnd.ipld.dag-cbor support is not implemented'),
|
|
272
|
+
json: async () => notSupportedResponse('application/json support is not implemented'),
|
|
273
|
+
cbor: async () => notSupportedResponse('application/cbor support is not implemented')
|
|
245
274
|
}
|
|
246
275
|
|
|
247
276
|
private readonly codecHandlers: Record<number, FetchHandlerFunction> = {
|
|
248
|
-
[dagJsonCode]: this.handleDagJson,
|
|
249
277
|
[dagPbCode]: this.handleDagPb,
|
|
278
|
+
[dagJsonCode]: this.handleJson,
|
|
250
279
|
[jsonCode]: this.handleJson,
|
|
251
280
|
[dagCborCode]: this.handleDagCbor,
|
|
252
|
-
[rawCode]: this.handleRaw
|
|
281
|
+
[rawCode]: this.handleRaw,
|
|
282
|
+
[identity.code]: this.handleRaw
|
|
253
283
|
}
|
|
254
284
|
|
|
255
285
|
async fetch (resource: Resource, opts?: VerifiedFetchOptions): Promise<Response> {
|
|
@@ -291,12 +321,12 @@ export class VerifiedFetch {
|
|
|
291
321
|
if (codecHandler != null) {
|
|
292
322
|
response = await codecHandler.call(this, { cid, path, options, terminalElement })
|
|
293
323
|
} else {
|
|
294
|
-
return
|
|
324
|
+
return notSupportedResponse(`Support for codec with code ${cid.code} is not yet implemented. Please open an issue at https://github.com/ipfs/helia/issues/new`)
|
|
295
325
|
}
|
|
296
326
|
}
|
|
297
327
|
|
|
298
|
-
response.headers.set('etag', cid
|
|
299
|
-
response.headers.set('cache-
|
|
328
|
+
response.headers.set('etag', getETag({ cid, reqFormat: format ?? undefined, weak: false }))
|
|
329
|
+
response.headers.set('cache-control', 'public, max-age=29030400, immutable')
|
|
300
330
|
response.headers.set('X-Ipfs-Path', resource.toString()) // https://specs.ipfs.tech/http-gateways/path-gateway/#x-ipfs-path-response-header
|
|
301
331
|
|
|
302
332
|
if (ipfsRoots != null) {
|
|
@@ -321,3 +351,7 @@ export class VerifiedFetch {
|
|
|
321
351
|
await this.helia.stop()
|
|
322
352
|
}
|
|
323
353
|
}
|
|
354
|
+
|
|
355
|
+
function isPromise <T> (p?: any): p is Promise<T> {
|
|
356
|
+
return p?.then != null
|
|
357
|
+
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
interface TestInput {
|
|
2
|
-
bytes: Uint8Array;
|
|
3
|
-
path: string;
|
|
4
|
-
}
|
|
5
|
-
export declare const DEFAULT_MIME_TYPE = "application/octet-stream";
|
|
6
|
-
/**
|
|
7
|
-
* Get the content type from the input based on the tests.
|
|
8
|
-
*/
|
|
9
|
-
export declare function getContentType(input: TestInput): Promise<string>;
|
|
10
|
-
export {};
|
|
11
|
-
//# sourceMappingURL=get-content-type.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"get-content-type.d.ts","sourceRoot":"","sources":["../../../src/utils/get-content-type.ts"],"names":[],"mappings":"AAEA,UAAU,SAAS;IACjB,KAAK,EAAE,UAAU,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb;AAID,eAAO,MAAM,iBAAiB,6BAA6B,CAAA;AAkC3D;;GAEG;AACH,wBAAsB,cAAc,CAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAQvE"}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import mime from 'mime-types';
|
|
2
|
-
export const DEFAULT_MIME_TYPE = 'application/octet-stream';
|
|
3
|
-
const xmlRegex = /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig;
|
|
4
|
-
/**
|
|
5
|
-
* Tests to determine the content type of the input.
|
|
6
|
-
* The order is important on this one.
|
|
7
|
-
*/
|
|
8
|
-
const tests = [
|
|
9
|
-
// svg
|
|
10
|
-
async ({ bytes }) => xmlRegex.test(new TextDecoder().decode(bytes.slice(0, 64)))
|
|
11
|
-
? 'image/svg+xml'
|
|
12
|
-
: undefined,
|
|
13
|
-
// testing file-type from path
|
|
14
|
-
async ({ path }) => {
|
|
15
|
-
const mimeType = mime.lookup(path);
|
|
16
|
-
if (mimeType !== false) {
|
|
17
|
-
return mimeType;
|
|
18
|
-
}
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
];
|
|
22
|
-
const overrides = {
|
|
23
|
-
'video/quicktime': 'video/mp4'
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Override the content type based on overrides.
|
|
27
|
-
*/
|
|
28
|
-
function overrideContentType(type) {
|
|
29
|
-
return overrides[type] ?? type;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Get the content type from the input based on the tests.
|
|
33
|
-
*/
|
|
34
|
-
export async function getContentType(input) {
|
|
35
|
-
for (const test of tests) {
|
|
36
|
-
const type = await test(input);
|
|
37
|
-
if (type !== undefined) {
|
|
38
|
-
return overrideContentType(type);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return DEFAULT_MIME_TYPE;
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=get-content-type.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"get-content-type.js","sourceRoot":"","sources":["../../../src/utils/get-content-type.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,YAAY,CAAA;AAS7B,MAAM,CAAC,MAAM,iBAAiB,GAAG,0BAA0B,CAAA;AAE3D,MAAM,QAAQ,GAAG,gCAAgC,CAAA;AAEjD;;;GAGG;AACH,MAAM,KAAK,GAA4C;IACrD,MAAM;IACN,KAAK,EAAE,EAAE,KAAK,EAAE,EAAc,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC1F,CAAC,CAAC,eAAe;QACjB,CAAC,CAAC,SAAS;IACb,8BAA8B;IAC9B,KAAK,EAAE,EAAE,IAAI,EAAE,EAAc,EAAE;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;YACvB,OAAO,QAAQ,CAAA;QACjB,CAAC;QACD,OAAO,SAAS,CAAA;IAClB,CAAC;CACF,CAAA;AAED,MAAM,SAAS,GAA2B;IACxC,iBAAiB,EAAE,WAAW;CAC/B,CAAA;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAE,IAAY;IACxC,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,CAAA;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAE,KAAgB;IACpD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAA;QAClC,CAAC;IACH,CAAC;IACD,OAAO,iBAAiB,CAAA;AAC1B,CAAC"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { VerifiedFetchInit } from '../index.js';
|
|
2
|
-
import type { ComponentLogger } from '@libp2p/interface';
|
|
3
|
-
/**
|
|
4
|
-
* Converts an async iterator of Uint8Array bytes to a stream and attempts to determine the content type of those bytes.
|
|
5
|
-
*/
|
|
6
|
-
export declare function getStreamAndContentType(iterator: AsyncIterable<Uint8Array>, path: string, logger: ComponentLogger, options?: Pick<VerifiedFetchInit, 'onProgress'>): Promise<{
|
|
7
|
-
contentType: string;
|
|
8
|
-
stream: ReadableStream<Uint8Array>;
|
|
9
|
-
}>;
|
|
10
|
-
//# sourceMappingURL=get-stream-and-content-type.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"get-stream-and-content-type.d.ts","sourceRoot":"","sources":["../../../src/utils/get-stream-and-content-type.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD;;GAEG;AACH,wBAAsB,uBAAuB,CAAE,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAA;CAAE,CAAC,CAmChP"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"get-stream-and-content-type.js","sourceRoot":"","sources":["../../../src/utils/get-stream-and-content-type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAItD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAE,QAAmC,EAAE,IAAY,EAAE,MAAuB,EAAE,OAA+C;IACxK,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,kDAAkD,CAAC,CAAA;IACnF,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAA;IAC/C,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;IAE3C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,GAAG,CAAC,KAAK,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAA;QAC5C,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IACrC,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAChE,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;QAChC,KAAK,CAAC,KAAK,CAAE,UAAU;YACrB,yCAAyC;YACzC,OAAO,EAAE,UAAU,EAAE,CAAC,IAAI,mBAAmB,CAAO,uCAAuC,CAAC,CAAC,CAAA;YAC7F,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC3B,CAAC;QACD,KAAK,CAAC,IAAI,CAAE,UAAU;YACpB,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;YAE3C,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;oBAClB,OAAO,EAAE,UAAU,EAAE,CAAC,IAAI,mBAAmB,CAAO,uCAAuC,CAAC,CAAC,CAAA;oBAC7F,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;gBAC3B,CAAC;gBACD,UAAU,CAAC,KAAK,EAAE,CAAA;gBAClB,OAAM;YACR,CAAC;YAED,OAAO,EAAE,UAAU,EAAE,CAAC,IAAI,mBAAmB,CAAO,uCAAuC,CAAC,CAAC,CAAA;YAC7F,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC3B,CAAC;KACF,CAAC,CAAA;IAEF,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,CAAA;AAChC,CAAC"}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import mime from 'mime-types'
|
|
2
|
-
|
|
3
|
-
interface TestInput {
|
|
4
|
-
bytes: Uint8Array
|
|
5
|
-
path: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
type TestOutput = Promise<string | undefined>
|
|
9
|
-
|
|
10
|
-
export const DEFAULT_MIME_TYPE = 'application/octet-stream'
|
|
11
|
-
|
|
12
|
-
const xmlRegex = /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Tests to determine the content type of the input.
|
|
16
|
-
* The order is important on this one.
|
|
17
|
-
*/
|
|
18
|
-
const tests: Array<(input: TestInput) => TestOutput> = [
|
|
19
|
-
// svg
|
|
20
|
-
async ({ bytes }): TestOutput => xmlRegex.test(new TextDecoder().decode(bytes.slice(0, 64)))
|
|
21
|
-
? 'image/svg+xml'
|
|
22
|
-
: undefined,
|
|
23
|
-
// testing file-type from path
|
|
24
|
-
async ({ path }): TestOutput => {
|
|
25
|
-
const mimeType = mime.lookup(path)
|
|
26
|
-
if (mimeType !== false) {
|
|
27
|
-
return mimeType
|
|
28
|
-
}
|
|
29
|
-
return undefined
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
const overrides: Record<string, string> = {
|
|
34
|
-
'video/quicktime': 'video/mp4'
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Override the content type based on overrides.
|
|
39
|
-
*/
|
|
40
|
-
function overrideContentType (type: string): string {
|
|
41
|
-
return overrides[type] ?? type
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Get the content type from the input based on the tests.
|
|
46
|
-
*/
|
|
47
|
-
export async function getContentType (input: TestInput): Promise<string> {
|
|
48
|
-
for (const test of tests) {
|
|
49
|
-
const type = await test(input)
|
|
50
|
-
if (type !== undefined) {
|
|
51
|
-
return overrideContentType(type)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return DEFAULT_MIME_TYPE
|
|
55
|
-
}
|