@viettelpost/react-native-ota 0.1.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.
- package/README.md +38 -0
- package/android/build.gradle +48 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAHashUtils.kt +21 -0
- package/android/src/main/java/com/viettelpost/otakit/OTATestReceiver.kt +51 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateBundleResolver.kt +405 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateCleanup.kt +186 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateDownloader.kt +649 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateMetadata.kt +72 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateModule.kt +140 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdatePackage.kt +30 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateSignatureVerifier.kt +63 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateStorage.kt +62 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAZipUtils.kt +100 -0
- package/android/src/main/res/raw/ota_public_key.pem +9 -0
- package/bin/cli/assets-zip.js +77 -0
- package/bin/cli/bundle.js +72 -0
- package/bin/cli/deploy.js +224 -0
- package/bin/cli/sign.js +97 -0
- package/bin/cli/upload.js +109 -0
- package/bin/ota.js +200 -0
- package/docs/BACKEND_CONTRACT.md +93 -0
- package/docs/DEPLOY_CLI.md +39 -0
- package/docs/INTEGRATION_ANDROID.md +20 -0
- package/docs/INTEGRATION_IOS.md +21 -0
- package/docs/RELEASE_WORKFLOW.md +14 -0
- package/ios/OTAHashUtils.swift +22 -0
- package/ios/OTAUpdateBundleResolver.swift +359 -0
- package/ios/OTAUpdateCleanup.swift +269 -0
- package/ios/OTAUpdateDownloader.swift +709 -0
- package/ios/OTAUpdateMetadata.swift +47 -0
- package/ios/OTAUpdateModule.mm +190 -0
- package/ios/OTAUpdateSignatureVerifier.swift +81 -0
- package/ios/OTAUpdateStorage.swift +83 -0
- package/ios/OTAZipUtils.swift +103 -0
- package/ios/ota_public_key.pem +9 -0
- package/lib/NativeOTAUpdate.d.ts +77 -0
- package/lib/NativeOTAUpdate.js +59 -0
- package/lib/OTAClient.d.ts +27 -0
- package/lib/OTAClient.js +101 -0
- package/lib/config.d.ts +14 -0
- package/lib/config.js +29 -0
- package/lib/devtools.d.ts +10 -0
- package/lib/devtools.js +54 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +32 -0
- package/lib/spec/NativeOTAUpdate.d.ts +16 -0
- package/lib/spec/NativeOTAUpdate.js +4 -0
- package/package.json +82 -0
- package/react-native-ota.podspec +21 -0
- package/scripts/run-bin.js +67 -0
- package/src/NativeOTAUpdate.ts +144 -0
- package/src/OTAClient.ts +151 -0
- package/src/config.ts +41 -0
- package/src/devtools.ts +64 -0
- package/src/index.ts +69 -0
- package/src/spec/NativeOTAUpdate.ts +21 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
package com.viettelpost.otakit
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import org.json.JSONArray
|
|
6
|
+
import org.json.JSONObject
|
|
7
|
+
import java.io.File
|
|
8
|
+
|
|
9
|
+
object OTAUpdateCleanup {
|
|
10
|
+
private const val TAG = "OTAKit"
|
|
11
|
+
private const val EMBEDDED_VERSION = "embedded"
|
|
12
|
+
private const val BUNDLES_DIR_NAME = "bundles"
|
|
13
|
+
private const val TMP_DIR_NAME = "tmp"
|
|
14
|
+
private const val METADATA_FILE_NAME = "metadata.json"
|
|
15
|
+
|
|
16
|
+
fun cleanupAfterSuccessfulActivation(context: Context, activeVersion: String): Boolean {
|
|
17
|
+
if (isEmbeddedVersion(activeVersion)) {
|
|
18
|
+
return cleanupOrphanedBundles(context, emptySet())
|
|
19
|
+
}
|
|
20
|
+
return cleanupOrphanedBundles(context, setOf(activeVersion))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fun cleanupFailedPendingBundle(context: Context, failedVersion: String?): Boolean {
|
|
24
|
+
if (isEmbeddedVersion(failedVersion)) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
val version = failedVersion.orEmpty()
|
|
28
|
+
val bundleDeleted = safeDeleteUnderOTARoot(context, bundleDirectory(context, version))
|
|
29
|
+
val tempDeleted = cleanupTemp(context, version)
|
|
30
|
+
return bundleDeleted && tempDeleted
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fun cleanupTemp(context: Context, version: String?): Boolean {
|
|
34
|
+
if (version.isNullOrBlank()) {
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
return safeDeleteUnderOTARoot(context, tempDirectory(context, version))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fun cleanupAllTemp(context: Context): Boolean =
|
|
41
|
+
safeDeleteChildrenUnderOTARoot(tmpRoot(context))
|
|
42
|
+
|
|
43
|
+
fun cleanupOrphanedBundles(context: Context, keepVersions: Set<String>): Boolean {
|
|
44
|
+
val normalizedKeepVersions = keepVersions
|
|
45
|
+
.filterNot { isEmbeddedVersion(it) }
|
|
46
|
+
.toSet()
|
|
47
|
+
return safeDeleteChildrenUnderOTARoot(bundleRoot(context)) { child ->
|
|
48
|
+
child.name !in normalizedKeepVersions
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fun cleanupOTAStorage(context: Context): Boolean {
|
|
53
|
+
val metadata = OTAUpdateStorage.readMetadata(context)
|
|
54
|
+
val keepVersions = mutableSetOf<String>()
|
|
55
|
+
if (!isEmbeddedVersion(metadata.activeBundleVersion)) {
|
|
56
|
+
keepVersions.add(metadata.activeBundleVersion)
|
|
57
|
+
}
|
|
58
|
+
if (!isEmbeddedVersion(metadata.pendingBundleVersion)) {
|
|
59
|
+
keepVersions.add(metadata.pendingBundleVersion.orEmpty())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var didCleanup = cleanupAllTemp(context)
|
|
63
|
+
if (!isEmbeddedVersion(metadata.failedBundleVersion) &&
|
|
64
|
+
metadata.failedBundleVersion !in keepVersions
|
|
65
|
+
) {
|
|
66
|
+
didCleanup = cleanupFailedPendingBundle(context, metadata.failedBundleVersion) && didCleanup
|
|
67
|
+
}
|
|
68
|
+
didCleanup = cleanupOrphanedBundles(context, keepVersions) && didCleanup
|
|
69
|
+
return didCleanup
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fun getOTADiskUsage(context: Context): JSONObject {
|
|
73
|
+
val metadata = OTAUpdateStorage.readMetadata(context)
|
|
74
|
+
val otaRoot = OTAUpdateBundleResolver.otaDirectory(context)
|
|
75
|
+
val tmpRoot = tmpRoot(context)
|
|
76
|
+
val bundlesRoot = bundleRoot(context)
|
|
77
|
+
val bundles = JSONArray()
|
|
78
|
+
|
|
79
|
+
bundlesRoot.listFiles()
|
|
80
|
+
?.filter { it.isDirectory }
|
|
81
|
+
?.sortedBy { it.name }
|
|
82
|
+
?.forEach { directory ->
|
|
83
|
+
bundles.put(
|
|
84
|
+
JSONObject().apply {
|
|
85
|
+
put("version", directory.name)
|
|
86
|
+
put("bytes", directorySize(directory))
|
|
87
|
+
put("isActive", directory.name == metadata.activeBundleVersion)
|
|
88
|
+
put("isPending", directory.name == metadata.pendingBundleVersion)
|
|
89
|
+
put("isFailed", directory.name == metadata.failedBundleVersion)
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return JSONObject().apply {
|
|
95
|
+
put("otaRootPath", otaRoot.absolutePath)
|
|
96
|
+
put("totalBytes", directorySize(otaRoot))
|
|
97
|
+
put("tmpBytes", directorySize(tmpRoot))
|
|
98
|
+
put("bundlesBytes", directorySize(bundlesRoot))
|
|
99
|
+
put("bundles", bundles)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fun deleteFinalBundleForFailedInstall(context: Context, version: String?): Boolean {
|
|
104
|
+
if (isEmbeddedVersion(version)) {
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
return safeDeleteUnderOTARoot(context, bundleDirectory(context, version.orEmpty()))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private fun safeDeleteChildrenUnderOTARoot(
|
|
111
|
+
directory: File,
|
|
112
|
+
shouldDelete: (File) -> Boolean = { true },
|
|
113
|
+
): Boolean {
|
|
114
|
+
if (!directory.exists()) {
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
val children = directory.listFiles() ?: return true
|
|
118
|
+
var didDeleteAll = true
|
|
119
|
+
children.forEach { child ->
|
|
120
|
+
if (shouldDelete(child)) {
|
|
121
|
+
didDeleteAll = safeDeleteUnderOTARoot(directory.parentFile ?: directory, child) && didDeleteAll
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return didDeleteAll
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private fun safeDeleteUnderOTARoot(context: Context, target: File): Boolean =
|
|
128
|
+
safeDeleteUnderOTARoot(OTAUpdateBundleResolver.otaDirectory(context), target)
|
|
129
|
+
|
|
130
|
+
private fun safeDeleteUnderOTARoot(otaRoot: File, target: File): Boolean {
|
|
131
|
+
if (!target.exists()) {
|
|
132
|
+
return true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return try {
|
|
136
|
+
val rootPath = otaRoot.canonicalFile.toPath()
|
|
137
|
+
val targetPath = target.canonicalFile.toPath()
|
|
138
|
+
|
|
139
|
+
// Cleanup must never remove metadata, the OTA root, or any path outside OTA storage.
|
|
140
|
+
if (target.name == METADATA_FILE_NAME || targetPath == rootPath || !targetPath.startsWith(rootPath)) {
|
|
141
|
+
Log.w(TAG, "OTA cleanup refused unsafe delete; path=${target.absolutePath}")
|
|
142
|
+
return false
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!target.deleteRecursively()) {
|
|
146
|
+
Log.w(TAG, "OTA cleanup could not delete ${target.absolutePath}")
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
Log.d(TAG, "OTA cleanup deleted ${target.absolutePath}")
|
|
150
|
+
true
|
|
151
|
+
} catch (error: Exception) {
|
|
152
|
+
Log.w(TAG, "OTA cleanup failed for ${target.absolutePath}", error)
|
|
153
|
+
false
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private fun directorySize(file: File): Long {
|
|
158
|
+
if (!file.exists()) {
|
|
159
|
+
return 0L
|
|
160
|
+
}
|
|
161
|
+
if (file.isFile) {
|
|
162
|
+
return file.length()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
var total = 0L
|
|
166
|
+
file.listFiles()?.forEach { child ->
|
|
167
|
+
total += directorySize(child)
|
|
168
|
+
}
|
|
169
|
+
return total
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private fun bundleRoot(context: Context): File =
|
|
173
|
+
File(OTAUpdateBundleResolver.otaDirectory(context), BUNDLES_DIR_NAME)
|
|
174
|
+
|
|
175
|
+
private fun tmpRoot(context: Context): File =
|
|
176
|
+
File(OTAUpdateBundleResolver.otaDirectory(context), TMP_DIR_NAME)
|
|
177
|
+
|
|
178
|
+
private fun bundleDirectory(context: Context, version: String): File =
|
|
179
|
+
File(bundleRoot(context), version)
|
|
180
|
+
|
|
181
|
+
private fun tempDirectory(context: Context, version: String): File =
|
|
182
|
+
File(tmpRoot(context), version)
|
|
183
|
+
|
|
184
|
+
private fun isEmbeddedVersion(version: String?): Boolean =
|
|
185
|
+
version.isNullOrBlank() || version == EMBEDDED_VERSION
|
|
186
|
+
}
|