// Cookie management for Epic Games // Based on https://github.com/claabs/epicgames-freegames-node import fs from 'node:fs'; import path from 'node:path'; import tough from 'tough-cookie'; import { filenamify } from './util.js'; import { dataDir } from './util.js'; const CONFIG_DIR = dataDir('config'); const DEFAULT_COOKIE_NAME = 'default'; // Ensure config directory exists if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, { recursive: true }); } function getCookiePath(username) { const fileSafeUsername = filenamify(username); const cookieFilename = path.join(CONFIG_DIR, `${fileSafeUsername}-cookies.json`); return cookieFilename; } // Cookie whitelist - only these cookies are stored const COOKIE_WHITELIST = ['EPIC_SSO_RM', 'EPIC_SESSION_AP', 'EPIC_DEVICE']; // Cookie jar cache const cookieJars = new Map(); function getCookieJar(username) { let cookieJar = cookieJars.get(username); if (cookieJar) { return cookieJar; } const cookieFilename = getCookiePath(username); cookieJar = new tough.CookieJar(); cookieJars.set(username, cookieJar); return cookieJar; } // Convert EditThisCookie format to tough-cookie file store format export function editThisCookieToToughCookieFileStore(etc) { const tcfs = {}; etc.forEach((etcCookie) => { const domain = etcCookie.domain.replace(/^\./, ''); const expires = etcCookie.expirationDate ? new Date(etcCookie.expirationDate * 1000).toISOString() : undefined; const { path: cookiePath, name } = etcCookie; if (COOKIE_WHITELIST.includes(name)) { const temp = { [domain]: { [cookiePath]: { [name]: { key: name, value: etcCookie.value, expires, domain, path: cookiePath, secure: etcCookie.secure, httpOnly: etcCookie.httpOnly, hostOnly: etcCookie.hostOnly, }, }, }, }; Object.assign(tcfs, temp); } }); return tcfs; } // Get cookies as simple object export function getCookies(username) { const cookieJar = getCookieJar(username); const cookies = cookieJar.toJSON()?.cookies || []; return cookies.reduce((accum, cookie) => { if (cookie.key && cookie.value) { return { ...accum, [cookie.key]: cookie.value }; } return accum; }, {}); } // Get raw cookies in tough-cookie file store format export async function getCookiesRaw(username) { const cookieFilename = getCookiePath(username); try { const existingCookies = JSON.parse(fs.readFileSync(cookieFilename, 'utf8')); return existingCookies; } catch { return {}; } } // Set cookies from Playwright/Cookie format export async function setPuppeteerCookies(username, newCookies) { const cookieJar = getCookieJar(username); for (const cookie of newCookies) { const domain = cookie.domain.replace(/^\./, ''); const tcfsCookie = new tough.Cookie({ key: cookie.name, value: cookie.value, expires: cookie.expires ? new Date(cookie.expires * 1000) : undefined, domain, path: cookie.path, secure: cookie.secure, httpOnly: cookie.httpOnly, hostOnly: !cookie.domain.startsWith('.'), }); try { await cookieJar.setCookie(tcfsCookie, `https://${domain}`); } catch (err) { console.error('Error setting cookie:', err); } } } // Delete cookies for a user export async function deleteCookies(username) { const cookieFilename = getCookiePath(username || DEFAULT_COOKIE_NAME); try { fs.unlinkSync(cookieFilename); } catch { // File doesn't exist, that's fine } } // Check if user has a valid cookie export async function userHasValidCookie(username, cookieName) { const cookieFilename = getCookiePath(username); try { const fileExists = fs.existsSync(cookieFilename); if (!fileExists) return false; const cookieData = JSON.parse(fs.readFileSync(cookieFilename, 'utf8')); const rememberCookieExpireDate = cookieData['epicgames.com']?.['/']?.[cookieName]?.expires; if (!rememberCookieExpireDate) return false; return new Date(rememberCookieExpireDate) > new Date(); } catch { return false; } } // Convert imported cookies (EditThisCookie format) export async function convertImportCookies(username) { const cookieFilename = getCookiePath(username); const fileExists = fs.existsSync(cookieFilename); if (fileExists) { try { const cookieData = fs.readFileSync(cookieFilename, 'utf8'); const cookieTest = JSON.parse(cookieData); if (Array.isArray(cookieTest)) { // Convert from EditThisCookie format const tcfsCookies = editThisCookieToToughCookieFileStore(cookieTest); fs.writeFileSync(cookieFilename, JSON.stringify(tcfsCookies, null, 2)); } } catch { // Invalid format, delete file fs.unlinkSync(cookieFilename); } } }