@hot-updater/react-native 1.0.0-rc.4 → 1.0.0-rc.40

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 (306) hide show
  1. package/android/build.gradle +13 -2
  2. package/android/proguard-rules.pro +8 -2
  3. package/android/src/bridge/BridgeReactInstance.kt +38 -0
  4. package/android/src/bridgeless/BridgeReactInstance.kt +24 -0
  5. package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +94 -0
  6. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  7. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1064 -844
  8. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +50 -11
  9. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  10. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  11. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +46 -30
  12. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +75 -192
  13. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  14. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  15. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  16. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  17. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  18. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  19. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  20. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  21. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  22. package/android/src/newarch/HotUpdater.kt +7 -9
  23. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  24. package/android/src/newarch/JavaScriptExceptionHooks.kt +48 -0
  25. package/android/src/newarch/ReactIntegrationManager.kt +6 -32
  26. package/android/src/oldarch/HotUpdater.kt +7 -9
  27. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  28. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  29. package/android/src/oldarch/JavaScriptExceptionHooks.kt +71 -0
  30. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  31. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  32. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1032 -1116
  33. package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -54
  34. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  35. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  36. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  37. package/ios/HotUpdater/Internal/HotUpdater.mm +32 -67
  38. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +229 -73
  39. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  40. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  41. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  42. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  43. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  44. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  45. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  46. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  47. package/lib/commonjs/appReady.js +102 -0
  48. package/lib/commonjs/appReady.js.map +1 -0
  49. package/lib/commonjs/checkForUpdate.js +120 -68
  50. package/lib/commonjs/checkForUpdate.js.map +1 -1
  51. package/lib/commonjs/clientPlugin.js +13 -0
  52. package/lib/commonjs/clientPlugin.js.map +1 -0
  53. package/lib/commonjs/error.js +4 -13
  54. package/lib/commonjs/error.js.map +1 -1
  55. package/lib/commonjs/fetchJSON.js +16 -27
  56. package/lib/commonjs/fetchJSON.js.map +1 -1
  57. package/lib/commonjs/fetchUpdateResponse.js +60 -0
  58. package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
  59. package/lib/commonjs/httpClient.js +63 -94
  60. package/lib/commonjs/httpClient.js.map +1 -1
  61. package/lib/commonjs/index.js +71 -18
  62. package/lib/commonjs/index.js.map +1 -1
  63. package/lib/commonjs/native.js +74 -60
  64. package/lib/commonjs/native.js.map +1 -1
  65. package/lib/commonjs/pluginHost.js +45 -0
  66. package/lib/commonjs/pluginHost.js.map +1 -0
  67. package/lib/commonjs/releaseCatalogCache.js +36 -87
  68. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  69. package/lib/commonjs/sdkVersion.js +1 -1
  70. package/lib/commonjs/sdkVersion.js.map +1 -1
  71. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  72. package/lib/commonjs/store.js +3 -10
  73. package/lib/commonjs/store.js.map +1 -1
  74. package/lib/commonjs/updateError.js +145 -0
  75. package/lib/commonjs/updateError.js.map +1 -0
  76. package/lib/commonjs/wrap.js +43 -26
  77. package/lib/commonjs/wrap.js.map +1 -1
  78. package/lib/module/appReady.js +95 -0
  79. package/lib/module/appReady.js.map +1 -0
  80. package/lib/module/checkForUpdate.js +113 -63
  81. package/lib/module/checkForUpdate.js.map +1 -1
  82. package/lib/module/clientPlugin.js +4 -0
  83. package/lib/module/clientPlugin.js.map +1 -0
  84. package/lib/module/error.js +4 -13
  85. package/lib/module/error.js.map +1 -1
  86. package/lib/module/fetchJSON.js +14 -26
  87. package/lib/module/fetchJSON.js.map +1 -1
  88. package/lib/module/fetchUpdateResponse.js +55 -0
  89. package/lib/module/fetchUpdateResponse.js.map +1 -0
  90. package/lib/module/httpClient.js +62 -93
  91. package/lib/module/httpClient.js.map +1 -1
  92. package/lib/module/index.js +58 -20
  93. package/lib/module/index.js.map +1 -1
  94. package/lib/module/native.js +67 -53
  95. package/lib/module/native.js.map +1 -1
  96. package/lib/module/pluginHost.js +36 -0
  97. package/lib/module/pluginHost.js.map +1 -0
  98. package/lib/module/releaseCatalogCache.js +32 -82
  99. package/lib/module/releaseCatalogCache.js.map +1 -1
  100. package/lib/module/sdkVersion.js +1 -1
  101. package/lib/module/sdkVersion.js.map +1 -1
  102. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  103. package/lib/module/store.js +3 -10
  104. package/lib/module/store.js.map +1 -1
  105. package/lib/module/updateError.js +139 -0
  106. package/lib/module/updateError.js.map +1 -0
  107. package/lib/module/wrap.js +43 -26
  108. package/lib/module/wrap.js.map +1 -1
  109. package/lib/typescript/commonjs/appReady.d.ts +21 -0
  110. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  112. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  114. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/error.d.ts +4 -13
  116. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
  118. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
  120. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/httpClient.d.ts +4 -33
  122. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/index.d.ts +4 -8
  124. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/native.d.ts +29 -22
  126. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  128. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
  130. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  132. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +35 -47
  134. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  135. package/lib/typescript/commonjs/store.d.ts +1 -3
  136. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/types.d.ts +1 -1
  138. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  140. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  143. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  144. package/lib/typescript/module/appReady.d.ts +21 -0
  145. package/lib/typescript/module/appReady.d.ts.map +1 -0
  146. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  147. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  148. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  149. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  150. package/lib/typescript/module/error.d.ts +4 -13
  151. package/lib/typescript/module/error.d.ts.map +1 -1
  152. package/lib/typescript/module/fetchJSON.d.ts +5 -5
  153. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  154. package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
  155. package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
  156. package/lib/typescript/module/httpClient.d.ts +4 -33
  157. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  158. package/lib/typescript/module/index.d.ts +4 -8
  159. package/lib/typescript/module/index.d.ts.map +1 -1
  160. package/lib/typescript/module/native.d.ts +29 -22
  161. package/lib/typescript/module/native.d.ts.map +1 -1
  162. package/lib/typescript/module/pluginHost.d.ts +4 -0
  163. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  164. package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
  165. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  166. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  167. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  168. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +35 -47
  169. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  170. package/lib/typescript/module/store.d.ts +1 -3
  171. package/lib/typescript/module/store.d.ts.map +1 -1
  172. package/lib/typescript/module/types.d.ts +1 -1
  173. package/lib/typescript/module/types.d.ts.map +1 -1
  174. package/lib/typescript/module/updateError.d.ts +26 -0
  175. package/lib/typescript/module/updateError.d.ts.map +1 -0
  176. package/lib/typescript/module/wrap.d.ts.map +1 -1
  177. package/lib/typescript/module/wrap.types.d.ts +6 -7
  178. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  179. package/package.json +7 -4
  180. package/src/appReady.ts +141 -0
  181. package/src/checkForUpdate.ts +145 -86
  182. package/src/clientPlugin.ts +18 -0
  183. package/src/error.ts +4 -14
  184. package/src/fetchJSON.ts +14 -30
  185. package/src/fetchUpdateResponse.ts +60 -0
  186. package/src/httpClient.ts +88 -159
  187. package/src/index.ts +90 -24
  188. package/src/native.ts +98 -102
  189. package/src/pluginHost.ts +47 -0
  190. package/src/releaseCatalogCache.ts +43 -172
  191. package/src/sdkVersion.ts +1 -1
  192. package/src/specs/NativeHotUpdater.ts +34 -48
  193. package/src/store.ts +2 -23
  194. package/src/types.ts +1 -1
  195. package/src/updateError.ts +167 -0
  196. package/src/wrap.tsx +57 -26
  197. package/src/wrap.types.ts +6 -7
  198. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  199. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  200. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  201. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  202. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  203. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  204. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  205. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  206. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  207. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  208. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  209. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  210. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  211. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  212. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  213. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  214. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  215. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  216. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  217. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  218. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  219. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  220. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  221. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -616
  222. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  223. package/lib/commonjs/httpClient.spec.js +0 -102
  224. package/lib/commonjs/httpClient.spec.js.map +0 -1
  225. package/lib/commonjs/index.spec.js +0 -302
  226. package/lib/commonjs/index.spec.js.map +0 -1
  227. package/lib/commonjs/iosInterop.spec.js +0 -18
  228. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  229. package/lib/commonjs/native.spec.js +0 -883
  230. package/lib/commonjs/native.spec.js.map +0 -1
  231. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  232. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  233. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  234. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  235. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  236. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  237. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  238. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  239. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  240. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  241. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  242. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  243. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  244. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  245. package/lib/commonjs/store.notification.spec.js +0 -76
  246. package/lib/commonjs/store.notification.spec.js.map +0 -1
  247. package/lib/commonjs/store.spec.js +0 -316
  248. package/lib/commonjs/store.spec.js.map +0 -1
  249. package/lib/commonjs/wrap.spec.js +0 -329
  250. package/lib/commonjs/wrap.spec.js.map +0 -1
  251. package/lib/module/catalogCacheNative.spec.js +0 -31
  252. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  253. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -615
  254. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  255. package/lib/module/httpClient.spec.js +0 -102
  256. package/lib/module/httpClient.spec.js.map +0 -1
  257. package/lib/module/index.spec.js +0 -301
  258. package/lib/module/index.spec.js.map +0 -1
  259. package/lib/module/iosInterop.spec.js +0 -18
  260. package/lib/module/iosInterop.spec.js.map +0 -1
  261. package/lib/module/native.spec.js +0 -882
  262. package/lib/module/native.spec.js.map +0 -1
  263. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  264. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  265. package/lib/module/notifyAppReadyInsights.js +0 -128
  266. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  267. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  268. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  269. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  270. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  271. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  272. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  273. package/lib/module/releaseCatalogCache.spec.js +0 -195
  274. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  275. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  276. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  277. package/lib/module/store.notification.spec.js +0 -77
  278. package/lib/module/store.notification.spec.js.map +0 -1
  279. package/lib/module/store.spec.js +0 -315
  280. package/lib/module/store.spec.js.map +0 -1
  281. package/lib/module/wrap.spec.js +0 -330
  282. package/lib/module/wrap.spec.js.map +0 -1
  283. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  284. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  285. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  286. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  287. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  288. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  289. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  290. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  291. package/src/catalogCacheNative.spec.ts +0 -63
  292. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -612
  293. package/src/httpClient.spec.ts +0 -132
  294. package/src/index.spec.ts +0 -337
  295. package/src/iosInterop.spec.ts +0 -30
  296. package/src/native.spec.ts +0 -945
  297. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  298. package/src/notifyAppReadyInsights.spec.ts +0 -243
  299. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  300. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  301. package/src/notifyAppReadyInsights.ts +0 -194
  302. package/src/releaseCatalogCache.spec.ts +0 -282
  303. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  304. package/src/store.notification.spec.ts +0 -92
  305. package/src/store.spec.ts +0 -372
  306. package/src/wrap.spec.ts +0 -406
