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
@@ -273,21 +273,43 @@ export const settingsDict = {
'settings.remoteInstances.direct.state.empty': 'まだ他のサーバーが追加されていません。',
'settings.remoteInstances.clientAuth.title': 'このサーバーに接続',
'settings.remoteInstances.clientAuth.description': 'OpenChamber Desktop がこのサーバーに接続できるように、安全なリンクまたは Token を作成します。',
'settings.remoteInstances.clientAuth.field.labelPlaceholder': 'デバイス名(任意)',
'settings.remoteInstances.clientAuth.field.labelPlaceholder': 'デバイス名 — 例: My iPhone',
'settings.remoteInstances.clientAuth.actions.create': 'Token を作成',
'settings.remoteInstances.clientAuth.actions.pair': 'リンクを作成',
'settings.remoteInstances.clientAuth.actions.revoke': '無効化',
'settings.remoteInstances.clientAuth.actions.clearRevoked': '無効化済みをクリア',
'settings.remoteInstances.clientAuth.qrAlt': 'OpenChamber 接続 QR コード',
'settings.remoteInstances.clientAuth.qrEnlarge': 'QR コードを拡大',
'settings.remoteInstances.clientAuth.qrScanHint': '別のデバイスの OpenChamber アプリでスキャンしてください。1 回限りで期限切れになります。',
'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': 'この Token を今すぐコピーしてください。セキュリティのため、再表示されません。',
'settings.remoteInstances.clientAuth.state.loading': 'Token を読み込み中...',
'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': 'リレーを有効にする',