CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-capacitor--core

Cross-platform runtime library that enables web applications to run natively on iOS, Android, Web, and other platforms with unified JavaScript APIs

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugin-development.mddocs/

Plugin Development

Base classes and utilities for developing custom Capacitor plugins with cross-platform support. The plugin development API provides a foundation for creating plugins that work seamlessly across web, iOS, and Android platforms.

Capabilities

Plugin Interface

Base interface that all Capacitor plugins must implement for event handling and lifecycle management.

/**
 * Base interface all plugins must implement
 */
interface Plugin {
  /** Add event listener and return handle for removal */
  addListener(eventName: string, listenerFunc: (...args: any[]) => any): Promise<PluginListenerHandle>;
  /** Remove all event listeners */
  removeAllListeners(): Promise<void>;
}

/**
 * Handle for managing plugin event listeners
 */
interface PluginListenerHandle {
  /** Remove this specific listener */
  remove(): Promise<void>;
}

WebPlugin Base Class

Base class for implementing web platform plugin functionality with built-in event management and utility methods.

/**
 * Base class web plugins should extend
 */
class WebPlugin implements Plugin {
  /** Add event listener with automatic management */
  addListener(eventName: string, listenerFunc: ListenerCallback): Promise<PluginListenerHandle>;
  /** Remove all listeners and clean up resources */
  removeAllListeners(): Promise<void>;
  
  /** Notify all listeners for an event (protected) */
  protected notifyListeners(eventName: string, data: any, retainUntilConsumed?: boolean): void;
  /** Check if any listeners exist for an event (protected) */
  protected hasListeners(eventName: string): boolean;
  /** Register window event listener (protected) */
  protected registerWindowListener(windowEventName: string, pluginEventName: string): void;
  /** Create unimplemented error (protected) */
  protected unimplemented(msg?: string): CapacitorException;
  /** Create unavailable error (protected) */
  protected unavailable(msg?: string): CapacitorException;
}

/**
 * Callback function type for event listeners
 */
type ListenerCallback = (err: any, ...args: any[]) => void;

Usage Examples:

import { WebPlugin } from "@capacitor/core";

// Create a web implementation
class MyPluginWeb extends WebPlugin {
  async echo(options: { value: string }): Promise<{ value: string }> {
    return { value: options.value };
  }

  async startListening(): Promise<void> {
    // Register window event listener
    this.registerWindowListener('resize', 'windowResize');
  }

  private handleResize = (event: Event) => {
    // Notify plugin listeners
    this.notifyListeners('windowResize', {
      width: window.innerWidth,
      height: window.innerHeight,
    });
  };

  async methodNotAvailableOnWeb(): Promise<void> {
    throw this.unimplemented('This method is not available on web');
  }

  async methodRequiringPermission(): Promise<void> {
    if (!navigator.geolocation) {
      throw this.unavailable('Geolocation not available');
    }
    // Implementation here
  }
}

// Register the plugin
import { registerPlugin } from "@capacitor/core";

interface MyPlugin {
  echo(options: { value: string }): Promise<{ value: string }>;
  startListening(): Promise<void>;
}

const MyPlugin = registerPlugin<MyPlugin>('MyPlugin', {
  web: () => new MyPluginWeb(),
});

// Use the plugin
const result = await MyPlugin.echo({ value: 'Hello' });
console.log(result.value); // 'Hello'

// Add event listener
const listener = await MyPlugin.addListener('windowResize', (data) => {
  console.log(`Window resized: ${data.width}x${data.height}`);
});

// Remove specific listener
await listener.remove();

// Or remove all listeners
await MyPlugin.removeAllListeners();

Event Management

Advanced event management system for handling plugin events with retention and window integration.

/**
 * Notify all listeners for a specific event
 * @param eventName Name of the event to fire
 * @param data Data to send to listeners
 * @param retainUntilConsumed Keep event data until listeners are added
 */
protected notifyListeners(eventName: string, data: any, retainUntilConsumed?: boolean): void;

/**
 * Check if any listeners exist for an event
 * @param eventName Name of the event to check
 * @returns true if listeners exist, false otherwise
 */
protected hasListeners(eventName: string): boolean;

/**
 * Register a window event listener that forwards to plugin listeners
 * @param windowEventName Native window event name
 * @param pluginEventName Plugin event name to forward to
 */
protected registerWindowListener(windowEventName: string, pluginEventName: string): void;

/**
 * Window listener handle for managing native event binding
 */
interface WindowListenerHandle {
  registered: boolean;
  windowEventName: string;
  pluginEventName: string;
  handler: (event: any) => void;
}

Usage Examples:

class MyAdvancedPluginWeb extends WebPlugin {
  constructor() {
    super();
    // Register window events to forward to plugin events
    this.registerWindowListener('online', 'networkChanged');
    this.registerWindowListener('offline', 'networkChanged');
  }

