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.
Files changed (48) hide show
  1. package/README.md +214 -2
  2. package/RNCustomOTA.podspec +20 -0
  3. package/android/build.gradle +21 -0
  4. package/android/src/main/java/com/customota/CustomOTABundleResolver.kt +85 -0
  5. package/android/src/main/java/com/customota/OTAUpdaterModule.kt +152 -0
  6. package/android/src/main/java/com/customota/OTAUpdaterPackage.kt +22 -0
  7. package/bin/custom-ota.js +217 -0
  8. package/ios/CustomOTABundleResolver.swift +79 -0
  9. package/ios/OTAUpdater.m +19 -0
  10. package/ios/OTAUpdater.swift +105 -0
  11. package/lib/commonjs/hoc.js +64 -0
  12. package/lib/commonjs/index.js +32 -0
  13. package/lib/commonjs/manager.js +262 -0
  14. package/lib/commonjs/native.js +9 -0
  15. package/lib/commonjs/storage.js +48 -0
  16. package/lib/commonjs/types.js +23 -0
  17. package/lib/module/hoc.js +28 -0
  18. package/lib/module/index.js +16 -0
  19. package/lib/module/manager.js +255 -0
  20. package/lib/module/native.js +6 -0
  21. package/lib/module/storage.js +39 -0
  22. package/lib/module/types.js +20 -0
  23. package/lib/typescript/hoc.d.ts +3 -0
  24. package/lib/typescript/hoc.js +64 -0
  25. package/lib/typescript/index.d.ts +16 -0
  26. package/lib/typescript/index.js +32 -0
  27. package/lib/typescript/manager.d.ts +35 -0
  28. package/lib/typescript/manager.js +262 -0
  29. package/lib/typescript/native.d.ts +8 -0
  30. package/lib/typescript/native.js +9 -0
  31. package/lib/typescript/src/hoc.d.ts +3 -0
  32. package/lib/typescript/src/index.d.ts +15 -0
  33. package/lib/typescript/src/manager.d.ts +33 -0
  34. package/lib/typescript/src/native.d.ts +8 -0
  35. package/lib/typescript/src/storage.d.ts +8 -0
  36. package/lib/typescript/src/types.d.ts +54 -0
  37. package/lib/typescript/storage.d.ts +8 -0
  38. package/lib/typescript/storage.js +48 -0
  39. package/lib/typescript/types.d.ts +60 -0
  40. package/lib/typescript/types.js +23 -0
  41. package/package.json +49 -4
  42. package/react-native.config.js +11 -0
  43. package/src/hoc.tsx +34 -0
  44. package/src/index.ts +19 -0
  45. package/src/manager.ts +328 -0
  46. package/src/native.ts +17 -0
  47. package/src/storage.ts +45 -0
  48. package/src/types.ts +66 -0
