Source: web.mjs

/**
 * @module kongUtilWeb
 */
import utilWeb from "./core.mjs";
import {parseCSV} from "./string.mjs";
import {parseHTML} from "./dom.mjs";

export * from "./core.mjs";

/**
 * @func fetchEx
 * @desc Similar to `fetch()` but works different if `body` exists.
 *  1. if `body` is a normal object (such as those created by `{}`), construct a `URLSearchParams` from it.
 *  2. if `body` is an `HTMLFormElement`, construct a `FormData` from it.
 *  3. if `body` is a normal object or a `URLSearchParams`,
 *     and `method` is set to 'GET' explicitly,
 *     convert the request into a URL with search param without,
 *     and then make the request without body.
 *  4. if `body` exists without setting `method`, warn and request with POST method.
 * @param {string | URL | Request} resource
 * @param {RequestInit} [options]
 */
export async function fetchEx(resource, options) {
    let body = resource.body || options?.body;
    if (body) {
        let newBody;
        if (body.constructor === HTMLFormElement) newBody = new FormData(body);
        else if (body.constructor === Object) newBody = new URLSearchParams(body);
        if (newBody) {
            body = newBody;
            if (resource.body) resource = new Request(resource, {body});
            if (options?.body) options.body = body;
        }

        switch (resource.method || options?.method) {
            case 'GET': {
                if (body instanceof URLSearchParams) {
                    const url = new URL(
                        resource.url ?? (resource + ''),
                        document?.baseURI
                    );
                    for (const [key, value] of body.entries())
                        url.searchParams.set(key, value);
                    resource = resource.url ? (new Request(resource, {url, body: undefined})) : url;
                    if (options?.body) delete options.body;
                }
                break;
            }
            case 'POST':
            case 'PUT': {
                break;
            }
            case '': {
                console.warn('While using `fetch()` with `body`, `method` shall be set to "POST" or "PUT".');
                if (resource.url) resource = new Request(resource, {method: 'POST'});
                if (typeof options === 'object') options.method = 'POST';
            }
            default: {}
        }
    }
    return await fetch(resource, options);
}

/**
 * @func fetchStrict
 * @desc Similar to `fetchEx()` but rejects if HTTP error (e.g "404 not found").
 * @param {any[]} args - same as `fetchEx()`
 * @returns {Promise<Response>}
 */
export async function fetchStrict(...args) {
    const response = await fetchEx(...args);
    if (response.ok) return response;
    throw response;
}

/**
 * @func fetchJSON
 * @desc Download a JSON file and deserialize it.
 * @param {...any} args - same as `fetchEx()`
 * @returns {Promise<Object>}
 */
export function fetchJSON(...args) {
    return fetchStrict(...args).then(res => res.json());
}

/**
 * @func fetchText
 * @desc Download a plain text file and read it.
 * @param {...any} args - same as `fetchEx()`
 * @returns {Promise<string>}
 */
export function fetchText(...args) {
    return fetchStrict(...args).then(res => res.text());
}

/**
 * @func fetchDOM
 * @desc Download an HTML file and parse it to `HTMLDocument`
 * @param {...any} args - same as `fetchEx()`
 * @returns {Promise<HTMLDocument>}
 */
export function fetchDOM(...args) {
    return fetchText(...args).then(html => parseHTML(html, 0));
}

/**
 * @func fetchCSV
 * @desc Download a CSV file and parse it to an array of objects
 * @param {...any} args - same as `fetchEx()`
 * @returns {Promise<Object>}
 */
export function fetchCSV(...args) {
    return fetchText(...args).then(csv => parseCSV(csv));
}

/**
 * @func readFile
 * @desc Promise version of `FileReader`.
 * @param {Blob} blob
 * @param {string} type - `arrayBuffer`, `binaryString`, `dataURL`, or `text`
 * @returns {Promise<any>}
 */
export function readFile(blob, type) {
    const method = 'readAs' + type.charAt(0).toUpperCase() + type.slice(1);
    const reader = new FileReader();
    return new Promise((resolve, reject) => {
        reader.addEventListener('load', () => resolve(reader.result));
        reader.addEventListener('error', () => reject(reader.error));
        reader[method](blob);
    });
}

/**
 * @func createFormData
 * @desc Creates a new `FormData` from an object
 * @param {Object | URLSearchParams} object
 * @returns FormData
 */
export function createFormData(object) {
    const fd = new FormData();
    if (object instanceof URLSearchParams) {
        for (const [key, value] of object.entries())
            fd.append(key, value);
    }
    else for (const key in object) fd.append(key, object[key]);
    return fd;
}


Object.assign(utilWeb, {
    fetchEx, fetchStrict,
    fetchJSON, fetchText, fetchDOM, fetchCSV,
    readFile, createFormData
});

export default utilWeb;