feat(remote-control): viewer can control a desktop sharer's screen, with consent (0.1.13/batch70)
Fixes the core "viewer can't control the sharer even on desktop" gap. Three root
causes addressed:
- share.html DISCARDED every input-channel message (onmessage=()=>{}). It now
parses the viewer's mouse/keyboard events and forwards them to the desktop shell.
- The main desktop app had NO OS injector (it lived only in the separate agent).
Ported the nut-js injector (agent/input/inject.js) into desktop/input, wired an
inject IPC + injectInput bridge, HARD-gated behind a consent flag (rcArmed).
- /share runs in an iframe (no direct bridge access) → it postMessages input to
the top frame (home.html), which relays to the native bridge.
Consent + safety: the sharer sees an Allow/Deny prompt the first time the agent
interacts; while active a persistent "your screen is being controlled — Stop"
banner; instant revoke; auto-release on session end/teardown. Browser sharers stay
view-only (no OS injection possible). nut-js is an optionalDependency (N-API, ABI-
stable across Electron) — degrades to no-op if the native module is unavailable.
Windows-first; maps to the primary display.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -83,6 +83,22 @@ function avatarToTempPng(src) {
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Remote control: OS input injection for a screen the local user is SHARING ----
|
||||
// The renderer (share flow) forwards a viewer's mouse/keyboard events here for injection. Injection is
|
||||
// HARD-GATED behind an explicit consent flag (rcArmed): nothing is injected until the local user clicks
|
||||
// "Allow control", and it stops the instant they revoke or the session ends. nut-js is optional — if the
|
||||
// native module isn't present it degrades to a no-op (no crash), so control simply won't take effect.
|
||||
let injector = null;
|
||||
try { injector = require('./input/inject'); } catch (_) { injector = null; }
|
||||
let rcArmed = false;
|
||||
// The renderer arms/disarms control (mirrors the on-screen consent banner). Disarming releases any
|
||||
// stuck keys immediately.
|
||||
ipcMain.on('rc-arm', (_e, on) => { rcArmed = !!on; if (!rcArmed && injector && injector.releaseAll) { try { injector.releaseAll(); } catch (_) {} } });
|
||||
ipcMain.on('rc-input', (_e, evt) => { if (rcArmed && injector && injector.inject && evt) { try { injector.inject(evt); } catch (_) {} } });
|
||||
// Whether OS injection is even possible on this machine (native module loaded). The renderer uses this
|
||||
// to show "control needs the desktop app" vs an actual Allow prompt.
|
||||
ipcMain.on('rc-available', (e) => { e.returnValue = !!(injector && injector.available); });
|
||||
|
||||
// Pre-warm the DP cache for the renderer's contacts (called after chats load), so the FIRST
|
||||
// notification from anyone already has their photo — no per-toast download wait.
|
||||
ipcMain.handle('precache-avatars', async (_e, urls = []) => {
|
||||
|
||||
Reference in New Issue
Block a user