react-native-custom-ota 0.0.0-stage → 1.0.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/README.md +214 -2
- package/RNCustomOTA.podspec +20 -0
- package/android/build.gradle +21 -0
- package/android/src/main/java/com/customota/CustomOTABundleResolver.kt +85 -0
- package/android/src/main/java/com/customota/OTAUpdaterModule.kt +152 -0
- package/android/src/main/java/com/customota/OTAUpdaterPackage.kt +22 -0
- package/bin/custom-ota.js +217 -0
- package/ios/CustomOTABundleResolver.swift +79 -0
- package/ios/OTAUpdater.m +19 -0
- package/ios/OTAUpdater.swift +105 -0
- package/lib/commonjs/hoc.js +64 -0
- package/lib/commonjs/index.js +32 -0
- package/lib/commonjs/manager.js +262 -0
- package/lib/commonjs/native.js +9 -0
- package/lib/commonjs/storage.js +48 -0
- package/lib/commonjs/types.js +23 -0
- package/lib/module/hoc.js +28 -0
- package/lib/module/index.js +16 -0
- package/lib/module/manager.js +255 -0
- package/lib/module/native.js +6 -0
- package/lib/module/storage.js +39 -0
- package/lib/module/types.js +20 -0
- package/lib/typescript/hoc.d.ts +3 -0
- package/lib/typescript/hoc.js +64 -0
- package/lib/typescript/index.d.ts +16 -0
- package/lib/typescript/index.js +32 -0
- package/lib/typescript/manager.d.ts +35 -0
- package/lib/typescript/manager.js +262 -0
- package/lib/typescript/native.d.ts +8 -0
- package/lib/typescript/native.js +9 -0
- package/lib/typescript/src/hoc.d.ts +3 -0
- package/lib/typescript/src/index.d.ts +15 -0
- package/lib/typescript/src/manager.d.ts +33 -0
- package/lib/typescript/src/native.d.ts +8 -0
- package/lib/typescript/src/storage.d.ts +8 -0
- package/lib/typescript/src/types.d.ts +54 -0
- package/lib/typescript/storage.d.ts +8 -0
- package/lib/typescript/storage.js +48 -0
- package/lib/typescript/types.d.ts +60 -0
- package/lib/typescript/types.js +23 -0
- package/package.json +48 -3
- package/react-native.config.js +11 -0
- package/src/hoc.tsx +34 -0
- package/src/index.ts +19 -0
- package/src/manager.ts +328 -0
- package/src/native.ts +17 -0
- package/src/storage.ts +45 -0
- package/src/types.ts +66 -0
package/README.md
CHANGED
|
@@ -1,3 +1,215 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-native-custom-ota ⚡
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Thư viện cập nhật ứng dụng từ xa qua mạng (Over-The-Air - OTA) tự host dành cho React Native, hoạt động tương tự như **`react-native-code-push`**, hỗ trợ đầy đủ New Architecture (Fabric/TurboModules), mã băm SHA-256 toàn vẹn và cơ chế **Tự phục hồi (Startup Rollback)** khi bản cập nhật bị crash.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🌟 Tính năng nổi bật
|
|
8
|
+
|
|
9
|
+
- 🔄 **Đồng bộ 1 chạm (CodePush-like API)**: Hỗ trợ `CustomOTA.sync()` và HOC `withCustomOTA(App)`.
|
|
10
|
+
- 🛡️ **Cơ chế Rollback an toàn**: Nếu bản cập nhật bị crash ở tầng JS trước khi boot thành công, Native Resolver sẽ tự động phát hiện và khôi phục về bản backup ổn định trước đó.
|
|
11
|
+
- 📦 **Tích hợp Native cực kỳ đơn giản**: Chỉ cần thêm 1 dòng code ở Android và 1 dòng ở iOS (Hỗ trợ React Native Autolinking).
|
|
12
|
+
- 🛠️ **Tích hợp CLI phát hành nhanh**: `npx custom-ota release` tự động chạy Metro, nén zip và upload lên server chỉ trong 1-2 giây.
|
|
13
|
+
- 🚀 **Tương thích cả Old Architecture & New Architecture** (React Native 0.70+ đến 0.87+).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 📦 Cài đặt
|
|
18
|
+
|
|
19
|
+
Trong dự án React Native của bạn:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Cài đặt qua Yarn
|
|
23
|
+
yarn add react-native-custom-ota react-native-fs
|
|
24
|
+
|
|
25
|
+
# Hoặc qua NPM
|
|
26
|
+
npm install react-native-custom-ota react-native-fs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Đối với **iOS**, cài đặt CocoaPods:
|
|
30
|
+
```bash
|
|
31
|
+
cd ios && pod install && cd ..
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ⚙️ Cấu hình Native (Chỉ 1 dòng mỗi nền tảng)
|
|
37
|
+
|
|
38
|
+
### 1. Android
|
|
39
|
+
Mở file `android/app/src/main/java/.../MainApplication.kt` (hoặc `MainApplication.java`):
|
|
40
|
+
|
|
41
|
+
```kotlin
|
|
42
|
+
import com.customota.CustomOTABundleResolver
|
|
43
|
+
|
|
44
|
+
class MainApplication : Application(), ReactApplication {
|
|
45
|
+
|
|
46
|
+
// New Architecture (RN 0.76+):
|
|
47
|
+
override val reactHost: ReactHost by lazy {
|
|
48
|
+
val otaBundle = CustomOTABundleResolver.getBundleFile(applicationContext)
|
|
49
|
+
|
|
50
|
+
getDefaultReactHost(
|
|
51
|
+
context = applicationContext,
|
|
52
|
+
packageList = PackageList(this).packages,
|
|
53
|
+
jsBundleFilePath = otaBundle, // <-- Thêm dòng này
|
|
54
|
+
useDevSupport = otaBundle == null && BuildConfig.DEBUG
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Hoặc với Old Architecture (ReactNativeHost):
|
|
59
|
+
override fun getJSBundleFile(): String? {
|
|
60
|
+
return CustomOTABundleResolver.getBundleFile(applicationContext) ?: super.getJSBundleFile()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### 2. iOS
|
|
68
|
+
Mở file `ios/.../AppDelegate.swift` (hoặc `AppDelegate.mm`):
|
|
69
|
+
|
|
70
|
+
```swift
|
|
71
|
+
import RNCustomOTA
|
|
72
|
+
|
|
73
|
+
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
|
|
74
|
+
override func bundleURL() -> URL? {
|
|
75
|
+
// 1. Kiểm tra bản OTA trước tiên:
|
|
76
|
+
if let otaURL = CustomOTABundleResolver.currentBundleURL() {
|
|
77
|
+
return otaURL
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 2. Mặc định fallback:
|
|
81
|
+
#if DEBUG
|
|
82
|
+
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
|
|
83
|
+
#else
|
|
84
|
+
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
85
|
+
#endif
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🚀 Cách sử dụng phía React Native (JS / TS)
|
|
93
|
+
|
|
94
|
+
> [!NOTE]
|
|
95
|
+
> **Bảo mật API Key**: Khi server bật xác thực bảo mật, bạn cần truyền `apiKey` (loại **Pull Key**, bắt đầu bằng `ota_pull_live_...`). Nếu không có key hợp lệ, server sẽ từ chối với mã lỗi `API_KEY_REQUIRED` (HTTP 401).
|
|
96
|
+
|
|
97
|
+
### Cách 1: Sử dụng HOC (Tương tự CodePush - Khuyên dùng)
|
|
98
|
+
|
|
99
|
+
Bọc component gốc `App` bằng HOC `withCustomOTA`:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import React from "react";
|
|
103
|
+
import { View, Text } from "react-native";
|
|
104
|
+
import CustomOTA, { CheckFrequency } from "react-native-custom-ota";
|
|
105
|
+
|
|
106
|
+
function App() {
|
|
107
|
+
return (
|
|
108
|
+
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
|
109
|
+
<Text>Ứng dụng của tôi</Text>
|
|
110
|
+
</View>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default CustomOTA.wrap({
|
|
115
|
+
serverUrl: "http://my-ota-server.com:4500",
|
|
116
|
+
apiKey: "ota_pull_live_xxxx", // API Key có quyền Pull để tải bản cập nhật
|
|
117
|
+
appId: "com.otademoapp", // Định danh ứng dụng (tuỳ chọn)
|
|
118
|
+
checkFrequency: CheckFrequency.ON_APP_RESUME, // Tự động check khi mở app hoặc resume
|
|
119
|
+
updateDialog: true, // Tự động hiện popup hỏi người dùng khi có bản mới
|
|
120
|
+
})(App);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### Cách 2: Gọi `CustomOTA.sync()` linh hoạt
|
|
126
|
+
|
|
127
|
+
Tự gọi đồng bộ bất cứ lúc nào (ví dụ trong `useEffect` hoặc khi người dùng bấm nút):
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import CustomOTA, { SyncStatus } from "react-native-custom-ota";
|
|
131
|
+
|
|
132
|
+
// Gọi đồng bộ tự động:
|
|
133
|
+
CustomOTA.sync({
|
|
134
|
+
serverUrl: "http://my-ota-server.com:4500",
|
|
135
|
+
apiKey: "ota_pull_live_xxxx", // Pull API Key
|
|
136
|
+
appId: "com.otademoapp",
|
|
137
|
+
updateDialog: {
|
|
138
|
+
title: "Có bản cập nhật mới!",
|
|
139
|
+
optionalUpdateMessage: "Bạn có muốn cài đặt ngay bây giờ?",
|
|
140
|
+
optionalInstallButtonLabel: "Cài đặt ngay",
|
|
141
|
+
optionalIgnoreButtonLabel: "Để sau"
|
|
142
|
+
}
|
|
143
|
+
}, (status) => {
|
|
144
|
+
switch (status) {
|
|
145
|
+
case SyncStatus.DOWNLOADING_PACKAGE:
|
|
146
|
+
console.log("Đang tải bản cập nhật...");
|
|
147
|
+
break;
|
|
148
|
+
case SyncStatus.UPDATE_INSTALLED:
|
|
149
|
+
console.log("Đã cài xong!");
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}, (progress) => {
|
|
153
|
+
console.log(`Tiến độ: ${Math.round(progress * 100)}%`);
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### Cách 3: Điều khiển thủ công từng bước
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
import CustomOTA from "react-native-custom-ota";
|
|
163
|
+
|
|
164
|
+
async function manualCheck() {
|
|
165
|
+
// 1. Kiểm tra update
|
|
166
|
+
const update = await CustomOTA.checkForUpdate({
|
|
167
|
+
serverUrl: "http://my-ota-server.com:4500",
|
|
168
|
+
apiKey: "ota_pull_live_xxxx", // Pull API Key
|
|
169
|
+
appId: "com.otademoapp"
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (update && update.updateAvailable) {
|
|
173
|
+
// 2. Tải và cài đặt
|
|
174
|
+
await CustomOTA.downloadAndInstall(update, (progress) => {
|
|
175
|
+
console.log("Progress:", progress);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// 3. Khởi động lại ứng dụng
|
|
179
|
+
await CustomOTA.restartApp();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 🛠️ Công cụ CLI: Phát hành bản cập nhật mới
|
|
187
|
+
|
|
188
|
+
Sau khi sửa code trong dự án React Native, bạn có thể dùng CLI đi kèm thư viện:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Phát hành cho iOS với Push Key (-k hoặc biến OTA_API_KEY)
|
|
192
|
+
npx custom-ota release -a com.otademoapp -p ios -v 1.0.4 -m "Sửa lỗi giao diện" -s http://localhost:4500 -k ota_push_live_xxxx
|
|
193
|
+
|
|
194
|
+
# Phát hành cho Android
|
|
195
|
+
npx custom-ota release -a com.otademoapp -p android -v 1.0.4 -s http://localhost:4500 -k ota_push_live_xxxx
|
|
196
|
+
|
|
197
|
+
# Phát hành cùng lúc cả iOS và Android qua biến môi trường
|
|
198
|
+
OTA_API_KEY=ota_push_live_xxxx npx custom-ota release -a com.otademoapp -p all -v 1.0.4 -s http://localhost:4500
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Các cờ hỗ trợ trong CLI:
|
|
202
|
+
| Cờ | Tên dài | Ý nghĩa | Mặc định |
|
|
203
|
+
| :--- | :--- | :--- | :--- |
|
|
204
|
+
| `-p` | `--platform` | Nền tảng: `android`, `ios`, `all` (Bắt buộc) | |
|
|
205
|
+
| `-v` | `--version` | Phiên bản OTA (Bắt buộc) | |
|
|
206
|
+
| `-a` | `--app-id` | Mã định danh ứng dụng | Tự đọc từ `app.json` / `package.json` |
|
|
207
|
+
| `-k` | `--api-key` | Push API Key (hoặc qua `OTA_API_KEY`) | |
|
|
208
|
+
| `-s` | `--server` | Địa chỉ OTA Server | `http://localhost:4500` |
|
|
209
|
+
| `-m` | `--notes` | Ghi chú phát hành | `"OTA update"` |
|
|
210
|
+
| `--min-native` | `--minNativeVersion` | Phiên bản Native tối thiểu hỗ trợ | `1.0.0` |
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 📄 License
|
|
215
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "RNCustomOTA"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = "https://github.com/custom-ota/react-native-custom-ota"
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
s.platforms = { :ios => "13.4" }
|
|
13
|
+
s.source = { :git => "https://github.com/custom-ota/react-native-custom-ota.git", :tag => "#{s.version}" }
|
|
14
|
+
|
|
15
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
16
|
+
s.requires_arc = true
|
|
17
|
+
|
|
18
|
+
s.dependency "React-Core"
|
|
19
|
+
s.dependency "SSZipArchive"
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
apply plugin: 'kotlin-android'
|
|
3
|
+
|
|
4
|
+
android {
|
|
5
|
+
namespace "com.customota"
|
|
6
|
+
compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : 34
|
|
7
|
+
|
|
8
|
+
defaultConfig {
|
|
9
|
+
minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : 23
|
|
10
|
+
targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : 34
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
lintOptions {
|
|
14
|
+
abortOnError false
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
dependencies {
|
|
19
|
+
implementation 'com.facebook.react:react-android'
|
|
20
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib"
|
|
21
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
package com.customota
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import java.io.File
|
|
5
|
+
import org.json.JSONArray
|
|
6
|
+
import org.json.JSONObject
|
|
7
|
+
|
|
8
|
+
object CustomOTABundleResolver {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Lấy đường dẫn file JS bundle của OTA.
|
|
12
|
+
* Tự động kiểm tra trạng thái crash/pending để phục hồi (Rollback) bản backup trước đó.
|
|
13
|
+
*/
|
|
14
|
+
@JvmStatic
|
|
15
|
+
fun getBundleFile(context: Context): String? {
|
|
16
|
+
checkAndRollbackIfPending(context)
|
|
17
|
+
|
|
18
|
+
val file = File(
|
|
19
|
+
context.filesDir,
|
|
20
|
+
"ota/current/index.android.bundle"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return if (file.exists() && file.length() > 0) {
|
|
24
|
+
file.absolutePath
|
|
25
|
+
} else {
|
|
26
|
+
null
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@JvmStatic
|
|
31
|
+
fun getStateFile(context: Context): File {
|
|
32
|
+
return File(
|
|
33
|
+
context.filesDir,
|
|
34
|
+
"ota/state.json"
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@JvmStatic
|
|
39
|
+
fun checkAndRollbackIfPending(context: Context) {
|
|
40
|
+
val stateFile = getStateFile(context)
|
|
41
|
+
if (!stateFile.exists()) return
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
val jsonStr = stateFile.readText()
|
|
45
|
+
val json = JSONObject(jsonStr)
|
|
46
|
+
|
|
47
|
+
val pending = json.optBoolean("pending", false)
|
|
48
|
+
if (!pending) return
|
|
49
|
+
|
|
50
|
+
val trial = json.optBoolean("trial", false)
|
|
51
|
+
val currentDir = File(context.filesDir, "ota/current")
|
|
52
|
+
val backupDir = File(context.filesDir, "ota/backup")
|
|
53
|
+
|
|
54
|
+
if (trial) {
|
|
55
|
+
// Đã thử chạy lần trước mà bị crash trước khi gọi markReady() -> ROLLBACK!
|
|
56
|
+
if (backupDir.exists()) {
|
|
57
|
+
currentDir.deleteRecursively()
|
|
58
|
+
backupDir.renameTo(currentDir)
|
|
59
|
+
} else {
|
|
60
|
+
currentDir.deleteRecursively()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
val failedVer = json.optString("currentVersion", "")
|
|
64
|
+
val failedArray = json.optJSONArray("failedVersions") ?: JSONArray()
|
|
65
|
+
if (failedVer.isNotEmpty()) {
|
|
66
|
+
failedArray.put(failedVer)
|
|
67
|
+
}
|
|
68
|
+
json.put("failedVersions", failedArray)
|
|
69
|
+
|
|
70
|
+
val prevVer = json.optString("previousVersion", "0.0.0")
|
|
71
|
+
json.put("currentVersion", prevVer)
|
|
72
|
+
json.put("pending", false)
|
|
73
|
+
json.put("trial", false)
|
|
74
|
+
json.put("ready", true)
|
|
75
|
+
} else {
|
|
76
|
+
// Lần đầu mở bản mới: cho phép chạy và đánh dấu trial = true
|
|
77
|
+
json.put("trial", true)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
stateFile.writeText(json.toString(2))
|
|
81
|
+
} catch (e: Exception) {
|
|
82
|
+
e.printStackTrace()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
package com.customota
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.Looper
|
|
6
|
+
import com.facebook.react.bridge.Promise
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
9
|
+
import com.facebook.react.bridge.ReactMethod
|
|
10
|
+
import java.io.File
|
|
11
|
+
import java.io.FileInputStream
|
|
12
|
+
import java.io.FileOutputStream
|
|
13
|
+
import java.util.zip.ZipInputStream
|
|
14
|
+
|
|
15
|
+
class OTAUpdaterModule(
|
|
16
|
+
private val reactContext: ReactApplicationContext
|
|
17
|
+
) : ReactContextBaseJavaModule(reactContext) {
|
|
18
|
+
|
|
19
|
+
override fun getName(): String = "OTAUpdater"
|
|
20
|
+
|
|
21
|
+
@ReactMethod
|
|
22
|
+
fun unzip(zipPath: String, targetDir: String, promise: Promise) {
|
|
23
|
+
try {
|
|
24
|
+
val dest = File(targetDir)
|
|
25
|
+
if (!dest.exists()) {
|
|
26
|
+
dest.mkdirs()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
val buffer = ByteArray(4096)
|
|
30
|
+
val zis = ZipInputStream(FileInputStream(zipPath))
|
|
31
|
+
var zipEntry = zis.nextEntry
|
|
32
|
+
|
|
33
|
+
while (zipEntry != null) {
|
|
34
|
+
val newFile = File(dest, zipEntry.name)
|
|
35
|
+
if (!newFile.canonicalPath.startsWith(dest.canonicalPath)) {
|
|
36
|
+
throw SecurityException("Zip Slip detected: " + zipEntry.name)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (zipEntry.isDirectory) {
|
|
40
|
+
newFile.mkdirs()
|
|
41
|
+
} else {
|
|
42
|
+
newFile.parentFile?.mkdirs()
|
|
43
|
+
val fos = FileOutputStream(newFile)
|
|
44
|
+
var len: Int
|
|
45
|
+
while (zis.read(buffer).also { len = it } > 0) {
|
|
46
|
+
fos.write(buffer, 0, len)
|
|
47
|
+
}
|
|
48
|
+
fos.close()
|
|
49
|
+
}
|
|
50
|
+
zipEntry = zis.nextEntry
|
|
51
|
+
}
|
|
52
|
+
zis.closeEntry()
|
|
53
|
+
zis.close()
|
|
54
|
+
|
|
55
|
+
promise.resolve(null)
|
|
56
|
+
} catch (e: Exception) {
|
|
57
|
+
promise.reject("OTA_UNZIP", e)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@ReactMethod
|
|
62
|
+
fun markReady(promise: Promise) {
|
|
63
|
+
try {
|
|
64
|
+
val stateFile = File(
|
|
65
|
+
reactContext.filesDir,
|
|
66
|
+
"ota/state.json"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if (stateFile.exists()) {
|
|
70
|
+
val json = JSONObject(stateFile.readText())
|
|
71
|
+
json.put("pending", false)
|
|
72
|
+
json.put("ready", true)
|
|
73
|
+
json.put("trial", false)
|
|
74
|
+
stateFile.writeText(json.toString(2))
|
|
75
|
+
|
|
76
|
+
File(
|
|
77
|
+
reactContext.filesDir,
|
|
78
|
+
"ota/backup"
|
|
79
|
+
).deleteRecursively()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
promise.resolve(null)
|
|
83
|
+
} catch (e: Exception) {
|
|
84
|
+
promise.reject("OTA_READY", e)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@ReactMethod
|
|
89
|
+
fun getCurrentVersion(promise: Promise) {
|
|
90
|
+
try {
|
|
91
|
+
val stateFile = File(
|
|
92
|
+
reactContext.filesDir,
|
|
93
|
+
"ota/state.json"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if (!stateFile.exists()) {
|
|
97
|
+
promise.resolve("0.0.0")
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
val json = stateFile.readText()
|
|
102
|
+
val regex =
|
|
103
|
+
Regex("\"currentVersion\"\\s*:\\s*\"([^\"]+)\"")
|
|
104
|
+
|
|
105
|
+
promise.resolve(
|
|
106
|
+
regex.find(json)?.groupValues?.get(1)
|
|
107
|
+
?: "0.0.0"
|
|
108
|
+
)
|
|
109
|
+
} catch (e: Exception) {
|
|
110
|
+
promise.reject("OTA_VERSION", e)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@ReactMethod
|
|
115
|
+
fun restart(promise: Promise) {
|
|
116
|
+
try {
|
|
117
|
+
val activity: Activity? =
|
|
118
|
+
reactContext.currentActivity
|
|
119
|
+
|
|
120
|
+
if (activity == null) {
|
|
121
|
+
promise.reject(
|
|
122
|
+
"OTA_RESTART",
|
|
123
|
+
"No current Activity"
|
|
124
|
+
)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
promise.resolve(null)
|
|
129
|
+
|
|
130
|
+
Handler(Looper.getMainLooper()).postDelayed({
|
|
131
|
+
val packageManager =
|
|
132
|
+
reactContext.packageManager
|
|
133
|
+
|
|
134
|
+
val intent =
|
|
135
|
+
packageManager.getLaunchIntentForPackage(
|
|
136
|
+
reactContext.packageName
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
intent?.addFlags(
|
|
140
|
+
android.content.Intent.FLAG_ACTIVITY_NEW_TASK or
|
|
141
|
+
android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP or
|
|
142
|
+
android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
reactContext.startActivity(intent)
|
|
146
|
+
activity.finishAffinity()
|
|
147
|
+
}, 300)
|
|
148
|
+
} catch (e: Exception) {
|
|
149
|
+
promise.reject("OTA_RESTART", e)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.customota
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class OTAUpdaterPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(
|
|
10
|
+
reactContext: ReactApplicationContext
|
|
11
|
+
): List<NativeModule> {
|
|
12
|
+
return listOf(
|
|
13
|
+
OTAUpdaterModule(reactContext)
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun createViewManagers(
|
|
18
|
+
reactContext: ReactApplicationContext
|
|
19
|
+
): List<ViewManager<*, *>> {
|
|
20
|
+
return emptyList()
|
|
21
|
+
}
|
|
22
|
+
}
|