From c31b680d95fd4e27ded870b6e7a12cb9c02c8c89 Mon Sep 17 00:00:00 2001 From: 1eon Date: Sun, 16 Nov 2025 16:10:55 +0800 Subject: [PATCH] feat: add dynamic user-agent version updater - Add user-agent-updater.js to automatically fetch latest factory-cli version - Fetch version from https://downloads.factory.ai/factory-cli/LATEST on startup - Automatically refresh version every hour - Implement retry mechanism: max 3 retries with 1-minute intervals on failure - Use user_agent from config.json as fallback value - Update config.js to use dynamic user-agent - Initialize updater in server.js startup sequence --- config.js | 4 +- server.js | 4 ++ user-agent-updater.js | 114 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 user-agent-updater.js diff --git a/config.js b/config.js index 69d193d..01579b1 100644 --- a/config.js +++ b/config.js @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { logInfo } from './logger.js'; +import { getCurrentUserAgent } from './user-agent-updater.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -64,8 +65,7 @@ export function getModelReasoning(modelId) { } export function getUserAgent() { - const cfg = getConfig(); - return cfg.user_agent || 'factory-cli/0.19.3'; + return getCurrentUserAgent(); } export function getProxyConfigs() { diff --git a/server.js b/server.js index a0e52a8..6c2d05d 100644 --- a/server.js +++ b/server.js @@ -3,6 +3,7 @@ import { loadConfig, isDevMode, getPort } from './config.js'; import { logInfo, logError } from './logger.js'; import router from './routes.js'; import { initializeAuth } from './auth.js'; +import { initializeUserAgentUpdater } from './user-agent-updater.js'; const app = express(); @@ -112,6 +113,9 @@ app.use((err, req, res, next) => { logInfo('Configuration loaded successfully'); logInfo(`Dev mode: ${isDevMode()}`); + // Initialize User-Agent version updater + initializeUserAgentUpdater(); + // Initialize auth system (load and setup API key if needed) // This won't throw error if no auth config is found - will use client auth await initializeAuth(); diff --git a/user-agent-updater.js b/user-agent-updater.js new file mode 100644 index 0000000..0c1e067 --- /dev/null +++ b/user-agent-updater.js @@ -0,0 +1,114 @@ +import https from 'https'; +import { logInfo, logError } from './logger.js'; +import { getConfig } from './config.js'; + +const VERSION_URL = 'https://downloads.factory.ai/factory-cli/LATEST'; +const USER_AGENT_PREFIX = 'factory-cli'; +const CHECK_INTERVAL = 60 * 60 * 1000; // 1 hour +const RETRY_INTERVAL = 60 * 1000; // 1 minute +const MAX_RETRIES = 3; + +let currentVersion = null; +let isUpdating = false; + +function getDefaultVersion() { + const cfg = getConfig(); + const userAgent = cfg.user_agent || 'factory-cli/0.19.3'; + const match = userAgent.match(/\/(\d+\.\d+\.\d+)/); + return match ? match[1] : '0.19.3'; +} + +function initializeVersion() { + if (currentVersion === null) { + currentVersion = getDefaultVersion(); + } +} + +export function getCurrentUserAgent() { + initializeVersion(); + return `${USER_AGENT_PREFIX}/${currentVersion}`; +} + +function fetchLatestVersion() { + return new Promise((resolve, reject) => { + const request = https.get(VERSION_URL, (res) => { + let data = ''; + + if (res.statusCode !== 200) { + reject(new Error(`HTTP ${res.statusCode}`)); + return; + } + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + const version = data.trim(); + if (version && /^\d+\.\d+\.\d+/.test(version)) { + resolve(version); + } else { + reject(new Error(`Invalid version format: ${version}`)); + } + }); + }); + + request.on('error', (err) => { + reject(err); + }); + + request.setTimeout(10000, () => { + request.destroy(); + reject(new Error('Request timeout')); + }); + }); +} + +async function updateVersionWithRetry(retryCount = 0) { + if (isUpdating) { + return; + } + + isUpdating = true; + + try { + const version = await fetchLatestVersion(); + if (version !== currentVersion) { + const oldVersion = currentVersion; + currentVersion = version; + logInfo(`User-Agent version updated: ${oldVersion} -> ${currentVersion}`); + } else { + logInfo(`User-Agent version is up to date: ${currentVersion}`); + } + isUpdating = false; + } catch (error) { + logError(`Failed to fetch latest version (attempt ${retryCount + 1}/${MAX_RETRIES})`, error); + + if (retryCount < MAX_RETRIES - 1) { + logInfo(`Retrying in 1 minute...`); + setTimeout(() => { + updateVersionWithRetry(retryCount + 1); + }, RETRY_INTERVAL); + } else { + logError(`Max retries reached. Will try again in next hourly check.`); + isUpdating = false; + } + } +} + +export function initializeUserAgentUpdater() { + initializeVersion(); + logInfo('Initializing User-Agent version updater...'); + logInfo(`Default User-Agent from config: ${USER_AGENT_PREFIX}/${currentVersion}`); + + // Fetch immediately on startup + updateVersionWithRetry(); + + // Schedule hourly checks + setInterval(() => { + logInfo('Running scheduled User-Agent version check...'); + updateVersionWithRetry(); + }, CHECK_INTERVAL); + + logInfo(`User-Agent updater initialized. Will check every hour.`); +}