DS Chat Desktop packages DS Chat as a desktop application. It provides installable builds for Linux, Windows, and macOS while keeping the app connected to the DS Chat release stream.

Repository metadata and release assets are refreshed from Gitea during the static site build when the build environment has access to the repository.

Repository README

DS Chat Desktop is a cross-platform Electron client for DS Chat. It displays the hosted PWA rather than bundling a separate copy of the web frontend, so normal DS Chat deployments remain immediately available to desktop users.

The desktop shell adds:

  • A Windows notification-area, Linux system-tray, or macOS menu-bar icon.
  • A distinct unread icon, unread-message tooltip, and supported OS badges.
  • Close-to-tray behavior with normal window minimizing.
  • Persistent login sessions and single-instance handling.
  • Secure external-link and permission handling for hosted web content.
  • Native message notifications while the desktop process is running.
  • Background shell updates on signed Windows and macOS releases.
  • Native Windows, macOS, Flatpak, and Debian packages for x64 and ARM64 systems.

The production application ID is org.darksingularity.DSChat, and packaged builds always load https://chat.darksingularity.org.

How it works

The Electron main process owns the window, tray, security policy, lifecycle, and updater. The renderer is treated as untrusted remote content: Node integration is disabled, context isolation and Chromium sandboxing are enabled, unexpected navigation is denied, and external HTTP(S) links open in the system browser.

The preload exposes one optional, context-isolated API:

window.dsDesktop?.setUnreadCount(unreadMessageCount)
window.dsDesktop?.showNotification({ eventId, roomId, title, body })
const unsubscribe = window.dsDesktop?.onNotificationClick((roomId) => {
  // Navigate with the frontend router, then call unsubscribe when appropriate.
})

The main process accepts bridge messages only from the top-level frame at the configured DS Chat origin. It validates unread counts and every notification field before using them. Any nonzero unread count enables the desktop unread indicator. Notification requests cannot contain navigation URLs; clicks return only the validated room ID to the frontend. Requests are ignored while the window is visible and focused, and replayed event IDs are deduplicated for ten minutes.

The notification request contract is:

interface DesktopNotificationRequest {
  eventId: string // nonempty, at most 128 characters
  roomId: string  // nonempty, at most 128 characters
  title: string   // nonempty, at most 100 characters
  body: string    // may be empty, at most 500 characters
}

DS Chat should use its authenticated real-time message stream and existing notification preferences to decide when to call showNotification. In desktop mode it must not call PushManager.subscribe(), because Electron does not ship a Web Push delivery service. Ordinary browsers should retain the existing Web Push flow. Desktop notifications are available while the application is open or hidden in the tray, not after it has quit completely.

The website remains the authority for unread state, using its room list and existing unread_update WebSocket events. Ordinary browsers do not see or need the optional desktop bridge.

Closing the window hides it to the tray. Minimizing uses the platform’s normal taskbar or Dock behavior. Use the tray icon or Show command to restore DS Chat; use Quit to stop the background process. Linux tray behavior depends on the desktop environment’s StatusNotifierItem or legacy tray support.

Repository layout

src/                         Electron main process, preload, tray, and updater
assets/                      Application and platform tray artwork
scripts/                     Asset and release-feed generation
tests/                       Unit and update-feed integration tests
.gitea/workflows/            CI and tagged cross-platform releases
patches/                     Coordinated ds-chat frontend integration
release/                     Generated packages; ignored by Git
dist/                        Compiled Electron JavaScript; ignored by Git

Development

Node.js 22.12 or newer and npm are required.

npm ci
npm run typecheck
npm run lint
npm test
npm run build
npm run dev

npm run dev compiles the shell and loads the production DS Chat site. To test against a local frontend or fixture server, set DS_CHAT_URL before launching:

DS_CHAT_URL=http://localhost:5173 npm run dev

The override is intentionally ignored in packaged applications. To regenerate tray assets after editing scripts/generate-assets.mjs, run npm run assets. To create an unpacked application directory for the current platform, run:

