"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
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.BentleyCloudRpcProtocol = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const IModelError_1 = require("../../IModelError");
const RpcConfiguration_1 = require("../core/RpcConfiguration");
const RpcOperation_1 = require("../core/RpcOperation");
const WebAppRpcProtocol_1 = require("./WebAppRpcProtocol");
var AppMode;
(function (AppMode) {
    AppMode["MilestoneReview"] = "1";
})(AppMode || (AppMode = {}));
/** An http protocol for Bentley cloud RPC interface deployments.
 * @internal
 */
class BentleyCloudRpcProtocol extends WebAppRpcProtocol_1.WebAppRpcProtocol {
    constructor() {
        super(...arguments);
        this.checkToken = true;
        /** The name of various HTTP request headers based on client's request context */
        this.serializedClientRequestContextHeaderNames = {
            /** The name of the HTTP request id header. */
            id: "X-Correlation-Id",
            /** The name of the HTTP application id header. */
            applicationId: "X-Application-Id",
            /** The name of the HTTP application version header. */
            applicationVersion: "X-Application-Version",
            /** The name of the HTTP session id header. */
            sessionId: "X-Session-Id",
            /** The name of the HTTP authorization header. */
            authorization: "Authorization",
        };
        /** The name of the RPC protocol version header. */
        this.protocolVersionHeaderName = "X-Protocol-Version";
    }
    /** Returns the operation specified by an OpenAPI-compatible URI path. */
    getOperationFromPath(path) {
        const url = new URL(path, "https://localhost/");
        const components = url.pathname.split("/").filter((x) => x); // filter out empty segments
        const operationComponent = components.slice(-1)[0];
        const encodedRequest = url.searchParams.get("parameters") || "";
        // The encodedRequest should be base64 - fail now if any other characters detected.
        if (/[^a-zA-Z0-9=+\/$]/.test(encodedRequest))
            throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, `Invalid request: Malformed URL parameters detected.`);
        const firstHyphen = operationComponent.indexOf("-");
        const lastHyphen = operationComponent.lastIndexOf("-");
        const interfaceDefinition = operationComponent.slice(0, firstHyphen);
        const interfaceVersion = operationComponent.slice(firstHyphen + 1, lastHyphen);
        const operationName = operationComponent.slice(lastHyphen + 1);
        return { interfaceDefinition, operationName, interfaceVersion, encodedRequest };
    }
    /** Supplies the OpenAPI-compatible URI path for an RPC operation. */
    supplyPathForOperation(operation, request) {
        const prefix = this.pathPrefix;
        const appTitle = this.info.title;
        const appVersion = this.info.version;
        const operationId = `${operation.interfaceDefinition.interfaceName}-${operation.interfaceVersion}-${operation.operationName}`;
        let appMode = "";
        let iTwinId = "";
        let iModelId = "";
        let routeChangesetId;
        /* Note: The changesetId field is omitted in the route in the case of ReadWrite connections since the connection is generally expected to be at the
         * latest version and not some specific changeset. Also, for the first version (before any changesets), the changesetId in the route is arbitrarily
         * set to "0" instead of an empty string, since the latter is more un-intuitive for a route. However, in all other use cases, including the changesetId
         * held by the IModelRpcProps itself, the changesetId of "" (i.e., empty string) signifies the first version - this is more intuitive and retains
         * compatibility with the majority of use cases. */
        if (request === undefined) {
            appMode = "{modeId}";
            iTwinId = "{iTwinId}";
            iModelId = "{iModelId}";
            routeChangesetId = "{changeSetId}";
        }
        else {
            let token = operation.policy.token(request) || RpcOperation_1.RpcOperation.fallbackToken;
            if (!token || !token.iModelId) {
                if (RpcConfiguration_1.RpcConfiguration.disableRoutingValidation) {
                    token = { key: "" };
                }
                else {
                    throw new IModelError_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Invalid iModelToken for RPC operation request");
                }
            }
            iTwinId = encodeURIComponent(token.iTwinId || "");
            iModelId = encodeURIComponent(token.iModelId);
            routeChangesetId = token.changeset?.id || "0";
            appMode = AppMode.MilestoneReview;
        }
        return `${prefix}/${appTitle}/${appVersion}/mode/${appMode}/context/${iTwinId}/imodel/${iModelId}${!!routeChangesetId ? `/changeset/${routeChangesetId}` : ""}/${operationId}`;
    }
    /**
     * Inflates the IModelRpcProps from the URL path for each request on the backend.
     * @note This function updates the IModelRpcProps value supplied in the request body.
     */
    inflateToken(tokenFromBody, request) {
        const urlPathComponents = request.path.split("/");
        let iModelId = tokenFromBody.iModelId;
        let iTwinId = tokenFromBody.iTwinId;
        const changeset = { id: tokenFromBody.changeset?.id ?? "0", index: tokenFromBody.changeset?.index };
        for (let i = 0; i <= urlPathComponents.length; ++i) {
            const key = urlPathComponents[i];
            const value = urlPathComponents[i + 1];
            if (key === "mode") {
                ++i;
            }
            else if (key === "context") {
                iTwinId = value;
                ++i;
            }
            else if (key === "imodel") {
                iModelId = value;
                ++i;
            }
            else if (key === "changeset") {
                changeset.id = (value === "0") ? "" : value;
                ++i;
            }
        }
        // Overwrite the key if it includes a : because its most likely a guid. We know what it should be based off of the url.
        // Leave it alone if its a non guid key.
        return { key: tokenFromBody.key === undefined || tokenFromBody.key.includes(":") ? `${iModelId}:${changeset.id}` : tokenFromBody.key, iTwinId, iModelId, changeset };
    }
    /** Returns the OpenAPI-compatible URI path parameters for an RPC operation.
     * @internal
     */
    supplyPathParametersForOperation(_operation) {
        return [
            { name: "modeId", in: "path", required: true, schema: { type: "string" } },
            { name: "iTwinId", in: "path", required: true, schema: { type: "string" } },
            { name: "iModelId", in: "path", required: true, schema: { type: "string" } },
            { name: "changeSetId", in: "path", required: false, schema: { type: "string" } },
        ];
    }
}
exports.BentleyCloudRpcProtocol = BentleyCloudRpcProtocol;
//# sourceMappingURL=BentleyCloudRpcProtocol.js.map