Files
dchain/desktop
vsecoder 3641cb113d fix(desktop): CSP via webRequest + boot error visibility
Two problems from the first alpha4 run reported as "blank window + CSP
warning in devtools":

1. CSP was set via <meta> in index.html with a strict policy (script-src
   'self'). Vite's dev server uses eval() for HMR, which the strict CSP
   blocked at module-load time, so the renderer never ran. The meta CSP
   also conflicted with Electron's own security heuristics (hence the
   warning even though *we* had a policy — Electron was looking for it
   on the HTTP response).

   Moved the CSP to electron/main.ts via session.webRequest
   .onHeadersReceived. Dev profile enables 'unsafe-eval' + ws:/wss: for
   HMR; production profile stays strict (no eval, no remote scripts,
   connect-src still wide because the user picks arbitrary node URLs).

2. When window.dchain isn't available (preload failed to load, dev
   misconfig, etc.), loadKeyFile() throws inside a useEffect. React
   swallows async-effect throws, so the app renders blank forever.

   Added:
     - requireDchain() guard in storage.ts with an explicit error.
     - App.tsx catches boot-effect errors and renders them inline.
     - ErrorBoundary.tsx for render-time throws.
     - window.addEventListener('error') in main.tsx as a last-resort
       paint for throws that escape React entirely.

Also: npm script electron:dev now rebuilds main.ts before spawning
Electron (was a silent concurrency bug — TypeScript errors in main.ts
would produce stale dist-electron/).
2026-04-22 17:13:26 +03:00
..

DChain Desktop

Electron shell for the DChain messenger and social feed.

Same functionality as the mobile client-app, re-imagined with a keyboard-first, 3-panel desktop layout:

┌──────────────────────────────────────────────────────────┐
│  DChain                                                   │ titlebar (drag)
├──────┬───────────────────┬────────────────────────────────┤
│ nav  │      list         │             detail             │
│ 72px │   340px fixed     │            flex 1              │
├──────┴───────────────────┴────────────────────────────────┤
│  ● online  ·  node.example:8080  ·  height 10942          │ status bar
└──────────────────────────────────────────────────────────┘

Sections (left rail): Messages · Feed · Wallet · Contacts · Settings · Profile.

Quick start

cd desktop
npm install
npm run dev          # concurrently: Vite dev server + Electron

The first boot will show the Welcome screen. Pick Create to generate fresh keys, or Import a node.json exported from the mobile client.

Build

npm run build        # produces dist/ (renderer) + dist-electron/ (main) + installers

Default installers are built with electron-builder: .dmg on macOS, NSIS .exe on Windows, AppImage + .deb on Linux. Adjust build.* in package.json for signing / notarisation.

Layout

  • electron/ — main + preload. TypeScript, compiled to dist-electron/ by tsc -p electron/tsconfig.json.
  • src/ — renderer. React + Vite. @/ aliases to src/.
  • src/shell/ — 3-panel chrome.
  • src/sections/ — one folder per nav section, each exports { List, Detail }.
  • src/auth/Welcome.tsx — shown when no key is loaded.
  • src/lib/ — api, storage, store, types. Mirrors (without React-Native deps) the relevant pieces of ../client-app/lib/.

Security model

Master Ed25519 priv lives in the OS keychain via Electron safeStorage (macOS Keychain / Windows DPAPI / libsecret). A renderer compromise cannot read or exfiltrate the key — it always travels through window.dchain.keyfile.* IPC, which main.ts validates and mediates.

contextIsolation: true, nodeIntegration: false. CSP in index.html pins script sources to 'self' while allowing connect-src * so the renderer can hit any node the user configures.

Pairing (v2.2.0-alpha5+)

Desktop will reuse the same 6-digit-code + relay-envelope handshake as the mobile client. The scaffold in src/auth/Welcome.tsx stubs the button until the polling loop lands.

Multi-device fan-out

When the node is at v2.2.0-alpha1+, lib/api.ts:fetchDevices returns every linked X25519 pub for a given identity; the sender then encrypts one envelope per device. Legacy nodes return an empty array and the client falls back to IdentityInfo.x25519_pub, preserving the pre-multi-device behaviour.