npm run pack

Output is written under release/.

DS Chat frontend integration

The coordinated, build-verified frontend change is included at patches/ds-chat-unread-bridge.patch. Apply it from the root of the ds-chat repository before distributing the desktop application:

git apply --check /path/to/ds-chat-desktop/patches/ds-chat-unread-bridge.patch
git apply /path/to/ds-chat-desktop/patches/ds-chat-unread-bridge.patch
cd frontend
npm ci
npm test
npm run lint
npm run build

Deploy the updated website first. The desktop shell can load an older deployment, but its tray cannot receive authoritative unread state until the bridge is present.

Building packages

Install dependencies with npm ci before building any target. Electron Builder downloads Electron and platform packaging tools during the first build, so the build host needs internet access. Build each platform on its native OS; the release workflow uses native runners and should be considered the authoritative way to produce all artifacts.

Windows x64 and ARM64

Requirements:

  • Windows 10/11 or Windows Server build runner.
  • Node.js 22.12 or newer.
  • An optional Windows code-signing certificate for distributable releases.

Build both NSIS installers:

npm ci
npm run dist:win

Build only one architecture:

npm run build
npx electron-builder --win nsis --x64 --publish never
# or
npx electron-builder --win nsis --arm64 --publish never

Installers are written to release/ as DS-Chat-<version>-x64.exe and DS-Chat-<version>-arm64.exe. The assisted NSIS installer permits choosing the installation directory and creates Start Menu and optional desktop shortcuts.

For signing, expose the certificate and password only to the build process:

$env:CSC_LINK = "C:\secure\ds-chat-signing.pfx"
$env:CSC_KEY_PASSWORD = "certificate-password"
npm run dist:win

CSC_LINK may also be a base64 value or another format supported by Electron Builder. Never commit certificate material or passwords.

macOS universal

Requirements:

  • A current macOS build host with Xcode Command Line Tools.
  • Node.js 22.12 or newer.
  • Apple Developer ID credentials for signed/notarized distribution.

Build a universal application containing Intel and Apple Silicon binaries:

npm ci
npm run dist:mac

This produces DS-Chat-<version>-universal.dmg and a matching ZIP in release/. The ZIP is required by the macOS updater even when users install from the DMG.

Unsigned local test packages can be built with the same command. For a signed and notarized release, configure the standard Electron Builder environment and enable notarization:

export CSC_LINK=/secure/path/developer-id-application.p12
export CSC_KEY_PASSWORD='certificate-password'
export APPLE_ID='developer@example.com'
export APPLE_APP_SPECIFIC_PASSWORD='app-specific-password'
export APPLE_TEAM_ID='TEAMID1234'

npm run build
npx electron-builder --mac dmg zip --universal --publish never -c.mac.notarize=true

Automatic macOS updates require a signed application. Unsigned packages are for development and manual testing only.

Linux Flatpak and Debian packages (x86_64 and ARM64)

Requirements:

  • A Linux host matching the target architecture.
  • Node.js 22.12 or newer.
  • flatpak and flatpak-builder.
  • The Flathub remote, Freedesktop 25.08 runtime/SDK, and Electron BaseApp.

Package names differ by distribution; for Debian or Ubuntu build hosts, install the native tools first:

sudo apt update
sudo apt install flatpak flatpak-builder
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak install --noninteractive flathub \
  org.freedesktop.Platform//25.08 \
  org.freedesktop.Sdk//25.08 \
  org.electronjs.Electron2.BaseApp//25.08

Build both configured architectures only on a host/toolchain capable of both:

npm ci
npm run dist:linux

The normal and recommended approach is one native runner per architecture:

# x86_64 host
npm run build
npx electron-builder --linux flatpak deb --x64 --publish never

# ARM64 host
npm run build
npx electron-builder --linux flatpak deb --arm64 --publish never

