expo-updates 0.12.0 → 0.13.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 (26) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/updates/UpdatesController.kt +1 -1
  4. package/android/src/main/java/expo/modules/updates/UpdatesDevLauncherController.kt +2 -1
  5. package/android/src/main/java/expo/modules/updates/UpdatesInterface.kt +1 -0
  6. package/android/src/main/java/expo/modules/updates/UpdatesModule.kt +8 -4
  7. package/android/src/main/java/expo/modules/updates/UpdatesService.kt +4 -0
  8. package/android/src/main/java/expo/modules/updates/loader/FileDownloader.kt +23 -4
  9. package/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt +1 -1
  10. package/android/src/main/java/expo/modules/updates/loader/RemoteLoader.kt +8 -5
  11. package/ios/EXUpdates/AppLauncher/EXUpdatesAppLauncherWithDatabase.m +2 -2
  12. package/ios/EXUpdates/AppLoader/EXUpdatesAppLoader+Private.h +1 -0
  13. package/ios/EXUpdates/AppLoader/EXUpdatesAppLoader.h +1 -0
  14. package/ios/EXUpdates/AppLoader/EXUpdatesAppLoader.m +2 -0
  15. package/ios/EXUpdates/AppLoader/EXUpdatesAppLoaderTask.m +4 -2
  16. package/ios/EXUpdates/AppLoader/EXUpdatesFileDownloader.h +10 -0
  17. package/ios/EXUpdates/AppLoader/EXUpdatesFileDownloader.m +21 -0
  18. package/ios/EXUpdates/AppLoader/EXUpdatesRemoteAppLoader.m +18 -7
  19. package/ios/EXUpdates/EXUpdatesAppController.m +1 -1
  20. package/ios/EXUpdates/EXUpdatesDevLauncherController.m +1 -1
  21. package/ios/EXUpdates/EXUpdatesModule.m +5 -6
  22. package/ios/EXUpdates/EXUpdatesService.h +1 -0
  23. package/ios/EXUpdates/EXUpdatesService.m +6 -0
  24. package/ios/EXUpdates/ReactDelegateHandler/ExpoUpdatesAppDelegateSubscriber.swift +1 -1
  25. package/ios/Tests/EXUpdatesFileDownloaderTests.m +47 -0
  26. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -10,6 +10,17 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.13.0 — 2022-04-21
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Fix asset hash validation. ([#17152](https://github.com/expo/expo/pull/17152) by [@wschurman](https://github.com/wschurman))
18
+
19
+ ### 💡 Others
20
+
21
+ - Add current and embedded update headers to manifest requests. ([#17033](https://github.com/expo/expo/pull/17033) by [@esamelson](https://github.com/esamelson))
22
+ - Fix return value in AppDelegateSubscriber (used with expo-dev-client). ([#17111](https://github.com/expo/expo/pull/17111) by [@esamelson](https://github.com/esamelson))
23
+
13
24
  ## 0.12.0 — 2022-04-18
14
25
 
15
26
  ### 🛠 Breaking changes
@@ -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.12.0'
7
+ version = '0.13.0'
8
8
 
9
9
  apply from: "../scripts/create-manifest-android.gradle"
10
10
 
@@ -77,7 +77,7 @@ android {
77
77
  minSdkVersion safeExtGet("minSdkVersion", 21)
78
78
  targetSdkVersion safeExtGet("targetSdkVersion", 31)
79
79
  versionCode 31
80
- versionName '0.12.0'
80
+ versionName '0.13.0'
81
81
  consumerProguardFiles("proguard-rules.pro")
82
82
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
83
83
  // uncomment below to export the database schema when making changes
@@ -307,7 +307,7 @@ class UpdatesController private constructor(
307
307
  }
308
308
  remoteLoadStatus = ErrorRecoveryDelegate.RemoteLoadStatus.NEW_UPDATE_LOADING
309
309
  val database = getDatabase()
310
- val remoteLoader = RemoteLoader(context, updatesConfiguration, database, fileDownloader, updatesDirectory)
310
+ val remoteLoader = RemoteLoader(context, updatesConfiguration, database, fileDownloader, updatesDirectory, launchedUpdate)
311
311
  remoteLoader.start(object : Loader.LoaderCallback {
312
312
  override fun onFailure(e: Exception) {
313
313
  setRemoteLoadStatus(ErrorRecoveryDelegate.RemoteLoadStatus.IDLE)
@@ -52,7 +52,8 @@ class UpdatesDevLauncherController : UpdatesInterface {
52
52
  updatesConfiguration,
53
53
  databaseHolder.database,
54
54
  controller.fileDownloader,
55
- controller.updatesDirectory
55
+ controller.updatesDirectory,
56
+ null
56
57
  )
57
58
  loader.start(object : Loader.LoaderCallback {
58
59
  override fun onFailure(e: Exception) {
@@ -23,6 +23,7 @@ interface UpdatesInterface {
23
23
  val isEmergencyLaunch: Boolean
24
24
  val isUsingEmbeddedAssets: Boolean
25
25
  fun canRelaunch(): Boolean
26
+ val embeddedUpdate: UpdateEntity?
26
27
  val launchedUpdate: UpdateEntity?
27
28
  val localAssetFiles: Map<AssetEntity, String>?
28
29
 
@@ -12,10 +12,10 @@ import expo.modules.core.interfaces.ExpoMethod
12
12
  import expo.modules.updates.db.entity.AssetEntity
13
13
  import expo.modules.updates.db.entity.UpdateEntity
14
14
  import expo.modules.updates.launcher.Launcher.LauncherCallback
15
+ import expo.modules.updates.loader.FileDownloader
15
16
  import expo.modules.updates.loader.FileDownloader.ManifestDownloadCallback
16
17
  import expo.modules.updates.loader.Loader
17
18
  import expo.modules.updates.loader.RemoteLoader
18
- import expo.modules.updates.manifest.ManifestMetadata
19
19
  import expo.modules.updates.manifest.UpdateManifest
20
20
 
21
21
  // these unused imports must stay because of versioning
@@ -127,8 +127,11 @@ class UpdatesModule(
127
127
  return
128
128
  }
129
129
  val databaseHolder = updatesServiceLocal.databaseHolder
130
- val extraHeaders = ManifestMetadata.getServerDefinedHeaders(
131
- databaseHolder.database, updatesServiceLocal.configuration
130
+ val extraHeaders = FileDownloader.getExtraHeaders(
131
+ databaseHolder.database,
132
+ updatesServiceLocal.configuration,
133
+ updatesServiceLocal.launchedUpdate,
134
+ updatesServiceLocal.embeddedUpdate
132
135
  )
133
136
  databaseHolder.releaseDatabase()
134
137
  updatesServiceLocal.fileDownloader.downloadManifest(
@@ -194,7 +197,8 @@ class UpdatesModule(
194
197
  updatesServiceLocal.configuration,
195
198
  databaseHolder.database,
196
199
  updatesServiceLocal.fileDownloader,
197
- updatesServiceLocal.directory
200
+ updatesServiceLocal.directory,
201
+ updatesServiceLocal.launchedUpdate
198
202
  )
199
203
  .start(
200
204
  object : Loader.LoaderCallback {
@@ -14,6 +14,8 @@ import java.io.File
14
14
  /* ktlint-disable no-unused-imports */
15
15
  import expo.modules.updates.UpdatesConfiguration
16
16
  import expo.modules.updates.UpdatesController
17
+ import expo.modules.updates.manifest.EmbeddedManifest
18
+
17
19
  /* ktlint-enable no-unused-imports */
18
20
 
19
21
  open class UpdatesService(protected var context: Context) : InternalModule, UpdatesInterface {
@@ -40,6 +42,8 @@ open class UpdatesService(protected var context: Context) : InternalModule, Upda
40
42
  return configuration.isEnabled && launchedUpdate != null
41
43
  }
42
44
 
45
+ override val embeddedUpdate: UpdateEntity?
46
+ get() = EmbeddedManifest.get(context, configuration)?.updateEntity
43
47
  override val launchedUpdate: UpdateEntity?
44
48
  get() = UpdatesController.instance.launchedUpdate
45
49
  override val localAssetFiles: Map<AssetEntity, String>?
@@ -7,9 +7,6 @@ import expo.modules.updates.UpdatesConfiguration
7
7
  import expo.modules.updates.UpdatesUtils
8
8
  import expo.modules.updates.db.entity.AssetEntity
9
9
  import expo.modules.updates.launcher.NoDatabaseLauncher
10
- import expo.modules.updates.manifest.ManifestFactory
11
- import expo.modules.updates.manifest.ManifestHeaderData
12
- import expo.modules.updates.manifest.UpdateManifest
13
10
  import expo.modules.updates.selectionpolicy.SelectionPolicies
14
11
  import okhttp3.*
15
12
  import org.json.JSONArray
@@ -27,6 +24,9 @@ import expo.modules.easclient.EASClientID
27
24
  import okhttp3.Headers.Companion.toHeaders
28
25
  import expo.modules.jsonutils.getNullable
29
26
  import expo.modules.updates.codesigning.ValidationResult
27
+ import expo.modules.updates.db.UpdatesDatabase
28
+ import expo.modules.updates.db.entity.UpdateEntity
29
+ import expo.modules.updates.manifest.*
30
30
  import java.security.cert.CertificateException
31
31
 
32
32
  open class FileDownloader(private val client: OkHttpClient) {
@@ -331,7 +331,7 @@ open class FileDownloader(private val client: OkHttpClient) {
331
331
  override fun onSuccess(file: File, hash: ByteArray) {
332
332
  // base64url - https://datatracker.ietf.org/doc/html/rfc4648#section-5
333
333
  val hashBase64String = Base64.encodeToString(hash, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
334
- val expectedAssetHash = asset.expectedHash?.toLowerCase(Locale.ROOT)
334
+ val expectedAssetHash = asset.expectedHash
335
335
  if (expectedAssetHash != null && expectedAssetHash != hashBase64String) {
336
336
  callback.onFailure(Exception("Asset hash invalid: ${asset.key}; expectedHash: $expectedAssetHash; actualHash: $hashBase64String"), asset)
337
337
  return
@@ -568,5 +568,24 @@ open class FileDownloader(private val client: OkHttpClient) {
568
568
  private fun getCacheDirectory(context: Context): File {
569
569
  return File(context.cacheDir, "okhttp")
570
570
  }
571
+
572
+ fun getExtraHeaders(
573
+ database: UpdatesDatabase,
574
+ configuration: UpdatesConfiguration,
575
+ launchedUpdate: UpdateEntity?,
576
+ embeddedUpdate: UpdateEntity?
577
+ ): JSONObject {
578
+ val extraHeaders =
579
+ ManifestMetadata.getServerDefinedHeaders(database, configuration) ?: JSONObject()
580
+
581
+ launchedUpdate?.let {
582
+ extraHeaders.put("Expo-Current-Update-ID", it.id.toString().lowercase())
583
+ }
584
+ embeddedUpdate?.let {
585
+ extraHeaders.put("Expo-Embedded-Update-ID", it.id.toString().lowercase())
586
+ }
587
+
588
+ return extraHeaders
589
+ }
571
590
  }
572
591
  }
@@ -274,7 +274,7 @@ class LoaderTask(
274
274
  private fun launchRemoteUpdateInBackground(context: Context, remoteUpdateCallback: Callback) {
275
275
  AsyncTask.execute {
276
276
  val database = databaseHolder.database
277
- RemoteLoader(context, configuration, database, fileDownloader, directory)
277
+ RemoteLoader(context, configuration, database, fileDownloader, directory, candidateLauncher?.launchedUpdate)
278
278
  .start(object : LoaderCallback {
279
279
  override fun onFailure(e: Exception) {
280
280
  databaseHolder.releaseDatabase()
@@ -4,9 +4,9 @@ import android.content.Context
4
4
  import expo.modules.updates.UpdatesConfiguration
5
5
  import expo.modules.updates.db.UpdatesDatabase
6
6
  import expo.modules.updates.db.entity.AssetEntity
7
+ import expo.modules.updates.db.entity.UpdateEntity
7
8
  import expo.modules.updates.loader.FileDownloader.AssetDownloadCallback
8
9
  import expo.modules.updates.loader.FileDownloader.ManifestDownloadCallback
9
- import expo.modules.updates.manifest.ManifestMetadata
10
10
  import java.io.File
11
11
 
12
12
  /**
@@ -22,15 +22,17 @@ class RemoteLoader internal constructor(
22
22
  database: UpdatesDatabase,
23
23
  private val mFileDownloader: FileDownloader,
24
24
  updatesDirectory: File?,
25
- loaderFiles: LoaderFiles
25
+ private val launchedUpdate: UpdateEntity?,
26
+ private val loaderFiles: LoaderFiles
26
27
  ) : Loader(context, configuration, database, updatesDirectory, loaderFiles) {
27
28
  constructor(
28
29
  context: Context,
29
30
  configuration: UpdatesConfiguration,
30
31
  database: UpdatesDatabase,
31
32
  fileDownloader: FileDownloader,
32
- updatesDirectory: File?
33
- ) : this(context, configuration, database, fileDownloader, updatesDirectory, LoaderFiles())
33
+ updatesDirectory: File?,
34
+ launchedUpdate: UpdateEntity?
35
+ ) : this(context, configuration, database, fileDownloader, updatesDirectory, launchedUpdate, LoaderFiles())
34
36
 
35
37
  override fun loadManifest(
36
38
  context: Context,
@@ -38,7 +40,8 @@ class RemoteLoader internal constructor(
38
40
  configuration: UpdatesConfiguration,
39
41
  callback: ManifestDownloadCallback
40
42
  ) {
41
- val extraHeaders = ManifestMetadata.getServerDefinedHeaders(database, configuration)
43
+ val embeddedUpdate = loaderFiles.readEmbeddedManifest(context, configuration)?.updateEntity
44
+ val extraHeaders = FileDownloader.getExtraHeaders(database, configuration, launchedUpdate, embeddedUpdate)
42
45
  mFileDownloader.downloadManifest(configuration, extraHeaders, context, callback)
43
46
  }
44
47
 
@@ -295,10 +295,10 @@ static NSString * const EXUpdatesAppLauncherErrorDomain = @"AppLauncher";
295
295
  successBlock:^(NSData *data, NSURLResponse *response) {
296
296
  dispatch_async(self->_launcherQueue, ^{
297
297
  NSString *hashBase64String = [EXUpdatesUtils base64UrlEncodedSHA256WithData:data];
298
- if (asset.expectedHash && ![asset.expectedHash.lowercaseString isEqualToString:hashBase64String.lowercaseString]) {
298
+ if (asset.expectedHash && ![asset.expectedHash isEqualToString:hashBase64String]) {
299
299
  completion([NSError errorWithDomain:EXUpdatesAppLauncherErrorDomain
300
300
  code:1016
301
- userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Asset hash invalid: %@; expectedHash: %@; actualHash: %@", asset.key, asset.expectedHash.lowercaseString, hashBase64String.lowercaseString]}],
301
+ userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Asset hash invalid: %@; expectedHash: %@; actualHash: %@", asset.key, asset.expectedHash, hashBase64String]}],
302
302
  asset,
303
303
  assetLocalUrl);
304
304
  return;
@@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
12
12
  @property (nonatomic, strong) EXUpdatesConfig *config;
13
13
  @property (nonatomic, strong) EXUpdatesDatabase *database;
14
14
  @property (nonatomic, strong) NSURL *directory;
15
+ @property (nonatomic, strong, nullable) EXUpdatesUpdate *launchedUpdate;
15
16
  @property (nonatomic, strong) EXUpdatesUpdate *updateManifest;
16
17
  @property (nonatomic, copy) EXUpdatesAppLoaderManifestBlock manifestBlock;
17
18
  @property (nonatomic, copy) EXUpdatesAppLoaderAssetBlock assetBlock;
@@ -17,6 +17,7 @@ typedef void (^EXUpdatesAppLoaderErrorBlock)(NSError *error);
17
17
  - (instancetype)initWithConfig:(EXUpdatesConfig *)config
18
18
  database:(EXUpdatesDatabase *)database
19
19
  directory:(NSURL *)directory
20
+ launchedUpdate:(nullable EXUpdatesUpdate *)launchedUpdate
20
21
  completionQueue:(dispatch_queue_t)completionQueue;
21
22
 
22
23
  /**
@@ -28,6 +28,7 @@ static NSString * const EXUpdatesAppLoaderErrorDomain = @"EXUpdatesAppLoader";
28
28
  - (instancetype)initWithConfig:(EXUpdatesConfig *)config
29
29
  database:(EXUpdatesDatabase *)database
30
30
  directory:(NSURL *)directory
31
+ launchedUpdate:(nullable EXUpdatesUpdate *)launchedUpdate
31
32
  completionQueue:(dispatch_queue_t)completionQueue
32
33
  {
33
34
  if (self = [super init]) {
@@ -39,6 +40,7 @@ static NSString * const EXUpdatesAppLoaderErrorDomain = @"EXUpdatesAppLoader";
39
40
  _config = config;
40
41
  _database = database;
41
42
  _directory = directory;
43
+ _launchedUpdate = launchedUpdate;
42
44
  _completionQueue = completionQueue;
43
45
  }
44
46
  return self;
@@ -210,7 +210,9 @@ static NSString * const EXUpdatesAppLoaderTaskErrorDomain = @"EXUpdatesAppLoader
210
210
  [self->_selectionPolicy shouldLoadNewUpdate:[EXUpdatesEmbeddedAppLoader embeddedManifestWithConfig:self->_config database:self->_database]
211
211
  withLaunchedUpdate:launchableUpdate
212
212
  filters:manifestFilters]) {
213
- self->_embeddedAppLoader = [[EXUpdatesEmbeddedAppLoader alloc] initWithConfig:self->_config database:self->_database directory:self->_directory completionQueue:self->_loaderTaskQueue];
213
+ // launchedUpdate is nil because we don't yet have one, and it doesn't matter as we won't
214
+ // be sending an HTTP request from EXUpdatesEmbeddedAppLoader
215
+ self->_embeddedAppLoader = [[EXUpdatesEmbeddedAppLoader alloc] initWithConfig:self->_config database:self->_database directory:self->_directory launchedUpdate:nil completionQueue:self->_loaderTaskQueue];
214
216
  [self->_embeddedAppLoader loadUpdateFromEmbeddedManifestWithCallback:^BOOL(EXUpdatesUpdate * _Nonnull update) {
215
217
  // we already checked using selection policy, so we don't need to check again
216
218
  return YES;
@@ -238,7 +240,7 @@ static NSString * const EXUpdatesAppLoaderTaskErrorDomain = @"EXUpdatesAppLoader
238
240
 
239
241
  - (void)_loadRemoteUpdateWithCompletion:(void (^)(NSError * _Nullable error, EXUpdatesUpdate * _Nullable update))completion
240
242
  {
241
- _remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:_config database:_database directory:_directory completionQueue:_loaderTaskQueue];
243
+ _remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:_config database:_database directory:_directory launchedUpdate:_candidateLauncher.launchedUpdate completionQueue:_loaderTaskQueue];
242
244
  [_remoteAppLoader loadUpdateFromUrl:_config.updateUrl onManifest:^BOOL(EXUpdatesUpdate * _Nonnull update) {
243
245
  if ([self->_selectionPolicy shouldLoadNewUpdate:update withLaunchedUpdate:self->_candidateLauncher.launchedUpdate filters:update.manifestFilters]) {
244
246
  self->_isUpToDate = NO;
@@ -1,6 +1,7 @@
1
1
  // Copyright © 2019 650 Industries. All rights reserved.
2
2
 
3
3
  #import <EXUpdates/EXUpdatesConfig.h>
4
+ #import <EXUpdates/EXUpdatesDatabase.h>
4
5
  #import <EXUpdates/EXUpdatesUpdate.h>
5
6
 
6
7
  NS_ASSUME_NONNULL_BEGIN
@@ -34,6 +35,15 @@ typedef void (^EXUpdatesFileDownloaderErrorBlock)(NSError *error);
34
35
 
35
36
  + (dispatch_queue_t)assetFilesQueue;
36
37
 
38
+ /**
39
+ * Get extra (stateful) headers to pass into `downloadManifestFromURL:`
40
+ * Must be called on the database queue
41
+ */
42
+ + (NSDictionary *)extraHeadersWithDatabase:(EXUpdatesDatabase *)database
43
+ config:(EXUpdatesConfig *)config
44
+ launchedUpdate:(nullable EXUpdatesUpdate *)launchedUpdate
45
+ embeddedUpdate:(nullable EXUpdatesUpdate *)embeddedUpdate;
46
+
37
47
  /**
38
48
  * For test purposes; shouldn't be needed in application code
39
49
  */
@@ -670,6 +670,27 @@ certificateChainFromManifestResponse:(nullable NSString *)certificateChainFromMa
670
670
  }
671
671
  }
672
672
 
673
+ + (NSDictionary *)extraHeadersWithDatabase:(EXUpdatesDatabase *)database
674
+ config:(EXUpdatesConfig *)config
675
+ launchedUpdate:(nullable EXUpdatesUpdate *)launchedUpdate
676
+ embeddedUpdate:(nullable EXUpdatesUpdate *)embeddedUpdate
677
+ {
678
+ NSError *headersError;
679
+ NSMutableDictionary *extraHeaders = [database serverDefinedHeadersWithScopeKey:config.scopeKey error:&headersError].mutableCopy ?: [NSMutableDictionary new];
680
+ if (headersError) {
681
+ NSLog(@"Error selecting serverDefinedHeaders from database: %@", headersError.localizedDescription);
682
+ }
683
+
684
+ if (launchedUpdate) {
685
+ extraHeaders[@"Expo-Current-Update-ID"] = launchedUpdate.updateId.UUIDString.lowercaseString;
686
+ }
687
+ if (embeddedUpdate) {
688
+ extraHeaders[@"Expo-Embedded-Update-ID"] = embeddedUpdate.updateId.UUIDString.lowercaseString;
689
+ }
690
+
691
+ return extraHeaders.copy;
692
+ }
693
+
673
694
  #pragma mark - NSURLSessionTaskDelegate
674
695
 
675
696
  - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler
@@ -2,7 +2,9 @@
2
2
 
3
3
  #import <EXUpdates/EXUpdatesRemoteAppLoader.h>
4
4
  #import <EXUpdates/EXUpdatesCrypto.h>
5
+ #import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
5
6
  #import <EXUpdates/EXUpdatesFileDownloader.h>
7
+ #import <EXUpdates/EXUpdatesUtils.h>
6
8
  #import <ExpoModulesCore/EXUtilities.h>
7
9
 
8
10
  NS_ASSUME_NONNULL_BEGIN
@@ -22,9 +24,10 @@ static NSString * const EXUpdatesRemoteAppLoaderErrorDomain = @"EXUpdatesRemoteA
22
24
  - (instancetype)initWithConfig:(EXUpdatesConfig *)config
23
25
  database:(EXUpdatesDatabase *)database
24
26
  directory:(NSURL *)directory
27
+ launchedUpdate:(nullable EXUpdatesUpdate *)launchedUpdate
25
28
  completionQueue:(dispatch_queue_t)completionQueue
26
29
  {
27
- if (self = [super initWithConfig:config database:database directory:directory completionQueue:completionQueue]) {
30
+ if (self = [super initWithConfig:config database:database directory:directory launchedUpdate:launchedUpdate completionQueue:completionQueue]) {
28
31
  _downloader = [[EXUpdatesFileDownloader alloc] initWithUpdatesConfig:self.config];
29
32
  _completionQueue = completionQueue;
30
33
  }
@@ -65,11 +68,11 @@ static NSString * const EXUpdatesRemoteAppLoaderErrorDomain = @"EXUpdatesRemoteA
65
68
  };
66
69
 
67
70
  dispatch_async(self.database.databaseQueue, ^{
68
- NSError *headersError;
69
- NSDictionary *extraHeaders = [self.database serverDefinedHeadersWithScopeKey:self.config.scopeKey error:&headersError];
70
- if (headersError) {
71
- NSLog(@"Error selecting serverDefinedHeaders from database: %@", headersError.localizedDescription);
72
- }
71
+ EXUpdatesUpdate *embeddedUpdate = [EXUpdatesEmbeddedAppLoader embeddedManifestWithConfig:self.config database:self.database];
72
+ NSDictionary *extraHeaders = [EXUpdatesFileDownloader extraHeadersWithDatabase:self.database
73
+ config:self.config
74
+ launchedUpdate:self.launchedUpdate
75
+ embeddedUpdate:embeddedUpdate];
73
76
  [self->_downloader downloadManifestFromURL:url withDatabase:self.database extraHeaders:extraHeaders successBlock:^(EXUpdatesUpdate *update) {
74
77
  self->_remoteUpdate = update;
75
78
  [self startLoadingFromManifest:update];
@@ -102,7 +105,15 @@ static NSString * const EXUpdatesRemoteAppLoaderErrorDomain = @"EXUpdatesRemoteA
102
105
  extraHeaders:asset.extraRequestHeaders ?: @{}
103
106
  successBlock:^(NSData *data, NSURLResponse *response) {
104
107
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
105
- [self handleAssetDownloadWithData:data response:response asset:asset];
108
+ NSString *hashBase64String = [EXUpdatesUtils base64UrlEncodedSHA256WithData:data];
109
+ if (asset.expectedHash && ![asset.expectedHash isEqualToString:hashBase64String]) {
110
+ NSError *error = [NSError errorWithDomain:EXUpdatesRemoteAppLoaderErrorDomain
111
+ code:1016
112
+ userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Asset hash invalid: %@; expectedHash: %@; actualHash: %@", asset.key, asset.expectedHash, hashBase64String]}];
113
+ [self handleAssetDownloadWithError:error asset:asset];
114
+ } else {
115
+ [self handleAssetDownloadWithData:data response:response asset:asset];
116
+ }
106
117
  });
107
118
  }
108
119
  errorBlock:^(NSError *error) {
@@ -367,7 +367,7 @@ static NSString * const EXUpdatesErrorEventName = @"error";
367
367
  }
368
368
 
369
369
  _remoteLoadStatus = EXUpdatesRemoteLoadStatusLoading;
370
- EXUpdatesAppLoader *remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:_config database:_database directory:_updatesDirectory completionQueue:_controllerQueue];
370
+ EXUpdatesAppLoader *remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:_config database:_database directory:_updatesDirectory launchedUpdate:self.launchedUpdate completionQueue:_controllerQueue];
371
371
  [remoteAppLoader loadUpdateFromUrl:_config.updateUrl onManifest:^BOOL(EXUpdatesUpdate *update) {
372
372
  return [self->_selectionPolicy shouldLoadNewUpdate:update withLaunchedUpdate:self.launchedUpdate filters:update.manifestFilters];
373
373
  } asset:^(EXUpdatesAsset *asset, NSUInteger successfulAssetCount, NSUInteger failedAssetCount, NSUInteger totalAssetCount) {
@@ -95,7 +95,7 @@ typedef NS_ENUM(NSInteger, EXUpdatesDevLauncherErrorCode) {
95
95
  [self _setDevelopmentSelectionPolicy];
96
96
  [controller setConfigurationInternal:updatesConfiguration];
97
97
 
98
- EXUpdatesRemoteAppLoader *loader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:updatesConfiguration database:controller.database directory:controller.updatesDirectory completionQueue:controller.controllerQueue];
98
+ EXUpdatesRemoteAppLoader *loader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:updatesConfiguration database:controller.database directory:controller.updatesDirectory launchedUpdate:nil completionQueue:controller.controllerQueue];
99
99
  [loader loadUpdateFromUrl:updatesConfiguration.updateUrl onManifest:^BOOL(EXUpdatesUpdate * _Nonnull update) {
100
100
  return manifestBlock(update.manifest.rawManifestJSON);
101
101
  } asset:^(EXUpdatesAsset * _Nonnull asset, NSUInteger successfulAssetCount, NSUInteger failedAssetCount, NSUInteger totalAssetCount) {
@@ -104,11 +104,10 @@ EX_EXPORT_METHOD_AS(checkForUpdateAsync,
104
104
 
105
105
  __block NSDictionary *extraHeaders;
106
106
  dispatch_sync(_updatesService.database.databaseQueue, ^{
107
- NSError *error;
108
- extraHeaders = [self->_updatesService.database serverDefinedHeadersWithScopeKey:self->_updatesService.config.scopeKey error:&error];
109
- if (error) {
110
- NSLog(@"Error selecting serverDefinedHeaders from database: %@", error.localizedDescription);
111
- }
107
+ extraHeaders = [EXUpdatesFileDownloader extraHeadersWithDatabase:self->_updatesService.database
108
+ config:self->_updatesService.config
109
+ launchedUpdate:self->_updatesService.launchedUpdate
110
+ embeddedUpdate:self->_updatesService.embeddedUpdate];
112
111
  });
113
112
 
114
113
  EXUpdatesFileDownloader *fileDownloader = [[EXUpdatesFileDownloader alloc] initWithUpdatesConfig:_updatesService.config];
@@ -146,7 +145,7 @@ EX_EXPORT_METHOD_AS(fetchUpdateAsync,
146
145
  return;
147
146
  }
148
147
 
149
- EXUpdatesRemoteAppLoader *remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:_updatesService.config database:_updatesService.database directory:_updatesService.directory completionQueue:self.methodQueue];
148
+ EXUpdatesRemoteAppLoader *remoteAppLoader = [[EXUpdatesRemoteAppLoader alloc] initWithConfig:_updatesService.config database:_updatesService.database directory:_updatesService.directory launchedUpdate:_updatesService.launchedUpdate completionQueue:self.methodQueue];
150
149
  [remoteAppLoader loadUpdateFromUrl:_updatesService.config.updateUrl onManifest:^BOOL(EXUpdatesUpdate * _Nonnull update) {
151
150
  return [self->_updatesService.selectionPolicy shouldLoadNewUpdate:update withLaunchedUpdate:self->_updatesService.launchedUpdate filters:update.manifestFilters];
152
151
  } asset:^(EXUpdatesAsset *asset, NSUInteger successfulAssetCount, NSUInteger failedAssetCount, NSUInteger totalAssetCount) {
@@ -17,6 +17,7 @@ typedef void (^EXUpdatesAppRelaunchCompletionBlock)(BOOL success);
17
17
  @property (nonatomic, readonly) EXUpdatesSelectionPolicy *selectionPolicy;
18
18
  @property (nonatomic, readonly) NSURL *directory;
19
19
 
20
+ @property (nullable, nonatomic, readonly, strong) EXUpdatesUpdate *embeddedUpdate;
20
21
  @property (nullable, nonatomic, readonly, strong) EXUpdatesUpdate *launchedUpdate;
21
22
  @property (nullable, nonatomic, readonly, strong) NSDictionary *assetFilesMap;
22
23
  @property (nonatomic, readonly, assign) BOOL isUsingEmbeddedAssets;
@@ -1,6 +1,7 @@
1
1
  // Copyright 2020-present 650 Industries. All rights reserved.
2
2
 
3
3
  #import <EXUpdates/EXUpdatesAppController.h>
4
+ #import <EXUpdates/EXUpdatesEmbeddedAppLoader.h>
4
5
  #import <EXUpdates/EXUpdatesService.h>
5
6
  #import <ExpoModulesCore/EXUtilities.h>
6
7
 
@@ -35,6 +36,11 @@ EX_REGISTER_MODULE();
35
36
  return EXUpdatesAppController.sharedInstance.updatesDirectory;
36
37
  }
37
38
 
39
+ - (nullable EXUpdatesUpdate *)embeddedUpdate
40
+ {
41
+ return [EXUpdatesEmbeddedAppLoader embeddedManifestWithConfig:self.config database:self.database];
42
+ }
43
+
38
44
  - (nullable EXUpdatesUpdate *)launchedUpdate
39
45
  {
40
46
  return EXUpdatesAppController.sharedInstance.launchedUpdate;
@@ -8,6 +8,6 @@ public class ExpoUpdatesAppDelegateSubscriber: ExpoAppDelegateSubscriber {
8
8
  if EXAppDefines.APP_DEBUG {
9
9
  EXUpdatesControllerRegistry.sharedInstance().controller = EXUpdatesDevLauncherController.sharedInstance()
10
10
  }
11
- return false
11
+ return true
12
12
  }
13
13
  }
@@ -4,6 +4,7 @@
4
4
 
5
5
  #import <EXUpdates/EXUpdatesConfig.h>
6
6
  #import <EXUpdates/EXUpdatesFileDownloader.h>
7
+ #import <EXUpdates/EXUpdatesUpdate.h>
7
8
 
8
9
  @interface EXUpdatesFileDownloaderTests : XCTestCase
9
10
 
@@ -79,6 +80,52 @@
79
80
  XCTAssertEqualObjects(@"custom", [actual valueForHTTPHeaderField:@"expo-updates-environment"]);
80
81
  }
81
82
 
83
+ #pragma clang diagnostic push
84
+ #pragma clang diagnostic ignored "-Wnonnull"
85
+ - (void)testGetExtraHeaders
86
+ {
87
+ NSString *launchedUpdateUUIDString = @"7c1d2bd0-f88b-454d-998c-7fa92a924dbf";
88
+ EXUpdatesUpdate *launchedUpdate = [EXUpdatesUpdate updateWithId:[[NSUUID alloc] initWithUUIDString:launchedUpdateUUIDString]
89
+ scopeKey:@"test"
90
+ commitTime:[NSDate date]
91
+ runtimeVersion:@"1.0"
92
+ manifest:nil
93
+ status:0
94
+ keep:YES
95
+ config:nil
96
+ database:nil];
97
+ NSString *embeddedUpdateUUIDString = @"9433b1ed-4006-46b8-8aa7-fdc7eeb203fd";
98
+ EXUpdatesUpdate *embeddedUpdate = [EXUpdatesUpdate updateWithId:[[NSUUID alloc] initWithUUIDString:embeddedUpdateUUIDString]
99
+ scopeKey:@"test"
100
+ commitTime:[NSDate date]
101
+ runtimeVersion:@"1.0"
102
+ manifest:nil
103
+ status:0
104
+ keep:YES
105
+ config:nil
106
+ database:nil];
107
+ NSDictionary *extraHeaders = [EXUpdatesFileDownloader extraHeadersWithDatabase:nil
108
+ config:nil
109
+ launchedUpdate:launchedUpdate
110
+ embeddedUpdate:embeddedUpdate];
111
+ XCTAssertEqualObjects(launchedUpdateUUIDString, extraHeaders[@"Expo-Current-Update-ID"]);
112
+ XCTAssertEqualObjects(embeddedUpdateUUIDString, extraHeaders[@"Expo-Embedded-Update-ID"]);
113
+ }
114
+ #pragma clang diagnostic pop
115
+
116
+ #pragma clang diagnostic push
117
+ #pragma clang diagnostic ignored "-Wnonnull"
118
+ - (void)testGetExtraHeaders_NoLaunchedOrEmbeddedUpdate
119
+ {
120
+ NSDictionary *extraHeaders = [EXUpdatesFileDownloader extraHeadersWithDatabase:nil
121
+ config:nil
122
+ launchedUpdate:nil
123
+ embeddedUpdate:nil];
124
+ XCTAssertNil(extraHeaders[@"Expo-Current-Update-ID"]);
125
+ XCTAssertNil(extraHeaders[@"Expo-Embedded-Update-ID"]);
126
+ }
127
+ #pragma clang diagnostic pop
128
+
82
129
  - (void)testAssetExtraHeaders_OverrideOrder
83
130
  {
84
131
  EXUpdatesConfig *config = [EXUpdatesConfig configWithDictionary:@{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-updates",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Fetches and manages remotely-hosted assets and updates to your app's JS bundle.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -64,5 +64,5 @@
64
64
  "peerDependencies": {
65
65
  "expo": "*"
66
66
  },
67
- "gitHead": "22dce752354bb429c84851bc4389abe47a766b1f"
67
+ "gitHead": "0f796e10a2b2f258c40f9d56aa141c372cd63fc1"
68
68
  }