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.
- The running version and a Download Latest link in the tray menu.
- 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, and lifecycle. 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?.roomOpened()
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. An indicator lit by a desktop notification, rather than by the
website’s unread count, is cleared when the window gains focus or the frontend
calls roomOpened(); rooms the website still reports as unread keep it lit.
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, and tray
assets/ Application and platform tray artwork
scripts/ Asset generation
tests/ Unit tests
.gitea/workflows-disabled/ CI and release workflows (disabled; see 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, as
described under Releases.
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/.
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
Unsigned packages must be approved manually by users (right-click, then Open) and are suitable for development and manual testing.
Linux Flatpak and Debian packages (x86_64 and ARM64)
Requirements:
- A Linux host matching the target architecture.
- Node.js 22.12 or newer.
flatpakandflatpak-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.
Updates
DS Chat Desktop does not update itself, on any platform. The tray menu shows the running version and a Download Latest… item that opens the releases page in the system browser; install the newer package over the existing one. The hosted DS Chat website continues to update independently of the desktop shell.
Releases
Releases are built by hand. No Gitea Actions runners are registered, so the
workflows are kept in .gitea/workflows-disabled/ where Gitea does not run them
(moving them back to .gitea/workflows/ re-enables them, once runners with the
labels they expect exist). Before releasing, run npm run typecheck,
npm run lint, and npm test.
-
Update
package.jsonand the lockfile to the date-based version (YYYY.M.D), commit, push, and tag it:git tag -a vYYYY.M.D -m "DS Chat Desktop YYYY.M.D" git push origin main vYYYY.M.D -
Build each platform natively from the tagged source, as described under Building packages: Windows (
npm run dist:win, both architectures), macOS (npm run dist:mac), and Linux (npm run dist:linux, both architectures; the aarch64 Flatpak runtime and BaseApp must be installed to cross-build from x86_64). The repository requires a login, so copy the source to build hosts withgit archive vYYYY.M.Drather than cloning. -
Check the Debian package orders correctly before publishing:
dpkg-deb -f <file>.deb Versionmust print the version without a~, andapt-get -s install ./<file>.debmust report an upgrade (not a downgrade) over the previous release. -
Collect the nine artifacts (two Flatpaks, two
.debfiles, the x64 and ARM64.exeinstallers, the combinedDS-Chat-<version>.exe, and the universal macOS.dmgand.zip) and aSHA256SUMSfile, then create the Gitea release for the tag and attach them.
Windows and macOS packages are unsigned unless signing credentials are supplied (see the platform sections above). Installed clients do not check for updates; users get new versions from the releases page.
The disabled workflows predate the removal of the in-app updater and still
reference the deleted scripts/prepare-update-feed.mjs and update-feed
publishing. Edit them before re-enabling.
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 bothflatpakandflatpak-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-chatpatch and confirm the page is loaded from the configured DS Chat origin. - macOS notifications 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 420 npm packages, all with declared license metadata. Direct third-party packages used by this project are:
| Package | Use | License |
|---|---|---|
@types/node | Node.js type definitions | MIT |
electron | Desktop application runtime | MIT |
electron-builder | Package and installer builds | MIT |
oxlint | TypeScript linting | MIT |
sharp | Asset generation image processing | Apache-2.0 |
typescript | TypeScript compiler | Apache-2.0 |
vitest | Unit test runner | MIT |
See package-lock.json for the complete transitive npm
dependency set and its recorded license metadata.