@hot-updater/react-native 1.0.0-rc.1 → 1.0.0-rc.15

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 (227) 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 +651 -637
  4. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +10 -0
  5. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +0 -13
  6. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +10 -14
  7. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
  8. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  9. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +19 -0
  10. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  11. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  12. package/android/src/newarch/HotUpdater.kt +7 -9
  13. package/android/src/newarch/HotUpdaterModule.kt +40 -20
  14. package/android/src/oldarch/HotUpdater.kt +7 -9
  15. package/android/src/oldarch/HotUpdaterModule.kt +40 -20
  16. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +1126 -74
  17. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -5
  18. package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +97 -0
  19. package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +44 -0
  20. package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +206 -0
  21. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  22. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  23. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +773 -1016
  24. package/ios/HotUpdater/Internal/BundleMetadata.swift +5 -0
  25. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  26. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  27. package/ios/HotUpdater/Internal/HotUpdater.mm +5 -27
  28. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +149 -51
  29. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  30. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  31. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +13 -72
  32. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  33. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  34. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  35. package/lib/commonjs/checkForUpdate.js +32 -23
  36. package/lib/commonjs/checkForUpdate.js.map +1 -1
  37. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +97 -28
  38. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  39. package/lib/commonjs/error.js +4 -13
  40. package/lib/commonjs/error.js.map +1 -1
  41. package/lib/commonjs/fetchJSON.js +9 -2
  42. package/lib/commonjs/fetchJSON.js.map +1 -1
  43. package/lib/commonjs/httpClient.js +46 -29
  44. package/lib/commonjs/httpClient.js.map +1 -1
  45. package/lib/commonjs/httpClient.spec.js +57 -5
  46. package/lib/commonjs/httpClient.spec.js.map +1 -1
  47. package/lib/commonjs/index.js +31 -14
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/index.spec.js +56 -11
  50. package/lib/commonjs/index.spec.js.map +1 -1
  51. package/lib/commonjs/native.js +15 -14
  52. package/lib/commonjs/native.js.map +1 -1
  53. package/lib/commonjs/native.spec.js +128 -22
  54. package/lib/commonjs/native.spec.js.map +1 -1
  55. package/lib/commonjs/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +28 -21
  56. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +1 -0
  57. package/lib/commonjs/notifyAppReadyInsights.js +188 -0
  58. package/lib/commonjs/notifyAppReadyInsights.js.map +1 -0
  59. package/lib/commonjs/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +161 -29
  60. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +1 -0
  61. package/lib/commonjs/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
  62. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +1 -0
  63. package/lib/commonjs/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +14 -7
  64. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +1 -0
  65. package/lib/commonjs/sdkVersion.js +1 -1
  66. package/lib/commonjs/sdkVersion.js.map +1 -1
  67. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  68. package/lib/commonjs/store.js +3 -10
  69. package/lib/commonjs/store.js.map +1 -1
  70. package/lib/commonjs/store.notification.spec.js +12 -4
  71. package/lib/commonjs/store.notification.spec.js.map +1 -1
  72. package/lib/commonjs/store.spec.js +2 -136
  73. package/lib/commonjs/store.spec.js.map +1 -1
  74. package/lib/commonjs/wrap.js +11 -12
  75. package/lib/commonjs/wrap.js.map +1 -1
  76. package/lib/commonjs/wrap.spec.js +97 -25
  77. package/lib/commonjs/wrap.spec.js.map +1 -1
  78. package/lib/module/checkForUpdate.js +32 -23
  79. package/lib/module/checkForUpdate.js.map +1 -1
  80. package/lib/module/checkForUpdate.releaseCatalog.spec.js +97 -28
  81. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  82. package/lib/module/error.js +4 -13
  83. package/lib/module/error.js.map +1 -1
  84. package/lib/module/fetchJSON.js +7 -1
  85. package/lib/module/fetchJSON.js.map +1 -1
  86. package/lib/module/httpClient.js +48 -31
  87. package/lib/module/httpClient.js.map +1 -1
  88. package/lib/module/httpClient.spec.js +57 -5
  89. package/lib/module/httpClient.spec.js.map +1 -1
  90. package/lib/module/index.js +32 -15
  91. package/lib/module/index.js.map +1 -1
  92. package/lib/module/index.spec.js +56 -11
  93. package/lib/module/index.spec.js.map +1 -1
  94. package/lib/module/native.js +13 -12
  95. package/lib/module/native.js.map +1 -1
  96. package/lib/module/native.spec.js +128 -22
  97. package/lib/module/native.spec.js.map +1 -1
  98. package/lib/module/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +23 -16
  99. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +1 -0
  100. package/lib/module/notifyAppReadyInsights.js +181 -0
  101. package/lib/module/notifyAppReadyInsights.js.map +1 -0
  102. package/lib/module/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +153 -21
  103. package/lib/module/notifyAppReadyInsights.spec.js.map +1 -0
  104. package/lib/module/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
  105. package/lib/module/notifyAppReadyInsights.test-utils.js.map +1 -0
  106. package/lib/module/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +11 -4
  107. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +1 -0
  108. package/lib/module/sdkVersion.js +1 -1
  109. package/lib/module/sdkVersion.js.map +1 -1
  110. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  111. package/lib/module/store.js +3 -10
  112. package/lib/module/store.js.map +1 -1
  113. package/lib/module/store.notification.spec.js +12 -4
  114. package/lib/module/store.notification.spec.js.map +1 -1
  115. package/lib/module/store.spec.js +2 -136
  116. package/lib/module/store.spec.js.map +1 -1
  117. package/lib/module/wrap.js +11 -12
  118. package/lib/module/wrap.js.map +1 -1
  119. package/lib/module/wrap.spec.js +99 -25
  120. package/lib/module/wrap.spec.js.map +1 -1
  121. package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -3
  122. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/error.d.ts +4 -13
  124. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/fetchJSON.d.ts +4 -0
  126. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/httpClient.d.ts +6 -6
  128. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/index.d.ts +4 -7
  130. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/native.d.ts +5 -9
  132. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +25 -0
  134. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +8 -0
  136. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  138. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +8 -35
  140. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/store.d.ts +1 -3
  142. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/wrap.types.d.ts +5 -8
  144. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  145. package/lib/typescript/module/checkForUpdate.d.ts +2 -3
  146. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  147. package/lib/typescript/module/error.d.ts +4 -13
  148. package/lib/typescript/module/error.d.ts.map +1 -1
  149. package/lib/typescript/module/fetchJSON.d.ts +4 -0
  150. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  151. package/lib/typescript/module/httpClient.d.ts +6 -6
  152. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  153. package/lib/typescript/module/index.d.ts +4 -7
  154. package/lib/typescript/module/index.d.ts.map +1 -1
  155. package/lib/typescript/module/native.d.ts +5 -9
  156. package/lib/typescript/module/native.d.ts.map +1 -1
  157. package/lib/typescript/module/notifyAppReadyInsights.d.ts +25 -0
  158. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +1 -0
  159. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +8 -0
  160. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
  161. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  162. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  163. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +8 -35
  164. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  165. package/lib/typescript/module/store.d.ts +1 -3
  166. package/lib/typescript/module/store.d.ts.map +1 -1
  167. package/lib/typescript/module/wrap.types.d.ts +5 -8
  168. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  169. package/package.json +3 -3
  170. package/src/checkForUpdate.releaseCatalog.spec.ts +137 -61
  171. package/src/checkForUpdate.ts +35 -29
  172. package/src/error.ts +4 -14
  173. package/src/fetchJSON.ts +12 -1
  174. package/src/httpClient.spec.ts +70 -5
  175. package/src/httpClient.ts +78 -64
  176. package/src/index.spec.ts +59 -11
  177. package/src/index.ts +41 -17
  178. package/src/native.spec.ts +133 -22
  179. package/src/native.ts +21 -32
  180. package/src/{notifyAppReadyAnalytics.boundary.spec.ts → notifyAppReadyInsights.boundary.spec.ts} +25 -17
  181. package/src/{notifyAppReadyAnalytics.spec.ts → notifyAppReadyInsights.spec.ts} +141 -22
  182. package/src/{notifyAppReadyAnalytics.test-utils.ts → notifyAppReadyInsights.test-utils.ts} +4 -4
  183. package/src/{notifyAppReadyAnalytics.transport.spec.ts → notifyAppReadyInsights.transport.spec.ts} +13 -5
  184. package/src/notifyAppReadyInsights.ts +293 -0
  185. package/src/sdkVersion.ts +1 -1
  186. package/src/specs/NativeHotUpdater.ts +8 -36
  187. package/src/store.notification.spec.ts +13 -3
  188. package/src/store.spec.ts +0 -157
  189. package/src/store.ts +2 -23
  190. package/src/wrap.spec.ts +109 -31
  191. package/src/wrap.tsx +11 -11
  192. package/src/wrap.types.ts +5 -8
  193. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  194. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  195. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  196. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  197. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  198. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  199. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  200. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  201. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  202. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  203. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  204. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  205. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  206. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  207. package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
  208. package/lib/commonjs/notifyAppReadyAnalytics.js +0 -133
  209. package/lib/commonjs/notifyAppReadyAnalytics.js.map +0 -1
  210. package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +0 -1
  211. package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +0 -1
  212. package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
  213. package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
  214. package/lib/module/notifyAppReadyAnalytics.js +0 -128
  215. package/lib/module/notifyAppReadyAnalytics.js.map +0 -1
  216. package/lib/module/notifyAppReadyAnalytics.spec.js.map +0 -1
  217. package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +0 -1
  218. package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
  219. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts +0 -13
  220. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +0 -1
  221. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
  222. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
  223. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts +0 -13
  224. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +0 -1
  225. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
  226. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
  227. package/src/notifyAppReadyAnalytics.ts +0 -194
