expo-updates 0.14.6 → 0.15.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 (118) hide show
  1. package/CHANGELOG.md +26 -6
  2. package/android/build.gradle +15 -2
  3. package/android/proguard-rules.pro +1 -1
  4. package/android/src/main/java/expo/modules/updates/UpdatesConfiguration.kt +11 -0
  5. package/android/src/main/java/expo/modules/updates/UpdatesController.kt +43 -1
  6. package/android/src/main/java/expo/modules/updates/UpdatesDevLauncherController.kt +17 -5
  7. package/android/src/main/java/expo/modules/updates/UpdatesInterface.kt +4 -0
  8. package/android/src/main/java/expo/modules/updates/UpdatesModule.kt +64 -0
  9. package/android/src/main/java/expo/modules/updates/UpdatesPackage.kt +9 -4
  10. package/android/src/main/java/expo/modules/updates/UpdatesService.kt +9 -2
  11. package/android/src/main/java/expo/modules/updates/UpdatesUtils.kt +3 -0
  12. package/android/src/main/java/expo/modules/updates/db/DatabaseHolder.kt +11 -0
  13. package/android/src/main/java/expo/modules/updates/db/Reaper.kt +6 -0
  14. package/android/src/main/java/expo/modules/updates/db/UpdatesDatabase.kt +25 -0
  15. package/android/src/main/java/expo/modules/updates/db/dao/AssetDao.kt +3 -0
  16. package/android/src/main/java/expo/modules/updates/db/dao/JSONDataDao.kt +3 -0
  17. package/android/src/main/java/expo/modules/updates/db/dao/UpdateDao.kt +3 -0
  18. package/android/src/main/java/expo/modules/updates/db/entity/AssetEntity.kt +8 -0
  19. package/android/src/main/java/expo/modules/updates/db/entity/JSONDataEntity.kt +10 -0
  20. package/android/src/main/java/expo/modules/updates/db/entity/UpdateAssetEntity.kt +7 -0
  21. package/android/src/main/java/expo/modules/updates/db/entity/UpdateEntity.kt +17 -0
  22. package/android/src/main/java/expo/modules/updates/errorrecovery/ErrorRecovery.kt +30 -9
  23. package/android/src/main/java/expo/modules/updates/errorrecovery/ErrorRecoveryDelegate.kt +4 -0
  24. package/android/src/main/java/expo/modules/updates/errorrecovery/ErrorRecoveryHandler.kt +54 -5
  25. package/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt +21 -0
  26. package/android/src/main/java/expo/modules/updates/launcher/Launcher.kt +25 -0
  27. package/android/src/main/java/expo/modules/updates/launcher/NoDatabaseLauncher.kt +8 -0
  28. package/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt +57 -34
  29. package/android/src/main/java/expo/modules/updates/loader/Loader.kt +7 -0
  30. package/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt +19 -0
  31. package/android/src/main/java/expo/modules/updates/logging/UpdatesErrorCode.kt +16 -0
  32. package/android/src/main/java/expo/modules/updates/logging/UpdatesLogEntry.kt +62 -0
  33. package/android/src/main/java/expo/modules/updates/logging/UpdatesLogReader.kt +60 -0
  34. package/android/src/main/java/expo/modules/updates/logging/UpdatesLogger.kt +165 -0
  35. package/android/src/main/java/expo/modules/updates/manifest/BareUpdateManifest.kt +6 -0
  36. package/android/src/main/java/expo/modules/updates/manifest/EmbeddedManifest.kt +3 -0
  37. package/android/src/main/java/expo/modules/updates/manifest/LegacyUpdateManifest.kt +8 -0
  38. package/android/src/main/java/expo/modules/updates/manifest/ManifestFactory.kt +3 -0
  39. package/android/src/main/java/expo/modules/updates/manifest/ManifestMetadata.kt +4 -0
  40. package/android/src/main/java/expo/modules/updates/manifest/NewUpdateManifest.kt +4 -0
  41. package/android/src/main/java/expo/modules/updates/manifest/UpdateManifest.kt +4 -0
  42. package/android/src/main/java/expo/modules/updates/selectionpolicy/LauncherSelectionPolicyFilterAware.kt +3 -2
  43. package/android/src/main/java/expo/modules/updates/selectionpolicy/LoaderSelectionPolicyFilterAware.kt +2 -0
  44. package/android/src/main/java/expo/modules/updates/selectionpolicy/ReaperSelectionPolicyDevelopmentClient.kt +2 -0
  45. package/android/src/main/java/expo/modules/updates/selectionpolicy/ReaperSelectionPolicyFilterAware.kt +2 -0
  46. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicies.kt +3 -0
  47. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicy.kt +36 -0
  48. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicyFactory.kt +4 -0
  49. package/build/Updates.d.ts +24 -2
  50. package/build/Updates.d.ts.map +1 -1
  51. package/build/Updates.js +33 -1
  52. package/build/Updates.js.map +1 -1
  53. package/build/Updates.types.d.ts +105 -13
  54. package/build/Updates.types.d.ts.map +1 -1
  55. package/build/Updates.types.js +27 -0
  56. package/build/Updates.types.js.map +1 -1
  57. package/build-cli/configureCodeSigning.js +3 -0
  58. package/build-cli/configureCodeSigningAsync.d.ts +2 -1
  59. package/build-cli/configureCodeSigningAsync.js +2 -2
  60. package/cli/configureCodeSigning.ts +3 -0
  61. package/cli/configureCodeSigningAsync.ts +3 -3
  62. package/guides/examples.md +88 -0
  63. package/guides/general.md +33 -0
  64. package/guides/migrations.md +65 -0
  65. package/guides/releasing.md +18 -0
  66. package/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncher.h +5 -0
  67. package/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherNoDatabase.m +8 -0
  68. package/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherWithDatabase.m +18 -0
  69. package/ios/EXUpdates/AppLoader/EXUpdatesAppLoader.m +7 -0
  70. package/ios/EXUpdates/AppLoader/EXUpdatesAppLoaderTask.m +38 -4
  71. package/ios/EXUpdates/AppLoader/EXUpdatesAsset.m +4 -0
  72. package/ios/EXUpdates/AppLoader/EXUpdatesCrypto.m +3 -0
  73. package/ios/EXUpdates/AppLoader/EXUpdatesEmbeddedAppLoader.m +10 -0
  74. package/ios/EXUpdates/AppLoader/EXUpdatesFileDownloader.m +66 -28
  75. package/ios/EXUpdates/AppLoader/EXUpdatesRemoteAppLoader.m +3 -0
  76. package/ios/EXUpdates/Database/EXUpdatesDatabase.h +24 -0
  77. package/ios/EXUpdates/Database/EXUpdatesDatabaseInitialization.m +3 -0
  78. package/ios/EXUpdates/Database/EXUpdatesDatabaseUtils.m +3 -0
  79. package/ios/EXUpdates/Database/EXUpdatesReaper.h +5 -0
  80. package/ios/EXUpdates/EXUpdatesAppController+Internal.h +1 -0
  81. package/ios/EXUpdates/EXUpdatesAppController.m +65 -2
  82. package/ios/EXUpdates/EXUpdatesConfig.m +11 -0
  83. package/ios/EXUpdates/EXUpdatesDevLauncherController.m +13 -0
  84. package/ios/EXUpdates/EXUpdatesErrorRecovery.m +48 -0
  85. package/ios/EXUpdates/EXUpdatesModule.m +47 -0
  86. package/ios/EXUpdates/EXUpdatesService.m +8 -0
  87. package/ios/EXUpdates/EXUpdatesUtils.h +2 -0
  88. package/ios/EXUpdates/EXUpdatesUtils.m +30 -1
  89. package/ios/EXUpdates/Logging/UpdatesErrorCode.swift +46 -0
  90. package/ios/EXUpdates/Logging/UpdatesLogEntry.swift +77 -0
  91. package/ios/EXUpdates/Logging/UpdatesLogReader.swift +91 -0
  92. package/ios/EXUpdates/Logging/UpdatesLogger.swift +176 -0
  93. package/ios/EXUpdates/ReactDelegateHandler/ExpoUpdatesAppDelegateSubscriber.swift +6 -1
  94. package/ios/EXUpdates/ReactDelegateHandler/ExpoUpdatesReactDelegateHandler.swift +8 -1
  95. package/ios/EXUpdates/SelectionPolicy/EXUpdatesLauncherSelectionPolicyFilterAware.h +4 -3
  96. package/ios/EXUpdates/SelectionPolicy/EXUpdatesLoaderSelectionPolicyFilterAware.h +2 -0
  97. package/ios/EXUpdates/SelectionPolicy/EXUpdatesReaperSelectionPolicyDevelopmentClient.h +2 -0
  98. package/ios/EXUpdates/SelectionPolicy/EXUpdatesReaperSelectionPolicyFilterAware.h +2 -0
  99. package/ios/EXUpdates/SelectionPolicy/EXUpdatesSelectionPolicies.m +3 -0
  100. package/ios/EXUpdates/SelectionPolicy/EXUpdatesSelectionPolicy.h +22 -0
  101. package/ios/EXUpdates/SelectionPolicy/EXUpdatesSelectionPolicyFactory.m +4 -0
  102. package/ios/EXUpdates/Update/EXUpdatesBareUpdate.m +7 -0
  103. package/ios/EXUpdates/Update/EXUpdatesLegacyUpdate.m +8 -0
  104. package/ios/EXUpdates/Update/EXUpdatesNewUpdate.m +5 -0
  105. package/ios/EXUpdates/Update/EXUpdatesUpdate.h +20 -0
  106. package/ios/EXUpdates/Update/EXUpdatesUpdate.m +5 -0
  107. package/ios/EXUpdates.podspec +10 -4
  108. package/ios/Tests/EXUpdatesFileDownloaderTests.m +26 -2
  109. package/ios/Tests/EXUpdatesLogReaderTests.swift +131 -0
  110. package/ios/Tests/EXUpdatesLoggerTests.swift +54 -0
  111. package/package.json +8 -8
  112. package/plugin/build/withUpdates.d.ts +1 -1
  113. package/plugin/build/withUpdates.js +2 -3
  114. package/plugin/src/withUpdates.ts +1 -1
  115. package/scripts/create-manifest-ios.sh +0 -1
  116. package/scripts/createManifest.js +18 -2
  117. package/src/Updates.ts +36 -1
  118. package/src/Updates.types.ts +111 -13
