Desktop client for the QXP messaging web app, built with Tauri v2 and TypeScript.
Clone with submodules, or initialize them after cloning:
git submodule update --init --recursivebun installOn NixOS, enter the prepared shell first:
nix developbun run devbun run buildscripts/tauri-build.sh, scripts/build-android.sh, and scripts/ios-build.sh automatically load a local .env file when present, so the packaged runtime can be injected consistently for Desktop, Android, and iOS.
Platform helpers are also available:
bun run build:mac
bun run build:win
bun run build:linuxThe packaged web client runtime payload (window.__QXP_RUNTIME__) can be generated in two ways:
- from
QXP_RUNTIME_CONFIG_URL, by fetching a remote production page and extracting its runtime payload - directly from environment variables during build
Supported environment variables:
QXP_RUNTIME_CONFIG_URL=https://example.com/
QXP_SERVER_ORIGIN=https://example.com
QXP_API_BASE_URL=https://example.com
QXP_WS_URL=wss://example.com/ws
QXP_TURN_URLS=turn:turn.example.com:3478?transport=udp,turns:turn.example.com:5349?transport=tcp
QXP_TURN_USERNAME=qxp-turn
QXP_TURN_CREDENTIAL=replace-me
QXP_RELAY_ONLY=true
QXP_CALLS_ENABLED=true
QXP_CALLS_UNAVAILABLE_REASON=QXP_RUNTIME_CONFIG_URL is optional. If it is omitted, client/scripts/sync-runtime-config.mjs builds client/dist/runtime-config.js directly from the other variables.
Example local .env:
QXP_SERVER_ORIGIN=https://chat.example.com
QXP_API_BASE_URL=https://chat.example.com
QXP_WS_URL=wss://chat.example.com/ws
QXP_TURN_URLS=turn:turn.example.com:3478?transport=udp,turns:turn.example.com:5349?transport=tcp
QXP_TURN_USERNAME=qxp-turn
QXP_TURN_CREDENTIAL=replace-me
QXP_RELAY_ONLY=true
QXP_CALLS_ENABLED=trueIn GitHub Actions, store public runtime values in repository/environment Variables and sensitive values such as QXP_TURN_CREDENTIAL in Secrets.
iOS builds require macOS with the full Xcode app installed.
nix develop
bun run ios:build --export-method developmentFor development on a simulator or device:
nix develop
bun run ios:dev -- --openAndroid builds are handled by scripts/build-android.sh through the package script:
bun run build:androidThe script automatically enters the Nix development shell with nix develop when it is not already running inside Nix. It also prepares the Android SDK/NDK environment, Rust Android targets, and Tauri build dependencies.
By default, bun run build:android builds a debug APK for aarch64:
bun run build:android
# equivalent default args: --debug --apk --target aarch64Debug APKs are signed with Android's debug key and are installable on development devices, but they are not suitable for release distribution.
To build a release APK:
bun run build:android -- --apk --target aarch64Release APK signing is configured through environment variables. scripts/build-android.sh loads a local .env file automatically if it exists, which can contain both Android signing settings and QXP runtime injection values. .env, keystores, and generated Gradle signing files are ignored by git.
Create a new local signing password and .env file:
ANDROID_SIGNING_PASSWORD="$(openssl rand -base64 48)"
mkdir -p "$HOME/.config/lqxp-client"
cat > .env <<EOF
LQXP_ANDROID_CREATE_KEYSTORE=1
LQXP_REWRITE_ANDROID_KEYSTORE_PROPERTIES=1
ANDROID_KEYSTORE_PATH=$HOME/.config/lqxp-client/lqxp-release.jks
ANDROID_KEYSTORE_PASSWORD='$ANDROID_SIGNING_PASSWORD'
ANDROID_KEY_ALIAS=lqxp
ANDROID_KEY_PASSWORD='$ANDROID_SIGNING_PASSWORD'
ANDROID_KEY_DNAME='CN=LQXP Client, OU=LQXP, O=LQXP, L=Unknown, ST=Unknown, C=XX'
EOF
chmod 600 .env
unset ANDROID_SIGNING_PASSWORDThen build a signed release APK:
bun run build:android -- --apk --target aarch64On the first release build, the script creates the keystore at:
~/.config/lqxp-client/lqxp-release.jks
and writes Gradle's generated signing configuration to:
src-tauri/gen/android/keystore.properties
The relevant variables are:
LQXP_ANDROID_CREATE_KEYSTORE=1
LQXP_REWRITE_ANDROID_KEYSTORE_PROPERTIES=1
ANDROID_KEYSTORE_PATH=/absolute/path/to/lqxp-release.jks
ANDROID_KEYSTORE_PASSWORD=change-me
ANDROID_KEY_ALIAS=lqxp
ANDROID_KEY_PASSWORD=change-me
ANDROID_KEY_DNAME=CN=LQXP Client, OU=LQXP, O=LQXP, L=Unknown, ST=Unknown, C=XXYou can also point .env to an existing keystore instead of generating a new one by setting ANDROID_KEYSTORE_PATH, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, and optionally ANDROID_KEY_PASSWORD.
Expected APK output locations include:
src-tauri/gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk
src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk
If release signing is not configured, Gradle may produce an unsigned release artifact such as:
src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release-unsigned.apk
If Gradle/Tauri fails with a WebSocket or IPC error such as failed to read CLI options or Connection refused, stop stale Gradle daemons and rebuild:
src-tauri/gen/android/gradlew --project-dir src-tauri/gen/android --stop
bun run build:androidThe script also disables the Gradle daemon for Android builds because Gradle daemons can keep stale Tauri IPC environment variables.
The GitHub workflow for simulator builds and signed IPA builds is documented in docs/tauri-ios.md.
This setup fetches QxChat from GitHub, imports its NixOS module, adds its package via overlay, and enables it system-wide.
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
qxchat-src = {
url = "git+https://github.com/lqxp/app.git?ref=main&submodules=1";
flake = false;
};
};
outputs = { nixpkgs, qxchat-src, ... }:
let
system = "x86_64-linux";
in {
nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
# QxChat module + package overlay
{
imports = [ "${qxchat-src}/nix/module.nix" ];
nixpkgs.overlays = [
(final: prev: {
qxchat = prev.callPackage "${qxchat-src}/nix/qxchat.nix" { };
})
];
programs.qxchat.enable = true;
}
./configuration.nix
];
};
};
}Then apply it with:
sudo nixos-rebuild switch --flake .#my-hostThe main Tauri capability is declared in src-tauri/capabilities/default.json.
It grants the bundled QXP web client access to core Tauri APIs, notification APIs for messaging, and restricted URL opening for QXP, mailto: and tel: links.
With "withGlobalTauri": true, the bundled page can use Tauri guest APIs through window.__TAURI__ when needed, including notifications.
Native media permissions for macOS are declared in src-tauri/Info.plist for camera and microphone access used by calls or voice features in the remote web app. Speaker output does not require a separate Tauri permission.
flake.nix include the Linux dependencies that Tauri expects on NixOS, including GTK, WebKitGTK 4.1, GLib, libsoup_3, librsvg, and the GIO networking module setup required by WebKit.
MIT