Files
textpipe/PLAN.md

326 lines
12 KiB
Markdown

# Textpipe SMS API Gateway - Implementation Plan
## Overview
Build an Android SMS API Gateway with an embedded HTTP server (Ktor), dual SIM support, local message database, and background service.
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ MainActivity │
│ (Compose UI + Permissions) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ TextpipeService │
│ (Foreground Service + Lifecycle) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ TextpipeServer (Ktor CIO) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ /api/status │ │/api/sms/send│ │/api/sms/messages│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
│ SimManager │ │ SmsSender │ │ SmsRepository │
│ (API Keys) │ │ (SmsManager)│ │ (Room + Send) │
└─────────────┘ └─────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────────────────────┐
│ AppDatabase (Room) │
│ sms_messages table │
└─────────────────────────────────┘
```
---
## Package Structure
```
sh.sar.textpipe/
├── MainActivity.kt (modify)
├── TextpipeApplication.kt (create)
├── data/
│ ├── db/
│ │ ├── AppDatabase.kt
│ │ ├── SmsMessageDao.kt
│ │ └── SmsMessageEntity.kt
│ ├── model/
│ │ └── ApiModels.kt
│ └── repository/
│ └── SmsRepository.kt
├── server/
│ ├── TextpipeServer.kt
│ ├── auth/
│ │ └── ApiKeyAuth.kt
│ └── routes/
│ ├── SmsRoutes.kt
│ └── StatusRoutes.kt
├── service/
│ ├── TextpipeService.kt
│ ├── BootReceiver.kt
│ ├── SmsReceiver.kt
│ └── SmsSender.kt
├── sim/
│ └── SimManager.kt
├── root/
│ └── RootManager.kt
└── ui/
├── MainScreen.kt
└── MainViewModel.kt
```
---
## API Endpoints
### Authentication
- API key in header: `Authorization: Bearer <key>` or `X-API-Key: <key>`
- Each SIM slot has its own unique API key (auto-generated, persistent)
- API key determines which SIM sends the message
### Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/status` | No | Server health, SIM info, uptime |
| POST | `/api/sms/send` | Yes | Send SMS |
| GET | `/api/sms/messages` | Yes | List messages (optional `?type=sent\|received`) |
| GET | `/api/sms/status/{id}` | Yes | Get specific message status |
### Request/Response Examples
**POST /api/sms/send**
```json
// Request
{ "to": "+1234567890", "text": "Hello World" }
// Response
{ "id": 1, "status": "pending", "simSlot": 0 }
```
**GET /api/sms/messages**
```json
[
{
"id": 1,
"type": "sent",
"address": "+1234567890",
"text": "Hello World",
"simSlot": 0,
"status": "delivered",
"timestamp": 1707148800000,
"deliveryTimestamp": 1707148805000
}
]
```
**GET /api/status**
```json
{
"running": true,
"port": 8080,
"uptime": 3600000,
"sims": [
{ "slot": 0, "carrier": "Carrier1", "number": "+1...", "hasApiKey": true },
{ "slot": 1, "carrier": "Carrier2", "number": "+1...", "hasApiKey": true }
]
}
```
---
## Dependencies to Add
### libs.versions.toml additions
```toml
[versions]
ktor = "3.0.3"
room = "2.6.1"
kotlinxSerialization = "1.7.3"
datastorePreferences = "1.1.1"
ksp = "2.0.21-1.0.28"
coroutines = "1.9.0"
[libraries]
ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" }
ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" }
ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-server-status-pages = { group = "io.ktor", name = "ktor-server-status-pages", version.ref = "ktor" }
ktor-server-cors = { group = "io.ktor", name = "ktor-server-cors", version.ref = "ktor" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
[plugins]
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
```
---
## Permissions Required
```xml
<!-- SMS -->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<!-- Phone/SIM -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<!-- Network -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Background Service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Battery -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
```
---
## Implementation Order
### Phase 0: Build Configuration
1. `gradle/libs.versions.toml` - Add all new dependencies
2. `build.gradle.kts` (root) - Add KSP and serialization plugins
3. `app/build.gradle.kts` - Add dependencies and plugins
4. `AndroidManifest.xml` - Add permissions, services, receivers
### Phase 1: Data Layer
5. `data/model/ApiModels.kt` - Request/response data classes
6. `data/db/SmsMessageEntity.kt` - Room entity
7. `data/db/SmsMessageDao.kt` - Database operations
8. `data/db/AppDatabase.kt` - Room database
### Phase 2: Core Managers
9. `sim/SimManager.kt` - SIM detection, API key management
10. `root/RootManager.kt` - Root access and iptables port redirect
### Phase 3: SMS Operations
11. `service/SmsSender.kt` - Send SMS with delivery tracking
12. `data/repository/SmsRepository.kt` - Combine database + sending
13. `service/SmsReceiver.kt` - Receive incoming SMS
### Phase 4: HTTP Server
14. `server/auth/ApiKeyAuth.kt` - API key validation
15. `server/routes/StatusRoutes.kt` - Health check endpoint
16. `server/routes/SmsRoutes.kt` - SMS API endpoints
17. `server/TextpipeServer.kt` - Ktor server configuration
### Phase 5: Service Layer
18. `service/TextpipeService.kt` - Foreground service
19. `service/BootReceiver.kt` - Auto-start on boot
20. `TextpipeApplication.kt` - Application class
### Phase 6: UI Layer
21. `ui/MainViewModel.kt` - UI state management
22. `ui/MainScreen.kt` - Compose UI
23. `MainActivity.kt` - Update with permissions and new UI
---
## Key Technical Decisions
### HTTP Server
- **Ktor CIO engine** (not Netty) - lighter weight for Android
- Server runs on configurable port (default 8080)
- For ports 80/443: Use iptables NAT redirect via `su` (don't run JVM as root)
### Dual SIM Support
- `SubscriptionManager.getActiveSubscriptionInfoList()` for SIM detection
- `SmsManager.createForSubscriptionId(subscriptionId)` for sending
- API key maps to subscription ID, not slot index
### SMS Delivery Tracking
- PendingIntents for sent confirmation and delivery reports
- Update database status: pending → sent → delivered/failed
- Multipart SMS via `sendMultipartTextMessage()`
### Background Persistence
- Foreground service with persistent notification
- `FOREGROUND_SERVICE_TYPE_SPECIAL_USE` for API 34+
- Boot receiver for auto-start
### API Key Storage
- SharedPreferences (not DataStore) for synchronous reads
- Key format: UUID, stored per SIM slot
- Generated on first run, persistent across app updates
---
## Files to Create/Modify (22 total)
| # | File | Action |
|---|------|--------|
| 1 | `gradle/libs.versions.toml` | Modify |
| 2 | `build.gradle.kts` (root) | Modify |
| 3 | `app/build.gradle.kts` | Modify |
| 4 | `app/src/main/AndroidManifest.xml` | Modify |
| 5 | `data/model/ApiModels.kt` | Create |
| 6 | `data/db/SmsMessageEntity.kt` | Create |
| 7 | `data/db/SmsMessageDao.kt` | Create |
| 8 | `data/db/AppDatabase.kt` | Create |
| 9 | `sim/SimManager.kt` | Create |
| 10 | `root/RootManager.kt` | Create |
| 11 | `service/SmsSender.kt` | Create |
| 12 | `data/repository/SmsRepository.kt` | Create |
| 13 | `service/SmsReceiver.kt` | Create |
| 14 | `server/auth/ApiKeyAuth.kt` | Create |
| 15 | `server/routes/StatusRoutes.kt` | Create |
| 16 | `server/routes/SmsRoutes.kt` | Create |
| 17 | `server/TextpipeServer.kt` | Create |
| 18 | `service/TextpipeService.kt` | Create |
| 19 | `service/BootReceiver.kt` | Create |
| 20 | `TextpipeApplication.kt` | Create |
| 21 | `ui/MainViewModel.kt` | Create |
| 22 | `ui/MainScreen.kt` | Create |
| 23 | `MainActivity.kt` | Modify |
---
## UI Features (Basic for now)
- Server status indicator (Running/Stopped)
- Port configuration input
- Start/Stop toggle button
- SIM cards list with:
- Carrier name
- Phone number (if available)
- API key with copy button
- Root port redirect toggle (if root available)
- Battery optimization exemption button
---
## Out of Scope (Deferred)
- WebUI for viewing messages in browser
- Rate limiting
- IP whitelisting
- HTTPS/TLS (beyond port redirect)
- Message scheduling
- Webhook callbacks