expo-updates 0.19.1 → 0.21.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 (102) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/android/build.gradle +11 -8
  3. package/android/src/main/java/expo/modules/updates/UpdatesController.kt +2 -2
  4. package/android/src/main/java/expo/modules/updates/UpdatesDevLauncherController.kt +1 -1
  5. package/android/src/main/java/expo/modules/updates/UpdatesModule.kt +61 -14
  6. package/android/src/main/java/expo/modules/updates/db/UpdatesDatabase.kt +22 -1
  7. package/android/src/main/java/expo/modules/updates/db/entity/UpdateEntity.kt +2 -4
  8. package/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt +37 -6
  9. package/android/src/main/java/expo/modules/updates/manifest/BareUpdateManifest.kt +3 -3
  10. package/android/src/main/java/expo/modules/updates/manifest/LegacyUpdateManifest.kt +1 -2
  11. package/android/src/main/java/expo/modules/updates/manifest/NewUpdateManifest.kt +1 -3
  12. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicies.kt +2 -3
  13. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateContext.kt +12 -3
  14. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateContextRollback.kt +14 -0
  15. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateEvent.kt +3 -2
  16. package/android/src/main/java/expo/modules/updates/statemachine/UpdatesStateMachine.kt +5 -4
  17. package/build/Updates.d.ts +10 -8
  18. package/build/Updates.d.ts.map +1 -1
  19. package/build/Updates.js +23 -14
  20. package/build/Updates.js.map +1 -1
  21. package/build/Updates.types.d.ts +61 -14
  22. package/build/Updates.types.d.ts.map +1 -1
  23. package/build/Updates.types.js +26 -0
  24. package/build/Updates.types.js.map +1 -1
  25. package/build/UpdatesEmitter.d.ts.map +1 -1
  26. package/build/UpdatesEmitter.js +9 -19
  27. package/build/UpdatesEmitter.js.map +1 -1
  28. package/build/UseUpdates.types.d.ts +34 -3
  29. package/build/UseUpdates.types.d.ts.map +1 -1
  30. package/build/UseUpdates.types.js +4 -0
  31. package/build/UseUpdates.types.js.map +1 -1
  32. package/build/UseUpdatesUtils.d.ts +2 -1
  33. package/build/UseUpdatesUtils.d.ts.map +1 -1
  34. package/build/UseUpdatesUtils.js +15 -7
  35. package/build/UseUpdatesUtils.js.map +1 -1
  36. package/build/statemachine/UpdatesStateMachine.d.ts +1 -1
  37. package/build/statemachine/UpdatesStateMachine.js.map +1 -1
  38. package/e2e/fixtures/App-apitest.tsx +12 -7
  39. package/e2e/fixtures/App.tsx +5 -5
  40. package/e2e/fixtures/Updates.e2e.ts +8 -1
  41. package/e2e/fixtures/project_files/eas-hooks/eas-build-on-success.sh +7 -1
  42. package/e2e/fixtures/project_files/eas-hooks/eas-build-pre-install.sh +0 -3
  43. package/e2e/setup/create-eas-project-tv.js +30 -0
  44. package/e2e/setup/create-eas-project.js +1 -1
  45. package/e2e/setup/create-updates-test.js +0 -1
  46. package/e2e/setup/project.js +40 -12
  47. package/expo-updates-gradle-plugin/src/main/kotlin/expo/modules/updates/ExpoUpdatesPlugin.kt +7 -2
  48. package/ios/EXUpdates/ASN1Decoder/ASN1Decoder.swift +272 -0
  49. package/ios/EXUpdates/ASN1Decoder/ASN1DistinguishedNames.swift +114 -0
  50. package/ios/EXUpdates/ASN1Decoder/ASN1Encoder.swift +60 -0
  51. package/ios/EXUpdates/ASN1Decoder/ASN1Identifier.swift +102 -0
  52. package/ios/EXUpdates/ASN1Decoder/ASN1Object.swift +107 -0
  53. package/ios/EXUpdates/ASN1Decoder/OID.swift +97 -0
  54. package/ios/EXUpdates/ASN1Decoder/PKCS7.swift +98 -0
  55. package/ios/EXUpdates/ASN1Decoder/PKCS7_AppleReceipt.swift +210 -0
  56. package/ios/EXUpdates/ASN1Decoder/PKCS7_Signature.swift +108 -0
  57. package/ios/EXUpdates/ASN1Decoder/README.md +161 -0
  58. package/ios/EXUpdates/ASN1Decoder/X509Certificate.swift +361 -0
  59. package/ios/EXUpdates/ASN1Decoder/X509Extension.swift +71 -0
  60. package/ios/EXUpdates/ASN1Decoder/X509ExtensionAltName.swift +86 -0
  61. package/ios/EXUpdates/ASN1Decoder/X509ExtensionClasses.swift +183 -0
  62. package/ios/EXUpdates/ASN1Decoder/X509PublicKey.swift +77 -0
  63. package/ios/EXUpdates/AppController.swift +3 -3
  64. package/ios/EXUpdates/AppLoader/AppLoader.swift +20 -20
  65. package/ios/EXUpdates/AppLoader/AppLoaderTask.swift +39 -9
  66. package/ios/EXUpdates/AppLoader/Crypto.swift +0 -1
  67. package/ios/EXUpdates/AppLoader/EmbeddedAppLoader.swift +1 -1
  68. package/ios/EXUpdates/AppLoader/FileDownloader.swift +9 -9
  69. package/ios/EXUpdates/AppLoader/RemoteAppLoader.swift +4 -4
  70. package/ios/EXUpdates/AppLoader/ResponseHeaderData.swift +3 -3
  71. package/ios/EXUpdates/AppLoader/UpdateResponse.swift +15 -10
  72. package/ios/EXUpdates/CodeSigning/CertificateChain.swift +0 -1
  73. package/ios/EXUpdates/CodeSigning/CodeSigningConfiguration.swift +0 -1
  74. package/ios/EXUpdates/Database/Migrations/UpdatesDatabaseMigration9To10.swift +51 -0
  75. package/ios/EXUpdates/Database/Migrations/UpdatesDatabaseMigrationRegistry.swift +2 -1
  76. package/ios/EXUpdates/Database/UpdatesDatabase.swift +3 -7
  77. package/ios/EXUpdates/Database/UpdatesDatabaseInitialization.swift +9 -2
  78. package/ios/EXUpdates/ErrorRecovery.swift +2 -2
  79. package/ios/EXUpdates/Logging/UpdatesLogReader.swift +0 -1
  80. package/ios/EXUpdates/Update/BareUpdate.swift +2 -2
  81. package/ios/EXUpdates/Update/LegacyUpdate.swift +2 -2
  82. package/ios/EXUpdates/Update/NewUpdate.swift +2 -2
  83. package/ios/EXUpdates/Update/Update.swift +1 -1
  84. package/ios/EXUpdates/UpdatesConfig.swift +1 -1
  85. package/ios/EXUpdates/UpdatesModule.swift +13 -2
  86. package/ios/EXUpdates/UpdatesStateMachine.swift +42 -13
  87. package/ios/EXUpdates/UpdatesUtils.swift +62 -10
  88. package/ios/EXUpdates.podspec +1 -2
  89. package/ios/Tests/DatabaseInitializationSpec.swift +120 -3
  90. package/ios/Tests/UpdatesStateMachineSpec.swift +3 -3
  91. package/package.json +8 -8
  92. package/plugin/build/withUpdates.d.ts +1 -3
  93. package/plugin/build/withUpdates.js +3 -6
  94. package/plugin/src/withUpdates.ts +3 -7
  95. package/scripts/createManifest.js +9 -3
  96. package/src/Updates.ts +24 -14
  97. package/src/Updates.types.ts +68 -22
  98. package/src/UpdatesEmitter.ts +9 -22
  99. package/src/UseUpdates.types.ts +34 -2
  100. package/src/UseUpdatesUtils.ts +18 -3
  101. package/src/statemachine/UpdatesStateMachine.ts +1 -1
  102. package/e2e/fixtures/project_files/xcode.env.local +0 -4
