Skip to content

iamEvanYT/osa-bridge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OSA Bridge

A Node.js native addon for handling Apple Events (OSA - Open Scripting Architecture) on macOS with cross-platform compatibility.

Features

  • 🍎 Apple Events support on macOS using Carbon framework
  • 🌐 Cross-platform compatible - graceful fallbacks on Windows and Linux
  • Asynchronous event handling with Promise support
  • 🎯 Event handler registration with wildcard pattern support
  • 📝 Full parameter parsing including object specifiers and hierarchies
  • 🔍 Human-readable object representations for complex Apple Event structures
  • 📝 TypeScript definitions with comprehensive type safety
  • 🛡️ Graceful degradation - never crashes on unsupported platforms
  • 🔧 Platform detection and debugging utilities
  • ⚙️ Electron compatible with process type detection

Installation

npm install osa-bridge

Requirements

macOS:

  • macOS with Carbon framework support
  • Xcode Command Line Tools: xcode-select --install
  • Node.js with native addon support

Other platforms:

  • No additional requirements (module provides no-op implementations)

Quick Start

import {
  on,
  isAppleEventsSupported,
  getPlatformInfo,
  getObjectSpecifierDescription,
  extractCommonParams,
  AEEvent,
  AEResult,
} from "osa-bridge";

// Check if Apple Events are supported
console.log("Platform info:", getPlatformInfo());

if (isAppleEventsSupported()) {
  console.log("🍎 Apple Events supported!");

  // Register a handler for "get data" events
  on("core", "getd", async (event: AEEvent): Promise<AEResult> => {
    console.log("Received Apple Event:", event);

    // Extract common parameters
    const commonParams = extractCommonParams(event.params);

    // Get human-readable description of object specifiers
    if (commonParams.directObject) {
      const description = getObjectSpecifierDescription(
        commonParams.directObject
      );
      console.log("Target object:", description);
      // Example output: "URL of active tab of front window"
    }

    return "Hello from Node.js!";
  });

  // Handle application quit events
  on("****", "quit", async (event: AEEvent): Promise<AEResult> => {
    console.log("Quit event received");
    process.exit(0);
  });
} else {
  console.log("🌐 Running in compatibility mode (no Apple Events)");
}

Platform Support

Platform Apple Events Status Notes
macOS ✅ Supported Native Full Apple Events functionality
Windows ❌ N/A Compatibility Functions available but non-functional
Linux ❌ N/A Compatibility Functions available but non-functional

API Reference

Core Functions

on(suite: AECode, event: AECode, handler: AEHandler): void

Register a handler for Apple Events.

// Handle specific events
on("core", "getd", async (event) => {
  return "Data retrieved";
});

// Use wildcards for any suite
on("****", "quit", async (event) => {
  process.exit(0);
});

// Use wildcards for any event in a suite
on("core", "****", async (event) => {
  console.log(`Core event: ${event.event}`);
  return null;
});

off(suite: AECode, event: AECode): boolean

Remove a specific event handler.

const wasRemoved = off("core", "getd");
console.log("Handler removed:", wasRemoved);

removeAllHandlers(): void

Remove all registered event handlers.

removeAllHandlers();

isAppleEventsSupported(): boolean

Check if Apple Events are supported on the current platform.

if (isAppleEventsSupported()) {
  // Safe to use Apple Events functionality
}

getPlatformInfo(): PlatformInfo

Get detailed platform and support information.

const info = getPlatformInfo();
console.log(info);
// Output example:
// {
//   platform: "darwin",
//   supported: true,
//   architecture: "arm64",
//   nodeVersion: "v20.0.0"
// }

getRegisteredHandlers(): string[]

Get list of all registered handler keys (useful for debugging).

const handlers = getRegisteredHandlers();
console.log("Registered handlers:", handlers);
// Output: ["coregetd", "****quit"]

Parameter Parsing

The module now provides comprehensive parsing of Apple Event parameters, including complex object specifiers.

