expo-updates 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/android/build.gradle +6 -4
  3. package/android/src/main/java/expo/modules/updates/DisabledUpdatesController.kt +132 -0
  4. package/android/src/main/java/expo/modules/updates/EnabledUpdatesController.kt +296 -0
  5. package/android/src/main/java/expo/modules/updates/IUpdatesController.kt +121 -0
  6. package/android/src/main/java/expo/modules/updates/UpdatesConfiguration.kt +58 -21
  7. package/android/src/main/java/expo/modules/updates/UpdatesController.kt +50 -592
  8. package/android/src/main/java/expo/modules/updates/UpdatesDevLauncherController.kt +178 -85
  9. package/android/src/main/java/expo/modules/updates/UpdatesModule.kt +181 -495
  10. package/android/src/main/java/expo/modules/updates/UpdatesPackage.kt +0 -10
  11. package/android/src/main/java/expo/modules/updates/UpdatesUtils.kt +14 -39
  12. package/android/src/main/java/expo/modules/updates/db/BuildData.kt +5 -2
  13. package/android/src/main/java/expo/modules/updates/db/dao/UpdateDao.kt +2 -2
  14. package/android/src/main/java/expo/modules/updates/errorrecovery/ErrorRecovery.kt +15 -5
  15. package/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt +37 -21
  16. package/android/src/main/java/expo/modules/updates/launcher/NoDatabaseLauncher.kt +5 -30
  17. package/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.kt +2 -2
  18. package/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt +3 -3
  19. package/android/src/main/java/expo/modules/updates/loader/Loader.kt +1 -1
  20. package/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt +33 -26
  21. package/android/src/main/java/expo/modules/updates/loader/RemoteLoader.kt +4 -9
  22. package/android/src/main/java/expo/modules/updates/manifest/BareUpdateManifest.kt +3 -4
  23. package/android/src/main/java/expo/modules/updates/manifest/EmbeddedManifest.kt +2 -2
  24. package/android/src/main/java/expo/modules/updates/manifest/LegacyUpdateManifest.kt +2 -2
  25. package/android/src/main/java/expo/modules/updates/manifest/ManifestFactory.kt +5 -9
  26. package/android/src/main/java/expo/modules/updates/manifest/ManifestMetadata.kt +3 -3
  27. package/android/src/main/java/expo/modules/updates/manifest/NewUpdateManifest.kt +2 -2
  28. package/android/src/main/java/expo/modules/updates/procedures/CheckForUpdateProcedure.kt +181 -0
  29. package/android/src/main/java/expo/modules/updates/procedures/FetchUpdateProcedure.kt +117 -0
  30. package/android/src/main/java/expo/modules/updates/procedures/RelaunchProcedure.kt +111 -0
  31. package/android/src/main/java/expo/modules/updates/procedures/StartupProcedure.kt +323 -0
  32. package/android/src/main/java/expo/modules/updates/procedures/StateMachineProcedure.kt +39 -0
  33. package/android/src/main/java/expo/modules/updates/procedures/StateMachineSerialExecutorQueue.kt +75 -0
  34. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateMachine.kt +29 -8
  35. package/build/Updates.d.ts +20 -6
  36. package/build/Updates.d.ts.map +1 -1
  37. package/build/Updates.js +20 -6
  38. package/build/Updates.js.map +1 -1
  39. package/build/Updates.types.d.ts +3 -1
  40. package/build/Updates.types.d.ts.map +1 -1
  41. package/build/Updates.types.js +3 -1
  42. package/build/Updates.types.js.map +1 -1
  43. package/build/UpdatesEmitter.d.ts +3 -1
  44. package/build/UpdatesEmitter.d.ts.map +1 -1
  45. package/build/UpdatesEmitter.js +3 -1
  46. package/build/UpdatesEmitter.js.map +1 -1
  47. package/build/UpdatesHooks.d.ts +3 -1
  48. package/build/UpdatesHooks.d.ts.map +1 -1
  49. package/build/UpdatesHooks.js +3 -1
  50. package/build/UpdatesHooks.js.map +1 -1
  51. package/e2e/README.md +5 -5
  52. package/e2e/fixtures/App-apitest.tsx +86 -108
  53. package/e2e/fixtures/App-updates-disabled.tsx +107 -0
  54. package/e2e/fixtures/App.tsx +98 -92
  55. package/e2e/fixtures/E2ETestModule.swift +0 -2
  56. package/e2e/fixtures/Updates-disabled.e2e.ts +49 -0
  57. package/e2e/fixtures/Updates-startup.e2e.ts +92 -0
  58. package/e2e/fixtures/Updates.e2e.ts +79 -15
  59. package/e2e/fixtures/UpdatesE2ETestModule.kt +27 -27
  60. package/e2e/fixtures/project_files/.detoxrc.json +5 -3
  61. package/e2e/fixtures/project_files/assetsInUpdates/coffee-prep.jpg +0 -0
  62. package/e2e/fixtures/project_files/e2e/tests/utils/server.ts +21 -8
  63. package/e2e/fixtures/project_files/eas-hooks/eas-build-on-success.sh +17 -6
  64. package/e2e/fixtures/project_files/eas-hooks/eas-build-pre-install.sh +1 -1
  65. package/e2e/fixtures/project_files/eas.json +36 -3
  66. package/e2e/fixtures/project_files/embeddedAssets/Abel_400Regular.ttf +0 -0
  67. package/e2e/fixtures/project_files/embeddedAssets/HankenGrotesk_300Light.ttf +0 -0
  68. package/e2e/fixtures/project_files/embeddedAssets/coffee-prep.jpg +0 -0
  69. package/e2e/fixtures/project_files/embeddedAssets/dougheadshot.jpg +0 -0
  70. package/e2e/fixtures/project_files/scripts/files/App.tsx.embedded +183 -0
  71. package/e2e/fixtures/project_files/scripts/files/App.tsx.update1 +189 -0
  72. package/e2e/fixtures/project_files/scripts/files/App.tsx.update2 +189 -0
  73. package/e2e/fixtures/project_files/scripts/{generate-test-update-bundles.js → generate-test-update-bundles.ts} +32 -21
  74. package/e2e/fixtures/project_files/scripts/reset-app.ts +29 -0
  75. package/e2e/setup/create-disabled-eas-project.ts +45 -0
  76. package/e2e/setup/{create-eas-project-tv.js → create-eas-project-tv.ts} +6 -3
  77. package/e2e/setup/{create-eas-project.js → create-eas-project.ts} +6 -3
  78. package/e2e/setup/create-startup-eas-project.ts +43 -0
  79. package/e2e/setup/{create-updates-test.js → create-updates-test.ts} +14 -5
  80. package/e2e/setup/{project.js → project.ts} +379 -153
  81. package/expo-module.config.json +3 -0
  82. package/ios/EXUpdates/AppController.swift +155 -645
  83. package/ios/EXUpdates/AppLauncher/AppLauncher.swift +1 -1
  84. package/ios/EXUpdates/AppLauncher/AppLauncherNoDatabase.swift +7 -25
  85. package/ios/EXUpdates/AppLauncher/AppLauncherWithDatabase.swift +18 -29
  86. package/ios/EXUpdates/AppLoader/AppLoader.swift +17 -9
  87. package/ios/EXUpdates/AppLoader/AppLoaderTask.swift +24 -41
  88. package/ios/EXUpdates/AppLoader/EmbeddedAppLoader.swift +6 -31
  89. package/ios/EXUpdates/AppLoader/FileDownloader.swift +3 -3
  90. package/ios/EXUpdates/AppLoader/RemoteAppLoader.swift +1 -2
  91. package/ios/EXUpdates/AppLoader/UpdateResponse.swift +0 -5
  92. package/ios/EXUpdates/Database/UpdatesBuildData.swift +2 -8
  93. package/ios/EXUpdates/Database/UpdatesDatabase.swift +0 -9
  94. package/ios/EXUpdates/Database/UpdatesReaper.swift +1 -1
  95. package/ios/EXUpdates/DevLauncherAppController.swift +318 -0
  96. package/ios/EXUpdates/DisabledAppController.swift +117 -0
  97. package/ios/EXUpdates/EnabledAppController.swift +339 -0
  98. package/ios/EXUpdates/ErrorRecovery.swift +15 -21
  99. package/ios/EXUpdates/Exceptions.swift +16 -6
  100. package/ios/EXUpdates/Logging/UpdatesLogReader.swift +7 -5
  101. package/ios/EXUpdates/Procedures/CheckForUpdateProcedure.swift +154 -0
  102. package/ios/EXUpdates/Procedures/FetchUpdateProcedure.swift +135 -0
  103. package/ios/EXUpdates/Procedures/RelaunchProcedure.swift +97 -0
  104. package/ios/EXUpdates/Procedures/StartupProcedure.swift +352 -0
  105. package/ios/EXUpdates/Procedures/StateMachineProcedure.swift +83 -0
  106. package/ios/EXUpdates/Procedures/StateMachineSerialExecutorQueue.swift +90 -0
  107. package/ios/EXUpdates/ReactDelegateHandler/ExpoUpdatesAppDelegateSubscriber.swift +1 -1
  108. package/ios/EXUpdates/ReactDelegateHandler/ExpoUpdatesReactDelegateHandler.swift +3 -2
  109. package/ios/EXUpdates/Update/BareUpdate.swift +3 -3
  110. package/ios/EXUpdates/Update/LegacyUpdate.swift +2 -2
  111. package/ios/EXUpdates/Update/NewUpdate.swift +1 -1
  112. package/ios/EXUpdates/Update/Update.swift +6 -14
  113. package/ios/EXUpdates/UpdatesConfig.swift +66 -33
  114. package/ios/EXUpdates/UpdatesModule.swift +74 -125
  115. package/ios/EXUpdates/UpdatesStateMachine.swift +38 -18
  116. package/ios/EXUpdates/UpdatesUtils.swift +17 -329
  117. package/ios/EXUpdates.podspec +1 -1
  118. package/ios/Tests/AppLauncherWithDatabaseSpec.swift +24 -48
  119. package/ios/Tests/CertificateChainSpec.swift +1 -1
  120. package/ios/Tests/CodeSigningAlgorithmSpec.swift +1 -1
  121. package/ios/Tests/CodeSigningConfigurationSpec.swift +1 -1
  122. package/ios/Tests/DatabaseInitializationSpec.swift +1 -1
  123. package/ios/Tests/DatabaseIntegrityCheckSpec.swift +5 -3
  124. package/ios/Tests/ErrorRecoverySpec.swift +73 -28
  125. package/ios/Tests/FileDownloaderManifestParsingSpec.swift +33 -17
  126. package/ios/Tests/FileDownloaderSpec.swift +13 -11
  127. package/ios/Tests/LegacyUpdateSpec.swift +30 -11
  128. package/ios/Tests/NewUpdateSpec.swift +16 -15
  129. package/ios/Tests/ReaperSelectionPolicyDevelopmentClientSpec.swift +21 -11
  130. package/ios/Tests/ReaperSelectionPolicyFilterAwareSpec.swift +9 -5
  131. package/ios/Tests/ResponseHeaderDataSpec.swift +1 -1
  132. package/ios/Tests/SelectionPolicyFilterAwareSpec.swift +3 -2
  133. package/ios/Tests/SignatureHeaderInfoSpec.swift +1 -1
  134. package/ios/Tests/StringStringDictionarySerializerSpec.swift +1 -1
  135. package/ios/Tests/UpdateAssetSpec.swift +1 -1
  136. package/ios/Tests/UpdateSpec.swift +15 -28
  137. package/ios/Tests/UpdatesBuildDataSpec.swift +18 -14
  138. package/ios/Tests/UpdatesConfigSpec.swift +25 -15
  139. package/ios/Tests/UpdatesDatabaseSpec.swift +4 -3
  140. package/ios/Tests/UpdatesStateMachineSpec.swift +49 -41
  141. package/package.json +8 -8
  142. package/src/Updates.ts +21 -6
  143. package/src/Updates.types.ts +3 -1
  144. package/src/UpdatesEmitter.ts +3 -1
  145. package/src/UpdatesHooks.ts +3 -1
  146. package/android/src/main/java/expo/modules/updates/UpdatesInterface.kt +0 -39
  147. package/android/src/main/java/expo/modules/updates/UpdatesService.kt +0 -76
  148. package/e2e/fixtures/project_files/detox.config.js +0 -77
  149. package/ios/EXUpdates/DevLauncherController.swift +0 -243
  150. package/ios/EXUpdates/EXUpdatesService.h +0 -40
  151. package/ios/EXUpdates/EXUpdatesService.m +0 -117
  152. /package/e2e/fixtures/{test.png → project_files/includedAssets/test.png} +0 -0