package/CHANGELOG.md CHANGED
@@ -10,6 +10,43 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.21.0 — 2023-09-15
14
+
15
+ ### 🎉 New features
16
+
17
+ - Added support for Apple tvOS. ([#24329](https://github.com/expo/expo/pull/24329) by [@douglowder](https://github.com/douglowder))
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - Fix updates enabled defaulting on iOS. ([#24327](https://github.com/expo/expo/pull/24327) by [@wschurman](https://github.com/wschurman))
22
+ - [Android] Make scopekey only required when getting database entity. ([#24466](https://github.com/expo/expo/pull/24466) by [@wschurman](https://github.com/wschurman))
23
+
24
+ ### 💡 Others
25
+
26
+ - Update E2E test. ([#24272](https://github.com/expo/expo/pull/24272) by [@EvanBacon](https://github.com/EvanBacon))
27
+ - [iOS] Disable packager and bundle JS when `EX_UPDATES_NATIVE_DEBUG` set. ([#24366](https://github.com/expo/expo/pull/24366) by [@douglowder](https://github.com/douglowder))
28
+ - Add Apple TV to Updates E2E (build only). ([#24411](https://github.com/expo/expo/pull/24411) by [@douglowder](https://github.com/douglowder))
29
+
30
+ ## 0.20.0 — 2023-09-04
31
+
32
+ ### 🛠 Breaking changes
33
+
34
+ - Change source of truth for constants types. ([#24049](https://github.com/expo/expo/pull/24049) by [@wschurman](https://github.com/wschurman))
35
+ - Remove classic manifest types and classic updates. ([#24053](https://github.com/expo/expo/pull/24053), [#24066](https://github.com/expo/expo/pull/24066) by [@wschurman](https://github.com/wschurman))
36
+
37
+ ### 🎉 New features
38
+
39
+ - Added support for React Native 0.73. ([#24018](https://github.com/expo/expo/pull/24018) by [@kudo](https://github.com/kudo))
40
+ - Add rollback support to useUpdates(). ([#24071](https://github.com/expo/expo/pull/24071) by [@douglowder](https://github.com/douglowder))
41
+
42
+ ### 🐛 Bug fixes
43
+
44
+ - [Android] completely fix `node` execution on Windows ([#24116](https://github.com/expo/expo/pull/24116) by [@weykon](https://github.com/weykon))
45
+ - [Android] Fixed the `node` execution on Windows. ([#23983](https://github.com/expo/expo/pull/23983) by [@kudo](https://github.com/kudo))
46
+ - Bare update manifest non-nullability parity. ([#23166](https://github.com/expo/expo/pull/23166) by [@wschurman](https://github.com/wschurman))
47
+ - Support importing assets from out of the project root when working in monorepos. ([#24090](https://github.com/expo/expo/pull/24090) by [@EvanBacon](https://github.com/EvanBacon))
48
+ - Prevent failed updates from passing checkForUpdateAsync(). ([#24112](https://github.com/expo/expo/pull/24112) by [@douglowder](https://github.com/douglowder))
49
+
13
50
  ## 0.19.1 — 2023-08-02
14
51
 
15
52
  ### 💡 Others
@@ -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.19.1'
7
+ version = '0.21.0'
8
8
 
9
9
  // Utility method to derive boolean values from the environment or from Java properties,
10
10
  // and return them as strings to be used in BuildConfig fields
@@ -81,13 +81,16 @@ afterEvaluate {
81
81
  android {
82
82
  compileSdkVersion safeExtGet("compileSdkVersion", 33)
83
83
 
84
- compileOptions {
85
- sourceCompatibility JavaVersion.VERSION_11
86
- targetCompatibility JavaVersion.VERSION_11
87
- }
84
+ def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
85
+ if (agpVersion.tokenize('.')[0].toInteger() < 8) {
86
+ compileOptions {
87
+ sourceCompatibility JavaVersion.VERSION_11
88
+ targetCompatibility JavaVersion.VERSION_11
89
+ }
88
90
 
89
- kotlinOptions {
90
- jvmTarget = JavaVersion.VERSION_11.majorVersion
91
+ kotlinOptions {
92
+ jvmTarget = JavaVersion.VERSION_11.majorVersion
93
+ }
91
94
  }
92
95
 
93
96
  namespace "expo.modules.updates"
@@ -95,7 +98,7 @@ android {
95
98
  minSdkVersion safeExtGet("minSdkVersion", 21)
96
99
  targetSdkVersion safeExtGet("targetSdkVersion", 33)
97
100
  versionCode 31
98
- versionName '0.19.1'
101
+ versionName '0.21.0'
99
102
  consumerProguardFiles("proguard-rules.pro")
100
103
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
101
104
 
@@ -289,7 +289,7 @@ class UpdatesController private constructor(
289
289
  val event = when (result) {
290
290
  is LoaderTask.RemoteCheckResult.NoUpdateAvailable -> UpdatesStateEvent.CheckCompleteUnavailable()
291
291
  is LoaderTask.RemoteCheckResult.UpdateAvailable -> UpdatesStateEvent.CheckCompleteWithUpdate(result.manifest)
292
- is LoaderTask.RemoteCheckResult.RollBackToEmbedded -> UpdatesStateEvent.CheckCompleteWithRollback()
292
+ is LoaderTask.RemoteCheckResult.RollBackToEmbedded -> UpdatesStateEvent.CheckCompleteWithRollback(result.commitTime)
293
293
  }
294
294
  stateMachine.processEvent(event)
295
295
  }
@@ -375,7 +375,7 @@ class UpdatesController private constructor(
375
375
  params.putString("manifestString", update.manifest.toString())
376
376
  sendLegacyUpdateEventToJS(UPDATE_AVAILABLE_EVENT, params)
377
377
  stateMachine.processEvent(
378
- UpdatesStateEvent.DownloadCompleteWithUpdate(update.manifest!!)
378
+ UpdatesStateEvent.DownloadCompleteWithUpdate(update.manifest)
379
379
  )
380
380
  }
381
381
  RemoteUpdateStatus.NO_UPDATE_AVAILABLE -> {
@@ -154,7 +154,7 @@ class UpdatesDevLauncherController : UpdatesInterface {
154
154
  controller.setLauncher(launcher)
155
155
  callback.onSuccess(object : UpdatesInterface.Update {
156
156
  override fun getManifest(): JSONObject {
157
- return launcher.launchedUpdate!!.manifest!!
157
+ return launcher.launchedUpdate!!.manifest
158
158
  }
159
159
 
160
160
  override fun getLaunchAssetPath(): String {
@@ -27,7 +27,6 @@ import expo.modules.updates.manifest.UpdateManifest
27
27
 
28
28
  // these unused imports must stay because of versioning
29
29
  /* ktlint-disable no-unused-imports */
30
- import expo.modules.updates.UpdatesConfiguration
31
30
 
32
31
  /* ktlint-enable no-unused-imports */
33
32
 
@@ -80,8 +79,7 @@ class UpdatesModule(
80
79
  if (launchedUpdate != null) {
81
80
  constants["updateId"] = launchedUpdate.id.toString()
82
81
  constants["commitTime"] = launchedUpdate.commitTime.time
83
- constants["manifestString"] =
84
- if (launchedUpdate.manifest != null) launchedUpdate.manifest.toString() else "{}"
82
+ constants["manifestString"] = launchedUpdate.manifest.toString()
85
83
  }
86
84
  val localAssetFiles = updatesServiceLocal.localAssetFiles
87
85
  if (localAssetFiles != null) {
@@ -138,7 +136,7 @@ class UpdatesModule(
138
136
  }
139
137
  }
140
138
 
141
- // Used internally by @expo/use-updates useUpdates() to get its initial state
139
+ // Used internally by useUpdates() to get its initial state
142
140
  @ExpoMethod
143
141
  fun getNativeStateMachineContextAsync(promise: Promise) {
144
142
  try {
@@ -200,13 +198,23 @@ class UpdatesModule(
200
198
  if (updateDirective != null) {
201
199
  if (updateDirective is UpdateDirective.RollBackToEmbeddedUpdateDirective) {
202
200
  if (!updatesServiceLocal.configuration.hasEmbeddedUpdate) {
203
- promise.resolveWithCheckForUpdateAsyncResult(CheckForUpdateAsyncResult.NoUpdateAvailable(), updatesServiceLocal)
201
+ promise.resolveWithCheckForUpdateAsyncResult(
202
+ CheckForUpdateAsyncResult.NoUpdateAvailable(
203
+ LoaderTask.RemoteCheckResultNotAvailableReason.ROLLBACK_NO_EMBEDDED
204
+ ),
205
+ updatesServiceLocal
206
+ )
204
207
  return
205
208
  }
206
209
 
207
210
  val embeddedUpdate = EmbeddedManifest.get(context, updatesServiceLocal.configuration)!!.updateEntity
208
211
  if (embeddedUpdate == null) {
209
- promise.resolveWithCheckForUpdateAsyncResult(CheckForUpdateAsyncResult.NoUpdateAvailable(), updatesServiceLocal)
212
+ promise.resolveWithCheckForUpdateAsyncResult(
213
+ CheckForUpdateAsyncResult.NoUpdateAvailable(
214
+ LoaderTask.RemoteCheckResultNotAvailableReason.ROLLBACK_NO_EMBEDDED
215
+ ),
216
+ updatesServiceLocal
217
+ )
210
218
  return
211
219
  }
212
220
 
@@ -217,17 +225,27 @@ class UpdatesModule(
217
225
  updateResponse.responseHeaderData?.manifestFilters
218
226
  )
219
227
  ) {
220
- promise.resolveWithCheckForUpdateAsyncResult(CheckForUpdateAsyncResult.NoUpdateAvailable(), updatesServiceLocal)
228
+ promise.resolveWithCheckForUpdateAsyncResult(
229
+ CheckForUpdateAsyncResult.NoUpdateAvailable(
230
+ LoaderTask.RemoteCheckResultNotAvailableReason.ROLLBACK_REJECTED_BY_SELECTION_POLICY
231
+ ),
232
+ updatesServiceLocal
233
+ )
221
234
  return
222
235
  }
223
236
 
224
- promise.resolveWithCheckForUpdateAsyncResult(CheckForUpdateAsyncResult.RollBackToEmbedded(), updatesServiceLocal)
237
+ promise.resolveWithCheckForUpdateAsyncResult(CheckForUpdateAsyncResult.RollBackToEmbedded(updateDirective.commitTime), updatesServiceLocal)
225
238
  return
226
239
  }
227
240
  }
228
241
 
229
242
  if (updateManifest == null) {
230
- promise.resolveWithCheckForUpdateAsyncResult(CheckForUpdateAsyncResult.NoUpdateAvailable(), updatesServiceLocal)
243
+ promise.resolveWithCheckForUpdateAsyncResult(
244
+ CheckForUpdateAsyncResult.NoUpdateAvailable(
245
+ LoaderTask.RemoteCheckResultNotAvailableReason.NO_UPDATE_AVAILABLE_ON_SERVER
246
+ ),
247
+ updatesServiceLocal
248
+ )
231
249
  return
232
250
  }
233
251
 
@@ -238,15 +256,43 @@ class UpdatesModule(
238
256
  return
239
257
  }
240
258
 
259
+ var shouldLaunch = false
260
+ var failedPreviously = false
241
261
  if (updatesServiceLocal.selectionPolicy.shouldLoadNewUpdate(
242
262
  updateManifest.updateEntity,
243
263
  launchedUpdate,
244
264
  updateResponse.responseHeaderData?.manifestFilters
245
265
  )
246
266
  ) {
267
+ // If "update" has failed to launch previously, then
268
+ // "launchedUpdate" will be an earlier update, and the test above
269
+ // will return true (incorrectly).
270
+ // We check to see if the new update is already in the DB, and if so,
271
+ // only allow the update if it has had no launch failures.
272
+ shouldLaunch = true
273
+ updateManifest.updateEntity?.let { updateEntity ->
274
+ val storedUpdateEntity = updatesServiceLocal.databaseHolder.database.updateDao().loadUpdateWithId(
275
+ updateEntity.id
276
+ )
277
+ updatesServiceLocal.databaseHolder.releaseDatabase()
278
+ storedUpdateEntity?.let { storedUpdateEntity ->
279
+ shouldLaunch = storedUpdateEntity.failedLaunchCount == 0
280
+ logger.info("Stored update found: ID = ${updateEntity.id}, failureCount = ${storedUpdateEntity.failedLaunchCount}")
281
+ failedPreviously = !shouldLaunch
282
+ }
283
+ }
284
+ }
285
+ if (shouldLaunch) {
247
286
  promise.resolveWithCheckForUpdateAsyncResult(CheckForUpdateAsyncResult.UpdateAvailable(updateManifest), updatesServiceLocal)
248
287
  } else {
249
- promise.resolveWithCheckForUpdateAsyncResult(CheckForUpdateAsyncResult.NoUpdateAvailable(), updatesServiceLocal)
288
+ val reason = when (failedPreviously) {
289
+ true -> LoaderTask.RemoteCheckResultNotAvailableReason.UPDATE_PREVIOUSLY_FAILED
290
+ else -> LoaderTask.RemoteCheckResultNotAvailableReason.UPDATE_REJECTED_BY_SELECTION_POLICY
291
+ }
292
+ promise.resolveWithCheckForUpdateAsyncResult(
293
+ CheckForUpdateAsyncResult.NoUpdateAvailable(reason),
294
+ updatesServiceLocal
295
+ )
250
296
  }
251
297
  }
252
298
  }
@@ -267,9 +313,9 @@ class UpdatesModule(
267
313
  ROLL_BACK_TO_EMBEDDED
268
314
  }
269
315
 
270
- class NoUpdateAvailable : CheckForUpdateAsyncResult(Status.NO_UPDATE_AVAILABLE)
316
+ class NoUpdateAvailable(val reason: LoaderTask.RemoteCheckResultNotAvailableReason) : CheckForUpdateAsyncResult(Status.NO_UPDATE_AVAILABLE)
271
317
  class UpdateAvailable(val updateManifest: UpdateManifest) : CheckForUpdateAsyncResult(Status.UPDATE_AVAILABLE)
272
- class RollBackToEmbedded : CheckForUpdateAsyncResult(Status.ROLL_BACK_TO_EMBEDDED)
318
+ class RollBackToEmbedded(val commitTime: Date) : CheckForUpdateAsyncResult(Status.ROLL_BACK_TO_EMBEDDED)
273
319
  }
274
320
 
275
321
  private fun Promise.resolveWithCheckForUpdateAsyncResult(checkForUpdateAsyncResult: CheckForUpdateAsyncResult, updatesServiceLocal: UpdatesInterface) {
@@ -279,6 +325,7 @@ class UpdatesModule(
279
325
  is CheckForUpdateAsyncResult.NoUpdateAvailable -> {
280
326
  putBoolean("isRollBackToEmbedded", false)
281
327
  putBoolean("isAvailable", false)
328
+ putString("reason", checkForUpdateAsyncResult.reason.value)
282
329
  }
283
330
 
284
331
  is CheckForUpdateAsyncResult.RollBackToEmbedded -> {
@@ -297,7 +344,7 @@ class UpdatesModule(
297
344
  updatesServiceLocal.stateMachine?.processEvent(
298
345
  when (checkForUpdateAsyncResult) {
299
346
  is CheckForUpdateAsyncResult.NoUpdateAvailable -> UpdatesStateEvent.CheckCompleteUnavailable()
300
- is CheckForUpdateAsyncResult.RollBackToEmbedded -> UpdatesStateEvent.CheckCompleteWithRollback()
347
+ is CheckForUpdateAsyncResult.RollBackToEmbedded -> UpdatesStateEvent.CheckCompleteWithRollback(checkForUpdateAsyncResult.commitTime)
301
348
  is CheckForUpdateAsyncResult.UpdateAvailable -> UpdatesStateEvent.CheckCompleteWithUpdate(
302
349
  checkForUpdateAsyncResult.updateManifest.manifest.getRawJson()
303
350
  )
@@ -418,7 +465,7 @@ class UpdatesModule(
418
465
  }
419
466
  )
420
467
  updatesServiceLocal.stateMachine?.processEvent(
421
- UpdatesStateEvent.DownloadCompleteWithUpdate(updateEntity.manifest!!)
468
+ UpdatesStateEvent.DownloadCompleteWithUpdate(updateEntity.manifest)
422
469
  )
423
470
  }
424
471
  }
@@ -44,7 +44,7 @@ import java.util.*
44
44
  @Database(
45
45
  entities = [UpdateEntity::class, UpdateAssetEntity::class, AssetEntity::class, JSONDataEntity::class],
46
46
  exportSchema = false,
47
- version = 11
47
+ version = 12
48
48
  )
49
49
  @TypeConverters(Converters::class)
50
50
  abstract class UpdatesDatabase : RoomDatabase() {
@@ -69,6 +69,7 @@ abstract class UpdatesDatabase : RoomDatabase() {
69
69
  .addMigrations(MIGRATION_8_9)
70
70
  .addMigrations(MIGRATION_9_10)
71
71
  .addMigrations(MIGRATION_10_11)
72
+ .addMigrations(MIGRATION_11_12)
72
73
  .fallbackToDestructiveMigration()
73
74
  .allowMainThreadQueries()
74
75
  .build()
@@ -199,5 +200,25 @@ abstract class UpdatesDatabase : RoomDatabase() {
199
200
  }
200
201
  }
201
202
  }
203
+
204
+ /**
205
+ * Change the `updates.manifest` column to be non-null
206
+ */
207
+ val MIGRATION_11_12: Migration = object : Migration(11, 12) {
208
+ override fun migrate(database: SupportSQLiteDatabase) {
209
+ database.runInTransactionWithForeignKeysOff {
210
+ execSQL("CREATE TABLE `new_updates` (`id` BLOB NOT NULL, `scope_key` TEXT NOT NULL, `commit_time` INTEGER NOT NULL, `runtime_version` TEXT NOT NULL, `launch_asset_id` INTEGER, `manifest` TEXT NOT NULL, `status` INTEGER NOT NULL, `keep` INTEGER NOT NULL, `last_accessed` INTEGER NOT NULL, `successful_launch_count` INTEGER NOT NULL DEFAULT 0, `failed_launch_count` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`), FOREIGN KEY(`launch_asset_id`) REFERENCES `assets`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
211
+
212
+ execSQL(
213
+ "INSERT INTO `new_updates` (`id`, `scope_key`, `commit_time`, `runtime_version`, `launch_asset_id`, `manifest`, `status`, `keep`, `last_accessed`, `successful_launch_count`, `failed_launch_count`)" +
214
+ " SELECT `id`, `scope_key`, `commit_time`, `runtime_version`, `launch_asset_id`, `manifest`, `status`, `keep`, `last_accessed`, `successful_launch_count`, `failed_launch_count` FROM `updates` WHERE `manifest` IS NOT NULL"
215
+ )
216
+ execSQL("DROP TABLE `updates`")
217
+ execSQL("ALTER TABLE `new_updates` RENAME TO `updates`")
218
+ execSQL("CREATE INDEX `index_updates_launch_asset_id` ON `updates` (`launch_asset_id`)")
219
+ execSQL("CREATE UNIQUE INDEX `index_updates_scope_key_commit_time` ON `updates` (`scope_key`, `commit_time`)")
220
+ }
221
+ }
222
+ }
202
223
  }
203
224
  }
@@ -36,14 +36,12 @@ class UpdateEntity(
36
36
  @field:ColumnInfo(typeAffinity = ColumnInfo.BLOB) @field:PrimaryKey var id: UUID,
37
37
  @field:ColumnInfo(name = "commit_time") var commitTime: Date,
38
38
  @field:ColumnInfo(name = "runtime_version") var runtimeVersion: String,
39
- @field:ColumnInfo(name = "scope_key") var scopeKey: String
39
+ @field:ColumnInfo(name = "scope_key") var scopeKey: String,
40
+ @field:ColumnInfo(name = "manifest") var manifest: JSONObject,
40
41
  ) {
41
42
  @ColumnInfo(name = "launch_asset_id")
42
43
  var launchAssetId: Long? = null
43
44
 
44
- @ColumnInfo(name = "manifest")
45
- var manifest: JSONObject? = null
46
-
47
45
  var status = UpdateStatus.PENDING
48
46
 
49
47
  var keep = false
@@ -21,6 +21,7 @@ import expo.modules.updates.manifest.UpdateManifest
21
21
  import expo.modules.updates.selectionpolicy.SelectionPolicy
22
22
  import org.json.JSONObject
23
23
  import java.io.File
24
+ import java.util.Date
24
25
 
25
26
  /**
26
27
  * Controlling class that handles the complex logic that needs to happen each time the app is cold
@@ -53,6 +54,32 @@ class LoaderTask(
53
54
  ERROR, NO_UPDATE_AVAILABLE, UPDATE_AVAILABLE
54
55
  }
55
56
 
57
+ enum class RemoteCheckResultNotAvailableReason(val value: String) {
58
+ /**
59
+ * No update manifest or rollback directive received from the update server.
60
+ */
61
+ NO_UPDATE_AVAILABLE_ON_SERVER("noUpdateAvailableOnServer"),
62
+ /**
63
+ * An update manifest was received from the update server, but the update is not launchable,
64
+ * or does not pass the configured selection policy.
65
+ */
66
+ UPDATE_REJECTED_BY_SELECTION_POLICY("updateRejectedBySelectionPolicy"),
67
+ /**
68
+ * An update manifest was received from the update server, but the update has been previously
69
+ * launched on this device and never successfully launched.
70
+ */
71
+ UPDATE_PREVIOUSLY_FAILED("updatePreviouslyFailed"),
72
+ /**
73
+ * A rollback directive was received from the update server, but the directive does not pass
74
+ * the configured selection policy.
75
+ */
76
+ ROLLBACK_REJECTED_BY_SELECTION_POLICY("rollbackRejectedBySelectionPolicy"),
77
+ /**
78
+ * A rollback directive was received from the update server, but this app has no embedded update.
79
+ */
80
+ ROLLBACK_NO_EMBEDDED("rollbackNoEmbeddedConfiguration"),
81
+ }
82
+
56
83
  sealed class RemoteCheckResult(private val status: Status) {
57
84
  private enum class Status {
58
85
  NO_UPDATE_AVAILABLE,
@@ -60,9 +87,9 @@ class LoaderTask(
60
87
  ROLL_BACK_TO_EMBEDDED
61
88
  }
62
89
 
63
- class NoUpdateAvailable : RemoteCheckResult(Status.NO_UPDATE_AVAILABLE)
90
+ class NoUpdateAvailable(val reason: RemoteCheckResultNotAvailableReason) : RemoteCheckResult(Status.NO_UPDATE_AVAILABLE)
64
91
  class UpdateAvailable(val manifest: JSONObject) : RemoteCheckResult(Status.UPDATE_AVAILABLE)
65
- class RollBackToEmbedded : RemoteCheckResult(Status.ROLL_BACK_TO_EMBEDDED)
92
+ class RollBackToEmbedded(val commitTime: Date) : RemoteCheckResult(Status.ROLL_BACK_TO_EMBEDDED)
66
93
  }
67
94
 
68
95
  interface LoaderTaskCallback {
@@ -335,12 +362,12 @@ class LoaderTask(
335
362
  return when (updateDirective) {
336
363
  is UpdateDirective.RollBackToEmbeddedUpdateDirective -> {
337
364
  isUpToDate = true
338
- callback.onRemoteCheckForUpdateFinished(RemoteCheckResult.RollBackToEmbedded())
365
+ callback.onRemoteCheckForUpdateFinished(RemoteCheckResult.RollBackToEmbedded(updateDirective.commitTime))
339
366
  Loader.OnUpdateResponseLoadedResult(shouldDownloadManifestIfPresentInResponse = false)
340
367
  }
341
368
  is UpdateDirective.NoUpdateAvailableUpdateDirective -> {
342
369
  isUpToDate = true
343
- callback.onRemoteCheckForUpdateFinished(RemoteCheckResult.NoUpdateAvailable())
370
+ callback.onRemoteCheckForUpdateFinished(RemoteCheckResult.NoUpdateAvailable(RemoteCheckResultNotAvailableReason.NO_UPDATE_AVAILABLE_ON_SERVER))
344
371
  Loader.OnUpdateResponseLoadedResult(shouldDownloadManifestIfPresentInResponse = false)
345
372
  }
346
373
  }
@@ -349,7 +376,7 @@ class LoaderTask(
349
376
  val updateManifest = updateResponse.manifestUpdateResponsePart?.updateManifest
350
377
  if (updateManifest == null) {
351
378
  isUpToDate = true
352
- callback.onRemoteCheckForUpdateFinished(RemoteCheckResult.NoUpdateAvailable())
379
+ callback.onRemoteCheckForUpdateFinished(RemoteCheckResult.NoUpdateAvailable(RemoteCheckResultNotAvailableReason.NO_UPDATE_AVAILABLE_ON_SERVER))
353
380
  return Loader.OnUpdateResponseLoadedResult(shouldDownloadManifestIfPresentInResponse = false)
354
381
  }
355
382
 
@@ -366,7 +393,11 @@ class LoaderTask(
366
393
  Loader.OnUpdateResponseLoadedResult(shouldDownloadManifestIfPresentInResponse = true)
367
394
  } else {
368
395
  isUpToDate = true
369
- callback.onRemoteCheckForUpdateFinished(RemoteCheckResult.NoUpdateAvailable())
396
+ callback.onRemoteCheckForUpdateFinished(
397
+ RemoteCheckResult.NoUpdateAvailable(
398
+ RemoteCheckResultNotAvailableReason.UPDATE_REJECTED_BY_SELECTION_POLICY
399
+ )
400
+ )
370
401
  Loader.OnUpdateResponseLoadedResult(shouldDownloadManifestIfPresentInResponse = false)
371
402
  }
372
403
  }
@@ -22,13 +22,13 @@ import java.util.*
22
22
  class BareUpdateManifest private constructor(
23
23
  override val manifest: BareManifest,
24
24
  private val mId: UUID,
25
- private val mScopeKey: String,
25
+ private val mScopeKey: String?,
26
26
  private val mCommitTime: Date,
27
27
  private val mRuntimeVersion: String,
28
28
  private val mAssets: JSONArray?
29
29
  ) : UpdateManifest {
30
30
  override val updateEntity: UpdateEntity by lazy {
31
- UpdateEntity(mId, mCommitTime, mRuntimeVersion, mScopeKey).apply {
31
+ UpdateEntity(mId, mCommitTime, mRuntimeVersion, mScopeKey!!, this@BareUpdateManifest.manifest.getRawJson()).apply {
32
32
  status = UpdateStatus.EMBEDDED
33
33
  }
34
34
  }
@@ -94,7 +94,7 @@ class BareUpdateManifest private constructor(
94
94
  return BareUpdateManifest(
95
95
  manifest,
96
96
  id,
97
- configuration.scopeKey!!,
97
+ configuration.scopeKey,
98
98
  commitTime,
99
99
  runtimeVersion,
100
100
  assets
@@ -34,8 +34,7 @@ class LegacyUpdateManifest private constructor(
34
34
  private val mAssets: JSONArray?
35
35
  ) : UpdateManifest {
36
36
  override val updateEntity: UpdateEntity by lazy {
37
- UpdateEntity(mId, mCommitTime, mRuntimeVersion, mScopeKey).apply {
38
- manifest = this@LegacyUpdateManifest.manifest.getRawJson()
37
+ UpdateEntity(mId, mCommitTime, mRuntimeVersion, mScopeKey, this@LegacyUpdateManifest.manifest.getRawJson()).apply {
39
38
  if (isDevelopmentMode) {
40
39
  status = UpdateStatus.DEVELOPMENT
41
40
  }
@@ -31,9 +31,7 @@ class NewUpdateManifest private constructor(
31
31
  private val mExtensions: JSONObject?
32
32
  ) : UpdateManifest {
33
33
  override val updateEntity: UpdateEntity by lazy {
34
- UpdateEntity(mId, mCommitTime, mRuntimeVersion, mScopeKey).apply {
35
- manifest = this@NewUpdateManifest.manifest.getRawJson()
36
- }
34
+ UpdateEntity(mId, mCommitTime, mRuntimeVersion, mScopeKey, this@NewUpdateManifest.manifest.getRawJson())
37
35
  }
38
36
 
39
37
  private val assetHeaders: Map<String, JSONObject> by lazy {
@@ -13,13 +13,12 @@ object SelectionPolicies {
13
13
  val TAG = SelectionPolicies::class.java.simpleName
14
14
 
15
15
  fun matchesFilters(update: UpdateEntity, manifestFilters: JSONObject?): Boolean {
16
- val rawManifest = update.manifest
17
- if (manifestFilters == null || rawManifest == null) {
16
+ if (manifestFilters == null) {
18
17
  // empty matches all
19
18
  return true
20
19
  }
21
20
 
22
- val manifest = Manifest.fromManifestJson(rawManifest)
21
+ val manifest = Manifest.fromManifestJson(update.manifest)
23
22
  val metadata = manifest.getMetadata() ?: return true // empty matches all
24
23
 
25
24
  try {
@@ -14,12 +14,12 @@ The state machine context, with information intended to be consumed by applicati
14
14
  data class UpdatesStateContext(
15
15
  val isUpdateAvailable: Boolean = false,
16
16
  val isUpdatePending: Boolean = false,
17
- val isRollback: Boolean = false,
18
17
  val isChecking: Boolean = false,
19
18
  val isDownloading: Boolean = false,
20
19
  val isRestarting: Boolean = false,
21
20
  val latestManifest: JSONObject? = null,
22
21
  val downloadedManifest: JSONObject? = null,
22
+ val rollback: UpdatesStateContextRollback? = null,
23
23
  val checkError: UpdatesStateError? = null,
24
24
  val downloadError: UpdatesStateError? = null,
25
25
  val lastCheckForUpdateTime: Date? = null,
@@ -30,7 +30,6 @@ data class UpdatesStateContext(
30
30
  val map: MutableMap<String, Any> = mutableMapOf(
31
31
  "isUpdateAvailable" to isUpdateAvailable,
32
32
  "isUpdatePending" to isUpdatePending,
33
- "isRollback" to isRollback,
34
33
  "isChecking" to isChecking,
35
34
  "isDownloading" to isDownloading,
36
35
  "isRestarting" to isRestarting
@@ -41,6 +40,9 @@ data class UpdatesStateContext(
41
40
  if (downloadedManifest != null) {
42
41
  map["downloadedManifest"] = downloadedManifest
43
42
  }
43
+ if (rollback != null) {
44
+ map["rollback"] = rollback.json
45
+ }
44
46
  if (checkError != null) {
45
47
  map["checkError"] = checkError.json
46
48
  }
@@ -71,7 +73,6 @@ data class UpdatesStateContext(
71
73
  return Bundle().apply {
72
74
  putBoolean("isUpdateAvailable", isUpdateAvailable)
73
75
  putBoolean("isUpdatePending", isUpdatePending)
74
- putBoolean("isRollback", isRollback)
75
76
  putBoolean("isChecking", isChecking)
76
77
  putBoolean("isDownloading", isDownloading)
77
78
  putBoolean("isRestarting", isRestarting)
@@ -81,6 +82,14 @@ data class UpdatesStateContext(
81
82
  if (downloadedManifest != null) {
82
83
  putString("downloadedManifestString", downloadedManifest.toString())
83
84
  }
85
+ if (rollback != null) {
86
+ putBundle(
87
+ "rollback",
88
+ Bundle().apply {
89
+ putString("commitTime", rollback.commitTimeString)
90
+ }
91
+ )
92
+ }
84
93
  if (checkError != null) {
85
94
  val errorMap = Bundle().apply {
86
95
  putString("message", checkError.message)
@@ -0,0 +1,14 @@
1
+ package expo.modules.updates.statemachine
2
+
3
+ import java.util.Date
4
+
5
+ data class UpdatesStateContextRollback(
6
+ val commitTime: Date
7
+ ) {
8
+
9
+ val commitTimeString: String
10
+ get() = UpdatesStateContext.DATE_FORMATTER.format(commitTime)
11
+
12
+ val json: Map<String, Any>
13
+ get() = mapOf("commitTime" to commitTimeString)
14
+ }
@@ -1,6 +1,7 @@
1
1
  package expo.modules.updates.statemachine
2
2
 
3
3
  import org.json.JSONObject
4
+ import java.util.Date
4
5
 
5
6
  /**
6
7
  Structure representing an event that can be sent to the machine.
@@ -20,9 +21,9 @@ sealed class UpdatesStateEvent(val type: UpdatesStateEventType) {
20
21
  return UpdatesStateError(errorMessage)
21
22
  }
22
23
  }
23
- class CheckCompleteUnavailable : UpdatesStateEvent(UpdatesStateEventType.CheckCompleteUnavailable)
24
+ class CheckCompleteUnavailable() : UpdatesStateEvent(UpdatesStateEventType.CheckCompleteUnavailable)
24
25
  class CheckCompleteWithUpdate(val manifest: JSONObject) : UpdatesStateEvent(UpdatesStateEventType.CheckCompleteAvailable)
25
- class CheckCompleteWithRollback : UpdatesStateEvent(UpdatesStateEventType.CheckCompleteAvailable)
26
+ class CheckCompleteWithRollback(val commitTime: Date) : UpdatesStateEvent(UpdatesStateEventType.CheckCompleteAvailable)
26
27
  class DownloadComplete : UpdatesStateEvent(UpdatesStateEventType.DownloadComplete)
27
28
  class DownloadCompleteWithUpdate(val manifest: JSONObject) : UpdatesStateEvent(UpdatesStateEventType.DownloadComplete)
28
29
  class DownloadCompleteWithRollback : UpdatesStateEvent(UpdatesStateEventType.DownloadComplete)
@@ -106,24 +106,24 @@ class UpdatesStateMachine(
106
106
  isChecking = false,
107
107
  checkError = null,
108
108
  latestManifest = null,
109
+ rollback = null,
109
110
  isUpdateAvailable = false,
110
- isRollback = false,
111
111
  lastCheckForUpdateTime = Date(),
112
112
  )
113
113
  is UpdatesStateEvent.CheckCompleteWithRollback -> context.copy(
114
114
  isChecking = false,
115
115
  checkError = null,
116
116
  latestManifest = null,
117
+ rollback = UpdatesStateContextRollback(event.commitTime),
117
118
  isUpdateAvailable = true,
118
- isRollback = true,
119
119
  lastCheckForUpdateTime = Date()
120
120
  )
121
121
  is UpdatesStateEvent.CheckCompleteWithUpdate -> context.copy(
122
122
  isChecking = false,
123
123
  checkError = null,
124
124
  latestManifest = event.manifest,
125
+ rollback = null,
125
126
  isUpdateAvailable = true,
126
- isRollback = false,
127
127
  lastCheckForUpdateTime = Date()
128
128
  )
129
129
  is UpdatesStateEvent.CheckError -> context.copy(
@@ -140,13 +140,14 @@ class UpdatesStateMachine(
140
140
  is UpdatesStateEvent.DownloadCompleteWithRollback -> context.copy(
141
141
  isDownloading = false,
142
142
  downloadError = null,
143
- isUpdatePending = true,
143
+ isUpdatePending = true
144
144
  )
145
145
  is UpdatesStateEvent.DownloadCompleteWithUpdate -> context.copy(
146
146
  isDownloading = false,
147
147
  downloadError = null,
148
148
  latestManifest = event.manifest,
149
149
  downloadedManifest = event.manifest,
150
+ rollback = null,
150
151
  isUpdatePending = true,
151
152
  isUpdateAvailable = true
152
153
  )