@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
@@ -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) {
@@ -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
  }