Object Specifiers

Object specifiers represent hierarchical references to application objects (like "URL of active tab of front window"):

on("core", "getd", async (event: AEEvent) => {
  const directObject = event.params["----"]; // Direct object parameter

  if (isObjectSpecifier(directObject)) {
    console.log("Object class:", directObject.class); // e.g., "prop"
    console.log("Property name:", directObject.keyData); // e.g., "URL "
    console.log("Human readable:", directObject.humanReadable);
    // Output: "URL of active tab of front window"

    // Access container hierarchy
    if (directObject.container) {
      console.log(
        "Container:",
        getObjectSpecifierDescription(directObject.container)
      );
      // Output: "active tab of front window"
    }
  }

  return "Success";
});

Helper Functions for Parameters

// Extract commonly used parameters
const { directObject, subject, data, file, url } = extractCommonParams(
  event.params
);

// Get human-readable description of any object specifier
const description = getObjectSpecifierDescription(directObject);

// Find all object specifiers in the event
const allSpecifiers = extractObjectSpecifiers(event.params);

// Type-safe checking
if (isObjectSpecifier(someParam)) {
  // TypeScript now knows this is an AEObjectSpecifier
  console.log(someParam.humanReadable);
}

Types

// 4-character Apple Event code
type AECode = string;

// Object specifier structure for Apple Events
interface AEObjectSpecifier {
  type: "objectSpecifier";
  class: string; // Object class (e.g., 'prop', 'cTab', 'cwin')
  keyForm: string; // Key form (e.g., 'prop', 'indx', 'name')
  keyData: string; // Key data (property name, index, etc.)
  keyDataRaw?: string; // Raw hex representation for debugging
  container?: AEObjectSpecifier; // Container object (recursive)
  humanReadable?: string; // Human-readable representation
}

// Common Apple Event parameter types
type AEParam =
  | string
  | number
  | boolean
  | null
  | AEObjectSpecifier
  | AEParam[]
  | { [key: string]: AEParam };

// Incoming Apple Event structure
interface AEEvent {
  suite: AECode; // e.g., 'core'
  event: AECode; // e.g., 'getd'
  params: Record<AECode, AEParam>; // Parsed event parameters
  targetApp?: string; // Target application bundle ID or process name
  sourceApp?: string; // Source application info
  transactionID?: number; // Transaction ID if present
}

// Return value from handlers
type AEResult = string | number | boolean | null;

// Event handler function signature
type AEHandler = (evt: AEEvent) => Promise<AEResult> | AEResult;

// Platform information
interface PlatformInfo {
  platform: string; // 'darwin', 'win32', 'linux'
  supported: boolean; // Apple Events support status
  error?: string; // Error message if module failed to load
  architecture: string; // 'x64', 'arm64', etc.
  nodeVersion: string; // Current Node.js version
}

Apple Event Codes

Common Event Suites

  • core - Core Apple Events (standard system events)
  • misc - Miscellaneous events
  • reqd - Required events
  • **** - Wildcard (matches any suite)

Common Event Types

  • getd - Get data
  • setd - Set data
  • quit - Quit application
  • oapp - Open application
  • odoc - Open document
  • **** - Wildcard (matches any event)

Parameter Examples

Simple Property Access

AppleScript: tell application "Safari" to get URL of front document

// Apple Event received:
{
  suite: "core",
  event: "getd",
  params: {
    "----": {
      type: "objectSpecifier",
      class: "prop",
      keyData: "URL ",
      container: {
        type: "objectSpecifier",
        class: "docu",
        keyForm: "indx",
        keyData: "first"
      },
      humanReadable: "URL of front document"
    }
  },
  targetApp: "com.apple.Safari"
}

Complex Hierarchy

AppleScript: tell application "Safari" to get URL of active tab of front window

