DEV Community

Steven Hans
Steven Hans

Posted on

NoamVC v0.3 — We deleted 3,500 lines and the app got better

🎙️ What is NoamVC?

NoamVC is a peer-to-peer encrypted voice chat desktop app — think Discord, but your voice never touches a server. Audio travels directly between participants via WebRTC with end-to-end encryption.

Built with Tauri 2 (Rust) + React 19 + TypeScript. Available for macOS (ARM + Intel) and Windows.

TL;DR of the security model:

  • P2P audio via RTCPeerConnection — zero server-side processing
  • E2E encryption with Insertable Streams + PBKDF2 (256-bit key, 100K SHA-256 iterations)
  • HMAC-SHA256 signed signaling — secret embedded in Rust binary, never touches JS
  • Secure storage with IOTA Stronghold — nothing in localStorage

🆕 What's new in v0.2.1 → v0.3.3

1. 🚪 Room Admission System (v0.3.0)

The biggest feature in this cycle. Previously, anyone with the room code could join silently. Now the room creator acts as host and must approve every join request.

New user → knock → Host sees dialog → Admit / Deny
Enter fullscreen mode Exit fullscreen mode

How it works:

  • The first user to enter a room automatically becomes the host.
  • Subsequent participants enter a "waiting" state until explicitly admitted.
  • The host sees a dialog with the requester's name and avatar, plus admit/deny buttons.
  • Denied users get a clear message and return to the lobby.
  • Fully managed at the signaling level — zero changes to the WebRTC connection flow.

This is a significant UX and security improvement. In the previous model, knowing the room code was equivalent to being inside. Now there's a human gatekeeper.

2. 🐛 In-App Bug Reports → Firestore (v0.2.1)

Users can report bugs without leaving the app:

type BugCategory = "crash" | "audio" | "connection" | "ui" | "other";
Enter fullscreen mode Exit fullscreen mode
  • Form with title, description, and category selector.
  • Real-time validation (title ≥ 3 chars, description ≥ 10 chars).
  • Sent to Firestore via REST API — no heavy Firebase SDK bundled.
  • Lazy-loaded: Firebase code doesn't load until the user opens the dialog.

The interesting part security-wise: the Firebase API key is embedded in the Rust binary at compile time. It never appears in the JavaScript bundle:

const FIREBASE_API_KEY: &str = env!("FIREBASE_API_KEY");

#[tauri::command]
fn get_firebase_api_key() -> String {
    FIREBASE_API_KEY.to_string()
}
Enter fullscreen mode Exit fullscreen mode

The JS side calls this Tauri command on demand, so the key only exists in the Rust binary and only flows to the frontend when needed.

3. 🗑️ Embedded Server Removal — The -3,546 LOC Delete (v0.3.1)

Previous versions of NoamVC bundled an embedded signaling server in the Tauri binary (Axum + Socketioxide) for local/LAN development. We nuked it entirely.

What was removed:

  • Axum, Socketioxide, tower, and all related Rust dependencies from the desktop app.
  • -3,546 lines of code.

What replaced it:

  • The signaling server is now a standalone Rust service (Axum + Socketioxide) deployed on Railway. Same tech, separate repo, clean boundary.

Why?

  1. The embedded server added ~30s of extra compile time.
  2. It confused developers about which server was active.
  3. Nobody used it for LAN in practice.
  4. Removing it shrinks the binary and reduces the attack surface.

The best code is the code you don't have. - Someone wise

4. 🔧 Duplicate Peers Fix (v0.3.1)

A subtle race condition: under certain reconnection scenarios, the same peer could appear twice in the participant list. Root cause was the de-duplication logic in the WebRTC hook not accounting for rapid disconnect/reconnect cycles. Fixed with proper peer identity tracking.

5. 🔒 CSP Update for Firestore (v0.3.2)

After adding bug reports, Tauri's Content Security Policy was silently blocking requests to Firestore. The fix:

"connect-src": "... https://firestore.googleapis.com"
Enter fullscreen mode Exit fullscreen mode

Small change, but a reminder: Tauri's CSP is strict by default (which is good), and every new external endpoint requires explicit allowlisting.

6. 🏗️ The CI/CD Bug That Took 3 Releases to Fix (v0.3.2 → v0.3.3)

This one was frustrating. The build.rs script loaded env vars from .env for local builds, but in GitHub Actions the variables were in the OS environment and weren't being forwarded to rustc.

The error:

error: environment variable `FIREBASE_API_KEY` not defined at compile time
 --> src\desktop.rs:11:32
Enter fullscreen mode Exit fullscreen mode

The fix — explicitly forward OS env vars to the Rust compiler in build.rs:

for key in keys {
    if let Ok(val) = std::env::var(key) {
        println!("cargo:rustc-env={}={}", key, val);
    }
}
Enter fullscreen mode Exit fullscreen mode

Plus the workflow secret:

env:
  FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
Enter fullscreen mode Exit fullscreen mode

Key takeaway for Tauri/Rust devs:
env!() reads variables that Cargo knows about, not the shell environment directly. If your CI sets env vars in the usual way, you need cargo:rustc-env in build.rs to bridge that gap. This isn't documented prominently and bit us hard.


📊 Numbers for this cycle

Metric Value
Commits 8
Files changed 51
Lines added ~1,222
Lines deleted ~4,768
Net -3,546 lines ✂️

We deleted 3.9x more code than we wrote. The best kind of release.


🛠️ Stack

Layer Technology
Frontend React 19, TypeScript 5.9, Vite 7
Styles Tailwind CSS 4, shadcn/ui
Desktop Tauri 2 (Rust backend)
Signaling Rust (Axum + Socketioxide) → Railway
Encryption Insertable Streams + PBKDF2, HMAC-SHA256
Storage IOTA Stronghold
CI/CD GitHub Actions → auto draft release
Bug Reports Firestore REST API

🔜 What's next

  • Push-to-talk — alternative mode for noisy environments
  • Per-peer connection quality indicators — RTT, packet loss, jitter visualized per participant
  • BLE data transmission — exploring Bluetooth Low Energy as an alternative signaling channel for ultra-local, serverless room setup between nearby devices
  • Linux support — AppImage / .deb packages

🔗 Links


NoamVC is under active development. If you're working with WebRTC, Tauri, or applied cryptography — or just want a voice chat that doesn't route your audio through someone else's server — give it a look. Feedback and contributions welcome.

Top comments (0)