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
@@ -240,21 +240,43 @@ export const settingsDict = {
"settings.remoteInstances.direct.state.empty": "Інших серверів ще не додано.",
"settings.remoteInstances.clientAuth.title": "Підключення до цього сервера",
"settings.remoteInstances.clientAuth.description": "Створіть безпечне посилання або токен, щоб OpenChamber Desktop міг підключитися до цього сервера.",
"settings.remoteInstances.clientAuth.field.labelPlaceholder": "Назва пристрою (необов’язково)",
"settings.remoteInstances.clientAuth.field.labelPlaceholder": "Назва пристрою — напр. Мій iPhone",
"settings.remoteInstances.clientAuth.actions.create": "Створити токен",
"settings.remoteInstances.clientAuth.actions.pair": "Створити посилання",
"settings.remoteInstances.clientAuth.actions.revoke": "Відкликати",
"settings.remoteInstances.clientAuth.actions.clearRevoked": "Очистити відкликані",
"settings.remoteInstances.clientAuth.qrAlt": "QR-код підключення OpenChamber",
"settings.remoteInstances.clientAuth.qrEnlarge": "Збільшити QR-код",
"settings.remoteInstances.clientAuth.qrScanHint": "Скануй це застосунком OpenChamber на іншому пристрої. Одноразовий і має термін дії.",
"settings.remoteInstances.clientAuth.qrDialogTitle": "Сканувати для підключення",
"settings.remoteInstances.clientAuth.actions.addDevice": "Додати пристрій",
"settings.remoteInstances.clientAuth.actions.copied": "Скопійовано",
"settings.remoteInstances.clientAuth.addDevice.transportLabel": "Де ви будете користуватись цим пристроєм?",
"settings.remoteInstances.clientAuth.addDevice.subtitle": "Створіть одноразовий QR-код, який підключить інший пристрій до цього сервера.",
"settings.remoteInstances.clientAuth.addDevice.transport.local": "Лише цей компʼютер",
"settings.remoteInstances.clientAuth.addDevice.transport.localHint": "Для застосунків на цій самій машині.",
"settings.remoteInstances.clientAuth.addDevice.transport.lan": "Лише домашня мережа",
"settings.remoteInstances.clientAuth.addDevice.transport.lanHint": "Підключається напряму через ваш Wi-Fi. Поза цією мережею не працює.",
"settings.remoteInstances.clientAuth.addDevice.transport.relay": "Будь-де",
"settings.remoteInstances.clientAuth.addDevice.transport.relayHint": "Працює вдома і поза домом. Поза домом трафік іде через OpenChamber Private Relay — наскрізно зашифрований тунель. Нічого налаштовувати не треба.",
"settings.remoteInstances.clientAuth.addDevice.fallback.relay": "Також дозволити зашифрований relay поза домом",
"settings.remoteInstances.clientAuth.addDevice.fallback.preferLocal": "Віддавати перевагу прямому домашньому підключенню, коли доступне",
"settings.remoteInstances.clientAuth.addDevice.create": "Створити QR-код",
"settings.remoteInstances.clientAuth.addDevice.done": "Готово",
"settings.remoteInstances.clientAuth.pairingUrl": "Посилання для підключення",
"settings.remoteInstances.clientAuth.createdToken": "Скопіюйте цей токен зараз. З міркувань безпеки він більше не показуватиметься.",
"settings.remoteInstances.clientAuth.state.loading": "Завантаження токенів...",
"settings.remoteInstances.clientAuth.state.empty": "Жоден пристрій ще не підключено.",
"settings.remoteInstances.clientAuth.state.revoked": "Відкликано",
"settings.remoteInstances.clientAuth.state.thisDevice": "Цей пристрій",
"settings.remoteInstances.clientAuth.state.pending": "Очікує підключення…",
"settings.remoteInstances.clientAuth.state.viaRelay": "Relay",
"settings.remoteInstances.clientAuth.state.connectedDirect": "Підключено · Локальна мережа",
"settings.remoteInstances.clientAuth.state.connectedRelay": "Підключено · Relay",
"settings.remoteInstances.clientAuth.lastUsed": "Останнє використання {date}",
"settings.remoteInstances.clientAuth.neverUsed": "Ще не використовувався",
"settings.remoteInstances.relay.title": "OpenChamber Relay",
"settings.remoteInstances.relay.autoHint": "Вмикається автоматично, коли ти паруєш пристрій через relay.",
"settings.remoteInstances.relay.description": "Дозволяє вашим іншим пристроям підключатися звідки завгодно без відкриття портів. Трафік шифрується наскрізно — релей не може його прочитати.",
"settings.remoteInstances.relay.enableHint": "Нічого не передається, доки ви не увімкнете релей на цьому сервері.",
"settings.remoteInstances.relay.actions.enable": "Увімкнути Relay",