package com.notifyhub.viewer.data.remote import com.google.gson.Gson import com.notifyhub.viewer.BuildConfig import com.notifyhub.viewer.data.prefs.SecurePreferences import okhttp3.CertificatePinner import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit object ApiClient { fun create(preferences: SecurePreferences): NotizApi { val authInterceptor = Interceptor { chain -> val token = preferences.apiToken val builder = chain.request().newBuilder() .addHeader("Accept", "application/json") .addHeader("Content-Type", "application/json") if (!token.isNullOrBlank()) { builder.addHeader("Authorization", "Bearer $token") } chain.proceed(builder.build()) } val logging = HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) { HttpLoggingInterceptor.Level.BODY } else { HttpLoggingInterceptor.Level.NONE } } val clientBuilder = OkHttpClient.Builder() .addInterceptor(authInterceptor) .addInterceptor(logging) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) if (!BuildConfig.DEBUG) { applyCertificatePinning(clientBuilder) } val client = clientBuilder.build() return Retrofit.Builder() .baseUrl(BuildConfig.API_BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() .create(NotizApi::class.java) } private fun applyCertificatePinning(builder: OkHttpClient.Builder) { val host = BuildConfig.PINNED_HOST.takeIf { it.isNotBlank() } ?: return val pinner = CertificatePinner.Builder() .apply { BuildConfig.PINNED_CERT_HASHES.split(',') .map { it.trim() } .filter { it.startsWith("sha256/") } .forEach { add(host, it) } } .build() builder.certificatePinner(pinner) } } object ApiErrorParser { private val gson = Gson() fun message(response: retrofit2.Response<*>): String { val body = parse(response) ?: return "Request failed (${response.code()})" return body.message ?: "Request failed (${response.code()})" } fun code(response: retrofit2.Response<*>): String? = parse(response)?.code private fun parse(response: retrofit2.Response<*>): ApiErrorBody? { val raw = response.errorBody()?.string().orEmpty() if (raw.isBlank()) return null return runCatching { gson.fromJson(raw, ApiErrorBody::class.java) }.getOrNull() } }