Problem
Summary
The desktop app cannot connect to self-hosted Fluxer instances. The browser handoff login flow fails with "Invalid or expired code" because the desktop app polls the official API server while the handoff code was generated on the self-hosted server's cache. Additionally, even when using the existing "custom Fluxer instance" option in the handoff modal, 6 separate hardcoded origin allowlists in the Electron main process block API proxy, WebSocket, media, and RPC connections from non-official origins.
Current Behavior
- User opens desktop app → loads official web app from
STABLE_APP_URL - User opens browser handoff modal → clicks "Open browser"
- Browser opens
{official_url}/login?desktop_handoff=1 - If user manually navigates to
https://my-instance.example.com/login?desktop_handoff=1 instead:- Browser generates handoff code on self-hosted server's Redis (
POST /auth/handoff/initiate) - Browser completes handoff on self-hosted server (
POST /auth/handoff/complete) - Desktop polls official server for handoff status (
GET /auth/handoff/{code}/status) - Code doesn't exist on official server → "Invalid or expired code"
Partial Implementation Already Exists
The
BrowserLoginHandoffModal already has a "I want to use a custom Fluxer instance" link (gated behind
IS_DEV || isDesktop() at
BrowserLoginHandoffModal.tsx:113) that:
- Accepts a custom API endpoint and validates it via
/instance discovery - Opens the custom instance's web app URL in the browser for login
- Polls the custom API for handoff status (via
customApiEndpoint parameter) - Calls
RuntimeConfigStore.connectToEndpoint() on success to switch future API calls
This is good groundwork, however, even when this flow succeeds, the session cannot continue because the Electron main process blocks all connections from non-official origins.
Hardcoded Origin Blockers
6 files in
src-electron/ define hardcoded origin allowlists that prevent custom servers from working:
|
src-electron/common/constants.ts:22-23 | STABLE_APP_URL, CANARY_APP_URL | All other files import from here |
src-electron/main/window.ts:42-53 | trustedWebOrigins = new Set([STABLE_APP_URL, CANARY_APP_URL]) | Blocks navigation, permissions, device access, HID, window.open for custom origins |
src-electron/main/api-proxy-server.ts:30-34 | ALLOWED_ORIGINS = [STABLE_APP_URL, CANARY_APP_URL] | Blocks API proxy requests from custom web app origins |
src-electron/main/ws-proxy-server.ts:28-35 | ALLOWED_ORIGINS = new Set([STABLE_APP_URL, CANARY_APP_URL]) | Blocks WebSocket/gateway connections → no real-time chat |
src-electron/main/media-proxy-server.ts:33-37 | ALLOWED_ORIGINS = [STABLE_APP_URL, CANARY_APP_URL] | Blocks avatar/banner/attachment loading |
src-electron/main/rpc-server.ts:29-33 | ALLOWED_ORIGINS = [STABLE_APP_URL, CANARY_APP_URL] | Blocks RPC navigation from custom origins |
The
BrowserWindow.loadURL() call at
window.ts:520-522 also unconditionally loads
STABLE_APP_URL or
CANARY_APP_URL.
Proposed solution
Proposed Solution
Tier 1: Make the Handoff Flow Work (Minimal)
The custom instance dialog in
BrowserLoginHandoffModal is nearly complete. To make it actually work end-to-end:
- Dynamically extend origin allowlists — When
RuntimeConfigStore.connectToEndpoint() succeeds, send the new origin to the main process via IPC. The main process adds it to trustedWebOrigins and notifies all proxy servers to accept it. - Persist the custom endpoint — Save the chosen instance in electron-store/localStorage so the user doesn't re-enter it on every login.
- Make the custom instance option more discoverable — Currently it's a small text link below the code input. Consider making it a first-class part of the login screen.
Tier 2: Server Selector on Login Screen (Better UX)
Add a server URL input directly on the login page (not buried in the handoff modal):
- Default: official Fluxer server (pre-filled, no input needed)
- Custom: user enters their instance URL, validated via
/instance discovery - Persisted per-account in
AccountStorage - All subsequent API/gateway/media calls route through the custom endpoints returned by
/instance
Tier 3: Full Custom Server Support (Complete)
- Support a
--server-url CLI flag or first-run server selection dialog - Load the custom server's web app directly in
BrowserWindow.loadURL() - All Electron-level proxy servers dynamically allow the configured origin
- Account switcher shows which server each account belongs to
Notes (optional)
Mobile App Applicability
This same pattern would apply to future mobile apps — the app needs a way to specify a custom API endpoint before authentication. The
RuntimeConfigStore +
/instance discovery pattern already handles endpoint resolution cleanly; the missing piece is surfacing the choice to users and removing hardcoded origin restrictions.
Environment
- Fluxer desktop app (Electron)
- Self-hosted instance with all services (API, gateway, media proxy, admin, MinIO)
- Instance discovery (
/api/instance) returns correct endpoints for the self-hosted deployment
Final notes and disclosures
I absolutely used Claude Code to help me with these findings as well as write the issue. This app is fantastic, and I did notice that you folks did mention that it's not
fully ready for self hosting yet. So this issue may be pre-mature. But I wanted to at least get this up here first. May be something you're already tracking, may not be. But I checked to see if there were any issues similar to this and there isn't any. If you feel its not appropriate at this time, flame me and close it -- I got thick skin!!