// SoftEther VPN Server JSON-RPC Stub code for TypeScript
// 
// vpnrpc.ts
// Automatically generated at __TIMESTAMP__ by vpnserver-jsonrpc-codegen
//
// Licensed under the Apache License 2.0
// Copyright (c) 2014-__YEAR__ SoftEther VPN Project


// Trivial utility codes
let is_node_js = (typeof navigator === "undefined") || navigator.userAgent.indexOf("Node.js") !== -1 || navigator.userAgent.indexOf("jsdom") !== -1;
function is_null(obj: any)
{
  return (typeof obj === "undefined") || (obj === null);
}
let debug_mode: boolean = false;

/** VPN Server RPC Stubs */
export class VpnServerRpc
{
    /** Determine if this JavaScript environment is on the Node.js or not. */
    public static IsNodeJS(): boolean
    {
        return is_node_js;
    }

    /** Set the debug mode flag */
    public static SetDebugMode(flag: boolean): void
    {
        debug_mode = flag;
    }

    private rpc_url: string;
    private rpc_client: JsonRpcClient;

    /** 
     * Constructor of the VpnServerRpc class
     * @param vpnserver_hostname The hostname or IP address of the destination VPN Server. In the web browser you can specify null if you want to connect to the server itself.
     * @param vpnserver_port The port number of the destination VPN Server. In the web browser you can specify null if you want to connect to the server itself.
     * @param hubname The name of the Virtual Hub if you want to connect to the VPN Server as a Virtual Hub Admin Mode. Specify null if you want to connect to the VPN Server as the Entire VPN Server Admin Mode.
     * @param password Specify the administration password. This value is valid only if vpnserver_hostname is sepcified.
     * @param nodejs_https_client_reject_untrusted_server_cert In Node.js set this true to check the SSL server certificate on the destination VPN Server. Set this false to ignore the SSL server certification.
     */
    constructor(vpnserver_hostname?: string, vpnserver_port?: number, hubname?: string, password?: string, nodejs_https_client_reject_untrusted_server_cert?: boolean)
    {
        let headers: { [name: string]: string } = {};
        let send_credentials: boolean = false;

        nodejs_https_client_reject_untrusted_server_cert = is_null(nodejs_https_client_reject_untrusted_server_cert) ? false : nodejs_https_client_reject_untrusted_server_cert!;

        if (is_null(vpnserver_hostname))
        {
            this.rpc_url = "/api/";
            send_credentials = true;
        }
        else
        {
            if (is_null(vpnserver_port)) vpnserver_port = 443;
            this.rpc_url = `https://${vpnserver_hostname}:${vpnserver_port}/api/`;


            headers["X-VPNADMIN-HUBNAME"] = is_null(hubname) ? "" : hubname!;
            headers["X-VPNADMIN-PASSWORD"] = is_null(password) ? "" : password!;
        }

        if (is_null(nodejs_https_client_reject_untrusted_server_cert)) nodejs_https_client_reject_untrusted_server_cert = false;

        this.rpc_client = new JsonRpcClient(this.rpc_url, headers, send_credentials, nodejs_https_client_reject_untrusted_server_cert);
    }

    // --- Stubs ---
__STUBS__

    // -- Utility functions --
    /** Call a RPC procedure */
    public async CallAsync<T>(method_name: string, request: T): Promise<T>
    {
        let response: T = await this.rpc_client.CallAsync<T>(method_name, request);

        return response;
    }
}




// --- Types ---
__TYPES__



// --- Utility codes ---

/** JSON-RPC request class. See https://www.jsonrpc.org/specification */
export class JsonRpcRequest
{
    public jsonrpc: string = "2.0";
    public method: string;
    public params: any;
    public id: string;

    constructor(method: string = "", param: any = null, id: string = "")
    {
        this.method = method;
        this.params = param;
        this.id = id;
    }
}

/** JSON-RPC error class. See https://www.jsonrpc.org/specification */
export class JsonRpcError
{
    public code: number;
    public message: string;
    public data: any;

