CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-electron

Build cross-platform desktop apps with JavaScript, HTML, and CSS

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

ipc.mddocs/

Inter-Process Communication (IPC)

Secure communication system between main and renderer processes, including contextBridge for secure API exposure.

Capabilities

IPC Main

Handle messages from renderer processes in the main process.

/**
 * Communicate asynchronously from the main process to renderer processes
 */
interface IpcMain extends EventEmitter {
  /** Listens to channel, when a new message arrives listener would be called with listener(event, args...) */
  on(channel: string, listener: (event: IpcMainEvent, ...args: any[]) => void): this;
  
  /** Adds a one time listener function for the event */
  once(channel: string, listener: (event: IpcMainEvent, ...args: any[]) => void): this;
  
  /** Removes the specified listener from the listener array for the specified channel */
  removeListener(channel: string, listener: (...args: any[]) => void): this;
  
  /** Removes all listeners, or those of the specified channel */
  removeAllListeners(channel?: string): this;
  
  /** Adds a handler for an invoke-able IPC */
  handle(channel: string, listener: (event: IpcMainInvokeEvent, ...args: any[]) => Promise<any> | any): void;
  
  /** Adds a handler for an invoke-able IPC */
  handleOnce(channel: string, listener: (event: IpcMainInvokeEvent, ...args: any[]) => Promise<any> | any): void;
  
  /** Removes any handler for channel, if present */
  removeHandler(channel: string): void;
  
  /** Returns a list of the names of all channels that have handlers */
  listenerCount(eventName: string | symbol): number;
}

declare const ipcMain: IpcMain;

interface IpcMainEvent extends Event {
  /** Set this to the value to be returned in a synchronous message */
  returnValue: any;
  
  /** Returns the webContents that sent the message */
  readonly sender: WebContents;
  
  /** Returns the ID of the renderer frame that sent this message */
  readonly senderFrame: WebFrameMain;
  
  /** A list of MessagePorts that were transferred with this message */
  readonly ports: MessagePortMain[];
  
  /** A function that will send an IPC message to the renderer frame that sent the original message */
  reply(channel: string, ...args: any[]): void;
}

interface IpcMainInvokeEvent extends Event {
  /** Returns the webContents that sent the message */
  readonly sender: WebContents;
  
  /** Returns the ID of the renderer frame that sent this message */
  readonly senderFrame: WebFrameMain;
}

Usage Examples:

const { ipcMain, BrowserWindow } = require('electron');

// Handle async messages
ipcMain.on('async-message', (event, arg) => {
  console.log('Received from renderer:', arg);
  
  // Reply back to renderer
  event.reply('async-reply', 'Response from main');
});

// Handle invoke (request-response pattern)
ipcMain.handle('get-app-version', async (event) => {
  return process.env.npm_package_version;
});

ipcMain.handle('read-file', async (event, filePath) => {
  try {
    const fs = require('fs').promises;
    const content = await fs.readFile(filePath, 'utf8');
    return content;
  } catch (error) {
    throw error;
  }
});

// Handle with validation
ipcMain.handle('database-query', async (event, query, params) => {
  // Validate sender
  if (!isValidSender(event.sender)) {
    throw new Error('Unauthorized');
  }
  
  // Execute query
  return await executeQuery(query, params);
});

// Broadcast to all windows
function broadcastToAll(channel, data) {
  BrowserWindow.getAllWindows().forEach(window => {
    window.webContents.send(channel, data);
  });
}

// Send to specific window
function sendToWindow(windowId, channel, data) {
  const window = BrowserWindow.fromId(windowId);
  if (window && !window.isDestroyed()) {
    window.webContents.send(channel, data);
  }
}

IPC Renderer

Send messages to the main process from renderer processes.

/**
 * Communicate asynchronously from a renderer process to the main process
 */
interface IpcRenderer extends EventEmitter {
  /** Listens to channel, when a new message arrives listener would be called with listener(event, args...) */
  on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this;
  
