Mobile App Services¶
The mobile app uses four singleton services for API communication, authentication, secure storage, and network detection.
ApiService¶
File: lib/services/api_service.dart
Singleton Dio HTTP client for all API communication.
Configuration¶
| Setting | Value |
|---|---|
| Base URL | API_URL environment define (default: http://localhost:3000) |
| Connect timeout | 15 seconds |
| Receive timeout | 15 seconds |
Interceptors¶
Request interceptor:
- Automatically injects
Authorization: Bearer <token>header on every request - Token retrieved from
StorageService
Response interceptor:
- Catches
401 Unauthorizedresponses - Calls
onUnauthenticatedcallback to trigger logout - Clears stored token and navigates to login screen
Debug logging:
- In development mode, logs full request and response details
- Disabled in production builds
Usage¶
final dio = ApiService.instance.dio;
// GET request
final response = await dio.get('/sessions/upcoming-cleans');
// POST with JSON body
await dio.post('/sessions/${id}/accept');
// POST with multipart form data (photo upload)
final formData = FormData.fromMap({
'photo': await MultipartFile.fromFile(file.path),
'type': 'before',
});
await dio.post('/photos/$roomCleanId', data: formData);
AuthService¶
File: lib/services/auth_service.dart
Manages JWT authentication, session persistence, and user state. Extends ChangeNotifier for reactive UI updates.
Properties¶
| Property | Type | Description |
|---|---|---|
user |
AuthUser? |
Current logged-in user |
isAuthenticated |
bool |
Whether user has a valid session |
isLoading |
bool |
Whether auth check is in progress |
error |
String? |
Last authentication error message |
Methods¶
login(String email, String password)¶
- Calls
POST /api/auth/sign-in/emailwith credentials - Extracts JWT token and user object from response
- Stores token via
StorageService - Sets
userand notifies listeners - Throws on failure (caught by login screen for error display)
logout()¶
- Calls
POST /api/auth/sign-out - Clears stored token via
StorageService - Sets
user = nulland notifies listeners
_checkSession() (internal)¶
Called automatically in the constructor on cold start:
- Retrieves stored token from
StorageService - If token exists, calls
GET /api/auth/get-session - On success: populates user state
- On failure: clears token, shows login screen
Provider Setup¶
Accessed via Provider.of<AuthService>(context) or context.watch<AuthService>().
StorageService¶
File: lib/services/storage_service.dart
Platform-aware secure token storage singleton.
Platform Behavior¶
| Platform | Storage Backend | Security |
|---|---|---|
| iOS | Keychain (via flutter_secure_storage) |
Hardware-backed encryption |
| Android | Keystore (via flutter_secure_storage) |
Hardware-backed encryption |
| Web | SharedPreferences |
Browser local storage |
Methods¶
| Method | Description |
|---|---|
saveAccessToken(String token) |
Store JWT token securely |
getAccessToken() |
Retrieve stored JWT token (returns null if not set) |
clearAccessToken() |
Remove stored token (used on logout) |
Platform Detection¶
The service detects the runtime platform using kIsWeb and selects the appropriate storage backend automatically. No configuration needed.
NetworkService¶
File: lib/services/network_service.dart
Detects whether the device is on the property's local WiFi or accessing remotely. Extends ChangeNotifier for reactive UI updates.
Purpose¶
OpenSTR can restrict active cleaning sessions to on-site cleaners only. This service determines if the cleaner is on the local network by calling a server endpoint that checks the client's IP against configured local ranges.
Properties¶
| Property | Type | Description |
|---|---|---|
isLocal |
bool |
Whether device is on local WiFi |
devOverride |
bool |
Developer override (bypass WiFi check) |
Methods¶
checkNetwork()¶
- Calls
GET /network-check - Server returns
{ is_local: 1 }if client IP is in local range - Caches result for 2 minutes to avoid excessive server calls
- Updates
isLocaland notifies listeners
setDevOverride(bool value)¶
Developer toggle available in the Profile screen's dev settings. When enabled, isLocal always returns true regardless of actual network.
How It Works¶
The nginx reverse proxy uses a geo module that tags requests with an X-Is-Local header based on IP ranges. The API's /network-check endpoint reads this header and returns the result.
Provider Setup¶
Network status is re-checked when the user switches tabs in the main shell.