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:
@@ -349,7 +349,14 @@ export function useSync() {
|
||||
setMetaFor(sessionID, { loading: true })
|
||||
|
||||
try {
|
||||
const limit = options?.before ? HISTORY_MESSAGE_PAGE_SIZE : m.limit
|
||||
// A resync (no `before`) must fetch at least as many messages as we
|
||||
// already have on screen. Live events append to the store WITHOUT growing
|
||||
// m.limit, so reusing the stale m.limit here would under-fetch and make
|
||||
// the server hand back a spurious "older" cursor — surfacing a phantom
|
||||
// "load older" button for a session whose full history is already shown
|
||||
// (e.g. after a reconnect resync following a few new messages).
|
||||
const storeMessageCount = store.getState().message[sessionID]?.length ?? 0
|
||||
const limit = options?.before ? HISTORY_MESSAGE_PAGE_SIZE : Math.max(m.limit, storeMessageCount)
|
||||
let page = await fetchMessages(sessionID, limit, options?.before)
|
||||
|
||||
// Keep the initial page small for switch performance. Some sessions
|
||||
|
||||
Reference in New Issue
Block a user