feat: pairing v2 — one-tap trusted devices over LAN and private relay (#2103)

Reworks how devices connect to an OpenChamber server, end to end.

Pairing v2:
- One-time pairing links/QR codes (openchamber://connect?v=2) carrying a set of transport candidates (LAN/tunnel/relay) and a single-use secret redeemed server-side; no tokens embedded in links
- Add-a-device dialog written for first-time users: intent-based transport choice (Anywhere / Home network only / This computer only) with plain-language descriptions, transparent fallback checkboxes, server-authoritative LAN detection, high-res QR dialog
- Private relay folded into pairing as a transport candidate with a demand-driven lifecycle (enables when a relay device is paired, disables when none remain)

Multi-transport devices:
- A saved device holds all its transports and one token; mobile re-probes on connect, resume, and network change and hot-switches LAN<->relay seamlessly (no re-pairing, no remount, session preserved)
- Desktop can import relay pairing links, switch to relay hosts through the E2EE tunnel, and restore a relay default host after relaunch

Device management:
- Device list (web + desktop) shows live per-device connectivity with the active transport (Connected - Local network / Relay) and platform badges (iOS/Android/macOS/Windows/Linux)
- One physical device = one record: stable per-install dedupe keys across pairing and password re-login; typed pairing label names the device, paired devices name the connection by the issuing server hostname
- Trusted desktop-local client manages all devices (list, revoke, clear revoked); relay host reaps dead client sockets after 3 missed keepalives

Android:
- LAN transport unblocked (cleartext + mixed content, mirroring iOS ATS exceptions); resume re-probe retries through network flux and silently auto-reconnects from a disconnected state
This commit is contained in:
Iuliia Ivashko
2026-07-10 00:12:33 +03:00
committed by GitHub
parent a1aae30e66
commit 91a95bfdaa
53 changed files with 4589 additions and 1369 deletions
@@ -1469,21 +1469,43 @@ export const settingsDict = {
'settings.remoteInstances.direct.state.empty': 'Nie dodano jeszcze innych serwerów.',
'settings.remoteInstances.clientAuth.title': 'Połącz z tym serwerem',
'settings.remoteInstances.clientAuth.description': 'Utwórz bezpieczny link lub token, aby OpenChamber Desktop mógł połączyć się z tym serwerem.',
'settings.remoteInstances.clientAuth.field.labelPlaceholder': 'Nazwa urządzenia (opcjonalnie)',
'settings.remoteInstances.clientAuth.field.labelPlaceholder': 'Nazwa urządzenia — np. Mój iPhone',
'settings.remoteInstances.clientAuth.actions.create': 'Utwórz token',
'settings.remoteInstances.clientAuth.actions.pair': 'Utwórz link',
'settings.remoteInstances.clientAuth.actions.revoke': 'Unieważnij',
'settings.remoteInstances.clientAuth.actions.clearRevoked': 'Wyczyść unieważnione',
'settings.remoteInstances.clientAuth.qrAlt': 'Kod QR połączenia OpenChamber',
'settings.remoteInstances.clientAuth.qrEnlarge': 'Powiększ kod QR',
'settings.remoteInstances.clientAuth.qrScanHint': 'Zeskanuj to aplikacją OpenChamber na drugim urządzeniu. Jednorazowy i wygasa.',
'settings.remoteInstances.clientAuth.qrDialogTitle': 'Zeskanuj, aby połączyć',
'settings.remoteInstances.clientAuth.actions.addDevice': 'Dodaj urządzenie',
'settings.remoteInstances.clientAuth.actions.copied': 'Skopiowano',
'settings.remoteInstances.clientAuth.addDevice.transportLabel': 'Gdzie będziesz używać tego urządzenia?',
'settings.remoteInstances.clientAuth.addDevice.subtitle': 'Utwórz jednorazowy kod QR, który połączy inne urządzenie z tym serwerem.',
'settings.remoteInstances.clientAuth.addDevice.transport.local': 'Tylko ten komputer',
'settings.remoteInstances.clientAuth.addDevice.transport.localHint': 'Dla aplikacji na tej samej maszynie.',
'settings.remoteInstances.clientAuth.addDevice.transport.lan': 'Tylko sieć domowa',
'settings.remoteInstances.clientAuth.addDevice.transport.lanHint': 'Łączy się bezpośrednio przez Wi-Fi. Nie działa poza tą siecią.',
'settings.remoteInstances.clientAuth.addDevice.transport.relay': 'Wszędzie',
'settings.remoteInstances.clientAuth.addDevice.transport.relayHint': 'Działa w domu i poza nim. Poza domem ruch przechodzi przez OpenChamber Private Relay — szyfrowany end-to-end tunel. Bez konfiguracji.',
'settings.remoteInstances.clientAuth.addDevice.fallback.relay': 'Zezwól też na szyfrowany relay poza domem',
'settings.remoteInstances.clientAuth.addDevice.fallback.preferLocal': 'Preferuj bezpośrednie połączenie domowe, gdy dostępne',
'settings.remoteInstances.clientAuth.addDevice.create': 'Utwórz kod QR',
'settings.remoteInstances.clientAuth.addDevice.done': 'Gotowe',
'settings.remoteInstances.clientAuth.pairingUrl': 'Link połączenia',
'settings.remoteInstances.clientAuth.createdToken': 'Skopiuj ten token teraz. Ze względów bezpieczeństwa nie zostanie pokazany ponownie.',
'settings.remoteInstances.clientAuth.state.loading': 'Ładowanie tokenów...',
'settings.remoteInstances.clientAuth.state.empty': 'Nie podłączono jeszcze żadnych urządzeń.',
'settings.remoteInstances.clientAuth.state.revoked': 'Unieważniony',
'settings.remoteInstances.clientAuth.state.thisDevice': 'To urządzenie',
'settings.remoteInstances.clientAuth.state.pending': 'Oczekiwanie na połączenie…',
'settings.remoteInstances.clientAuth.state.viaRelay': 'Relay',
'settings.remoteInstances.clientAuth.state.connectedDirect': 'Połączono · Sieć lokalna',
'settings.remoteInstances.clientAuth.state.connectedRelay': 'Połączono · Relay',
'settings.remoteInstances.clientAuth.lastUsed': 'Ostatnio użyto {date}',
'settings.remoteInstances.clientAuth.neverUsed': 'Nigdy nie użyto',
'settings.remoteInstances.relay.title': 'OpenChamber Relay',
'settings.remoteInstances.relay.autoHint': 'Włącza się automatycznie po sparowaniu urządzenia przez relay.',
'settings.remoteInstances.relay.description': 'Pozwól swoim innym urządzeniom łączyć się z dowolnego miejsca bez otwierania portów. Ruch jest szyfrowany od końca do końca — relay nie może go odczytać.',
'settings.remoteInstances.relay.enableHint': 'Nic nie jest udostępniane, dopóki nie włączysz relay na tym serwerze.',
'settings.remoteInstances.relay.actions.enable': 'Włącz Relay',