or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

app-lifecycle.mddevelopment.mdindex.mdipc.mdmenu-system.mdnetwork-protocols.mdplatform-features.mdsecurity.mdsystem-integration.mdwindow-management.md
tile.json

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;
}