import type { Browser, BrowserContext, Page } from 'playwright' import { chromium } from 'playwright-extra' import StealthPlugin from 'puppeteer-extra-plugin-stealth' import { store } from './storage.js' import { logMemory } from './utils.js' chromium.use(StealthPlugin()) const createContext = async (browser: Browser) => { const context = await browser.newContext({ locale: 'en-US', timezoneId: 'America/New_York', bypassCSP: true, deviceScaleFactor: 1, }) await context.addCookies([ { name: 'sp_dc', value: process.env.SP_DC!, domain: '.spotify.com', path: '/', httpOnly: true, secure: true, sameSite: 'None', }, { name: 'sp_key', value: process.env.SP_KEY!, domain: '.spotify.com', path: '/', httpOnly: false, secure: true, sameSite: 'None', }, ]) return context } export const createPageFromBrowser = async (browser: Browser) => { const context = await createContext(browser) const page = await context.newPage() await page.route('**/*.{png,jpg,jpeg,gif,woff,woff2,sentry}', (r) => r.abort()) await page.route('**/*{onetrust,i.scdn.co/image/,mosaic.scdn.co/,encore.scdn.co/fonts}*', (r) => r.abort()) return page } let browserPromise: Promise | null = null export async function ensureBrowser(): Promise { if (browserPromise) { const b = await browserPromise.catch(() => null) if (!b || !b.isConnected()) { browserPromise = null store.browser = null } } if (!browserPromise) { browserPromise = (async () => { const b = await chromium.launch({ headless: true, args: ['--disable-dev-shm-usage', '--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu'], }) b.on('disconnected', () => { console.warn('Browser disconnected') browserPromise = null store.browser = null }) return b })() } const browser = await browserPromise store.browser = browser return browser } export async function restartBrowser() { if (store.browser) { await store.browser.close() store.browser = null } return ensureBrowser() } export function handleError(error: unknown) { const details = error instanceof Error ? error.message : 'Unknown error' console.error('💥 Error:', details) return details } export async function killBrowser(browser: Browser | null) { console.log(`<- Closing browser`) if (browser) await browser.close().catch(() => {}) } export async function closeContexts(browser: Browser | null) { console.log(`<- Closing contexts`) if (browser) { const contexts = browser.contexts() for (const context of contexts) { await context.close().catch(() => {}) } } } export async function withPage(fn: (page: Page) => Promise): Promise { let page: Page | null = null try { const browser = await ensureBrowser() page = await createPageFromBrowser(browser) return await fn(page) } catch (err) { handleError(err) return null } finally { if (page) { await page .context() .close() .catch(() => {}) } logMemory('Context closed') } }