monitor/core/modules/UpdateChecker/index.ts
2025-04-16 22:30:27 +07:00

158 lines
5.1 KiB
TypeScript

const modulename = 'UpdateChecker';
import { txEnv } from '@core/globalData';
import consoleFactory from '@lib/console';
import { UpdateDataType } from '@shared/otherTypes';
import { UpdateAvailableEventType } from '@shared/socketioTypes';
import { queryChangelogApi } from './queryChangelogApi';
import { getUpdateRolloutDelay } from './updateRollout';
const console = consoleFactory(modulename);
type CachedDelayType = {
ts: number,
diceRoll: number,
}
/**
* Creates a cache string.
*/
const createCacheString = (delayData: CachedDelayType) => {
return `${delayData.ts},${delayData.diceRoll}`;
}
/**
* Parses the cached string.
* Format: "ts,diceRoll"
*/
const parseCacheString = (raw: any) => {
if (typeof raw !== 'string' || !raw) return;
const [ts, diceRoll] = raw.split(',');
const obj = {
ts: parseInt(ts),
diceRoll: parseInt(diceRoll),
} satisfies CachedDelayType;
if (isNaN(obj.ts) || isNaN(obj.diceRoll)) return;
return obj;
}
/**
* Rolls dice, gets integer between 0 and 100
*/
const rollDice = () => {
return Math.floor(Math.random() * 101);
}
const DELAY_CACHE_KEY = 'updateDelay';
/**
* Module to check for updates and notify the user according to a rollout strategy randomly picked.
*/
export default class UpdateChecker {
txaUpdateData?: UpdateDataType;
fxsUpdateData?: UpdateDataType;
constructor() {
//Check for updates ASAP
setImmediate(() => {
this.checkChangelog();
});
//Check again every 15 mins
setInterval(() => {
this.checkChangelog();
}, 15 * 60_000);
}
/**
* Check for txAdmin and FXServer updates
*/
async checkChangelog() {
const updates = await queryChangelogApi();
if (!updates) return;
//If fxserver, don't print anything, just update the data
if (updates.fxs) {
this.fxsUpdateData = {
version: updates.fxs.version,
isImportant: updates.fxs.isImportant,
}
}
//If txAdmin update, check for delay before printing
if (updates.txa) {
//Setup delay data
const currTs = Date.now();
let delayData: CachedDelayType;
const rawCache = txCore.cacheStore.get(DELAY_CACHE_KEY);
const cachedData = parseCacheString(rawCache);
if (cachedData) {
delayData = cachedData;
} else {
delayData = {
diceRoll: rollDice(),
ts: currTs,
}
txCore.cacheStore.set(DELAY_CACHE_KEY, createCacheString(delayData));
}
//Get the delay
const notifDelayDays = getUpdateRolloutDelay(
updates.txa.semverDiff,
txEnv.txaVersion.includes('-'),
delayData.diceRoll
);
const notifDelayMs = notifDelayDays * 24 * 60 * 60 * 1000;
console.verbose.debug(`Update available, notification delayed by: ${notifDelayDays} day(s).`);
if (currTs - delayData.ts >= notifDelayMs) {
txCore.cacheStore.delete(DELAY_CACHE_KEY);
this.txaUpdateData = {
version: updates.txa.version,
isImportant: updates.txa.isImportant,
}
if (updates.txa.isImportant) {
console.error('This version of txAdmin is outdated.');
console.error('Please update as soon as possible.');
console.error('For more information: https://discord.gg/uAmsGa2');
} else {
console.warn('This version of txAdmin is outdated.');
console.warn('A patch (bug fix) update is available for txAdmin.');
console.warn('If you are experiencing any kind of issue, please update now.');
console.warn('For more information: https://discord.gg/uAmsGa2');
}
}
}
//Sending event to the UI
if (this.txaUpdateData || this.fxsUpdateData) {
txCore.webServer.webSocket.pushEvent<UpdateAvailableEventType>('updateAvailable', {
fxserver: this.fxsUpdateData,
txadmin: this.txaUpdateData,
});
}
}
};
/*
TODO:
Create an page with the changelog, that queries for the following endpoint and caches it for 15 minutes:
https://changelogs-live.fivem.net/api/changelog/versions/2385/2375?tag=server
Maybe even grab the data from commits:
https://changelogs-live.fivem.net/api/changelog/versions/5562
Other relevant apis:
https://changelogs-live.fivem.net/api/changelog/versions/win32/server? (the one being used below)
https://changelogs-live.fivem.net/api/changelog/versions
https://api.github.com/repos/tabarra/txAdmin/releases (changelog in [].body)
NOTE: old logic
if == recommended, you're fine
if > recommended && < optional, pls update to optional
if == optional, you're fine
if > optional && < latest, pls update to latest
if == latest, duh
if < critical, BIG WARNING
*/