@hot-updater/react-native 0.36.7 → 1.0.0-rc.0

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 (239) hide show
  1. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +357 -6
  2. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +268 -34
  3. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +75 -9
  4. package/android/src/main/java/com/hotupdater/NativeConfigUtils.kt +2 -16
  5. package/android/src/main/java/com/hotupdater/ReleaseCatalogCacheService.kt +166 -0
  6. package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +1 -2
  7. package/android/src/newarch/HotUpdater.kt +1 -1
  8. package/android/src/newarch/HotUpdaterModule.kt +116 -0
  9. package/android/src/oldarch/HotUpdater.kt +1 -1
  10. package/android/src/oldarch/HotUpdaterModule.kt +128 -0
  11. package/android/src/oldarch/HotUpdaterSpec.kt +38 -0
  12. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +280 -2
  13. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +57 -2
  14. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +118 -0
  15. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +491 -30
  16. package/ios/HotUpdater/Internal/BundleMetadata.swift +229 -6
  17. package/ios/HotUpdater/Internal/HotUpdater.mm +122 -0
  18. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +176 -7
  19. package/ios/HotUpdater/Internal/ReleaseCatalogCacheService.swift +213 -0
  20. package/lib/commonjs/catalogCacheNative.js +44 -0
  21. package/lib/commonjs/catalogCacheNative.js.map +1 -0
  22. package/lib/commonjs/catalogCacheNative.spec.js +31 -0
  23. package/lib/commonjs/catalogCacheNative.spec.js.map +1 -0
  24. package/lib/commonjs/checkForUpdate.js +231 -60
  25. package/lib/commonjs/checkForUpdate.js.map +1 -1
  26. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +604 -0
  27. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -0
  28. package/lib/commonjs/error.js +8 -1
  29. package/lib/commonjs/error.js.map +1 -1
  30. package/lib/commonjs/{fetchUpdateInfo.js → fetchJSON.js} +11 -14
  31. package/lib/commonjs/fetchJSON.js.map +1 -0
  32. package/lib/commonjs/httpClient.js +153 -0
  33. package/lib/commonjs/httpClient.js.map +1 -0
  34. package/lib/commonjs/httpClient.spec.js +102 -0
  35. package/lib/commonjs/httpClient.spec.js.map +1 -0
  36. package/lib/commonjs/index.js +48 -71
  37. package/lib/commonjs/index.js.map +1 -1
  38. package/lib/commonjs/index.spec.js +117 -161
  39. package/lib/commonjs/index.spec.js.map +1 -1
  40. package/lib/commonjs/iosInterop.spec.js +18 -0
  41. package/lib/commonjs/iosInterop.spec.js.map +1 -0
  42. package/lib/commonjs/native.js +222 -55
  43. package/lib/commonjs/native.js.map +1 -1
  44. package/lib/commonjs/native.spec.js +226 -12
  45. package/lib/commonjs/native.spec.js.map +1 -1
  46. package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js +218 -0
  47. package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +1 -0
  48. package/lib/commonjs/notifyAppReadyAnalytics.js +133 -0
  49. package/lib/commonjs/notifyAppReadyAnalytics.js.map +1 -0
  50. package/lib/commonjs/notifyAppReadyAnalytics.spec.js +246 -0
  51. package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +1 -0
  52. package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js +24 -0
  53. package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +1 -0
  54. package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js +117 -0
  55. package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +1 -0
  56. package/lib/commonjs/releaseCatalogCache.js +179 -0
  57. package/lib/commonjs/releaseCatalogCache.js.map +1 -0
  58. package/lib/commonjs/releaseCatalogCache.spec.js +195 -0
  59. package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -0
  60. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +64 -0
  61. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +1 -0
  62. package/lib/commonjs/sdkVersion.js +1 -1
  63. package/lib/commonjs/sdkVersion.js.map +1 -1
  64. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  65. package/lib/commonjs/types.js +0 -12
  66. package/lib/commonjs/types.js.map +1 -1
  67. package/lib/commonjs/wrap.js +7 -82
  68. package/lib/commonjs/wrap.js.map +1 -1
  69. package/lib/commonjs/wrap.spec.js +203 -56
  70. package/lib/commonjs/wrap.spec.js.map +1 -1
  71. package/lib/commonjs/wrap.types.js +6 -0
  72. package/lib/commonjs/wrap.types.js.map +1 -0
  73. package/lib/module/catalogCacheNative.js +36 -0
  74. package/lib/module/catalogCacheNative.js.map +1 -0
  75. package/lib/module/catalogCacheNative.spec.js +31 -0
  76. package/lib/module/catalogCacheNative.spec.js.map +1 -0
  77. package/lib/module/checkForUpdate.js +233 -62
  78. package/lib/module/checkForUpdate.js.map +1 -1
  79. package/lib/module/checkForUpdate.releaseCatalog.spec.js +603 -0
  80. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -0
  81. package/lib/module/error.js +6 -0
  82. package/lib/module/error.js.map +1 -1
  83. package/lib/module/{fetchUpdateInfo.js → fetchJSON.js} +9 -12
  84. package/lib/module/fetchJSON.js.map +1 -0
  85. package/lib/module/httpClient.js +148 -0
  86. package/lib/module/httpClient.js.map +1 -0
  87. package/lib/module/httpClient.spec.js +102 -0
  88. package/lib/module/httpClient.spec.js.map +1 -0
  89. package/lib/module/index.js +49 -66
  90. package/lib/module/index.js.map +1 -1
  91. package/lib/module/index.spec.js +117 -161
  92. package/lib/module/index.spec.js.map +1 -1
  93. package/lib/module/iosInterop.spec.js +18 -0
  94. package/lib/module/iosInterop.spec.js.map +1 -0
  95. package/lib/module/native.js +206 -51
  96. package/lib/module/native.js.map +1 -1
  97. package/lib/module/native.spec.js +226 -12
  98. package/lib/module/native.spec.js.map +1 -1
  99. package/lib/module/notifyAppReadyAnalytics.boundary.spec.js +217 -0
  100. package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +1 -0
  101. package/lib/module/notifyAppReadyAnalytics.js +128 -0
  102. package/lib/module/notifyAppReadyAnalytics.js.map +1 -0
  103. package/lib/module/notifyAppReadyAnalytics.spec.js +245 -0
  104. package/lib/module/notifyAppReadyAnalytics.spec.js.map +1 -0
  105. package/lib/module/notifyAppReadyAnalytics.test-utils.js +18 -0
  106. package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +1 -0
  107. package/lib/module/notifyAppReadyAnalytics.transport.spec.js +116 -0
  108. package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +1 -0
  109. package/lib/module/releaseCatalogCache.js +171 -0
  110. package/lib/module/releaseCatalogCache.js.map +1 -0
  111. package/lib/module/releaseCatalogCache.spec.js +195 -0
  112. package/lib/module/releaseCatalogCache.spec.js.map +1 -0
  113. package/lib/module/releaseCatalogCacheNativeContract.spec.js +64 -0
  114. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +1 -0
  115. package/lib/module/sdkVersion.js +1 -1
  116. package/lib/module/sdkVersion.js.map +1 -1
  117. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  118. package/lib/module/types.js +0 -12
  119. package/lib/module/types.js.map +1 -1
  120. package/lib/module/wrap.js +6 -81
  121. package/lib/module/wrap.js.map +1 -1
  122. package/lib/module/wrap.spec.js +203 -56
  123. package/lib/module/wrap.spec.js.map +1 -1
  124. package/lib/module/wrap.types.js +4 -0
  125. package/lib/module/wrap.types.js.map +1 -0
  126. package/lib/typescript/commonjs/catalogCacheNative.d.ts +4 -0
  127. package/lib/typescript/commonjs/catalogCacheNative.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/checkForUpdate.d.ts +14 -4
  129. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/error.d.ts +3 -0
  131. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  132. package/lib/typescript/commonjs/fetchJSON.d.ts +6 -0
  133. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -0
  134. package/lib/typescript/commonjs/httpClient.d.ts +56 -0
  135. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -0
  136. package/lib/typescript/commonjs/index.d.ts +32 -41
  137. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  138. package/lib/typescript/commonjs/native.d.ts +83 -18
  139. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  140. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts +13 -0
  141. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +1 -0
  142. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +8 -0
  143. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +1 -0
  144. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +25 -0
  145. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -0
  146. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  147. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  148. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +40 -5
  149. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  150. package/lib/typescript/commonjs/types.d.ts +0 -118
  151. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  152. package/lib/typescript/commonjs/wrap.d.ts +2 -191
  153. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  154. package/lib/typescript/commonjs/wrap.types.d.ts +65 -0
  155. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -0
  156. package/lib/typescript/module/catalogCacheNative.d.ts +4 -0
  157. package/lib/typescript/module/catalogCacheNative.d.ts.map +1 -0
  158. package/lib/typescript/module/checkForUpdate.d.ts +14 -4
  159. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  160. package/lib/typescript/module/error.d.ts +3 -0
  161. package/lib/typescript/module/error.d.ts.map +1 -1
  162. package/lib/typescript/module/fetchJSON.d.ts +6 -0
  163. package/lib/typescript/module/fetchJSON.d.ts.map +1 -0
  164. package/lib/typescript/module/httpClient.d.ts +56 -0
  165. package/lib/typescript/module/httpClient.d.ts.map +1 -0
  166. package/lib/typescript/module/index.d.ts +32 -41
  167. package/lib/typescript/module/index.d.ts.map +1 -1
  168. package/lib/typescript/module/native.d.ts +83 -18
  169. package/lib/typescript/module/native.d.ts.map +1 -1
  170. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts +13 -0
  171. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +1 -0
  172. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +8 -0
  173. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts.map +1 -0
  174. package/lib/typescript/module/releaseCatalogCache.d.ts +25 -0
  175. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -0
  176. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  177. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  178. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +40 -5
  179. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  180. package/lib/typescript/module/types.d.ts +0 -118
  181. package/lib/typescript/module/types.d.ts.map +1 -1
  182. package/lib/typescript/module/wrap.d.ts +2 -191
  183. package/lib/typescript/module/wrap.d.ts.map +1 -1
  184. package/lib/typescript/module/wrap.types.d.ts +65 -0
  185. package/lib/typescript/module/wrap.types.d.ts.map +1 -0
  186. package/package.json +6 -24
  187. package/src/catalogCacheNative.spec.ts +63 -0
  188. package/src/catalogCacheNative.ts +74 -0
  189. package/src/checkForUpdate.releaseCatalog.spec.ts +596 -0
  190. package/src/checkForUpdate.ts +368 -97
  191. package/src/error.ts +7 -0
  192. package/src/{fetchUpdateInfo.ts → fetchJSON.ts} +8 -15
  193. package/src/httpClient.spec.ts +132 -0
  194. package/src/httpClient.ts +261 -0
  195. package/src/index.spec.ts +110 -203
  196. package/src/index.ts +70 -93
  197. package/src/iosInterop.spec.ts +30 -0
  198. package/src/native.spec.ts +226 -10
  199. package/src/native.ts +385 -83
  200. package/src/notifyAppReadyAnalytics.boundary.spec.ts +234 -0
  201. package/src/notifyAppReadyAnalytics.spec.ts +243 -0
  202. package/src/notifyAppReadyAnalytics.test-utils.ts +31 -0
  203. package/src/notifyAppReadyAnalytics.transport.spec.ts +130 -0
  204. package/src/notifyAppReadyAnalytics.ts +194 -0
  205. package/src/releaseCatalogCache.spec.ts +282 -0
  206. package/src/releaseCatalogCache.ts +341 -0
  207. package/src/releaseCatalogCacheNativeContract.spec.ts +80 -0
  208. package/src/sdkVersion.ts +1 -1
  209. package/src/specs/NativeHotUpdater.ts +51 -5
  210. package/src/types.ts +0 -140
  211. package/src/wrap.spec.ts +251 -56
  212. package/src/wrap.tsx +23 -318
  213. package/src/wrap.types.ts +78 -0
  214. package/android/src/main/java/com/hotupdater/StringResourceUtils.kt +0 -45
  215. package/app.plugin.js +0 -1
  216. package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.h +0 -7
  217. package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.mm +0 -4
  218. package/lib/commonjs/DefaultResolver.js +0 -50
  219. package/lib/commonjs/DefaultResolver.js.map +0 -1
  220. package/lib/commonjs/DefaultResolver.spec.js +0 -144
  221. package/lib/commonjs/DefaultResolver.spec.js.map +0 -1
  222. package/lib/commonjs/fetchUpdateInfo.js.map +0 -1
  223. package/lib/module/DefaultResolver.js +0 -46
  224. package/lib/module/DefaultResolver.js.map +0 -1
  225. package/lib/module/DefaultResolver.spec.js +0 -144
  226. package/lib/module/DefaultResolver.spec.js.map +0 -1
  227. package/lib/module/fetchUpdateInfo.js.map +0 -1
  228. package/lib/typescript/commonjs/DefaultResolver.d.ts +0 -11
  229. package/lib/typescript/commonjs/DefaultResolver.d.ts.map +0 -1
  230. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +0 -7
  231. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +0 -1
  232. package/lib/typescript/module/DefaultResolver.d.ts +0 -11
  233. package/lib/typescript/module/DefaultResolver.d.ts.map +0 -1
  234. package/lib/typescript/module/fetchUpdateInfo.d.ts +0 -7
  235. package/lib/typescript/module/fetchUpdateInfo.d.ts.map +0 -1
  236. package/plugin/build/transformers.js +0 -314
  237. package/plugin/build/withHotUpdater.js +0 -228
  238. package/src/DefaultResolver.spec.ts +0 -193
  239. package/src/DefaultResolver.ts +0 -63
