expo-updates 0.10.15 → 0.11.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 (153) hide show
  1. package/CHANGELOG.md +27 -23
  2. package/android/build.gradle +9 -4
  3. package/android/src/main/java/expo/modules/updates/UpdatesConfiguration.kt +195 -0
  4. package/android/src/main/java/expo/modules/updates/UpdatesController.kt +367 -0
  5. package/android/src/main/java/expo/modules/updates/UpdatesDevLauncherController.kt +161 -0
  6. package/android/src/main/java/expo/modules/updates/UpdatesInterface.kt +31 -0
  7. package/android/src/main/java/expo/modules/updates/UpdatesModule.kt +246 -0
  8. package/android/src/main/java/expo/modules/updates/UpdatesPackage.kt +61 -0
  9. package/android/src/main/java/expo/modules/updates/UpdatesService.kt +54 -0
  10. package/android/src/main/java/expo/modules/updates/UpdatesUtils.kt +251 -0
  11. package/android/src/main/java/expo/modules/updates/db/BuildData.kt +104 -0
  12. package/android/src/main/java/expo/modules/updates/db/Converters.kt +107 -0
  13. package/android/src/main/java/expo/modules/updates/db/DatabaseHolder.kt +31 -0
  14. package/android/src/main/java/expo/modules/updates/db/DatabaseIntegrityCheck.kt +41 -0
  15. package/android/src/main/java/expo/modules/updates/db/Reaper.kt +82 -0
  16. package/android/src/main/java/expo/modules/updates/db/UpdatesDatabase.kt +165 -0
  17. package/android/src/main/java/expo/modules/updates/db/dao/AssetDao.kt +135 -0
  18. package/android/src/main/java/expo/modules/updates/db/dao/JSONDataDao.kt +51 -0
  19. package/android/src/main/java/expo/modules/updates/db/dao/UpdateDao.kt +106 -0
  20. package/android/src/main/java/expo/modules/updates/db/entity/AssetEntity.kt +51 -0
  21. package/android/src/main/java/expo/modules/updates/db/entity/JSONDataEntity.kt +18 -0
  22. package/android/src/main/java/expo/modules/updates/db/entity/UpdateAssetEntity.kt +30 -0
  23. package/android/src/main/java/expo/modules/updates/db/entity/UpdateEntity.kt +47 -0
  24. package/android/src/main/java/expo/modules/updates/db/enums/HashType.kt +5 -0
  25. package/android/src/main/java/expo/modules/updates/db/enums/{UpdateStatus.java → UpdateStatus.kt} +2 -2
  26. package/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.kt +217 -0
  27. package/android/src/main/java/expo/modules/updates/launcher/Launcher.kt +17 -0
  28. package/android/src/main/java/expo/modules/updates/launcher/NoDatabaseLauncher.kt +81 -0
  29. package/android/src/main/java/expo/modules/updates/loader/Crypto.kt +107 -0
  30. package/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.kt +123 -0
  31. package/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt +365 -0
  32. package/android/src/main/java/expo/modules/updates/loader/Loader.kt +305 -0
  33. package/android/src/main/java/expo/modules/updates/loader/LoaderFiles.kt +78 -0
  34. package/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt +368 -0
  35. package/android/src/main/java/expo/modules/updates/loader/RemoteLoader.kt +61 -0
  36. package/android/src/main/java/expo/modules/updates/manifest/BareUpdateManifest.kt +4 -3
  37. package/android/src/main/java/expo/modules/updates/manifest/EmbeddedManifest.kt +36 -0
  38. package/android/src/main/java/expo/modules/updates/manifest/LegacyUpdateManifest.kt +3 -3
  39. package/android/src/main/java/expo/modules/updates/manifest/ManifestFactory.kt +1 -1
  40. package/android/src/main/java/expo/modules/updates/manifest/ManifestMetadata.kt +60 -0
  41. package/android/src/main/java/expo/modules/updates/manifest/ManifestResponse.kt +18 -0
  42. package/android/src/main/java/expo/modules/updates/manifest/NewUpdateManifest.kt +1 -1
  43. package/android/src/main/java/expo/modules/updates/manifest/UpdateManifest.kt +1 -1
  44. package/android/src/main/java/expo/modules/updates/selectionpolicy/LauncherSelectionPolicy.kt +11 -0
  45. package/android/src/main/java/expo/modules/updates/selectionpolicy/LauncherSelectionPolicyFilterAware.kt +29 -0
  46. package/android/src/main/java/expo/modules/updates/selectionpolicy/LauncherSelectionPolicySingleUpdate.kt +17 -0
  47. package/android/src/main/java/expo/modules/updates/selectionpolicy/LoaderSelectionPolicy.kt +17 -0
  48. package/android/src/main/java/expo/modules/updates/selectionpolicy/{LoaderSelectionPolicyFilterAware.java → LoaderSelectionPolicyFilterAware.kt} +15 -16
  49. package/android/src/main/java/expo/modules/updates/selectionpolicy/ReaperSelectionPolicy.kt +16 -0
  50. package/android/src/main/java/expo/modules/updates/selectionpolicy/ReaperSelectionPolicyDevelopmentClient.kt +57 -0
  51. package/android/src/main/java/expo/modules/updates/selectionpolicy/{ReaperSelectionPolicyFilterAware.java → ReaperSelectionPolicyFilterAware.kt} +24 -27
  52. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicies.kt +41 -0
  53. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicy.kt +30 -0
  54. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicyFactory.kt +19 -0
  55. package/build/ExpoUpdates.js +1 -1
  56. package/build/ExpoUpdates.js.map +1 -1
  57. package/build/Updates.d.ts +45 -37
  58. package/build/Updates.js +46 -49
  59. package/build/Updates.js.map +1 -1
  60. package/build/Updates.types.d.ts +20 -10
  61. package/build/Updates.types.js +4 -2
  62. package/build/Updates.types.js.map +1 -1
  63. package/expo-module.config.json +7 -0
  64. package/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncher.h +2 -0
  65. package/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherNoDatabase.h +0 -2
  66. package/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherNoDatabase.m +0 -59
  67. package/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherWithDatabase.h +0 -1
  68. package/ios/EXUpdates/AppLoader/EXUpdatesAppLoaderTask.h +2 -0
  69. package/ios/EXUpdates/AppLoader/EXUpdatesAppLoaderTask.m +9 -1
  70. package/ios/EXUpdates/AppLoader/EXUpdatesFileDownloader.m +2 -2
  71. package/ios/EXUpdates/Database/EXUpdatesBuildData+Tests.h +14 -0
  72. package/ios/EXUpdates/Database/EXUpdatesBuildData.h +14 -0
  73. package/ios/EXUpdates/Database/EXUpdatesBuildData.m +102 -0
  74. package/ios/EXUpdates/Database/EXUpdatesDatabase.h +4 -0
  75. package/ios/EXUpdates/Database/EXUpdatesDatabase.m +36 -4
  76. package/ios/EXUpdates/Database/EXUpdatesDatabaseInitialization.m +3 -1
  77. package/ios/EXUpdates/Database/Migrations/EXUpdatesDatabaseMigration6To7.h +11 -0
  78. package/ios/EXUpdates/Database/Migrations/EXUpdatesDatabaseMigration6To7.m +64 -0
  79. package/ios/EXUpdates/Database/Migrations/EXUpdatesDatabaseMigrationRegistry.m +3 -2
  80. package/ios/EXUpdates/EXUpdates.h +5 -0
  81. package/ios/EXUpdates/EXUpdatesAppController.m +123 -19
  82. package/ios/EXUpdates/EXUpdatesConfig.h +2 -1
  83. package/ios/EXUpdates/EXUpdatesConfig.m +3 -0
  84. package/ios/EXUpdates/EXUpdatesErrorRecovery.h +52 -0
  85. package/ios/EXUpdates/EXUpdatesErrorRecovery.m +377 -0
  86. package/ios/EXUpdates/EXUpdatesUtils.h +0 -1
  87. package/ios/EXUpdates/EXUpdatesUtils.m +3 -21
  88. package/ios/EXUpdates/ReactDelegateHandler/DeferredRCTBridge.h +14 -0
  89. package/ios/EXUpdates/ReactDelegateHandler/DeferredRCTBridge.m +44 -0
  90. package/ios/EXUpdates/ReactDelegateHandler/DeferredRCTRootView.h +14 -0
  91. package/ios/EXUpdates/ReactDelegateHandler/DeferredRCTRootView.m +47 -0
  92. package/ios/EXUpdates/ReactDelegateHandler/ExpoUpdatesReactDelegateHandler.swift +109 -0
  93. package/ios/EXUpdates/Update/EXUpdatesUpdate.h +31 -3
  94. package/ios/EXUpdates/Update/EXUpdatesUpdate.m +2 -0
  95. package/ios/EXUpdates.podspec +5 -2
  96. package/ios/Tests/EXUpdatesBuildDataTests.m +263 -0
  97. package/ios/Tests/EXUpdatesDatabaseInitializationTests.m +163 -2
  98. package/ios/Tests/EXUpdatesDatabaseTests.m +1 -0
  99. package/ios/Tests/EXUpdatesErrorRecoveryTests.m +428 -0
  100. package/package.json +9 -7
  101. package/plugin/build/withUpdates.js +2 -2
  102. package/scripts/create-manifest-android.gradle +2 -2
  103. package/scripts/create-manifest-ios.sh +6 -3
  104. package/scripts/createManifest.js +7 -5
  105. package/scripts/source-login-scripts.sh +47 -0
  106. package/src/ExpoUpdates.ts +1 -1
  107. package/src/Updates.ts +47 -50
  108. package/src/Updates.types.ts +22 -15
  109. package/src/ts-declarations/lib.dom.d.ts +14 -0
  110. package/android/src/main/java/expo/modules/updates/UpdatesConfiguration.java +0 -249
  111. package/android/src/main/java/expo/modules/updates/UpdatesController.java +0 -398
  112. package/android/src/main/java/expo/modules/updates/UpdatesDevLauncherController.java +0 -150
  113. package/android/src/main/java/expo/modules/updates/UpdatesInterface.java +0 -32
  114. package/android/src/main/java/expo/modules/updates/UpdatesModule.java +0 -236
  115. package/android/src/main/java/expo/modules/updates/UpdatesPackage.java +0 -93
  116. package/android/src/main/java/expo/modules/updates/UpdatesService.java +0 -98
  117. package/android/src/main/java/expo/modules/updates/UpdatesUtils.java +0 -238
  118. package/android/src/main/java/expo/modules/updates/db/Converters.java +0 -137
  119. package/android/src/main/java/expo/modules/updates/db/DatabaseHolder.java +0 -33
  120. package/android/src/main/java/expo/modules/updates/db/DatabaseIntegrityCheck.java +0 -46
  121. package/android/src/main/java/expo/modules/updates/db/Reaper.java +0 -70
  122. package/android/src/main/java/expo/modules/updates/db/UpdatesDatabase.java +0 -129
  123. package/android/src/main/java/expo/modules/updates/db/dao/AssetDao.java +0 -144
  124. package/android/src/main/java/expo/modules/updates/db/dao/JSONDataDao.java +0 -58
  125. package/android/src/main/java/expo/modules/updates/db/dao/UpdateDao.java +0 -114
  126. package/android/src/main/java/expo/modules/updates/db/entity/AssetEntity.java +0 -74
  127. package/android/src/main/java/expo/modules/updates/db/entity/JSONDataEntity.java +0 -38
  128. package/android/src/main/java/expo/modules/updates/db/entity/UpdateAssetEntity.java +0 -38
  129. package/android/src/main/java/expo/modules/updates/db/entity/UpdateEntity.java +0 -69
  130. package/android/src/main/java/expo/modules/updates/db/enums/HashType.java +0 -5
  131. package/android/src/main/java/expo/modules/updates/launcher/DatabaseLauncher.java +0 -245
  132. package/android/src/main/java/expo/modules/updates/launcher/Launcher.java +0 -21
  133. package/android/src/main/java/expo/modules/updates/launcher/NoDatabaseLauncher.java +0 -104
  134. package/android/src/main/java/expo/modules/updates/loader/Crypto.java +0 -106
  135. package/android/src/main/java/expo/modules/updates/loader/EmbeddedLoader.java +0 -243
  136. package/android/src/main/java/expo/modules/updates/loader/FileDownloader.java +0 -335
  137. package/android/src/main/java/expo/modules/updates/loader/LoaderTask.java +0 -340
  138. package/android/src/main/java/expo/modules/updates/loader/RemoteLoader.java +0 -266
  139. package/android/src/main/java/expo/modules/updates/manifest/ManifestMetadata.java +0 -51
  140. package/android/src/main/java/expo/modules/updates/manifest/ManifestResponse.java +0 -27
  141. package/android/src/main/java/expo/modules/updates/selectionpolicy/LauncherSelectionPolicy.java +0 -14
  142. package/android/src/main/java/expo/modules/updates/selectionpolicy/LauncherSelectionPolicyFilterAware.java +0 -40
  143. package/android/src/main/java/expo/modules/updates/selectionpolicy/LauncherSelectionPolicySingleUpdate.java +0 -30
  144. package/android/src/main/java/expo/modules/updates/selectionpolicy/LoaderSelectionPolicy.java +0 -14
  145. package/android/src/main/java/expo/modules/updates/selectionpolicy/ReaperSelectionPolicy.java +0 -15
  146. package/android/src/main/java/expo/modules/updates/selectionpolicy/ReaperSelectionPolicyDevelopmentClient.java +0 -67
  147. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicies.java +0 -46
  148. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicy.java +0 -46
  149. package/android/src/main/java/expo/modules/updates/selectionpolicy/SelectionPolicyFactory.java +0 -22
  150. package/ios/EXUpdates/EXUpdatesAppDelegate.h +0 -14
  151. package/ios/EXUpdates/EXUpdatesAppDelegate.m +0 -169
  152. package/src/.gitkeep +0 -0
  153. package/unimodule.json +0 -4
