Skip to content

Commit

Permalink
Merge pull request #1260 from embrace-io/refactor-api-code
Browse files Browse the repository at this point in the history
Begin refactoring api code
  • Loading branch information
fractalwrench authored Aug 19, 2024
2 parents 5ce858a + e56063c commit 801fb81
Show file tree
Hide file tree
Showing 73 changed files with 809 additions and 542 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,35 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import io.embrace.android.embracesdk.internal.clock.Clock
import io.embrace.android.embracesdk.internal.comms.delivery.NetworkStatus
import io.embrace.android.embracesdk.internal.logging.EmbLogger
import io.embrace.android.embracesdk.internal.logging.InternalErrorType
import io.embrace.android.embracesdk.internal.utils.Provider
import io.embrace.android.embracesdk.internal.worker.BackgroundWorker
import java.net.Inet4Address
import java.net.NetworkInterface

@Suppress("DEPRECATION") // uses deprecated APIs for backwards compat
public class EmbraceNetworkConnectivityService(
private val context: Context,
private val clock: Clock,
private val backgroundWorker: BackgroundWorker,
private val logger: EmbLogger,
private val connectivityManager: ConnectivityManager?,
private val networkStatusDataSourceProvider: Provider<NetworkStatusDataSource?>,
private val connectivityManager: ConnectivityManager?
) : BroadcastReceiver(), NetworkConnectivityService {

private val intentFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
private var lastNetworkStatus: NetworkStatus? = null
private var lastNetworkStatus: NetworkStatus = NetworkStatus.UNKNOWN
private val networkConnectivityListeners = mutableListOf<NetworkConnectivityListener>()
override val ipAddress: String? by lazy { calculateIpAddress() }

override fun onReceive(context: Context, intent: Intent): Unit = handleNetworkStatus(true)

override fun networkStatusOnSessionStarted(startTime: Long): Unit = handleNetworkStatus(false, startTime)
override fun networkStatusOnSessionStarted(startTime: Long): Unit = handleNetworkStatus(false)

private fun handleNetworkStatus(notifyListeners: Boolean, timestamp: Long = clock.now()) {
private fun handleNetworkStatus(notifyListeners: Boolean) {
try {
val networkStatus = getCurrentNetworkStatus()
if (didNetworkStatusChange(networkStatus)) {
lastNetworkStatus = networkStatus
networkStatusDataSourceProvider()?.networkStatusChange(networkStatus, timestamp)

if (notifyListeners) {
logger.logInfo("Network status changed to: " + networkStatus.name)
Expand Down Expand Up @@ -82,8 +77,7 @@ public class EmbraceNetworkConnectivityService(
return networkStatus
}

private fun didNetworkStatusChange(newNetworkStatus: NetworkStatus) =
lastNetworkStatus == null || lastNetworkStatus != newNetworkStatus
private fun didNetworkStatusChange(newNetworkStatus: NetworkStatus) = lastNetworkStatus != newNetworkStatus

override fun register() {
backgroundWorker.submit {
Expand All @@ -108,6 +102,7 @@ public class EmbraceNetworkConnectivityService(
*/
override fun addNetworkConnectivityListener(listener: NetworkConnectivityListener) {
networkConnectivityListeners.add(listener)
listener.onNetworkConnectivityStatusChanged(lastNetworkStatus)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.embrace.android.embracesdk.internal.capture.connectivity

import io.embrace.android.embracesdk.internal.comms.delivery.NetworkStatus

public interface NetworkConnectivityListener {
public fun interface NetworkConnectivityListener {

/**
* Called when the network status has changed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ import io.embrace.android.embracesdk.internal.injection.SerializationAction
/**
* A simple interface to make internal HTTP requests to the Embrace API
*/
internal interface ApiClient {
public interface ApiClient {
/**
* Executes [ApiRequest] as a GET, returning the response as a [ApiResponse]
*/
fun executeGet(request: ApiRequest): ApiResponse
public fun executeGet(request: ApiRequest): ApiResponse

/**
* Executes [ApiRequest] as a POST with the supplied action that writes to an outputstream,
* returning the response as a [ApiResponse]. The body will be gzip compressed.
*/
fun executePost(request: ApiRequest, action: SerializationAction): ApiResponse
public fun executePost(request: ApiRequest, action: SerializationAction): ApiResponse

companion object {
public companion object {

const val NO_HTTP_RESPONSE = -1
public const val NO_HTTP_RESPONSE: Int = -1

const val TOO_MANY_REQUESTS = 429
public const val TOO_MANY_REQUESTS: Int = 429

const val defaultTimeoutMs = 60 * 1000
public const val defaultTimeoutMs: Int = 60 * 1000
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.net.HttpURLConnection
* The development endpoint is only used if the build is a debug build, and if integration
* testing is enabled when calling [Embrace.start()].
*/
internal class ApiClientImpl(
public class ApiClientImpl(
private val logger: EmbLogger
) : ApiClient {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.embrace.android.embracesdk.internal.comms.api

import io.embrace.android.embracesdk.network.http.HttpMethod
import java.io.IOException

public fun ApiRequest.getHeaders(): Map<String, String> {
val headers = mutableMapOf(
"Accept" to accept,
"User-Agent" to userAgent,
"Content-Type" to contentType
)
contentEncoding?.let { headers["Content-Encoding"] = it }
acceptEncoding?.let { headers["Accept-Encoding"] = it }
appId?.let { headers["X-EM-AID"] = it }
deviceId?.let { headers["X-EM-DID"] = it }
eventId?.let { headers["X-EM-SID"] = it }
logId?.let { headers["X-EM-LID"] = it }
eTag?.let { headers["If-None-Match"] = it }
return headers
}

internal fun ApiRequest.toConnection(): EmbraceConnection {
try {
val connection = EmbraceUrl.create(url.url).openConnection()

getHeaders().forEach {
connection.setRequestProperty(it.key, it.value)
}
connection.setRequestMethod(httpMethod.name)
if (httpMethod == HttpMethod.POST) {
connection.setDoOutput(true)
}
return connection
} catch (ex: IOException) {
throw IllegalStateException(ex.localizedMessage ?: "", ex)
}
}

/**
* Returns true if the request is a session request. This heuristic should not be widely used
* - it is only used to prioritise session requests over other requests.
*/
public fun ApiRequest.isSessionRequest(): Boolean = url.url.endsWith("spans")
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.embrace.android.embracesdk.internal.comms.api

import io.embrace.android.embracesdk.BuildConfig
import io.embrace.android.embracesdk.core.BuildConfig
import io.embrace.android.embracesdk.internal.payload.Envelope
import io.embrace.android.embracesdk.internal.payload.EventMessage
import io.embrace.android.embracesdk.internal.payload.EventType
Expand All @@ -9,7 +9,7 @@ import io.embrace.android.embracesdk.internal.payload.NetworkEvent
import io.embrace.android.embracesdk.internal.payload.SessionPayload
import io.embrace.android.embracesdk.network.http.HttpMethod

internal class ApiRequestMapper(
public class ApiRequestMapper(
private val urlBuilder: ApiUrlBuilder,
private val lazyDeviceId: Lazy<String>,
private val appId: String
Expand All @@ -27,19 +27,20 @@ internal class ApiRequestMapper(
httpMethod = HttpMethod.POST,
appId = appId,
deviceId = lazyDeviceId.value,
contentEncoding = "gzip"
contentEncoding = "gzip",
userAgent = "Embrace/a/" + BuildConfig.VERSION_NAME
)
}

fun configRequest(url: String) = ApiRequest(
public fun configRequest(url: String): ApiRequest = ApiRequest(
contentType = "application/json",
userAgent = "Embrace/a/" + BuildConfig.VERSION_NAME,
accept = "application/json",
url = ApiRequestUrl(url),
httpMethod = HttpMethod.GET,
)

fun logRequest(
public fun logRequest(
eventMessage: EventMessage
): ApiRequest {
checkNotNull(eventMessage.event) { "event must be set" }
Expand All @@ -53,23 +54,23 @@ internal class ApiRequestMapper(
}

@Suppress("UNUSED_PARAMETER")
fun logsEnvelopeRequest(envelope: Envelope<LogPayload>): ApiRequest {
public fun logsEnvelopeRequest(envelope: Envelope<LogPayload>): ApiRequest {
val url = Endpoint.LOGS.asEmbraceUrl()
return requestBuilder(url)
}

@Suppress("UNUSED_PARAMETER")
fun sessionEnvelopeRequest(envelope: Envelope<SessionPayload>): ApiRequest {
public fun sessionEnvelopeRequest(envelope: Envelope<SessionPayload>): ApiRequest {
val url = Endpoint.SESSIONS_V2.asEmbraceUrl()
return requestBuilder(url)
}

fun sessionRequest(): ApiRequest {
public fun sessionRequest(): ApiRequest {
val url = Endpoint.SESSIONS_V2
return requestBuilder(url.asEmbraceUrl())
}

fun eventMessageRequest(eventMessage: EventMessage): ApiRequest {
public fun eventMessageRequest(eventMessage: EventMessage): ApiRequest {
checkNotNull(eventMessage.event) { "event must be set" }
val event = eventMessage.event
checkNotNull(event.type) { "event type must be set" }
Expand All @@ -84,7 +85,7 @@ internal class ApiRequestMapper(
return requestBuilder(url).copy(eventId = eventIdentifier)
}

fun networkEventRequest(networkEvent: NetworkEvent): ApiRequest {
public fun networkEventRequest(networkEvent: NetworkEvent): ApiRequest {
val url = Endpoint.NETWORK.asEmbraceUrl()
val abbreviation = EventType.NETWORK_LOG.abbreviation
val networkIdentifier = "$abbreviation:${networkEvent.eventId}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package io.embrace.android.embracesdk.internal.comms.api
/**
* ApiResponse is a sealed class that represents the result of an API call.
*/
internal sealed class ApiResponse {
public sealed class ApiResponse {

/**
* Returns true if the API call should be retried.
*/
val shouldRetry: Boolean
public val shouldRetry: Boolean
get() = when (this) {
is TooManyRequests -> true
is Incomplete -> true
Expand All @@ -18,30 +18,30 @@ internal sealed class ApiResponse {
/**
* Represents an API call that returned a 200 OK status code.
*/
data class Success(val body: String?, val headers: Map<String, String>?) : ApiResponse()
public data class Success(val body: String?, val headers: Map<String, String>?) : ApiResponse()

/**
* Represents an API call that returned a 304 Not Modified status code.
*/
object NotModified : ApiResponse()
public object NotModified : ApiResponse()

/**
* Represents an API call that returned a 413 Payload Too Large status code.
*/
object PayloadTooLarge : ApiResponse()
public object PayloadTooLarge : ApiResponse()

/**
* Represents an API call that returned a 429 Too Many Requests status code.
*/
data class TooManyRequests(val endpoint: Endpoint, val retryAfter: Long?) : ApiResponse()
public data class TooManyRequests(val endpoint: Endpoint, val retryAfter: Long?) : ApiResponse()

/**
* Represents a failed API call. (status code 400-499 or 500-599 except 413 and 429)
*/
data class Failure(val code: Int, val headers: Map<String, String>?) : ApiResponse()
public data class Failure(val code: Int, val headers: Map<String, String>?) : ApiResponse()

/**
* Represents an exception thrown while making the API call.
*/
data class Incomplete(val exception: Throwable) : ApiResponse()
public data class Incomplete(val exception: Throwable) : ApiResponse()
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import java.net.URI
* means the eTag can be set in the request header & we can avoid unnecessary work on the client
* & on the server.
*/
internal class ApiResponseCache(
public class ApiResponseCache(
private val serializer: PlatformSerializer,
private val storageService: StorageService,
private val logger: EmbLogger
) : Closeable {

companion object {
private companion object {
private const val MAX_CACHE_SIZE_BYTES: Long = 2 * 1024 * 1024 // 2 MiB
private const val ETAG_HEADER = "ETag"
}
Expand Down Expand Up @@ -56,7 +56,7 @@ internal class ApiResponseCache(
cache?.flush()
}

fun retrieveCachedConfig(url: String, request: ApiRequest): CachedConfig {
public fun retrieveCachedConfig(url: String, request: ApiRequest): CachedConfig {
val cachedResponse = retrieveCacheResponse(url, request)
val obj = cachedResponse?.runCatching {
serializer.fromJson(body, RemoteConfig::class.java)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.embrace.android.embracesdk.internal.comms.api

import io.embrace.android.embracesdk.internal.capture.connectivity.NetworkConnectivityListener
import io.embrace.android.embracesdk.internal.config.RemoteConfigSource
import io.embrace.android.embracesdk.internal.injection.SerializationAction
import io.embrace.android.embracesdk.internal.payload.Envelope
Expand All @@ -8,54 +9,54 @@ import io.embrace.android.embracesdk.internal.payload.LogPayload
import io.embrace.android.embracesdk.internal.payload.NetworkEvent
import java.util.concurrent.Future

internal interface ApiService : RemoteConfigSource {
public interface ApiService : RemoteConfigSource, NetworkConnectivityListener {

/**
* Sends a log message to the API.
*
* @param eventMessage the event message containing the log entry
* @return a future containing the response body from the server
*/
fun sendLog(eventMessage: EventMessage)
public fun sendLog(eventMessage: EventMessage)

/**
* Sends a list of OTel Logs to the API.
*
* @param logEnvelope containing the logs
*/
fun sendLogEnvelope(logEnvelope: Envelope<LogPayload>)
public fun sendLogEnvelope(logEnvelope: Envelope<LogPayload>)

/**
* Saves a list of OTel Logs to disk to be sent on restart.
*
* @param logEnvelope containing the logs
*/
fun saveLogEnvelope(logEnvelope: Envelope<LogPayload>)
public fun saveLogEnvelope(logEnvelope: Envelope<LogPayload>)

/**
* Sends a network event to the API.
*
* @param networkEvent the event containing the network call information
*/
fun sendNetworkCall(networkEvent: NetworkEvent)
public fun sendNetworkCall(networkEvent: NetworkEvent)

/**
* Sends an event to the API.
*
* @param eventMessage the event message containing the event
*/
fun sendEvent(eventMessage: EventMessage)
public fun sendEvent(eventMessage: EventMessage)

/**
* Sends a crash event to the API and reschedules it if the request times out
*
* @param crash the event message containing the crash
*/
fun sendCrash(crash: EventMessage): Future<*>
public fun sendCrash(crash: EventMessage): Future<*>

/**
* Sends a session to the API. This can be either a v1 or v2 session - the implementation
* is responsible for routing the payload correctly.
*/
fun sendSession(action: SerializationAction, onFinish: ((successful: Boolean) -> Unit)?): Future<*>?
public fun sendSession(action: SerializationAction, onFinish: ((successful: Boolean) -> Unit)?): Future<*>?
}
Loading

0 comments on commit 801fb81

Please sign in to comment.