UNPKG

@itwin/core-frontend

Version:
169 lines • 9.61 kB
"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), 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