Add base kubernetes support final fix 4

This commit is contained in:
Urtzi Alfaro
2025-09-29 07:54:25 +02:00
parent 57f77638cc
commit 4777e59e7a
14 changed files with 1041 additions and 167 deletions

View File

@@ -91,7 +91,27 @@ class ApiClient {
// Response interceptor for error handling and automatic token refresh
this.client.interceptors.response.use(
(response) => response,
(response) => {
// Enhanced logging for token refresh header detection
const refreshSuggested = response.headers['x-token-refresh-suggested'];
if (refreshSuggested) {
console.log('🔍 TOKEN REFRESH HEADER DETECTED:', {
url: response.config?.url,
method: response.config?.method,
status: response.status,
refreshSuggested,
hasRefreshToken: !!this.refreshToken,
currentTokenLength: this.authToken?.length || 0
});
}
// Check if server suggests token refresh
if (refreshSuggested === 'true' && this.refreshToken) {
console.log('🔄 Server suggests token refresh - refreshing proactively');
this.proactiveTokenRefresh();
}
return response;
},
async (error) => {
const originalRequest = error.config;
@@ -228,6 +248,40 @@ class ApiClient {
}
}
private async proactiveTokenRefresh() {
// Avoid multiple simultaneous proactive refreshes
if (this.isRefreshing) {
return;
}
try {
this.isRefreshing = true;
console.log('🔄 Proactively refreshing token...');
const response = await this.client.post('/auth/refresh', {
refresh_token: this.refreshToken
});
const { access_token, refresh_token } = response.data;
// Update tokens
this.setAuthToken(access_token);
if (refresh_token) {
this.setRefreshToken(refresh_token);
}
// Update auth store
await this.updateAuthStore(access_token, refresh_token);
console.log('✅ Proactive token refresh successful');
} catch (error) {
console.warn('⚠️ Proactive token refresh failed:', error);
// Don't handle as auth failure here - let the next 401 handle it
} finally {
this.isRefreshing = false;
}
}
private async handleAuthFailure() {
try {
// Clear tokens
@@ -275,6 +329,117 @@ class ApiClient {
return this.tenantId;
}
// Token synchronization methods for WebSocket connections
getCurrentValidToken(): string | null {
return this.authToken;
}
async ensureValidToken(): Promise<string | null> {
const originalToken = this.authToken;
const originalTokenShort = originalToken ? `${originalToken.slice(0, 20)}...${originalToken.slice(-10)}` : 'null';
console.log('🔍 ensureValidToken() called:', {
hasToken: !!this.authToken,
tokenPreview: originalTokenShort,
isRefreshing: this.isRefreshing,
hasRefreshToken: !!this.refreshToken
});
// If we have a valid token, return it
if (this.authToken && !this.isTokenNearExpiry(this.authToken)) {
const expiryInfo = this.getTokenExpiryInfo(this.authToken);
console.log('✅ Token is valid, returning current token:', {
tokenPreview: originalTokenShort,
expiryInfo
});
return this.authToken;
}
// If token is near expiry or expired, try to refresh
if (this.refreshToken && !this.isRefreshing) {
console.log('🔄 Token needs refresh, attempting proactive refresh:', {
reason: this.authToken ? 'near expiry' : 'no token',
expiryInfo: this.authToken ? this.getTokenExpiryInfo(this.authToken) : 'N/A'
});
try {
await this.proactiveTokenRefresh();
const newTokenShort = this.authToken ? `${this.authToken.slice(0, 20)}...${this.authToken.slice(-10)}` : 'null';
const tokenChanged = originalToken !== this.authToken;
console.log('✅ Token refresh completed:', {
tokenChanged,
oldTokenPreview: originalTokenShort,
newTokenPreview: newTokenShort,
newExpiryInfo: this.authToken ? this.getTokenExpiryInfo(this.authToken) : 'N/A'
});
return this.authToken;
} catch (error) {
console.warn('❌ Failed to refresh token in ensureValidToken:', error);
return null;
}
}
console.log('⚠️ Returning current token without refresh:', {
reason: this.isRefreshing ? 'already refreshing' : 'no refresh token',
tokenPreview: originalTokenShort
});
return this.authToken;
}
private getTokenExpiryInfo(token: string): any {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
const exp = payload.exp;
const iat = payload.iat;
if (!exp) return { error: 'No expiry in token' };
const now = Math.floor(Date.now() / 1000);
const timeUntilExpiry = exp - now;
const tokenLifetime = exp - iat;
return {
issuedAt: new Date(iat * 1000).toISOString(),
expiresAt: new Date(exp * 1000).toISOString(),
lifetimeMinutes: Math.floor(tokenLifetime / 60),
secondsUntilExpiry: timeUntilExpiry,
minutesUntilExpiry: Math.floor(timeUntilExpiry / 60),
isNearExpiry: timeUntilExpiry < 300,
isExpired: timeUntilExpiry <= 0
};
} catch (error) {
return { error: 'Failed to parse token', details: error };
}
}
private isTokenNearExpiry(token: string): boolean {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
const exp = payload.exp;
if (!exp) return false;
const now = Math.floor(Date.now() / 1000);
const timeUntilExpiry = exp - now;
// Consider token near expiry if less than 5 minutes remaining
const isNear = timeUntilExpiry < 300;
if (isNear) {
console.log('⏰ Token is near expiry:', {
secondsUntilExpiry: timeUntilExpiry,
minutesUntilExpiry: Math.floor(timeUntilExpiry / 60),
expiresAt: new Date(exp * 1000).toISOString()
});
}
return isNear;
} catch (error) {
console.warn('Failed to parse token for expiry check:', error);
return true; // Assume expired if we can't parse
}
}
// HTTP Methods - Return direct data for React Query
async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response: AxiosResponse<T> = await this.client.get(url, config);