    constructor(code: number = 0, message: string = "", data: any = null)
    {
        this.code = code;
        this.message = message;
        this.data = data;
    }
}

/** JSON-RPC response class with generics */
export class JsonRpcResponse<TResult>
{
    public jsonrpc: string = "2.0";
    public result: TResult = null!;
    public error: JsonRpcError = null!;
    public id: string = "";
}

/** JSON-RPC client class. See https://www.jsonrpc.org/specification */
export class JsonRpcClient
{
    /** A utility function to convert any object to JSON string */
    public static ObjectToJson(obj: any): string
    {
        return JSON.stringify(obj,
            (key, value) =>
            {
                if (key.endsWith("_bin"))
                {
                    return Util_Base64_Encode(value);
                }
                return value;
            }
            , 4);
    }

    /** A utility function to convert JSON string to object */
    public static JsonToObject(str: string): any
    {
        return JSON.parse(str,
            (key, value) =>
            {
                if (key.endsWith("_bin"))
                {
                    return Util_Base64_Decode(value);
                }
                else if (key.endsWith("_dt"))
                {
                    return new Date(value);
                }
                return value;
            });
    }

    /** Base URL */
    public BaseUrl: string;

    /** The instance of HTTP client */
    private client: HttpClient;

    /** Additional HTTP headers */
    private headers: { [name: string]: string };

    /**
     * JSON-RPC client class constructor
     * @param url The URL
     * @param headers Additional HTTP headers
     * @param send_credential Set true to use the same credential with the browsing web site. Valid only if the code is running on the web browser.
     */
    constructor(url: string, headers: { [name: string]: string }, send_credential: boolean, nodejs_https_client_reject_untrusted_server_cert: boolean)
    {
        this.BaseUrl = url;
        this.headers = headers;

        this.client = new HttpClient();
        this.client.SendCredential = send_credential;
        this.client.NodeJS_HTTPS_Client_Reject_Unauthorized = nodejs_https_client_reject_untrusted_server_cert;
    }

    /**
     * Call a single RPC call (without error check). You can wait for the response with Promise<string> or await statement.
     * @param method_name The name of RPC method
     * @param param The parameters
     */
    public async CallInternalAsync(method_name: string, param: any): Promise<string>
    {
        let id = "1";

        let req = new JsonRpcRequest(method_name, param, id);

        let req_string = JsonRpcClient.ObjectToJson(req);

        if (debug_mode)
        {
            console.log("--- RPC Request Body ---");
            console.log(req_string);
            console.log("------------------------");
        }

        let http_response = await this.client.PostAsync(this.BaseUrl, this.headers,
            req_string, "application/json");

        let ret_string = http_response.Body;

        if (debug_mode)
        {
            console.log("--- RPC Response Body ---");
            console.log(ret_string);
            console.log("-------------------------");
        }

        return ret_string;
    }

    /**
     * Call a single RPC call (with error check). You can wait for the response with Promise<TResult> or await statement. In the case of error, it will be thrown.
     * @param method_name The name of RPC method
     * @param param The parameters
     */
    public async CallAsync<TResult>(method_name: string, param: any): Promise<TResult>
    {
        let ret_string = await this.CallInternalAsync(method_name, param);

        let ret: JsonRpcResponse<TResult> = JSON.parse(ret_string);

        if (is_null(ret.error) === false)
        {
            throw new JsonRpcException(ret.error);
        }

        return ret.result;
    }
}

/** JSON-RPC exception class */
export class JsonRpcException extends Error
{
    public Error: JsonRpcError;

    constructor(error: JsonRpcError)
    {
        super(`Code=${error.code}, Message=${error.message}`);
        this.Error = error;
    }
}

/** HTTP client exception class */
export class HttpClientException extends Error
{
    constructor(message: string)
    {
        super(message);
    }
}

/** HTTP client response class */
export class HttpClientResponse
{
    public Body: string = "";
}

