@hot-updater/react-native 1.0.0-rc.3 → 1.0.0-rc.30

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 (300) 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 +124 -69
  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 +16 -27
  50. package/lib/commonjs/fetchJSON.js.map +1 -1
  51. package/lib/commonjs/fetchUpdateResponse.js +54 -0
  52. package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
  53. package/lib/commonjs/httpClient.js +63 -94
  54. package/lib/commonjs/httpClient.js.map +1 -1
  55. package/lib/commonjs/index.js +71 -18
  56. package/lib/commonjs/index.js.map +1 -1
  57. package/lib/commonjs/native.js +78 -60
  58. package/lib/commonjs/native.js.map +1 -1
  59. package/lib/commonjs/pluginHost.js +44 -0
  60. package/lib/commonjs/pluginHost.js.map +1 -0
  61. package/lib/commonjs/releaseCatalogCache.js +36 -87
  62. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  63. package/lib/commonjs/sdkVersion.js +1 -1
  64. package/lib/commonjs/sdkVersion.js.map +1 -1
  65. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  66. package/lib/commonjs/store.js +3 -10
  67. package/lib/commonjs/store.js.map +1 -1
  68. package/lib/commonjs/updateError.js +132 -0
  69. package/lib/commonjs/updateError.js.map +1 -0
  70. package/lib/commonjs/wrap.js +43 -26
  71. package/lib/commonjs/wrap.js.map +1 -1
  72. package/lib/module/appReady.js +103 -0
  73. package/lib/module/appReady.js.map +1 -0
  74. package/lib/module/checkForUpdate.js +117 -64
  75. package/lib/module/checkForUpdate.js.map +1 -1
  76. package/lib/module/clientPlugin.js +4 -0
  77. package/lib/module/clientPlugin.js.map +1 -0
  78. package/lib/module/error.js +4 -13
  79. package/lib/module/error.js.map +1 -1
  80. package/lib/module/fetchJSON.js +14 -26
  81. package/lib/module/fetchJSON.js.map +1 -1
  82. package/lib/module/fetchUpdateResponse.js +49 -0
  83. package/lib/module/fetchUpdateResponse.js.map +1 -0
  84. package/lib/module/httpClient.js +62 -93
  85. package/lib/module/httpClient.js.map +1 -1
  86. package/lib/module/index.js +58 -20
  87. package/lib/module/index.js.map +1 -1
  88. package/lib/module/native.js +71 -53
  89. package/lib/module/native.js.map +1 -1
  90. package/lib/module/pluginHost.js +35 -0
  91. package/lib/module/pluginHost.js.map +1 -0
  92. package/lib/module/releaseCatalogCache.js +32 -82
  93. package/lib/module/releaseCatalogCache.js.map +1 -1
  94. package/lib/module/sdkVersion.js +1 -1
  95. package/lib/module/sdkVersion.js.map +1 -1
  96. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  97. package/lib/module/store.js +3 -10
  98. package/lib/module/store.js.map +1 -1
  99. package/lib/module/updateError.js +126 -0
  100. package/lib/module/updateError.js.map +1 -0
  101. package/lib/module/wrap.js +43 -26
  102. package/lib/module/wrap.js.map +1 -1
  103. package/lib/typescript/commonjs/appReady.d.ts +23 -0
  104. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  106. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  108. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/error.d.ts +4 -13
  110. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
  112. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
  114. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/httpClient.d.ts +4 -33
  116. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/index.d.ts +4 -8
  118. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/native.d.ts +31 -22
  120. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  122. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
  124. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  126. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +42 -47
  128. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/store.d.ts +1 -3
  130. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/types.d.ts +1 -1
  132. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  134. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  136. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  137. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  138. package/lib/typescript/module/appReady.d.ts +23 -0
  139. package/lib/typescript/module/appReady.d.ts.map +1 -0
  140. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  141. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  142. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  143. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  144. package/lib/typescript/module/error.d.ts +4 -13
  145. package/lib/typescript/module/error.d.ts.map +1 -1
  146. package/lib/typescript/module/fetchJSON.d.ts +5 -5
  147. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  148. package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
  149. package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
  150. package/lib/typescript/module/httpClient.d.ts +4 -33
  151. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  152. package/lib/typescript/module/index.d.ts +4 -8
  153. package/lib/typescript/module/index.d.ts.map +1 -1
  154. package/lib/typescript/module/native.d.ts +31 -22
  155. package/lib/typescript/module/native.d.ts.map +1 -1
  156. package/lib/typescript/module/pluginHost.d.ts +4 -0
  157. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  158. package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
  159. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  160. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  161. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  162. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +42 -47
  163. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  164. package/lib/typescript/module/store.d.ts +1 -3
  165. package/lib/typescript/module/store.d.ts.map +1 -1
  166. package/lib/typescript/module/types.d.ts +1 -1
  167. package/lib/typescript/module/types.d.ts.map +1 -1
  168. package/lib/typescript/module/updateError.d.ts +26 -0
  169. package/lib/typescript/module/updateError.d.ts.map +1 -0
  170. package/lib/typescript/module/wrap.d.ts.map +1 -1
  171. package/lib/typescript/module/wrap.types.d.ts +6 -7
  172. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  173. package/package.json +7 -4
  174. package/src/appReady.ts +149 -0
  175. package/src/checkForUpdate.ts +151 -88
  176. package/src/clientPlugin.ts +18 -0
  177. package/src/error.ts +4 -14
  178. package/src/fetchJSON.ts +14 -30
  179. package/src/fetchUpdateResponse.ts +54 -0
  180. package/src/httpClient.ts +88 -159
  181. package/src/index.ts +90 -24
  182. package/src/native.ts +106 -102
  183. package/src/pluginHost.ts +45 -0
  184. package/src/releaseCatalogCache.ts +43 -172
  185. package/src/sdkVersion.ts +1 -1
  186. package/src/specs/NativeHotUpdater.ts +41 -48
  187. package/src/store.ts +2 -23
  188. package/src/types.ts +1 -1
  189. package/src/updateError.ts +153 -0
  190. package/src/wrap.tsx +57 -26
  191. package/src/wrap.types.ts +6 -7
  192. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  193. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  194. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  195. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  196. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  197. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  198. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  199. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  200. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  201. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  202. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  203. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  204. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  205. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  206. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  207. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  208. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  209. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  210. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  211. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  212. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  213. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  214. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  215. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -615
  216. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  217. package/lib/commonjs/httpClient.spec.js +0 -102
  218. package/lib/commonjs/httpClient.spec.js.map +0 -1
  219. package/lib/commonjs/index.spec.js +0 -302
  220. package/lib/commonjs/index.spec.js.map +0 -1
  221. package/lib/commonjs/iosInterop.spec.js +0 -18
  222. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  223. package/lib/commonjs/native.spec.js +0 -883
  224. package/lib/commonjs/native.spec.js.map +0 -1
  225. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  226. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  227. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  228. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  229. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  230. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  231. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  232. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  233. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  234. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  235. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  236. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  237. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  238. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  239. package/lib/commonjs/store.notification.spec.js +0 -76
  240. package/lib/commonjs/store.notification.spec.js.map +0 -1
  241. package/lib/commonjs/store.spec.js +0 -316
  242. package/lib/commonjs/store.spec.js.map +0 -1
  243. package/lib/commonjs/wrap.spec.js +0 -329
  244. package/lib/commonjs/wrap.spec.js.map +0 -1
  245. package/lib/module/catalogCacheNative.spec.js +0 -31
  246. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  247. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -614
  248. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  249. package/lib/module/httpClient.spec.js +0 -102
  250. package/lib/module/httpClient.spec.js.map +0 -1
  251. package/lib/module/index.spec.js +0 -301
  252. package/lib/module/index.spec.js.map +0 -1
  253. package/lib/module/iosInterop.spec.js +0 -18
  254. package/lib/module/iosInterop.spec.js.map +0 -1
  255. package/lib/module/native.spec.js +0 -882
  256. package/lib/module/native.spec.js.map +0 -1
  257. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  258. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  259. package/lib/module/notifyAppReadyInsights.js +0 -128
  260. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  261. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  262. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  263. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  264. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  265. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  266. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  267. package/lib/module/releaseCatalogCache.spec.js +0 -195
  268. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  269. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  270. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  271. package/lib/module/store.notification.spec.js +0 -77
  272. package/lib/module/store.notification.spec.js.map +0 -1
  273. package/lib/module/store.spec.js +0 -315
  274. package/lib/module/store.spec.js.map +0 -1
  275. package/lib/module/wrap.spec.js +0 -330
  276. package/lib/module/wrap.spec.js.map +0 -1
  277. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  278. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  279. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  280. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  281. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  282. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  283. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  284. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  285. package/src/catalogCacheNative.spec.ts +0 -63
  286. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -611
  287. package/src/httpClient.spec.ts +0 -132
  288. package/src/index.spec.ts +0 -337
  289. package/src/iosInterop.spec.ts +0 -30
  290. package/src/native.spec.ts +0 -945
  291. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  292. package/src/notifyAppReadyInsights.spec.ts +0 -243
  293. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  294. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  295. package/src/notifyAppReadyInsights.ts +0 -194
  296. package/src/releaseCatalogCache.spec.ts +0 -282
  297. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  298. package/src/store.notification.spec.ts +0 -92
  299. package/src/store.spec.ts +0 -372
  300. package/src/wrap.spec.ts +0 -406