package/CHANGELOG.md CHANGED
@@ -10,39 +10,43 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
- ## 0.10.15 — 2021-11-11
13
+ ## 0.11.0 — 2021-12-03
14
14
 
15
- ### 🐛 Bug fixes
16
-
17
- - Fix iOS auto-layout breaking for RCTRootView. on a iPad simulator, the view is not updated after rotation. ([#15100](https://github.com/expo/expo/pull/15100) by [@kudo](https://github.com/kudo))
18
-
19
- ## 0.10.14 — 2021-11-09
20
-
21
- ### 🐛 Bug fixes
22
-
23
- - Retain embedded asset fields when merging existing asset entities on Android. ([#15123](https://github.com/expo/expo/pull/15123) by [@esamelson](https://github.com/esamelson))
24
-
25
- ## 0.10.13 — 2021-11-05
26
-
27
- ### 🐛 Bug fixes
28
-
29
- - Fix issue with assets that are duplicated in the local SQLite db being reaped when they are still in use. ([#15049](https://github.com/expo/expo/pull/15049) by [@esamelson](https://github.com/esamelson))
30
-
31
- ## 0.10.12 — 2021-11-04
15
+ ### 🛠 Breaking changes
32
16
 
33
- ### 🐛 Bug fixes
17
+ - Add local SQLite fields for error recovery manager on iOS. ([#14610](https://github.com/expo/expo/pull/14610) by [@esamelson](https://github.com/esamelson))
18
+ - Add DB migration for above. ([#14718](https://github.com/expo/expo/pull/14718) by [@esamelson](https://github.com/esamelson))
19
+ - Add local SQLite fields and DB migration for error recovery manager on Android. ([#15218](https://github.com/expo/expo/pull/15218) by [@esamelson](https://github.com/esamelson))
20
+ - Add DEFAULT 0 to new error recovery DB columns. ([#15360](https://github.com/expo/expo/pull/15360) by [@esamelson](https://github.com/esamelson))
34
21
 
35
- - Workaround for bridge being initialized twice on startup. ([#15019](https://github.com/expo/expo/pull/15019) by [@kudo](https://github.com/kudo))
22
+ ### 🎉 New features
36
23
 
37
- ## 0.10.11 2021-11-02
24
+ - Add error recovery manager on iOS. ([#14397](https://github.com/expo/expo/pull/14397) by [@esamelson](https://github.com/esamelson))
25
+ - Hook up error recovery manager to rest of module on iOS. ([#14398](https://github.com/expo/expo/pull/14398) by [@esamelson](https://github.com/esamelson))
26
+ - Move persisted error log to EXUpdatesErrorRecovery on iOS. ([#14399](https://github.com/expo/expo/pull/14399) by [@esamelson](https://github.com/esamelson))
27
+ - Add native EXUpdatesCheckOnLaunch: ERROR_RECOVERY_ONLY setting on iOS. ([#14673](https://github.com/expo/expo/pull/14673) by [@esamelson](https://github.com/esamelson))
28
+ - Small fixes for error recovery manager on iOS. ([#15223](https://github.com/expo/expo/pull/15223) by [@esamelson](https://github.com/esamelson))
29
+ - Add native checkOnLaunch: ERROR_RECOVERY_ONLY setting on Android. ([#15219](https://github.com/expo/expo/pull/15219) by [@esamelson](https://github.com/esamelson))
30
+ - Enhance node binary resolution for Xcode build phases scripts by the vendoring source-login-scripts.sh. ([#15336](https://github.com/expo/expo/pull/15336) by [@kudo](https://github.com/kudo))
38
31
 
39
32
  ### 🐛 Bug fixes
40
33
 
34
+ - Fix auto setup `EXUpdatesAppDelegate` breaking reanimated installation. ([#14755](https://github.com/expo/expo/pull/14755) by [@kudo](https://github.com/kudo))
35
+ - Fix support for `react.entryFile` gradle config. ([#14934](https://github.com/expo/expo/pull/14934) by [@EvanBacon](https://github.com/EvanBacon))
36
+ - Fix Android app.manifest not generated when in OneSignal gradle plugin integration. ([#14938](https://github.com/expo/expo/pull/14938) by [@kudo](https://github.com/kudo))
37
+ - Fix Android app.manifest not generated from [#14938](https://github.com/expo/expo/pull/14938) regression. ([#14953](https://github.com/expo/expo/pull/14953) by [@kudo](https://github.com/kudo))
38
+ - Fix iOS app.manifest generation error in `eas build --local` mode. ([#14956](https://github.com/expo/expo/pull/14956) by [@kudo](https://github.com/kudo))
41
39
  - Fix handling of unexpectedly missing assets on iOS. ([#15008](https://github.com/expo/expo/pull/15008) by [@esamelson](https://github.com/esamelson))
40
+ - Fix issue with assets that are duplicated in the local SQLite db being reaped when they are still in use. ([#15049](https://github.com/expo/expo/pull/15049) by [@esamelson](https://github.com/esamelson))
41
+ - Retain embedded asset fields when merging existing asset entities on Android. ([#15123](https://github.com/expo/expo/pull/15123) by [@esamelson](https://github.com/esamelson))
42
+ - Fix `RCTBridge` initialized twice on startup. ([#15142](https://github.com/expo/expo/pull/15142) by [@kudo](https://github.com/kudo))
42
43
 
43
- ## 0.10.10 — 2021-11-02
44
+ ### 💡 Others
44
45
 
45
- _This version does not introduce any user-facing changes._
46
+ - Add error when entryfile is not found in expo-updates scripts. ([#15234](https://github.com/expo/expo/pull/15234) by [@AamuLumi](https://github.com/AamuLumi))
47
+ - Update `@expo/config` and `@expo/metro-config` dependencies. ([#14801](https://github.com/expo/expo/pull/14801) by [@Simek](https://github.com/Simek))
48
+ - Refactor and unify Loader classes on Android. ([#14334](https://github.com/expo/expo/pull/14334) by [@esamelson](https://github.com/esamelson))
49
+ - Kotlinize expo-updates. ([#14818](https://github.com/expo/expo/pull/14334) by [@wschurman](https://github.com/wschurman))
46
50
 
47
51
  ## 0.10.9 — 2021-10-29
48
52
 
@@ -1,9 +1,10 @@
1
1
  apply plugin: 'com.android.library'
2
2
  apply plugin: 'kotlin-android'
3
+ apply plugin: 'kotlin-kapt'
3
4
  apply plugin: 'maven'
4
5
 
5
6
  group = 'host.exp.exponent'
6
- version = '0.10.15'
7
+ version = '0.11.0'
7
8
 
8
9
  apply from: "../scripts/create-manifest-android.gradle"
9
10
 
@@ -55,11 +56,15 @@ android {
55
56
  targetCompatibility JavaVersion.VERSION_1_8
56
57
  }
57
58
 
59
+ kotlinOptions {
60
+ jvmTarget = JavaVersion.VERSION_1_8
61
+ }
62
+
58
63
  defaultConfig {
59
64
  minSdkVersion safeExtGet("minSdkVersion", 21)
60
65
  targetSdkVersion safeExtGet("targetSdkVersion", 30)
61
66
  versionCode 31
62
- versionName '0.10.15'
67
+ versionName '0.11.0'
63
68
  consumerProguardFiles("proguard-rules.pro")
64
69
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
65
70
  // uncomment below to export the database schema when making changes
@@ -90,10 +95,10 @@ dependencies {
90
95
  //noinspection GradleDynamicVersion
91
96
  implementation "com.facebook.react:react-native:+"
92
97
 
93
- def room_version = "2.1.0"
98
+ def room_version = "2.3.0"
94
99
 
95
100
  implementation "androidx.room:room-runtime:$room_version"
96
- annotationProcessor "androidx.room:room-compiler:$room_version"
101
+ kapt "androidx.room:room-compiler:$room_version"
97
102
 
98
103
  implementation("com.squareup.okhttp3:okhttp:3.12.1")
99
104
  implementation("com.squareup.okhttp3:okhttp-urlconnection:3.12.1")
@@ -0,0 +1,195 @@
1
+ package expo.modules.updates
2
+
3
+ import android.content.Context
4
+ import android.content.pm.PackageManager
5
+ import android.net.Uri
6
+ import android.util.Log
7
+
8
+ class UpdatesConfiguration {
9
+ enum class CheckAutomaticallyConfiguration {
10
+ NEVER, ERROR_RECOVERY_ONLY, WIFI_ONLY, ALWAYS
11
+ }
12
+
13
+ var isEnabled = false
14
+ private set
15
+ var expectsSignedManifest = false
16
+ private set
17
+ var scopeKey: String? = null
18
+ private set
19
+ var updateUrl: Uri? = null
20
+ private set
21
+ var sdkVersion: String? = null
22
+ private set
23
+ var runtimeVersion: String? = null
24
+ private set
25
+ var releaseChannel = UPDATES_CONFIGURATION_RELEASE_CHANNEL_DEFAULT_VALUE
26
+ private set
27
+ var launchWaitMs = UPDATES_CONFIGURATION_LAUNCH_WAIT_MS_DEFAULT_VALUE
28
+ private set
29
+ var checkOnLaunch = CheckAutomaticallyConfiguration.ALWAYS
30
+ private set
31
+ var hasEmbeddedUpdate = true
32
+ var requestHeaders = mapOf<String, String>()
33
+ private set
34
+
35
+ val isMissingRuntimeVersion: Boolean
36
+ get() = (runtimeVersion == null || runtimeVersion!!.isEmpty()) &&
37
+ (sdkVersion == null || sdkVersion!!.isEmpty())
38
+
39
+ fun loadValuesFromMetadata(context: Context): UpdatesConfiguration {
40
+ try {
41
+ val ai = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
42
+ updateUrl = ai.metaData.getString("expo.modules.updates.EXPO_UPDATE_URL")?.let { Uri.parse(it) }
43
+ scopeKey = ai.metaData.getString("expo.modules.updates.EXPO_SCOPE_KEY")
44
+ maybeSetDefaultScopeKey()
45
+ isEnabled = ai.metaData.getBoolean("expo.modules.updates.ENABLED", true)
46
+ sdkVersion = ai.metaData.getString("expo.modules.updates.EXPO_SDK_VERSION")
47
+ releaseChannel = ai.metaData.getString("expo.modules.updates.EXPO_RELEASE_CHANNEL", "default")
48
+ launchWaitMs = ai.metaData.getInt("expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS", 0)
49
+ runtimeVersion = ai.metaData["expo.modules.updates.EXPO_RUNTIME_VERSION"]?.toString()?.replaceFirst("^string:".toRegex(), "")
50
+
51
+ val checkOnLaunchString = ai.metaData.getString("expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH", "ALWAYS")
52
+ checkOnLaunch = try {
53
+ CheckAutomaticallyConfiguration.valueOf(checkOnLaunchString)
54
+ } catch (e: IllegalArgumentException) {
55
+ Log.e(
56
+ TAG,
57
+ "Invalid value $checkOnLaunchString for expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH in AndroidManifest; defaulting to ALWAYS"
58
+ )
59
+ CheckAutomaticallyConfiguration.ALWAYS
60
+ }
61
+
62
+ requestHeaders = UpdatesUtils.getHeadersMapFromJSONString(
63
+ ai.metaData.getString(
64
+ "expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY",
65
+ "{}"
66
+ )
67
+ )
68
+
69
+ // used only for expo-updates development
70
+ hasEmbeddedUpdate = ai.metaData.getBoolean("expo.modules.updates.HAS_EMBEDDED_UPDATE", true)
71
+ } catch (e: Exception) {
72
+ Log.e(TAG, "Could not read expo-updates configuration data in AndroidManifest", e)
73
+ }
74
+ return this
75
+ }
76
+
77
+ fun loadValuesFromMap(map: Map<String, Any>): UpdatesConfiguration {
78
+ val isEnabledFromMap = map.readValueCheckingType<Boolean>(UPDATES_CONFIGURATION_ENABLED_KEY)
79
+ if (isEnabledFromMap != null) {
80
+ isEnabled = isEnabledFromMap
81
+ }
82
+
83
+ expectsSignedManifest = map.readValueCheckingType("expectsSignedManifest") ?: false
84
+
85
+ val updateUrlFromMap = map.readValueCheckingType<Uri>(UPDATES_CONFIGURATION_UPDATE_URL_KEY)
86
+ if (updateUrlFromMap != null) {
87
+ updateUrl = updateUrlFromMap
88
+ }
89
+
90
+ val scopeKeyFromMap = map.readValueCheckingType<String>(UPDATES_CONFIGURATION_SCOPE_KEY_KEY)
91
+ if (scopeKeyFromMap != null) {
92
+ scopeKey = scopeKeyFromMap
93
+ }
94
+ maybeSetDefaultScopeKey()
95
+
96
+ val requestHeadersFromMap = map.readValueCheckingType<Map<String, String>>(UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY)
97
+ if (requestHeadersFromMap != null) {
98
+ requestHeaders = requestHeadersFromMap
99
+ }
100
+
101
+ val releaseChannelFromMap = map.readValueCheckingType<String>(UPDATES_CONFIGURATION_RELEASE_CHANNEL_KEY)
102
+ if (releaseChannelFromMap != null) {
103
+ releaseChannel = releaseChannelFromMap
104
+ }
105
+
106
+ val sdkVersionFromMap = map.readValueCheckingType<String>(UPDATES_CONFIGURATION_SDK_VERSION_KEY)
107
+ if (sdkVersionFromMap != null) {
108
+ sdkVersion = sdkVersionFromMap
109
+ }
110
+
111
+ val runtimeVersionFromMap = map.readValueCheckingType<String>(UPDATES_CONFIGURATION_RUNTIME_VERSION_KEY)
112
+ if (runtimeVersionFromMap != null) {
113
+ runtimeVersion = runtimeVersionFromMap
114
+ }
115
+
116
+ val checkOnLaunchFromMap = map.readValueCheckingType<String>(UPDATES_CONFIGURATION_CHECK_ON_LAUNCH_KEY)
117
+ if (checkOnLaunchFromMap != null) {
118
+ try {
119
+ checkOnLaunch = CheckAutomaticallyConfiguration.valueOf(checkOnLaunchFromMap)
120
+ } catch (e: IllegalArgumentException) {
121
+ throw AssertionError("UpdatesConfiguration failed to initialize: invalid value $checkOnLaunchFromMap provided for checkOnLaunch")
122
+ }
123
+ }
124
+
125
+ val launchWaitMsFromMap = map.readValueCheckingType<Int>(UPDATES_CONFIGURATION_LAUNCH_WAIT_MS_KEY)
126
+ if (launchWaitMsFromMap != null) {
127
+ launchWaitMs = launchWaitMsFromMap
128
+ }
129
+
130
+ val hasEmbeddedUpdateFromMap = map.readValueCheckingType<Boolean>(UPDATES_CONFIGURATION_HAS_EMBEDDED_UPDATE_KEY)
131
+ if (hasEmbeddedUpdateFromMap != null) {
132
+ hasEmbeddedUpdate = hasEmbeddedUpdateFromMap
133
+ }
134
+
135
+ return this
136
+ }
137
+
138
+ private inline fun <reified T : Any> Map<String, Any>.readValueCheckingType(key: String): T? {
139
+ if (!containsKey(key)) {
140
+ return null
141
+ }
142
+ val value = this[key]
143
+ return if (value is T) {
144
+ value
145
+ } else {
146
+ throw AssertionError("UpdatesConfiguration failed to initialize: bad value of type " + value!!.javaClass.simpleName + " provided for key " + key)
147
+ }
148
+ }
149
+
150
+ private fun maybeSetDefaultScopeKey() {
151
+ // set updateUrl as the default value if none is provided
152
+ if (scopeKey == null) {
153
+ if (updateUrl != null) {
154
+ scopeKey = getNormalizedUrlOrigin(updateUrl!!)
155
+ }
156
+ }
157
+ }
158
+
159
+ companion object {
160
+ private val TAG = UpdatesConfiguration::class.java.simpleName
161
+
162
+ const val UPDATES_CONFIGURATION_ENABLED_KEY = "enabled"
163
+ const val UPDATES_CONFIGURATION_SCOPE_KEY_KEY = "scopeKey"
164
+ const val UPDATES_CONFIGURATION_UPDATE_URL_KEY = "updateUrl"
165
+ const val UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY = "requestHeaders"
166
+ const val UPDATES_CONFIGURATION_RELEASE_CHANNEL_KEY = "releaseChannel"
167
+ const val UPDATES_CONFIGURATION_SDK_VERSION_KEY = "sdkVersion"
168
+ const val UPDATES_CONFIGURATION_RUNTIME_VERSION_KEY = "runtimeVersion"
169
+ const val UPDATES_CONFIGURATION_CHECK_ON_LAUNCH_KEY = "checkOnLaunch"
170
+ const val UPDATES_CONFIGURATION_LAUNCH_WAIT_MS_KEY = "launchWaitMs"
171
+ const val UPDATES_CONFIGURATION_HAS_EMBEDDED_UPDATE_KEY = "hasEmbeddedUpdate"
172
+ private const val UPDATES_CONFIGURATION_RELEASE_CHANNEL_DEFAULT_VALUE = "default"
173
+ private const val UPDATES_CONFIGURATION_LAUNCH_WAIT_MS_DEFAULT_VALUE = 0
174
+
175
+ internal fun getNormalizedUrlOrigin(url: Uri): String {
176
+ val scheme = url.scheme
177
+ var port = url.port
178
+ if (port == getDefaultPortForScheme(scheme)) {
179
+ port = -1
180
+ }
181
+ return if (port > -1) "$scheme://${url.host}:$port" else "$scheme://${url.host}"
182
+ }
183
+
184
+ private fun getDefaultPortForScheme(scheme: String?): Int {
185
+ if ("http" == scheme || "ws" == scheme) {
186
+ return 80
187
+ } else if ("https" == scheme || "wss" == scheme) {
188
+ return 443
189
+ } else if ("ftp" == scheme) {
190
+ return 21
191
+ }
192
+ return -1
193
+ }
194
+ }
195
+ }
@@ -0,0 +1,367 @@
1
+ package expo.modules.updates
2
+
3
+ import android.content.Context
4
+ import android.net.Uri
5
+ import android.os.AsyncTask
6
+ import android.os.Handler
7
+ import android.os.Looper
8
+ import android.util.Log
9
+ import com.facebook.react.ReactApplication
10
+ import com.facebook.react.ReactNativeHost
11
+ import com.facebook.react.bridge.Arguments
12
+ import com.facebook.react.bridge.JSBundleLoader
13
+ import expo.modules.updates.db.BuildData
14
+ import expo.modules.updates.db.DatabaseHolder
15
+ import expo.modules.updates.db.Reaper
16
+ import expo.modules.updates.db.UpdatesDatabase
17
+ import expo.modules.updates.db.entity.AssetEntity
18
+ import expo.modules.updates.db.entity.UpdateEntity
19
+ import expo.modules.updates.launcher.DatabaseLauncher
20
+ import expo.modules.updates.launcher.Launcher
21
+ import expo.modules.updates.launcher.Launcher.LauncherCallback
22
+ import expo.modules.updates.launcher.NoDatabaseLauncher
23
+ import expo.modules.updates.loader.FileDownloader
24
+ import expo.modules.updates.loader.LoaderTask
25
+ import expo.modules.updates.loader.LoaderTask.BackgroundUpdateStatus
26
+ import expo.modules.updates.loader.LoaderTask.LoaderTaskCallback
27
+ import expo.modules.updates.manifest.UpdateManifest
28
+ import expo.modules.updates.selectionpolicy.SelectionPolicy
29
+ import expo.modules.updates.selectionpolicy.SelectionPolicyFactory
30
+ import java.io.File
31
+ import java.lang.ref.WeakReference
32
+
33
+ class UpdatesController private constructor(
34
+ context: Context,
35
+ var updatesConfiguration: UpdatesConfiguration
36
+ ) {
37
+ private var reactNativeHost: WeakReference<ReactNativeHost>? = if (context is ReactApplication) {
38
+ WeakReference((context as ReactApplication).reactNativeHost)
39
+ } else {
40
+ null
41
+ }
42
+
43
+ var updatesDirectory: File? = null
44
+ var updatesDirectoryException: Exception? = null
45
+
46
+ private var launcher: Launcher? = null
47
+ val databaseHolder = DatabaseHolder(UpdatesDatabase.getInstance(context))
48
+
49
+ private var mSelectionPolicy: SelectionPolicy? = null
50
+ private var defaultSelectionPolicy: SelectionPolicy = SelectionPolicyFactory.createFilterAwarePolicy(
51
+ UpdatesUtils.getRuntimeVersion(updatesConfiguration)
52
+ )
53
+ val fileDownloader: FileDownloader = FileDownloader(context)
54
+
55
+ // launch conditions
56
+ private var isLoaderTaskFinished = false
57
+ var isEmergencyLaunch = false
58
+ private set
59
+
60
+ /**
61
+ * If UpdatesController.initialize() is not provided with a [ReactApplication], this method
62
+ * can be used to set a [ReactNativeHost] on the class. This is optional, but required in
63
+ * order for `Updates.reload()` and some Updates module events to work.
64
+ * @param reactNativeHost the ReactNativeHost of the application running the Updates module
65
+ */
66
+ fun setReactNativeHost(reactNativeHost: ReactNativeHost) {
67
+ this.reactNativeHost = WeakReference(reactNativeHost)
68
+ }
69
+
70
+ /**
71
+ * Returns the path on disk to the launch asset (JS bundle) file for the React Native host to use.
72
+ * Blocks until the configured timeout runs out, or a new update has been downloaded and is ready
73
+ * to use (whichever comes sooner). ReactNativeHost.getJSBundleFile() should call into this.
74
+ *
75
+ * If this returns null, something has gone wrong and expo-updates has not been able to launch or
76
+ * find an update to use. In (and only in) this case, `getBundleAssetName()` will return a nonnull
77
+ * fallback value to use.
78
+ */
79
+ @get:Synchronized
80
+ val launchAssetFile: String?
81
+ get() {
82
+ while (!isLoaderTaskFinished) {
83
+ try {
84
+ (this as java.lang.Object).wait()
85
+ } catch (e: InterruptedException) {
86
+ Log.e(TAG, "Interrupted while waiting for launch asset file", e)
87
+ }
88
+ }
89
+ return launcher?.launchAssetFile
90
+ }
91
+
92
+ /**
93
+ * Returns the filename of the launch asset (JS bundle) file embedded in the APK bundle, which can
94
+ * be read using `context.getAssets()`. This is only nonnull if `getLaunchAssetFile` is null and
95
+ * should only be used in such a situation. ReactNativeHost.getBundleAssetName() should call into
96
+ * this.
97
+ */
98
+ val bundleAssetName: String?
99
+ get() = launcher?.bundleAssetName
100
+
101
+ /**
102
+ * Returns a map of the locally downloaded assets for the current update. Keys are the remote URLs
103
+ * of the assets and values are local paths. This should be exported by the Updates JS module and
104
+ * can be used by `expo-asset` or a similar module to override React Native's asset resolution and
105
+ * use the locally downloaded assets.
106
+ */
107
+ val localAssetFiles: Map<AssetEntity, String>?
108
+ get() = launcher?.localAssetFiles
109
+
110
+ val isUsingEmbeddedAssets: Boolean
111
+ get() = launcher?.isUsingEmbeddedAssets ?: false
112
+
113
+ val database: UpdatesDatabase
114
+ get() = databaseHolder.database
115
+
116
+ fun releaseDatabase() {
117
+ databaseHolder.releaseDatabase()
118
+ }
119
+
120
+ val updateUrl: Uri?
121
+ get() = updatesConfiguration.updateUrl
122
+
123
+ val launchedUpdate: UpdateEntity?
124
+ get() = launcher?.launchedUpdate
125
+
126
+ val selectionPolicy: SelectionPolicy
127
+ get() = mSelectionPolicy ?: defaultSelectionPolicy
128
+
129
+ // Internal Setters
130
+
131
+ /**
132
+ * For external modules that want to modify the selection policy used at runtime.
133
+ *
134
+ * This method does not provide any guarantees about how long the provided selection policy will
135
+ * persist; sometimes expo-updates will reset the selection policy in situations where it makes
136
+ * sense to have explicit control (e.g. if the developer/user has programmatically fetched an
137
+ * update, expo-updates will reset the selection policy so the new update is launched on th
138
+ * next reload).
139
+ * @param selectionPolicy The SelectionPolicy to use next, until overridden by expo-updates
140
+ */
141
+ fun setNextSelectionPolicy(selectionPolicy: SelectionPolicy?) {
142
+ mSelectionPolicy = selectionPolicy
143
+ }
144
+
145
+ fun resetSelectionPolicyToDefault() {
146
+ mSelectionPolicy = null
147
+ }
148
+
149
+ fun setDefaultSelectionPolicy(selectionPolicy: SelectionPolicy) {
150
+ defaultSelectionPolicy = selectionPolicy
151
+ }
152
+
153
+ fun setLauncher(launcher: Launcher?) {
154
+ this.launcher = launcher
155
+ }
156
+
157
+ /**
158
+ * Starts the update process to launch a previously-loaded update and (if configured to do so)
159
+ * check for a new update from the server. This method should be called as early as possible in
160
+ * the application's lifecycle.
161
+ * @param context the base context of the application, ideally a [ReactApplication]
162
+ */
163
+ @Synchronized
164
+ fun start(context: Context) {
165
+ if (!updatesConfiguration.isEnabled) {
166
+ launcher = NoDatabaseLauncher(context, updatesConfiguration)
167
+ }
168
+ if (updatesConfiguration.updateUrl == null || updatesConfiguration.scopeKey == null) {
169
+ throw AssertionError("expo-updates is enabled, but no valid URL is configured in AndroidManifest.xml. If you are making a release build for the first time, make sure you have run `expo publish` at least once.")
170
+ }
171
+ if (updatesDirectory == null) {
172
+ launcher = NoDatabaseLauncher(context, updatesConfiguration, updatesDirectoryException)
173
+ isEmergencyLaunch = true
174
+ }
175
+
176
+ val databaseLocal = database
177
+ BuildData.ensureBuildDataIsConsistent(updatesConfiguration, databaseLocal)
178
+ releaseDatabase()
179
+
180
+ LoaderTask(
181
+ updatesConfiguration,
182
+ databaseHolder,
183
+ updatesDirectory,
184
+ fileDownloader,
185
+ selectionPolicy,
186
+ object : LoaderTaskCallback {
187
+ override fun onFailure(e: Exception) {
188
+ launcher = NoDatabaseLauncher(context, updatesConfiguration, e)
189
+ isEmergencyLaunch = true
190
+ notifyController()
191
+ }
192
+
193
+ override fun onCachedUpdateLoaded(update: UpdateEntity): Boolean {
194
+ return true
195
+ }
196
+
197
+ override fun onRemoteUpdateManifestLoaded(updateManifest: UpdateManifest) {}
198
+ override fun onSuccess(launcher: Launcher, isUpToDate: Boolean) {
199
+ this@UpdatesController.launcher = launcher
200
+ notifyController()
201
+ }
202
+
203
+ override fun onBackgroundUpdateFinished(
204
+ status: BackgroundUpdateStatus,
205
+ update: UpdateEntity?,
206
+ exception: Exception?
207
+ ) {
208
+ when (status) {
209
+ BackgroundUpdateStatus.ERROR -> {
210
+ if (exception == null) {
211
+ throw AssertionError("Background update with error status must have a nonnull exception object")
212
+ }
213
+ val params = Arguments.createMap()
214
+ params.putString("message", exception.message)
215
+ UpdatesUtils.sendEventToReactNative(reactNativeHost, UPDATE_ERROR_EVENT, params)
216
+ }
217
+ BackgroundUpdateStatus.UPDATE_AVAILABLE -> {
218
+ if (update == null) {
219
+ throw AssertionError("Background update with error status must have a nonnull update object")
220
+ }
221
+ val params = Arguments.createMap()
222
+ params.putString("manifestString", update.manifest.toString())
223
+ UpdatesUtils.sendEventToReactNative(reactNativeHost, UPDATE_AVAILABLE_EVENT, params)
224
+ }
225
+ BackgroundUpdateStatus.NO_UPDATE_AVAILABLE -> {
226
+ UpdatesUtils.sendEventToReactNative(
227
+ reactNativeHost,
228
+ UPDATE_NO_UPDATE_AVAILABLE_EVENT,
229
+ null
230
+ )
231
+ }
232
+ }
233
+ }
234
+ }
235
+ ).start(context)
236
+ }
237
+
238
+ @Synchronized
239
+ private fun notifyController() {
240
+ isLoaderTaskFinished = true
241
+ (this as java.lang.Object).notify()
242
+ }
243
+
244
+ fun runReaper() {
245
+ AsyncTask.execute {
246
+ val databaseLocal = database
247
+ Reaper.reapUnusedUpdates(
248
+ updatesConfiguration,
249
+ databaseLocal,
250
+ updatesDirectory,
251
+ launchedUpdate,
252
+ selectionPolicy
253
+ )
254
+ releaseDatabase()
255
+ }
256
+ }
257
+
258
+ fun relaunchReactApplication(context: Context, callback: LauncherCallback) {
259
+ val host = reactNativeHost?.get()
260
+ if (host == null) {
261
+ callback.onFailure(Exception("Could not reload application. Ensure you have passed the correct instance of ReactApplication into UpdatesController.initialize()."))
262
+ return
263
+ }
264
+
265
+ val oldLaunchAssetFile = launcher!!.launchAssetFile
266
+
267
+ val databaseLocal = database
268
+ val newLauncher = DatabaseLauncher(
269
+ updatesConfiguration,
270
+ updatesDirectory!!,
271
+ fileDownloader,
272
+ selectionPolicy
273
+ )
274
+ newLauncher.launch(
275
+ databaseLocal, context,
276
+ object : LauncherCallback {
277
+ override fun onFailure(e: Exception) {
278
+ callback.onFailure(e)
279
+ }
280
+
281
+ override fun onSuccess() {
282
+ launcher = newLauncher
283
+ releaseDatabase()
284
+
285
+ val instanceManager = host.reactInstanceManager
286
+
287
+ val newLaunchAssetFile = launcher!!.launchAssetFile
288
+ if (newLaunchAssetFile != null && newLaunchAssetFile != oldLaunchAssetFile) {
289
+ // Unfortunately, even though RN exposes a way to reload an application,
290
+ // it assumes that the JS bundle will stay at the same location throughout
291
+ // the entire lifecycle of the app. Since we need to change the location of
292
+ // the bundle, we need to use reflection to set an otherwise inaccessible
293
+ // field of the ReactInstanceManager.
294
+ try {
295
+ val newJSBundleLoader = JSBundleLoader.createFileLoader(newLaunchAssetFile)
296
+ val jsBundleLoaderField = instanceManager.javaClass.getDeclaredField("mBundleLoader")
297
+ jsBundleLoaderField.isAccessible = true
298
+ jsBundleLoaderField[instanceManager] = newJSBundleLoader
299
+ } catch (e: Exception) {
300
+ Log.e(TAG, "Could not reset JSBundleLoader in ReactInstanceManager", e)
301
+ }
302
+ }
303
+ callback.onSuccess()
304
+ val handler = Handler(Looper.getMainLooper())
305
+ handler.post { instanceManager.recreateReactContextInBackground() }
306
+ runReaper()
307
+ }
308
+ }
309
+ )
310
+ }
311
+
312
+ companion object {
313
+ private val TAG = UpdatesController::class.java.simpleName
314
+
315
+ private const val UPDATE_AVAILABLE_EVENT = "updateAvailable"
316
+ private const val UPDATE_NO_UPDATE_AVAILABLE_EVENT = "noUpdateAvailable"
317
+ private const val UPDATE_ERROR_EVENT = "error"
318
+
319
+ private var singletonInstance: UpdatesController? = null
320
+ @JvmStatic val instance: UpdatesController
321
+ get() {
322
+ return checkNotNull(singletonInstance) { "UpdatesController.instance was called before the module was initialized" }
323
+ }
324
+
325
+ @JvmStatic fun initializeWithoutStarting(context: Context) {
326
+ if (singletonInstance == null) {
327
+ val updatesConfiguration = UpdatesConfiguration().loadValuesFromMetadata(context)
328
+ singletonInstance = UpdatesController(context, updatesConfiguration)
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Initializes the UpdatesController singleton. This should be called as early as possible in the
334
+ * application's lifecycle.
335
+ * @param context the base context of the application, ideally a [ReactApplication]
336
+ */
337
+ @JvmStatic fun initialize(context: Context) {
338
+ initializeWithoutStarting(context)
339
+ singletonInstance!!.start(context)
340
+ }
341
+
342
+ /**
343
+ * Initializes the UpdatesController singleton. This should be called as early as possible in the
344
+ * application's lifecycle. Use this method to set or override configuration values at runtime
345
+ * rather than from AndroidManifest.xml.
346
+ * @param context the base context of the application, ideally a [ReactApplication]
347
+ */
348
+ @JvmStatic fun initialize(context: Context, configuration: Map<String, Any>) {
349
+ if (singletonInstance == null) {
350
+ val updatesConfiguration = UpdatesConfiguration()
351
+ .loadValuesFromMetadata(context)
352
+ .loadValuesFromMap(configuration)
353
+ singletonInstance = UpdatesController(context, updatesConfiguration)
354
+ singletonInstance!!.start(context)
355
+ }
356
+ }
357
+ }
358
+
359
+ init {
360
+ try {
361
+ updatesDirectory = UpdatesUtils.getOrCreateUpdatesDirectory(context)
362
+ } catch (e: Exception) {
363
+ updatesDirectoryException = e
364
+ updatesDirectory = null
365
+ }
366
+ }
367
+ }