# SMS Backup Viewer (SBV) - Technical Specification A privacy-focused, self-hosted web application for viewing SMS, MMS, and call log backups from the Android "SMS Backup & Restore" application. ## Overview SBV provides a modern conversation-based interface for browsing message backups while keeping all data local. Key design principles: - **Privacy-first**: No telemetry, no remote servers, 100% self-hosted - **Per-user isolation**: Each user has their own SQLite database - **Familiar UI**: Conversation threading similar to native messaging apps - **Media support**: Inline display of images, videos, and contact cards ## Technology Stack ### Backend - **Go 1.25** with Echo v4 web framework - **SQLite** with FTS5 for full-text search - **bcrypt** for password hashing - **libheif** (optional) for HEIC image conversion - **ffmpeg** for 3GP video support ### Frontend - **React 19** with React Router 7 - **Vite 7** for building - **Bootstrap 5** / React Bootstrap for styling - **Axios** for API communication ### Deployment - **Docker** with multi-stage Alpine-based builds - **Docker Compose** for easy deployment - **GitHub Actions** for CI/CD to GitHub Container Registry ## Project Structure ``` sbv/ ├── main.go # Entry point, route setup, server config ├── internal/ # Core backend logic │ ├── auth.go # User/session management │ ├── auth_handlers.go # Auth API endpoints │ ├── database.go # SQLite initialization, queries │ ├── handlers.go # Message/call API endpoints │ ├── parser.go # XML backup file parsing │ ├── models.go # Data structures │ ├── middleware.go # Auth middleware │ ├── settings.go # User preferences │ ├── heic_enabled.go # HEIC conversion (with libheif) │ └── heic_disabled.go # HEIC fallback (without libheif) ├── frontend/ │ ├── src/ │ │ ├── App.jsx # Main app with routing │ │ ├── components/ # React components │ │ │ ├── ConversationList.jsx # Contact/conversation list │ │ │ ├── MessageThread.jsx # Message display │ │ │ ├── Calls.jsx # Call log view │ │ │ ├── Search.jsx # Full-text search │ │ │ ├── Upload.jsx # XML file upload │ │ │ ├── Activity.jsx # Timeline view │ │ │ ├── LazyMedia.jsx # Lazy-loaded media │ │ │ ├── MediaGrid.jsx # Media gallery │ │ │ └── ... │ │ └── contexts/ │ │ └── AuthContext.jsx # Auth state management │ └── dist/ # Production build output ├── Dockerfile # Multi-stage build ├── docker-compose.yaml # Deployment config └── build.sh # Local build script ``` ## Architecture ### Authentication Flow 1. User registers with username/password (bcrypt hashed) 2. Login creates a session stored in the auth database 3. Session ID stored in httpOnly cookie (`sbv_session`) 4. Middleware validates session on protected routes 5. Each user gets a unique database file: `sbv_[user-uuid].db` ### Data Model **Auth Database (`sbv.db`)**: - `users` - User accounts with hashed passwords - `sessions` - Active sessions with expiration - `settings` - Per-user JSON preferences **Per-User Database (`sbv_[uuid].db`)**: - `messages` - Unified table for SMS, MMS, and calls - `record_type`: 1=SMS, 2=MMS, 3=Call - `type`: Message direction (1=received, 2=sent, etc.) - `media_data`: BLOB storage for attachments - `messages_fts` - FTS5 virtual table for search ### Message Import Pipeline 1. User uploads XML file via `/api/upload` 2. Server saves to temp file, returns immediately 3. Parser reads XML incrementally: - SMS messages parsed with metadata - MMS messages parsed with parts/attachments - Call logs parsed with duration/type 4. Records inserted with unique constraint (idempotent) 5. Client polls `/api/progress` for status ### Media Handling Media is stored as base64-encoded BLOBs in the `messages` table: - **Images**: JPEG, PNG, GIF, WebP, HEIC (converted to JPEG) - **Videos**: MP4, 3GP (converted to MP4 via ffmpeg) - **vCards**: Parsed and rendered as contact previews The frontend uses lazy loading (`LazyMedia.jsx`) to fetch media on-demand. ## API Reference ### Authentication (Public) | Method | Endpoint | Description | |--------|----------|-------------| | POST | `/api/auth/register` | Create account | | POST | `/api/auth/login` | Login, sets cookie | | POST | `/api/auth/logout` | Logout, clears cookie | ### User (Protected) | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/auth/me` | Current user info | | POST | `/api/auth/change-password` | Update password | | GET | `/api/settings` | Get user settings | | PUT | `/api/settings` | Update user settings | ### Messages & Data (Protected) | Method | Endpoint | Query Params | Description | |--------|----------|--------------|-------------| | GET | `/api/conversations` | `start_date`, `end_date` | List conversations | | GET | `/api/messages` | `address`, `start_date`, `end_date`, `limit`, `offset` | Messages for conversation | | GET | `/api/activity` | `start_date`, `end_date`, `limit`, `offset` | Timeline of messages + calls | | GET | `/api/calls` | `start_date`, `end_date` | Call log | | GET | `/api/search` | `q`, `start_date`, `end_date` | Full-text search | | GET | `/api/media` | `address` | All media for conversation | | GET | `/api/media-items` | `address` | Media items only (no data) | | GET | `/api/daterange` | - | Min/max dates in database | ### Upload (Protected) | Method | Endpoint | Description | |--------|----------|-------------| | POST | `/api/upload` | Upload XML backup (async) | | GET | `/api/progress` | Check upload status | ### System | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/health` | Health check | | GET | `/api/version` | App version | ## Database Schema ### messages table ```sql CREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, record_type INTEGER NOT NULL, -- 1=SMS, 2=MMS, 3=Call address TEXT, -- Phone number body TEXT, -- Message text type INTEGER, -- Direction (1=recv, 2=sent) date INTEGER, -- Unix timestamp (ms) read INTEGER, -- Read status thread_id INTEGER, -- Conversation thread contact_name TEXT, -- Resolved contact name -- MMS-specific msg_id TEXT, -- MMS message ID m_type INTEGER, -- MMS type media_type TEXT, -- MIME type media_data BLOB, -- Binary media -- Call-specific duration INTEGER, -- Call duration (seconds) -- Additional metadata service_center TEXT, protocol INTEGER, subject TEXT, ... ); -- Key indexes CREATE UNIQUE INDEX idx_message_unique ON messages(...); CREATE INDEX idx_address ON messages(address); CREATE INDEX idx_date ON messages(date); CREATE INDEX idx_thread ON messages(thread_id); CREATE INDEX idx_record_type ON messages(record_type); -- Full-text search CREATE VIRTUAL TABLE messages_fts USING fts5( body, address, contact_name, content='messages', content_rowid='id' ); ``` ## Configuration ### Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `PORT` | `8081` | Server port | | `DB_PATH_PREFIX` | `.` | Database directory | | `PUID` | `1000` | Docker user ID | | `PGID` | `1000` | Docker group ID | ### Build Tags | Tag | Description | |-----|-------------| | `fts5` | Enable full-text search (always used) | | `heic` | Enable HEIC image conversion (requires libheif) | ### User Settings Stored per-user as JSON: ```json { "conversations": { "show_calls": true } } ``` ## Build & Development ### Local Development ```bash # Backend (with hot reload) air # Frontend (with hot reload) cd frontend && npm run dev ``` Frontend dev server runs on `:5173` and proxies API calls to `:8081`. ### Production Build ```bash # With HEIC support ./build.sh # Without HEIC go build -tags fts5 -o sbv . # Frontend cd frontend && npm run build ``` ### Docker ```bash docker compose up -d ``` The multi-stage Dockerfile: 1. Builds frontend with Node.js 22 2. Compiles Go binary with FTS5 + HEIC 3. Creates minimal Alpine runtime with ffmpeg/libheif ## Security Considerations - **httpOnly cookies**: Session tokens not accessible to JavaScript - **SameSite=Lax**: CSRF protection - **bcrypt hashing**: Secure password storage - **Per-user isolation**: Users cannot access other users' data - **No internet exposure recommended**: Designed for local/trusted networks ## Known Limitations 1. **Large imports**: Processing slows with many media attachments 2. **MMS group sender**: Contact names unavailable (XML limitation) 3. **100k message limit**: Use date filters for older conversations 4. **Android only**: Requires SMS Backup & Restore app (no iOS support)