"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module RpcInterface
 */
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebAppRpcRequest = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const IModelError_1 = require("../../IModelError");
const RpcConstants_1 = require("../core/RpcConstants");
const RpcMarshaling_1 = require("../core/RpcMarshaling");
const RpcRequest_1 = require("../core/RpcRequest");
const RpcMultipartParser_1 = require("./multipart/RpcMultipartParser");
const RpcMultipart_1 = require("./RpcMultipart");
const WebAppRpcProtocol_1 = require("./WebAppRpcProtocol");
/** A web application RPC request.
 * @internal
 */
class WebAppRpcRequest extends RpcRequest_1.RpcRequest {
    get _headers() { return this._request.headers; }
    /** Parses a request. */
    static async parseRequest(protocol, req) {
        return this.backend.parseRequest(protocol, req);
    }
    /** Sends the response for a web request. */
    static async sendResponse(protocol, request, fulfillment, req, res) {
        return this.backend.sendResponse(protocol, request, fulfillment, req, res);
    }
    /** Determines the most efficient transport type for an RPC value. */
    static computeTransportType(value, source) {
        if (source instanceof Uint8Array || (Array.isArray(source) && source[0] instanceof Uint8Array)) {
            return RpcConstants_1.RpcContentType.Binary;
        }
        else if (value.data.length > 0) {
            return RpcConstants_1.RpcContentType.Multipart;
        }
        else if (value.stream) {
            return RpcConstants_1.RpcContentType.Stream;
        }
        else {
            return RpcConstants_1.RpcContentType.Text;
        }
    }
    /** Constructs a web application request. */
    constructor(client, operation, parameters) {
        super(client, operation, parameters);
        this._loading = false;
        this._request = {};
        this._pathSuffix = "";
        /** Convenience access to the protocol of this request. */
        this.protocol = this.client.configuration.protocol;
        /** Standardized access to metadata about the request (useful for purposes such as logging). */
        this.metadata = { status: 0, message: "" };
        this.path = this.protocol.supplyPathForOperation(this.operation, this);
        this.method = "head";
        this._request.headers = {};
    }
    /** Sets request header values. */
    setHeader(name, value) {
        this._headers[name] = value;
    }
    /** Sends the request. */
    async send() {
        this._loading = true;
        await this.setupTransport();
        return new Promise(async (resolve, reject) => {
            try {
                resolve(await this.performFetch());
            }
            catch (reason) {
                reject(new IModelError_1.ServerError(-1, typeof (reason) === "string" ? reason : "Server connection error."));
            }
        });
    }
    computeRetryAfter(attempts) {
        const retryAfter = this._response && this._response.headers.get("Retry-After");
        if (retryAfter) {
            this.resetTransientFaultCount();
            const r = Number(retryAfter);
            if (Number.isFinite(r)) {
                return r * 1000;
            }
            const d = Date.parse(retryAfter);
            if (!Number.isNaN(d)) {
                return d - Date.now();
            }
        }
        else {
            this.recordTransientFault();
        }
        return super.computeRetryAfter(attempts);
    }
    handleUnknownResponse(code) {
        if (this.protocol.isTimeout(code)) {
            this.reject(new IModelError_1.ServerTimeoutError("Request timeout."));
        }
        else {
            this.reject(new IModelError_1.ServerError(code, "Unknown server response code."));
        }
    }
    async load() {
        return new Promise(async (resolve, reject) => {
            try {
                if (!this._loading)
                    return;
                const response = this._response;
                if (!response) {
                    reject(new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Invalid state."));
                    return;
                }
                if (this.protocol.protocolVersionHeaderName) {
                    const version = response.headers.get(this.protocol.protocolVersionHeaderName);
                    if (version) {
                        this.responseProtocolVersion = parseInt(version, 10);
                    }
                }
                const contentType = response.headers.get(RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT);
                const responseType = WebAppRpcProtocol_1.WebAppRpcProtocol.computeContentType(contentType);
                if (responseType === RpcConstants_1.RpcContentType.Binary) {
                    resolve(await this.loadBinary(response));
                }
                else if (responseType === RpcConstants_1.RpcContentType.Multipart) {
                    resolve(await this.loadMultipart(response, contentType));
                }
                else {
                    resolve(await this.loadText(response));
                }
                this._loading = false;
                this.setLastUpdatedTime();
                this.protocol.events.raiseEvent(RpcConstants_1.RpcProtocolEvent.ResponseLoaded, this);
            }
            catch (reason) {
                if (!this._loading)
                    return;
                this._loading = false;
                reject(new IModelError_1.ServerError(this.metadata.status, typeof (reason) === "string" ? reason : "Unknown server response error."));
            }
        });
    }
    /** Override to supply an alternate fetch function. */
    supplyFetch() {
        return fetch;
    }
    /** Override to supply an alternate Request function. */
    supplyRequest() {
        return Request;
    }
    async performFetch() {
        const requestClass = this.supplyRequest();
        const fetchFunction = this.supplyFetch();
        const path = new URL(this.path, typeof (location) !== "undefined" ? location.origin : undefined);
        if (this._pathSuffix) {
            const params = new URLSearchParams();
            params.set("parameters", this._pathSuffix);
            path.search = `?${params.toString()}`;
        }
        const request = new requestClass(path.toString(), this._request);
        const response = await fetchFunction(request);
        this._response = response;
        this.metadata.status = response.status;
        return response.status;
    }
    async loadText(response) {
        const value = await response.text();
        this.metadata.message = value;
        return RpcMarshaling_1.RpcSerializedValue.create(value);
    }
    async loadBinary(response) {
        const value = new Uint8Array(await response.arrayBuffer());
        const objects = JSON.stringify(RpcMarshaling_1.MarshalingBinaryMarker.createDefault());
        return RpcMarshaling_1.RpcSerializedValue.create(objects, [value]);
    }
    async loadMultipart(response, contentType) {
        const data = await response.arrayBuffer();
        const value = new RpcMultipartParser_1.RpcMultipartParser(contentType, new Uint8Array(data)).parse();
        return value;
    }
    async setupTransport() {
        const parameters = (await this.protocol.serialize(this)).parameters;
        const transportType = _a.computeTransportType(parameters, this.parameters);
        if (transportType === RpcConstants_1.RpcContentType.Binary) {
            this.setupBinaryTransport(parameters);
        }
        else if (transportType === RpcConstants_1.RpcContentType.Multipart) {
            this.setupMultipartTransport(parameters);
        }
        else {
            this.setupTextTransport(parameters);
        }
    }
    setupBinaryTransport(parameters) {
        this._headers[RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT] = RpcConstants_1.WEB_RPC_CONSTANTS.BINARY;
        this._request.method = "post";
        this._request.body = parameters.data[0];
    }
    setupMultipartTransport(parameters) {
        // IMPORTANT: do not set a multipart Content-Type header value. The browser does this automatically!
        delete this._headers[RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT];
        this._request.method = "post";
        this._request.body = RpcMultipart_1.RpcMultipart.createForm(parameters);
    }
    setupTextTransport(parameters) {
        if (this.operation.policy.allowResponseCaching(this)) {
            const encodedBody = btoa(parameters.objects);
            if (encodedBody.length <= _a.maxUrlComponentSize) {
                this._request.method = "get";
                this._request.body = undefined;
                delete this._headers[RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT];
                this._pathSuffix = encodedBody;
                return;
            }
        }
        this._pathSuffix = "";
        this._headers[RpcConstants_1.WEB_RPC_CONSTANTS.CONTENT] = RpcConstants_1.WEB_RPC_CONSTANTS.TEXT;
        this._request.method = "post";
        this._request.body = parameters.objects;
    }
}
exports.WebAppRpcRequest = WebAppRpcRequest;
_a = WebAppRpcRequest;
/** The maximum size permitted for an encoded component in a URL.
 * Note that some backends limit the total cumulative request size. Our current node backends accept requests with a max size of 16 kb.
 * In addition to the url size, an authorization header may also add considerably to the request size.
 * @note This is used for features like encoding the payload of a cacheable request in the URL.
 */
WebAppRpcRequest.maxUrlComponentSize = 1024 * 8;
/** @internal */
WebAppRpcRequest.backend = {
    sendResponse: async (_protocol, _request, _fulfillment, _req, _res) => {
        throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Not bound.");
    },
    parseRequest: async (_protocol, _req) => {
        throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Not bound.");
    },
};
//# sourceMappingURL=WebAppRpcRequest.js.map