@@ -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
@@ -166,90 +178,105 @@ class HotUpdaterModule internal constructor(
166
178
  try {
167
179
  val bundleId = params.getString("bundleId")
168
180
  if (bundleId == null || bundleId.isEmpty()) {
169
- promise.reject("MISSING_BUNDLE_ID", "Missing or empty 'bundleId'")
181
+ promise.rejectUpdateBundle(HotUpdaterException.missingBundleId())
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.rejectUpdateBundle(HotUpdaterException.invalidManifestParams())
190
+ return@launch
191
+ }
192
+ try {
193
+ java.net.URL(manifestUrl)
194
+ } catch (e: java.net.MalformedURLException) {
195
+ promise.rejectUpdateBundle(
196
+ HotUpdaterException.invalidFileUrl("manifestUrl", manifestUrl, UpdateFailureResource.MANIFEST),
197
+ )
198
+ return@launch
199
+ }
200
+ val channel = params.getString("channel")
201
+ val archiveUrl =
202
+ if (params.hasKey("archiveUrl") && !params.isNull("archiveUrl")) {
203
+ params.getString("archiveUrl")
204
+ } else {
205
+ null
206
+ }
207
+ if (archiveUrl != null) {
177
208
  try {
178
- java.net.URL(fileUrl)
209
+ java.net.URL(archiveUrl)
179
210
  } catch (e: java.net.MalformedURLException) {
180
- promise.reject("INVALID_FILE_URL", "Invalid 'fileUrl' provided: $fileUrl")
211
+ promise.rejectUpdateBundle(
212
+ HotUpdaterException.invalidFileUrl("archiveUrl", archiveUrl, UpdateFailureResource.ARCHIVE),
213
+ )
181
214
  return@launch
182
215
  }
183
216
  }
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
217
  val selection = parseSelection(params)
191
218
 
192
219
  val impl = getInstance()
193
220
 
194
- impl.updateBundle(
195
- bundleId,
196
- fileUrl,
197
- fileHash,
198
- manifestUrl,
199
- manifestFileHash,
200
- changedAssets,
201
- channel,
202
- selection,
203
- ) { progress ->
204
- // Post to Main thread for React Native event emission
205
- Handler(Looper.getMainLooper()).post {
206
- try {
207
- val progressParams =
208
- Arguments.createMap().apply {
209
- putDouble("progress", progress.progress)
210
- putString("artifactType", progress.artifactType)
211
- progress.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
212
- progress.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
213
- progress.details?.let { details ->
214
- val files = Arguments.createArray()
215
- details.files.forEach { file ->
216
- files.pushMap(
221
+ val result =
222
+ impl.updateBundle(
223
+ bundleId,
224
+ manifestUrl,
225
+ manifestFileHash,
226
+ assets,
227
+ channel,
228
+ selection,
229
+ archiveUrl,
230
+ ) { progress ->
231
+ // Post to Main thread for React Native event emission
232
+ Handler(Looper.getMainLooper()).post {
233
+ try {
234
+ val progressParams =
235
+ Arguments.createMap().apply {
236
+ putDouble("progress", progress.progress)
237
+ putString("artifactType", progress.artifactType)
238
+ progress.details?.let { details ->
239
+ val files = Arguments.createArray()
240
+ details.files.forEach { file ->
241
+ files.pushMap(
242
+ Arguments.createMap().apply {
243
+ putString("path", file.path)
244
+ putString("downloadPath", file.downloadPath)
245
+ putString("status", file.status)
246
+ putDouble("progress", file.progress)
247
+ putInt("order", file.order)
248
+ file.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
249
+ file.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
250
+ },
251
+ )
252
+ }
253
+
254
+ putMap(
255
+ "details",
217
256
  Arguments.createMap().apply {
218
- putString("path", file.path)
219
- putString("downloadPath", file.downloadPath)
220
- putString("status", file.status)
221
- putDouble("progress", file.progress)
222
- putInt("order", file.order)
223
- file.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
224
- file.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
257
+ putInt("totalFilesCount", details.totalFilesCount)
258
+ putInt("completedFilesCount", details.completedFilesCount)
259
+ putArray("files", files)
225
260
  },
226
261
  )
227
262
  }
228
-
229
- putMap(
230
- "details",
231
- Arguments.createMap().apply {
232
- putInt("totalFilesCount", details.totalFilesCount)
233
- putInt("completedFilesCount", details.completedFilesCount)
234
- putArray("files", files)
235
- },
236
- )
237
263
  }
238
- }
239
-
240
- this@HotUpdaterModule
241
- .mReactApplicationContext
242
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
243
- ?.emit("onProgress", progressParams)
244
- } catch (e: Exception) {
245
- Log.w("HotUpdater", "Failed to emit progress (bridge may be unavailable): ${e.message}")
264
+
265
+ this@HotUpdaterModule
266
+ .mReactApplicationContext
267
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
268
+ ?.emit("onProgress", progressParams)
269
+ } catch (e: Exception) {
270
+ Log.w("HotUpdater", "Failed to emit progress (bridge may be unavailable): ${e.message}")
271
+ }
246
272
  }
247
273
  }
248
- }
249
- promise.resolve(true)
274
+ promise.resolve(result.toWritableMap())
250
275
  } catch (e: HotUpdaterException) {
251
- promise.reject(e.code, e.message)
276
+ promise.rejectUpdateBundle(e)
252
277
  } catch (e: Exception) {
278
+ // Not an update failure (a stale Release selection, for example),
279
+ // so the rejection carries no classification.
253
280
  promise.reject("UNKNOWN_ERROR", e.message ?: "An unknown error occurred")
254
281
  }
255
282
  }
@@ -414,17 +441,14 @@ class HotUpdaterModule internal constructor(
414
441
  override fun getInstallId(): String = getInstance().getInstallId()
415
442
 
416
443
  @ReactMethod(isBlockingSynchronousMethod = true)
417
- override fun getUserId(): String? = getInstance().getUserId()
418
-
419
- @ReactMethod(isBlockingSynchronousMethod = true)
420
- override fun getUsername(): String? = getInstance().getUsername()
444
+ override fun getStorageItem(key: String): String? = getInstance().getStorageItem(key)
421
445
 
422
446
  @ReactMethod
423
- override fun setUser(
424
- userId: String?,
425
- username: String?,
447
+ override fun setStorageItem(
448
+ key: String,
449
+ value: String?,
426
450
  ) {
427
- getInstance().setUser(userId, username)
451
+ getInstance().setStorageItem(key, value)
428
452
  }
429
453
 
430
454
  @ReactMethod
@@ -62,13 +62,11 @@ abstract class HotUpdaterSpec internal constructor(
62
62
 
63
63
  abstract fun getInstallId(): String
64
64
 
65
- abstract fun getUserId(): String?
65
+ abstract fun getStorageItem(key: String): String?
66
66
 
67
- abstract fun getUsername(): String?
68
-
69
- abstract fun setUser(
70
- userId: String?,
71
- username: String?,
67
+ abstract fun setStorageItem(
68
+ key: String,
69
+ value: String?,
72
70
  )
73
71
 
74
72
  abstract fun resetChannel(promise: Promise)
@@ -0,0 +1,167 @@
1
+ import Compression
2
+ import Foundation
3
+
4
+ enum BrotliFileDecompressor {
5
+ private static let bufferSize = 64 * 1024
6
+
7
+ static func decompress(
8
+ from sourcePath: String,
9
+ to outputPath: String,
10
+ expectedOutputByteSize: Int64? = nil
11
+ ) throws {
12
+ if let expectedOutputByteSize, expectedOutputByteSize < 0 {
13
+ throw error(6, "Invalid expected Brotli output size")
14
+ }
15
+ let fileManager = FileManager.default
16
+ let parentDirectory = (outputPath as NSString).deletingLastPathComponent
17
+ try fileManager.createDirectory(
18
+ atPath: parentDirectory,
19
+ withIntermediateDirectories: true
20
+ )
21
+ if fileManager.fileExists(atPath: outputPath) {
22
+ try fileManager.removeItem(atPath: outputPath)
23
+ }
24
+ guard fileManager.createFile(atPath: outputPath, contents: nil) else {
25
+ throw error(1, "Failed to create Brotli output file")
26
+ }
27
+
28
+ let input = try FileHandle(forReadingFrom: URL(fileURLWithPath: sourcePath))
29
+ let output = try FileHandle(forWritingTo: URL(fileURLWithPath: outputPath))
30
+ defer {
31
+ try? input.close()
32
+ try? output.close()
33
+ }
34
+
35
+ var stream = compression_stream(
36
+ dst_ptr: UnsafeMutablePointer<UInt8>(bitPattern: 1)!,
37
+ dst_size: 0,
38
+ src_ptr: UnsafePointer<UInt8>(bitPattern: 1)!,
39
+ src_size: 0,
40
+ state: nil
41
+ )
42
+ guard compression_stream_init(
43
+ &stream,
44
+ COMPRESSION_STREAM_DECODE,
45
+ COMPRESSION_BROTLI
46
+ ) == COMPRESSION_STATUS_OK else {
47
+ throw error(2, "Failed to initialize Brotli decompression")
48
+ }
49
+ defer { compression_stream_destroy(&stream) }
50
+
51
+ let outputBuffer = UnsafeMutablePointer<UInt8>.allocate(
52
+ capacity: bufferSize
53
+ )
54
+ defer { outputBuffer.deallocate() }
55
+
56
+ var reachedEnd = false
57
+ var totalOutputByteSize: UInt64 = 0
58
+ let maximumOutputByteSize = expectedOutputByteSize.map(UInt64.init)
59
+ while !reachedEnd {
60
+ let chunk = try FileUtilities.readUpToCount(
61
+ from: input,
62
+ count: bufferSize
63
+ ) ?? Data()
64
+ let status: compression_status
65
+ if chunk.isEmpty {
66
+ stream.src_ptr = UnsafePointer<UInt8>(bitPattern: 1)!
67
+ stream.src_size = 0
68
+ status = try process(
69
+ &stream,
70
+ output: output,
71
+ outputBuffer: outputBuffer,
72
+ finalize: true,
73
+ totalOutputByteSize: &totalOutputByteSize,
74
+ maximumOutputByteSize: maximumOutputByteSize
75
+ )
76
+ } else {
77
+ status = try chunk.withUnsafeBytes { bytes in
78
+ guard let source = bytes.baseAddress?
79
+ .assumingMemoryBound(to: UInt8.self) else {
80
+ return COMPRESSION_STATUS_OK
81
+ }
82
+ stream.src_ptr = source
83
+ stream.src_size = chunk.count
84
+ return try process(
85
+ &stream,
86
+ output: output,
87
+ outputBuffer: outputBuffer,
88
+ finalize: false,
89
+ totalOutputByteSize: &totalOutputByteSize,
90
+ maximumOutputByteSize: maximumOutputByteSize
91
+ )
92
+ }
93
+ }
94
+
95
+ if status == COMPRESSION_STATUS_END {
96
+ guard stream.src_size == 0 else {
97
+ throw error(7, "Brotli stream contains trailing compressed bytes")
98
+ }
99
+ let trailing = try FileUtilities.readUpToCount(from: input, count: 1)
100
+ guard trailing?.isEmpty != false else {
101
+ throw error(7, "Brotli stream contains trailing compressed bytes")
102
+ }
103
+ reachedEnd = true
104
+ } else if chunk.isEmpty {
105
+ throw error(3, "Brotli input ended before the stream completed")
106
+ }
107
+ }
108
+
109
+ if let maximumOutputByteSize,
110
+ totalOutputByteSize != maximumOutputByteSize {
111
+ throw error(
112
+ 8,
113
+ "Brotli output size mismatch: expected \(maximumOutputByteSize), got \(totalOutputByteSize)"
114
+ )
115
+ }
116
+ }
117
+
118
+ private static func process(
119
+ _ stream: inout compression_stream,
120
+ output: FileHandle,
121
+ outputBuffer: UnsafeMutablePointer<UInt8>,
122
+ finalize: Bool,
123
+ totalOutputByteSize: inout UInt64,
124
+ maximumOutputByteSize: UInt64?
125
+ ) throws -> compression_status {
126
+ let flags = finalize ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0
127
+ var status = COMPRESSION_STATUS_OK
128
+ repeat {
129
+ let previousSourceSize = stream.src_size
130
+ stream.dst_ptr = outputBuffer
131
+ stream.dst_size = bufferSize
132
+ status = compression_stream_process(&stream, flags)
133
+
134
+ guard status == COMPRESSION_STATUS_OK ||
135
+ status == COMPRESSION_STATUS_END else {
136
+ throw error(4, "Brotli decompression failed")
137
+ }
138
+ let produced = bufferSize - stream.dst_size
139
+ if produced > 0 {
140
+ let (nextOutputByteSize, overflowed) = totalOutputByteSize
141
+ .addingReportingOverflow(UInt64(produced))
142
+ guard !overflowed,
143
+ maximumOutputByteSize.map({ nextOutputByteSize <= $0 }) ?? true else {
144
+ throw error(6, "Brotli output exceeds expected size")
145
+ }
146
+ output.write(Data(bytes: outputBuffer, count: produced))
147
+ totalOutputByteSize = nextOutputByteSize
148
+ }
149
+ if finalize,
150
+ status == COMPRESSION_STATUS_OK,
151
+ produced == 0,
152
+ previousSourceSize == stream.src_size {
153
+ throw error(5, "Brotli decompression stalled")
154
+ }
155
+ } while stream.src_size > 0 || stream.dst_size == 0 ||
156
+ (finalize && status == COMPRESSION_STATUS_OK)
157
+ return status
158
+ }
159
+
160
+ private static func error(_ code: Int, _ message: String) -> NSError {
161
+ NSError(
162
+ domain: "BrotliFileDecompressor",
163
+ code: code,
164
+ userInfo: [NSLocalizedDescriptionKey: message]
165
+ )
166
+ }
167
+ }
@@ -0,0 +1,196 @@
1
+ import Foundation
2
+
3
+ protocol BuiltInAssetResolver: AnyObject {
4
+ func use(bundle: Bundle)
5
+ func copyIfMatches(assetPath: String, expectedHash: String, destination: String) -> Bool
6
+ }
7
+
8
+ final class IOSBuiltInAssetResolver: BuiltInAssetResolver {
9
+ private struct CachedIndex: Codable {
10
+ let schemaVersion: Int
11
+ let packageIdentity: String
12
+ var locators: [String: String]
13
+ }
14
+
15
+ private let cacheURL: URL
16
+ private let lock = NSLock()
17
+ private var bundle: Bundle = .main
18
+
19
+ init(cacheURL: URL) {
20
+ self.cacheURL = cacheURL
21
+ }
22
+
23
+ func use(bundle: Bundle) {
24
+ lock.lock()
25
+ self.bundle = bundle
26
+ lock.unlock()
27
+ }
28
+
29
+ func copyIfMatches(
30
+ assetPath: String,
31
+ expectedHash: String,
32
+ destination: String
33
+ ) -> Bool {
34
+ lock.lock()
35
+ defer { lock.unlock() }
36
+
37
+ guard FileUtilities.normalizedRelativePath(from: assetPath) == assetPath else {
38
+ return false
39
+ }
40
+ let packageIdentity = packageIdentity(for: bundle)
41
+ var index = readIndex(packageIdentity: packageIdentity)
42
+
43
+ if let cachedLocator = index.locators[assetPath],
44
+ copyLocatorIfMatches(
45
+ cachedLocator,
46
+ expectedHash: expectedHash,
47
+ destination: destination
48
+ ) {
49
+ return true
50
+ }
51
+
52
+ guard let locator = resolveLocator(assetPath: assetPath),
53
+ copyLocatorIfMatches(
54
+ locator,
55
+ expectedHash: expectedHash,
56
+ destination: destination
57
+ ) else {
58
+ return false
59
+ }
60
+
61
+ index.locators[assetPath] = locator
62
+ writeIndex(index)
63
+ return true
64
+ }
65
+
66
+ private func resolveLocator(assetPath: String) -> String? {
67
+ if assetPath == "index.ios.bundle" {
68
+ guard let bundleURL = bundle.url(forResource: "main", withExtension: "jsbundle"),
69
+ let relativePath = relativePath(of: bundleURL, inside: bundle.bundleURL) else {
70
+ return nil
71
+ }
72
+ return "bundle:\(relativePath)"
73
+ }
74
+
75
+ guard let resourceURL = bundle.resourceURL else { return nil }
76
+ let sourceURL = resourceURL.appendingPathComponent(assetPath).standardizedFileURL
77
+ guard relativePath(of: sourceURL, inside: resourceURL) == assetPath,
78
+ isRegularFile(sourceURL) else {
79
+ return nil
80
+ }
81
+ return "resource:\(assetPath)"
82
+ }
83
+
84
+ private func copyLocatorIfMatches(
85
+ _ locator: String,
86
+ expectedHash: String,
87
+ destination: String
88
+ ) -> Bool {
89
+ guard let sourceURL = sourceURL(for: locator), isRegularFile(sourceURL) else {
90
+ return false
91
+ }
92
+ let destinationURL = URL(fileURLWithPath: destination)
93
+ let temporaryURL = destinationURL
94
+ .deletingLastPathComponent()
95
+ .appendingPathComponent("\(destinationURL.lastPathComponent).builtin.tmp")
96
+ do {
97
+ try FileManager.default.createDirectory(
98
+ at: destinationURL.deletingLastPathComponent(),
99
+ withIntermediateDirectories: true
100
+ )
101
+ try? FileManager.default.removeItem(at: temporaryURL)
102
+ try FileManager.default.copyItem(at: sourceURL, to: temporaryURL)
103
+ guard HashUtils.verifyHash(fileURL: temporaryURL, expectedHash: expectedHash) else {
104
+ try? FileManager.default.removeItem(at: temporaryURL)
105
+ return false
106
+ }
107
+ try? FileManager.default.removeItem(at: destinationURL)
108
+ try FileManager.default.moveItem(at: temporaryURL, to: destinationURL)
109
+ return true
110
+ } catch {
111
+ try? FileManager.default.removeItem(at: temporaryURL)
112
+ return false
113
+ }
114
+ }
115
+
116
+ private func sourceURL(for locator: String) -> URL? {
117
+ if locator.hasPrefix("bundle:") {
118
+ let relativePath = String(locator.dropFirst("bundle:".count))
119
+ let sourceURL = bundle.bundleURL.appendingPathComponent(relativePath).standardizedFileURL
120
+ return self.relativePath(of: sourceURL, inside: bundle.bundleURL) == relativePath
121
+ ? sourceURL
122
+ : nil
123
+ }
124
+ if locator.hasPrefix("resource:"), let resourceURL = bundle.resourceURL {
125
+ let relativePath = String(locator.dropFirst("resource:".count))
126
+ let sourceURL = resourceURL.appendingPathComponent(relativePath).standardizedFileURL
127
+ return self.relativePath(of: sourceURL, inside: resourceURL) == relativePath
128
+ ? sourceURL
129
+ : nil
130
+ }
131
+ return nil
132
+ }
133
+
134
+ private func relativePath(of fileURL: URL, inside directoryURL: URL) -> String? {
135
+ let directory = directoryURL.standardizedFileURL.resolvingSymlinksInPath().path
136
+ let file = fileURL.standardizedFileURL.resolvingSymlinksInPath().path
137
+ let prefix = directory.hasSuffix("/") ? directory : "\(directory)/"
138
+ guard file.hasPrefix(prefix) else { return nil }
139
+ return String(file.dropFirst(prefix.count))
140
+ }
141
+
142
+ private func isRegularFile(_ url: URL) -> Bool {
143
+ (try? url.resourceValues(forKeys: [.isRegularFileKey]).isRegularFile) == true
144
+ }
145
+
146
+ private func packageIdentity(for bundle: Bundle) -> String {
147
+ let values = try? bundle.bundleURL.resourceValues(forKeys: [.contentModificationDateKey, .fileSizeKey])
148
+ let bundleIdentifier = bundle.bundleIdentifier ?? ""
149
+ let shortVersion = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""
150
+ let bundleVersion = bundle.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? ""
151
+ let bundlePath = bundle.bundleURL.standardizedFileURL.path
152
+ let fileSize = String(values?.fileSize ?? -1)
153
+ let modificationTime = String(values?.contentModificationDate?.timeIntervalSince1970 ?? -1)
154
+
155
+ return [
156
+ bundleIdentifier,
157
+ shortVersion,
158
+ bundleVersion,
159
+ bundlePath,
160
+ fileSize,
161
+ modificationTime,
162
+ ].joined(separator: "|")
163
+ }
164
+
165
+ private func readIndex(packageIdentity: String) -> CachedIndex {
166
+ if let data = try? Data(contentsOf: cacheURL),
167
+ let index = try? JSONDecoder().decode(CachedIndex.self, from: data),
168
+ index.schemaVersion == Self.schemaVersion,
169
+ index.packageIdentity == packageIdentity {
170
+ return index
171
+ }
172
+ return CachedIndex(
173
+ schemaVersion: Self.schemaVersion,
174
+ packageIdentity: packageIdentity,
175
+ locators: [:]
176
+ )
177
+ }
178
+
179
+ private func writeIndex(_ index: CachedIndex) {
180
+ do {
181
+ try FileManager.default.createDirectory(
182
+ at: cacheURL.deletingLastPathComponent(),
183
+ withIntermediateDirectories: true
184
+ )
185
+ let temporaryURL = cacheURL.appendingPathExtension("tmp")
186
+ let data = try JSONEncoder().encode(index)
187
+ try data.write(to: temporaryURL, options: .atomic)
188
+ try? FileManager.default.removeItem(at: cacheURL)
189
+ try FileManager.default.moveItem(at: temporaryURL, to: cacheURL)
190
+ } catch {
191
+ // Cache persistence must never block a valid install.
192
+ }
193
+ }
194
+
195
+ private static let schemaVersion = 1
196
+ }