@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
@@ -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
@@ -166,36 +178,46 @@ class HotUpdaterModule internal constructor(
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.reject("INVALID_MANIFEST", "Manifest URL, hash, and assets are required")
186
+ return@launch
187
+ }
188
+ try {
189
+ java.net.URL(manifestUrl)
190
+ } catch (e: java.net.MalformedURLException) {
191
+ promise.reject("INVALID_FILE_URL", "Invalid 'manifestUrl' provided: $manifestUrl")
192
+ return@launch
193
+ }
194
+ val channel = params.getString("channel")
195
+ val archiveUrl =
196
+ if (params.hasKey("archiveUrl") && !params.isNull("archiveUrl")) {
197
+ params.getString("archiveUrl")
198
+ } else {
199
+ null
200
+ }
201
+ if (archiveUrl != null) {
173
202
  try {
174
- java.net.URL(fileUrl)
203
+ java.net.URL(archiveUrl)
175
204
  } catch (e: java.net.MalformedURLException) {
176
- promise.reject("INVALID_FILE_URL", "Invalid 'fileUrl' provided: $fileUrl")
205
+ promise.reject("INVALID_FILE_URL", "Invalid 'archiveUrl' provided: $archiveUrl")
177
206
  return@launch
178
207
  }
179
208
  }
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
209
  val selection = parseSelection(params)
187
210
 
188
211
  val impl = getInstance()
189
212
 
190
213
  impl.updateBundle(
191
214
  bundleId,
192
- fileUrl,
193
- fileHash,
194
215
  manifestUrl,
195
216
  manifestFileHash,
196
- changedAssets,
217
+ assets,
197
218
  channel,
198
219
  selection,
220
+ archiveUrl,
199
221
  ) { progress ->
200
222
  // Post to Main thread for React Native event emission
201
223
  Handler(Looper.getMainLooper()).post {
@@ -204,8 +226,6 @@ class HotUpdaterModule internal constructor(
204
226
  WritableNativeMap().apply {
205
227
  putDouble("progress", progress.progress)
206
228
  putString("artifactType", progress.artifactType)
207
- progress.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
208
- progress.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
209
229
  progress.details?.let { details ->
210
230
  putMap(
211
231
  "details",
@@ -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
  }
@@ -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 onCatalystInstanceDestroy() {
30
40
  super.onCatalystInstanceDestroy()
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
@@ -170,36 +182,46 @@ class HotUpdaterModule internal constructor(
170
182
  return@launch
171
183
  }
172
184
 
173
- val fileUrl = params.getString("fileUrl")
174
-
175
- // Validate fileUrl format if provided
176
- if (fileUrl != null && fileUrl.isNotEmpty()) {
185
+ val manifestUrl = params.getString("manifestUrl")
186
+ val manifestFileHash = params.getString("manifestFileHash")
187
+ val assets = parseAssets(params)
188
+ if (manifestUrl.isNullOrEmpty() || manifestFileHash.isNullOrEmpty() || assets == null) {
189
+ promise.reject("INVALID_MANIFEST", "Manifest URL, hash, and assets are required")
190
+ return@launch
191
+ }
192
+ try {
193
+ java.net.URL(manifestUrl)
194
+ } catch (e: java.net.MalformedURLException) {
195
+ promise.reject("INVALID_FILE_URL", "Invalid 'manifestUrl' provided: $manifestUrl")
196
+ return@launch
197
+ }
198
+ val channel = params.getString("channel")
199
+ val archiveUrl =
200
+ if (params.hasKey("archiveUrl") && !params.isNull("archiveUrl")) {
201
+ params.getString("archiveUrl")
202
+ } else {
203
+ null
204
+ }
205
+ if (archiveUrl != null) {
177
206
  try {
178
- java.net.URL(fileUrl)
207
+ java.net.URL(archiveUrl)
179
208
  } catch (e: java.net.MalformedURLException) {
180
- promise.reject("INVALID_FILE_URL", "Invalid 'fileUrl' provided: $fileUrl")
209
+ promise.reject("INVALID_FILE_URL", "Invalid 'archiveUrl' provided: $archiveUrl")
181
210
  return@launch
182
211
  }
183
212
  }
184
-
185
- val fileHash = params.getString("fileHash")
186
- val manifestUrl = params.getString("manifestUrl")
187
- val manifestFileHash = params.getString("manifestFileHash")
188
- val changedAssets = parseChangedAssets(params)
189
- val channel = params.getString("channel")
190
213
  val selection = parseSelection(params)
191
214
 
192
215
  val impl = getInstance()
193
216
 
194
217
  impl.updateBundle(
195
218
  bundleId,
196
- fileUrl,
197
- fileHash,
198
219
  manifestUrl,
199
220
  manifestFileHash,
200
- changedAssets,
221
+ assets,
201
222
  channel,
202
223
  selection,
224
+ archiveUrl,
203
225
  ) { progress ->
204
226
  // Post to Main thread for React Native event emission
205
227
  Handler(Looper.getMainLooper()).post {
@@ -208,8 +230,6 @@ class HotUpdaterModule internal constructor(
208
230
  Arguments.createMap().apply {
209
231
  putDouble("progress", progress.progress)
210
232
  putString("artifactType", progress.artifactType)
211
- progress.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
212
- progress.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
213
233
  progress.details?.let { details ->
214
234
  val files = Arguments.createArray()
215
235
  details.files.forEach { file ->