@@ -0,0 +1,119 @@
1
+ package com.hotupdater
2
+
3
+ import com.hotupdater.vendor.brotli.dec.BrotliInputStream
4
+ import java.io.File
5
+ import java.io.FileInputStream
6
+ import java.io.IOException
7
+ import java.io.InputStream
8
+
9
+ internal object TarBrArchiveExtractor {
10
+ fun extract(
11
+ archiveFile: File,
12
+ destination: File,
13
+ expectedTarByteSize: Long,
14
+ expectedFiles: Map<String, Long>,
15
+ ) {
16
+ require(expectedTarByteSize >= 0) { "Invalid TAR byte size" }
17
+ if (destination.exists() && !destination.deleteRecursively()) {
18
+ throw IOException("Cannot clear archive extraction directory")
19
+ }
20
+ if (!destination.mkdirs()) throw IOException("Cannot create archive extraction directory")
21
+
22
+ try {
23
+ FileInputStream(archiveFile).use { compressedInput ->
24
+ BrotliInputStream(compressedInput).use { brotliInput ->
25
+ val boundedInput = ExactSizeInputStream(brotliInput, expectedTarByteSize)
26
+ TarArchiveInputStream(boundedInput).use { tarInput ->
27
+ val extractedPaths = linkedSetOf<String>()
28
+ var entry = tarInput.getNextEntry()
29
+ while (entry != null) {
30
+ if (!entry.isRegularFile) {
31
+ throw IOException("Unsupported TAR entry type for ${entry.name}")
32
+ }
33
+ val normalizedPath = RelativePathResolver.normalizeRelativePath(entry.name)
34
+ if (normalizedPath == null || normalizedPath != entry.name) {
35
+ throw IOException("Unsafe TAR entry path: ${entry.name}")
36
+ }
37
+ val expectedSize =
38
+ expectedFiles[normalizedPath]
39
+ ?: throw IOException("Unexpected TAR entry: $normalizedPath")
40
+ if (!extractedPaths.add(normalizedPath)) {
41
+ throw IOException("Duplicate TAR entry: $normalizedPath")
42
+ }
43
+ if (entry.size != expectedSize) {
44
+ throw IOException("TAR entry size mismatch: $normalizedPath")
45
+ }
46
+
47
+ val outputFile =
48
+ RelativePathResolver.resolveInside(destination, normalizedPath)
49
+ ?: throw IOException("Unsafe TAR entry path: $normalizedPath")
50
+ if (outputFile.exists()) throw IOException("TAR output already exists: $normalizedPath")
51
+ val parentDirectory = outputFile.parentFile
52
+ if (parentDirectory != null && !parentDirectory.isDirectory && !parentDirectory.mkdirs()) {
53
+ throw IOException("Cannot create TAR output directory: $normalizedPath")
54
+ }
55
+ outputFile.outputStream().use { output ->
56
+ val buffer = ByteArray(8192)
57
+ var written = 0L
58
+ while (true) {
59
+ val count = tarInput.read(buffer)
60
+ if (count < 0) break
61
+ output.write(buffer, 0, count)
62
+ written += count
63
+ }
64
+ if (written != expectedSize) {
65
+ throw IOException("TAR entry was truncated: $normalizedPath")
66
+ }
67
+ }
68
+ entry = tarInput.getNextEntry()
69
+ }
70
+
71
+ if (extractedPaths != expectedFiles.keys) {
72
+ throw IOException("TAR file set does not match manifest")
73
+ }
74
+ }
75
+ boundedInput.requireExactSize()
76
+ }
77
+ }
78
+ } catch (error: Throwable) {
79
+ destination.deleteRecursively()
80
+ throw error
81
+ }
82
+ }
83
+
84
+ private class ExactSizeInputStream(
85
+ private val input: InputStream,
86
+ private val expectedSize: Long,
87
+ ) : InputStream() {
88
+ private var byteCount = 0L
89
+
90
+ override fun read(): Int {
91
+ val result = input.read()
92
+ if (result >= 0) recordRead(1)
93
+ return result
94
+ }
95
+
96
+ override fun read(
97
+ buffer: ByteArray,
98
+ offset: Int,
99
+ length: Int,
100
+ ): Int {
101
+ val count = input.read(buffer, offset, length)
102
+ if (count > 0) recordRead(count.toLong())
103
+ return count
104
+ }
105
+
106
+ fun requireExactSize() {
107
+ if (byteCount != expectedSize) {
108
+ throw IOException("TAR stream size mismatch: expected $expectedSize, got $byteCount")
109
+ }
110
+ }
111
+
112
+ private fun recordRead(count: Long) {
113
+ if (byteCount > expectedSize - count) {
114
+ throw IOException("TAR stream exceeds declared size")
115
+ }
116
+ byteCount += count
117
+ }
118
+ }
119
+ }
@@ -36,7 +36,7 @@ class HotUpdater {
36
36
  * Updates the bundle using the default singleton instance
37
37
  * @param context Application context
38
38
  * @param bundleId ID of the bundle to update
39
- * @param fileUrl URL of the bundle file to download (or null to reset)
39
+ * @param params Manifest and asset descriptors for the target bundle
40
40
  * @param fileHash Combined hash string for verification (sig:<signature> or <hex_hash>)
41
41
  * @param progressCallback Callback for download progress updates
42
42
  * @throws HotUpdaterException if the update fails
@@ -44,22 +44,20 @@ class HotUpdater {
44
44
  suspend fun updateBundle(
45
45
  context: Context,
46
46
  bundleId: String,
47
- fileUrl: String?,
48
- fileHash: String?,
49
- manifestUrl: String?,
50
- manifestFileHash: String?,
51
- changedAssets: Map<String, ChangedAssetDescriptor>?,
47
+ manifestUrl: String,
48
+ manifestFileHash: String,
49
+ assets: Map<String, ChangedAssetDescriptor>,
52
50
  channel: String?,
51
+ archiveUrl: String? = null,
53
52
  progressCallback: (UpdateProgressPayload) -> Unit,
54
53
  ) {
55
54
  getInstance(context).updateBundle(
56
55
  bundleId,
57
- fileUrl,
58
- fileHash,
59
56
  manifestUrl,
60
57
  manifestFileHash,
61
- changedAssets,
58
+ assets,
62
59
  channel,
60
+ archiveUrl = archiveUrl,
63
61
  progressCallback = progressCallback,
64
62
  )
65
63
  }
@@ -26,6 +26,16 @@ class HotUpdaterModule internal constructor(
26
26
 
27
27
  override fun getName(): String = NAME
28
28
 
29
+ private fun readSafeInteger(
30
+ map: ReadableMap,
31
+ key: String,
32
+ ): Long? {
33
+ if (!map.hasKey(key) || map.isNull(key)) return null
34
+ val value = runCatching { map.getDouble(key) }.getOrNull() ?: return null
35
+ if (!value.isFinite() || value % 1.0 != 0.0) return null
36
+ return value.toLong().takeIf { it in 0..9_007_199_254_740_991L }
37
+ }
38
+
29
39
  override fun invalidate() {
30
40
  super.invalidate()
31
41
  // Cancel all ongoing coroutines when module is destroyed
@@ -37,12 +47,12 @@ class HotUpdaterModule internal constructor(
37
47
  */
38
48
  private fun getInstance(): HotUpdaterImpl = HotUpdater.getInstance(mReactApplicationContext)
39
49
 
40
- private fun parseChangedAssets(params: ReadableMap): Map<String, ChangedAssetDescriptor>? {
41
- if (!params.hasKey("changedAssets") || params.isNull("changedAssets")) {
50
+ private fun parseAssets(params: ReadableMap): Map<String, ChangedAssetDescriptor>? {
51
+ if (!params.hasKey("assets") || params.isNull("assets")) {
42
52
  return null
43
53
  }
44
54
 
45
- val changedAssetsMap = params.getMap("changedAssets") ?: return null
55
+ val changedAssetsMap = params.getMap("assets") ?: return null
46
56
  val parsedAssets = linkedMapOf<String, ChangedAssetDescriptor>()
47
57
  val iterator = changedAssetsMap.keySetIterator()
48
58
 
@@ -58,6 +68,7 @@ class HotUpdaterModule internal constructor(
58
68
  val baseFileHash = patchMap.getString("baseFileHash")
59
69
  val patchFileHash = patchMap.getString("patchFileHash")
60
70
  val patchUrl = patchMap.getString("patchUrl")
71
+ val byteSize = readSafeInteger(patchMap, "byteSize")
61
72
 
62
73
  if (
63
74
  algorithm != null &&
@@ -72,6 +83,7 @@ class HotUpdaterModule internal constructor(
72
83
  baseFileHash = baseFileHash,
73
84
  patchFileHash = patchFileHash,
74
85
  patchUrl = patchUrl,
86
+ byteSize = byteSize,
75
87
  )
76
88
  } else {
77
89
  null
@@ -162,97 +174,112 @@ class HotUpdaterModule internal constructor(
162
174
  try {
163
175
  val bundleId = params.getString("bundleId")
164
176
  if (bundleId == null || bundleId.isEmpty()) {
165
- promise.reject("MISSING_BUNDLE_ID", "Missing or empty 'bundleId'")
177
+ promise.rejectUpdateBundle(HotUpdaterException.missingBundleId())
166
178
  return@launch
167
179
  }
168
180
 
169
- val fileUrl = params.getString("fileUrl")
170
-
171
- // Validate fileUrl format if provided
172
- if (fileUrl != null && fileUrl.isNotEmpty()) {
181
+ val manifestUrl = params.getString("manifestUrl")
182
+ val manifestFileHash = params.getString("manifestFileHash")
183
+ val assets = parseAssets(params)
184
+ if (manifestUrl.isNullOrEmpty() || manifestFileHash.isNullOrEmpty() || assets == null) {
185
+ promise.rejectUpdateBundle(HotUpdaterException.invalidManifestParams())
186
+ return@launch
187
+ }
188
+ try {
189
+ java.net.URL(manifestUrl)
190
+ } catch (e: java.net.MalformedURLException) {
191
+ promise.rejectUpdateBundle(
192
+ HotUpdaterException.invalidFileUrl("manifestUrl", manifestUrl, UpdateFailureResource.MANIFEST),
193
+ )
194
+ return@launch
195
+ }
196
+ val channel = params.getString("channel")
197
+ val archiveUrl =
198
+ if (params.hasKey("archiveUrl") && !params.isNull("archiveUrl")) {
199
+ params.getString("archiveUrl")
200
+ } else {
201
+ null
202
+ }
203
+ if (archiveUrl != null) {
173
204
  try {
174
- java.net.URL(fileUrl)
205
+ java.net.URL(archiveUrl)
175
206
  } catch (e: java.net.MalformedURLException) {
176
- promise.reject("INVALID_FILE_URL", "Invalid 'fileUrl' provided: $fileUrl")
207
+ promise.rejectUpdateBundle(
208
+ HotUpdaterException.invalidFileUrl("archiveUrl", archiveUrl, UpdateFailureResource.ARCHIVE),
209
+ )
177
210
  return@launch
178
211
  }
179
212
  }
180
-
181
- val fileHash = params.getString("fileHash")
182
- val manifestUrl = params.getString("manifestUrl")
183
- val manifestFileHash = params.getString("manifestFileHash")
184
- val changedAssets = parseChangedAssets(params)
185
- val channel = params.getString("channel")
186
213
  val selection = parseSelection(params)
187
214
 
188
215
  val impl = getInstance()
189
216
 
190
- impl.updateBundle(
191
- bundleId,
192
- fileUrl,
193
- fileHash,
194
- manifestUrl,
195
- manifestFileHash,
196
- changedAssets,
197
- channel,
198
- selection,
199
- ) { progress ->
200
- // Post to Main thread for React Native event emission
201
- Handler(Looper.getMainLooper()).post {
202
- try {
203
- val progressParams =
204
- WritableNativeMap().apply {
205
- putDouble("progress", progress.progress)
206
- putString("artifactType", progress.artifactType)
207
- progress.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
208
- progress.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
209
- progress.details?.let { details ->
210
- putMap(
211
- "details",
212
- WritableNativeMap().apply {
213
- putInt("totalFilesCount", details.totalFilesCount)
214
- putInt("completedFilesCount", details.completedFilesCount)
215
- putArray(
216
- "files",
217
- WritableNativeArray().apply {
218
- details.files.forEach { file ->
219
- pushMap(
220
- WritableNativeMap().apply {
221
- putString("path", file.path)
222
- putString("downloadPath", file.downloadPath)
223
- putString("status", file.status)
224
- putDouble("progress", file.progress)
225
- putInt("order", file.order)
226
- file.downloadedBytes?.let {
227
- putDouble(
228
- "downloadedBytes",
229
- it.toDouble(),
230
- )
231
- }
232
- file.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
233
- },
234
- )
235
- }
236
- },
237
- )
238
- },
239
- )
217
+ val result =
218
+ impl.updateBundle(
219
+ bundleId,
220
+ manifestUrl,
221
+ manifestFileHash,
222
+ assets,
223
+ channel,
224
+ selection,
225
+ archiveUrl,
226
+ ) { progress ->
227
+ // Post to Main thread for React Native event emission
228
+ Handler(Looper.getMainLooper()).post {
229
+ try {
230
+ val progressParams =
231
+ WritableNativeMap().apply {
232
+ putDouble("progress", progress.progress)
233
+ putString("artifactType", progress.artifactType)
234
+ progress.details?.let { details ->
235
+ putMap(
236
+ "details",
237
+ WritableNativeMap().apply {
238
+ putInt("totalFilesCount", details.totalFilesCount)
239
+ putInt("completedFilesCount", details.completedFilesCount)
240
+ putArray(
241
+ "files",
242
+ WritableNativeArray().apply {
243
+ details.files.forEach { file ->
244
+ pushMap(
245
+ WritableNativeMap().apply {
246
+ putString("path", file.path)
247
+ putString("downloadPath", file.downloadPath)
248
+ putString("status", file.status)
249
+ putDouble("progress", file.progress)
250
+ putInt("order", file.order)
251
+ file.downloadedBytes?.let {
252
+ putDouble(
253
+ "downloadedBytes",
254
+ it.toDouble(),
255
+ )
256
+ }
257
+ file.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
258
+ },
259
+ )
260
+ }
261
+ },
262
+ )
263
+ },
264
+ )
265
+ }
240
266
  }
241
- }
242
-
243
- this@HotUpdaterModule
244
- .mReactApplicationContext
245
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
246
- ?.emit("onProgress", progressParams)
247
- } catch (e: Exception) {
248
- Log.w("HotUpdater", "Failed to emit progress (bridge may be unavailable): ${e.message}")
267
+
268
+ this@HotUpdaterModule
269
+ .mReactApplicationContext
270
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
271
+ ?.emit("onProgress", progressParams)
272
+ } catch (e: Exception) {
273
+ Log.w("HotUpdater", "Failed to emit progress (bridge may be unavailable): ${e.message}")
274
+ }
249
275
  }
250
276
  }
251
- }
252
- promise.resolve(true)
277
+ promise.resolve(result.toWritableMap())
253
278
  } catch (e: HotUpdaterException) {
254
- promise.reject(e.code, e.message)
279
+ promise.rejectUpdateBundle(e)
255
280
  } catch (e: Exception) {
281
+ // Not an update failure (a stale Release selection, for example),
282
+ // so the rejection carries no classification.
256
283
  promise.reject("UNKNOWN_ERROR", e.message ?: "An unknown error occurred")
257
284
  }
258
285
  }
@@ -389,15 +416,13 @@ class HotUpdaterModule internal constructor(
389
416
 
390
417
  override fun getInstallId(): String = getInstance().getInstallId()
391
418
 
392
- override fun getUserId(): String? = getInstance().getUserId()
393
-
394
- override fun getUsername(): String? = getInstance().getUsername()
419
+ override fun getStorageItem(key: String): String? = getInstance().getStorageItem(key)
395
420
 
396
- override fun setUser(
397
- userId: String?,
398
- username: String?,
421
+ override fun setStorageItem(
422
+ key: String,
423
+ value: String?,
399
424
  ) {
400
- getInstance().setUser(userId, username)
425
+ getInstance().setStorageItem(key, value)
401
426
  }
402
427
 
403
428
  override fun resetChannel(promise: Promise) {
@@ -0,0 +1,48 @@
1
+ package com.hotupdater
2
+
3
+ import com.facebook.react.ReactApplication
4
+
5
+ /**
6
+ * Routes JavaScript errors of the running React instance to crash recovery.
7
+ * The New Architecture reaches the instance through ReactHost.
8
+ */
9
+ internal object JavaScriptExceptionHooks {
10
+ private val patchedReactHostDelegateIds = mutableSetOf<Int>()
11
+
12
+ fun install(application: ReactApplication) {
13
+ val reactHost = application.reactHost ?: return
14
+ val reactHostDelegate =
15
+ HotUpdaterRecoveryManager.findField(reactHost.javaClass, "mReactHostDelegate")?.let { field ->
16
+ field.isAccessible = true
17
+ field.get(reactHost)
18
+ }
19
+ ?: HotUpdaterRecoveryManager.findField(reactHost.javaClass, "reactHostDelegate")?.let { field ->
20
+ field.isAccessible = true
21
+ field.get(reactHost)
22
+ }
23
+ ?: return
24
+
25
+ val delegateIdentity = System.identityHashCode(reactHostDelegate)
26
+ if (patchedReactHostDelegateIds.add(delegateIdentity)) {
27
+ val exceptionHandlerField =
28
+ HotUpdaterRecoveryManager.findField(reactHostDelegate.javaClass, "exceptionHandler")
29
+ if (exceptionHandlerField == null) {
30
+ patchedReactHostDelegateIds.remove(delegateIdentity)
31
+ return
32
+ }
33
+
34
+ exceptionHandlerField.isAccessible = true
35
+ @Suppress("UNCHECKED_CAST")
36
+ val previousHandler =
37
+ exceptionHandlerField.get(reactHostDelegate) as? (Exception) -> Unit
38
+
39
+ exceptionHandlerField.set(reactHostDelegate) { exception: Exception ->
40
+ if (!HotUpdaterRecoveryManager.handleJavaScriptExceptionForRecovery(exception)) {
41
+ previousHandler?.invoke(exception) ?: throw exception
42
+ }
43
+ }
44
+ }
45
+
46
+ reactHost.currentReactContext?.let(HotUpdaterRecoveryManager::patchReactContextExceptionHandler)
47
+ }
48
+ }
@@ -5,12 +5,10 @@ import android.util.Log
5
5
  import com.facebook.react.ReactApplication
6
6
  import com.facebook.react.ReactHost
7
7
  import com.facebook.react.ReactInstanceEventListener
8
- import com.facebook.react.bridge.JSBundleLoader
9
8
  import com.facebook.react.bridge.ReactApplicationContext
10
9
  import com.facebook.react.bridge.ReactContext
11
10
  import com.facebook.react.common.LifecycleState
12
11
  import kotlinx.coroutines.suspendCancellableCoroutine
13
- import java.lang.reflect.Field
14
12
  import kotlin.coroutines.resume
15
13
 
16
14
  class ReactIntegrationManager(
@@ -89,7 +87,7 @@ class ReactIntegrationManager(
89
87
 
90
88
  /**
91
89
  * Sets the JS bundle.
92
- * Priority: ReactHostHolder (if set) > application.reactHost > reactNativeHost
90
+ * Priority: ReactHostHolder (if set) > application.reactHost > the bridge (React Native 0.81 and older)
93
91
  * @param bundleURL The bundle URL to set
94
92
  */
95
93
  public fun setJSBundle(bundleURL: String) {
@@ -115,19 +113,8 @@ class ReactIntegrationManager(
115
113
  return
116
114
  }
117
115
 
118
- // 4. Fallback to old architecture (reactNativeHost)
119
- @Suppress("DEPRECATION")
120
- val instanceManager = application.reactNativeHost.reactInstanceManager
121
- val bundleLoader: JSBundleLoader? = this.getJSBundlerLoader(bundleURL)
122
- val bundleLoaderField: Field =
123
- instanceManager::class.java.getDeclaredField("mBundleLoader")
124
- bundleLoaderField.isAccessible = true
125
-
126
- if (bundleLoader != null) {
127
- bundleLoaderField.set(instanceManager, bundleLoader)
128
- } else {
129
- bundleLoaderField.set(instanceManager, null)
130
- }
116
+ // 4. Fall back to the bridge, which React Native 0.81 and older can run
117
+ BridgeReactInstance.setJSBundle(application, getJSBundlerLoader(bundleURL))
131
118
  } catch (e: Exception) {
132
119
  Log.d("HotUpdater", "Failed to setJSBundle: ${e.message}")
133
120
  }
@@ -135,7 +122,7 @@ class ReactIntegrationManager(
135
122
 
136
123
  /**
137
124
  * Reload the React Native application.
138
- * Priority: ReactHostHolder (if set) > application.reactHost > reactNativeHost
125
+ * Priority: ReactHostHolder (if set) > application.reactHost > the bridge (React Native 0.81 and older)
139
126
  */
140
127
  public suspend fun reload() {
141
128
  try {
@@ -160,21 +147,8 @@ class ReactIntegrationManager(
160
147
  return
161
148
  }
162
149
 
163
- // 4. Fallback to old architecture (reactNativeHost)
164
- @Suppress("DEPRECATION")
165
- val reactNativeHost = application.reactNativeHost
166
- try {
167
- reactNativeHost.reactInstanceManager.recreateReactContextInBackground()
168
- } catch (e: Exception) {
169
- val currentActivity = reactNativeHost.reactInstanceManager.currentReactContext?.currentActivity
170
- if (currentActivity == null) {
171
- return
172
- }
173
-
174
- currentActivity.runOnUiThread {
175
- currentActivity.recreate()
176
- }
177
- }
150
+ // 4. Fall back to the bridge, which React Native 0.81 and older can run
151
+ BridgeReactInstance.reload(application)
178
152
  } catch (e: Exception) {
179
153
  Log.d("HotUpdater", "Failed to reload: ${e.message}")
180
154
  }
@@ -35,7 +35,7 @@ class HotUpdater {
35
35
  * Updates the bundle using the default singleton instance
36
36
  * @param context Application context
37
37
  * @param bundleId ID of the bundle to update
38
- * @param fileUrl URL of the bundle file to download (or null to reset)
38
+ * @param params Manifest and asset descriptors for the target bundle
39
39
  * @param fileHash Combined hash string for verification (sig:<signature> or <hex_hash>)
40
40
  * @param progressCallback Callback for download progress updates
41
41
  * @throws HotUpdaterException if the update fails
@@ -43,22 +43,20 @@ class HotUpdater {
43
43
  suspend fun updateBundle(
44
44
  context: Context,
45
45
  bundleId: String,
46
- fileUrl: String?,
47
- fileHash: String?,
48
- manifestUrl: String?,
49
- manifestFileHash: String?,
50
- changedAssets: Map<String, ChangedAssetDescriptor>?,
46
+ manifestUrl: String,
47
+ manifestFileHash: String,
48
+ assets: Map<String, ChangedAssetDescriptor>,
51
49
  channel: String?,
50
+ archiveUrl: String? = null,
52
51
  progressCallback: (UpdateProgressPayload) -> Unit,
53
52
  ) {
54
53
  getInstance(context).updateBundle(
55
54
  bundleId,
56
- fileUrl,
57
- fileHash,
58
55
  manifestUrl,
59
56
  manifestFileHash,
60
- changedAssets,
57
+ assets,
61
58
  channel,
59
+ archiveUrl = archiveUrl,
62
60
  progressCallback = progressCallback,
63
61
  )
64
62
  }