@hot-updater/react-native 0.30.11 → 0.31.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 (128) hide show
  1. package/HotUpdater.podspec +1 -0
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/com/hotupdater/BsdiffPatch.kt +284 -0
  4. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +885 -5
  5. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +13 -2
  6. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +37 -8
  7. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +48 -12
  8. package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +60 -22
  9. package/android/src/newarch/HotUpdater.kt +14 -2
  10. package/android/src/newarch/HotUpdaterModule.kt +112 -1
  11. package/android/src/oldarch/HotUpdater.kt +14 -2
  12. package/android/src/oldarch/HotUpdaterModule.kt +107 -3
  13. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +46 -0
  14. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +29 -1
  15. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -1
  16. package/ios/HotUpdater/Internal/BsdiffPatchBridge.h +16 -0
  17. package/ios/HotUpdater/Internal/BsdiffPatchBridge.mm +343 -0
  18. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1093 -47
  19. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +2 -0
  20. package/ios/HotUpdater/Internal/HotUpdater.mm +12 -2
  21. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +64 -5
  22. package/ios/HotUpdater/Internal/NotificationExtension.swift +2 -1
  23. package/ios/HotUpdater/Internal/SignatureVerifier.swift +28 -5
  24. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +11 -0
  25. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +11 -5
  26. package/lib/commonjs/DefaultResolver.js +18 -4
  27. package/lib/commonjs/DefaultResolver.js.map +1 -1
  28. package/lib/commonjs/DefaultResolver.spec.js +136 -0
  29. package/lib/commonjs/DefaultResolver.spec.js.map +1 -0
  30. package/lib/commonjs/checkForUpdate.js +6 -0
  31. package/lib/commonjs/checkForUpdate.js.map +1 -1
  32. package/lib/commonjs/index.js +53 -18
  33. package/lib/commonjs/index.js.map +1 -1
  34. package/lib/commonjs/index.spec.js +133 -1
  35. package/lib/commonjs/index.spec.js.map +1 -1
  36. package/lib/commonjs/native.js +40 -16
  37. package/lib/commonjs/native.js.map +1 -1
  38. package/lib/commonjs/native.spec.js +119 -3
  39. package/lib/commonjs/native.spec.js.map +1 -1
  40. package/lib/commonjs/sdkVersion.js +8 -0
  41. package/lib/commonjs/sdkVersion.js.map +1 -0
  42. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  43. package/lib/commonjs/store.js +72 -5
  44. package/lib/commonjs/store.js.map +1 -1
  45. package/lib/commonjs/store.spec.js +265 -8
  46. package/lib/commonjs/store.spec.js.map +1 -1
  47. package/lib/commonjs/types.js.map +1 -1
  48. package/lib/commonjs/wrap.js +19 -5
  49. package/lib/commonjs/wrap.js.map +1 -1
  50. package/lib/commonjs/wrap.spec.js +74 -0
  51. package/lib/commonjs/wrap.spec.js.map +1 -1
  52. package/lib/module/DefaultResolver.js +18 -4
  53. package/lib/module/DefaultResolver.js.map +1 -1
  54. package/lib/module/DefaultResolver.spec.js +135 -0
  55. package/lib/module/DefaultResolver.spec.js.map +1 -0
  56. package/lib/module/checkForUpdate.js +6 -0
  57. package/lib/module/checkForUpdate.js.map +1 -1
  58. package/lib/module/index.js +53 -18
  59. package/lib/module/index.js.map +1 -1
  60. package/lib/module/index.spec.js +133 -1
  61. package/lib/module/index.spec.js.map +1 -1
  62. package/lib/module/native.js +40 -16
  63. package/lib/module/native.js.map +1 -1
  64. package/lib/module/native.spec.js +119 -3
  65. package/lib/module/native.spec.js.map +1 -1
  66. package/lib/module/sdkVersion.js +4 -0
  67. package/lib/module/sdkVersion.js.map +1 -0
  68. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  69. package/lib/module/store.js +72 -5
  70. package/lib/module/store.js.map +1 -1
  71. package/lib/module/store.spec.js +265 -8
  72. package/lib/module/store.spec.js.map +1 -1
  73. package/lib/module/types.js.map +1 -1
  74. package/lib/module/wrap.js +19 -5
  75. package/lib/module/wrap.js.map +1 -1
  76. package/lib/module/wrap.spec.js +74 -0
  77. package/lib/module/wrap.spec.js.map +1 -1
  78. package/lib/typescript/commonjs/DefaultResolver.d.ts +4 -3
  79. package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -2
  81. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/index.d.ts +37 -9
  83. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/native.d.ts +56 -3
  85. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/sdkVersion.d.ts +2 -0
  87. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -0
  88. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +27 -0
  89. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  90. package/lib/typescript/commonjs/store.d.ts +5 -0
  91. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/types.d.ts +1 -0
  93. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/wrap.d.ts +48 -32
  95. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  96. package/lib/typescript/module/DefaultResolver.d.ts +4 -3
  97. package/lib/typescript/module/DefaultResolver.d.ts.map +1 -1
  98. package/lib/typescript/module/checkForUpdate.d.ts +2 -2
  99. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  100. package/lib/typescript/module/index.d.ts +37 -9
  101. package/lib/typescript/module/index.d.ts.map +1 -1
  102. package/lib/typescript/module/native.d.ts +56 -3
  103. package/lib/typescript/module/native.d.ts.map +1 -1
  104. package/lib/typescript/module/sdkVersion.d.ts +2 -0
  105. package/lib/typescript/module/sdkVersion.d.ts.map +1 -0
  106. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +27 -0
  107. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  108. package/lib/typescript/module/store.d.ts +5 -0
  109. package/lib/typescript/module/store.d.ts.map +1 -1
  110. package/lib/typescript/module/types.d.ts +1 -0
  111. package/lib/typescript/module/types.d.ts.map +1 -1
  112. package/lib/typescript/module/wrap.d.ts +48 -32
  113. package/lib/typescript/module/wrap.d.ts.map +1 -1
  114. package/package.json +17 -9
  115. package/src/DefaultResolver.spec.ts +178 -0
  116. package/src/DefaultResolver.ts +32 -6
  117. package/src/checkForUpdate.ts +9 -2
  118. package/src/index.spec.ts +170 -2
  119. package/src/index.ts +100 -40
  120. package/src/native.spec.ts +139 -1
  121. package/src/native.ts +145 -31
  122. package/src/sdkVersion.ts +1 -0
  123. package/src/specs/NativeHotUpdater.ts +28 -0
  124. package/src/store.spec.ts +324 -9
  125. package/src/store.ts +133 -7
  126. package/src/types.ts +2 -0
  127. package/src/wrap.spec.ts +91 -0
  128. package/src/wrap.tsx +84 -45
