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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/android/build.gradle +13 -2
  2. package/android/proguard-rules.pro +8 -2
  3. package/android/src/bridge/BridgeReactInstance.kt +38 -0
  4. package/android/src/bridgeless/BridgeReactInstance.kt +24 -0
  5. package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +94 -0
  6. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  7. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1064 -844
  8. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +50 -11
  9. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  10. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  11. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +46 -30
  12. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +75 -192
  13. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  14. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  15. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  16. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  17. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  18. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  19. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  20. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  21. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  22. package/android/src/newarch/HotUpdater.kt +7 -9
  23. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  24. package/android/src/newarch/JavaScriptExceptionHooks.kt +48 -0
  25. package/android/src/newarch/ReactIntegrationManager.kt +6 -32
  26. package/android/src/oldarch/HotUpdater.kt +7 -9
  27. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  28. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  29. package/android/src/oldarch/JavaScriptExceptionHooks.kt +71 -0
  30. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  31. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  32. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1032 -1116
  33. package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -54
  34. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  35. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  36. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  37. package/ios/HotUpdater/Internal/HotUpdater.mm +32 -67
  38. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +229 -73
  39. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  40. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  41. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  42. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  43. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  44. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  45. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  46. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  47. package/lib/commonjs/appReady.js +102 -0
  48. package/lib/commonjs/appReady.js.map +1 -0
  49. package/lib/commonjs/checkForUpdate.js +120 -68
  50. package/lib/commonjs/checkForUpdate.js.map +1 -1
  51. package/lib/commonjs/clientPlugin.js +13 -0
  52. package/lib/commonjs/clientPlugin.js.map +1 -0
  53. package/lib/commonjs/error.js +4 -13
  54. package/lib/commonjs/error.js.map +1 -1
  55. package/lib/commonjs/fetchJSON.js +16 -27
  56. package/lib/commonjs/fetchJSON.js.map +1 -1
  57. package/lib/commonjs/fetchUpdateResponse.js +60 -0
  58. package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
  59. package/lib/commonjs/httpClient.js +63 -94
  60. package/lib/commonjs/httpClient.js.map +1 -1
  61. package/lib/commonjs/index.js +71 -18
  62. package/lib/commonjs/index.js.map +1 -1
  63. package/lib/commonjs/native.js +74 -60
  64. package/lib/commonjs/native.js.map +1 -1
  65. package/lib/commonjs/pluginHost.js +45 -0
  66. package/lib/commonjs/pluginHost.js.map +1 -0
  67. package/lib/commonjs/releaseCatalogCache.js +36 -87
  68. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  69. package/lib/commonjs/sdkVersion.js +1 -1
  70. package/lib/commonjs/sdkVersion.js.map +1 -1
  71. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  72. package/lib/commonjs/store.js +3 -10
  73. package/lib/commonjs/store.js.map +1 -1
  74. package/lib/commonjs/updateError.js +145 -0
  75. package/lib/commonjs/updateError.js.map +1 -0
  76. package/lib/commonjs/wrap.js +43 -26
  77. package/lib/commonjs/wrap.js.map +1 -1
  78. package/lib/module/appReady.js +95 -0
  79. package/lib/module/appReady.js.map +1 -0
  80. package/lib/module/checkForUpdate.js +113 -63
  81. package/lib/module/checkForUpdate.js.map +1 -1
  82. package/lib/module/clientPlugin.js +4 -0
  83. package/lib/module/clientPlugin.js.map +1 -0
  84. package/lib/module/error.js +4 -13
  85. package/lib/module/error.js.map +1 -1
  86. package/lib/module/fetchJSON.js +14 -26
  87. package/lib/module/fetchJSON.js.map +1 -1
  88. package/lib/module/fetchUpdateResponse.js +55 -0
  89. package/lib/module/fetchUpdateResponse.js.map +1 -0
  90. package/lib/module/httpClient.js +62 -93
  91. package/lib/module/httpClient.js.map +1 -1
  92. package/lib/module/index.js +58 -20
  93. package/lib/module/index.js.map +1 -1
  94. package/lib/module/native.js +67 -53
  95. package/lib/module/native.js.map +1 -1
  96. package/lib/module/pluginHost.js +36 -0
  97. package/lib/module/pluginHost.js.map +1 -0
  98. package/lib/module/releaseCatalogCache.js +32 -82
  99. package/lib/module/releaseCatalogCache.js.map +1 -1
  100. package/lib/module/sdkVersion.js +1 -1
  101. package/lib/module/sdkVersion.js.map +1 -1
  102. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  103. package/lib/module/store.js +3 -10
  104. package/lib/module/store.js.map +1 -1
  105. package/lib/module/updateError.js +139 -0
  106. package/lib/module/updateError.js.map +1 -0
  107. package/lib/module/wrap.js +43 -26
  108. package/lib/module/wrap.js.map +1 -1
  109. package/lib/typescript/commonjs/appReady.d.ts +21 -0
  110. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  112. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  114. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/error.d.ts +4 -13
  116. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
  118. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
  120. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
  121. package/lib/typescript/commonjs/httpClient.d.ts +4 -33
  122. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/index.d.ts +4 -8
  124. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/native.d.ts +29 -22
  126. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  128. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
  130. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  132. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +35 -47
  134. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  135. package/lib/typescript/commonjs/store.d.ts +1 -3
  136. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/types.d.ts +1 -1
  138. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  140. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  142. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  143. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  144. package/lib/typescript/module/appReady.d.ts +21 -0
  145. package/lib/typescript/module/appReady.d.ts.map +1 -0
  146. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  147. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  148. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  149. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  150. package/lib/typescript/module/error.d.ts +4 -13
  151. package/lib/typescript/module/error.d.ts.map +1 -1
  152. package/lib/typescript/module/fetchJSON.d.ts +5 -5
  153. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  154. package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
  155. package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
  156. package/lib/typescript/module/httpClient.d.ts +4 -33
  157. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  158. package/lib/typescript/module/index.d.ts +4 -8
  159. package/lib/typescript/module/index.d.ts.map +1 -1
  160. package/lib/typescript/module/native.d.ts +29 -22
  161. package/lib/typescript/module/native.d.ts.map +1 -1
  162. package/lib/typescript/module/pluginHost.d.ts +4 -0
  163. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  164. package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
  165. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  166. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  167. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  168. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +35 -47
  169. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  170. package/lib/typescript/module/store.d.ts +1 -3
  171. package/lib/typescript/module/store.d.ts.map +1 -1
  172. package/lib/typescript/module/types.d.ts +1 -1
  173. package/lib/typescript/module/types.d.ts.map +1 -1
  174. package/lib/typescript/module/updateError.d.ts +26 -0
  175. package/lib/typescript/module/updateError.d.ts.map +1 -0
  176. package/lib/typescript/module/wrap.d.ts.map +1 -1
  177. package/lib/typescript/module/wrap.types.d.ts +6 -7
  178. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  179. package/package.json +7 -4
  180. package/src/appReady.ts +141 -0
  181. package/src/checkForUpdate.ts +145 -86
  182. package/src/clientPlugin.ts +18 -0
  183. package/src/error.ts +4 -14
  184. package/src/fetchJSON.ts +14 -30
  185. package/src/fetchUpdateResponse.ts +60 -0
  186. package/src/httpClient.ts +88 -159
  187. package/src/index.ts +90 -24
  188. package/src/native.ts +98 -102
  189. package/src/pluginHost.ts +47 -0
  190. package/src/releaseCatalogCache.ts +43 -172
  191. package/src/sdkVersion.ts +1 -1
  192. package/src/specs/NativeHotUpdater.ts +34 -48
  193. package/src/store.ts +2 -23
  194. package/src/types.ts +1 -1
  195. package/src/updateError.ts +167 -0
  196. package/src/wrap.tsx +57 -26
  197. package/src/wrap.types.ts +6 -7
  198. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  199. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  200. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  201. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  202. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  203. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  204. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  205. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  206. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  207. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  208. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  209. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  210. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  211. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  212. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  213. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  214. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  215. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  216. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  217. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  218. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  219. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  220. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  221. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -616
  222. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  223. package/lib/commonjs/httpClient.spec.js +0 -102
  224. package/lib/commonjs/httpClient.spec.js.map +0 -1
  225. package/lib/commonjs/index.spec.js +0 -302
  226. package/lib/commonjs/index.spec.js.map +0 -1
  227. package/lib/commonjs/iosInterop.spec.js +0 -18
  228. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  229. package/lib/commonjs/native.spec.js +0 -883
  230. package/lib/commonjs/native.spec.js.map +0 -1
  231. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  232. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  233. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  234. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  235. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  236. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  237. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  238. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  239. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  240. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  241. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  242. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  243. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  244. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  245. package/lib/commonjs/store.notification.spec.js +0 -76
  246. package/lib/commonjs/store.notification.spec.js.map +0 -1
  247. package/lib/commonjs/store.spec.js +0 -316
  248. package/lib/commonjs/store.spec.js.map +0 -1
  249. package/lib/commonjs/wrap.spec.js +0 -329
  250. package/lib/commonjs/wrap.spec.js.map +0 -1
  251. package/lib/module/catalogCacheNative.spec.js +0 -31
  252. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  253. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -615
  254. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  255. package/lib/module/httpClient.spec.js +0 -102
  256. package/lib/module/httpClient.spec.js.map +0 -1
  257. package/lib/module/index.spec.js +0 -301
  258. package/lib/module/index.spec.js.map +0 -1
  259. package/lib/module/iosInterop.spec.js +0 -18
  260. package/lib/module/iosInterop.spec.js.map +0 -1
  261. package/lib/module/native.spec.js +0 -882
  262. package/lib/module/native.spec.js.map +0 -1
  263. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  264. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  265. package/lib/module/notifyAppReadyInsights.js +0 -128
  266. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  267. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  268. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  269. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  270. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  271. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  272. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  273. package/lib/module/releaseCatalogCache.spec.js +0 -195
  274. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  275. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  276. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  277. package/lib/module/store.notification.spec.js +0 -77
  278. package/lib/module/store.notification.spec.js.map +0 -1
  279. package/lib/module/store.spec.js +0 -315
  280. package/lib/module/store.spec.js.map +0 -1
  281. package/lib/module/wrap.spec.js +0 -330
  282. package/lib/module/wrap.spec.js.map +0 -1
  283. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  284. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  285. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  286. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  287. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  288. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  289. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  290. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  291. package/src/catalogCacheNative.spec.ts +0 -63
  292. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -612
  293. package/src/httpClient.spec.ts +0 -132
  294. package/src/index.spec.ts +0 -337
  295. package/src/iosInterop.spec.ts +0 -30
  296. package/src/native.spec.ts +0 -945
  297. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  298. package/src/notifyAppReadyInsights.spec.ts +0 -243
  299. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  300. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  301. package/src/notifyAppReadyInsights.ts +0 -194
  302. package/src/releaseCatalogCache.spec.ts +0 -282
  303. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  304. package/src/store.notification.spec.ts +0 -92
  305. package/src/store.spec.ts +0 -372
  306. package/src/wrap.spec.ts +0 -406
