NoamVC v0.3 — 우리는 3,500줄을 삭제했고 앱이 더 좋아졌습니다

발행: (2026년 2월 18일 오전 05:34 GMT+9)
10 분 소요
원문: Dev.to

Source: Dev.to

NoamVC 스크린샷

🎙️ NoamVC란?

NoamVC피어‑투‑피어 암호화 음성 채팅 데스크톱 앱 — Discord와 비슷하지만 음성이 서버에 전송되지 않습니다. 오디오는 WebRTC를 통해 참가자 간에 직접 전송되며 종단‑대‑종단 암호화가 적용됩니다.

Tauri 2(Rust) + React 19 + TypeScript로 구축되었습니다. macOS(ARM + Intel)와 Windows에서 사용할 수 있습니다.

보안 모델 TL;DR

  • RTCPeerConnection을 이용한 P2P 오디오 — 서버 측 처리 전혀 없음
  • Insertable Streams + PBKDF2(256‑비트 키, 100 K SHA‑256 반복)로 E2E 암호화
  • HMAC‑SHA256 서명 시그널링 — 비밀이 Rust 바이너리에 내장되어 JS에 절대 노출되지 않음
  • IOTA Stronghold를 이용한 안전한 저장 — localStorage에 아무것도 저장되지 않음

처음 오셨나요? 전체 아키텍처 분석을 위해 시리즈의 이전 글들을 확인해 보세요.

🆕 v0.2.1 → v0.3.3에서 새로워진 점

1. 🚪 방 입장 시스템 (v0.3.0)

이번 사이클의 가장 큰 기능. 이전에는 방 코드를 아는 사람이라면 누구나 조용히 입장할 수 있었습니다. 이제 방 생성자가 호스트 역할을 하며 모든 입장 요청을 승인해야 합니다.

New user → knock → Host sees dialog → Admit / Deny

작동 방식

  • 방에 처음 들어간 사용자가 자동으로 호스트가 됩니다.
  • 이후 참가자는 명시적으로 입장 허가를 받을 때까지 “대기” 상태에 머뭅니다.
  • 호스트는 요청자의 이름과 아바타가 표시된 대화창을 보고 허가 / 거부 버튼을 선택합니다.
  • 거부된 사용자는 명확한 메시지를 받고 로비로 돌아갑니다.
  • 전부 시그널링 레벨에서 관리되며 — WebRTC 연결 흐름에는 전혀 변화가 없습니다.

이는 UX와 보안 측면에서 큰 개선입니다. 이전 모델에서는 방 코드를 알면 곧바로 방 안에 들어갈 수 있었지만, 이제는 인간 게이트키퍼가 존재합니다.

2. 🐛 인앱 버그 리포트 → Firestore (v0.2.1)

앱을 떠나지 않고도 버그를 보고할 수 있습니다:

type BugCategory = "crash" | "audio" | "connection" | "ui" | "other";
  • 제목, 설명, 카테고리 선택기가 있는 폼.
  • 실시간 검증 (제목 ≥ 3 자, 설명 ≥ 10 자).
  • Firestore 로 REST API를 통해 전송 — 무거운 Firebase SDK는 번들에 포함되지 않음.

지연 로드: 사용자가 대화창을 열 때만 Firebase 코드가 로드됩니다.

보안 주의 – Firebase API 키는 컴파일 시 Rust 바이너리에 삽입되며 JavaScript 번들에는 절대 나타나지 않습니다:

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

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

JS 측에서는 필요할 때 이 Tauri 명령을 호출하므로, 키는 Rust 바이너리 안에만 존재하고 프론트엔드로 흐르는 시점도 필요할 때만입니다.

3. 🗑️ 임베디드 서버 제거 — –3,546 LOC 삭제 (v0.3.1)

이전 버전에서는 로컬/LAN 개발을 위해 임베디드 시그널링 서버(Axum + Socketioxide)를 번들에 포함했습니다. 우리는 이를 완전히 없앴습니다.

제거된 내용

  • Axum, Socketioxide, tower 및 데스크톱 앱과 관련된 모든 Rust 의존성.
  • ‑3,546 라인의 코드.

대체된 내용

  • 시그널링 서버는 이제 독립형 Rust 서비스(Axum + Socketioxide)로 Railway에 배포됩니다. 동일한 기술이지만 별도 레포지토리이며 경계가 명확합니다.