package/CHANGELOG.md CHANGED
@@ -10,19 +10,39 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
- ## 0.14.6 — 2022-09-26
13
+ ## 0.15.0 — 2022-10-25
14
14
 
15
- _This version does not introduce any user-facing changes._
15
+ ### 🛠 Breaking changes
16
+
17
+ - [plugin] Upgrade minimum runtime requirement to Node 14 (LTS). ([#18204](https://github.com/expo/expo/pull/18204) by [@EvanBacon](https://github.com/EvanBacon))
18
+ - Bumped iOS deployment target to 13.0 and deprecated support for iOS 12. ([#18873](https://github.com/expo/expo/pull/18873) by [@tsapeta](https://github.com/tsapeta))
19
+
20
+ ### 🎉 New features
16
21
 
17
- ## 0.14.5 2022-08-22
22
+ - [iOS] New logger and log reader for unifying logging support in expo-updates. ([#18284](https://github.com/expo/expo/pull/18284) by [@douglowder](https://github.com/douglowder))
23
+ - [Android] New logger and log reader for unifying logging support in expo-updates. ([#18318](https://github.com/expo/expo/pull/18318) by [@douglowder](https://github.com/douglowder))
24
+ - Add JS methods to read and clear client logs. ([#18390](https://github.com/expo/expo/pull/18390) by [@douglowder](https://github.com/douglowder))
25
+ - Add Logger support for writing logs to a file; add Logger and associated classes to Android. ([#18513](https://github.com/expo/expo/pull/18513) by [@douglowder](https://github.com/douglowder))
26
+ - Make extra header processing code consistent between manifests and assets. ([#18564](https://github.com/expo/expo/pull/18564) by [@wschurman](https://github.com/wschurman))
27
+ - Type `UpdateCheckResult` and `UpdateFetchResult` to reflect when `manifest` is defined or not. ([#18577](https://github.com/expo/expo/pull/18577) by [@SimenB](https://github.com/SimenB))
18
28
 
19
29
  ### 🐛 Bug fixes
20
30
 
21
- - Fixed *with-node.sh* doesn't keep quotes when passing arguments to Node.js and caused build errors when there are spaces in target name. ([#18741](https://github.com/expo/expo/pull/18741) by [@kudo](https://github.com/kudo))
31
+ - Fix small race condition in recovery code on Android where in very rare scenarios, a bundle could be downloaded twice. ([#18377](https://github.com/expo/expo/pull/18377) by [@esamelson](https://github.com/esamelson))
32
+ - Fixed _with-node.sh_ doesn't keep quotes when passing arguments to Node.js and caused build errors when there are spaces in target name. ([#18741](https://github.com/expo/expo/pull/18741) by [@kudo](https://github.com/kudo))
22
33
 
23
- ## 0.14.4 — 2022-08-12
34
+ ### 💡 Others
24
35
 
25
- _This version does not introduce any user-facing changes._
36
+ - [plugin] Migrate import from @expo/config-plugins to expo/config-plugins and @expo/config-types to expo/config. ([#18855](https://github.com/expo/expo/pull/18855) by [@brentvatne](https://github.com/brentvatne))
37
+ - Update doc block link for manifests. ([#18981](https://github.com/expo/expo/pull/18981) by [@EvanBacon](https://github.com/EvanBacon))
38
+ - Drop `@expo/config-plugins` dependency in favor of peer dependency on `expo`. ([#18595](https://github.com/expo/expo/pull/18595) by [@EvanBacon](https://github.com/EvanBacon))
39
+ - Log various errors with the new Updates logger; add E2E tests for the logger. ([#18810](https://github.com/expo/expo/pull/18810) by [@douglowder](https://github.com/douglowder))
40
+ - [iOS] Flag to enable native debugging of updates. ([#19292](https://github.com/expo/expo/pull/19292) by [@douglowder](https://github.com/douglowder))
41
+ - [Android] Flag to enable native debugging of updates. ([#19441](https://github.com/expo/expo/pull/19441) by [@douglowder](https://github.com/douglowder))
42
+
43
+ ### ⚠️ Notices
44
+
45
+ - Added support for React Native 0.70.x. ([#19261](https://github.com/expo/expo/pull/19261) by [@kudo](https://github.com/kudo))
26
46
 
27
47
  ## 0.14.3 — 2022-07-25
28
48
 
@@ -4,7 +4,19 @@ apply plugin: 'kotlin-kapt'
4
4
  apply plugin: 'maven-publish'
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '0.14.6'
7
+ version = '0.15.0'
8
+
9
+ def ex_updates_native_debug_env = System.getenv("EX_UPDATES_NATIVE_DEBUG") ?: "0"
10
+ def ex_updates_native_debug = ex_updates_native_debug_env == "1" ? "true" : "false"
11
+
12
+ // For native updates debugging, override these properties
13
+ if (findProject(":app")) {
14
+ def appProjectExt = project(":app").ext
15
+ if (appProjectExt.has("react") && ex_updates_native_debug) {
16
+ appProjectExt.react.bundleInDebug = true
17
+ appProjectExt.react.devDisabledInDebug = true
18
+ }
19
+ }
8
20
 
9
21
  apply from: "../scripts/create-manifest-android.gradle"
10
22
 
@@ -77,9 +89,10 @@ android {
77
89
  minSdkVersion safeExtGet("minSdkVersion", 21)
78
90
  targetSdkVersion safeExtGet("targetSdkVersion", 31)
79
91
  versionCode 31
80
- versionName '0.14.6'
92
+ versionName '0.15.0'
81
93
  consumerProguardFiles("proguard-rules.pro")
82
94
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
95
+ buildConfigField("boolean", "EX_UPDATES_NATIVE_DEBUG", ex_updates_native_debug)
83
96
  // uncomment below to export the database schema when making changes
84
97
  /* javaCompileOptions {
85
98
  annotationProcessorOptions {
@@ -3,5 +3,5 @@
3
3
  }
4
4
 
5
5
  -keepclassmembers class com.facebook.react.devsupport.DisabledDevSupportManager {
6
- private final com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler;
6
+ private final com.facebook.react.bridge.DefaultJSExceptionHandler mDefaultJSExceptionHandler;
7
7
  }
@@ -6,6 +6,17 @@ import android.net.Uri
6
6
  import android.util.Log
7
7
  import expo.modules.updates.codesigning.CodeSigningConfiguration
8
8
 
9
+ /**
10
+ * Holds global, immutable configuration values for updates, as well as doing some rudimentary
11
+ * validation.
12
+ *
13
+ * In most apps, these configuration values are baked into the build, and this class functions as a
14
+ * utility for reading and memoizing the values.
15
+ *
16
+ * In development clients (including Expo Go) where this configuration is intended to be dynamic at
17
+ * runtime and updates from multiple scopes can potentially be opened, multiple instances of this
18
+ * class may be created over the lifetime of the app, but only one should be active at a time.
19
+ */
9
20
  class UpdatesConfiguration private constructor (
10
21
  val isEnabled: Boolean,
11
22
  val expectsSignedManifest: Boolean,
@@ -30,12 +30,34 @@ import expo.modules.updates.loader.LoaderTask
30
30
  import expo.modules.updates.loader.LoaderTask.BackgroundUpdateStatus
31
31
  import expo.modules.updates.loader.LoaderTask.LoaderTaskCallback
32
32
  import expo.modules.updates.loader.RemoteLoader
33
+ import expo.modules.updates.logging.UpdatesErrorCode
34
+ import expo.modules.updates.logging.UpdatesLogReader
35
+ import expo.modules.updates.logging.UpdatesLogger
33
36
  import expo.modules.updates.manifest.UpdateManifest
34
37
  import expo.modules.updates.selectionpolicy.SelectionPolicy
35
38
  import expo.modules.updates.selectionpolicy.SelectionPolicyFactory
36
39
  import java.io.File
37
40
  import java.lang.ref.WeakReference
38
41
 
42
+ /**
43
+ * Main entry point to expo-updates in normal release builds (development clients, including Expo
44
+ * Go, use a different entry point). Singleton that keeps track of updates state, holds references
45
+ * to instances of other updates classes, and is the central hub for all updates-related tasks.
46
+ *
47
+ * The `start` method in this class should be invoked early in the application lifecycle, via
48
+ * [UpdatesPackage]. It delegates to an instance of [LoaderTask] to start the process of loading and
49
+ * launching an update, then responds appropriately depending on the callbacks that are invoked.
50
+ *
51
+ * This class also provides getter methods to access information about the updates state, which are
52
+ * used by the exported [UpdatesModule] through [UpdatesService]. Such information includes
53
+ * references to: the database, the [UpdatesConfiguration] object, the path on disk to the updates
54
+ * directory, any currently active [LoaderTask], the current [SelectionPolicy], the error recovery
55
+ * handler, and the current launched update. This class is intended to be the source of truth for
56
+ * these objects, so other classes shouldn't retain any of them indefinitely.
57
+ *
58
+ * This class also optionally holds a reference to the app's [ReactNativeHost], which allows
59
+ * expo-updates to reload JS and send events through the bridge.
60
+ */
39
61
  class UpdatesController private constructor(
40
62
  context: Context,
41
63
  var updatesConfiguration: UpdatesConfiguration
@@ -62,6 +84,15 @@ class UpdatesController private constructor(
62
84
  }
63
85
  }
64
86
 
87
+ private fun purgeUpdatesLogsOlderThanOneDay(context: Context) {
88
+ UpdatesLogReader(context).purgeLogEntries {
89
+ if (it != null) {
90
+ Log.e(TAG, "UpdatesLogReader: error in purgeLogEntries", it)
91
+ }
92
+ }
93
+ }
94
+
95
+ private val logger = UpdatesLogger(context)
65
96
  private var isStarted = false
66
97
  private var loaderTask: LoaderTask? = null
67
98
  private var remoteLoadStatus = ErrorRecoveryDelegate.RemoteLoadStatus.IDLE
@@ -71,7 +102,7 @@ class UpdatesController private constructor(
71
102
  UpdatesUtils.getRuntimeVersion(updatesConfiguration)
72
103
  )
73
104
  val fileDownloader: FileDownloader = FileDownloader(context)
74
- private val errorRecovery: ErrorRecovery = ErrorRecovery()
105
+ private val errorRecovery: ErrorRecovery = ErrorRecovery(context)
75
106
 
76
107
  private fun setRemoteLoadStatus(status: ErrorRecoveryDelegate.RemoteLoadStatus) {
77
108
  remoteLoadStatus = status
@@ -143,6 +174,10 @@ class UpdatesController private constructor(
143
174
  val isUsingEmbeddedAssets: Boolean
144
175
  get() = launcher?.isUsingEmbeddedAssets ?: false
145
176
 
177
+ /**
178
+ * Any process that calls this *must* manually release the lock by calling `releaseDatabase()` in
179
+ * every possible case (success, error) as soon as it is finished.
180
+ */
146
181
  fun getDatabase(): UpdatesDatabase = databaseHolder.database
147
182
 
148
183
  fun releaseDatabase() {
@@ -214,6 +249,8 @@ class UpdatesController private constructor(
214
249
  return
215
250
  }
216
251
 
252
+ purgeUpdatesLogsOlderThanOneDay(context)
253
+
217
254
  initializeDatabaseHandler()
218
255
  initializeErrorRecovery(context)
219
256
 
@@ -229,6 +266,7 @@ class UpdatesController private constructor(
229
266
  selectionPolicy,
230
267
  object : LoaderTaskCallback {
231
268
  override fun onFailure(e: Exception) {
269
+ logger.error("UpdatesController loaderTask onFailure: ${e.localizedMessage}", UpdatesErrorCode.None)
232
270
  launcher = NoDatabaseLauncher(context, updatesConfiguration, e)
233
271
  isEmergencyLaunch = true
234
272
  notifyController()
@@ -260,6 +298,7 @@ class UpdatesController private constructor(
260
298
  if (exception == null) {
261
299
  throw AssertionError("Background update with error status must have a nonnull exception object")
262
300
  }
301
+ logger.error("UpdatesController onBackgroundUpdateFinished: Error: ${exception.localizedMessage}", UpdatesErrorCode.Unknown, exception)
263
302
  remoteLoadStatus = ErrorRecoveryDelegate.RemoteLoadStatus.IDLE
264
303
  val params = Arguments.createMap()
265
304
  params.putString("message", exception.message)
@@ -270,12 +309,14 @@ class UpdatesController private constructor(
270
309
  throw AssertionError("Background update with error status must have a nonnull update object")
271
310
  }
272
311
  remoteLoadStatus = ErrorRecoveryDelegate.RemoteLoadStatus.NEW_UPDATE_LOADED
312
+ logger.info("UpdatesController onBackgroundUpdateFinished: Update available", UpdatesErrorCode.None)
273
313
  val params = Arguments.createMap()
274
314
  params.putString("manifestString", update.manifest.toString())
275
315
  UpdatesUtils.sendEventToReactNative(reactNativeHost, UPDATE_AVAILABLE_EVENT, params)
276
316
  }
277
317
  BackgroundUpdateStatus.NO_UPDATE_AVAILABLE -> {
278
318
  remoteLoadStatus = ErrorRecoveryDelegate.RemoteLoadStatus.IDLE
319
+ logger.error("UpdatesController onBackgroundUpdateFinished: No update available", UpdatesErrorCode.NoUpdatesAvailable)
279
320
  UpdatesUtils.sendEventToReactNative(
280
321
  reactNativeHost,
281
322
  UPDATE_NO_UPDATE_AVAILABLE_EVENT,
@@ -310,6 +351,7 @@ class UpdatesController private constructor(
310
351
  val remoteLoader = RemoteLoader(context, updatesConfiguration, database, fileDownloader, updatesDirectory, launchedUpdate)
311
352
  remoteLoader.start(object : Loader.LoaderCallback {
312
353
  override fun onFailure(e: Exception) {
354
+ logger.error("UpdatesController loadRemoteUpdate onFailure: ${e.localizedMessage}", UpdatesErrorCode.UpdateFailedToLoad, launchedUpdate?.loggingId, null)
313
355
  setRemoteLoadStatus(ErrorRecoveryDelegate.RemoteLoadStatus.IDLE)
314
356
  releaseDatabase()
315
357
  }
@@ -17,17 +17,25 @@ import expo.modules.updatesinterface.UpdatesInterface.UpdateCallback
17
17
  import org.json.JSONObject
18
18
  import java.util.*
19
19
 
20
- // this unused import must stay because of versioning
21
- /* ktlint-disable no-unused-imports */
22
-
23
- /* ktlint-enable no-unused-imports */
24
-
20
+ /**
21
+ * Main entry point to expo-updates in development builds with expo-dev-client. Singleton that still
22
+ * makes use of [UpdatesController] for keeping track of updates state, but provides capabilities
23
+ * that are not usually exposed but that expo-dev-client needs (launching and downloading a specific
24
+ * update by URL, allowing dynamic configuration, introspecting the database).
25
+ *
26
+ * Implements the external UpdatesInterface from the expo-updates-interface package. This allows
27
+ * expo-dev-client to compile without needing expo-updates to be installed.
28
+ */
25
29
  class UpdatesDevLauncherController : UpdatesInterface {
26
30
  private var mTempConfiguration: UpdatesConfiguration? = null
27
31
  override fun reset() {
28
32
  UpdatesController.instance.setLauncher(null)
29
33
  }
30
34
 
35
+ /**
36
+ * Fetch an update using a dynamically generated configuration object (including a potentially
37
+ * different update URL than the one embedded in the build).
38
+ */
31
39
  override fun fetchUpdateWithConfiguration(
32
40
  configuration: HashMap<String, Any>,
33
41
  context: Context,
@@ -99,6 +107,10 @@ class UpdatesDevLauncherController : UpdatesInterface {
99
107
 
100
108
  // ensure that we launch the update we want, even if it isn't the latest one
101
109
  val currentSelectionPolicy = controller.selectionPolicy
110
+ // Calling `setNextSelectionPolicy` allows the Updates module's `reloadAsync` method to reload
111
+ // with a different (newer) update if one is downloaded, e.g. using `fetchUpdateAsync`. If we
112
+ // set the default selection policy here instead, the update we are launching here would keep
113
+ // being launched by `reloadAsync` even if a newer one is downloaded.
102
114
  controller.setNextSelectionPolicy(
103
115
  SelectionPolicy(
104
116
  LauncherSelectionPolicySingleUpdate(update.id),
@@ -13,6 +13,10 @@ import java.io.File
13
13
  import expo.modules.updates.UpdatesConfiguration
14
14
  /* ktlint-enable no-unused-imports */
15
15
 
16
+ /**
17
+ * Interface implemented by the [UpdatesService] internal module and used by [UpdatesModule] to
18
+ * search for the correct internal module in the registry.
19
+ */
16
20
  interface UpdatesInterface {
17
21
  val configuration: UpdatesConfiguration
18
22
  val selectionPolicy: SelectionPolicy
@@ -17,12 +17,26 @@ import expo.modules.updates.loader.FileDownloader.ManifestDownloadCallback
17
17
  import expo.modules.updates.loader.Loader
18
18
  import expo.modules.updates.loader.RemoteLoader
19
19
  import expo.modules.updates.manifest.UpdateManifest
20
+ import expo.modules.updates.logging.UpdatesErrorCode
21
+ import expo.modules.updates.logging.UpdatesLogEntry
22
+ import expo.modules.updates.logging.UpdatesLogReader
23
+ import expo.modules.updates.logging.UpdatesLogger
24
+ import java.util.Date
20
25
 
21
26
  // these unused imports must stay because of versioning
22
27
  /* ktlint-disable no-unused-imports */
23
28
  import expo.modules.updates.UpdatesConfiguration
24
29
  /* ktlint-enable no-unused-imports */
25
30
 
31
+ /**
32
+ * Exported module which provides to the JS runtime information about the currently running update
33
+ * and updates state, along with methods to check for and download new updates, reload with the
34
+ * newest downloaded update applied, and read/clear native log entries.
35
+ *
36
+ * Communicates with the updates hub ([UpdatesController] in most apps, [ExpoUpdatesAppLoader] in
37
+ * Expo Go and legacy standalone apps) via [UpdatesService], an internal module which is overridden
38
+ * by [UpdatesBinding], a scoped module, in Expo Go.
39
+ */
26
40
  class UpdatesModule(
27
41
  context: Context,
28
42
  private val moduleRegistryDelegate: ModuleRegistryDelegate = ModuleRegistryDelegate()
@@ -40,6 +54,7 @@ class UpdatesModule(
40
54
  }
41
55
 
42
56
  override fun getConstants(): Map<String, Any> {
57
+ UpdatesLogger(context).info("UpdatesModule: getConstants called", UpdatesErrorCode.None)
43
58
  val constants = mutableMapOf<String, Any>()
44
59
  try {
45
60
  val updatesServiceLocal: UpdatesInterface? = updatesService
@@ -246,6 +261,55 @@ class UpdatesModule(
246
261
  }
247
262
  }
248
263
 
264
+ @ExpoMethod
265
+ fun readLogEntriesAsync(maxAge: Long, promise: Promise) {
266
+ AsyncTask.execute {
267
+ val reader = UpdatesLogReader(context)
268
+ val date = Date()
269
+ val epoch = Date(date.time - maxAge)
270
+ val results = reader.getLogEntries(epoch)
271
+ .mapNotNull { UpdatesLogEntry.create(it) }
272
+ .map { entry ->
273
+ Bundle().apply {
274
+ putLong("timestamp", entry.timestamp)
275
+ putString("message", entry.message)
276
+ putString("code", entry.code)
277
+ putString("level", entry.level)
278
+ if (entry.updateId != null) {
279
+ putString("updateId", entry.updateId)
280
+ }
281
+ if (entry.assetId != null) {
282
+ putString("assetId", entry.assetId)
283
+ }
284
+ if (entry.stacktrace != null) {
285
+ putStringArray("stacktrace", entry.stacktrace.toTypedArray())
286
+ }
287
+ }
288
+ }
289
+ promise.resolve(results)
290
+ }
291
+ }
292
+
293
+ @ExpoMethod
294
+ fun clearLogEntriesAsync(promise: Promise) {
295
+ AsyncTask.execute {
296
+ val reader = UpdatesLogReader(context)
297
+ reader.purgeLogEntries(
298
+ olderThan = Date()
299
+ ) { error ->
300
+ if (error != null) {
301
+ promise.reject(
302
+ "ERR_UPDATES_READ_LOGS",
303
+ "There was an error when clearing the expo-updates log file",
304
+ error
305
+ )
306
+ } else {
307
+ promise.resolve(null)
308
+ }
309
+ }
310
+ }
311
+ }
312
+
249
313
  companion object {
250
314
  private const val NAME = "ExpoUpdates"
251
315
 
@@ -15,6 +15,10 @@ import expo.modules.core.interfaces.ReactNativeHostHandler
15
15
  import expo.modules.updates.UpdatesController
16
16
  /* ktlint-enable no-unused-imports */
17
17
 
18
+ /**
19
+ * Defines the internal and exported modules for expo-updates, as well as the auto-setup behavior in
20
+ * applicable environments.
21
+ */
18
22
  class UpdatesPackage : Package {
19
23
  override fun createInternalModules(context: Context): List<InternalModule> {
20
24
  return listOf(UpdatesService(context) as InternalModule)
@@ -25,19 +29,20 @@ class UpdatesPackage : Package {
25
29
  }
26
30
 
27
31
  override fun createReactNativeHostHandlers(context: Context): List<ReactNativeHostHandler> {
32
+ val useNativeDebug = BuildConfig.EX_UPDATES_NATIVE_DEBUG
28
33
  val handler: ReactNativeHostHandler = object : ReactNativeHostHandler {
29
34
  private var mShouldAutoSetup: Boolean? = null
30
35
 
31
36
  override fun getJSBundleFile(useDeveloperSupport: Boolean): String? {
32
- return if (shouldAutoSetup(context) && !useDeveloperSupport) UpdatesController.instance.launchAssetFile else null
37
+ return if (shouldAutoSetup(context) && (useNativeDebug || !useDeveloperSupport)) UpdatesController.instance.launchAssetFile else null
33
38
  }
34
39
 
35
40
  override fun getBundleAssetName(useDeveloperSupport: Boolean): String? {
36
- return if (shouldAutoSetup(context) && !useDeveloperSupport) UpdatesController.instance.bundleAssetName else null
41
+ return if (shouldAutoSetup(context) && (useNativeDebug || !useDeveloperSupport)) UpdatesController.instance.bundleAssetName else null
37
42
  }
38
43
 
39
44
  override fun onWillCreateReactInstanceManager(useDeveloperSupport: Boolean) {
40
- if (shouldAutoSetup(context) && !useDeveloperSupport) {
45
+ if (shouldAutoSetup(context) && (useNativeDebug || !useDeveloperSupport)) {
41
46
  UpdatesController.initialize(context)
42
47
  }
43
48
  }
@@ -45,7 +50,7 @@ class UpdatesPackage : Package {
45
50
  override fun onDidCreateReactInstanceManager(reactInstanceManager: ReactInstanceManager, useDeveloperSupport: Boolean) {
46
51
  // WHEN_VERSIONING_REMOVE_FROM_HERE
47
52
  // This code path breaks versioning and is not necessary for Expo Go.
48
- if (shouldAutoSetup(context) && !useDeveloperSupport) {
53
+ if (shouldAutoSetup(context) && (useNativeDebug || !useDeveloperSupport)) {
49
54
  UpdatesController.instance.onDidCreateReactInstanceManager(reactInstanceManager)
50
55
  }
51
56
  // WHEN_VERSIONING_REMOVE_TO_HERE
@@ -7,6 +7,7 @@ import expo.modules.updates.db.entity.AssetEntity
7
7
  import expo.modules.updates.db.entity.UpdateEntity
8
8
  import expo.modules.updates.launcher.Launcher.LauncherCallback
9
9
  import expo.modules.updates.loader.FileDownloader
10
+ import expo.modules.updates.manifest.EmbeddedManifest
10
11
  import expo.modules.updates.selectionpolicy.SelectionPolicy
11
12
  import java.io.File
12
13
 
@@ -14,10 +15,16 @@ import java.io.File
14
15
  /* ktlint-disable no-unused-imports */
15
16
  import expo.modules.updates.UpdatesConfiguration
16
17
  import expo.modules.updates.UpdatesController
17
- import expo.modules.updates.manifest.EmbeddedManifest
18
-
19
18
  /* ktlint-enable no-unused-imports */
20
19
 
20
+ /**
21
+ * Internal module whose purpose is to connect [UpdatesModule] with the central updates entry point.
22
+ * In most apps, this is [UpdatesController].
23
+ *
24
+ * In other cases, this module can be overridden at runtime to redirect [UpdatesModule] to a
25
+ * different entry point. This is the case in Expo Go, where this module is overridden by
26
+ * [UpdatesBinding] in order to get data from [ExpoUpdatesAppLoader].
27
+ */
21
28
  open class UpdatesService(protected var context: Context) : InternalModule, UpdatesInterface {
22
29
  override fun getExportedInterfaces(): List<Class<*>> {
23
30
  return listOf(UpdatesInterface::class.java as Class<*>)
@@ -29,6 +29,9 @@ import java.text.SimpleDateFormat
29
29
  import java.util.*
30
30
  import kotlin.experimental.and
31
31
 
32
+ /**
33
+ * Miscellaneous helper functions that are used by multiple classes in the library.
34
+ */
32
35
  object UpdatesUtils {
33
36
  private val TAG = UpdatesUtils::class.java.simpleName
34
37
 
@@ -2,6 +2,17 @@ package expo.modules.updates.db
2
2
 
3
3
  import android.util.Log
4
4
 
5
+ /**
6
+ * Wrapper class that provides a rudimentary locking mechanism for the database. This allows us to
7
+ * control what high-level operations involving the database can occur simultaneously. Most classes
8
+ * should access [UpdatesDatabase] through this class.
9
+ *
10
+ * Any process that calls `getDatabase` *must* manually release the lock by calling
11
+ * `releaseDatabase` in every possible case (success, error) as soon as it is finished.
12
+ *
13
+ * On iOS we use GCD queues as a more sophisticated way of achieving the same thing; we may also
14
+ * eventually want to migrate to a coroutine- or [Handler]-based system in lieu of this class.
15
+ */
5
16
  class DatabaseHolder(private val mDatabase: UpdatesDatabase) {
6
17
  private var isInUse = false
7
18
 
@@ -8,6 +8,12 @@ import expo.modules.updates.manifest.ManifestMetadata
8
8
  import expo.modules.updates.selectionpolicy.SelectionPolicy
9
9
  import java.io.File
10
10
 
11
+ /**
12
+ * Safely clears old, unused assets and updates from the filesystem and database.
13
+ *
14
+ * Should be run in a background process when no other updates-related events are occurring (e.g.
15
+ * update download).
16
+ */
11
17
  object Reaper {
12
18
  private val TAG = Reaper::class.java.simpleName
13
19
 
@@ -16,6 +16,31 @@ import expo.modules.updates.db.entity.UpdateAssetEntity
16
16
  import expo.modules.updates.db.entity.UpdateEntity
17
17
  import java.util.*
18
18
 
19
+ /**
20
+ * SQLite database that keeps track of updates currently loaded/loading to disk, including the
21
+ * update manifest and metadata, status, and the individual assets (including bundles/bytecode) that
22
+ * comprise the update. (Assets themselves are stored on the device's file system, and a relative
23
+ * path is kept in SQLite.)
24
+ *
25
+ * SQLite allows a many-to-many relationship between updates and assets, which means we can keep
26
+ * only one copy of each asset on disk at a time while also being able to clear unused assets with
27
+ * relative ease (see [Reaper]).
28
+ *
29
+ * We use the Android Room library here, which provides a friendly abstraction over SQLite. The
30
+ * database schema is autogenerated from the `Entity` classes, and `DAO` classes provide access to
31
+ * the actual data.
32
+ *
33
+ * Occasionally it's necessary to add migrations when the data structures for updates or assets must
34
+ * change. Extra care must be taken here, since these migrations will happen on users' devices for
35
+ * apps we do not control. See
36
+ * https://github.com/expo/expo/blob/main/packages/expo-updates/guides/migrations.md for step by
37
+ * step instructions.
38
+ *
39
+ * [DatabaseHolder] provides a rudimentary locking mechanism, and most other classes access the
40
+ * database through this class. This allows control over what high-level operations involving the
41
+ * database can occur simultaneously - e.g. we don't want to be trying to download a new update at
42
+ * the same time the [Reaper] is running.
43
+ */
19
44
  @Database(
20
45
  entities = [UpdateEntity::class, UpdateAssetEntity::class, AssetEntity::class, JSONDataEntity::class],
21
46
  exportSchema = false,
@@ -6,6 +6,9 @@ import expo.modules.updates.db.entity.UpdateAssetEntity
6
6
  import expo.modules.updates.db.entity.UpdateEntity
7
7
  import java.util.*
8
8
 
9
+ /**
10
+ * Utility class for accessing and modifying data in SQLite relating to assets.
11
+ */
9
12
  @Dao
10
13
  abstract class AssetDao {
11
14
  /**
@@ -7,6 +7,9 @@ import androidx.room.Transaction
7
7
  import expo.modules.updates.db.entity.JSONDataEntity
8
8
  import java.util.*
9
9
 
10
+ /**
11
+ * Utility class for accessing and modifying data in the `json_data` SQLite table.
12
+ */
10
13
  @Dao
11
14
  abstract class JSONDataDao {
12
15
  /**
@@ -6,6 +6,9 @@ import expo.modules.updates.db.entity.UpdateEntity
6
6
  import expo.modules.updates.db.enums.UpdateStatus
7
7
  import java.util.*
8
8
 
9
+ /**
10
+ * Utility class for accessing and modifying data in SQLite relating to updates.
11
+ */
9
12
  @Dao
10
13
  abstract class UpdateDao {
11
14
  /**
@@ -6,6 +6,14 @@ import expo.modules.updates.db.enums.HashType
6
6
  import org.json.JSONObject
7
7
  import java.util.*
8
8
 
9
+ /**
10
+ * Data class that represents a (potential) row in the `assets` table in SQLite. The table schema is
11
+ * autogenerated from this file.
12
+ *
13
+ * The `id` field here only has meaning within an individual SQLite instance, as a foreign key;
14
+ * unlike [UpdateEntity.id], it does *not* uniquely identify an asset across multiple installations
15
+ * of an app. [AssetEntity.hash] should be used in most places where a unique identifier is needed.
16
+ */
9
17
  @Entity(tableName = "assets", indices = [Index(value = ["key"], unique = true)])
10
18
  class AssetEntity(@field:ColumnInfo(name = "key") var key: String?, var type: String?) {
11
19
  @PrimaryKey(autoGenerate = true) // 0 is treated as unset while inserting the entity into the db
@@ -6,6 +6,16 @@ import androidx.room.Index
6
6
  import androidx.room.PrimaryKey
7
7
  import java.util.*
8
8
 
9
+ /**
10
+ * Data class representing a (potential) row of arbitrary JSON data to be stored in the `json_data`
11
+ * table in SQLite. The table schema is autogenerated from this file.
12
+ *
13
+ * Used to store per-scope data that should be persisted and overridden via key, such as items in
14
+ * [ManifestMetadata].
15
+ *
16
+ * The `scopeKey` field is only relevant in environments such as Expo Go in which updates from
17
+ * multiple scopes can be launched.
18
+ */
9
19
  @Entity(tableName = "json_data", indices = [Index(value = ["scope_key"])])
10
20
  class JSONDataEntity(
11
21
  var key: String,
@@ -6,6 +6,13 @@ import androidx.room.ForeignKey
6
6
  import androidx.room.Index
7
7
  import java.util.*
8
8
 
9
+ /**
10
+ * Data class that represents a (potential) row in the `updates_assets` table, the schema for which
11
+ * is autogenerated from this file.
12
+ *
13
+ * This entity exists only within the database framework, to represent the many-to-many relation
14
+ * between updates and assets. It should not be used by any other outside classes.
15
+ */
9
16
  @Entity(
10
17
  tableName = "updates_assets",
11
18
  primaryKeys = ["update_id", "asset_id"],
@@ -5,6 +5,18 @@ import expo.modules.updates.db.enums.UpdateStatus
5
5
  import org.json.JSONObject
6
6
  import java.util.*
7
7
 
8
+ /**
9
+ * Data class that represents a (potential) row in the `updates` table in SQLite. The table schema
10
+ * is autogenerated from this file.
11
+ *
12
+ * expo-updates treats most fields (other than `status`, `keep`, `lastAccessed`, and the launch
13
+ * counts) as effectively immutable once in the database. This means an update server should never
14
+ * host two manifests with the same `id` that differ in any other field, as expo-updates will not
15
+ * take the difference into account.
16
+ *
17
+ * The `scopeKey` field is only relevant in environments such as Expo Go in which updates from
18
+ * multiple scopes can be launched.
19
+ */
8
20
  @Entity(
9
21
  tableName = "updates",
10
22
  foreignKeys = [
@@ -36,6 +48,11 @@ class UpdateEntity(
36
48
 
37
49
  var keep = false
38
50
 
51
+ val loggingId: String
52
+ get() {
53
+ return id.toString().lowercase()
54
+ }
55
+
39
56
  @ColumnInfo(name = "last_accessed")
40
57
  var lastAccessed: Date = Date()
41
58