@@ -1,273 +0,0 @@
1
- package com.hotupdater
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Assert.assertNull
5
- import org.junit.Assert.assertTrue
6
- import org.junit.Test
7
-
8
- class HotUpdaterImplTest {
9
- @Test
10
- fun `getBundleId returns built in while native launch selection is missing`() {
11
- val impl =
12
- createImpl(
13
- storageBundleId = "staged-bundle",
14
- storageManifest = mapOf("bundleId" to "staged-bundle"),
15
- storageBaseURL = "file:///bundle-store/staged-bundle",
16
- )
17
-
18
- assertNull(impl.getBundleId())
19
- assertTrue(impl.getManifest().isEmpty())
20
- assertNull(impl.getBaseURL())
21
- }
22
-
23
- @Test
24
- fun `getBundleId returns current launched bundle over staged metadata`() {
25
- val impl = createImpl(storageBundleId = "staged-bundle")
26
-
27
- setCurrentLaunchSelection(
28
- impl,
29
- LaunchSelection(
30
- bundleUrl = "file:///bundle-store/launched-bundle/index.android.bundle",
31
- launchedBundleId = "launched-bundle",
32
- shouldRollbackOnCrash = false,
33
- ),
34
- )
35
-
36
- assertEquals("launched-bundle", impl.getBundleId())
37
- }
38
-
39
- @Test
40
- fun `getBundleId returns null for built in launch even when staged metadata exists`() {
41
- val impl = createImpl(storageBundleId = "staged-bundle")
42
-
43
- setCurrentLaunchSelection(
44
- impl,
45
- LaunchSelection(
46
- bundleUrl = "assets://index.android.bundle",
47
- launchedBundleId = null,
48
- shouldRollbackOnCrash = false,
49
- ),
50
- )
51
-
52
- assertNull(impl.getBundleId())
53
- }
54
-
55
- @Test
56
- fun `getManifest and getBaseURL return current launched bundle over staged metadata`() {
57
- val impl =
58
- createImpl(
59
- storageBundleId = "staged-bundle",
60
- storageManifest = mapOf("bundleId" to "staged-bundle"),
61
- storageBaseURL = "file:///bundle-store/staged-bundle",
62
- launchedBundleManifests =
63
- mapOf(
64
- "launched-bundle" to mapOf("bundleId" to "launched-bundle"),
65
- ),
66
- launchedBundleBaseURLs =
67
- mapOf(
68
- "launched-bundle" to "file:///bundle-store/launched-bundle",
69
- ),
70
- )
71
-
72
- setCurrentLaunchSelection(
73
- impl,
74
- LaunchSelection(
75
- bundleUrl = "file:///bundle-store/launched-bundle/index.android.bundle",
76
- launchedBundleId = "launched-bundle",
77
- shouldRollbackOnCrash = false,
78
- ),
79
- )
80
-
81
- assertEquals(
82
- mapOf("bundleId" to "launched-bundle"),
83
- impl.getManifest(),
84
- )
85
- assertEquals("file:///bundle-store/launched-bundle", impl.getBaseURL())
86
- }
87
-
88
- @Test
89
- fun `getManifest and getBaseURL return built in values for built in launch`() {
90
- val impl =
91
- createImpl(
92
- storageBundleId = "staged-bundle",
93
- storageManifest = mapOf("bundleId" to "staged-bundle"),
94
- storageBaseURL = "file:///bundle-store/staged-bundle",
95
- )
96
-
97
- setCurrentLaunchSelection(
98
- impl,
99
- LaunchSelection(
100
- bundleUrl = "assets://index.android.bundle",
101
- launchedBundleId = null,
102
- shouldRollbackOnCrash = false,
103
- ),
104
- )
105
-
106
- assertTrue(impl.getManifest().isEmpty())
107
- assertNull(impl.getBaseURL())
108
- }
109
-
110
- @Test
111
- fun `notifyAppReady reads the report without completing the current launch`() {
112
- val storage = FakeBundleStorageService(bundleId = null)
113
- val impl = allocateWithoutConstructor<HotUpdaterImpl>()
114
- setField(impl, "bundleStorage", storage)
115
- setCurrentLaunchSelection(
116
- impl,
117
- LaunchSelection(
118
- bundleUrl = "file:///bundle-store/launched-bundle/index.android.bundle",
119
- launchedBundleId = "launched-bundle",
120
- shouldRollbackOnCrash = false,
121
- ),
122
- )
123
-
124
- assertEquals(mapOf("status" to "UNCHANGED"), impl.notifyAppReady())
125
- assertNull(storage.lastCompletedBundleId)
126
- }
127
-
128
- @Test
129
- fun `getInstallId and setUser delegate to bundle storage`() {
130
- val storage = FakeBundleStorageService(bundleId = null)
131
- val impl = allocateWithoutConstructor<HotUpdaterImpl>()
132
- setField(impl, "bundleStorage", storage)
133
-
134
- assertEquals("test-install-id", impl.getInstallId())
135
-
136
- impl.setUser("user-123", "alice")
137
- assertEquals("user-123", storage.lastUserId)
138
- assertEquals("alice", storage.lastUsername)
139
-
140
- impl.setUser(null, null)
141
- assertNull(storage.lastUserId)
142
- assertNull(storage.lastUsername)
143
- }
144
-
145
- private fun createImpl(
146
- storageBundleId: String?,
147
- storageManifest: Map<String, Any?> = emptyMap(),
148
- storageBaseURL: String = "",
149
- launchedBundleManifests: Map<String, Map<String, Any?>> = emptyMap(),
150
- launchedBundleBaseURLs: Map<String, String> = emptyMap(),
151
- ): HotUpdaterImpl =
152
- allocateWithoutConstructor<HotUpdaterImpl>().also { impl ->
153
- setField(
154
- impl,
155
- "bundleStorage",
156
- FakeBundleStorageService(
157
- bundleId = storageBundleId,
158
- manifest = storageManifest,
159
- baseURL = storageBaseURL,
160
- launchedBundleManifests = launchedBundleManifests,
161
- launchedBundleBaseURLs = launchedBundleBaseURLs,
162
- ),
163
- )
164
- }
165
-
166
- private fun setCurrentLaunchSelection(
167
- impl: HotUpdaterImpl,
168
- selection: LaunchSelection,
169
- ) {
170
- setField(impl, "currentLaunchSelection", selection)
171
- }
172
-
173
- private fun setField(
174
- target: Any,
175
- fieldName: String,
176
- value: Any?,
177
- ) {
178
- val field = HotUpdaterImpl::class.java.getDeclaredField(fieldName)
179
- field.isAccessible = true
180
- field.set(target, value)
181
- }
182
-
183
- private inline fun <reified T> allocateWithoutConstructor(): T {
184
- val field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe")
185
- field.isAccessible = true
186
- val unsafe = field.get(null)
187
- val allocateInstance = unsafe.javaClass.getMethod("allocateInstance", Class::class.java)
188
- @Suppress("UNCHECKED_CAST")
189
- return allocateInstance.invoke(unsafe, T::class.java) as T
190
- }
191
-
192
- private class FakeBundleStorageService(
193
- private val bundleId: String?,
194
- private val manifest: Map<String, Any?> = emptyMap(),
195
- private val baseURL: String = "",
196
- private val launchedBundleManifests: Map<String, Map<String, Any?>> =
197
- emptyMap(),
198
- private val launchedBundleBaseURLs: Map<String, String> = emptyMap(),
199
- ) : BundleStorageService {
200
- var lastCompletedBundleId: String? = null
201
- var lastUserId: String? = null
202
- var lastUsername: String? = null
203
-
204
- override fun setBundleURL(localPath: String?): Boolean = true
205
-
206
- override fun getCachedBundleURL(): String? = null
207
-
208
- override fun getFallbackBundleURL(): String = "assets://index.android.bundle"
209
-
210
- override fun prepareLaunch(pendingRecovery: PendingCrashRecovery?): LaunchSelection =
211
- LaunchSelection(
212
- bundleUrl = "assets://index.android.bundle",
213
- launchedBundleId = null,
214
- shouldRollbackOnCrash = false,
215
- )
216
-
217
- override suspend fun updateBundle(
218
- bundleId: String,
219
- fileUrl: String?,
220
- fileHash: String?,
221
- manifestUrl: String?,
222
- manifestFileHash: String?,
223
- changedAssets: Map<String, ChangedAssetDescriptor>?,
224
- progressCallback: (UpdateProgressPayload) -> Unit,
225
- ) = Unit
226
-
227
- override fun markLaunchCompleted(currentBundleId: String?) {
228
- lastCompletedBundleId = currentBundleId
229
- }
230
-
231
- override fun notifyAppReady(): Map<String, Any?> = mapOf("status" to "UNCHANGED")
232
-
233
- override fun getInstallId(): String = "test-install-id"
234
-
235
- override fun getUserId(): String? = lastUserId
236
-
237
- override fun getUsername(): String? = lastUsername
238
-
239
- override fun setUser(
240
- userId: String?,
241
- username: String?,
242
- ) {
243
- lastUserId = userId
244
- lastUsername = username
245
- }
246
-
247
- override fun getCrashHistory(): CrashedHistory = CrashedHistory()
248
-
249
- override fun clearCrashHistory(): Boolean = true
250
-
251
- override fun getBaseURL(): String = baseURL
252
-
253
- override fun getBaseURLForBundle(bundleId: String?): String =
254
- if (bundleId == null) {
255
- ""
256
- } else {
257
- launchedBundleBaseURLs[bundleId] ?: ""
258
- }
259
-
260
- override fun getBundleId(): String? = bundleId
261
-
262
- override fun getManifest(): Map<String, Any?> = manifest
263
-
264
- override fun getManifestForBundle(bundleId: String?): Map<String, Any?> =
265
- if (bundleId == null) {
266
- emptyMap()
267
- } else {
268
- launchedBundleManifests[bundleId] ?: emptyMap()
269
- }
270
-
271
- override suspend fun resetChannel(): Boolean = true
272
- }
273
- }
@@ -1,20 +0,0 @@
1
- package com.hotupdater
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Assert.assertNull
5
- import org.junit.Test
6
-
7
- class NativeConfigUtilsTest {
8
- @Test
9
- fun `coerces typed manifest metadata to strings`() {
10
- assertEquals("123", NativeConfigUtils.coerceManifestMetaDataString(123))
11
- assertEquals("true", NativeConfigUtils.coerceManifestMetaDataString(true))
12
- assertEquals("-16711936", NativeConfigUtils.coerceManifestMetaDataString(-16711936))
13
- }
14
-
15
- @Test
16
- fun `ignores missing and empty manifest metadata`() {
17
- assertNull(NativeConfigUtils.coerceManifestMetaDataString(null))
18
- assertNull(NativeConfigUtils.coerceManifestMetaDataString(""))
19
- }
20
- }
@@ -1,111 +0,0 @@
1
- package com.hotupdater
2
-
3
- import kotlinx.coroutines.runBlocking
4
- import org.junit.Assert.assertArrayEquals
5
- import org.junit.Assert.assertEquals
6
- import org.junit.Assert.assertNull
7
- import org.junit.Assert.assertTrue
8
- import org.junit.Rule
9
- import org.junit.Test
10
- import org.junit.rules.TemporaryFolder
11
- import java.io.File
12
- import java.net.InetAddress
13
- import java.net.ServerSocket
14
- import java.net.Socket
15
- import java.net.URL
16
- import kotlin.concurrent.thread
17
-
18
- class OkHttpDownloadServiceTest {
19
- @get:Rule
20
- val temporaryFolder = TemporaryFolder()
21
-
22
- @Test
23
- fun `downloadFile succeeds when content length is unknown`() =
24
- runBlocking {
25
- val payload = "bundle-content-without-content-length".repeat(512).toByteArray()
26
- val server = ChunkedResponseServer(payload)
27
-
28
- try {
29
- val destinationDir = temporaryFolder.newFolder("downloads")
30
- val destination = File(destinationDir, "bundle.android.bundle")
31
- var reportedSize: Long? = null
32
-
33
- val result =
34
- OkHttpDownloadService().downloadFile(
35
- fileUrl = URL("http://127.0.0.1:${server.port}/bundle"),
36
- destination = destination,
37
- fileSizeCallback = { size -> reportedSize = size },
38
- progressCallback = {},
39
- )
40
-
41
- assertTrue(result is DownloadResult.Success)
42
- assertEquals(payload.size.toLong(), destination.length())
43
- assertArrayEquals(payload, destination.readBytes())
44
- assertNull(reportedSize)
45
- } finally {
46
- server.close()
47
- }
48
- }
49
-
50
- private class ChunkedResponseServer(
51
- private val payload: ByteArray,
52
- ) : AutoCloseable {
53
- private val serverSocket = ServerSocket(0, 1, InetAddress.getByName("127.0.0.1"))
54
- private val worker =
55
- thread(start = true, isDaemon = true) {
56
- serverSocket.use { socket ->
57
- socket.accept().use(::respond)
58
- }
59
- }
60
-
61
- val port: Int = serverSocket.localPort
62
-
63
- private fun respond(client: Socket) {
64
- drainRequestHeaders(client)
65
-
66
- client.getOutputStream().use { output ->
67
- output.write(
68
- (
69
- "HTTP/1.1 200 OK\r\n" +
70
- "Content-Type: application/octet-stream\r\n" +
71
- "Transfer-Encoding: chunked\r\n" +
72
- "Connection: close\r\n" +
73
- "\r\n"
74
- ).toByteArray(),
75
- )
76
- output.write(payload.size.toString(16).toByteArray())
77
- output.write("\r\n".toByteArray())
78
- output.write(payload)
79
- output.write("\r\n0\r\n\r\n".toByteArray())
80
- output.flush()
81
- }
82
- }
83
-
84
- private fun drainRequestHeaders(client: Socket) {
85
- val input = client.getInputStream()
86
- var matched = 0
87
- val terminator = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte(), '\r'.code.toByte(), '\n'.code.toByte())
88
-
89
- while (matched < terminator.size) {
90
- val next = input.read()
91
- if (next == -1) {
92
- break
93
- }
94
-
95
- matched =
96
- if (next.toByte() == terminator[matched]) {
97
- matched + 1
98
- } else if (next.toByte() == terminator[0]) {
99
- 1
100
- } else {
101
- 0
102
- }
103
- }
104
- }
105
-
106
- override fun close() {
107
- serverSocket.close()
108
- worker.join(1_000)
109
- }
110
- }
111
- }
@@ -1,41 +0,0 @@
1
- package com.hotupdater
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Assert.assertNull
5
- import org.junit.Rule
6
- import org.junit.Test
7
- import org.junit.rules.TemporaryFolder
8
- import java.io.File
9
-
10
- class RelativePathResolverTest {
11
- @get:Rule
12
- val temporaryFolder = TemporaryFolder()
13
-
14
- @Test
15
- fun `resolves a standard archive directory entry ending in a slash`() {
16
- val root = temporaryFolder.root
17
-
18
- val resolved = RelativePathResolver.resolveInside(root, "drawable-mdpi/")
19
-
20
- assertEquals(
21
- File(root, "drawable-mdpi").canonicalFile,
22
- resolved?.canonicalFile,
23
- )
24
- }
25
-
26
- @Test
27
- fun `rejects unsafe archive directory paths ending in a slash`() {
28
- val root = temporaryFolder.root
29
- val unsafePaths =
30
- listOf(
31
- "/",
32
- "../",
33
- "drawable-mdpi/../",
34
- "drawable-mdpi//",
35
- )
36
-
37
- unsafePaths.forEach { path ->
38
- assertNull(path, RelativePathResolver.resolveInside(root, path))
39
- }
40
- }
41
- }
@@ -1,118 +0,0 @@
1
- package com.hotupdater
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Assert.assertFalse
5
- import org.junit.Assert.assertNull
6
- import org.junit.Assert.assertTrue
7
- import org.junit.Rule
8
- import org.junit.Test
9
- import org.junit.rules.TemporaryFolder
10
- import java.io.File
11
- import java.nio.file.Files
12
- import java.nio.file.StandardCopyOption
13
-
14
- class ReleaseCatalogCacheServiceTest {
15
- @get:Rule
16
- val temporaryFolder = TemporaryFolder()
17
-
18
- @Test
19
- fun `persists checksum verified entries without credential filenames`() {
20
- val root = temporaryFolder.newFolder("catalog-cache")
21
- val first = createService(root)
22
- val partition = "https://updates.example.com|x-api-key=raw-secret"
23
-
24
- assertTrue(first.set(partition, "validated-catalog"))
25
- assertEquals("validated-catalog", createService(root).get(partition))
26
-
27
- val entry = root.listFiles()!!.single()
28
- assertTrue(entry.name.matches(Regex("[0-9a-f]{64}\\.catalog")))
29
- assertFalse(entry.name.contains("raw-secret"))
30
- assertFalse(entry.readText().contains("raw-secret"))
31
-
32
- entry.appendText("corrupt")
33
- assertNull(createService(root).get(partition))
34
- assertFalse(entry.exists())
35
- }
36
-
37
- @Test
38
- fun `failed atomic replacement preserves the previous entry`() {
39
- val root = temporaryFolder.newFolder("interrupted-write")
40
- val partition = "partition"
41
- assertTrue(createService(root).set(partition, "previous"))
42
-
43
- val interrupted =
44
- ReleaseCatalogCacheService(
45
- rootDirectory = root,
46
- atomicReader = ::readForTest,
47
- atomicWriter = { _, _ -> false },
48
- atomicRemover = ::removeForTest,
49
- )
50
- assertFalse(interrupted.set(partition, "replacement"))
51
- assertEquals("previous", createService(root).get(partition))
52
- }
53
-
54
- @Test
55
- fun `evicts least recently used entries and rejects oversized payloads`() {
56
- val root = temporaryFolder.newFolder("bounded-cache")
57
- val service =
58
- ReleaseCatalogCacheService(
59
- rootDirectory = root,
60
- maxEntries = 2,
61
- maxEntryBytes = 16,
62
- maxTotalBytes = 200,
63
- atomicReader = ::readForTest,
64
- atomicWriter = ::writeForTest,
65
- atomicRemover = ::removeForTest,
66
- )
67
-
68
- assertTrue(service.set("one", "first"))
69
- Thread.sleep(5)
70
- assertTrue(service.set("two", "second"))
71
- Thread.sleep(5)
72
- assertTrue(service.set("three", "third"))
73
-
74
- assertNull(service.get("one"))
75
- assertEquals("second", service.get("two"))
76
- assertEquals("third", service.get("three"))
77
- assertFalse(service.set("two", "x".repeat(17)))
78
- assertEquals("second", service.get("two"))
79
- }
80
-
81
- private fun createService(root: File): ReleaseCatalogCacheService =
82
- ReleaseCatalogCacheService(
83
- rootDirectory = root,
84
- atomicReader = ::readForTest,
85
- atomicWriter = ::writeForTest,
86
- atomicRemover = ::removeForTest,
87
- )
88
-
89
- private fun readForTest(file: File): ByteArray? = if (file.isFile) file.readBytes() else null
90
-
91
- private fun removeForTest(file: File): Boolean = !file.exists() || file.delete()
92
-
93
- private fun writeForTest(
94
- destination: File,
95
- bytes: ByteArray,
96
- ): Boolean =
97
- try {
98
- val temporary = File.createTempFile("entry", ".tmp", destination.parentFile)
99
- temporary.writeBytes(bytes)
100
- try {
101
- Files.move(
102
- temporary.toPath(),
103
- destination.toPath(),
104
- StandardCopyOption.ATOMIC_MOVE,
105
- StandardCopyOption.REPLACE_EXISTING,
106
- )
107
- } catch (_: Exception) {
108
- Files.move(
109
- temporary.toPath(),
110
- destination.toPath(),
111
- StandardCopyOption.REPLACE_EXISTING,
112
- )
113
- }
114
- true
115
- } catch (_: Exception) {
116
- false
117
- }
118
- }
@@ -1,68 +0,0 @@
1
- package com.hotupdater
2
-
3
- import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
4
- import org.junit.Assert.assertArrayEquals
5
- import org.junit.Assert.assertEquals
6
- import org.junit.Assert.assertNull
7
- import org.junit.Assert.assertThrows
8
- import org.junit.Test
9
- import java.io.ByteArrayInputStream
10
- import java.io.ByteArrayOutputStream
11
- import org.apache.commons.compress.archivers.tar.TarArchiveEntry as CommonsTarArchiveEntry
12
-
13
- class TarArchiveInputStreamTest {
14
- @Test
15
- fun `reads a long basename from a POSIX PAX header`() {
16
- val path = "raw/${"long-name-".repeat(14)}asset.bmp"
17
- val contents = "pax asset".toByteArray()
18
- val archive = createPaxArchive(path, contents)
19
-
20
- TarArchiveInputStream(ByteArrayInputStream(archive)).use { tar ->
21
- val entry = tar.getNextEntry()
22
-
23
- assertEquals(path, entry?.name)
24
- assertArrayEquals(contents, tar.readBytes())
25
- assertNull(tar.getNextEntry())
26
- }
27
- }
28
-
29
- @Test
30
- fun `rejects path traversal from a POSIX PAX header`() {
31
- val path = "../${"long-name-".repeat(14)}asset.bmp"
32
- val archive =
33
- createPaxArchive(
34
- path = path,
35
- contents = "malicious".toByteArray(),
36
- headerPath = "safe.bmp",
37
- )
38
-
39
- val error =
40
- assertThrows(SecurityException::class.java) {
41
- TarArchiveInputStream(ByteArrayInputStream(archive)).use { tar ->
42
- tar.getNextEntry()
43
- }
44
- }
45
-
46
- assertEquals("Path traversal detected: $path", error.message)
47
- }
48
-
49
- private fun createPaxArchive(
50
- path: String,
51
- contents: ByteArray,
52
- headerPath: String = path,
53
- ): ByteArray {
54
- val output = ByteArrayOutputStream()
55
- TarArchiveOutputStream(output).use { tar ->
56
- tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX)
57
- val entry =
58
- CommonsTarArchiveEntry(headerPath).apply {
59
- size = contents.size.toLong()
60
- if (path != headerPath) addPaxHeader("path", path)
61
- }
62
- tar.putArchiveEntry(entry)
63
- tar.write(contents)
64
- tar.closeArchiveEntry()
65
- }
66
- return output.toByteArray()
67
- }
68
- }