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

permissions.mddocs/

Permission Management

React Hook-based permission system with standardized request/response patterns and automatic lifecycle management.

Capabilities

Permission Status and Response Types

Core types for representing permission states and responses.

/**
 * Enumeration of possible permission states
 */
enum PermissionStatus {
  /** User has granted the permission */
  GRANTED = 'granted',
  /** User hasn't granted or denied the permission yet */
  UNDETERMINED = 'undetermined',
  /** User has denied the permission */
  DENIED = 'denied',
}

/**
 * Permission expiration time - currently all permissions are permanent
 */
type PermissionExpiration = 'never' | number;

/**
 * Standard permission response object returned by get and request functions
 */
interface PermissionResponse {
  /** Current permission status */
  status: PermissionStatus;
  /** When the permission expires */
  expires: PermissionExpiration;
  /** Convenience boolean indicating if permission is granted */
  granted: boolean;
  /** Whether user can be asked again or should be directed to Settings */
  canAskAgain: boolean;
}

Usage Examples:

import { PermissionStatus, type PermissionResponse } from "expo-modules-core";

// Check permission status
function handlePermissionResponse(response: PermissionResponse) {
  switch (response.status) {
    case PermissionStatus.GRANTED:
      console.log("Permission granted!");
      // Proceed with protected functionality
      break;
      
    case PermissionStatus.DENIED:
      if (response.canAskAgain) {
        console.log("Permission denied, but can ask again");
        // Show explanation and request again
      } else {
        console.log("Permission permanently denied, direct to settings");
        // Guide user to system settings
      }
      break;
      
    case PermissionStatus.UNDETERMINED:
      console.log("Permission not yet requested");
      // First time, can request permission
      break;
  }
  
  // Use convenience property
  if (response.granted) {
    enableFeature();
  } else {
    showPermissionRequiredMessage();
  }
}

Permission Hook Creation

Factory function for creating permission hooks with standardized patterns.

/**
 * Creates a new permission hook with built-in permission methods
 * @param methods - Object containing request and get methods
 * @returns Hook function for managing permissions
 */
function createPermissionHook<Permission extends PermissionResponse, Options extends object>(
  methods: PermissionHookMethods<Permission, Options>
): (options?: PermissionHookOptions<Options>) => 
  [Permission | null, () => Promise<Permission>, () => Promise<Permission>];

/**
 * Permission methods required for creating a hook
 */
interface PermissionHookMethods<Permission extends PermissionResponse, Options = never> {
  /** Method that requests permission from user */
  requestMethod: (options?: Options) => Promise<Permission>;
  /** Method that only fetches current permission status */
  getMethod: (options?: Options) => Promise<Permission>;
}

/**
 * Options for controlling permission hook behavior
 */
interface PermissionHookOptions<Options extends object> extends Options {
  /** Automatically fetch permission status on mount */
  get?: boolean;
  /** Automatically request permission on mount */
  request?: boolean;
}

Usage Examples:

import { createPermissionHook, PermissionResponse, PermissionStatus } from "expo-modules-core";

// Define permission-specific response type
interface CameraPermissionResponse extends PermissionResponse {
  canAskAgain: boolean;
  // Additional camera-specific properties
}

// Define options for camera permission
interface CameraPermissionOptions {
  allowsEditing?: boolean;
  aspect?: [number, number];
}

// Create camera permission methods
const cameraPermissionMethods = {
  async requestMethod(options?: CameraPermissionOptions): Promise<CameraPermissionResponse> {
    // Implementation would call native camera permission request
    const nativeResponse = await requestCameraPermissionNative(options);
    return {
      status: nativeResponse.granted ? PermissionStatus.GRANTED : PermissionStatus.DENIED,
      expires: 'never',
      granted: nativeResponse.granted,
      canAskAgain: nativeResponse.canAskAgain,
    };
  },
  
  async getMethod(options?: CameraPermissionOptions): Promise<CameraPermissionResponse> {
    // Implementation would check current camera permission status
    const nativeResponse = await getCameraPermissionNative();
    return {
      status: nativeResponse.status,
      expires: 'never',
      granted: nativeResponse.granted,
      canAskAgain: nativeResponse.canAskAgain,
    };
  },
};

// Create the hook
const useCameraPermissions = createPermissionHook(cameraPermissionMethods);

// Use in component
function CameraComponent() {
  // Hook returns [status, requestPermission, getPermission]
  const [permission, requestPermission, getPermission] = useCameraPermissions({
    get: true, // Automatically fetch status on mount
    allowsEditing: true, // Custom option
  });
  
  const handleCameraPress = async () => {
    if (!permission?.granted) {
      const newPermission = await requestPermission();
      if (!newPermission.granted) {
        alert("Camera permission required");
        return;
      }
    }
    
    // Open camera
    openCamera();
  };
  
  if (permission === null) {
    return <div>Loading permissions...</div>;
  }
  
  return (
    <button onClick={handleCameraPress} disabled={!permission.granted}>
      {permission.granted ? "Open Camera" : "Grant Camera Permission"}
    </button>
  );
}

Advanced Permission Hook Usage

