feat(desktop): let desktop users turn the relay off
The relay was on with no way to turn it off on Mac, Windows and Linux (TODO #134). #130 landed it in the shared src/bare.js and desktop picks the ENGINE up automatically - prepack.js vendors src/ - but the desktop renderer is a separate UI tree, so the control that shipped alongside it on mobile was never wired. That is the one setting a privacy-minded user is most likely to look for, so it should not have stayed mobile-only. Two pieces, as the item described: - getRelayStatus / setUseRelay added to the db map in src/ui-desktop/main.jsx - a Connection section in the desktop SettingsModal, gated on `relayStatus.configured` so a build with no relay wired shows nothing rather than a switch that controls nothing The copy is deliberately the same as mobile's, down to the sentence about the relay only ever carrying scrambled data it cannot read: same feature, same promise, and no reason for two devices to describe it differently. Both the in-use counters and the off-state explanation are carried over too. Verified on the running desktop app over CDP, driving the real UI rather than the IPC alone: - getRelayStatus returns configured:true, so the section renders - the CONNECTION heading, the toggle and the promise copy are all present in the Settings modal - clicking the toggle in the UI flips the worklet from useRelay=true to useRelay=false, and the off-state copy ("strictly device to device") appears - setUseRelay round-trips both ways and the status reflects it 336 unit tests still pass; this adds no pure decision to test, so the evidence is the round trip above rather than new unit tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HG8ayyquJuDMPSVLKDQKVh
This commit is contained in:
co-authored by
Claude Opus 5
parent
e2ff3cbba3
commit
3dc72b1ade
@@ -43,6 +43,10 @@ export function SettingsModal ({ tokens, profile, updateProfile, db, sync, event
|
||||
// auto-started at login. Default off; the truth lives in the OS login-item,
|
||||
// read here on open via db.getLaunchAtLogin (electron main intercept).
|
||||
const [launchAtLogin, setLaunchAtLogin] = useState(false)
|
||||
// TODO #134 - null until loaded; `configured: false` means this build has no
|
||||
// relay wired at all, in which case the section stays hidden rather than
|
||||
// offering a switch that controls nothing.
|
||||
const [relayStatus, setRelayStatus] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
function onKey (e) { if (e.key === 'Escape') onClose() }
|
||||
@@ -56,6 +60,25 @@ export function SettingsModal ({ tokens, profile, updateProfile, db, sync, event
|
||||
return () => { alive = false }
|
||||
}, [db])
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true
|
||||
db?.getRelayStatus?.().then(s => { if (alive) setRelayStatus(s ?? null) }).catch(() => {})
|
||||
return () => { alive = false }
|
||||
}, [db])
|
||||
|
||||
// Optimistic like the toggle above, and re-read afterwards so the counters
|
||||
// below reflect what the worklet actually did rather than what we asked for.
|
||||
async function handleUseRelayChange (next) {
|
||||
setRelayStatus(prev => prev ? { ...prev, useRelay: next } : prev)
|
||||
try {
|
||||
await db?.setUseRelay?.(next)
|
||||
const fresh = await db?.getRelayStatus?.()
|
||||
if (fresh) setRelayStatus(fresh)
|
||||
} catch (e) {
|
||||
setRelayStatus(prev => prev ? { ...prev, useRelay: !next } : prev)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLaunchAtLoginChange (next) {
|
||||
setLaunchAtLogin(next) // optimistic
|
||||
try {
|
||||
@@ -295,6 +318,40 @@ export function SettingsModal ({ tokens, profile, updateProfile, db, sync, event
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Connection — the off-LAN relay backstop (TODO #130 engine, #134 control).
|
||||
Wording deliberately mirrors mobile's: same feature, same promise. */}
|
||||
{relayStatus?.configured && (
|
||||
<div style={{ marginBottom: 18 }}>
|
||||
<div style={label}>Connection</div>
|
||||
<div style={row}>
|
||||
<div style={{ flex: 1, fontSize: 13 }}>Use a relay when direct fails</div>
|
||||
<ToggleSwitch tokens={tokens} value={relayStatus.useRelay !== false}
|
||||
onChange={handleUseRelayChange} />
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: tokens.muted, lineHeight: 1.5, paddingTop: 2 }}>
|
||||
Some networks block devices from connecting straight to each other. When
|
||||
that happens, PearCal routes through a relay run by PeerLoom. It only ever
|
||||
carries scrambled data it can’t read, and it’s only used after a direct
|
||||
connection has already failed.
|
||||
</div>
|
||||
{relayStatus.useRelay !== false
|
||||
&& (relayStatus.offers > 0 || (relayStatus.relaying?.successes ?? 0) > 0) && (
|
||||
<div style={{ fontSize: 12, color: tokens.muted, lineHeight: 1.5, paddingTop: 6 }}>
|
||||
Used since the app started: {relayStatus.offers} outgoing
|
||||
{(relayStatus.relaying?.successes ?? 0) > 0
|
||||
? `, ${relayStatus.relaying.successes} incoming`
|
||||
: ''}
|
||||
</div>
|
||||
)}
|
||||
{relayStatus.useRelay === false && (
|
||||
<div style={{ fontSize: 12, color: tokens.muted, lineHeight: 1.5, paddingTop: 6 }}>
|
||||
Off — connections stay strictly device to device. On a network that
|
||||
blocks them, syncing may not work at all.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: 18 }}>
|
||||
<div style={label}>Holidays</div>
|
||||
<div style={{ opacity: holidayWorking ? 0.6 : 1, transition: 'opacity 0.2s' }}>
|
||||
|
||||
@@ -101,6 +101,13 @@ const db = {
|
||||
putRsvp: (eid, mid, s, gids) => window.__pearDB.call('putRsvp', eid, mid, s, gids),
|
||||
getPrivateNote: (id) => window.__pearDB.call('getPrivateNote', id),
|
||||
putPrivateNote: (id, text) => window.__pearDB.call('putPrivateNote', id, text),
|
||||
// TODO #134 - the relay ENGINE was already here (prepack.js vendors src/, so
|
||||
// desktop picks up the shared worklet automatically), but the renderer is a
|
||||
// separate UI tree, so the relay was on with no way to turn it off. It is the
|
||||
// one control a privacy-minded user might actually want, so it should not have
|
||||
// stayed mobile-only.
|
||||
getRelayStatus: () => window.__pearDB.call('getRelayStatus'),
|
||||
setUseRelay: (on) => window.__pearDB.call('setUseRelay', on),
|
||||
getBlindPeerKey: () => window.__pearDB.call('getBlindPeerKey'),
|
||||
setBlindPeerKey: (k) => window.__pearDB.call('setBlindPeerKey', k),
|
||||
removeBlindPeerKey: () => window.__pearDB.call('removeBlindPeerKey'),
|
||||
|
||||
Reference in New Issue
Block a user