CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-expo-modules-core

The core infrastructure for Expo Modules architecture enabling seamless integration between React Native applications and native platform code.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

error-handling.mddocs/

Error Handling

Standardized error classes with error codes for consistent error handling across Expo modules.

Capabilities

CodedError Class

General-purpose error class with error codes for categorizing and handling different types of errors.

/**
 * A general error class that should be used for all errors in Expo modules
 * Guarantees a code field for error differentiation without subclassing
 */
class CodedError extends Error {
  /** Error code for programmatic error identification */
  code: string;
  /** Additional error information */
  info?: any;
  
  /**
   * Creates a new coded error
   * @param code - Unique error code for this error type
   * @param message - Human-readable error message
   */
  constructor(code: string, message: string);
}

Usage Examples:

import { CodedError } from "expo-modules-core";

// Create custom error types using CodedError
function validateUserInput(input: string): void {
  if (!input) {
    throw new CodedError("VALIDATION_ERROR", "Input cannot be empty");
  }
  
  if (input.length < 3) {
    throw new CodedError("VALIDATION_ERROR", "Input must be at least 3 characters");
  }
  
  if (!/^[a-zA-Z0-9]+$/.test(input)) {
    throw new CodedError("INVALID_FORMAT", "Input contains invalid characters");
  }
}

// Handle errors by code
try {
  validateUserInput("ab");
} catch (error) {
  if (error instanceof CodedError) {
    switch (error.code) {
      case "VALIDATION_ERROR":
        console.log("Validation failed:", error.message);
        break;
      case "INVALID_FORMAT":
        console.log("Format error:", error.message);
        break;
      default:
        console.log("Unknown error:", error.message);
    }
  }
}

// Add additional error information
class NetworkError extends CodedError {
  constructor(message: string, statusCode: number, url: string) {
    super("NETWORK_ERROR", message);
    this.info = { statusCode, url };
  }
}

// Usage with additional info
try {
  throw new NetworkError("Request failed", 404, "https://api.example.com/users");
} catch (error) {
  if (error instanceof CodedError && error.code === "NETWORK_ERROR") {
    console.log(`Network error ${error.info.statusCode}: ${error.message}`);
    console.log(`Failed URL: ${error.info.url}`);
  }
}

UnavailabilityError Class

Specialized error for handling unavailable platform functionality with automatic platform detection.

/**
 * Error thrown when a property is accessed that is unavailable, unsupported,
 * or not currently implemented on the running platform
 */
class UnavailabilityError extends CodedError {
  /**
   * Creates an unavailability error with platform-specific message
   * @param moduleName - Name of the module containing the unavailable functionality
   * @param propertyName - Name of the unavailable property or method
   */
  constructor(moduleName: string, propertyName: string);
}

Usage Examples:

import { UnavailabilityError, Platform } from "expo-modules-core";

// Throw unavailability errors for unsupported functionality
class LocationModule {
  getCurrentPosition(): Promise<Location> {
    if (Platform.OS === 'web' && !navigator.geolocation) {
      throw new UnavailabilityError("LocationModule", "getCurrentPosition");
    }
    
    // Implementation for supported platforms
    return this.nativeGetCurrentPosition();
  }
  
  startBackgroundUpdates(): void {
    if (Platform.OS === 'web') {
      throw new UnavailabilityError("LocationModule", "startBackgroundUpdates");
    }
    
    // Implementation for native platforms only
    this.nativeStartBackgroundUpdates();
  }
}

// Handle unavailability errors
const locationModule = new LocationModule();

try {
  await locationModule.getCurrentPosition();
} catch (error) {
  if (error instanceof UnavailabilityError) {
    console.log("Feature not available on this platform:", error.message);
    // Provide alternative or disable feature
    showUnavailableFeatureMessage();
  } else {
    console.error("Unexpected error:", error);
  }
}

