working api and app
This commit is contained in:
325
PLAN.md
Normal file
325
PLAN.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user