react-native-custom-ota 0.0.0-stage → 1.0.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 +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 +49 -4
- 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
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
const http = require("http");
|
|
8
|
+
const https = require("https");
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const command = args[0];
|
|
12
|
+
|
|
13
|
+
if (!command || command === "--help" || command === "help") {
|
|
14
|
+
console.log(`
|
|
15
|
+
\x1b[35m⚡ REACT NATIVE CUSTOM OTA CLI\x1b[0m
|
|
16
|
+
|
|
17
|
+
\x1b[33mCú pháp:\x1b[0m
|
|
18
|
+
npx custom-ota release -p <platform> -v <version> [-a <appId>] [-k <apiKey>] [-s <serverUrl>] [-m <notes>]
|
|
19
|
+
|
|
20
|
+
\x1b[33mTham số:\x1b[0m
|
|
21
|
+
-a, --app-id Mã định danh App (Mặc định: đọc từ app.json / com.otademoapp)
|
|
22
|
+
-p, --platform Hệ điều hành: android, ios, all (Bắt buộc)
|
|
23
|
+
-v, --version Phiên bản OTA: ví dụ 1.0.4 (Bắt buộc)
|
|
24
|
+
-k, --api-key API Key phát hành (Push Key)
|
|
25
|
+
-s, --server Địa chỉ OTA Server (Mặc định: http://localhost:4500)
|
|
26
|
+
-m, --notes Ghi chú phát hành (Mặc định: "OTA update")
|
|
27
|
+
-t, --token Admin secret token (Mặc định: demo-secret)
|
|
28
|
+
--min-native Phiên bản native tối thiểu (Mặc định: 1.0.0)
|
|
29
|
+
|
|
30
|
+
\x1b[33mVí dụ:\x1b[0m
|
|
31
|
+
npx custom-ota release -a com.company.shop -p ios -v 1.0.4 -m "Fix UI lỗi font"
|
|
32
|
+
npx custom-ota release -a com.company.rider -p android -v 1.0.4
|
|
33
|
+
npx custom-ota release -p all -v 1.0.4 -s http://192.168.1.10:4500
|
|
34
|
+
`);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (command !== "release") {
|
|
39
|
+
console.error(`\x1b[31mLệnh không hợp lệ: "${command}". Hãy dùng "release" hoặc "--help"\x1b[0m`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getArgValue(shortKey, longKey, defaultValue = null) {
|
|
44
|
+
const shortIndex = args.indexOf(shortKey);
|
|
45
|
+
if (shortIndex !== -1 && args[shortIndex + 1]) return args[shortIndex + 1];
|
|
46
|
+
const longIndex = args.indexOf(longKey);
|
|
47
|
+
if (longIndex !== -1 && args[longIndex + 1]) return args[longIndex + 1];
|
|
48
|
+
return defaultValue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function detectAppId() {
|
|
52
|
+
const root = process.cwd();
|
|
53
|
+
const appJsonPath = path.join(root, "app.json");
|
|
54
|
+
if (fs.existsSync(appJsonPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const data = JSON.parse(fs.readFileSync(appJsonPath, "utf8"));
|
|
57
|
+
if (data.name) return String(data.name).toLowerCase();
|
|
58
|
+
} catch (e) {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const pkgJsonPath = path.join(root, "package.json");
|
|
62
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
63
|
+
try {
|
|
64
|
+
const data = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
65
|
+
if (data.name) return String(data.name).toLowerCase();
|
|
66
|
+
} catch (e) {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return "com.otademoapp";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const appId = getArgValue("-a", "--app-id", detectAppId());
|
|
73
|
+
const platformArg = getArgValue("-p", "--platform");
|
|
74
|
+
const version = getArgValue("-v", "--version");
|
|
75
|
+
const serverUrl = getArgValue("-s", "--server", process.env.OTA_SERVER || "http://localhost:4500");
|
|
76
|
+
const releaseNotes = getArgValue("-m", "--notes", "OTA update");
|
|
77
|
+
const adminToken = getArgValue("-t", "--token", process.env.OTA_ADMIN_TOKEN || "demo-secret");
|
|
78
|
+
const apiKey = getArgValue("-k", "--api-key", process.env.OTA_API_KEY || "");
|
|
79
|
+
const minNativeVersion = getArgValue("--min-native", "--minNativeVersion", "1.0.0");
|
|
80
|
+
|
|
81
|
+
if (!platformArg || !version) {
|
|
82
|
+
console.error("\x1b[31m❌ Thiếu tham số -p <platform> hoặc -v <version>!\x1b[0m");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const platforms = platformArg === "all" ? ["android", "ios"] : [platformArg.toLowerCase()];
|
|
87
|
+
const root = process.cwd();
|
|
88
|
+
|
|
89
|
+
async function buildAndPublish(p) {
|
|
90
|
+
console.log(`\n\x1b[36m==================================================\x1b[0m`);
|
|
91
|
+
console.log(`🚀 Đang đóng gói OTA [${p.toUpperCase()}] v${version}...`);
|
|
92
|
+
console.log(`\x1b[36m==================================================\x1b[0m`);
|
|
93
|
+
|
|
94
|
+
const outDir = path.join(root, ".ota_temp", p, version);
|
|
95
|
+
fs.rmSync(outDir, { recursive: true, force: true });
|
|
96
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
97
|
+
|
|
98
|
+
const bundleFile = p === "android" ? "index.android.bundle" : "main.jsbundle";
|
|
99
|
+
const bundlePath = path.join(outDir, bundleFile);
|
|
100
|
+
const assetsDir = path.join(outDir, "assets");
|
|
101
|
+
|
|
102
|
+
// 1. Metro Bundle
|
|
103
|
+
console.log(`📦 [1/3] Đang chạy Metro Bundle...`);
|
|
104
|
+
const bundleCmd = [
|
|
105
|
+
"npx react-native bundle",
|
|
106
|
+
`--platform ${p}`,
|
|
107
|
+
"--dev false",
|
|
108
|
+
"--minify true",
|
|
109
|
+
"--entry-file index.js",
|
|
110
|
+
`--bundle-output "${bundlePath}"`,
|
|
111
|
+
`--assets-dest "${assetsDir}"`
|
|
112
|
+
].join(" ");
|
|
113
|
+
|
|
114
|
+
execSync(bundleCmd, { cwd: root, stdio: "inherit" });
|
|
115
|
+
|
|
116
|
+
const sha256 = crypto
|
|
117
|
+
.createHash("sha256")
|
|
118
|
+
.update(fs.readFileSync(bundlePath))
|
|
119
|
+
.digest("hex");
|
|
120
|
+
|
|
121
|
+
const manifest = {
|
|
122
|
+
appId,
|
|
123
|
+
platform: p,
|
|
124
|
+
otaVersion: version,
|
|
125
|
+
bundle: bundleFile,
|
|
126
|
+
sha256,
|
|
127
|
+
channel: "production",
|
|
128
|
+
minNativeVersion,
|
|
129
|
+
mandatory: false,
|
|
130
|
+
releaseNotes,
|
|
131
|
+
createdAt: new Date().toISOString()
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(path.join(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
|
|
135
|
+
|
|
136
|
+
// 2. Package ZIP
|
|
137
|
+
console.log(`🗜️ [2/3] Đang nén file update.zip...`);
|
|
138
|
+
const zipPath = path.join(root, ".ota_temp", `${p}-${version}.zip`);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const archiver = require("archiver");
|
|
142
|
+
await new Promise((resolve, reject) => {
|
|
143
|
+
const output = fs.createWriteStream(zipPath);
|
|
144
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
145
|
+
output.on("close", resolve);
|
|
146
|
+
archive.on("error", reject);
|
|
147
|
+
archive.pipe(output);
|
|
148
|
+
archive.directory(outDir, false);
|
|
149
|
+
archive.finalize();
|
|
150
|
+
});
|
|
151
|
+
} catch (err) {
|
|
152
|
+
// Fallback sang công cụ nén zip có sẵn của hệ điều hành
|
|
153
|
+
if (process.platform === "win32") {
|
|
154
|
+
execSync(`powershell -command "Compress-Archive -Path '${outDir}/*' -DestinationPath '${zipPath}' -Force"`, { stdio: "ignore" });
|
|
155
|
+
} else {
|
|
156
|
+
execSync(`cd "${outDir}" && zip -r -q "${zipPath}" .`, { stdio: "ignore" });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 3. Publish to OTA Server
|
|
161
|
+
console.log(`📡 [3/3] Đang upload lên server: ${serverUrl} (App: ${appId})...`);
|
|
162
|
+
const zipBuffer = fs.readFileSync(zipPath);
|
|
163
|
+
manifest.size = zipBuffer.length;
|
|
164
|
+
|
|
165
|
+
const url = new URL(`/admin/publish/${appId}/${p}/${version}`, serverUrl);
|
|
166
|
+
const payload = Buffer.from(
|
|
167
|
+
JSON.stringify({
|
|
168
|
+
manifest,
|
|
169
|
+
zipBase64: zipBuffer.toString("base64")
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const client = url.protocol === "https:" ? https : http;
|
|
174
|
+
|
|
175
|
+
await new Promise((resolve, reject) => {
|
|
176
|
+
const req = client.request(
|
|
177
|
+
{
|
|
178
|
+
method: "POST",
|
|
179
|
+
hostname: url.hostname,
|
|
180
|
+
port: url.port || undefined,
|
|
181
|
+
path: url.pathname,
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "application/json",
|
|
184
|
+
"Content-Length": payload.length,
|
|
185
|
+
"X-OTA-ADMIN-TOKEN": adminToken,
|
|
186
|
+
...(apiKey ? { "X-OTA-API-KEY": apiKey } : {})
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
(res) => {
|
|
190
|
+
let body = "";
|
|
191
|
+
res.on("data", chunk => { body += chunk; });
|
|
192
|
+
res.on("end", () => {
|
|
193
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
194
|
+
console.log(`\x1b[32m✔ [${p.toUpperCase()}] Phát hành thành công v${version}!\x1b[0m`);
|
|
195
|
+
resolve();
|
|
196
|
+
} else {
|
|
197
|
+
reject(new Error(`Server response ${res.statusCode}: ${body}`));
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
req.on("error", reject);
|
|
204
|
+
req.write(payload);
|
|
205
|
+
req.end();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Cleanup temp files
|
|
209
|
+
fs.rmSync(path.join(root, ".ota_temp"), { recursive: true, force: true });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
(async () => {
|
|
213
|
+
for (const p of platforms) {
|
|
214
|
+
await buildAndPublish(p);
|
|
215
|
+
}
|
|
216
|
+
console.log("\n\x1b[32m🎉 TẤT CẢ PHIÊN BẢN ĐÃ PHÁT HÀNH THÀNH CÔNG!\x1b[0m\n");
|
|
217
|
+
})();
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
@objc(CustomOTABundleResolver)
|
|
4
|
+
public final class CustomOTABundleResolver: NSObject {
|
|
5
|
+
|
|
6
|
+
@objc public static func getOTADirectory() -> URL? {
|
|
7
|
+
FileManager.default.urls(
|
|
8
|
+
for: .documentDirectory,
|
|
9
|
+
in: .userDomainMask
|
|
10
|
+
).first?.appendingPathComponent("ota")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Kiểm tra trạng thái pending trial theo chuẩn CodePush 2-stage boot:
|
|
15
|
+
* - Lần khởi động đầu tiên của bản mới: Cho phép nạp bundle mới và đánh dấu trial = true.
|
|
16
|
+
* - Nếu trial == true mà pending vẫn là true -> Nghĩa là lần trước app bị CRASH trước khi gọi markReady() -> ROLLBACK!
|
|
17
|
+
*/
|
|
18
|
+
@objc public static func checkAndRollbackIfPending() {
|
|
19
|
+
guard let stateURL = stateURL(), FileManager.default.fileExists(atPath: stateURL.path) else { return }
|
|
20
|
+
do {
|
|
21
|
+
let data = try Data(contentsOf: stateURL)
|
|
22
|
+
guard var json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
23
|
+
let pending = json["pending"] as? Bool, pending else { return }
|
|
24
|
+
|
|
25
|
+
guard let otaDir = getOTADirectory() else { return }
|
|
26
|
+
let currentDir = otaDir.appendingPathComponent("current")
|
|
27
|
+
let backupDir = otaDir.appendingPathComponent("backup")
|
|
28
|
+
|
|
29
|
+
let trial = json["trial"] as? Bool ?? false
|
|
30
|
+
if trial {
|
|
31
|
+
// Đã thử chạy một lần trước đó mà app bị crash/tắt trước khi markReady() -> ROLLBACK!
|
|
32
|
+
print("[CustomOTA] Detected crash on pending release! Rolling back to backup...")
|
|
33
|
+
try? FileManager.default.removeItem(at: currentDir)
|
|
34
|
+
if FileManager.default.fileExists(atPath: backupDir.path) {
|
|
35
|
+
try? FileManager.default.moveItem(at: backupDir, to: currentDir)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Cập nhật failedVersions và lùi version
|
|
39
|
+
let failedVer = json["currentVersion"] as? String ?? ""
|
|
40
|
+
var failedVersions = json["failedVersions"] as? [String] ?? []
|
|
41
|
+
if !failedVer.isEmpty && !failedVersions.contains(failedVer) {
|
|
42
|
+
failedVersions.append(failedVer)
|
|
43
|
+
}
|
|
44
|
+
json["failedVersions"] = failedVersions
|
|
45
|
+
|
|
46
|
+
let prevVer = json["previousVersion"] as? String ?? "0.0.0"
|
|
47
|
+
json["currentVersion"] = prevVer
|
|
48
|
+
json["pending"] = false
|
|
49
|
+
json["trial"] = false
|
|
50
|
+
json["ready"] = true
|
|
51
|
+
} else {
|
|
52
|
+
// Lần khởi động đầu tiên: cho phép nạp bản mới và đánh dấu trial = true
|
|
53
|
+
json["trial"] = true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let updatedData = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted])
|
|
57
|
+
try updatedData.write(to: stateURL)
|
|
58
|
+
} catch {
|
|
59
|
+
print("OTA Rollback error: \(error)")
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@objc public static func currentBundleURL() -> URL? {
|
|
64
|
+
checkAndRollbackIfPending()
|
|
65
|
+
|
|
66
|
+
guard let otaDir = getOTADirectory() else { return nil }
|
|
67
|
+
let bundlePath = otaDir.appendingPathComponent("current").appendingPathComponent("main.jsbundle")
|
|
68
|
+
|
|
69
|
+
guard FileManager.default.fileExists(atPath: bundlePath.path) else {
|
|
70
|
+
return nil
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return bundlePath
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@objc public static func stateURL() -> URL? {
|
|
77
|
+
getOTADirectory()?.appendingPathComponent("state.json")
|
|
78
|
+
}
|
|
79
|
+
}
|
package/ios/OTAUpdater.m
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(OTAUpdater, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(unzip:(NSString *)zipPath
|
|
6
|
+
targetDir:(NSString *)targetDir
|
|
7
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
8
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
9
|
+
|
|
10
|
+
RCT_EXTERN_METHOD(markReady:(RCTPromiseResolveBlock)resolve
|
|
11
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
12
|
+
|
|
13
|
+
RCT_EXTERN_METHOD(getCurrentVersion:(RCTPromiseResolveBlock)resolve
|
|
14
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
15
|
+
|
|
16
|
+
RCT_EXTERN_METHOD(restart:(RCTPromiseResolveBlock)resolve
|
|
17
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
18
|
+
|
|
19
|
+
@end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import React
|
|
4
|
+
import SSZipArchive
|
|
5
|
+
|
|
6
|
+
@objc(OTAUpdater)
|
|
7
|
+
final class OTAUpdater: NSObject, RCTBridgeModule {
|
|
8
|
+
|
|
9
|
+
static func moduleName() -> String! {
|
|
10
|
+
return "OTAUpdater"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@objc
|
|
18
|
+
func unzip(
|
|
19
|
+
_ zipPath: String,
|
|
20
|
+
targetDir: String,
|
|
21
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
22
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
23
|
+
) {
|
|
24
|
+
let success = SSZipArchive.unzipFile(atPath: zipPath, toDestination: targetDir)
|
|
25
|
+
if success {
|
|
26
|
+
resolve(nil)
|
|
27
|
+
} else {
|
|
28
|
+
reject("OTA_UNZIP", "Failed to unzip archive at \(zipPath)", nil)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@objc
|
|
33
|
+
func markReady(
|
|
34
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
35
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
36
|
+
) {
|
|
37
|
+
do {
|
|
38
|
+
guard let stateURL = CustomOTABundleResolver.stateURL() else {
|
|
39
|
+
resolve(nil)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if FileManager.default.fileExists(atPath: stateURL.path) {
|
|
44
|
+
var json = try JSONSerialization.jsonObject(
|
|
45
|
+
with: Data(contentsOf: stateURL),
|
|
46
|
+
options: []
|
|
47
|
+
) as? [String: Any] ?? [:]
|
|
48
|
+
|
|
49
|
+
json["pending"] = false
|
|
50
|
+
json["ready"] = true
|
|
51
|
+
json["trial"] = false
|
|
52
|
+
|
|
53
|
+
let data = try JSONSerialization.data(
|
|
54
|
+
withJSONObject: json,
|
|
55
|
+
options: [.prettyPrinted]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
try data.write(to: stateURL)
|
|
59
|
+
|
|
60
|
+
if let otaDir = CustomOTABundleResolver.getOTADirectory() {
|
|
61
|
+
let backup = otaDir.appendingPathComponent("backup")
|
|
62
|
+
try? FileManager.default.removeItem(at: backup)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
resolve(nil)
|
|
67
|
+
} catch {
|
|
68
|
+
reject("OTA_READY", error.localizedDescription, error)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@objc
|
|
73
|
+
func getCurrentVersion(
|
|
74
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
75
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
76
|
+
) {
|
|
77
|
+
do {
|
|
78
|
+
guard let stateURL = CustomOTABundleResolver.stateURL(),
|
|
79
|
+
FileManager.default.fileExists(atPath: stateURL.path) else {
|
|
80
|
+
resolve("0.0.0")
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let object = try JSONSerialization.jsonObject(
|
|
85
|
+
with: Data(contentsOf: stateURL),
|
|
86
|
+
options: []
|
|
87
|
+
) as? [String: Any]
|
|
88
|
+
|
|
89
|
+
resolve(object?["currentVersion"] as? String ?? "0.0.0")
|
|
90
|
+
} catch {
|
|
91
|
+
reject("OTA_VERSION", error.localizedDescription, error)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@objc
|
|
96
|
+
func restart(
|
|
97
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
98
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
99
|
+
) {
|
|
100
|
+
resolve(nil)
|
|
101
|
+
DispatchQueue.main.async {
|
|
102
|
+
RCTTriggerReloadCommandListeners("OTA Update")
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.withCustomOTA = withCustomOTA;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_1 = require("react-native");
|
|
39
|
+
const manager_1 = require("./manager");
|
|
40
|
+
const types_1 = require("./types");
|
|
41
|
+
function withCustomOTA(options) {
|
|
42
|
+
return function (WrappedComponent) {
|
|
43
|
+
return function CustomOTAWrapper(props) {
|
|
44
|
+
(0, react_1.useEffect)(() => {
|
|
45
|
+
// Tự động kiểm tra cập nhật khi app khởi động
|
|
46
|
+
if (options.checkFrequency !== types_1.CheckFrequency.MANUAL) {
|
|
47
|
+
manager_1.CustomOTAManager.sync(options);
|
|
48
|
+
}
|
|
49
|
+
// Nếu bật kiểm tra khi App Resume từ background
|
|
50
|
+
if (options.checkFrequency === types_1.CheckFrequency.ON_APP_RESUME) {
|
|
51
|
+
const subscription = react_native_1.AppState.addEventListener("change", (nextAppState) => {
|
|
52
|
+
if (nextAppState === "active") {
|
|
53
|
+
manager_1.CustomOTAManager.sync(options);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return () => {
|
|
57
|
+
subscription.remove();
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
return react_1.default.createElement(WrappedComponent, { ...props });
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
const manager_1 = require("./manager");
|
|
18
|
+
const hoc_1 = require("./hoc");
|
|
19
|
+
__exportStar(require("./types"), exports);
|
|
20
|
+
__exportStar(require("./manager"), exports);
|
|
21
|
+
__exportStar(require("./hoc"), exports);
|
|
22
|
+
__exportStar(require("./storage"), exports);
|
|
23
|
+
const CustomOTA = {
|
|
24
|
+
sync: manager_1.CustomOTAManager.sync.bind(manager_1.CustomOTAManager),
|
|
25
|
+
checkForUpdate: manager_1.CustomOTAManager.checkForUpdate.bind(manager_1.CustomOTAManager),
|
|
26
|
+
downloadAndInstall: manager_1.CustomOTAManager.downloadAndInstall.bind(manager_1.CustomOTAManager),
|
|
27
|
+
restartApp: manager_1.CustomOTAManager.restartApp.bind(manager_1.CustomOTAManager),
|
|
28
|
+
getUpdateMetadata: manager_1.CustomOTAManager.getUpdateMetadata.bind(manager_1.CustomOTAManager),
|
|
29
|
+
markReady: manager_1.CustomOTAManager.markReady.bind(manager_1.CustomOTAManager),
|
|
30
|
+
wrap: hoc_1.withCustomOTA
|
|
31
|
+
};
|
|
32
|
+
exports.default = CustomOTA;
|