package/CHANGELOG.md CHANGED
@@ -10,6 +10,71 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.24.0 — 2023-12-12
14
+
15
+ ### 🛠 Breaking changes
16
+
17
+ - Add state machine procedure serial runner. ([#25386](https://github.com/expo/expo/pull/25386), [#25431](https://github.com/expo/expo/pull/25431) by [@wschurman](https://github.com/wschurman))
18
+
19
+ ### 🎉 New features
20
+
21
+ - [Android] Asset exclusion on Android part 2. ([#25504](https://github.com/expo/expo/pull/25504) by [@douglowder](https://github.com/douglowder))
22
+ - Added support for React Native 0.73.0. ([#24971](https://github.com/expo/expo/pull/24971), [#25453](https://github.com/expo/expo/pull/25453) by [@gabrieldonadel](https://github.com/gabrieldonadel))
23
+
24
+ ### 🐛 Bug fixes
25
+
26
+ - [Android] Fix interaction between reload JS API and ErrorRecovery. ([#25651](https://github.com/expo/expo/pull/25651) by [@wschurman](https://github.com/wschurman))
27
+ - [Android] Fix wait notify bug in launch asset when enabled. ([#25676](https://github.com/expo/expo/pull/25676) by [@wschurman](https://github.com/wschurman))
28
+
29
+ ### 💡 Others
30
+
31
+ - Update E2E tests to use local copies of `@expo/metro-config`, `@expo/env`, `@expo/config`. ([#25430](https://github.com/expo/expo/pull/25430) by [@EvanBacon](https://github.com/EvanBacon))
32
+ - Add e2e tests for disabled mode. ([#25301](https://github.com/expo/expo/pull/25301) by [@wschurman](https://github.com/wschurman))
33
+ - Modify E2E manual test for asset exclusion. ([#25406](https://github.com/expo/expo/pull/25406) by [@douglowder](https://github.com/douglowder))
34
+ - Move tvOS compile test out of updates E2E test matrix. ([#25438](https://github.com/expo/expo/pull/25438) by [@douglowder](https://github.com/douglowder))
35
+ - Assert valid state transitions in debug. ([#25474](https://github.com/expo/expo/pull/25474) by [@wschurman](https://github.com/wschurman))
36
+ - Improve JS API error messages and documentation for Expo Go and Dev Client. ([#25751](https://github.com/expo/expo/pull/25751) by [@wschurman](https://github.com/wschurman))
37
+
38
+ ## 0.23.0 — 2023-11-14
39
+
40
+ ### 🛠 Breaking changes
41
+
42
+ - Bumped iOS deployment target to 13.4. ([#25063](https://github.com/expo/expo/pull/25063) by [@gabrieldonadel](https://github.com/gabrieldonadel))
43
+ - Split updates controllers depending on configuration, changing native public API. ([#25085](https://github.com/expo/expo/pull/25085), [#25192](https://github.com/expo/expo/pull/25192) by [@wschurman](https://github.com/wschurman))
44
+ - On `Android` bump `compileSdkVersion` and `targetSdkVersion` to `34`. ([#24708](https://github.com/expo/expo/pull/24708) by [@alanjhughes](https://github.com/alanjhughes))
45
+
46
+ ### 🎉 New features
47
+
48
+ - [iOS] Make asset exclusion work. ([#25216](https://github.com/expo/expo/pull/25216) by [@douglowder](https://github.com/douglowder))
49
+ - [Android] Asset exclusion on Android part 1. ([#25277](https://github.com/expo/expo/pull/25277) by [@douglowder](https://github.com/douglowder))
50
+
51
+ ### 🐛 Bug fixes
52
+
53
+ - [iOS] Fix the E2E tests. ([#24865](https://github.com/expo/expo/pull/24865) by [@douglowder](https://github.com/douglowder))
54
+ - [Android] Simplify UpdatesUtils.parseDateString, fix UpdatesLoggingTest. ([#24951](https://github.com/expo/expo/pull/24951) by [@douglowder](https://github.com/douglowder))
55
+ - [iOS] Fix expo-localization tvOS compile, add CI. ([#25082](https://github.com/expo/expo/pull/25082) by [@douglowder](https://github.com/douglowder))
56
+ - Fix instrumentation tests. ([#25367](https://github.com/expo/expo/pull/25367) by [@wschurman](https://github.com/wschurman))
57
+
58
+ ### 💡 Others
59
+
60
+ - Android: Stub expo-updates in Expo Go and remove service pattern. ([#24890](https://github.com/expo/expo/pull/24890) by [@wschurman](https://github.com/wschurman))
61
+ - iOS: Refactor responsibility of app controller. ([#24934](https://github.com/expo/expo/pull/24934), [#24949](https://github.com/expo/expo/pull/24949) by [@wschurman](https://github.com/wschurman))
62
+ - Android: Refactor responsibility of app controller. ([#24954](https://github.com/expo/expo/pull/24954), [#24975](https://github.com/expo/expo/pull/24975), [#25043](https://github.com/expo/expo/pull/25043) by [@wschurman](https://github.com/wschurman))
63
+ - Android: Backport ExpoGoUpdatesModule to SDK 49. ([#24974](https://github.com/expo/expo/pull/24974) by [@wschurman](https://github.com/wschurman))
64
+ - Remove unused `storedUpdateIdsWithConfiguration` method. ([#25194](https://github.com/expo/expo/pull/25194) by [@wschurman](https://github.com/wschurman))
65
+ - Remove ability for embedded manifests to be legacy manifests. ([#25195](https://github.com/expo/expo/pull/25195) by [@wschurman](https://github.com/wschurman))
66
+ - Convert e2e setup scripts to typescript. ([#25271](https://github.com/expo/expo/pull/25271) by [@wschurman](https://github.com/wschurman))
67
+
68
+ ### ⚠️ Notices
69
+
70
+ - Deprecated `useUpdateEvents()` and `addListener()` in favor of the new `useUpdates()` API. ([#25345](https://github.com/expo/expo/pull/25345) by [@douglowder](https://github.com/douglowder))
71
+
72
+ ## 0.18.17 — 2023-10-25
73
+
74
+ ### 🐛 Bug fixes
75
+
76
+ - [Android] Simplify UpdatesUtils.parseDateString, fix UpdatesLoggingTest. ([#24951](https://github.com/expo/expo/pull/24951) by [@douglowder](https://github.com/douglowder))
77
+
13
78
  ## 0.22.0 — 2023-10-17
14
79
 
15
80
  ### 🐛 Bug fixes
@@ -20,6 +85,7 @@
20
85
  ### 💡 Others
21
86
 
22
87
  - Transpile for Node 18 (LTS). ([#24471](https://github.com/expo/expo/pull/24471) by [@EvanBacon](https://github.com/EvanBacon))
88
+ - iOS: Stub expo-updates in Expo Go and remove service pattern. ([#24860](https://github.com/expo/expo/pull/24860) by [@wschurman](https://github.com/wschurman))
23
89
 
24
90
  ## 0.18.16 — 2023-10-05
25
91
 
@@ -4,7 +4,7 @@ apply plugin: 'kotlin-kapt'
4
4
  apply plugin: 'maven-publish'
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '0.22.0'
7
+ version = '0.24.0'
8
8
 
9
9
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
10
10
  if (expoModulesCorePlugin.exists()) {
@@ -89,11 +89,11 @@ if (!safeExtGet("expoProvidesDefaultConfig", false)) {
89
89
  android {
90
90
  // Remove this if and it's contents, when support for SDK49 is dropped
91
91
  if (!safeExtGet("expoProvidesDefaultConfig", false)) {
92
- compileSdkVersion safeExtGet("compileSdkVersion", 33)
92
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
93
93
 
94
94
  defaultConfig {
95
95
  minSdkVersion safeExtGet("minSdkVersion", 23)
96
- targetSdkVersion safeExtGet("targetSdkVersion", 33)
96
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
97
97
  }
98
98
 
99
99
  publishing {
@@ -122,7 +122,7 @@ android {
122
122
  namespace "expo.modules.updates"
123
123
  defaultConfig {
124
124
  versionCode 31
125
- versionName '0.22.0'
125
+ versionName '0.24.0'
126
126
  consumerProguardFiles("proguard-rules.pro")
127
127
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
128
128
 
@@ -174,6 +174,8 @@ dependencies {
174
174
  implementation("commons-codec:commons-codec:1.10")
175
175
  implementation("commons-io:commons-io:2.6")
176
176
  implementation("commons-fileupload:commons-fileupload:1.4")
177
+ implementation("javax.portlet:portlet-api:3.0.1")
178
+ implementation("javax.servlet:javax.servlet-api:4.0.1")
177
179
  implementation("org.apache.commons:commons-lang3:3.9")
178
180
  implementation("org.bouncycastle:bcutil-jdk15to18:1.70")
179
181
 
@@ -0,0 +1,132 @@
1
+ package expo.modules.updates
2
+
3
+ import android.content.Context
4
+ import android.os.Bundle
5
+ import android.util.Log
6
+ import com.facebook.react.ReactInstanceManager
7
+ import expo.modules.kotlin.exception.CodedException
8
+ import expo.modules.updates.UpdatesConfiguration.Companion.UPDATES_CONFIGURATION_RELEASE_CHANNEL_DEFAULT_VALUE
9
+ import expo.modules.updates.launcher.Launcher
10
+ import expo.modules.updates.launcher.NoDatabaseLauncher
11
+ import expo.modules.updates.statemachine.UpdatesStateContext
12
+ import java.io.File
13
+
14
+ // this needs to stay for versioning to work
15
+ /* ktlint-disable no-unused-imports */
16
+ import expo.modules.updates.UpdatesConfiguration
17
+ /* ktlint-enable no-unused-imports */
18
+
19
+ /**
20
+ * Updates controller for applications that either disable updates explicitly or have an error
21
+ * during initialization. Errors that may occur include but are not limited to:
22
+ * - Disk access errors
23
+ * - Internal database initialization errors
24
+ * - Configuration errors (missing required configuration)
25
+ */
26
+ class DisabledUpdatesController(
27
+ private val context: Context,
28
+ private val fatalException: Exception?,
29
+ private val isMissingRuntimeVersion: Boolean
30
+ ) : IUpdatesController {
31
+ private var isStarted = false
32
+ private var launcher: Launcher? = null
33
+ private var isLoaderTaskFinished = false
34
+ override var updatesDirectory: File? = null
35
+
36
+ override var isEmergencyLaunch = false
37
+ private set
38
+
39
+ @get:Synchronized
40
+ override val launchAssetFile: String?
41
+ get() {
42
+ while (!isLoaderTaskFinished) {
43
+ try {
44
+ (this as java.lang.Object).wait()
45
+ } catch (e: InterruptedException) {
46
+ Log.e(TAG, "Interrupted while waiting for launch asset file", e)
47
+ }
48
+ }
49
+ return launcher?.launchAssetFile
50
+ }
51
+
52
+ override val bundleAssetName: String?
53
+ get() = launcher?.bundleAssetName
54
+
55
+ override fun onDidCreateReactInstanceManager(reactInstanceManager: ReactInstanceManager) {}
56
+
57
+ @Synchronized
58
+ override fun start() {
59
+ if (isStarted) {
60
+ return
61
+ }
62
+ isStarted = true
63
+
64
+ launcher = NoDatabaseLauncher(context, fatalException)
65
+ isEmergencyLaunch = fatalException != null
66
+ notifyController()
67
+ return
68
+ }
69
+
70
+ class UpdatesDisabledException(message: String) : CodedException(message)
71
+
72
+ override fun getConstantsForModule(): IUpdatesController.UpdatesModuleConstants {
73
+ return IUpdatesController.UpdatesModuleConstants(
74
+ launchedUpdate = launcher?.launchedUpdate,
75
+ embeddedUpdate = null,
76
+ isEmergencyLaunch = isEmergencyLaunch,
77
+ isEnabled = false,
78
+ releaseChannel = UPDATES_CONFIGURATION_RELEASE_CHANNEL_DEFAULT_VALUE,
79
+ isUsingEmbeddedAssets = launcher?.isUsingEmbeddedAssets ?: false,
80
+ runtimeVersion = null,
81
+ checkOnLaunch = UpdatesConfiguration.CheckAutomaticallyConfiguration.NEVER,
82
+ requestHeaders = mapOf(),
83
+ localAssetFiles = launcher?.localAssetFiles,
84
+ isMissingRuntimeVersion = isMissingRuntimeVersion,
85
+ )
86
+ }
87
+
88
+ override fun relaunchReactApplicationForModule(callback: IUpdatesController.ModuleCallback<Unit>) {
89
+ callback.onFailure(UpdatesDisabledException("You cannot reload when expo-updates is not enabled."))
90
+ }
91
+
92
+ override fun getNativeStateMachineContext(callback: IUpdatesController.ModuleCallback<UpdatesStateContext>) {
93
+ callback.onFailure(UpdatesDisabledException("You cannot check for updates when expo-updates is not enabled."))
94
+ }
95
+
96
+ override fun checkForUpdate(
97
+ callback: IUpdatesController.ModuleCallback<IUpdatesController.CheckForUpdateResult>
98
+ ) {
99
+ callback.onFailure(UpdatesDisabledException("You cannot check for updates when expo-updates is not enabled."))
100
+ }
101
+
102
+ override fun fetchUpdate(
103
+ callback: IUpdatesController.ModuleCallback<IUpdatesController.FetchUpdateResult>
104
+ ) {
105
+ callback.onFailure(UpdatesDisabledException("You cannot fetch update when expo-updates is not enabled."))
106
+ }
107
+
108
+ override fun getExtraParams(callback: IUpdatesController.ModuleCallback<Bundle>) {
109
+ callback.onFailure(UpdatesDisabledException("You cannot use extra params when expo-updates is not enabled."))
110
+ }
111
+
112
+ override fun setExtraParam(
113
+ key: String,
114
+ value: String?,
115
+ callback: IUpdatesController.ModuleCallback<Unit>
116
+ ) {
117
+ callback.onFailure(UpdatesDisabledException("You cannot use extra params when expo-updates is not enabled."))
118
+ }
119
+
120
+ @Synchronized
121
+ private fun notifyController() {
122
+ if (launcher == null) {
123
+ throw AssertionError("UpdatesController.notifyController was called with a null launcher, which is an error. This method should only be called when an update is ready to launch.")
124
+ }
125
+ isLoaderTaskFinished = true
126
+ (this as java.lang.Object).notify()
127
+ }
128
+
129
+ companion object {
130
+ private val TAG = DisabledUpdatesController::class.java.simpleName
131
+ }
132
+ }
@@ -0,0 +1,296 @@
1
+ package expo.modules.updates
2
+
3
+ import android.content.Context
4
+ import android.os.AsyncTask
5
+ import android.os.Bundle
6
+ import android.util.Log
7
+ import com.facebook.react.ReactApplication
8
+ import com.facebook.react.ReactInstanceManager
9
+ import com.facebook.react.ReactNativeHost
10
+ import com.facebook.react.bridge.Arguments
11
+ import com.facebook.react.bridge.WritableMap
12
+ import expo.modules.kotlin.exception.CodedException
13
+ import expo.modules.kotlin.exception.toCodedException
14
+ import expo.modules.updates.db.BuildData
15
+ import expo.modules.updates.db.DatabaseHolder
16
+ import expo.modules.updates.db.UpdatesDatabase
17
+ import expo.modules.updates.launcher.Launcher.LauncherCallback
18
+ import expo.modules.updates.loader.FileDownloader
19
+ import expo.modules.updates.logging.UpdatesLogReader
20
+ import expo.modules.updates.logging.UpdatesLogger
21
+ import expo.modules.updates.manifest.EmbeddedManifest
22
+ import expo.modules.updates.manifest.ManifestMetadata
23
+ import expo.modules.updates.procedures.CheckForUpdateProcedure
24
+ import expo.modules.updates.procedures.FetchUpdateProcedure
25
+ import expo.modules.updates.procedures.RelaunchProcedure
26
+ import expo.modules.updates.selectionpolicy.SelectionPolicyFactory
27
+ import expo.modules.updates.procedures.StartupProcedure
28
+ import expo.modules.updates.statemachine.UpdatesStateChangeEventSender
29
+ import expo.modules.updates.statemachine.UpdatesStateContext
30
+ import expo.modules.updates.statemachine.UpdatesStateEventType
31
+ import expo.modules.updates.statemachine.UpdatesStateMachine
32
+ import java.io.File
33
+ import java.lang.ref.WeakReference
34
+
35
+ // this needs to stay for versioning to work
36
+ /* ktlint-disable no-unused-imports */
37
+ import expo.modules.updates.UpdatesConfiguration
38
+ /* ktlint-enable no-unused-imports */
39
+
40
+ /**
41
+ * Updates controller for applications that have updates enabled and properly-configured.
42
+ */
43
+ class EnabledUpdatesController(
44
+ private val context: Context,
45
+ private val updatesConfiguration: UpdatesConfiguration,
46
+ override val updatesDirectory: File
47
+ ) : IUpdatesController, UpdatesStateChangeEventSender {
48
+ private var reactNativeHost: WeakReference<ReactNativeHost>? = if (context is ReactApplication) {
49
+ WeakReference(context.reactNativeHost)
50
+ } else {
51
+ null
52
+ }
53
+ private val logger = UpdatesLogger(context)
54
+ private val fileDownloader = FileDownloader(context)
55
+ private val selectionPolicy = SelectionPolicyFactory.createFilterAwarePolicy(
56
+ updatesConfiguration.getRuntimeVersion()
57
+ )
58
+ private val stateMachine = UpdatesStateMachine(context, this)
59
+ private val databaseHolder = DatabaseHolder(UpdatesDatabase.getInstance(context))
60
+
61
+ private fun purgeUpdatesLogsOlderThanOneDay() {
62
+ UpdatesLogReader(context).purgeLogEntries {
63
+ if (it != null) {
64
+ Log.e(TAG, "UpdatesLogReader: error in purgeLogEntries", it)
65
+ }
66
+ }
67
+ }
68
+
69
+ private var isStarted = false
70
+
71
+ private var isStartupFinished = false
72
+
73
+ @Synchronized
74
+ private fun onStartupProcedureFinished() {
75
+ isStartupFinished = true
76
+ (this@EnabledUpdatesController as java.lang.Object).notify()
77
+ }
78
+
79
+ private val startupProcedure = StartupProcedure(
80
+ context,
81
+ updatesConfiguration,
82
+ databaseHolder,
83
+ updatesDirectory,
84
+ fileDownloader,
85
+ selectionPolicy,
86
+ logger,
87
+ object : StartupProcedure.StartupProcedureCallback {
88
+ override fun onFinished() {
89
+ onStartupProcedureFinished()
90
+ }
91
+
92
+ override fun onLegacyJSEvent(event: StartupProcedure.StartupProcedureCallback.LegacyJSEvent) {
93
+ when (event) {
94
+ is StartupProcedure.StartupProcedureCallback.LegacyJSEvent.Error -> sendLegacyUpdateEventToJS(
95
+ UPDATE_ERROR_EVENT,
96
+ Arguments.createMap().apply {
97
+ putString("message", event.exception.message)
98
+ }
99
+ )
100
+ is StartupProcedure.StartupProcedureCallback.LegacyJSEvent.NoUpdateAvailable -> sendLegacyUpdateEventToJS(UPDATE_NO_UPDATE_AVAILABLE_EVENT, null)
101
+ is StartupProcedure.StartupProcedureCallback.LegacyJSEvent.UpdateAvailable -> sendLegacyUpdateEventToJS(
102
+ UPDATE_AVAILABLE_EVENT,
103
+ Arguments.createMap().apply {
104
+ putString("manifestString", event.manifest.toString())
105
+ }
106
+ )
107
+ }
108
+ }
109
+
110
+ override fun onRequestRelaunch(shouldRunReaper: Boolean, callback: LauncherCallback) {
111
+ relaunchReactApplication(shouldRunReaper, callback)
112
+ }
113
+ }
114
+ )
115
+
116
+ private val launchedUpdate
117
+ get() = startupProcedure.launchedUpdate
118
+ private val isUsingEmbeddedAssets
119
+ get() = startupProcedure.isUsingEmbeddedAssets
120
+ private val localAssetFiles
121
+ get() = startupProcedure.localAssetFiles
122
+ override val isEmergencyLaunch: Boolean
123
+ get() = startupProcedure.isEmergencyLaunch
124
+
125
+ @get:Synchronized
126
+ override val launchAssetFile: String?
127
+ get() {
128
+ while (!isStartupFinished) {
129
+ try {
130
+ (this as java.lang.Object).wait()
131
+ } catch (e: InterruptedException) {
132
+ Log.e(TAG, "Interrupted while waiting for launch asset file", e)
133
+ }
134
+ }
135
+ return startupProcedure.launchAssetFile
136
+ }
137
+ override val bundleAssetName: String?
138
+ get() = startupProcedure.bundleAssetName
139
+
140
+ override fun onDidCreateReactInstanceManager(reactInstanceManager: ReactInstanceManager) {
141
+ startupProcedure.onDidCreateReactInstanceManager(reactInstanceManager)
142
+ }
143
+
144
+ @Synchronized
145
+ override fun start() {
146
+ if (isStarted) {
147
+ return
148
+ }
149
+ isStarted = true
150
+
151
+ purgeUpdatesLogsOlderThanOneDay()
152
+
153
+ BuildData.ensureBuildDataIsConsistent(updatesConfiguration, databaseHolder.database)
154
+ databaseHolder.releaseDatabase()
155
+
156
+ stateMachine.queueExecution(startupProcedure)
157
+ }
158
+
159
+ private fun relaunchReactApplication(shouldRunReaper: Boolean, callback: LauncherCallback) {
160
+ val procedure = RelaunchProcedure(
161
+ context,
162
+ updatesConfiguration,
163
+ databaseHolder,
164
+ updatesDirectory,
165
+ fileDownloader,
166
+ selectionPolicy,
167
+ reactNativeHost,
168
+ getCurrentLauncher = { startupProcedure.launcher!! },
169
+ setCurrentLauncher = { currentLauncher -> startupProcedure.setLauncher(currentLauncher) },
170
+ shouldRunReaper = shouldRunReaper,
171
+ callback
172
+ )
173
+ stateMachine.queueExecution(procedure)
174
+ }
175
+
176
+ override fun sendUpdateStateChangeEventToBridge(eventType: UpdatesStateEventType, context: UpdatesStateContext) {
177
+ sendEventToJS(UPDATES_STATE_CHANGE_EVENT_NAME, eventType.type, context.writableMap)
178
+ }
179
+
180
+ private fun sendLegacyUpdateEventToJS(eventType: String, params: WritableMap?) {
181
+ sendEventToJS(UPDATES_EVENT_NAME, eventType, params)
182
+ }
183
+
184
+ private fun sendEventToJS(eventName: String, eventType: String, params: WritableMap?) {
185
+ UpdatesUtils.sendEventToReactNative(reactNativeHost, logger, eventName, eventType, params)
186
+ }
187
+
188
+ override fun getConstantsForModule(): IUpdatesController.UpdatesModuleConstants {
189
+ return IUpdatesController.UpdatesModuleConstants(
190
+ launchedUpdate = launchedUpdate,
191
+ embeddedUpdate = EmbeddedManifest.get(context, updatesConfiguration)?.updateEntity,
192
+ isEmergencyLaunch = isEmergencyLaunch,
193
+ isEnabled = true,
194
+ releaseChannel = updatesConfiguration.releaseChannel,
195
+ isUsingEmbeddedAssets = isUsingEmbeddedAssets,
196
+ runtimeVersion = updatesConfiguration.runtimeVersionRaw,
197
+ checkOnLaunch = updatesConfiguration.checkOnLaunch,
198
+ requestHeaders = updatesConfiguration.requestHeaders,
199
+ localAssetFiles = localAssetFiles,
200
+ isMissingRuntimeVersion = false,
201
+ )
202
+ }
203
+
204
+ override fun relaunchReactApplicationForModule(callback: IUpdatesController.ModuleCallback<Unit>) {
205
+ val canRelaunch = launchedUpdate != null
206
+ if (!canRelaunch) {
207
+ callback.onFailure(object : CodedException("ERR_UPDATES_RELOAD", "Cannot relaunch without a launched update.", null) {})
208
+ } else {
209
+ relaunchReactApplication(
210
+ shouldRunReaper = true,
211
+ object : LauncherCallback {
212
+ override fun onFailure(e: Exception) {
213
+ callback.onFailure(e.toCodedException())
214
+ }
215
+
216
+ override fun onSuccess() {
217
+ callback.onSuccess(Unit)
218
+ }
219
+ }
220
+ )
221
+ }
222
+ }
223
+
224
+ override fun getNativeStateMachineContext(callback: IUpdatesController.ModuleCallback<UpdatesStateContext>) {
225
+ callback.onSuccess(stateMachine.context)
226
+ }
227
+
228
+ override fun checkForUpdate(callback: IUpdatesController.ModuleCallback<IUpdatesController.CheckForUpdateResult>) {
229
+ val procedure = CheckForUpdateProcedure(context, updatesConfiguration, databaseHolder, logger, fileDownloader, selectionPolicy, launchedUpdate) {
230
+ callback.onSuccess(it)
231
+ }
232
+ stateMachine.queueExecution(procedure)
233
+ }
234
+
235
+ override fun fetchUpdate(callback: IUpdatesController.ModuleCallback<IUpdatesController.FetchUpdateResult>) {
236
+ val procedure = FetchUpdateProcedure(context, updatesConfiguration, databaseHolder, updatesDirectory, fileDownloader, selectionPolicy, launchedUpdate) {
237
+ callback.onSuccess(it)
238
+ }
239
+ stateMachine.queueExecution(procedure)
240
+ }
241
+
242
+ override fun getExtraParams(callback: IUpdatesController.ModuleCallback<Bundle>) {
243
+ AsyncTask.execute {
244
+ try {
245
+ val result = ManifestMetadata.getExtraParams(
246
+ databaseHolder.database,
247
+ updatesConfiguration,
248
+ )
249
+ databaseHolder.releaseDatabase()
250
+ val resultMap = when (result) {
251
+ null -> Bundle()
252
+ else -> {
253
+ Bundle().apply {
254
+ result.forEach {
255
+ putString(it.key, it.value)
256
+ }
257
+ }
258
+ }
259
+ }
260
+ callback.onSuccess(resultMap)
261
+ } catch (e: Exception) {
262
+ databaseHolder.releaseDatabase()
263
+ callback.onFailure(e.toCodedException())
264
+ }
265
+ }
266
+ }
267
+
268
+ override fun setExtraParam(key: String, value: String?, callback: IUpdatesController.ModuleCallback<Unit>) {
269
+ AsyncTask.execute {
270
+ try {
271
+ ManifestMetadata.setExtraParam(
272
+ databaseHolder.database,
273
+ updatesConfiguration,
274
+ key,
275
+ value
276
+ )
277
+ databaseHolder.releaseDatabase()
278
+ callback.onSuccess(Unit)
279
+ } catch (e: Exception) {
280
+ databaseHolder.releaseDatabase()
281
+ callback.onFailure(e.toCodedException())
282
+ }
283
+ }
284
+ }
285
+
286
+ companion object {
287
+ private val TAG = EnabledUpdatesController::class.java.simpleName
288
+
289
+ private const val UPDATE_AVAILABLE_EVENT = "updateAvailable"
290
+ private const val UPDATE_NO_UPDATE_AVAILABLE_EVENT = "noUpdateAvailable"
291
+ private const val UPDATE_ERROR_EVENT = "error"
292
+
293
+ private const val UPDATES_EVENT_NAME = "Expo.nativeUpdatesEvent"
294
+ private const val UPDATES_STATE_CHANGE_EVENT_NAME = "Expo.nativeUpdatesStateChangeEvent"
295
+ }
296
+ }
@@ -0,0 +1,121 @@
1
+ package expo.modules.updates
2
+
3
+ import android.os.Bundle
4
+ import com.facebook.react.ReactApplication
5
+ import com.facebook.react.ReactInstanceManager
6
+ import expo.modules.kotlin.exception.CodedException
7
+ import expo.modules.updates.db.entity.AssetEntity
8
+ import expo.modules.updates.db.entity.UpdateEntity
9
+ import expo.modules.updates.loader.LoaderTask
10
+ import expo.modules.updates.manifest.UpdateManifest
11
+ import expo.modules.updates.statemachine.UpdatesStateContext
12
+ import java.io.File
13
+ import java.util.Date
14
+
15
+ interface IUpdatesController {
16
+ val isEmergencyLaunch: Boolean
17
+
18
+ /**
19
+ * The path on disk to the launch asset (JS bundle) file for the React Native host to use.
20
+ * Blocks until the configured timeout runs out, or a new update has been downloaded and is ready
21
+ * to use (whichever comes sooner). ReactNativeHost.getJSBundleFile() should call into this.
22
+ *
23
+ * If this returns null, something has gone wrong and expo-updates has not been able to launch or
24
+ * find an update to use. In (and only in) this case, `getBundleAssetName()` will return a nonnull
25
+ * fallback value to use.
26
+ */
27
+ val launchAssetFile: String?
28
+
29
+ /**
30
+ * Returns the filename of the launch asset (JS bundle) file embedded in the APK bundle, which can
31
+ * be read using `context.getAssets()`. This is only nonnull if `getLaunchAssetFile` is null and
32
+ * should only be used in such a situation. ReactNativeHost.getBundleAssetName() should call into
33
+ * this.
34
+ */
35
+ val bundleAssetName: String?
36
+
37
+ /**
38
+ * Public for E2E tests.
39
+ */
40
+ val updatesDirectory: File?
41
+
42
+ fun onDidCreateReactInstanceManager(reactInstanceManager: ReactInstanceManager)
43
+
44
+ /**
45
+ * Starts the update process to launch a previously-loaded update and (if configured to do so)
46
+ * check for a new update from the server. This method should be called as early as possible in
47
+ * the application's lifecycle.
48
+ * @param context the base context of the application, ideally a [ReactApplication]
49
+ */
50
+ fun start()
51
+
52
+ interface ModuleCallback<T> {
53
+ fun onSuccess(result: T)
54
+ fun onFailure(exception: CodedException)
55
+ }
56
+
57
+ data class UpdatesModuleConstants(
58
+ val launchedUpdate: UpdateEntity?,
59
+ val embeddedUpdate: UpdateEntity?,
60
+ val isEmergencyLaunch: Boolean,
61
+ val isEnabled: Boolean,
62
+ val releaseChannel: String,
63
+ val isUsingEmbeddedAssets: Boolean,
64
+ val runtimeVersion: String?,
65
+ val checkOnLaunch: UpdatesConfiguration.CheckAutomaticallyConfiguration,
66
+ val requestHeaders: Map<String, String>,
67
+
68
+ /**
69
+ * Returns a map of the locally downloaded assets for the current update. Keys are the remote URLs
70
+ * of the assets and values are local paths. This should be exported by the Updates JS module and
71
+ * can be used by `expo-asset` or a similar module to override React Native's asset resolution and
72
+ * use the locally downloaded assets.
73
+ */
74
+ val localAssetFiles: Map<AssetEntity, String>?,
75
+
76
+ /**
77
+ * Whether there is no runtime version (or sdkVersion) provided in configuration. If it is missing,
78
+ * updates will be disabled and a warning will be logged.
79
+ */
80
+ val isMissingRuntimeVersion: Boolean,
81
+ )
82
+ fun getConstantsForModule(): UpdatesModuleConstants
83
+
84
+ fun relaunchReactApplicationForModule(callback: ModuleCallback<Unit>)
85
+
86
+ fun getNativeStateMachineContext(callback: ModuleCallback<UpdatesStateContext>)
87
+
88
+ sealed class CheckForUpdateResult(private val status: Status) {
89
+ private enum class Status {
90
+ NO_UPDATE_AVAILABLE,
91
+ UPDATE_AVAILABLE,
92
+ ROLL_BACK_TO_EMBEDDED,
93
+ ERROR
94
+ }
95
+
96
+ class NoUpdateAvailable(val reason: LoaderTask.RemoteCheckResultNotAvailableReason) : CheckForUpdateResult(Status.NO_UPDATE_AVAILABLE)
97
+ class UpdateAvailable(val updateManifest: UpdateManifest) : CheckForUpdateResult(Status.UPDATE_AVAILABLE)
98
+ class RollBackToEmbedded(val commitTime: Date) : CheckForUpdateResult(Status.ROLL_BACK_TO_EMBEDDED)
99
+ class ErrorResult(val error: Exception, val message: String) : CheckForUpdateResult(Status.ERROR)
100
+ }
101
+ fun checkForUpdate(callback: ModuleCallback<CheckForUpdateResult>)
102
+
103
+ sealed class FetchUpdateResult(private val status: Status) {
104
+ private enum class Status {
105
+ SUCCESS,
106
+ FAILURE,
107
+ ROLL_BACK_TO_EMBEDDED,
108
+ ERROR
109
+ }
110
+
111
+ class Success(val update: UpdateEntity) : FetchUpdateResult(Status.SUCCESS)
112
+ class Failure : FetchUpdateResult(Status.FAILURE)
113
+ class RollBackToEmbedded : FetchUpdateResult(Status.ROLL_BACK_TO_EMBEDDED)
114
+ class ErrorResult(val error: Exception) : FetchUpdateResult(Status.ERROR)
115
+ }
116
+ fun fetchUpdate(callback: ModuleCallback<FetchUpdateResult>)
117
+
118
+ fun getExtraParams(callback: ModuleCallback<Bundle>)
119
+
120
+ fun setExtraParam(key: String, value: String?, callback: ModuleCallback<Unit>)
121
+ }