이유

  • 임베디드 서버 때문에 컴파일 시간이 약 30 초 늘어났습니다.
  • 어느 서버가 실제로 동작하는지 개발자들이 혼란스러워했습니다.
  • 실제 LAN 사용 사례가 거의 없었습니다.
  • 제거함으로써 바이너리 크기가 줄고 공격 표면도 감소했습니다.

“가장 좋은 코드는 존재하지 않는 코드다.” — 현명한 누군가

4. 🔧 중복 피어 수정 (v0.3.1)

빠른 연결/해제 사이클 동안 동일한 피어가 참가자 목록에 두 번 나타나는 미묘한 레이스 컨디션이 있었습니다. 이번 수정에서는 WebRTC 훅에서 피어‑아이덴티티 추적을 올바르게 수행하도록 했습니다.

5. 🔒 Firestore용 CSP 업데이트 (v0.3.2)

버그 리포트를 추가한 뒤, Tauri의 콘텐츠 보안 정책(CSP)이 Firestore 요청을 조용히 차단했습니다. 필요한 소스를 다음과 같이 추가했습니다:

"connect-src": "... https://firestore.googleapis.com"

작은 변화이지만, Tauri의 CSP는 기본적으로 엄격합니다(좋은 점). 새로운 외부 엔드포인트를 추가할 때마다 명시적으로 허용 목록에 넣어야 합니다.

6. 🏗️ 3번의 릴리즈가 걸린 CI/CD 버그 (v0.3.2 → v0.3.3)

build.rs 스크립트가 로컬 빌드용 .env 파일에서 환경 변수를 읽었지만, GitHub Actions에서는 변수들이 OS 환경에만 존재해 rustc에 전달되지 않았습니다.

에러 내용

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

수정 방법build.rs에서 OS 환경 변수를 명시적으로 Rust 컴파일러에 전달하도록 변경:

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

그리고 워크플로에 비밀을 추가:

env:
  FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}

Tauri/Rust 개발자를 위한 핵심 교훈

env!()Cargo가 알고 있는 변수만 읽으며, 쉘에 존재하는 변수는 자동으로 전달되지 않습니다. 따라서 CI 환경에서는 명시적으로 전달해 주어야 합니다.

환경을 직접 설정합니다. CI가 일반적인 방법으로 환경 변수를 설정한다면, 그 차이를 메우기 위해 build.rs에서 cargo:rustc-env가 필요합니다. 이는 눈에 잘 띄게 문서화되지 않았으며 우리에게 큰 어려움을 주었습니다.

📊 이번 사이클의 수치

지표
Commits112
Pull Requests27
Issues Closed45
Downloads (macOS)3,842
Downloads (Windows)2,917
Active Users1,274
Bug Reports Submitted38
Average Session Length42 min

모든 데이터는 앱의 텔레메트리(옵트인)와 GitHub 통계에서 수집되었습니다.

📊 요약

변경된 파일: 51
추가된 라인: ~1,222
삭제된 라인: ~4,768

순: ‑3,546 라인 ✂️

우리는 작성한 코드보다 3.9× 더 많은 코드를 삭제했습니다 – 최고의 release.

🛠️ Stack

레이어기술
프론트엔드React 19, TypeScript 5.9, Vite 7
스타일Tailwind CSS 4, shadcn/ui
데스크톱Tauri 2 (Rust backend)
시그널링Rust (Axum + Socketioxide) → Railway
암호화Insertable Streams + PBKDF2, HMAC‑SHA256
스토리지IOTA Stronghold
CI/CDGitHub Actions → auto‑draft release
버그 보고서Firestore REST API

🔜 다음 예정

  • Push‑to‑talk – 소음이 많은 환경을 위한 대체 모드.
  • Per‑peer connection quality indicators – RTT, 패킷 손실, 지터가 참가자별로 시각화됩니다.
  • BLE data transmission – 근거리 장치 간 초국소, 서버리스 방 설정을 위한 대체 신호 채널로 Bluetooth Low Energy를 탐색합니다.
  • Linux support – AppImage / .deb 패키지.

🌐 Landing:

NoamVC는 현재 활발히 개발 중입니다. WebRTC, Tauri, 혹은 응용 암호학을 다루고 있거나, 다른 사람의 서버를 거치지 않는 음성 채팅을 원한다면 한 번 살펴보세요. 피드백과 기여를 환영합니다.

0 조회
Back to Blog

관련 글

더 보기 »