Inspiration The problem statement hit differently when framed not as a technical challenge but as a human one: what happens to ordinary people when a regime severs every line of communication? No internet, no cellular, no independent broadcast. What remains is proximity — people still cross paths, devices still exist, and information still needs to move. We didn't want to build a chat app that works offline. We wanted to build something that thinks about information the way epidemiologists think about disease spread, the way ants coordinate without a queen, and the way wartime resistance cells passed messages without ever knowing the full network. That tension between the purely technical and the deeply human shaped every decision we made. What We Built Nexus Relay is an offline-first progressive web application where each device is an autonomous node in a self-organizing mesh. No central server coordinates anything. No user account exists. No message carries a sender. The network forms through movement and proximity. The core architecture is two genuine agents, not a pipeline dressed up as one: The Prioritization-Routing Agent runs continuously in the background, scoring every stored message using a formula that weighs urgency, freshness, relay weight, and a mild hop penalty. Messages are classified as hot, warm, or cold — not by a human, but by the network's collective behavior. A message that has been relayed fifteen times by strangers is hot. One that nobody has touched is cold. The agent uses this temperature to decide what travels first in every encounter window. The Sentinel is the device's immune system. It monitors the accelerometer continuously. Three hard shakes within two seconds triggers a full Level 3 wipe — IndexedDB deleted, localStorage cleared, all caches purged, page reloaded — in under 500 milliseconds. A PIN layer adds a Level 2 selective wipe that removes only hot and alert messages on three failed attempts, leaving the device looking partially used rather than wiped. If authorities seize a device, it stops being a node. The propagation model borrows from stigmergy. Every message carries a weight field that increments by 0.15 on every relay, capped at 10. Eviction under storage pressure removes lowest-weight messages first, never alerts, never hot messages. The network self-organizes around what matters without any device making a deliberate judgment call — high-weight messages survive because many people passed them on, not because any authority decided they were important. Three transfer channels, each serving a different scenario: Snapshot QR encodes compressed message bundles directly into a QR image using pako deflate. Two devices never need to connect — one shows a QR, the other scans it, messages transfer. This is the physical drop point mechanism: a QR printed on a wall encodes the most critical local information. Anyone who scans it carries that information forward. WebRTC with room code signaling enables live bidirectional sync. Both devices generate a 4-digit room code, exchange WebRTC SDP offers and answers through a minimal Upstash Redis signaling relay, and establish a direct peer-to-peer data channel. Once connected, a bloom filter exchange happens first — each device sends a 2KB probabilistic fingerprint of its message inventory. The routing agent uses the peer's bloom filter to send only messages the other device probably doesn't have, making every connection window count even when bandwidth is scarce. Audio broadcast and capture is the channel that requires no connection at all. A device plays a message through Speech Synthesis at rate 0.9, lang en-IN, through any connected speaker including Bluetooth. A nearby device taps Listen, Speech Recognition captures and transcribes it, the user reviews and corrects the transcript, and it enters the network as a low-confidence audio message. The air is the transmission medium. No pairing, no camera, no network. A Bluetooth speaker in a public space becomes a broadcast point that anyone walking past can receive from.
The message schema is the heart of everything: id — SHA-256 hash of type+created_at+payload, first 16 hex chars type — text | audio | alert priority — 1 to 5, immutable after creation ttl — Unix timestamp, hour-resolution, hard expiry created_at — Unix timestamp, hour-resolution, never modified after creation hop_count — increments on every ingest at a new device weight — starts at 1.0, +0.15 per relay, max 10.0 payload — UTF-8 text only, audio stored as transcribed text confidence — high for text/alert, low for audio captures schema_version — always 1, unknown versions quarantined not rejected
No sender field. No device ID. No location. The privacy model is structural, not policy — there is no field to strip because it was never written. Conflict handling is made visible, not resolved. When two messages about the same topic contradict each other, both are retained. A confidence score derived from relay count and age surfaces the more-credible version at the top, but the user sees the conflict flagged in the UI. We don't pick a winner silently. Tampering is made visible rather than invisible. How We Built It Next.js 16 PWA with a Service Worker caching the app shell for fully offline operation. IndexedDB via a custom repository layer with a memory fallback for environments where IndexedDB is restricted. All pipeline logic in TypeScript with a strict schema — no any casts, every message field validated on ingest. The bloom filter is implemented from scratch in about 60 lines using FNV-1a hash functions, no library. WebRTC uses no STUN servers — purely local host candidates on the shared network. The signaling relay is a single Upstash Redis route with 60-second key TTL, serving both offer and answer without deleting on read to tolerate retries. The agent runtime runs two continuous loops: the scoring loop every 15 seconds updating temperature and score for every stored message, and the expiry sweep every 60 seconds deleting messages past their TTL. Both run independent of any user action. The device is always ready for the next encounter. Challenges The hardest technical problem was WebRTC signaling without infrastructure. WebRTC needs an out-of-band channel to exchange SDP offer and answer strings before the peer connection can open. In a normal deployment a signaling server on the internet handles this in milliseconds. Our constraint was zero internet. We went through three approaches — QR-based signaling (too much friction, cameras required, timing-dependent), local hotspot HTTP (requires a TCP server, impossible in a pure PWA browser context), and finally a minimal Upstash Redis relay that the app reaches during the handshake only. The data channel itself is peer-to-peer after that. The signaling server sees two compressed SDP blobs for 60 seconds then forgets them. No message content ever leaves the devices. The Mixed Content error cost us significant time. The app deployed on Vercel over HTTPS cannot make HTTP requests to a local signaling server. Every attempt to use a local IP for signaling was blocked at the browser level regardless of CORS configuration. The solution was accepting that signaling requires a tiny internet dependency for the handshake, while keeping all message data strictly local. The hydration mismatch between server-rendered HTML and client state was another sharp edge. Any state initialized from localStorage — the PIN hash, the signaling host, the profile preferences — caused the server and client renders to diverge. The fix was a mounted guard that renders null on the server pass and lets the client render catch up immediately after, eliminating the mismatch without sacrificing the first-load experience. Designing the Sentinel required thinking about irreversibility carefully. A false positive wipe is catastrophic — the user loses all their stored messages and network context permanently. We graduated the response: inactivity triggers a lock, PIN failures trigger selective wipe of only high-risk messages, and triple shake triggers full wipe. The full wipe has no confirmation dialog by design. In the scenario this system exists for, the 500 milliseconds saved by skipping confirmation is not a UX decision — it is a safety decision. What We Learned The most important lesson was that the interesting design decisions in this space are not technical. They are about threat models, human behavior under duress, and the cost of false positives versus false negatives. Every feature had a corresponding adversarial case — QR codes can encode false information, audio broadcast can be jammed or faked, bloom filters have false positive rates that cost bandwidth. Understanding the system means understanding its failure modes as carefully as its success modes.
Log in or sign up for Devpost to join the conversation.