package com.peachpasswords.feedback import com.peachpasswords.BuildConfig import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONObject import javax.inject.Inject import javax.inject.Singleton /** * Submits user feedback to the Peach feedback endpoint. Payload shape mirrors * the browser extension's `SUBMIT_FEEDBACK` handler exactly * (`src/background/messages.ts`): a JSON body of * `{ category, message, version, browser, source }`, POSTed as * `application/json`. * * MN-7: [FEEDBACK_ENDPOINT] is empty until a real backend exists. The browser * returns ok:true (a silent no-op) in this case, but here we deliberately diverge * and throw [FeedbackUnavailableException] so the UI tells the user feedback is * unavailable rather than falsely reporting it was sent. The `source` discriminator * is `"peach-android"` (the browser sends `"peach-extension"`). */ class FeedbackUnavailableException : Exception("feedback_endpoint_not_configured") @Singleton class FeedbackRepository @Inject constructor() { private val client: OkHttpClient by lazy { OkHttpClient() } suspend fun submit(type: String, message: String) { if (message.isBlank()) throw IllegalArgumentException("message_empty") // No endpoint → be honest, never fake a successful send (MN-7). if (FEEDBACK_ENDPOINT.isBlank()) throw FeedbackUnavailableException() withContext(Dispatchers.IO) { val payload = JSONObject().apply { put("category", type) put("message", message) put("version", BuildConfig.VERSION_NAME) put("browser", "android") put("source", "peach-android") } val body = payload.toString().toRequestBody(JSON) val request = Request.Builder() .url(FEEDBACK_ENDPOINT) .post(body) .build() client.newCall(request).execute().use { res -> if (!res.isSuccessful) throw java.io.IOException("HTTP ${res.code}") } } } companion object { // Set to the real Peach feedback endpoint when it exists (the browser source // also ships this empty). While empty, submit() throws so the UI reports the // feature as unavailable rather than silently pretending success (MN-7). const val FEEDBACK_ENDPOINT: String = "" private val JSON = "application/json; charset=utf-8".toMediaType() } }