Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions sentry-android-buddy/api/sentry-android-buddy.api
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,11 @@ public final class io/sentry/android/buddy/ComposableSingletons$SentryBuddyUiKt
public final fun getLambda$-1944467248$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function2;
public final fun getLambda$-218216137$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda$-682238859$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda$-90711693$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda$1215034671$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda$1525882558$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda$1884831281$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda$1892283088$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda$342625061$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda$356797945$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda$567083575$sentry_android_buddy_release ()Lkotlin/jvm/functions/Function2;
}
Expand All @@ -331,6 +331,12 @@ public final class io/sentry/android/buddy/DummySentryBuddyFlowAnalysesApi : io/
public fun submit (Lio/sentry/android/buddy/FlowAnalysisRequest;)Lio/sentry/android/buddy/FlowAnalysisResponse;
}

public final class io/sentry/android/buddy/DummySentryBuddyOpenUrlApi : io/sentry/android/buddy/SentryBuddyOpenUrlApi {
public static final field $stable I
public static final field INSTANCE Lio/sentry/android/buddy/DummySentryBuddyOpenUrlApi;
public fun open (Landroid/content/Context;Ljava/lang/String;)V
}

public final class io/sentry/android/buddy/FlowAnalysisEvent : io/sentry/JsonSerializable {
public static final field $stable I
public fun <init> (Ljava/lang/String;JLjava/util/Map;)V
Expand Down Expand Up @@ -480,24 +486,39 @@ public final class io/sentry/android/buddy/SentryBuddyHttpFlowAnalysesApi : io/s
public fun submit (Lio/sentry/android/buddy/FlowAnalysisRequest;)Lio/sentry/android/buddy/FlowAnalysisResponse;
}

public final class io/sentry/android/buddy/SentryBuddyHttpOpenUrlApi : io/sentry/android/buddy/SentryBuddyOpenUrlApi {
public static final field $stable I
public fun <init> (Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Lokhttp3/OkHttpClient;)V
public synthetic fun <init> (Ljava/lang/String;Lokhttp3/OkHttpClient;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun open (Landroid/content/Context;Ljava/lang/String;)V
}

public abstract interface class io/sentry/android/buddy/SentryBuddyOpenUrlApi {
public abstract fun open (Landroid/content/Context;Ljava/lang/String;)V
}

public final class io/sentry/android/buddy/SentryBuddyOptions {
public static final field $stable I
public fun <init> ()V
public fun <init> (Z)V
public fun <init> (ZZ)V
public fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;)V
public fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Ljava/lang/String;)V
public fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Ljava/lang/String;Ljava/lang/String;)V
public fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;)V
public fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Ljava/lang/String;)V
public fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Ljava/lang/String;Ljava/lang/String;)V
public fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getEnabled ()Z
public final fun getFlowAnalysesApi ()Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;
public final fun getOpenUrlApi ()Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;
public final fun getSentryUiBaseUrl ()Ljava/lang/String;
public final fun getSentryUiOrganizationSlug ()Ljava/lang/String;
public final fun getSentryUiProjectId ()Ljava/lang/String;
public final fun getShowOverlay ()Z
public final fun setEnabled (Z)V
public final fun setFlowAnalysesApi (Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;)V
public final fun setOpenUrlApi (Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;)V
public final fun setSentryUiBaseUrl (Ljava/lang/String;)V
public final fun setSentryUiOrganizationSlug (Ljava/lang/String;)V
public final fun setSentryUiProjectId (Ljava/lang/String;)V
Expand All @@ -515,13 +536,15 @@ public final class io/sentry/android/buddy/SentryBuddySessionController {
public fun <init> ()V
public fun <init> (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;)V
public fun <init> (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;)V
public fun <init> (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;)V
public fun <init> (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Lkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun analyze ()V
public final fun briefRecording ()V
public final fun close ()V
public final fun getState ()Lio/sentry/android/buddy/SentryBuddySessionState;
public final fun open ()V
public final fun openUrl (Landroid/content/Context;Ljava/lang/String;)V
public final fun pollFlowAnalysis ()V
public final fun recordAgain ()V
public final fun startRecording ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ public object SentryBuddy {
return null
}
return BuddyOverlayManager(
SentryBuddySessionController(flowAnalysesApi = options.flowAnalysesApi)
SentryBuddySessionController(
flowAnalysesApi = options.flowAnalysesApi,
openUrlApi = options.openUrlApi,
)
)
.also { it.updateOptions(options) }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package io.sentry.android.buddy

import android.content.Context
import io.sentry.ILogger
import io.sentry.JsonObjectReader
import io.sentry.JsonSerializable
import io.sentry.JsonSerializer
import io.sentry.ObjectWriter
import io.sentry.SentryOptions
import java.io.IOException
import java.io.StringReader
import java.io.StringWriter
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jetbrains.annotations.ApiStatus

@ApiStatus.Experimental
public class SentryBuddyHttpOpenUrlApi
@JvmOverloads
public constructor(
private val baseUrl: String,
private val client: OkHttpClient = OkHttpClient(),
) : SentryBuddyOpenUrlApi {
private val json = JsonSerializer(SentryOptions())

override fun open(context: Context, url: String) {
val httpRequest =
Request.Builder()
.url(baseUrl.toHttpUrl().newBuilder().addPathSegments("v1/open-url").build())
.post(serialize(OpenUrlRequest(url)).toRequestBody(JSON_MEDIA_TYPE))
.build()
execute(httpRequest)
}

private fun serialize(request: OpenUrlRequest): String {
val writer = StringWriter()
json.serialize(request, writer)
return writer.toString()
}

private fun execute(request: Request) {
try {
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IllegalStateException(response.errorMessage())
}
}
} catch (exception: IOException) {
throw IllegalStateException(
"Failed to call open URL bridge: ${exception.message}",
exception,
)
}
}

private fun Response.errorMessage(): String {
val error = extractError(body?.string().orEmpty())
return buildString {
append("Open URL bridge request failed with HTTP ").append(code)
error?.let { append(": ").append(it) }
}
}

private fun extractError(body: String): String? {
if (body.isBlank()) {
return null
}
return try {
(JsonObjectReader(StringReader(body)).use { it.nextObjectOrNull() } as? Map<*, *>)
?.get("error")
?.toString()
} catch (_: Exception) {
body.take(200)
}
}

private companion object {
private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaType()
}
}