  async startMonitoring(): Promise<void> {
    // Simulate retained event - will be delivered to future listeners
    this.notifyListeners('initialStatus', {
      isOnline: navigator.onLine,
      timestamp: Date.now(),
    }, true); // retainUntilConsumed = true
  }

  async sendUpdate(): Promise<void> {
    // Only send if someone is listening
    if (this.hasListeners('update')) {
      this.notifyListeners('update', {
        data: 'New update available',
        timestamp: Date.now(),
      });
    }
  }

  // Window events are automatically forwarded
  private handleNetworkChange = (event: Event) => {
    this.notifyListeners('networkChanged', {
      isOnline: event.type === 'online',
      timestamp: Date.now(),
    });
  };
}

Error Handling Utilities

Built-in utilities for creating standard plugin exceptions with appropriate error codes.

/**
 * Create an unimplemented exception for unsupported methods
 * @param msg Custom error message (default: 'not implemented')
 * @returns CapacitorException with Unimplemented code
 */
protected unimplemented(msg?: string): CapacitorException;

/**
 * Create an unavailable exception for temporarily unavailable features
 * @param msg Custom error message (default: 'not available')
 * @returns CapacitorException with Unavailable code
 */
protected unavailable(msg?: string): CapacitorException;

Usage Examples:

class MyPluginWeb extends WebPlugin {
  async nativeOnlyMethod(): Promise<void> {
    // Throw unimplemented for features not available on web
    throw this.unimplemented('This method is only available on native platforms');
  }

  async cameraMethod(): Promise<void> {
    // Check for required APIs
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
      throw this.unavailable('Camera access not available in this browser');
    }
    
    try {
      // Implementation here
      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
      // Use stream...
    } catch (error) {
      throw this.unavailable('Camera permission denied or not available');
    }
  }

  async locationMethod(): Promise<void> {
    if (!navigator.geolocation) {
      throw this.unavailable('Geolocation not supported');
    }

    // Check if HTTPS is required
    if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
      throw this.unavailable('Geolocation requires HTTPS');
    }

    // Implementation here
  }
}

Plugin Registration Patterns

Common patterns for registering plugins with platform-specific implementations.

Usage Examples:

import { registerPlugin } from "@capacitor/core";

// Pattern 1: Direct implementation objects
const SimplePlugin = registerPlugin<SimplePluginInterface>('SimplePlugin', {
  web: new SimplePluginWeb(),
  android: new SimplePluginAndroid(), // If pre-loaded
});

// Pattern 2: Lazy-loaded implementations
const LazyPlugin = registerPlugin<LazyPluginInterface>('LazyPlugin', {
  web: () => import('./lazy-plugin-web').then(m => new m.LazyPluginWeb()),
  android: () => import('./lazy-plugin-android').then(m => new m.LazyPluginAndroid()),
});

// Pattern 3: Factory functions
const FactoryPlugin = registerPlugin<FactoryPluginInterface>('FactoryPlugin', {
  web: async () => {
    const config = await loadConfiguration();
    return new FactoryPluginWeb(config);
  },
});

// Pattern 4: Conditional implementations
const ConditionalPlugin = registerPlugin<ConditionalPluginInterface>('ConditionalPlugin', {
  web: () => {
    if (supportsAdvancedFeatures()) {
      return new AdvancedPluginWeb();
    } else {
      return new BasicPluginWeb();
    }
  },
});

// Pattern 5: Native-only plugin (no web implementation)
const NativeOnlyPlugin = registerPlugin<NativeOnlyPluginInterface>('NativeOnlyPlugin');
// Will throw CapacitorException with Unimplemented code on web

Plugin Testing Patterns

Recommended patterns for testing plugin implementations.

Usage Examples:

// Test web implementation
import { MyPluginWeb } from './my-plugin-web';

describe('MyPluginWeb', () => {
  let plugin: MyPluginWeb;

  beforeEach(() => {
    plugin = new MyPluginWeb();
  });

  afterEach(async () => {
    await plugin.removeAllListeners();
  });

  it('should echo value', async () => {
    const result = await plugin.echo({ value: 'test' });
    expect(result.value).toBe('test');
  });

  it('should handle listeners', async () => {
    const callback = jest.fn();
    const listener = await plugin.addListener('testEvent', callback);
    
    // Trigger event
    plugin.notifyListeners('testEvent', { data: 'test' });
    
    expect(callback).toHaveBeenCalledWith({ data: 'test' });
    
    await listener.remove();
  });

  it('should throw unimplemented for unsupported methods', async () => {
    await expect(plugin.nativeOnlyMethod()).rejects.toThrow();
  });
});

Install with Tessl CLI

npx tessl i tessl/npm-capacitor--core

docs

cookie-management.md

http-client.md

index.md

platform-runtime.md

plugin-development.md

webview-management.md

tile.json