Simple Syncing Audiobook is a multiplatform app that syncs your listening position across devices using Convex. It is designed to be lightweight and low cost where users bring their own audiobook files.
See the architecture and upgrade notes.
- Desktop: Tauri 2 + Vite + React + shadcn/ui + Tailwind CSS
- Mobile (Android): Expo + React Native + NativeWind + gluestack-ui
- Backend: Convex (position sync, audiobook metadata)
- Shared: TypeScript revision protocol, durable outbox, content fingerprints, cloud access boundary
.
|- convex/ # Convex backend (schema, mutations, queries)
|- packages/shared/ # Shared logic (sync engine, types, checksum)
|- apps/desktop/ # Tauri 2 desktop app
`- apps/mobile/ # Expo React Native app
- Node.js 24+
- pnpm (
npm install -g pnpm) - A Convex account
- Rust toolchain (for Tauri desktop)
- Android SDK (for mobile)
Run from the repository root:
pnpm installMost users should choose hosted Google sign-in in the released app. Self-hosting is an advanced option for people who want to operate their own backend. It uses a shared access key and stores data in a Convex project you control.
Run this from the repository root:
npx convex dev --onceOn the first run, the Convex CLI prompts you to log in and create or select a
project. It then pushes this repo's schema and functions and writes deployment
details to .env.local.
Copy the Convex cloud URL for your deployment:
https://your-project-123.convex.cloud
You can find it in one of these places:
- the
CONVEX_URLvalue in the generated.env.local - the Convex CLI output after
npx convex dev --once - the Convex dashboard for your deployment
Set SYNC_ACCESS_KEY in your Convex deployment environment to a randomly
generated secret of at least 32 characters. Enter the same access key and
.convex.cloud URL on each device. Keys are kept in Windows Credential Manager,
macOS Keychain, Linux Secret Service, or mobile SecureStore. Linux requires an
unlocked Secret Service provider (such as GNOME Keyring or KWallet). Do not
paste the .convex.site URL; that site URL is only used for hosted Google
OAuth callbacks.
For a personal backend, leave REQUIRE_AUTH unset or set to anything other
than true. Setting REQUIRE_AUTH=true turns on hosted-auth behavior and will
make the unauthenticated "bring your own URL" flow fail.
Useful Convex commands:
| Command | When to use it |
|---|---|
npx convex dev |
Keep Convex running while developing backend changes |
npx convex dev --once |
Push backend changes once for local testing |
npx convex deploy |
Deploy backend changes to a production Convex deployment |
Self-hosted access fails closed without a configured key.
ALLOW_INSECURE_SELF_HOSTED=true explicitly restores the old unauthenticated
behavior for isolated development only; it is not the default. Never put an
access key in a public build environment variable or a Git commit.
cd apps/desktop
pnpm tauri devcd apps/mobile
npx expo prebuild
npx expo run:androidThe app supports two ways to connect:
- Sign in with Google: uses a shared hosted Convex deployment with authentication, per-user data isolation, and usage limits (200 audiobooks, 10 devices). Regular app users do not need to set up Convex for this mode.
- Bring your own Convex URL: the user creates their own Convex deployment
and enters its
.convex.cloudURL and access key. This mode gives the operator control over the synced data. All devices sharing a key share a library.
Both options appear on the setup screen. The Google option only shows in builds
where VITE_HOSTED_CONVEX_URL (desktop) or EXPO_PUBLIC_HOSTED_CONVEX_URL
(mobile) is configured.
- On first launch, sign in with Google or enter your own Convex deployment URL.
- Add audiobook folders on desktop, or select a folder / M4B file on mobile.
- Play audiobooks. Your position syncs automatically across connected devices.
- Work offline. Position is saved locally and synced when you're back online.
- Local position persists every 2 seconds
- Remote sync every 20 seconds while playing
- Immediate sync on: pause, chapter change, app background, app close
- Offline queue: latest position stored locally, flushed on reconnect
- Manual sync available via the Sync button
- Server revisions reject concurrent offline forks without relying on device clocks
- Choose this device or the other device when positions conflict
- Recent positions provides the last 20 server recovery points
- Previously signed-in accounts can open their local library while reconnecting
- Desktop playback continues when navigating to the library or settings
New imports are matched by SHA-256 fingerprints of their ordered file contents, independent of display names. Legacy imports retain their old IDs and progress. Different encodings and narrations are not automatically equivalent. Manual links assert that the files share the same timeline; link only compatible editions.
This section is for maintainers or distributors who want to build the app with the shared "Sign in with Google" option. Normal users who bring their own Convex URL can skip this section.
Use a separate Convex deployment for hosted Google sign-in. Do not reuse a personal self-hosted development deployment for the shared hosted backend.
Create a new Convex project at https://dashboard.convex.dev. Keep track of both deployment URLs:
https://your-hosted-project-123.convex.cloud
https://your-hosted-project-123.convex.site
Use the .convex.cloud URL in app build environment variables. Use the
.convex.site URL for Google OAuth redirect setup.
Create .env.hosted at the repo root:
CONVEX_DEPLOYMENT=dev:<your-hosted-deployment-name>Use the deployment name expected by the Convex CLI, not the full URL.
npx convex dev --once --env-file .env.hosted- Go to Google Cloud Console > Credentials.
- Create an OAuth 2.0 Client ID with type Web application.
- Add this Authorized redirect URI, using your hosted
.convex.siteURL:
https://your-hosted-project-123.convex.site/api/auth/callback/google
Leave Authorized JavaScript origins blank. Google redirects to Convex first; Convex then redirects back into the desktop or mobile app.
In the Convex dashboard for the hosted deployment, add:
| Variable | Value |
|---|---|
REQUIRE_AUTH |
true |
AUTH_GOOGLE_ID |
Your Google OAuth client ID |
AUTH_GOOGLE_SECRET |
Your Google OAuth client secret |
JWT_PRIVATE_KEY |
Generated Convex Auth private key |
JWKS |
Matching JWKS JSON for the private key |
SITE_URL |
http://tauri.localhost |
JWT_PRIVATE_KEY and JWKS must be a matching pair. One way to generate and
set the Convex Auth values is:
pnpm exec auth --deployment-name <your-hosted-deployment-name> --web-server-url http://tauri.localhostThis repo already contains the Convex Auth files, so review any file changes if you run the auth setup command. The important hosted deployment env vars are the ones listed above.
Keep SITE_URL as http://tauri.localhost for the desktop Tauri redirect. The
mobile deep link redirect is allowed in convex/auth.ts. Do not set SITE_URL
to the Convex cloud URL, Convex site URL, or Play Store URL.
In .env.local at the repo root:
VITE_HOSTED_CONVEX_URL=https://your-hosted-project-123.convex.cloud
EXPO_PUBLIC_HOSTED_CONVEX_URL=https://your-hosted-project-123.convex.cloudThese variables only enable the hosted Google sign-in option in local builds. They are not required for the personal "bring your own Convex URL" flow.
When developing against your personal backend, run:
npx convex devAfter backend changes that should also be available to hosted Google sign-in users, push the same Convex functions to the hosted deployment:
npx convex dev --once --env-file .env.hostedA GitHub Actions workflow automatically builds desktop installers (Windows, macOS, Linux) and the Android APK when a version tag is pushed. To create a release:
./scripts/bump-version.sh 1.0.0This single command:
- Updates the version in
package.json,apps/desktop/src-tauri/tauri.conf.json, andapps/mobile/app.json - Commits the version bump
- Creates a git tag (
v1.0.0) - Pushes the commit and tag to GitHub
The tag push triggers the release workflow, which builds and uploads:
| Platform | Artifacts |
|---|---|
| Windows | .msi, .exe (NSIS installer) |
| macOS (Apple Silicon) | .dmg |
| macOS (Intel) | .dmg |
| Linux | .deb, .AppImage, .rpm |
| Android | .apk |
Once builds finish, a draft release appears on the Releases page. Review it, edit the notes if needed, and click Publish.
Deploy to both the personal/self-hosted and hosted production deployments before tagging a release:
npx convex deploy
npx convex deploy --env-file .env.hostedRelease builds also require the GitHub Actions repository variable
HOSTED_CONVEX_URL to point at the hosted .convex.cloud URL, and the secret
CONVEX_DEPLOY_KEY_PROD to contain a production deploy key for the hosted
Convex project.
Update the backend before the apps, and upgrade all devices together. The schema upgrade is additive: existing libraries, links, and positions remain readable. Recording references and revision state are adopted transactionally on first use; there is no destructive bulk migration or required backfill. Tests exercise 0.x positions, old link chains, concurrent first writes, and deletion of linked roots.
Once a recording has a 1.0 revision, 0.x clients can read its position but their writes are rejected with an upgrade message. This prevents legacy timestamps from overwriting revision-controlled progress. Rolling clients back to 0.x will not restore writes to those recordings. Keep a database backup before operating any deployment rollback; do not remove the additive schema fields.
Self-hosted users must configure SYNC_ACCESS_KEY, deploy 1.0, then reconnect
clients using the URL and key. Upgraded clients with an old URL-only configuration
can still open local books while awaiting a key. Mobile imports are copied into
app documents and therefore need enough free disk space for the imported audio.
Existing imports are preserved; re-import a legacy mobile book if its picker
cache was removed by the operating system.
Run pnpm verify for all TypeScript checks, regression/integration tests, the
production desktop web build, and the Android JavaScript export. Run
cargo test --lib --locked in apps/desktop/src-tauri for native disk tests.
Release CI builds Windows, Linux, macOS (ARM and Intel), and the Android APK.
Physical-device audio routing and OS notification behavior still need device QA;
compile and mocked native-service tests cannot certify those behaviors.