@@ -84,6 +84,7 @@ public struct BundleMetadata: Codable {
84
84
  var stableSelection: PersistedSelection?
85
85
  var stagingSelection: PersistedSelection?
86
86
  var verificationPending: Bool
87
+ var launchInProgress: Bool
87
88
  var pendingTransition: PendingBundleTransition?
88
89
  var pendingSelectionTransition: PendingSelectionTransition?
89
90
  var highestSeenCatalogs: [String: CatalogHighWater]
@@ -98,6 +99,7 @@ public struct BundleMetadata: Codable {
98
99
  case stableSelection = "stable_selection"
99
100
  case stagingSelection = "staging_selection"
100
101
  case verificationPending = "verification_pending"
102
+ case launchInProgress = "launch_in_progress"
101
103
  case pendingTransition = "pending_transition"
102
104
  case pendingSelectionTransition = "pending_selection_transition"
103
105
  case highestSeenCatalogs = "highest_seen_catalogs"
@@ -113,6 +115,7 @@ public struct BundleMetadata: Codable {
113
115
  stableSelection: PersistedSelection? = nil,
114
116
  stagingSelection: PersistedSelection? = nil,
115
117
  verificationPending: Bool = false,
118
+ launchInProgress: Bool = false,
116
119
  pendingTransition: PendingBundleTransition? = nil,
117
120
  pendingSelectionTransition: PendingSelectionTransition? = nil,
118
121
  highestSeenCatalogs: [String: CatalogHighWater] = [:],
@@ -126,6 +129,7 @@ public struct BundleMetadata: Codable {
126
129
  self.stableSelection = stableSelection
127
130
  self.stagingSelection = stagingSelection
128
131
  self.verificationPending = verificationPending
132
+ self.launchInProgress = launchInProgress
129
133
  self.pendingTransition = pendingTransition
130
134
  self.pendingSelectionTransition = pendingSelectionTransition
131
135
  self.highestSeenCatalogs = highestSeenCatalogs
@@ -144,6 +148,7 @@ public struct BundleMetadata: Codable {
144
148
  stagingSelection = try values.decodeIfPresent(PersistedSelection.self, forKey: .stagingSelection)
145
149
  ?? stagingBundleId.map(PersistedSelection.legacyBundle)
146
150
  verificationPending = try values.decodeIfPresent(Bool.self, forKey: .verificationPending) ?? false
151
+ launchInProgress = try values.decodeIfPresent(Bool.self, forKey: .launchInProgress) ?? false
147
152
  pendingTransition = try values.decodeIfPresent(PendingBundleTransition.self, forKey: .pendingTransition)
148
153
  pendingSelectionTransition = try values.decodeIfPresent(PendingSelectionTransition.self, forKey: .pendingSelectionTransition)
149
154
  highestSeenCatalogs = try values.decodeIfPresent([String: CatalogHighWater].self, forKey: .highestSeenCatalogs) ?? [:]
@@ -0,0 +1,64 @@
1
+ import Foundation
2
+
3
+ enum FileUtilities {
4
+ static func readUpToCount(from handle: FileHandle, count: Int) throws -> Data? {
5
+ guard count >= 0 else {
6
+ throw NSError(
7
+ domain: "FileUtilities",
8
+ code: 1,
9
+ userInfo: [NSLocalizedDescriptionKey: "Invalid read size: \(count)"]
10
+ )
11
+ }
12
+
13
+ if count == 0 {
14
+ return Data()
15
+ }
16
+
17
+ if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
18
+ return try handle.read(upToCount: count)
19
+ }
20
+
21
+ return handle.readData(ofLength: count)
22
+ }
23
+
24
+ static func normalizedRelativePath(from rawPath: String) -> String? {
25
+ let candidate = rawPath.trimmingCharacters(in: .whitespacesAndNewlines)
26
+ guard !candidate.isEmpty,
27
+ !candidate.contains("\0"),
28
+ !candidate.contains("\\"),
29
+ !candidate.hasPrefix("/"),
30
+ candidate.range(of: #"^[A-Za-z]:"#, options: .regularExpression) == nil
31
+ else {
32
+ return nil
33
+ }
34
+
35
+ let components = candidate.split(separator: "/").map(String.init)
36
+ guard !components.isEmpty,
37
+ components.count == candidate.split(separator: "/", omittingEmptySubsequences: false).count,
38
+ !components.contains(".."),
39
+ !components.contains(".")
40
+ else {
41
+ return nil
42
+ }
43
+
44
+ return components.joined(separator: "/")
45
+ }
46
+
47
+ static func fileURL(for relativePath: String, destinationRoot: String) throws -> URL {
48
+ let rootURL = URL(fileURLWithPath: destinationRoot, isDirectory: true)
49
+ let targetURL = rootURL.appendingPathComponent(relativePath)
50
+ let standardizedRoot = rootURL.standardizedFileURL.path
51
+ let standardizedTarget = targetURL.standardizedFileURL.path
52
+
53
+ guard standardizedTarget == standardizedRoot ||
54
+ standardizedTarget.hasPrefix(standardizedRoot + "/") else {
55
+ throw NSError(
56
+ domain: "FileUtilities",
57
+ code: 2,
58
+ userInfo: [NSLocalizedDescriptionKey: "Path traversal attempt detected: \(relativePath)"]
59
+ )
60
+ }
61
+
62
+ return targetURL
63
+ }
64
+ }
@@ -7,7 +7,6 @@
7
7
  #import "React/RCTConstants.h"
8
8
  #import "React/RCTRootView.h"
9
9
  #import "React/RCTUtils.h" // Needed for RCTPromiseResolveBlock/RejectBlock in Swift
10
- #import <SSZipArchive/SSZipArchive.h>
11
10
  #import "BsdiffPatchBridge.h"
12
11
 
13
12
  @interface HotUpdaterRecoverySignalBridge : NSObject
@@ -274,8 +274,6 @@ extern "C" void *HotUpdaterCopyMinBundleId(void)
274
274
 
275
275
 
276
276
  // Define Notification names used for observing Swift Core
277
- NSNotificationName const HotUpdaterDownloadProgressUpdateNotification = @"HotUpdaterDownloadProgressUpdate";
278
- NSNotificationName const HotUpdaterDownloadDidFinishNotification = @"HotUpdaterDownloadDidFinish";
279
277
  NSNotificationName const HotUpdaterUpdateProgressDidChangeNotification = @"HotUpdaterUpdateProgressDidChange";
280
278
 
281
279
  @interface HotUpdaterRecoverySignalBridge : NSObject
@@ -300,8 +298,6 @@ NSNotificationName const HotUpdaterUpdateProgressDidChangeNotification = @"HotUp
300
298
 
301
299
  @implementation HotUpdater {
302
300
  bool hasListeners;
303
- // Keep track of tasks ONLY for removing observers when this ObjC instance is invalidated
304
- NSMutableSet<NSURLSessionTask *> *observedTasks; // Changed to NSURLSessionTask for broader compatibility if needed
305
301
  }
306
302
 
307
303
  @synthesize bridge = _bridge;
@@ -314,16 +310,10 @@ NSNotificationName const HotUpdaterUpdateProgressDidChangeNotification = @"HotUp
314
310
  - (instancetype)init {
315
311
  self = [super init];
316
312
  if (self) {
317
- observedTasks = [NSMutableSet set];
318
-
319
313
  [[NSNotificationCenter defaultCenter] addObserver:self
320
314
  selector:@selector(handleDownloadProgress:)
321
315
  name:HotUpdaterUpdateProgressDidChangeNotification
322
316
  object:nil];
323
- [[NSNotificationCenter defaultCenter] addObserver:self
324
- selector:@selector(handleDownloadCompletion:)
325
- name:HotUpdaterDownloadDidFinishNotification
326
- object:nil];
327
317
 
328
318
  _lastUpdateTime = 0;
329
319
  }
@@ -349,7 +339,6 @@ NSNotificationName const HotUpdaterUpdateProgressDidChangeNotification = @"HotUp
349
339
  if (gHotUpdaterBridge == self.bridge) {
350
340
  gHotUpdaterBridge = nil;
351
341
  }
352
- // Swift side should handle KVO observer removal for its tasks
353
342
  [super invalidate];
354
343
  }
355
344
 
@@ -520,14 +509,6 @@ RCT_EXPORT_MODULE();
520
509
  }
521
510
  }
522
511
 
523
- - (void)handleDownloadCompletion:(NSNotification *)notification {
524
- NSURLSessionTask *task = notification.object; // Task that finished
525
- RCTLogInfo(@"[HotUpdater.mm] Received download completion notification for task: %@", task.originalRequest.URL);
526
- // Swift side handles KVO observer removal internally now when task finishes.
527
- // No specific cleanup needed here based on this notification anymore.
528
- }
529
-
530
-
531
512
  #pragma mark - React Native Events (Keep as is)
532
513
 
533
514
  - (NSArray<NSString *> *)supportedEvents {
@@ -594,20 +575,17 @@ RCT_EXPORT_MODULE();
594
575
  if (params.bundleId()) {
595
576
  paramDict[@"bundleId"] = params.bundleId();
596
577
  }
597
- if (params.fileUrl()) {
598
- paramDict[@"fileUrl"] = params.fileUrl();
599
- }
600
- if (params.fileHash()) {
601
- paramDict[@"fileHash"] = params.fileHash();
602
- }
603
578
  if (params.manifestUrl()) {
604
579
  paramDict[@"manifestUrl"] = params.manifestUrl();
605
580
  }
606
581
  if (params.manifestFileHash()) {
607
582
  paramDict[@"manifestFileHash"] = params.manifestFileHash();
608
583
  }
609
- if (params.changedAssets()) {
610
- paramDict[@"changedAssets"] = params.changedAssets();
584
+ if (params.archiveUrl()) {
585
+ paramDict[@"archiveUrl"] = params.archiveUrl();
586
+ }
587
+ if (params.assets()) {
588
+ paramDict[@"assets"] = params.assets();
611
589
  }
612
590
  if (params.channel()) {
613
591
  paramDict[@"channel"] = params.channel();
@@ -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
 
@@ -49,6 +61,21 @@ private func hotUpdaterGetMinBundleId() -> String {
49
61
  private static let DEFAULT_CHANNEL = "production"
50
62
  private static let CHANNEL_STORAGE_KEY = "HotUpdaterChannel"
51
63
 
64
+ private static func parseSafeByteSize(_ value: Any?) -> Int64? {
65
+ guard let number = value as? NSNumber,
66
+ String(cString: number.objCType) != "c" else {
67
+ return nil
68
+ }
69
+ let doubleValue = number.doubleValue
70
+ guard doubleValue.isFinite,
71
+ doubleValue >= 0,
72
+ doubleValue <= 9_007_199_254_740_991,
73
+ doubleValue.rounded(.towardZero) == doubleValue else {
74
+ return nil
75
+ }
76
+ return Int64(doubleValue)
77
+ }
78
+
52
79
  // MARK: - Initialization
53
80
 
54
81
  /**
@@ -59,12 +86,9 @@ private func hotUpdaterGetMinBundleId() -> String {
59
86
  let isolationKey = HotUpdaterImpl.getIsolationKey()
60
87
  let preferences = VersionedPreferencesService()
61
88
  let downloadService = URLSessionDownloadService()
62
- let decompressService = DecompressService()
63
-
64
89
  let bundleStorage = BundleFileStorageService(
65
90
  fileSystem: fileSystem,
66
91
  downloadService: downloadService,
67
- decompressService: decompressService,
68
92
  preferences: preferences,
69
93
  isolationKey: isolationKey,
70
94
  builtInBundleIdProvider: { HotUpdaterImpl.minBundleId() }
@@ -219,7 +243,7 @@ private func hotUpdaterGetMinBundleId() -> String {
219
243
  /**
220
244
  * Updates the bundle from JavaScript bridge.
221
245
  * This method acts as the primary error boundary for all bundle operations.
222
- * @param params Dictionary with bundleId and fileUrl parameters
246
+ * @param params Dictionary with bundleId, manifest, and asset parameters
223
247
  * @param resolve Promise resolve callback
224
248
  * @param reject Promise reject callback
225
249
  */
@@ -243,36 +267,40 @@ private func hotUpdaterGetMinBundleId() -> String {
243
267
  return
244
268
  }
245
269
 
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
270
+ guard let manifestFileHash = data["manifestFileHash"] as? String,
271
+ !manifestFileHash.isEmpty else {
272
+ let error = NSError(domain: "HotUpdater", code: 0,
273
+ userInfo: [NSLocalizedDescriptionKey: "Missing manifest file hash"])
274
+ reject("INVALID_MANIFEST", error.localizedDescription, error)
275
+ return
257
276
  }
258
-
259
- // Extract fileHash if provided
260
- let fileHash = data["fileHash"] as? String
261
- let manifestFileHash = data["manifestFileHash"] as? String
262
277
  let channel = data["channel"] as? String
263
278
  let manifestUrlString = data["manifestUrl"] as? String ?? ""
264
- var manifestUrl: URL? = nil
265
- if !manifestUrlString.isEmpty {
266
- guard let url = URL(string: manifestUrlString) else {
279
+ guard let manifestUrl = URL(string: manifestUrlString) else {
280
+ let error = NSError(domain: "HotUpdater", code: 0,
281
+ userInfo: [NSLocalizedDescriptionKey: "Invalid 'manifestUrl' provided: \(manifestUrlString)"])
282
+ reject("INVALID_FILE_URL", error.localizedDescription, error)
283
+ return
284
+ }
285
+ let archiveUrl: URL?
286
+ if let archiveUrlString = data["archiveUrl"] as? String {
287
+ guard let parsedArchiveUrl = URL(string: archiveUrlString) else {
267
288
  let error = NSError(domain: "HotUpdater", code: 0,
268
- userInfo: [NSLocalizedDescriptionKey: "Invalid 'manifestUrl' provided: \(manifestUrlString)"])
289
+ userInfo: [NSLocalizedDescriptionKey: "Invalid 'archiveUrl' provided: \(archiveUrlString)"])
269
290
  reject("INVALID_FILE_URL", error.localizedDescription, error)
270
291
  return
271
292
  }
272
- manifestUrl = url
293
+ archiveUrl = parsedArchiveUrl
294
+ } else {
295
+ archiveUrl = nil
273
296
  }
274
- let changedAssetsPayload = data["changedAssets"] as? [String: [String: Any]]
275
- let changedAssets = changedAssetsPayload?.reduce(into: [String: ChangedAssetDescriptor]()) { partialResult, entry in
297
+ guard let assetsPayload = data["assets"] as? [String: [String: Any]] else {
298
+ let error = NSError(domain: "HotUpdater", code: 0,
299
+ userInfo: [NSLocalizedDescriptionKey: "Missing manifest assets"])
300
+ reject("INVALID_MANIFEST", error.localizedDescription, error)
301
+ return
302
+ }
303
+ let assets = assetsPayload.reduce(into: [String: ChangedAssetDescriptor]()) { partialResult, entry in
276
304
  guard let fileHash = entry.value["fileHash"] as? String,
277
305
  !fileHash.isEmpty
278
306
  else {
@@ -296,7 +324,8 @@ private func hotUpdaterGetMinBundleId() -> String {
296
324
  baseBundleId: baseBundleId,
297
325
  baseFileHash: baseFileHash,
298
326
  patchFileHash: patchFileHash,
299
- patchUrl: patchUrl
327
+ patchUrl: patchUrl,
328
+ byteSize: Self.parseSafeByteSize(payload["byteSize"])
300
329
  )
301
330
  }
302
331
  let filePayload = entry.value["file"] as? [String: Any]
@@ -327,10 +356,10 @@ private func hotUpdaterGetMinBundleId() -> String {
327
356
  // Extract progress callback if provided
328
357
  let progressCallback = data["progressCallback"] as? RCTResponseSenderBlock
329
358
 
330
- NSLog("[HotUpdaterImpl] updateBundle called with bundleId: \(bundleId), fileUrl: \(fileUrl?.absoluteString ?? "nil"), fileHash: \(fileHash ?? "nil")")
359
+ NSLog("[HotUpdaterImpl] updateBundle called with bundleId: \(bundleId), manifestUrl: \(manifestUrl.absoluteString)")
331
360
 
332
361
  // 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
362
+ bundleStorage.updateBundle(bundleId: bundleId, manifestUrl: manifestUrl, manifestFileHash: manifestFileHash, archiveUrl: archiveUrl, assets: assets, progressHandler: { payload in
334
363
  DispatchQueue.main.async {
335
364
  NotificationCenter.default.post(
336
365
  name: .updateProgressDidChange,
@@ -532,7 +561,6 @@ private func hotUpdaterGetMinBundleId() -> String {
532
561
  "DIRECTORY_CREATION_FAILED",
533
562
  "DOWNLOAD_FAILED",
534
563
  "INCOMPLETE_DOWNLOAD",
535
- "EXTRACTION_FORMAT_ERROR",
536
564
  "INVALID_BUNDLE",
537
565
  "INSUFFICIENT_DISK_SPACE",
538
566
  "SIGNATURE_VERIFICATION_FAILED",
@@ -545,6 +573,11 @@ private func hotUpdaterGetMinBundleId() -> String {
545
573
  // MARK: - Rollback Support
546
574
 
547
575
  public func notifyAppReady() -> [String: Any] {
576
+ // A failed runtime can finish queued native calls while recovery starts.
577
+ // Keep its readiness loop from consuming the new launch's report.
578
+ if hotUpdaterIsFailedRuntime().boolValue {
579
+ return ["status": "PENDING"]
580
+ }
548
581
  return bundleStorage.notifyAppReady()
549
582
  }
550
583
 
@@ -597,7 +630,7 @@ private func hotUpdaterGetMinBundleId() -> String {
597
630
 
598
631
  /**
599
632
  * Gets the current active bundle ID from bundle storage.
600
- * Reads manifest.json first and falls back to the legacy BUNDLE_ID file.
633
+ * Reads the required OTA manifest.json.
601
634
  * Built-in bundle fallback is handled in JS.
602
635
  */
603
636
  public func getBundleId() -> String? {
@@ -672,6 +705,7 @@ final class HotUpdaterRecoveryManager: NSObject {
672
705
  private var handlersInstalled = false
673
706
  private var isMonitoring = false
674
707
  private var recoveryRequested = false
708
+ private var monitoringGeneration = 0
675
709
  private var currentBundleId: String?
676
710
  private var shouldRollbackOnCrash = false
677
711
  private var contentAppearedCallback: ((String?) -> Void)?
@@ -709,6 +743,10 @@ final class HotUpdaterRecoveryManager: NSObject {
709
743
  shouldRollback: Bool,
710
744
  onContentAppeared: @escaping (String?) -> Void
711
745
  ) {
746
+ objc_sync_enter(self)
747
+ defer { objc_sync_exit(self) }
748
+
749
+ monitoringGeneration += 1
712
750
  currentBundleId = bundleId
713
751
  shouldRollbackOnCrash = shouldRollback
714
752
  recoveryRequested = false
@@ -719,6 +757,7 @@ final class HotUpdaterRecoveryManager: NSObject {
719
757
  stopMonitoringWorkItem = nil
720
758
 
721
759
  installHandlersIfNeeded()
760
+ hotUpdaterInstallRecoveryHooks()
722
761
  registerObservers()
723
762
  installSignalHandlersIfNeeded()
724
763
  hotUpdaterUpdateSignalLaunchState(bundleId, shouldRollback: shouldRollback)
@@ -726,9 +765,8 @@ final class HotUpdaterRecoveryManager: NSObject {
726
765
 
727
766
  func handleUncaughtException(_ exception: NSException) {
728
767
  writeCrashMarker()
729
- if requestRecoveryReloadIfNeeded() {
730
- return
731
- }
768
+ // An uncaught native exception cannot safely continue in this process.
769
+ // Preserve the marker so the next launch can select the safe bundle.
732
770
  previousUncaughtExceptionHandler?(exception)
733
771
  }
734
772
 
@@ -742,16 +780,22 @@ final class HotUpdaterRecoveryManager: NSObject {
742
780
  previousUncaughtExceptionHandler = NSGetUncaughtExceptionHandler()
743
781
 
744
782
  RCTSetFatalHandler { [weak self] error in
745
- self?.writeCrashMarker()
746
- if self?.requestRecoveryReloadIfNeeded() != true {
747
- self?.previousFatalHandler?(error)
783
+ guard !hotUpdaterIsFailedRuntime().boolValue else { return }
784
+ if self?.requestRecoveryReloadIfNeeded(onFailure: {
785
+ self?.forwardFatalError(error)
786
+ }) != true {
787
+ self?.writeCrashMarker()
788
+ self?.forwardFatalError(error)
748
789
  }
749
790
  }
750
791
 
751
792
  RCTSetFatalExceptionHandler { [weak self] exception in
752
- self?.writeCrashMarker()
753
- if self?.requestRecoveryReloadIfNeeded() != true {
754
- self?.previousFatalExceptionHandler?(exception)
793
+ guard !hotUpdaterIsFailedRuntime().boolValue else { return }
794
+ if self?.requestRecoveryReloadIfNeeded(onFailure: {
795
+ self?.forwardFatalException(exception)
796
+ }) != true {
797
+ self?.writeCrashMarker()
798
+ self?.forwardFatalException(exception)
755
799
  }
756
800
  }
757
801
 
@@ -759,6 +803,33 @@ final class HotUpdaterRecoveryManager: NSObject {
759
803
  handlersInstalled = true
760
804
  }
761
805
 
806
+ private func forwardFatalError(_ error: Error?) {
807
+ if let previousFatalHandler {
808
+ previousFatalHandler(error)
809
+ } else {
810
+ let handler = RCTGetFatalHandler()
811
+ RCTSetFatalHandler(nil)
812
+ RCTFatal(error)
813
+ // React Native's DEBUG default handler can return.
814
+ if RCTGetFatalHandler() == nil {
815
+ RCTSetFatalHandler(handler)
816
+ }
817
+ }
818
+ }
819
+
820
+ private func forwardFatalException(_ exception: NSException?) {
821
+ if let previousFatalExceptionHandler {
822
+ previousFatalExceptionHandler(exception)
823
+ } else {
824
+ let handler = RCTGetFatalExceptionHandler()
825
+ RCTSetFatalExceptionHandler(nil)
826
+ RCTFatalException(exception)
827
+ if RCTGetFatalExceptionHandler() == nil {
828
+ RCTSetFatalExceptionHandler(handler)
829
+ }
830
+ }
831
+ }
832
+
762
833
  private func installSignalHandlersIfNeeded() {
763
834
  guard !signalHandlersInstalled else {
764
835
  return
@@ -777,7 +848,7 @@ final class HotUpdaterRecoveryManager: NSObject {
777
848
  unregisterObservers()
778
849
  NotificationCenter.default.addObserver(
779
850
  self,
780
- selector: #selector(handleJavaScriptDidFailToLoad),
851
+ selector: #selector(handleJavaScriptDidFailToLoad(_:)),
781
852
  name: NSNotification.Name.RCTJavaScriptDidFailToLoad,
782
853
  object: nil
783
854
  )
@@ -802,15 +873,25 @@ final class HotUpdaterRecoveryManager: NSObject {
802
873
  )
803
874
  }
804
875
 
805
- @objc private func handleJavaScriptDidFailToLoad() {
806
- if requestRecoveryReloadIfNeeded() {
876
+ @objc private func handleJavaScriptDidFailToLoad(_ notification: Notification) {
877
+ let error = notification.userInfo?["error"] as? NSError ?? NSError(
878
+ domain: "HotUpdaterRecovery",
879
+ code: 1,
880
+ userInfo: [NSLocalizedDescriptionKey: "JavaScript bundle failed to load"]
881
+ )
882
+ if requestRecoveryReloadIfNeeded(onFailure: { [weak self] in
883
+ self?.forwardFatalError(error)
884
+ }) {
807
885
  return
808
886
  }
809
887
  unregisterObservers()
810
888
  }
811
889
 
812
890
  @objc private func handleContentDidAppear() {
813
- guard isMonitoring else {
891
+ objc_sync_enter(self)
892
+ defer { objc_sync_exit(self) }
893
+
894
+ guard isMonitoring, !recoveryRequested else {
814
895
  return
815
896
  }
816
897
 
@@ -820,14 +901,19 @@ final class HotUpdaterRecoveryManager: NSObject {
820
901
  hotUpdaterUpdateSignalLaunchState(currentBundleId, shouldRollback: false)
821
902
 
822
903
  stopMonitoringWorkItem?.cancel()
904
+ let generation = monitoringGeneration
823
905
  let workItem = DispatchWorkItem { [weak self] in
824
- self?.finishMonitoring()
906
+ self?.finishMonitoring(generation: generation)
825
907
  }
826
908
  stopMonitoringWorkItem = workItem
827
909
  DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10), execute: workItem)
828
910
  }
829
911
 
830
- private func finishMonitoring() {
912
+ private func finishMonitoring(generation: Int) {
913
+ objc_sync_enter(self)
914
+ defer { objc_sync_exit(self) }
915
+ guard monitoringGeneration == generation else { return }
916
+
831
917
  isMonitoring = false
832
918
  recoveryRequested = false
833
919
  stopMonitoringWorkItem = nil
@@ -837,31 +923,40 @@ final class HotUpdaterRecoveryManager: NSObject {
837
923
  hotUpdaterUpdateSignalLaunchState(nil, shouldRollback: false)
838
924
  }
839
925
 
840
- private func requestRecoveryReloadIfNeeded() -> Bool {
926
+ private func requestRecoveryReloadIfNeeded(onFailure: @escaping () -> Void) -> Bool {
927
+ objc_sync_enter(self)
928
+ defer { objc_sync_exit(self) }
929
+
841
930
  guard isMonitoring, shouldRollbackOnCrash else {
842
931
  return false
843
932
  }
844
933
 
845
- objc_sync_enter(self)
934
+ hotUpdaterMarkFailedRuntime()
846
935
  if recoveryRequested {
847
- objc_sync_exit(self)
848
936
  return true
849
937
  }
850
938
  recoveryRequested = true
851
- objc_sync_exit(self)
939
+ writeCrashMarker()
852
940
 
853
941
  let bundleId = currentBundleId
854
- DispatchQueue.main.async { [weak self] in
942
+ let generation = monitoringGeneration
943
+ hotUpdaterAfterSurfaceStarts { [weak self] in
855
944
  guard let self else {
856
945
  return
857
946
  }
858
947
 
948
+ objc_sync_enter(self)
949
+ let isCurrentLaunch = self.monitoringGeneration == generation
950
+ objc_sync_exit(self)
951
+ guard isCurrentLaunch else { return }
952
+
859
953
  let started = hotUpdaterPerformRecoveryReload()
860
954
  if !started {
861
955
  objc_sync_enter(self)
862
956
  self.recoveryRequested = false
863
957
  objc_sync_exit(self)
864
958
  NSLog("[HotUpdaterRecovery] Failed to trigger recovery reload")
959
+ onFailure()
865
960
  } else {
866
961
  NSLog("[HotUpdaterRecovery] Triggered recovery reload for bundleId=\(bundleId ?? "nil")")
867
962
  }
@@ -871,6 +966,9 @@ final class HotUpdaterRecoveryManager: NSObject {
871
966
  }
872
967
 
873
968
  private func writeCrashMarker() {
969
+ objc_sync_enter(self)
970
+ defer { objc_sync_exit(self) }
971
+
874
972
  guard isMonitoring else {
875
973
  return
876
974
  }
@@ -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
  }