Files

157 lines
4.8 KiB
JavaScript
Raw Permalink Normal View History

2026-06-20 04:47:34 +08:00
/**
* 会话 Cookie 持久化:按 sessionKey 保存完整 cookie jar,降低重复过盾概率
2026-06-20 04:47:34 +08:00
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { RUNTIME_DIR, SESSION_TTL_MS } = require('./constants');
const SESSIONS_DIR = path.join(RUNTIME_DIR, 'sessions');
function ensureSessionsDir() {
fs.mkdirSync(SESSIONS_DIR, { recursive: true, mode: 0o777 });
}
function keyToFilename(sessionKey) {
const hash = crypto.createHash('md5').update(sessionKey).digest('hex');
return `${hash}.json`;
}
/**
* @param {{name:string,domain?:string,path?:string}} cookie
* @returns {string}
2026-06-20 04:47:34 +08:00
*/
function cookieIdentity(cookie) {
return `${cookie.name}|${cookie.domain || ''}|${cookie.path || '/'}`;
}
function mergeCookies(requestCookies, sessionCookies) {
const map = new Map();
for (const cookie of sessionCookies || []) {
if (cookie && cookie.name) map.set(cookieIdentity(cookie), cookie);
}
for (const cookie of requestCookies || []) {
if (cookie && cookie.name) map.set(cookieIdentity(cookie), cookie);
}
return Array.from(map.values());
}
function isSessionLikelyValid(session) {
if (!session || !Array.isArray(session.cookies)) return false;
if (session.expiresAt && Date.now() > session.expiresAt) return false;
return session.cookies.some((c) => c.name === 'cf_clearance' && c.value);
}
function hasCfClearance(session) {
if (!session || !Array.isArray(session.cookies)) return false;
return session.cookies.some((c) => c.name === 'cf_clearance' && c.value);
}
2026-06-20 04:47:34 +08:00
function loadSession(sessionKey) {
if (!sessionKey) return null;
ensureSessionsDir();
const filePath = path.join(SESSIONS_DIR, keyToFilename(sessionKey));
if (!fs.existsSync(filePath)) return null;
try {
const raw = JSON.parse(fs.readFileSync(filePath, 'utf8'));
if (!raw || !Array.isArray(raw.cookies)) return null;
if (raw.expiresAt && Date.now() > raw.expiresAt) {
fs.unlinkSync(filePath);
return null;
}
return raw;
} catch (_) {
return null;
}
}
function touchSession(sessionKey, ttlMs = SESSION_TTL_MS) {
const session = loadSession(sessionKey);
if (!session) return;
const now = Date.now();
session.savedAt = now;
session.expiresAt = now + ttlMs;
fs.writeFileSync(path.join(SESSIONS_DIR, keyToFilename(sessionKey)), JSON.stringify(session, null, 0), { mode: 0o666 });
}
function saveSession(sessionKey, cookies, ttlMs = SESSION_TTL_MS, meta = {}) {
2026-06-20 04:47:34 +08:00
if (!sessionKey || !Array.isArray(cookies) || cookies.length === 0) return;
ensureSessionsDir();
const now = Date.now();
const payload = {
sessionKey,
savedAt: now,
lastSuccessAt: now,
2026-06-20 04:47:34 +08:00
expiresAt: now + ttlMs,
lastHost: meta.lastHost || '',
savedProfile: meta.savedProfile || '',
cookies: cookies.map((c) => ({
2026-06-20 04:47:34 +08:00
name: c.name,
value: c.value,
domain: c.domain,
path: c.path || '/',
})),
};
fs.writeFileSync(path.join(SESSIONS_DIR, keyToFilename(sessionKey)), JSON.stringify(payload, null, 0), { mode: 0o666 });
2026-06-20 04:47:34 +08:00
}
function sessionCookiesForPage(session, pageUrl) {
if (!session || !Array.isArray(session.cookies)) return [];
let hostname = '';
try {
hostname = new URL(pageUrl).hostname;
} catch (_) {
return session.cookies;
}
return session.cookies.map((c) => {
const cookie = { ...c };
if (!cookie.domain && hostname) cookie.url = `https://${hostname}/`;
2026-06-20 04:47:34 +08:00
return cookie;
});
}
function getSessionDebugInfo(sessionKey) {
if (!sessionKey) return null;
const filePath = path.join(SESSIONS_DIR, keyToFilename(sessionKey));
const session = loadSession(sessionKey);
if (!session) {
return { sessionKey, exists: false, filePath };
}
let mtime = null;
try {
mtime = fs.statSync(filePath).mtime.toISOString();
} catch (_) { /* ignore */ }
return {
sessionKey,
exists: true,
filePath,
mtime,
savedAt: session.savedAt,
expiresAt: session.expiresAt,
lastSuccessAt: session.lastSuccessAt,
lastHost: session.lastHost,
savedProfile: session.savedProfile,
cookieCount: session.cookies.length,
hasCfClearance: hasCfClearance(session),
likelyValid: isSessionLikelyValid(session),
};
}
function getSessionStats() {
ensureSessionsDir();
const count = fs.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith('.json')).length;
return { sessionsDir: SESSIONS_DIR, count };
}
2026-06-20 04:47:34 +08:00
module.exports = {
loadSession,
saveSession,
touchSession,
2026-06-20 04:47:34 +08:00
sessionCookiesForPage,
mergeCookies,
isSessionLikelyValid,
hasCfClearance,
getSessionDebugInfo,
getSessionStats,
2026-06-20 04:47:34 +08:00
};