WindowIPC API
WindowIPC extends Window with a decorator-based IPC system. Use @windowName and @mainHandler decorators to automatically register backend handlers and generate typed frontend APIs.
import { WindowIPC, windowName, mainHandler } from "tronbun";Overview
Instead of manually calling registerIPCHandler, you decorate methods and the system:
- Registers each
@mainHandlermethod as an IPC handler - Generates a typed
window.<WindowName>.<handlerName>()API on the frontend - Creates
src/ipc-types.tswith full TypeScript definitions
Decorators
@windowName(name: string)
Class decorator that sets the frontend API namespace.
@windowName("MyApp")
class MainWindow extends WindowIPC { ... }
// Frontend: window.MyAppThe name should be a valid JavaScript identifier (camelCase recommended).
@mainHandler(name: string)
Method decorator that registers a method as an IPC handler.
@mainHandler("greet")
async handleGreet(name: string) {
return `Hello, ${name}!`;
}
// Frontend: window.MyApp.greet("World")The handler name is the IPC channel name and becomes the frontend method name.
Class: WindowIPC
Constructor
@windowName("MyApp")
class MainWindow extends WindowIPC {
constructor() {
super(options?: WindowOptions);
}
}Accepts the same WindowOptions as Window. On construction, automatically:
- Scans the class for
@mainHandlerdecorators - Registers each as an IPC handler
- Calls
initialize()to inject the frontend API script
Methods
getWindowName(): string
Returns the name set by @windowName.
const win = new MainWindow();
console.log(win.getWindowName()); // "MyApp"getHandlerNames(): string[]
Returns an array of all registered handler names.
console.log(win.getHandlerNames()); // ["greet", "calculate", "getSystemInfo"]initialize(): Promise<void>
Sets up the frontend API scripts. Called automatically by the constructor. You typically don't need to call this manually.
Complete Example
Backend (src/main.ts)
import { WindowIPC, windowName, mainHandler } from "tronbun";
interface CalculateInput {
a: number;
b: number;
operation: "add" | "subtract" | "multiply" | "divide";
}
interface SystemInfo {
platform: string;
version: string;
uptime: number;
}
@windowName("MyApp")
class MainWindow extends WindowIPC {
constructor() {
super({
title: "Calculator",
width: 400,
height: 300,
center: true,
});
this.navigate("public/index.html");
}
@mainHandler("greet")
async handleGreet(name: string): Promise<string> {
return `Hello, ${name}!`;
}
@mainHandler("calculate")
async handleCalculate(data: CalculateInput): Promise<number> {
switch (data.operation) {
case "add": return data.a + data.b;
case "subtract": return data.a - data.b;
case "multiply": return data.a * data.b;
case "divide": return data.a / data.b;
}
}
@mainHandler("getSystemInfo")
async handleGetSystemInfo(): Promise<SystemInfo> {
return {
platform: process.platform,
version: process.version,
uptime: process.uptime(),
};
}
}
new MainWindow();Frontend (src/web/index.ts)
// Types are auto-generated in src/ipc-types.ts
// window.MyApp is fully typed
async function init() {
// Simple string parameter
const greeting = await window.MyApp.greet("World");
document.getElementById("greeting")!.textContent = greeting;
// Object parameter
const result = await window.MyApp.calculate({
a: 10,
b: 5,
operation: "multiply",
});
document.getElementById("result")!.textContent = `Result: ${result}`;
// No parameter (void)
const info = await window.MyApp.getSystemInfo();
document.getElementById("platform")!.textContent = info.platform;
}
init();Generated Types (src/ipc-types.ts)
Running tronbun build or tronbun generate-types produces:
interface CalculateInput {
a: number;
b: number;
operation: "add" | "subtract" | "multiply" | "divide";
}
interface SystemInfo {
platform: string;
version: string;
uptime: number;
}
export interface MyAppHandlers {
greet: (data: string) => Promise<string>;
calculate: (data: CalculateInput) => Promise<number>;
getSystemInfo: (data: void) => Promise<SystemInfo>;
}
declare global {
interface Window {
MyApp: MyAppHandlers;
}
}Multiple Windows
You can have multiple WindowIPC classes, each with their own namespace:
@windowName("Editor")
class EditorWindow extends WindowIPC {
@mainHandler("save")
async handleSave(content: string) { ... }
@mainHandler("load")
async handleLoad(path: string) { ... }
}
@windowName("Settings")
class SettingsWindow extends WindowIPC {
@mainHandler("getPreferences")
async handleGetPreferences() { ... }
@mainHandler("setPreference")
async handleSetPreference(data: { key: string; value: any }) { ... }
}
new EditorWindow();
new SettingsWindow();Frontend for each window gets its own typed API:
window.Editor.save(content)/window.Editor.load(path)window.Settings.getPreferences()/window.Settings.setPreference({ key, value })
Handler Metadata
The decorator system stores metadata that can be inspected:
import { getWindowMetadata, getAllWindowMetadata } from "tronbun";
// Get metadata for a specific class
const meta = getWindowMetadata(MainWindow);
// { name: "MyApp", handlers: [{ name: "greet", method: "handleGreet" }, ...] }
// Get all registered window metadata
const allMeta = getAllWindowMetadata();Mixing Approaches
You can use both decorators and manual handle() calls:
@windowName("MyApp")
class MainWindow extends WindowIPC {
constructor() {
super({ title: "My App" });
// Manual handler alongside decorators
this.handle("legacyEndpoint", async (data) => {
return processLegacy(data);
});
}
@mainHandler("newEndpoint")
async handleNew(data: any) {
return processNew(data);
}
}Note that manually registered handlers won't appear in generated types.
See Also
- Window — Base Window API
- IPC Communication Guide — Patterns and best practices
