diff --git a/sentry-android-buddy/api/sentry-android-buddy.api b/sentry-android-buddy/api/sentry-android-buddy.api index 6b134019de..71602f42df 100644 --- a/sentry-android-buddy/api/sentry-android-buddy.api +++ b/sentry-android-buddy/api/sentry-android-buddy.api @@ -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; } @@ -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 (Ljava/lang/String;JLjava/util/Map;)V @@ -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 (Ljava/lang/String;)V + public fun (Ljava/lang/String;Lokhttp3/OkHttpClient;)V + public synthetic fun (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 ()V public fun (Z)V public fun (ZZ)V public fun (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;)V - public fun (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Ljava/lang/String;)V - public fun (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Ljava/lang/String;Ljava/lang/String;)V - public fun (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;)V + public fun (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Ljava/lang/String;)V + public fun (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Ljava/lang/String;Ljava/lang/String;)V + public fun (ZZLio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (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 @@ -515,13 +536,15 @@ public final class io/sentry/android/buddy/SentryBuddySessionController { public fun ()V public fun (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;)V public fun (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;)V - public fun (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lkotlin/jvm/functions/Function0;)V - public synthetic fun (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;)V + public fun (Lio/sentry/android/buddy/SentryBuddyRecorderFacade;Lio/sentry/android/buddy/SentryBuddyFlowAnalysesApi;Lio/sentry/android/buddy/SentryBuddyOpenUrlApi;Lkotlin/jvm/functions/Function0;)V + public synthetic fun (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 diff --git a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddy.kt b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddy.kt index a5a5e885a5..711adaf647 100644 --- a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddy.kt +++ b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddy.kt @@ -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) } } diff --git a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyHttpOpenUrlApi.kt b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyHttpOpenUrlApi.kt new file mode 100644 index 0000000000..59ba03c187 --- /dev/null +++ b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyHttpOpenUrlApi.kt @@ -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() + } +} diff --git a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyOpenUrlApi.kt b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyOpenUrlApi.kt new file mode 100644 index 0000000000..06890a55d8 --- /dev/null +++ b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyOpenUrlApi.kt @@ -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. + } + } +} diff --git a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyOptions.kt b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyOptions.kt index 5e9bbf2eea..77a5dcba13 100644 --- a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyOptions.kt +++ b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyOptions.kt @@ -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, diff --git a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddySession.kt b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddySession.kt index 7c3b7ddd9b..3ef9c7217b 100644 --- a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddySession.kt +++ b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddySession.kt @@ -1,5 +1,6 @@ package io.sentry.android.buddy +import android.content.Context import java.util.Locale import org.jetbrains.annotations.ApiStatus @@ -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 @@ -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> diff --git a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyUi.kt b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyUi.kt index fdb2977082..11ce8878a0 100644 --- a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyUi.kt +++ b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/SentryBuddyUi.kt @@ -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 @@ -176,6 +173,10 @@ private fun SentryBuddyOverlayContent( } } + fun openUrl(context: Context, url: String) { + analysisScope.launch { withContext(Dispatchers.IO) { controller.openUrl(context, url) } } + } + LaunchedEffect(state) { if (state !is SentryBuddySessionState.Closed) { while (true) { @@ -255,6 +256,7 @@ private fun SentryBuddyOverlayContent( nowMs = nowMs, onDispatch = { dispatch(it) }, onAnalyze = { dispatchAnalysis { analyze() } }, + onOpenUrl = { context, url -> openUrl(context, url) }, ) } } @@ -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 @@ -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) @@ -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() } @@ -902,6 +913,7 @@ private fun LiveFeedSheet( nowMs = nowMs, emptyArtIndex = emptyAttentionArtIndex, onDismiss = { onDispatch { dismissLiveFeedAttention() } }, + onOpenUrl = onOpenUrl, ) Spacer(Modifier.height(12.dp)) Button( @@ -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) } } @@ -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) } @@ -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, @@ -1125,6 +1138,7 @@ private fun LiveFeedRows( items: List, sentryUiLinks: BuddySentryUiLinks, nowMs: Long, + onOpenUrl: (Context, String) -> Unit, ) { val context = LocalContext.current Surface( @@ -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, @@ -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.mapValue(key: String): Map<*, *> = this[key] as? Map<*, *> ?: emptyMap() diff --git a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/internal.kt b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/internal.kt index d1873e412c..5b0998fde4 100644 --- a/sentry-android-buddy/src/main/java/io/sentry/android/buddy/internal.kt +++ b/sentry-android-buddy/src/main/java/io/sentry/android/buddy/internal.kt @@ -599,7 +599,12 @@ internal class BuddyActivityLifecycleCallbacks( } if (overlayManager == null) { overlayManager = - BuddyOverlayManager(SentryBuddySessionController(flowAnalysesApi = options.flowAnalysesApi)) + BuddyOverlayManager( + SentryBuddySessionController( + flowAnalysesApi = options.flowAnalysesApi, + openUrlApi = options.openUrlApi, + ) + ) } overlayManager?.updateOptions(options) } diff --git a/sentry-android-buddy/src/test/java/io/sentry/android/buddy/SentryBuddyHttpOpenUrlApiTest.kt b/sentry-android-buddy/src/test/java/io/sentry/android/buddy/SentryBuddyHttpOpenUrlApiTest.kt new file mode 100644 index 0000000000..7104f6e746 --- /dev/null +++ b/sentry-android-buddy/src/test/java/io/sentry/android/buddy/SentryBuddyHttpOpenUrlApiTest.kt @@ -0,0 +1,53 @@ +package io.sentry.android.buddy + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertFailsWith +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.runner.RunWith +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +@Config(sdk = [31]) +class SentryBuddyHttpOpenUrlApiTest { + private val server = MockWebServer() + + @AfterTest + fun tearDown() { + server.shutdown() + } + + @Test + fun `open posts url to bridge`() { + server.enqueue(MockResponse().setResponseCode(200)) + val api = SentryBuddyHttpOpenUrlApi(server.url("/").toString()) + + api.open(RuntimeEnvironment.getApplication(), "https://sentry.io/issues/1") + + val recordedRequest = server.takeRequest() + assertThat(recordedRequest.method).isEqualTo("POST") + assertThat(recordedRequest.path).isEqualTo("/v1/open-url") + assertThat(recordedRequest.body.readUtf8()) + .isEqualTo("""{"url":"https://sentry.io/issues/1"}""") + } + + @Test + fun `http errors include bridge error message`() { + server.enqueue( + MockResponse().setResponseCode(400).setBody("""{"error":"url must use https"}""") + ) + val api = SentryBuddyHttpOpenUrlApi(server.url("/").toString()) + + val error = + assertFailsWith { + api.open(RuntimeEnvironment.getApplication(), "http://sentry.io") + } + + assertThat(error).hasMessageThat().contains("HTTP 400") + assertThat(error).hasMessageThat().contains("url must use https") + } +} diff --git a/sentry-samples/sentry-samples-android/src/debug/java/io/sentry/samples/android/SentryBuddySampleIntegration.kt b/sentry-samples/sentry-samples-android/src/debug/java/io/sentry/samples/android/SentryBuddySampleIntegration.kt index d19adcfdc6..912fbdbdd1 100644 --- a/sentry-samples/sentry-samples-android/src/debug/java/io/sentry/samples/android/SentryBuddySampleIntegration.kt +++ b/sentry-samples/sentry-samples-android/src/debug/java/io/sentry/samples/android/SentryBuddySampleIntegration.kt @@ -3,12 +3,14 @@ package io.sentry.samples.android import android.app.Application import io.sentry.android.buddy.SentryBuddy import io.sentry.android.buddy.SentryBuddyHttpFlowAnalysesApi +import io.sentry.android.buddy.SentryBuddyHttpOpenUrlApi object SentryBuddySampleIntegration { @JvmStatic fun install(application: Application) { SentryBuddy.install(application) { flowAnalysesApi = SentryBuddyHttpFlowAnalysesApi("http://10.0.2.2:8080") + openUrlApi = SentryBuddyHttpOpenUrlApi("http://10.0.2.2:8080") sentryUiBaseUrl = "https://sentry-sdks.sentry.io" sentryUiOrganizationSlug = "sentry-sdks" sentryUiProjectId = "5428559"