"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TokenAcquisitionEventArgs = exports.BeforeRedirectEventArgs = void 0;
var tslib_1 = require("tslib");
var sp_core_library_1 = require("@microsoft/sp-core-library");
var sp_diagnostics_1 = require("@microsoft/sp-diagnostics");
var sp_telemetry_1 = require("@ms/sp-telemetry");
var Flights_1 = require("../common/Flights");
/**
 * Arguments for before redirecting for a full page authentication
 * @public
 */
var BeforeRedirectEventArgs = /** @class */ (function (_super) {
    tslib_1.__extends(BeforeRedirectEventArgs, _super);
    function BeforeRedirectEventArgs(redirectUrl, cancel) {
        var _this = _super.call(this) || this;
        _this.redirectUrl = redirectUrl;
        _this.cancel = cancel;
        return _this;
    }
    return BeforeRedirectEventArgs;
}(sp_core_library_1.SPEventArgs));
exports.BeforeRedirectEventArgs = BeforeRedirectEventArgs;
/**
 * Arguments for a token acquisition failure event.
 * @public
 */
var TokenAcquisitionEventArgs = /** @class */ (function (_super) {
    tslib_1.__extends(TokenAcquisitionEventArgs, _super);
    function TokenAcquisitionEventArgs(message, redirectUrl) {
        var _this = _super.call(this) || this;
        _this.message = message;
        _this.redirectUrl = redirectUrl;
        return _this;
    }
    return TokenAcquisitionEventArgs;
}(sp_core_library_1.SPEventArgs));
exports.TokenAcquisitionEventArgs = TokenAcquisitionEventArgs;
/**
 * This class allows a developer to obtain OAuth2 tokens from Azure AD.
 *
 * OAuth2 tokens are used to authenticate the user from the SharePoint page
 * to other services such as PowerBI, Sway, Exchange, Yammer, etc.
 *
 * @privateRemarks
 * AadTokenProvider is replacing the /_api.SP.OAuth.Token/Acquire endpoint
 * for authentication with ADAL.js. At some point in the near future, when Azure AD v2.0
 * can support the same scenarios as the original version, we will switch to MSAL.
 *
 * @public
 * @sealed
 */
