react-native-custom-ota 1.0.2 → 1.0.3

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.
@@ -0,0 +1,163 @@
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.OTAProgressModal = void 0;
7
+ exports.formatBytes = formatBytes;
8
+ const react_1 = __importDefault(require("react"));
9
+ const react_native_1 = require("react-native");
10
+ function formatBytes(bytes, decimals = 1) {
11
+ if (!bytes || bytes <= 0)
12
+ return "0 B";
13
+ const k = 1024;
14
+ const dm = decimals < 0 ? 0 : decimals;
15
+ const sizes = ["B", "KB", "MB", "GB"];
16
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
17
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
18
+ }
19
+ const OTAProgressModal = ({ visible, progress, title = "Cập nhật ứng dụng", statusText = "Đang tải bản cập nhật...", version }) => {
20
+ // Chuẩn hóa dữ liệu tiến độ
21
+ let percent = 0;
22
+ let receivedBytes = 0;
23
+ let totalBytes = 0;
24
+ if (typeof progress === "number") {
25
+ percent = Math.min(100, Math.max(0, Math.round(progress * 100)));
26
+ }
27
+ else if (progress && typeof progress === "object") {
28
+ percent = Math.min(100, Math.max(0, Math.round(progress.percentage || 0)));
29
+ receivedBytes = progress.receivedBytes || 0;
30
+ totalBytes = progress.totalBytes || 0;
31
+ }
32
+ const bytesInfo = totalBytes > 0
33
+ ? `${formatBytes(receivedBytes)} / ${formatBytes(totalBytes)}`
34
+ : "";
35
+ return (react_1.default.createElement(react_native_1.Modal, { transparent: true, animationType: "fade", visible: visible, statusBarTranslucent: true },
36
+ react_1.default.createElement(react_native_1.View, { style: styles.overlay },
37
+ react_1.default.createElement(react_native_1.View, { style: styles.card },
38
+ react_1.default.createElement(react_native_1.View, { style: styles.header },
39
+ react_1.default.createElement(react_native_1.View, { style: styles.iconContainer },
40
+ react_1.default.createElement(react_native_1.Text, { style: styles.iconText }, "\u26A1")),
41
+ react_1.default.createElement(react_native_1.View, { style: styles.headerTextContainer },
42
+ react_1.default.createElement(react_native_1.Text, { style: styles.title }, title),
43
+ version ? (react_1.default.createElement(react_native_1.Text, { style: styles.versionBadge },
44
+ "Phi\u00EAn b\u1EA3n v",
45
+ version)) : null)),
46
+ react_1.default.createElement(react_native_1.Text, { style: styles.statusText }, statusText),
47
+ react_1.default.createElement(react_native_1.View, { style: styles.progressBarTrack },
48
+ react_1.default.createElement(react_native_1.View, { style: [styles.progressBarFill, { width: `${percent}%` }] })),
49
+ react_1.default.createElement(react_native_1.View, { style: styles.detailsRow },
50
+ react_1.default.createElement(react_native_1.Text, { style: styles.percentageText },
51
+ percent,
52
+ "%"),
53
+ bytesInfo ? (react_1.default.createElement(react_native_1.Text, { style: styles.bytesText }, bytesInfo)) : null),
54
+ react_1.default.createElement(react_native_1.View, { style: styles.footer },
55
+ react_1.default.createElement(react_native_1.ActivityIndicator, { size: "small", color: "#38bdf8", style: { marginRight: 8 } }),
56
+ react_1.default.createElement(react_native_1.Text, { style: styles.footerText }, "Vui l\u00F2ng kh\u00F4ng \u0111\u00F3ng \u1EE9ng d\u1EE5ng..."))))));
57
+ };
58
+ exports.OTAProgressModal = OTAProgressModal;
59
+ const styles = react_native_1.StyleSheet.create({
60
+ overlay: {
61
+ flex: 1,
62
+ backgroundColor: "rgba(0, 0, 0, 0.72)",
63
+ justifyContent: "center",
64
+ alignItems: "center",
65
+ paddingHorizontal: 24
66
+ },
67
+ card: {
68
+ width: "100%",
69
+ maxWidth: 380,
70
+ backgroundColor: "#0f172a",
71
+ borderRadius: 20,
72
+ padding: 24,
73
+ borderWidth: 1,
74
+ borderColor: "rgba(255, 255, 255, 0.12)",
75
+ shadowColor: "#000",
76
+ shadowOffset: { width: 0, height: 12 },
77
+ shadowOpacity: 0.5,
78
+ shadowRadius: 20,
79
+ elevation: 16
80
+ },
81
+ header: {
82
+ flexDirection: "row",
83
+ alignItems: "center",
84
+ marginBottom: 16
85
+ },
86
+ iconContainer: {
87
+ width: 44,
88
+ height: 44,
89
+ borderRadius: 12,
90
+ backgroundColor: "rgba(56, 189, 248, 0.15)",
91
+ justifyContent: "center",
92
+ alignItems: "center",
93
+ marginRight: 14,
94
+ borderWidth: 1,
95
+ borderColor: "rgba(56, 189, 248, 0.3)"
96
+ },
97
+ iconText: {
98
+ fontSize: 22
99
+ },
100
+ headerTextContainer: {
101
+ flex: 1
102
+ },
103
+ title: {
104
+ fontSize: 18,
105
+ fontWeight: "700",
106
+ color: "#f8fafc",
107
+ letterSpacing: 0.2
108
+ },
109
+ versionBadge: {
110
+ fontSize: 12,
111
+ color: "#38bdf8",
112
+ fontWeight: "600",
113
+ marginTop: 2
114
+ },
115
+ statusText: {
116
+ fontSize: 14,
117
+ color: "#94a3b8",
118
+ marginBottom: 16,
119
+ lineHeight: 20
120
+ },
121
+ progressBarTrack: {
122
+ height: 10,
123
+ backgroundColor: "rgba(255, 255, 255, 0.08)",
124
+ borderRadius: 999,
125
+ overflow: "hidden",
126
+ marginBottom: 10
127
+ },
128
+ progressBarFill: {
129
+ height: "100%",
130
+ backgroundColor: "#38bdf8",
131
+ borderRadius: 999
132
+ },
133
+ detailsRow: {
134
+ flexDirection: "row",
135
+ justifyContent: "space-between",
136
+ alignItems: "center",
137
+ marginBottom: 20
138
+ },
139
+ percentageText: {
140
+ fontSize: 16,
141
+ fontWeight: "800",
142
+ color: "#38bdf8"
143
+ },
144
+ bytesText: {
145
+ fontSize: 13,
146
+ color: "#64748b",
147
+ fontWeight: "500"
148
+ },
149
+ footer: {
150
+ flexDirection: "row",
151
+ alignItems: "center",
152
+ justifyContent: "center",
153
+ paddingTop: 12,
154
+ borderTopWidth: 1,
155
+ borderTopColor: "rgba(255, 255, 255, 0.06)"
156
+ },
157
+ footerText: {
158
+ fontSize: 12,
159
+ color: "#64748b",
160
+ fontWeight: "500"
161
+ }
162
+ });
163
+ exports.default = exports.OTAProgressModal;
@@ -1,3 +1,4 @@
1
1
  import { ComponentType } from "react";
