"use strict";
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiError = void 0;
exports.apiMiddleware = apiMiddleware;
const sse_1 = __importDefault(require("./sse"));
const stream_1 = require("stream");
const misc_1 = require("./misc");
const const_1 = require("./const");
const config_1 = require("./config");
const plugins_1 = require("./plugins");
class ApiError extends Error {
    status;
    constructor(status, message) {
        super(typeof message === 'string' ? message : message && message instanceof Error ? message.message : JSON.stringify(message));
        this.status = status;
    }
}
exports.ApiError = ApiError;
const logApi = (0, config_1.defineConfig)(misc_1.CFG.log_api, true);
function apiMiddleware(apis) {
    return async (ctx) => {
        if (!logApi.get())
            ctx.state.dontLog = true;
        const isPost = ctx.state.params;
        const params = isPost ? ctx.state.params || {} : ctx.query;
        const apiName = ctx.path;
        console.debug('API', ctx.method, apiName, { ...params });
        const csrfSafe = !isPost
            || ctx.get('x-hfs-anti-csrf') // automatic browser actions won't carry this header
            || apiName.startsWith('get_') // "get_" apis are safe because they make no change
            || /^(curl|wget|python|go-|java|axios|postman|httpie|insomnia|bruno)/i.test(ctx.get('user-agent') || ''); // only browser are subject to CSRF
        if (!csrfSafe)
            return send(const_1.HTTP_FOOL, "missing header x-hfs-anti-csrf=1");
        const customApiRest = apiName.startsWith(const_1.PLUGIN_CUSTOM_REST_PREFIX) && apiName.slice(const_1.PLUGIN_CUSTOM_REST_PREFIX.length);
        const apiFun = customApiRest && (0, plugins_1.firstPlugin)(pl => pl.getData().customRest?.[customApiRest])
            || apis.hasOwnProperty(apiName) && apis[apiName];
        if (!apiFun)
            return send(const_1.HTTP_BAD_REQUEST, 'invalid api');
        // we don't rely on SameSite cookie option because it's https-only
        let res;
        try {
            res = await apiFun(params, ctx);
            if (res === null)
                return;
        }
        catch (e) {
            if (typeof e === 'string') // message meant to be transmitted
                return send(const_1.HTTP_BAD_REQUEST, e);
            if (typeof e === 'number')
                e = new ApiError(e);
            res = e;
        }
        if (isAsyncGenerator(res))
            res = (0, misc_1.asyncGeneratorToReadable)(res);
        if (res instanceof stream_1.Readable) { // Readable, we'll go SSE-mode
            res.pipe((0, sse_1.default)(ctx));
            const resAsReadable = res; // satisfy ts
            ctx.req.on('close', () => // by closing the generated stream, creator of the stream will know the request is over without having to access anything else
             resAsReadable.destroy());
            return;
        }
        if (res instanceof ApiError)
            return send(res.status, res.message);
        if (res instanceof Error) // generic error/exception
            return send(const_1.HTTP_BAD_REQUEST, res.stack || res.message || String(res));
        ctx.body = res;
        function send(status, body) {
            ctx.body = body;
            ctx.status = status;
        }
    };
}
function isAsyncGenerator(x) {
    return typeof x?.next === 'function';
}