var AadTokenProvider = /** @class */ (function () {
    /**
     * @internal
     */
    function AadTokenProvider(configuration, oboConfiguration) {
        this._aadConfiguration = configuration;
        this._oboConfiguration = oboConfiguration;
        this._tokenAcquisitionEvent = new sp_core_library_1.SPEvent(AadTokenProvider._tokenAcquisitionEventId);
        this.onBeforeRedirectEvent = new sp_core_library_1.SPEvent(AadTokenProvider._onBeforeRedirectEventId);
        this._collectTokenPrefetchingTelemetry = sp_core_library_1._SPFlight.isEnabled(60114); /* SPPPLATPrefetchTokenAadCallCounter */
        this._endpointToUrlMapping = new Map();
        this._loggedGetToken = false;
        this._loggedGetTokenInternal = false;
    }
    /**
     * Fetches the AAD OAuth2 token for a resource if the user that's currently logged in has
     * access to that resource.
     *
     * The OAuth2 token should not be cached by the caller since it is already cached by the method
     * itself.
     *
     * @param resourceEndpoint - the resource for which the token should be obtained
     * @param useCachedToken - Allows the developer to specify if cached tokens should be returned.
     * An example of a resourceEndpoint would be https://graph.microsoft.com
     * @returns A promise that will be fullfiled with the token or that will reject
     *          with an error message
     */
    AadTokenProvider.prototype.getToken = function (resourceEndpoint, useCachedToken) {
        var _this = this;
        if (useCachedToken === void 0) { useCachedToken = true; }
        return this._getDeferredAadTokenProvider().then(function (tokenProvider) {
            if (_this._utilizeTokenPrefetching && useCachedToken) {
                var endPoint_1 = _this._getMappedAppIdIfPossible(resourceEndpoint);
                // If the endPoint returned is different, this means a viable substitute was found
                if (endPoint_1 !== resourceEndpoint) {
                    var usePrefetchedTokenQosMonitor_1;
                    if (!_this._loggedGetToken) {
                        _this._loggedGetToken = true;
                        usePrefetchedTokenQosMonitor_1 = new sp_diagnostics_1._QosMonitor('AadHttpClient.UsePrefetchedTokenQosMonitor');
                    }
                    return _this._applyTimeOut(AadTokenProvider._fetchUsingPrefetchedTokenTimeout, tokenProvider.getToken(endPoint_1, useCachedToken).then(function (token) {
                        // If on average it took longer than _fetchUsingPrefetchedTokenTimeout to succeed, we may want to increase timeout.
                        usePrefetchedTokenQosMonitor_1 === null || usePrefetchedTokenQosMonitor_1 === void 0 ? void 0 : usePrefetchedTokenQosMonitor_1.writeSuccess();
                        return token;
                    })).catch(function (error) {
                        // If getToken fails after using substituted endpoint, use regular endpoint.
                        // We log the error again because we want to track how many are due to timeouts vs failed fetches.
                        usePrefetchedTokenQosMonitor_1 === null || usePrefetchedTokenQosMonitor_1 === void 0 ? void 0 : usePrefetchedTokenQosMonitor_1.writeUnexpectedFailure(error);
                        _this._removeResourceEndpointFromListAndStorage(endPoint_1, resourceEndpoint);
                        _this._removeResourceEndpointFromMap(resourceEndpoint);
                        return tokenProvider.getToken(resourceEndpoint, useCachedToken);
                    });
                }
            }
            var skipPrefetchedTokenQosMonitor;
            if (!_this._loggedGetToken && _this._collectTokenPrefetchingTelemetry && useCachedToken) {
                skipPrefetchedTokenQosMonitor = new sp_diagnostics_1._QosMonitor('AadHttpClient.skipPrefetchedTokenQosMonitor');
            }
            return tokenProvider.getToken(resourceEndpoint, useCachedToken).then(function (response) {
                if (skipPrefetchedTokenQosMonitor) {
                    skipPrefetchedTokenQosMonitor.writeSuccess();
                }
                return response;
            });
        });
    };
    /**
     * Fetches the AAD OAuth2 token for a resource if the user that's currently logged in has
     * access to that resource.
     *
     * The OAuth2 token should not be cached by the caller since it is already cached by the method
     * itself.
     *
     * @param resourceEndpoint - the resource for which the token should be obtained
     * @param useCachedToken - Allows the developer to specify if cached tokens should be returned.
     * An example of a resourceEndpoint would be https://graph.microsoft.com
     * @returns A promise that will be fullfiled with the token or that will reject
     *          with an error message
     *
     * @internal
     */
    AadTokenProvider.prototype._getTokenInternal = function (resourceEndpoint, configuration, useCachedToken) {
        var _this = this;
        if (useCachedToken === void 0) { useCachedToken = true; }
        return this._getDeferredAadTokenProvider().then(function (tokenProvider) {
            if (_this._utilizeTokenPrefetching && useCachedToken) {
                var endPoint_2 = _this._getMappedAppIdIfPossible(resourceEndpoint);
                if (endPoint_2 !== resourceEndpoint) {
                    var usePrefetchedTokenInternalQosMonitor_1;
                    if (!_this._loggedGetTokenInternal) {
                        _this._loggedGetTokenInternal = true;
                        usePrefetchedTokenInternalQosMonitor_1 = new sp_diagnostics_1._QosMonitor('AadHttpClient.UsePrefetchedTokenInternalQosMonitor');
                    }
                    return _this._applyTimeOut(AadTokenProvider._fetchUsingPrefetchedTokenTimeout, tokenProvider._getTokenInternal(endPoint_2, configuration, useCachedToken).then(function (token) {
                        // If on average it took longer than _fetchUsingPrefetchedTokenTimeout to succeed, we may want to increase timeout.
                        usePrefetchedTokenInternalQosMonitor_1 === null || usePrefetchedTokenInternalQosMonitor_1 === void 0 ? void 0 : usePrefetchedTokenInternalQosMonitor_1.writeSuccess();
                        return token;
                    })).catch(function (error) {
                        // If getToken fails after using substituted endpoint, use regular endpoint.
                        // We log the error again because we want to track how many are due to timeouts vs failed fetches.
                        usePrefetchedTokenInternalQosMonitor_1 === null || usePrefetchedTokenInternalQosMonitor_1 === void 0 ? void 0 : usePrefetchedTokenInternalQosMonitor_1.writeUnexpectedFailure(error);
                        _this._removeResourceEndpointFromListAndStorage(endPoint_2, resourceEndpoint);
                        _this._removeResourceEndpointFromMap(endPoint_2);
                        return tokenProvider._getTokenInternal(resourceEndpoint, configuration, useCachedToken);
                    });
                }
            }
            var skipPrefetchedTokenInternalQosMonitor;
            if (!_this._loggedGetTokenInternal && _this._collectTokenPrefetchingTelemetry && useCachedToken) {
                _this._loggedGetTokenInternal = true;
                skipPrefetchedTokenInternalQosMonitor = new sp_diagnostics_1._QosMonitor('AadHttpClient.skipPrefetchedTokenInternalQosMonitor');
            }
            return tokenProvider
                ._getTokenInternal(resourceEndpoint, configuration, useCachedToken)
                .then(function (response) {
                if (skipPrefetchedTokenInternalQosMonitor) {
                    skipPrefetchedTokenInternalQosMonitor.writeSuccess();
                }
                return response;
            });
        });
    };
    /**
     * Fetches the AAD OAuth2 token data for a resource if the user that's currently logged in has
     * access to that resource.
     *
     * @internal
     */
    AadTokenProvider.prototype._getTokenData = function (resourceEndpoint, useCachedToken) {
        if (useCachedToken === void 0) { useCachedToken = true; }
        return this._getDeferredAadTokenProvider().then(function (tokenProvider) {
            return tokenProvider._getTokenData(resourceEndpoint, useCachedToken, true);
        });
    };
    /**
     * Used for prefetching tokens for API endpoints needed by apps.
     * Should only be called when _resourceUrl is passed in as a map.
     * It will only write to session the endpoint which had successful token fetching.
     * @internal
     */
    AadTokenProvider.prototype._prefetchTokensAndWriteMapToStorage = function (endpointsMapString) {
        var _this = this;
        if (!endpointsMapString) {
            return Promise.resolve();
        }
        if (this._shouldUtilizeTokenPrefetching()) {
            this._setResourceEndpointListFromStorage();
            this._setResourceEndpointMappingFromList();
            this._utilizeTokenPrefetching = true;
        }
        var endPointMappings;
        try {
            endPointMappings = JSON.parse(endpointsMapString);
        }
        catch (_a) {
            throw new Error('Could not JSON parse endpoints id to name mapping string.');
        }
        var totalEndpointsReceived = endPointMappings.mappings.length;
        // We should always pre-fetch tokens here, since either we need to generate new tokens for endpoints
        // or the tokens already exist in MSAL sessionStorage cache, which adds little overhead.
        var endPointsToPrefetch = [];
        var tokenPrefetchingArray = [];
        var existingCache = !!(this._endpointToUrlList && !this._isCachedMapExpired(this._endpointToUrlList));
        if (existingCache) {
            endPointsToPrefetch = this._endpointToUrlList.mappings;
        }
        else {
            this._endpointToUrlList = {
                time: new Date().toUTCString(),
                mappings: []
            };
            endPointsToPrefetch = endPointMappings.mappings;
        }
        var newTokenGenerationCounter = 0;
        var errorStrings = [];
        if (!this._utilizeTokenPrefetching) {
            if (this._collectTokenPrefetchingTelemetry) {
                var aadPrefetchTokenCountEsimateQosMonitor = new sp_diagnostics_1._QosMonitor('AadHttpClient.AadPrefetchTokenCountEsimateQosMonitor');
                aadPrefetchTokenCountEsimateQosMonitor.writeSuccess({
                    newTokensGenerated: endPointsToPrefetch.length
                });
            }
            return Promise.resolve();
        }
        // We want to limit the number of endpoints we prefetch, but we always want to fetch the one for MsGraph if it exists
        for (var i = AadTokenProvider._maxEndpointsToFetch; i < endPointsToPrefetch.length; i++) {
            if (endPointsToPrefetch[i].appId === AadTokenProvider._msGraphEndpointAppId) {
                endPointsToPrefetch[AadTokenProvider._maxEndpointsToFetch - 1] = endPointsToPrefetch[i];
                break;
            }
        }
        endPointsToPrefetch = endPointsToPrefetch.slice(0, AadTokenProvider._maxEndpointsToFetch);
        var _loop_1 = function (endpoint) {
            tokenPrefetchingArray.push(new Promise(function (resolve, reject) {
                // Because of the fact that MSAL caches tokens in the session storage, this will not always result in a server call
                return _this._getTokenData(endpoint.appId)
                    .then(function (response) {
                    // Map each of the names to the app id
                    var endpointsToStore = {
                        appId: endpoint.appId,
                        names: endpoint.names
                    };
                    for (var _i = 0, _a = endpointsToStore.names; _i < _a.length; _i++) {
                        var name_1 = _a[_i];
                        _this._endpointToUrlMapping.set(name_1, endpointsToStore.appId);
                    }
                    if (!existingCache) {
                        _this._endpointToUrlList.mappings.push(endpointsToStore);
                    }
                    if (!response.fromCache) {
                        newTokenGenerationCounter++;
                    }
                    resolve();
                })
                    .catch(function (error) {
                    // Need to resolve and manually concatenate errors, otherwise Promise.All will reject immediately
                    // if a single promise rejects.
                    errorStrings.push("Error fetching app id " + endpoint.appId + ": " + error);
                    resolve();
                });
            }));
        };
        for (var _i = 0, endPointsToPrefetch_1 = endPointsToPrefetch; _i < endPointsToPrefetch_1.length; _i++) {
            var endpoint = endPointsToPrefetch_1[_i];
            _loop_1(endpoint);
        }
        return Promise.all(tokenPrefetchingArray).then(function () {
            var callAadPrefetchTokenQosMonitor = new sp_diagnostics_1._QosMonitor('AadHttpClient.CallAadPrefetchTokenQosMonitor');
            if (errorStrings.length === 0) {
                callAadPrefetchTokenQosMonitor.writeSuccess({
                    newTokensGenerated: newTokenGenerationCounter,
                    totalEndpointsReceived: totalEndpointsReceived
                });
            }
            else {
                callAadPrefetchTokenQosMonitor.writeUnexpectedFailure('Error with prefetching one or more endpoint.', new Error(errorStrings.join()), { newTokensGenerated: newTokenGenerationCounter });
            }
            localStorage.setItem(AadTokenProvider._endpointIdUrlMappinglocalStorageKey, JSON.stringify(_this._endpointToUrlList));
            return Promise.resolve();
        });
    };
    Object.defineProperty(AadTokenProvider.prototype, "tokenAcquisitionEvent", {
        /**
         * Notifies the developer when Token Acquisition requires user action.
         * @eventproperty
         */
        get: function () {
            return this._tokenAcquisitionEvent;
        },
        enumerable: false,
        configurable: true
    });
    AadTokenProvider.prototype._getDeferredAadTokenProvider = function () {
        var _this = this;
        if (!this._aadTokenProvider) {
            this._aadTokenProvider = Promise.resolve().then(function () { return tslib_1.__importStar(require('./chunks/aadTokenProvider/DeferredAadTokenProvider' /* webpackChunkName: 'aadTokenProvider' */)); }).then(function (aadTokenProviderModule) {
                var deferredTokenProvider = new aadTokenProviderModule.DeferredAadTokenProvider(_this._tokenAcquisitionEvent, _this.onBeforeRedirectEvent, _this._aadConfiguration, _this._oboConfiguration, Flights_1.isTeamsRichClientAuthFlightEnabled() ? _this._oboFirstPartyTokenCallback : undefined);
                _this._tokenAcquisitionEvent = deferredTokenProvider.tokenAcquisitionEvent;
                return deferredTokenProvider;
            });
        }
        return this._aadTokenProvider;
    };
    /**
     * Get the list of urls for which tokens have already been pre-fetched
     */
    AadTokenProvider.prototype._setResourceEndpointListFromStorage = function () {
        var mapString = localStorage.getItem(AadTokenProvider._endpointIdUrlMappinglocalStorageKey);
        if (mapString) {
            this._endpointToUrlList = JSON.parse(mapString);
        }
    };
    /**
     * Any checks for SPPerfExperiments cannot be called in the constructor because the object is not yet intialized.
     */
    AadTokenProvider.prototype._shouldUtilizeTokenPrefetching = function () {
        return (sp_telemetry_1._SPPerfExperiment.getVariantAndTrackExperiment(sp_telemetry_1._PerformanceExperiment.SPFxAddInTokenPrefetchingExp) ===
            1 || sp_core_library_1._SPFlight.isEnabled(60129) /* SPPPLATPrefetchTokenForApplicationsClientSide */);
    };
    AadTokenProvider.prototype._setResourceEndpointMappingFromList = function () {
        if (this._endpointToUrlList && !this._isCachedMapExpired(this._endpointToUrlList)) {
            for (var _i = 0, _a = this._endpointToUrlList.mappings; _i < _a.length; _i++) {
                var endpoint = _a[_i];
                for (var _b = 0, _c = endpoint.names; _b < _c.length; _b++) {
                    var name_2 = _c[_b];
                    this._endpointToUrlMapping.set(name_2, endpoint.appId);
                }
            }
        }
    };
    /**
     * Used to get resource endpoint, which may be substituted for a functionally identical one acquired when prefetching tokens.
     * Should only be called when used to acquire a token.
     */
    AadTokenProvider.prototype._getMappedAppIdIfPossible = function (url) {
        var mappedResourceEndpoint = '';
        if (this._endpointToUrlMapping.has(url)) {
            mappedResourceEndpoint = url;
        }
        else if (this._endpointToUrlMapping.has(url + '/')) {
            mappedResourceEndpoint = url + '/';
        }
        if (mappedResourceEndpoint) {
            return this._endpointToUrlMapping.get(mappedResourceEndpoint);
        }
        return url;
    };
    AadTokenProvider.prototype._isCachedMapExpired = function (cachedMap) {
        var previousTime = new Date(cachedMap.time);
        var currentTime = new Date();
        if (previousTime &&
            (currentTime.valueOf() - previousTime.valueOf()) / AadTokenProvider._millisecondsToHourRatio <
                AadTokenProvider._cachePersistenceTime) {
            return false;
        }
        return true;
    };
    AadTokenProvider.prototype._applyTimeOut = function (time, promise) {
        var timeoutPromise = new Promise(function (resolve, reject) {
            setTimeout(function () {
                reject("Timed out after " + time + " milliseconds");
            }, time);
        });
        return Promise.race([promise, timeoutPromise]);
    };
    /**
     * remove endpoint from list of urls with prefetched tokens. Used in the event that the url for which token was prefetched
     * is not a viable substitute.
     */
    AadTokenProvider.prototype._removeResourceEndpointFromListAndStorage = function (appId, endPointName) {
        if (this._endpointToUrlList && !this._isCachedMapExpired(this._endpointToUrlList)) {
            for (var _i = 0, _a = this._endpointToUrlList.mappings; _i < _a.length; _i++) {
                var endPointMap = _a[_i];
                if (endPointMap.appId === appId) {
                    for (var i = 0; i < endPointMap.names.length; i++) {
                        if (endPointMap.names[i] === endPointName || endPointMap.names[i] === endPointName + '/') {
                            endPointMap.names.splice(i, 1);
                            localStorage.setItem(AadTokenProvider._endpointIdUrlMappinglocalStorageKey, JSON.stringify(this._endpointToUrlList));
                            return;
                        }
                    }
                }
            }
        }
    };
    AadTokenProvider.prototype._removeResourceEndpointFromMap = function (endPointName) {
        var _this = this;
        this._endpointToUrlMapping.forEach(function (value, key) {
            if (key === endPointName || key === endPointName + '/') {
                _this._endpointToUrlMapping.delete(key);
            }
        });
    };
    /**
     * Token Acquisition Event String
     * @internal
     */
    AadTokenProvider._tokenAcquisitionEventId = 'tokenAcquisitionFailure';
    /**
     * Auth redirect Event String
     * @internal
     */
    AadTokenProvider._onBeforeRedirectEventId = 'onBeforeAuthRedirect';
    AadTokenProvider._endpointIdUrlMappinglocalStorageKey = 'endpointIdUrlMapping';
    AadTokenProvider._msGraphEndpointAppId = '00000003-0000-0000-c000-000000000000';
    AadTokenProvider._maxEndpointsToFetch = 3;
    // The allowed timeout for retrieving token with a pre-fetched endpoint
    AadTokenProvider._fetchUsingPrefetchedTokenTimeout = 6000;
    // The maximum amount of time (hours) before populating session storage with new prefetched tokens
    AadTokenProvider._cachePersistenceTime = 1;
    AadTokenProvider._millisecondsToHourRatio = 60 * 60 * 1000;
    return AadTokenProvider;
}());
exports.default = AadTokenProvider;
//# sourceMappingURL=AadTokenProvider.js.map