@helia/verified-fetch 0.0.0-8a5bc6f → 0.0.0-f58d467
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 +80 -43
- package/dist/index.min.js +4 -29
- package/dist/src/index.d.ts +110 -51
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +86 -47
- 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/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/verified-fetch.d.ts +4 -1
- package/dist/src/verified-fetch.d.ts.map +1 -1
- package/dist/src/verified-fetch.js +34 -6
- package/dist/src/verified-fetch.js.map +1 -1
- package/package.json +19 -19
- package/src/index.ts +117 -52
- package/src/singleton.ts +20 -0
- package/src/utils/{get-stream-and-content-type.ts → get-stream-from-async-iterable.ts} +9 -8
- package/src/verified-fetch.ts +41 -10
- 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
package/src/singleton.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createVerifiedFetch } from './index.js'
|
|
2
|
+
import type { Resource, VerifiedFetch, VerifiedFetchInit } from './index.js'
|
|
3
|
+
|
|
4
|
+
let impl: VerifiedFetch | undefined
|
|
5
|
+
|
|
6
|
+
export const verifiedFetch: VerifiedFetch = async function verifiedFetch (resource: Resource, options?: VerifiedFetchInit): Promise<Response> {
|
|
7
|
+
if (impl == null) {
|
|
8
|
+
impl = await createVerifiedFetch()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return impl(resource, options)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
verifiedFetch.start = async function () {
|
|
15
|
+
await impl?.start()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
verifiedFetch.stop = async function () {
|
|
19
|
+
await impl?.stop()
|
|
20
|
+
}
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { CustomProgressEvent } from 'progress-events'
|
|
2
|
-
import { getContentType } from './get-content-type.js'
|
|
3
2
|
import type { VerifiedFetchInit } from '../index.js'
|
|
4
3
|
import type { ComponentLogger } from '@libp2p/interface'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* Converts an async iterator of Uint8Array bytes to a stream and
|
|
6
|
+
* Converts an async iterator of Uint8Array bytes to a stream and returns the first chunk of bytes
|
|
8
7
|
*/
|
|
9
|
-
export async function
|
|
10
|
-
const log = logger.forComponent('helia:verified-fetch:get-stream-
|
|
8
|
+
export async function getStreamFromAsyncIterable (iterator: AsyncIterable<Uint8Array>, path: string, logger: ComponentLogger, options?: Pick<VerifiedFetchInit, 'onProgress'>): Promise<{ stream: ReadableStream<Uint8Array>, firstChunk: Uint8Array }> {
|
|
9
|
+
const log = logger.forComponent('helia:verified-fetch:get-stream-from-async-iterable')
|
|
11
10
|
const reader = iterator[Symbol.asyncIterator]()
|
|
12
|
-
const { value, done } = await reader.next()
|
|
11
|
+
const { value: firstChunk, done } = await reader.next()
|
|
13
12
|
|
|
14
13
|
if (done === true) {
|
|
15
14
|
log.error('No content found for path', path)
|
|
16
15
|
throw new Error('No content found')
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
const contentType = await getContentType({ bytes: value, path })
|
|
20
18
|
const stream = new ReadableStream({
|
|
21
19
|
async start (controller) {
|
|
22
20
|
// the initial value is already available
|
|
23
21
|
options?.onProgress?.(new CustomProgressEvent<void>('verified-fetch:request:progress:chunk'))
|
|
24
|
-
controller.enqueue(
|
|
22
|
+
controller.enqueue(firstChunk)
|
|
25
23
|
},
|
|
26
24
|
async pull (controller) {
|
|
27
25
|
const { value, done } = await reader.next()
|
|
@@ -40,5 +38,8 @@ export async function getStreamAndContentType (iterator: AsyncIterable<Uint8Arra
|
|
|
40
38
|
}
|
|
41
39
|
})
|
|
42
40
|
|
|
43
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
stream,
|
|
43
|
+
firstChunk
|
|
44
|
+
}
|
|
44
45
|
}
|
package/src/verified-fetch.ts
CHANGED
|
@@ -10,10 +10,10 @@ import { code as dagPbCode } from '@ipld/dag-pb'
|
|
|
10
10
|
import { code as jsonCode } from 'multiformats/codecs/json'
|
|
11
11
|
import { decode, code as rawCode } from 'multiformats/codecs/raw'
|
|
12
12
|
import { CustomProgressEvent } from 'progress-events'
|
|
13
|
-
import {
|
|
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
17
|
import type { Helia } from '@helia/interface'
|
|
18
18
|
import type { AbortOptions, Logger } from '@libp2p/interface'
|
|
19
19
|
import type { UnixFSEntry } from 'ipfs-unixfs-exporter'
|
|
@@ -32,9 +32,8 @@ interface VerifiedFetchComponents {
|
|
|
32
32
|
/**
|
|
33
33
|
* Potential future options for the VerifiedFetch constructor.
|
|
34
34
|
*/
|
|
35
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
36
35
|
interface VerifiedFetchInit {
|
|
37
|
-
|
|
36
|
+
contentTypeParser?: ContentTypeParser
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
interface FetchHandlerFunctionArg {
|
|
@@ -72,6 +71,7 @@ export class VerifiedFetch {
|
|
|
72
71
|
private readonly json: JSON
|
|
73
72
|
private readonly pathWalker: PathWalkerFn
|
|
74
73
|
private readonly log: Logger
|
|
74
|
+
private readonly contentTypeParser: ContentTypeParser | undefined
|
|
75
75
|
|
|
76
76
|
constructor ({ helia, ipns, unixfs, dagJson, json, dagCbor, pathWalker }: VerifiedFetchComponents, init?: VerifiedFetchInit) {
|
|
77
77
|
this.helia = helia
|
|
@@ -87,6 +87,7 @@ export class VerifiedFetch {
|
|
|
87
87
|
this.json = json ?? heliaJson(helia)
|
|
88
88
|
this.dagCbor = dagCbor ?? heliaDagCbor(helia)
|
|
89
89
|
this.pathWalker = pathWalker ?? walkPath
|
|
90
|
+
this.contentTypeParser = init?.contentTypeParser
|
|
90
91
|
this.log.trace('created VerifiedFetch instance')
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -133,13 +134,13 @@ export class VerifiedFetch {
|
|
|
133
134
|
private async handleDagCbor ({ cid, path, options }: FetchHandlerFunctionArg): Promise<Response> {
|
|
134
135
|
this.log.trace('fetching %c/%s', cid, path)
|
|
135
136
|
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:start', { cid: cid.toString(), path }))
|
|
136
|
-
const result = await this.dagCbor.get(cid, {
|
|
137
|
+
const result = await this.dagCbor.get<Uint8Array>(cid, {
|
|
137
138
|
signal: options?.signal,
|
|
138
139
|
onProgress: options?.onProgress
|
|
139
140
|
})
|
|
140
141
|
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid: cid.toString(), path }))
|
|
141
|
-
const response = new Response(
|
|
142
|
-
|
|
142
|
+
const response = new Response(result, { status: 200 })
|
|
143
|
+
await this.setContentType(result, path, response)
|
|
143
144
|
return response
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -179,11 +180,11 @@ export class VerifiedFetch {
|
|
|
179
180
|
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid: resolvedCID.toString(), path: '' }))
|
|
180
181
|
this.log('got async iterator for %c/%s', cid, path)
|
|
181
182
|
|
|
182
|
-
const {
|
|
183
|
+
const { stream, firstChunk } = await getStreamFromAsyncIterable(asyncIter, path ?? '', this.helia.logger, {
|
|
183
184
|
onProgress: options?.onProgress
|
|
184
185
|
})
|
|
185
186
|
const response = new Response(stream, { status: 200 })
|
|
186
|
-
|
|
187
|
+
await this.setContentType(firstChunk, path, response)
|
|
187
188
|
|
|
188
189
|
return response
|
|
189
190
|
}
|
|
@@ -194,10 +195,36 @@ export class VerifiedFetch {
|
|
|
194
195
|
const result = await this.helia.blockstore.get(cid)
|
|
195
196
|
options?.onProgress?.(new CustomProgressEvent<CIDDetail>('verified-fetch:request:end', { cid: cid.toString(), path }))
|
|
196
197
|
const response = new Response(decode(result), { status: 200 })
|
|
197
|
-
|
|
198
|
+
await this.setContentType(result, path, response)
|
|
198
199
|
return response
|
|
199
200
|
}
|
|
200
201
|
|
|
202
|
+
private async setContentType (bytes: Uint8Array, path: string, response: Response): Promise<void> {
|
|
203
|
+
let contentType = 'application/octet-stream'
|
|
204
|
+
|
|
205
|
+
if (this.contentTypeParser != null) {
|
|
206
|
+
try {
|
|
207
|
+
let fileName = path.split('/').pop()?.trim()
|
|
208
|
+
fileName = fileName === '' ? undefined : fileName
|
|
209
|
+
const parsed = this.contentTypeParser(bytes, fileName)
|
|
210
|
+
|
|
211
|
+
if (isPromise(parsed)) {
|
|
212
|
+
const result = await parsed
|
|
213
|
+
|
|
214
|
+
if (result != null) {
|
|
215
|
+
contentType = result
|
|
216
|
+
}
|
|
217
|
+
} else if (parsed != null) {
|
|
218
|
+
contentType = parsed
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
this.log.error('Error parsing content type', err)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
response.headers.set('content-type', contentType)
|
|
226
|
+
}
|
|
227
|
+
|
|
201
228
|
/**
|
|
202
229
|
* Determines the format requested by the client, defaults to `null` if no format is requested.
|
|
203
230
|
*
|
|
@@ -321,3 +348,7 @@ export class VerifiedFetch {
|
|
|
321
348
|
await this.helia.stop()
|
|
322
349
|
}
|
|
323
350
|
}
|
|
351
|
+
|
|
352
|
+
function isPromise <T> (p?: any): p is Promise<T> {
|
|
353
|
+
return p?.then != null
|
|
354
|
+
}
|
|
@@ -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
|
-
}
|