# Fluxer Deep Linking Architecture: How It Handles Instance URL Switching

> Explore Fluxer's deep linking architecture and learn how it manages instance URL switching using a three-layer Electron design. Discover its efficient IPC handling for seamless origin reloads.

- Repository: [Fluxer/fluxer](https://github.com/fluxerapp/fluxer)
- Tags: architecture
- Published: 2026-03-17

---

**Fluxer implements deep linking through a three-layer Electron architecture that captures custom protocol URLs in the main process, exposes them to the renderer via a preload bridge, and validates instance switches through a dedicated IPC handler that reloads the window to the new origin.**

Fluxer is an Electron-based desktop application that supports custom protocol deep linking to enable seamless navigation and instance switching. According to the fluxerapp/fluxer source code, the deep linking architecture coordinates between the main process, preload scripts, and renderer to handle both cold starts and warm handoffs. This system allows users to open specific Fluxer instances via `APP_PROTOCOL://` links and dynamically switch between self-hosted deployments without restarting the application.

## Protocol Registration and Initial Link Capture (Main Process)

The deep linking flow begins in [`fluxer_desktop/src/main/DeepLinks.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/DeepLinks.tsx), where the application registers itself as the default handler for a custom protocol.

### Registering the Custom Protocol

The main process calls `app.setAsDefaultProtocolClient` during initialization to associate the operating system with the `APP_PROTOCOL://` scheme.

```typescript
// fluxer_desktop/src/main/DeepLinks.tsx
if (process.defaultApp) {
  app.setAsDefaultProtocolClient(APP_PROTOCOL, process.execPath, [process.argv[1]]);
} else {
  app.setAsDefaultProtocolClient(APP_PROTOCOL);
}

```

### Capturing URLs on Cold Start

When Fluxer launches from a deep link, the URL appears in `process.argv`. The main process scans for arguments starting with the registered protocol and stores the match in `initialDeepLink` for later retrieval.

```typescript
// fluxer_desktop/src/main/DeepLinks.tsx
const deepLinkArg = process.argv.find(arg => arg.startsWith(`${APP_PROTOCOL}://`));
if (deepLinkArg) initialDeepLink = deepLinkArg;

```

### Handling Warm Starts and Subsequent Links

For links received while the app is running, the main process listens for the `second-instance` event (Windows/Linux) and the `open-url` event (macOS). Each captured URL is forwarded to the renderer via the `deep-link` IPC channel, or cached if the window is not yet ready.

```typescript
// fluxer_desktop/src/main/DeepLinks.tsx
export function handleOpenUrl(url: string) {
  const mainWindow = getMainWindow();
  if (mainWindow && !mainWindow.isDestroyed()) {
    mainWindow.webContents.send('deep-link', url);
    showWindow();
  } else {
    initialDeepLink = url;
  }
}

```

## Renderer-Side Subscription via Preload Bridge

The renderer cannot access Electron APIs directly; instead, [`fluxer_desktop/src/preload/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/preload/index.tsx) exposes controlled functions through `contextBridge`.

### Exposing IPC Helpers

The preload script registers two critical methods: `getInitialDeepLink` retrieves the URL that launched the app, while `onDeepLink` subscribes to future deep link events.

```typescript
// fluxer_desktop/src/preload/index.tsx
onDeepLink: (cb) => {
  const handler = (_e, url) => cb(url);
  ipcRenderer.on('deep-link', handler);
  return () => ipcRenderer.removeListener('deep-link', handler);
},
getInitialDeepLink: () => ipcRenderer.invoke('get-initial-deep-link'),

```

### Consuming Deep Links in the UI

The renderer calls `electron.getInitialDeepLink()` once during startup to handle cold launches, then registers `electron.onDeepLink(callback)` to react to links received while the app is already open.

## Instance URL Switching Architecture

When a deep link points to a different Fluxer deployment, the application must validate the target and migrate the session. This logic resides in [`fluxer_desktop/src/main/IpcHandlers.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/IpcHandlers.tsx).

### Validating Target Instances

The `switch-instance-url` handler first normalizes the supplied URL to a fully-qualified origin, then fetches `/.well-known/fluxer` to verify the presence of required `endpoints.api` and `endpoints.gateway` fields.

```typescript
// fluxer_desktop/src/main/IpcHandlers.tsx
ipcMain.handle('switch-instance-url', async (_ev, opts) => {
  const instanceOrigin = normalizeInstanceOrigin(opts.instanceUrl);
  await assertValidFluxerInstance(instanceOrigin);
  // ... proceed with switch
});

```

### Executing the Switch

After validation, the handler updates the custom app URL via `setCustomAppUrl(instanceOrigin)` and reloads the main window to the new origin. If loading fails, the custom URL is cleared and the error propagates to the renderer.

```typescript
// fluxer_desktop/src/main/IpcHandlers.tsx
const mainWindow = getMainWindow();
pendingDesktopHandoffCode = opts.desktopHandoffCode ?? null;
setCustomAppUrl(instanceOrigin);
await mainWindow.loadURL(instanceOrigin);

```

## Summary

- **Protocol Registration**: [`fluxer_desktop/src/main/DeepLinks.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/DeepLinks.tsx) registers `APP_PROTOCOL://` and captures initial URLs from `process.argv`.
- **IPC Bridge**: [`fluxer_desktop/src/preload/index.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/preload/index.tsx) exposes `getInitialDeepLink` and `onDeepLink` to safely transport URLs from main to renderer.
- **Instance Validation**: The `switch-instance-url` handler verifies new deployments via `/.well-known/fluxer` before accepting the switch.
- **Dynamic Reload**: Validated switches trigger `mainWindow.loadURL()` to migrate the user to the new instance without manual restart.

## Frequently Asked Questions

### How does Fluxer capture deep links when the app is already running?

When a second instance is spawned or the OS emits an `open-url` event, the main process forwards the URL to the existing window via `mainWindow.webContents.send('deep-link', url)`. The renderer receives this through the `onDeepLink` callback registered in the preload bridge.

### What happens if a deep link targets an invalid Fluxer instance?

The `assertValidFluxerInstance` function fetches the well-known endpoint and validates the JSON structure. If the origin lacks the required `endpoints.api` or `endpoints.gateway` fields, the promise rejects before `loadURL` is called, preventing navigation to non-Fluxer URLs.

### Can the renderer directly access the initial deep link URL?

No. The renderer must invoke `electron.getInitialDeepLink()`, which returns a promise that resolves to the cached `initialDeepLink` value stored during main process startup. This isolation prevents untrusted code from accessing process arguments directly.

### Where is the instance switching logic implemented?

The `switch-instance-url` IPC channel is defined in [`fluxer_desktop/src/main/IpcHandlers.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_desktop/src/main/IpcHandlers.tsx). It coordinates URL normalization, instance discovery, and window reloading, ensuring the application only switches to verified Fluxer deployments.