expo-updates 55.0.31 → 55.0.32
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.
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 55.0.32 — 2026-09-28
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [iOS] Reuse an embedded asset from the app binary instead of re-downloading it when an update requires the same asset key. ([#49650](https://github.com/expo/expo/pull/49650) by [@alanjhughes](https://github.com/alanjhughes))
|
|
18
|
+
|
|
13
19
|
## 55.0.31 — 2026-09-17
|
|
14
20
|
|
|
15
21
|
### 🐛 Bug fixes
|
package/android/build.gradle
CHANGED
|
@@ -42,7 +42,7 @@ expoModule {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
group = 'host.exp.exponent'
|
|
45
|
-
version = '55.0.
|
|
45
|
+
version = '55.0.32'
|
|
46
46
|
|
|
47
47
|
// Utility method to derive boolean values from the environment or from Java properties,
|
|
48
48
|
// and return them as strings to be used in BuildConfig fields
|
|
@@ -89,7 +89,7 @@ android {
|
|
|
89
89
|
namespace "expo.modules.updates"
|
|
90
90
|
defaultConfig {
|
|
91
91
|
versionCode 31
|
|
92
|
-
versionName '55.0.
|
|
92
|
+
versionName '55.0.32'
|
|
93
93
|
consumerProguardFiles("proguard-rules.pro")
|
|
94
94
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
95
95
|
|
|
@@ -49,6 +49,16 @@ open class AppLoader: NSObject {
|
|
|
49
49
|
private let arrayLock: NSLock = NSLock()
|
|
50
50
|
private let completionQueue: DispatchQueue
|
|
51
51
|
|
|
52
|
+
/// Overridable for testing, where the bundle has no `app.manifest`.
|
|
53
|
+
internal lazy var embeddedUpdate: Update? = EmbeddedAppLoader.embeddedManifest(withConfig: config, database: database)
|
|
54
|
+
|
|
55
|
+
/// Overridable for testing, where fixtures live in the test bundle rather than in `updatesBundle`.
|
|
56
|
+
internal var embeddedAssetsBundle: Bundle = updatesBundle
|
|
57
|
+
|
|
58
|
+
/// Whether an asset with no database row may be copied out of the app binary rather than
|
|
59
|
+
/// downloaded. Only remote loads have anything to reuse.
|
|
60
|
+
internal var reusesEmbeddedAssets: Bool { true }
|
|
61
|
+
|
|
52
62
|
public init(
|
|
53
63
|
config: UpdatesConfig,
|
|
54
64
|
logger: UpdatesLogger,
|
|
@@ -208,7 +218,13 @@ open class AppLoader: NSObject {
|
|
|
208
218
|
if let assets = updateManifest.assets(),
|
|
209
219
|
!assets.isEmpty {
|
|
210
220
|
self.assetsToLoad = assets
|
|
211
|
-
let embeddedUpdate =
|
|
221
|
+
let embeddedUpdate = self.embeddedUpdate
|
|
222
|
+
let embeddedAssetsByKey = Dictionary(
|
|
223
|
+
(embeddedUpdate?.assets() ?? []).compactMap { embeddedAsset in
|
|
224
|
+
embeddedAsset.key.map { ($0, embeddedAsset) }
|
|
225
|
+
},
|
|
226
|
+
uniquingKeysWith: { first, _ in first }
|
|
227
|
+
)
|
|
212
228
|
let requestedUpdate: Update? = {
|
|
213
229
|
if let launchedUpdate = self.launchedUpdate,
|
|
214
230
|
launchedUpdate.updateId == updateManifest.updateId {
|
|
@@ -251,7 +267,12 @@ open class AppLoader: NSObject {
|
|
|
251
267
|
}
|
|
252
268
|
}
|
|
253
269
|
} else {
|
|
254
|
-
|
|
270
|
+
FileDownloader.assetFilesQueue.async {
|
|
271
|
+
if self.copyAssetFromEmbeddedBundleIfPresent(asset, embeddedAssetsByKey: embeddedAssetsByKey) {
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
self.downloadAsset(asset, extraHeaders: extraHeaders)
|
|
275
|
+
}
|
|
255
276
|
}
|
|
256
277
|
}
|
|
257
278
|
} else {
|
|
@@ -260,6 +281,60 @@ open class AppLoader: NSObject {
|
|
|
260
281
|
}
|
|
261
282
|
}
|
|
262
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Satisfies an asset from the embedded update in the app binary rather than downloading it.
|
|
286
|
+
* Embedded assets have no database row when copying is off, so the lookup above misses them and
|
|
287
|
+
* a remote update would otherwise re-download bytes that already ship in the app.
|
|
288
|
+
*
|
|
289
|
+
* Returns true when it handled the asset. Every failure returns false so the caller downloads it.
|
|
290
|
+
*
|
|
291
|
+
* Must be called on `FileDownloader.assetFilesQueue`.
|
|
292
|
+
*/
|
|
293
|
+
internal func copyAssetFromEmbeddedBundleIfPresent(
|
|
294
|
+
_ asset: UpdateAsset,
|
|
295
|
+
embeddedAssetsByKey: [String: UpdateAsset]
|
|
296
|
+
) -> Bool {
|
|
297
|
+
guard reusesEmbeddedAssets else {
|
|
298
|
+
return false
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
guard let key = asset.key,
|
|
302
|
+
UpdatesUtils.isSafeFilename(asset.filename),
|
|
303
|
+
let embeddedAsset = embeddedAssetsByKey[key],
|
|
304
|
+
embeddedAsset.mainBundleFilename != nil,
|
|
305
|
+
let bundlePath = UpdatesUtils.path(forBundledAsset: embeddedAsset, in: embeddedAssetsBundle) else {
|
|
306
|
+
return false
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
guard let data = try? Data(contentsOf: URL(fileURLWithPath: bundlePath), options: .mappedIfSafe) else {
|
|
310
|
+
logger.warn(message: "AppLoader: could not read embedded asset \(key), downloading it instead")
|
|
311
|
+
return false
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Keys are content-addressed, so a mismatch means the manifest is wrong about what we shipped.
|
|
315
|
+
if let expectedHash = asset.expectedHash {
|
|
316
|
+
let actualHash = UpdatesUtils.base64UrlEncodedSHA256WithData(data)
|
|
317
|
+
if expectedHash != actualHash {
|
|
318
|
+
logger.warn(
|
|
319
|
+
message: "AppLoader: embedded asset \(key) hashed to \(actualHash) but the update expects \(expectedHash), downloading it instead"
|
|
320
|
+
)
|
|
321
|
+
return false
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
do {
|
|
326
|
+
try data.write(to: directory.appendingPathComponent(asset.filename), options: .atomic)
|
|
327
|
+
} catch {
|
|
328
|
+
logger.warn(message: "AppLoader: could not copy embedded asset \(key) into the updates directory, downloading it instead")
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
DispatchQueue.global().async {
|
|
333
|
+
self.handleAssetDownload(withData: data, response: nil, asset: asset)
|
|
334
|
+
}
|
|
335
|
+
return true
|
|
336
|
+
}
|
|
337
|
+
|
|
263
338
|
public func handleAssetDownloadAlreadyExists(_ asset: UpdateAsset) {
|
|
264
339
|
arrayLock.lock()
|
|
265
340
|
assetsToLoad.remove(asset)
|
|
@@ -205,6 +205,10 @@ public final class EmbeddedAppLoader: AppLoader {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/// This loader is already reading the bundle it would copy from, so the copy-enabled path keeps
|
|
209
|
+
/// resolving files through the launcher as it did before.
|
|
210
|
+
override internal var reusesEmbeddedAssets: Bool { false }
|
|
211
|
+
|
|
208
212
|
override public func downloadAsset(_ asset: UpdateAsset, extraHeaders: [String: Any]) {
|
|
209
213
|
FileDownloader.assetFilesQueue.async {
|
|
210
214
|
self.handleAssetDownloadAlreadyExists(asset)
|
|
@@ -149,10 +149,14 @@ public final class UpdatesUtils: NSObject {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
internal static func path(forBundledAsset asset: UpdateAsset) -> String? {
|
|
152
|
+
return path(forBundledAsset: asset, in: updatesBundle)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
internal static func path(forBundledAsset asset: UpdateAsset, in bundle: Bundle) -> String? {
|
|
152
156
|
guard let mainBundleDir = asset.mainBundleDir else {
|
|
153
|
-
return
|
|
157
|
+
return bundle.path(forResource: asset.mainBundleFilename, ofType: asset.type)
|
|
154
158
|
}
|
|
155
|
-
return
|
|
159
|
+
return bundle.path(forResource: asset.mainBundleFilename, ofType: asset.type, inDirectory: mainBundleDir)
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-updates",
|
|
3
|
-
"version": "55.0.
|
|
3
|
+
"version": "55.0.32",
|
|
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",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"react": "*",
|
|
72
72
|
"react-native": "*"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "915622ac118d2dc5a30df45cb55f7cfa1a635063"
|
|
75
75
|
}
|