managarten/manadeck/apps/mobile/utils/networkErrorUtils.ts
Till-JS e7f5f942f3 chore: initial commit - consolidate 4 projects into monorepo
Projects included:
- maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing)
- manacore (Expo mobile + SvelteKit web + Astro landing)
- manadeck (NestJS backend + Expo mobile + SvelteKit web)
- memoro (Expo mobile + SvelteKit web + Astro landing)

This commit preserves the current state before monorepo restructuring.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 23:38:24 +01:00

201 lines
6.4 KiB
TypeScript

import NetInfo from '@react-native-community/netinfo';
/**
* Utility functions for detecting and handling network errors
*/
interface NetworkErrorInfo {
isNetworkError: boolean;
errorType: 'offline' | 'timeout' | 'connection' | 'server' | 'unknown';
userMessage: string;
technicalMessage: string;
}
/**
* Checks if the device has internet connectivity using NetInfo
*/
export async function isDeviceConnected(): Promise<boolean> {
try {
const netInfo = await NetInfo.fetch();
// During network transitions, isInternetReachable can be null
// Only consider device online if both connected AND internet is explicitly reachable
// If isInternetReachable is null (unknown), consider it offline to prevent premature API calls
return netInfo.isConnected === true && netInfo.isInternetReachable === true;
} catch (error) {
console.debug('Error checking network connectivity:', error);
// Fallback to true to avoid blocking app functionality
return true;
}
}
/**
* Checks if the device has a stable internet connection
* Useful for critical operations like token refresh
*/
export async function hasStableConnection(): Promise<boolean> {
try {
// First check basic connectivity
const isConnected = await isDeviceConnected();
if (!isConnected) return false;
// For critical operations, double-check after a short delay
// This helps ensure the connection is stable, not just transitioning
await new Promise((resolve) => setTimeout(resolve, 500));
const netInfo = await NetInfo.fetch();
// Require explicit true values for stable connection
return (
netInfo.isConnected === true &&
netInfo.isInternetReachable === true &&
netInfo.details !== null
);
} catch (error) {
console.debug('Error checking stable connectivity:', error);
return false;
}
}
/**
* Synchronous version for immediate error analysis (pattern matching only)
*/
export function analyzeNetworkErrorSync(error: unknown): NetworkErrorInfo {
return analyzeErrorPatterns(error);
}
/**
* Async version with NetInfo connectivity check for enhanced detection
*/
export async function analyzeNetworkError(error: unknown): Promise<NetworkErrorInfo> {
// First, check actual device connectivity using NetInfo
const isConnected = await isDeviceConnected();
if (!isConnected) {
return {
isNetworkError: true,
errorType: 'offline',
userMessage: 'No internet connection detected. Please check your connection and try again.',
technicalMessage: 'NetInfo: Device is offline',
};
}
// Fall back to pattern matching
return analyzeErrorPatterns(error);
}
/**
* Internal function to analyze error patterns
*/
function analyzeErrorPatterns(error: unknown): NetworkErrorInfo {
const errorMessage = (error as any)?.message?.toLowerCase() || '';
const errorName = (error as any)?.name?.toLowerCase() || '';
// Offline/No internet connection errors
if (
errorMessage.includes('not connected to the internet') ||
errorMessage.includes('no internet connection') ||
errorMessage.includes('offline') ||
errorMessage.includes('internet connection appears to be offline') ||
errorMessage.includes('the internet connection appears to be offline')
) {
return {
isNetworkError: true,
errorType: 'offline',
userMessage: 'No internet connection detected. Please check your connection and try again.',
technicalMessage: (error as any).message || 'Device appears to be offline',
};
}
// Network connection errors
if (
errorMessage.includes('network') ||
errorMessage.includes('fetch') ||
errorMessage.includes('network request failed') ||
errorMessage.includes('network error') ||
errorName.includes('networkerror') ||
errorMessage.includes('failed to fetch') ||
errorMessage.includes('request failed') ||
errorMessage.includes('unable to connect')
) {
return {
isNetworkError: true,
errorType: 'connection',
userMessage: 'Unable to connect to server. Please check your internet connection and try again.',
technicalMessage: (error as any).message || 'Network connection failed',
};
}
// Timeout errors
if (
errorMessage.includes('timeout') ||
errorMessage.includes('timed out') ||
errorName.includes('timeout')
) {
return {
isNetworkError: true,
errorType: 'timeout',
userMessage: 'Request timed out. Please check your connection and try again.',
technicalMessage: (error as any).message || 'Request timeout',
};
}
// Connection refused/reset errors
if (
errorMessage.includes('connection refused') ||
errorMessage.includes('connection reset') ||
errorMessage.includes('econnrefused') ||
errorMessage.includes('econnreset')
) {
return {
isNetworkError: true,
errorType: 'connection',
userMessage: 'Unable to reach server. Please check your internet connection.',
technicalMessage: (error as any).message || 'Connection refused',
};
}
// Check for HTTP status-based network errors if it's a fetch response
if ((error as any)?.status || (error instanceof Error && 'status' in error)) {
const status = (error as any).status;
// Server errors that could be network-related
if (status >= 500) {
return {
isNetworkError: true,
errorType: 'server',
userMessage: 'Server is temporarily unavailable. Please try again in a moment.',
technicalMessage: `Server error: ${status}`,
};
}
// Gateway timeout, bad gateway, service unavailable
if (status === 502 || status === 503 || status === 504) {
return {
isNetworkError: true,
errorType: 'server',
userMessage: 'Service is temporarily unavailable. Please try again in a moment.',
technicalMessage: `Gateway error: ${status}`,
};
}
}
// DNS resolution errors
if (
errorMessage.includes('dns') ||
errorMessage.includes('getaddrinfo') ||
errorMessage.includes('enotfound')
) {
return {
isNetworkError: true,
errorType: 'connection',
userMessage: 'Unable to reach server. Please check your internet connection.',
technicalMessage: (error as any).message || 'DNS resolution failed',
};
}
// Not a recognized network error
return {
isNetworkError: false,
errorType: 'unknown',
userMessage: 'An unexpected error occurred. Please try again.',
technicalMessage: (error as any).message || 'Unknown error',
};
}