# 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 `:5175` and proxies API calls to `:8085`.

### 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)
