Add base kubernetes support final fix 4
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user