Vybe

A private, Android-first social app: a live friends map with consent-based location sharing, private photo sharing and end-to-end encrypted chat.

Status
Beta

APK 0.1.5 published 22 Oct 2026 with push; E2EE chat and push not yet confirmed on a physical phone

Role
Owner and developer: product decisions, architecture, the Android release builds and the server deployment.
Built with
Expo SDK 58, React Native, TypeScript, Kotlin, libsignal, Express 5, Prisma 7, PostgreSQL 17, Firebase Cloud Messaging, Docker, Caddy
System diagram of Vybe, drawn from its architecture notes. Vybe system diagram: Android app with the libsignal module talks over HTTPS and WebSocket to Caddy, which proxies to the Express API and PostgreSQL on one VM.

The problem

Location-sharing apps tend to share too much with too many people. Vybe keeps the circle to accepted friends, makes every share an explicit choice, and encrypts chat so the server only ever stores ciphertext.

How it works

  • The Android app (Expo dev build, expo-router, TanStack Query) talks to a single Express 5 API over HTTPS and an authenticated WebSocket for realtime fan-out.
  • Location runs as a foreground service with a 120 s target. The server decides who may see a point in one place, authz.ts, and purges locations after 24 hours.
  • Chat uses Signal's own libsignal through a Kotlin native module: PQXDH session setup and the Double Ratchet. Private keys never leave the device; history sits in SQLCipher.
  • The API, PostgreSQL 17 and media run in Docker Compose behind Caddy with automatic TLS on an Oracle Cloud VM. Over-the-air updates are code-signed.

Decisions that mattered

libsignal instead of custom crypto
A reviewed, maintained Signal implementation with PQXDH and the Double Ratchet. It makes the app AGPL-3.0.
One encrypted device per account
Multi-device Signal needs sender keys and device lists; getting it wrong breaks the security model.
Store-and-forward only for chat
Ciphertext is deleted on acknowledgement, and read receipts are encrypted control messages, so the server never learns read state.
No Redis, no PostGIS
One API process on one VM. Queries are 'latest point for these friends', never 'users near X'.

Problems I had to solve

Chat failed only in release builds

Every send failed in processBundle. Replaying a friend's real key bundle with desktop libsignal worked, so the keys were fine. The R8 mapping showed a libsignal callback type renamed by minification; adding keep rules fixed it.

Sessions looked up with ourselves

libsignal's SessionCipher and SessionBuilder take the local and remote addresses in opposite orders. The module passed them the same way to both. Fixed and verified in a full two-phone round trip.

Windows path limits broke the native build

React Native codegen paths exceeded 260 characters. Long paths plus a newer SDK CMake got the first signed release APK built on Windows.

What is verified, and what isn't

Verification status of each part of Vybe
PartState
79 server integration tests against real Postgres 17 (auth, friends, location, photos, E2EE via the API, realtime, stories, admin)Verified
API live over HTTPS with a Let's Encrypt certificate; only Caddy exposes portsVerified
Signed release APKs 0.1.0 to 0.1.5 and a code-signed OTA channelBuilt, not yet verified
E2EE chat, camera and background location on a physical phoneBuilt, not yet verified
Push notifications through Firebase (server key accepted by Google; not yet confirmed on a phone)Built, not yet verified
Google sign-in (needs an OAuth client)Blocked
Encrypted backups (need an age key pair)Blocked

Taken from the project's own status documents.

Still unfinished

  • Device testing of chat, camera, background location cadence and Keystore storage
  • Confirm push on a phone, and set up Google sign-in
  • Legal review of the privacy policy and terms before a public launch
  • Multi-device encryption (deliberately out of scope for v1)