diff --git a/CHANGELOG.md b/CHANGELOG.md index 49951384..6ecee4ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,44 @@ ## Unreleased +### Features + +- Allow initializing the KMP SDK with native options ([#221](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/221)) + - This allows you to initialize the SDK with platform-specific options that may not be available in the common code of the KMP SDK yet. + +Usage: +```kotlin +// build.gradle.kts +kotlin { + sourceSets { + all { + languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi") + } + } +} + +// commonMain +fun init() { + Sentry.initWithPlatformOptions(createPlatformOptions()) +} + +expect fun platformOptionsConfiguration(): PlatformOptionsConfiguration + +// iOS +actual fun createPlatformOptions(): PlatformOptionsConfiguration = { + dsn = "your_dsn" + release = "1.0.0" + // ... +} + +// Android +actual fun createPlatformOptions(): PlatformOptionsConfiguration = { + dsn = "your_dsn" + release = "1.0.0" + // ... +} +``` + ### Dependencies - Bump Java SDK from v7.8.0 to v7.9.0 ([#219](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/219)) diff --git a/scripts/build-apple.sh b/scripts/build-apple.sh index fb1c37c5..532922ca 100755 --- a/scripts/build-apple.sh +++ b/scripts/build-apple.sh @@ -11,6 +11,7 @@ PROJECT_NAME="$1" "iosX64Test" \ "watchosX64Test" \ "tvosX64Test" \ + "iosSimulatorArm64Test" \ "publishKotlinMultiplatformPublicationToMavenLocal" \ "publishIosArm64PublicationToMavenLocal" \ "publishIosSimulatorArm64PublicationToMavenLocal" \ diff --git a/sentry-kotlin-multiplatform/api/android/sentry-kotlin-multiplatform.api b/sentry-kotlin-multiplatform/api/android/sentry-kotlin-multiplatform.api index 5691a51c..e52c5bac 100644 --- a/sentry-kotlin-multiplatform/api/android/sentry-kotlin-multiplatform.api +++ b/sentry-kotlin-multiplatform/api/android/sentry-kotlin-multiplatform.api @@ -84,6 +84,7 @@ public final class io/sentry/kotlin/multiplatform/Sentry { public final fun crash ()V public final fun init (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V public final fun init (Lkotlin/jvm/functions/Function1;)V + public final fun initWithPlatformOptions (Lkotlin/jvm/functions/Function1;)V public final fun isCrashedLastRun ()Z public final fun setUser (Lio/sentry/kotlin/multiplatform/protocol/User;)V } diff --git a/sentry-kotlin-multiplatform/api/jvm/sentry-kotlin-multiplatform.api b/sentry-kotlin-multiplatform/api/jvm/sentry-kotlin-multiplatform.api index e916561d..f44183bc 100644 --- a/sentry-kotlin-multiplatform/api/jvm/sentry-kotlin-multiplatform.api +++ b/sentry-kotlin-multiplatform/api/jvm/sentry-kotlin-multiplatform.api @@ -81,6 +81,7 @@ public final class io/sentry/kotlin/multiplatform/Sentry { public final fun crash ()V public final fun init (Lio/sentry/kotlin/multiplatform/Context;Lkotlin/jvm/functions/Function1;)V public final fun init (Lkotlin/jvm/functions/Function1;)V + public final fun initWithPlatformOptions (Lkotlin/jvm/functions/Function1;)V public final fun isCrashedLastRun ()Z public final fun setUser (Lio/sentry/kotlin/multiplatform/protocol/User;)V } diff --git a/sentry-kotlin-multiplatform/build.gradle.kts b/sentry-kotlin-multiplatform/build.gradle.kts index 45dda945..7bffbefe 100644 --- a/sentry-kotlin-multiplatform/build.gradle.kts +++ b/sentry-kotlin-multiplatform/build.gradle.kts @@ -1,4 +1,5 @@ import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -64,6 +65,11 @@ kotlin { macosArm64() sourceSets { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + all { languageSettings.apply { optIn("kotlinx.cinterop.ExperimentalForeignApi") @@ -87,7 +93,7 @@ kotlin { } androidMain.dependencies { - implementation(Config.Libs.sentryAndroid) + api(Config.Libs.sentryAndroid) } // androidUnitTest.dependencies doesn't exist @@ -102,7 +108,7 @@ kotlin { val commonJvmMain by creating { dependsOn(commonMain.get()) dependencies { - implementation(Config.Libs.sentryJava) + api(Config.Libs.sentryJava) } } diff --git a/sentry-kotlin-multiplatform/sentry_kotlin_multiplatform.podspec b/sentry-kotlin-multiplatform/sentry_kotlin_multiplatform.podspec index c2fab5b2..80f877cb 100644 --- a/sentry-kotlin-multiplatform/sentry_kotlin_multiplatform.podspec +++ b/sentry-kotlin-multiplatform/sentry_kotlin_multiplatform.podspec @@ -50,4 +50,4 @@ Pod::Spec.new do |spec| } ] -end +end \ No newline at end of file diff --git a/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.android.kt b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/Context.android.kt similarity index 80% rename from sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.android.kt rename to sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/Context.android.kt index f895e3b5..aa0d51e7 100644 --- a/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.android.kt +++ b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/Context.android.kt @@ -6,27 +6,12 @@ import android.content.Context import android.database.Cursor import android.net.Uri import io.sentry.android.core.SentryAndroid -import io.sentry.kotlin.multiplatform.extensions.toAndroidSentryOptionsCallback -internal actual fun initSentry(configuration: OptionsConfiguration) { - val options = SentryOptions() - configuration.invoke(options) - - val context = applicationContext ?: run { - // TODO: add logging later - return - } - - SentryAndroid.init(context) { sentryOptions -> - options.toAndroidSentryOptionsCallback().invoke(sentryOptions) - } -} +public actual typealias Context = Context internal var applicationContext: Context? = null private set -public actual typealias Context = Context - /** * A ContentProvider that does NOT store or provide any data for read or write operations. * diff --git a/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.android.kt b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.android.kt new file mode 100644 index 00000000..92631332 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.android.kt @@ -0,0 +1,14 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.android.core.SentryAndroid + +internal actual class SentryPlatformInstance : SentryInstance { + override fun init(configuration: PlatformOptionsConfiguration) { + val context = applicationContext ?: run { + // TODO: add logging later + return + } + + SentryAndroid.init(context, configuration) + } +} diff --git a/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.android.kt b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.android.kt new file mode 100644 index 00000000..2770c89a --- /dev/null +++ b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.android.kt @@ -0,0 +1,20 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.extensions.toAndroidSentryOptionsCallback + +public actual typealias SentryPlatformOptions = io.sentry.android.core.SentryAndroidOptions + +internal actual fun SentryPlatformOptions.prepareForInit() { + sdkVersion?.name = BuildKonfig.SENTRY_KMP_ANDROID_SDK_NAME + sdkVersion?.version = BuildKonfig.VERSION_NAME + if (sdkVersion?.packageSet?.none { it.name == BuildKonfig.SENTRY_ANDROID_PACKAGE_NAME } == true) { + sdkVersion?.addPackage(BuildKonfig.SENTRY_ANDROID_PACKAGE_NAME, BuildKonfig.SENTRY_ANDROID_VERSION) + } +} + +internal actual fun SentryOptions.toPlatformOptionsConfiguration(): PlatformOptionsConfiguration = + toAndroidSentryOptionsCallback() + +internal actual fun SentryPlatformOptions.prepareForInitBridge() { + prepareForInit() +} diff --git a/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.android.kt b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.android.kt index 3688bc3b..02fa5a96 100644 --- a/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.android.kt +++ b/sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.android.kt @@ -1,7 +1,6 @@ package io.sentry.kotlin.multiplatform.extensions import io.sentry.android.core.SentryAndroidOptions -import io.sentry.kotlin.multiplatform.BuildKonfig import io.sentry.kotlin.multiplatform.SentryOptions import kotlin.collections.forEach as kForEach @@ -15,16 +14,9 @@ internal fun SentryOptions.toAndroidSentryOptionsCallback(): (SentryAndroidOptio it.isAnrEnabled = this.isAnrEnabled it.anrTimeoutIntervalMillis = this.anrTimeoutIntervalMillis - it.sdkVersion?.name = this.sdk?.name ?: BuildKonfig.SENTRY_KMP_ANDROID_SDK_NAME - it.sdkVersion?.version = this.sdk?.version ?: BuildKonfig.VERSION_NAME - // kForEach solves an issue with linter where it thinks forEach is the Java version // see here: https://stackoverflow.com/questions/44751469/kotlin-extension-functions-suddenly-require-api-level-24/68897591#68897591 this.sdk?.packages?.kForEach { sdkPackage -> it.sdkVersion?.addPackage(sdkPackage.name, sdkPackage.version) } - - if (it.sdkVersion?.packages?.none { it.name == BuildKonfig.SENTRY_ANDROID_PACKAGE_NAME } == true) { - it.sdkVersion?.addPackage(BuildKonfig.SENTRY_ANDROID_PACKAGE_NAME, BuildKonfig.SENTRY_ANDROID_VERSION) - } } diff --git a/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt b/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt index 87a14629..d3287734 100644 --- a/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt +++ b/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt @@ -13,6 +13,10 @@ actual abstract class BaseSentryTest { Sentry.init(optionsConfiguration) } + actual fun sentryInitWithPlatformOptions(platformOptionsConfiguration: PlatformOptionsConfiguration) { + Sentry.initWithPlatformOptions(platformOptionsConfiguration) + } + @BeforeTest open fun setUp() { // Set up the provider needed for Sentry.init on Android diff --git a/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.android.kt b/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.android.kt index 1ba45516..d91ec957 100644 --- a/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.android.kt +++ b/sentry-kotlin-multiplatform/src/androidUnitTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.android.kt @@ -2,6 +2,7 @@ package io.sentry.kotlin.multiplatform import io.sentry.android.core.SentryAndroidOptions import io.sentry.kotlin.multiplatform.extensions.toAndroidSentryOptionsCallback +import io.sentry.kotlin.multiplatform.utils.fakeDsn import kotlin.test.assertEquals actual interface PlatformOptions : CommonPlatformOptions { @@ -75,3 +76,7 @@ actual fun PlatformOptions.assertPlatformSpecificOptions(options: SentryOptions) assertEquals(isAnrEnabled, options.isAnrEnabled) assertEquals(anrTimeoutIntervalMillis, options.anrTimeoutIntervalMillis) } + +actual fun createSentryPlatformOptionsConfiguration(): PlatformOptionsConfiguration = { + it.dsn = fakeDsn +} diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryApple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryApple.kt index 2f897b86..610486f3 100644 --- a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryApple.kt +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryApple.kt @@ -5,6 +5,6 @@ import io.sentry.kotlin.multiplatform.nsexception.setSentryUnhandledExceptionHoo /** Convenience extension to setup unhandled exception hook */ internal fun SentrySDK.Companion.start(configuration: (CocoaSentryOptions?) -> Unit) { - this.startWithConfigureOptions(configuration) + startWithConfigureOptions(configuration) setSentryUnhandledExceptionHook() } diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt index 409fb4b8..fad46614 100644 --- a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt @@ -1,10 +1,13 @@ package io.sentry.kotlin.multiplatform +import NSException.Sentry.SentryEvent +import PrivateSentrySDKOnly.Sentry.PrivateSentrySDKOnly import cocoapods.Sentry.SentrySDK import io.sentry.kotlin.multiplatform.extensions.toCocoaBreadcrumb import io.sentry.kotlin.multiplatform.extensions.toCocoaUser import io.sentry.kotlin.multiplatform.extensions.toCocoaUserFeedback import io.sentry.kotlin.multiplatform.nsexception.asNSException +import io.sentry.kotlin.multiplatform.nsexception.dropKotlinCrashEvent import io.sentry.kotlin.multiplatform.protocol.Breadcrumb import io.sentry.kotlin.multiplatform.protocol.SentryId import io.sentry.kotlin.multiplatform.protocol.User @@ -14,16 +17,53 @@ import platform.Foundation.NSException public actual abstract class Context -internal expect fun initSentry(configuration: OptionsConfiguration) +// Since the function is the same on all apple platforms, we don't split it into expect/actual +// like on JVM and Android, we may do that later on if needed. +internal actual fun SentryPlatformOptions.prepareForInit() { + val cocoa = this as? CocoaSentryOptions + val existingBeforeSend = cocoa?.beforeSend + val modifiedBeforeSend: (CocoaSentryEvent?) -> CocoaSentryEvent? = beforeSend@{ event -> + // Return early if the user's beforeSend returns null + if (existingBeforeSend?.invoke(event) == null) { + return@beforeSend null + } + + val cocoaName = BuildKonfig.SENTRY_COCOA_PACKAGE_NAME + val cocoaVersion = BuildKonfig.SENTRY_COCOA_VERSION + + val sdk = event?.sdk?.toMutableMap() ?: mutableMapOf() + val packages = sdk["packages"] as? MutableList> ?: mutableListOf() + + packages.add(mapOf("name" to cocoaName, "version" to cocoaVersion)) + sdk["packages"] = packages + event?.sdk = sdk + + dropKotlinCrashEvent(event as SentryEvent?) as CocoaSentryEvent? + } -internal actual object SentryBridge { + cocoa?.setBeforeSend(modifiedBeforeSend) + PrivateSentrySDKOnly.setSdkName(BuildKonfig.SENTRY_KMP_COCOA_SDK_NAME, BuildKonfig.VERSION_NAME) +} + +internal actual class SentryBridge actual constructor(private val sentryInstance: SentryInstance) { actual fun init(context: Context, configuration: OptionsConfiguration) { - initSentry(configuration) + init(configuration) } actual fun init(configuration: OptionsConfiguration) { - initSentry(configuration) + val options = SentryOptions() + configuration.invoke(options) + initWithPlatformOptions(options.toPlatformOptionsConfiguration()) + } + + actual fun initWithPlatformOptions(configuration: PlatformOptionsConfiguration) { + val finalConfiguration: PlatformOptionsConfiguration = { + configuration(it) + // We modify beforeSend so we need this to run after the user's configuration + it.prepareForInit() + } + sentryInstance.init(finalConfiguration) } actual fun captureMessage(message: String): SentryId { diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.apple.kt index 419c429b..91361d3b 100644 --- a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.apple.kt +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.apple.kt @@ -1,16 +1,11 @@ package io.sentry.kotlin.multiplatform.extensions -import PrivateSentrySDKOnly.Sentry.PrivateSentrySDKOnly import cocoapods.Sentry.SentryHttpStatusCodeRange -import io.sentry.kotlin.multiplatform.BuildKonfig -import io.sentry.kotlin.multiplatform.CocoaSentryEvent import io.sentry.kotlin.multiplatform.CocoaSentryOptions import io.sentry.kotlin.multiplatform.SentryEvent import io.sentry.kotlin.multiplatform.SentryOptions -import io.sentry.kotlin.multiplatform.nsexception.dropKotlinCrashEvent import kotlinx.cinterop.convert import platform.Foundation.NSNumber -import NSException.Sentry.SentryEvent as NSExceptionSentryEvent internal fun SentryOptions.toCocoaOptionsConfiguration(): (CocoaSentryOptions?) -> Unit = { it?.applyCocoaBaseOptions(this) @@ -42,39 +37,26 @@ internal fun CocoaSentryOptions.applyCocoaBaseOptions(options: SentryOptions) { tracesSampleRate = NSNumber(double = it) } beforeSend = { event -> - val cocoaName = BuildKonfig.SENTRY_COCOA_PACKAGE_NAME - val cocoaVersion = BuildKonfig.SENTRY_COCOA_VERSION - val sdk = event?.sdk?.toMutableMap() val packages = options.sdk?.packages?.map { mapOf("name" to it.name, "version" to it.version) }?.toMutableList() ?: mutableListOf() - val names = packages.map { it["name"] } - if (!names.contains(cocoaName)) { - packages.add(mapOf("name" to cocoaName, "version" to cocoaVersion)) - } - sdk?.set("packages", packages) event?.sdk = sdk if (options.beforeSend == null) { - dropKotlinCrashEvent(event as NSExceptionSentryEvent?) as CocoaSentryEvent? + event } else { - val modifiedEvent = event?.let { SentryEvent(it) }?.let { unwrappedEvent -> + event?.let { SentryEvent(it) }?.let { unwrappedEvent -> val result = options.beforeSend?.invoke(unwrappedEvent) result?.let { event.applyKmpEvent(it) } } - dropKotlinCrashEvent(modifiedEvent as NSExceptionSentryEvent?) as CocoaSentryEvent? } } - val sdkName = options.sdk?.name ?: BuildKonfig.SENTRY_KMP_COCOA_SDK_NAME - val sdkVersion = options.sdk?.version ?: BuildKonfig.VERSION_NAME - PrivateSentrySDKOnly.setSdkName(sdkName, sdkVersion) - beforeBreadcrumb = { cocoaBreadcrumb -> if (options.beforeBreadcrumb == null) { cocoaBreadcrumb diff --git a/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt b/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt index 1ea225ae..3fdcc19e 100644 --- a/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt +++ b/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt @@ -6,4 +6,8 @@ actual abstract class BaseSentryTest { actual fun sentryInit(optionsConfiguration: OptionsConfiguration) { Sentry.init(optionsConfiguration) } + + actual fun sentryInitWithPlatformOptions(platformOptionsConfiguration: PlatformOptionsConfiguration) { + Sentry.initWithPlatformOptions(platformOptionsConfiguration) + } } diff --git a/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/SentryBridgeTest.apple.kt b/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/SentryBridgeTest.apple.kt new file mode 100644 index 00000000..34a99e7b --- /dev/null +++ b/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/SentryBridgeTest.apple.kt @@ -0,0 +1,116 @@ +package io.sentry.kotlin.multiplatform + +import PrivateSentrySDKOnly.Sentry.PrivateSentrySDKOnly +import io.sentry.kotlin.multiplatform.fakes.FakeSentryInstance +import io.sentry.kotlin.multiplatform.utils.fakeDsn +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +actual class SentryBridgeTest { + private lateinit var fixture: Fixture + + @BeforeTest + fun `set up`() { + fixture = Fixture() + } + + @Test + actual fun `init sets correct configuration`() { + // Given + val configuration: OptionsConfiguration = { + it.dsn = fakeDsn + it.release = "1.0.0" + } + + // When + fixture.sut.init(configuration) + + // Then + val expectedOptions = SentryOptions().apply(configuration) + val actualOptions = + SentryPlatformOptions().apply(fixture.sentryInstance.lastConfiguration!!) as CocoaSentryOptions + + // Note: We don't test every single combination because creating a converter from + // Platform options to SentryOptions for every single platform and every property is overkill + // We test a few properties to make sure the conversion is working + assertEquals(expectedOptions.dsn, actualOptions.dsn) + assertEquals(expectedOptions.release, actualOptions.releaseName) + } + + @Test + actual fun `init sets SDK version and name`() { + // Given + val configuration: OptionsConfiguration = { + it.dsn = fakeDsn + it.release = "1.0.0" + } + + // When + fixture.sut.init(configuration) + + // Then + assertTrue(PrivateSentrySDKOnly.getSdkName()!!.contains("kmp")) + assertEquals(PrivateSentrySDKOnly.getSdkVersionString()!!, BuildKonfig.VERSION_NAME) + } + + @Test + actual fun `setting null in beforeSend during init drops the event`() { + // GIVEN + fixture.sut.init { + it.beforeSend = { + null + } + } + + // WHEN + val option = SentryPlatformOptions().apply { + fixture.sentryInstance.lastConfiguration?.invoke(this) + }.let { it as CocoaSentryOptions } + + // THEN + assert(option.beforeSend != null) + assert(option.beforeSend!!.invoke(CocoaSentryEvent()) == null) + } + + @Test + actual fun `default beforeSend in init does not drop the event`() { + // GIVEN + fixture.sut.init { } + + // WHEN + val option = SentryPlatformOptions().apply { + fixture.sentryInstance.lastConfiguration?.invoke(this) + }.let { it as CocoaSentryOptions } + + // THEN + assert(option.beforeSend != null) + assert(option.beforeSend!!.invoke(CocoaSentryEvent()) != null) + } + + @Test + actual fun `init sets the SDK packages`() { + // GIVEN + fixture.sut.init { } + + // WHEN + val option = SentryPlatformOptions().apply { + fixture.sentryInstance.lastConfiguration?.invoke(this) + }.let { it as CocoaSentryOptions } + val event = option.beforeSend!!.invoke(CocoaSentryEvent()) + val packages = event?.sdk()?.get("packages") as? List> + + // THEN + assert(option.beforeSend != null) + assert(packages != null) + assert(packages!!.isNotEmpty()) + assert(packages.find { it["name"]!!.contains("cocoa") } != null) + } + + internal class Fixture { + val sentryInstance = FakeSentryInstance() + + val sut get() = SentryBridge(sentryInstance) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.commonJvm.kt similarity index 75% rename from sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt rename to sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.commonJvm.kt index 68eab581..66443f27 100644 --- a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.commonJvm.kt @@ -9,16 +9,25 @@ import io.sentry.kotlin.multiplatform.protocol.SentryId import io.sentry.kotlin.multiplatform.protocol.User import io.sentry.kotlin.multiplatform.protocol.UserFeedback -internal expect fun initSentry(configuration: OptionsConfiguration) - -internal actual object SentryBridge { +internal expect fun SentryPlatformOptions.prepareForInitBridge() +internal actual class SentryBridge actual constructor(private val sentryInstance: SentryInstance) { actual fun init(context: Context, configuration: OptionsConfiguration) { - initSentry(configuration) + init(configuration) } actual fun init(configuration: OptionsConfiguration) { - initSentry(configuration = configuration) + val options = SentryOptions() + configuration.invoke(options) + initWithPlatformOptions(options.toPlatformOptionsConfiguration()) + } + + actual fun initWithPlatformOptions(configuration: PlatformOptionsConfiguration) { + val finalConfiguration: PlatformOptionsConfiguration = { + configuration(it) + it.prepareForInitBridge() + } + sentryInstance.init(finalConfiguration) } actual fun captureMessage(message: String): SentryId { @@ -37,7 +46,8 @@ internal actual object SentryBridge { } actual fun captureException(throwable: Throwable, scopeCallback: ScopeCallback): SentryId { - val jvmSentryId = Sentry.captureException(throwable, configureScopeCallback(scopeCallback)) + val jvmSentryId = + Sentry.captureException(throwable, configureScopeCallback(scopeCallback)) return SentryId(jvmSentryId.toString()) } diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.jvm.kt index 3821083b..cb559b85 100644 --- a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.jvm.kt +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.jvm.kt @@ -1,6 +1,5 @@ package io.sentry.kotlin.multiplatform.extensions -import io.sentry.kotlin.multiplatform.BuildKonfig import io.sentry.kotlin.multiplatform.JvmSentryOptions import io.sentry.kotlin.multiplatform.SentryEvent import io.sentry.kotlin.multiplatform.SentryOptions @@ -8,20 +7,9 @@ import io.sentry.kotlin.multiplatform.SentryOptions internal fun SentryOptions.toJvmSentryOptionsCallback(): (JvmSentryOptions) -> Unit = { it.applyJvmBaseOptions(this) - // Apply JVM specific options - it.sdkVersion?.name = sdk?.name ?: BuildKonfig.SENTRY_KMP_JAVA_SDK_NAME - it.sdkVersion?.version = sdk?.version ?: BuildKonfig.VERSION_NAME - sdk?.packages?.forEach { sdkPackage -> it.sdkVersion?.addPackage(sdkPackage.name, sdkPackage.version) } - - if (it.sdkVersion?.packages?.none { it.name == BuildKonfig.SENTRY_JAVA_PACKAGE_NAME } == true) { - it.sdkVersion?.addPackage( - BuildKonfig.SENTRY_JAVA_PACKAGE_NAME, - BuildKonfig.SENTRY_JAVA_VERSION - ) - } } /** diff --git a/sentry-kotlin-multiplatform/src/commonJvmTest/kotlin/io/sentry/kotlin/multiplatform/SentryBridgeTest.commonJvm.kt b/sentry-kotlin-multiplatform/src/commonJvmTest/kotlin/io/sentry/kotlin/multiplatform/SentryBridgeTest.commonJvm.kt new file mode 100644 index 00000000..da16380e --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonJvmTest/kotlin/io/sentry/kotlin/multiplatform/SentryBridgeTest.commonJvm.kt @@ -0,0 +1,114 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.Hint +import io.sentry.kotlin.multiplatform.fakes.FakeSentryInstance +import io.sentry.kotlin.multiplatform.utils.fakeDsn +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +actual class SentryBridgeTest { + private lateinit var fixture: Fixture + + @BeforeTest + fun `set up`() { + fixture = Fixture() + } + + @Test + actual fun `init sets correct configuration`() { + // Given + val configuration: OptionsConfiguration = { + it.dsn = fakeDsn + it.release = "1.0.0" + } + + // When + fixture.sut.init(configuration) + + // Then + val expectedOptions = SentryOptions().apply(configuration) + val actualOptions = + SentryPlatformOptions().apply(fixture.sentryInstance.lastConfiguration!!) as JvmSentryOptions + + // Note: We don't test every single combination because creating a converter from + // Platform options to SentryOptions for every single platform and every property is overkill + // We test a few properties to make sure the conversion is working + assertEquals(expectedOptions.dsn, actualOptions.dsn) + assertEquals(expectedOptions.release, actualOptions.release) + } + + @Test + actual fun `setting null in beforeSend during init drops the event`() { + // GIVEN + fixture.sut.init { + it.beforeSend = { + null + } + } + + // WHEN + val option = SentryPlatformOptions().apply { + fixture.sentryInstance.lastConfiguration?.invoke(this) + }.let { it as JvmSentryOptions } + + // THEN + assert(option.beforeSend != null) + assert(option.beforeSend!!.execute(JvmSentryEvent(), Hint()) == null) + } + + @Test + actual fun `default beforeSend in init does not drop the event`() { + // GIVEN + fixture.sut.init { } + + // WHEN + val option = SentryPlatformOptions().apply { + fixture.sentryInstance.lastConfiguration?.invoke(this) + }.let { it as JvmSentryOptions } + + // THEN + assert(option.beforeSend != null) + assert(option.beforeSend!!.execute(JvmSentryEvent(), Hint()) != null) + } + + @Test + actual fun `init sets the SDK packages`() { + // WHEN + fixture.sut.init { } + val option = SentryPlatformOptions().apply { + fixture.sentryInstance.lastConfiguration?.invoke(this) + }.let { it as JvmSentryOptions } + + // THEN + assert(option.sdkVersion?.packageSet != null) + assert(option.sdkVersion?.packageSet!!.isNotEmpty()) + assert(option.sdkVersion?.packageSet!!.find { it.name.contains("sentry") } != null) + } + + @Test + actual fun `init sets SDK version and name`() { + // Given + val configuration: OptionsConfiguration = { + it.dsn = fakeDsn + it.release = "1.0.0" + } + + // When + fixture.sut.init(configuration) + val option = SentryPlatformOptions().apply { + fixture.sentryInstance.lastConfiguration?.invoke(this) + }.let { it as JvmSentryOptions } + + // Then + assertTrue(option.sdkVersion!!.name.contains("kmp")) + assertEquals(option.sdkVersion!!.version, BuildKonfig.VERSION_NAME) + } + + internal class Fixture { + val sentryInstance = FakeSentryInstance() + + val sut get() = SentryBridge(sentryInstance) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt index 72111626..8b517677 100644 --- a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt @@ -5,12 +5,13 @@ import io.sentry.kotlin.multiplatform.protocol.SentryId import io.sentry.kotlin.multiplatform.protocol.User import io.sentry.kotlin.multiplatform.protocol.UserFeedback -internal expect object SentryBridge { - +internal expect class SentryBridge(sentryInstance: SentryInstance = SentryPlatformInstance()) { fun init(context: Context, configuration: OptionsConfiguration) fun init(configuration: OptionsConfiguration) + fun initWithPlatformOptions(configuration: PlatformOptionsConfiguration) + fun captureMessage(message: String): SentryId fun captureMessage(message: String, scopeCallback: ScopeCallback): SentryId diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt index 3b52dd56..6d9b2f3e 100644 --- a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt @@ -9,13 +9,15 @@ import kotlin.native.HiddenFromObjC public typealias ScopeCallback = (Scope) -> Unit public typealias OptionsConfiguration = (SentryOptions) -> Unit +public typealias PlatformOptionsConfiguration = (SentryPlatformOptions) -> Unit -/** The context used for Android initialization. */ +/** The context used for Android initiaclization. */ @Deprecated("No longer necessary to initialize Sentry on Android.") public expect abstract class Context /** Sentry Kotlin Multiplatform SDK API entry point. */ public object Sentry { + private val bridge = SentryBridge() /** * Sentry initialization with an option configuration handler. @@ -30,7 +32,7 @@ public object Sentry { ReplaceWith("Sentry.init(configuration)") ) public fun init(context: Context, configuration: OptionsConfiguration) { - SentryBridge.init(context, configuration) + bridge.init(context, configuration) } /** @@ -40,7 +42,28 @@ public object Sentry { * @param configuration Options configuration handler. */ public fun init(configuration: OptionsConfiguration) { - SentryBridge.init(configuration = configuration) + bridge.init(configuration = configuration) + } + + /** + * Sentry initialization with an option configuration handler for platform options. + * + * Since [SentryPlatformOptions] is implemented via typealias you need to have direct access + * to the platform-specific SDKs API to use this method. + * + * ### Java / Android + * You can achieve this by specifying `compileOnly(java/android_dependency)` + * in your `build.gradle.kts`. + * + * ### Cocoa + * - This integration requires the use of the Cocoapods gradle plugin and + * adding Sentry as a pod + * + * @see SentryPlatformOptions + * @param configuration Platform options configuration handler. + */ + public fun initWithPlatformOptions(configuration: PlatformOptionsConfiguration) { + bridge.initWithPlatformOptions(configuration) } /** @@ -49,7 +72,7 @@ public object Sentry { * @param message The message to send. */ public fun captureMessage(message: String): SentryId { - return SentryBridge.captureMessage(message) + return bridge.captureMessage(message) } /** @@ -59,7 +82,7 @@ public object Sentry { * @param scopeCallback The local scope callback. */ public fun captureMessage(message: String, scopeCallback: ScopeCallback): SentryId { - return SentryBridge.captureMessage(message, scopeCallback) + return bridge.captureMessage(message, scopeCallback) } /** @@ -68,7 +91,7 @@ public object Sentry { * @param throwable The exception. */ public fun captureException(throwable: Throwable): SentryId { - return SentryBridge.captureException(throwable) + return bridge.captureException(throwable) } /** @@ -78,7 +101,7 @@ public object Sentry { * @param scopeCallback The local scope callback. */ public fun captureException(throwable: Throwable, scopeCallback: ScopeCallback): SentryId { - return SentryBridge.captureException(throwable, scopeCallback) + return bridge.captureException(throwable, scopeCallback) } /** @@ -87,7 +110,7 @@ public object Sentry { * @param userFeedback The user feedback to send to Sentry. */ public fun captureUserFeedback(userFeedback: UserFeedback) { - return SentryBridge.captureUserFeedback(userFeedback) + return bridge.captureUserFeedback(userFeedback) } /** @@ -96,7 +119,7 @@ public object Sentry { * @param scopeCallback The configure scope callback. */ public fun configureScope(scopeCallback: ScopeCallback) { - SentryBridge.configureScope(scopeCallback) + bridge.configureScope(scopeCallback) } /** @@ -105,7 +128,7 @@ public object Sentry { * @param breadcrumb The breadcrumb to add. */ public fun addBreadcrumb(breadcrumb: Breadcrumb) { - SentryBridge.addBreadcrumb(breadcrumb) + bridge.addBreadcrumb(breadcrumb) } /** @@ -114,14 +137,14 @@ public object Sentry { * @param user The user to set. */ public fun setUser(user: User?) { - SentryBridge.setUser(user) + bridge.setUser(user) } /** * Returns true if the app crashed during last run. */ public fun isCrashedLastRun(): Boolean { - return SentryBridge.isCrashedLastRun() + return bridge.isCrashedLastRun() } /** @@ -135,6 +158,6 @@ public object Sentry { * Closes the SDK. */ public fun close() { - SentryBridge.close() + bridge.close() } } diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.kt new file mode 100644 index 00000000..e32b9e5d --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.kt @@ -0,0 +1,10 @@ +package io.sentry.kotlin.multiplatform + +internal interface SentryInstance { + fun init(configuration: PlatformOptionsConfiguration) +} + +/** + * Represents the actual Sentry SDK instance. + */ +internal expect class SentryPlatformInstance() : SentryInstance diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.kt new file mode 100644 index 00000000..e2772145 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.kt @@ -0,0 +1,26 @@ +package io.sentry.kotlin.multiplatform + +/** + * Platform-specific configuration class for Sentry. + * + * This class is expected to be implemented on each platform. + * It is directly type aliased to the platform-specific configuration options provided by the + * native Sentry SDKs, allowing developers to utilize native platform capabilities and + * override settings with configurations that are specific to each platform. + * + * This is useful if you need to configure options that might not be supported through the + * KMP SDK in the common code or are still experimental in the native sdks. + * + * Using this class in common code will do nothing, as its implementation + * is platform-dependent and expected to be empty in shared code. + * + * @see Sentry.initWithPlatformOptions + */ +public expect class SentryPlatformOptions() + +/** + * Prepare the platform-specific options for initialization. + */ +internal expect fun SentryPlatformOptions.prepareForInit() + +internal expect fun SentryOptions.toPlatformOptionsConfiguration(): PlatformOptionsConfiguration diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt index a23652f5..512b69c4 100644 --- a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt @@ -4,4 +4,5 @@ expect abstract class BaseSentryTest() { val platform: String val authToken: String? fun sentryInit(optionsConfiguration: OptionsConfiguration) + fun sentryInitWithPlatformOptions(platformOptionsConfiguration: PlatformOptionsConfiguration) } diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.kt index 3e2ab65e..9bf680cb 100644 --- a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.kt +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.kt @@ -20,3 +20,5 @@ interface CommonPlatformOptions { expect interface PlatformOptions : CommonPlatformOptions expect fun createPlatformOptions(): PlatformOptions + +expect fun createSentryPlatformOptionsConfiguration(): PlatformOptionsConfiguration diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryBridgeTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryBridgeTest.kt new file mode 100644 index 00000000..76514aca --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryBridgeTest.kt @@ -0,0 +1,9 @@ +package io.sentry.kotlin.multiplatform + +expect class SentryBridgeTest { + fun `init sets correct configuration`() + fun `setting null in beforeSend during init drops the event`() + fun `default beforeSend in init does not drop the event`() + fun `init sets the SDK packages`() + fun `init sets SDK version and name`() +} diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryIntegrationTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryIntegrationTest.kt index 8acbef9d..9cb7a8b0 100644 --- a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryIntegrationTest.kt +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryIntegrationTest.kt @@ -17,6 +17,12 @@ class SentryIntegrationTest : BaseSentryTest() { Sentry.close() } + @Test + fun `init with native options is successful`() { + val sentryPlatformOptions = createSentryPlatformOptionsConfiguration() + sentryInitWithPlatformOptions(sentryPlatformOptions) + } + @Test fun `captureMessage sends the correct message`() { val expected = "test" diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/fakes/FakeSentryInstance.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/fakes/FakeSentryInstance.kt new file mode 100644 index 00000000..10b4535b --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/fakes/FakeSentryInstance.kt @@ -0,0 +1,12 @@ +package io.sentry.kotlin.multiplatform.fakes + +import io.sentry.kotlin.multiplatform.PlatformOptionsConfiguration +import io.sentry.kotlin.multiplatform.SentryInstance + +class FakeSentryInstance : SentryInstance { + internal var lastConfiguration: PlatformOptionsConfiguration? = null + + override fun init(configuration: PlatformOptionsConfiguration) { + lastConfiguration = configuration + } +} diff --git a/sentry-kotlin-multiplatform/src/commonTvWatchMacOsMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.tvwatchmacos.kt b/sentry-kotlin-multiplatform/src/commonTvWatchMacOsMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.tvwatchmacos.kt deleted file mode 100644 index 2cd5ff69..00000000 --- a/sentry-kotlin-multiplatform/src/commonTvWatchMacOsMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.tvwatchmacos.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.sentry.kotlin.multiplatform - -import cocoapods.Sentry.SentrySDK -import io.sentry.kotlin.multiplatform.extensions.toCocoaOptionsConfiguration - -internal actual fun initSentry(configuration: OptionsConfiguration) { - val options = SentryOptions() - configuration.invoke(options) - SentrySDK.start(options.toCocoaOptionsConfiguration()) -} diff --git a/sentry-kotlin-multiplatform/src/commonTvWatchMacOsMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.tvwatchmacos.kt b/sentry-kotlin-multiplatform/src/commonTvWatchMacOsMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.tvwatchmacos.kt new file mode 100644 index 00000000..02532b89 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonTvWatchMacOsMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.tvwatchmacos.kt @@ -0,0 +1,14 @@ +package io.sentry.kotlin.multiplatform + +import cocoapods.Sentry.SentrySDK + +internal actual class SentryPlatformInstance : SentryInstance { + override fun init(configuration: PlatformOptionsConfiguration) { + val finalConfiguration: (CocoaSentryOptions?) -> Unit = { + if (it != null) { + configuration(it) + } + } + SentrySDK.start(finalConfiguration) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonTvWatchMacOsMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.tvwatchmacos.kt b/sentry-kotlin-multiplatform/src/commonTvWatchMacOsMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.tvwatchmacos.kt new file mode 100644 index 00000000..c2c52ad0 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonTvWatchMacOsMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.tvwatchmacos.kt @@ -0,0 +1,8 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.extensions.toCocoaOptionsConfiguration + +public actual typealias SentryPlatformOptions = cocoapods.Sentry.SentryOptions + +internal actual fun SentryOptions.toPlatformOptionsConfiguration(): PlatformOptionsConfiguration = + toCocoaOptionsConfiguration() diff --git a/sentry-kotlin-multiplatform/src/commonTvWatchMacOsTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.tvwatchmacos.kt b/sentry-kotlin-multiplatform/src/commonTvWatchMacOsTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.tvwatchmacos.kt index e584625d..3cc87d35 100644 --- a/sentry-kotlin-multiplatform/src/commonTvWatchMacOsTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.tvwatchmacos.kt +++ b/sentry-kotlin-multiplatform/src/commonTvWatchMacOsTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.tvwatchmacos.kt @@ -1,6 +1,7 @@ package io.sentry.kotlin.multiplatform import io.sentry.kotlin.multiplatform.extensions.toCocoaOptionsConfiguration +import io.sentry.kotlin.multiplatform.utils.fakeDsn import kotlinx.cinterop.convert actual interface PlatformOptions : CommonPlatformOptions @@ -54,3 +55,7 @@ actual fun createPlatformOptions(): PlatformOptions = actual fun PlatformOptions.assertPlatformSpecificOptions(options: SentryOptions) { // no-op } + +actual fun createSentryPlatformOptionsConfiguration(): PlatformOptionsConfiguration = { + it.dsn = fakeDsn +} diff --git a/sentry-kotlin-multiplatform/src/iosMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.ios.kt b/sentry-kotlin-multiplatform/src/iosMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.ios.kt deleted file mode 100644 index e00d1b02..00000000 --- a/sentry-kotlin-multiplatform/src/iosMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.ios.kt +++ /dev/null @@ -1,10 +0,0 @@ -package io.sentry.kotlin.multiplatform - -import cocoapods.Sentry.SentrySDK -import io.sentry.kotlin.multiplatform.extensions.toIosOptionsConfiguration - -internal actual fun initSentry(configuration: OptionsConfiguration) { - val options = SentryOptions() - configuration.invoke(options) - SentrySDK.start(options.toIosOptionsConfiguration()) -} diff --git a/sentry-kotlin-multiplatform/src/iosMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.ios.kt b/sentry-kotlin-multiplatform/src/iosMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.ios.kt new file mode 100644 index 00000000..02532b89 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/iosMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.ios.kt @@ -0,0 +1,14 @@ +package io.sentry.kotlin.multiplatform + +import cocoapods.Sentry.SentrySDK + +internal actual class SentryPlatformInstance : SentryInstance { + override fun init(configuration: PlatformOptionsConfiguration) { + val finalConfiguration: (CocoaSentryOptions?) -> Unit = { + if (it != null) { + configuration(it) + } + } + SentrySDK.start(finalConfiguration) + } +} diff --git a/sentry-kotlin-multiplatform/src/iosMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.ios.kt b/sentry-kotlin-multiplatform/src/iosMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.ios.kt new file mode 100644 index 00000000..b495be18 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/iosMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.ios.kt @@ -0,0 +1,8 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.extensions.toIosOptionsConfiguration + +public actual typealias SentryPlatformOptions = cocoapods.Sentry.SentryOptions + +internal actual fun SentryOptions.toPlatformOptionsConfiguration(): PlatformOptionsConfiguration = + toIosOptionsConfiguration() diff --git a/sentry-kotlin-multiplatform/src/iosTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.ios.kt b/sentry-kotlin-multiplatform/src/iosTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.ios.kt index 98e1c797..1e2775d8 100644 --- a/sentry-kotlin-multiplatform/src/iosTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.ios.kt +++ b/sentry-kotlin-multiplatform/src/iosTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.ios.kt @@ -1,6 +1,7 @@ package io.sentry.kotlin.multiplatform import io.sentry.kotlin.multiplatform.extensions.toIosOptionsConfiguration +import io.sentry.kotlin.multiplatform.utils.fakeDsn import kotlinx.cinterop.convert import kotlin.test.assertEquals @@ -73,3 +74,7 @@ actual fun PlatformOptions.assertPlatformSpecificOptions(options: SentryOptions) assertEquals(enableAppHangTracking, options.enableAppHangTracking) assertEquals(appHangTimeoutIntervalMillis, options.appHangTimeoutIntervalMillis) } + +actual fun createSentryPlatformOptionsConfiguration(): PlatformOptionsConfiguration = { + it.dsn = fakeDsn +} diff --git a/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/Context.jvm.kt b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/Context.jvm.kt new file mode 100644 index 00000000..678914e6 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/Context.jvm.kt @@ -0,0 +1,4 @@ +package io.sentry.kotlin.multiplatform + +// The context is unused here and only implemented to satisfy the expect +public actual abstract class Context diff --git a/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt new file mode 100644 index 00000000..82f439c2 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt @@ -0,0 +1,5 @@ +package io.sentry.kotlin.multiplatform + +internal actual fun SentryPlatformOptions.prepareForInitBridge() { + prepareForInit() +} diff --git a/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.jvm.kt b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.jvm.kt deleted file mode 100644 index 547712f1..00000000 --- a/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryInit.jvm.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.sentry.kotlin.multiplatform - -import io.sentry.Sentry -import io.sentry.kotlin.multiplatform.extensions.toJvmSentryOptionsCallback - -internal actual fun initSentry(configuration: OptionsConfiguration) { - val options = SentryOptions() - configuration.invoke(options) - Sentry.init(options.toJvmSentryOptionsCallback()) -} - -// The context is unused here and only implemented to satisfy the expect -public actual abstract class Context diff --git a/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.jvm.kt b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.jvm.kt new file mode 100644 index 00000000..40a6a8c6 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformInstance.jvm.kt @@ -0,0 +1,9 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.Sentry as JvmSentry + +internal actual class SentryPlatformInstance : SentryInstance { + override fun init(configuration: PlatformOptionsConfiguration) { + JvmSentry.init(configuration) + } +} diff --git a/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.jvm.kt b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.jvm.kt new file mode 100644 index 00000000..0a7a8d28 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/jvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryPlatformOptions.jvm.kt @@ -0,0 +1,16 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.extensions.toJvmSentryOptionsCallback + +public actual typealias SentryPlatformOptions = io.sentry.SentryOptions + +internal actual fun SentryPlatformOptions.prepareForInit() { + sdkVersion?.name = BuildKonfig.SENTRY_KMP_JAVA_SDK_NAME + sdkVersion?.version = BuildKonfig.VERSION_NAME + if (sdkVersion?.packageSet?.none { it.name == BuildKonfig.SENTRY_JAVA_PACKAGE_NAME } == true) { + sdkVersion?.addPackage(BuildKonfig.SENTRY_JAVA_PACKAGE_NAME, BuildKonfig.SENTRY_JAVA_VERSION) + } +} + +internal actual fun SentryOptions.toPlatformOptionsConfiguration(): PlatformOptionsConfiguration = + toJvmSentryOptionsCallback() diff --git a/sentry-kotlin-multiplatform/src/jvmTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt b/sentry-kotlin-multiplatform/src/jvmTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt index 7a13455e..717a2b0e 100644 --- a/sentry-kotlin-multiplatform/src/jvmTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt +++ b/sentry-kotlin-multiplatform/src/jvmTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt @@ -6,4 +6,8 @@ actual abstract class BaseSentryTest { actual fun sentryInit(optionsConfiguration: OptionsConfiguration) { Sentry.init(optionsConfiguration) } + + actual fun sentryInitWithPlatformOptions(platformOptionsConfiguration: PlatformOptionsConfiguration) { + Sentry.initWithPlatformOptions(platformOptionsConfiguration) + } } diff --git a/sentry-kotlin-multiplatform/src/jvmTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.jvm.kt b/sentry-kotlin-multiplatform/src/jvmTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.jvm.kt index e93be2ff..40d7841e 100644 --- a/sentry-kotlin-multiplatform/src/jvmTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.jvm.kt +++ b/sentry-kotlin-multiplatform/src/jvmTest/kotlin/io/sentry/kotlin/multiplatform/PlatformOptions.jvm.kt @@ -1,6 +1,7 @@ package io.sentry.kotlin.multiplatform import io.sentry.kotlin.multiplatform.extensions.toJvmSentryOptionsCallback +import io.sentry.kotlin.multiplatform.utils.fakeDsn actual interface PlatformOptions : CommonPlatformOptions @@ -51,3 +52,7 @@ actual fun createPlatformOptions(): PlatformOptions = SentryJvmOptionsWrapper(Jv actual fun PlatformOptions.assertPlatformSpecificOptions(options: SentryOptions) { // no-op } + +actual fun createSentryPlatformOptionsConfiguration(): PlatformOptionsConfiguration = { + it.dsn = fakeDsn +} diff --git a/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/iOSApp.swift b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/iOSApp.swift index 3c03f0d4..5fc135d8 100644 --- a/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/iOSApp.swift +++ b/sentry-samples/kmp-app-cocoapods/iosApp/iosApp/iOSApp.swift @@ -7,7 +7,7 @@ struct iOSApp: App { init() { // Initialize Sentry using shared code - SentrySetupKt.initializeSentry() + SentrySetupKt.initializeSentry(useNativeOptions: false) // Shared scope across all platforms SentrySetupKt.configureSentryScope() diff --git a/sentry-samples/kmp-app-cocoapods/shared/build.gradle.kts b/sentry-samples/kmp-app-cocoapods/shared/build.gradle.kts index fde5761c..1f5841f6 100644 --- a/sentry-samples/kmp-app-cocoapods/shared/build.gradle.kts +++ b/sentry-samples/kmp-app-cocoapods/shared/build.gradle.kts @@ -43,6 +43,10 @@ kotlin { } sourceSets { + all { + languageSettings.optIn("kotlinx.cinterop.ExperimentalForeignApi") + } + commonMain.dependencies { api(project(":sentry-kotlin-multiplatform")) } diff --git a/sentry-samples/kmp-app-cocoapods/shared/src/androidMain/kotlin/sample/kmp/app/SentrySetup.android.kt b/sentry-samples/kmp-app-cocoapods/shared/src/androidMain/kotlin/sample/kmp/app/SentrySetup.android.kt new file mode 100644 index 00000000..11c6831d --- /dev/null +++ b/sentry-samples/kmp-app-cocoapods/shared/src/androidMain/kotlin/sample/kmp/app/SentrySetup.android.kt @@ -0,0 +1,7 @@ +package sample.kmp.app + +import io.sentry.kotlin.multiplatform.PlatformOptionsConfiguration + +actual fun createPlatformOptionsConfiguration(): PlatformOptionsConfiguration = { + it.dsn = "https://83f281ded2844eda83a8a413b080dbb9@o447951.ingest.sentry.io/5903800" +} diff --git a/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt index ffdd4af6..dbefcb9b 100644 --- a/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt +++ b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt @@ -3,6 +3,7 @@ package sample.kmp.app import io.sentry.kotlin.multiplatform.Attachment import io.sentry.kotlin.multiplatform.HttpStatusCodeRange import io.sentry.kotlin.multiplatform.OptionsConfiguration +import io.sentry.kotlin.multiplatform.PlatformOptionsConfiguration import io.sentry.kotlin.multiplatform.Sentry /** Configure scope applicable to all platforms */ @@ -23,10 +24,16 @@ fun configureSentryScope() { * Initializes Sentry with given options. * Make sure to hook this into your native platforms as early as possible */ -fun initializeSentry() { - Sentry.init(optionsConfiguration()) +fun initializeSentry(useNativeOptions: Boolean = false) { + if (useNativeOptions) { + Sentry.initWithPlatformOptions(createPlatformOptionsConfiguration()) + } else { + Sentry.init(optionsConfiguration()) + } } +expect fun createPlatformOptionsConfiguration(): PlatformOptionsConfiguration + /** Returns a shared options configuration */ private fun optionsConfiguration(): OptionsConfiguration { return { diff --git a/sentry-samples/kmp-app-cocoapods/shared/src/iosMain/kotlin/sample.kmp.app/SentrySetup.ios.kt b/sentry-samples/kmp-app-cocoapods/shared/src/iosMain/kotlin/sample.kmp.app/SentrySetup.ios.kt new file mode 100644 index 00000000..11c6831d --- /dev/null +++ b/sentry-samples/kmp-app-cocoapods/shared/src/iosMain/kotlin/sample.kmp.app/SentrySetup.ios.kt @@ -0,0 +1,7 @@ +package sample.kmp.app + +import io.sentry.kotlin.multiplatform.PlatformOptionsConfiguration + +actual fun createPlatformOptionsConfiguration(): PlatformOptionsConfiguration = { + it.dsn = "https://83f281ded2844eda83a8a413b080dbb9@o447951.ingest.sentry.io/5903800" +} diff --git a/sentry-samples/kmp-app-cocoapods/shared/src/jvmMain/kotlin/sample/kmp/app/SentrySetup.jvm.kt b/sentry-samples/kmp-app-cocoapods/shared/src/jvmMain/kotlin/sample/kmp/app/SentrySetup.jvm.kt new file mode 100644 index 00000000..11c6831d --- /dev/null +++ b/sentry-samples/kmp-app-cocoapods/shared/src/jvmMain/kotlin/sample/kmp/app/SentrySetup.jvm.kt @@ -0,0 +1,7 @@ +package sample.kmp.app + +import io.sentry.kotlin.multiplatform.PlatformOptionsConfiguration + +actual fun createPlatformOptionsConfiguration(): PlatformOptionsConfiguration = { + it.dsn = "https://83f281ded2844eda83a8a413b080dbb9@o447951.ingest.sentry.io/5903800" +}