@aicodewith/ccstatusline
Version:
AiCodeWith credits display plugin for Claude Code with customizable status line
202 lines (176 loc) • 5.84 kB
JavaScript
/**
* 后台刷新进程(已废弃)
*
* 注意:根据 CLAUDE.md 的教训,独立进程模式已被废弃
* 原因:spawn detached 模式会导致进程泄露
*
* 当前方案:主程序使用内置定时器机制,避免进程泄露
* 保留此文件仅为向后兼容
*/
// 立即退出,不再作为独立进程运行
console.log('[Background Refresh] This script is deprecated. Using timer-based refresh instead.');
process.exit(0);
const https = require('https');
const fs = require('fs');
const path = require('path');
const os = require('os');
// ========== 配置常量 ==========
const REFRESH_INTERVAL = Number(process.env.CCSTATUSLINE_REFRESH_INTERVAL_MS || 30000); // 刷新间隔
const CONFIG_DIR = path.join(os.homedir(), '.config', 'ccstatusline', 'aicodewith');
const CACHE_FILE = path.join(CONFIG_DIR, 'full-data-cache.json');
const PID_FILE = path.join(CONFIG_DIR, 'refresh.pid');
const LOG_FILE = path.join(CONFIG_DIR, 'refresh.log');
// API 配置
const API_HOST = 'status.aicodewith.com';
const TIMEOUT_MS = 8000; // 单个请求超时时间
// ========== 初始化 ==========
// 确保配置目录存在
if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, { recursive: true });
}
// 清理旧版缓存文件(如果存在)
const LEGACY_CACHE = path.join(CONFIG_DIR, 'credits-cache.json');
try {
if (fs.existsSync(LEGACY_CACHE)) {
fs.unlinkSync(LEGACY_CACHE);
}
} catch {}
// 写入进程ID,便于其他进程检查
fs.writeFileSync(PID_FILE, process.pid.toString());
// ========== 工具函数 ==========
/**
* 写入日志(控制日志文件大小,超过1MB自动清空)
*/
function log(message) {
try {
// 检查日志文件大小
if (fs.existsSync(LOG_FILE)) {
const stats = fs.statSync(LOG_FILE);
if (stats.size > 1024 * 1024) { // 1MB
fs.writeFileSync(LOG_FILE, ''); // 清空
}
}
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}\n`;
fs.appendFileSync(LOG_FILE, logMessage);
} catch {}
}
/**
* 获取API Key
* 优先级:环境变量 > 配置文件
*/
function getApiKey() {
// 从环境变量获取
if (process.env.CCSTATUSLINE_API_KEY) {
return process.env.CCSTATUSLINE_API_KEY;
}
// 从配置文件获取
const apiConfigFile = path.join(CONFIG_DIR, 'api-config.json');
try {
if (fs.existsSync(apiConfigFile)) {
const config = JSON.parse(fs.readFileSync(apiConfigFile, 'utf8'));
return config.apiKey || null;
}
} catch {}
return null;
}
/**
* 发送HTTPS请求获取JSON数据
*/
function fetchJSON(path, apiKey) {
return new Promise((resolve) => {
const req = https.get({
hostname: API_HOST,
path: path,
headers: {
'X-API-Key': apiKey,
'Cache-Control': 'no-cache',
'Accept': 'application/json'
},
timeout: TIMEOUT_MS
}, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
if (res.statusCode === 200) {
const json = JSON.parse(body);
resolve(json.data);
} else {
resolve(null);
}
} catch {
resolve(null);
}
});
});
req.on('error', () => resolve(null));
req.on('timeout', () => {
req.destroy();
resolve(null);
});
});
}
/**
* 刷新所有数据
* 并行请求三个API端点,获取完整数据
*/
async function refreshData() {
const apiKey = getApiKey();
if (!apiKey) {
log('ERROR: 未找到 API Key');
return;
}
log('开始刷新数据...');
try {
// 并行请求四个端点
const [credits, subscription, brief, summary] = await Promise.all([
fetchJSON('/api/v1/user/credits', apiKey),
fetchJSON('/api/v1/user/subscription', apiKey),
fetchJSON('/api/v1/user/brief', apiKey),
fetchJSON('/api/v1/user/summary', apiKey)
]);
// 组装完整数据
const fullData = {
credits,
subscription,
brief,
summary,
timestamp: Date.now()
};
// 保存到缓存文件
fs.writeFileSync(CACHE_FILE, JSON.stringify(fullData, null, 2));
const creditsValue = credits?.subscription_credits || '-';
log(`数据刷新成功,月卡剩余: ${creditsValue}`);
} catch (error) {
log(`ERROR: 刷新失败 - ${error.message}`);
}
}
/**
* 清理并退出
*/
function cleanup() {
log('后台刷新进程关闭');
try {
fs.unlinkSync(PID_FILE);
} catch {}
process.exit(0);
}
// ========== 主程序 ==========
// 注册退出信号处理
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
process.on('exit', () => {
try { fs.unlinkSync(PID_FILE); } catch {}
});
// 启动日志
log('========================================');
log(`后台刷新进程启动 (PID: ${process.pid})`);
log(`刷新间隔: ${REFRESH_INTERVAL}ms`);
// 立即执行一次刷新
refreshData();
// 设置定时刷新
setInterval(refreshData, REFRESH_INTERVAL);
// 保持进程运行
process.stdin.resume();