"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resetHostedDemoTokenRefreshSingleFlightForTests = exports.runHostedDemoTokenRefreshSingleFlight = exports.refreshAccessTokenViaProxy = exports.shouldProactivelyRefreshAccessToken = exports.getJwtDisplayName = exports.getJwtSub = exports.getJwtSecondsUntilExpiry = exports.ACCESS_TOKEN_PROACTIVE_REFRESH_THRESHOLD_SECONDS = void 0;
const logger_1 = require("../utils/logger");
const log = (0, logger_1.createLogger)('AccessTokenRefresh');
/**
 * When the access JWT has fewer than this many seconds until `exp`, call the proxy refresh endpoint.
 * Matches desktop `checkAuth` behavior (300 seconds).
 */
exports.ACCESS_TOKEN_PROACTIVE_REFRESH_THRESHOLD_SECONDS = 300;
/**
 * Returns seconds until JWT `exp`, or null when the token cannot be decoded.
 */
const getJwtSecondsUntilExpiry = (accessToken) => {
    if (!accessToken) {
        return null;
    }
    try {
        const segment = accessToken.split('.')[1];
        if (!segment) {
            return null;
        }
        const normalized = segment.replace(/-/g, '+').replace(/_/g, '/');
        const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
        const decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
        if (typeof decoded.exp !== 'number') {
            return null;
        }
        const now = Math.floor(Date.now() / 1000);
        return decoded.exp - now;
    }
    catch {
        return null;
    }
};
exports.getJwtSecondsUntilExpiry = getJwtSecondsUntilExpiry;
/**
 * Returns the JWT `sub` claim (Supabase user id), or null when the token cannot
 * be decoded. No signature verification — callers use this only to recover the
 * canonical user id from a token we already trust locally.
 */
const getJwtSub = (accessToken) => {
    if (!accessToken) {
        return null;
    }
    try {
        const segment = accessToken.split('.')[1];
        if (!segment) {
            return null;
        }
        const normalized = segment.replace(/-/g, '+').replace(/_/g, '/');
        const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
        const decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
        return typeof decoded.sub === 'string' && decoded.sub.length > 0
            ? decoded.sub
            : null;
    }
    catch {
        return null;
    }
};
exports.getJwtSub = getJwtSub;
/**
 * Decodes the user's display name from a Supabase access token's payload. For
 * OAuth sign-ins Supabase copies the provider profile into `user_metadata`, so
 * Google logins carry `full_name`/`name` here (separate given/family names live
 * in identity_data, which isn't in the token). No signature verification — we
 * already trust the locally-stored token, same as {@link getJwtSub}.
 */
const getJwtDisplayName = (accessToken) => {
    if (!accessToken) {
        return null;
    }
    try {
        const segment = accessToken.split('.')[1];
        if (!segment) {
            return null;
        }
        const normalized = segment.replace(/-/g, '+').replace(/_/g, '/');
        const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
        const decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
        const candidate = decoded.user_metadata?.full_name ?? decoded.user_metadata?.name;
        return typeof candidate === 'string' && candidate.trim().length > 0
            ? candidate.trim()
            : null;
    }
    catch {
        return null;
    }
};
exports.getJwtDisplayName = getJwtDisplayName;
/**
 * Returns true when the access token should be refreshed before use (expired, unreadable, or within threshold).
 */
const shouldProactivelyRefreshAccessToken = (accessToken, thresholdSeconds = exports.ACCESS_TOKEN_PROACTIVE_REFRESH_THRESHOLD_SECONDS) => {
    const timeUntilExpiry = (0, exports.getJwtSecondsUntilExpiry)(accessToken);
    if (timeUntilExpiry === null) {
        return true;
    }
    return timeUntilExpiry < thresholdSeconds;
};
exports.shouldProactivelyRefreshAccessToken = shouldProactivelyRefreshAccessToken;
/**
 * Exchanges a refresh token for new credentials using the Rivet proxy `/api/auth/refresh` endpoint.
 */
const refreshAccessTokenViaProxy = async (params) => {
    const fetchFn = params.fetchImpl ?? fetch;
    const base = params.proxyUrl.replace(/\/$/, '');
    try {
        const response = await fetchFn(`${base}/api/auth/refresh`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ refreshToken: params.refreshToken }),
            signal: AbortSignal.timeout(10_000),
        });
        if (!response.ok) {
            log.warn('Proxy token refresh returned non-OK status', { status: response.status });
            return null;
        }
        const result = (await response.json());
        if (result.success && result.token) {
            return {
                token: result.token,
                refreshToken: result.refreshToken,
            };
        }
        return null;
    }
    catch (error) {
        log.warn('Proxy token refresh request failed', { error });
        return null;
    }
};
exports.refreshAccessTokenViaProxy = refreshAccessTokenViaProxy;
const hostedDemoRefreshInFlight = new Map();
/**
 * Runs at most one in-flight hosted-demo token refresh per session id.
 * Concurrent requests share the same promise so the proxy refresh endpoint is not hit in parallel with the same refresh token.
 */
const runHostedDemoTokenRefreshSingleFlight = (sessionId, refresh) => {
    const existing = hostedDemoRefreshInFlight.get(sessionId);
    if (existing) {
        return existing;
    }
    const started = refresh().finally(() => {
        hostedDemoRefreshInFlight.delete(sessionId);
    });
    hostedDemoRefreshInFlight.set(sessionId, started);
    return started;
};
exports.runHostedDemoTokenRefreshSingleFlight = runHostedDemoTokenRefreshSingleFlight;
/** Clears in-flight refresh state. Intended for tests only. */
const resetHostedDemoTokenRefreshSingleFlightForTests = () => {
    hostedDemoRefreshInFlight.clear();
};
exports.resetHostedDemoTokenRefreshSingleFlightForTests = resetHostedDemoTokenRefreshSingleFlightForTests;
//# sourceMappingURL=accessTokenRefresh.js.map