| | import path from 'path' |
| | import fs from 'fs' |
| | import crypto from 'crypto' |
| |
|
| | import { fetchWithRetry } from './fetch-utils' |
| | import statsd from '@/observability/lib/statsd' |
| |
|
| | |
| | |
| | |
| | |
| | export const cache = new Map<string, any>() |
| |
|
| | const inProd = process.env.NODE_ENV === 'production' |
| |
|
| | interface GetRemoteJSONConfig { |
| | retry?: { |
| | limit?: number |
| | } |
| | timeout?: { |
| | response?: number |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export default async function getRemoteJSON( |
| | url: string, |
| | config?: GetRemoteJSONConfig, |
| | ): Promise<any> { |
| | |
| | |
| | |
| | const cacheKey = url |
| |
|
| | |
| | |
| | let fromCache = 'memory' |
| |
|
| | if (!cache.has(cacheKey)) { |
| | fromCache = 'not' |
| |
|
| | let foundOnDisk = false |
| | const tempFilename = crypto.createHash('md5').update(url).digest('hex') |
| |
|
| | |
| | |
| | const ROOT = process.env.GET_REMOTE_JSON_DISK_CACHE_ROOT || '.remotejson-cache' |
| |
|
| | const onDisk = path.join(ROOT, `${tempFilename}.json`) |
| |
|
| | try { |
| | const body = fs.readFileSync(onDisk, 'utf-8') |
| | |
| | if (body) { |
| | try { |
| | |
| | cache.set(cacheKey, JSON.parse(body)) |
| | fromCache = 'disk' |
| | foundOnDisk = true |
| | } catch (error) { |
| | if (!(error instanceof SyntaxError)) { |
| | throw error |
| | } |
| | } |
| | } |
| | } catch (error) { |
| | if ( |
| | !( |
| | error instanceof SyntaxError || |
| | (error instanceof Error && |
| | 'code' in error && |
| | (error as NodeJS.ErrnoException).code === 'ENOENT') |
| | ) |
| | ) { |
| | throw error |
| | } |
| | } |
| |
|
| | if (!foundOnDisk) { |
| | |
| | |
| | |
| | |
| | const retries = config?.retry?.limit || 0 |
| | const timeout = config?.timeout?.response |
| |
|
| | const res = await fetchWithRetry( |
| | url, |
| | {}, |
| | { |
| | retries, |
| | timeout, |
| | throwHttpErrors: true, |
| | }, |
| | ) |
| |
|
| | const contentType = res.headers.get('content-type') |
| | if (!contentType || !contentType.startsWith('application/json')) { |
| | throw new Error(`Fetching '${url}' resulted in a non-JSON response (${contentType})`) |
| | } |
| |
|
| | const body = await res.text() |
| | cache.set(cacheKey, JSON.parse(body)) |
| |
|
| | |
| | |
| | if (!inProd) { |
| | fs.mkdirSync(path.dirname(onDisk), { recursive: true }) |
| | fs.writeFileSync(onDisk, body, 'utf-8') |
| | } |
| | } |
| | } |
| | const tags = [`from_cache:${fromCache}`] |
| | statsd.increment('middleware.get_remote_json', 1, tags) |
| | return cache.get(cacheKey) |
| | } |
| |
|