// Apple Event received:
{
  suite: "core",
  event: "getd",
  params: {
    "----": {
      type: "objectSpecifier",
      class: "prop",
      keyData: "URL ",
      container: {
        type: "objectSpecifier",
        class: "cTab",
        keyForm: "indx",
        keyData: "first",
        container: {
          type: "objectSpecifier",
          class: "cwin",
          keyForm: "indx",
          keyData: "first"
        }
      },
      humanReadable: "URL of active tab of front window"
    }
  },
  targetApp: "com.apple.Safari"
}

Electron Integration

The module automatically detects when running in Electron and provides appropriate warnings:

Main Process (Recommended)

// main.js
import { app } from "electron";
import { on, isAppleEventsSupported } from "osa-bridge";

app.whenReady().then(() => {
  if (isAppleEventsSupported()) {
    on("****", "quit", async () => {
      app.quit();
      return null;
    });
  }
});

Renderer Process

Apple Events only work in the main process. The module will log a warning if used in a renderer process:

osa-bridge: Running in Electron renderer process. Apple Events only work in the main process.

Error Handling

The module is designed to never crash your application:

// Safe on all platforms
on("core", "getd", async (event) => {
  return "This works on macOS, is ignored elsewhere";
});

// Check for errors
const info = getPlatformInfo();
if (info.error) {
  console.log("Module load error:", info.error);
}

Common error scenarios:

  • Non-macOS platforms: Functions are no-ops, isAppleEventsSupported() returns false
  • Missing native module: Error captured in getPlatformInfo().error
  • Electron renderer process: Warning logged, functions still available but non-functional

Advanced Usage

Analyzing Complex Object Hierarchies

on("core", "getd", async (event: AEEvent) => {
  // Extract all object specifiers from the event
  const specifiers = extractObjectSpecifiers(event.params);

  specifiers.forEach((spec, index) => {
    console.log(`Object ${index + 1}:`, spec.humanReadable);
    console.log(`  Class: ${spec.class}`);
    console.log(`  Key Data: ${spec.keyData}`);
    if (spec.keyDataRaw) {
      console.log(`  Raw Hex: ${spec.keyDataRaw}`);
    }
  });

  return "Analysis complete";
});

Building Custom Object Descriptions

function buildCustomDescription(param: AEParam): string {
  if (!isObjectSpecifier(param)) {
    return String(param);
  }

  // Custom formatting logic
  const parts = [];
  if (param.class === "prop") parts.push("property");
  if (param.keyData) parts.push(`"${param.keyData.trim()}"`);

  if (param.container) {
    parts.push("in", buildCustomDescription(param.container));
  }

  return parts.join(" ");
}

Debugging Apple Events

on("****", "****", async (event: AEEvent) => {
  console.log("=== Apple Event Debug ===");
  console.log("Event:", formatAEEvent(event));
  console.log("Parameters:");

  Object.entries(event.params).forEach(([key, value]) => {
    console.log(`  ${key}:`, getObjectSpecifierDescription(value));
  });

  return null; // Continue processing
});

Building from Source

# Install dependencies
npm install

# Build native module (macOS only)
npm run build

# Run tests
npm test

Build Requirements (macOS)

# Install Xcode Command Line Tools
xcode-select --install

# Verify installation
xcode-select -p

Current Limitations

  • Complex return values: Advanced Apple Event reply structures may need enhancement
  • Error handling: Native-level errors may need more detailed reporting
  • Performance: Large object hierarchies could benefit from optimization

Development Status

This is an active project with comprehensive Apple Event support:

  • ✅ Event handler registration and dispatch
  • ✅ Cross-platform compatibility
  • ✅ Full Apple Event parameter parsing
  • ✅ Object specifier parsing with hierarchies
  • ✅ Human-readable object representations
  • ✅ TypeScript type safety
  • ✅ Helper functions for common operations
  • 🔄 Enhanced error reporting (planned)
  • 🔄 Performance optimizations (planned)

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature-name
  3. Test on macOS (if possible)
  4. Ensure cross-platform compatibility
  5. Submit a pull request

License

MIT License - see LICENSE file for details.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published