Files
dchain/desktop
vsecoder 82d3706e38 fix(desktop): auto-link device on sign-in + publish key on accept
Two bugs reported by the user:

1. After accepting a contact request on the desktop, the requester's
   "Send message" call errored with "no encryption key published" for
   the newly-accepted contact. Root cause: desktop never ran the
   device-registry bootstrap (mobile does it from _layout.tsx on
   sign-in) — so the desktop's X25519 pub was never published via
   LINK_DEVICE, and resolveRecipientKeys returned an empty list.

2. On the accepting device, the new chat didn't appear in Messages
   after tapping Accept — accept wrote the contact to store + disk
   but didn't switch sections, so the user was stuck in Contacts
   watching nothing happen.

Fixes:

  * hooks/useDeviceBootstrap — direct port of mobile's _layout.tsx
    bootstrap effect. On every sign-in:
      - fetchDevices(master) → if our X25519 is listed, mark local
        registered flag.
      - not listed + was registered before → REVOKED → wipe state +
        bounce to Welcome.
      - not listed + never registered → submit LINK_DEVICE. Tx may
        bounce if balance is zero; next launch retries.
    Mounted from App.tsx so it runs once per authenticated session.

  * RequestsList.accept — after submitting ACCEPT_CONTACT, check if
    OUR X25519 is in the on-chain registry. If not, submit LINK_DEVICE
    immediately (balance is now covered by the contact fee the peer
    paid us). This closes the window where the peer couldn't encrypt
    to us because our key wasn't published yet.
    Also: after a successful accept, setSection('messages') +
    setActiveChat(requester_pub), matching mobile's
    router.replace('/chats/<pub>') flow.

  * Conversation.send — nicer error copy when
    resolveRecipientKeys returns []. Was: "recipient has no
    encryption key published". Now: actionable text asking the peer
    to re-open their app so the LINK_DEVICE tx commits.
2026-04-22 19:13:29 +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.