@@ -0,0 +1,39 @@
1
+ import RNFS from "react-native-fs";
2
+ export const OTA_ROOT = `${RNFS.DocumentDirectoryPath}/ota`;
3
+ export const CURRENT_DIR = `${OTA_ROOT}/current`;
4
+ export const BACKUP_DIR = `${OTA_ROOT}/backup`;
5
+ export const TEMP_DIR = `${OTA_ROOT}/temp`;
6
+ const STATE_FILE = `${OTA_ROOT}/state.json`;
7
+ export async function ensureOTADirs() {
8
+ await RNFS.mkdir(OTA_ROOT).catch(() => { });
9
+ await RNFS.mkdir(TEMP_DIR).catch(() => { });
10
+ }
11
+ export async function readState() {
12
+ await ensureOTADirs();
13
+ if (!(await RNFS.exists(STATE_FILE))) {
14
+ return {
15
+ currentVersion: "0.0.0",
16
+ previousVersion: null,
17
+ pending: false,
18
+ ready: true,
19
+ failedVersions: []
20
+ };
21
+ }
22
+ try {
23
+ const content = await RNFS.readFile(STATE_FILE, "utf8");
24
+ return JSON.parse(content);
25
+ }
26
+ catch {
27
+ return {
28
+ currentVersion: "0.0.0",
29
+ previousVersion: null,
30
+ pending: false,
31
+ ready: true,
32
+ failedVersions: []
33
+ };
34
+ }
35
+ }
36
+ export async function writeState(state) {
37
+ await ensureOTADirs();
38
+ await RNFS.writeFile(STATE_FILE, JSON.stringify(state, null, 2), "utf8");
39
+ }
@@ -0,0 +1,20 @@
1
+ export var SyncStatus;
2
+ (function (SyncStatus) {
3
+ SyncStatus[SyncStatus["UP_TO_DATE"] = 0] = "UP_TO_DATE";
4
+ SyncStatus[SyncStatus["UPDATE_AVAILABLE"] = 1] = "UPDATE_AVAILABLE";
5
+ SyncStatus[SyncStatus["DOWNLOADING_PACKAGE"] = 2] = "DOWNLOADING_PACKAGE";
6
+ SyncStatus[SyncStatus["INSTALLING_UPDATE"] = 3] = "INSTALLING_UPDATE";
7
+ SyncStatus[SyncStatus["UPDATE_INSTALLED"] = 4] = "UPDATE_INSTALLED";
8
+ SyncStatus[SyncStatus["ERROR"] = -1] = "ERROR";
9
+ })(SyncStatus || (SyncStatus = {}));
10
+ export var CheckFrequency;
11
+ (function (CheckFrequency) {
12
+ CheckFrequency["ON_APP_START"] = "ON_APP_START";
13
+ CheckFrequency["ON_APP_RESUME"] = "ON_APP_RESUME";
14
+ CheckFrequency["MANUAL"] = "MANUAL";
15
+ })(CheckFrequency || (CheckFrequency = {}));
16
+ export var InstallMode;
17
+ (function (InstallMode) {
18
+ InstallMode["IMMEDIATE"] = "IMMEDIATE";
19
+ InstallMode["ON_NEXT_RESTART"] = "ON_NEXT_RESTART";
20
+ })(InstallMode || (InstallMode = {}));
@@ -0,0 +1,3 @@
1
+ import { ComponentType } from "react";
2
+ import { SyncOptions } from "./types";
3
+ export declare function withCustomOTA(options: SyncOptions): <P extends object>(WrappedComponent: ComponentType<P>) => ComponentType<P>;
@@ -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,16 @@
1
+ import { CustomOTAManager } from "./manager";
2
+ import { withCustomOTA } from "./hoc";
3
+ export * from "./types";
4
+ export * from "./manager";
5
+ export * from "./hoc";
6
+ export * from "./storage";
7
+ declare const CustomOTA: {
8
+ sync: typeof CustomOTAManager.sync;
9
+ checkForUpdate: typeof CustomOTAManager.checkForUpdate;
10
+ downloadAndInstall: typeof CustomOTAManager.downloadAndInstall;
11
+ restartApp: typeof CustomOTAManager.restartApp;
12
+ getUpdateMetadata: typeof CustomOTAManager.getUpdateMetadata;
13
+ markReady: typeof CustomOTAManager.markReady;
14
+ wrap: typeof withCustomOTA;
15
+ };
16
+ export default CustomOTA;
@@ -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;
@@ -0,0 +1,35 @@
1
+ import { InstallMode, LocalPackage, RemotePackage, SyncOptions, SyncStatus } from "./types";
2
+ export declare class CustomOTAManager {
3
+ private static defaultDialog;
4
+ /**
5
+ * Đánh dấu bản build OTA hiện tại đã khởi động thành công.
6
+ * Xóa bản backup trước đó và tắt cờ pending.
7
+ */
8
+ static markReady(): Promise<void>;
9
+ /**
10
+ * Khởi động lại ứng dụng để nạp ngay bản OTA mới.
11
+ */
12
+ static restartApp(): Promise<void>;
13
+ /**
14
+ * Lấy thông tin phiên bản OTA hiện hành trên máy.
15
+ */
16
+ static getUpdateMetadata(): Promise<LocalPackage>;
17
+ /**
18
+ * Kiểm tra xem server có bản cập nhật mới hay không.
19
+ */
20
+ static checkForUpdate(options: {
21
+ serverUrl: string;
22
+ appId?: string;
23
+ apiKey?: string;
24
+ nativeVersion?: string;
25
+ channel?: string;
26
+ }): Promise<RemotePackage | null>;
27
+ /**
28
+ * Tải và cài đặt bản cập nhật.
29
+ */
30
+ static downloadAndInstall(update: RemotePackage, onProgress?: (progress: number) => void, installMode?: InstallMode): Promise<boolean>;
31
+ /**
32
+ * Phương thức đồng bộ tự động như CodePush.sync()
33
+ */
34
+ static sync(options: SyncOptions, syncStatusCallback?: (status: SyncStatus) => void, downloadProgressCallback?: (progress: number) => void): Promise<SyncStatus>;
35
+ }
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CustomOTAManager = void 0;
7
+ const react_native_1 = require("react-native");
8
+ const react_native_fs_1 = __importDefault(require("react-native-fs"));
9
+ const native_1 = require("./native");
10
+ const storage_1 = require("./storage");
11
+ const types_1 = require("./types");
12
+ class CustomOTAManager {
13
+ /**
14
+ * Đánh dấu bản build OTA hiện tại đã khởi động thành công.
15
+ * Xóa bản backup trước đó và tắt cờ pending.
16
+ */
17
+ static async markReady() {
18
+ try {
19
+ const state = await (0, storage_1.readState)();
20
+ state.pending = false;
21
+ state.ready = true;
22
+ state.trial = false;
23
+ await (0, storage_1.writeState)(state);
24
+ }
25
+ catch (e) {
26
+ console.warn("[CustomOTA] Error updating state in markReady:", e);
27
+ }
28
+ if (native_1.OTAUpdater?.markReady) {
29
+ try {
30
+ await native_1.OTAUpdater.markReady();
31
+ }
32
+ catch (e) {
33
+ console.warn("[CustomOTA] Error calling native markReady:", e);
34
+ }
35
+ }
36
+ }
37
+ /**
38
+ * Khởi động lại ứng dụng để nạp ngay bản OTA mới.
39
+ */
40
+ static async restartApp() {
41
+ if (native_1.OTAUpdater?.restart) {
42
+ await native_1.OTAUpdater.restart();
43
+ }
44
+ }
45
+ /**
46
+ * Lấy thông tin phiên bản OTA hiện hành trên máy.
47
+ */
48
+ static async getUpdateMetadata() {
49
+ return (0, storage_1.readState)();
50
+ }
51
+ /**
52
+ * Kiểm tra xem server có bản cập nhật mới hay không.
53
+ */
54
+ static async checkForUpdate(options) {
55
+ await (0, storage_1.ensureOTADirs)();
56
+ const state = await (0, storage_1.readState)();
57
+ const platform = react_native_1.Platform.OS === "ios" ? "ios" : "android";
58
+ const nativeVersion = options.nativeVersion || "1.0.0";
59
+ const channel = options.channel || "production";
60
+ const params = new URLSearchParams({
61
+ platform,
62
+ nativeVersion,
63
+ otaVersion: state.currentVersion,
64
+ channel
65
+ });
66
+ if (options.appId) {
67
+ params.append("appId", options.appId);
68
+ }
69
+ if (options.apiKey) {
70
+ params.append("apiKey", options.apiKey);
71
+ }
72
+ const headers = {};
73
+ if (options.apiKey) {
74
+ headers["X-OTA-API-KEY"] = options.apiKey;
75
+ }
76
+ const response = await fetch(`${options.serverUrl.replace(/\/$/, "")}/ota/check?${params}`, {
77
+ headers
78
+ });
79
+ if (!response.ok) {
80
+ throw new Error(`OTA Check failed: HTTP ${response.status}`);
81
+ }
82
+ const rawData = await response.json();
83
+ if (!rawData.updateAvailable) {
84
+ return null;
85
+ }
86
+ const targetVersion = rawData.otaVersion || rawData.version;
87
+ // Không báo cập nhật nếu phiên bản đã được cài đặt trên máy
88
+ if (targetVersion && state.currentVersion === targetVersion) {
89
+ return null;
90
+ }
91
+ // Không báo cập nhật nếu phiên bản này từng bị crash và tự rollback
92
+ if (targetVersion && state.failedVersions?.includes(targetVersion)) {
93
+ return null;
94
+ }
95
+ const serverUrlClean = options.serverUrl.replace(/\/$/, "");
96
+ const rawUrl = rawData.downloadUrl || "";
97
+ const fullDownloadUrl = rawUrl.startsWith("http")
98
+ ? rawUrl
99
+ : `${serverUrlClean}${rawUrl.startsWith("/") ? "" : "/"}${rawUrl}`;
100
+ const data = {
101
+ ...rawData,
102
+ updateAvailable: true,
103
+ version: targetVersion,
104
+ otaVersion: targetVersion,
105
+ downloadUrl: fullDownloadUrl,
106
+ mandatory: Boolean(rawData.mandatory || rawData.isMandatory),
107
+ isMandatory: Boolean(rawData.mandatory || rawData.isMandatory)
108
+ };
109
+ return data;
110
+ }
111
+ /**
112
+ * Tải và cài đặt bản cập nhật.
113
+ */
114
+ static async downloadAndInstall(update, onProgress, installMode) {
115
+ const version = update.otaVersion || update.version;
116
+ if (!update.downloadUrl || !version || !update.sha256) {
117
+ const missingFields = [
118
+ !update.downloadUrl && "downloadUrl",
119
+ !version && "version",
120
+ !update.sha256 && "sha256"
121
+ ].filter(Boolean).join(", ");
122
+ throw new Error(`Invalid update package metadata: missing ${missingFields}`);
123
+ }
124
+ const zipPath = `${storage_1.TEMP_DIR}/update-${version}.zip`;
125
+ await react_native_fs_1.default.unlink(zipPath).catch(() => { });
126
+ // Download zip file
127
+ const downloadRes = react_native_fs_1.default.downloadFile({
128
+ fromUrl: update.downloadUrl.startsWith("http") ? update.downloadUrl : `${update.downloadUrl}`,
129
+ toFile: zipPath,
130
+ progressDivider: 1,
131
+ progress: ({ bytesWritten, contentLength }) => {
132
+ if (contentLength > 0 && onProgress) {
133
+ onProgress(bytesWritten / contentLength);
134
+ }
135
+ }
136
+ });
137
+ const res = await downloadRes.promise;
138
+ if (res.statusCode < 200 || res.statusCode >= 300) {
139
+ throw new Error(`Download failed with status ${res.statusCode}`);
140
+ }
141
+ // SHA-256 verification
142
+ const actualHash = await react_native_fs_1.default.hash(zipPath, "sha256");
143
+ if (actualHash.toLowerCase() !== update.sha256.toLowerCase()) {
144
+ await react_native_fs_1.default.unlink(zipPath).catch(() => { });
145
+ throw new Error(`SHA-256 Checksum mismatch! Expected ${update.sha256}, got ${actualHash}`);
146
+ }
147
+ // Unpack temp directory
148
+ const tempDir = `${storage_1.TEMP_DIR}/${version}`;
149
+ await react_native_fs_1.default.unlink(tempDir).catch(() => { });
150
+ await react_native_fs_1.default.mkdir(tempDir);
151
+ await native_1.OTAUpdater.unzip(zipPath, tempDir);
152
+ const bundleName = react_native_1.Platform.OS === "ios" ? "main.jsbundle" : "index.android.bundle";
153
+ const bundleFile = `${tempDir}/${bundleName}`;
154
+ if (!(await react_native_fs_1.default.exists(bundleFile))) {
155
+ throw new Error(`Bundle missing after unzip: ${bundleName}`);
156
+ }
157
+ // Atomic move & backup
158
+ await react_native_fs_1.default.unlink(storage_1.BACKUP_DIR).catch(() => { });
159
+ if (await react_native_fs_1.default.exists(storage_1.CURRENT_DIR)) {
160
+ await react_native_fs_1.default.moveFile(storage_1.CURRENT_DIR, storage_1.BACKUP_DIR);
161
+ }
162
+ await react_native_fs_1.default.moveFile(tempDir, storage_1.CURRENT_DIR);
163
+ // Save state
164
+ const oldState = await (0, storage_1.readState)();
165
+ await (0, storage_1.writeState)({
166
+ currentVersion: version,
167
+ previousVersion: oldState.currentVersion !== "0.0.0" ? oldState.currentVersion : null,
168
+ pending: true,
169
+ ready: false,
170
+ trial: false,
171
+ failedVersions: oldState.failedVersions || []
172
+ });
173
+ await react_native_fs_1.default.unlink(zipPath).catch(() => { });
174
+ if (installMode === types_1.InstallMode.IMMEDIATE) {
175
+ await this.restartApp();
176
+ }
177
+ return true;
178
+ }
179
+ /**
180
+ * Phương thức đồng bộ tự động như CodePush.sync()
181
+ */
182
+ static async sync(options, syncStatusCallback, downloadProgressCallback) {
183
+ try {
184
+ // 1. Tự động markReady khi khởi động
185
+ await this.markReady();
186
+ // 2. Kiểm tra update
187
+ const update = await this.checkForUpdate({
188
+ serverUrl: options.serverUrl,
189
+ appId: options.appId,
190
+ apiKey: options.apiKey,
191
+ nativeVersion: options.nativeVersion,
192
+ channel: options.channel
193
+ });
194
+ if (!update || !update.updateAvailable) {
195
+ syncStatusCallback?.(types_1.SyncStatus.UP_TO_DATE);
196
+ return types_1.SyncStatus.UP_TO_DATE;
197
+ }
198
+ syncStatusCallback?.(types_1.SyncStatus.UPDATE_AVAILABLE);
199
+ const proceedWithInstall = async () => {
200
+ syncStatusCallback?.(types_1.SyncStatus.DOWNLOADING_PACKAGE);
201
+ const serverUrlClean = options.serverUrl.replace(/\/$/, "");
202
+ const fullDownloadUrl = update.downloadUrl?.startsWith("http")
203
+ ? update.downloadUrl
204
+ : `${serverUrlClean}${update.downloadUrl}`;
205
+ await this.downloadAndInstall({ ...update, downloadUrl: fullDownloadUrl }, downloadProgressCallback);
206
+ syncStatusCallback?.(types_1.SyncStatus.INSTALLING_UPDATE);
207
+ syncStatusCallback?.(types_1.SyncStatus.UPDATE_INSTALLED);
208
+ const effectiveInstallMode = (update.mandatory && options.mandatoryInstallMode)
209
+ ? options.mandatoryInstallMode
210
+ : (options.installMode || types_1.InstallMode.ON_NEXT_RESTART);
211
+ if (effectiveInstallMode === types_1.InstallMode.ON_NEXT_RESTART) {
212
+ return types_1.SyncStatus.UPDATE_INSTALLED;
213
+ }
214
+ // Restart immediate
215
+ await this.restartApp();
216
+ return types_1.SyncStatus.UPDATE_INSTALLED;
217
+ };
218
+ // 3. Nếu cấu hình hiển thị Update Dialog
219
+ if (options.updateDialog) {
220
+ const dialogOpts = typeof options.updateDialog === "object"
221
+ ? { ...this.defaultDialog, ...options.updateDialog }
222
+ : this.defaultDialog;
223
+ const isMandatory = Boolean(update.mandatory);
224
+ const message = isMandatory
225
+ ? `${dialogOpts.mandatoryUpdateMessage}\n\nPhiên bản: ${update.otaVersion}\n${update.releaseNotes || ""}`
226
+ : `${dialogOpts.optionalUpdateMessage}\n\nPhiên bản: ${update.otaVersion}\n${update.releaseNotes || ""}`;
227
+ const buttons = [];
228
+ if (!isMandatory) {
229
+ buttons.push({
230
+ text: dialogOpts.optionalIgnoreButtonLabel,
231
+ style: "cancel"
232
+ });
233
+ }
234
+ buttons.push({
235
+ text: dialogOpts.optionalInstallButtonLabel,
236
+ onPress: () => {
237
+ proceedWithInstall();
238
+ }
239
+ });
240
+ react_native_1.Alert.alert(dialogOpts.title || "Cập nhật ứng dụng", message, buttons, {
241
+ cancelable: !isMandatory
242
+ });
243
+ return types_1.SyncStatus.UPDATE_AVAILABLE;
244
+ }
245
+ // Tự động tải ngầm không hiện dialog
246
+ return await proceedWithInstall();
247
+ }
248
+ catch (error) {
249
+ console.error("[react-native-custom-ota] Sync error:", error);
250
+ syncStatusCallback?.(types_1.SyncStatus.ERROR);
251
+ return types_1.SyncStatus.ERROR;
252
+ }
253
+ }
254
+ }
255
+ exports.CustomOTAManager = CustomOTAManager;
256
+ CustomOTAManager.defaultDialog = {
257
+ title: "Có bản cập nhật mới",
258
+ optionalUpdateMessage: "Đã có bản cập nhật ứng dụng mới. Bạn có muốn cài đặt ngay bây giờ?",
259
+ mandatoryUpdateMessage: "Đã có bản cập nhật bắt buộc. Vui lòng cài đặt để tiếp tục sử dụng.",
260
+ optionalInstallButtonLabel: "Cập nhật",
261
+ optionalIgnoreButtonLabel: "Để sau"
262
+ };
@@ -0,0 +1,8 @@
1
+ interface OTAUpdaterInterface {
2
+ unzip(zipPath: string, targetDir: string): Promise<void>;
3
+ markReady(): Promise<void>;
4
+ getCurrentVersion(): Promise<string>;
5
+ restart(): Promise<void>;
6
+ }
7
+ export declare const OTAUpdater: OTAUpdaterInterface;
8
+ export {};
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OTAUpdater = void 0;
4
+ const react_native_1 = require("react-native");
5
+ exports.OTAUpdater = react_native_1.NativeModules.OTAUpdater;
6
+ if (!exports.OTAUpdater) {
7
+ console.warn("[react-native-custom-ota] NativeModule.OTAUpdater is null! " +
8
+ "Make sure you have linked the library and rebuilt the native binary.");
9
+ }
@@ -0,0 +1,3 @@
1
+ import { ComponentType } from "react";
2
+ import { SyncOptions } from "./types";
3
+ export declare function withCustomOTA(options: SyncOptions): <P extends object>(WrappedComponent: ComponentType<P>) => ComponentType<P>;
@@ -0,0 +1,15 @@
1
+ import { CustomOTAManager } from "./manager";
2
+ import { withCustomOTA } from "./hoc";
3
+ export * from "./types";
4
+ export * from "./manager";
5
+ export * from "./hoc";
6
+ declare const CustomOTA: {
7
+ sync: typeof CustomOTAManager.sync;
8
+ checkForUpdate: typeof CustomOTAManager.checkForUpdate;
9
+ downloadAndInstall: typeof CustomOTAManager.downloadAndInstall;
10
+ restartApp: typeof CustomOTAManager.restartApp;
11
+ getUpdateMetadata: typeof CustomOTAManager.getUpdateMetadata;
12
+ markReady: typeof CustomOTAManager.markReady;
13
+ wrap: typeof withCustomOTA;
14
+ };
15
+ export default CustomOTA;
@@ -0,0 +1,33 @@
1
+ import { LocalPackage, RemotePackage, SyncOptions, SyncStatus } from "./types";
2
+ export declare class CustomOTAManager {
3
+ private static defaultDialog;
4
+ /**
5
+ * Đánh dấu bản build OTA hiện tại đã khởi động thành công.
6
+ * Xóa bản backup trước đó và tắt cờ pending.
7
+ */
8
+ static markReady(): Promise<void>;
9
+ /**
10
+ * Khởi động lại ứng dụng để nạp ngay bản OTA mới.
11
+ */
12
+ static restartApp(): Promise<void>;
13
+ /**
14
+ * Lấy thông tin phiên bản OTA hiện hành trên máy.
15
+ */
16
+ static getUpdateMetadata(): Promise<LocalPackage>;
17
+ /**
18
+ * Kiểm tra xem server có bản cập nhật mới hay không.
19
+ */
20
+ static checkForUpdate(options: {
21
+ serverUrl: string;
22
+ nativeVersion?: string;
23
+ channel?: string;
24
+ }): Promise<RemotePackage>;
25
+ /**
26
+ * Tải và cài đặt bản cập nhật.
27
+ */
28
+ static downloadAndInstall(update: RemotePackage, onProgress?: (progress: number) => void): Promise<void>;
29
+ /**
30
+ * Phương thức đồng bộ tự động như CodePush.sync()
31
+ */
32
+ static sync(options: SyncOptions, syncStatusCallback?: (status: SyncStatus) => void, downloadProgressCallback?: (progress: number) => void): Promise<SyncStatus>;
33
+ }
@@ -0,0 +1,8 @@
1
+ interface OTAUpdaterInterface {
2
+ unzip(zipPath: string, targetDir: string): Promise<void>;
3
+ markReady(): Promise<void>;
4
+ getCurrentVersion(): Promise<string>;
5
+ restart(): Promise<void>;
6
+ }
7
+ export declare const OTAUpdater: OTAUpdaterInterface;
8
+ export {};
@@ -0,0 +1,8 @@
1
+ import { LocalPackage } from "./types";
2
+ export declare const OTA_ROOT: string;
3
+ export declare const CURRENT_DIR: string;
4
+ export declare const BACKUP_DIR: string;
5
+ export declare const TEMP_DIR: string;
6
+ export declare function ensureOTADirs(): Promise<void>;
7
+ export declare function readState(): Promise<LocalPackage>;
8
+ export declare function writeState(state: LocalPackage): Promise<void>;
@@ -0,0 +1,54 @@
1
+ export declare enum SyncStatus {
2
+ UP_TO_DATE = 0,
3
+ UPDATE_AVAILABLE = 1,
4
+ DOWNLOADING_PACKAGE = 2,
5
+ INSTALLING_UPDATE = 3,
6
+ UPDATE_INSTALLED = 4,
7
+ ERROR = -1
8
+ }
9
+ export declare enum CheckFrequency {
10
+ ON_APP_START = "ON_APP_START",
11
+ ON_APP_RESUME = "ON_APP_RESUME",
12
+ MANUAL = "MANUAL"
13
+ }
14
+ export declare enum InstallMode {
15
+ IMMEDIATE = "IMMEDIATE",
16
+ ON_NEXT_RESTART = "ON_NEXT_RESTART"
17
+ }
18
+ export interface UpdateDialogOptions {
19
+ title?: string;
20
+ optionalUpdateMessage?: string;
21
+ mandatoryUpdateMessage?: string;
22
+ optionalInstallButtonLabel?: string;
23
+ optionalIgnoreButtonLabel?: string;
24
+ }
25
+ export interface SyncOptions {
26
+ serverUrl: string;
27
+ nativeVersion?: string;
28
+ channel?: string;
29
+ installMode?: InstallMode;
30
+ updateDialog?: boolean | UpdateDialogOptions;
31
+ checkFrequency?: CheckFrequency;
32
+ }
33
+ export interface RemotePackage {
34
+ updateAvailable: boolean;
35
+ appId?: string;
36
+ platform?: string;
37
+ otaVersion?: string;
38
+ bundle?: string;
39
+ sha256?: string;
40
+ channel?: string;
41
+ minNativeVersion?: string;
42
+ mandatory?: boolean;
43
+ releaseNotes?: string;
44
+ downloadUrl?: string;
45
+ size?: number;
46
+ reason?: string;
47
+ }
48
+ export interface LocalPackage {
49
+ currentVersion: string;
50
+ previousVersion: string | null;
51
+ pending: boolean;
52
+ ready: boolean;
53
+ failedVersions: string[];
54
+ }
@@ -0,0 +1,8 @@
1
+ import { LocalPackage } from "./types";
2
+ export declare const OTA_ROOT: string;
3
+ export declare const CURRENT_DIR: string;
4
+ export declare const BACKUP_DIR: string;
5
+ export declare const TEMP_DIR: string;
6
+ export declare function ensureOTADirs(): Promise<void>;
7
+ export declare function readState(): Promise<LocalPackage>;
8
+ export declare function writeState(state: LocalPackage): Promise<void>;