# How the OpenWork Desktop App Communicates with the Server API Surface

> Discover how the OpenWork desktop app securely communicates with the server API surface using a type-safe Electron-IPC bridge for proxied HTTP requests and native commands.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: architecture
- Published: 2026-08-01

---

**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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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.

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/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:

```typescript
// 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:

```javascript
// 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`](https://github.com/different-ai/openwork/blob/main/desktop-bootstrap.json) (schema defined in [`packages/install-config/src/index.ts`](https://github.com/different-ai/openwork/blob/main/packages/install-config/src/index.ts)) and exposes it to the renderer via:

```javascript
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.

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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.