"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.browserChromeSessions = void 0;
exports.mintSessionToken = mintSessionToken;
exports.verifySessionToken = verifySessionToken;
const playwright_1 = require("playwright");
const uuid_1 = require("uuid");
const events_1 = require("events");
const jwt = __importStar(require("jsonwebtoken"));
const config_1 = require("../config");
const logger_1 = require("../logger");
const log = (0, logger_1.modLogger)('browser-chrome');
const MAX_SESSIONS = 3;
const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
const DEFAULT_VIEWPORT = { w: 1280, h: 800 };
class SessionManager extends events_1.EventEmitter {
    constructor() {
        super(...arguments);
        this.sessions = new Map();
        this.idleTimer = null;
        this.started = false;
    }
    start() {
        if (this.started)
            return;
        this.started = true;
        this.idleTimer = setInterval(() => this.sweepIdle(), 30 * 1000);
    }
    /** Return existing session for this user (reuse) or create a new one. */
    async getOrCreate(username) {
        this.start();
        for (const s of this.sessions.values()) {
            if (s.username === username) {
                s.lastActivityAt = Date.now();
                return s;
            }
        }
        if (this.sessions.size >= MAX_SESSIONS) {
            throw new Error(`Max ${MAX_SESSIONS} browser sessions reached`);
        }
        const sid = (0, uuid_1.v4)();
        let browser;
        try {
            browser = await playwright_1.chromium.launch({
                headless: true,
                args: ['--disable-dev-shm-usage', '--no-sandbox', '--disable-gpu'],
            });
        }
        catch (err) {
            log.error({ err }, 'chromium launch failed — is `npx playwright install chromium` done?');
            throw new Error('Chromium not available — run: npx playwright install chromium');
        }
        const context = await browser.newContext({
            viewport: { width: DEFAULT_VIEWPORT.w, height: DEFAULT_VIEWPORT.h },
            userAgent: 'ccweb-browser-chrome/1.0',
        });
        const page = await context.newPage();
        const cdp = await context.newCDPSession(page);
        const session = {
            sid, username, browser, context, page, cdp,
            createdAt: Date.now(),
            lastActivityAt: Date.now(),
            viewport: { ...DEFAULT_VIEWPORT },
            url: 'about:blank',
        };
        this.sessions.set(sid, session);
        log.info({ sid, username, count: this.sessions.size }, 'session created');
        // Auto-destroy if browser dies underneath us (chromium crash).
        browser.once('disconnected', () => {
            log.warn({ sid }, 'browser disconnected unexpectedly');
            this.sessions.delete(sid);
        });
        return session;
    }
    get(sid, username) {
        const s = this.sessions.get(sid);
        if (!s)
            return null;
        if (s.username !== username)
            return null;
        s.lastActivityAt = Date.now();
        return s;
    }
    async destroy(sid) {
        const s = this.sessions.get(sid);
        if (!s)
            return;
        this.sessions.delete(sid);
        try {
            await s.cdp.detach().catch(() => { });
            await s.browser.close();
        }
        catch (err) {
            log.warn({ err, sid }, 'browser close failed');
        }
        log.info({ sid, count: this.sessions.size }, 'session destroyed');
    }
    async destroyAll() {
        const sids = Array.from(this.sessions.keys());
        await Promise.all(sids.map(sid => this.destroy(sid)));
        if (this.idleTimer)
            clearInterval(this.idleTimer);
        this.idleTimer = null;
        this.started = false;
    }
    size() {
        return this.sessions.size;
    }
    sweepIdle() {
        const now = Date.now();
        for (const [sid, s] of this.sessions) {
            if (now - s.lastActivityAt > IDLE_TIMEOUT_MS) {
                log.info({ sid, idleMs: now - s.lastActivityAt }, 'idle timeout');
                this.destroy(sid).catch(err => log.warn({ err, sid }, 'idle destroy failed'));
            }
        }
    }
}
exports.browserChromeSessions = new SessionManager();
// ── JWT helpers for WS auth ────────────────────────────────────────────────
// HTTP endpoints use Bearer admin (via requireBearerAdmin); WS upgrade can't
// carry headers reliably across browsers / proxies, so we issue a short-lived
// JWT bound to {sid, username} returned by POST /_session.
function mintSessionToken(sid, username) {
    const config = (0, config_1.getConfig)();
    return jwt.sign({ sid, username, typ: 'browser-chrome' }, config.jwtSecret, { expiresIn: '1h' });
}
function verifySessionToken(token) {
    try {
        const config = (0, config_1.getConfig)();
        const decoded = jwt.verify(token, config.jwtSecret);
        if (decoded.typ !== 'browser-chrome')
            return null;
        if (typeof decoded.sid !== 'string' || typeof decoded.username !== 'string')
            return null;
        return { sid: decoded.sid, username: decoded.username };
    }
    catch {
        return null;
    }
}
//# sourceMappingURL=session-manager.js.map