pulse-updates 1.5.1 → 1.5.2
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.
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Handles launching updates with asset fallback support
|
|
4
4
|
|
|
5
5
|
import Foundation
|
|
6
|
+
import CryptoKit
|
|
6
7
|
|
|
7
8
|
// MARK: - Launcher Protocol
|
|
8
9
|
|
|
@@ -132,7 +133,8 @@ final class PulseAppLauncher {
|
|
|
132
133
|
if assets.isEmpty {
|
|
133
134
|
// No assets to verify, just find the bundle
|
|
134
135
|
if let bundleHash = update.bundleHash {
|
|
135
|
-
|
|
136
|
+
let candidate = bundleStorePath(hash: bundleHash)
|
|
137
|
+
launchAssetUrl = bundleIsIntact(at: candidate, expectedHash: bundleHash) ? candidate : nil
|
|
136
138
|
}
|
|
137
139
|
completion(launchAssetUrl != nil, launchAssetUrl == nil ? PulseLauncherError.bundleNotFound : nil)
|
|
138
140
|
return
|
|
@@ -171,7 +173,8 @@ final class PulseAppLauncher {
|
|
|
171
173
|
// All assets accounted for, but we need the bundle
|
|
172
174
|
if let bundleHash = update.bundleHash {
|
|
173
175
|
launchAssetUrl = bundleStorePath(hash: bundleHash)
|
|
174
|
-
if !
|
|
176
|
+
if !bundleIsIntact(at: launchAssetUrl, expectedHash: bundleHash) {
|
|
177
|
+
launchAssetUrl = nil
|
|
175
178
|
completion(false, PulseLauncherError.bundleNotFound)
|
|
176
179
|
return
|
|
177
180
|
}
|
|
@@ -374,6 +377,34 @@ final class PulseAppLauncher {
|
|
|
374
377
|
.appendingPathComponent(hash.lowercased())
|
|
375
378
|
}
|
|
376
379
|
|
|
380
|
+
/// Whether the launch bundle is not just present but whole.
|
|
381
|
+
///
|
|
382
|
+
/// `fileExists` says a file is there, not that it is the file we downloaded. A write cut short
|
|
383
|
+
/// by a kill or a full disk leaves a short one at the right path, and React Native does not
|
|
384
|
+
/// survive being handed it: it throws while loading the bundle before any JS runs, so no
|
|
385
|
+
/// recovery hook fires and the same file is picked again on the next launch. The store is
|
|
386
|
+
/// content-addressed — the name IS the sha256 of the bytes — so compare it.
|
|
387
|
+
///
|
|
388
|
+
/// `PulseController.selectBestUpdateSync` already does this on the primary launch path; this
|
|
389
|
+
/// closes the same gap here, and matches `PulseAppLauncher.bundleIsIntact` on Android.
|
|
390
|
+
private func bundleIsIntact(at url: URL?, expectedHash: String) -> Bool {
|
|
391
|
+
guard let url, let handle = try? FileHandle(forReadingFrom: url) else { return false }
|
|
392
|
+
defer { try? handle.close() }
|
|
393
|
+
|
|
394
|
+
var hasher = SHA256()
|
|
395
|
+
var sawBytes = false
|
|
396
|
+
while true {
|
|
397
|
+
let chunk = try? handle.read(upToCount: 1024 * 1024)
|
|
398
|
+
guard let data = chunk, !data.isEmpty else { break }
|
|
399
|
+
sawBytes = true
|
|
400
|
+
hasher.update(data: data)
|
|
401
|
+
}
|
|
402
|
+
guard sawBytes else { return false }
|
|
403
|
+
|
|
404
|
+
let actual = hasher.finalize().map { String(format: "%02x", $0) }.joined()
|
|
405
|
+
return actual == expectedHash.lowercased()
|
|
406
|
+
}
|
|
407
|
+
|
|
377
408
|
private func bundleStorePath(hash: String) -> URL {
|
|
378
409
|
// Bundle is stored in assets directory (same location as other assets)
|
|
379
410
|
return directory
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pulse-updates",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "Pulse app-experience SDK for React Native: updates, config, experiments, events, decisions and first-party links",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|