/** Enable timestamped console output — call once at startup for daemon/log mode. */
export function enableLogTimestamps() {
    for (const [method, level] of [['log', 'INFO'], ['warn', 'WARN'], ['error', 'ERROR'], ['info', 'INFO']]) {
        const orig = console[method];
        console[method] = (...args) => orig(`[${new Date().toISOString()}] [${level}]`, ...args);
    }
}
// Auto-enable timestamps when output is redirected (daemon mode)
if (!process.stdout.isTTY)
    enableLogTimestamps();
import { createServer } from 'http';
import { createRequire } from 'module';
import { WebSocket, WebSocketServer } from 'ws';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { sessions } from './session-manager.js';
import { auth } from './auth-manager.js';
import { createApp } from './http.js';
import { handleAgentConnection } from './ws-agent.js';
import { handleWebConnection } from './ws-client.js';
import { handleTunnelWsUpgrade } from './tunnel.js';
import { saveServerRuntimeState, clearServerRuntimeState, writePidFile } from './config.js';
import { encryptAndSend } from './encryption.js';
import { loadPersistedState, setStateCollector, markDirty, flushState } from './state-persistence.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
const pkg = require('../package.json');
const startedAt = new Date();
// ── Restore persisted state ──
const persisted = loadPersistedState();
if (persisted) {
    auth.loadFrom(persisted);
    sessions.loadSessionOwners(persisted.sessionOwners);
    sessions.loadKnownSessions(persisted.knownSessions);
    console.log(`[AgentLink] Restored persisted state (${Object.keys(persisted.knownSessions).length} known sessions)`);
}
// Wire mutation hooks → debounced persistence
sessions.onMutation = markDirty;
auth.onMutation = markDirty;
setStateCollector(() => {
    const authData = auth.serialize();
    const so = {};
    for (const [k, v] of sessions.sessionOwners)
        so[k] = v;
    const ks = {};
    for (const [k, v] of sessions.knownSessions)
        ks[k] = v;
    return {
        version: 1,
        serverSecret: authData.serverSecret,
        sessionOwners: so,
        sessionAuth: authData.sessionAuth,
        authAttempts: authData.authAttempts,
        knownSessions: ks,
    };
});
// ── Listening target ──
// Scenario 1 (Local / Linux): PORT is a numeric string, default 3456
// Scenario 2 (Azure App Service Windows): iisnode sets PORT to a named pipe ("\\.\pipe\GUID")
const rawPort = process.env.PORT || '3456';
const isNamedPipe = isNaN(parseInt(rawPort, 10));
const LISTEN_TARGET = isNamedPipe ? rawPort : parseInt(rawPort, 10);
const PORT_NUMBER = isNamedPipe ? undefined : LISTEN_TARGET;
const webDir = join(__dirname, '../web/dist');
const app = createApp(webDir, pkg, startedAt);
const server = createServer(app);
const wss = new WebSocketServer({ noServer: true, maxPayload: 16 * 1024 * 1024 }); // 16MB (10MB file + base64 overhead)
// A separate WSS for tunnel WebSocket upgrades (browser ↔ agent proxy)
const tunnelWss = new WebSocketServer({ noServer: true, maxPayload: 16 * 1024 * 1024 }); // 16MB
// Handle HTTP upgrade: route proxy WS to tunnel handler, others to main WSS
server.on('upgrade', (req, socket, head) => {
    // Disable Nagle's algorithm — reduce latency for small terminal input packets
    socket.setNoDelay(true);
    // Check if this is a tunnel proxy WebSocket upgrade
    if (handleTunnelWsUpgrade(req, socket, head, tunnelWss)) {
        return; // Handled by tunnel module
    }
    // Regular WebSocket (agent or web client)
    wss.handleUpgrade(req, socket, head, (ws) => {
        wss.emit('connection', ws, req);
    });
});
// WebSocket routing: agent or web client
wss.on('connection', (ws, req) => {
    const url = new URL(req.url || '/', `http://${req.headers.host}`);
    const type = url.searchParams.get('type');
    if (type === 'agent') {
        handleAgentConnection(ws, req);
    }
    else if (type === 'web') {
        handleWebConnection(ws, req);
    }
    else {
        ws.send(JSON.stringify({ type: 'error', message: 'Unknown type. Use ?type=agent or ?type=web' }));
        ws.close();
    }
});
// Heartbeat every 30s to detect dead connections
setInterval(() => {
    sessions.cleanupDeadConnections((client, msg) => {
        if (client.ws.readyState === WebSocket.OPEN) {
            encryptAndSend(client.ws, msg, client.sessionKey).catch((err) => {
                console.error(`[Heartbeat] Failed to notify client:`, err.message);
            });
        }
    });
}, 30_000);
server.listen(LISTEN_TARGET, () => {
    if (isNamedPipe) {
        // Azure App Service: iisnode manages the process lifecycle, no local state needed
        console.log(`[AgentLink] Server listening on named pipe`);
    }
    else {
        // Local development: write state for CLI stop command & test harness
        console.log(`[AgentLink] Server listening on http://localhost:${PORT_NUMBER}`);
        console.log(`[AgentLink] Web UI: ${webDir}`);
        saveServerRuntimeState({
            pid: process.pid,
            port: PORT_NUMBER,
            startedAt: new Date().toISOString(),
        });
        if (process.env.AGENTLINK_PID_FILE) {
            writePidFile(process.env.AGENTLINK_PID_FILE, { pid: process.pid, port: PORT_NUMBER });
        }
    }
});
// Clean up runtime state on exit
process.on('exit', clearServerRuntimeState);
process.on('SIGINT', () => { flushState(); clearServerRuntimeState(); process.exit(0); });
process.on('SIGTERM', () => { flushState(); clearServerRuntimeState(); process.exit(0); });
//# sourceMappingURL=index.js.map