@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
|
@@ -282,10 +282,21 @@ class HotUpdaterImpl {
|
|
|
282
282
|
bundleId: String,
|
|
283
283
|
fileUrl: String?,
|
|
284
284
|
fileHash: String?,
|
|
285
|
+
manifestUrl: String?,
|
|
286
|
+
manifestFileHash: String?,
|
|
287
|
+
changedAssets: Map<String, ChangedAssetDescriptor>?,
|
|
285
288
|
channel: String?,
|
|
286
|
-
progressCallback: (
|
|
289
|
+
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
287
290
|
) {
|
|
288
|
-
bundleStorage.updateBundle(
|
|
291
|
+
bundleStorage.updateBundle(
|
|
292
|
+
bundleId,
|
|
293
|
+
fileUrl,
|
|
294
|
+
fileHash,
|
|
295
|
+
manifestUrl,
|
|
296
|
+
manifestFileHash,
|
|
297
|
+
changedAssets,
|
|
298
|
+
progressCallback,
|
|
299
|
+
)
|
|
289
300
|
|
|
290
301
|
if (!channel.isNullOrEmpty()) {
|
|
291
302
|
if (channel == getDefaultChannel()) {
|
|
@@ -44,6 +44,12 @@ sealed class DownloadResult {
|
|
|
44
44
|
/**
|
|
45
45
|
* Interface for download operations
|
|
46
46
|
*/
|
|
47
|
+
data class DownloadProgress(
|
|
48
|
+
val progress: Double,
|
|
49
|
+
val downloadedBytes: Long,
|
|
50
|
+
val totalBytes: Long? = null,
|
|
51
|
+
)
|
|
52
|
+
|
|
47
53
|
interface DownloadService {
|
|
48
54
|
/**
|
|
49
55
|
* Downloads a file from a URL
|
|
@@ -57,7 +63,7 @@ interface DownloadService {
|
|
|
57
63
|
fileUrl: URL,
|
|
58
64
|
destination: File,
|
|
59
65
|
fileSizeCallback: ((Long) -> Unit)? = null,
|
|
60
|
-
progressCallback: (
|
|
66
|
+
progressCallback: (DownloadProgress) -> Unit,
|
|
61
67
|
): DownloadResult
|
|
62
68
|
}
|
|
63
69
|
|
|
@@ -66,7 +72,7 @@ interface DownloadService {
|
|
|
66
72
|
*/
|
|
67
73
|
private class ProgressResponseBody(
|
|
68
74
|
private val responseBody: ResponseBody,
|
|
69
|
-
private val progressCallback: (
|
|
75
|
+
private val progressCallback: (DownloadProgress) -> Unit,
|
|
70
76
|
) : ResponseBody() {
|
|
71
77
|
private var bufferedSource: BufferedSource? = null
|
|
72
78
|
|
|
@@ -95,8 +101,25 @@ private class ProgressResponseBody(
|
|
|
95
101
|
val currentTime = System.currentTimeMillis()
|
|
96
102
|
|
|
97
103
|
if (currentTime - lastProgressTime >= 100) {
|
|
98
|
-
val
|
|
99
|
-
|
|
104
|
+
val totalBytes = contentLength()
|
|
105
|
+
if (totalBytes > 0) {
|
|
106
|
+
val progress = totalBytesRead.toDouble() / totalBytes
|
|
107
|
+
progressCallback.invoke(
|
|
108
|
+
DownloadProgress(
|
|
109
|
+
progress = progress,
|
|
110
|
+
downloadedBytes = totalBytesRead,
|
|
111
|
+
totalBytes = totalBytes,
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
} else {
|
|
115
|
+
progressCallback.invoke(
|
|
116
|
+
DownloadProgress(
|
|
117
|
+
progress = 0.0,
|
|
118
|
+
downloadedBytes = totalBytesRead,
|
|
119
|
+
totalBytes = null,
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
}
|
|
100
123
|
lastProgressTime = currentTime
|
|
101
124
|
}
|
|
102
125
|
return bytesRead
|
|
@@ -127,7 +150,7 @@ class OkHttpDownloadService : DownloadService {
|
|
|
127
150
|
fileUrl: URL,
|
|
128
151
|
destination: File,
|
|
129
152
|
fileSizeCallback: ((Long) -> Unit)?,
|
|
130
|
-
progressCallback: (
|
|
153
|
+
progressCallback: (DownloadProgress) -> Unit,
|
|
131
154
|
): DownloadResult =
|
|
132
155
|
withContext(Dispatchers.IO) {
|
|
133
156
|
var attempt = 0
|
|
@@ -166,7 +189,7 @@ class OkHttpDownloadService : DownloadService {
|
|
|
166
189
|
fileUrl: URL,
|
|
167
190
|
destination: File,
|
|
168
191
|
fileSizeCallback: ((Long) -> Unit)?,
|
|
169
|
-
progressCallback: (
|
|
192
|
+
progressCallback: (DownloadProgress) -> Unit,
|
|
170
193
|
): DownloadResult =
|
|
171
194
|
withContext(Dispatchers.IO) {
|
|
172
195
|
// Make sure parent directories exist
|
|
@@ -235,7 +258,7 @@ class OkHttpDownloadService : DownloadService {
|
|
|
235
258
|
|
|
236
259
|
// Verify file size
|
|
237
260
|
val finalSize = destination.length()
|
|
238
|
-
if (totalSize
|
|
261
|
+
if (totalSize >= 0 && finalSize != totalSize) {
|
|
239
262
|
Log.d(TAG, "Download incomplete: $finalSize / $totalSize bytes")
|
|
240
263
|
|
|
241
264
|
// Delete incomplete file
|
|
@@ -249,7 +272,13 @@ class OkHttpDownloadService : DownloadService {
|
|
|
249
272
|
}
|
|
250
273
|
|
|
251
274
|
Log.d(TAG, "Download completed successfully: $finalSize bytes")
|
|
252
|
-
progressCallback.invoke(
|
|
275
|
+
progressCallback.invoke(
|
|
276
|
+
DownloadProgress(
|
|
277
|
+
progress = 1.0,
|
|
278
|
+
downloadedBytes = finalSize,
|
|
279
|
+
totalBytes = if (totalSize > 0) totalSize else null,
|
|
280
|
+
),
|
|
281
|
+
)
|
|
253
282
|
DownloadResult.Success(destination)
|
|
254
283
|
} catch (e: Exception) {
|
|
255
284
|
response.close()
|
|
@@ -26,30 +26,66 @@ private fun WritableMap.putReactValue(
|
|
|
26
26
|
value: Any?,
|
|
27
27
|
) {
|
|
28
28
|
when (value) {
|
|
29
|
-
null ->
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
null -> {
|
|
30
|
+
putNull(key)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
is Boolean -> {
|
|
34
|
+
putBoolean(key, value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
is Number -> {
|
|
38
|
+
putDouble(key, value.toDouble())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
is String -> {
|
|
42
|
+
putString(key, value)
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
is Map<*, *> -> {
|
|
34
46
|
@Suppress("UNCHECKED_CAST")
|
|
35
47
|
putMap(key, (value as Map<String, Any?>).toWritableNativeMap())
|
|
36
48
|
}
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
|
|
50
|
+
is List<*> -> {
|
|
51
|
+
putArray(key, value.toWritableNativeArray())
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
else -> {
|
|
55
|
+
putString(key, value.toString())
|
|
56
|
+
}
|
|
39
57
|
}
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
private fun WritableArray.pushReactValue(value: Any?) {
|
|
43
61
|
when (value) {
|
|
44
|
-
null ->
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
null -> {
|
|
63
|
+
pushNull()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
is Boolean -> {
|
|
67
|
+
pushBoolean(value)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
is Number -> {
|
|
71
|
+
pushDouble(value.toDouble())
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
is String -> {
|
|
75
|
+
pushString(value)
|
|
76
|
+
}
|
|
77
|
+
|
|
48
78
|
is Map<*, *> -> {
|
|
49
79
|
@Suppress("UNCHECKED_CAST")
|
|
50
80
|
pushMap((value as Map<String, Any?>).toWritableNativeMap())
|
|
51
81
|
}
|
|
52
|
-
|
|
53
|
-
|
|
82
|
+
|
|
83
|
+
is List<*> -> {
|
|
84
|
+
pushArray(value.toWritableNativeArray())
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
else -> {
|
|
88
|
+
pushString(value.toString())
|
|
89
|
+
}
|
|
54
90
|
}
|
|
55
91
|
}
|
|
@@ -247,30 +247,36 @@ object SignatureVerifier {
|
|
|
247
247
|
|
|
248
248
|
Log.d(TAG, "Calculated file hash: $fileHashHex")
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// Convert hex fileHash to bytes
|
|
260
|
-
val fileHashBytes = hexToByteArray(fileHashHex)
|
|
250
|
+
verifySignatureForHash(publicKey, fileHashHex, signatureBase64)
|
|
251
|
+
} catch (e: SignatureVerificationException) {
|
|
252
|
+
throw e
|
|
253
|
+
} catch (e: Exception) {
|
|
254
|
+
Log.e(TAG, "Signature verification error", e)
|
|
255
|
+
throw SignatureVerificationException.SecurityFrameworkError(e)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
261
258
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Verifies an RSA-SHA256 signature for an explicit SHA256 file hash.
|
|
261
|
+
*/
|
|
262
|
+
fun verifyHashSignature(
|
|
263
|
+
context: Context,
|
|
264
|
+
fileHash: String,
|
|
265
|
+
signatureBase64: String,
|
|
266
|
+
) {
|
|
267
|
+
val publicKeyPEM =
|
|
268
|
+
getPublicKeyFromConfig(context)
|
|
269
|
+
?: run {
|
|
270
|
+
Log.e(TAG, "Cannot verify signature: public key not configured in strings.xml")
|
|
271
|
+
throw SignatureVerificationException.PublicKeyNotConfigured()
|
|
272
|
+
}
|
|
267
273
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
try {
|
|
275
|
+
verifySignatureForHash(
|
|
276
|
+
createPublicKey(publicKeyPEM),
|
|
277
|
+
fileHash,
|
|
278
|
+
signatureBase64,
|
|
279
|
+
)
|
|
274
280
|
} catch (e: SignatureVerificationException) {
|
|
275
281
|
throw e
|
|
276
282
|
} catch (e: Exception) {
|
|
@@ -279,6 +285,38 @@ object SignatureVerifier {
|
|
|
279
285
|
}
|
|
280
286
|
}
|
|
281
287
|
|
|
288
|
+
private fun verifySignatureForHash(
|
|
289
|
+
publicKey: PublicKey,
|
|
290
|
+
fileHash: String,
|
|
291
|
+
signatureBase64: String,
|
|
292
|
+
) {
|
|
293
|
+
if (signatureBase64.isEmpty()) {
|
|
294
|
+
throw SignatureVerificationException.InvalidSignatureFormat()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
val signatureBytes =
|
|
298
|
+
try {
|
|
299
|
+
Base64.decode(signatureBase64, Base64.DEFAULT)
|
|
300
|
+
} catch (e: Exception) {
|
|
301
|
+
Log.e(TAG, "Failed to decode signature from base64", e)
|
|
302
|
+
throw SignatureVerificationException.InvalidSignatureFormat()
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
val fileHashBytes = hexToByteArray(fileHash)
|
|
306
|
+
|
|
307
|
+
val verifier = Signature.getInstance("SHA256withRSA")
|
|
308
|
+
verifier.initVerify(publicKey)
|
|
309
|
+
verifier.update(fileHashBytes)
|
|
310
|
+
val isValid = verifier.verify(signatureBytes)
|
|
311
|
+
|
|
312
|
+
if (isValid) {
|
|
313
|
+
Log.i(TAG, "✅ Signature verified successfully")
|
|
314
|
+
} else {
|
|
315
|
+
Log.e(TAG, "❌ Signature verification failed")
|
|
316
|
+
throw SignatureVerificationException.SignatureVerificationFailed()
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
282
320
|
/**
|
|
283
321
|
* Converts PEM-formatted public key to PublicKey.
|
|
284
322
|
* @param publicKeyPEM Public key in PEM format
|
|
@@ -46,10 +46,22 @@ class HotUpdater {
|
|
|
46
46
|
bundleId: String,
|
|
47
47
|
fileUrl: String?,
|
|
48
48
|
fileHash: String?,
|
|
49
|
+
manifestUrl: String?,
|
|
50
|
+
manifestFileHash: String?,
|
|
51
|
+
changedAssets: Map<String, ChangedAssetDescriptor>?,
|
|
49
52
|
channel: String?,
|
|
50
|
-
progressCallback: (
|
|
53
|
+
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
51
54
|
) {
|
|
52
|
-
getInstance(context).updateBundle(
|
|
55
|
+
getInstance(context).updateBundle(
|
|
56
|
+
bundleId,
|
|
57
|
+
fileUrl,
|
|
58
|
+
fileHash,
|
|
59
|
+
manifestUrl,
|
|
60
|
+
manifestFileHash,
|
|
61
|
+
changedAssets,
|
|
62
|
+
channel,
|
|
63
|
+
progressCallback,
|
|
64
|
+
)
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
/**
|
|
@@ -37,6 +37,76 @@ class HotUpdaterModule internal constructor(
|
|
|
37
37
|
*/
|
|
38
38
|
private fun getInstance(): HotUpdaterImpl = HotUpdater.getInstance(mReactApplicationContext)
|
|
39
39
|
|
|
40
|
+
private fun parseChangedAssets(params: ReadableMap): Map<String, ChangedAssetDescriptor>? {
|
|
41
|
+
if (!params.hasKey("changedAssets") || params.isNull("changedAssets")) {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
val changedAssetsMap = params.getMap("changedAssets") ?: return null
|
|
46
|
+
val parsedAssets = linkedMapOf<String, ChangedAssetDescriptor>()
|
|
47
|
+
val iterator = changedAssetsMap.keySetIterator()
|
|
48
|
+
|
|
49
|
+
while (iterator.hasNextKey()) {
|
|
50
|
+
val assetPath = iterator.nextKey()
|
|
51
|
+
val assetMap = changedAssetsMap.getMap(assetPath) ?: continue
|
|
52
|
+
val assetHash = assetMap.getString("fileHash") ?: continue
|
|
53
|
+
val patchMap = assetMap.getMap("patch")
|
|
54
|
+
val patch =
|
|
55
|
+
if (patchMap != null) {
|
|
56
|
+
val algorithm = patchMap.getString("algorithm")
|
|
57
|
+
val baseBundleId = patchMap.getString("baseBundleId")
|
|
58
|
+
val baseFileHash = patchMap.getString("baseFileHash")
|
|
59
|
+
val patchFileHash = patchMap.getString("patchFileHash")
|
|
60
|
+
val patchUrl = patchMap.getString("patchUrl")
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
algorithm != null &&
|
|
64
|
+
baseBundleId != null &&
|
|
65
|
+
baseFileHash != null &&
|
|
66
|
+
patchFileHash != null &&
|
|
67
|
+
patchUrl != null
|
|
68
|
+
) {
|
|
69
|
+
BsdiffPatchDescriptor(
|
|
70
|
+
algorithm = algorithm,
|
|
71
|
+
baseBundleId = baseBundleId,
|
|
72
|
+
baseFileHash = baseFileHash,
|
|
73
|
+
patchFileHash = patchFileHash,
|
|
74
|
+
patchUrl = patchUrl,
|
|
75
|
+
)
|
|
76
|
+
} else {
|
|
77
|
+
null
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
null
|
|
81
|
+
}
|
|
82
|
+
val fileMap =
|
|
83
|
+
if (assetMap.hasKey("file") && !assetMap.isNull("file")) {
|
|
84
|
+
assetMap.getMap("file")
|
|
85
|
+
} else {
|
|
86
|
+
null
|
|
87
|
+
}
|
|
88
|
+
val assetUrl = fileMap?.getString("url")
|
|
89
|
+
val fileCompression =
|
|
90
|
+
if (fileMap != null && fileMap.hasKey("compression") && !fileMap.isNull("compression")) {
|
|
91
|
+
fileMap.getString("compression")
|
|
92
|
+
} else {
|
|
93
|
+
null
|
|
94
|
+
}
|
|
95
|
+
if (assetUrl == null && patch == null) {
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
parsedAssets[assetPath] =
|
|
99
|
+
ChangedAssetDescriptor(
|
|
100
|
+
fileUrl = assetUrl,
|
|
101
|
+
fileHash = assetHash,
|
|
102
|
+
fileCompression = fileCompression,
|
|
103
|
+
patch = patch,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return parsedAssets
|
|
108
|
+
}
|
|
109
|
+
|
|
40
110
|
override fun reload(promise: Promise) {
|
|
41
111
|
moduleScope.launch {
|
|
42
112
|
try {
|
|
@@ -88,6 +158,9 @@ class HotUpdaterModule internal constructor(
|
|
|
88
158
|
}
|
|
89
159
|
|
|
90
160
|
val fileHash = params.getString("fileHash")
|
|
161
|
+
val manifestUrl = params.getString("manifestUrl")
|
|
162
|
+
val manifestFileHash = params.getString("manifestFileHash")
|
|
163
|
+
val changedAssets = parseChangedAssets(params)
|
|
91
164
|
val channel = params.getString("channel")
|
|
92
165
|
|
|
93
166
|
val impl = getInstance()
|
|
@@ -96,6 +169,9 @@ class HotUpdaterModule internal constructor(
|
|
|
96
169
|
bundleId,
|
|
97
170
|
fileUrl,
|
|
98
171
|
fileHash,
|
|
172
|
+
manifestUrl,
|
|
173
|
+
manifestFileHash,
|
|
174
|
+
changedAssets,
|
|
99
175
|
channel,
|
|
100
176
|
) { progress ->
|
|
101
177
|
// Post to Main thread for React Native event emission
|
|
@@ -103,7 +179,42 @@ class HotUpdaterModule internal constructor(
|
|
|
103
179
|
try {
|
|
104
180
|
val progressParams =
|
|
105
181
|
WritableNativeMap().apply {
|
|
106
|
-
putDouble("progress", progress)
|
|
182
|
+
putDouble("progress", progress.progress)
|
|
183
|
+
putString("artifactType", progress.artifactType)
|
|
184
|
+
progress.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
|
|
185
|
+
progress.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
|
|
186
|
+
progress.details?.let { details ->
|
|
187
|
+
putMap(
|
|
188
|
+
"details",
|
|
189
|
+
WritableNativeMap().apply {
|
|
190
|
+
putInt("totalFilesCount", details.totalFilesCount)
|
|
191
|
+
putInt("completedFilesCount", details.completedFilesCount)
|
|
192
|
+
putArray(
|
|
193
|
+
"files",
|
|
194
|
+
WritableNativeArray().apply {
|
|
195
|
+
details.files.forEach { file ->
|
|
196
|
+
pushMap(
|
|
197
|
+
WritableNativeMap().apply {
|
|
198
|
+
putString("path", file.path)
|
|
199
|
+
putString("downloadPath", file.downloadPath)
|
|
200
|
+
putString("status", file.status)
|
|
201
|
+
putDouble("progress", file.progress)
|
|
202
|
+
putInt("order", file.order)
|
|
203
|
+
file.downloadedBytes?.let {
|
|
204
|
+
putDouble(
|
|
205
|
+
"downloadedBytes",
|
|
206
|
+
it.toDouble(),
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
file.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
|
|
210
|
+
},
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
)
|
|
215
|
+
},
|
|
216
|
+
)
|
|
217
|
+
}
|
|
107
218
|
}
|
|
108
219
|
|
|
109
220
|
this@HotUpdaterModule
|
|
@@ -45,10 +45,22 @@ class HotUpdater {
|
|
|
45
45
|
bundleId: String,
|
|
46
46
|
fileUrl: String?,
|
|
47
47
|
fileHash: String?,
|
|
48
|
+
manifestUrl: String?,
|
|
49
|
+
manifestFileHash: String?,
|
|
50
|
+
changedAssets: Map<String, ChangedAssetDescriptor>?,
|
|
48
51
|
channel: String?,
|
|
49
|
-
progressCallback: (
|
|
52
|
+
progressCallback: (UpdateProgressPayload) -> Unit,
|
|
50
53
|
) {
|
|
51
|
-
getInstance(context).updateBundle(
|
|
54
|
+
getInstance(context).updateBundle(
|
|
55
|
+
bundleId,
|
|
56
|
+
fileUrl,
|
|
57
|
+
fileHash,
|
|
58
|
+
manifestUrl,
|
|
59
|
+
manifestFileHash,
|
|
60
|
+
changedAssets,
|
|
61
|
+
channel,
|
|
62
|
+
progressCallback,
|
|
63
|
+
)
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
/**
|
|
@@ -3,11 +3,11 @@ package com.hotupdater
|
|
|
3
3
|
import android.os.Handler
|
|
4
4
|
import android.os.Looper
|
|
5
5
|
import android.util.Log
|
|
6
|
+
import com.facebook.react.bridge.Arguments
|
|
6
7
|
import com.facebook.react.bridge.Promise
|
|
7
8
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
9
|
import com.facebook.react.bridge.ReactMethod
|
|
9
10
|
import com.facebook.react.bridge.ReadableMap
|
|
10
|
-
import com.facebook.react.bridge.WritableNativeMap
|
|
11
11
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
12
12
|
import kotlinx.coroutines.CoroutineScope
|
|
13
13
|
import kotlinx.coroutines.Dispatchers
|
|
@@ -37,6 +37,76 @@ class HotUpdaterModule internal constructor(
|
|
|
37
37
|
*/
|
|
38
38
|
private fun getInstance(): HotUpdaterImpl = HotUpdater.getInstance(mReactApplicationContext)
|
|
39
39
|
|
|
40
|
+
private fun parseChangedAssets(params: ReadableMap): Map<String, ChangedAssetDescriptor>? {
|
|
41
|
+
if (!params.hasKey("changedAssets") || params.isNull("changedAssets")) {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
val changedAssetsMap = params.getMap("changedAssets") ?: return null
|
|
46
|
+
val parsedAssets = linkedMapOf<String, ChangedAssetDescriptor>()
|
|
47
|
+
val iterator = changedAssetsMap.keySetIterator()
|
|
48
|
+
|
|
49
|
+
while (iterator.hasNextKey()) {
|
|
50
|
+
val assetPath = iterator.nextKey()
|
|
51
|
+
val assetMap = changedAssetsMap.getMap(assetPath) ?: continue
|
|
52
|
+
val assetHash = assetMap.getString("fileHash") ?: continue
|
|
53
|
+
val patchMap = assetMap.getMap("patch")
|
|
54
|
+
val patch =
|
|
55
|
+
if (patchMap != null) {
|
|
56
|
+
val algorithm = patchMap.getString("algorithm")
|
|
57
|
+
val baseBundleId = patchMap.getString("baseBundleId")
|
|
58
|
+
val baseFileHash = patchMap.getString("baseFileHash")
|
|
59
|
+
val patchFileHash = patchMap.getString("patchFileHash")
|
|
60
|
+
val patchUrl = patchMap.getString("patchUrl")
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
algorithm != null &&
|
|
64
|
+
baseBundleId != null &&
|
|
65
|
+
baseFileHash != null &&
|
|
66
|
+
patchFileHash != null &&
|
|
67
|
+
patchUrl != null
|
|
68
|
+
) {
|
|
69
|
+
BsdiffPatchDescriptor(
|
|
70
|
+
algorithm = algorithm,
|
|
71
|
+
baseBundleId = baseBundleId,
|
|
72
|
+
baseFileHash = baseFileHash,
|
|
73
|
+
patchFileHash = patchFileHash,
|
|
74
|
+
patchUrl = patchUrl,
|
|
75
|
+
)
|
|
76
|
+
} else {
|
|
77
|
+
null
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
null
|
|
81
|
+
}
|
|
82
|
+
val fileMap =
|
|
83
|
+
if (assetMap.hasKey("file") && !assetMap.isNull("file")) {
|
|
84
|
+
assetMap.getMap("file")
|
|
85
|
+
} else {
|
|
86
|
+
null
|
|
87
|
+
}
|
|
88
|
+
val assetUrl = fileMap?.getString("url")
|
|
89
|
+
val fileCompression =
|
|
90
|
+
if (fileMap != null && fileMap.hasKey("compression") && !fileMap.isNull("compression")) {
|
|
91
|
+
fileMap.getString("compression")
|
|
92
|
+
} else {
|
|
93
|
+
null
|
|
94
|
+
}
|
|
95
|
+
if (assetUrl == null && patch == null) {
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
parsedAssets[assetPath] =
|
|
99
|
+
ChangedAssetDescriptor(
|
|
100
|
+
fileUrl = assetUrl,
|
|
101
|
+
fileHash = assetHash,
|
|
102
|
+
fileCompression = fileCompression,
|
|
103
|
+
patch = patch,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return parsedAssets
|
|
108
|
+
}
|
|
109
|
+
|
|
40
110
|
@ReactMethod
|
|
41
111
|
override fun reload(promise: Promise) {
|
|
42
112
|
moduleScope.launch {
|
|
@@ -91,6 +161,9 @@ class HotUpdaterModule internal constructor(
|
|
|
91
161
|
}
|
|
92
162
|
|
|
93
163
|
val fileHash = params.getString("fileHash")
|
|
164
|
+
val manifestUrl = params.getString("manifestUrl")
|
|
165
|
+
val manifestFileHash = params.getString("manifestFileHash")
|
|
166
|
+
val changedAssets = parseChangedAssets(params)
|
|
94
167
|
val channel = params.getString("channel")
|
|
95
168
|
|
|
96
169
|
val impl = getInstance()
|
|
@@ -99,14 +172,45 @@ class HotUpdaterModule internal constructor(
|
|
|
99
172
|
bundleId,
|
|
100
173
|
fileUrl,
|
|
101
174
|
fileHash,
|
|
175
|
+
manifestUrl,
|
|
176
|
+
manifestFileHash,
|
|
177
|
+
changedAssets,
|
|
102
178
|
channel,
|
|
103
179
|
) { progress ->
|
|
104
180
|
// Post to Main thread for React Native event emission
|
|
105
181
|
Handler(Looper.getMainLooper()).post {
|
|
106
182
|
try {
|
|
107
183
|
val progressParams =
|
|
108
|
-
|
|
109
|
-
putDouble("progress", progress)
|
|
184
|
+
Arguments.createMap().apply {
|
|
185
|
+
putDouble("progress", progress.progress)
|
|
186
|
+
putString("artifactType", progress.artifactType)
|
|
187
|
+
progress.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
|
|
188
|
+
progress.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
|
|
189
|
+
progress.details?.let { details ->
|
|
190
|
+
val files = Arguments.createArray()
|
|
191
|
+
details.files.forEach { file ->
|
|
192
|
+
files.pushMap(
|
|
193
|
+
Arguments.createMap().apply {
|
|
194
|
+
putString("path", file.path)
|
|
195
|
+
putString("downloadPath", file.downloadPath)
|
|
196
|
+
putString("status", file.status)
|
|
197
|
+
putDouble("progress", file.progress)
|
|
198
|
+
putInt("order", file.order)
|
|
199
|
+
file.downloadedBytes?.let { putDouble("downloadedBytes", it.toDouble()) }
|
|
200
|
+
file.totalBytes?.let { putDouble("totalBytes", it.toDouble()) }
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
putMap(
|
|
206
|
+
"details",
|
|
207
|
+
Arguments.createMap().apply {
|
|
208
|
+
putInt("totalFilesCount", details.totalFilesCount)
|
|
209
|
+
putInt("completedFilesCount", details.completedFilesCount)
|
|
210
|
+
putArray("files", files)
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
}
|
|
110
214
|
}
|
|
111
215
|
|
|
112
216
|
this@HotUpdaterModule
|