Examples of complex permission scenarios and patterns.

import { createPermissionHook, PermissionResponse } from "expo-modules-core";

// Location permission with multiple precision levels
interface LocationPermissionResponse extends PermissionResponse {
  accuracy: 'coarse' | 'fine' | 'none';
}

interface LocationPermissionOptions {
  accuracy?: 'coarse' | 'fine';
  backgroundLocation?: boolean;
}

const useLocationPermissions = createPermissionHook({
  async requestMethod(options?: LocationPermissionOptions): Promise<LocationPermissionResponse> {
    // Request location permission with specified accuracy
    const result = await requestLocationPermission(options);
    return result;
  },
  
  async getMethod(options?: LocationPermissionOptions): Promise<LocationPermissionResponse> {
    // Get current location permission status
    const result = await getLocationPermission();
    return result;
  },
});

// Multi-permission component
function LocationAwareComponent() {
  const [locationPermission, requestLocation] = useLocationPermissions({
    get: true,
    accuracy: 'fine',
  });
  
  const [backgroundPermission, requestBackground] = useLocationPermissions({
    backgroundLocation: true,
  });
  
  const handleEnableLocation = async () => {
    // Request foreground location first
    const foreground = await requestLocation();
    
    if (foreground.granted && foreground.accuracy === 'fine') {
      // Then request background location
      const background = await requestBackground();
      
      if (background.granted) {
        console.log("Both permissions granted");
        enableBackgroundTracking();
      }
    }
  };
  
  return (
    <div>
      <p>Foreground Location: {locationPermission?.status}</p>
      <p>Background Location: {backgroundPermission?.status}</p>
      <button onClick={handleEnableLocation}>
        Enable Location Tracking
      </button>
    </div>
  );
}

// Permission wrapper component
function withPermissions<T extends object>(
  WrappedComponent: React.ComponentType<T>,
  requiredPermissions: string[]
) {
  return function PermissionWrapper(props: T) {
    const [allGranted, setAllGranted] = useState(false);
    
    useEffect(() => {
      // Check all required permissions
      Promise.all(
        requiredPermissions.map(checkPermission)
      ).then(results => {
        setAllGranted(results.every(r => r.granted));
      });
    }, []);
    
    if (!allGranted) {
      return <PermissionGate permissions={requiredPermissions} />;
    }
    
    return <WrappedComponent {...props} />;
  };
}

// Usage
const LocationEnabledMap = withPermissions(MapComponent, ['location']);

Permission Management Best Practices

Guidelines for effective permission handling:

import { createPermissionHook, PermissionStatus } from "expo-modules-core";

// ✅ Good: Explain why permission is needed
function PermissionExplanation({ onGrant }: { onGrant: () => void }) {
  const [permission, requestPermission] = useCameraPermissions();
  
  const handleRequest = async () => {
    if (permission?.status === PermissionStatus.DENIED && !permission.canAskAgain) {
      // Guide to settings
      alert("Please enable camera access in Settings");
      return;
    }
    
    const result = await requestPermission();
    if (result.granted) {
      onGrant();
    }
  };
  
  return (
    <div>
      <h3>Camera Access Required</h3>
      <p>This app needs camera access to take photos for your profile.</p>
      <button onClick={handleRequest}>Grant Camera Access</button>
    </div>
  );
}

// ✅ Good: Handle all permission states
function PermissionAwareFeature() {
  const [permission, requestPermission] = useLocationPermissions({ get: true });
  
  const renderPermissionState = () => {
    if (!permission) {
      return <div>Checking permissions...</div>;
    }
    
    switch (permission.status) {
      case PermissionStatus.GRANTED:
        return <LocationFeature />;
        
      case PermissionStatus.UNDETERMINED:
        return (
          <button onClick={requestPermission}>
            Enable Location Services
          </button>
        );
        
      case PermissionStatus.DENIED:
        return permission.canAskAgain ? (
          <div>
            <p>Location access is needed for this feature.</p>
            <button onClick={requestPermission}>Try Again</button>
          </div>
        ) : (
          <div>
            <p>Please enable location access in Settings</p>
            <button onClick={openSettings}>Open Settings</button>
          </div>
        );
        
      default:
        return <div>Unknown permission state</div>;
    }
  };
  
  return <div>{renderPermissionState()}</div>;
}

// ❌ Bad: Not handling denied permissions
function BadPermissionComponent() {
  const [permission, requestPermission] = useCameraPermissions();
  
  // Only checks granted, ignores denied/undetermined
  return permission?.granted ? (
    <CameraView />
  ) : (
    <button onClick={requestPermission}>Enable Camera</button>
  );
}

Types

/**
 * Generic permission hook return type
 */
type PermissionHookReturn<Permission extends PermissionResponse> = [
  Permission | null,              // Current permission status
  () => Promise<Permission>,      // Request permission function
  () => Promise<Permission>       // Get current permission function
];

/**
 * Base permission response interface
 */
interface PermissionResponse {
  status: PermissionStatus;
  expires: PermissionExpiration;
  granted: boolean;
  canAskAgain: boolean;
}

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