electrobun-rpc

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Electrobun RPC System

Electrobun RPC 系统

Type-safe bidirectional communication between the Bun process and webview renderers. Uses WebSockets for transport with AES-GCM encryption. Two call types:
requests
(expects a response) and
messages
(fire-and-forget).
Bun 进程与 WebView 渲染器之间的类型安全双向通信。使用 WebSockets 作为传输层,并采用 AES-GCM 加密。包含两种调用类型:
requests
(需要响应)和
messages
(无需等待响应)。

Schema Definition (shared type)

Schema 定义(共享类型)

Define the schema in a shared file both sides import:
typescript
// src/shared/rpc-schema.ts
import type { ElectrobunRPCSchema, RPCSchema } from "electrobun/view";

// bun: what the BUN process handles (requests from renderer, messages from renderer)
// webview: what the WEBVIEW handles (requests from bun, messages from bun)
export type AppRPC = {
  bun: RPCSchema<{
    requests: {
      getNotes: { args: {}; response: Note[] };
      saveNote: { args: { id: string; title: string; content: string }; response: { success: boolean } };
      deleteNote: { args: { id: string }; response: void };
    };
    messages: {
      userIdleDetected: {};
    };
  }>;
  webview: RPCSchema<{
    requests: {
      getSelectedText: { args: {}; response: string };
    };
    messages: {
      menuAction: { action: string; role?: string };
      noteUpdatedExternally: { id: string };
    };
  }>;
} & ElectrobunRPCSchema;
在双方共同导入的共享文件中定义 schema:
typescript
// src/shared/rpc-schema.ts
import type { ElectrobunRPCSchema, RPCSchema } from "electrobun/view";

// bun: what the BUN process handles (requests from renderer, messages from renderer)
// webview: what the WEBVIEW handles (requests from bun, messages from bun)
export type AppRPC = {
  bun: RPCSchema<{
    requests: {
      getNotes: { args: {}; response: Note[] };
      saveNote: { args: { id: string; title: string; content: string }; response: { success: boolean } };
      deleteNote: { args: { id: string }; response: void };
    };
    messages: {
      userIdleDetected: {};
    };
  }>;
  webview: RPCSchema<{
    requests: {
      getSelectedText: { args: {}; response: string };
    };
    messages: {
      menuAction: { action: string; role?: string };
      noteUpdatedExternally: { id: string };
    };
  }>;
} & ElectrobunRPCSchema;

Bun Side — BrowserView.defineRPC

Bun 端 — BrowserView.defineRPC

typescript
import { BrowserView } from "electrobun/bun";
import type { AppRPC } from "../shared/rpc-schema";

const appRPC = BrowserView.defineRPC<AppRPC>({
  maxRequestTime: 10000, // ms — set higher for file dialogs, heavy DB ops
  handlers: {
    requests: {
      getNotes: async () => {
        return await db.getAllNotes();
      },
      saveNote: async ({ id, title, content }) => {
        await db.saveNote({ id, title, content });
        return { success: true };
      },
      deleteNote: async ({ id }) => {
        await db.deleteNote(id);
      },
    },
    messages: {
      userIdleDetected: () => {
        console.log("User idle, saving state...");
      },
    },
  },
});

// Pass rpc to BrowserView
const view = new BrowserView({ url: "...", rpc: appRPC });

// Send a message to renderer
view.rpc.send.menuAction({ action: "file-new" });

// Make a request to renderer
const selectedText = await view.rpc.request.getSelectedText({});
typescript
import { BrowserView } from "electrobun/bun";
import type { AppRPC } from "../shared/rpc-schema";

const appRPC = BrowserView.defineRPC<AppRPC>({
  maxRequestTime: 10000, // ms — set higher for file dialogs, heavy DB ops
  handlers: {
    requests: {
      getNotes: async () => {
        return await db.getAllNotes();
      },
      saveNote: async ({ id, title, content }) => {
        await db.saveNote({ id, title, content });
        return { success: true };
      },
      deleteNote: async ({ id }) => {
        await db.deleteNote(id);
      },
    },
    messages: {
      userIdleDetected: () => {
        console.log("User idle, saving state...");
      },
    },
  },
});

// Pass rpc to BrowserView
const view = new BrowserView({ url: "...", rpc: appRPC });

