react-native-ota-hot-update 2.3.5 → 2.4.0-rc.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/android/generated/java/com/otahotupdate/NativeOtaHotUpdateSpec.java +14 -1
- package/android/generated/jni/RNOtaHotUpdateSpec-generated.cpp +20 -2
- package/android/generated/jni/react/renderer/components/RNOtaHotUpdateSpec/RNOtaHotUpdateSpecJSI-generated.cpp +26 -2
- package/android/generated/jni/react/renderer/components/RNOtaHotUpdateSpec/RNOtaHotUpdateSpecJSI.h +32 -5
- package/android/gradle.properties +4 -0
- package/android/src/main/java/com/otahotupdate/OtaHotUpdate.kt +6 -1
- package/android/src/main/java/com/otahotupdate/OtaHotUpdateModule.kt +365 -36
- package/android/src/main/java/com/otahotupdate/SharedPrefs.kt +12 -0
- package/android/src/main/java/com/otahotupdate/Utils.kt +9 -3
- package/android/src/oldarch/OtaHotUpdateSpec.kt +4 -1
- package/ios/OtaHotUpdate.mm +383 -42
- package/ios/generated/RNOtaHotUpdateSpec/RNOtaHotUpdateSpec-generated.mm +23 -2
- package/ios/generated/RNOtaHotUpdateSpec/RNOtaHotUpdateSpec.h +12 -0
- package/ios/generated/RNOtaHotUpdateSpecJSI-generated.cpp +26 -2
- package/ios/generated/RNOtaHotUpdateSpecJSI.h +32 -5
- package/lib/commonjs/NativeOtaHotUpdate.js.map +1 -1
- package/lib/commonjs/index.js +26 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeOtaHotUpdate.js.map +1 -1
- package/lib/module/index.js +26 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/NativeOtaHotUpdate.d.ts +4 -1
- package/lib/typescript/commonjs/src/NativeOtaHotUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +8 -2
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/type.d.ts +36 -0
- package/lib/typescript/commonjs/src/type.d.ts.map +1 -1
- package/lib/typescript/module/src/NativeOtaHotUpdate.d.ts +4 -1
- package/lib/typescript/module/src/NativeOtaHotUpdate.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +8 -2
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/type.d.ts +36 -0
- package/lib/typescript/module/src/type.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeOtaHotUpdate.ts +4 -1
- package/src/index.d.ts +25 -2
- package/src/index.tsx +36 -5
- package/src/type.ts +44 -1
|
@@ -6,12 +6,15 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
6
6
|
import com.facebook.react.bridge.ReactMethod
|
|
7
7
|
import com.jakewharton.processphoenix.ProcessPhoenix
|
|
8
8
|
import com.otahotupdate.OtaHotUpdate.Companion.getVersionCode
|
|
9
|
+
import com.rnhotupdate.Common
|
|
9
10
|
import com.rnhotupdate.Common.CURRENT_VERSION_CODE
|
|
10
11
|
import com.rnhotupdate.Common.PATH
|
|
11
12
|
import com.rnhotupdate.Common.PREVIOUS_PATH
|
|
12
13
|
import com.rnhotupdate.Common.VERSION
|
|
13
14
|
import com.rnhotupdate.Common.PREVIOUS_VERSION
|
|
14
15
|
import com.rnhotupdate.Common.METADATA
|
|
16
|
+
import com.rnhotupdate.Common.BUNDLE_HISTORY
|
|
17
|
+
import com.rnhotupdate.Common.DEFAULT_MAX_BUNDLE_VERSIONS
|
|
15
18
|
import com.rnhotupdate.SharedPrefs
|
|
16
19
|
import kotlinx.coroutines.CoroutineScope
|
|
17
20
|
import kotlinx.coroutines.Dispatchers
|
|
@@ -20,6 +23,15 @@ import kotlinx.coroutines.cancel
|
|
|
20
23
|
import kotlinx.coroutines.launch
|
|
21
24
|
import kotlinx.coroutines.withContext
|
|
22
25
|
import java.io.File
|
|
26
|
+
import org.json.JSONArray
|
|
27
|
+
import org.json.JSONObject
|
|
28
|
+
|
|
29
|
+
data class BundleVersion(
|
|
30
|
+
val version: Int,
|
|
31
|
+
val path: String,
|
|
32
|
+
val timestamp: Long,
|
|
33
|
+
val metadata: String? = null
|
|
34
|
+
)
|
|
23
35
|
|
|
24
36
|
class OtaHotUpdateModule internal constructor(context: ReactApplicationContext) :
|
|
25
37
|
OtaHotUpdateSpec(context) {
|
|
@@ -35,20 +47,173 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
35
47
|
scope.cancel()
|
|
36
48
|
}
|
|
37
49
|
|
|
38
|
-
private fun
|
|
50
|
+
private fun loadBundleHistory(): List<BundleVersion> {
|
|
51
|
+
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
52
|
+
val historyJson = sharedPrefs.getString(BUNDLE_HISTORY)
|
|
53
|
+
|
|
54
|
+
// If history exists, load it
|
|
55
|
+
if (!historyJson.isNullOrEmpty()) {
|
|
56
|
+
return try {
|
|
57
|
+
val jsonArray = JSONArray(historyJson)
|
|
58
|
+
(0 until jsonArray.length()).map { i ->
|
|
59
|
+
val obj = jsonArray.getJSONObject(i)
|
|
60
|
+
BundleVersion(
|
|
61
|
+
version = obj.getInt("version"),
|
|
62
|
+
path = obj.getString("path"),
|
|
63
|
+
timestamp = obj.getLong("timestamp"),
|
|
64
|
+
metadata = if (obj.has("metadata") && !obj.isNull("metadata")) obj.getString("metadata") else null
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
} catch (e: Exception) {
|
|
68
|
+
emptyList()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Migration: If history is empty but PATH exists, migrate from old system
|
|
73
|
+
val currentPath = sharedPrefs.getString(PATH)
|
|
74
|
+
val currentVersion = sharedPrefs.getString(VERSION)
|
|
75
|
+
val previousPath = sharedPrefs.getString(PREVIOUS_PATH)
|
|
76
|
+
val previousVersion = sharedPrefs.getString(PREVIOUS_VERSION)
|
|
77
|
+
|
|
78
|
+
if (currentPath.isNullOrEmpty()) {
|
|
79
|
+
return emptyList()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Migrate current bundle
|
|
83
|
+
val migratedHistory = mutableListOf<BundleVersion>()
|
|
84
|
+
|
|
85
|
+
// Add current bundle if has version
|
|
86
|
+
if (!currentVersion.isNullOrEmpty()) {
|
|
87
|
+
try {
|
|
88
|
+
val version = currentVersion.toInt()
|
|
89
|
+
val bundleFile = File(currentPath)
|
|
90
|
+
if (bundleFile.exists()) {
|
|
91
|
+
migratedHistory.add(
|
|
92
|
+
BundleVersion(
|
|
93
|
+
version = version,
|
|
94
|
+
path = currentPath,
|
|
95
|
+
timestamp = bundleFile.lastModified(), // Use file modification time
|
|
96
|
+
metadata = null
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
} catch (e: Exception) {
|
|
101
|
+
// Version is not a number, skip
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add previous bundle if exists
|
|
106
|
+
if (!previousPath.isNullOrEmpty() && !previousVersion.isNullOrEmpty()) {
|
|
107
|
+
try {
|
|
108
|
+
val version = previousVersion.toInt()
|
|
109
|
+
val bundleFile = File(previousPath)
|
|
110
|
+
if (bundleFile.exists()) {
|
|
111
|
+
migratedHistory.add(
|
|
112
|
+
BundleVersion(
|
|
113
|
+
version = version,
|
|
114
|
+
path = previousPath,
|
|
115
|
+
timestamp = bundleFile.lastModified(),
|
|
116
|
+
metadata = null
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
} catch (e: Exception) {
|
|
121
|
+
// Version is not a number, skip
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Save migrated history if any
|
|
126
|
+
if (migratedHistory.isNotEmpty()) {
|
|
127
|
+
saveBundleHistory(migratedHistory.sortedByDescending { it.version })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return migratedHistory.sortedByDescending { it.version }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private fun saveBundleHistory(history: List<BundleVersion>) {
|
|
134
|
+
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
135
|
+
val jsonArray = JSONArray()
|
|
136
|
+
history.forEach { bundle ->
|
|
137
|
+
val obj = JSONObject()
|
|
138
|
+
obj.put("version", bundle.version)
|
|
139
|
+
obj.put("path", bundle.path)
|
|
140
|
+
obj.put("timestamp", bundle.timestamp)
|
|
141
|
+
if (bundle.metadata != null) {
|
|
142
|
+
obj.put("metadata", bundle.metadata)
|
|
143
|
+
} else {
|
|
144
|
+
obj.put("metadata", JSONObject.NULL)
|
|
145
|
+
}
|
|
146
|
+
jsonArray.put(obj)
|
|
147
|
+
}
|
|
148
|
+
sharedPrefs.putString(BUNDLE_HISTORY, jsonArray.toString())
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private fun extractFolderName(path: String): String {
|
|
152
|
+
val file = File(path)
|
|
153
|
+
return file.parentFile?.name ?: ""
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private fun saveBundleVersion(
|
|
157
|
+
newPath: String,
|
|
158
|
+
version: Int,
|
|
159
|
+
maxVersions: Int,
|
|
160
|
+
metadata: String?
|
|
161
|
+
) {
|
|
162
|
+
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
163
|
+
val history = loadBundleHistory()
|
|
164
|
+
|
|
165
|
+
// Add new version
|
|
166
|
+
val newBundle = BundleVersion(
|
|
167
|
+
version = version,
|
|
168
|
+
path = newPath,
|
|
169
|
+
timestamp = System.currentTimeMillis(),
|
|
170
|
+
metadata = metadata
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
// Combine and sort by version descending
|
|
174
|
+
val updatedHistory = (listOf(newBundle) + history)
|
|
175
|
+
.sortedByDescending { it.version }
|
|
176
|
+
.distinctBy { it.version } // Remove duplicates by version
|
|
177
|
+
|
|
178
|
+
// Keep only maxVersions most recent
|
|
179
|
+
val finalHistory = updatedHistory.take(maxVersions)
|
|
180
|
+
|
|
181
|
+
// Delete old versions beyond limit
|
|
182
|
+
val versionsToKeep = finalHistory.map { it.version }.toSet()
|
|
183
|
+
updatedHistory.forEach { bundle ->
|
|
184
|
+
if (bundle.version !in versionsToKeep) {
|
|
185
|
+
utils.deleteOldBundleIfneeded(bundle.path)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Save updated history
|
|
190
|
+
saveBundleHistory(finalHistory)
|
|
191
|
+
|
|
192
|
+
// Set current path
|
|
193
|
+
sharedPrefs.putString(PATH, newPath)
|
|
194
|
+
sharedPrefs.putString(VERSION, version.toString())
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private fun processBundleFile(path: String?, extension: String?, version: Int?, maxVersions: Int?, metadata: String?): Boolean {
|
|
39
198
|
if (path != null) {
|
|
40
199
|
val file = File(path)
|
|
41
200
|
if (file.exists() && file.isFile) {
|
|
42
|
-
val fileUnzip = utils.extractZipFile(file, extension ?: ".bundle")
|
|
201
|
+
val fileUnzip = utils.extractZipFile(file, extension ?: ".bundle", version)
|
|
43
202
|
if (fileUnzip != null) {
|
|
44
203
|
file.delete()
|
|
45
204
|
utils.deleteOldBundleIfneeded(null)
|
|
46
205
|
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
47
206
|
val oldPath = sharedPrefs.getString(PATH)
|
|
48
|
-
|
|
49
|
-
|
|
207
|
+
|
|
208
|
+
// If version is provided, save to history system
|
|
209
|
+
if (version != null) {
|
|
210
|
+
val maxVersionsToKeep = maxVersions ?: Common.DEFAULT_MAX_BUNDLE_VERSIONS
|
|
211
|
+
saveBundleVersion(fileUnzip, version, maxVersionsToKeep, metadata)
|
|
212
|
+
} else {
|
|
213
|
+
// No version (e.g., Git update) - just set path, no history
|
|
214
|
+
sharedPrefs.putString(PATH, fileUnzip)
|
|
50
215
|
}
|
|
51
|
-
|
|
216
|
+
|
|
52
217
|
sharedPrefs.putString(
|
|
53
218
|
CURRENT_VERSION_CODE,
|
|
54
219
|
reactApplicationContext.getVersionCode()
|
|
@@ -66,10 +231,12 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
66
231
|
}
|
|
67
232
|
}
|
|
68
233
|
@ReactMethod
|
|
69
|
-
override fun setupBundlePath(path: String?, extension: String?, promise: Promise) {
|
|
234
|
+
override fun setupBundlePath(path: String?, extension: String?, version: Double?, maxVersions: Double?, metadata: String?, promise: Promise) {
|
|
70
235
|
scope.launch {
|
|
71
236
|
try {
|
|
72
|
-
val
|
|
237
|
+
val versionInt = version?.toInt()
|
|
238
|
+
val maxVersionsInt = maxVersions?.toInt()
|
|
239
|
+
val result = processBundleFile(path, extension, versionInt, maxVersionsInt, metadata)
|
|
73
240
|
withContext(Dispatchers.Main) {
|
|
74
241
|
promise.resolve(result)
|
|
75
242
|
}
|
|
@@ -83,11 +250,34 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
83
250
|
|
|
84
251
|
@ReactMethod
|
|
85
252
|
override fun deleteBundle(i: Double, promise: Promise) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
253
|
+
scope.launch {
|
|
254
|
+
try {
|
|
255
|
+
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
256
|
+
val currentPath = sharedPrefs.getString(PATH)
|
|
257
|
+
|
|
258
|
+
// Delete current bundle from file system
|
|
259
|
+
val isDeleted = utils.deleteOldBundleIfneeded(PATH)
|
|
260
|
+
|
|
261
|
+
// Remove current bundle from history if exists
|
|
262
|
+
if (currentPath != null && currentPath.isNotEmpty()) {
|
|
263
|
+
val history = loadBundleHistory()
|
|
264
|
+
val updatedHistory = history.filter { it.path != currentPath }
|
|
265
|
+
saveBundleHistory(updatedHistory)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Clear paths and version (no longer clear PREVIOUS_PATH, use history instead)
|
|
269
|
+
sharedPrefs.putString(PATH, "")
|
|
270
|
+
sharedPrefs.putString(VERSION, "0")
|
|
271
|
+
|
|
272
|
+
withContext(Dispatchers.Main) {
|
|
273
|
+
promise.resolve(isDeleted)
|
|
274
|
+
}
|
|
275
|
+
} catch (e: Exception) {
|
|
276
|
+
withContext(Dispatchers.Main) {
|
|
277
|
+
promise.reject("DELETE_BUNDLE_ERROR", e)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
91
281
|
}
|
|
92
282
|
|
|
93
283
|
@ReactMethod
|
|
@@ -110,12 +300,7 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
110
300
|
@ReactMethod
|
|
111
301
|
override fun setCurrentVersion(version: String?, promise: Promise) {
|
|
112
302
|
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
113
|
-
|
|
114
|
-
val currentVersion = sharedPrefs.getString(VERSION)
|
|
115
|
-
if (currentVersion != "" && currentVersion != version) {
|
|
116
|
-
sharedPrefs.putString(PREVIOUS_VERSION, currentVersion)
|
|
117
|
-
}
|
|
118
|
-
|
|
303
|
+
// No longer save PREVIOUS_VERSION, use history instead
|
|
119
304
|
sharedPrefs.putString(VERSION, version)
|
|
120
305
|
promise.resolve(true)
|
|
121
306
|
}
|
|
@@ -157,31 +342,175 @@ class OtaHotUpdateModule internal constructor(context: ReactApplicationContext)
|
|
|
157
342
|
|
|
158
343
|
@ReactMethod
|
|
159
344
|
override fun rollbackToPreviousBundle(a: Double, promise: Promise) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
345
|
+
scope.launch {
|
|
346
|
+
try {
|
|
347
|
+
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
348
|
+
val currentPath = sharedPrefs.getString(PATH)
|
|
163
349
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
350
|
+
// Use history to find previous version (closest to current)
|
|
351
|
+
val history = loadBundleHistory()
|
|
352
|
+
if (history.isNotEmpty() && currentPath != null && currentPath.isNotEmpty()) {
|
|
353
|
+
// Find current bundle in history
|
|
354
|
+
val currentBundle = history.find { it.path == currentPath }
|
|
355
|
+
if (currentBundle != null) {
|
|
356
|
+
// Find previous version (version < current, max version = closest to current)
|
|
357
|
+
val previousBundle = history
|
|
358
|
+
.filter { it.version < currentBundle.version }
|
|
359
|
+
.maxByOrNull { it.version }
|
|
169
360
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
361
|
+
if (previousBundle != null && File(previousBundle.path).exists()) {
|
|
362
|
+
// Rollback to previous bundle from history
|
|
363
|
+
val isDeleted = utils.deleteOldBundleIfneeded(PATH)
|
|
364
|
+
if (isDeleted) {
|
|
365
|
+
sharedPrefs.putString(PATH, previousBundle.path)
|
|
366
|
+
sharedPrefs.putString(VERSION, previousBundle.version.toString())
|
|
367
|
+
|
|
368
|
+
withContext(Dispatchers.Main) {
|
|
369
|
+
promise.resolve(true)
|
|
370
|
+
}
|
|
371
|
+
return@launch
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
175
375
|
}
|
|
176
376
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
377
|
+
withContext(Dispatchers.Main) {
|
|
378
|
+
promise.resolve(false)
|
|
379
|
+
}
|
|
380
|
+
} catch (e: Exception) {
|
|
381
|
+
withContext(Dispatchers.Main) {
|
|
382
|
+
promise.reject("ROLLBACK_ERROR", e)
|
|
383
|
+
}
|
|
180
384
|
}
|
|
181
|
-
} else {
|
|
182
|
-
promise.resolve(false)
|
|
183
385
|
}
|
|
184
386
|
}
|
|
387
|
+
|
|
388
|
+
@ReactMethod
|
|
389
|
+
override fun getBundleList(a: Double, promise: Promise) {
|
|
390
|
+
scope.launch {
|
|
391
|
+
try {
|
|
392
|
+
val history = loadBundleHistory()
|
|
393
|
+
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
394
|
+
val activePath = sharedPrefs.getString(PATH)
|
|
395
|
+
|
|
396
|
+
val bundleList = history.map { bundle ->
|
|
397
|
+
val folderName = extractFolderName(bundle.path)
|
|
398
|
+
val bundleObj = JSONObject()
|
|
399
|
+
bundleObj.put("id", folderName)
|
|
400
|
+
bundleObj.put("version", bundle.version)
|
|
401
|
+
bundleObj.put("date", bundle.timestamp)
|
|
402
|
+
bundleObj.put("path", bundle.path)
|
|
403
|
+
bundleObj.put("isActive", bundle.path == activePath)
|
|
404
|
+
if (bundle.metadata != null) {
|
|
405
|
+
try {
|
|
406
|
+
// Try to parse as JSON, if fails use as string
|
|
407
|
+
val metadataJson = JSONObject(bundle.metadata)
|
|
408
|
+
bundleObj.put("metadata", metadataJson)
|
|
409
|
+
} catch (e: Exception) {
|
|
410
|
+
bundleObj.put("metadata", bundle.metadata)
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
bundleObj.put("metadata", JSONObject.NULL)
|
|
414
|
+
}
|
|
415
|
+
bundleObj
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
val jsonArray = JSONArray()
|
|
419
|
+
bundleList.forEach { jsonArray.put(it) }
|
|
420
|
+
|
|
421
|
+
withContext(Dispatchers.Main) {
|
|
422
|
+
promise.resolve(jsonArray.toString())
|
|
423
|
+
}
|
|
424
|
+
} catch (e: Exception) {
|
|
425
|
+
withContext(Dispatchers.Main) {
|
|
426
|
+
promise.reject("GET_BUNDLE_LIST_ERROR", e)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
@ReactMethod
|
|
433
|
+
override fun deleteBundleById(id: String, promise: Promise) {
|
|
434
|
+
scope.launch {
|
|
435
|
+
try {
|
|
436
|
+
val history = loadBundleHistory()
|
|
437
|
+
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
438
|
+
val activePath = sharedPrefs.getString(PATH)
|
|
439
|
+
|
|
440
|
+
val bundleToDelete = history.find { extractFolderName(it.path) == id }
|
|
441
|
+
if (bundleToDelete == null) {
|
|
442
|
+
withContext(Dispatchers.Main) {
|
|
443
|
+
promise.resolve(false)
|
|
444
|
+
}
|
|
445
|
+
return@launch
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// If deleting active bundle, rollback to oldest remaining bundle or clear
|
|
449
|
+
if (bundleToDelete.path == activePath) {
|
|
450
|
+
val remainingBundles = history.filter { it.path != bundleToDelete.path }
|
|
451
|
+
if (remainingBundles.isNotEmpty()) {
|
|
452
|
+
val oldestBundle = remainingBundles.minByOrNull { it.version }
|
|
453
|
+
if (oldestBundle != null) {
|
|
454
|
+
sharedPrefs.putString(PATH, oldestBundle.path)
|
|
455
|
+
sharedPrefs.putString(VERSION, oldestBundle.version.toString())
|
|
456
|
+
} else {
|
|
457
|
+
sharedPrefs.putString(PATH, "")
|
|
458
|
+
sharedPrefs.putString(VERSION, "")
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
sharedPrefs.putString(PATH, "")
|
|
462
|
+
sharedPrefs.putString(VERSION, "")
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Delete bundle folder
|
|
467
|
+
val isDeleted = utils.deleteOldBundleIfneeded(bundleToDelete.path)
|
|
468
|
+
|
|
469
|
+
// Remove from history
|
|
470
|
+
val updatedHistory = history.filter { it.path != bundleToDelete.path }
|
|
471
|
+
saveBundleHistory(updatedHistory)
|
|
472
|
+
|
|
473
|
+
withContext(Dispatchers.Main) {
|
|
474
|
+
promise.resolve(isDeleted)
|
|
475
|
+
}
|
|
476
|
+
} catch (e: Exception) {
|
|
477
|
+
withContext(Dispatchers.Main) {
|
|
478
|
+
promise.reject("DELETE_BUNDLE_ERROR", e)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
@ReactMethod
|
|
485
|
+
override fun clearAllBundles(a: Double, promise: Promise) {
|
|
486
|
+
scope.launch {
|
|
487
|
+
try {
|
|
488
|
+
val history = loadBundleHistory()
|
|
489
|
+
val sharedPrefs = SharedPrefs(reactApplicationContext)
|
|
490
|
+
|
|
491
|
+
// Delete all bundle folders
|
|
492
|
+
history.forEach { bundle ->
|
|
493
|
+
utils.deleteOldBundleIfneeded(bundle.path)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Clear history
|
|
497
|
+
saveBundleHistory(emptyList())
|
|
498
|
+
|
|
499
|
+
// Clear current path and version
|
|
500
|
+
sharedPrefs.putString(PATH, "")
|
|
501
|
+
sharedPrefs.putString(VERSION, "")
|
|
502
|
+
|
|
503
|
+
withContext(Dispatchers.Main) {
|
|
504
|
+
promise.resolve(true)
|
|
505
|
+
}
|
|
506
|
+
} catch (e: Exception) {
|
|
507
|
+
withContext(Dispatchers.Main) {
|
|
508
|
+
promise.reject("CLEAR_ALL_BUNDLES_ERROR", e)
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
185
514
|
companion object {
|
|
186
515
|
const val NAME = "OtaHotUpdate"
|
|
187
516
|
}
|
|
@@ -19,6 +19,16 @@ class SharedPrefs internal constructor(context: Context) {
|
|
|
19
19
|
editor.apply()
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
fun getInt(key: String?, defaultValue: Int): Int {
|
|
23
|
+
return mSharedPreferences.getInt(key, defaultValue)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fun putInt(key: String?, value: Int) {
|
|
27
|
+
val editor = mSharedPreferences.edit()
|
|
28
|
+
editor.putInt(key, value)
|
|
29
|
+
editor.apply()
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
fun clear() {
|
|
23
33
|
mSharedPreferences.edit().clear().apply()
|
|
24
34
|
}
|
|
@@ -32,4 +42,6 @@ object Common {
|
|
|
32
42
|
val SHARED_PREFERENCE_NAME = "HOT-UPDATE-REACT_NATIVE"
|
|
33
43
|
val DEFAULT_BUNDLE = "assets://index.android.bundle"
|
|
34
44
|
val METADATA = "METADATA"
|
|
45
|
+
val BUNDLE_HISTORY = "BUNDLE_HISTORY"
|
|
46
|
+
const val DEFAULT_MAX_BUNDLE_VERSIONS = 2
|
|
35
47
|
}
|
|
@@ -47,11 +47,11 @@ class Utils internal constructor(private val context: Context) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
fun extractZipFile(
|
|
50
|
-
zipFile: File,extension: String
|
|
50
|
+
zipFile: File,extension: String, version: Int? = null
|
|
51
51
|
): String? {
|
|
52
52
|
return try {
|
|
53
53
|
val outputDir = zipFile.parentFile
|
|
54
|
-
val timestamp = SimpleDateFormat("
|
|
54
|
+
val timestamp = SimpleDateFormat("yyyy_MM_dd_HH_mm", Locale.getDefault()).format(Date())
|
|
55
55
|
var topLevelFolder: String? = null
|
|
56
56
|
var bundlePath: String? = null
|
|
57
57
|
ZipFile(zipFile).use { zip ->
|
|
@@ -83,7 +83,13 @@ class Utils internal constructor(private val context: Context) {
|
|
|
83
83
|
// Rename the detected top-level folder
|
|
84
84
|
if (topLevelFolder != null) {
|
|
85
85
|
val extractedFolder = File(outputDir, topLevelFolder)
|
|
86
|
-
|
|
86
|
+
// Include version in folder name if provided, otherwise use timestamp only
|
|
87
|
+
val folderName = if (version != null) {
|
|
88
|
+
"output_v${version}_$timestamp"
|
|
89
|
+
} else {
|
|
90
|
+
"output_$timestamp"
|
|
91
|
+
}
|
|
92
|
+
val renamedFolder = File(outputDir, folderName)
|
|
87
93
|
if (extractedFolder.exists()) {
|
|
88
94
|
extractedFolder.renameTo(renamedFolder)
|
|
89
95
|
// Update bundlePath if the file was inside the renamed folder
|
|
@@ -7,7 +7,7 @@ import com.facebook.react.bridge.Promise
|
|
|
7
7
|
abstract class OtaHotUpdateSpec internal constructor(context: ReactApplicationContext) :
|
|
8
8
|
ReactContextBaseJavaModule(context) {
|
|
9
9
|
|
|
10
|
-
abstract fun setupBundlePath(path: String?, extension: String?, promise: Promise)
|
|
10
|
+
abstract fun setupBundlePath(path: String?, extension: String?, version: Double?, maxVersions: Double?, metadata: String?, promise: Promise)
|
|
11
11
|
abstract fun deleteBundle(i: Double, promise: Promise)
|
|
12
12
|
abstract fun restart()
|
|
13
13
|
abstract fun getCurrentVersion(a: Double, promise: Promise)
|
|
@@ -16,4 +16,7 @@ abstract class OtaHotUpdateSpec internal constructor(context: ReactApplicationCo
|
|
|
16
16
|
abstract fun rollbackToPreviousBundle(a: Double, promise: Promise)
|
|
17
17
|
abstract fun getUpdateMetadata(a: Double, promise: Promise)
|
|
18
18
|
abstract fun setUpdateMetadata(metadata: String?, promise: Promise)
|
|
19
|
+
abstract fun getBundleList(a: Double, promise: Promise)
|
|
20
|
+
abstract fun deleteBundleById(id: String, promise: Promise)
|
|
21
|
+
abstract fun clearAllBundles(a: Double, promise: Promise)
|
|
19
22
|
}
|