UNPKG

trainingpeaks-sdk

Version:
123 lines (122 loc) 4.78 kB
import { refreshAuthToken as refreshUserAuthToken } from '../../adapters/public-api/endpoints/users/v3/token/index.js'; import { hasRefreshCapability, isTokenExpired, refreshAuthToken as updateAuthToken, shouldRefreshToken, } from '../../domain/index.js'; const performTokenRefresh = async (currentToken, httpClient, sessionStorage, logger) => { try { logger.info('Starting token refresh', { currentTokenExpires: currentToken.expires, refreshToken: currentToken.refreshToken ? '***' : 'not_available', }); const refreshResponse = await refreshUserAuthToken(httpClient, { refreshToken: currentToken.refreshToken, }); if (!refreshResponse.success || !refreshResponse.data) { logger.error('Token refresh failed - invalid response', { success: refreshResponse.success, hasData: !!refreshResponse.data, error: refreshResponse.error, }); return null; } const tokenData = refreshResponse.data.token; const newToken = updateAuthToken(currentToken, tokenData.access_token, new Date(tokenData.expires), tokenData.refresh_token); logger.info('Token refresh successful', { oldExpires: currentToken.expires, newExpires: newToken.expires, tokenType: newToken.tokenType, }); const session = await sessionStorage.get(); if (session) { await sessionStorage.set({ ...session, token: newToken, }); logger.debug('Session updated with refreshed token'); } return newToken; } catch (error) { logger.error('Token refresh failed with error', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); return null; } }; const ensureValidToken = async (state, httpClient, sessionStorage, logger) => { try { const session = await sessionStorage.get(); if (!session?.token) { logger.debug('No token found in session'); return null; } const token = session.token; if (!shouldRefreshToken(token) && !isTokenExpired(token)) { return token; } logger.info('Token needs refresh', { isExpired: isTokenExpired(token), shouldRefresh: shouldRefreshToken(token), hasRefreshCapability: hasRefreshCapability(token), }); if (!hasRefreshCapability(token)) { logger.warn('Token cannot be refreshed - no refresh token available'); return null; } if (state.refreshPromise) { logger.debug('Refresh already in progress, waiting for completion'); return await state.refreshPromise; } const now = Date.now(); const currentCooldown = Math.min(30000 * Math.pow(2, state.failureCount), 300000); if (now - state.lastRefreshAttempt < currentCooldown) { logger.warn('Token refresh attempted too recently, skipping', { lastAttempt: state.lastRefreshAttempt, failureCount: state.failureCount, currentCooldown, cooldownRemaining: currentCooldown - (now - state.lastRefreshAttempt), }); return isTokenExpired(token) ? null : token; } state.refreshPromise = performTokenRefresh(token, httpClient, sessionStorage, logger); try { const refreshedToken = await state.refreshPromise; state.lastRefreshAttempt = now; if (refreshedToken) { state.failureCount = 0; } else { state.failureCount++; } return refreshedToken; } catch (error) { state.lastRefreshAttempt = now; state.failureCount++; throw error; } finally { state.refreshPromise = null; } } catch (error) { logger.error('Failed to ensure valid token', { error }); return null; } }; const resetTokenRefreshState = (state) => { state.refreshPromise = null; state.lastRefreshAttempt = 0; }; export const createTokenRefreshHandler = (httpClient, sessionStorage, logger) => { const state = { refreshPromise: null, lastRefreshAttempt: 0, failureCount: 0, REFRESH_COOLDOWN: 30000, MAX_BACKOFF: 300000, }; return { ensureValidToken: () => ensureValidToken(state, httpClient, sessionStorage, logger), reset: () => resetTokenRefreshState(state), }; };