import CacheableLookup from "cacheable-lookup";
import fetch from "node-fetch";
import config from "../config/index.js";
import { CheckedHttpAgent, CheckedHttpProxyAgent, CheckedHttpsAgent, CheckedHttpsProxyAgent } from "./checked-fetch.js";
export async function getJson(url, accept = "application/json, */*", timeout = 10000, headers) {
    const res = await getResponse({
        url,
        method: "GET",
        headers: Object.assign({
            "User-Agent": config.userAgent,
            Accept: accept
        }, headers || {}),
        timeout
    });
    return await res.json();
}
export async function getJsonActivity(url, accept = "application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", timeout = 10000, headers) {
    const res = await getResponse({
        url,
        method: "GET",
        headers: Object.assign({
            "User-Agent": config.userAgent,
            Accept: accept
        }, headers || {}),
        timeout
    });
    const contentType = res.headers.get('content-type');
    if (contentType == null || contentType !== 'application/activity+json' && !contentType.startsWith('application/activity+json;') && (!contentType.startsWith('application/ld+json;') || !contentType.includes('profile="https://www.w3.org/ns/activitystreams"'))) {
        throw new Error(`getJsonActivity response had unexpected content-type: ${contentType}`);
    }
    return {
        finalUrl: res.url,
        content: await res.json()
    };
}
export async function getHtml(url, accept = "text/html, */*", timeout = 10000, headers) {
    const res = await getResponse({
        url,
        method: "GET",
        headers: Object.assign({
            "User-Agent": config.userAgent,
            Accept: accept
        }, headers || {}),
        timeout
    });
    return await res.text();
}
export async function getResponse(args) {
    const timeout = args.timeout || 10 * 1000;
    const controller = new AbortController();
    setTimeout(()=>{
        controller.abort();
    }, timeout * 6);
    const res = await fetch(args.url, {
        method: args.method,
        headers: args.headers,
        body: args.body,
        timeout,
        size: 10 * 1024 * 1024,
        agent: getAgentByUrl,
        signal: controller.signal,
        redirect: args.redirect
    });
    if (args.redirect === "manual" && [
        301,
        302,
        307,
        308
    ].includes(res.status)) {
        return res;
    }
    if (!res.ok) {
        throw new StatusError(`${res.status} ${res.statusText}`, res.status, res.statusText);
    }
    return res;
}
const cache = new CacheableLookup({
    maxTtl: 3600,
    errorTtl: 30,
    lookup: false
});
/**
 * Get http non-proxy agent
 */ const _http = new CheckedHttpAgent({
    keepAlive: true,
    keepAliveMsecs: 30 * 1000,
    lookup: cache.lookup
});
/**
 * Get https non-proxy agent
 */ const _https = new CheckedHttpsAgent({
    keepAlive: true,
    keepAliveMsecs: 30 * 1000,
    lookup: cache.lookup
});
const maxSockets = Math.max(256, config.deliverJobConcurrency || 128);
/**
 * Get http proxy or non-proxy agent
 */ export const httpAgent = config.proxy ? new CheckedHttpProxyAgent({
    keepAlive: true,
    keepAliveMsecs: 30 * 1000,
    maxSockets,
    maxFreeSockets: 256,
    scheduling: "lifo",
    proxy: config.proxy
}) : _http;
/**
 * Get https proxy or non-proxy agent
 */ export const httpsAgent = config.proxy ? new CheckedHttpsProxyAgent({
    keepAlive: true,
    keepAliveMsecs: 30 * 1000,
    maxSockets,
    maxFreeSockets: 256,
    scheduling: "lifo",
    proxy: config.proxy
}) : _https;
/**
 * Get agent by URL
 * @param url URL
 * @param bypassProxy Allways bypass proxy
 */ export function getAgentByUrl(url, bypassProxy = false) {
    if (bypassProxy || (config.proxyBypassHosts || []).includes(url.hostname)) {
        return url.protocol === "http:" ? _http : _https;
    } else {
        return url.protocol === "http:" ? httpAgent : httpsAgent;
    }
}
export class StatusError extends Error {
    statusCode;
    statusMessage;
    isClientError;
    isRetryable;
    constructor(message, statusCode, statusMessage){
        super(message);
        this.name = "StatusError";
        this.statusCode = statusCode;
        this.statusMessage = statusMessage;
        this.isClientError = typeof this.statusCode === "number" && this.statusCode >= 400 && this.statusCode < 500;
        this.isRetryable = this.isClientError && this.statusCode != 429;
    }
}