/** An HTTP client which can be used in both web browsers and Node.js */
export class HttpClient
{
    public TimeoutMsecs: number = 60 * 5 * 1000;
    public SendCredential: boolean = true;
    public NodeJS_HTTPS_Client_Reject_Unauthorized: boolean = false;

    /** Post method. In web browsers this function will process the request by itself. In Node.js this function will call PostAsync_NodeJS() instead. */
    public async PostAsync(url: string, headers: { [name: string]: string },
        req_body: string, req_media_type: string): Promise<HttpClientResponse>
    {
        if (is_node_js)
        {
            return this.PostAsync_NodeJS(url, headers, req_body, req_media_type);
        }

        let fetch_header_list = new Headers();

        for (let name of Object.keys(headers))
        {
            fetch_header_list.append(name, headers[name]);
        }

        let fetch_init: RequestInit =
        {
            mode: "cors",
            headers: fetch_header_list,
            credentials: (this.SendCredential ? "include" : "omit"),
            method: "POST",
            cache: "no-cache",
            keepalive: true,
            redirect: "follow",
            body: req_body,
        };

        let fetch_response = await fetch(url, fetch_init);

        if (fetch_response.ok === false)
        {
            throw new HttpClientException("HTTP Error: " + fetch_response.status + " " + fetch_response.statusText);
        }

        let ret = new HttpClientResponse();

        ret.Body = await fetch_response.text();

        return ret;
    }

    /** Post method for Node.js. */
    public PostAsync_NodeJS(url: string, headers: { [name: string]: string },
        req_body: string, req_media_type: string): Promise<HttpClientResponse>
    {
        const https = require("https");
        const keepAliveAgent = new https.Agent({ keepAlive: true });
        const urlparse = require("url");

        const urlobj = urlparse.parse(url);

        if (is_null(urlobj.host)) throw new Error("URL is invalid.");

        let options =
        {
            host: urlobj.hostname,
            port: urlobj.port,
            path: urlobj.path,
            rejectUnauthorized: this.NodeJS_HTTPS_Client_Reject_Unauthorized,
            method: "POST",
            timeout: this.TimeoutMsecs,
            agent: keepAliveAgent,
        };

        return new Promise(function (resolve, reject)
        {
            let req = https.request(options, (res: any) =>
            {
                if (res.statusCode !== 200)
                {
                    reject(new HttpClientException("HTTP Error: " + res.statusCode + " " + res.statusMessage));
                }

                let recv_str: string = "";

                res.on("data", (body: any) =>
                {
                    recv_str += body;
                });

                res.on("end", () =>
                {
                    let ret = new HttpClientResponse();

                    ret.Body = recv_str;

                    resolve(ret);
                });
            }).on("error", (err: any) =>
            {
                throw err;
            }
            );

            for (let name of Object.keys(headers))
            {
                req.setHeader(name, !is_null(headers[name]) ? headers[name] : "");
            }
            req.setHeader("Content-Type", req_media_type);
            req.setHeader("Content-Length", Buffer.byteLength(req_body));
            req.write(req_body);
            req.end();
        });
    }
}




//////// BEGIN: Base64 encode / decode utility functions from https://github.com/beatgammit/base64-js
// The MIT License(MIT)
// Copyright(c) 2014
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
//     in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
//     all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

var lookup: any = [];
var revLookup: any = [];

var code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (var i = 0, len = code.length; i < len; ++i)
{
    lookup[i] = code[i];
    revLookup[code.charCodeAt(i)] = i;
}

// Support decoding URL-safe base64 strings, as Node.js does.
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
revLookup["-".charCodeAt(0)] = 62;
revLookup["_".charCodeAt(0)] = 63;

function getLens(b64: any)
{
    var len = b64.length;

    if (len % 4 > 0)
    {
        throw new Error("Invalid string. Length must be a multiple of 4");
    }

    // Trim off extra bytes after placeholder bytes are found
    // See: https://github.com/beatgammit/base64-js/issues/42
    var validLen = b64.indexOf("=");
    if (validLen === -1) validLen = len;

    var placeHoldersLen = validLen === len
        ? 0
        : 4 - (validLen % 4);

    return [validLen, placeHoldersLen];
}

