import crypto from 'node:crypto'; export function createPageDataSessionStore(options = {}) { const ttlMs = Number(options.ttlMs ?? 30 * 60 * 1000); const sessions = new Map(); function hashToken(token) { return crypto.createHash('sha256').update(String(token)).digest('hex'); } function issue({ pageId, ownerUserId, publicationId, accessMode }) { const token = crypto.randomBytes(32).toString('base64url'); const tokenHash = hashToken(token); const expiresAt = Date.now() + ttlMs; sessions.set(tokenHash, { pageId, ownerUserId, publicationId, accessMode, sessionId: crypto.randomUUID(), expiresAt, }); return { token, expiresAt, sessionId: sessions.get(tokenHash).sessionId, }; } function verify(token) { const session = sessions.get(hashToken(token)); if (!session) return null; if (session.expiresAt <= Date.now()) { sessions.delete(hashToken(token)); return null; } return session; } function revoke(token) { return sessions.delete(hashToken(token)); } function revokeByPageId(pageId) { let count = 0; for (const [key, session] of sessions.entries()) { if (session.pageId === pageId) { sessions.delete(key); count += 1; } } return count; } function listByPageId(pageId) { const now = Date.now(); const active = []; for (const session of sessions.values()) { if (session.pageId !== pageId) continue; if (session.expiresAt <= now) continue; active.push({ sessionId: session.sessionId, pageId: session.pageId, ownerUserId: session.ownerUserId, accessMode: session.accessMode, expiresAt: session.expiresAt, }); } return active; } function clearExpired() { const now = Date.now(); for (const [key, session] of sessions.entries()) { if (session.expiresAt <= now) sessions.delete(key); } } return { issue, verify, revoke, revokeByPageId, listByPageId, clearExpired }; } export function createPageDataRateLimiter(options = {}) { const windowMs = Number(options.windowMs ?? 60_000); const maxRequests = Number(options.maxRequests ?? 30); const buckets = new Map(); function check(key) { const now = Date.now(); const bucket = buckets.get(key) ?? { count: 0, resetAt: now + windowMs }; if (now >= bucket.resetAt) { bucket.count = 0; bucket.resetAt = now + windowMs; } bucket.count += 1; buckets.set(key, bucket); if (bucket.count > maxRequests) { throw Object.assign(new Error('请求过于频繁,请稍后再试'), { code: 'rate_limited', status: 429, }); } return { remaining: Math.max(0, maxRequests - bucket.count), resetAt: bucket.resetAt }; } return { check }; } export function hashClientMeta(value) { return crypto.createHash('sha256').update(String(value ?? '')).digest('hex').slice(0, 16); }