diff --git a/src/config/app.ts b/src/config/app.ts index 937e4e2..b382354 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -31,6 +31,17 @@ export const appConfig: AppConfig = { // Log stuff log: true, + + // Enable update + allowUpdate: true, + + // Required parameter to include in `/update` query to trigger update + // Value must match environment variable `APP_UPDATE_SECRET` + updateRequiredParam: 'secret', + + // Update check throttling + // Delay to wait between successful update request and actual update + updateThrottle: 60, }; /** diff --git a/src/data/icon-sets.ts b/src/data/icon-sets.ts index c81e0d0..68c0d80 100644 --- a/src/data/icon-sets.ts +++ b/src/data/icon-sets.ts @@ -84,3 +84,26 @@ export function updateIconSets(): number { prefixes = Array.from(newPrefixes); return prefixes.length; } + +/** + * Trigger update + */ +export function triggerIconSetsUpdate() { + if (!importers) { + return; + } + console.log('Checking for updates...'); + + (async () => { + let updated = false; + for (let i = 0; i < importers?.length; i++) { + updated = (await importers[i].checkForUpdate()) || updated; + } + return updated; + })() + .then((updated) => { + console.log(updated ? 'Update complete' : 'Nothing to update'); + updateIconSets(); + }) + .catch(console.error); +} diff --git a/src/http/index.ts b/src/http/index.ts index 981bec0..93a1231 100644 --- a/src/http/index.ts +++ b/src/http/index.ts @@ -5,6 +5,7 @@ import { iconNameRoutePartialRegEx, iconNameRouteRegEx, splitIconName } from '.. import { generateIconsDataResponse } from './responses/icons'; import { generateLastModifiedResponse } from './responses/modified'; import { generateSVGResponse } from './responses/svg'; +import { generateUpdateResponse } from './responses/update'; import { initVersionResponse, versionResponse } from './responses/version'; /** @@ -105,6 +106,14 @@ export async function startHTTPServer() { }); }); + // Update icon sets + server.get('/update', (req, res) => { + generateUpdateResponse(req.query, res); + }); + server.post('/update', (req, res) => { + generateUpdateResponse(req.query, res); + }); + // Options server.options('/*', (req, res) => { res.send(200); diff --git a/src/http/responses/update.ts b/src/http/responses/update.ts new file mode 100644 index 0000000..01fe083 --- /dev/null +++ b/src/http/responses/update.ts @@ -0,0 +1,64 @@ +import type { FastifyReply, FastifyRequest } from 'fastify'; +import { appConfig } from '../../config/app'; +import { triggerIconSetsUpdate } from '../../data/icon-sets'; +import { runWhenLoaded } from '../../data/loading'; + +let pendingUpdate = false; +let lastError = 0; + +const envKey = 'APP_UPDATE_SECRET'; + +function logError(msg: string) { + const time = Date.now(); + + // Do not log error too often + if (time > lastError + 3600000) { + lastError = time; + console.error(msg); + } +} + +function checkKey(query: Record): boolean { + if (appConfig.updateRequiredParam) { + const expectedValue = process.env[envKey]; + if (!expectedValue) { + // Missing env variable + logError(`Cannot process update request: missing env variable "${envKey}"`); + return false; + } + + const value = query[appConfig.updateRequiredParam]; + if (value !== expectedValue) { + return false; + } + + // Success + return true; + } + + // No param + logError( + 'Auto-update can be triggered by anyone. Set `updateRequiredParam` config or UPDATE_REQUIRED_PARAM env variable to require secret to trigger update' + ); + return true; +} + +/** + * Generate icons data + */ +export function generateUpdateResponse(query: FastifyRequest['query'], res: FastifyReply) { + if (appConfig.allowUpdate && checkKey((query || {}) as Record) && !pendingUpdate) { + pendingUpdate = true; + runWhenLoaded(() => { + const delay = appConfig.updateThrottle; + console.log('Will check for update in', delay, 'seconds...'); + setTimeout(() => { + triggerIconSetsUpdate(); + pendingUpdate = false; + }, delay * 1000); + }); + } + + // Fake 404 + res.send(404); +} diff --git a/src/types/config/app.ts b/src/types/config/app.ts index 8b639ff..8f8d303 100644 --- a/src/types/config/app.ts +++ b/src/types/config/app.ts @@ -23,4 +23,13 @@ export interface AppConfig { // Logging log: boolean; + + // Allow update + allowUpdate: boolean; + + // Update key + updateRequiredParam: string; + + // Update check throttling + updateThrottle: number; }