2
2
  import { SyncOptions } from "./types";
3
3
  export declare function withCustomOTA(options: SyncOptions): <P extends object>(WrappedComponent: ComponentType<P>) => ComponentType<P>;
4
+ export default withCustomOTA;
@@ -38,10 +38,39 @@ const react_1 = __importStar(require("react"));
38
38
  const react_native_1 = require("react-native");
39
39
  const manager_1 = require("./manager");
40
40
  const types_1 = require("./types");
41
+ const ProgressModal_1 = require("./ProgressModal");
41
42
  function withCustomOTA(options) {
42
43
  return function (WrappedComponent) {
43
44
  return function CustomOTAWrapper(props) {
45
+ const [isDownloading, setIsDownloading] = (0, react_1.useState)(false);
46
+ const [progress, setProgress] = (0, react_1.useState)({
47
+ totalBytes: 0,
48
+ receivedBytes: 0,
49
+ percentage: 0,
50
+ progress: 0
51
+ });
52
+ const [statusText, setStatusText] = (0, react_1.useState)("Đang tải bản cập nhật...");
44
53
  (0, react_1.useEffect)(() => {
54
+ let removeProg;
55
+ let removeStatus;
56
+ if (options.showProgressModal) {
57
+ removeProg = manager_1.CustomOTAManager.addProgressListener((p) => {
58
+ setProgress(p);
59
+ setIsDownloading(true);
60
+ });
61
+ removeStatus = manager_1.CustomOTAManager.addDetailedStatusListener((s, msg) => {
62
+ if (msg)
63
+ setStatusText(msg);
64
+ if (s === types_1.DetailedStatus.DOWNLOADING ||
65
+ s === types_1.DetailedStatus.VERIFYING ||
66
+ s === types_1.DetailedStatus.INSTALLING) {
67
+ setIsDownloading(true);
68
+ }
69
+ else if (s === types_1.DetailedStatus.COMPLETED || s === types_1.DetailedStatus.ERROR) {
70
+ setIsDownloading(false);
71
+ }
72
+ });
73
+ }
45
74
  // Tự động kiểm tra cập nhật khi app khởi động
46
75
  if (options.checkFrequency !== types_1.CheckFrequency.MANUAL) {
47
76
  manager_1.CustomOTAManager.sync(options);
@@ -55,10 +84,19 @@ function withCustomOTA(options) {
55
84
  });
56
85
  return () => {
57
86
  subscription.remove();
87
+ removeProg?.();
88
+ removeStatus?.();
58
89
  };
59
90
  }
91
+ return () => {
92
+ removeProg?.();
93
+ removeStatus?.();
94
+ };
60
95
  }, []);
61
- return react_1.default.createElement(WrappedComponent, { ...props });
96
+ return (react_1.default.createElement(react_native_1.View, { style: { flex: 1 } },
97
+ react_1.default.createElement(WrappedComponent, { ...props }),
98
+ options.showProgressModal && (react_1.default.createElement(ProgressModal_1.OTAProgressModal, { visible: isDownloading, progress: progress, statusText: statusText }))));
62
99
  };
63
100
  };