// Check for specific unavailability
function isFeatureUnavailable(error: any): boolean {
  return error instanceof UnavailabilityError || 
         (error instanceof CodedError && error.code === "ERR_UNAVAILABLE");
}

// Graceful degradation pattern
async function getLocationWithFallback(): Promise<Location | null> {
  try {
    return await locationModule.getCurrentPosition();
  } catch (error) {
    if (isFeatureUnavailable(error)) {
      console.log("Location unavailable, using IP-based location");
      return await getIPLocation();
    }
    throw error; // Re-throw if not unavailability error
  }
}

Error Handling Patterns

Common patterns for handling Expo module errors effectively.

import { CodedError, UnavailabilityError } from "expo-modules-core";

// Comprehensive error handling utility
class ErrorHandler {
  static handle(error: any, context: string): void {
    if (error instanceof UnavailabilityError) {
      console.warn(`Feature unavailable in ${context}:`, error.message);
      this.showUnavailableFeatureDialog(context);
    } else if (error instanceof CodedError) {
      console.error(`Coded error in ${context}:`, error.code, error.message);
      this.handleCodedError(error, context);
    } else if (error instanceof Error) {
      console.error(`General error in ${context}:`, error.message);
      this.handleGeneralError(error, context);
    } else {
      console.error(`Unknown error in ${context}:`, error);
      this.handleUnknownError(error, context);
    }
  }
  
  private static handleCodedError(error: CodedError, context: string): void {
    switch (error.code) {
      case "PERMISSION_DENIED":
        this.showPermissionDialog();
        break;
      case "NETWORK_ERROR":
        this.showNetworkErrorDialog();
        break;
      case "VALIDATION_ERROR":
        this.showValidationErrorDialog(error.message);
        break;
      default:
        this.showGenericErrorDialog(error.message);
    }
  }
  
  private static showUnavailableFeatureDialog(context: string): void {
    // Implementation for showing unavailable feature dialog
  }
  
  private static showPermissionDialog(): void {
    // Implementation for permission error dialog
  }
  
  private static showNetworkErrorDialog(): void {
    // Implementation for network error dialog
  }
  
  private static showValidationErrorDialog(message: string): void {
    // Implementation for validation error dialog
  }
  
  private static showGenericErrorDialog(message: string): void {
    // Implementation for generic error dialog
  }
  
  private static handleGeneralError(error: Error, context: string): void {
    // Implementation for general error handling
  }
  
  private static handleUnknownError(error: any, context: string): void {
    // Implementation for unknown error handling
  }
}

// Module-specific error handling
class CameraModule {
  private static throwError(code: string, message: string, additionalInfo?: any): never {
    const error = new CodedError(code, message);
    if (additionalInfo) {
      error.info = additionalInfo;
    }
    throw error;
  }
  
  async takePicture(): Promise<Photo> {
    try {
      // Check permissions first
      const permission = await this.checkPermissions();
      if (!permission.granted) {
        this.throwError(
          "PERMISSION_DENIED", 
          "Camera permission not granted",
          { permission }
        );
      }
      
      // Check camera availability
      if (!await this.isCameraAvailable()) {
        throw new UnavailabilityError("CameraModule", "takePicture");
      }
      
      return await this.nativeTakePicture();
    } catch (error) {
      if (error instanceof CodedError || error instanceof UnavailabilityError) {
        throw error; // Re-throw known errors
      }
      
      // Wrap unknown errors
      this.throwError(
        "CAMERA_ERROR",
        `Failed to take picture: ${error.message}`,
        { originalError: error }
      );
    }
  }
}

// Usage with comprehensive error handling
async function handleCameraCapture() {
  const camera = new CameraModule();
  
  try {
    const photo = await camera.takePicture();
    console.log("Photo captured:", photo);
    return photo;
  } catch (error) {
    ErrorHandler.handle(error, "Camera Capture");
    return null;
  }
}

Custom Error Types

Creating domain-specific error types using CodedError as a base.