// Send a message to renderer
view.rpc.send.menuAction({ action: "file-new" });

// Make a request to renderer
const selectedText = await view.rpc.request.getSelectedText({});

Renderer Side — Electroview.defineRPC

渲染器端 — Electroview.defineRPC

typescript
// src/mainview/index.ts
import { Electroview } from "electrobun/view";
import type { AppRPC } from "../shared/rpc-schema";

const rpc = Electroview.defineRPC<AppRPC>({
  maxRequestTime: 10000,
  handlers: {
    requests: {
      getSelectedText: () => {
        return window.getSelection()?.toString() ?? "";
      },
    },
    messages: {
      menuAction: ({ action, role }) => {
        handleMenuAction(action ?? role ?? "unknown");
      },
      noteUpdatedExternally: ({ id }) => {
        reloadNote(id);
      },
    },
  },
});

// Make a request to bun
const notes = await rpc.request.getNotes({});

// Send a message to bun
rpc.send.userIdleDetected({});
typescript
// src/mainview/index.ts
import { Electroview } from "electrobun/view";
import type { AppRPC } from "../shared/rpc-schema";

const rpc = Electroview.defineRPC<AppRPC>({
  maxRequestTime: 10000,
  handlers: {
    requests: {
      getSelectedText: () => {
        return window.getSelection()?.toString() ?? "";
      },
    },
    messages: {
      menuAction: ({ action, role }) => {
        handleMenuAction(action ?? role ?? "unknown");
      },
      noteUpdatedExternally: ({ id }) => {
        reloadNote(id);
      },
    },
  },
});

// Make a request to bun
const notes = await rpc.request.getNotes({});

// Send a message to bun
rpc.send.userIdleDetected({});

Key Rules

关键规则

  • requests
    : bidirectional, async, returns a value. Use for: data fetching, file I/O, native dialogs.
  • messages
    : fire-and-forget, no return value. Use for: notifications, state updates, events.
  • maxRequestTime
    : always set explicitly. Default may be too low for:
    • File save dialogs: set ≥ 30000
    • Database operations: set ≥ 10000
    • Quick in-memory ops: 5000 is fine
  • Type safety: the schema type is shared — import it on both sides. Never duplicate the type definition.
  • Encryption: RPC uses AES-GCM. Keys are auto-generated per session. Don't bypass this.
  • requests
    :双向、异步并返回一个值。适用于:数据获取、文件 I/O、原生对话框。
  • messages
    :无需等待响应,不返回值。适用于:通知、状态更新、事件。
  • maxRequestTime
    :始终显式设置。以下场景的默认值可能过低:
    • 文件保存对话框:设置为 ≥ 30000
    • 数据库操作:设置为 ≥ 10000
    • 快速内存操作:设置为 5000 即可
  • 类型安全:schema 类型由双方共享——两端都应导入该类型。切勿重复定义类型。
  • 加密:RPC 使用 AES-GCM。密钥会为每个会话自动生成。请勿绕过此机制。

Common Gotchas

常见问题

  1. RPC not available yet: Wait for
    dom-ready
    event before calling
    view.rpc.request.*
    .
  2. maxRequestTime timeout: Error message is generic. If you see "RPC timeout", increase
    maxRequestTime
    .
  3. sandbox: true: Disables RPC entirely. Don't set sandbox on windows that need RPC.
  4. Schema mismatch: If bun and renderer import different schema types, calls silently fail. Use a single shared type file.
  5. Renderer not initialized:
    Electroview.defineRPC
    must be called before any
    rpc.request.*
    calls.
  1. RPC 尚不可用:调用
    view.rpc.request.*
    前,请等待
    dom-ready
    事件。
  2. maxRequestTime
    超时
    :错误消息比较通用。如果看到“RPC timeout”,请增加
    maxRequestTime
    。
  3. sandbox: true
    :会完全禁用 RPC。需要 RPC 的窗口不应设置 sandbox。
  4. Schema 不匹配:如果 bun 和渲染器导入的 schema 类型不同,调用会静默失败。请使用同一个共享类型文件。
  5. 渲染器尚未初始化:调用任何
    rpc.request.*
    前,必须先调用
    Electroview.defineRPC
    。