expo-updates 0.17.0 → 0.18.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 (65) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +8 -1
  3. package/android/build.gradle +13 -13
  4. package/android/src/main/AndroidManifest.xml +1 -2
  5. package/android/src/main/java/expo/modules/updates/UpdatesController.kt +102 -14
  6. package/android/src/main/java/expo/modules/updates/UpdatesInterface.kt +3 -1
  7. package/android/src/main/java/expo/modules/updates/UpdatesModule.kt +133 -68
  8. package/android/src/main/java/expo/modules/updates/UpdatesPackage.kt +61 -13
  9. package/android/src/main/java/expo/modules/updates/UpdatesService.kt +4 -0
  10. package/android/src/main/java/expo/modules/updates/UpdatesUtils.kt +23 -13
  11. package/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt +8 -1
  12. package/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt +103 -22
  13. package/android/src/main/java/expo/modules/updates/manifest/LegacyUpdateManifest.kt +8 -4
  14. package/android/src/main/java/expo/modules/updates/manifest/NewUpdateManifest.kt +1 -1
  15. package/android/src/main/java/expo/modules/updates/selectionpolicy/LoaderSelectionPolicy.kt +8 -0
  16. package/android/src/main/java/expo/modules/updates/selectionpolicy/LoaderSelectionPolicyFilterAware.kt +25 -1
  17. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicy.kt +16 -0
  18. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateChangeEventSender.kt +10 -0
  19. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateContext.kt +80 -0
  20. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateError.kt +16 -0
  21. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateEvent.kt +54 -0
  22. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateEventType.kt +16 -0
  23. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateMachine.kt +150 -0
  24. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateValue.kt +11 -0
  25. package/build/Updates.d.ts +3 -2
  26. package/build/Updates.d.ts.map +1 -1
  27. package/build/Updates.js +5 -4
  28. package/build/Updates.js.map +1 -1
  29. package/build/statemachine/UpdatesStateMachine.d.ts +50 -0
  30. package/build/statemachine/UpdatesStateMachine.d.ts.map +1 -0
  31. package/build/statemachine/UpdatesStateMachine.js +116 -0
  32. package/build/statemachine/UpdatesStateMachine.js.map +1 -0
  33. package/e2e/fixtures/App-apitest.tsx +22 -6
  34. package/e2e/fixtures/App.tsx +70 -13
  35. package/e2e/fixtures/Updates.e2e.ts +225 -4
  36. package/e2e/fixtures/UpdatesE2ETestModule.kt +1 -1
  37. package/e2e/fixtures/project_files/eas-hooks/eas-build-on-success.sh +3 -0
  38. package/e2e/setup/project.js +31 -2
  39. package/expo-module.config.json +9 -0
  40. package/expo-updates-gradle-plugin/build.gradle.kts +39 -0
  41. package/expo-updates-gradle-plugin/src/main/kotlin/expo/modules/updates/ExpoUpdatesPlugin.kt +103 -0
  42. package/ios/EXUpdates/AppController.swift +145 -45
  43. package/ios/EXUpdates/AppLoader/AppLoaderTask.swift +135 -43
  44. package/ios/EXUpdates/AppLoader/EmbeddedAppLoader.swift +2 -1
  45. package/ios/EXUpdates/AppLoader/UpdateResponse.swift +5 -3
  46. package/ios/EXUpdates/EXUpdatesService.h +1 -0
  47. package/ios/EXUpdates/EXUpdatesService.m +5 -0
  48. package/ios/EXUpdates/Exceptions.swift +16 -0
  49. package/ios/EXUpdates/SelectionPolicy/LoaderSelectionPolicy.swift +15 -2
  50. package/ios/EXUpdates/SelectionPolicy/LoaderSelectionPolicyFilterAware.swift +26 -1
  51. package/ios/EXUpdates/SelectionPolicy/SelectionPolicy.swift +15 -2
  52. package/ios/EXUpdates/Update/Update.swift +1 -1
  53. package/ios/EXUpdates/UpdatesModule.swift +41 -104
  54. package/ios/EXUpdates/UpdatesStateMachine.swift +416 -0
  55. package/ios/EXUpdates/UpdatesUtils.swift +268 -38
  56. package/ios/EXUpdates.podspec +7 -0
  57. package/ios/Tests/SelectionPolicyFilterAwareSpec.swift +49 -0
  58. package/ios/Tests/UpdatesStateMachineSpec.swift +149 -0
  59. package/package.json +11 -10
  60. package/plugin/build/withUpdates.js +1 -0
  61. package/plugin/src/withUpdates.ts +2 -0
  62. package/scripts/createManifest.js +11 -1
  63. package/src/Updates.ts +5 -4
  64. package/src/statemachine/UpdatesStateMachine.ts +164 -0
  65. package/scripts/create-manifest-android.gradle +0 -122
