@hot-updater/react-native 0.30.12 → 0.31.1
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.
- package/HotUpdater.podspec +1 -0
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/hotupdater/BsdiffPatch.kt +284 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +885 -5
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +13 -2
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +37 -8
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +48 -12
- package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +60 -22
- package/android/src/newarch/HotUpdater.kt +14 -2
- package/android/src/newarch/HotUpdaterModule.kt +112 -1
- package/android/src/oldarch/HotUpdater.kt +14 -2
- package/android/src/oldarch/HotUpdaterModule.kt +107 -3
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +46 -0
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +29 -1
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -1
- package/ios/HotUpdater/Internal/BsdiffPatchBridge.h +16 -0
- package/ios/HotUpdater/Internal/BsdiffPatchBridge.mm +343 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1093 -47
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +2 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +12 -2
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +64 -5
- package/ios/HotUpdater/Internal/NotificationExtension.swift +2 -1
- package/ios/HotUpdater/Internal/SignatureVerifier.swift +28 -5
- package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +11 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +11 -5
- package/lib/commonjs/DefaultResolver.js +6 -2
- package/lib/commonjs/DefaultResolver.js.map +1 -1
- package/lib/commonjs/DefaultResolver.spec.js +79 -3
- package/lib/commonjs/DefaultResolver.spec.js.map +1 -1
- package/lib/commonjs/checkForUpdate.js +6 -0
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +53 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +117 -1
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +40 -16
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +119 -3
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +8 -0
- package/lib/commonjs/sdkVersion.js.map +1 -0
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/store.js +72 -5
- package/lib/commonjs/store.js.map +1 -1
- package/lib/commonjs/store.spec.js +265 -8
- package/lib/commonjs/store.spec.js.map +1 -1
- package/lib/commonjs/wrap.js +19 -5
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +74 -0
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/DefaultResolver.js +6 -2
- package/lib/module/DefaultResolver.js.map +1 -1
- package/lib/module/DefaultResolver.spec.js +78 -3
- package/lib/module/DefaultResolver.spec.js.map +1 -1
- package/lib/module/checkForUpdate.js +6 -0
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +53 -18
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +117 -1
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +40 -16
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +119 -3
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/sdkVersion.js +4 -0
- package/lib/module/sdkVersion.js.map +1 -0
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/store.js +72 -5
- package/lib/module/store.js.map +1 -1
- package/lib/module/store.spec.js +265 -8
- package/lib/module/store.spec.js.map +1 -1
- package/lib/module/wrap.js +19 -5
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +74 -0
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -2
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +36 -8
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +56 -3
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +2 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +27 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/store.d.ts +5 -0
- package/lib/typescript/commonjs/store.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +43 -29
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/module/DefaultResolver.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts +2 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +36 -8
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +56 -3
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +2 -0
- package/lib/typescript/module/sdkVersion.d.ts.map +1 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +27 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/store.d.ts +5 -0
- package/lib/typescript/module/store.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +43 -29
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/package.json +20 -10
- package/src/DefaultResolver.spec.ts +103 -3
- package/src/DefaultResolver.ts +9 -2
- package/src/checkForUpdate.ts +9 -2
- package/src/index.spec.ts +149 -1
- package/src/index.ts +99 -40
- package/src/native.spec.ts +139 -1
- package/src/native.ts +145 -31
- package/src/sdkVersion.ts +1 -0
- package/src/specs/NativeHotUpdater.ts +28 -0
- package/src/store.spec.ts +324 -9
- package/src/store.ts +133 -7
- package/src/wrap.spec.ts +91 -0
- package/src/wrap.tsx +79 -42
package/HotUpdater.podspec
CHANGED
|
@@ -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",
|
package/android/build.gradle
CHANGED
|
@@ -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
|
+
}
|