diff --git a/embrace-android-sdk/api/embrace-android-sdk.api b/embrace-android-sdk/api/embrace-android-sdk.api index fd66b537c..e700e6c7e 100644 --- a/embrace-android-sdk/api/embrace-android-sdk.api +++ b/embrace-android-sdk/api/embrace-android-sdk.api @@ -144,6 +144,7 @@ public abstract interface class io/embrace/android/embracesdk/ReactNativeInterna public abstract fun logRnAction (Ljava/lang/String;JJLjava/util/Map;ILjava/lang/String;)V public abstract fun logRnView (Ljava/lang/String;)V public abstract fun logUnhandledJsException (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public abstract fun setCacheableJavaScriptBundleUrl (Landroid/content/Context;Ljava/lang/String;Z)V public abstract fun setJavaScriptBundleUrl (Landroid/content/Context;Ljava/lang/String;)V public abstract fun setJavaScriptPatchNumber (Ljava/lang/String;)V public abstract fun setReactNativeSdkVersion (Ljava/lang/String;)V diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterface.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterface.kt index c6824466c..de9f25888 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterface.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterface.kt @@ -31,8 +31,23 @@ public interface ReactNativeInternalInterface : EmbraceInternalInterface { public fun setReactNativeVersionNumber(version: String?) + /** + * Sets the React Native Bundle URL. + * @param context the context + * @param url the JavaScript bundle URL + */ public fun setJavaScriptBundleUrl(context: Context, url: String) + /** + * Sets the React Native Bundle URL, indicating if the bundle was updated or not. + * If it was updated, the bundle ID will be recomputed. + * If not, the bundle ID will be retrieved from cache. + * @param context the context + * @param url the JavaScript bundle URL + * @param didUpdate if the bundle was updated + */ + public fun setCacheableJavaScriptBundleUrl(context: Context, url: String, didUpdate: Boolean) + /** * Logs a React Native Redux Action - this is not intended for public use. */ diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl.kt index f2fae8a5b..5dbc85235 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImpl.kt @@ -96,18 +96,11 @@ internal class ReactNativeInternalInterfaceImpl( } override fun setJavaScriptBundleUrl(context: Context, url: String) { - if (embrace.isStarted) { - if (framework != AppFramework.REACT_NATIVE) { - logger.logError( - "Failed to set Java Script bundle ID URL. Current framework: " + - framework.name + " is not React Native." - ) - return - } - metadataService.setReactNativeBundleId(context, url) - } else { - logger.logSDKNotInitialized("set JavaScript bundle URL") - } + setJavaScriptBundleUrl(context, url, null) + } + + override fun setCacheableJavaScriptBundleUrl(context: Context, url: String, didUpdate: Boolean) { + setJavaScriptBundleUrl(context, url, didUpdate) } override fun logRnAction( @@ -124,4 +117,19 @@ internal class ReactNativeInternalInterfaceImpl( override fun logRnView(screen: String) { embrace.logRnView(screen) } + + private fun setJavaScriptBundleUrl(context: Context, url: String, didUpdate: Boolean? = null) { + if (embrace.isStarted) { + if (framework != AppFramework.REACT_NATIVE) { + logger.logError( + "Failed to set Java Script bundle ID URL. Current framework: " + + framework.name + " is not React Native." + ) + return + } + metadataService.setReactNativeBundleId(context, url, didUpdate) + } else { + logger.logSDKNotInitialized("set JavaScript bundle URL") + } + } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService.kt index f1723a373..7ac3dc13c 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataService.kt @@ -383,20 +383,25 @@ internal class EmbraceMetadataService private constructor( override fun getEgl(): String? = egl - override fun setReactNativeBundleId(context: Context, jsBundleUrl: String?) { + override fun setReactNativeBundleId(context: Context, jsBundleUrl: String?, forceUpdate: Boolean?) { val currentUrl = preferencesService.javaScriptBundleURL - if (currentUrl != jsBundleUrl) { + if (currentUrl != jsBundleUrl || forceUpdate == true) { // It`s a new JS bundle URL, save the new value in preferences. preferencesService.javaScriptBundleURL = jsBundleUrl // Calculate the bundle ID for the new bundle URL reactNativeBundleId = metadataBackgroundWorker.submit { - computeReactNativeBundleId( + val bundleId = computeReactNativeBundleId( context, jsBundleUrl, buildInfo.buildId ) + if (forceUpdate != null) { + // if we have a value for forceUpdate, it means the bundleId is cacheable and we should store it. + preferencesService.javaScriptBundleId = bundleId + } + bundleId } } } @@ -501,11 +506,19 @@ internal class EmbraceMetadataService private constructor( if (appFramework == AppFramework.REACT_NATIVE) { reactNativeBundleId = metadataBackgroundWorker.submit { val lastKnownJsBundleUrl = preferencesService.javaScriptBundleURL - computeReactNativeBundleId( - context, - lastKnownJsBundleUrl, - buildInfo.buildId - ) + val lastKnownJsBundleId = preferencesService.javaScriptBundleId + if (!lastKnownJsBundleUrl.isNullOrEmpty() && !lastKnownJsBundleId.isNullOrEmpty()) { + // If we have a lastKnownJsBundleId, we use that as the last known bundle ID. + return@submit lastKnownJsBundleId + } else { + // If we don't have a lastKnownJsBundleId, we compute the bundle ID from the last known JS bundle URL. + // If the last known JS bundle URL is null, we set React Native bundle ID to the buildId. + return@submit computeReactNativeBundleId( + context, + lastKnownJsBundleUrl, + buildInfo.buildId + ) + } } javaScriptPatchNumber = preferencesService.javaScriptPatchNumber if (javaScriptPatchNumber != null) { diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/metadata/MetadataService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/metadata/MetadataService.kt index 98aa31bd8..4b91c94e9 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/metadata/MetadataService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/metadata/MetadataService.kt @@ -112,8 +112,11 @@ internal interface MetadataService { /** * Sets React Native Bundle ID from a custom JavaScript Bundle URL. + * @param context the context + * @param jsBundleUrl the JavaScript bundle URL + * @param forceUpdate if the bundle was updated and we need to recompute the bundleId */ - fun setReactNativeBundleId(context: Context, jsBundleUrl: String?) + fun setReactNativeBundleId(context: Context, jsBundleUrl: String?, forceUpdate: Boolean? = null) /** * Sets the Embrace Flutter SDK version diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/prefs/EmbracePreferencesService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/prefs/EmbracePreferencesService.kt index 81ea43787..ca5e3b0f2 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/prefs/EmbracePreferencesService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/prefs/EmbracePreferencesService.kt @@ -265,6 +265,10 @@ internal class EmbracePreferencesService( get() = prefs.getStringPreference(JAVA_SCRIPT_BUNDLE_URL_KEY) set(value) = prefs.setStringPreference(JAVA_SCRIPT_BUNDLE_URL_KEY, value) + override var javaScriptBundleId: String? + get() = prefs.getStringPreference(JAVA_SCRIPT_BUNDLE_ID_KEY) + set(value) = prefs.setStringPreference(JAVA_SCRIPT_BUNDLE_ID_KEY, value) + override var rnSdkVersion: String? get() = prefs.getStringPreference(REACT_NATIVE_SDK_VERSION_KEY) set(value) = prefs.setStringPreference(REACT_NATIVE_SDK_VERSION_KEY, value) @@ -372,6 +376,7 @@ internal class EmbracePreferencesService( private const val LAST_CRASH_NUMBER_KEY = "io.embrace.crashnumber" private const val LAST_NATIVE_CRASH_NUMBER_KEY = "io.embrace.nativecrashnumber" private const val JAVA_SCRIPT_BUNDLE_URL_KEY = "io.embrace.jsbundle.url" + private const val JAVA_SCRIPT_BUNDLE_ID_KEY = "io.embrace.jsbundle.id" private const val JAVA_SCRIPT_PATCH_NUMBER_KEY = "io.embrace.javascript.patch" private const val REACT_NATIVE_VERSION_KEY = "io.embrace.reactnative.version" private const val REACT_NATIVE_SDK_VERSION_KEY = "io.embrace.reactnative.sdk.version" diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/prefs/PreferencesService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/prefs/PreferencesService.kt index b230f43b6..7bd85c943 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/prefs/PreferencesService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/prefs/PreferencesService.kt @@ -116,6 +116,11 @@ internal interface PreferencesService { */ var javaScriptBundleURL: String? + /** + * Last javaScript bundle ID. + */ + var javaScriptBundleId: String? + /** * Embrace sdk version. */ diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImplTest.kt index 77a61192e..f624dbfcc 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/ReactNativeInternalInterfaceImplTest.kt @@ -158,6 +158,44 @@ internal class ReactNativeInternalInterfaceImplTest { } } + @Test + fun testSetCacheableJavaScriptBundleUrl() { + impl = ReactNativeInternalInterfaceImpl( + embrace, + mockk(), + REACT_NATIVE, + preferencesService, + crashService, + metadataService, + logger + ) + + every { embrace.isStarted } returns true + impl.setCacheableJavaScriptBundleUrl(context, "index.android.bundle", true) + // Test that the metadata service was called with the correct parameters + assertEquals("index.android.bundle", metadataService.fakeReactNativeBundleId) + assertEquals(true, metadataService.forceUpdate) + } + + @Test + fun testSetJavaScriptBundleURLForOtherOTAs() { + impl = ReactNativeInternalInterfaceImpl( + embrace, + mockk(), + REACT_NATIVE, + preferencesService, + crashService, + metadataService, + logger + ) + + every { embrace.isStarted } returns true + impl.setJavaScriptBundleUrl(context, "index.android.bundle") + // Test that the metadata service was called with the correct parameters + assertEquals("index.android.bundle", metadataService.fakeReactNativeBundleId) + assertEquals(null, metadataService.forceUpdate) + } + @Test fun testLogUnhandledJsException() { every { embrace.isStarted } returns true diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataReactNativeTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataReactNativeTest.kt index 09b22bf4c..31af3c9e1 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataReactNativeTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataReactNativeTest.kt @@ -104,6 +104,15 @@ internal class EmbraceMetadataReactNativeTest { assertEquals(buildInfo.buildId, metadataService.getReactNativeBundleId()) } + @Test + fun `test React Native bundle ID from preference if jsBundleIdUrl is a new value`() { + preferencesService.javaScriptBundleURL = "oldJavaScriptBundleURL" + val metadataService = getMetadataService() + + metadataService.setReactNativeBundleId(context, "newJavaScriptBundleURL") + assertEquals(buildInfo.buildId, metadataService.getReactNativeBundleId()) + } + @Test fun `test React Native bundle ID url as Asset`() { val bundleIdFile = Files.createTempFile("bundle-test", ".temp").toFile() @@ -115,8 +124,6 @@ internal class EmbraceMetadataReactNativeTest { val metadataService = getMetadataService() metadataService.setReactNativeBundleId(context, "assets://index.android.bundle") - // get the react native Bundle ID once to call the lazy property - metadataService.getReactNativeBundleId() verify(exactly = 1) { assetManager.open(eq("index.android.bundle")) } @@ -124,6 +131,62 @@ internal class EmbraceMetadataReactNativeTest { assertEquals("D41D8CD98F00B204E9800998ECF8427E", metadataService.getReactNativeBundleId()) } + @Test + fun `test React Native bundle ID url as Asset with forceUpdate param in true`() { + val bundleIdFile = Files.createTempFile("bundle-test", ".temp").toFile() + val inputStream = FileInputStream(bundleIdFile) + preferencesService.javaScriptBundleURL = null + preferencesService.javaScriptBundleId = null + + every { context.assets } returns assetManager + every { assetManager.open(any()) } returns inputStream + + val metadataService = getMetadataService() + metadataService.setReactNativeBundleId(context, "assets://index.android.bundle", true) + + verify(exactly = 1) { assetManager.open(eq("index.android.bundle")) } + + assertNotEquals(buildInfo.buildId, metadataService.getReactNativeBundleId()) + assertEquals("D41D8CD98F00B204E9800998ECF8427E", metadataService.getReactNativeBundleId()) + assertEquals("D41D8CD98F00B204E9800998ECF8427E", preferencesService.javaScriptBundleId) + } + + @Test + fun `test React Native bundle ID url as Asset with forceUpdate param in false`() { + val bundleIdFile = Files.createTempFile("bundle-test", ".temp").toFile() + val inputStream = FileInputStream(bundleIdFile) + preferencesService.javaScriptBundleURL = "assets://index.android.bundle" + preferencesService.javaScriptBundleId = "persistedBundleId" + + every { context.assets } returns assetManager + every { assetManager.open(any()) } returns inputStream + + val metadataService = getMetadataService() + metadataService.setReactNativeBundleId(context, "assets://index.android.bundle", false) + + assertNotEquals(buildInfo.buildId, metadataService.getReactNativeBundleId()) + assertEquals("persistedBundleId", metadataService.getReactNativeBundleId()) + assertEquals("persistedBundleId", preferencesService.javaScriptBundleId) + } + + @Test + fun `test React Native bundle ID url as Asset with forceUpdate param being null`() { + val bundleIdFile = Files.createTempFile("bundle-test", ".temp").toFile() + val inputStream = FileInputStream(bundleIdFile) + preferencesService.javaScriptBundleURL = null + preferencesService.javaScriptBundleId = null + + every { context.assets } returns assetManager + every { assetManager.open(any()) } returns inputStream + + val metadataService = getMetadataService() + metadataService.setReactNativeBundleId(context, "assets://index.android.bundle", null) + + assertNotEquals(buildInfo.buildId, metadataService.getReactNativeBundleId()) + assertEquals("D41D8CD98F00B204E9800998ECF8427E", metadataService.getReactNativeBundleId()) + assertEquals(null, preferencesService.javaScriptBundleId) + } + @Test fun `test React Native bundle ID url as a custom file`() { val bundleIdFile = Files.createTempFile("index.android.bundle", "temp").toFile() @@ -132,9 +195,6 @@ internal class EmbraceMetadataReactNativeTest { context, bundleIdFile.absolutePath ) - // get the react native Bundle ID once to call the lazy property - metadataService.getReactNativeBundleId() - assertNotEquals(buildInfo.buildId, metadataService.getReactNativeBundleId()) assertEquals("D41D8CD98F00B204E9800998ECF8427E", metadataService.getReactNativeBundleId()) } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataServiceTest.kt index 79394243e..d7a08196a 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataServiceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/metadata/EmbraceMetadataServiceTest.kt @@ -217,6 +217,8 @@ internal class EmbraceMetadataServiceTest { every { preferencesService.unityBuildIdNumber }.returns(null) every { preferencesService.rnSdkVersion }.returns(null) every { preferencesService.javaScriptPatchNumber }.returns(null) + every { preferencesService.javaScriptBundleURL }.returns(null) + every { preferencesService.javaScriptBundleId }.returns(null) every { MetadataUtils.appEnvironment(any()) }.returns("UNKNOWN") val metadataService = getReactNativeMetadataService() diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeMetadataService.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeMetadataService.kt index 2bea8e807..6a00486ae 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeMetadataService.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeMetadataService.kt @@ -44,6 +44,7 @@ internal class FakeMetadataService(sessionId: String? = null) : MetadataService var fakeAppId: String = "o0o0o" var fakeDeviceId: String = "07D85B44E4E245F4A30E559BFC0D07FF" var fakeReactNativeBundleId: String? = "fakeReactNativeBundleId" + var forceUpdate: Boolean? = null var fakeFlutterSdkVersion: String? = "fakeFlutterSdkVersion" var fakeDartVersion: String? = "fakeDartVersion" var fakeRnSdkVersion: String? = "fakeRnSdkVersion" @@ -96,8 +97,9 @@ internal class FakeMetadataService(sessionId: String? = null) : MetadataService override fun getAppState(): String = appState - override fun setReactNativeBundleId(context: Context, jsBundleUrl: String?) { + override fun setReactNativeBundleId(context: Context, jsBundleUrl: String?, forceUpdate: Boolean?) { fakeReactNativeBundleId = jsBundleUrl + this.forceUpdate = forceUpdate } override fun setEmbraceFlutterSdkVersion(version: String?) { diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakePreferenceService.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakePreferenceService.kt index 033b986de..0d2c0b165 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakePreferenceService.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakePreferenceService.kt @@ -27,6 +27,7 @@ internal class FakePreferenceService( override var backgroundActivityEnabled: Boolean = false, override var dartSdkVersion: String? = null, override var javaScriptBundleURL: String? = null, + override var javaScriptBundleId: String? = null, override var rnSdkVersion: String? = null, override var javaScriptPatchNumber: String? = null, override var embraceFlutterSdkVersion: String? = null, diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/prefs/EmbracePreferencesServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/prefs/EmbracePreferencesServiceTest.kt index 6b4a82cc9..3194d91d8 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/prefs/EmbracePreferencesServiceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/prefs/EmbracePreferencesServiceTest.kt @@ -247,6 +247,15 @@ internal class EmbracePreferencesServiceTest { assertEquals(url, service.javaScriptBundleURL) } + @Test + fun `test java script bundle id is saved`() { + assertNull(service.javaScriptBundleId) + + val id = "0d48510589c0426b43f01a5fa060a333" + service.javaScriptBundleId = id + assertEquals(id, service.javaScriptBundleId) + } + @Test fun `test java script patch number is saved`() { assertNull(service.javaScriptPatchNumber)