Skip to content

Latest commit

 

History

History
143 lines (101 loc) · 8.67 KB

File metadata and controls

143 lines (101 loc) · 8.67 KB

Vector

Private messaging app built with Tauri v2 (Rust backend + vanilla JS frontend) on the Nostr protocol. Supports desktop (macOS, Windows, Linux) and Android.

Build & Run

npm run dev              # Desktop development (Tauri dev server)
npm run build            # Desktop release build
npm run dev:bare         # Dev without whisper feature (faster compile)
npm run build:bare       # Release without whisper feature
npm run android:dev      # Android dev (./scripts/android-dev.sh)
npm run android:build    # Android release (tauri android build)

Frontend build: node scripts/build-frontend.mjs copies src/ to dist/ with optional minification (terser + lightningcss in release).

Vector Core test suite: cd crates && cargo test -p vector-core.

Architecture

Vector Core (crates/vector-core/) — Single Source of Truth

All business logic lives here, fully decoupled from Tauri. Any client (GUI, CLI, SDK, bot) imports this crate.

  • macros.rs — log_info!, log_debug!, log_trace!, log_warn! (#[macro_export])
  • types.rs — Message, Attachment, Reaction, EditEntry, ImageMetadata, SiteMetadata
  • profile/ — Profile, ProfileFlags, SlimProfile (Box optimized, u16 interner handles)
    • profile/sync.rs — ProfileSyncHandler trait, SyncPriority queue, load_profile, update_profile, update_status, block/unblock, nickname, background processor
  • chat.rs — Chat, ChatType, ChatMetadata, SerializableChat
  • compact.rs — CompactMessage (u64 ms timestamps), CompactMessageVec, NpubInterner, TinyVec, bitflags
  • state.rs — ChatState, all globals (NOSTR_CLIENT, MY_SECRET_KEY, STATE, etc.), WrapperIdCache, processing gate
  • crypto/ — GuardedKey vault, GuardedSigner, Argon2id, AES-GCM, ChaCha20, decrypt_data, extension_from_mime, sanitize_filename, resolve_unique_filename, format_bytes, mime_from_magic_bytes, mime_from_extension (full MIME map)
  • db/ — SQLite schema, 20 atomic migrations, connection pools, RAII guards, settings KV
  • hex.rs — SIMD hex encode/decode (NEON ARM64, SSE2/AVX2 x86_64, scalar fallback)
  • rumor.rs — process_rumor() inbound message parser, RumorEvent, 11 result variants
  • stored_event.rs — StoredEvent, StoredEventBuilder, event_kind constants
  • sending.rs — SendCallback trait, SendConfig, send_dm/send_file_dm/send_rumor_dm, retry_send_gift_wrap
  • blossom.rs — File upload with progress tracking, retry, server failover
  • inbox_relays.rs — NIP-17 kind 10050 relay resolution, stampede-protected cache, gift-wrap sending
  • net.rs — SSRF protection, build_http_client
  • stats.rs — CacheStats, DeepSize trait for memory benchmarking (debug builds)
  • traits.rs — EventEmitter trait (abstracts UI notification), ProgressReporter

src-tauri consumes vector-core via path = "../crates/vector-core". Types and globals are re-exported — same instances, shared memory.

Tauri Shell (src-tauri/src/)

  • lib.rs — App entry, plugin registration, invoke_handler with 150+ commands
  • commands/ — Tauri command handlers (thin wrappers around vector-core logic)
  • state/ — Re-exports vector-core globals + local TAURI_APP + TauriEventEmitter (bridges emit_event to Tauri)
  • macros.rs — log_error! only (toast + log file via TAURI_APP; log_info/debug/trace/warn in vector-core)
  • rumor.rs — Thin wrapper: re-exports vector-core + parse_mls_imeta_attachments + process_rumor_with_mls + resolve_download_dir
  • message/ — Re-exports vector-core types + TauriSendCallback + file dedup logic
  • services/ — Event handler, subscription handler, notifications
  • mls/ — MLS group encryption via OpenMLS/MDK (not yet in vector-core)
  • miniapps/ — WebXDC-compatible mini apps (Tauri-specific: custom protocol, WebView, Iroh P2P)
  • android/ — JNI bindings, localhost media server, background sync
  • simd/ — SIMD image, audio, URL, HTML operations (hex moved to vector-core)

Frontend (src/)

  • main.js — Main application logic (~25k lines, bundled)
  • js/ — ES modules: chat-scroll, emoji, file-preview, marketplace, settings, voice, db, platforms/
  • styles.css — All styles (~7k lines)
  • index.html — Single-page app shell

Frontend communicates with backend via window.__TAURI__.core.invoke().

Key Patterns

Adding new Tauri commands

Every new #[tauri::command] requires THREE things:

  1. Permission TOML in src-tauri/permissions/autogenerated/<command_name>.toml (create allow- and deny- entries)
  2. "allow-<command-name-with-hyphens>" added to src-tauri/capabilities/default.json
  3. Registration in the invoke_handler macro in lib.rs

Missing any = invoke() silently rejects with "Command X not allowed by ACL".

SendCallback — Unified DM Send Pipeline

All DM sends (text + file) flow through vector-core's send_dm/send_file_dm/send_rumor_dm:

  • SendCallback trait — 7 lifecycle hooks (on_pending, on_sent, on_failed, on_upload_progress, on_upload_complete, on_attachment_preview, on_persist) with default no-ops
  • SendConfig — per-call config: max_send_attempts, retry_delay, self_send, cancel_token. Presets: gui() (12 retries), headless() (3), default() (1)
  • TauriSendCallback — emits to JS frontend + DB persistence
  • CliSendCallback — terminal output for sent/failed/progress
  • Text DMs: message() short-circuits to vector_core::send_dm with TauriSendCallback
  • File DMs: src-tauri handles dedup + upload, then calls vector_core::send_rumor_dm for gift-wrap + retry
  • MLS groups: stay in src-tauri (MDK dependency)

ProfileSyncHandler — Unified Profile Pipeline

All profile operations (fetch, publish, block, nickname) flow through vector-core's profile::sync module:

  • ProfileSyncHandler traiton_profile_fetched(slim, avatar_url, banner_url) with default no-op. Covers DB persistence + image caching.
  • TauriProfileSyncHandler — spawns db::set_profile + cache_profile_images
  • EventEmitter trait — abstracts UI notification. TauriEventEmitter bridges to TAURI_APP.emit(), registered at startup.
  • Profile ops in vector-core: load_profile, update_profile, update_status, block_user, unblock_user, set_nickname, get_blocked_users
  • Sync queue: SyncPriority (Critical/High/Medium/Low), ProfileSyncQueue, start_profile_sync_processor
  • src-tauri profile commands are one-line delegates to vector-core

State access

Global state lives in src-tauri/src/state/ and is re-exported at crate root:

  • TAURI_APP, NOSTR_CLIENT, MY_KEYS, MY_PUBLIC_KEY, STATE
  • STATE holds Arc<Mutex<AppState>> with chats, profiles, settings
  • Multi-account: separate SQLite DB per account in ~/.local/share/io.vectorapp/data/<npub>/

Error handling

All commands return Result<T, String>. Errors are string-formatted for frontend display.

Android-specific

  • WebView shouldInterceptRequest threads have NO tokio runtime — Handle::current() will PANIC. Use try_lock() with retry loops for STATE access from JNI threads.
  • Localhost media server (android/media_server.rs) serves files because asset:// doesn't support Range requests for audio/video.
  • rustls must use ring provider (not aws-lc-rs) — pkarr is patched in src-tauri/patches/pkarr/.

Compact messages

message/compact.rs defines CompactMessage / CompactMessageVec — a memory-optimized format using Box<str>, u16 npub interning, and [u8; 32] IDs instead of hex strings. Messages are stored in compact form in memory and converted to full Message structs for frontend serialization.

File attachments

Files are encrypted (NIP-96/Blossom), uploaded to media servers, and referenced via SHA-256 hash. The name field carries the original filename through the protocol. Downloads save with human-readable names + collision suffixes (-1, -2). Hash-based dedup prevents re-downloading identical content.

Dependencies

Key crates: nostr-sdk 0.44, tauri 2.10, tokio 1.49, rusqlite 0.32, openmls, iroh 0.96, iroh-gossip 0.96, aes-gcm, argon2, image 0.25

Local path deps: ../../mdk/crates/mdk-* (MDK media/encryption library)

wry fork: [patch.crates-io] in Cargo.toml points to local ../../wry for WKWebView background color fix.

Platform Notes

  • macOS: WKWebView white flash prevented via drawsBackground KVC on config (wry fork). Metal GPU for Whisper.
  • Linux: WEBKIT_DISABLE_DMABUF_RENDERER=1 set for WebKitGTK compatibility.
  • Android: API 26+. Vulkan GPU disabled for Whisper (device freeze). OpenSSL vendored.
  • Feature flag: whisper (default) — enables OpenAI Whisper transcription. Use --no-default-features to skip.