@hot-updater/react-native 1.0.0-rc.15 → 1.0.0-rc.17
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/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +491 -388
- package/android/src/main/java/com/hotupdater/BundleMetadata.kt +5 -11
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +290 -12
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +35 -19
- package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
- package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
- package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
- package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +110 -52
- package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
- package/android/src/newarch/HotUpdaterModule.kt +74 -69
- package/android/src/oldarch/HotUpdaterModule.kt +63 -59
- package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +1 -1
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +446 -61
- package/android/src/test/java/com/hotupdater/HotUpdaterExceptionTest.kt +220 -0
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +19 -27
- package/android/src/test/java/com/hotupdater/InstallIdentityServiceTest.kt +71 -0
- package/android/src/test/java/com/hotupdater/KeyValueStorageServiceTest.kt +106 -0
- package/android/src/test/java/com/hotupdater/NoBackupContext.kt +11 -0
- package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +309 -23
- package/android/src/test/java/com/hotupdater/PreviousProcessExitTest.kt +57 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +238 -157
- package/ios/HotUpdater/Internal/BundleMetadata.swift +3 -53
- package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +8 -18
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +48 -29
- package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +108 -1
- package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
- package/lib/commonjs/appReady.js +111 -0
- package/lib/commonjs/appReady.js.map +1 -0
- package/lib/commonjs/appReady.spec.js +360 -0
- package/lib/commonjs/appReady.spec.js.map +1 -0
- package/lib/commonjs/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
- package/lib/commonjs/appReady.test-utils.js.map +1 -0
- package/lib/commonjs/baseURL.js +16 -0
- package/lib/commonjs/baseURL.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +104 -56
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +337 -74
- package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/commonjs/clientPlugin.js +49 -0
- package/lib/commonjs/clientPlugin.js.map +1 -0
- package/lib/commonjs/entryImportGraph.spec.js +50 -0
- package/lib/commonjs/entryImportGraph.spec.js.map +1 -0
- package/lib/commonjs/fetchJSON.js +2 -6
- package/lib/commonjs/fetchJSON.js.map +1 -1
- package/lib/commonjs/httpClient.js +15 -67
- package/lib/commonjs/httpClient.js.map +1 -1
- package/lib/commonjs/httpClient.spec.js +26 -9
- package/lib/commonjs/httpClient.spec.js.map +1 -1
- package/lib/commonjs/index.js +49 -25
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +126 -40
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +67 -48
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +80 -58
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/pluginHost.js +208 -0
- package/lib/commonjs/pluginHost.js.map +1 -0
- package/lib/commonjs/pluginHost.spec.js +292 -0
- package/lib/commonjs/pluginHost.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/eventId.js +129 -0
- package/lib/commonjs/plugins/insights/eventId.js.map +1 -0
- package/lib/commonjs/plugins/insights/eventId.spec.js +47 -0
- package/lib/commonjs/plugins/insights/eventId.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/index.js +272 -0
- package/lib/commonjs/plugins/insights/index.js.map +1 -0
- package/lib/commonjs/plugins/insights/insights.spec.js +640 -0
- package/lib/commonjs/plugins/insights/insights.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/sender.js +128 -0
- package/lib/commonjs/plugins/insights/sender.js.map +1 -0
- package/lib/commonjs/plugins/insights/sender.spec.js +265 -0
- package/lib/commonjs/plugins/insights/sender.spec.js.map +1 -0
- package/lib/commonjs/plugins/insights/state.js +145 -0
- package/lib/commonjs/plugins/insights/state.js.map +1 -0
- package/lib/commonjs/releaseCatalogCache.js +13 -2
- package/lib/commonjs/releaseCatalogCache.js.map +1 -1
- package/lib/commonjs/releaseCatalogCache.spec.js +18 -0
- package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/updateError.js +132 -0
- package/lib/commonjs/updateError.js.map +1 -0
- package/lib/commonjs/updateError.spec.js +149 -0
- package/lib/commonjs/updateError.spec.js.map +1 -0
- package/lib/commonjs/wrap.js +43 -25
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.progress.spec.js +195 -0
- package/lib/commonjs/wrap.progress.spec.js.map +1 -0
- package/lib/commonjs/wrap.spec.js +94 -230
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/appReady.js +103 -0
- package/lib/module/appReady.js.map +1 -0
- package/lib/module/appReady.spec.js +359 -0
- package/lib/module/appReady.spec.js.map +1 -0
- package/lib/module/{notifyAppReadyInsights.test-utils.js → appReady.test-utils.js} +4 -3
- package/lib/module/appReady.test-utils.js.map +1 -0
- package/lib/module/baseURL.js +11 -0
- package/lib/module/baseURL.js.map +1 -0
- package/lib/module/checkForUpdate.js +103 -57
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/checkForUpdate.releaseCatalog.spec.js +337 -74
- package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
- package/lib/module/clientPlugin.js +44 -0
- package/lib/module/clientPlugin.js.map +1 -0
- package/lib/module/entryImportGraph.spec.js +50 -0
- package/lib/module/entryImportGraph.spec.js.map +1 -0
- package/lib/module/fetchJSON.js +2 -6
- package/lib/module/fetchJSON.js.map +1 -1
- package/lib/module/httpClient.js +14 -66
- package/lib/module/httpClient.js.map +1 -1
- package/lib/module/httpClient.spec.js +26 -9
- package/lib/module/httpClient.spec.js.map +1 -1
- package/lib/module/index.js +44 -27
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +127 -41
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +63 -44
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +80 -58
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/pluginHost.js +200 -0
- package/lib/module/pluginHost.js.map +1 -0
- package/lib/module/pluginHost.spec.js +291 -0
- package/lib/module/pluginHost.spec.js.map +1 -0
- package/lib/module/plugins/insights/eventId.js +122 -0
- package/lib/module/plugins/insights/eventId.js.map +1 -0
- package/lib/module/plugins/insights/eventId.spec.js +47 -0
- package/lib/module/plugins/insights/eventId.spec.js.map +1 -0
- package/lib/module/plugins/insights/index.js +268 -0
- package/lib/module/plugins/insights/index.js.map +1 -0
- package/lib/module/plugins/insights/insights.spec.js +639 -0
- package/lib/module/plugins/insights/insights.spec.js.map +1 -0
- package/lib/module/plugins/insights/sender.js +123 -0
- package/lib/module/plugins/insights/sender.js.map +1 -0
- package/lib/module/plugins/insights/sender.spec.js +265 -0
- package/lib/module/plugins/insights/sender.spec.js.map +1 -0
- package/lib/module/plugins/insights/state.js +139 -0
- package/lib/module/plugins/insights/state.js.map +1 -0
- package/lib/module/releaseCatalogCache.js +13 -2
- package/lib/module/releaseCatalogCache.js.map +1 -1
- package/lib/module/releaseCatalogCache.spec.js +18 -0
- package/lib/module/releaseCatalogCache.spec.js.map +1 -1
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/updateError.js +126 -0
- package/lib/module/updateError.js.map +1 -0
- package/lib/module/updateError.spec.js +149 -0
- package/lib/module/updateError.spec.js.map +1 -0
- package/lib/module/wrap.js +43 -25
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.progress.spec.js +196 -0
- package/lib/module/wrap.progress.spec.js.map +1 -0
- package/lib/module/wrap.spec.js +93 -229
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/appReady.d.ts +23 -0
- package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
- package/lib/typescript/commonjs/appReady.test-utils.d.ts +9 -0
- package/lib/typescript/commonjs/appReady.test-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/baseURL.d.ts +4 -0
- package/lib/typescript/commonjs/baseURL.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +15 -2
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/clientPlugin.d.ts +185 -0
- package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/commonjs/fetchJSON.d.ts +2 -3
- package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/commonjs/httpClient.d.ts +2 -30
- package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -6
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +29 -15
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/pluginHost.d.ts +29 -0
- package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts +18 -0
- package/lib/typescript/commonjs/plugins/insights/eventId.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/index.d.ts +40 -0
- package/lib/typescript/commonjs/plugins/insights/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts +74 -0
- package/lib/typescript/commonjs/plugins/insights/sender.d.ts.map +1 -0
- package/lib/typescript/commonjs/plugins/insights/state.d.ts +46 -0
- package/lib/typescript/commonjs/plugins/insights/state.d.ts.map +1 -0
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts +5 -1
- package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +34 -12
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/updateError.d.ts +26 -0
- package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.types.d.ts +6 -5
- package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
- package/lib/typescript/module/appReady.d.ts +23 -0
- package/lib/typescript/module/appReady.d.ts.map +1 -0
- package/lib/typescript/module/appReady.test-utils.d.ts +9 -0
- package/lib/typescript/module/appReady.test-utils.d.ts.map +1 -0
- package/lib/typescript/module/baseURL.d.ts +4 -0
- package/lib/typescript/module/baseURL.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +15 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/clientPlugin.d.ts +185 -0
- package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
- package/lib/typescript/module/fetchJSON.d.ts +2 -3
- package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
- package/lib/typescript/module/httpClient.d.ts +2 -30
- package/lib/typescript/module/httpClient.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -6
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +29 -15
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/pluginHost.d.ts +29 -0
- package/lib/typescript/module/pluginHost.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/eventId.d.ts +18 -0
- package/lib/typescript/module/plugins/insights/eventId.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/index.d.ts +40 -0
- package/lib/typescript/module/plugins/insights/index.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/sender.d.ts +74 -0
- package/lib/typescript/module/plugins/insights/sender.d.ts.map +1 -0
- package/lib/typescript/module/plugins/insights/state.d.ts +46 -0
- package/lib/typescript/module/plugins/insights/state.d.ts.map +1 -0
- package/lib/typescript/module/releaseCatalogCache.d.ts +5 -1
- package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +34 -12
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/updateError.d.ts +26 -0
- package/lib/typescript/module/updateError.d.ts.map +1 -0
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/lib/typescript/module/wrap.types.d.ts +6 -5
- package/lib/typescript/module/wrap.types.d.ts.map +1 -1
- package/package.json +14 -2
- package/plugins/insights/package.json +8 -0
- package/src/appReady.spec.ts +393 -0
- package/src/{notifyAppReadyInsights.test-utils.ts → appReady.test-utils.ts} +7 -7
- package/src/appReady.ts +149 -0
- package/src/baseURL.ts +15 -0
- package/src/checkForUpdate.releaseCatalog.spec.ts +370 -76
- package/src/checkForUpdate.ts +135 -80
- package/src/clientPlugin.ts +231 -0
- package/src/entryImportGraph.spec.ts +65 -0
- package/src/fetchJSON.ts +3 -10
- package/src/httpClient.spec.ts +28 -9
- package/src/httpClient.ts +16 -109
- package/src/index.spec.ts +129 -36
- package/src/index.ts +67 -37
- package/src/native.spec.ts +98 -72
- package/src/native.ts +93 -77
- package/src/pluginHost.spec.ts +297 -0
- package/src/pluginHost.ts +262 -0
- package/src/plugins/insights/eventId.spec.ts +60 -0
- package/src/plugins/insights/eventId.ts +171 -0
- package/src/plugins/insights/index.ts +367 -0
- package/src/plugins/insights/insights.spec.ts +732 -0
- package/src/plugins/insights/sender.spec.ts +287 -0
- package/src/plugins/insights/sender.ts +221 -0
- package/src/plugins/insights/state.ts +184 -0
- package/src/releaseCatalogCache.spec.ts +22 -0
- package/src/releaseCatalogCache.ts +19 -4
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +34 -13
- package/src/updateError.spec.ts +144 -0
- package/src/updateError.ts +153 -0
- package/src/wrap.progress.spec.ts +232 -0
- package/src/wrap.spec.ts +100 -283
- package/src/wrap.tsx +57 -26
- package/src/wrap.types.ts +6 -5
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -225
- package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.js +0 -188
- package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -378
- package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -124
- package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -224
- package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.js +0 -181
- package/lib/module/notifyAppReadyInsights.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.spec.js +0 -377
- package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
- package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -123
- package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -25
- package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -25
- package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
- package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
- package/src/notifyAppReadyInsights.boundary.spec.ts +0 -242
- package/src/notifyAppReadyInsights.spec.ts +0 -362
- package/src/notifyAppReadyInsights.transport.spec.ts +0 -138
- package/src/notifyAppReadyInsights.ts +0 -293
|
@@ -164,6 +164,34 @@ public struct UpdateProgressPayload {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/// How a staged bundle arrived. `updateBundle` resolves with `dictionary`.
|
|
168
|
+
public struct UpdateDelivery: Equatable {
|
|
169
|
+
public enum Method: String {
|
|
170
|
+
/// A bsdiff patch produced at least one file.
|
|
171
|
+
case patch
|
|
172
|
+
/// Only changed files were downloaded, possibly none.
|
|
173
|
+
case manifest
|
|
174
|
+
/// The full tar.br archive was downloaded.
|
|
175
|
+
case archive
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public let method: Method
|
|
179
|
+
/// A file's patch was tried, but the full file was downloaded instead.
|
|
180
|
+
public let patchFallback: Bool
|
|
181
|
+
|
|
182
|
+
public init(method: Method, patchFallback: Bool) {
|
|
183
|
+
self.method = method
|
|
184
|
+
self.patchFallback = patchFallback
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public var dictionary: [String: Any] {
|
|
188
|
+
[
|
|
189
|
+
"delivery": method.rawValue,
|
|
190
|
+
"patchFallback": patchFallback,
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
167
195
|
public enum BundleStorageError: Error, CustomNSError {
|
|
168
196
|
case directoryCreationFailed
|
|
169
197
|
case downloadFailed(Error)
|
|
@@ -257,7 +285,7 @@ public protocol BundleStorageService {
|
|
|
257
285
|
func prepareLaunch(bundle: Bundle, pendingRecovery: PendingCrashRecovery?) -> LaunchSelection
|
|
258
286
|
|
|
259
287
|
// Bundle update
|
|
260
|
-
func updateBundle(bundleId: String, manifestUrl: URL, manifestFileHash: String, archiveUrl: URL?, assets: [String: ChangedAssetDescriptor], progressHandler: @escaping (UpdateProgressPayload) -> Void, completion: @escaping (Result<
|
|
288
|
+
func updateBundle(bundleId: String, manifestUrl: URL, manifestFileHash: String, archiveUrl: URL?, assets: [String: ChangedAssetDescriptor], progressHandler: @escaping (UpdateProgressPayload) -> Void, completion: @escaping (Result<UpdateDelivery, Error>) -> Void)
|
|
261
289
|
func stageReleaseSelection(_ selection: PersistedSelection) -> Bool
|
|
262
290
|
func acceptReleaseCatalog(catalogId: String, scopeKey: String, generation: Int64, catalogHash: String, channel: String, selectionContextHash: String) -> Bool
|
|
263
291
|
func getActiveUpdateState() -> [String: Any]
|
|
@@ -270,10 +298,7 @@ public protocol BundleStorageService {
|
|
|
270
298
|
func getCrashHistory() -> CrashedHistory
|
|
271
299
|
func clearCrashHistory() -> Bool
|
|
272
300
|
func getInstallId() -> String
|
|
273
|
-
|
|
274
|
-
func getUsername() -> String?
|
|
275
|
-
func setUser(userId: String?, username: String?)
|
|
276
|
-
|
|
301
|
+
|
|
277
302
|
/**
|
|
278
303
|
* Gets the base URL for the current active bundle directory
|
|
279
304
|
* @return Base URL string (e.g., "file:///data/.../bundle-store/abc123") or empty string
|
|
@@ -338,6 +363,8 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
338
363
|
|
|
339
364
|
private let fileSystem: FileSystemService
|
|
340
365
|
private let downloadService: DownloadService
|
|
366
|
+
/// The wait before a download's second attempt; it doubles for each later one.
|
|
367
|
+
private let downloadRetryDelay: TimeInterval
|
|
341
368
|
private let preferences: PreferencesService
|
|
342
369
|
private let isolationKey: String
|
|
343
370
|
|
|
@@ -354,26 +381,16 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
354
381
|
private let releaseStateLock = NSRecursiveLock()
|
|
355
382
|
private var pendingInstallSelections: [String: PersistedSelection] = [:]
|
|
356
383
|
|
|
384
|
+
private let installIdentityLock = NSLock()
|
|
357
385
|
private var currentInstallIdentity: InstallIdentity?
|
|
358
|
-
private var currentUserIdentity: UserIdentity?
|
|
359
386
|
|
|
360
387
|
private let protectedBundleStoreEntries: Set<String> = [
|
|
361
388
|
BundleMetadata.metadataFilename,
|
|
362
389
|
CrashedHistory.crashedHistoryFilename,
|
|
363
390
|
LaunchReport.launchReportFilename,
|
|
364
|
-
InstallIdentity.installIdentityFilename,
|
|
365
|
-
UserIdentity.userIdentityFilename,
|
|
366
391
|
"builtin-index-v1.json",
|
|
367
392
|
]
|
|
368
393
|
|
|
369
|
-
private func normalizeIdentityValue(_ value: String?) -> String? {
|
|
370
|
-
guard let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
371
|
-
!trimmed.isEmpty else {
|
|
372
|
-
return nil
|
|
373
|
-
}
|
|
374
|
-
return trimmed
|
|
375
|
-
}
|
|
376
|
-
|
|
377
394
|
private func createDiffProgressFiles(
|
|
378
395
|
changedAssets: [String: ChangedAssetDescriptor]
|
|
379
396
|
) -> [UpdateProgressPayload.DiffProgressFileSnapshot] {
|
|
@@ -723,9 +740,11 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
723
740
|
let fingerprintHash = HotUpdaterConfig.shared.fingerprintHash
|
|
724
741
|
?? Bundle.main.object(forInfoDictionaryKey: "HOT_UPDATER_FINGERPRINT_HASH") as? String
|
|
725
742
|
return (fingerprintHash?.isEmpty == false) ? .fingerprint : .appVersion
|
|
726
|
-
}
|
|
743
|
+
},
|
|
744
|
+
downloadRetryDelay: TimeInterval = 1) {
|
|
727
745
|
|
|
728
746
|
self.fileSystem = fileSystem
|
|
747
|
+
self.downloadRetryDelay = downloadRetryDelay
|
|
729
748
|
self.downloadService = downloadService
|
|
730
749
|
self.preferences = preferences
|
|
731
750
|
self.isolationKey = isolationKey
|
|
@@ -773,18 +792,9 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
773
792
|
return URL(fileURLWithPath: storeDir).appendingPathComponent(LaunchReport.launchReportFilename)
|
|
774
793
|
}
|
|
775
794
|
|
|
776
|
-
private func installIdentityFileURL() -> URL
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
return URL(fileURLWithPath: storeDir).appendingPathComponent(InstallIdentity.installIdentityFilename)
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
private func userIdentityFileURL() -> URL? {
|
|
784
|
-
guard case .success(let storeDir) = bundleStoreDir() else {
|
|
785
|
-
return nil
|
|
786
|
-
}
|
|
787
|
-
return URL(fileURLWithPath: storeDir).appendingPathComponent(UserIdentity.userIdentityFilename)
|
|
795
|
+
private func installIdentityFileURL() -> URL {
|
|
796
|
+
NoBackupStorage.directory(in: fileSystem)
|
|
797
|
+
.appendingPathComponent(InstallIdentity.installIdentityFilename)
|
|
788
798
|
}
|
|
789
799
|
|
|
790
800
|
// MARK: - Metadata Operations
|
|
@@ -833,54 +843,28 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
833
843
|
_ = report.save(to: file)
|
|
834
844
|
}
|
|
835
845
|
|
|
836
|
-
|
|
846
|
+
/// Loads the install identity, creating it once when its file is missing.
|
|
847
|
+
@discardableResult
|
|
848
|
+
private func loadOrCreateInstallIdentity() -> InstallIdentity {
|
|
849
|
+
installIdentityLock.lock()
|
|
850
|
+
defer { installIdentityLock.unlock() }
|
|
851
|
+
|
|
837
852
|
if let currentInstallIdentity {
|
|
838
853
|
return currentInstallIdentity
|
|
839
854
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
855
|
+
let file = installIdentityFileURL()
|
|
856
|
+
if let identity = InstallIdentity.load(from: file) {
|
|
857
|
+
currentInstallIdentity = identity
|
|
858
|
+
return identity
|
|
843
859
|
}
|
|
844
|
-
currentInstallIdentity = identity
|
|
845
|
-
return identity
|
|
846
|
-
}
|
|
847
860
|
|
|
848
|
-
|
|
861
|
+
let identity = InstallIdentity()
|
|
862
|
+
// Keep the new id for this process even when it cannot be saved.
|
|
849
863
|
currentInstallIdentity = identity
|
|
850
|
-
|
|
851
|
-
return false
|
|
852
|
-
}
|
|
853
|
-
return identity.save(to: file)
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
private func loadUserIdentity() -> UserIdentity? {
|
|
857
|
-
if let currentUserIdentity {
|
|
858
|
-
return currentUserIdentity
|
|
859
|
-
}
|
|
860
|
-
guard let file = userIdentityFileURL(),
|
|
861
|
-
let identity = UserIdentity.load(from: file) else {
|
|
862
|
-
return nil
|
|
863
|
-
}
|
|
864
|
-
currentUserIdentity = identity
|
|
864
|
+
_ = identity.save(to: file)
|
|
865
865
|
return identity
|
|
866
866
|
}
|
|
867
867
|
|
|
868
|
-
private func saveUserIdentity(_ identity: UserIdentity?) -> Bool {
|
|
869
|
-
currentUserIdentity = identity
|
|
870
|
-
guard let file = userIdentityFileURL() else {
|
|
871
|
-
return false
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
guard let identity else {
|
|
875
|
-
if FileManager.default.fileExists(atPath: file.path) {
|
|
876
|
-
try? FileManager.default.removeItem(at: file)
|
|
877
|
-
}
|
|
878
|
-
return true
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
return identity.save(to: file)
|
|
882
|
-
}
|
|
883
|
-
|
|
884
868
|
private func resolveBuiltInBundleId() -> String? {
|
|
885
869
|
let bundleId = builtInBundleIdProvider().trimmingCharacters(in: .whitespacesAndNewlines)
|
|
886
870
|
return bundleId.isEmpty ? nil : bundleId
|
|
@@ -1636,7 +1620,42 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1636
1620
|
DispatchQueue.global(qos: .background).async(execute: workItem)
|
|
1637
1621
|
}
|
|
1638
1622
|
|
|
1623
|
+
/**
|
|
1624
|
+
* Downloads `url`, trying again after a retryable failure (see
|
|
1625
|
+
* `DownloadError.isRetryable`) up to `DownloadError.maximumAttempts`
|
|
1626
|
+
* attempts in all, 1 and then 2 seconds apart, unless `retrying` is
|
|
1627
|
+
* false. The last attempt's result is returned, so a failure keeps what
|
|
1628
|
+
* classifies it. Blocks the calling thread.
|
|
1629
|
+
*/
|
|
1639
1630
|
private func downloadFileSynchronously(
|
|
1631
|
+
from url: URL,
|
|
1632
|
+
to destination: String,
|
|
1633
|
+
retrying: Bool = true,
|
|
1634
|
+
progressHandler: @escaping (DownloadProgress) -> Void
|
|
1635
|
+
) -> Result<URL, Error> {
|
|
1636
|
+
var attempt = 1
|
|
1637
|
+
while true {
|
|
1638
|
+
let result = downloadFileOnce(
|
|
1639
|
+
from: url,
|
|
1640
|
+
to: destination,
|
|
1641
|
+
progressHandler: progressHandler
|
|
1642
|
+
)
|
|
1643
|
+
guard case .failure(let error) = result,
|
|
1644
|
+
retrying,
|
|
1645
|
+
attempt < DownloadError.maximumAttempts,
|
|
1646
|
+
DownloadError.isRetryable(error) else {
|
|
1647
|
+
return result
|
|
1648
|
+
}
|
|
1649
|
+
let delay = downloadRetryDelay * pow(2, Double(attempt - 1))
|
|
1650
|
+
NSLog("[BundleStorage] Download failed (attempt \(attempt)/\(DownloadError.maximumAttempts)): \(error.localizedDescription). Retrying in \(delay)s")
|
|
1651
|
+
if delay > 0 {
|
|
1652
|
+
Thread.sleep(forTimeInterval: delay)
|
|
1653
|
+
}
|
|
1654
|
+
attempt += 1
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
private func downloadFileOnce(
|
|
1640
1659
|
from url: URL,
|
|
1641
1660
|
to destination: String,
|
|
1642
1661
|
progressHandler: @escaping (DownloadProgress) -> Void
|
|
@@ -1960,10 +1979,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1960
1979
|
func prepareLaunch(bundle: Bundle, pendingRecovery: PendingCrashRecovery?) -> LaunchSelection {
|
|
1961
1980
|
builtInAssetResolver.use(bundle: bundle)
|
|
1962
1981
|
saveLaunchReport(nil)
|
|
1963
|
-
|
|
1964
|
-
let identity = InstallIdentity()
|
|
1965
|
-
_ = saveInstallIdentity(identity)
|
|
1966
|
-
}
|
|
1982
|
+
loadOrCreateInstallIdentity()
|
|
1967
1983
|
applyPendingRecoveryIfNeeded(pendingRecovery)
|
|
1968
1984
|
// Only a new storage instance consumes an unfinished launch. Repeated bundle
|
|
1969
1985
|
// lookups or bridge reloads in this process must not reject its own launch.
|
|
@@ -1993,7 +2009,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
1993
2009
|
* @param progressHandler Callback for download and extraction progress (0.0 to 1.0)
|
|
1994
2010
|
* @param completion Callback with result of the operation
|
|
1995
2011
|
*/
|
|
1996
|
-
func updateBundle(bundleId: String, manifestUrl: URL, manifestFileHash: String, archiveUrl: URL? = nil, assets: [String: ChangedAssetDescriptor], progressHandler: @escaping (UpdateProgressPayload) -> Void, completion: @escaping (Result<
|
|
2012
|
+
func updateBundle(bundleId: String, manifestUrl: URL, manifestFileHash: String, archiveUrl: URL? = nil, assets: [String: ChangedAssetDescriptor], progressHandler: @escaping (UpdateProgressPayload) -> Void, completion: @escaping (Result<UpdateDelivery, Error>) -> Void) {
|
|
1997
2013
|
// Check if bundle is in crashed history
|
|
1998
2014
|
let crashedHistory = loadCrashedHistory()
|
|
1999
2015
|
if crashedHistory.contains(bundleId) {
|
|
@@ -2005,7 +2021,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2005
2021
|
fileOperationQueue.async(flags: .barrier) {
|
|
2006
2022
|
let storeDirResult = self.bundleStoreDir()
|
|
2007
2023
|
guard case .success(let storeDir) = storeDirResult else {
|
|
2008
|
-
completion(.failure(
|
|
2024
|
+
completion(.failure(UpdateFailureError.classifying(
|
|
2025
|
+
storeDirResult.failureError ?? BundleStorageError.unknown(nil),
|
|
2026
|
+
during: .storage
|
|
2027
|
+
)))
|
|
2009
2028
|
return
|
|
2010
2029
|
}
|
|
2011
2030
|
self.updateBundleFromManifest(
|
|
@@ -2021,6 +2040,14 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2021
2040
|
}
|
|
2022
2041
|
}
|
|
2023
2042
|
|
|
2043
|
+
/// How the per-file install produced one file.
|
|
2044
|
+
private enum AssetInstallOutcome {
|
|
2045
|
+
case patched
|
|
2046
|
+
/// `patchFallback` is true when a patch for the running bundle was
|
|
2047
|
+
/// offered but the original file was downloaded instead.
|
|
2048
|
+
case downloaded(patchFallback: Bool)
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2024
2051
|
private func downloadManifestAsset(
|
|
2025
2052
|
assetPath: String,
|
|
2026
2053
|
expectedAsset: ParsedManifestAsset,
|
|
@@ -2031,7 +2058,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2031
2058
|
tempDirectory: String,
|
|
2032
2059
|
progressFile: UpdateProgressPayload.DiffProgressFileSnapshot,
|
|
2033
2060
|
progressHandler: @escaping (UpdateProgressPayload) -> Void
|
|
2034
|
-
) throws {
|
|
2061
|
+
) throws -> AssetInstallOutcome {
|
|
2035
2062
|
let expectedHash = expectedAsset.fileHash
|
|
2036
2063
|
var diffFiles = [progressFile]
|
|
2037
2064
|
let patched = applyPatchAssetIfPossible(
|
|
@@ -2046,7 +2073,10 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2046
2073
|
progressHandler: progressHandler
|
|
2047
2074
|
)
|
|
2048
2075
|
if patched {
|
|
2049
|
-
|
|
2076
|
+
// The patch produced these bytes, so a mismatch is a patch failure.
|
|
2077
|
+
try UpdateFailureError.during(.patch, resource: .patch) {
|
|
2078
|
+
try verifyManifestAssetFile(atPath: destinationPath, asset: expectedAsset)
|
|
2079
|
+
}
|
|
2050
2080
|
updateDiffProgressFile(
|
|
2051
2081
|
files: &diffFiles,
|
|
2052
2082
|
assetPath: assetPath,
|
|
@@ -2060,7 +2090,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2060
2090
|
: "downloading",
|
|
2061
2091
|
files: diffFiles
|
|
2062
2092
|
)
|
|
2063
|
-
return
|
|
2093
|
+
return .patched
|
|
2064
2094
|
}
|
|
2065
2095
|
|
|
2066
2096
|
guard let changedAssetFileUrl = changedAsset.fileUrl else {
|
|
@@ -2075,10 +2105,14 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2075
2105
|
phase: "downloading",
|
|
2076
2106
|
files: diffFiles
|
|
2077
2107
|
)
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2108
|
+
// With no original file to fall back to, the patch decides the install.
|
|
2109
|
+
throw UpdateFailureError(
|
|
2110
|
+
underlying: BundleStorageError.downloadFailed(
|
|
2111
|
+
NSError(domain: "HotUpdater", code: 0, userInfo: [
|
|
2112
|
+
NSLocalizedDescriptionKey: "Changed asset fileUrl missing and patch could not be applied: \(assetPath)"
|
|
2113
|
+
])
|
|
2114
|
+
),
|
|
2115
|
+
failure: UpdateFailure.patch.with(resource: .patch)
|
|
2082
2116
|
)
|
|
2083
2117
|
}
|
|
2084
2118
|
|
|
@@ -2119,15 +2153,23 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2119
2153
|
) {
|
|
2120
2154
|
case .success(let downloadedFileURL):
|
|
2121
2155
|
do {
|
|
2122
|
-
try
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2156
|
+
try UpdateFailureError.during(.extraction, resource: .file) {
|
|
2157
|
+
try materializeDownloadedAsset(
|
|
2158
|
+
downloadedPath: downloadedFileURL.path,
|
|
2159
|
+
destinationPath: destinationPath,
|
|
2160
|
+
changedAsset: changedAsset
|
|
2161
|
+
)
|
|
2162
|
+
}
|
|
2163
|
+
// Compressed files are checked after decoding, so their
|
|
2164
|
+
// mismatch is an extraction failure.
|
|
2165
|
+
let verificationPhase: UpdatePhase =
|
|
2166
|
+
changedAsset.fileCompression == "br" ? .extraction : .verification
|
|
2167
|
+
try UpdateFailureError.during(verificationPhase, resource: .file) {
|
|
2168
|
+
try verifyManifestAssetFile(
|
|
2169
|
+
atPath: destinationPath,
|
|
2170
|
+
asset: expectedAsset
|
|
2171
|
+
)
|
|
2172
|
+
}
|
|
2131
2173
|
} catch {
|
|
2132
2174
|
updateDiffProgressFile(
|
|
2133
2175
|
files: &diffFiles,
|
|
@@ -2155,6 +2197,12 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2155
2197
|
: "downloading",
|
|
2156
2198
|
files: diffFiles
|
|
2157
2199
|
)
|
|
2200
|
+
// A patch for the running bundle was offered, but the original
|
|
2201
|
+
// file came instead; a patch for another base was never tried.
|
|
2202
|
+
let patchWasTried = changedAsset.patch.map {
|
|
2203
|
+
$0.algorithm == "bsdiff" && $0.baseBundleId == currentBundleId
|
|
2204
|
+
} ?? false
|
|
2205
|
+
return .downloaded(patchFallback: patchWasTried)
|
|
2158
2206
|
case .failure(let error):
|
|
2159
2207
|
let lastKnownProgress = diffFiles.first(where: { $0.path == assetPath })?.progress ?? 0
|
|
2160
2208
|
updateDiffProgressFile(
|
|
@@ -2168,11 +2216,14 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2168
2216
|
phase: "downloading",
|
|
2169
2217
|
files: diffFiles
|
|
2170
2218
|
)
|
|
2219
|
+
let transferError: Error
|
|
2171
2220
|
if let downloadError = error as? DownloadError,
|
|
2172
2221
|
case .incompleteDownload(let expected, let actual) = downloadError {
|
|
2173
|
-
|
|
2222
|
+
transferError = BundleStorageError.incompleteDownload(expected: expected, actual: actual)
|
|
2223
|
+
} else {
|
|
2224
|
+
transferError = BundleStorageError.downloadFailed(error)
|
|
2174
2225
|
}
|
|
2175
|
-
throw
|
|
2226
|
+
throw UpdateFailureError.classifying(transferError, during: .transfer, resource: .file)
|
|
2176
2227
|
}
|
|
2177
2228
|
}
|
|
2178
2229
|
|
|
@@ -2221,9 +2272,11 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2221
2272
|
files: progressFiles
|
|
2222
2273
|
)
|
|
2223
2274
|
|
|
2275
|
+
// One attempt: a failed archive falls back to per-file downloads, which retry.
|
|
2224
2276
|
switch downloadFileSynchronously(
|
|
2225
2277
|
from: archiveUrl,
|
|
2226
2278
|
to: compressedPath,
|
|
2279
|
+
retrying: false,
|
|
2227
2280
|
progressHandler: { progress in
|
|
2228
2281
|
self.updateDiffProgressFile(
|
|
2229
2282
|
files: &progressFiles,
|
|
@@ -2254,14 +2307,19 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2254
2307
|
fileURL: URL(fileURLWithPath: compressedPath),
|
|
2255
2308
|
expectedHash: archive.downloadFileHash
|
|
2256
2309
|
) else {
|
|
2257
|
-
throw
|
|
2310
|
+
throw UpdateFailureError(
|
|
2311
|
+
underlying: BundleStorageError.invalidBundle,
|
|
2312
|
+
failure: .hashMismatch
|
|
2313
|
+
)
|
|
2258
2314
|
}
|
|
2259
2315
|
|
|
2260
|
-
try
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2316
|
+
try UpdateFailureError.during(.extraction) {
|
|
2317
|
+
try BrotliFileDecompressor.decompress(
|
|
2318
|
+
from: compressedPath,
|
|
2319
|
+
to: tarPath,
|
|
2320
|
+
expectedOutputByteSize: archive.tarByteSize
|
|
2321
|
+
)
|
|
2322
|
+
}
|
|
2265
2323
|
|
|
2266
2324
|
let expectedFiles = try targetManifest.assets.reduce(
|
|
2267
2325
|
into: [String: Int64](),
|
|
@@ -2272,17 +2330,19 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2272
2330
|
result[entry.key] = byteSize
|
|
2273
2331
|
}
|
|
2274
2332
|
)
|
|
2275
|
-
try
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2333
|
+
try UpdateFailureError.during(.extraction) {
|
|
2334
|
+
try TarArchiveExtractor.extract(
|
|
2335
|
+
from: tarPath,
|
|
2336
|
+
to: extractedDirectory,
|
|
2337
|
+
expectedFiles: expectedFiles
|
|
2338
|
+
)
|
|
2339
|
+
for (path, asset) in targetManifest.assets {
|
|
2340
|
+
let extractedPath = try FileUtilities.fileURL(
|
|
2341
|
+
for: path,
|
|
2342
|
+
destinationRoot: extractedDirectory
|
|
2343
|
+
).path
|
|
2344
|
+
try verifyManifestAssetFile(atPath: extractedPath, asset: asset)
|
|
2345
|
+
}
|
|
2286
2346
|
}
|
|
2287
2347
|
|
|
2288
2348
|
try promoteVerifiedArchiveDirectory(
|
|
@@ -2380,8 +2440,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2380
2440
|
defer { releaseStateLock.unlock() }
|
|
2381
2441
|
let currentMetadata = loadMetadataOrNull() ?? createInitialMetadata()
|
|
2382
2442
|
guard let updatedMetadata = prepareMetadataForNewStagingBundle(currentMetadata, bundleId: bundleId) else {
|
|
2383
|
-
throw BundleStorageError.unknown(
|
|
2384
|
-
userInfo: [NSLocalizedDescriptionKey: "Release catalog selection is stale"]))
|
|
2443
|
+
throw BundleStorageError.unknown(StaleReleaseSelectionError())
|
|
2385
2444
|
}
|
|
2386
2445
|
let finalBundlePath = try resolveBundlePathAfterMove(
|
|
2387
2446
|
bundlePathInStaging, from: stagingDirectory, to: finalDirectory
|
|
@@ -2400,8 +2459,11 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2400
2459
|
// Metadata is the durable activation point. Launch never uses an
|
|
2401
2460
|
// untracked preference if this write fails or the process terminates.
|
|
2402
2461
|
guard saveMetadata(updatedMetadata) else {
|
|
2403
|
-
throw
|
|
2404
|
-
|
|
2462
|
+
throw UpdateFailureError(
|
|
2463
|
+
underlying: BundleStorageError.unknown(NSError(domain: "HotUpdater", code: 0,
|
|
2464
|
+
userInfo: [NSLocalizedDescriptionKey: "Failed to persist bundle activation metadata"])),
|
|
2465
|
+
failure: .storage
|
|
2466
|
+
)
|
|
2405
2467
|
}
|
|
2406
2468
|
} catch {
|
|
2407
2469
|
let installError = error
|
|
@@ -2425,17 +2487,23 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2425
2487
|
changedAssets: [String: ChangedAssetDescriptor],
|
|
2426
2488
|
storeDir: String,
|
|
2427
2489
|
progressHandler: @escaping (UpdateProgressPayload) -> Void,
|
|
2428
|
-
completion: @escaping (Result<
|
|
2490
|
+
completion: @escaping (Result<UpdateDelivery, Error>) -> Void
|
|
2429
2491
|
) {
|
|
2430
2492
|
let tempDirResult = tempDir()
|
|
2431
2493
|
guard case .success(let tempDirectory) = tempDirResult else {
|
|
2432
|
-
completion(.failure(
|
|
2494
|
+
completion(.failure(UpdateFailureError.classifying(
|
|
2495
|
+
tempDirResult.failureError ?? BundleStorageError.unknown(nil),
|
|
2496
|
+
during: .storage
|
|
2497
|
+
)))
|
|
2433
2498
|
return
|
|
2434
2499
|
}
|
|
2435
2500
|
|
|
2436
2501
|
try? self.fileSystem.removeItem(atPath: tempDirectory)
|
|
2437
2502
|
guard self.fileSystem.createDirectory(atPath: tempDirectory) else {
|
|
2438
|
-
completion(.failure(
|
|
2503
|
+
completion(.failure(UpdateFailureError.classifying(
|
|
2504
|
+
BundleStorageError.directoryCreationFailed,
|
|
2505
|
+
during: .storage
|
|
2506
|
+
)))
|
|
2439
2507
|
return
|
|
2440
2508
|
}
|
|
2441
2509
|
|
|
@@ -2447,9 +2515,14 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2447
2515
|
let tmpDir = (storeDir as NSString).appendingPathComponent("\(bundleId).tmp")
|
|
2448
2516
|
let realDir = (storeDir as NSString).appendingPathComponent(bundleId)
|
|
2449
2517
|
var diffFiles: [UpdateProgressPayload.DiffProgressFileSnapshot] = []
|
|
2518
|
+
// The running step classifies the errors whose type does not.
|
|
2519
|
+
var failurePhase = UpdatePhase.storage
|
|
2520
|
+
var failureResource: UpdateFailureResource?
|
|
2450
2521
|
|
|
2451
2522
|
do {
|
|
2452
2523
|
try recoverInterruptedPromotion(finalDirectory: realDir)
|
|
2524
|
+
failurePhase = .transfer
|
|
2525
|
+
failureResource = .manifest
|
|
2453
2526
|
self.emitDiffProgress(
|
|
2454
2527
|
progressHandler: progressHandler,
|
|
2455
2528
|
phase: "manifest",
|
|
@@ -2478,6 +2551,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2478
2551
|
throw BundleStorageError.downloadFailed(error)
|
|
2479
2552
|
}
|
|
2480
2553
|
|
|
2554
|
+
failurePhase = .verification
|
|
2481
2555
|
let manifestVerificationResult = SignatureVerifier.verifyBundle(
|
|
2482
2556
|
fileURL: URL(fileURLWithPath: tempManifestPath),
|
|
2483
2557
|
fileHash: manifestFileHash
|
|
@@ -2489,6 +2563,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2489
2563
|
throw BundleStorageError.unknown(nil)
|
|
2490
2564
|
}
|
|
2491
2565
|
|
|
2566
|
+
failurePhase = .manifest
|
|
2492
2567
|
guard let manifestData = try? Data(contentsOf: URL(fileURLWithPath: tempManifestPath)),
|
|
2493
2568
|
let manifestJson = try JSONSerialization.jsonObject(with: manifestData) as? [String: Any],
|
|
2494
2569
|
let targetManifest = parseBundleManifest(from: manifestJson),
|
|
@@ -2511,6 +2586,9 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2511
2586
|
_ = try FileUtilities.fileURL(for: path, destinationRoot: tmpDir)
|
|
2512
2587
|
}
|
|
2513
2588
|
|
|
2589
|
+
// Downloads, patches, and extraction below classify their own errors.
|
|
2590
|
+
failurePhase = .storage
|
|
2591
|
+
failureResource = nil
|
|
2514
2592
|
guard self.fileSystem.createDirectory(atPath: tmpDir) else {
|
|
2515
2593
|
throw BundleStorageError.directoryCreationFailed
|
|
2516
2594
|
}
|
|
@@ -2554,13 +2632,19 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2554
2632
|
}
|
|
2555
2633
|
if reused { continue }
|
|
2556
2634
|
if builtInAssetResolver.copyIfMatches(assetPath: assetPath, expectedHash: expectedAsset.fileHash, destination: destinationPath) {
|
|
2557
|
-
try
|
|
2635
|
+
try UpdateFailureError.during(.verification, resource: .file) {
|
|
2636
|
+
try verifyManifestAssetFile(atPath: destinationPath, asset: expectedAsset)
|
|
2637
|
+
}
|
|
2558
2638
|
continue
|
|
2559
2639
|
}
|
|
2560
2640
|
downloads[assetPath] = changedAssets[assetPath]
|
|
2561
2641
|
}
|
|
2562
2642
|
|
|
2563
2643
|
var archiveInstalled = false
|
|
2644
|
+
var patchedFiles = false
|
|
2645
|
+
// The archive is chosen before any patch is tried, so only the
|
|
2646
|
+
// per-file install can fall back from a patch.
|
|
2647
|
+
var patchFallbacks = false
|
|
2564
2648
|
if shouldUseArchive(
|
|
2565
2649
|
archive: targetManifest.archive,
|
|
2566
2650
|
archiveUrl: archiveUrl,
|
|
@@ -2568,14 +2652,16 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2568
2652
|
assets: targetManifest.assets
|
|
2569
2653
|
), let archive = targetManifest.archive, let archiveUrl {
|
|
2570
2654
|
do {
|
|
2571
|
-
diffFiles = try
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2655
|
+
diffFiles = try UpdateFailureError.during(.storage, resource: .archive) {
|
|
2656
|
+
try installArchive(
|
|
2657
|
+
archive: archive,
|
|
2658
|
+
archiveUrl: archiveUrl,
|
|
2659
|
+
targetManifest: targetManifest,
|
|
2660
|
+
preparedDirectory: tmpDir,
|
|
2661
|
+
tempDirectory: tempDirectory,
|
|
2662
|
+
progressHandler: progressHandler
|
|
2663
|
+
)
|
|
2664
|
+
}
|
|
2579
2665
|
archiveInstalled = true
|
|
2580
2666
|
} catch {
|
|
2581
2667
|
NSLog("[BundleStorage] tar.br optimization failed; falling back to individual files: \(error.localizedDescription)")
|
|
@@ -2592,6 +2678,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2592
2678
|
let workers = OperationQueue()
|
|
2593
2679
|
workers.maxConcurrentOperationCount = 4
|
|
2594
2680
|
var installError: Error?
|
|
2681
|
+
var installOutcomes: [AssetInstallOutcome] = []
|
|
2595
2682
|
for progressFile in diffFiles {
|
|
2596
2683
|
let assetPath = progressFile.path
|
|
2597
2684
|
let expectedAsset = targetManifest.assets[assetPath]!
|
|
@@ -2606,7 +2693,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2606
2693
|
if shouldSkip { return }
|
|
2607
2694
|
do {
|
|
2608
2695
|
guard self.fileSystem.createDirectory(atPath: workerTemp) else { throw BundleStorageError.directoryCreationFailed }
|
|
2609
|
-
try self.downloadManifestAsset(
|
|
2696
|
+
let outcome = try self.downloadManifestAsset(
|
|
2610
2697
|
assetPath: assetPath, expectedAsset: expectedAsset, changedAsset: descriptor,
|
|
2611
2698
|
currentBundleId: currentBundleId, currentBundleDir: currentBundleDir,
|
|
2612
2699
|
destinationPath: destinationPath, tempDirectory: workerTemp, progressFile: progressFile,
|
|
@@ -2620,6 +2707,9 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2620
2707
|
self.emitDiffProgress(progressHandler: progressHandler, phase: "downloading", files: diffFiles)
|
|
2621
2708
|
}
|
|
2622
2709
|
)
|
|
2710
|
+
progressLock.lock()
|
|
2711
|
+
installOutcomes.append(outcome)
|
|
2712
|
+
progressLock.unlock()
|
|
2623
2713
|
} catch {
|
|
2624
2714
|
progressLock.lock()
|
|
2625
2715
|
if installError == nil { installError = error }
|
|
@@ -2630,6 +2720,14 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2630
2720
|
// Join all workers before cleaning scratch files, even after a failure.
|
|
2631
2721
|
workers.waitUntilAllOperationsAreFinished()
|
|
2632
2722
|
if let installError { throw installError }
|
|
2723
|
+
for outcome in installOutcomes {
|
|
2724
|
+
switch outcome {
|
|
2725
|
+
case .patched:
|
|
2726
|
+
patchedFiles = true
|
|
2727
|
+
case .downloaded(let patchFallback):
|
|
2728
|
+
patchFallbacks = patchFallbacks || patchFallback
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2633
2731
|
}
|
|
2634
2732
|
|
|
2635
2733
|
self.emitDiffProgress(
|
|
@@ -2644,7 +2742,11 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2644
2742
|
switch self.findBundleFile(in: tmpDir, expectedBundleId: bundleId) {
|
|
2645
2743
|
case .success(let maybeBundlePath):
|
|
2646
2744
|
guard let bundlePathInTmp = maybeBundlePath else {
|
|
2647
|
-
|
|
2745
|
+
// The manifest does not name exactly one platform bundle file.
|
|
2746
|
+
throw UpdateFailureError(
|
|
2747
|
+
underlying: BundleStorageError.invalidBundle,
|
|
2748
|
+
failure: UpdateFailure.invalidResponse.with(resource: .manifest)
|
|
2749
|
+
)
|
|
2648
2750
|
}
|
|
2649
2751
|
|
|
2650
2752
|
let updatedMetadata = try promoteStagedBundle(
|
|
@@ -2666,7 +2768,9 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2666
2768
|
files: diffFiles
|
|
2667
2769
|
)
|
|
2668
2770
|
))
|
|
2669
|
-
|
|
2771
|
+
let delivery: UpdateDelivery.Method =
|
|
2772
|
+
archiveInstalled ? .archive : (patchedFiles ? .patch : .manifest)
|
|
2773
|
+
completion(.success(UpdateDelivery(method: delivery, patchFallback: patchFallbacks)))
|
|
2670
2774
|
case .failure(let error):
|
|
2671
2775
|
throw error
|
|
2672
2776
|
}
|
|
@@ -2674,7 +2778,11 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2674
2778
|
NSLog("[BundleStorage] Manifest-driven install failed: \(error.localizedDescription).")
|
|
2675
2779
|
// Preserve staging for a hash-checked retry. Never trust its presence alone.
|
|
2676
2780
|
self.cleanupTemporaryFiles([tempDirectory])
|
|
2677
|
-
completion(.failure(
|
|
2781
|
+
completion(.failure(UpdateFailureError.classifying(
|
|
2782
|
+
error,
|
|
2783
|
+
during: failurePhase,
|
|
2784
|
+
resource: failureResource
|
|
2785
|
+
)))
|
|
2678
2786
|
}
|
|
2679
2787
|
}
|
|
2680
2788
|
|
|
@@ -2756,34 +2864,7 @@ class BundleFileStorageService: BundleStorageService {
|
|
|
2756
2864
|
}
|
|
2757
2865
|
|
|
2758
2866
|
func getInstallId() -> String {
|
|
2759
|
-
|
|
2760
|
-
return installId
|
|
2761
|
-
}
|
|
2762
|
-
|
|
2763
|
-
let identity = InstallIdentity()
|
|
2764
|
-
guard saveInstallIdentity(identity) else {
|
|
2765
|
-
return identity.installId
|
|
2766
|
-
}
|
|
2767
|
-
return identity.installId
|
|
2768
|
-
}
|
|
2769
|
-
|
|
2770
|
-
func getUserId() -> String? {
|
|
2771
|
-
loadUserIdentity()?.userId
|
|
2772
|
-
}
|
|
2773
|
-
|
|
2774
|
-
func getUsername() -> String? {
|
|
2775
|
-
loadUserIdentity()?.username
|
|
2776
|
-
}
|
|
2777
|
-
|
|
2778
|
-
func setUser(userId: String?, username: String?) {
|
|
2779
|
-
let normalizedUserId = normalizeIdentityValue(userId)
|
|
2780
|
-
let normalizedUsername = normalizeIdentityValue(username)
|
|
2781
|
-
let identity = UserIdentity(userId: normalizedUserId, username: normalizedUsername)
|
|
2782
|
-
if identity.isEmpty {
|
|
2783
|
-
_ = saveUserIdentity(nil)
|
|
2784
|
-
return
|
|
2785
|
-
}
|
|
2786
|
-
_ = saveUserIdentity(identity)
|
|
2867
|
+
loadOrCreateInstallIdentity().installId
|
|
2787
2868
|
}
|
|
2788
2869
|
|
|
2789
2870
|
/**
|