# 🚨 ПОЛНОЕ ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Доработка модуля экстренных сообщений для мобильного приложения ## 📋 ОБЗОР ПРОЕКТА **Цель:** Интегрировать мобильное приложение с Emergency Service через WebSocket и REST API **Архитектура:** Микросервисная система с JWT аутентификацией **Основные компоненты:** Emergency Service (порт 8002), API Gateway (порт 8000), WebSocket подключения --- ## 🔧 ЭТАП 1: НАСТРОЙКА АУТЕНТИФИКАЦИИ ### 1.1 Удаление временных токенов **Проблема:** В коде используются временные токены вида `temp_token_for_${email}` **Задачи:** ```kotlin // ❌ УДАЛИТЬ ЭТО: val tempToken = "temp_token_for_${userEmail}" headers["Authorization"] = "Bearer $tempToken" // ✅ ЗАМЕНИТЬ НА: val jwtToken = authManager.getValidJwtToken() headers["Authorization"] = "Bearer $jwtToken" ``` ### 1.2 Реализация JWT аутентификации **Создать класс AuthManager:** ```kotlin class AuthManager { private val baseUrl = "http://YOUR_SERVER:8000" private var jwtToken: String? = null private var refreshToken: String? = null suspend fun login(email: String, password: String): Result { val loginData = LoginRequest(email, password) return try { val response = apiService.login(loginData) if (response.isSuccessful) { val authData = response.body()!! jwtToken = authData.access_token saveTokens(authData.access_token, authData.refresh_token) Result.success(authData) } else { Result.failure(Exception("Login failed: ${response.code()}")) } } catch (e: Exception) { Result.failure(e) } } fun getValidJwtToken(): String? { return if (isTokenValid(jwtToken)) jwtToken else null } private fun isTokenValid(token: String?): Boolean { // Проверка срока действия JWT токена if (token.isNullOrEmpty()) return false return try { val payload = decodeJwtPayload(token) val exp = payload["exp"] as? Long ?: 0 exp > System.currentTimeMillis() / 1000 } catch (e: Exception) { false } } } ``` ### 1.3 Модели данных для аутентификации ```kotlin data class LoginRequest( val email: String, val password: String ) data class LoginResponse( val access_token: String, val token_type: String, val user: UserData ) data class UserData( val id: Int, val email: String, val first_name: String?, val last_name: String? ) ``` --- ## 🌐 ЭТАП 2: НАСТРОЙКА REST API CLIENT ### 2.1 Создание Emergency API Service ```kotlin interface EmergencyApiService { @POST("api/v1/alert") suspend fun createAlert( @Body alertRequest: CreateAlertRequest, @Header("Authorization") auth: String ): Response @GET("api/v1/alerts/my") suspend fun getMyAlerts( @Header("Authorization") auth: String ): Response> @GET("api/v1/alerts/active") suspend fun getActiveAlerts( @Header("Authorization") auth: String ): Response> @GET("api/v1/alerts/nearby") suspend fun getNearbyAlerts( @Query("latitude") latitude: Double, @Query("longitude") longitude: Double, @Query("radius") radius: Int, @Header("Authorization") auth: String ): Response> @POST("api/v1/alert/{alertId}/respond") suspend fun respondToAlert( @Path("alertId") alertId: Int, @Body response: AlertResponseRequest, @Header("Authorization") auth: String ): Response @POST("api/v1/report") suspend fun createReport( @Body reportRequest: CreateReportRequest, @Header("Authorization") auth: String ): Response @POST("api/v1/safety-check") suspend fun createSafetyCheck( @Body safetyCheck: SafetyCheckRequest, @Header("Authorization") auth: String ): Response @GET("api/v1/stats") suspend fun getStatistics( @Header("Authorization") auth: String ): Response } ``` ### 2.2 Модели данных для Emergency API ```kotlin // Запросы data class CreateAlertRequest( val alert_type: String, // "medical", "fire", "police", "other" val latitude: Double, val longitude: Double, val address: String? = null, val description: String ) data class CreateReportRequest( val incident_type: String, val latitude: Double, val longitude: Double, val address: String? = null, val description: String, val severity: String // "low", "medium", "high" ) data class SafetyCheckRequest( val latitude: Double, val longitude: Double, val status: String, // "safe", "unsafe", "need_help" val message: String? = null ) data class AlertResponseRequest( val response_type: String, // "help_on_way", "safe_now", "false_alarm" val message: String? = null ) // Ответы data class EmergencyAlertResponse( val id: Int, val alert_type: String, val latitude: Double, val longitude: Double, val address: String?, val description: String, val status: String, val created_at: String, val responded_users_count: Int ) data class NearbyAlertResponse( val id: Int, val alert_type: String, val latitude: Double, val longitude: Double, val address: String?, val distance: Double, val created_at: String, val responded_users_count: Int ) data class EmergencyResponseResponse( val id: Int, val response_type: String, val message: String?, val created_at: String ) data class EmergencyReportResponse( val id: Int, val incident_type: String, val latitude: Double, val longitude: Double, val address: String?, val description: String, val severity: String, val status: String, val created_at: String ) data class SafetyCheckResponse( val id: Int, val latitude: Double, val longitude: Double, val status: String, val message: String?, val created_at: String ) data class EmergencyStatistics( val total_alerts: Int, val active_alerts: Int, val resolved_alerts: Int, val total_reports: Int, val my_alerts_count: Int, val my_responses_count: Int ) ``` --- ## 📡 ЭТАП 3: РЕАЛИЗАЦИЯ WEBSOCKET ПОДКЛЮЧЕНИЯ ### 3.1 WebSocket Manager ```kotlin class EmergencyWebSocketManager( private val authManager: AuthManager, private val coroutineScope: CoroutineScope ) { private var webSocket: WebSocket? = null private var isConnected = false private val listeners = mutableListOf() fun connect() { val jwtToken = authManager.getValidJwtToken() if (jwtToken == null) { notifyError("No valid JWT token available") return } val request = Request.Builder() .url("ws://YOUR_SERVER:8002/api/v1/emergency/ws/current_user_id?token=$jwtToken") .build() val client = OkHttpClient.Builder() .readTimeout(30, TimeUnit.SECONDS) .build() webSocket = client.newWebSocket(request, object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { isConnected = true notifyConnectionOpened() // Отправляем ping для поддержания соединения startPingKeepAlive() } override fun onMessage(webSocket: WebSocket, text: String) { handleMessage(text) } override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { isConnected = false notifyConnectionClosing(code, reason) } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { isConnected = false notifyError("WebSocket connection failed: ${t.message}") // Автоматическое переподключение scheduleReconnect() } }) } private fun handleMessage(jsonMessage: String) { try { val message = Json.decodeFromString(jsonMessage) when (message.type) { "connection_established" -> { notifyConnectionEstablished(message.user_id) } "emergency_alert" -> { val alertData = Json.decodeFromString( Json.encodeToString(message.data) ) notifyNewAlert(alertData) } "alert_update" -> { val updateData = Json.decodeFromString( Json.encodeToString(message.data) ) notifyAlertUpdate(updateData) } "alert_response" -> { val responseData = Json.decodeFromString( Json.encodeToString(message.data) ) notifyAlertResponse(responseData) } } } catch (e: Exception) { notifyError("Failed to parse WebSocket message: ${e.message}") } } fun sendMessage(message: Any) { if (isConnected) { val jsonMessage = Json.encodeToString(message) webSocket?.send(jsonMessage) } } fun addListener(listener: EmergencyWebSocketListener) { listeners.add(listener) } fun removeListener(listener: EmergencyWebSocketListener) { listeners.remove(listener) } private fun startPingKeepAlive() { coroutineScope.launch { while (isConnected) { delay(30_000) // Ping каждые 30 секунд sendMessage(mapOf("type" to "ping")) } } } private fun scheduleReconnect() { coroutineScope.launch { delay(5_000) // Ждем 5 секунд перед переподключением connect() } } } interface EmergencyWebSocketListener { fun onConnectionOpened() fun onConnectionEstablished(userId: Int) fun onNewAlert(alert: EmergencyAlertResponse) fun onAlertUpdate(update: AlertUpdateData) fun onAlertResponse(response: EmergencyResponseResponse) fun onConnectionClosing(code: Int, reason: String) fun onError(error: String) } @Serializable data class WebSocketMessage( val type: String, val data: JsonElement? = null, val user_id: Int? = null, val message: String? = null ) @Serializable data class AlertUpdateData( val alert_id: Int, val status: String, val responded_users_count: Int ) ``` --- ## 🎯 ЭТАП 4: СОЗДАНИЕ UI КОМПОНЕНТОВ ### 4.1 Emergency Fragment/Activity ```kotlin class EmergencyFragment : Fragment(), EmergencyWebSocketListener { private lateinit var binding: FragmentEmergencyBinding private lateinit var emergencyRepository: EmergencyRepository private lateinit var webSocketManager: EmergencyWebSocketManager private lateinit var viewModel: EmergencyViewModel override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { binding = FragmentEmergencyBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupWebSocket() setupUI() observeViewModel() } private fun setupWebSocket() { webSocketManager.addListener(this) webSocketManager.connect() } private fun setupUI() { binding.btnCreateAlert.setOnClickListener { showCreateAlertDialog() } binding.btnSafetyCheck.setOnClickListener { createSafetyCheck() } binding.btnViewNearby.setOnClickListener { loadNearbyAlerts() } binding.swipeRefresh.setOnRefreshListener { refreshData() } } private fun showCreateAlertDialog() { val dialog = CreateAlertDialog { alertRequest -> viewModel.createAlert(alertRequest) } dialog.show(parentFragmentManager, "CREATE_ALERT") } private fun createSafetyCheck() { locationManager.getCurrentLocation { location -> val safetyCheck = SafetyCheckRequest( latitude = location.latitude, longitude = location.longitude, status = "safe", message = "Regular safety check" ) viewModel.createSafetyCheck(safetyCheck) } } // WebSocket Listener методы override fun onConnectionEstablished(userId: Int) { activity?.runOnUiThread { binding.connectionStatus.text = "Connected (User: $userId)" binding.connectionStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.green)) } } override fun onNewAlert(alert: EmergencyAlertResponse) { activity?.runOnUiThread { showNewAlertNotification(alert) viewModel.refreshAlerts() } } override fun onAlertUpdate(update: AlertUpdateData) { activity?.runOnUiThread { viewModel.updateAlert(update) } } override fun onError(error: String) { activity?.runOnUiThread { binding.connectionStatus.text = "Error: $error" binding.connectionStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.red)) Toast.makeText(context, error, Toast.LENGTH_SHORT).show() } } override fun onDestroy() { webSocketManager.removeListener(this) super.onDestroy() } } ``` ### 4.2 Emergency ViewModel ```kotlin class EmergencyViewModel( private val repository: EmergencyRepository ) : ViewModel() { private val _alerts = MutableLiveData>() val alerts: LiveData> = _alerts private val _nearbyAlerts = MutableLiveData>() val nearbyAlerts: LiveData> = _nearbyAlerts private val _isLoading = MutableLiveData() val isLoading: LiveData = _isLoading private val _error = MutableLiveData() val error: LiveData = _error fun createAlert(request: CreateAlertRequest) { viewModelScope.launch { _isLoading.value = true try { val result = repository.createAlert(request) if (result.isSuccess) { // Обновляем список после создания loadMyAlerts() } else { _error.value = "Failed to create alert: ${result.exceptionOrNull()?.message}" } } catch (e: Exception) { _error.value = "Error creating alert: ${e.message}" } finally { _isLoading.value = false } } } fun loadMyAlerts() { viewModelScope.launch { _isLoading.value = true try { val result = repository.getMyAlerts() if (result.isSuccess) { _alerts.value = result.getOrNull() ?: emptyList() } else { _error.value = "Failed to load alerts: ${result.exceptionOrNull()?.message}" } } catch (e: Exception) { _error.value = "Error loading alerts: ${e.message}" } finally { _isLoading.value = false } } } fun loadNearbyAlerts(latitude: Double, longitude: Double, radius: Int = 5) { viewModelScope.launch { _isLoading.value = true try { val result = repository.getNearbyAlerts(latitude, longitude, radius) if (result.isSuccess) { _nearbyAlerts.value = result.getOrNull() ?: emptyList() } else { _error.value = "Failed to load nearby alerts: ${result.exceptionOrNull()?.message}" } } catch (e: Exception) { _error.value = "Error loading nearby alerts: ${e.message}" } finally { _isLoading.value = false } } } fun respondToAlert(alertId: Int, responseType: String, message: String? = null) { viewModelScope.launch { _isLoading.value = true try { val request = AlertResponseRequest(responseType, message) val result = repository.respondToAlert(alertId, request) if (result.isSuccess) { // Обновляем список после ответа loadMyAlerts() } else { _error.value = "Failed to respond to alert: ${result.exceptionOrNull()?.message}" } } catch (e: Exception) { _error.value = "Error responding to alert: ${e.message}" } finally { _isLoading.value = false } } } fun createSafetyCheck(request: SafetyCheckRequest) { viewModelScope.launch { try { val result = repository.createSafetyCheck(request) if (result.isFailure) { _error.value = "Failed to create safety check: ${result.exceptionOrNull()?.message}" } } catch (e: Exception) { _error.value = "Error creating safety check: ${e.message}" } } } fun updateAlert(update: AlertUpdateData) { val currentAlerts = _alerts.value?.toMutableList() ?: return val index = currentAlerts.indexOfFirst { it.id == update.alert_id } if (index != -1) { currentAlerts[index] = currentAlerts[index].copy( status = update.status, responded_users_count = update.responded_users_count ) _alerts.value = currentAlerts } } } ``` ### 4.3 Emergency Repository ```kotlin class EmergencyRepository( private val apiService: EmergencyApiService, private val authManager: AuthManager ) { private fun getAuthHeader(): String { val token = authManager.getValidJwtToken() return "Bearer $token" } suspend fun createAlert(request: CreateAlertRequest): Result { return try { val response = apiService.createAlert(request, getAuthHeader()) if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!) } else { Result.failure(Exception("HTTP ${response.code()}: ${response.message()}")) } } catch (e: Exception) { Result.failure(e) } } suspend fun getMyAlerts(): Result> { return try { val response = apiService.getMyAlerts(getAuthHeader()) if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!) } else { Result.failure(Exception("HTTP ${response.code()}: ${response.message()}")) } } catch (e: Exception) { Result.failure(e) } } suspend fun getActiveAlerts(): Result> { return try { val response = apiService.getActiveAlerts(getAuthHeader()) if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!) } else { Result.failure(Exception("HTTP ${response.code()}: ${response.message()}")) } } catch (e: Exception) { Result.failure(e) } } suspend fun getNearbyAlerts(latitude: Double, longitude: Double, radius: Int): Result> { return try { val response = apiService.getNearbyAlerts(latitude, longitude, radius, getAuthHeader()) if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!) } else { Result.failure(Exception("HTTP ${response.code()}: ${response.message()}")) } } catch (e: Exception) { Result.failure(e) } } suspend fun respondToAlert(alertId: Int, request: AlertResponseRequest): Result { return try { val response = apiService.respondToAlert(alertId, request, getAuthHeader()) if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!) } else { Result.failure(Exception("HTTP ${response.code()}: ${response.message()}")) } } catch (e: Exception) { Result.failure(e) } } suspend fun createReport(request: CreateReportRequest): Result { return try { val response = apiService.createReport(request, getAuthHeader()) if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!) } else { Result.failure(Exception("HTTP ${response.code()}: ${response.message()}")) } } catch (e: Exception) { Result.failure(e) } } suspend fun createSafetyCheck(request: SafetyCheckRequest): Result { return try { val response = apiService.createSafetyCheck(request, getAuthHeader()) if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!) } else { Result.failure(Exception("HTTP ${response.code()}: ${response.message()}")) } } catch (e: Exception) { Result.failure(e) } } suspend fun getStatistics(): Result { return try { val response = apiService.getStatistics(getAuthHeader()) if (response.isSuccessful && response.body() != null) { Result.success(response.body()!!) } else { Result.failure(Exception("HTTP ${response.code()}: ${response.message()}")) } } catch (e: Exception) { Result.failure(e) } } } ``` --- ## 🔧 ЭТАП 5: КОНФИГУРАЦИЯ И НАСТРОЙКА ### 5.1 Network Configuration ```kotlin object NetworkConfig { const val BASE_URL = "http://YOUR_SERVER_IP:8000/" const val EMERGENCY_URL = "http://YOUR_SERVER_IP:8002/" const val WS_URL = "ws://YOUR_SERVER_IP:8002/" const val CONNECT_TIMEOUT = 30L const val READ_TIMEOUT = 30L const val WRITE_TIMEOUT = 30L } // Retrofit setup val retrofit = Retrofit.Builder() .baseUrl(NetworkConfig.BASE_URL) .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) .client( OkHttpClient.Builder() .connectTimeout(NetworkConfig.CONNECT_TIMEOUT, TimeUnit.SECONDS) .readTimeout(NetworkConfig.READ_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(NetworkConfig.WRITE_TIMEOUT, TimeUnit.SECONDS) .addInterceptor(AuthInterceptor(authManager)) .build() ) .build() class AuthInterceptor(private val authManager: AuthManager) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() // Добавляем JWT токен к запросам, если он есть val token = authManager.getValidJwtToken() return if (token != null && !originalRequest.header("Authorization")?.startsWith("Bearer") == true) { val newRequest = originalRequest.newBuilder() .header("Authorization", "Bearer $token") .build() chain.proceed(newRequest) } else { chain.proceed(originalRequest) } } } ``` ### 5.2 Dependency Injection (Hilt/Dagger) ```kotlin @Module @InstallIn(SingletonComponent::class) object EmergencyModule { @Provides @Singleton fun provideAuthManager(@ApplicationContext context: Context): AuthManager { return AuthManager(context) } @Provides @Singleton fun provideEmergencyApiService(): EmergencyApiService { return retrofit.create(EmergencyApiService::class.java) } @Provides @Singleton fun provideEmergencyRepository( apiService: EmergencyApiService, authManager: AuthManager ): EmergencyRepository { return EmergencyRepository(apiService, authManager) } @Provides @Singleton fun provideEmergencyWebSocketManager( authManager: AuthManager, @ApplicationScope scope: CoroutineScope ): EmergencyWebSocketManager { return EmergencyWebSocketManager(authManager, scope) } } @ViewModelScope class EmergencyViewModel @Inject constructor( private val repository: EmergencyRepository ) : ViewModel() ``` --- ## 🧪 ЭТАП 6: ТЕСТИРОВАНИЕ И ОТЛАДКА ### 6.1 Unit Tests ```kotlin class EmergencyRepositoryTest { @Mock private lateinit var apiService: EmergencyApiService @Mock private lateinit var authManager: AuthManager private lateinit var repository: EmergencyRepository @Before fun setup() { MockitoAnnotations.openMocks(this) repository = EmergencyRepository(apiService, authManager) } @Test fun `createAlert should return success when API call succeeds`() = runTest { // Arrange val request = CreateAlertRequest("medical", 55.7558, 37.6176, null, "Test alert") val response = EmergencyAlertResponse(1, "medical", 55.7558, 37.6176, null, "Test alert", "active", "2025-10-18T00:00:00Z", 0) `when`(authManager.getValidJwtToken()).thenReturn("valid_token") `when`(apiService.createAlert(any(), any())).thenReturn(Response.success(response)) // Act val result = repository.createAlert(request) // Assert assertTrue(result.isSuccess) assertEquals(response, result.getOrNull()) } @Test fun `createAlert should return failure when API call fails`() = runTest { // Arrange val request = CreateAlertRequest("medical", 55.7558, 37.6176, null, "Test alert") `when`(authManager.getValidJwtToken()).thenReturn("valid_token") `when`(apiService.createAlert(any(), any())).thenReturn(Response.error(500, "".toResponseBody())) // Act val result = repository.createAlert(request) // Assert assertTrue(result.isFailure) } } ``` ### 6.2 Integration Tests ```kotlin class EmergencyIntegrationTest { private lateinit var webSocketManager: EmergencyWebSocketManager private lateinit var repository: EmergencyRepository @Test fun `should connect to WebSocket and receive messages`() = runTest { // Этот тест требует запущенного сервера val authManager = TestAuthManager() // Мок с валидным токеном webSocketManager = EmergencyWebSocketManager(authManager, this) var connectionEstablished = false var messageReceived = false webSocketManager.addListener(object : EmergencyWebSocketListener { override fun onConnectionEstablished(userId: Int) { connectionEstablished = true } override fun onNewAlert(alert: EmergencyAlertResponse) { messageReceived = true } // Другие методы... }) webSocketManager.connect() // Ждем подключения delay(5000) assertTrue("WebSocket connection should be established", connectionEstablished) } } ``` --- ## 📱 ЭТАП 7: UI/UX УЛУЧШЕНИЯ ### 7.1 Push Notifications ```kotlin class EmergencyNotificationService : FirebaseMessagingService() { override fun onMessageReceived(remoteMessage: RemoteMessage) { super.onMessageReceived(remoteMessage) val data = remoteMessage.data when (data["type"]) { "emergency_alert" -> { showEmergencyAlertNotification(data) } "alert_response" -> { showAlertResponseNotification(data) } "safety_check_reminder" -> { showSafetyCheckReminder() } } } private fun showEmergencyAlertNotification(data: Map) { val alertType = data["alert_type"] ?: "emergency" val location = data["address"] ?: "Unknown location" val notification = NotificationCompat.Builder(this, EMERGENCY_CHANNEL_ID) .setSmallIcon(R.drawable.ic_emergency) .setContentTitle("🚨 Emergency Alert: ${alertType.uppercase()}") .setContentText("Emergency situation reported near $location") .setPriority(NotificationCompat.PRIORITY_HIGH) .setCategory(NotificationCompat.CATEGORY_ALARM) .setAutoCancel(false) .addAction(R.drawable.ic_help, "Respond", createRespondPendingIntent(data["alert_id"])) .addAction(R.drawable.ic_view, "View Details", createViewPendingIntent(data["alert_id"])) .build() NotificationManagerCompat.from(this).notify(EMERGENCY_NOTIFICATION_ID, notification) } } ``` ### 7.2 Location Services Integration ```kotlin class LocationManager @Inject constructor( @ApplicationContext private val context: Context ) { private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) @SuppressLint("MissingPermission") fun getCurrentLocation(callback: (Location) -> Unit) { if (hasLocationPermission()) { fusedLocationClient.lastLocation.addOnSuccessListener { location -> if (location != null) { callback(location) } else { requestNewLocationData(callback) } } } else { // Request location permissions } } @SuppressLint("MissingPermission") private fun requestNewLocationData(callback: (Location) -> Unit) { val locationRequest = LocationRequest.create().apply { priority = LocationRequest.PRIORITY_HIGH_ACCURACY interval = 0 fastestInterval = 0 numUpdates = 1 } fusedLocationClient.requestLocationUpdates( locationRequest, object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { locationResult.lastLocation?.let { callback(it) } } }, Looper.myLooper() ) } private fun hasLocationPermission(): Boolean { return ContextCompat.checkSelfPermission( context, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED } } ``` --- ## 🔒 ЭТАП 8: БЕЗОПАСНОСТЬ И ОПТИМИЗАЦИЯ ### 8.1 Security Best Practices ```kotlin object SecurityManager { // Шифрование токенов в SharedPreferences fun saveEncryptedToken(context: Context, token: String) { val sharedPrefs = EncryptedSharedPreferences.create( "secure_prefs", MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) sharedPrefs.edit() .putString("jwt_token", token) .apply() } // Certificate Pinning fun createSecureOkHttpClient(): OkHttpClient { val certificatePinner = CertificatePinner.Builder() .add("your-server.com", "sha256/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") .build() return OkHttpClient.Builder() .certificatePinner(certificatePinner) .build() } // Защита от Man-in-the-Middle атак fun validateServerCertificate(hostname: String): Boolean { // Implement certificate validation logic return true } } ``` ### 8.2 Performance Optimization ```kotlin class EmergencyDataCache @Inject constructor() { private val alertsCache = LruCache>(50) private val cacheExpiry = mutableMapOf() private val cacheTimeout = 5 * 60 * 1000L // 5 minutes fun cacheAlerts(key: String, alerts: List) { alertsCache.put(key, alerts) cacheExpiry[key] = System.currentTimeMillis() + cacheTimeout } fun getCachedAlerts(key: String): List? { val expiry = cacheExpiry[key] ?: return null return if (System.currentTimeMillis() < expiry) { alertsCache.get(key) } else { alertsCache.remove(key) cacheExpiry.remove(key) null } } } ``` --- ## 📋 ЭТАП 9: ЧЕКЛИСТ ГОТОВНОСТИ ### ✅ Обязательные компоненты: - [ ] Удалены все `temp_token_` токены - [ ] Реализована JWT аутентификация - [ ] WebSocket подключение с правильным URL - [ ] REST API интеграция для всех endpoint'ов - [ ] Обработка ошибок сети и аутентификации - [ ] Push уведомления для экстренных сообщений - [ ] Location services для определения координат - [ ] UI для создания и просмотра экстренных вызовов ### ✅ Тестирование: - [ ] Unit тесты для Repository и ViewModel - [ ] Integration тесты WebSocket подключения - [ ] UI тесты для критических сценариев - [ ] Тесты на различных сетевых условиях - [ ] Тесты безопасности токенов ### ✅ Безопасность: - [ ] Шифрование токенов в хранилище - [ ] Certificate pinning - [ ] Проверка SSL сертификатов - [ ] Обфускация кода - [ ] Защита от reverse engineering ### ✅ Производительность: - [ ] Кеширование данных - [ ] Оптимизация изображений - [ ] Минификация сетевых запросов - [ ] Фоновая синхронизация - [ ] Battery optimization --- ## 🚀 ЭТАП 10: РАЗВЕРТЫВАНИЕ ### 10.1 Build Configuration ```gradle android { buildTypes { debug { buildConfigField "String", "BASE_URL", "\"http://192.168.1.100:8000/\"" buildConfigField "String", "WS_URL", "\"ws://192.168.1.100:8002/\"" } release { buildConfigField "String", "BASE_URL", "\"https://your-production-server.com/\"" buildConfigField "String", "WS_URL", "\"wss://your-production-server.com/\"" minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } ``` ### 10.2 Monitoring and Analytics ```kotlin class EmergencyAnalytics @Inject constructor() { fun trackEmergencyAlertCreated(alertType: String) { FirebaseAnalytics.getInstance(context).logEvent("emergency_alert_created") { param("alert_type", alertType) param("timestamp", System.currentTimeMillis()) } } fun trackWebSocketConnection(success: Boolean, errorMessage: String? = null) { FirebaseAnalytics.getInstance(context).logEvent("websocket_connection") { param("success", success) errorMessage?.let { param("error", it) } } } fun trackResponseTime(endpoint: String, responseTime: Long) { FirebaseAnalytics.getInstance(context).logEvent("api_response_time") { param("endpoint", endpoint) param("response_time_ms", responseTime) } } } ``` --- ## 📞 КОНТАКТЫ И ПОДДЕРЖКА **Сервер endpoints для тестирования:** - API Gateway: `http://YOUR_SERVER:8000` - Emergency Service: `http://YOUR_SERVER:8002` - WebSocket: `ws://YOUR_SERVER:8002/api/v1/emergency/ws/current_user_id?token=JWT_TOKEN` **Тестовые данные:** - Email: `shadow85@list.ru` - Password: `R0sebud1985` **Документация API:** `/home/data/chat/docs/WEBSOCKET_AUTH_EXPLANATION.md` --- Этот промпт содержит полное пошаговое техническое задание для интеграции мобильного приложения с Emergency Service. Следуйте этапам последовательно для успешной реализации! 🚀