@hot-updater/react-native 1.0.0-rc.2 → 1.0.0-rc.22

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 (291) hide show
  1. package/android/proguard-rules.pro +17 -2
  2. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  3. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +954 -837
  4. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +15 -11
  5. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  6. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  7. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +43 -31
  8. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
  9. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  10. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  11. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  12. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  13. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  14. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  15. package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
  16. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  17. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  18. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  19. package/android/src/newarch/HotUpdater.kt +7 -9
  20. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  21. package/android/src/oldarch/HotUpdater.kt +7 -9
  22. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  23. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  24. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  25. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  26. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +950 -1112
  27. package/ios/HotUpdater/Internal/BundleMetadata.swift +8 -53
  28. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  29. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  30. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  31. package/ios/HotUpdater/Internal/HotUpdater.mm +13 -45
  32. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +190 -73
  33. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  34. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  35. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  36. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  37. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  38. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  39. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  40. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  41. package/lib/commonjs/appReady.js +111 -0
  42. package/lib/commonjs/appReady.js.map +1 -0
  43. package/lib/commonjs/checkForUpdate.js +125 -70
  44. package/lib/commonjs/checkForUpdate.js.map +1 -1
  45. package/lib/commonjs/clientPlugin.js +13 -0
  46. package/lib/commonjs/clientPlugin.js.map +1 -0
  47. package/lib/commonjs/error.js +4 -13
  48. package/lib/commonjs/error.js.map +1 -1
  49. package/lib/commonjs/fetchJSON.js +5 -2
  50. package/lib/commonjs/fetchJSON.js.map +1 -1
  51. package/lib/commonjs/httpClient.js +55 -92
  52. package/lib/commonjs/httpClient.js.map +1 -1
  53. package/lib/commonjs/index.js +68 -20
  54. package/lib/commonjs/index.js.map +1 -1
  55. package/lib/commonjs/native.js +82 -62
  56. package/lib/commonjs/native.js.map +1 -1
  57. package/lib/commonjs/pluginHost.js +44 -0
  58. package/lib/commonjs/pluginHost.js.map +1 -0
  59. package/lib/commonjs/releaseCatalogCache.js +20 -61
  60. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  61. package/lib/commonjs/sdkVersion.js +1 -1
  62. package/lib/commonjs/sdkVersion.js.map +1 -1
  63. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  64. package/lib/commonjs/store.js +3 -10
  65. package/lib/commonjs/store.js.map +1 -1
  66. package/lib/commonjs/updateError.js +132 -0
  67. package/lib/commonjs/updateError.js.map +1 -0
  68. package/lib/commonjs/wrap.js +44 -27
  69. package/lib/commonjs/wrap.js.map +1 -1
  70. package/lib/module/appReady.js +103 -0
  71. package/lib/module/appReady.js.map +1 -0
  72. package/lib/module/checkForUpdate.js +118 -65
  73. package/lib/module/checkForUpdate.js.map +1 -1
  74. package/lib/module/clientPlugin.js +4 -0
  75. package/lib/module/clientPlugin.js.map +1 -0
  76. package/lib/module/error.js +4 -13
  77. package/lib/module/error.js.map +1 -1
  78. package/lib/module/fetchJSON.js +3 -1
  79. package/lib/module/fetchJSON.js.map +1 -1
  80. package/lib/module/httpClient.js +53 -90
  81. package/lib/module/httpClient.js.map +1 -1
  82. package/lib/module/index.js +55 -22
  83. package/lib/module/index.js.map +1 -1
  84. package/lib/module/native.js +74 -54
  85. package/lib/module/native.js.map +1 -1
  86. package/lib/module/pluginHost.js +35 -0
  87. package/lib/module/pluginHost.js.map +1 -0
  88. package/lib/module/releaseCatalogCache.js +16 -56
  89. package/lib/module/releaseCatalogCache.js.map +1 -1
  90. package/lib/module/sdkVersion.js +1 -1
  91. package/lib/module/sdkVersion.js.map +1 -1
  92. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  93. package/lib/module/store.js +3 -10
  94. package/lib/module/store.js.map +1 -1
  95. package/lib/module/updateError.js +126 -0
  96. package/lib/module/updateError.js.map +1 -0
  97. package/lib/module/wrap.js +45 -28
  98. package/lib/module/wrap.js.map +1 -1
  99. package/lib/typescript/commonjs/appReady.d.ts +23 -0
  100. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  102. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  104. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/error.d.ts +4 -13
  106. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/fetchJSON.d.ts +3 -0
  108. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/httpClient.d.ts +3 -32
  110. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/index.d.ts +7 -12
  112. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/native.d.ts +33 -23
  114. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  116. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +6 -14
  118. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  120. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +42 -47
  122. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/store.d.ts +1 -3
  124. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/types.d.ts +1 -1
  126. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  128. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  131. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  132. package/lib/typescript/module/appReady.d.ts +23 -0
  133. package/lib/typescript/module/appReady.d.ts.map +1 -0
  134. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  135. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  136. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  137. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  138. package/lib/typescript/module/error.d.ts +4 -13
  139. package/lib/typescript/module/error.d.ts.map +1 -1
  140. package/lib/typescript/module/fetchJSON.d.ts +3 -0
  141. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  142. package/lib/typescript/module/httpClient.d.ts +3 -32
  143. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  144. package/lib/typescript/module/index.d.ts +7 -12
  145. package/lib/typescript/module/index.d.ts.map +1 -1
  146. package/lib/typescript/module/native.d.ts +33 -23
  147. package/lib/typescript/module/native.d.ts.map +1 -1
  148. package/lib/typescript/module/pluginHost.d.ts +4 -0
  149. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  150. package/lib/typescript/module/releaseCatalogCache.d.ts +6 -14
  151. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  152. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  153. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  154. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +42 -47
  155. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  156. package/lib/typescript/module/store.d.ts +1 -3
  157. package/lib/typescript/module/store.d.ts.map +1 -1
  158. package/lib/typescript/module/types.d.ts +1 -1
  159. package/lib/typescript/module/types.d.ts.map +1 -1
  160. package/lib/typescript/module/updateError.d.ts +26 -0
  161. package/lib/typescript/module/updateError.d.ts.map +1 -0
  162. package/lib/typescript/module/wrap.d.ts.map +1 -1
  163. package/lib/typescript/module/wrap.types.d.ts +6 -7
  164. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  165. package/package.json +7 -4
  166. package/src/appReady.ts +149 -0
  167. package/src/checkForUpdate.ts +152 -89
  168. package/src/clientPlugin.ts +17 -0
  169. package/src/error.ts +4 -14
  170. package/src/fetchJSON.ts +5 -1
  171. package/src/httpClient.ts +78 -157
  172. package/src/index.ts +87 -29
  173. package/src/native.ts +109 -104
  174. package/src/pluginHost.ts +45 -0
  175. package/src/releaseCatalogCache.ts +26 -140
  176. package/src/sdkVersion.ts +1 -1
  177. package/src/specs/NativeHotUpdater.ts +41 -48
  178. package/src/store.ts +2 -23
  179. package/src/types.ts +1 -1
  180. package/src/updateError.ts +153 -0
  181. package/src/wrap.tsx +59 -28
  182. package/src/wrap.types.ts +6 -7
  183. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  184. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  185. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  186. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  187. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  188. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  189. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  190. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  191. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  192. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  193. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  194. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  195. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  196. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  197. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  198. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  199. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  200. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  201. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  202. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  203. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  204. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  205. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  206. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -604
  207. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  208. package/lib/commonjs/httpClient.spec.js +0 -102
  209. package/lib/commonjs/httpClient.spec.js.map +0 -1
  210. package/lib/commonjs/index.spec.js +0 -298
  211. package/lib/commonjs/index.spec.js.map +0 -1
  212. package/lib/commonjs/iosInterop.spec.js +0 -18
  213. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  214. package/lib/commonjs/native.spec.js +0 -795
  215. package/lib/commonjs/native.spec.js.map +0 -1
  216. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  217. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  218. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  219. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  220. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  221. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  222. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  223. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  224. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  225. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  226. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  227. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  228. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  229. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  230. package/lib/commonjs/store.notification.spec.js +0 -76
  231. package/lib/commonjs/store.notification.spec.js.map +0 -1
  232. package/lib/commonjs/store.spec.js +0 -316
  233. package/lib/commonjs/store.spec.js.map +0 -1
  234. package/lib/commonjs/wrap.spec.js +0 -296
  235. package/lib/commonjs/wrap.spec.js.map +0 -1
  236. package/lib/module/catalogCacheNative.spec.js +0 -31
  237. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  238. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -603
  239. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  240. package/lib/module/httpClient.spec.js +0 -102
  241. package/lib/module/httpClient.spec.js.map +0 -1
  242. package/lib/module/index.spec.js +0 -297
  243. package/lib/module/index.spec.js.map +0 -1
  244. package/lib/module/iosInterop.spec.js +0 -18
  245. package/lib/module/iosInterop.spec.js.map +0 -1
  246. package/lib/module/native.spec.js +0 -794
  247. package/lib/module/native.spec.js.map +0 -1
  248. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  249. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  250. package/lib/module/notifyAppReadyInsights.js +0 -128
  251. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  252. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  253. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  254. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  255. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  256. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  257. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  258. package/lib/module/releaseCatalogCache.spec.js +0 -195
  259. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  260. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  261. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  262. package/lib/module/store.notification.spec.js +0 -77
  263. package/lib/module/store.notification.spec.js.map +0 -1
  264. package/lib/module/store.spec.js +0 -315
  265. package/lib/module/store.spec.js.map +0 -1
  266. package/lib/module/wrap.spec.js +0 -295
  267. package/lib/module/wrap.spec.js.map +0 -1
  268. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  269. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  270. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  271. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  272. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  273. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  274. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  275. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  276. package/src/catalogCacheNative.spec.ts +0 -63
  277. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -596
  278. package/src/httpClient.spec.ts +0 -132
  279. package/src/index.spec.ts +0 -331
  280. package/src/iosInterop.spec.ts +0 -30
  281. package/src/native.spec.ts +0 -855
  282. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  283. package/src/notifyAppReadyInsights.spec.ts +0 -243
  284. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  285. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  286. package/src/notifyAppReadyInsights.ts +0 -194
  287. package/src/releaseCatalogCache.spec.ts +0 -282
  288. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  289. package/src/store.notification.spec.ts +0 -92
  290. package/src/store.spec.ts +0 -372
  291. package/src/wrap.spec.ts +0 -367
