"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 Tiles
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ArcGISImageryProvider = void 0;
const internal_1 = require("../../../../tile/internal");
const IModelApp_1 = require("../../../../IModelApp");
const NotificationManager_1 = require("../../../../NotificationManager");
const utils_1 = require("../../../../request/utils");
/** Base class for ArcGIS map-layer imagery providers.
 *
 * The initial purpose of this class is to offer shared methods
 * to query ArcGIS services and apply the appropriate security token.
 * @see [[ArcGISMapLayerImageryProvider]]
 * @internal
 */
class ArcGISImageryProvider extends internal_1.MapLayerImageryProvider {
    _accessClient;
    _lastAccessToken;
    /** Flag indicating if access token should be added to request.
    * @note We assume a service to require access token for the entire viewing session.
    */
    _accessTokenRequired = false;
    _querySupported = false;
    get supportsMapFeatureInfo() { return this._querySupported; }
    constructor(settings, usesCachedTiles) {
        super(settings, usesCachedTiles);
        this._accessClient = IModelApp_1.IModelApp.mapLayerFormatRegistry?.getAccessClient(settings.formatId);
    }
    /** Updates the accessClient token state whenever the status of the provider change.
     * */
    onStatusUpdated(status) {
        if (status === internal_1.MapLayerImageryProviderStatus.RequireAuth) {
            // Invalidate the token, so a new one get generated
            if (this._accessClient?.invalidateToken !== undefined && this._lastAccessToken !== undefined) {
                this._accessClient.invalidateToken(this._lastAccessToken);
            }
            // Make sure we don't re-use this token again (i.e force login process)
            this._lastAccessToken = undefined;
        }
    }
    /**
     * Fetch an ArcGIS service metadata, and returns its JSON representation.
     * This wrapper maintains token state and should be used instead of the the ArcGisUtilities version.
    */
    async getServiceJson() {
        let metadata;
        try {
            metadata = await internal_1.ArcGisUtilities.getServiceJson({ url: this._settings.url, formatId: this._settings.formatId, userName: this._settings.userName, password: this._settings.password, queryParams: this._settings.collectQueryParams() });
        }
        catch {
        }
        if (metadata && metadata.accessTokenRequired) {
            const accessClient = IModelApp_1.IModelApp.mapLayerFormatRegistry.getAccessClient(this._settings.formatId);
            if (accessClient) {
                try {
                    // Keep track of last used access token, so we can invalidate it later when an errors occurs
                    const accessToken = await accessClient.getAccessToken({ mapLayerUrl: new URL(this._settings.url) });
                    this._lastAccessToken = accessToken;
                }
                catch {
                }
            }
            // By turning this ON, tiles requests will include security token
            this._accessTokenRequired = metadata.accessTokenRequired;
        }
        return metadata;
    }
    /**
     * Make a request to an ArcGIS service using the provided URL and init parameters.
     * @param url URL to query
     * @param options Custom settings to apply to the request.
     * Refer to fetch API for more details (https://developer.mozilla.org/en-US/docs/Web/API/fetch)
     */
    async fetch(url, options) {
        let errorCode;
        const urlObj = new URL(url);
        const queryParams = this._settings.collectQueryParams();
        Object.keys(queryParams).forEach((paramKey) => {
            if (!urlObj.searchParams.has(paramKey))
                urlObj.searchParams.append(paramKey, queryParams[paramKey]);
        });
        if (this._accessTokenRequired && this._accessClient) {
            this._lastAccessToken = await internal_1.ArcGisUtilities.appendSecurityToken(urlObj, this._accessClient, {
                mapLayerUrl: new URL(this._settings.url),
                portal: typeof this._settings.properties?.portal === "string" ? this._settings.properties.portal : undefined,
                userName: this._settings.userName,
                password: this._settings.password
            });
        }
        // We want to complete the first request before letting other requests go;
        // this done to avoid flooding server with requests missing credentials
        if (!this._firstRequestPromise)
            this._firstRequestPromise = new Promise((resolve) => this.onFirstRequestCompleted.addOnce(() => resolve()));
        else
            await this._firstRequestPromise;
        let response;
        try {
            response = await fetch(urlObj, { ...options, credentials: this._includeUserCredentials ? "include" : undefined });
            if (response.status === 401 && !this._lastAccessToken && (0, utils_1.headersIncludeAuthMethod)(response.headers, ["ntlm", "negotiate"])) {
                // We got a http 401 challenge, lets try again with SSO enabled (i.e. Windows Authentication)
                response = await fetch(urlObj, { ...options, credentials: "include" });
                if (response.status === 200) {
                    this._includeUserCredentials = true; // avoid going through 401 challenges over and over
                }
            }
            if ((this._lastAccessToken && response.status === 400)
                || response.headers.get("content-type")?.toLowerCase().includes("htm")) {
                // For some reasons when we make a request with the fetch() api and there is a token error
                // we receive a status 400 instead of proper json response. (i.e doing the same request in the browser gives a different response)
                // For some other request, we also seen error message  in html.
                // When it occurs, we fall back to root service request so we get a proper JSON response with error code.
                const tmpUrl = new URL(this._settings.url);
                if (this._lastAccessToken && this._accessTokenRequired)
                    tmpUrl.searchParams.append("token", this._lastAccessToken.token);
                tmpUrl.searchParams.append("f", "json");
                response = await fetch(tmpUrl.toString(), options);
            }
            errorCode = await internal_1.ArcGisUtilities.checkForResponseErrorCode(response);
            if (errorCode !== undefined &&
                (errorCode === internal_1.ArcGisErrorCode.TokenRequired
                    || errorCode === internal_1.ArcGisErrorCode.InvalidToken
                    || errorCode === internal_1.ArcGisErrorCode.MissingPermissions)) {
                if (this._settings.userName && this._settings.userName.length > 0 && this._lastAccessToken) {
                    // **** Legacy token ONLY ***
                    // Token might have expired, make a second attempt by forcing new token.
                    if (this._accessClient?.invalidateToken !== undefined && this._lastAccessToken !== undefined)
                        this._accessClient.invalidateToken(this._lastAccessToken);
                    const urlObj2 = new URL(url);
                    if (this._accessClient) {
                        try {
                            this._lastAccessToken = await internal_1.ArcGisUtilities.appendSecurityToken(urlObj, this._accessClient, { mapLayerUrl: urlObj, userName: this._settings.userName, password: this._settings.password });
                        }
                        catch {
                        }
                    }
                    // Make a second attempt with refreshed token
                    response = await fetch(urlObj2.toString(), options);
                    errorCode = await internal_1.ArcGisUtilities.checkForResponseErrorCode(response);
                }
                if (errorCode !== undefined &&
                    (errorCode === internal_1.ArcGisErrorCode.TokenRequired
                        || errorCode === internal_1.ArcGisErrorCode.InvalidToken
                        || errorCode === internal_1.ArcGisErrorCode.MissingPermissions)) {
                    // Looks like the initially generated token has expired.
                    if (this.status === internal_1.MapLayerImageryProviderStatus.Valid) {
                        // Only report new status change to avoid spamming the UI
                        this.setStatus(internal_1.MapLayerImageryProviderStatus.RequireAuth);
                        this.onStatusChanged.raiseEvent(this);
                        const msg = IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:MapLayers.Messages.FetchTooltipTokenError", { layerName: this._settings.name });
                        IModelApp_1.IModelApp.notifications.outputMessage(new NotificationManager_1.NotifyMessageDetails(NotificationManager_1.OutputMessagePriority.Warning, msg));
                    }
                }
            }
        }
        finally {
            this.onFirstRequestCompleted.raiseEvent();
        }
        if (response === undefined)
            throw new Error("fetch call failed");
        return response;
    }
}
exports.ArcGISImageryProvider = ArcGISImageryProvider;
//# sourceMappingURL=ArcGISImageryProvider.js.map