/*! @azure/msal-browser v2.30.0 2022-10-10 */
'use strict';
import { __extends, __awaiter, __generator, __rest, __assign } from '../_virtual/_tslib.js';
import { PerformanceEvents, TimeUtils, ScopeSet, ClientAuthError, IdTokenEntity, Constants, AccessTokenEntity, AuthenticationScheme, PopTokenGenerator, AccountEntity, AuthorityType, AuthToken, UrlString, OIDC_DEFAULT_SCOPES, PromptValue } from '@azure/msal-common';
import { BaseInteractionClient } from './BaseInteractionClient.js';
import { TemporaryCacheKeys, NativeExtensionMethod, NativeConstants, ApiId } from '../utils/BrowserConstants.js';
import { NativeAuthError } from '../error/NativeAuthError.js';
import { BrowserAuthError } from '../error/BrowserAuthError.js';
import { SilentCacheClient } from './SilentCacheClient.js';

/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
var NativeInteractionClient = /** @class */ (function (_super) {
    __extends(NativeInteractionClient, _super);
    function NativeInteractionClient(config, browserStorage, browserCrypto, logger, eventHandler, navigationClient, apiId, performanceClient, provider, accountId, nativeStorageImpl, correlationId) {
        var _this = _super.call(this, config, browserStorage, browserCrypto, logger, eventHandler, navigationClient, performanceClient, provider, correlationId) || this;
        _this.apiId = apiId;
        _this.accountId = accountId;
        _this.nativeMessageHandler = provider;
        _this.nativeStorageManager = nativeStorageImpl;
        _this.silentCacheClient = new SilentCacheClient(config, _this.nativeStorageManager, browserCrypto, logger, eventHandler, navigationClient, performanceClient, provider, correlationId);
        return _this;
    }
    /**
     * Acquire token from native platform via browser extension
     * @param request
     */
    NativeInteractionClient.prototype.acquireToken = function (request) {
        return __awaiter(this, void 0, void 0, function () {
            var nativeATMeasurement, reqTimestamp, nativeRequest, result, messageBody, response, validatedResponse;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.logger.trace("NativeInteractionClient - acquireToken called.");
                        nativeATMeasurement = this.performanceClient.startMeasurement(PerformanceEvents.NativeInteractionClientAcquireToken, request.correlationId);
                        reqTimestamp = TimeUtils.nowSeconds();
                        return [4 /*yield*/, this.initializeNativeRequest(request)];
                    case 1:
                        nativeRequest = _a.sent();
                        _a.label = 2;
                    case 2:
                        _a.trys.push([2, 4, , 5]);
                        return [4 /*yield*/, this.acquireTokensFromCache(this.accountId, nativeRequest)];
                    case 3:
                        result = _a.sent();
                        nativeATMeasurement.endMeasurement({
                            success: true,
                            isNativeBroker: true,
                            fromCache: true
                        });
                        return [2 /*return*/, result];
                    case 4:
                        _a.sent();
                        // continue with a native call for any and all errors
                        this.logger.info("MSAL internal Cache does not contain tokens, proceed to make a native call");
                        return [3 /*break*/, 5];
                    case 5:
                        messageBody = {
                            method: NativeExtensionMethod.GetToken,
                            request: nativeRequest
                        };
                        return [4 /*yield*/, this.nativeMessageHandler.sendMessage(messageBody)];
                    case 6:
                        response = _a.sent();
                        validatedResponse = this.validateNativeResponse(response);
                        return [2 /*return*/, this.handleNativeResponse(validatedResponse, nativeRequest, reqTimestamp)
                                .then(function (result) {
                                nativeATMeasurement.endMeasurement({
                                    success: true,
                                    isNativeBroker: true,
                                    requestId: result.requestId
                                });
                                return result;
                            })
                                .catch(function (error) {
                                nativeATMeasurement.endMeasurement({
                                    success: false,
                                    errorCode: error.errorCode,
                                    subErrorCode: error.subError,
                                    isNativeBroker: true
                                });
                                throw error;
                            })];
                }
            });
        });
    };
    /**
     * Creates silent flow request
     * @param request
     * @param cachedAccount
     * @returns CommonSilentFlowRequest
     */
    NativeInteractionClient.prototype.createSilentCacheRequest = function (request, cachedAccount) {
        return {
            authority: request.authority,
            correlationId: this.correlationId,
            scopes: ScopeSet.fromString(request.scope).asArray(),
            account: cachedAccount,
            forceRefresh: false,
        };
    };
    /**
     * Fetches the tokens from the cache if un-expired
     * @param nativeAccountId
     * @param request
     * @returns authenticationResult
     */
    NativeInteractionClient.prototype.acquireTokensFromCache = function (nativeAccountId, request) {
        return __awaiter(this, void 0, void 0, function () {
            var accountEntity, account, silentRequest, result, e_2;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        accountEntity = this.browserStorage.readAccountFromCacheWithNativeAccountId(nativeAccountId);
                        if (!accountEntity) {
                            throw ClientAuthError.createNoAccountFoundError();
                        }
                        account = accountEntity.getAccountInfo();
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        silentRequest = this.createSilentCacheRequest(request, account);
                        return [4 /*yield*/, this.silentCacheClient.acquireToken(silentRequest)];
                    case 2:
                        result = _a.sent();
                        return [2 /*return*/, result];
                    case 3:
                        e_2 = _a.sent();
                        throw e_2;
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Acquires a token from native platform then redirects to the redirectUri instead of returning the response
     * @param request
     */
    NativeInteractionClient.prototype.acquireTokenRedirect = function (request) {
        return __awaiter(this, void 0, void 0, function () {
            var nativeRequest, messageBody, response, e_3, navigationOptions, redirectUri;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.logger.trace("NativeInteractionClient - acquireTokenRedirect called.");
                        return [4 /*yield*/, this.initializeNativeRequest(request)];
                    case 1:
                        nativeRequest = _a.sent();
                        messageBody = {
                            method: NativeExtensionMethod.GetToken,
                            request: nativeRequest
                        };
                        _a.label = 2;
                    case 2:
                        _a.trys.push([2, 4, , 5]);
                        return [4 /*yield*/, this.nativeMessageHandler.sendMessage(messageBody)];
                    case 3:
                        response = _a.sent();
                        this.validateNativeResponse(response);
                        return [3 /*break*/, 5];
                    case 4:
                        e_3 = _a.sent();
                        // Only throw fatal errors here to allow application to fallback to regular redirect. Otherwise proceed and the error will be thrown in handleRedirectPromise
                        if (e_3 instanceof NativeAuthError && e_3.isFatal()) {
                            throw e_3;
                        }
                        return [3 /*break*/, 5];
                    case 5:
                        this.browserStorage.setTemporaryCache(TemporaryCacheKeys.NATIVE_REQUEST, JSON.stringify(nativeRequest), true);
                        navigationOptions = {
                            apiId: ApiId.acquireTokenRedirect,
                            timeout: this.config.system.redirectNavigationTimeout,
                            noHistory: false
                        };
                        redirectUri = this.config.auth.navigateToLoginRequestUrl ? window.location.href : this.getRedirectUri(request.redirectUri);
                        return [4 /*yield*/, this.navigationClient.navigateExternal(redirectUri, navigationOptions)];
                    case 6:
                        _a.sent(); // Need to treat this as external to ensure handleRedirectPromise is run again
                        return [2 /*return*/];
                }
            });
        });
    };
    /**
     * If the previous page called native platform for a token using redirect APIs, send the same request again and return the response
     */
    NativeInteractionClient.prototype.handleRedirectPromise = function () {
        return __awaiter(this, void 0, void 0, function () {
            var cachedRequest, messageBody, reqTimestamp, response, result, e_4;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.logger.trace("NativeInteractionClient - handleRedirectPromise called.");
                        if (!this.browserStorage.isInteractionInProgress(true)) {
                            this.logger.info("handleRedirectPromise called but there is no interaction in progress, returning null.");
                            return [2 /*return*/, null];
                        }
                        cachedRequest = this.browserStorage.getCachedNativeRequest();
                        if (!cachedRequest) {
                            this.logger.verbose("NativeInteractionClient - handleRedirectPromise called but there is no cached request, returning null.");
                            return [2 /*return*/, null];
                        }
                        this.browserStorage.removeItem(this.browserStorage.generateCacheKey(TemporaryCacheKeys.NATIVE_REQUEST));
                        messageBody = {
                            method: NativeExtensionMethod.GetToken,
                            request: cachedRequest
                        };
                        reqTimestamp = TimeUtils.nowSeconds();
                        _a.label = 1;
                    case 1:
                        _a.trys.push([1, 3, , 4]);
                        this.logger.verbose("NativeInteractionClient - handleRedirectPromise sending message to native broker.");
                        return [4 /*yield*/, this.nativeMessageHandler.sendMessage(messageBody)];
                    case 2:
                        response = _a.sent();
                        this.validateNativeResponse(response);
                        result = this.handleNativeResponse(response, cachedRequest, reqTimestamp);
                        this.browserStorage.setInteractionInProgress(false);
                        return [2 /*return*/, result];
                    case 3:
                        e_4 = _a.sent();
                        this.browserStorage.setInteractionInProgress(false);
                        throw e_4;
                    case 4: return [2 /*return*/];
                }
            });
        });
    };
    /**
     * Logout from native platform via browser extension
     * @param request
     */
    NativeInteractionClient.prototype.logout = function () {
        this.logger.trace("NativeInteractionClient - logout called.");
        return Promise.reject("Logout not implemented yet");
    };
    /**
     * Transform response from native platform into AuthenticationResult object which will be returned to the end user
     * @param response
     * @param request
     * @param reqTimestamp
     */
    NativeInteractionClient.prototype.handleNativeResponse = function (response, request, reqTimestamp) {
        return __awaiter(this, void 0, void 0, function () {
            var idTokenObj, authority, authorityPreferredCache, homeAccountIdentifier, accountEntity, responseScopes, accountProperties, uid, tid, responseAccessToken, responseTokenType, _a, popTokenGenerator, shrParameters, mats, result, idTokenEntity, expiresIn, tokenExpirationSeconds, accessTokenEntity;
            var _this = this;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        this.logger.trace("NativeInteractionClient - handleNativeResponse called.");
                        if (response.account.id !== request.accountId) {
                            // User switch in native broker prompt is not supported. All users must first sign in through web flow to ensure server state is in sync
                            throw NativeAuthError.createUserSwitchError();
                        }
                        idTokenObj = new AuthToken(response.id_token || Constants.EMPTY_STRING, this.browserCrypto);
                        return [4 /*yield*/, this.getDiscoveredAuthority(request.authority)];
                    case 1:
                        authority = _b.sent();
                        authorityPreferredCache = authority.getPreferredCache();
                        homeAccountIdentifier = AccountEntity.generateHomeAccountId(response.client_info || Constants.EMPTY_STRING, AuthorityType.Default, this.logger, this.browserCrypto, idTokenObj);
                        accountEntity = AccountEntity.createAccount(response.client_info, homeAccountIdentifier, idTokenObj, undefined, undefined, undefined, authorityPreferredCache, response.account.id);
                        this.browserStorage.setAccount(accountEntity);
                        responseScopes = response.scope ? ScopeSet.fromString(response.scope) : ScopeSet.fromString(request.scope);
                        accountProperties = response.account.properties || {};
                        uid = accountProperties["UID"] || idTokenObj.claims.oid || idTokenObj.claims.sub || Constants.EMPTY_STRING;
                        tid = accountProperties["TenantId"] || idTokenObj.claims.tid || Constants.EMPTY_STRING;
                        responseTokenType = AuthenticationScheme.BEARER;
                        _a = request.tokenType;
                        switch (_a) {
                            case AuthenticationScheme.POP: return [3 /*break*/, 2];
                        }
                        return [3 /*break*/, 4];
                    case 2:
                        // Set the token type to POP in the response
                        responseTokenType = AuthenticationScheme.POP;
                        // Check if native layer returned an SHR token
                        if (response.shr) {
                            this.logger.trace("handleNativeServerResponse: SHR is enabled in native layer");
                            responseAccessToken = response.shr;
                            return [3 /*break*/, 5];
                        }
                        popTokenGenerator = new PopTokenGenerator(this.browserCrypto);
                        shrParameters = {
                            resourceRequestMethod: request.resourceRequestMethod,
                            resourceRequestUri: request.resourceRequestUri,
                            shrClaims: request.shrClaims,
                            shrNonce: request.shrNonce
                        };
                        /**
                         * KeyID must be present in the native request from when the PoP key was generated in order for
                         * PopTokenGenerator to query the full key for signing
                         */
                        if (!request.keyId) {
                            throw ClientAuthError.createKeyIdMissingError();
                        }
                        return [4 /*yield*/, popTokenGenerator.signPopToken(response.access_token, request.keyId, shrParameters)];
                    case 3:
                        responseAccessToken = _b.sent();
                        return [3 /*break*/, 5];
                    case 4:
                        {
                            responseAccessToken = response.access_token;
                        }
                        _b.label = 5;
                    case 5:
                        mats = this.getMATSFromResponse(response);
                        result = {
                            authority: authority.canonicalAuthority,
                            uniqueId: uid,
                            tenantId: tid,
                            scopes: responseScopes.asArray(),
                            account: accountEntity.getAccountInfo(),
                            idToken: response.id_token,
                            idTokenClaims: idTokenObj.claims,
                            accessToken: responseAccessToken,
                            fromCache: mats ? this.isResponseFromCache(mats) : false,
                            expiresOn: new Date(Number(reqTimestamp + response.expires_in) * 1000),
                            tokenType: responseTokenType,
                            correlationId: this.correlationId,
                            state: response.state,
                            fromNativeBroker: true
                        };
                        idTokenEntity = IdTokenEntity.createIdTokenEntity(homeAccountIdentifier, request.authority, response.id_token || Constants.EMPTY_STRING, request.clientId, idTokenObj.claims.tid || Constants.EMPTY_STRING);
                        this.nativeStorageManager.setIdTokenCredential(idTokenEntity);
                        expiresIn = (responseTokenType === AuthenticationScheme.POP)
                            ? Constants.SHR_NONCE_VALIDITY
                            : (typeof response.expires_in === "string"
                                ? parseInt(response.expires_in, 10)
                                : response.expires_in) || 0;
                        tokenExpirationSeconds = reqTimestamp + expiresIn;
                        accessTokenEntity = AccessTokenEntity.createAccessTokenEntity(homeAccountIdentifier, request.authority, responseAccessToken, request.clientId, tid, responseScopes.printScopes(), tokenExpirationSeconds, 0, this.browserCrypto);
                        this.nativeStorageManager.setAccessTokenCredential(accessTokenEntity);
                        // Remove any existing cached tokens for this account in browser storage
                        this.browserStorage.removeAccountContext(accountEntity).catch(function (e) {
                            _this.logger.error("Error occurred while removing account context from browser storage. " + e);
                        });
                        return [2 /*return*/, result];
                }
            });
        });
    };
    /**
     * Validates native platform response before processing
     * @param response
     */
    NativeInteractionClient.prototype.validateNativeResponse = function (response) {
        if (response.hasOwnProperty("access_token") &&
            response.hasOwnProperty("id_token") &&
            response.hasOwnProperty("client_info") &&
            response.hasOwnProperty("account") &&
            response.hasOwnProperty("scope") &&
            response.hasOwnProperty("expires_in")) {
            return response;
        }
        else {
            throw NativeAuthError.createUnexpectedError("Response missing expected properties.");
        }
    };
    /**
     * Gets MATS telemetry from native response
     * @param response
     * @returns
     */
    NativeInteractionClient.prototype.getMATSFromResponse = function (response) {
        if (response.properties.MATS) {
            try {
                return JSON.parse(response.properties.MATS);
            }
            catch (e) {
                this.logger.error("NativeInteractionClient - Error parsing MATS telemetry, returning null instead");
            }
        }
        return null;
    };
    /**
     * Returns whether or not response came from native cache
     * @param response
     * @returns
     */
    NativeInteractionClient.prototype.isResponseFromCache = function (mats) {
        if (typeof mats.is_cached === "undefined") {
            this.logger.verbose("NativeInteractionClient - MATS telemetry does not contain field indicating if response was served from cache. Returning false.");
            return false;
        }
        return !!mats.is_cached;
    };
    /**
     * Translates developer provided request object into NativeRequest object
     * @param request
     */
    NativeInteractionClient.prototype.initializeNativeRequest = function (request) {
        return __awaiter(this, void 0, void 0, function () {
            var authority, canonicalAuthority, scopes, remainingProperties, scopeSet, getPrompt, validatedRequest, shrParameters, popTokenGenerator, reqCnfData;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.logger.trace("NativeInteractionClient - initializeNativeRequest called");
                        authority = request.authority || this.config.auth.authority;
                        canonicalAuthority = new UrlString(authority);
                        canonicalAuthority.validateAsUri();
                        scopes = request.scopes, remainingProperties = __rest(request, ["scopes"]);
                        scopeSet = new ScopeSet(scopes || []);
                        scopeSet.appendScopes(OIDC_DEFAULT_SCOPES);
                        getPrompt = function () {
                            // If request is silent, prompt is always none
                            switch (_this.apiId) {
                                case ApiId.ssoSilent:
                                case ApiId.acquireTokenSilent_silentFlow:
                                    _this.logger.trace("initializeNativeRequest: silent request sets prompt to none");
                                    return PromptValue.NONE;
                            }
                            // Prompt not provided, request may proceed and native broker decides if it needs to prompt
                            if (!request.prompt) {
                                _this.logger.trace("initializeNativeRequest: prompt was not provided");
                                return undefined;
                            }
                            // If request is interactive, check if prompt provided is allowed to go directly to native broker
                            switch (request.prompt) {
                                case PromptValue.NONE:
                                case PromptValue.CONSENT:
                                case PromptValue.LOGIN:
                                    _this.logger.trace("initializeNativeRequest: prompt is compatible with native flow");
                                    return request.prompt;
                                default:
                                    _this.logger.trace("initializeNativeRequest: prompt = " + request.prompt + " is not compatible with native flow");
                                    throw BrowserAuthError.createNativePromptParameterNotSupportedError();
                            }
                        };
                        validatedRequest = __assign(__assign({}, remainingProperties), { accountId: this.accountId, clientId: this.config.auth.clientId, authority: canonicalAuthority.urlString, scope: scopeSet.printScopes(), redirectUri: this.getRedirectUri(request.redirectUri), prompt: getPrompt(), correlationId: this.correlationId, tokenType: request.authenticationScheme, windowTitleSubstring: document.title, extraParameters: __assign(__assign(__assign({}, request.extraQueryParameters), request.tokenQueryParameters), { telemetry: NativeConstants.MATS_TELEMETRY }), extendedExpiryToken: false // Make this configurable?
                         });
                        if (!(request.authenticationScheme === AuthenticationScheme.POP)) return [3 /*break*/, 2];
                        shrParameters = {
                            resourceRequestUri: request.resourceRequestUri,
                            resourceRequestMethod: request.resourceRequestMethod,
                            shrClaims: request.shrClaims,
                            shrNonce: request.shrNonce
                        };
                        popTokenGenerator = new PopTokenGenerator(this.browserCrypto);
                        return [4 /*yield*/, popTokenGenerator.generateCnf(shrParameters)];
                    case 1:
                        reqCnfData = _a.sent();
                        // to reduce the URL length, it is recommended to send the hash of the req_cnf instead of the whole string
                        validatedRequest.reqCnf = reqCnfData.reqCnfHash;
                        validatedRequest.keyId = reqCnfData.kid;
                        _a.label = 2;
                    case 2: return [2 /*return*/, validatedRequest];
                }
            });
        });
    };
    return NativeInteractionClient;
}(BaseInteractionClient));

export { NativeInteractionClient };
//# sourceMappingURL=NativeInteractionClient.js.map