Each native runner produces a Flatpak bundle and a .deb installer. The resulting files include release/DS-Chat-<version>-x86_64.flatpak, release/DS-Chat-<version>-aarch64.flatpak, release/DS-Chat-<version>-amd64.deb, and release/DS-Chat-<version>-arm64.deb.

Install or upgrade a local Flatpak bundle with:

flatpak install --user --reinstall release/DS-Chat-<version>-x86_64.flatpak
flatpak run org.darksingularity.DSChat

Install a Debian package on a matching architecture with:

sudo apt install ./release/DS-Chat-<version>-amd64.deb

Use the ARM64 filename on ARM64. These are standalone bundles, not a hosted Flatpak repository, so shell updates require installing a newer bundle manually. The hosted DS Chat website continues to update independently.

Automatic updates

Windows NSIS and macOS DMG/ZIP releases use electron-updater. The application checks shortly after startup and every four hours, downloads an available update in the background, and offers Restart Now or Later. It never forces a restart during an active session.

Updater metadata lives on the public update-feed branch and points to immutable Gitea release assets. Feeds are separated into win32-x64, win32-arm64, and darwin-universal. The release workflow publishes feed updates only when PUBLISH_AUTO_UPDATE=true and the corresponding signing credentials exist.

The desktop repository and release assets must be public before production clients are distributed. No Gitea access token is embedded in the application.

Gitea Actions releases

The CI workflow verifies every push and pull request. Pushing a vX.Y.Z tag runs tests, creates all platform packages, uploads a Gitea release, and advances eligible signed update feeds.

Expected runner labels:

  • linux-x64: Node 22.12, Flatpak, Flatpak Builder, curl, and jq.
  • linux-arm64: the same tools on native ARM64 hardware.
  • windows-x64 and windows-arm64: Windows with Node 22.12.
  • macos: macOS with Node 22.12 and Xcode Command Line Tools.

Repository secrets:

  • RELEASE_TOKEN: repository-scoped Gitea token allowed to create releases and push the update-feed branch.
  • WINDOWS_CSC_LINK and WINDOWS_CSC_KEY_PASSWORD.
  • MAC_CSC_LINK and MAC_CSC_KEY_PASSWORD.
  • APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID.
  • PUBLISH_AUTO_UPDATE=true only after Windows signing and Apple signing/notarization are operational.

Create a release by updating package.json and the lockfile to the intended version, committing that change, and pushing the matching tag:

npm version 0.2.0
git push origin main
git push origin v0.2.0

Unsigned artifacts are still useful for testing, but the workflow deliberately does not advertise them through automatic-update feeds.

Common problems

  • The window closes but DS Chat is still running: this is expected; choose Quit from the tray menu to exit.
  • No Linux tray icon: enable the StatusNotifier/AppIndicator extension or equivalent integration for the desktop environment.
  • flatpak-builder: not found: install both flatpak and flatpak-builder; installing only the Flatpak runtime is insufficient.
  • Electron reports an unsupported Node engine: use Node 22.12 or newer.
  • The unread icon never changes: deploy the companion ds-chat patch and confirm the page is loaded from the configured DS Chat origin.
  • macOS notifications or updates fail in an unsigned build: use a signed and notarized application for production testing.

License

DS Chat Desktop is licensed under the GNU Affero General Public License v3.0 only. See LICENSE for the full license text.

Third-party package licenses

The current lockfile contains 425 npm packages, all with declared license metadata. Direct third-party packages used by this project are:

PackageUseLicense
electron-updaterRuntime update clientMIT
@types/nodeNode.js type definitionsMIT
electronDesktop application runtimeMIT
electron-builderPackage and installer buildsMIT
oxlintTypeScript lintingMIT
sharpAsset generation image processingApache-2.0
typescriptTypeScript compilerApache-2.0
vitestUnit test runnerMIT
yamlUpdate-feed YAML generationISC

See package-lock.json for the complete transitive npm dependency set and its recorded license metadata.