import { CodedError } from "expo-modules-core";

// Authentication errors
class AuthenticationError extends CodedError {
  constructor(message: string, reason?: string) {
    super("AUTH_ERROR", message);
    if (reason) {
      this.info = { reason };
    }
  }
}

class TokenExpiredError extends AuthenticationError {
  constructor(expiryTime: number) {
    super("Token has expired", "TOKEN_EXPIRED");
    this.info = { expiryTime, expiredAt: new Date(expiryTime) };
  }
}

// Storage errors
class StorageError extends CodedError {
  constructor(operation: string, key: string, reason: string) {
    super("STORAGE_ERROR", `Storage ${operation} failed for key "${key}": ${reason}`);
    this.info = { operation, key, reason };
  }
}

// Network errors with retry information
class NetworkError extends CodedError {
  constructor(
    message: string, 
    statusCode?: number, 
    retryable: boolean = false
  ) {
    super("NETWORK_ERROR", message);
    this.info = { statusCode, retryable, timestamp: Date.now() };
  }
  
  isRetryable(): boolean {
    return this.info?.retryable === true;
  }
}

// Usage of custom error types
class UserService {
  async getUser(userId: string): Promise<User> {
    try {
      const token = await this.getAuthToken();
      const response = await fetch(`/api/users/${userId}`, {
        headers: { Authorization: `Bearer ${token}` }
      });
      
      if (response.status === 401) {
        throw new AuthenticationError("Invalid or expired token");
      }
      
      if (response.status === 403) {
        throw new AuthenticationError("Insufficient permissions", "FORBIDDEN");
      }
      
      if (!response.ok) {
        const retryable = response.status >= 500;
        throw new NetworkError(
          `Request failed with status ${response.status}`,
          response.status,
          retryable
        );
      }
      
      return await response.json();
    } catch (error) {
      if (error instanceof CodedError) {
        throw error; // Re-throw our custom errors
      }
      
      // Wrap unexpected errors
      throw new NetworkError(`Unexpected error: ${error.message}`);
    }
  }
  
  private async getAuthToken(): Promise<string> {
    try {
      const token = await this.storage.getItem("auth_token");
      if (!token) {
        throw new AuthenticationError("No authentication token found");
      }
      
      const payload = JSON.parse(atob(token.split('.')[1]));
      if (payload.exp * 1000 < Date.now()) {
        throw new TokenExpiredError(payload.exp * 1000);
      }
      
      return token;
    } catch (error) {
      if (error instanceof AuthenticationError) {
        throw error;
      }
      
      throw new StorageError("get", "auth_token", error.message);
    }
  }
}

// Error handling with custom types
async function handleUserFetch(userId: string) {
  const userService = new UserService();
  
  try {
    const user = await userService.getUser(userId);
    return user;
  } catch (error) {
    if (error instanceof TokenExpiredError) {
      console.log("Token expired at:", error.info.expiredAt);
      // Redirect to login
      return null;
    } else if (error instanceof AuthenticationError) {
      console.log("Auth error:", error.message);
      // Handle authentication failure
      return null;
    } else if (error instanceof NetworkError && error.isRetryable()) {
      console.log("Retryable network error, will retry...");
      // Implement retry logic
      return null;
    } else if (error instanceof StorageError) {
      console.log("Storage error:", error.info);
      // Handle storage issues
      return null;
    } else {
      console.error("Unexpected error:", error);
      throw error;
    }
  }
}

Types

/**
 * Base coded error interface
 */
interface CodedError extends Error {
  code: string;
  info?: any;
}

/**
 * Unavailability error interface
 */
interface UnavailabilityError extends CodedError {
  code: 'ERR_UNAVAILABLE';
}

docs

app-utilities.md

error-handling.md

event-communication.md

index.md

native-modules.md

native-views.md

permissions.md

platform-utilities.md

shared-memory.md

uuid-generation.md

tile.json