// base64 is 4/3 + up to two characters of the original data
function byteLength(b64: any)
{
    var lens = getLens(b64);
    var validLen = lens[0];
    var placeHoldersLen = lens[1];
    return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen;
}

function _byteLength(b64: any, validLen: any, placeHoldersLen: any)
{
    return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen;
}

export function Util_Base64_Decode(b64: any)
{
    var tmp;
    var lens = getLens(b64);
    var validLen = lens[0];
    var placeHoldersLen = lens[1];

    var arr = new Uint8Array(_byteLength(b64, validLen, placeHoldersLen));

    var curByte = 0;

    // if there are placeholders, only get up to the last complete 4 chars
    var len = placeHoldersLen > 0
        ? validLen - 4
        : validLen;

    for (var i = 0; i < len; i += 4)
    {
        tmp =
            (revLookup[b64.charCodeAt(i)] << 18) |
            (revLookup[b64.charCodeAt(i + 1)] << 12) |
            (revLookup[b64.charCodeAt(i + 2)] << 6) |
            revLookup[b64.charCodeAt(i + 3)];
        arr[curByte++] = (tmp >> 16) & 0xFF;
        arr[curByte++] = (tmp >> 8) & 0xFF;
        arr[curByte++] = tmp & 0xFF;
    }

    if (placeHoldersLen === 2)
    {
        tmp =
            (revLookup[b64.charCodeAt(i)] << 2) |
            (revLookup[b64.charCodeAt(i + 1)] >> 4);
        arr[curByte++] = tmp & 0xFF;
    }

    if (placeHoldersLen === 1)
    {
        tmp =
            (revLookup[b64.charCodeAt(i)] << 10) |
            (revLookup[b64.charCodeAt(i + 1)] << 4) |
            (revLookup[b64.charCodeAt(i + 2)] >> 2);
        arr[curByte++] = (tmp >> 8) & 0xFF;
        arr[curByte++] = tmp & 0xFF;
    }

    return arr;
}

function tripletToBase64(num: any)
{
    return lookup[num >> 18 & 0x3F] +
        lookup[num >> 12 & 0x3F] +
        lookup[num >> 6 & 0x3F] +
        lookup[num & 0x3F];
}

function encodeChunk(uint8: any, start: any, end: any)
{
    var tmp;
    var output = [];
    for (var i = start; i < end; i += 3)
    {
        tmp =
            ((uint8[i] << 16) & 0xFF0000) +
            ((uint8[i + 1] << 8) & 0xFF00) +
            (uint8[i + 2] & 0xFF);
        output.push(tripletToBase64(tmp));
    }
    return output.join("");
}

export function Util_Base64_Encode(uint8: any)
{
    var tmp;
    var len = uint8.length;
    var extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
    var parts = [];
    var maxChunkLength = 16383; // must be multiple of 3

    // go through the array every three bytes, we'll deal with trailing stuff later
    for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength)
    {
        parts.push(encodeChunk(
            uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)
        ));
    }

    // pad the end with zeros, but make sure to not forget the extra bytes
    if (extraBytes === 1)
    {
        tmp = uint8[len - 1];
        parts.push(
            lookup[tmp >> 2] +
            lookup[(tmp << 4) & 0x3F] +
            "=="
        );
    } else if (extraBytes === 2)
    {
        tmp = (uint8[len - 2] << 8) + uint8[len - 1];
        parts.push(
            lookup[tmp >> 10] +
            lookup[(tmp >> 4) & 0x3F] +
            lookup[(tmp << 2) & 0x3F] +
            "="
        );
    }

    return parts.join("");
}
//////// END: Base64 encode / decode utility functions from https://github.com/beatgammit/base64-js