64
101
  }
102
+ exports.default = withCustomOTA;
@@ -1,9 +1,12 @@
1
1
  import { CustomOTAManager } from "./manager";
2
2
  import { withCustomOTA } from "./hoc";
3
+ import { useCustomOTA } from "./useCustomOTA";
3
4
  export * from "./types";
4
5
  export * from "./manager";
5
6
  export * from "./hoc";
6
7
  export * from "./storage";
8
+ export * from "./ProgressModal";
9
+ export * from "./useCustomOTA";
7
10
  declare const CustomOTA: {
8
11
  sync: typeof CustomOTAManager.sync;
9
12
  checkForUpdate: typeof CustomOTAManager.checkForUpdate;
@@ -11,6 +14,10 @@ declare const CustomOTA: {
11
14
  restartApp: typeof CustomOTAManager.restartApp;
12
15
  getUpdateMetadata: typeof CustomOTAManager.getUpdateMetadata;
13
16
  markReady: typeof CustomOTAManager.markReady;
17
+ addProgressListener: typeof CustomOTAManager.addProgressListener;
18
+ addDetailedStatusListener: typeof CustomOTAManager.addDetailedStatusListener;
14
19
  wrap: typeof withCustomOTA;
20
+ ProgressModal: import("@types/react").FC<import("./ProgressModal").OTAProgressModalProps>;
21
+ useCustomOTA: typeof useCustomOTA;
15
22
  };
16
23
  export default CustomOTA;
@@ -16,10 +16,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  const manager_1 = require("./manager");
18
18
  const hoc_1 = require("./hoc");
19
+ const ProgressModal_1 = require("./ProgressModal");
20
+ const useCustomOTA_1 = require("./useCustomOTA");
19
21
  __exportStar(require("./types"), exports);
20
22
  __exportStar(require("./manager"), exports);
21
23
  __exportStar(require("./hoc"), exports);
22
24
  __exportStar(require("./storage"), exports);
25
+ __exportStar(require("./ProgressModal"), exports);
26
+ __exportStar(require("./useCustomOTA"), exports);
23
27
  const CustomOTA = {
24
28
  sync: manager_1.CustomOTAManager.sync.bind(manager_1.CustomOTAManager),
25
29
  checkForUpdate: manager_1.CustomOTAManager.checkForUpdate.bind(manager_1.CustomOTAManager),
@@ -27,6 +31,10 @@ const CustomOTA = {
27
31
  restartApp: manager_1.CustomOTAManager.restartApp.bind(manager_1.CustomOTAManager),
28
32
  getUpdateMetadata: manager_1.CustomOTAManager.getUpdateMetadata.bind(manager_1.CustomOTAManager),
29
33
  markReady: manager_1.CustomOTAManager.markReady.bind(manager_1.CustomOTAManager),
30
- wrap: hoc_1.withCustomOTA
34
+ addProgressListener: manager_1.CustomOTAManager.addProgressListener.bind(manager_1.CustomOTAManager),
35
+ addDetailedStatusListener: manager_1.CustomOTAManager.addDetailedStatusListener.bind(manager_1.CustomOTAManager),
36
+ wrap: hoc_1.withCustomOTA,
37
+ ProgressModal: ProgressModal_1.OTAProgressModal,
38
+ useCustomOTA: useCustomOTA_1.useCustomOTA
31
39
  };
32
40
  exports.default = CustomOTA;
@@ -1,6 +1,20 @@
1
- import { InstallMode, LocalPackage, RemotePackage, SyncOptions, SyncStatus } from "./types";
1
+ import { DownloadProgress, InstallMode, LocalPackage, RemotePackage, SyncOptions, SyncStatus, DetailedStatus } from "./types";
2
2
  export declare class CustomOTAManager {
3
3
  private static defaultDialog;
4
+ private static progressListeners;
5
+ private static detailedStatusListeners;
6
+ /**
7
+ * Đăng ký lắng nghe tiến độ tải về toàn cục
8
+ * @returns Hàm hủy đăng ký (unsubscribe)
9
+ */
10
+ static addProgressListener(listener: (progress: DownloadProgress) => void): () => void;
11
+ /**
12
+ * Đăng ký lắng nghe trạng thái chi tiết của quá trình OTA
13
+ * @returns Hàm hủy đăng ký (unsubscribe)
14
+ */
15
+ static addDetailedStatusListener(listener: (status: DetailedStatus, message?: string) => void): () => void;
16
+ private static emitProgress;
17
+ private static emitDetailedStatus;
4
18
  /**
5
19
  * Đánh dấu bản build OTA hiện tại đã khởi động thành công.
6
20
  * Xóa bản backup trước đó và tắt cờ pending.
@@ -15,7 +29,7 @@ export declare class CustomOTAManager {
15
29
  */
16
30
  static getUpdateMetadata(): Promise<LocalPackage>;
17
31
  /**
18
- * Kiểm tra xem server có bản cập nhật mới hay không.
32
+ * Kiểm tra xem có bản cập nhật mới từ OTA Server hay không.
19
33
  */
20
34
  static checkForUpdate(options: {
21
35
  serverUrl: string;
@@ -25,11 +39,11 @@ export declare class CustomOTAManager {
25
39
  channel?: string;
26
40
  }): Promise<RemotePackage | null>;
27
41
  /**
28
- * Tải và cài đặt bản cập nhật.
42
+ * Tải và cài đặt bản cập nhật với theo dõi tiến độ chi tiết.
29
43
  */
30
- static downloadAndInstall(update: RemotePackage, onProgress?: (progress: number) => void, installMode?: InstallMode): Promise<boolean>;
44
+ static downloadAndInstall(update: RemotePackage, onProgress?: ((progress: DownloadProgress) => void) | ((progressValue: number) => void), installMode?: InstallMode): Promise<boolean>;
31
45
  /**
32
46
  * Phương thức đồng bộ tự động như CodePush.sync()
33
47
  */
34
- static sync(options: SyncOptions, syncStatusCallback?: (status: SyncStatus) => void, downloadProgressCallback?: (progress: number) => void): Promise<SyncStatus>;
48
+ static sync(options: SyncOptions, syncStatusCallback?: (status: SyncStatus) => void, downloadProgressCallback?: ((progress: DownloadProgress) => void) | ((progressValue: number) => void)): Promise<SyncStatus>;
35
49
  }
@@ -10,6 +10,46 @@ const native_1 = require("./native");
10
10
  const storage_1 = require("./storage");
11
11
  const types_1 = require("./types");
12
12
  class CustomOTAManager {
13
+ /**
14
+ * Đăng ký lắng nghe tiến độ tải về toàn cục
15
+ * @returns Hàm hủy đăng ký (unsubscribe)
16
+ */
17
+ static addProgressListener(listener) {
18
+ this.progressListeners.add(listener);
19
+ return () => {
20
+ this.progressListeners.delete(listener);
21
+ };
22
+ }
23
+ /**
24
+ * Đăng ký lắng nghe trạng thái chi tiết của quá trình OTA
25
+ * @returns Hàm hủy đăng ký (unsubscribe)
26
+ */
27
+ static addDetailedStatusListener(listener) {
28
+ this.detailedStatusListeners.add(listener);
29
+ return () => {
30
+ this.detailedStatusListeners.delete(listener);
31
+ };
32
+ }
33
+ static emitProgress(progress) {
34
+ for (const listener of this.progressListeners) {
35
+ try {
36
+ listener(progress);
37
+ }
38
+ catch (e) {
39
+ console.warn("[CustomOTA] Error in progress listener:", e);
40
+ }
41
+ }
42
+ }
43
+ static emitDetailedStatus(status, message) {
44
+ for (const listener of this.detailedStatusListeners) {
45
+ try {
46
+ listener(status, message);
47
+ }
48
+ catch (e) {
49
+ console.warn("[CustomOTA] Error in detailed status listener:", e);
50
+ }
51
+ }
52
+ }
13
53
  /**
14
54
  * Đánh dấu bản build OTA hiện tại đã khởi động thành công.
15
55
  * Xóa bản backup trước đó và tắt cờ pending.
@@ -49,7 +89,7 @@ class CustomOTAManager {
49
89
  return (0, storage_1.readState)();
50
90
  }
51
91
  /**
52
- * Kiểm tra xem server có bản cập nhật mới hay không.
92
+ * Kiểm tra xem có bản cập nhật mới từ OTA Server hay không.
53
93
  */
54
94
  static async checkForUpdate(options) {
55
95
  await (0, storage_1.ensureOTADirs)();
@@ -109,7 +149,7 @@ class CustomOTAManager {
109
149
  return data;
110
150
  }
111
151
  /**
112
- * Tải và cài đặt bản cập nhật.
152
+ * Tải và cài đặt bản cập nhật với theo dõi tiến độ chi tiết.
113
153
  */
114
154
  static async downloadAndInstall(update, onProgress, installMode) {
115
155
  const version = update.otaVersion || update.version;
@@ -121,30 +161,51 @@ class CustomOTAManager {
121
161
  ].filter(Boolean).join(", ");
122
162
  throw new Error(`Invalid update package metadata: missing ${missingFields}`);
123
163
  }
164
+ this.emitDetailedStatus(types_1.DetailedStatus.DOWNLOADING, `Bắt đầu tải bản cập nhật v${version}...`);
124
165
  const zipPath = `${storage_1.TEMP_DIR}/update-${version}.zip`;
125
166
  await react_native_fs_1.default.unlink(zipPath).catch(() => { });
126
- // Download zip file
167
+ // Download zip file với theo dõi byte chính xác
127
168
  const downloadRes = react_native_fs_1.default.downloadFile({
128
169
  fromUrl: update.downloadUrl.startsWith("http") ? update.downloadUrl : `${update.downloadUrl}`,
129
170
  toFile: zipPath,
130
171
  progressDivider: 1,
131
172
  progress: ({ bytesWritten, contentLength }) => {
132
- if (contentLength > 0 && onProgress) {
133
- onProgress(bytesWritten / contentLength);
173
+ const total = contentLength > 0 ? contentLength : (update.size || 0);
174
+ const percent = total > 0 ? Math.min(100, Math.round((bytesWritten / total) * 100)) : 0;
175
+ const progressVal = total > 0 ? Math.min(1, bytesWritten / total) : 0;
176
+ const progressObj = {
177
+ totalBytes: total,
178
+ receivedBytes: bytesWritten,
179
+ percentage: percent,
180
+ progress: progressVal
181
+ };
182
+ if (onProgress) {
183
+ // Callback nhận cả object lẫn hỗ trợ kiểu cũ nhận số
184
+ try {
185
+ onProgress(progressObj, progressVal);
186
+ }
187
+ catch {
188
+ onProgress(progressVal);
189
+ }
134
190
  }
191
+ this.emitProgress(progressObj);
135
192
  }
136
193
  });
137
194
  const res = await downloadRes.promise;
138
195
  if (res.statusCode < 200 || res.statusCode >= 300) {
196
+ this.emitDetailedStatus(types_1.DetailedStatus.ERROR, `Tải thất bại với mã lỗi HTTP ${res.statusCode}`);
139
197
  throw new Error(`Download failed with status ${res.statusCode}`);
140
198
  }
141
199
  // SHA-256 verification
200
+ this.emitDetailedStatus(types_1.DetailedStatus.VERIFYING, "Đang kiểm tra tính toàn vẹn (SHA-256)...");
142
201
  const actualHash = await react_native_fs_1.default.hash(zipPath, "sha256");
143
202
  if (actualHash.toLowerCase() !== update.sha256.toLowerCase()) {
144
203
  await react_native_fs_1.default.unlink(zipPath).catch(() => { });
204
+ this.emitDetailedStatus(types_1.DetailedStatus.ERROR, "Mã băm SHA-256 không trùng khớp!");
145
205
  throw new Error(`SHA-256 Checksum mismatch! Expected ${update.sha256}, got ${actualHash}`);
146
206
  }
147
207
  // Unpack temp directory
208
+ this.emitDetailedStatus(types_1.DetailedStatus.INSTALLING, "Đang giải nén và nạp gói cập nhật...");
148
209
  const tempDir = `${storage_1.TEMP_DIR}/${version}`;
149
210
  await react_native_fs_1.default.unlink(tempDir).catch(() => { });
150
211
  await react_native_fs_1.default.mkdir(tempDir);
@@ -171,6 +232,7 @@ class CustomOTAManager {
171
232
  failedVersions: oldState.failedVersions || []
172
233
  });
173
234
  await react_native_fs_1.default.unlink(zipPath).catch(() => { });
235
+ this.emitDetailedStatus(types_1.DetailedStatus.COMPLETED, `Đã cài đặt bản cập nhật v${version} thành công!`);
174
236
  if (installMode === types_1.InstallMode.IMMEDIATE) {
175
237
  await this.restartApp();
176
238
  }
@@ -258,5 +320,8 @@ CustomOTAManager.defaultDialog = {
258
320
  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
321
  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
322
  optionalInstallButtonLabel: "Cập nhật",
261
- optionalIgnoreButtonLabel: "Để sau"
323
+ optionalIgnoreButtonLabel: "Để sau",
324
+ showProgressModal: true
262
325
  };
326
+ CustomOTAManager.progressListeners = new Set();
327
+ CustomOTAManager.detailedStatusListeners = new Set();
@@ -6,6 +6,16 @@ export declare enum SyncStatus {
6
6
  UPDATE_INSTALLED = 4,
7
7
  ERROR = -1
8
8
  }
9
+ export declare enum DetailedStatus {
10
+ IDLE = "IDLE",
11
+ CHECKING = "CHECKING",
12
+ UPDATE_AVAILABLE = "UPDATE_AVAILABLE",
13
+ DOWNLOADING = "DOWNLOADING",
14
+ VERIFYING = "VERIFYING",
15
+ INSTALLING = "INSTALLING",
16
+ COMPLETED = "COMPLETED",
17
+ ERROR = "ERROR"
18
+ }
9
19
  export declare enum CheckFrequency {
10
20
  ON_APP_START = "ON_APP_START",
11
21
  ON_APP_RESUME = "ON_APP_RESUME",
@@ -15,12 +25,29 @@ export declare enum InstallMode {
15
25
  IMMEDIATE = "IMMEDIATE",
16
26
  ON_NEXT_RESTART = "ON_NEXT_RESTART"
17
27
  }
28
+ /**
29
+ * Thông tin tiến độ tải về bản cập nhật
30
+ */
31
+ export interface DownloadProgress {
32
+ /** Tổng số byte cần tải */
33
+ totalBytes: number;
34
+ /** Số byte đã tải về máy */
35
+ receivedBytes: number;
36
+ /** Tỷ lệ hoàn thành từ 0 đến 100 (%) */
37
+ percentage: number;
38
+ /** Giá trị tỷ lệ từ 0.0 đến 1.0 (tương thích CodePush / Progress Bar) */
39
+ progress: number;
40
+ }
41
+ export type DownloadProgressCallback = (progress: DownloadProgress) => void;
42
+ export type DetailedStatusCallback = (status: DetailedStatus, message?: string) => void;
18
43
  export interface UpdateDialogOptions {
19
44
  title?: string;
20
45
  optionalUpdateMessage?: string;
21
46
  mandatoryUpdateMessage?: string;
22
47
  optionalInstallButtonLabel?: string;
23
48
  optionalIgnoreButtonLabel?: string;
49
+ /** Hiển thị modal tiến độ tải sau khi người dùng bấm cập nhật */
50
+ showProgressModal?: boolean;
24
51
  }
25
52
  export interface SyncOptions {
26
53
  serverUrl: string;
@@ -31,6 +58,8 @@ export interface SyncOptions {
31
58
  installMode?: InstallMode;
32
59
  mandatoryInstallMode?: InstallMode;
33
60
  updateDialog?: boolean | UpdateDialogOptions;
61
+ /** Tự động hiển thị modal tiến độ tải cập nhật */
62
+ showProgressModal?: boolean;
34
63
  checkFrequency?: CheckFrequency;
35
64
  }
36
65
  export interface RemotePackage {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InstallMode = exports.CheckFrequency = exports.SyncStatus = void 0;
3
+ exports.InstallMode = exports.CheckFrequency = exports.DetailedStatus = exports.SyncStatus = void 0;
4
4
  var SyncStatus;
5
5
  (function (SyncStatus) {
6
6
  SyncStatus[SyncStatus["UP_TO_DATE"] = 0] = "UP_TO_DATE";
@@ -10,6 +10,17 @@ var SyncStatus;
10
10
  SyncStatus[SyncStatus["UPDATE_INSTALLED"] = 4] = "UPDATE_INSTALLED";
11
11
  SyncStatus[SyncStatus["ERROR"] = -1] = "ERROR";
12
12
  })(SyncStatus || (exports.SyncStatus = SyncStatus = {}));
13
+ var DetailedStatus;
14
+ (function (DetailedStatus) {
15
+ DetailedStatus["IDLE"] = "IDLE";
16
+ DetailedStatus["CHECKING"] = "CHECKING";
17
+ DetailedStatus["UPDATE_AVAILABLE"] = "UPDATE_AVAILABLE";
18
+ DetailedStatus["DOWNLOADING"] = "DOWNLOADING";
19
+ DetailedStatus["VERIFYING"] = "VERIFYING";
20
+ DetailedStatus["INSTALLING"] = "INSTALLING";
21
+ DetailedStatus["COMPLETED"] = "COMPLETED";
22
+ DetailedStatus["ERROR"] = "ERROR";
23
+ })(DetailedStatus || (exports.DetailedStatus = DetailedStatus = {}));
13
24
  var CheckFrequency;
14
25
  (function (CheckFrequency) {
15
26
  CheckFrequency["ON_APP_START"] = "ON_APP_START";
@@ -0,0 +1,35 @@
1
+ import { DownloadProgress, LocalPackage, RemotePackage, SyncOptions, SyncStatus, DetailedStatus } from "./types";
2
+ export interface UseCustomOTAReturn {
3
+ /** Đang kiểm tra cập nhật */
4
+ isChecking: boolean;
5
+ /** Đang trong tiến trình tải xuống hoặc cài đặt */
6
+ isDownloading: boolean;
7
+ /** Thông tin tiến độ tải chi tiết */
8
+ progress: DownloadProgress;
9
+ /** Trạng thái tổng quát của Sync */
10
+ syncStatus: SyncStatus;
11
+ /** Trạng thái chi tiết (IDLE, CHECKING, DOWNLOADING, VERIFYING, INSTALLING, COMPLETED, ERROR) */
12
+ detailedStatus: DetailedStatus;
13
+ /** Thông điệp trạng thái bằng chữ (tiếng Việt thân thiện) */
14
+ statusText: string;
15
+ /** Thông tin phiên bản OTA hiện tại của máy */
16
+ activeMetadata: LocalPackage | null;
17
+ /** Phiên bản active hiện tại (vd: '1.0.8') */
18
+ activeVersion: string;
19
+ /** Kiểm tra bản cập nhật mới từ server */
20
+ checkForUpdate: (options: {
21
+ serverUrl: string;
22
+ appId?: string;
23
+ apiKey?: string;
24
+ nativeVersion?: string;
25
+ channel?: string;
26
+ }) => Promise<RemotePackage | null>;
27
+ /** Thực hiện đồng bộ tự động */
28
+ sync: (options: SyncOptions) => Promise<SyncStatus>;
29
+ /** Khởi động lại ứng dụng để áp dụng bản OTA */
30
+ restartApp: () => Promise<void>;
31
+ /** Làm mới thông tin metadata phiên bản hiện tại */
32
+ refreshMetadata: () => Promise<LocalPackage>;
33
+ }
34
+ export declare function useCustomOTA(): UseCustomOTAReturn;
35
+ export default useCustomOTA;