UNPKG

@microcursor/node-axkit

Version:

Native macOS Accessibility API integration for Node.js

359 lines (357 loc) 14.2 kB
/** * Type definitions for the native Swift module. * * This interface defines the structure of the native Swift module that is loaded * and provides type safety for all exported functions and values. */ interface NativeModule { /** Lists all windows for a given application bundle ID */ listWindows: (bundleId: string) => Promise<string[]>; /** Focuses a specific window by bundle ID and window title */ focusWindow: (bundleId: string, windowTitle: string) => Promise<string>; /** Checks if accessibility permissions are granted */ checkAccessibilityPermission: () => boolean; /** Requests accessibility permissions by showing the system dialog */ requestAccessibilityPermission: () => boolean; /** Requests accessibility permissions and waits for them to be granted with a timeout */ awaitAccessibilityPermission: (timeout: number) => Promise<boolean>; } /** * Lists all windows for a given application bundle ID using macOS Accessibility API. * * This function queries the macOS Accessibility API to retrieve all windows * for the specified application. Requires accessibility permissions to be granted. * * @param bundleId - The bundle identifier of the application (e.g., "com.apple.Safari") * @returns Promise that resolves to an array of window titles * * @throws {Error} When accessibility permissions are not granted or app is not running * * @example * ```typescript * import { listWindows } from 'node-axkit'; * * // List Safari windows * const windows = await listWindows('com.apple.Safari'); * console.log(windows); // ["Google - Safari", "GitHub - Safari"] * * // List VS Code windows * const codeWindows = await listWindows('com.microsoft.VSCode'); * console.log(codeWindows); // ["index.ts — node-axkit", "README.md — node-axkit"] * ``` * * @remarks * **Prerequisites:** * - macOS Accessibility permissions must be granted * - Target application must be currently running * - Only works on macOS due to Accessibility API dependency * * **Common Bundle IDs:** * - Safari: `com.apple.Safari` * - Chrome: `com.google.Chrome` * - VS Code: `com.microsoft.VSCode` * - Finder: `com.apple.finder` * - Terminal: `com.apple.Terminal` */ declare const listWindows: (bundleId: string) => Promise<string[]>; /** * Focuses a specific window by bundle ID and window title using macOS Accessibility API. * * This function brings the specified window to the front and activates the application. * The window title matching is case-insensitive and requires an exact match. * * @param bundleId - The bundle identifier of the application * @param windowTitle - The exact title of the window to focus * @returns Promise that resolves to a success message * * @throws {Error} When accessibility permissions are not granted, app is not running, or window is not found * * @example * ```typescript * import { focusWindow } from 'node-axkit'; * * // Focus a specific Safari tab * await focusWindow('com.apple.Safari', 'Google - Safari'); * * // Focus a VS Code window * await focusWindow('com.microsoft.VSCode', 'index.ts — node-axkit'); * * // Error handling * try { * await focusWindow('com.apple.Safari', 'Non-existent Window'); * } catch (error) { * console.error('Failed to focus window:', error.message); * } * ``` * * @remarks * **Window Title Matching:** * - Requires exact title match (case-insensitive) * - Use `listWindows()` first to get exact window titles * - Window titles can change dynamically (e.g., web page titles in browsers) * * **Best Practices:** * - Always check accessibility permissions first with `checkAccessibilityPermission()` * - Use `listWindows()` to get current window titles before focusing * - Handle errors gracefully as window titles can change frequently */ declare const focusWindow: (bundleId: string, windowTitle: string) => Promise<string>; /** * Checks if accessibility permissions are granted for the current application. * * This function verifies whether the application has the necessary permissions * to use the macOS Accessibility API for window management operations. * * @returns Boolean indicating if accessibility permissions are granted * * @example * ```typescript * import { checkAccessibilityPermission } from 'node-axkit'; * * if (checkAccessibilityPermission()) { * console.log('✅ Accessibility permissions granted'); * // Safe to use listWindows() and focusWindow() * } else { * console.log('❌ Accessibility permissions required'); * console.log('Please grant permissions in System Preferences > Security & Privacy > Privacy > Accessibility'); * } * ``` * * @remarks * **Granting Permissions:** * 1. Open System Preferences > Security & Privacy * 2. Click the Privacy tab * 3. Select Accessibility from the left sidebar * 4. Click the lock icon to make changes (requires admin password) * 5. Add your Node.js application or Terminal to the list * 6. Ensure the checkbox is checked * * **Important Notes:** * - Permissions are required for both `listWindows()` and `focusWindow()` * - The application must be restarted after granting permissions * - This only works on macOS due to Accessibility API dependency */ declare const checkAccessibilityPermission: () => boolean; /** * Requests accessibility permissions by showing the system dialog. * * This function prompts the user to grant accessibility permissions by showing * the macOS System Preferences dialog. If permissions are already granted, * it returns true immediately without showing the dialog. * * @returns Boolean indicating if accessibility permissions are granted (after the request) * * @example * ```typescript * import { requestAccessibilityPermission, checkAccessibilityPermission } from 'node-axkit'; * * // Check if permissions are already granted * if (!checkAccessibilityPermission()) { * console.log('Requesting accessibility permissions...'); * * // Show system dialog to request permissions * const granted = requestAccessibilityPermission(); * * if (granted) { * console.log('✅ Accessibility permissions granted!'); * } else { * console.log('❌ Accessibility permissions denied'); * console.log('Please manually grant permissions in System Preferences'); * } * } * ``` * * @example * ```typescript * // Helper function to ensure permissions before using accessibility features * async function ensureAccessibilityPermissions(): Promise<boolean> { * if (checkAccessibilityPermission()) { * return true; * } * * console.log('This app requires accessibility permissions to manage windows.'); * console.log('A system dialog will appear - please click "Open System Preferences"'); * * const granted = requestAccessibilityPermission(); * * if (granted) { * console.log('Permissions granted! You can now use window management features.'); * return true; * } else { * console.error('Permissions denied. Please grant them manually:'); * console.error('System Preferences > Security & Privacy > Privacy > Accessibility'); * return false; * } * } * * // Usage * if (await ensureAccessibilityPermissions()) { * const windows = await listWindows('com.apple.Safari'); * // ... use accessibility features * } * ``` * * @remarks * **How it Works:** * - First checks if permissions are already granted * - If not granted, shows the system dialog automatically * - User can click "Open System Preferences" to grant permissions * - Returns the final permission state after the dialog interaction * * **Dialog Behavior:** * - Only shows dialog if permissions are not already granted * - Dialog appears once per application launch * - User must manually add the app to accessibility list in System Preferences * - Application may need to be restarted after granting permissions * * **Important Notes:** * - This is a one-time setup process for each application * - The system dialog provides a direct link to System Preferences * - Only works on macOS due to Accessibility API dependency * - Some applications may require a restart after permissions are granted */ declare const requestAccessibilityPermission: () => boolean; /** * Requests accessibility permissions and waits for them to be granted with a timeout. * * This is an async version of `requestAccessibilityPermission()` that shows the system dialog * and then waits for the user to actually grant permissions in System Preferences. * It polls for permission status until either granted or timeout is reached. * * @param timeout - Maximum time to wait for permissions in milliseconds (default: 30000ms = 30 seconds) * @returns Promise that resolves to true if permissions are granted, false if timeout reached * * @example * ```typescript * import { awaitAccessibilityPermission } from 'node-axkit'; * * // Basic usage with default 30-second timeout * const granted = await awaitAccessibilityPermission(); * if (granted) { * console.log('✅ Permissions granted!'); * } else { * console.log('❌ Timeout reached - permissions not granted'); * } * ``` * * @example * ```typescript * // Custom timeout (60 seconds = 60000ms) * const granted = await awaitAccessibilityPermission(60000); * if (granted) { * console.log('✅ Permissions granted within 60 seconds!'); * } else { * console.log('❌ User did not grant permissions within 60 seconds'); * } * ``` * * @example * ```typescript * // Complete workflow with restart handling * async function setupAccessibilityPermissions(): Promise<boolean> { * console.log('🔍 Checking accessibility permissions...'); * * if (checkAccessibilityPermission()) { * console.log('✅ Accessibility permissions already granted'); * return true; * } * * console.log('📋 Requesting accessibility permissions...'); * console.log('💡 A system dialog will appear. Please:'); * console.log(' 1. Click "Open System Preferences"'); * console.log(' 2. Add this app to the Accessibility list'); * console.log(' 3. Check the checkbox next to the app'); * console.log('⏳ Waiting for permissions (up to 45 seconds)...'); * * const granted = await awaitAccessibilityPermission(45000); * * if (granted) { * console.log('🎉 Accessibility permissions granted successfully!'); * return true; * } else { * console.log('⚠️ Permissions not detected within timeout period'); * console.log('📝 If you granted permissions in System Preferences:'); * console.log(' • This is normal macOS behavior'); * console.log(' • Please restart this application'); * console.log(' • Permissions will be detected on next launch'); * return false; * } * } * * // Usage with restart handling * if (await setupAccessibilityPermissions()) { * // Proceed with accessibility features * const windows = await listWindows('com.apple.Safari'); * } else { * console.log('Exiting - restart required to detect permissions'); * process.exit(1); * } * ``` * * @remarks * **How it Works:** * 1. First checks if permissions are already granted (returns immediately if true) * 2. Shows the system dialog with "Open System Preferences" option * 3. Polls permission status every 500ms until granted or timeout * 4. Returns `true` when permissions are granted, `false` on timeout * * **Timeout Behavior:** * - Default timeout: 30000ms (30 seconds) * - Minimum recommended: 15000ms (15 seconds) - gives user time to navigate * - Maximum practical: 120000ms (120 seconds) - avoid hanging too long * - Polls every 500ms to detect permission changes quickly * * **User Experience:** * - Shows system dialog immediately * - User clicks "Open System Preferences" to access settings * - User manually adds app to Accessibility list * - Function attempts to detect permission change * - **Important**: May require application restart to detect granted permissions * * **macOS Restart Behavior:** * - macOS accessibility permissions are often cached per-process * - After granting permissions, the same process may not detect them immediately * - This is normal macOS behavior, not a bug in this library * - The function will timeout and suggest restarting the application * - On restart, `checkAccessibilityPermission()` will return `true` * * **Best Practices:** * - Always provide user guidance about what to do in the dialog * - Use appropriate timeout values (30000-60000ms is usually sufficient) * - Handle timeout gracefully and inform users about potential restart need * - Consider showing a progress indicator during the wait * - Test the workflow: grant permissions → restart → verify functionality */ declare const awaitAccessibilityPermission: (timeout: number) => Promise<boolean>; /** * Default export containing all module functionality. * * This provides an alternative import style for users who prefer default imports. * * @example * ```typescript * import nodeAxkit from 'node-axkit'; * * // Accessibility API with async permission request * if (!nodeAxkit.checkAccessibilityPermission()) { * console.log('Requesting accessibility permissions...'); * // Wait up to 45 seconds (45000ms) for user to grant permissions * const granted = await nodeAxkit.awaitAccessibilityPermission(45000); * if (!granted) { * console.log('Accessibility permissions required'); * return; * } * } * * const windows = await nodeAxkit.listWindows('com.apple.Safari'); * await nodeAxkit.focusWindow('com.apple.Safari', windows[0]); * ``` */ declare const _default: { listWindows: (bundleId: string) => Promise<string[]>; focusWindow: (bundleId: string, windowTitle: string) => Promise<string>; checkAccessibilityPermission: () => boolean; requestAccessibilityPermission: () => boolean; awaitAccessibilityPermission: (timeout: number) => Promise<boolean>; }; export { type NativeModule, awaitAccessibilityPermission, checkAccessibilityPermission, _default as default, focusWindow, listWindows, requestAccessibilityPermission };