How the OpenWork Desktop App Communicates with the Server API Surface

The OpenWork desktop client communicates with the server through a type-safe Electron-IPC bridge that proxies HTTP requests and native commands through the main process, enabling secure cross-origin API calls and privileged operations.

The OpenWork application pairs a React-based renderer process with an Electron main process to access server functionality. When communication with the remote Den API is needed, the desktop app routes requests through a thin abstraction layer rather than calling fetch directly. This architecture enforces consistent authentication, handles CORS policies, and enables operations unavailable to browser contexts.

The Desktop IPC Contract Architecture

All communication between the renderer and main processes flows through a strictly typed Desktop IPC contract defined in packages/types/src/desktop-ipc.ts. This contract enumerates every command that the renderer can invoke, along with precise argument and return type shapes.

On the renderer side, src/app/lib/desktop.ts constructs a desktopBridge proxy object. Each property on this proxy becomes an async function that ultimately calls window.__OPENWORK_ELECTRON__.invokeDesktop(commandName, args). The proxy ensures compile-time type safety: the same contract file drives both the client-side method signatures and the main process handler implementations.

// From packages/types/src/desktop-ipc.ts — the shared contract
export interface DesktopCommands {
  __fetch: { args: [DesktopFetchRequest]; return: DesktopFetchResponse };
  engineInfo: { args: []; return: EngineInfo };
  workspaceCreate: { args: [WorkspaceCreatePayload]; return: Workspace };
  openDesktopPath: { args: [string]; return: void };
  // ... additional commands
}

HTTP Requests via desktopFetch

For server API communication, the renderer uses desktopFetch exported from desktop.ts. This function transparently handles two scenarios based on the target URL.

First, desktopFetch detects loop-back addresses (127.0.0.1, localhost, or [::1]). For these, it uses the standard browser fetch directly—no IPC overhead required.

For remote servers (including the Den API), desktopFetch serializes the request and passes it through the __fetch command:

// From apps/app/src/app/lib/desktop.ts — simplified desktopFetch implementation
export async function desktopFetch(
  input: RequestInfo | URL,
  init?: RequestInit
): Promise<Response> {
  const url = typeof input === "string" ? input : input.toString();
  
  // Direct browser fetch for loopback addresses
  if (isLoopback(url)) {
    return fetch(input, init);
  }
  
  // IPC-mediated fetch for remote APIs
  const response = await invokeDesktop("__fetch", {
    url,
    method: init?.method ?? "GET",
    headers: init?.headers,
    body: init?.body,
  });
  
  // Reconstruct a native Response object from IPC result
  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers: response.headers,
  });
}

The Main Process Request Handler

In apps/desktop/electron/main.mjs, the main process registers handlers for each command in the IPC contract. The __fetch handler performs the actual network request using Node.js fetch (or an equivalent), then returns a serializable response payload:

// From apps/desktop/electron/main.mjs — __fetch handler
ipcMain.handle("__fetch", async (_event, request) => {
  const { url, method, headers, body } = request;
  
  // Execute the network request in the privileged main process
  const response = await fetch(url, {
    method,
    headers: {
      ...headers,
      // Inject OpenWork authentication headers
      "Authorization": `Bearer ${getStoredToken()}`,
    },
    body,
  });
  
  // Return a structured object that the renderer can reconstruct
  return {
    status: response.status,
    statusText: response.statusText,
    headers: Object.fromEntries(response.headers.entries()),
    body: await response.arrayBuffer(),
  };
});

By executing requests in the main process, the desktop app:

  • Injects authentication tokens automatically without exposing them to renderer code
  • Bypasses CORS restrictions that would block browser fetch to the Den API
  • Enforces request policies such as allowed domains and rate limits

Bootstrap Configuration and API Discovery

The desktop client learns its server endpoints from the desktop bootstrap configuration injected at startup. The Electron main process reads desktop-bootstrap.json (schema defined in packages/install-config/src/index.ts) and exposes it to the renderer via:

window.__OPENWORK_ELECTRON__.meta.desktopBootstrap

This configuration object carries apiBaseUrl and other connection parameters that desktopFetch and other bridge commands use to construct complete request URLs.

// Accessing bootstrap configuration in renderer code
const { apiBaseUrl } = window.__OPENWORK_ELECTRON__.meta.desktopBootstrap;
const response = await desktopFetch(`${apiBaseUrl}/v1/workspaces`);

Complete Communication Flow

The OpenWork desktop app server communication follows this pipeline:

  1. Renderer invocation: Application code calls desktopFetch(url) or desktopBridge.commandName(args)
  2. IPC serialization: The proxy packages arguments and calls invokeDesktop(command, payload)
  3. Main process handling: Electron's ipcMain routes to the registered handler in main.mjs
  4. Network execution: For __fetch, the handler executes the HTTP request against the server API with injected credentials
  5. Response marshaling: Results serialize across IPC boundary; the renderer reconstructs native objects or returns typed data

High-level operations like workspace management, engine control, and skill installation follow identical patterns—each command name corresponds to a handler in the main process that may perform local filesystem operations, spawn child processes, or call remote APIs as needed.

Summary

  • Desktop IPC contract in packages/types/src/desktop-ipc.ts defines the type-safe command interface between renderer and main processes
  • desktopFetch in apps/app/src/app/lib/desktop.ts routes HTTP requests through the main process for remote URLs, using direct fetch only for loopback addresses
  • __fetch handler in apps/desktop/electron/main.mjs executes actual network requests with automatic authentication header injection
  • Bootstrap configuration from desktop-bootstrap.json supplies apiBaseUrl and connection parameters via window.__OPENWORK_ELECTRON__.meta
  • All commands follow the same invoke → IPC → handler → return pattern, enabling consistent error handling and type safety across the surface

Frequently Asked Questions

Why doesn't the OpenWork desktop app use browser fetch for all API calls?

Browser fetch is blocked by CORS policy when calling the Den API from the renderer process. By routing through the Electron main process via desktopFetch, the app executes requests in a privileged context that ignores CORS restrictions, while also enabling automatic injection of OpenWork bearer tokens without exposing them to renderer JavaScript.

How does the desktop app know which server API endpoint to contact?

The apiBaseUrl is provided through the desktop bootstrap configuration injected at application startup. The main process reads desktop-bootstrap.json and exposes it via window.__OPENWORK_ELECTRON__.meta.desktopBootstrap, which desktopFetch and other bridge methods reference to construct complete request URLs.

What happens if the main process fetch fails?

The __fetch handler in main.mjs returns the actual HTTP response status regardless of success or failure. The renderer reconstructs a native Response object with the original status, statusText, headers, and body, so calling code can check response.ok and handle errors exactly as with standard fetch. Network-level failures throw from the IPC layer and propagate as rejected promises.

Can the desktop bridge perform operations beyond HTTP requests?

Yes. The Desktop IPC contract includes commands for native operations unavailable to browser contexts. Examples include openDesktopPath for opening files in system applications, engineInfo for querying local engine status, and workspaceCreate for filesystem-level workspace initialization. Each command follows the same invokeDesktop pattern but executes arbitrary Node.js logic in the main process.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →