"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Hot = exports.DeveloperMode = void 0;
const HotFile_1 = require("./HotFile");
const HotStaq_1 = require("./HotStaq");
const HotTestElement_1 = require("./HotTestElement");
const HotRouteMethod_1 = require("./HotRouteMethod");
const HotHttpError_1 = require("./HotHttpError");
const events_1 = __importDefault(require("events"));
const dompurify_1 = __importDefault(require("dompurify"));
const js_cookie_1 = __importDefault(require("js-cookie"));
const node_fetch_1 = __importDefault(require("node-fetch"));
const form_data_1 = __importDefault(require("form-data"));
const fetch_event_source_1 = require("@microsoft/fetch-event-source");
/**
 * The available developer modes.
 */
var DeveloperMode;
(function (DeveloperMode) {
    /**
     * The default developer mode. No tests will be executed and
     * any test related data will be ignored.
     */
    DeveloperMode[DeveloperMode["Production"] = 0] = "Production";
    /**
     * For use during development/debugging. All test data will
     * be collected and executed if necessary.
     */
    DeveloperMode[DeveloperMode["Development"] = 1] = "Development";
})(DeveloperMode || (exports.DeveloperMode = DeveloperMode = {}));
/**
 * The api used during processing.
 */
class Hot {
    /**
     * Get a validated URL parameter.
     *
     * @example
     * ```ts
     * <*
     * 	const id = Hot.getUrlParam ('id', 'int');
     * *>
     *
     * <div>The id is: ${id}</div>
     * ```
     *
     * @param validation This parameter is for validating and sanitizing a URL parameter.
     * If it's a string, the available options are:
     * * int
     *   * This will only allow integers. parseInt will be called and will return an integer.
     * * float
     *   * This will only allow floating point numbers. parseFloat will be called and will return a float.
     * * email
     *   * Will verify that the string is in the form of an email address.
     * * phone
     *   * Will verify that the string is in the form of an email address.
     * * uuid
     *   * This will verify that the string is in the form of a UUID. It does not check if the UUID is valid.
     * * string(number_of_chars)
     *   * This will only allow a string of the specified number of characters.
     *
     * If this is a function, this will call the supplied function to validate. Validation checks must happen
     * in the function and return the sanitized value. It is simply a passthrough, be wise with this. Throw
     * an error if the value is not valid.
     *
     * @returns Returns the URL parameter, only if it passes validation. If the parameter is not found,
     * null will be returned.
     */
    static getUrlParam(param, validation) {
        const urlParams = new URLSearchParams(window.location.search);
        const value = urlParams.get(param);
        if (value == null)
            return (null);
        if (typeof (validation) === "string") {
            switch (validation) {
                case "int":
                    return (parseInt(value));
                case "float":
                    return (parseFloat(value));
                case "email":
                    {
                        const emailRegex = new RegExp("^\\S+@\\S+\\.\\S+$");
                        if (emailRegex.test(value) === true)
                            return (value);
                        break;
                    }
                case "phone":
                    {
                        const phoneRegex = new RegExp("^\\+?([0-9 ]?){6,14}[0-9]$");
                        if (phoneRegex.test(value) === true)
                            return (value);
                        break;
                    }
                case "uuid":
                    {
                        const uuidRegex = new RegExp("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
                        if (uuidRegex.test(value) === true)
                            return (value);
                        break;
                    }
                default:
                    if (validation.indexOf("string") > -1) {
                        const numChars = parseInt(validation.split("(")[1].split(")")[0]);
                        if (value.length === numChars)
                            return (value);
                    }
                    break;
            }
            throw new Error(`getUrlParam retreiving parameter "${param}" using validation type "${validation}" failed.`);
        }
        return (validation(value));
    }
    /**
     * Navigate to a route using SPA mode. This is a convenience wrapper
     * around HotStaq.navigateTo().
     *
     * @param path The URL path to navigate to (e.g. "/proposals").
     */
    static navigateTo(path) {
        return __awaiter(this, void 0, void 0, function* () {
            yield HotStaq_1.HotStaq.navigateTo(path);
        });
    }
    /**
     * Retrieve a file and echo out it's contents.
     */
    static include(file_1) {
        return __awaiter(this, arguments, void 0, function* (file, args = null) {
            if (HotStaq_1.HotStaq.isWeb === true) {
                if (typeof (file) === "string") {
                    const lowerFile = file.toLowerCase();
                    // If the file to be included does not have a nahfam, add it. This 
                    // will ensure the server sends only the file content.
                    if (lowerFile.indexOf(".hott") > -1) {
                        if (lowerFile.indexOf("nahfam") < 0)
                            file += "?hstqserve=nahfam";
                    }
                }
            }
            Hot.echoUnsafe(yield Hot.getFile(file, args));
        });
    }
    /**
     * Include and execute JavaScript for use when running the preprocessor.
     */
    static includeJS(file_1) {
        return __awaiter(this, arguments, void 0, function* (file, args = null, parentObject = null) {
            const output = yield Hot.getFile(file);
            if (HotStaq_1.HotStaq.isWeb === true) {
                if (parentObject == null)
                    parentObject = window;
            }
            else {
                if (parentObject == null)
                    parentObject = global;
            }
            let func = new Function(output);
            return (func.apply(parentObject, args));
        });
    }
    /**
     * Retrieve a file and echo out it's contents.
     */
    static import(moduleName_1) {
        return __awaiter(this, arguments, void 0, function* (moduleName, args = null, parentObject = null) {
            let foundModule = Hot.CurrentPage.processor.getModule(moduleName);
            if (foundModule != null)
                return (foundModule);
            const file = `./hotstaq_modules/${moduleName}/index.js`;
            const output = yield Hot.getFile(file, args);
            if (HotStaq_1.HotStaq.isWeb === true) {
                if (parentObject == null)
                    parentObject = window;
            }
            else {
                if (parentObject == null)
                    parentObject = global;
            }
            let newModule = new Function(output).apply(parentObject);
            if (newModule.loadHTML != null)
                yield newModule.loadHTML();
            Hot.CurrentPage.processor.addModule(moduleName, newModule);
            return (newModule);
        });
    }
    /**
     * Run an already loaded file and echo out it's contents.
     */
    static runFile(fileName_1) {
        return __awaiter(this, arguments, void 0, function* (fileName, args = null) {
            let file = Hot.CurrentPage.processor.getFile(fileName);
            /// @fixme Does the file need to be deep cloned first?
            //let clonedFile: HotFile = new HotFile (Object.assign ({}, file));
            let tempFile = file;
            tempFile.page = this.CurrentPage;
            let content = yield tempFile.process(args);
            Hot.echoUnsafe(content);
        });
    }
    /**
     * Get the content of a file.
     */
    static getFile(path_1) {
        return __awaiter(this, arguments, void 0, function* (path, args = null) {
            let tempFile = null;
            if (typeof (path) === "string") {
                tempFile = new HotFile_1.HotFile();
                tempFile.name = path;
                if (HotStaq_1.HotStaq.isWeb === true)
                    tempFile.url = path;
                else
                    tempFile.localFile = path;
            }
            else
                tempFile = path;
            let checkFile = Hot.CurrentPage.processor.getFile(tempFile.name, false);
            if (checkFile != null)
                tempFile = checkFile;
            yield tempFile.load();
            tempFile.page = this.CurrentPage;
            let content = yield tempFile.process(args);
            return (content);
        });
    }
    /**
     * Make an api call. This must include the route version.
     *
     * Simple apiCall:
     * @example
     * ```ts
     * await Hot.apiCall ('/v1/hello_world/echo', { message: "Hello!" });
     * ```
     *
     * Make an API call and upload a file:
     * @example
     * ```ts
     * let input = document.getElementById ("fileInput");
     * let file = input.files[0];
     *
     * await Hot.apiCall ('/v1/hello_world/echo',
     * 		{ message: "Hello!" }, HotEventMethod.POST,
     * 		{
     * 			"indexFileKey": file
     * 		});
     * ```
     */
    static apiCall(route_1) {
        return __awaiter(this, arguments, void 0, function* (route, data = null, httpMethod = HotRouteMethod_1.HotEventMethod.POST, files = {}, bearerToken = "") {
            let result = null;
            if (Hot.CurrentPage == null)
                throw new Error("Current page is null!");
            if (Hot.CurrentPage.processor == null)
                throw new Error("Current page's processor is null!");
            if (Hot.CurrentPage.processor.api == null)
                throw new Error("Current page's processor api is null! Did you forget to set the API name or URL?");
            if (Hot.CurrentPage.processor.api != null) {
                result = yield Hot.CurrentPage.processor.api.makeCall(route, data, httpMethod, files, bearerToken);
            }
            return (result);
        });
    }
    /**
     * Make a HTTP SSE JSON request. This will await until a connection is established.
     *
     * @example
     * ```ts
     * await Hot.sseRequest ('/v1/hello_world/echo', { message: "Hello!" });
     * ```
     *
     * @param url The full url to make the HTTP call.
     * @param data The data to JSON.stringify and send.
     * @param httpMethod The HTTP method to use to send the data.
     *
     * @returns The parsed JSON object.
     */
    static sseRequest(request) {
        let statusCode = 200;
        const emitter = new events_1.default();
        try {
            if ((request.url == null) || (request.url === ""))
                throw new Error(`A url must be provided to the request.`);
            if (request.ctrl == null)
                throw new Error(`A ctrl AbortController must be provided to the request.`);
            if (request.httpMethod == null)
                request.httpMethod = HotRouteMethod_1.HotEventMethod.POST;
            if (request.echoErrorToConsole == null)
                request.echoErrorToConsole = true;
            if (request.autoParseMsgs == null)
                request.autoParseMsgs = true;
            let sseObj = {
                method: request.httpMethod,
                headers: {
                    "Accept": "application/json",
                    "Content-Type": "application/json"
                },
                signal: request.ctrl.signal
            };
            if (request.httpMethod !== HotRouteMethod_1.HotEventMethod.GET)
                sseObj["body"] = JSON.stringify(request.data);
            if (request.bearerToken != null) {
                if (request.bearerToken !== "")
                    sseObj.headers["Authorization"] = `Bearer ${request.bearerToken}`;
            }
            (0, fetch_event_source_1.fetchEventSource)(request.url, Object.assign(Object.assign({}, sseObj), { onopen: (res) => __awaiter(this, void 0, void 0, function* () {
                    statusCode = res.status;
                    if (res.ok === false) {
                        let errorObj = yield res.json();
                        emitter.emit("error", errorObj.error);
                        return;
                    }
                    emitter.emit("open", res);
                }), onerror: (error) => {
                    if (request.echoErrorToConsole === true)
                        console.error(`SSE Error: ${error.message}`);
                    emitter.emit("error", error);
                }, onmessage: (event) => __awaiter(this, void 0, void 0, function* () {
                    let msg = event.data;
                    if (request.autoParseMsgs === true)
                        msg = JSON.parse(event.data);
                    // @ts-ignore
                    emitter.emit(event.event, msg);
                }), onclose: () => __awaiter(this, void 0, void 0, function* () {
                    emitter.emit("close");
                }) }));
        }
        catch (ex) {
            throw new HotHttpError_1.HttpError(ex.message, statusCode);
        }
        return (emitter);
    }
    /**
     * Get a HTTP JSON file or request using HTTP GET.
     *
     * @example
     * ```ts
     * await Hot.getJSON (`http://example.com/example.json`);
     * ```
     *
     * @param url The full url to make the HTTP call.
     *
     * @returns The parsed JSON object.
     */
    static getJSON(url_1) {
        return __awaiter(this, arguments, void 0, function* (url, bearerToken = "") {
            return (Hot.jsonRequest(url, null, bearerToken, HotRouteMethod_1.HotEventMethod.GET));
        });
    }
    /**
     * Make a HTTP JSON request.
     *
     * @example
     * ```ts
     * await Hot.jsonRequest ('/v1/hello_world/echo', { message: "Hello!" });
     * ```
     *
     * @param url The full url to make the HTTP call.
     * @param data The data to JSON.stringify and send.
     * @param httpMethod The HTTP method to use to send the data.
     *
     * @returns The parsed JSON object.
     */
    static jsonRequest(url_1) {
        return __awaiter(this, arguments, void 0, function* (url, data = null, bearerToken = "", httpMethod = HotRouteMethod_1.HotEventMethod.POST) {
            let statusCode = 200;
            try {
                let fetchObj = {
                    "method": httpMethod,
                    "headers": {
                        "Accept": "application/json",
                        "Content-Type": "application/json"
                    }
                };
                if (httpMethod !== HotRouteMethod_1.HotEventMethod.GET)
                    fetchObj["body"] = JSON.stringify(data);
                if (bearerToken != "")
                    fetchObj.headers["Authorization"] = `Bearer ${bearerToken}`;
                let res = yield (0, node_fetch_1.default)(url, fetchObj);
                statusCode = res.status;
                if (res.ok === false) {
                    let errorObj = null;
                    try {
                        errorObj = yield res.json();
                    }
                    catch (ex) {
                        errorObj = yield res.text();
                    }
                    throw new Error(errorObj.error);
                }
                let result = yield res.json();
                return (result);
            }
            catch (ex) {
                return ({ "error": ex.message, "errorCode": statusCode });
            }
        });
    }
    /**
     * Make a HTTP request. This is basically just a wrapper for fetch that can also upload files.
     *
     * @example
     * HTTP JSON POST
     * ```ts
     * await Hot.httpRequest ('http://other-hotstaq-api.com/v1/hello_world/echo', { message: "Hello!" });
     * ```
     *
     * @example
     * Upload file using HTTP POST
     * ```ts
     * let input = document.getElementById ("fileInput");
     * let file = input.files[0];
     *
     * await Hot.httpRequest ('http://other-hotstaq-api.com/v1/hello_world/echo',
     * 		{ message: "Hello!" }, HotEventMethod.POST,
     * 		{
     * 			"indexFileKey": file
     * 		});
     * ```
     */
    static httpRequest(url_1, data_1) {
        return __awaiter(this, arguments, void 0, function* (url, data, httpMethod = HotRouteMethod_1.HotEventMethod.POST, files = {}, bearerToken = "") {
            const numFiles = Object.keys(files).length;
            let httpMethodLower = httpMethod.toLowerCase();
            if (httpMethod === HotRouteMethod_1.HotEventMethod.FILE_UPLOAD)
                httpMethodLower = "post";
            let uploadFile = false;
            const formData = new form_data_1.default();
            if (numFiles > 0) {
                if (httpMethodLower !== "post")
                    throw new Error(`To upload files, you must set the httpMethod to POST.`);
                for (let key in files) {
                    const file = files[key];
                    if (file != null) {
                        formData.append(key, file);
                        uploadFile = true;
                    }
                }
            }
            if (uploadFile === true) {
                let requestInit = {
                    method: "POST",
                    headers: {
                        "HotStaqUpload": "true"
                    },
                    // @ts-ignore
                    body: formData
                };
                if (bearerToken != "") {
                    requestInit.headers = {
                        "Authorization": `Bearer ${bearerToken}`
                    };
                }
                // @ts-ignore
                let res = yield (0, node_fetch_1.default)(url, requestInit);
                let jsonRes = yield res.json();
                if (data["hotstaq"] == null)
                    data["hotstaq"] = { skipValidation: true };
                if (data["hotstaq"]["uploads"] == null)
                    data["hotstaq"]["uploads"] = {};
                data["hotstaq"]["uploads"]["uploadId"] =
                    jsonRes["hotstaq"]["uploads"]["uploadId"];
                // After the upload, make the actual JSON call. Do not pass files again.
                const result = yield Hot.httpRequest(url, data, HotRouteMethod_1.HotEventMethod.POST, {}, bearerToken);
                return (result);
            }
            let fetchObj = {
                method: httpMethodLower,
                headers: {
                    "Accept": "application/json",
                    "Content-Type": "application/json"
                }
            };
            if (bearerToken != "")
                fetchObj.headers["Authorization"] = `Bearer ${bearerToken}`;
            if ((httpMethodLower !== "get") &&
                (httpMethodLower !== "head")) {
                fetchObj["body"] = JSON.stringify(data);
            }
            let promise = new Promise((resolve, reject) => {
                (0, node_fetch_1.default)(url, fetchObj).then((res) => __awaiter(this, void 0, void 0, function* () {
                    res.json().then((jsonObj) => {
                        resolve(jsonObj);
                    })
                        .catch((reason) => {
                        throw new Error(`${url}: ${reason.message}`);
                    });
                }))
                    .catch((reason) => {
                    throw new Error(`${url}: ${reason.message}`);
                });
            });
            return (promise);
        });
    }
    /**
     * Echo out some output. This uses DOMPurify to sanitize the output, you can pass
     * a configuration object to DOMPurify to change the sanitization behavior.
     */
    static echo(message, cfg = undefined) {
        Hot.Output += dompurify_1.default.sanitize(message, cfg);
    }
    /**
     * Echo out some output. Use this if you intend to output HTML that is not escaped.
     */
    static echoUnsafe(message) {
        Hot.Output += message;
    }
    /**
     * Echo out the CSS for the current page being generated.
     */
    static displayCSS() {
        for (let iIdx = 0; iIdx < Hot.CSS.length; iIdx++) {
            let cssFile = Hot.CSS[iIdx];
            let cssOut = Hot.cssStr;
            cssOut = cssOut.replace(/\%CSS_FILE\%/g, cssFile);
            Hot.echoUnsafe(cssOut);
        }
    }
    /**
     * Echo out the JS files for the current page being generated.
     */
    static displayJSFiles() {
        for (let iIdx = 0; iIdx < Hot.JSFiles.length; iIdx++) {
            let jsFile = Hot.JSFiles[iIdx];
            let jsFileOut = Hot.jsFileStr;
            jsFileOut = jsFileOut.replace(/\%JS_FILE\%/g, jsFile);
            Hot.echoUnsafe(jsFileOut);
        }
    }
    /**
     * Echo out the JS scripts for the current page being generated.
     */
    static displayJSScripts() {
        for (let iIdx = 0; iIdx < Hot.JSScripts.length; iIdx++) {
            let jsScript = Hot.JSScripts[iIdx];
            let jsScriptOut = Hot.jsScriptsStr;
            jsScriptOut = jsScriptOut.replace(/\%JS_CODE\%/g, jsScript);
            Hot.echoUnsafe(jsScriptOut);
        }
    }
}
exports.Hot = Hot;
/**
 * The JSDOM window that is meant to only be used internally by HotStaq on the server side and should not be used.
 */
Hot.__purifyWindow = null;
/**
 * The DOMPurify object that is meant to only be used internally by HotStaq on the server and should not be used.
 */
Hot.__domPurify = null;
/**
 * Debugging, testing, and benchmarking related options.
 */
Hot.Debugger = {
    /**
     * If set to true, whenever HotFile.process is called, performance.now() will be called
     * and the time taken to process the file will be logged to the console. In addition the number
     * of times process has been called will be stored in Hot.Debugger.processCount.
     */
    benchmark: false,
    /**
     * The number of times the process function has been called. This is used for benchmarking purposes.
     */
    processCount: 0,
    /**
     * The total amount of time that has been spent processing files. This is used for benchmarking purposes.
     */
    processTotalTime: 0,
    /**
     * The average time spent processing files. This is used for benchmarking purposes.
     */
    processAvgTime: 0
};
/**
 * The currently generated page being displayed. This is cleared between every file processed.
 */
Hot.CurrentPage = null;
/**
 * The arguments passed.
 */
Hot.Arguments = null;
/**
 * The mode in which this application is running. If it's set to development mode, all testing
 * related data will be collected, parsed, and executed if necessary.
 */
Hot.DeveloperMode = DeveloperMode;
/**
 * The mode in which this application is running. If it's set to development mode, all testing
 * related data will be collected, parsed, and executed if necessary.
 */
Hot.HotTestElement = HotTestElement_1.HotTestElement;
/**
 * The mode in which this application is running. If it's set to development mode, all testing
 * related data will be collected, parsed, and executed if necessary.
 */
Hot.Mode = DeveloperMode.Production;
/**
 *The current API used on this page. This is cleared between every file processed.
 */
Hot.API = null;
/**
 * The API being used by the tester.
 */
Hot.TesterAPI = null;
/**
 * Contains the buffer to output. This is cleared between every file processed.
 */
Hot.Output = "";
/**
 * The data to share across all the different files and pages. This data will be public.
 */
Hot.Data = {};
/**
 * The cookies to use between pages.
 */
Hot.Cookies = js_cookie_1.default;
/**
 * Any public keys that need to be shown. These can be passed from HotSite.json.
 */
Hot.PublicKeys = {};
/**
 * The CSS string to use when echoing out the CSS files.
 */
Hot.cssStr = `<link rel = "stylesheet" href = "%CSS_FILE%" />`;
/**
 * The CSS files to use in the current page being generated.
 *
 * @todo Make this a "string | CSSObject" data type so it can also include
 * the integrity hashes as well.
 */
Hot.CSS = [];
/**
 * The JavaScript files to use in the current page being generated.
 *
 * @todo Make this a "string | JSFileObject" data type so it can also include
 * the integrity hashes as well.
 */
Hot.JSFiles = [];
/**
 * The JavaScript inline code to use in the current page being generated.
 */
Hot.JSScripts = [];
/**
 * The JavaScript string to use when echoing out the Scripts files.
 */
Hot.jsFileStr = `<script type = "text/javascript" src = "%JS_FILE%"></script>`;
/**
 * The JavaScript string to use when echoing out the Scripts files.
 */
Hot.jsScriptsStr = `<script type = "text/javascript">%JS_CODE%</script>`;
if (typeof (window) == "undefined") {
    const { JSDOM } = eval("require")("jsdom");
    Hot.__purifyWindow = new JSDOM("").window;
    Hot.__domPurify = (0, dompurify_1.default)(Hot.__purifyWindow);
    Hot.echo = (message, cfg = undefined) => {
        Hot.Output += Hot.__domPurify.sanitize(message, cfg);
    };
}
//# sourceMappingURL=Hot.js.map