← Code-Übersicht

server-core.js

Pfad: wetell-headless/server-core.js

Ext: js

Größe: 2925 Bytes

Geändert: 2026-07-16 17:51:21+02

'use strict';

const crypto = require('node:crypto');

function send(response, status, payload) {
    const body = JSON.stringify(payload);
    response.writeHead(status, {
        'content-type': 'application/json; charset=utf-8',
        'content-length': Buffer.byteLength(body),
        'cache-control': 'no-store',
    });
    response.end(body);
}

function readJson(request, maximumBytes = 65536) {
    return new Promise((resolve, reject) => {
        const chunks = [];
        let size = 0;
        let failed = false;
        request.on('data', (chunk) => {
            if (failed) return;
            size += chunk.length;
            if (size > maximumBytes) {
                failed = true;
                reject(new Error('body_too_large'));
                return;
            }
            chunks.push(chunk);
        });
        request.on('end', () => {
            if (failed) return;
            try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8'))); }
            catch { reject(new Error('invalid_json')); }
        });
        request.on('error', reject);
    });
}

function validSecret(actual, expected) {
    if (typeof actual !== 'string') return false;
    const left = Buffer.from(actual);
    const right = Buffer.from(expected);
    return left.length === right.length && crypto.timingSafeEqual(left, right);
}

function createRequestHandler(browser, secret, maximumActive = 2) {
    if (!secret) throw new Error('secret_required');
    let active = 0;
    return async (request, response) => {
        if (request.method === 'GET' && request.url === '/health') {
            send(response, 200, { ok: true, ready: Boolean(browser.browser), active });
            return;
        }
        if (request.method !== 'POST' || request.url !== '/') {
            send(response, 405, { ok: false, error: 'method_not_allowed' });
            return;
        }
        if (active >= maximumActive) {
            send(response, 429, { ok: false, error: 'capacity_exceeded' });
            return;
        }
        active++;
        try {
            const body = await readJson(request);
            if (!body || !validSecret(body.secret, secret)) {
                send(response, 403, { ok: false, error: 'forbidden' });
                return;
            }
            const result = await browser.execute(String(body.action || ''), body);
            send(response, 200, result);
        } catch (error) {
            const code = error instanceof Error ? error.message : 'runtime_error';
            if (code === 'body_too_large') send(response, 413, { ok: false, error: code });
            else if (code === 'invalid_json') send(response, 400, { ok: false, error: code });
            else send(response, 502, { ok: false, error: 'upstream_failure' });
        } finally {
            active--;
        }
    };
}

module.exports = { createRequestHandler, readJson, validSecret };