@hot-updater/react-native 1.0.0-rc.2 → 1.0.0-rc.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/android/proguard-rules.pro +17 -2
  2. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  3. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +954 -837
  4. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +15 -11
  5. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  6. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  7. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +43 -31
  8. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +37 -54
  9. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  10. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  11. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  12. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  13. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  14. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  15. package/android/src/main/java/com/hotupdater/PreviousProcessExit.kt +89 -0
  16. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  17. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  18. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  19. package/android/src/newarch/HotUpdater.kt +7 -9
  20. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  21. package/android/src/oldarch/HotUpdater.kt +7 -9
  22. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  23. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  24. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  25. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  26. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +950 -1112
  27. package/ios/HotUpdater/Internal/BundleMetadata.swift +8 -53
  28. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  29. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  30. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  31. package/ios/HotUpdater/Internal/HotUpdater.mm +13 -45
  32. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +190 -73
  33. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  34. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  35. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  36. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  37. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  38. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  39. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  40. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  41. package/lib/commonjs/appReady.js +111 -0
  42. package/lib/commonjs/appReady.js.map +1 -0
  43. package/lib/commonjs/checkForUpdate.js +125 -70
  44. package/lib/commonjs/checkForUpdate.js.map +1 -1
  45. package/lib/commonjs/clientPlugin.js +13 -0
  46. package/lib/commonjs/clientPlugin.js.map +1 -0
  47. package/lib/commonjs/error.js +4 -13
  48. package/lib/commonjs/error.js.map +1 -1
  49. package/lib/commonjs/fetchJSON.js +5 -2
  50. package/lib/commonjs/fetchJSON.js.map +1 -1
  51. package/lib/commonjs/httpClient.js +55 -92
  52. package/lib/commonjs/httpClient.js.map +1 -1
  53. package/lib/commonjs/index.js +68 -20
  54. package/lib/commonjs/index.js.map +1 -1
  55. package/lib/commonjs/native.js +82 -62
  56. package/lib/commonjs/native.js.map +1 -1
  57. package/lib/commonjs/pluginHost.js +44 -0
  58. package/lib/commonjs/pluginHost.js.map +1 -0
  59. package/lib/commonjs/releaseCatalogCache.js +20 -61
  60. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  61. package/lib/commonjs/sdkVersion.js +1 -1
  62. package/lib/commonjs/sdkVersion.js.map +1 -1
  63. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  64. package/lib/commonjs/store.js +3 -10
  65. package/lib/commonjs/store.js.map +1 -1
  66. package/lib/commonjs/updateError.js +132 -0
  67. package/lib/commonjs/updateError.js.map +1 -0
  68. package/lib/commonjs/wrap.js +44 -27
  69. package/lib/commonjs/wrap.js.map +1 -1
  70. package/lib/module/appReady.js +103 -0
  71. package/lib/module/appReady.js.map +1 -0
  72. package/lib/module/checkForUpdate.js +118 -65
  73. package/lib/module/checkForUpdate.js.map +1 -1
  74. package/lib/module/clientPlugin.js +4 -0
  75. package/lib/module/clientPlugin.js.map +1 -0
  76. package/lib/module/error.js +4 -13
  77. package/lib/module/error.js.map +1 -1
  78. package/lib/module/fetchJSON.js +3 -1
  79. package/lib/module/fetchJSON.js.map +1 -1
  80. package/lib/module/httpClient.js +53 -90
  81. package/lib/module/httpClient.js.map +1 -1
  82. package/lib/module/index.js +55 -22
  83. package/lib/module/index.js.map +1 -1
  84. package/lib/module/native.js +74 -54
  85. package/lib/module/native.js.map +1 -1
  86. package/lib/module/pluginHost.js +35 -0
  87. package/lib/module/pluginHost.js.map +1 -0
  88. package/lib/module/releaseCatalogCache.js +16 -56
  89. package/lib/module/releaseCatalogCache.js.map +1 -1
  90. package/lib/module/sdkVersion.js +1 -1
  91. package/lib/module/sdkVersion.js.map +1 -1
  92. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  93. package/lib/module/store.js +3 -10
  94. package/lib/module/store.js.map +1 -1
  95. package/lib/module/updateError.js +126 -0
  96. package/lib/module/updateError.js.map +1 -0
  97. package/lib/module/wrap.js +45 -28
  98. package/lib/module/wrap.js.map +1 -1
  99. package/lib/typescript/commonjs/appReady.d.ts +23 -0
  100. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/checkForUpdate.d.ts +16 -4
  102. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  104. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/error.d.ts +4 -13
  106. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  107. package/lib/typescript/commonjs/fetchJSON.d.ts +3 -0
  108. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  109. package/lib/typescript/commonjs/httpClient.d.ts +3 -32
  110. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/index.d.ts +7 -12
  112. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/native.d.ts +33 -23
  114. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/pluginHost.d.ts +4 -0
  116. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  117. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +6 -14
  118. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  120. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +42 -47
  122. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/store.d.ts +1 -3
  124. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/types.d.ts +1 -1
  126. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  128. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/wrap.types.d.ts +6 -7
  131. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  132. package/lib/typescript/module/appReady.d.ts +23 -0
  133. package/lib/typescript/module/appReady.d.ts.map +1 -0
  134. package/lib/typescript/module/checkForUpdate.d.ts +16 -4
  135. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  136. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  137. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  138. package/lib/typescript/module/error.d.ts +4 -13
  139. package/lib/typescript/module/error.d.ts.map +1 -1
  140. package/lib/typescript/module/fetchJSON.d.ts +3 -0
  141. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  142. package/lib/typescript/module/httpClient.d.ts +3 -32
  143. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  144. package/lib/typescript/module/index.d.ts +7 -12
  145. package/lib/typescript/module/index.d.ts.map +1 -1
  146. package/lib/typescript/module/native.d.ts +33 -23
  147. package/lib/typescript/module/native.d.ts.map +1 -1
  148. package/lib/typescript/module/pluginHost.d.ts +4 -0
  149. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  150. package/lib/typescript/module/releaseCatalogCache.d.ts +6 -14
  151. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  152. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  153. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  154. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +42 -47
  155. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  156. package/lib/typescript/module/store.d.ts +1 -3
  157. package/lib/typescript/module/store.d.ts.map +1 -1
  158. package/lib/typescript/module/types.d.ts +1 -1
  159. package/lib/typescript/module/types.d.ts.map +1 -1
  160. package/lib/typescript/module/updateError.d.ts +26 -0
  161. package/lib/typescript/module/updateError.d.ts.map +1 -0
  162. package/lib/typescript/module/wrap.d.ts.map +1 -1
  163. package/lib/typescript/module/wrap.types.d.ts +6 -7
  164. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  165. package/package.json +7 -4
  166. package/src/appReady.ts +149 -0
  167. package/src/checkForUpdate.ts +152 -89
  168. package/src/clientPlugin.ts +17 -0
  169. package/src/error.ts +4 -14
  170. package/src/fetchJSON.ts +5 -1
  171. package/src/httpClient.ts +78 -157
  172. package/src/index.ts +87 -29
  173. package/src/native.ts +109 -104
  174. package/src/pluginHost.ts +45 -0
  175. package/src/releaseCatalogCache.ts +26 -140
  176. package/src/sdkVersion.ts +1 -1
  177. package/src/specs/NativeHotUpdater.ts +41 -48
  178. package/src/store.ts +2 -23
  179. package/src/types.ts +1 -1
  180. package/src/updateError.ts +153 -0
  181. package/src/wrap.tsx +59 -28
  182. package/src/wrap.types.ts +6 -7
  183. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  184. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  185. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  186. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  187. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  188. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  189. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  190. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  191. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  192. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  193. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  194. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  195. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  196. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  197. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  198. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  199. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  200. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  201. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  202. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  203. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  204. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  205. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  206. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -604
  207. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  208. package/lib/commonjs/httpClient.spec.js +0 -102
  209. package/lib/commonjs/httpClient.spec.js.map +0 -1
  210. package/lib/commonjs/index.spec.js +0 -298
  211. package/lib/commonjs/index.spec.js.map +0 -1
  212. package/lib/commonjs/iosInterop.spec.js +0 -18
  213. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  214. package/lib/commonjs/native.spec.js +0 -795
  215. package/lib/commonjs/native.spec.js.map +0 -1
  216. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  217. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  218. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  219. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  220. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  221. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  222. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  223. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  224. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  225. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  226. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  227. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  228. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  229. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  230. package/lib/commonjs/store.notification.spec.js +0 -76
  231. package/lib/commonjs/store.notification.spec.js.map +0 -1
  232. package/lib/commonjs/store.spec.js +0 -316
  233. package/lib/commonjs/store.spec.js.map +0 -1
  234. package/lib/commonjs/wrap.spec.js +0 -296
  235. package/lib/commonjs/wrap.spec.js.map +0 -1
  236. package/lib/module/catalogCacheNative.spec.js +0 -31
  237. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  238. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -603
  239. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  240. package/lib/module/httpClient.spec.js +0 -102
  241. package/lib/module/httpClient.spec.js.map +0 -1
  242. package/lib/module/index.spec.js +0 -297
  243. package/lib/module/index.spec.js.map +0 -1
  244. package/lib/module/iosInterop.spec.js +0 -18
  245. package/lib/module/iosInterop.spec.js.map +0 -1
  246. package/lib/module/native.spec.js +0 -794
  247. package/lib/module/native.spec.js.map +0 -1
  248. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  249. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  250. package/lib/module/notifyAppReadyInsights.js +0 -128
  251. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  252. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  253. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  254. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  255. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  256. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  257. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  258. package/lib/module/releaseCatalogCache.spec.js +0 -195
  259. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  260. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  261. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  262. package/lib/module/store.notification.spec.js +0 -77
  263. package/lib/module/store.notification.spec.js.map +0 -1
  264. package/lib/module/store.spec.js +0 -315
  265. package/lib/module/store.spec.js.map +0 -1
  266. package/lib/module/wrap.spec.js +0 -295
  267. package/lib/module/wrap.spec.js.map +0 -1
  268. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  269. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  270. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  271. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  272. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  273. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  274. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  275. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  276. package/src/catalogCacheNative.spec.ts +0 -63
  277. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -596
  278. package/src/httpClient.spec.ts +0 -132
  279. package/src/index.spec.ts +0 -331
  280. package/src/iosInterop.spec.ts +0 -30
  281. package/src/native.spec.ts +0 -855
  282. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  283. package/src/notifyAppReadyInsights.spec.ts +0 -243
  284. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  285. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  286. package/src/notifyAppReadyInsights.ts +0 -194
  287. package/src/releaseCatalogCache.spec.ts +0 -282
  288. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  289. package/src/store.notification.spec.ts +0 -92
  290. package/src/store.spec.ts +0 -372
  291. package/src/wrap.spec.ts +0 -367
@@ -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
- }