9.3 KiB
9.3 KiB
Update this file when making significant changes.
See also: CONVEX_RULES.md | CONVEX_EXAMPLES.md
Architecture
- Monorepo: npm workspaces (
packages/*,apps/*) - Backend: Convex (reactive database + serverless functions)
- Frontend: React + Vite, shared codebase in
packages/shared/ - Platforms: Electron (
apps/electron/), Web (apps/web/), Android via Capacitor (apps/android/) - Platform Abstraction:
usePlatform()hook provides crypto, session, settings, idle, links, screenCapture, windowControls, updates APIs - Auth: Zero-knowledge custom auth via Convex mutations (getSalt, verifyUser, createUserWithProfile)
- Real-time: Convex reactive queries (
useQueryauto-updates all connected clients) - Voice/Video: LiveKit (token generation via Convex Node action)
- E2E Encryption: Platform-specific crypto (Electron: Node crypto via IPC, Web: Web Crypto API)
- File Storage: Convex built-in storage (
generateUploadUrl+getUrl)
Project Structure
Discord Clone/
├── convex/ # Backend (Convex functions + schema)
├── packages/
│ ├── shared/ # Shared React app (all components, pages, contexts, styles)
│ │ └── src/
│ │ ├── components/ # All UI components
│ │ ├── pages/ # Login, Register, Chat
│ │ ├── contexts/ # VoiceContext, ThemeContext, PresenceContext
│ │ ├── platform/ # PlatformProvider + usePlatform hook
│ │ ├── styles/ # themes.css
│ │ ├── utils/ # userPreferences.js, streamUtils.jsx
│ │ ├── assets/ # sounds, icons, emojis, fonts
│ │ ├── App.jsx # Router + AuthGuard
│ │ └── index.css # Global styles
│ └── platform-web/ # Web/Capacitor platform implementations
│ └── src/
│ ├── crypto.js # Web Crypto API (RSA-OAEP, Ed25519, AES-256-GCM, scrypt)
│ ├── session.js # localStorage session persistence
│ ├── settings.js # localStorage settings
│ ├── idle.js # Page Visibility API idle detection
│ └── index.js # Bundles all web platform APIs
├── apps/
│ ├── electron/ # Electron desktop app
│ │ ├── main.cjs # Electron main process
│ │ ├── preload.cjs # IPC bridge (window.* APIs)
│ │ ├── updater.cjs # electron-updater integration
│ │ ├── splash.html # Update splash screen
│ │ └── src/
│ │ ├── main.jsx # Entry: PlatformProvider + HashRouter
│ │ └── platform/index.js # Electron platform (delegates to window.* APIs)
│ ├── web/ # Web browser app
│ │ └── src/
│ │ └── main.jsx # Entry: PlatformProvider + BrowserRouter
│ └── android/ # Capacitor Android wrapper
│ └── capacitor.config.ts # Points webDir at ../web/dist
├── package.json # Root workspace config
├── .env.local # Convex + LiveKit + Tenor keys
└── CLAUDE.md
Key Convex Files (convex/)
schema.ts- Full schema: userProfiles (with avatarStorageId, aboutMe, customStatus, joinSoundStorageId), categories (name, position), channels (with categoryId, topic, position), messages, messageReactions, channelKeys, roles, userRoles, invites, dmParticipants, typingIndicators, voiceStates, channelReadState, serverSettings (serverName, afkChannelId, afkTimeout, iconStorageId)auth.ts- getSalt, verifyUser, createUserWithProfile, getPublicKeys (includes avatarUrl, aboutMe, customStatus, joinSoundUrl), updateProfile (includes joinSoundStorageId, removeJoinSound), updateStatus, getMyJoinSoundUrlcategories.ts- list, create, rename, remove, reorderchannels.ts- list, get, create (with categoryId/topic/position), rename, remove (cascade), updateTopic, moveChannel, reorderChannelsmembers.ts- getChannelMembers (includes isHoist on roles, avatarUrl, aboutMe, customStatus)channelKeys.ts- uploadKeys, getKeysForUsermessages.ts- list (with reactions + username), send, edit, pin, listPinned, remove (with manage_messages permission check)reactions.ts- add, removeserverSettings.ts- get (resolves iconUrl), update (manage_channels permission), updateName (manage_channels permission), updateIcon (manage_channels permission), clearAfkChannel (internal)typing.ts- startTyping, stopTyping, getTyping, cleanExpired (scheduled)dms.ts- openDM, listDMsinvites.ts- create, use, revokeroles.ts- list, create, update, remove, listMembers, assign, unassign, getMyPermissionsvoiceState.ts- join, leave, updateState, getAll (includes joinSoundUrl per user), afkMove (self-move to AFK channel)voice.ts- getToken (Node action, livekit-server-sdk)files.ts- generateUploadUrl, getFileUrlgifs.ts- search, categories (Node actions, Tenor API)readState.ts- getReadState, markRead, getAllReadStates, getLatestMessageTimestamps (unread tracking)
Shared Frontend (packages/shared/src/)
App.jsx- Router + AuthGuard (usesusePlatform().session)pages/Login.jsx- Convex auth (usesusePlatform().cryptofor key derivation)pages/Register.jsx- Convex auth (usesusePlatform().cryptofor key generation)pages/Chat.jsx- useQuery for channels, categories, channelKeys, DMscomponents/ChatArea.jsx- Messages, typing, reactions via Convex queries/mutationscomponents/Sidebar.jsx- Channel/category creation, key distribution, invites, drag-and-drop via @dnd-kitcontexts/VoiceContext.jsx- Voice state via Convex + LiveKit + idle detection viausePlatform().idlecomponents/TitleBar.jsx- Conditional: only renders ifplatform.features.hasWindowControlscomponents/UpdateBanner.jsx- Conditional: only renders ifplatform.features.hasNativeUpdatescomponents/ScreenShareModal.jsx- UsesusePlatform().screenCapturecomponents/MessageItem.jsx- Message rendering, link opening viausePlatform().linksplatform/PlatformProvider.jsx- React context providing platform APIs viausePlatform()hook
Platform Abstraction (usePlatform())
All platform-specific APIs are accessed via the usePlatform() hook:
crypto- generateKeys, randomBytes, sha256, signMessage, verifySignature, deriveAuthKeys, encryptData, decryptData, decryptBatch, verifyBatch, publicEncrypt, privateDecryptsession- save, load, clearsettings- get, setidle- getSystemIdleTime, onIdleStateChanged, removeIdleStateListenerlinks- openExternal, fetchMetadatascreenCapture- getScreenSourceswindowControls- minimize, maximize, close (Electron only, null on web)updates- checkUpdate (Electron only, null on web)features- hasWindowControls, hasScreenCapture, hasNativeUpdates
Important Patterns
- Channel IDs use Convex
_id(notid) - all references usechannel._id - Auth: client hashes DAK -> HAK before sending, server does string comparison
- First user bootstrap: createUserWithProfile creates Owner + @everyone roles
- Vite configs use
envDir: '../../'to pick up root.env.local - Convex queries are reactive - no need for manual refresh or socket listeners
- File uploads use Convex storage:
generateUploadUrl-> POST blob ->getFileUrl - Typing indicators use scheduled functions for TTL cleanup
- CSS uses Discord dark theme colors via
:rootvariables (--bg-primary: #313338,--bg-secondary: #2b2d31,--bg-tertiary: #1e1f22) - Sidebar width is 312px (72px server strip + 240px channel panel)
- Channels grouped by
categoryIdwith collapsible headers and @dnd-kit drag-and-drop - Members list groups by hoisted roles (isHoist) then Online/Offline
- Unread tracking:
channelReadStatetable per user/channel. ChatArea shows red "NEW" divider, Sidebar shows white dot - Server name from
serverSettingssingleton, editable via Server Settings (requiresmanage_channels) - AFK voice channel: VoiceContext polls idle time, auto-moves idle users
- Custom join sounds: stored as
joinSoundStorageIdonuserProfiles - Server icon:
serverSettingsstoresiconStorageId, resolved toiconUrl userPreferences.jssetUserPreftakes optionalsettingsparam for disk persistence via platform
Environment Variables
In .env.local at project root:
CONVEX_DEPLOYMENT- Convex deployment URL (set bynpx convex dev)VITE_CONVEX_URL- Convex URL for frontend (set bynpx convex dev)VITE_LIVEKIT_URL- LiveKit server URLLIVEKIT_API_KEY- LiveKit API key (used in Convex Node action)LIVEKIT_API_SECRET- LiveKit API secret (used in Convex Node action)TENOR_API_KEY- Tenor GIF API key (used in Convex Node action)
Running the App
npm install(installs all workspaces)npx convex dev(starts Convex backend, creates.env.local)- Electron:
npm run dev:electron - Web:
npm run dev:web - Android:
npm run build:web && cd apps/android && npx cap sync && npx cap open android