Edit history

Earlier versions of Support connecting desktop (and mobile) apps to custom/self-hosted Fluxer instances, newest first.

Current version | Edited by Rex
Changes
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!!Removed: ### ChecksRemoved: Removed: - ☑ I searched for existing issues and didn't find a duplicate.Removed:
Show

Support connecting desktop (and mobile) apps to custom/self-hosted Fluxer instances

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

  1. User opens desktop app → loads official web app from STABLE_APP_URL
  2. User opens browser handoff modal → clicks "Open browser"
  3. Browser opens {official_url}/login?desktop_handoff=1
  4. 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:
FileWhat's HardcodedImpact
src-electron/common/constants.ts:22-23STABLE_APP_URL, CANARY_APP_URLAll other files import from here
src-electron/main/window.ts:42-53trustedWebOrigins = 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-34ALLOWED_ORIGINS = [STABLE_APP_URL, CANARY_APP_URL]Blocks API proxy requests from custom web app origins
src-electron/main/ws-proxy-server.ts:28-35ALLOWED_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-37ALLOWED_ORIGINS = [STABLE_APP_URL, CANARY_APP_URL]Blocks avatar/banner/attachment loading
src-electron/main/rpc-server.ts:29-33ALLOWED_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:
  1. 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.
  2. Persist the custom endpoint — Save the chosen instance in electron-store/localStorage so the user doesn't re-enter it on every login.
  3. 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!!
Original by Rex
Show

Support connecting desktop (and mobile) apps to custom/self-hosted Fluxer instances

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

  1. User opens desktop app → loads official web app from STABLE_APP_URL
  2. User opens browser handoff modal → clicks "Open browser"
  3. Browser opens {official_url}/login?desktop_handoff=1
  4. 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:
FileWhat's HardcodedImpact
src-electron/common/constants.ts:22-23STABLE_APP_URL, CANARY_APP_URLAll other files import from here
src-electron/main/window.ts:42-53trustedWebOrigins = 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-34ALLOWED_ORIGINS = [STABLE_APP_URL, CANARY_APP_URL]Blocks API proxy requests from custom web app origins
src-electron/main/ws-proxy-server.ts:28-35ALLOWED_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-37ALLOWED_ORIGINS = [STABLE_APP_URL, CANARY_APP_URL]Blocks avatar/banner/attachment loading
src-electron/main/rpc-server.ts:29-33ALLOWED_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:
  1. 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.
  2. Persist the custom endpoint — Save the chosen instance in electron-store/localStorage so the user doesn't re-enter it on every login.
  3. 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!!

Checks

  • ☑ I searched for existing issues and didn't find a duplicate.