Skip to content

Commit

Permalink
refactor: move set-content-type function (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
SgtPooki authored Dec 9, 2024
1 parent 6803d14 commit 860f361
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 48 deletions.
15 changes: 2 additions & 13 deletions packages/verified-fetch/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ import { type Libp2p, type ServiceMap } from '@libp2p/interface'
import { dns } from '@multiformats/dns'
import { createHelia, type HeliaInit } from 'helia'
import { createLibp2p, type Libp2pOptions } from 'libp2p'
import { type ContentTypeParser } from './types.js'
import { getLibp2pConfig } from './utils/libp2p-defaults.js'
import { VerifiedFetch as VerifiedFetchClass } from './verified-fetch.js'
import type { GetBlockProgressEvents, Helia, Routing } from '@helia/interface'
Expand Down Expand Up @@ -751,19 +752,7 @@ export interface CreateVerifiedFetchOptions {
sessionTTLms?: number
}

/**
* A ContentTypeParser attempts to return the mime type of a given file. It
* receives the first chunk of the file data and the file name, if it is
* available. The function can be sync or async and if it returns/resolves to
* `undefined`, `application/octet-stream` will be used.
*/
export interface ContentTypeParser {
/**
* Attempt to determine a mime type, either via of the passed bytes or the
* filename if it is available.
*/
(bytes: Uint8Array, fileName?: string): Promise<string | undefined> | string | undefined
}
export type { ContentTypeParser } from './types.js'

export type BubbledProgressEvents =
// unixfs-exporter
Expand Down
14 changes: 14 additions & 0 deletions packages/verified-fetch/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,17 @@ export interface FetchHandlerFunctionArg {
*/
resource: string
}

/**
* A ContentTypeParser attempts to return the mime type of a given file. It
* receives the first chunk of the file data and the file name, if it is
* available. The function can be sync or async and if it returns/resolves to
* `undefined`, `application/octet-stream` will be used.
*/
export interface ContentTypeParser {
/**
* Attempt to determine a mime type, either via of the passed bytes or the
* filename if it is available.
*/
(bytes: Uint8Array, fileName?: string): Promise<string | undefined> | string | undefined
}
38 changes: 38 additions & 0 deletions packages/verified-fetch/src/utils/set-content-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { type Logger } from '@libp2p/interface'
import { type ContentTypeParser } from '../types.js'
import { isPromise } from './type-guards.js'

export interface SetContentTypeOptions {
bytes: Uint8Array
path: string
response: Response
defaultContentType?: string
contentTypeParser: ContentTypeParser | undefined
log: Logger
}

export async function setContentType ({ bytes, path, response, contentTypeParser, log, defaultContentType = 'application/octet-stream' }: SetContentTypeOptions): Promise<void> {
let contentType: string | undefined

if (contentTypeParser != null) {
try {
let fileName = path.split('/').pop()?.trim()
fileName = fileName === '' ? undefined : fileName
const parsed = contentTypeParser(bytes, fileName)

if (isPromise(parsed)) {
const result = await parsed

if (result != null) {
contentType = result
}
} else if (parsed != null) {
contentType = parsed
}
} catch (err) {
log.error('error parsing content type', err)
}
}
log.trace('setting content type to "%s"', contentType ?? defaultContentType)
response.headers.set('content-type', contentType ?? defaultContentType)
}
3 changes: 3 additions & 0 deletions packages/verified-fetch/src/utils/type-guards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isPromise <T> (p?: any): p is Promise<T> {
return p?.then != null
}
2 changes: 1 addition & 1 deletion packages/verified-fetch/src/utils/walk-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface PathWalkerFn {
(blockstore: ReadableStorage, path: string, options?: PathWalkerOptions): Promise<PathWalkerResponse>
}

export async function walkPath (blockstore: ReadableStorage, path: string, options?: PathWalkerOptions): Promise<PathWalkerResponse> {
async function walkPath (blockstore: ReadableStorage, path: string, options?: PathWalkerOptions): Promise<PathWalkerResponse> {
const ipfsRoots: CID[] = []
let terminalElement: UnixFSEntry | undefined

Expand Down
38 changes: 4 additions & 34 deletions packages/verified-fetch/src/verified-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { code as dagPbCode } from '@ipld/dag-pb'
import { type AbortOptions, type Logger, type PeerId } from '@libp2p/interface'
import { Record as DHTRecord } from '@libp2p/kad-dht'
import { Key } from 'interface-datastore'
import { exporter } from 'ipfs-unixfs-exporter'
import { exporter, type ObjectNode } from 'ipfs-unixfs-exporter'
import toBrowserReadableStream from 'it-to-browser-readablestream'
import { LRUCache } from 'lru-cache'
import { type CID } from 'multiformats/cid'
Expand All @@ -32,12 +32,12 @@ import { resourceToSessionCacheKey } from './utils/resource-to-cache-key.js'
import { setCacheControlHeader, setIpfsRoots } from './utils/response-headers.js'
import { badRequestResponse, movedPermanentlyResponse, notAcceptableResponse, notSupportedResponse, okResponse, badRangeResponse, okRangeResponse, badGatewayResponse, notFoundResponse } from './utils/responses.js'
import { selectOutputType } from './utils/select-output-type.js'
import { setContentType } from './utils/set-content-type.js'
import { handlePathWalking, isObjectNode } from './utils/walk-path.js'
import type { CIDDetail, ContentTypeParser, CreateVerifiedFetchOptions, Resource, ResourceDetail, VerifiedFetchInit as VerifiedFetchOptions } from './index.js'
import type { FetchHandlerFunctionArg, RequestFormatShorthand } from './types.js'
import type { Helia, SessionBlockstore } from '@helia/interface'
import type { Blockstore } from 'interface-blockstore'
import type { ObjectNode } from 'ipfs-unixfs-exporter'

const SESSION_CACHE_MAX_SIZE = 100
const SESSION_CACHE_TTL_MS = 60 * 1000
Expand Down Expand Up @@ -398,7 +398,7 @@ export class VerifiedFetch {
redirected
})

await this.setContentType(firstChunk, path, response)
await setContentType({ bytes: firstChunk, path, response, contentTypeParser: this.contentTypeParser, log: this.log })
setIpfsRoots(response, ipfsRoots)

return response
Expand Down Expand Up @@ -434,37 +434,11 @@ export class VerifiedFetch {
// if the user has specified an `Accept` header that corresponds to a raw
// type, honour that header, so for example they don't request
// `application/vnd.ipld.raw` but get `application/octet-stream`
await this.setContentType(result, path, response, getOverridenRawContentType({ headers: options?.headers, accept }))
await setContentType({ bytes: result, path, response, defaultContentType: getOverridenRawContentType({ headers: options?.headers, accept }), contentTypeParser: this.contentTypeParser, log: this.log })

return response
}

private async setContentType (bytes: Uint8Array, path: string, response: Response, defaultContentType = 'application/octet-stream'): Promise<void> {
let contentType: string | undefined

if (this.contentTypeParser != null) {
try {
let fileName = path.split('/').pop()?.trim()
fileName = fileName === '' ? undefined : fileName
const parsed = this.contentTypeParser(bytes, fileName)

if (isPromise(parsed)) {
const result = await parsed

if (result != null) {
contentType = result
}
} else if (parsed != null) {
contentType = parsed
}
} catch (err) {
this.log.error('error parsing content type', err)
}
}
this.log.trace('setting content type to "%s"', contentType ?? defaultContentType)
response.headers.set('content-type', contentType ?? defaultContentType)
}

/**
* If the user has not specified an Accept header or format query string arg,
* use the CID codec to choose an appropriate handler for the block data.
Expand Down Expand Up @@ -614,7 +588,3 @@ export class VerifiedFetch {
await this.helia.stop()
}
}

function isPromise <T> (p?: any): p is Promise<T> {
return p?.then != null
}

0 comments on commit 860f361

Please sign in to comment.