Newer
Older
import { config } from './config.js'
import { getRedisKey, isJSFile } from './util.js'
import { getCSSDependenciesFor, getViteManifests } from './manifests.js'
import * as cache from './cache.js'
import { logger } from './logger.js'
import { NotFoundError } from './errors.js'
import * as redis from './redis.js'

richard.petersen
committed
import zlib from 'node:zlib'
import { promisify } from 'node:util'
const gzip = promisify(zlib.gzip)
const compressFileSize = Number(process.env.COMPRESS_FILE_SIZE)
const compressionMimeTypes = (process.env.COMPRESS_FILE_TYPES || '').split(' ')
const compressionWhitelistRegex = new RegExp(`^(${compressionMimeTypes.join('|')})$`)
export function createWritable (body) {
if (typeof body !== 'string' && !(body instanceof Buffer)) return JSON.stringify(body)
return body
}

richard.petersen
committed
async function createFileBuffer (response, dependencies) {
const cssString = dependencies && dependencies.map(file => `"${file}"`).join(',')
const appendix = cssString && `\n/*injected by ui-middleware*/document.dispatchEvent(new CustomEvent("load-css",{detail:{css:[${cssString}]}}))`
const resBuffer = await response.arrayBuffer()
const appendixLength = appendix?.length || 0
const buffer = Buffer.alloc(resBuffer.byteLength + appendixLength)
buffer.fill(Buffer.from(resBuffer), 0, resBuffer.byteLength)
if (appendix) buffer.write(appendix, resBuffer.byteLength)

richard.petersen
committed
}
export async function fetchFileWithHeadersFromBaseUrl (path, baseUrl, version) {
const [response, dependencies] = await Promise.all([
fetch(new URL(path, baseUrl)),
isJSFile(path) && getCSSDependenciesFor({ file: path.substr(1), version })
])
if (!response.ok) throw new NotFoundError(`Error fetching file: ${path}`)
const result = {
body: await createFileBuffer(response, dependencies),
headers: {
'content-type': response.headers.get('content-type'),
if (result.body.length > compressFileSize && compressionWhitelistRegex.test(result.headers['content-type'])) {
result.body = await gzip(result.body)
result.headers['content-encoding'] = 'gzip'
}
return result
export async function fetchFileWithHeaders ({ path, version }) {
const viteManifests = await getViteManifests({ version })
const module = viteManifests[path.substr(1)]
if (module?.meta?.baseUrl) {
try {
return fetchFileWithHeadersFromBaseUrl(path, module.meta.baseUrl, version)
} catch (err) {
logger.debug(`[Files] File ${path} had a baseUrl but could not be found on that server: ${err}`)
return Promise.any(config.urls.map(baseUrl => fetchFileWithHeadersFromBaseUrl(path, baseUrl, version)))
}
export function getFile ({ version, path }) {
const key = getRedisKey({ version, name: `${path}` })
// try to get the file synchronously.
const data = cache.getCache()[key]
logger.debug(`[Files] Resolve from memory: ${key}`)
// if synchronously does not work, store the async promise for further requests
const promise = (async () => {
const bodyKey = getRedisKey({ version, name: `${path}:body` })
const metaKey = getRedisKey({ version, name: `${path}:meta` })
if (redis.isEnabled()) {
const [body, meta = '{}'] = await Promise.all([
redis.client.getBuffer(bodyKey),
redis.client.get(metaKey)
])
if (body) {
logger.debug(`[Files] Resolve from redis: ${key}`)
return (cache.getCache()[key] = { body, ...JSON.parse(meta) })
}
}
const dataFromServer = await fetchFileWithHeaders({ version, path })
if (redis.isEnabled()) {
logger.debug(`[Files] Store in redis: ${key}`)
const { body, ...rest } = dataFromServer
redis.client.set(bodyKey, createWritable(body))
redis.client.set(metaKey, JSON.stringify(rest))
}
// overwrite cache with synchronous data
logger.debug(`[Files] Store in memory: ${key}`)
return (cache.getCache()[key] = dataFromServer)
})()
// temporary set to promise
cache.getCache()[key] = promise
return promise