-
richard.petersen authored
Root cause: Version has was updated before file cache Solution: Keep all updates in one place and switch to new version at the same time
richard.petersen authoredRoot cause: Version has was updated before file cache Solution: Keep all updates in one place and switch to new version at the same time
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
files.js 3.50 KiB
import fetch from 'node-fetch'
import crypto from 'crypto'
import { config } from './config.js'
import promClient from 'prom-client'
import { logger } from './logger.js'
import { isJSFile, viteToOxManifest } from './util.js'
async function fetchData (path, baseUrl, appendix) {
const response = await fetch(new URL(path, baseUrl))
if (!response.ok) throw new Error(`Error fetching file: ${path}`)
const resBuffer = await response.arrayBuffer()
const appendixLength = appendix?.length || 0
const content = Buffer.alloc(resBuffer.byteLength + appendixLength)
content.fill(Buffer.from(resBuffer), 0, resBuffer.byteLength)
if (appendix) content.write(appendix, resBuffer.byteLength)
const sha256Sum = crypto.createHash('sha256').update(content).digest('base64')
return [path, {
'content-type': response.headers.get('content-type'),
sha256Sum,
content
}]
}
const fileCounter = new promClient.Counter({
name: 'manifest_service_file_cache_fetches',
help: 'Number of fetched files'
})
const fileErrorCounter = new promClient.Counter({
name: 'manifest_service_file_cache_fetch_errors',
help: 'Number of errors while fetching files'
})
class FileCache {
constructor () {
this._cache = {}
this._manifests = {}
this._hash = ''
this._dependencies = {}
this._oxManifests = {}
}
async warmUp (manifests, deps) {
logger.debug('beginning to warm up cache')
const cache = Object.fromEntries(await (async function () {
const files = Object.keys(deps)
const chunkSize = 50
const result = []
while (files.length > 0) {
result.push.apply(result, (await Promise.all(files.splice(0, chunkSize).map(async file => {
try {
const manifest = manifests[file] || Object.values(manifests).find(m =>
m.file === file ||
(m?.assets?.indexOf(file) >= 0) ||
(m?.css?.indexOf(file) >= 0)
)
if (!manifest) {
logger.error(`could not find manifest for "${file}"`)
return null
}
let appendix
if (manifest.css && isJSFile(file)) {
const cssString = manifest.css.map(file => `"${file}"`).join(',')
appendix = `\n/*injected by ui-middleware*/document.dispatchEvent(new CustomEvent("load-css",{detail:{css:[${cssString}]}}))`
}
return await fetchData(file, manifest.meta.baseUrl, appendix)
} catch (e) {
fileErrorCounter.inc()
logger.error(e)
}
}))).filter(data => Array.isArray(data) && data.length === 2))
}
fileCounter.inc(result.length)
return result
}()))
this._cache = cache
this._manifests = manifests
this._hash = `${+new Date()}.${manifests.__hash__}`
this._dependencies = deps
this._oxManifests = viteToOxManifest(manifests)
logger.debug('cache warmed up')
}
async fetchAndStore (path) {
if (config.urls.length === 0) await config.load()
const [[key, value]] =
(await Promise.allSettled(config.urls.map(baseUrl => fetchData(path, baseUrl))))
.filter(r => r.status === 'fulfilled').map(r => r.value)
this._cache[key] = value
return value
}
get (path) {
return this?._cache[path.slice(1)] || {}
}
get manifests () {
return this?._manifests
}
get hash () {
return this?._hash
}
get dependencies () {
return this?._dependencies
}
get oxManifests () {
return this?._oxManifests
}
}
export const fileCache = new FileCache()