@@ -0,0 +1,166 @@
1
+ package com.hotupdater
2
+
3
+ import android.util.AtomicFile
4
+ import java.io.File
5
+ import java.io.FileOutputStream
6
+ import java.nio.charset.StandardCharsets
7
+ import java.security.MessageDigest
8
+
9
+ internal class ReleaseCatalogCacheService(
10
+ private val rootDirectory: File,
11
+ private val maxEntries: Int = DEFAULT_MAX_ENTRIES,
12
+ private val maxEntryBytes: Int = DEFAULT_MAX_ENTRY_BYTES,
13
+ private val maxTotalBytes: Long = DEFAULT_MAX_TOTAL_BYTES,
14
+ private val atomicReader: (File) -> ByteArray? = ::readAtomically,
15
+ private val atomicWriter: (File, ByteArray) -> Boolean = ::writeAtomically,
16
+ private val atomicRemover: (File) -> Boolean = ::removeAtomically,
17
+ ) {
18
+ companion object {
19
+ private const val DIGEST_BYTES = 64
20
+ private const val MAX_COMPILED_CATALOG_BYTES = 256 * 1024
21
+ private const val MAX_WIRE_ENVELOPE_BYTES = 4 * 1024
22
+ private const val MAX_ETAG_BYTES = 1024
23
+ private const val CACHE_FORMAT_BYTES = 3
24
+ const val DEFAULT_MAX_ENTRIES = 8
25
+ const val DEFAULT_MAX_ENTRY_BYTES =
26
+ MAX_COMPILED_CATALOG_BYTES + MAX_WIRE_ENVELOPE_BYTES + MAX_ETAG_BYTES + CACHE_FORMAT_BYTES
27
+ const val DEFAULT_MAX_TOTAL_BYTES =
28
+ DEFAULT_MAX_ENTRIES.toLong() * (DEFAULT_MAX_ENTRY_BYTES + DIGEST_BYTES + 1)
29
+ private const val ENTRY_SUFFIX = ".catalog"
30
+
31
+ private fun readAtomically(file: File): ByteArray? =
32
+ try {
33
+ val atomicFile = AtomicFile(file)
34
+ atomicFile.openRead().use { input -> input.readBytes() }
35
+ } catch (_: Exception) {
36
+ null
37
+ }
38
+
39
+ private fun removeAtomically(file: File): Boolean {
40
+ val atomicFile = AtomicFile(file)
41
+ atomicFile.delete()
42
+ return !file.exists() &&
43
+ !File("${file.path}.bak").exists() &&
44
+ !File("${file.path}.new").exists()
45
+ }
46
+
47
+ private fun writeAtomically(
48
+ destination: File,
49
+ bytes: ByteArray,
50
+ ): Boolean {
51
+ val atomicFile = AtomicFile(destination)
52
+ var output: FileOutputStream? = null
53
+ return try {
54
+ output = atomicFile.startWrite()
55
+ output.write(bytes)
56
+ output.fd.sync()
57
+ atomicFile.finishWrite(output)
58
+ true
59
+ } catch (_: Exception) {
60
+ output?.let(atomicFile::failWrite)
61
+ false
62
+ }
63
+ }
64
+ }
65
+
66
+ init {
67
+ recoverInterruptedWrites()
68
+ trim()
69
+ }
70
+
71
+ @Synchronized
72
+ fun get(partition: String): String? {
73
+ val file = entryFile(partition)
74
+ return try {
75
+ val stored = atomicReader(file) ?: return null
76
+ if (stored.size > maxEntryBytes + DIGEST_BYTES + 1) {
77
+ atomicRemover(file)
78
+ return null
79
+ }
80
+ val separator = stored.indexOf('\n'.code.toByte())
81
+ if (separator != DIGEST_BYTES) {
82
+ atomicRemover(file)
83
+ return null
84
+ }
85
+ val expectedDigest = String(stored, 0, separator, StandardCharsets.US_ASCII)
86
+ val payload = stored.copyOfRange(separator + 1, stored.size)
87
+ if (payload.size > maxEntryBytes || sha256(payload) != expectedDigest) {
88
+ atomicRemover(file)
89
+ return null
90
+ }
91
+
92
+ file.setLastModified(System.currentTimeMillis())
93
+ String(payload, StandardCharsets.UTF_8)
94
+ } catch (_: Exception) {
95
+ atomicRemover(file)
96
+ null
97
+ }
98
+ }
99
+
100
+ @Synchronized
101
+ fun set(
102
+ partition: String,
103
+ value: String,
104
+ ): Boolean {
105
+ val payload = value.toByteArray(StandardCharsets.UTF_8)
106
+ if (payload.size > maxEntryBytes) return false
107
+ if (!rootDirectory.exists() && !rootDirectory.mkdirs()) return false
108
+
109
+ val destination = entryFile(partition)
110
+ return try {
111
+ val stored =
112
+ sha256(payload).toByteArray(StandardCharsets.US_ASCII) +
113
+ byteArrayOf('\n'.code.toByte()) +
114
+ payload
115
+ if (!atomicWriter(destination, stored)) return false
116
+ destination.setLastModified(System.currentTimeMillis())
117
+ trim()
118
+ true
119
+ } catch (_: Exception) {
120
+ false
121
+ }
122
+ }
123
+
124
+ @Synchronized
125
+ fun remove(partition: String): Boolean = atomicRemover(entryFile(partition))
126
+
127
+ private fun entryFile(partition: String): File =
128
+ File(rootDirectory, "${sha256(partition.toByteArray(StandardCharsets.UTF_8))}$ENTRY_SUFFIX")
129
+
130
+ private fun recoverInterruptedWrites() {
131
+ rootDirectory.listFiles()?.forEach { file ->
132
+ when {
133
+ file.name.endsWith("$ENTRY_SUFFIX.bak") -> {
134
+ val destination = File(file.parentFile, file.name.removeSuffix(".bak"))
135
+ if (atomicReader(destination) == null) atomicRemover(destination)
136
+ }
137
+
138
+ file.name.endsWith("$ENTRY_SUFFIX.new") -> {
139
+ val destination = File(file.parentFile, file.name.removeSuffix(".new"))
140
+ if (atomicReader(destination) == null) atomicRemover(destination)
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ private fun trim() {
147
+ val entries =
148
+ rootDirectory
149
+ .listFiles { file -> file.isFile && file.name.endsWith(ENTRY_SUFFIX) }
150
+ ?.sortedByDescending { it.lastModified() }
151
+ .orEmpty()
152
+ var retainedBytes = 0L
153
+ entries.forEachIndexed { index, file ->
154
+ retainedBytes += file.length()
155
+ if (index >= maxEntries || retainedBytes > maxTotalBytes) {
156
+ atomicRemover(file)
157
+ }
158
+ }
159
+ }
160
+
161
+ private fun sha256(value: ByteArray): String =
162
+ MessageDigest
163
+ .getInstance("SHA-256")
164
+ .digest(value)
165
+ .joinToString("") { byte -> "%02x".format(byte.toInt() and 0xff) }
166
+ }
@@ -92,7 +92,7 @@ object SignatureVerifier {
92
92
  private const val TAG = "SignatureVerifier"
93
93
 
94
94
  /**
95
- * Reads public key from Android string resources.
95
+ * Reads public key from AndroidManifest metadata.
96
96
  * @param context Application context
97
97
  * @return Public key PEM string or null if not configured
98
98
  */
@@ -101,7 +101,6 @@ object SignatureVerifier {
101
101
  NativeConfigUtils.getString(
102
102
  context,
103
103
  NativeConfigUtils.PUBLIC_KEY_META_DATA_KEY,
104
- "hot_updater_public_key",
105
104
  )
106
105
 
107
106
  if (publicKeyPEM.isNullOrEmpty()) {
@@ -60,7 +60,7 @@ class HotUpdater {
60
60
  manifestFileHash,
61
61
  changedAssets,
62
62
  channel,
63
- progressCallback,
63
+ progressCallback = progressCallback,
64
64
  )
65
65
  }
66
66
 
@@ -107,6 +107,27 @@ class HotUpdaterModule internal constructor(
107
107
  return parsedAssets
108
108
  }
109
109
 
110
+ private fun parseSelection(params: ReadableMap): PersistedSelection? {
111
+ if (!params.hasKey("selection") || params.isNull("selection")) return null
112
+ val selection = params.getMap("selection") ?: return null
113
+ return PersistedSelection(
114
+ kind = selection.getString("kind") ?: return null,
115
+ releaseId = selection.getString("releaseId"),
116
+ bundleId = selection.getString("bundleId") ?: return null,
117
+ catalogId = selection.getString("catalogId"),
118
+ scopeKey = selection.getString("scopeKey"),
119
+ generation =
120
+ if (selection.hasKey("generation") && !selection.isNull("generation")) {
121
+ selection.getDouble("generation").toLong()
122
+ } else {
123
+ null
124
+ },
125
+ catalogHash = selection.getString("catalogHash"),
126
+ channel = selection.getString("channel") ?: "",
127
+ selectionContextHash = selection.getString("selectionContextHash"),
128
+ )
129
+ }
130
+
110
131
  override fun reload(promise: Promise) {
111
132
  moduleScope.launch {
112
133
  try {
@@ -162,6 +183,7 @@ class HotUpdaterModule internal constructor(
162
183
  val manifestFileHash = params.getString("manifestFileHash")
163
184
  val changedAssets = parseChangedAssets(params)
164
185
  val channel = params.getString("channel")
186
+ val selection = parseSelection(params)
165
187
 
166
188
  val impl = getInstance()
167
189
 
@@ -173,6 +195,7 @@ class HotUpdaterModule internal constructor(
173
195
  manifestFileHash,
174
196
  changedAssets,
175
197
  channel,
198
+ selection,
176
199
  ) { progress ->
177
200
  // Post to Main thread for React Native event emission
178
201
  Handler(Looper.getMainLooper()).post {
@@ -235,6 +258,86 @@ class HotUpdaterModule internal constructor(
235
258
  }
236
259
  }
237
260
 
261
+ override fun acceptReleaseCatalog(params: ReadableMap): Boolean {
262
+ val catalogId = params.getString("catalogId") ?: return false
263
+ val scopeKey = params.getString("scopeKey") ?: return false
264
+ val catalogHash = params.getString("catalogHash") ?: return false
265
+ val channel = params.getString("channel") ?: return false
266
+ val selectionContextHash = params.getString("selectionContextHash") ?: return false
267
+ return getInstance().acceptReleaseCatalog(
268
+ catalogId = catalogId,
269
+ scopeKey = scopeKey,
270
+ generation = params.getDouble("generation").toLong(),
271
+ catalogHash = catalogHash,
272
+ channel = channel,
273
+ selectionContextHash = selectionContextHash,
274
+ )
275
+ }
276
+
277
+ override fun getActiveUpdateState(): WritableNativeMap = getInstance().getActiveUpdateState().toWritableNativeMap()
278
+
279
+ override fun getReleaseCatalogCache(
280
+ partition: String,
281
+ promise: Promise,
282
+ ) {
283
+ moduleScope.launch(Dispatchers.IO) {
284
+ promise.resolve(getInstance().getReleaseCatalogCache(partition))
285
+ }
286
+ }
287
+
288
+ override fun setReleaseCatalogCache(
289
+ partition: String,
290
+ payload: String,
291
+ promise: Promise,
292
+ ) {
293
+ moduleScope.launch(Dispatchers.IO) {
294
+ promise.resolve(getInstance().setReleaseCatalogCache(partition, payload))
295
+ }
296
+ }
297
+
298
+ override fun removeReleaseCatalogCache(
299
+ partition: String,
300
+ promise: Promise,
301
+ ) {
302
+ moduleScope.launch(Dispatchers.IO) {
303
+ promise.resolve(getInstance().removeReleaseCatalogCache(partition))
304
+ }
305
+ }
306
+
307
+ override fun isReleaseSelectionCurrent(params: ReadableMap): Boolean {
308
+ val catalogId = params.getString("catalogId") ?: return false
309
+ val scopeKey = params.getString("scopeKey") ?: return false
310
+ val catalogHash = params.getString("catalogHash") ?: return false
311
+ val channel = params.getString("channel") ?: return false
312
+ val selectionContextHash = params.getString("selectionContextHash") ?: return false
313
+ return getInstance().isReleaseSelectionCurrent(
314
+ catalogId = catalogId,
315
+ scopeKey = scopeKey,
316
+ generation = params.getDouble("generation").toLong(),
317
+ catalogHash = catalogHash,
318
+ channel = channel,
319
+ selectionContextHash = selectionContextHash,
320
+ )
321
+ }
322
+
323
+ override fun commitReleaseSelection(
324
+ params: ReadableMap,
325
+ promise: Promise,
326
+ ) {
327
+ val selection = parseSelection(params)
328
+ if (selection == null) {
329
+ promise.reject("INVALID_SELECTION", "Invalid Release selection")
330
+ return
331
+ }
332
+ moduleScope.launch {
333
+ try {
334
+ promise.resolve(getInstance().commitReleaseSelection(selection))
335
+ } catch (error: Exception) {
336
+ promise.reject("UNKNOWN_ERROR", error.message, error)
337
+ }
338
+ }
339
+ }
340
+
238
341
  override fun getTypedExportedConstants(): Map<String, Any?> {
239
342
  val constants: MutableMap<String, Any?> = HashMap()
240
343
  constants["MIN_BUNDLE_ID"] = HotUpdater.getMinBundleId()
@@ -284,6 +387,19 @@ class HotUpdaterModule internal constructor(
284
387
 
285
388
  override fun getManifest(): WritableNativeMap = getInstance().getManifest().toWritableNativeMap()
286
389
 
390
+ override fun getInstallId(): String = getInstance().getInstallId()
391
+
392
+ override fun getUserId(): String? = getInstance().getUserId()
393
+
394
+ override fun getUsername(): String? = getInstance().getUsername()
395
+
396
+ override fun setUser(
397
+ userId: String?,
398
+ username: String?,
399
+ ) {
400
+ getInstance().setUser(userId, username)
401
+ }
402
+
287
403
  override fun resetChannel(promise: Promise) {
288
404
  moduleScope.launch {
289
405
  try {
@@ -59,7 +59,7 @@ class HotUpdater {
59
59
  manifestFileHash,
60
60
  changedAssets,
61
61
  channel,
62
- progressCallback,
62
+ progressCallback = progressCallback,
63
63
  )
64
64
  }
65
65
 
@@ -107,6 +107,28 @@ class HotUpdaterModule internal constructor(
107
107
  return parsedAssets
108
108
  }
109
109
 
110
+ private fun parseSelection(params: ReadableMap): PersistedSelection? {
111
+ if (!params.hasKey("selection") || params.isNull("selection")) return null
112
+ val selection = params.getMap("selection") ?: return null
113
+ val bundleId = selection.getString("bundleId") ?: return null
114
+ return PersistedSelection(
115
+ kind = selection.getString("kind") ?: return null,
116
+ releaseId = selection.getString("releaseId"),
117
+ bundleId = bundleId,
118
+ catalogId = selection.getString("catalogId"),
119
+ scopeKey = selection.getString("scopeKey"),
120
+ generation =
121
+ if (selection.hasKey("generation") && !selection.isNull("generation")) {
122
+ selection.getDouble("generation").toLong()
123
+ } else {
124
+ null
125
+ },
126
+ catalogHash = selection.getString("catalogHash"),
127
+ channel = selection.getString("channel") ?: "",
128
+ selectionContextHash = selection.getString("selectionContextHash"),
129
+ )
130
+ }
131
+
110
132
  @ReactMethod
111
133
  override fun reload(promise: Promise) {
112
134
  moduleScope.launch {
@@ -165,6 +187,7 @@ class HotUpdaterModule internal constructor(
165
187
  val manifestFileHash = params.getString("manifestFileHash")
166
188
  val changedAssets = parseChangedAssets(params)
167
189
  val channel = params.getString("channel")
190
+ val selection = parseSelection(params)
168
191
 
169
192
  val impl = getInstance()
170
193
 
@@ -176,6 +199,7 @@ class HotUpdaterModule internal constructor(
176
199
  manifestFileHash,
177
200
  changedAssets,
178
201
  channel,
202
+ selection,
179
203
  ) { progress ->
180
204
  // Post to Main thread for React Native event emission
181
205
  Handler(Looper.getMainLooper()).post {
@@ -231,6 +255,93 @@ class HotUpdaterModule internal constructor(
231
255
  }
232
256
  }
233
257
 
258
+ @ReactMethod(isBlockingSynchronousMethod = true)
259
+ override fun acceptReleaseCatalog(params: ReadableMap): Boolean {
260
+ val catalogId = params.getString("catalogId") ?: return false
261
+ val scopeKey = params.getString("scopeKey") ?: return false
262
+ val catalogHash = params.getString("catalogHash") ?: return false
263
+ val channel = params.getString("channel") ?: return false
264
+ val selectionContextHash = params.getString("selectionContextHash") ?: return false
265
+ return getInstance().acceptReleaseCatalog(
266
+ catalogId = catalogId,
267
+ scopeKey = scopeKey,
268
+ generation = params.getDouble("generation").toLong(),
269
+ catalogHash = catalogHash,
270
+ channel = channel,
271
+ selectionContextHash = selectionContextHash,
272
+ )
273
+ }
274
+
275
+ @ReactMethod(isBlockingSynchronousMethod = true)
276
+ override fun getActiveUpdateState(): String = org.json.JSONObject(getInstance().getActiveUpdateState()).toString()
277
+
278
+ @ReactMethod
279
+ override fun getReleaseCatalogCache(
280
+ partition: String,
281
+ promise: Promise,
282
+ ) {
283
+ moduleScope.launch(Dispatchers.IO) {
284
+ promise.resolve(getInstance().getReleaseCatalogCache(partition))
285
+ }
286
+ }
287
+
288
+ @ReactMethod
289
+ override fun setReleaseCatalogCache(
290
+ partition: String,
291
+ payload: String,
292
+ promise: Promise,
293
+ ) {
294
+ moduleScope.launch(Dispatchers.IO) {
295
+ promise.resolve(getInstance().setReleaseCatalogCache(partition, payload))
296
+ }
297
+ }
298
+
299
+ @ReactMethod
300
+ override fun removeReleaseCatalogCache(
301
+ partition: String,
302
+ promise: Promise,
303
+ ) {
304
+ moduleScope.launch(Dispatchers.IO) {
305
+ promise.resolve(getInstance().removeReleaseCatalogCache(partition))
306
+ }
307
+ }
308
+
309
+ @ReactMethod(isBlockingSynchronousMethod = true)
310
+ override fun isReleaseSelectionCurrent(params: ReadableMap): Boolean {
311
+ val catalogId = params.getString("catalogId") ?: return false
312
+ val scopeKey = params.getString("scopeKey") ?: return false
313
+ val catalogHash = params.getString("catalogHash") ?: return false
314
+ val channel = params.getString("channel") ?: return false
315
+ val selectionContextHash = params.getString("selectionContextHash") ?: return false
316
+ return getInstance().isReleaseSelectionCurrent(
317
+ catalogId = catalogId,
318
+ scopeKey = scopeKey,
319
+ generation = params.getDouble("generation").toLong(),
320
+ catalogHash = catalogHash,
321
+ channel = channel,
322
+ selectionContextHash = selectionContextHash,
323
+ )
324
+ }
325
+
326
+ @ReactMethod
327
+ override fun commitReleaseSelection(
328
+ params: ReadableMap,
329
+ promise: Promise,
330
+ ) {
331
+ val selection = parseSelection(params)
332
+ if (selection == null) {
333
+ promise.reject("INVALID_SELECTION", "Invalid Release selection")
334
+ return
335
+ }
336
+ moduleScope.launch {
337
+ try {
338
+ promise.resolve(getInstance().commitReleaseSelection(selection))
339
+ } catch (error: Exception) {
340
+ promise.reject("UNKNOWN_ERROR", error.message, error)
341
+ }
342
+ }
343
+ }
344
+
234
345
  @ReactMethod
235
346
  fun addListener(
236
347
  @Suppress("UNUSED_PARAMETER") eventName: String?,
@@ -299,6 +410,23 @@ class HotUpdaterModule internal constructor(
299
410
  @ReactMethod(isBlockingSynchronousMethod = true)
300
411
  override fun getCohort(): String = cohortService.getCohort()
301
412
 
413
+ @ReactMethod(isBlockingSynchronousMethod = true)
414
+ override fun getInstallId(): String = getInstance().getInstallId()
415
+
416
+ @ReactMethod(isBlockingSynchronousMethod = true)
417
+ override fun getUserId(): String? = getInstance().getUserId()
418
+
419
+ @ReactMethod(isBlockingSynchronousMethod = true)
420
+ override fun getUsername(): String? = getInstance().getUsername()
421
+
422
+ @ReactMethod
423
+ override fun setUser(
424
+ userId: String?,
425
+ username: String?,
426
+ ) {
427
+ getInstance().setUser(userId, username)
428
+ }
429
+
302
430
  @ReactMethod
303
431
  override fun resetChannel(promise: Promise) {
304
432
  moduleScope.launch {
@@ -13,6 +13,33 @@ abstract class HotUpdaterSpec internal constructor(
13
13
  promise: Promise,
14
14
  )
15
15
 
16
+ abstract fun acceptReleaseCatalog(params: ReadableMap): Boolean
17
+
18
+ abstract fun getActiveUpdateState(): String
19
+
20
+ abstract fun getReleaseCatalogCache(
21
+ partition: String,
22
+ promise: Promise,
23
+ )
24
+
25
+ abstract fun setReleaseCatalogCache(
26
+ partition: String,
27
+ payload: String,
28
+ promise: Promise,
29
+ )
30
+
31
+ abstract fun removeReleaseCatalogCache(
32
+ partition: String,
33
+ promise: Promise,
34
+ )
35
+
36
+ abstract fun isReleaseSelectionCurrent(params: ReadableMap): Boolean
37
+
38
+ abstract fun commitReleaseSelection(
39
+ params: ReadableMap,
40
+ promise: Promise,
41
+ )
42
+
16
43
  abstract fun reload(promise: Promise)
17
44
 
18
45
  abstract fun reloadProcess(promise: Promise)
@@ -33,5 +60,16 @@ abstract class HotUpdaterSpec internal constructor(
33
60
 
34
61
  abstract fun getManifest(): String
35
62
 
63
+ abstract fun getInstallId(): String
64
+
65
+ abstract fun getUserId(): String?
66
+
67
+ abstract fun getUsername(): String?
68
+
69
+ abstract fun setUser(
70
+ userId: String?,
71
+ username: String?,
72
+ )
73
+
36
74
  abstract fun resetChannel(promise: Promise)
37
75
  }