package/CHANGELOG.md CHANGED
@@ -10,6 +10,29 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.18.0 — 2023-06-22
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 0.17.1 — 2023-06-21
18
+
19
+ ### 📚 3rd party library updates
20
+
21
+ - Updated `junit` to `4.13.2`. ([#22395](https://github.com/expo/expo/pull/22395) by [@josephyanks](https://github.com/josephyanks))
22
+
23
+ ### 🎉 New features
24
+
25
+ - [Android] Load updates in background thread and prevent ANR from initial launch. ([#20273](https://github.com/expo/expo/pull/20273) by [@kudo](https://github.com/kudo))
26
+ - Added support for React Native 0.72. ([#22588](https://github.com/expo/expo/pull/22588) by [@kudo](https://github.com/kudo))
27
+ - Added the Brotli compression support for EAS Update on Android. ([#22982](https://github.com/expo/expo/pull/22982) by [@kudo](https://github.com/kudo))
28
+ - [Android][iOS] State machine implementation. ([#22845](https://github.com/expo/expo/pull/22845) by [@douglowder](https://github.com/douglowder))
29
+
30
+ ### 🐛 Bug fixes
31
+
32
+ - [Android] Resolve up the project root when creating production manifest. ([#22044](https://github.com/expo/expo/pull/22044) by [@EvanBacon](https://github.com/EvanBacon))
33
+ - Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
34
+ - Fixed broken `create-manifest-android.gradle` on Android Gradle version 8. ([#22538](https://github.com/expo/expo/pull/22538) by [@kudo](https://github.com/kudo))
35
+
13
36
  ## 0.17.0 — 2023-05-08
14
37
 
15
38
  ### 🛠 Breaking changes
@@ -37,6 +60,7 @@
37
60
  - Fix empty body no-op multipart response. ([#22227](https://github.com/expo/expo/pull/22227) by [@wschurman](https://github.com/wschurman))
38
61
  - Put extra data mutation in transaction. ([#22252](https://github.com/expo/expo/pull/22252) by [@wschurman](https://github.com/wschurman))
39
62
  - [iOS] fix bizarre bug when downloading update twice. ([#22355](https://github.com/expo/expo/pull/22355) by [@douglowder](https://github.com/douglowder))
63
+ - Fix rollback to embedded logic. ([#22433](https://github.com/expo/expo/pull/22433), [#22434](https://github.com/expo/expo/pull/22434) by [@wschurman](https://github.com/wschurman))
40
64
 
41
65
  ### 💡 Others
42
66
 
package/README.md CHANGED
@@ -1,4 +1,11 @@
1
- # expo-updates
1
+ <p>
2
+ <a href="https://docs.expo.dev/versions/latest/sdk/updates/">
3
+ <img
4
+ src="../../.github/resources/expo-updates.svg"
5
+ alt="expo-updates"
6
+ height="64" />
7
+ </a>
8
+ </p>
2
9
 
3
10
  `expo-updates` fetches and manages updates to your app stored on a remote server.
4
11
 
@@ -4,12 +4,10 @@ apply plugin: 'kotlin-kapt'
4
4
  apply plugin: 'maven-publish'
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '0.17.0'
7
+ version = '0.18.0'
8
8
 
9
9
  def ex_updates_native_debug = System.getenv("EX_UPDATES_NATIVE_DEBUG") == "1" ? "true" : "false"
10
10
 
11
- apply from: "../scripts/create-manifest-android.gradle"
12
-
13
11
  buildscript {
14
12
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
15
13
  if (expoModulesCorePlugin.exists()) {
@@ -40,19 +38,11 @@ buildscript {
40
38
  }
41
39
  }
42
40
 
43
- // Creating sources with comments
44
- task androidSourcesJar(type: Jar) {
45
- classifier = 'sources'
46
- from android.sourceSets.main.java.srcDirs
47
- }
48
-
49
41
  afterEvaluate {
50
42
  publishing {
51
43
  publications {
52
44
  release(MavenPublication) {
53
45
  from components.release
54
- // Add additional sourcesJar to artifacts
55
- artifact(androidSourcesJar)
56
46
  }
57
47
  }
58
48
  repositories {
@@ -75,14 +65,18 @@ android {
75
65
  jvmTarget = JavaVersion.VERSION_11.majorVersion
76
66
  }
77
67
 
68
+ namespace "expo.modules.updates"
78
69
  defaultConfig {
79
70
  minSdkVersion safeExtGet("minSdkVersion", 21)
80
71
  targetSdkVersion safeExtGet("targetSdkVersion", 33)
81
72
  versionCode 31
82
- versionName '0.17.0'
73
+ versionName '0.18.0'
83
74
  consumerProguardFiles("proguard-rules.pro")
84
75
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
76
+
85
77
  buildConfigField("boolean", "EX_UPDATES_NATIVE_DEBUG", ex_updates_native_debug)
78
+ buildConfigField("boolean", "EX_UPDATES_ANDROID_DELAY_LOAD_APP", boolish(findProperty("EX_UPDATES_ANDROID_DELAY_LOAD_APP") ?: true).toString())
79
+
86
80
  // uncomment below to export the database schema when making changes
87
81
  /* javaCompileOptions {
88
82
  annotationProcessorOptions {
@@ -102,6 +96,11 @@ android {
102
96
  androidTest.assets.srcDirs += files("$projectDir/src/androidTest/schemas".toString())
103
97
  androidTest.assets.srcDirs += files("$projectDir/src/androidTest/certificates".toString())
104
98
  }
99
+ publishing {
100
+ singleVariant("release") {
101
+ withSourcesJar()
102
+ }
103
+ }
105
104
  }
106
105
 
107
106
  dependencies {
@@ -121,6 +120,7 @@ dependencies {
121
120
 
122
121
  implementation("com.squareup.okhttp3:okhttp:4.9.2")
123
122
  implementation("com.squareup.okhttp3:okhttp-urlconnection:4.9.2")
123
+ implementation("com.squareup.okhttp3:okhttp-brotli:4.9.2")
124
124
  implementation("com.squareup.okio:okio:2.9.0")
125
125
  implementation("commons-codec:commons-codec:1.10")
126
126
  implementation("commons-io:commons-io:2.6")
@@ -128,7 +128,7 @@ dependencies {
128
128
  implementation("org.apache.commons:commons-lang3:3.9")
129
129
  implementation("org.bouncycastle:bcutil-jdk15to18:1.70")
130
130
 
131
- testImplementation 'junit:junit:4.13.1'
131
+ testImplementation 'junit:junit:4.13.2'
132
132
  testImplementation 'androidx.test:core:1.4.0'
133
133
  testImplementation 'io.mockk:mockk:1.12.3'
134
134
  testImplementation "org.jetbrains.kotlin:kotlin-test-junit:${getKotlinVersion()}"
@@ -1,4 +1,3 @@
1
- <manifest package="expo.modules.updates"
2
- xmlns:android="http://schemas.android.com/apk/res/android">
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
2
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
4
3
  </manifest>
@@ -12,6 +12,7 @@ import com.facebook.react.ReactInstanceManager
12
12
  import com.facebook.react.ReactNativeHost
13
13
  import com.facebook.react.bridge.Arguments
14
14
  import com.facebook.react.bridge.JSBundleLoader
15
+ import com.facebook.react.bridge.WritableMap
15
16
  import expo.modules.updates.db.BuildData
16
17
  import expo.modules.updates.db.DatabaseHolder
17
18
  import expo.modules.updates.db.Reaper
@@ -25,7 +26,7 @@ import expo.modules.updates.launcher.Launcher
25
26
  import expo.modules.updates.launcher.Launcher.LauncherCallback
26
27
  import expo.modules.updates.launcher.NoDatabaseLauncher
27
28
  import expo.modules.updates.loader.*
28
- import expo.modules.updates.loader.LoaderTask.BackgroundUpdateStatus
29
+ import expo.modules.updates.loader.LoaderTask.RemoteUpdateStatus
29
30
  import expo.modules.updates.loader.LoaderTask.LoaderTaskCallback
30
31
  import expo.modules.updates.logging.UpdatesErrorCode
31
32
  import expo.modules.updates.logging.UpdatesLogReader
@@ -33,6 +34,12 @@ import expo.modules.updates.logging.UpdatesLogger
33
34
  import expo.modules.updates.manifest.UpdateManifest
34
35
  import expo.modules.updates.selectionpolicy.SelectionPolicy
35
36
  import expo.modules.updates.selectionpolicy.SelectionPolicyFactory
37
+ import expo.modules.updates.statemachine.UpdatesStateChangeEventSender
38
+ import expo.modules.updates.statemachine.UpdatesStateContext
39
+ import expo.modules.updates.statemachine.UpdatesStateEvent
40
+ import expo.modules.updates.statemachine.UpdatesStateEventType
41
+ import expo.modules.updates.statemachine.UpdatesStateMachine
42
+ import expo.modules.updates.statemachine.UpdatesStateValue
36
43
  import java.io.File
37
44
  import java.lang.ref.WeakReference
38
45
 
@@ -58,7 +65,7 @@ import java.lang.ref.WeakReference
58
65
  class UpdatesController private constructor(
59
66
  context: Context,
60
67
  var updatesConfiguration: UpdatesConfiguration
61
- ) {
68
+ ) : UpdatesStateChangeEventSender {
62
69
  private var reactNativeHost: WeakReference<ReactNativeHost>? = if (context is ReactApplication) {
63
70
  WeakReference((context as ReactApplication).reactNativeHost)
64
71
  } else {
@@ -67,6 +74,7 @@ class UpdatesController private constructor(
67
74
 
68
75
  var updatesDirectory: File? = null
69
76
  var updatesDirectoryException: Exception? = null
77
+ var stateMachine: UpdatesStateMachine = UpdatesStateMachine(context, this)
70
78
 
71
79
  private var launcher: Launcher? = null
72
80
  val databaseHolder = DatabaseHolder(UpdatesDatabase.getInstance(context))
@@ -273,6 +281,22 @@ class UpdatesController private constructor(
273
281
  return true
274
282
  }
275
283
 
284
+ override fun onRemoteCheckForUpdateStarted() {
285
+ stateMachine.processEvent(UpdatesStateEvent.Check())
286
+ }
287
+
288
+ override fun onRemoteCheckForUpdateFinished(result: LoaderTask.RemoteCheckResult) {
289
+ var event = UpdatesStateEvent.CheckComplete()
290
+ if (result.manifest != null) {
291
+ event = UpdatesStateEvent.CheckCompleteWithUpdate(
292
+ result.manifest
293
+ )
294
+ } else if (result.isRollBackToEmbedded == true) {
295
+ event = UpdatesStateEvent.CheckCompleteWithRollback()
296
+ }
297
+ stateMachine.processEvent(event)
298
+ }
299
+
276
300
  override fun onRemoteUpdateManifestResponseManifestLoaded(updateManifest: UpdateManifest) {
277
301
  remoteLoadStatus = ErrorRecoveryDelegate.RemoteLoadStatus.NEW_UPDATE_LOADING
278
302
  }
@@ -285,13 +309,34 @@ class UpdatesController private constructor(
285
309
  notifyController()
286
310
  }
287
311
 
288
- override fun onBackgroundUpdateFinished(
289
- status: BackgroundUpdateStatus,
312
+ override fun onRemoteUpdateLoadStarted() {
313
+ stateMachine.processEvent(UpdatesStateEvent.Download())
314
+ }
315
+
316
+ override fun onRemoteUpdateAssetLoaded(
317
+ asset: AssetEntity,
318
+ successfulAssetCount: Int,
319
+ failedAssetCount: Int,
320
+ totalAssetCount: Int
321
+ ) {
322
+ val body = mapOf(
323
+ "assetInfo" to mapOf(
324
+ "name" to asset.embeddedAssetFilename,
325
+ "successfulAssetCount" to successfulAssetCount,
326
+ "failedAssetCount" to failedAssetCount,
327
+ "totalAssetCount" to totalAssetCount
328
+ )
329
+ )
330
+ logger.info("AppController appLoaderTask didLoadAsset: $body", UpdatesErrorCode.None, null, asset.expectedHash)
331
+ }
332
+
333
+ override fun onRemoteUpdateFinished(
334
+ status: RemoteUpdateStatus,
290
335
  update: UpdateEntity?,
291
336
  exception: Exception?
292
337
  ) {
293
338
  when (status) {
294
- BackgroundUpdateStatus.ERROR -> {
339
+ RemoteUpdateStatus.ERROR -> {
295
340
  if (exception == null) {
296
341
  throw AssertionError("Background update with error status must have a nonnull exception object")
297
342
  }
@@ -299,9 +344,31 @@ class UpdatesController private constructor(
299
344
  remoteLoadStatus = ErrorRecoveryDelegate.RemoteLoadStatus.IDLE
300
345
  val params = Arguments.createMap()
301
346
  params.putString("message", exception.message)
302
- UpdatesUtils.sendEventToReactNative(reactNativeHost, UPDATE_ERROR_EVENT, params)
347
+ sendLegacyUpdateEventToJS(UPDATE_ERROR_EVENT, params)
348
+
349
+ // Since errors can happen through a number of paths, we do these checks
350
+ // to make sure the state machine is valid
351
+ when (stateMachine.state) {
352
+ UpdatesStateValue.Idle -> {
353
+ stateMachine.processEvent(UpdatesStateEvent.Download())
354
+ stateMachine.processEvent(
355
+ UpdatesStateEvent.DownloadError(exception.message ?: "")
356
+ )
357
+ }
358
+ UpdatesStateValue.Checking -> {
359
+ stateMachine.processEvent(
360
+ UpdatesStateEvent.CheckError(exception.message ?: "")
361
+ )
362
+ }
363
+ else -> {
364
+ // .downloading
365
+ stateMachine.processEvent(
366
+ UpdatesStateEvent.DownloadError(exception.message ?: "")
367
+ )
368
+ }
369
+ }
303
370
  }
304
- BackgroundUpdateStatus.UPDATE_AVAILABLE -> {
371
+ RemoteUpdateStatus.UPDATE_AVAILABLE -> {
305
372
  if (update == null) {
306
373
  throw AssertionError("Background update with error status must have a nonnull update object")
307
374
  }
@@ -309,16 +376,19 @@ class UpdatesController private constructor(
309
376
  logger.info("UpdatesController onBackgroundUpdateFinished: Update available", UpdatesErrorCode.None)
310
377
  val params = Arguments.createMap()
311
378
  params.putString("manifestString", update.manifest.toString())
312
- UpdatesUtils.sendEventToReactNative(reactNativeHost, UPDATE_AVAILABLE_EVENT, params)
379
+ sendLegacyUpdateEventToJS(UPDATE_AVAILABLE_EVENT, params)
380
+ stateMachine.processEvent(
381
+ UpdatesStateEvent.DownloadCompleteWithUpdate(update.manifest)
382
+ )
313
383
  }
314
- BackgroundUpdateStatus.NO_UPDATE_AVAILABLE -> {
384
+ RemoteUpdateStatus.NO_UPDATE_AVAILABLE -> {
315
385
  remoteLoadStatus = ErrorRecoveryDelegate.RemoteLoadStatus.IDLE
316
386
  logger.error("UpdatesController onBackgroundUpdateFinished: No update available", UpdatesErrorCode.NoUpdatesAvailable)
317
- UpdatesUtils.sendEventToReactNative(
318
- reactNativeHost,
319
- UPDATE_NO_UPDATE_AVAILABLE_EVENT,
320
- null
321
- )
387
+ sendLegacyUpdateEventToJS(UPDATE_NO_UPDATE_AVAILABLE_EVENT, null)
388
+ // TODO: handle rollbacks properly, but this works for now
389
+ if (stateMachine.state == UpdatesStateValue.Downloading) {
390
+ stateMachine.processEvent(UpdatesStateEvent.DownloadComplete())
391
+ }
322
392
  }
323
393
  }
324
394
  errorRecovery.notifyNewRemoteLoadStatus(remoteLoadStatus)
@@ -438,6 +508,8 @@ class UpdatesController private constructor(
438
508
  return
439
509
  }
440
510
 
511
+ stateMachine.processEvent(UpdatesStateEvent.Restart())
512
+
441
513
  val oldLaunchAssetFile = launcher!!.launchAssetFile
442
514
 
443
515
  val databaseLocal = getDatabase()
@@ -482,11 +554,24 @@ class UpdatesController private constructor(
482
554
  if (shouldRunReaper) {
483
555
  runReaper()
484
556
  }
557
+ stateMachine.reset()
485
558
  }
486
559
  }
487
560
  )
488
561
  }
489
562
 
563
+ override fun sendUpdateStateChangeEventToBridge(eventType: UpdatesStateEventType, context: UpdatesStateContext) {
564
+ sendEventToJS(UPDATES_STATE_CHANGE_EVENT_NAME, eventType.type, context.writableMap)
565
+ }
566
+
567
+ fun sendLegacyUpdateEventToJS(eventType: String, params: WritableMap?) {
568
+ sendEventToJS(UPDATES_EVENT_NAME, eventType, params)
569
+ }
570
+
571
+ private fun sendEventToJS(eventName: String, eventType: String, params: WritableMap?) {
572
+ UpdatesUtils.sendEventToReactNative(reactNativeHost, logger, eventName, eventType, params)
573
+ }
574
+
490
575
  companion object {
491
576
  private val TAG = UpdatesController::class.java.simpleName
492
577
 
@@ -494,6 +579,9 @@ class UpdatesController private constructor(
494
579
  private const val UPDATE_NO_UPDATE_AVAILABLE_EVENT = "noUpdateAvailable"
495
580
  private const val UPDATE_ERROR_EVENT = "error"
496
581
 
582
+ private const val UPDATES_EVENT_NAME = "Expo.nativeUpdatesEvent"
583
+ private const val UPDATES_STATE_CHANGE_EVENT_NAME = "Expo.nativeUpdatesStateChangeEvent"
584
+
497
585
  private var singletonInstance: UpdatesController? = null
498
586
  @JvmStatic val instance: UpdatesController
499
587
  get() {
@@ -10,7 +10,8 @@ import java.io.File
10
10
 
11
11
  // this unused import must stay because of versioning
12
12
  /* ktlint-disable no-unused-imports */
13
- import expo.modules.updates.UpdatesConfiguration
13
+ import expo.modules.updates.statemachine.UpdatesStateMachine
14
+
14
15
  /* ktlint-enable no-unused-imports */
15
16
 
16
17
  /**
@@ -23,6 +24,7 @@ interface UpdatesInterface {
23
24
  val directory: File?
24
25
  val databaseHolder: DatabaseHolder
25
26
  val fileDownloader: FileDownloader
27
+ val stateMachine: UpdatesStateMachine?
26
28
 
27
29
  val isEmergencyLaunch: Boolean
28
30
  val isEmbeddedLaunch: Boolean
@@ -18,6 +18,7 @@ import expo.modules.updates.logging.UpdatesLogEntry
18
18
  import expo.modules.updates.logging.UpdatesLogReader
19
19
  import expo.modules.updates.logging.UpdatesLogger
20
20
  import expo.modules.updates.manifest.ManifestMetadata
21
+ import expo.modules.updates.statemachine.UpdatesStateEvent
21
22
  import java.util.Date
22
23
 
23
24
  // these unused imports must stay because of versioning
@@ -40,6 +41,8 @@ class UpdatesModule(
40
41
  ) : ExportedModule(context) {
41
42
  private inline fun <reified T> moduleRegistry() = moduleRegistryDelegate.getFromModuleRegistry<T>()
42
43
 
44
+ private val logger = UpdatesLogger(context)
45
+
43
46
  private val updatesService: UpdatesInterface? by moduleRegistry()
44
47
 
45
48
  override fun onCreate(moduleRegistry: ModuleRegistry) {
@@ -141,69 +144,91 @@ class UpdatesModule(
141
144
  )
142
145
  return
143
146
  }
144
- val databaseHolder = updatesServiceLocal.databaseHolder
145
- val extraHeaders = FileDownloader.getExtraHeadersForRemoteUpdateRequest(
146
- databaseHolder.database,
147
- updatesServiceLocal.configuration,
148
- updatesServiceLocal.launchedUpdate,
149
- updatesServiceLocal.embeddedUpdate
150
- )
151
- databaseHolder.releaseDatabase()
152
- updatesServiceLocal.fileDownloader.downloadRemoteUpdate(
153
- updatesServiceLocal.configuration,
154
- extraHeaders,
155
- context,
156
- object : RemoteUpdateDownloadCallback {
157
- override fun onFailure(message: String, e: Exception) {
158
- promise.reject("ERR_UPDATES_CHECK", message, e)
159
- Log.e(TAG, message, e)
160
- }
147
+ updatesServiceLocal.stateMachine?.processEvent(UpdatesStateEvent.Check())
148
+ AsyncTask.execute {
149
+ val databaseHolder = updatesServiceLocal.databaseHolder
150
+ val extraHeaders = FileDownloader.getExtraHeadersForRemoteUpdateRequest(
151
+ databaseHolder.database,
152
+ updatesServiceLocal.configuration,
153
+ updatesServiceLocal.launchedUpdate,
154
+ updatesServiceLocal.embeddedUpdate
155
+ )
156
+ databaseHolder.releaseDatabase()
157
+ updatesServiceLocal.fileDownloader.downloadRemoteUpdate(
158
+ updatesServiceLocal.configuration,
159
+ extraHeaders,
160
+ context,
161
+ object : RemoteUpdateDownloadCallback {
162
+ override fun onFailure(message: String, e: Exception) {
163
+ promise.reject("ERR_UPDATES_CHECK", message, e)
164
+ Log.e(TAG, message, e)
165
+ }
161
166
 
162
- override fun onSuccess(updateResponse: UpdateResponse) {
163
- val updateDirective = updateResponse.directiveUpdateResponsePart?.updateDirective
164
- val updateManifest = updateResponse.manifestUpdateResponsePart?.updateManifest
167
+ override fun onSuccess(updateResponse: UpdateResponse) {
168
+ val updateDirective = updateResponse.directiveUpdateResponsePart?.updateDirective
169
+ val updateManifest = updateResponse.manifestUpdateResponsePart?.updateManifest
170
+
171
+ val updateInfo = Bundle()
172
+ if (updateDirective != null) {
173
+ if (updateDirective is UpdateDirective.RollBackToEmbeddedUpdateDirective) {
174
+ updateInfo.putBoolean("isRollBackToEmbedded", true)
175
+ promise.resolve(updateInfo)
176
+ updatesServiceLocal.stateMachine?.processEvent(
177
+ UpdatesStateEvent.CheckCompleteWithRollback()
178
+ )
179
+ return
180
+ }
181
+ }
165
182
 
166
- val updateInfo = Bundle()
167
- if (updateDirective != null) {
168
- if (updateDirective is UpdateDirective.RollBackToEmbeddedUpdateDirective) {
169
- updateInfo.putBoolean("isRollBackToEmbedded", true)
183
+ if (updateManifest == null) {
184
+ updateInfo.putBoolean("isAvailable", false)
170
185
  promise.resolve(updateInfo)
186
+ updatesServiceLocal.stateMachine?.processEvent(
187
+ UpdatesStateEvent.CheckComplete()
188
+ )
171
189
  return
172
190
  }
173
- }
174
191
 
175
- if (updateManifest == null) {
176
- updateInfo.putBoolean("isAvailable", false)
177
- promise.resolve(updateInfo)
178
- return
179
- }
180
-
181
- val launchedUpdate = updatesServiceLocal.launchedUpdate
182
- if (launchedUpdate == null) {
183
- // this shouldn't ever happen, but if we don't have anything to compare
184
- // the new manifest to, let the user know an update is available
185
- updateInfo.putBoolean("isAvailable", true)
186
- updateInfo.putString("manifestString", updateManifest.manifest.toString())
187
- promise.resolve(updateInfo)
188
- return
189
- }
192
+ val launchedUpdate = updatesServiceLocal.launchedUpdate
193
+ if (launchedUpdate == null) {
194
+ // this shouldn't ever happen, but if we don't have anything to compare
195
+ // the new manifest to, let the user know an update is available
196
+ updateInfo.putBoolean("isAvailable", true)
197
+ updateInfo.putString("manifestString", updateManifest.manifest.toString())
198
+ promise.resolve(updateInfo)
199
+ updatesServiceLocal.stateMachine?.processEvent(
200
+ UpdatesStateEvent.CheckCompleteWithUpdate(
201
+ updateManifest.manifest.getRawJson()
202
+ )
203
+ )
204
+ return
205
+ }
190
206
 
191
- if (updatesServiceLocal.selectionPolicy.shouldLoadNewUpdate(
192
- updateManifest.updateEntity,
193
- launchedUpdate,
194
- updateResponse.responseHeaderData?.manifestFilters
195
- )
196
- ) {
197
- updateInfo.putBoolean("isAvailable", true)
198
- updateInfo.putString("manifestString", updateManifest.manifest.toString())
199
- promise.resolve(updateInfo)
200
- } else {
201
- updateInfo.putBoolean("isAvailable", false)
202
- promise.resolve(updateInfo)
207
+ if (updatesServiceLocal.selectionPolicy.shouldLoadNewUpdate(
208
+ updateManifest.updateEntity,
209
+ launchedUpdate,
210
+ updateResponse.responseHeaderData?.manifestFilters
211
+ )
212
+ ) {
213
+ updateInfo.putBoolean("isAvailable", true)
214
+ updateInfo.putString("manifestString", updateManifest.manifest.toString())
215
+ promise.resolve(updateInfo)
216
+ updatesServiceLocal.stateMachine?.processEvent(
217
+ UpdatesStateEvent.CheckCompleteWithUpdate(
218
+ updateManifest.manifest.getRawJson()
219
+ )
220
+ )
221
+ } else {
222
+ updateInfo.putBoolean("isAvailable", false)
223
+ promise.resolve(updateInfo)
224
+ updatesServiceLocal.stateMachine?.processEvent(
225
+ UpdatesStateEvent.CheckComplete()
226
+ )
227
+ }
203
228
  }
204
229
  }
205
- }
206
- )
230
+ )
231
+ }
207
232
  } catch (e: IllegalStateException) {
208
233
  promise.reject(
209
234
  "ERR_UPDATES_CHECK",
@@ -223,6 +248,7 @@ class UpdatesModule(
223
248
  )
224
249
  return
225
250
  }
251
+ updatesServiceLocal.stateMachine?.processEvent(UpdatesStateEvent.Download())
226
252
  AsyncTask.execute {
227
253
  val databaseHolder = updatesServiceLocal.databaseHolder
228
254
  RemoteLoader(
@@ -238,6 +264,9 @@ class UpdatesModule(
238
264
  override fun onFailure(e: Exception) {
239
265
  databaseHolder.releaseDatabase()
240
266
  promise.reject("ERR_UPDATES_FETCH", "Failed to download new update", e)
267
+ updatesServiceLocal.stateMachine?.processEvent(
268
+ UpdatesStateEvent.DownloadError("Failed to download new update: ${e.message}")
269
+ )
241
270
  }
242
271
 
243
272
  override fun onAssetLoaded(
@@ -276,9 +305,15 @@ class UpdatesModule(
276
305
 
277
306
  if (loaderResult.updateDirective is UpdateDirective.RollBackToEmbeddedUpdateDirective) {
278
307
  updateInfo.putBoolean("isRollBackToEmbedded", true)
308
+ updatesServiceLocal.stateMachine?.processEvent(
309
+ UpdatesStateEvent.DownloadCompleteWithRollback()
310
+ )
279
311
  } else {
280
312
  if (loaderResult.updateEntity == null) {
281
313
  updateInfo.putBoolean("isNew", false)
314
+ updatesServiceLocal.stateMachine?.processEvent(
315
+ UpdatesStateEvent.DownloadComplete()
316
+ )
282
317
  } else {
283
318
  updatesServiceLocal.resetSelectionPolicy()
284
319
  updateInfo.putBoolean("isNew", true)
@@ -286,6 +321,9 @@ class UpdatesModule(
286
321
  "manifestString",
287
322
  loaderResult.updateEntity.manifest.toString()
288
323
  )
324
+ updatesServiceLocal.stateMachine?.processEvent(
325
+ UpdatesStateEvent.DownloadCompleteWithUpdate(loaderResult.updateEntity.manifest)
326
+ )
289
327
  }
290
328
  }
291
329
 
@@ -295,15 +333,14 @@ class UpdatesModule(
295
333
  )
296
334
  }
297
335
  } catch (e: IllegalStateException) {
298
- promise.reject(
299
- "ERR_UPDATES_FETCH",
300
- "The updates module controller has not been properly initialized. If you're using a development client, you cannot fetch updates. Otherwise, make sure you have called the native method UpdatesController.initialize()."
301
- )
336
+ val message = "The updates module controller has not been properly initialized. If you're using a development client, you cannot fetch updates. Otherwise, make sure you have called the native method UpdatesController.initialize()."
337
+ promise.reject("ERR_UPDATES_FETCH", message)
302
338
  }
303
339
  }
304
340
 
305
341
  @ExpoMethod
306
342
  fun getExtraParamsAsync(promise: Promise) {
343
+ logger.debug("Called getExtraParamsAsync")
307
344
  val updatesServiceLocal = updatesService
308
345
  if (!updatesServiceLocal!!.configuration.isEnabled) {
309
346
  promise.reject(
@@ -315,17 +352,36 @@ class UpdatesModule(
315
352
 
316
353
  AsyncTask.execute {
317
354
  val databaseHolder = updatesServiceLocal.databaseHolder
318
- promise.resolve(
319
- ManifestMetadata.getExtraParams(
355
+ try {
356
+ val result = ManifestMetadata.getExtraParams(
320
357
  databaseHolder.database,
321
358
  updatesServiceLocal.configuration,
322
359
  )
323
- )
360
+ databaseHolder.releaseDatabase()
361
+ val resultMap = when (result) {
362
+ null -> Bundle()
363
+ else -> {
364
+ Bundle().apply {
365
+ result.forEach {
366
+ putString(it.key, it.value)
367
+ }
368
+ }
369
+ }
370
+ }
371
+ promise.resolve(resultMap)
372
+ } catch (e: Exception) {
373
+ databaseHolder.releaseDatabase()
374
+ promise.reject(
375
+ "ERR_UPDATES_FETCH",
376
+ "Exception in getExtraParamsAsync: ${e.message}, ${e.stackTraceToString()}"
377
+ )
378
+ }
324
379
  }
325
380
  }
326
381
 
327
382
  @ExpoMethod
328
383
  fun setExtraParamAsync(key: String, value: String?, promise: Promise) {
384
+ logger.debug("Called setExtraParamAsync with key = $key, value = $value")
329
385
  val updatesServiceLocal = updatesService
330
386
  if (!updatesServiceLocal!!.configuration.isEnabled) {
331
387
  promise.reject(
@@ -337,13 +393,22 @@ class UpdatesModule(
337
393
 
338
394
  AsyncTask.execute {
339
395
  val databaseHolder = updatesServiceLocal.databaseHolder
340
- ManifestMetadata.setExtraParam(
341
- databaseHolder.database,
342
- updatesServiceLocal.configuration,
343
- key,
344
- value
345
- )
346
- promise.resolve(null)
396
+ try {
397
+ ManifestMetadata.setExtraParam(
398
+ databaseHolder.database,
399
+ updatesServiceLocal.configuration,
400
+ key,
401
+ value
402
+ )
403
+ databaseHolder.releaseDatabase()
404
+ promise.resolve(null)
405
+ } catch (e: Exception) {
406
+ databaseHolder.releaseDatabase()
407
+ promise.reject(
408
+ "ERR_UPDATES_FETCH",
409
+ "Exception in setExtraParamAsync: ${e.message}, ${e.stackTraceToString()}"
410
+ )
411
+ }
347
412
  }
348
413
  }
349
414