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

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
@@ -23,27 +23,118 @@ protocol DownloadService {
23
23
  }
24
24
 
25
25
 
26
- enum DownloadError: Error {
26
+ enum DownloadError: Error, Equatable {
27
27
  case incompleteDownload(expected: Int64, actual: Int64)
28
28
  case invalidContentLength
29
+ /// The server answered with a status outside 200-299. `originCode` is the
30
+ /// `<Code>` of a storage origin's XML error body, when it has one.
31
+ case httpStatus(Int, originCode: String? = nil)
32
+
33
+ /// How much of an error body is searched for a storage origin's code.
34
+ static let maximumErrorBodyPrefixByteCount = 4 * 1024
35
+ private static let maximumOriginCodeLength = 64
36
+
37
+ /// The failure for an HTTP response outside 200-299, or nil for a
38
+ /// successful or non-HTTP response. `body` is the saved response body.
39
+ static func httpStatusError(for response: URLResponse?, body: URL?) -> DownloadError? {
40
+ guard let httpResponse = response as? HTTPURLResponse,
41
+ !(200..<300).contains(httpResponse.statusCode) else {
42
+ return nil
43
+ }
44
+ let originCode = body.flatMap { body -> String? in
45
+ guard let handle = try? FileHandle(forReadingFrom: body) else {
46
+ return nil
47
+ }
48
+ defer { try? handle.close() }
49
+ let prefix = try? FileUtilities.readUpToCount(
50
+ from: handle,
51
+ count: maximumErrorBodyPrefixByteCount
52
+ )
53
+ return prefix.flatMap { storageOriginErrorCode(in: $0) }
54
+ }
55
+ return .httpStatus(httpResponse.statusCode, originCode: originCode)
56
+ }
57
+
58
+ /// The `<Code>` of an S3, R2, or GCS style `<Error>` document in the
59
+ /// first 4 KB of `body`, such as "AccessDenied". Nothing else from the
60
+ /// body is kept, and a code must be 1-64 characters of [A-Za-z0-9._-].
61
+ static func storageOriginErrorCode(in body: Data) -> String? {
62
+ let text = String(decoding: body.prefix(maximumErrorBodyPrefixByteCount), as: UTF8.self)
63
+ guard let errorElement = text.range(of: "<Error"),
64
+ let codeStart = text.range(of: "<Code>", range: errorElement.upperBound..<text.endIndex),
65
+ let codeEnd = text.range(of: "</Code>", range: codeStart.upperBound..<text.endIndex) else {
66
+ return nil
67
+ }
68
+ let code = text[codeStart.upperBound..<codeEnd.lowerBound]
69
+ .trimmingCharacters(in: .whitespacesAndNewlines)
70
+ guard (1...maximumOriginCodeLength).contains(code.unicodeScalars.count),
71
+ code.unicodeScalars.allSatisfy(isOriginCodeCharacter) else {
72
+ return nil
73
+ }
74
+ return code
75
+ }
76
+
77
+ private static func isOriginCodeCharacter(_ scalar: Unicode.Scalar) -> Bool {
78
+ switch scalar {
79
+ case "A"..."Z", "a"..."z", "0"..."9", ".", "_", "-":
80
+ return true
81
+ default:
82
+ return false
83
+ }
84
+ }
29
85
  }
30
86
 
31
- // Task state for persistence and recovery
32
- struct TaskState: Codable {
33
- let taskIdentifier: Int
34
- let destination: String
35
- let bundleId: String
36
- let startedAt: TimeInterval
87
+ extension DownloadError {
88
+ /// Attempts per download that retries, the first one included.
89
+ static let maximumAttempts = 3
90
+
91
+ /// Whether a failed download may succeed if tried again: a network
92
+ /// failure or timeout, a body that ended early, or a 408, 429, or 5xx
93
+ /// answer. Another 4xx, a TLS failure, a cancelled task, and a local file
94
+ /// failure would fail the same way again.
95
+ static func isRetryable(_ error: Error) -> Bool {
96
+ if let downloadError = error as? DownloadError {
97
+ switch downloadError {
98
+ case .httpStatus(let status, _):
99
+ return status == 408 || status == 429 || status >= 500
100
+ case .incompleteDownload:
101
+ return true
102
+ case .invalidContentLength:
103
+ return false
104
+ }
105
+ }
106
+ let nsError = error as NSError
107
+ guard nsError.domain == NSURLErrorDomain else {
108
+ return false
109
+ }
110
+ switch URLError.Code(rawValue: nsError.code) {
111
+ case .timedOut,
112
+ .cannotFindHost,
113
+ .cannotConnectToHost,
114
+ .networkConnectionLost,
115
+ .dnsLookupFailed,
116
+ .notConnectedToInternet,
117
+ .cannotLoadFromNetwork,
118
+ .callIsActive,
119
+ .dataNotAllowed,
120
+ .internationalRoamingOff,
121
+ .backgroundSessionWasDisconnected,
122
+ .badServerResponse:
123
+ return true
124
+ default:
125
+ return false
126
+ }
127
+ }
37
128
  }
38
129
 
39
130
  class URLSessionDownloadService: NSObject, DownloadService {
131
+ private let stateLock = NSRecursiveLock()
40
132
  private var session: URLSession!
41
133
  private var backgroundSession: URLSession!
42
134
  private var progressHandlers: [URLSessionTask: (DownloadProgress) -> Void] = [:]
43
135
  private var completionHandlers: [URLSessionTask: (Result<URL, Error>) -> Void] = [:]
44
136
  private var destinations: [URLSessionTask: String] = [:]
45
137
  private var fileSizeHandlers: [URLSessionTask: (Int64) -> Void] = [:]
46
- private var taskStates: [Int: TaskState] = [:]
47
138
 
48
139
  override init() {
49
140
  super.init()
@@ -61,51 +152,22 @@ class URLSessionDownloadService: NSObject, DownloadService {
61
152
  backgroundConfig.sessionSendsLaunchEvents = true
62
153
  }
63
154
  backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)
64
-
65
- // Load persisted task states
66
- taskStates = loadTaskStates()
67
- }
68
-
69
- // MARK: - State Persistence
70
-
71
- private var stateFileURL: URL {
72
- let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
73
- return documentsPath.appendingPathComponent("download-state.json")
74
- }
75
-
76
- private func saveTaskState(_ state: TaskState) {
77
- taskStates[state.taskIdentifier] = state
78
-
79
- if let data = try? JSONEncoder().encode(taskStates) {
80
- try? data.write(to: stateFileURL)
81
- }
82
- }
83
-
84
- private func loadTaskStates() -> [Int: TaskState] {
85
- guard let data = try? Data(contentsOf: stateFileURL),
86
- let states = try? JSONDecoder().decode([Int: TaskState].self, from: data) else {
87
- return [:]
88
- }
89
- return states
90
- }
91
-
92
- private func removeTaskState(_ taskIdentifier: Int) {
93
- taskStates.removeValue(forKey: taskIdentifier)
94
-
95
- if let data = try? JSONEncoder().encode(taskStates) {
96
- try? data.write(to: stateFileURL)
97
- }
98
155
  }
99
156
 
100
157
  func downloadFile(from url: URL, to destination: String, fileSizeHandler: ((Int64) -> Void)?, progressHandler: @escaping (DownloadProgress) -> Void, completion: @escaping (Result<URL, Error>) -> Void) -> URLSessionDownloadTask? {
158
+ // UIKit state is read on the main thread before taking the task-state lock.
101
159
  // Determine if we should use background session
102
160
  #if !os(macOS)
103
- let appState = UIApplication.shared.applicationState
161
+ let appState = Thread.isMainThread
162
+ ? UIApplication.shared.applicationState
163
+ : DispatchQueue.main.sync { UIApplication.shared.applicationState }
104
164
  let useBackgroundSession = (appState == .background || appState == .inactive)
105
165
  #else
106
166
  let useBackgroundSession = false
107
167
  #endif
108
168
 
169
+ stateLock.lock()
170
+ defer { stateLock.unlock() }
109
171
  let selectedSession = useBackgroundSession ? backgroundSession : session
110
172
  let task = selectedSession?.downloadTask(with: url)
111
173
 
@@ -120,20 +182,6 @@ class URLSessionDownloadService: NSObject, DownloadService {
120
182
  fileSizeHandlers[task] = handler
121
183
  }
122
184
 
123
- // Extract bundleId from destination path (e.g., "bundle-store/{bundleId}/bundle.zip")
124
- let bundleId = (destination as NSString).pathComponents
125
- .dropFirst()
126
- .first(where: { $0 != "bundle-store" }) ?? "unknown"
127
-
128
- // Save task metadata for background recovery
129
- let taskState = TaskState(
130
- taskIdentifier: task.taskIdentifier,
131
- destination: destination,
132
- bundleId: bundleId,
133
- startedAt: Date().timeIntervalSince1970
134
- )
135
- saveTaskState(taskState)
136
-
137
185
  task.resume()
138
186
  return task
139
187
  }
@@ -141,6 +189,8 @@ class URLSessionDownloadService: NSObject, DownloadService {
141
189
 
142
190
  extension URLSessionDownloadService: URLSessionDownloadDelegate {
143
191
  func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
192
+ stateLock.lock()
193
+ defer { stateLock.unlock() }
144
194
  let completion = completionHandlers[downloadTask]
145
195
  let destination = destinations[downloadTask]
146
196
 
@@ -149,10 +199,6 @@ extension URLSessionDownloadService: URLSessionDownloadDelegate {
149
199
  completionHandlers.removeValue(forKey: downloadTask)
150
200
  destinations.removeValue(forKey: downloadTask)
151
201
  fileSizeHandlers.removeValue(forKey: downloadTask)
152
- removeTaskState(downloadTask.taskIdentifier)
153
-
154
- // 다운로드 완료 알림
155
- NotificationCenter.default.post(name: .downloadDidFinish, object: downloadTask)
156
202
  }
157
203
 
158
204
  guard let destination = destination else {
@@ -160,6 +206,14 @@ extension URLSessionDownloadService: URLSessionDownloadDelegate {
160
206
  return
161
207
  }
162
208
 
209
+ // URLSession saves error bodies too, so reject them before they reach verification.
210
+ if let httpError = DownloadError.httpStatusError(for: downloadTask.response, body: location) {
211
+ NSLog("[DownloadService] Download failed: \(httpError)")
212
+ try? FileManager.default.removeItem(at: location)
213
+ completion?(.failure(httpError))
214
+ return
215
+ }
216
+
163
217
  // Verify file size
164
218
  let expectedSize = downloadTask.response?.expectedContentLength ?? -1
165
219
  let actualSize: Int64?
@@ -197,15 +251,14 @@ extension URLSessionDownloadService: URLSessionDownloadDelegate {
197
251
  }
198
252
 
199
253
  func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
254
+ stateLock.lock()
255
+ defer { stateLock.unlock() }
200
256
  let completion = completionHandlers[task]
201
257
  defer {
202
258
  progressHandlers.removeValue(forKey: task)
203
259
  completionHandlers.removeValue(forKey: task)
204
260
  destinations.removeValue(forKey: task)
205
261
  fileSizeHandlers.removeValue(forKey: task)
206
- removeTaskState(task.taskIdentifier)
207
-
208
- NotificationCenter.default.post(name: .downloadDidFinish, object: task)
209
262
  }
210
263
 
211
264
  if let error = error {
@@ -214,6 +267,8 @@ extension URLSessionDownloadService: URLSessionDownloadDelegate {
214
267
  }
215
268
 
216
269
  func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
270
+ stateLock.lock()
271
+ defer { stateLock.unlock() }
217
272
  let progressHandler = progressHandlers[downloadTask]
218
273
 
219
274
  // Call file size handler on first callback when size is known
@@ -232,16 +287,9 @@ extension URLSessionDownloadService: URLSessionDownloadDelegate {
232
287
  let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
233
288
  progressHandler?(DownloadProgress(progress: progress, downloadedBytes: totalBytesWritten, totalBytes: totalBytesExpectedToWrite))
234
289
 
235
- let progressInfo: [String: Any] = [
236
- "progress": progress,
237
- "totalBytesReceived": totalBytesWritten,
238
- "totalBytesExpected": totalBytesExpectedToWrite
239
- ]
240
- NotificationCenter.default.post(name: .downloadProgressUpdate, object: downloadTask, userInfo: progressInfo)
241
290
  } else {
242
291
  progressHandler?(DownloadProgress(progress: 0, downloadedBytes: totalBytesWritten, totalBytes: nil))
243
292
 
244
- NotificationCenter.default.post(name: .downloadProgressUpdate, object: downloadTask, userInfo: ["progress": 0.0, "totalBytesReceived": 0, "totalBytesExpected": 0])
245
293
  }
246
294
  }
247
295
  }
@@ -0,0 +1,390 @@
1
+ import Foundation
2
+
3
+ /// Where an update failed: `download` covers transferring the update's files
4
+ /// and verifying the downloaded bytes, and `install` covers producing the
5
+ /// bundle on disk (patching, extracting, and moving files into place).
6
+ enum UpdateFailureStage: String {
7
+ case download
8
+ case install
9
+ }
10
+
11
+ /// Why an update failed.
12
+ enum UpdateFailureReason: String {
13
+ case network
14
+ case http
15
+ case invalidResponse = "invalid_response"
16
+ case hashMismatch = "hash_mismatch"
17
+ case signature
18
+ case patch
19
+ case extract
20
+ case storage
21
+ case unknown
22
+ }
23
+
24
+ /// What was being fetched or applied when an update failed.
25
+ enum UpdateFailureResource: String {
26
+ case manifest
27
+ case file
28
+ case patch
29
+ case archive
30
+ }
31
+
32
+ /// How a transfer failed when no HTTP response arrived.
33
+ enum UpdateFailureTransport: String {
34
+ case timeout
35
+ case dns
36
+ case tls
37
+ case connection
38
+ case offline
39
+ case cancelled
40
+
41
+ /// Returns nil for codes that do not name a transport failure.
42
+ init?(_ code: URLError.Code) {
43
+ switch code {
44
+ case .timedOut:
45
+ self = .timeout
46
+ case .cannotFindHost, .dnsLookupFailed:
47
+ self = .dns
48
+ case .secureConnectionFailed,
49
+ .serverCertificateHasBadDate,
50
+ .serverCertificateUntrusted,
51
+ .serverCertificateHasUnknownRoot,
52
+ .serverCertificateNotYetValid,
53
+ .clientCertificateRejected,
54
+ .clientCertificateRequired:
55
+ self = .tls
56
+ case .cannotConnectToHost, .networkConnectionLost:
57
+ self = .connection
58
+ case .notConnectedToInternet, .dataNotAllowed, .internationalRoamingOff:
59
+ self = .offline
60
+ case .cancelled:
61
+ self = .cancelled
62
+ default:
63
+ return nil
64
+ }
65
+ }
66
+ }
67
+
68
+ /// The classification an `updateBundle` rejection carries in its userInfo.
69
+ struct UpdateFailure: Equatable {
70
+ static let stageKey = "stage"
71
+ static let reasonKey = "reason"
72
+ static let resourceKey = "resource"
73
+ static let httpStatusKey = "httpStatus"
74
+ static let transportKey = "transport"
75
+ static let originCodeKey = "originCode"
76
+
77
+ static let network = UpdateFailure(stage: .download, reason: .network)
78
+ static let invalidResponse = UpdateFailure(stage: .download, reason: .invalidResponse)
79
+ static let hashMismatch = UpdateFailure(stage: .download, reason: .hashMismatch)
80
+ static let signature = UpdateFailure(stage: .download, reason: .signature)
81
+ static let patch = UpdateFailure(stage: .install, reason: .patch)
82
+ static let extract = UpdateFailure(stage: .install, reason: .extract)
83
+ static let storage = UpdateFailure(stage: .install, reason: .storage)
84
+
85
+ static func network(transport: UpdateFailureTransport?) -> UpdateFailure {
86
+ UpdateFailure(stage: .download, reason: .network, transport: transport)
87
+ }
88
+
89
+ static func http(status: Int, originCode: String? = nil) -> UpdateFailure {
90
+ UpdateFailure(
91
+ stage: .download,
92
+ reason: .http,
93
+ httpStatus: status,
94
+ originCode: originCode
95
+ )
96
+ }
97
+
98
+ static func unknown(_ stage: UpdateFailureStage) -> UpdateFailure {
99
+ UpdateFailure(stage: stage, reason: .unknown)
100
+ }
101
+
102
+ let stage: UpdateFailureStage
103
+ let reason: UpdateFailureReason
104
+ /// What was being fetched or applied, when the failure concerns one.
105
+ private(set) var resource: UpdateFailureResource?
106
+ /// The response status; only set when `reason` is `.http`.
107
+ let httpStatus: Int?
108
+ /// Only set when `reason` is `.network` and no response arrived.
109
+ let transport: UpdateFailureTransport?
110
+ /// The storage origin's XML error code; only set when `reason` is `.http`.
111
+ let originCode: String?
112
+
113
+ private init(
114
+ stage: UpdateFailureStage,
115
+ reason: UpdateFailureReason,
116
+ httpStatus: Int? = nil,
117
+ transport: UpdateFailureTransport? = nil,
118
+ originCode: String? = nil
119
+ ) {
120
+ self.stage = stage
121
+ self.reason = reason
122
+ self.httpStatus = httpStatus
123
+ self.transport = transport
124
+ self.originCode = originCode
125
+ }
126
+
127
+ /// This failure, attributed to `resource` unless it already names one.
128
+ func with(resource: UpdateFailureResource?) -> UpdateFailure {
129
+ guard self.resource == nil, let resource else {
130
+ return self
131
+ }
132
+ var failure = self
133
+ failure.resource = resource
134
+ return failure
135
+ }
136
+
137
+ /// JSON-safe entries React Native copies into the JS error's `userInfo`.
138
+ var userInfo: [String: Any] {
139
+ var userInfo: [String: Any] = [
140
+ Self.stageKey: stage.rawValue,
141
+ Self.reasonKey: reason.rawValue,
142
+ ]
143
+ if let resource {
144
+ userInfo[Self.resourceKey] = resource.rawValue
145
+ }
146
+ if let httpStatus {
147
+ userInfo[Self.httpStatusKey] = NSNumber(value: httpStatus)
148
+ }
149
+ if let transport {
150
+ userInfo[Self.transportKey] = transport.rawValue
151
+ }
152
+ if let originCode {
153
+ userInfo[Self.originCodeKey] = originCode
154
+ }
155
+ return userInfo
156
+ }
157
+
158
+ /// An error carrying this classification, for rejections raised before
159
+ /// the update pipeline starts, such as invalid `updateBundle` parameters.
160
+ func error(description: String) -> NSError {
161
+ var userInfo = self.userInfo
162
+ userInfo[NSLocalizedDescriptionKey] = description
163
+ return NSError(domain: "HotUpdater", code: 0, userInfo: userInfo)
164
+ }
165
+ }
166
+
167
+ /// The update step an error came from. It classifies the errors whose type
168
+ /// does not: a hash mismatch is a download failure after a transfer but a
169
+ /// patch failure after patching, and an unexpected error keeps the stage.
170
+ enum UpdatePhase {
171
+ /// Transferring manifest, archive, asset, or patch bytes.
172
+ case transfer
173
+ /// Checking downloaded bytes against the manifest.
174
+ case verification
175
+ /// Parsing and validating the manifest against the update parameters.
176
+ case manifest
177
+ /// Applying a bsdiff patch and checking its output.
178
+ case patch
179
+ /// Decoding brotli or tar data and checking the extracted files.
180
+ case extraction
181
+ /// Creating directories and copying, moving, or writing files.
182
+ case storage
183
+ }
184
+
185
+ /// An update pipeline error and its classification. `underlying` still
186
+ /// decides the rejection code, so classifying an error never changes it.
187
+ struct UpdateFailureError: Error {
188
+ let underlying: Error
189
+ let failure: UpdateFailure
190
+
191
+ /// Classifies `error` as raised during `phase` while fetching or applying
192
+ /// `resource`. An error that is already classified keeps its
193
+ /// classification and only gains a missing resource. Returns `error`
194
+ /// unchanged when it is not an update failure.
195
+ static func classifying(
196
+ _ error: Error,
197
+ during phase: UpdatePhase,
198
+ resource: UpdateFailureResource? = nil
199
+ ) -> Error {
200
+ if let classified = error as? UpdateFailureError {
201
+ return UpdateFailureError(
202
+ underlying: classified.underlying,
203
+ failure: classified.failure.with(resource: resource)
204
+ )
205
+ }
206
+ guard let failure = UpdateFailureClassifier.classify(error, during: phase) else {
207
+ return error
208
+ }
209
+ return UpdateFailureError(underlying: error, failure: failure.with(resource: resource))
210
+ }
211
+
212
+ /// Runs `body`, classifying what it throws as raised during `phase`.
213
+ static func during<T>(
214
+ _ phase: UpdatePhase,
215
+ resource: UpdateFailureResource? = nil,
216
+ _ body: () throws -> T
217
+ ) throws -> T {
218
+ do {
219
+ return try body()
220
+ } catch {
221
+ throw classifying(error, during: phase, resource: resource)
222
+ }
223
+ }
224
+
225
+ /// The error that decides the rejection code.
226
+ static func unwrap(_ error: Error) -> Error {
227
+ (error as? UpdateFailureError)?.underlying ?? error
228
+ }
229
+
230
+ /// The error `updateBundle` rejects with: the underlying error's domain,
231
+ /// code, and userInfo, plus the classification entries.
232
+ static func rejectionError(for error: Error) -> NSError {
233
+ guard let classified = error as? UpdateFailureError else {
234
+ return error as NSError
235
+ }
236
+ let underlying = classified.underlying as NSError
237
+ var userInfo = underlying.userInfo
238
+ userInfo[NSLocalizedDescriptionKey] = underlying.localizedDescription
239
+ userInfo.merge(classified.failure.userInfo) { _, classification in classification }
240
+ return NSError(domain: underlying.domain, code: underlying.code, userInfo: userInfo)
241
+ }
242
+ }
243
+
244
+ extension UpdateFailureError: LocalizedError {
245
+ var errorDescription: String? {
246
+ underlying.localizedDescription
247
+ }
248
+ }
249
+
250
+ /// The Release selection an install was staged for changed before the bundle
251
+ /// was activated. The rejection is not an update failure.
252
+ struct StaleReleaseSelectionError: Error, CustomNSError {
253
+ static var errorDomain: String {
254
+ "HotUpdater"
255
+ }
256
+
257
+ var errorCode: Int {
258
+ 0
259
+ }
260
+
261
+ var errorUserInfo: [String: Any] {
262
+ [NSLocalizedDescriptionKey: "Release catalog selection is stale"]
263
+ }
264
+ }
265
+
266
+ enum UpdateFailureClassifier {
267
+ /// Classifies an error raised during `phase`, or returns nil when the
268
+ /// error is not an update failure.
269
+ static func classify(_ error: Error, during phase: UpdatePhase) -> UpdateFailure? {
270
+ switch error {
271
+ case let error as UpdateFailureError:
272
+ return error.failure
273
+ case is StaleReleaseSelectionError:
274
+ return nil
275
+ case let error as BundleStorageError:
276
+ return classifyStorageError(error, during: phase)
277
+ case let error as SignatureVerificationError:
278
+ return classifySignatureError(error, during: phase)
279
+ case let error as DownloadError:
280
+ return classifyDownloadError(error)
281
+ case let error as URLError:
282
+ return classifyURLError(error)
283
+ default:
284
+ return isFileSystemError(error) ? .storage : fallback(during: phase)
285
+ }
286
+ }
287
+
288
+ private static func classifyStorageError(
289
+ _ error: BundleStorageError,
290
+ during phase: UpdatePhase
291
+ ) -> UpdateFailure? {
292
+ switch error {
293
+ case .directoryCreationFailed, .moveOperationFailed:
294
+ return .storage
295
+ case .downloadFailed(let underlying):
296
+ return classify(underlying, during: .transfer)
297
+ case .incompleteDownload:
298
+ return .network
299
+ case .invalidBundle:
300
+ return .invalidResponse
301
+ case .signatureVerificationFailed(let underlying):
302
+ return classifySignatureError(underlying, during: phase)
303
+ case .bundleInCrashedHistory:
304
+ return nil
305
+ case .unknown(.some(let underlying)):
306
+ return classify(underlying, during: phase)
307
+ case .unknown(.none):
308
+ return fallback(during: phase)
309
+ }
310
+ }
311
+
312
+ private static func classifySignatureError(
313
+ _ error: SignatureVerificationError,
314
+ during phase: UpdatePhase
315
+ ) -> UpdateFailure {
316
+ switch error {
317
+ case .fileHashMismatch:
318
+ switch phase {
319
+ case .patch:
320
+ return .patch
321
+ case .extraction:
322
+ return .extract
323
+ case .transfer, .verification, .manifest, .storage:
324
+ return .hashMismatch
325
+ }
326
+ case .fileReadFailed:
327
+ return .storage
328
+ case .missingFileHash:
329
+ return .invalidResponse
330
+ case .publicKeyNotConfigured,
331
+ .invalidPublicKeyFormat,
332
+ .invalidSignatureFormat,
333
+ .signatureVerificationFailed,
334
+ .unsignedNotAllowed,
335
+ .securityFrameworkError:
336
+ return .signature
337
+ }
338
+ }
339
+
340
+ private static func classifyDownloadError(_ error: DownloadError) -> UpdateFailure {
341
+ switch error {
342
+ case .httpStatus(let status, let originCode):
343
+ return .http(status: status, originCode: originCode)
344
+ case .incompleteDownload, .invalidContentLength:
345
+ // A response arrived, so the failure names no transport.
346
+ return .network
347
+ }
348
+ }
349
+
350
+ private static func classifyURLError(_ error: URLError) -> UpdateFailure {
351
+ switch error.code {
352
+ case .badURL, .unsupportedURL:
353
+ return .invalidResponse
354
+ case .cannotCreateFile,
355
+ .cannotOpenFile,
356
+ .cannotCloseFile,
357
+ .cannotWriteToFile,
358
+ .cannotRemoveFile,
359
+ .cannotMoveFile:
360
+ return .storage
361
+ default:
362
+ return .network(transport: UpdateFailureTransport(error.code))
363
+ }
364
+ }
365
+
366
+ private static func isFileSystemError(_ error: Error) -> Bool {
367
+ if error is FileSystemError {
368
+ return true
369
+ }
370
+ if let cocoaError = error as? CocoaError {
371
+ return cocoaError.isFileError
372
+ }
373
+ return (error as NSError).domain == NSPOSIXErrorDomain
374
+ }
375
+
376
+ private static func fallback(during phase: UpdatePhase) -> UpdateFailure {
377
+ switch phase {
378
+ case .transfer, .verification:
379
+ return .unknown(.download)
380
+ case .manifest:
381
+ return .invalidResponse
382
+ case .patch:
383
+ return .patch
384
+ case .extraction:
385
+ return .extract
386
+ case .storage:
387
+ return .unknown(.install)
388
+ }
389
+ }
390
+ }
@@ -0,0 +1,16 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ #ifdef __cplusplus
4
+ extern "C" {
5
+ #endif
6
+
7
+ void HotUpdaterMarkFailedRuntime(void);
8
+ BOOL HotUpdaterIsFailedRuntime(void);
9
+ void HotUpdaterInstallRecoveryHooks(void);
10
+ BOOL HotUpdaterBeginSurfaceStart(const void *surfaceHandler);
11
+ void HotUpdaterFinishSurfaceStart(const void *surfaceHandler);
12
+ void HotUpdaterAfterSurfaceStarts(dispatch_block_t action);
13
+
14
+ #ifdef __cplusplus
15
+ }
16
+ #endif