129 lines
7.0 KiB
Markdown
129 lines
7.0 KiB
Markdown
# 31. iOS App — API Client, tRPC Bridge, and Offline Support
|
|
|
|
meta:
|
|
id: shieldai-unified-restructure-31
|
|
feature: shieldai-unified-restructure
|
|
priority: P1
|
|
depends_on: [shieldai-unified-restructure-28, shieldai-unified-restructure-29, shieldai-unified-restructure-30]
|
|
tags: [ios, swift, api, networking, offline, mobile]
|
|
|
|
objective:
|
|
- Build the API client layer for the iOS app that communicates with the unified monolith's tRPC endpoints. Since tRPC is TypeScript-native, we'll use a thin HTTP bridge approach: the iOS client calls tRPC procedures as typed HTTP JSON requests. Add offline support with local caching and request queuing.
|
|
|
|
deliverables:
|
|
- `iOS/ShieldAI/Services/APIClient.swift` — HTTP client:
|
|
- Wraps `URLSession` with async/await (`async throws` methods)
|
|
- Base URL from environment/config (`https://api.shieldai.com` or local)
|
|
- Automatic auth header injection (JWT from Keychain)
|
|
- Request/response logging in debug builds
|
|
- Retry logic with exponential backoff for network errors
|
|
- Timeout configuration
|
|
- `iOS/ShieldAI/Services/TRPCBridge.swift` — tRPC procedure caller:
|
|
- `callProcedure<T: Decodable>(path: String, input: Encodable?) async throws -> T`
|
|
- Serializes input to JSON, sends POST to `/api/trpc/{path}`
|
|
- Deserializes response JSON to Swift `Decodable` type
|
|
- Handles tRPC error format (`{ error: { message, code } }`)
|
|
- Type-safe wrappers for common procedures:
|
|
- `user.me() -> User`
|
|
- `billing.getSubscription() -> Subscription`
|
|
- `darkwatch.getWatchlist() -> [WatchlistItem]`
|
|
- etc.
|
|
- `iOS/ShieldAI/Models/` — Swift data models:
|
|
- `User.swift`, `Subscription.swift`, `WatchlistItem.swift`, `Exposure.swift`, `Alert.swift`, `VoiceEnrollment.swift`, `VoiceAnalysis.swift`, `SpamRule.swift`, `PropertyWatchlistItem.swift`, `RemovalRequest.swift`, `BrokerListing.swift`, `NormalizedAlert.swift`, `CorrelationGroup.swift`, `SecurityReport.swift`
|
|
- All models conform to `Codable`, `Identifiable`, `Equatable`
|
|
- Enum types for Swift (e.g., `SubscriptionTier`, `AlertSeverity`, `ExposureSource`)
|
|
- `iOS/ShieldAI/Services/CacheManager.swift` — Offline cache:
|
|
- `CacheEntry` with data, timestamp, TTL
|
|
- Stores in `UserDefaults` (small data) or file system (large data)
|
|
- `getCached<T>(key: String) -> T?`
|
|
- `setCached<T>(key: String, value: T, ttl: TimeInterval)`
|
|
- Automatic cache invalidation on TTL expiry
|
|
- `iOS/ShieldAI/Services/OfflineQueue.swift` — Request queue:
|
|
- Queues mutations when offline
|
|
- Retries queued requests when connectivity restored
|
|
- Persists queue to disk
|
|
- `addToQueue(request: QueuedRequest)`
|
|
- `processQueue()` — called when network becomes available
|
|
- `iOS/ShieldAI/Services/NetworkMonitor.swift` — Connectivity monitoring:
|
|
- Uses `NWPathMonitor` to track network state
|
|
- Publishes `isConnected` boolean
|
|
- Triggers queue processing when connection restored
|
|
|
|
steps:
|
|
1. Create `iOS/ShieldAI/Services/` and `iOS/ShieldAI/Models/` directories.
|
|
2. **APIClient**:
|
|
- Create `APIClient` class as `@Observable` or singleton
|
|
- `request<T: Decodable>(endpoint: String, method: String, body: Data?) async throws -> T`
|
|
- Add request interceptor for auth header
|
|
- Add response interceptor for error parsing
|
|
- Use `JSONEncoder`/`JSONDecoder` with custom date formatting
|
|
3. **TRPCBridge**:
|
|
- `callProcedure` constructs tRPC batch request format:
|
|
```json
|
|
{ "0": { "json": { "email": "test@example.com" } } }
|
|
```
|
|
- Parse tRPC response format:
|
|
```json
|
|
{ "0": { "result": { "data": { ... } } } }
|
|
```
|
|
- Map tRPC error codes to Swift `APIError` enum
|
|
- Create convenience methods for each router procedure
|
|
4. **Models**:
|
|
- Define Swift structs matching Drizzle schema shapes
|
|
- Use `CodingKeys` where JSON keys differ from Swift naming
|
|
- Define enums for all database enums (e.g., `SubscriptionTier: String, Codable`)
|
|
- Add computed properties where needed (e.g., `Alert.isCritical: Bool`)
|
|
5. **CacheManager**:
|
|
- Use `JSONEncoder` to serialize values
|
|
- Store in `UserDefaults` with key prefix `shieldai.cache.`
|
|
- TTL check: compare `Date()` to stored timestamp
|
|
- Clear all cache on logout
|
|
6. **OfflineQueue**:
|
|
- `QueuedRequest` struct: `endpoint`, `method`, `body`, `timestamp`, `retryCount`
|
|
- Store array in `UserDefaults` or JSON file
|
|
- `processQueue`: iterate requests, call APIClient, remove on success, increment retry on failure
|
|
- Max retries: 3, then mark as failed and notify user
|
|
7. **NetworkMonitor**:
|
|
- `NWPathMonitor` with `.queue = .main`
|
|
- `@Published var isConnected: Bool`
|
|
- On change to connected: trigger `OfflineQueue.processQueue()`
|
|
8. Create `APIConfig.swift`:
|
|
- `baseURL`, `timeout`, `maxRetries` from environment or plist
|
|
- Different values for debug/release builds
|
|
9. Test all components with mocked URLSession.
|
|
|
|
steps:
|
|
- Unit: APIClient injects auth header correctly
|
|
- Unit: TRPCBridge parses tRPC response format correctly
|
|
- Unit: CacheManager stores and retrieves values with TTL
|
|
- Unit: OfflineQueue persists requests and processes them in order
|
|
- Unit: NetworkMonitor publishes correct connectivity state
|
|
- Integration: APIClient successfully calls `user.me` against local dev server
|
|
|
|
acceptance_criteria:
|
|
- [ ] APIClient makes authenticated HTTP requests to tRPC endpoints
|
|
- [ ] TRPCBridge correctly serializes tRPC batch input and deserializes responses
|
|
- [ ] All common API procedures have type-safe Swift wrappers
|
|
- [ ] Network errors trigger retry with exponential backoff
|
|
- [ ] CacheManager stores GET responses and returns cached data when offline
|
|
- [ ] OfflineQueue persists mutations and retries when connectivity restored
|
|
- [ ] NetworkMonitor accurately tracks connectivity state
|
|
- [ ] All models are Codable and match backend schema shapes
|
|
- [ ] API configuration supports different environments (dev, staging, prod)
|
|
|
|
validation:
|
|
- Point APIClient to local dev server (`http://localhost:3000`)
|
|
- Call `user.me()` and verify response parsed into `User` model
|
|
- Disconnect network, attempt a mutation, verify it queues
|
|
- Reconnect network, verify queued mutation executes automatically
|
|
- Verify cache hit by calling same endpoint twice with network disabled
|
|
- Run unit tests via Xcode Cmd+U
|
|
|
|
notes:
|
|
- Since tRPC is TypeScript-native, the iOS client cannot use tRPC's type-safe client directly. The HTTP bridge is the pragmatic approach.
|
|
- Consider generating Swift models and API wrappers automatically from the tRPC router types using a code generation tool (e.g., custom script parsing tRPC router exports). For now, manual definitions are fine.
|
|
- The tRPC batch link sends multiple procedures in one HTTP request. For simplicity, the iOS client can use single-procedure requests (`/api/trpc/user.me`) instead of batching.
|
|
- Use `OSLog` for structured logging in debug builds. Avoid printing sensitive data (tokens, passwords).
|
|
- For large responses (e.g., full alert history), consider pagination and storing pages in cache separately.
|
|
- The offline queue should only persist safe mutations (idempotent or retry-safe). Avoid queuing destructive operations without user confirmation.
|