@@ -17,6 +17,7 @@ Pod::Spec.new do |s|
17
17
  s.public_header_files = "ios/HotUpdater/Public/*.h"
18
18
  s.private_header_files = "ios/HotUpdater/Internal/*.h"
19
19
  s.exclude_files = ["ios/HotUpdater/Package.swift", "ios/HotUpdater/Test/**/*.{swift,h,m,mm}"]
20
+ s.libraries = "bz2"
20
21
 
21
22
  s.pod_target_xcconfig = {
22
23
  "DEFINES_MODULE" => "YES",
@@ -144,6 +144,7 @@ dependencies {
144
144
  implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
145
145
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
146
146
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
147
+ implementation "org.apache.commons:commons-compress:1.27.1"
147
148
  implementation "com.squareup.okhttp3:okhttp:4.12.0"
148
149
  implementation files('libs/org.brotli.dec-1.2.0.jar')
149
150
  testImplementation "junit:junit:4.13.2"
@@ -0,0 +1,284 @@
1
+ package com.hotupdater
2
+
3
+ import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream
4
+ import java.io.ByteArrayInputStream
5
+ import java.io.ByteArrayOutputStream
6
+ import java.io.EOFException
7
+ import java.io.File
8
+ import java.io.FileInputStream
9
+ import java.io.FileOutputStream
10
+ import java.io.InputStream
11
+ import java.io.OutputStream
12
+ import java.io.RandomAccessFile
13
+ import kotlin.math.min
14
+
15
+ object BsdiffPatch {
16
+ private const val HEADER = "ENDSLEY/BSDIFF43"
17
+ private val HEADER_BYTES = HEADER.toByteArray(Charsets.US_ASCII)
18
+ private const val HEADER_SIZE = 24
19
+ private const val STREAM_BUFFER_SIZE = 64 * 1024
20
+
21
+ fun apply(
22
+ baseFile: File,
23
+ patchFile: File,
24
+ outputFile: File,
25
+ ) {
26
+ outputFile.parentFile?.mkdirs()
27
+ val tempOutputFile =
28
+ File(
29
+ outputFile.parentFile ?: outputFile.absoluteFile.parentFile,
30
+ "${outputFile.name}.tmp",
31
+ )
32
+
33
+ if (tempOutputFile.exists()) {
34
+ tempOutputFile.delete()
35
+ }
36
+
37
+ try {
38
+ RandomAccessFileBaseReader(RandomAccessFile(baseFile, "r")).use { baseReader ->
39
+ FileInputStream(patchFile).use { patchStream ->
40
+ FileOutputStream(tempOutputFile).use { outputStream ->
41
+ apply(baseReader, patchStream, outputStream)
42
+ }
43
+ }
44
+ }
45
+
46
+ if (outputFile.exists()) {
47
+ outputFile.delete()
48
+ }
49
+ if (!tempOutputFile.renameTo(outputFile)) {
50
+ tempOutputFile.copyTo(outputFile, overwrite = true)
51
+ tempOutputFile.delete()
52
+ }
53
+ } catch (error: Throwable) {
54
+ tempOutputFile.delete()
55
+ throw error
56
+ }
57
+ }
58
+
59
+ fun apply(
60
+ baseBytes: ByteArray,
61
+ patchBytes: ByteArray,
62
+ ): ByteArray {
63
+ RandomAccessByteArray(baseBytes).use { baseReader ->
64
+ ByteArrayInputStream(patchBytes).use { patchStream ->
65
+ ByteArrayOutputStream().use { outputStream ->
66
+ apply(baseReader, patchStream, outputStream)
67
+ return outputStream.toByteArray()
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ private fun apply(
74
+ baseReader: BaseReader,
75
+ patchStream: InputStream,
76
+ outputStream: OutputStream,
77
+ ) {
78
+ val header = ByteArray(HEADER_SIZE)
79
+ readExactly(patchStream, header)
80
+
81
+ if (!header.copyOfRange(0, 16).contentEquals(HEADER_BYTES)) {
82
+ throw IllegalArgumentException("Invalid ENDSLEY/BSDIFF43 header")
83
+ }
84
+
85
+ val newSize = readOfft(header, 16)
86
+ if (newSize < 0) {
87
+ throw IllegalArgumentException("Negative ENDSLEY/BSDIFF43 target size")
88
+ }
89
+
90
+ val diffBuffer = ByteArray(STREAM_BUFFER_SIZE)
91
+ val baseBuffer = ByteArray(STREAM_BUFFER_SIZE)
92
+ val newBuffer = ByteArray(STREAM_BUFFER_SIZE)
93
+ var oldPos = 0L
94
+ var outputSize = 0L
95
+
96
+ BZip2CompressorInputStream(patchStream, true).use { patchReader ->
97
+ while (outputSize < newSize) {
98
+ val controlBytes = ByteArray(24)
99
+ readExactly(patchReader, controlBytes)
100
+
101
+ val addLen = readOfft(controlBytes, 0)
102
+ val copyLen = readOfft(controlBytes, 8)
103
+ val seekLen = readOfft(controlBytes, 16)
104
+ if (addLen < 0 || copyLen < 0) {
105
+ throw IllegalArgumentException(
106
+ "Negative add/copy length in control block",
107
+ )
108
+ }
109
+
110
+ val addCount = checkedInt(addLen, "add length overflow")
111
+ val copyCount = checkedInt(copyLen, "copy length overflow")
112
+ val remainingOutput = newSize - outputSize
113
+ if (addLen > remainingOutput || copyLen > remainingOutput - addLen) {
114
+ throw IllegalArgumentException("ENDSLEY/BSDIFF43 stream is truncated")
115
+ }
116
+
117
+ var remainingAdd = addCount
118
+ while (remainingAdd > 0) {
119
+ val chunkSize = min(STREAM_BUFFER_SIZE, remainingAdd)
120
+ readExactly(patchReader, diffBuffer, chunkSize)
121
+ baseReader.readAt(oldPos, baseBuffer, chunkSize)
122
+ for (index in 0 until chunkSize) {
123
+ val oldByte = baseBuffer[index].toInt() and 0xFF
124
+ val deltaByte = diffBuffer[index].toInt() and 0xFF
125
+ newBuffer[index] = ((deltaByte + oldByte) and 0xFF).toByte()
126
+ }
127
+ outputStream.write(newBuffer, 0, chunkSize)
128
+ oldPos += chunkSize
129
+ outputSize += chunkSize
130
+ remainingAdd -= chunkSize
131
+ }
132
+
133
+ var remainingCopy = copyCount
134
+ while (remainingCopy > 0) {
135
+ val chunkSize = min(STREAM_BUFFER_SIZE, remainingCopy)
136
+ readExactly(patchReader, diffBuffer, chunkSize)
137
+ outputStream.write(diffBuffer, 0, chunkSize)
138
+ outputSize += chunkSize
139
+ remainingCopy -= chunkSize
140
+ }
141
+
142
+ oldPos =
143
+ checkedAdd(
144
+ oldPos,
145
+ seekLen,
146
+ "old file seek overflow",
147
+ )
148
+ }
149
+ }
150
+
151
+ if (outputSize != newSize) {
152
+ throw IllegalArgumentException("Patch output length mismatch")
153
+ }
154
+ }
155
+
156
+ private fun checkedInt(
157
+ value: Long,
158
+ message: String,
159
+ ): Int {
160
+ if (value > Int.MAX_VALUE) {
161
+ throw IllegalArgumentException(message)
162
+ }
163
+ return value.toInt()
164
+ }
165
+
166
+ private fun checkedAdd(
167
+ left: Long,
168
+ right: Long,
169
+ message: String,
170
+ ): Long {
171
+ val result = left + right
172
+ if ((left xor result) and (right xor result) < 0) {
173
+ throw IllegalArgumentException(message)
174
+ }
175
+ return result
176
+ }
177
+
178
+ private fun readExactly(
179
+ input: InputStream,
180
+ target: ByteArray,
181
+ ) = readExactly(input, target, target.size)
182
+
183
+ private fun readExactly(
184
+ input: InputStream,
185
+ target: ByteArray,
186
+ count: Int,
187
+ ) {
188
+ var offset = 0
189
+ while (offset < count) {
190
+ val read = input.read(target, offset, count - offset)
191
+ if (read < 0) {
192
+ throw EOFException("Unexpected end of ENDSLEY/BSDIFF43 stream")
193
+ }
194
+ offset += read
195
+ }
196
+ }
197
+
198
+ private fun readOfft(
199
+ bytes: ByteArray,
200
+ offset: Int,
201
+ ): Long {
202
+ if (bytes.size - offset < 8) {
203
+ throw IllegalArgumentException("Offset bytes too short")
204
+ }
205
+
206
+ var value = 0L
207
+ for (index in 0 until 8) {
208
+ value = value or ((bytes[offset + index].toLong() and 0xFF) shl (index * 8))
209
+ }
210
+
211
+ return if ((value and Long.MIN_VALUE) == 0L) {
212
+ value
213
+ } else {
214
+ -(value and Long.MAX_VALUE)
215
+ }
216
+ }
217
+
218
+ private interface BaseReader : AutoCloseable {
219
+ fun readAt(
220
+ offset: Long,
221
+ target: ByteArray,
222
+ count: Int,
223
+ )
224
+ }
225
+
226
+ private class RandomAccessFileBaseReader(
227
+ private val file: RandomAccessFile,
228
+ ) : BaseReader {
229
+ private val length = file.length()
230
+
231
+ override fun readAt(
232
+ offset: Long,
233
+ target: ByteArray,
234
+ count: Int,
235
+ ) {
236
+ fillBaseBytes(offset, length, target, count) { position, buffer, bufferOffset, length ->
237
+ file.seek(position)
238
+ file.readFully(buffer, bufferOffset, length)
239
+ }
240
+ }
241
+
242
+ override fun close() = file.close()
243
+ }
244
+
245
+ private class RandomAccessByteArray(
246
+ private val bytes: ByteArray,
247
+ ) : BaseReader {
248
+ override fun readAt(
249
+ offset: Long,
250
+ target: ByteArray,
251
+ count: Int,
252
+ ) {
253
+ fillBaseBytes(offset, bytes.size.toLong(), target, count) { position, buffer, bufferOffset, length ->
254
+ bytes.copyInto(
255
+ destination = buffer,
256
+ destinationOffset = bufferOffset,
257
+ startIndex = position.toInt(),
258
+ endIndex = position.toInt() + length,
259
+ )
260
+ }
261
+ }
262
+
263
+ override fun close() = Unit
264
+ }
265
+
266
+ private inline fun fillBaseBytes(
267
+ offset: Long,
268
+ baseLength: Long,
269
+ target: ByteArray,
270
+ count: Int,
271
+ readValidRange: (position: Long, buffer: ByteArray, bufferOffset: Int, length: Int) -> Unit,
272
+ ) {
273
+ target.fill(0, 0, count)
274
+ if (count == 0 || offset >= baseLength || offset + count <= 0) {
275
+ return
276
+ }
277
+
278
+ val validStart = maxOf(offset, 0)
279
+ val validEnd = minOf(offset + count, baseLength)
280
+ val destinationOffset = (validStart - offset).toInt()
281
+ val validLength = (validEnd - validStart).toInt()
282
+ readValidRange(validStart, target, destinationOffset, validLength)
283
+ }
284
+ }