"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.cleanupExpiredSessions = cleanupExpiredSessions;exports.clearSession = clearSession;exports.getAllSessions = getAllSessions;exports.loadSession = loadSession;exports.saveSession = saveSession;exports.updateLastSeq = updateLastSeq; var _nodeFs = _interopRequireDefault(require("node:fs")); var _nodePath = _interopRequireDefault(require("node:path"));function _interopRequireDefault(e) {return e && e.__esModule ? e : { default: e };} /** * Session 持久化存储 * 将 WebSocket 连接状态(sessionId、lastSeq)持久化到文件 * 支持进程重启后通过 Resume 机制快速恢复连接 */ // Session 状态接口 // Session 文件目录 const SESSION_DIR = _nodePath.default.join( process.env.HOME || "/tmp", ".openclaw", "qqbot", "sessions" ); // Session 过期时间(5分钟)- Resume 要求在断开后一定时间内恢复 const SESSION_EXPIRE_TIME = 5 * 60 * 1000; // 写入节流时间(避免频繁写入) const SAVE_THROTTLE_MS = 1000; // 每个账户的节流状态 const throttleState = new Map( ); /** * 确保目录存在 */ function ensureDir() { if (!_nodeFs.default.existsSync(SESSION_DIR)) { _nodeFs.default.mkdirSync(SESSION_DIR, { recursive: true }); } } /** * 获取 Session 文件路径 */ function getSessionPath(accountId) { // 清理 accountId 中的特殊字符 const safeId = accountId.replace(/[^a-zA-Z0-9_-]/g, "_"); return _nodePath.default.join(SESSION_DIR, `session-${safeId}.json`); } /** * 加载 Session 状态 * @param accountId 账户 ID * @returns Session 状态,如果不存在或已过期返回 null */ function loadSession(accountId) { const filePath = getSessionPath(accountId); try { if (!_nodeFs.default.existsSync(filePath)) { return null; } const data = _nodeFs.default.readFileSync(filePath, "utf-8"); const state = JSON.parse(data); // 检查是否过期 const now = Date.now(); if (now - state.savedAt > SESSION_EXPIRE_TIME) { console.log(`[session-store] Session expired for ${accountId}, age: ${Math.round((now - state.savedAt) / 1000)}s`); // 删除过期文件 try { _nodeFs.default.unlinkSync(filePath); } catch { // 忽略删除错误 }return null; } // 验证必要字段 if (!state.sessionId || state.lastSeq === null || state.lastSeq === undefined) { console.log(`[session-store] Invalid session data for ${accountId}`); return null; } console.log(`[session-store] Loaded session for ${accountId}: sessionId=${state.sessionId}, lastSeq=${state.lastSeq}, age=${Math.round((now - state.savedAt) / 1000)}s`); return state; } catch (err) { console.error(`[session-store] Failed to load session for ${accountId}: ${err}`); return null; } } /** * 保存 Session 状态(带节流,避免频繁写入) * @param state Session 状态 */ function saveSession(state) { const { accountId } = state; // 获取或初始化节流状态 let throttle = throttleState.get(accountId); if (!throttle) { throttle = { pendingState: null, lastSaveTime: 0, throttleTimer: null }; throttleState.set(accountId, throttle); } const now = Date.now(); const timeSinceLastSave = now - throttle.lastSaveTime; // 如果距离上次保存时间足够长,立即保存 if (timeSinceLastSave >= SAVE_THROTTLE_MS) { doSaveSession(state); throttle.lastSaveTime = now; throttle.pendingState = null; // 清除待定的节流定时器 if (throttle.throttleTimer) { clearTimeout(throttle.throttleTimer); throttle.throttleTimer = null; } } else { // 记录待保存的状态 throttle.pendingState = state; // 如果没有设置定时器,设置一个 if (!throttle.throttleTimer) { const delay = SAVE_THROTTLE_MS - timeSinceLastSave; throttle.throttleTimer = setTimeout(() => { const t = throttleState.get(accountId); if (t && t.pendingState) { doSaveSession(t.pendingState); t.lastSaveTime = Date.now(); t.pendingState = null; } if (t) { t.throttleTimer = null; } }, delay); } } } /** * 实际执行保存操作 */ function doSaveSession(state) { const filePath = getSessionPath(state.accountId); try { ensureDir(); // 更新保存时间 const stateToSave = { ...state, savedAt: Date.now() }; _nodeFs.default.writeFileSync(filePath, JSON.stringify(stateToSave, null, 2), "utf-8"); console.log(`[session-store] Saved session for ${state.accountId}: sessionId=${state.sessionId}, lastSeq=${state.lastSeq}`); } catch (err) { console.error(`[session-store] Failed to save session for ${state.accountId}: ${err}`); } } /** * 清除 Session 状态 * @param accountId 账户 ID */ function clearSession(accountId) { const filePath = getSessionPath(accountId); // 清除节流状态 const throttle = throttleState.get(accountId); if (throttle) { if (throttle.throttleTimer) { clearTimeout(throttle.throttleTimer); } throttleState.delete(accountId); } try { if (_nodeFs.default.existsSync(filePath)) { _nodeFs.default.unlinkSync(filePath); console.log(`[session-store] Cleared session for ${accountId}`); } } catch (err) { console.error(`[session-store] Failed to clear session for ${accountId}: ${err}`); } } /** * 更新 lastSeq(轻量级更新) * @param accountId 账户 ID * @param lastSeq 最新的消息序号 */ function updateLastSeq(accountId, lastSeq) { const existing = loadSession(accountId); if (existing && existing.sessionId) { saveSession({ ...existing, lastSeq }); } } /** * 获取所有保存的 Session 状态 */ function getAllSessions() { const sessions = []; try { ensureDir(); const files = _nodeFs.default.readdirSync(SESSION_DIR); for (const file of files) { if (file.startsWith("session-") && file.endsWith(".json")) { const filePath = _nodePath.default.join(SESSION_DIR, file); try { const data = _nodeFs.default.readFileSync(filePath, "utf-8"); const state = JSON.parse(data); sessions.push(state); } catch { // 忽略解析错误 }} } } catch { // 目录不存在等错误 } return sessions; } /** * 清理过期的 Session 文件 */ function cleanupExpiredSessions() { let cleaned = 0; try { ensureDir(); const files = _nodeFs.default.readdirSync(SESSION_DIR); const now = Date.now(); for (const file of files) { if (file.startsWith("session-") && file.endsWith(".json")) { const filePath = _nodePath.default.join(SESSION_DIR, file); try { const data = _nodeFs.default.readFileSync(filePath, "utf-8"); const state = JSON.parse(data); if (now - state.savedAt > SESSION_EXPIRE_TIME) { _nodeFs.default.unlinkSync(filePath); cleaned++; console.log(`[session-store] Cleaned expired session: ${file}`); } } catch { // 忽略解析错误,但也删除损坏的文件 try { _nodeFs.default.unlinkSync(filePath); cleaned++; } catch { // 忽略 }} } } } catch { // 目录不存在等错误 } return cleaned; } /* v9-8a3988cfdcbbe21a */