"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JwtBridgedAuthService = void 0;
const XHR_1 = require("../../icc-api/api/XHR");
const LoginCredentials_1 = require("../../icc-api/model/LoginCredentials");
const utils_1 = require("../utils");
var XHRError = XHR_1.XHR.XHRError;
const JwtError_1 = require("./JwtError");
/**
 * Differs from JwtAuthService in that it can create new refresh tokens if the old one is expired
 */
class JwtBridgedAuthService {
    constructor(authApi, username, password, thirdPartyTokens = {}, initialJwt) {
        this.authApi = authApi;
        this.username = username;
        this.password = password;
        this.thirdPartyTokens = thirdPartyTokens;
        this._error = null;
        this._currentPromise = Promise.resolve({});
        this.jwtGetter = this.getIcureTokens;
        this._currentPromise = Promise.resolve(initialJwt !== null && initialJwt !== void 0 ? initialJwt : {});
    }
    get refreshToken() {
        return this._currentPromise.then((x) => x.refreshJwt);
    }
    getIcureTokens() {
        return this.getAuthHeaders().then(() => this._currentPromise.then(({ authJwt, refreshJwt }) => ({ token: authJwt, refreshToken: refreshJwt })));
    }
    getJWT() {
        return __awaiter(this, void 0, void 0, function* () {
            return this._currentPromise
                .then(({ authJwt, refreshJwt }) => ({ authJwt, refreshJwt }), (e) => {
                if (e instanceof JwtError_1.JwtError) {
                    const wrappedError = e.reason;
                    if (!!wrappedError.statusCode && wrappedError.statusCode >= 400 && wrappedError.statusCode < 500) {
                        return { authJwt: undefined, refreshJwt: undefined };
                    }
                    else {
                        return { authJwt: e.jwt, refreshJwt: e.refreshJwt };
                    }
                }
                else {
                    return { authJwt: undefined, refreshJwt: undefined };
                }
            })
                .then(({ authJwt, refreshJwt }) => {
                if (!authJwt || this._isJwtInvalidOrExpired(authJwt)) {
                    // If it does not have the JWT, tries to get it
                    // If the JWT is expired, tries to refresh it
                    this._currentPromise = this._refreshAuthJwt(refreshJwt).then((updatedTokens) => {
                        // If here the token is null,
                        // it goes in a suspension status
                        if (!updatedTokens.authJwt) {
                            throw new Error('Your iCure back-end version does not support JWT authentication');
                        }
                        return updatedTokens;
                    }, (e) => {
                        throw new JwtError_1.JwtError(authJwt, refreshJwt, 'There was an error while refreshing the token, please re-instantiate the API or try again.', e);
                    });
                }
                else if (!!this._error) {
                    throw this._error;
                }
                return this._currentPromise;
            })
                .then(({ authJwt }) => {
                return authJwt;
            });
        });
    }
    getAuthHeaders() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.getJWT().then((authJwt) => {
                return [new XHR_1.XHR.Header('Authorization', `Bearer ${authJwt}`)];
            });
        });
    }
    _refreshAuthJwt(refreshJwt) {
        return __awaiter(this, void 0, void 0, function* () {
            // If I do not have a refresh JWT or the refresh JWT is expired,
            // I have to log in again
            if (!refreshJwt || this._isJwtInvalidOrExpired(refreshJwt)) {
                return this._loginAndGetTokens();
            }
            else {
                return this.authApi.refreshAuthenticationJWT(refreshJwt).then((refreshResponse) => ({
                    authJwt: refreshResponse.token,
                    refreshJwt: refreshJwt,
                }));
            }
        });
    }
    _loginAndGetTokens() {
        return __awaiter(this, void 0, void 0, function* () {
            let authResponse;
            let firstError;
            if (this.username && this.password) {
                try {
                    authResponse = yield this.authApi.login(new LoginCredentials_1.LoginCredentials({
                        username: this.username,
                        password: this.password,
                    }));
                }
                catch (e) {
                    firstError = e;
                }
            }
            if (!authResponse) {
                authResponse = yield Object.entries(this.thirdPartyTokens).reduce((acc_1, _a) => __awaiter(this, [acc_1, _a], void 0, function* (acc, [thirdParty, token]) {
                    const prev = yield acc;
                    return (prev !== null && prev !== void 0 ? prev : (token
                        ? this.authApi.loginWithThirdPartyToken(thirdParty, token).catch((e) => {
                            if (!firstError) {
                                firstError = e;
                            }
                            return Promise.resolve();
                        })
                        : undefined));
                }), Promise.resolve());
            }
            if (!authResponse) {
                if (firstError)
                    throw firstError;
                throw new XHRError('', 'Unknown error', 401, 'Unauthorized', new Headers());
            }
            return {
                authJwt: authResponse.token,
                refreshJwt: authResponse.refreshToken,
            };
        });
    }
    _isJwtInvalidOrExpired(jwt) {
        const parts = jwt.split('.');
        if (parts.length !== 3) {
            return true;
        }
        const payload = this._base64Decode(parts[1]);
        // Using the 'exp' string is safe to use as it is part of the JWT RFC and cannot be modified by us.
        return !('exp' in payload) || payload['exp'] * 1000 < new Date().getTime() - 10000;
    }
    _base64Decode(encodedString) {
        return JSON.parse((0, utils_1.a2b)(encodedString));
    }
    invalidateHeader(error) {
        this._error = error;
    }
    isInErrorState() {
        return !!this._error;
    }
}
exports.JwtBridgedAuthService = JwtBridgedAuthService;
//# sourceMappingURL=JwtBridgedAuthService.js.map