Skip to content

feat: Discord widget — embedded Discord web with voice + screen share - #118

Merged
nishant merged 1 commit into
masterfrom
feat/discord-widget
Jul 14, 2026
Merged

nishant merged 1 commit into
masterfrom
feat/discord-widget

Conversation

@nishant

@nishant nishant commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • New Discord widget: the real discord.com web app embedded in an Electron <webview> on a dedicated persist:discord partition — servers, DMs & group chats, replying, voice channels, and screen sharing with an in-widget screens/windows picker. Zero setup: no Discord application, no keys — log in once inside the widget; the session survives restarts.
  • Why embed instead of a custom client: official Discord APIs cannot do user-account DMs/messaging/voice, the local RPC API is private-beta with no send-message command, and a user-token self-bot violates ToS (ban risk). Embedding the web app is the WebCord-proven, ToS-conformant path.

Main-process plumbing (apps/main/src/discord.ts)

  • persist:discord session: full clean-Chrome UA (the Nishboard/x app token alone trips Discord's unsupported-browser wall) + permission request and check handlers (Discord gates notifications on the sync Notification.permission read), scoped to *.discord.com.
  • Screen share: setDisplayMediaRequestHandler → desktopCapturer sources cross as data-URI thumbnails (discord:screenshare-* IPC, subscribe-while-mounted lifecycle, 60s deny timeout). Windows-only system audio via audio: 'loopback'; macOS shares video-only.
  • Hardening: webviewTag: true on main + popout windows, gated by an app-wide will-attach-webview guard (https *.discord.com src only, URL-parsed — lookalike hosts rejected; preload/nodeIntegration stripped). Guest window.open/off-site navigations open the system browser.

Renderer

  • Widget with loading shimmer, main-frame-only error card, mention badge from the guest title (N), Home/Reload/Sign-out header actions (sign-out clears the partition after an in-body confirm).
  • WidgetShell grows opt-in keepMounted: collapsing Discord hides the body in an h-0 overflow-hidden wrapper instead of unmounting — voice keeps running (display:none kills a webview guest).
  • macOS packaging: mac.extendInfo mic/camera usage strings (TCC would silently deny voice).

Tests

  • apps/main/src/discord.test.ts: UA shape, URL allowlist (incl. discord.com.evil.com rejection), permission matrix, capturer-source serialization.
  • apps/renderer/.../lib.test.ts: title-badge parsing. Electron stub grew session/desktopCapturer/app.on.
  • Full pnpm test / typecheck / lint green; dev boot smoke-tested.

Known caveats (documented in CLAUDE.md)

  • Unpin / pop-out toggle / quit remounts the webview → an active voice call drops (login persists).
  • Screen-share system audio is Windows-only; badge counts mentions/DMs only (Discord's (N) title prefix).

Manual verification checklist (needs a logged-in account)

  • Pin the widget → Discord login renders → log in → relaunch → still signed in
  • Join a voice channel → mic works, no permission popup
  • Screen share → picker → stream visible to a second account; Cancel + timeout behave
  • Collapse mid-voice → audio continues; expand repaints
  • External chat link → system browser; DM while unfocused → native toast + badge
  • Sign out → back to login; other widgets untouched; pop-out remounts signed-in

🤖 Generated with Claude Code

Embeds the real discord.com web app in an Electron <webview> on a dedicated
persist:discord partition — servers, DMs, replying, voice channels, and screen
share with an in-widget source picker, with zero API keys or Discord app setup
(official APIs can't do user-account features; self-bots risk a ToS ban).

Main-side plumbing (apps/main/src/discord.ts): clean-Chrome UA on the
partition, permission request+check handlers scoped to *.discord.com,
setDisplayMediaRequestHandler -> desktopCapturer picker over discord:* IPC
(Windows-only loopback system audio), and an app-wide will-attach-webview
guard (https discord.com only, preload/node stripped) now that webviewTag is
enabled on the main + popout windows.

WidgetShell grows an opt-in keepMounted prop: collapsing Discord hides the
body in an h-0 wrapper instead of unmounting, so voice keeps running
(display:none would kill the guest).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nishant
nishant merged commit 7286630 into master Jul 14, 2026
1 check passed
@nishant
nishant deleted the feat/discord-widget branch July 14, 2026 04:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant