All files / src/utils errorClassifier.ts

100% Statements 18/18
100% Branches 42/42
100% Functions 2/2
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105                              6x 27x 27x     27x                     10x               17x                       10x               7x           4x               3x                   6x             10x 3x       7x 3x 3x 1x       6x    
/**
 * Classifies errors to determine if provider should be reconnected
 */
 
export type ErrorType = 'auth' | 'network' | 'config' | 'unknown';
 
export interface ErrorClassification {
  type: ErrorType;
  shouldReconnect: boolean;
  message: string;
}
 
/**
 * Classify error and determine if reconnection should be attempted
 */
export function classifyError(error: unknown): ErrorClassification {
  const errorMessage = error instanceof Error ? error.message : String(error);
  const lowerMessage = errorMessage.toLowerCase();
 
  // Authentication/Authorization errors - do not reconnect
  if (
    lowerMessage.includes('unauthorized') ||
    lowerMessage.includes('401') ||
    lowerMessage.includes('forbidden') ||
    lowerMessage.includes('403') ||
    lowerMessage.includes('invalid token') ||
    lowerMessage.includes('authentication failed') ||
    lowerMessage.includes('access denied') ||
    lowerMessage.includes('bad credentials') ||
    lowerMessage.includes('token expired')
  ) {
    return {
      type: 'auth',
      shouldReconnect: false,
      message: `Authentication failed: ${errorMessage}`,
    };
  }
 
  // Network/Connection errors - can reconnect
  if (
    lowerMessage.includes('connection') ||
    lowerMessage.includes('timeout') ||
    lowerMessage.includes('network') ||
    lowerMessage.includes('econnrefused') ||
    lowerMessage.includes('enotfound') ||
    lowerMessage.includes('socket') ||
    lowerMessage.includes('dns') ||
    lowerMessage.includes('502') ||
    lowerMessage.includes('503') ||
    lowerMessage.includes('504')
  ) {
    return {
      type: 'network',
      shouldReconnect: true,
      message: `Network error: ${errorMessage}`,
    };
  }
 
  // Configuration errors - do not reconnect
  if (
    (lowerMessage.includes('not found') && lowerMessage.includes('404')) ||
    lowerMessage.includes('invalid configuration') ||
    lowerMessage.includes('missing parameter') ||
    (lowerMessage.includes('organization') && lowerMessage.includes('not found'))
  ) {
    return {
      type: 'config',
      shouldReconnect: false,
      message: `Configuration error: ${errorMessage}`,
    };
  }
 
  // Unknown errors - can try to reconnect
  return {
    type: 'unknown',
    shouldReconnect: true,
    message: `Unknown error: ${errorMessage}`,
  };
}
 
/**
 * Check if provider should be reconnected based on error count and time
 */
export function shouldAttemptReconnection(
  reconnectAttempts: number = 0,
  lastReconnectTime?: Date,
  maxAttempts: number = 3,
  cooldownMs: number = 30000, // 30 seconds
): boolean {
  // Too many attempts
  if (reconnectAttempts >= maxAttempts) {
    return false;
  }
 
  // Need to wait for cooldown period
  if (lastReconnectTime) {
    const timeSinceLastAttempt = Date.now() - lastReconnectTime.getTime();
    if (timeSinceLastAttempt < cooldownMs) {
      return false;
    }
  }
 
  return true;
}