  /** Adds a one time listener function for the event */
  once(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this;
  
  /** Removes the specified listener from the listener array for the specified channel */
  removeListener(channel: string, listener: (...args: any[]) => void): this;
  
  /** Removes all listeners, or those of the specified channel */
  removeAllListeners(channel?: string): this;
  
  /** Send an asynchronous message to the main process via channel */
  send(channel: string, ...args: any[]): void;
  
  /** Send a message to the main process via channel and expect a result asynchronously */
  invoke(channel: string, ...args: any[]): Promise<any>;
  
  /** Send a message to the main process via channel and expect a result synchronously */
  sendSync(channel: string, ...args: any[]): any;
  
  /** Sends a message to a window with webContentsId via channel */
  sendTo(webContentsId: number, channel: string, ...args: any[]): void;
  
  /** Send a message to the host page in a <webview> via channel */
  sendToHost(channel: string, ...args: any[]): void;
  
  /** Like ipcRenderer.send but the event will be sent to the <webview> element in the host page instead of the main process */
  postMessage(channel: string, message: any, transfer?: MessagePort[]): void;
}

declare const ipcRenderer: IpcRenderer;

interface IpcRendererEvent extends Event {
  /** A list of MessagePorts that were transferred with this message */
  readonly ports: MessagePort[];
  
  /** Returns the webContents that sent the message */
  readonly sender: WebContents;
  
  /** The ID of the renderer frame that sent this message */
  readonly senderId: number;
  
  /** The ID of the frame that sent this message */
  readonly senderFrame: WebFrameMain;
}

Usage Examples:

// In renderer process (with nodeIntegration enabled)
const { ipcRenderer } = require('electron');

// Send async message
ipcRenderer.send('async-message', 'Hello from renderer');

// Listen for reply
ipcRenderer.on('async-reply', (event, message) => {
  console.log('Reply from main:', message);
});

// Use invoke for request-response
async function getAppVersion() {
  try {
    const version = await ipcRenderer.invoke('get-app-version');
    console.log('App version:', version);
    return version;
  } catch (error) {
    console.error('Failed to get version:', error);
  }
}

// File operations
async function loadFile(filePath) {
  try {
    const content = await ipcRenderer.invoke('read-file', filePath);
    document.getElementById('content').textContent = content;
  } catch (error) {
    console.error('Failed to read file:', error);
  }
}

// Form submission
document.getElementById('myForm').addEventListener('submit', async (e) => {
  e.preventDefault();
  const formData = new FormData(e.target);
  const data = Object.fromEntries(formData);
  
  try {
    const result = await ipcRenderer.invoke('submit-form', data);
    console.log('Form submitted successfully:', result);
  } catch (error) {
    console.error('Form submission failed:', error);
  }
});

// Real-time updates
ipcRenderer.on('data-update', (event, data) => {
  updateUI(data);
});

// Clean up listeners
window.addEventListener('beforeunload', () => {
  ipcRenderer.removeAllListeners();
});

Context Bridge

Secure API exposure between main and renderer processes.

/**
 * Create a safe, bi-directional, synchronous bridge across isolated contexts
 */
interface ContextBridge {
  /** Exposes an API object to the main world context */
  exposeInMainWorld(apiKey: string, api: any): void;
  
  /** Exposes an API object to an isolated world context */
  exposeInIsolatedWorld(worldId: number, apiKey: string, api: any): void;
}

declare const contextBridge: ContextBridge;

Usage Examples:

Preload script (secure bridge):

// preload.js
const { contextBridge, ipcRenderer } = require('electron');

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('electronAPI', {
  // App control
  getAppVersion: () => ipcRenderer.invoke('get-app-version'),
  
  // File operations
  readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
  writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content),
  
  // Database operations
  query: (sql, params) => ipcRenderer.invoke('db-query', sql, params),
  
  // Window control
  minimizeWindow: () => ipcRenderer.invoke('minimize-window'),
  maximizeWindow: () => ipcRenderer.invoke('maximize-window'),
  closeWindow: () => ipcRenderer.invoke('close-window'),
  
  // Event listeners
  onUpdateAvailable: (callback) => {
    ipcRenderer.on('update-available', (event, ...args) => callback(...args));
  },
  
  onDataChanged: (callback) => {
    ipcRenderer.on('data-changed', (event, ...args) => callback(...args));
  },
  
  // Remove listeners
  removeAllListeners: (channel) => {
    ipcRenderer.removeAllListeners(channel);
  }
});

// Expose specific utilities
contextBridge.exposeInMainWorld('fileUtils', {
  basename: require('path').basename,
  dirname: require('path').dirname,
  extname: require('path').extname,
  join: require('path').join
});

Renderer usage (secure):

// renderer.js (in web page)
// No direct require() needed - use exposed APIs

