Skip to content

Latest commit

 

History

History
324 lines (247 loc) · 6.62 KB

File metadata and controls

324 lines (247 loc) · 6.62 KB

@assemblerjs/electron

Electron integration for AssemblerJS with type-safe IPC communication between main process, renderer process, and preload scripts.

Overview

@assemblerjs/electron brings the power of AssemblerJS dependency injection to Electron applications, providing a unified architecture across all Electron processes with type-safe inter-process communication (IPC).

Features

  • 🔌 Main Process Integration - Use AssemblerJS DI in Electron's main process
  • 🖥️ Renderer Process Support - DI for renderer processes
  • 🔗 Preload Scripts - Bridge main and renderer with type-safety
  • 🎯 Type-safe IPC - Full TypeScript support for IPC communication
  • 🏗️ Unified Architecture - Same patterns across all processes
  • ♻️ Lifecycle Management - Proper cleanup and resource management

Installation

npm install @assemblerjs/electron assemblerjs electron reflect-metadata
# or
yarn add @assemblerjs/electron assemblerjs electron reflect-metadata

Package Exports

The package provides three entry points for different Electron contexts:

// Main process
import { /* ... */ } from '@assemblerjs/electron';

// Renderer process
import { /* ... */ } from '@assemblerjs/electron/renderer';

// Preload script
import { /* ... */ } from '@assemblerjs/electron/preload';

Quick Start

Main Process

import 'reflect-metadata';
import { app, BrowserWindow } from 'electron';
import { Assemblage, Assembler, AbstractAssemblage } from 'assemblerjs';

@Assemblage()
class WindowManager implements AbstractAssemblage {
  private mainWindow: BrowserWindow | null = null;

  async onInit() {
    await app.whenReady();
    this.createWindow();
  }

  createWindow() {
    this.mainWindow = new BrowserWindow({
      width: 800,
      height: 600,
      webPreferences: {
        nodeIntegration: false,
        contextIsolation: true,
        preload: path.join(__dirname, 'preload.js')
      }
    });

    this.mainWindow.loadFile('index.html');
  }

  async onDispose() {
    this.mainWindow?.close();
  }
}

@Assemblage({
  inject: [[WindowManager]]
})
class ElectronApp implements AbstractAssemblage {
  constructor(private windowManager: WindowManager) {}

  async onInit() {
    console.log('Electron app started');
  }
}

// Bootstrap
const electronApp = Assembler.build(ElectronApp);

Preload Script

import { contextBridge, ipcRenderer } from 'electron';

// Expose safe APIs to renderer
contextBridge.exposeInMainWorld('electronAPI', {
  send: (channel: string, data: any) => {
    ipcRenderer.send(channel, data);
  },
  on: (channel: string, callback: Function) => {
    ipcRenderer.on(channel, (_, ...args) => callback(...args));
  }
});

Renderer Process

import 'reflect-metadata';
import { Assemblage, Assembler, AbstractAssemblage } from 'assemblerjs';

declare global {
  interface Window {
    electronAPI: {
      send: (channel: string, data: any) => void;
      on: (channel: string, callback: Function) => void;
    };
  }
}

@Assemblage()
class AppService implements AbstractAssemblage {
  sendMessage(message: string) {
    window.electronAPI.send('message', message);
  }

  onInit() {
    window.electronAPI.on('response', (data: any) => {
      console.log('Received:', data);
    });
  }
}

@Assemblage({
  inject: [[AppService]]
})
class RendererApp implements AbstractAssemblage {
  constructor(private appService: AppService) {}

  async onInit() {
    this.appService.sendMessage('Hello from renderer');
  }
}

const app = Assembler.build(RendererApp);

IPC Communication Example

Main Process - IPC Handler

import { ipcMain } from 'electron';
import { Assemblage, AbstractAssemblage } from 'assemblerjs';

@Assemblage()
class IpcService implements AbstractAssemblage {
  onInit() {
    ipcMain.on('message', (event, data) => {
      console.log('Received from renderer:', data);
      event.reply('response', { status: 'ok', data });
    });

    ipcMain.handle('get-data', async () => {
      return { result: 'Some data' };
    });
  }

  onDispose() {
    ipcMain.removeAllListeners('message');
    ipcMain.removeHandler('get-data');
  }
}

Renderer - IPC Client

@Assemblage()
class DataService implements AbstractAssemblage {
  async getData() {
    const result = await window.electronAPI.invoke('get-data');
    return result;
  }
}

Architecture Patterns

Service Layer

// Main process
@Assemblage()
class DatabaseService implements AbstractAssemblage {
  private db: any;

  async onInit() {
    this.db = await this.connect();
  }

  async query(sql: string) {
    return this.db.query(sql);
  }

  async onDispose() {
    await this.db.close();
  }
}

@Assemblage({
  inject: [[DatabaseService]]
})
class UserRepository implements AbstractAssemblage {
  constructor(private db: DatabaseService) {}

  async findById(id: string) {
    return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
  }
}

Window Management

@Assemblage()
class MultiWindowManager implements AbstractAssemblage {
  private windows = new Map<string, BrowserWindow>();

  createWindow(id: string, options: BrowserWindowConstructorOptions) {
    const window = new BrowserWindow(options);
    this.windows.set(id, window);
    return window;
  }

  getWindow(id: string) {
    return this.windows.get(id);
  }

  closeWindow(id: string) {
    const window = this.windows.get(id);
    window?.close();
    this.windows.delete(id);
  }

  async onDispose() {
    for (const window of this.windows.values()) {
      window.close();
    }
    this.windows.clear();
  }
}

Best Practices

1. Process Separation

Keep main and renderer logic separated. Use IPC for communication:

// ✅ Good - Separate concerns
// Main: File system, native APIs
// Renderer: UI logic

2. Security

Always use contextIsolation and disable nodeIntegration:

webPreferences: {
  nodeIntegration: false,
  contextIsolation: true,
  preload: path.join(__dirname, 'preload.js')
}

3. Resource Cleanup

Use lifecycle hooks for proper cleanup:

@Assemblage()
class ResourceManager implements AbstractAssemblage {
  async onDispose() {
    // Clean up resources
    await this.cleanup();
  }
}

Requirements

  • Node.js: ≥ 18.12.0
  • Electron: ≥ 28.0.0
  • TypeScript: ≥ 5.0
  • reflect-metadata: Required

For Contributors

Development

# Build the package
npx nx build electron

# Run tests
npx nx test electron

# Lint
npx nx lint electron

License

MIT


Part of the AssemblerJS monorepo