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': '기기 이름 — 예: My 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': 'OpenChamber connection QR code',
'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': '밖에서는 암호화 릴레이 연결도 허용',
'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': '연결됨 · 릴레이',
'settings.remoteInstances.clientAuth.lastUsed': '마지막 사용 {date}',
'settings.remoteInstances.clientAuth.neverUsed': '사용한 적 없음',
'settings.remoteInstances.relay.title': 'OpenChamber Relay',
'settings.remoteInstances.relay.autoHint': '릴레이로 기기를 페어링하면 자동으로 켜집니다.',
'settings.remoteInstances.relay.description': '포트를 열지 않고도 다른 기기가 어디서든 연결할 수 있습니다. 트래픽은 종단 간 암호화되어 릴레이는 내용을 읽을 수 없습니다.',
'settings.remoteInstances.relay.enableHint': '이 서버에서 릴레이를 켜기 전까지는 아무것도 공유되지 않습니다.',
'settings.remoteInstances.relay.actions.enable': '릴레이 켜기',