Logo
Back to insights
1/10/2024 12 min readLibrary Development

Apple.js: Building a macOS Controller Library with JavaScript

Saif
Saif ur RehmanNext-Gen Architect
Apple.js: Building a macOS Controller Library with JavaScript

Apple.js: Building a macOS Controller Library with JavaScript

Creating Apple.js was one of my most ambitious projects - building a comprehensive JavaScript library that allows developers to control macOS system functions directly from Node.js applications.

The Vision

The goal was to create a developer-friendly wrapper around Apple's osascript that would:

  • Provide intuitive JavaScript APIs for macOS control
  • Handle complex system interactions seamlessly
  • Offer comprehensive error handling and validation
  • Support both synchronous and asynchronous operations
  • Maintain cross-version compatibility

Core Architecture

1. Base Controller Class

class AppleController {
  constructor(options = {}) {
    this.options = {
      timeout: 30000,
      verbose: false,
      ...options
    };
    this.isAvailable = this.checkAvailability();
  }
  
  async checkAvailability() {
    try {
      await this.executeScript('return true');
      return true;
    } catch (error) {
      return false;
    }
  }
}

2. Script Execution Engine

async executeScript(script, options = {}) {
  const { timeout, verbose } = { ...this.options, ...options };
  
  return new Promise((resolve, reject) => {
    const process = spawn('osascript', ['-e', script], {
      timeout,
      stdio: verbose ? 'inherit' : 'pipe'
    });
    
    let output = '';
    let error = '';
    
    process.stdout.on('data', (data) => {
      output += data.toString();
    });
    
    process.stderr.on('data', (data) => {
      error += data.toString();
    });
    
    process.on('close', (code) => {
      if (code === 0) {
        resolve(this.parseOutput(output));
      } else {
        reject(new AppleScriptError(error, code));
      }
    });
  });
}

Advanced Features

1. System Information

class SystemInfo extends AppleController {
  async getSystemVersion() {
    const script = `
      tell application "System Events"
        return system version
      end tell
    `;
    
    return await this.executeScript(script);
  }
  
  async getHardwareInfo() {
    const script = `
      tell application "System Events"
        set hardwareInfo to {machine name, processor type, memory}
        return hardwareInfo
      end tell
    `;
    
    return await this.executeScript(script);
  }
}

2. Application Control

class ApplicationController extends AppleController {
  async launchApplication(appName) {
    const script = `
      tell application "${appName}"
        activate
        return true
      end tell
    `;
    
    return await this.executeScript(script);
  }
  
  async getRunningApplications() {
    const script = `
      tell application "System Events"
        return name of every process
      end tell
    `;
    
    return await this.executeScript(script);
  }
}

3. File System Operations

class FileSystemController extends AppleController {
  async selectFile(options = {}) {
    const { multiple = false, types = [] } = options;
    
    const script = `
      set fileTypes to {${types.map(t => `"${t}"`).join(', ')}}
      set selectedFiles to choose file ${multiple ? 'with multiple selections allowed' : ''} ${types.length ? 'of type fileTypes' : ''}
      return selectedFiles
    `;
    
    return await this.executeScript(script);
  }
  
  async showInFinder(filePath) {
    const script = `
      tell application "Finder"
        reveal POSIX file "${filePath}"
        activate
      end tell
    `;
    
    return await this.executeScript(script);
  }
}

Error Handling & Validation

1. Custom Error Classes

class AppleScriptError extends Error {
  constructor(message, code, script) {
    super(message);
    this.name = 'AppleScriptError';
    this.code = code;
    this.script = script;
    this.timestamp = new Date().toISOString();
  }
}

class UnsupportedOperationError extends Error {
  constructor(operation, version) {
    super(`Operation '${operation}' not supported on macOS ${version}`);
    this.name = 'UnsupportedOperationError';
    this.operation = operation;
    this.version = version;
  }
}

2. Input Validation

validateInput(input, type, required = true) {
  if (required && (input === undefined || input === null)) {
    throw new ValidationError(`${type} is required`);
  }
  
  switch (type) {
    case 'string':
      if (typeof input !== 'string') {
        throw new ValidationError(`Expected string, got ${typeof input}`);
      }
      break;
    case 'array':
      if (!Array.isArray(input)) {
        throw new ValidationError(`Expected array, got ${typeof input}`);
      }
      break;
    // ... more validations
  }
}

Performance Optimizations

1. Connection Pooling

class ConnectionPool {
  constructor(maxConnections = 5) {
    this.maxConnections = maxConnections;
    this.connections = [];
    this.waitingQueue = [];
  }
  
  async getConnection() {
    if (this.connections.length < this.maxConnections) {
      const connection = new AppleController();
      this.connections.push(connection);
      return connection;
    }
    
    return new Promise((resolve) => {
      this.waitingQueue.push(resolve);
    });
  }
}

2. Caching System

class CacheManager {
  constructor(ttl = 300000) { // 5 minutes default
    this.cache = new Map();
    this.ttl = ttl;
  }
  
  set(key, value) {
    this.cache.set(key, {
      value,
      timestamp: Date.now()
    });
  }
  
  get(key) {
    const item = this.cache.get(key);
    if (!item) return null;
    
    if (Date.now() - item.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }
    
    return item.value;
  }
}

Real-World Usage Examples

1. Automated Screenshot Tool

const apple = new AppleController();
const screenshot = new ScreenshotController(apple);

// Take screenshot of specific area
const image = await screenshot.captureArea({
  x: 100,
  y: 100,
  width: 800,
  height: 600
});

// Save with timestamp
await screenshot.save(image, `screenshot-${Date.now()}.png`);

2. System Monitor

const monitor = new SystemMonitor(apple);

// Monitor system resources
setInterval(async () => {
  const stats = await monitor.getSystemStats();
  console.log(`CPU: ${stats.cpu}%, Memory: ${stats.memory}%`);
}, 5000);

Publishing & Distribution

1. NPM Package Structure

{
  "name": "apple-js",
  "version": "1.0.0",
  "description": "JavaScript library for controlling macOS",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "test": "jest",
    "publish": "npm publish"
  }
}

2. TypeScript Support

interface AppleControllerOptions {
  timeout?: number;
  verbose?: boolean;
}

interface SystemInfo {
  version: string;
  build: string;
  architecture: string;
}

class AppleController {
  constructor(options?: AppleControllerOptions);
  async executeScript(script: string): Promise;
  async getSystemInfo(): Promise;
}

Results & Impact

Apple.js has been:

  • Downloaded 10,000+ times from NPM
  • Used in 50+ projects across different domains
  • Featured in developer communities for its innovative approach
  • Contributed to by developers worldwide

Key Learnings

  1. Native Integration requires deep understanding of system APIs
  2. Error Handling is crucial for system-level operations
  3. Performance matters when dealing with system calls
  4. Documentation is essential for complex libraries
  5. Community Feedback drives continuous improvement

Apple.js represents the power of JavaScript beyond web development, enabling developers to create sophisticated desktop applications with familiar tools and patterns.

Technical Discussion

Have insights to share or a project that needs this level of engineering? Let's connect.

Read Next