private data class OpenUrlRequest(val url: String) : JsonSerializable {
@Throws(IOException::class)
override fun serialize(writer: ObjectWriter, logger: ILogger) {
writer.beginObject()
writer.name("url").value(url)
writer.endObject()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.sentry.android.buddy

import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Handler
import android.os.Looper
import org.jetbrains.annotations.ApiStatus

@ApiStatus.Experimental
public interface SentryBuddyOpenUrlApi {
public fun open(context: Context, url: String)
}

@ApiStatus.Experimental
public object DummySentryBuddyOpenUrlApi : SentryBuddyOpenUrlApi {
override fun open(context: Context, url: String) {
if (Looper.myLooper() != Looper.getMainLooper()) {
Handler(Looper.getMainLooper()).post { open(context, url) }
return
}
try {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
} catch (_: ActivityNotFoundException) {
// A debug overlay should not crash the app when no browser can handle the link.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public constructor(
public var enabled: Boolean = true,
public var showOverlay: Boolean = true,
public var flowAnalysesApi: SentryBuddyFlowAnalysesApi = DummySentryBuddyFlowAnalysesApi,
public var openUrlApi: SentryBuddyOpenUrlApi = DummySentryBuddyOpenUrlApi,
public var sentryUiBaseUrl: String? = null,
public var sentryUiOrganizationSlug: String? = null,
public var sentryUiProjectId: String? = null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.android.buddy

import android.content.Context
import java.util.Locale
import org.jetbrains.annotations.ApiStatus

Expand Down Expand Up @@ -189,6 +190,7 @@ public class SentryBuddySessionController
public constructor(
private val recorderFacade: SentryBuddyRecorderFacade = RealSentryBuddyRecorderFacade,
private val flowAnalysesApi: SentryBuddyFlowAnalysesApi = DummySentryBuddyFlowAnalysesApi,
private val openUrlApi: SentryBuddyOpenUrlApi = DummySentryBuddyOpenUrlApi,
private val clock: () -> Long = { System.currentTimeMillis() },
) {
public var state: SentryBuddySessionState = SentryBuddySessionState.Closed
Expand Down Expand Up @@ -347,6 +349,14 @@ public constructor(
state = SentryBuddySessionState.Intro
}

public fun openUrl(context: Context, url: String) {
try {
openUrlApi.open(context, url)
} catch (_: IllegalStateException) {
// A debug overlay should not disrupt the current session when the bridge is unreachable.
}
}

internal fun recordTransientEvent(text: String) {
val event: TransientRecordingEvent
val listeners: List<(TransientRecordingEvent) -> Unit>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package io.sentry.android.buddy

import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing
Expand Down Expand Up @@ -176,6 +173,10 @@ private fun SentryBuddyOverlayContent(
}
}

fun openUrl(context: Context, url: String) {
analysisScope.launch { withContext(Dispatchers.IO) { controller.openUrl(context, url) } }
}
Comment thread
sentry[bot] marked this conversation as resolved.

LaunchedEffect(state) {
if (state !is SentryBuddySessionState.Closed) {
while (true) {
Expand Down Expand Up @@ -255,6 +256,7 @@ private fun SentryBuddyOverlayContent(
nowMs = nowMs,
onDispatch = { dispatch(it) },
onAnalyze = { dispatchAnalysis { analyze() } },
onOpenUrl = { context, url -> openUrl(context, url) },
)
}
}
Expand Down Expand Up @@ -792,6 +794,7 @@ private fun BuddySheet(
nowMs: Long,
onDispatch: (SentryBuddySessionController.() -> Unit) -> Unit,
onAnalyze: () -> Unit,
onOpenUrl: (Context, String) -> Unit,
) {
if (state is SentryBuddySessionState.Closed || state is SentryBuddySessionState.Recording) {
return
Expand Down Expand Up @@ -823,7 +826,14 @@ private fun BuddySheet(
) {
when (state) {
SentryBuddySessionState.LiveFeed ->
LiveFeedSheet(liveFeed, sentryUiLinks, nowMs, onDispatch, ::startRecordingAfterSheetExit)
LiveFeedSheet(
liveFeed,
sentryUiLinks,
nowMs,
onDispatch,
::startRecordingAfterSheetExit,
onOpenUrl,
)
SentryBuddySessionState.Intro -> IntroSheet(::startRecordingAfterSheetExit)
is SentryBuddySessionState.StoppedSummary -> StoppedSummarySheet(state, onDispatch)
is SentryBuddySessionState.Briefing -> BriefingSheet(state, onDispatch, onAnalyze)
Expand Down Expand Up @@ -892,6 +902,7 @@ private fun LiveFeedSheet(
nowMs: Long,
onDispatch: (SentryBuddySessionController.() -> Unit) -> Unit,
onStartRecording: () -> Unit,
onOpenUrl: (Context, String) -> Unit,
) {
SheetTitle("Sentry Buddy", "Live Feed")
val emptyAttentionArtIndex = remember { EmptyAttentionArtIndex.next() }
Expand All @@ -902,6 +913,7 @@ private fun LiveFeedSheet(
nowMs = nowMs,
emptyArtIndex = emptyAttentionArtIndex,
onDismiss = { onDispatch { dismissLiveFeedAttention() } },
onOpenUrl = onOpenUrl,
)
Spacer(Modifier.height(12.dp))
Button(
Expand All @@ -921,7 +933,7 @@ private fun LiveFeedSheet(
if (liveFeed.items.isEmpty()) {
EmptyLiveFeedCard()
} else {
LiveFeedRows(liveFeed.items.take(LIVE_FEED_VISIBLE_ITEM_LIMIT), sentryUiLinks, nowMs)
LiveFeedRows(liveFeed.items.take(LIVE_FEED_VISIBLE_ITEM_LIMIT), sentryUiLinks, nowMs, onOpenUrl)
}
}

Expand All @@ -932,6 +944,7 @@ private fun AttentionCard(
nowMs: Long,
emptyArtIndex: Int,
onDismiss: () -> Unit,
onOpenUrl: (Context, String) -> Unit,
) {
val item = liveFeed.latestUnviewedAdverseItem
val dismissOffset = remember(item?.id) { Animatable(0f) }
Expand Down Expand Up @@ -1002,7 +1015,7 @@ private fun AttentionCard(
modifier =
Modifier.matchParentSize()
.offset { IntOffset(dismissOffset.value.roundToInt(), 0) }
.clickable(enabled = link != null) { link?.let { openSentryLink(context, it) } }
.clickable(enabled = link != null) { link?.let { onOpenUrl(context, it) } }
) {
AttentionItemContent(
item = item,
Expand Down Expand Up @@ -1125,6 +1138,7 @@ private fun LiveFeedRows(
items: List<BuddyLiveFeedItem>,
sentryUiLinks: BuddySentryUiLinks,
nowMs: Long,
onOpenUrl: (Context, String) -> Unit,
) {
val context = LocalContext.current
Surface(
Expand All @@ -1141,7 +1155,7 @@ private fun LiveFeedRows(
Row(
modifier =
Modifier.fillMaxWidth()
.clickable(enabled = link != null) { link?.let { openSentryLink(context, it) } }
.clickable(enabled = link != null) { link?.let { onOpenUrl(context, it) } }
.padding(vertical = 7.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
Expand Down Expand Up @@ -1413,14 +1427,6 @@ private fun relativeTime(timestampMs: Long, nowMs: Long): String {
return "${ageSeconds / 60}m ago"
}

private fun openSentryLink(context: Context, link: String) {
try {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link)))
} catch (_: ActivityNotFoundException) {
// A debug overlay should not crash the app when no browser can handle the link.
}
}

private fun Map<String, Any?>.mapValue(key: String): Map<*, *> =
this[key] as? Map<*, *> ?: emptyMap<Any, Any>()

Expand Down
Loading
Loading