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());
    });
};
function generateRandomString(length) {
    const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
    const values = crypto.getRandomValues(new Uint8Array(length));
    return Array.from(values)
        .map((v) => possible[v % possible.length])
        .join("");
}
function sha256(plain) {
    return __awaiter(this, void 0, void 0, function* () {
        const encoder = new TextEncoder();
        const data = encoder.encode(plain);
        const hash = yield crypto.subtle.digest("SHA-256", data);
        return base64urlEncode(hash);
    });
}
function base64urlEncode(buffer) {
    const bytes = new Uint8Array(buffer);
    let binary = "";
    for (let i = 0; i < bytes.length; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
export function isTokenExpired(tokenExpiry) {
    if (!tokenExpiry)
        return true;
    return Date.now() >= tokenExpiry - 60000;
}
export function startOAuth2Flow(config) {
    return __awaiter(this, void 0, void 0, function* () {
        const codeVerifier = generateRandomString(128);
        const codeChallenge = yield sha256(codeVerifier);
        const state = generateRandomString(32);
        const flowId = generateRandomString(16);
        sessionStorage.setItem(`oauth2_code_verifier_${flowId}`, codeVerifier);
        sessionStorage.setItem(`oauth2_state_${flowId}`, state);
        const authUrl = new URL(config.authorizationEndpoint);
        authUrl.searchParams.set("client_id", config.clientId);
        authUrl.searchParams.set("response_type", "code");
        authUrl.searchParams.set("redirect_uri", config.redirectUri);
        authUrl.searchParams.set("code_challenge", codeChallenge);
        authUrl.searchParams.set("code_challenge_method", "S256");
        authUrl.searchParams.set("state", `${state}:${flowId}`);
        if (config.scope) {
            authUrl.searchParams.set("scope", config.scope);
        }
        const width = 600;
        const height = 700;
        const left = window.screenX + (window.outerWidth - width) / 2;
        const top = window.screenY + (window.outerHeight - height) / 2;
        const popup = window.open(authUrl.toString(), "OAuth2 Authorization", `width=${width},height=${height},left=${left},top=${top},popup=yes,scrollbars=yes`);
        if (!popup) {
            throw new Error("Failed to open OAuth 2.0 authorization popup. Please allow popups for this site.");
        }
        return new Promise((resolve, reject) => {
            let flowCompleted = false;
            const cleanup = () => {
                clearInterval(checkInterval);
                window.removeEventListener("message", messageHandler);
                sessionStorage.removeItem(`oauth2_code_verifier_${flowId}`);
                sessionStorage.removeItem(`oauth2_state_${flowId}`);
            };
            const checkInterval = setInterval(() => {
                try {
                    if (popup.closed) {
                        if (!flowCompleted) {
                            flowCompleted = true;
                            cleanup();
                            reject(new Error("OAuth 2.0 authorization was cancelled"));
                        }
                        return;
                    }
                    try {
                        const popupUrl = popup.location.href;
                        if (popupUrl.startsWith(config.redirectUri) && !flowCompleted) {
                            flowCompleted = true;
                            cleanup();
                            popup.close();
                            const url = new URL(popupUrl);
                            const code = url.searchParams.get("code");
                            const returnedState = url.searchParams.get("state");
                            const error = url.searchParams.get("error");
                            const errorDescription = url.searchParams.get("error_description");
                            if (error) {
                                reject(new Error(`OAuth 2.0 error: ${error}${errorDescription ? " - " + errorDescription : ""}`));
                                return;
                            }
                            if (!code) {
                                reject(new Error("No authorization code received"));
                                return;
                            }
                            const expectedState = `${state}:${flowId}`;
                            if (returnedState !== expectedState) {
                                reject(new Error("State mismatch - possible CSRF attack"));
                                return;
                            }
                            exchangeCodeForToken(config, code, codeVerifier).then(resolve).catch(reject);
                        }
                    }
                    catch (e) {
                    }
                }
                catch (e) {
                }
            }, 500);
            const messageHandler = (event) => {
                if (event.data && event.data.type === "oauth2_callback" && !flowCompleted) {
                    flowCompleted = true;
                    cleanup();
                    if (popup && !popup.closed) {
                        popup.close();
                    }
                    const { code, state: returnedState, error, error_description } = event.data;
                    if (error) {
                        reject(new Error(`OAuth 2.0 error: ${error}${error_description ? " - " + error_description : ""}`));
                        return;
                    }
                    if (!code) {
                        reject(new Error("No authorization code received"));
                        return;
                    }
                    const expectedState = `${state}:${flowId}`;
                    if (returnedState !== expectedState) {
                        reject(new Error("State mismatch - possible CSRF attack"));
                        return;
                    }
                    exchangeCodeForToken(config, code, codeVerifier).then(resolve).catch(reject);
                }
            };
            window.addEventListener("message", messageHandler);
            setTimeout(() => {
                if (!flowCompleted) {
                    flowCompleted = true;
                    cleanup();
                    if (popup && !popup.closed) {
                        popup.close();
                    }
                    reject(new Error("OAuth 2.0 authorization timeout"));
                }
            }, 5 * 60 * 1000);
        });
    });
}
function exchangeCodeForToken(config, code, codeVerifier) {
    return __awaiter(this, void 0, void 0, function* () {
        const tokenUrl = config.tokenEndpoint;
        const body = new URLSearchParams();
        body.set("grant_type", "authorization_code");
        body.set("code", code);
        body.set("redirect_uri", config.redirectUri);
        body.set("client_id", config.clientId);
        body.set("code_verifier", codeVerifier);
        try {
            const response = yield fetch(tokenUrl, {
                method: "POST",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded",
                },
                body: body.toString(),
            });
            if (!response.ok) {
                const errorText = yield response.text();
                throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${errorText}`);
            }
            const tokenResponse = yield response.json();
            return tokenResponse;
        }
        catch (error) {
            throw error;
        }
    });
}
export function refreshOAuth2Token(config, refreshToken) {
    return __awaiter(this, void 0, void 0, function* () {
        const tokenUrl = config.tokenEndpoint;
        const body = new URLSearchParams();
        body.set("grant_type", "refresh_token");
        body.set("refresh_token", refreshToken);
        body.set("client_id", config.clientId);
        try {
            const response = yield fetch(tokenUrl, {
                method: "POST",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded",
                },
                body: body.toString(),
            });
            if (!response.ok) {
                const errorText = yield response.text();
                throw new Error(`Token refresh failed: ${response.status} ${response.statusText} - ${errorText}`);
            }
            const tokenResponse = yield response.json();
            return tokenResponse;
        }
        catch (error) {
            throw error;
        }
    });
}
export function calculateTokenExpiry(expiresIn) {
    if (!expiresIn)
        return undefined;
    return Date.now() + expiresIn * 1000;
}
//# sourceMappingURL=OAuth2Utils.js.map