async function initApp() {
  // Use exposed API
  const version = await window.electronAPI.getAppVersion();
  document.getElementById('version').textContent = version;
  
  // Set up event listeners
  window.electronAPI.onUpdateAvailable((info) => {
    showUpdateNotification(info);
  });
  
  window.electronAPI.onDataChanged((data) => {
    refreshUI(data);
  });
}

// File operations
async function openFile() {
  try {
    const content = await window.electronAPI.readFile('/path/to/file.txt');
    document.getElementById('content').value = content;
  } catch (error) {
    console.error('Failed to open file:', error);
  }
}

// Database queries
async function loadUsers() {
  try {
    const users = await window.electronAPI.query('SELECT * FROM users WHERE active = ?', [true]);
    displayUsers(users);
  } catch (error) {
    console.error('Failed to load users:', error);
  }
}

// Window controls
document.getElementById('minimize').addEventListener('click', () => {
  window.electronAPI.minimizeWindow();
});

// Clean up on page unload
window.addEventListener('beforeunload', () => {
  window.electronAPI.removeAllListeners('data-changed');
  window.electronAPI.removeAllListeners('update-available');
});

Message Channels

Advanced IPC using MessageChannel API for structured communication.

/**
 * MessageChannelMain creates MessageChannel-like pair of ports to allow passing messages between main and renderer
 */
class MessageChannelMain {
  /** Create a new MessageChannelMain */
  constructor();
  
  /** The port1 property */
  readonly port1: MessagePortMain;
  
  /** The port2 property */
  readonly port2: MessagePortMain;
}

/**
 * MessagePortMain is the main-process-side message port of a MessageChannel
 */
class MessagePortMain extends EventEmitter {
  /** Sends a message from the port */
  postMessage(message: any): void;
  
  /** Starts the sending of messages queued on the port */
  start(): void;
  
  /** Disconnects the port */
  close(): void;
}

Usage Examples:

// In main process
const { MessageChannelMain, BrowserWindow } = require('electron');

// Create message channel
const { port1, port2 } = new MessageChannelMain();

// Send port to renderer
const mainWindow = new BrowserWindow({
  webPreferences: {
    preload: path.join(__dirname, 'preload.js')
  }
});

mainWindow.webContents.postMessage('port-message', null, [port2]);

// Listen on main side
port1.on('message', (event) => {
  console.log('Received message from renderer:', event.data);
  
  // Send response
  port1.postMessage('Response from main');
});

port1.start();

Preload script:

// preload.js
const { ipcRenderer } = require('electron');

ipcRenderer.on('port-message', (event) => {
  const port = event.ports[0];
  
  // Expose port to renderer
  window.messagePort = {
    send: (message) => port.postMessage(message),
    onMessage: (callback) => {
      port.on('message', (event) => callback(event.data));
    }
  };
  
  port.start();
});

Types

interface WebFrameMain extends EventEmitter {
  /** A integer representing the unique ID of the frame */
  readonly frameId: number;
  
  /** A string representing the name of the frame */
  readonly name: string;
  
  /** A integer representing the operating system pid of the process which owns this frame */
  readonly processId: number;
  
  /** A integer representing the unique ID of the frame's internal routing */
  readonly routingId: number;
  
  /** A WebContents instance representing the top level frame */
  readonly top: WebFrameMain;
  
  /** A WebContents instance representing the parent of this frame */
  readonly parent: WebFrameMain | null;
  
  /** A WebFrameMain[] collection containing the direct children of this frame */
  readonly frames: WebFrameMain[];
  
  /** A WebFrameMain[] collection containing every frame in the subtree of this frame */
  readonly framesInSubtree: WebFrameMain[];
  
  /** A string representing the current URL of this frame */
  readonly url: string;
  
  /** A string representing the origin of this frame */
  readonly origin: string;
  
  /** Executes JavaScript in the frame */
  executeJavaScript(code: string, userGesture?: boolean): Promise<unknown>;
  
  /** Returns a promise that resolves to the result of the executed code or is rejected if execution throws or results in a rejected promise */
  reload(): boolean;
}

interface PreloadScriptOptions {
  filePath: string;
  worldId?: number;
}

interface IsolatedWorldInfo {
  worldId: number;
  name: string;
}

Install with Tessl CLI

npx tessl i tessl/npm-electron

docs

app-lifecycle.md

development.md

index.md

ipc.md

menu-system.md

network-protocols.md

platform-features.md

security.md

system-integration.md

window-management.md

tile.json