@@ -17,6 +17,18 @@ private func hotUpdaterUpdateSignalLaunchStateSymbol(
17
17
  @_silgen_name("HotUpdaterPerformRecoveryReload")
18
18
  private func hotUpdaterPerformRecoveryReloadSymbol() -> ObjCBool
19
19
 
20
+ @_silgen_name("HotUpdaterMarkFailedRuntime")
21
+ private func hotUpdaterMarkFailedRuntime()
22
+
23
+ @_silgen_name("HotUpdaterIsFailedRuntime")
24
+ private func hotUpdaterIsFailedRuntime() -> ObjCBool
25
+
26
+ @_silgen_name("HotUpdaterInstallRecoveryHooks")
27
+ private func hotUpdaterInstallRecoveryHooks()
28
+
29
+ @_silgen_name("HotUpdaterAfterSurfaceStarts")
30
+ private func hotUpdaterAfterSurfaceStarts(_ action: @convention(block) @escaping () -> Void)
31
+
20
32
  @_silgen_name("HotUpdaterCopyMinBundleId")
21
33
  private func hotUpdaterCopyMinBundleIdSymbol() -> UnsafeMutableRawPointer
22
34
 
@@ -44,11 +56,27 @@ private func hotUpdaterGetMinBundleId() -> String {
44
56
  private let cohortService: CohortService
45
57
  private let recoveryManager: HotUpdaterRecoveryManager
46
58
  private let releaseCatalogCache: ReleaseCatalogCacheService
59
+ private let keyValueStorage: KeyValueStorageService
47
60
  private var currentLaunchSelection: LaunchSelection?
48
61
 
49
62
  private static let DEFAULT_CHANNEL = "production"
50
63
  private static let CHANNEL_STORAGE_KEY = "HotUpdaterChannel"
51
64
 
65
+ private static func parseSafeByteSize(_ value: Any?) -> Int64? {
66
+ guard let number = value as? NSNumber,
67
+ String(cString: number.objCType) != "c" else {
68
+ return nil
69
+ }
70
+ let doubleValue = number.doubleValue
71
+ guard doubleValue.isFinite,
72
+ doubleValue >= 0,
73
+ doubleValue <= 9_007_199_254_740_991,
74
+ doubleValue.rounded(.towardZero) == doubleValue else {
75
+ return nil
76
+ }
77
+ return Int64(doubleValue)
78
+ }
79
+
52
80
  // MARK: - Initialization
53
81
 
54
82
  /**
@@ -59,12 +87,9 @@ private func hotUpdaterGetMinBundleId() -> String {
59
87
  let isolationKey = HotUpdaterImpl.getIsolationKey()
60
88
  let preferences = VersionedPreferencesService()
61
89
  let downloadService = URLSessionDownloadService()
62
- let decompressService = DecompressService()
63
-
64
90
  let bundleStorage = BundleFileStorageService(
65
91
  fileSystem: fileSystem,
66
92
  downloadService: downloadService,
67
- decompressService: decompressService,
68
93
  preferences: preferences,
69
94
  isolationKey: isolationKey,
70
95
  builtInBundleIdProvider: { HotUpdaterImpl.minBundleId() }
@@ -75,7 +100,10 @@ private func hotUpdaterGetMinBundleId() -> String {
75
100
  bundleStorage: bundleStorage,
76
101
  preferences: preferences,
77
102
  recoveryManager: recoveryManager,
78
- releaseCatalogCache: ReleaseCatalogCacheService()
103
+ releaseCatalogCache: ReleaseCatalogCacheService(),
104
+ keyValueStorage: KeyValueStorageService(
105
+ directory: NoBackupStorage.directory(in: fileSystem)
106
+ )
79
107
  )
80
108
  }
81
109
 
@@ -83,18 +111,23 @@ private func hotUpdaterGetMinBundleId() -> String {
83
111
  * Primary initializer with dependency injection.
84
112
  * @param bundleStorage Service for bundle storage operations
85
113
  * @param preferences Service for preference storage
114
+ * @param keyValueStorage Persistent key-value store for client plugins
86
115
  */
87
116
  internal init(
88
117
  bundleStorage: BundleStorageService,
89
118
  preferences: PreferencesService,
90
119
  recoveryManager: HotUpdaterRecoveryManager,
91
- releaseCatalogCache: ReleaseCatalogCacheService = ReleaseCatalogCacheService()
120
+ releaseCatalogCache: ReleaseCatalogCacheService = ReleaseCatalogCacheService(),
121
+ keyValueStorage: KeyValueStorageService = KeyValueStorageService(
122
+ directory: NoBackupStorage.directory(in: FileManagerService())
123
+ )
92
124
  ) {
93
125
  self.bundleStorage = bundleStorage
94
126
  self.preferences = preferences
95
127
  self.cohortService = CohortService()
96
128
  self.recoveryManager = recoveryManager
97
129
  self.releaseCatalogCache = releaseCatalogCache
130
+ self.keyValueStorage = keyValueStorage
98
131
  super.init()
99
132
 
100
133
  // Configure preferences with isolation key
@@ -219,9 +252,11 @@ private func hotUpdaterGetMinBundleId() -> String {
219
252
  /**
220
253
  * Updates the bundle from JavaScript bridge.
221
254
  * This method acts as the primary error boundary for all bundle operations.
222
- * @param params Dictionary with bundleId and fileUrl parameters
223
- * @param resolve Promise resolve callback
224
- * @param reject Promise reject callback
255
+ * @param params Dictionary with bundleId, manifest, and asset parameters
256
+ * @param resolve Promise resolve callback, called with the staged bundle's
257
+ * `{ delivery, patchFallback }`
258
+ * @param reject Promise reject callback; download and install failures
259
+ * carry `stage`, `reason`, and their details in the error's userInfo
225
260
  */
226
261
  public func updateBundle(_ params: NSDictionary?,
227
262
  resolver resolve: @escaping RCTPromiseResolveBlock,
@@ -236,43 +271,49 @@ private func hotUpdaterGetMinBundleId() -> String {
236
271
  return
237
272
  }
238
273
 
274
+ // Invalid update parameters are an invalid update response.
239
275
  guard let bundleId = data["bundleId"] as? String, !bundleId.isEmpty else {
240
- let error = NSError(domain: "HotUpdater", code: 0,
241
- userInfo: [NSLocalizedDescriptionKey: "Missing or empty 'bundleId'"])
276
+ let error = UpdateFailure.invalidResponse.error(description: "Missing or empty 'bundleId'")
242
277
  reject("MISSING_BUNDLE_ID", error.localizedDescription, error)
243
278
  return
244
279
  }
245
280
 
246
- let fileUrlString = data["fileUrl"] as? String ?? ""
247
-
248
- var fileUrl: URL? = nil
249
- if !fileUrlString.isEmpty {
250
- guard let url = URL(string: fileUrlString) else {
251
- let error = NSError(domain: "HotUpdater", code: 0,
252
- userInfo: [NSLocalizedDescriptionKey: "Invalid 'fileUrl' provided: \(fileUrlString)"])
253
- reject("INVALID_FILE_URL", error.localizedDescription, error)
254
- return
255
- }
256
- fileUrl = url
281
+ guard let manifestFileHash = data["manifestFileHash"] as? String,
282
+ !manifestFileHash.isEmpty else {
283
+ let error = UpdateFailure.invalidResponse.with(resource: .manifest)
284
+ .error(description: "Missing manifest file hash")
285
+ reject("INVALID_MANIFEST", error.localizedDescription, error)
286
+ return
257
287
  }
258
-
259
- // Extract fileHash if provided
260
- let fileHash = data["fileHash"] as? String
261
- let manifestFileHash = data["manifestFileHash"] as? String
262
288
  let channel = data["channel"] as? String
263
289
  let manifestUrlString = data["manifestUrl"] as? String ?? ""
264
- var manifestUrl: URL? = nil
265
- if !manifestUrlString.isEmpty {
266
- guard let url = URL(string: manifestUrlString) else {
267
- let error = NSError(domain: "HotUpdater", code: 0,
268
- userInfo: [NSLocalizedDescriptionKey: "Invalid 'manifestUrl' provided: \(manifestUrlString)"])
290
+ guard let manifestUrl = URL(string: manifestUrlString) else {
291
+ let error = UpdateFailure.invalidResponse.with(resource: .manifest).error(
292
+ description: "Invalid 'manifestUrl' provided: \(manifestUrlString)"
293
+ )
294
+ reject("INVALID_FILE_URL", error.localizedDescription, error)
295
+ return
296
+ }
297
+ let archiveUrl: URL?
298
+ if let archiveUrlString = data["archiveUrl"] as? String {
299
+ guard let parsedArchiveUrl = URL(string: archiveUrlString) else {
300
+ let error = UpdateFailure.invalidResponse.with(resource: .archive).error(
301
+ description: "Invalid 'archiveUrl' provided: \(archiveUrlString)"
302
+ )
269
303
  reject("INVALID_FILE_URL", error.localizedDescription, error)
270
304
  return
271
305
  }
272
- manifestUrl = url
306
+ archiveUrl = parsedArchiveUrl
307
+ } else {
308
+ archiveUrl = nil
309
+ }
310
+ guard let assetsPayload = data["assets"] as? [String: [String: Any]] else {
311
+ let error = UpdateFailure.invalidResponse.with(resource: .manifest)
312
+ .error(description: "Missing manifest assets")
313
+ reject("INVALID_MANIFEST", error.localizedDescription, error)
314
+ return
273
315
  }
274
- let changedAssetsPayload = data["changedAssets"] as? [String: [String: Any]]
275
- let changedAssets = changedAssetsPayload?.reduce(into: [String: ChangedAssetDescriptor]()) { partialResult, entry in
316
+ let assets = assetsPayload.reduce(into: [String: ChangedAssetDescriptor]()) { partialResult, entry in
276
317
  guard let fileHash = entry.value["fileHash"] as? String,
277
318
  !fileHash.isEmpty
278
319
  else {
@@ -296,7 +337,8 @@ private func hotUpdaterGetMinBundleId() -> String {
296
337
  baseBundleId: baseBundleId,
297
338
  baseFileHash: baseFileHash,
298
339
  patchFileHash: patchFileHash,
299
- patchUrl: patchUrl
340
+ patchUrl: patchUrl,
341
+ byteSize: Self.parseSafeByteSize(payload["byteSize"])
300
342
  )
301
343
  }
302
344
  let filePayload = entry.value["file"] as? [String: Any]
@@ -315,11 +357,8 @@ private func hotUpdaterGetMinBundleId() -> String {
315
357
  }
316
358
  let selection = (data["selection"] as? [String: Any]).flatMap(Self.parseSelection)
317
359
  if let selection, !bundleStorage.stageReleaseSelection(selection) {
318
- let error = NSError(
319
- domain: "HotUpdater",
320
- code: 0,
321
- userInfo: [NSLocalizedDescriptionKey: "Release catalog selection is stale"]
322
- )
360
+ // Not an update failure, so the rejection carries no stage or reason.
361
+ let error = StaleReleaseSelectionError() as NSError
323
362
  reject("UNKNOWN_ERROR", error.localizedDescription, error)
324
363
  return
325
364
  }
@@ -327,10 +366,10 @@ private func hotUpdaterGetMinBundleId() -> String {
327
366
  // Extract progress callback if provided
328
367
  let progressCallback = data["progressCallback"] as? RCTResponseSenderBlock
329
368
 
330
- NSLog("[HotUpdaterImpl] updateBundle called with bundleId: \(bundleId), fileUrl: \(fileUrl?.absoluteString ?? "nil"), fileHash: \(fileHash ?? "nil")")
369
+ NSLog("[HotUpdaterImpl] updateBundle called with bundleId: \(bundleId), manifestUrl: \(manifestUrl.absoluteString)")
331
370
 
332
371
  // Heavy work is delegated to bundle storage service with safe error handling
333
- bundleStorage.updateBundle(bundleId: bundleId, fileUrl: fileUrl, fileHash: fileHash, manifestUrl: manifestUrl, manifestFileHash: manifestFileHash, changedAssets: changedAssets, progressHandler: { payload in
372
+ bundleStorage.updateBundle(bundleId: bundleId, manifestUrl: manifestUrl, manifestFileHash: manifestFileHash, archiveUrl: archiveUrl, assets: assets, progressHandler: { payload in
334
373
  DispatchQueue.main.async {
335
374
  NotificationCenter.default.post(
336
375
  name: .updateProgressDidChange,
@@ -354,7 +393,7 @@ private func hotUpdaterGetMinBundleId() -> String {
354
393
  // Return results on main thread for React Native bridge
355
394
  DispatchQueue.main.async {
356
395
  switch result {
357
- case .success:
396
+ case .success(let delivery):
358
397
  NSLog("[HotUpdaterImpl] Update successful for \(bundleId). Resolving promise.")
359
398
  if let channel, !channel.isEmpty {
360
399
  do {
@@ -367,12 +406,14 @@ private func hotUpdaterGetMinBundleId() -> String {
367
406
  NSLog("[HotUpdaterImpl] Failed to persist channel override: \(error)")
368
407
  }
369
408
  }
370
- resolve(true)
409
+ // { delivery: "patch" | "manifest" | "archive", patchFallback }
410
+ resolve(delivery.dictionary)
371
411
  case .failure(let error):
372
412
  NSLog("[HotUpdaterImpl] Update failed for \(bundleId) - Error: \(error)")
373
413
 
374
414
  let normalizedCode = HotUpdaterImpl.normalizeErrorCode(from: error)
375
- let nsError = error as NSError
415
+ // A classified failure adds stage, reason, and its details to userInfo.
416
+ let nsError = UpdateFailureError.rejectionError(for: error)
376
417
  reject(normalizedCode, nsError.localizedDescription, nsError)
377
418
  }
378
419
  }
@@ -507,6 +548,8 @@ private func hotUpdaterGetMinBundleId() -> String {
507
548
  * Rare or platform-specific codes are collapsed to UNKNOWN_ERROR to reduce surface area.
508
549
  */
509
550
  private static func normalizeErrorCode(from error: Error) -> String {
551
+ // Classification wraps an error without changing its code.
552
+ let error = UpdateFailureError.unwrap(error)
510
553
  let baseCode: String
511
554
 
512
555
  if let storageError = error as? BundleStorageError {
@@ -532,7 +575,6 @@ private func hotUpdaterGetMinBundleId() -> String {
532
575
  "DIRECTORY_CREATION_FAILED",
533
576
  "DOWNLOAD_FAILED",
534
577
  "INCOMPLETE_DOWNLOAD",
535
- "EXTRACTION_FORMAT_ERROR",
536
578
  "INVALID_BUNDLE",
537
579
  "INSUFFICIENT_DISK_SPACE",
538
580
  "SIGNATURE_VERIFICATION_FAILED",
@@ -545,6 +587,11 @@ private func hotUpdaterGetMinBundleId() -> String {
545
587
  // MARK: - Rollback Support
546
588
 
547
589
  public func notifyAppReady() -> [String: Any] {
590
+ // A failed runtime can finish queued native calls while recovery starts.
591
+ // Keep its readiness loop from consuming the new launch's report.
592
+ if hotUpdaterIsFailedRuntime().boolValue {
593
+ return ["status": "PENDING"]
594
+ }
548
595
  return bundleStorage.notifyAppReady()
549
596
  }
550
597
 
@@ -568,16 +615,21 @@ private func hotUpdaterGetMinBundleId() -> String {
568
615
  return bundleStorage.getInstallId()
569
616
  }
570
617
 
571
- public func getUserId() -> String? {
572
- return bundleStorage.getUserId()
573
- }
618
+ // MARK: - Client Plugin Storage
574
619
 
575
- public func getUsername() -> String? {
576
- return bundleStorage.getUsername()
620
+ /**
621
+ * Reads a value from the persistent key-value store for client plugins.
622
+ * @return The stored value or nil when the key has none
623
+ */
624
+ public func getStorageItem(_ key: String) -> String? {
625
+ return keyValueStorage.getItem(key)
577
626
  }
578
627
 
579
- public func setUser(_ userId: String?, username: String?) {
580
- bundleStorage.setUser(userId: userId, username: username)
628
+ /**
629
+ * Writes a value to the persistent key-value store; nil removes the key.
630
+ */
631
+ public func setStorageItem(_ key: String, value: String?) {
632
+ keyValueStorage.setItem(key, value: value)
581
633
  }
582
634
 
583
635
  /**
@@ -597,7 +649,7 @@ private func hotUpdaterGetMinBundleId() -> String {
597
649
 
598
650
  /**
599
651
  * Gets the current active bundle ID from bundle storage.
600
- * Reads manifest.json first and falls back to the legacy BUNDLE_ID file.
652
+ * Reads the required OTA manifest.json.
601
653
  * Built-in bundle fallback is handled in JS.
602
654
  */
603
655
  public func getBundleId() -> String? {
@@ -672,6 +724,7 @@ final class HotUpdaterRecoveryManager: NSObject {
672
724
  private var handlersInstalled = false
673
725
  private var isMonitoring = false
674
726
  private var recoveryRequested = false
727
+ private var monitoringGeneration = 0
675
728
  private var currentBundleId: String?
676
729
  private var shouldRollbackOnCrash = false
677
730
  private var contentAppearedCallback: ((String?) -> Void)?
@@ -709,6 +762,10 @@ final class HotUpdaterRecoveryManager: NSObject {
709
762
  shouldRollback: Bool,
710
763
  onContentAppeared: @escaping (String?) -> Void
711
764
  ) {
765
+ objc_sync_enter(self)
766
+ defer { objc_sync_exit(self) }
767
+
768
+ monitoringGeneration += 1
712
769
  currentBundleId = bundleId
713
770
  shouldRollbackOnCrash = shouldRollback
714
771
  recoveryRequested = false
@@ -719,6 +776,7 @@ final class HotUpdaterRecoveryManager: NSObject {
719
776
  stopMonitoringWorkItem = nil
720
777
 
721
778
  installHandlersIfNeeded()
779
+ hotUpdaterInstallRecoveryHooks()
722
780
  registerObservers()
723
781
  installSignalHandlersIfNeeded()
724
782
  hotUpdaterUpdateSignalLaunchState(bundleId, shouldRollback: shouldRollback)
@@ -726,9 +784,8 @@ final class HotUpdaterRecoveryManager: NSObject {
726
784
 
727
785
  func handleUncaughtException(_ exception: NSException) {
728
786
  writeCrashMarker()
729
- if requestRecoveryReloadIfNeeded() {
730
- return
731
- }
787
+ // An uncaught native exception cannot safely continue in this process.
788
+ // Preserve the marker so the next launch can select the safe bundle.
732
789
  previousUncaughtExceptionHandler?(exception)
733
790
  }
734
791
 
@@ -742,16 +799,22 @@ final class HotUpdaterRecoveryManager: NSObject {
742
799
  previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler()
743
800
 
744
801
  RCTSetFatalHandler { [weak self] error in
745
- self?.writeCrashMarker()
746
- if self?.requestRecoveryReloadIfNeeded() != true {
747
- self?.previousFatalHandler?(error)
802
+ guard !hotUpdaterIsFailedRuntime().boolValue else { return }
803
+ if self?.requestRecoveryReloadIfNeeded(onFailure: {
804
+ self?.forwardFatalError(error)
805
+ }) != true {
806
+ self?.writeCrashMarker()
807
+ self?.forwardFatalError(error)
748
808
  }
749
809
  }
750
810
 
751
811
  RCTSetFatalExceptionHandler { [weak self] exception in
752
- self?.writeCrashMarker()
753
- if self?.requestRecoveryReloadIfNeeded() != true {
754
- self?.previousFatalExceptionHandler?(exception)
812
+ guard !hotUpdaterIsFailedRuntime().boolValue else { return }
813
+ if self?.requestRecoveryReloadIfNeeded(onFailure: {
814
+ self?.forwardFatalException(exception)
815
+ }) != true {
816
+ self?.writeCrashMarker()
817
+ self?.forwardFatalException(exception)
755
818
  }
756
819
  }
757
820
 
@@ -759,6 +822,33 @@ final class HotUpdaterRecoveryManager: NSObject {
759
822
  handlersInstalled = true
760
823
  }
761
824
 
825
+ private func forwardFatalError(_ error: Error?) {
826
+ if let previousFatalHandler {
827
+ previousFatalHandler(error)
828
+ } else {
829
+ let handler = RCTGetFatalHandler()
830
+ RCTSetFatalHandler(nil)
831
+ RCTFatal(error)
832
+ // React Native's DEBUG default handler can return.
833
+ if RCTGetFatalHandler() == nil {
834
+ RCTSetFatalHandler(handler)
835
+ }
836
+ }
837
+ }
838
+
839
+ private func forwardFatalException(_ exception: NSException?) {
840
+ if let previousFatalExceptionHandler {
841
+ previousFatalExceptionHandler(exception)
842
+ } else {
843
+ let handler = RCTGetFatalExceptionHandler()
844
+ RCTSetFatalExceptionHandler(nil)
845
+ RCTFatalException(exception)
846
+ if RCTGetFatalExceptionHandler() == nil {
847
+ RCTSetFatalExceptionHandler(handler)
848
+ }
849
+ }
850
+ }
851
+
762
852
  private func installSignalHandlersIfNeeded() {
763
853
  guard !signalHandlersInstalled else {
764
854
  return
@@ -777,7 +867,7 @@ final class HotUpdaterRecoveryManager: NSObject {
777
867
  unregisterObservers()
778
868
  NotificationCenter.default.addObserver(
779
869
  self,
780
- selector: #selector(handleJavaScriptDidFailToLoad),
870
+ selector: #selector(handleJavaScriptDidFailToLoad(_:)),
781
871
  name: NSNotification.Name.RCTJavaScriptDidFailToLoad,
782
872
  object: nil
783
873
  )
@@ -802,15 +892,25 @@ final class HotUpdaterRecoveryManager: NSObject {
802
892
  )
803
893
  }
804
894
 
805
- @objc private func handleJavaScriptDidFailToLoad() {
806
- if requestRecoveryReloadIfNeeded() {
895
+ @objc private func handleJavaScriptDidFailToLoad(_ notification: Notification) {
896
+ let error = notification.userInfo?["error"] as? NSError ?? NSError(
897
+ domain: "HotUpdaterRecovery",
898
+ code: 1,
899
+ userInfo: [NSLocalizedDescriptionKey: "JavaScript bundle failed to load"]
900
+ )
901
+ if requestRecoveryReloadIfNeeded(onFailure: { [weak self] in
902
+ self?.forwardFatalError(error)
903
+ }) {
807
904
  return
808
905
  }
809
906
  unregisterObservers()
810
907
  }
811
908
 
812
909
  @objc private func handleContentDidAppear() {
813
- guard isMonitoring else {
910
+ objc_sync_enter(self)
911
+ defer { objc_sync_exit(self) }
912
+
913
+ guard isMonitoring, !recoveryRequested else {
814
914
  return
815
915
  }
816
916
 
@@ -820,14 +920,19 @@ final class HotUpdaterRecoveryManager: NSObject {
820
920
  hotUpdaterUpdateSignalLaunchState(currentBundleId, shouldRollback: false)
821
921
 
822
922
  stopMonitoringWorkItem?.cancel()
923
+ let generation = monitoringGeneration
823
924
  let workItem = DispatchWorkItem { [weak self] in
824
- self?.finishMonitoring()
925
+ self?.finishMonitoring(generation: generation)
825
926
  }
826
927
  stopMonitoringWorkItem = workItem
827
928
  DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute: workItem)
828
929
  }
829
930
 
830
- private func finishMonitoring() {
931
+ private func finishMonitoring(generation: Int) {
932
+ objc_sync_enter(self)
933
+ defer { objc_sync_exit(self) }
934
+ guard monitoringGeneration == generation else { return }
935
+
831
936
  isMonitoring = false
832
937
  recoveryRequested = false
833
938
  stopMonitoringWorkItem = nil
@@ -837,31 +942,40 @@ final class HotUpdaterRecoveryManager: NSObject {
837
942
  hotUpdaterUpdateSignalLaunchState(nil, shouldRollback: false)
838
943
  }
839
944
 
840
- private func requestRecoveryReloadIfNeeded() -> Bool {
945
+ private func requestRecoveryReloadIfNeeded(onFailure: @escaping () -> Void) -> Bool {
946
+ objc_sync_enter(self)
947
+ defer { objc_sync_exit(self) }
948
+
841
949
  guard isMonitoring, shouldRollbackOnCrash else {
842
950
  return false
843
951
  }
844
952
 
845
- objc_sync_enter(self)
953
+ hotUpdaterMarkFailedRuntime()
846
954
  if recoveryRequested {
847
- objc_sync_exit(self)
848
955
  return true
849
956
  }
850
957
  recoveryRequested = true
851
- objc_sync_exit(self)
958
+ writeCrashMarker()
852
959
 
853
960
  let bundleId = currentBundleId
854
- DispatchQueue.main.async { [weak self] in
961
+ let generation = monitoringGeneration
962
+ hotUpdaterAfterSurfaceStarts { [weak self] in
855
963
  guard let self else {
856
964
  return
857
965
  }
858
966
 
967
+ objc_sync_enter(self)
968
+ let isCurrentLaunch = self.monitoringGeneration == generation
969
+ objc_sync_exit(self)
970
+ guard isCurrentLaunch else { return }
971
+
859
972
  let started = hotUpdaterPerformRecoveryReload()
860
973
  if !started {
861
974
  objc_sync_enter(self)
862
975
  self.recoveryRequested = false
863
976
  objc_sync_exit(self)
864
977
  NSLog("[HotUpdaterRecovery] Failed to trigger recovery reload")
978
+ onFailure()
865
979
  } else {
866
980
  NSLog("[HotUpdaterRecovery] Triggered recovery reload for bundleId=\(bundleId ?? "nil")")
867
981
  }
@@ -871,6 +985,9 @@ final class HotUpdaterRecoveryManager: NSObject {
871
985
  }
872
986
 
873
987
  private func writeCrashMarker() {
988
+ objc_sync_enter(self)
989
+ defer { objc_sync_exit(self) }
990
+
874
991
  guard isMonitoring else {
875
992
  return
876
993
  }
@@ -0,0 +1,71 @@
1
+ import Foundation
2
+
3
+ /// Persistent string key-value store for client plugins, kept in
4
+ /// `Application Support/HotUpdater/storage.json` outside device backups.
5
+ ///
6
+ /// Keys are opaque; JavaScript namespaces them per plugin. The file is read
7
+ /// into memory on first use, and every write replaces the whole file.
8
+ final class KeyValueStorageService {
9
+ static let storageFilename = "storage.json"
10
+
11
+ private let fileURL: URL
12
+ private let lock = NSLock()
13
+ private var items: [String: String]?
14
+
15
+ init(directory: URL) {
16
+ self.fileURL = directory.appendingPathComponent(Self.storageFilename)
17
+ }
18
+
19
+ func getItem(_ key: String) -> String? {
20
+ lock.lock()
21
+ defer { lock.unlock() }
22
+ return loadedItems()[key]
23
+ }
24
+
25
+ /// Stores `value` under `key`, or removes the key when `value` is nil.
26
+ /// Returns false when the file could not be written.
27
+ @discardableResult
28
+ func setItem(_ key: String, value: String?) -> Bool {
29
+ lock.lock()
30
+ defer { lock.unlock() }
31
+
32
+ var updatedItems = loadedItems()
33
+ updatedItems[key] = value
34
+ // Memory stays the source of truth for this process, so the next
35
+ // successful write also persists a value whose own write failed.
36
+ items = updatedItems
37
+
38
+ do {
39
+ let data = try JSONSerialization.data(
40
+ withJSONObject: updatedItems,
41
+ options: [.sortedKeys]
42
+ )
43
+ try NoBackupStorage.write(data, to: fileURL)
44
+ return true
45
+ } catch {
46
+ NSLog("[KeyValueStorage] Failed to write \(fileURL.path): \(error)")
47
+ return false
48
+ }
49
+ }
50
+
51
+ private func loadedItems() -> [String: String] {
52
+ if let items {
53
+ return items
54
+ }
55
+ let loadedItems = Self.readItems(from: fileURL)
56
+ items = loadedItems
57
+ return loadedItems
58
+ }
59
+
60
+ /// A missing, unreadable, or corrupt file reads as an empty store.
61
+ private static func readItems(from file: URL) -> [String: String] {
62
+ guard let data = try? Data(contentsOf: file) else {
63
+ return [:]
64
+ }
65
+ guard let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
66
+ NSLog("[KeyValueStorage] Ignoring unreadable store at \(file.path)")
67
+ return [:]
68
+ }
69
+ return object.compactMapValues { $0 as? String }
70
+ }
71
+ }
@@ -1,7 +1,5 @@
1
1
  import Foundation
2
2
 
3
3
  extension Notification.Name {
4
- static let downloadProgressUpdate = Notification.Name("HotUpdaterDownloadProgressUpdate")
5
- static let downloadDidFinish = Notification.Name("HotUpdaterDownloadDidFinish")
6
4
  static let updateProgressDidChange = Notification.Name("HotUpdaterUpdateProgressDidChange")
7
5
  }