react-native-custom-ota 1.0.4 → 1.0.6
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/bin/custom-ota.js +11 -3
- package/lib/commonjs/manager.js +40 -2
- package/lib/module/manager.js +40 -2
- package/lib/typescript/manager.js +40 -2
- package/package.json +1 -1
- package/src/manager.ts +39 -2
package/bin/custom-ota.js
CHANGED
|
@@ -55,7 +55,7 @@ function detectAppId() {
|
|
|
55
55
|
try {
|
|
56
56
|
const data = JSON.parse(fs.readFileSync(appJsonPath, "utf8"));
|
|
57
57
|
if (data.name) return String(data.name).toLowerCase();
|
|
58
|
-
} catch (e) {}
|
|
58
|
+
} catch (e) { }
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const pkgJsonPath = path.join(root, "package.json");
|
|
@@ -63,7 +63,7 @@ function detectAppId() {
|
|
|
63
63
|
try {
|
|
64
64
|
const data = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
65
65
|
if (data.name) return String(data.name).toLowerCase();
|
|
66
|
-
} catch (e) {}
|
|
66
|
+
} catch (e) { }
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
return "com.otademoapp";
|
|
@@ -97,7 +97,15 @@ async function buildAndPublish(p) {
|
|
|
97
97
|
|
|
98
98
|
const bundleFile = p === "android" ? "index.android.bundle" : "main.jsbundle";
|
|
99
99
|
const bundlePath = path.join(outDir, bundleFile);
|
|
100
|
-
|
|
100
|
+
|
|
101
|
+
// Metro bundle output:
|
|
102
|
+
// Android tạo drawable-xxx/ và raw/, iOS tạo assets/ bên trong thư mục chỉ định.
|
|
103
|
+
// Đặt assetsDir = outDir để đồng bộ cấu trúc với JSBundle offline.
|
|
104
|
+
const testBrokenAssets =
|
|
105
|
+
process.env.TEST_BROKEN_ASSETS === "true" ||
|
|
106
|
+
args.includes("--broken-assets");
|
|
107
|
+
|
|
108
|
+
const assetsDir = testBrokenAssets ? path.join(outDir, "assets") : outDir;
|
|
101
109
|
|
|
102
110
|
// 1. Metro Bundle
|
|
103
111
|
console.log(`📦 [1/3] Đang chạy Metro Bundle...`);
|
package/lib/commonjs/manager.js
CHANGED
|
@@ -165,10 +165,32 @@ class CustomOTAManager {
|
|
|
165
165
|
const zipPath = `${storage_1.TEMP_DIR}/update-${version}.zip`;
|
|
166
166
|
await react_native_fs_1.default.unlink(zipPath).catch(() => { });
|
|
167
167
|
// Download zip file với theo dõi byte chính xác
|
|
168
|
+
// LƯU Ý QUAN TRỌNG VỀ RNFS TRÊN IOS:
|
|
169
|
+
// RNFS Downloader.m trên iOS chỉ gán `_statusCode` và `_contentLength` khi có `begin` callback.
|
|
170
|
+
// Nếu thiếu `begin`, `_statusCode` sẽ là nil, dẫn tới điều kiện emit progress bị bỏ qua hoàn toàn!
|
|
168
171
|
const downloadRes = react_native_fs_1.default.downloadFile({
|
|
169
172
|
fromUrl: update.downloadUrl.startsWith("http") ? update.downloadUrl : `${update.downloadUrl}`,
|
|
170
173
|
toFile: zipPath,
|
|
171
|
-
|
|
174
|
+
begin: (res) => {
|
|
175
|
+
const total = res.contentLength > 0 ? res.contentLength : (update.size || 0);
|
|
176
|
+
const initialProgress = {
|
|
177
|
+
totalBytes: total,
|
|
178
|
+
receivedBytes: 0,
|
|
179
|
+
percentage: 0,
|
|
180
|
+
progress: 0
|
|
181
|
+
};
|
|
182
|
+
if (onProgress) {
|
|
183
|
+
try {
|
|
184
|
+
onProgress(initialProgress, 0);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
onProgress(0);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
this.emitProgress(initialProgress);
|
|
191
|
+
},
|
|
192
|
+
progressDivider: 0,
|
|
193
|
+
progressInterval: 50,
|
|
172
194
|
progress: ({ bytesWritten, contentLength }) => {
|
|
173
195
|
const total = contentLength > 0 ? contentLength : (update.size || 0);
|
|
174
196
|
const percent = total > 0 ? Math.min(100, Math.round((bytesWritten / total) * 100)) : 0;
|
|
@@ -180,7 +202,6 @@ class CustomOTAManager {
|
|
|
180
202
|
progress: progressVal
|
|
181
203
|
};
|
|
182
204
|
if (onProgress) {
|
|
183
|
-
// Callback nhận cả object lẫn hỗ trợ kiểu cũ nhận số
|
|
184
205
|
try {
|
|
185
206
|
onProgress(progressObj, progressVal);
|
|
186
207
|
}
|
|
@@ -196,6 +217,23 @@ class CustomOTAManager {
|
|
|
196
217
|
this.emitDetailedStatus(types_1.DetailedStatus.ERROR, `Tải thất bại với mã lỗi HTTP ${res.statusCode}`);
|
|
197
218
|
throw new Error(`Download failed with status ${res.statusCode}`);
|
|
198
219
|
}
|
|
220
|
+
// Đảm bảo phát ra trạng thái 100% khi download hoàn tất
|
|
221
|
+
const finalTotal = update.size || 0;
|
|
222
|
+
const completedProgress = {
|
|
223
|
+
totalBytes: finalTotal,
|
|
224
|
+
receivedBytes: finalTotal,
|
|
225
|
+
percentage: 100,
|
|
226
|
+
progress: 1
|
|
227
|
+
};
|
|
228
|
+
if (onProgress) {
|
|
229
|
+
try {
|
|
230
|
+
onProgress(completedProgress, 1);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
onProgress(1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
this.emitProgress(completedProgress);
|
|
199
237
|
// SHA-256 verification
|
|
200
238
|
this.emitDetailedStatus(types_1.DetailedStatus.VERIFYING, "Đang kiểm tra tính toàn vẹn (SHA-256)...");
|
|
201
239
|
const actualHash = await react_native_fs_1.default.hash(zipPath, "sha256");
|
package/lib/module/manager.js
CHANGED
|
@@ -159,10 +159,32 @@ export class CustomOTAManager {
|
|
|
159
159
|
const zipPath = `${TEMP_DIR}/update-${version}.zip`;
|
|
160
160
|
await RNFS.unlink(zipPath).catch(() => { });
|
|
161
161
|
// Download zip file với theo dõi byte chính xác
|
|
162
|
+
// LƯU Ý QUAN TRỌNG VỀ RNFS TRÊN IOS:
|
|
163
|
+
// RNFS Downloader.m trên iOS chỉ gán `_statusCode` và `_contentLength` khi có `begin` callback.
|
|
164
|
+
// Nếu thiếu `begin`, `_statusCode` sẽ là nil, dẫn tới điều kiện emit progress bị bỏ qua hoàn toàn!
|
|
162
165
|
const downloadRes = RNFS.downloadFile({
|
|
163
166
|
fromUrl: update.downloadUrl.startsWith("http") ? update.downloadUrl : `${update.downloadUrl}`,
|
|
164
167
|
toFile: zipPath,
|
|
165
|
-
|
|
168
|
+
begin: (res) => {
|
|
169
|
+
const total = res.contentLength > 0 ? res.contentLength : (update.size || 0);
|
|
170
|
+
const initialProgress = {
|
|
171
|
+
totalBytes: total,
|
|
172
|
+
receivedBytes: 0,
|
|
173
|
+
percentage: 0,
|
|
174
|
+
progress: 0
|
|
175
|
+
};
|
|
176
|
+
if (onProgress) {
|
|
177
|
+
try {
|
|
178
|
+
onProgress(initialProgress, 0);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
onProgress(0);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
this.emitProgress(initialProgress);
|
|
185
|
+
},
|
|
186
|
+
progressDivider: 0,
|
|
187
|
+
progressInterval: 50,
|
|
166
188
|
progress: ({ bytesWritten, contentLength }) => {
|
|
167
189
|
const total = contentLength > 0 ? contentLength : (update.size || 0);
|
|
168
190
|
const percent = total > 0 ? Math.min(100, Math.round((bytesWritten / total) * 100)) : 0;
|
|
@@ -174,7 +196,6 @@ export class CustomOTAManager {
|
|
|
174
196
|
progress: progressVal
|
|
175
197
|
};
|
|
176
198
|
if (onProgress) {
|
|
177
|
-
// Callback nhận cả object lẫn hỗ trợ kiểu cũ nhận số
|
|
178
199
|
try {
|
|
179
200
|
onProgress(progressObj, progressVal);
|
|
180
201
|
}
|
|
@@ -190,6 +211,23 @@ export class CustomOTAManager {
|
|
|
190
211
|
this.emitDetailedStatus(DetailedStatus.ERROR, `Tải thất bại với mã lỗi HTTP ${res.statusCode}`);
|
|
191
212
|
throw new Error(`Download failed with status ${res.statusCode}`);
|
|
192
213
|
}
|
|
214
|
+
// Đảm bảo phát ra trạng thái 100% khi download hoàn tất
|
|
215
|
+
const finalTotal = update.size || 0;
|
|
216
|
+
const completedProgress = {
|
|
217
|
+
totalBytes: finalTotal,
|
|
218
|
+
receivedBytes: finalTotal,
|
|
219
|
+
percentage: 100,
|
|
220
|
+
progress: 1
|
|
221
|
+
};
|
|
222
|
+
if (onProgress) {
|
|
223
|
+
try {
|
|
224
|
+
onProgress(completedProgress, 1);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
onProgress(1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
this.emitProgress(completedProgress);
|
|
193
231
|
// SHA-256 verification
|
|
194
232
|
this.emitDetailedStatus(DetailedStatus.VERIFYING, "Đang kiểm tra tính toàn vẹn (SHA-256)...");
|
|
195
233
|
const actualHash = await RNFS.hash(zipPath, "sha256");
|
|
@@ -165,10 +165,32 @@ class CustomOTAManager {
|
|
|
165
165
|
const zipPath = `${storage_1.TEMP_DIR}/update-${version}.zip`;
|
|
166
166
|
await react_native_fs_1.default.unlink(zipPath).catch(() => { });
|
|
167
167
|
// Download zip file với theo dõi byte chính xác
|
|
168
|
+
// LƯU Ý QUAN TRỌNG VỀ RNFS TRÊN IOS:
|
|
169
|
+
// RNFS Downloader.m trên iOS chỉ gán `_statusCode` và `_contentLength` khi có `begin` callback.
|
|
170
|
+
// Nếu thiếu `begin`, `_statusCode` sẽ là nil, dẫn tới điều kiện emit progress bị bỏ qua hoàn toàn!
|
|
168
171
|
const downloadRes = react_native_fs_1.default.downloadFile({
|
|
169
172
|
fromUrl: update.downloadUrl.startsWith("http") ? update.downloadUrl : `${update.downloadUrl}`,
|
|
170
173
|
toFile: zipPath,
|
|
171
|
-
|
|
174
|
+
begin: (res) => {
|
|
175
|
+
const total = res.contentLength > 0 ? res.contentLength : (update.size || 0);
|
|
176
|
+
const initialProgress = {
|
|
177
|
+
totalBytes: total,
|
|
178
|
+
receivedBytes: 0,
|
|
179
|
+
percentage: 0,
|
|
180
|
+
progress: 0
|
|
181
|
+
};
|
|
182
|
+
if (onProgress) {
|
|
183
|
+
try {
|
|
184
|
+
onProgress(initialProgress, 0);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
onProgress(0);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
this.emitProgress(initialProgress);
|
|
191
|
+
},
|
|
192
|
+
progressDivider: 0,
|
|
193
|
+
progressInterval: 50,
|
|
172
194
|
progress: ({ bytesWritten, contentLength }) => {
|
|
173
195
|
const total = contentLength > 0 ? contentLength : (update.size || 0);
|
|
174
196
|
const percent = total > 0 ? Math.min(100, Math.round((bytesWritten / total) * 100)) : 0;
|
|
@@ -180,7 +202,6 @@ class CustomOTAManager {
|
|
|
180
202
|
progress: progressVal
|
|
181
203
|
};
|
|
182
204
|
if (onProgress) {
|
|
183
|
-
// Callback nhận cả object lẫn hỗ trợ kiểu cũ nhận số
|
|
184
205
|
try {
|
|
185
206
|
onProgress(progressObj, progressVal);
|
|
186
207
|
}
|
|
@@ -196,6 +217,23 @@ class CustomOTAManager {
|
|
|
196
217
|
this.emitDetailedStatus(types_1.DetailedStatus.ERROR, `Tải thất bại với mã lỗi HTTP ${res.statusCode}`);
|
|
197
218
|
throw new Error(`Download failed with status ${res.statusCode}`);
|
|
198
219
|
}
|
|
220
|
+
// Đảm bảo phát ra trạng thái 100% khi download hoàn tất
|
|
221
|
+
const finalTotal = update.size || 0;
|
|
222
|
+
const completedProgress = {
|
|
223
|
+
totalBytes: finalTotal,
|
|
224
|
+
receivedBytes: finalTotal,
|
|
225
|
+
percentage: 100,
|
|
226
|
+
progress: 1
|
|
227
|
+
};
|
|
228
|
+
if (onProgress) {
|
|
229
|
+
try {
|
|
230
|
+
onProgress(completedProgress, 1);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
onProgress(1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
this.emitProgress(completedProgress);
|
|
199
237
|
// SHA-256 verification
|
|
200
238
|
this.emitDetailedStatus(types_1.DetailedStatus.VERIFYING, "Đang kiểm tra tính toàn vẹn (SHA-256)...");
|
|
201
239
|
const actualHash = await react_native_fs_1.default.hash(zipPath, "sha256");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-custom-ota",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Self-hosted OTA (Over-The-Air) update library for React Native with automatic rollback, similar to react-native-code-push",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
package/src/manager.ts
CHANGED
|
@@ -211,10 +211,31 @@ export class CustomOTAManager {
|
|
|
211
211
|
await RNFS.unlink(zipPath).catch(() => {});
|
|
212
212
|
|
|
213
213
|
// Download zip file với theo dõi byte chính xác
|
|
214
|
+
// LƯU Ý QUAN TRỌNG VỀ RNFS TRÊN IOS:
|
|
215
|
+
// RNFS Downloader.m trên iOS chỉ gán `_statusCode` và `_contentLength` khi có `begin` callback.
|
|
216
|
+
// Nếu thiếu `begin`, `_statusCode` sẽ là nil, dẫn tới điều kiện emit progress bị bỏ qua hoàn toàn!
|
|
214
217
|
const downloadRes = RNFS.downloadFile({
|
|
215
218
|
fromUrl: update.downloadUrl.startsWith("http") ? update.downloadUrl : `${update.downloadUrl}`,
|
|
216
219
|
toFile: zipPath,
|
|
217
|
-
|
|
220
|
+
begin: (res) => {
|
|
221
|
+
const total = res.contentLength > 0 ? res.contentLength : (update.size || 0);
|
|
222
|
+
const initialProgress: DownloadProgress = {
|
|
223
|
+
totalBytes: total,
|
|
224
|
+
receivedBytes: 0,
|
|
225
|
+
percentage: 0,
|
|
226
|
+
progress: 0
|
|
227
|
+
};
|
|
228
|
+
if (onProgress) {
|
|
229
|
+
try {
|
|
230
|
+
(onProgress as any)(initialProgress, 0);
|
|
231
|
+
} catch {
|
|
232
|
+
(onProgress as any)(0);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
this.emitProgress(initialProgress);
|
|
236
|
+
},
|
|
237
|
+
progressDivider: 0,
|
|
238
|
+
progressInterval: 50,
|
|
218
239
|
progress: ({ bytesWritten, contentLength }) => {
|
|
219
240
|
const total = contentLength > 0 ? contentLength : (update.size || 0);
|
|
220
241
|
const percent = total > 0 ? Math.min(100, Math.round((bytesWritten / total) * 100)) : 0;
|
|
@@ -228,7 +249,6 @@ export class CustomOTAManager {
|
|
|
228
249
|
};
|
|
229
250
|
|
|
230
251
|
if (onProgress) {
|
|
231
|
-
// Callback nhận cả object lẫn hỗ trợ kiểu cũ nhận số
|
|
232
252
|
try {
|
|
233
253
|
(onProgress as any)(progressObj, progressVal);
|
|
234
254
|
} catch {
|
|
@@ -246,6 +266,23 @@ export class CustomOTAManager {
|
|
|
246
266
|
throw new Error(`Download failed with status ${res.statusCode}`);
|
|
247
267
|
}
|
|
248
268
|
|
|
269
|
+
// Đảm bảo phát ra trạng thái 100% khi download hoàn tất
|
|
270
|
+
const finalTotal = update.size || 0;
|
|
271
|
+
const completedProgress: DownloadProgress = {
|
|
272
|
+
totalBytes: finalTotal,
|
|
273
|
+
receivedBytes: finalTotal,
|
|
274
|
+
percentage: 100,
|
|
275
|
+
progress: 1
|
|
276
|
+
};
|
|
277
|
+
if (onProgress) {
|
|
278
|
+
try {
|
|
279
|
+
(onProgress as any)(completedProgress, 1);
|
|
280
|
+
} catch {
|
|
281
|
+
(onProgress as any)(1);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
this.emitProgress(completedProgress);
|
|
285
|
+
|
|
249
286
|
// SHA-256 verification
|
|
250
287
|
this.emitDetailedStatus(DetailedStatus.VERIFYING, "Đang kiểm tra tính toàn vẹn (SHA-256)...");
|
|
251
288
|
const actualHash = await RNFS.hash(zipPath, "sha256");
|