@appsonair.ir/react-native 1.0.6 → 1.0.7

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 (2) hide show
  1. package/lib/sync.js +114 -7
  2. package/package.json +1 -1
package/lib/sync.js CHANGED
@@ -456,6 +456,98 @@ function getApkFilePath(label) {
456
456
  const dirs = ReactNativeBlobUtil.fs.dirs;
457
457
  return `${dirs.DocumentDir}/ota-updates/${label}.apk`;
458
458
  }
459
+ function getApkZipPath(label) {
460
+ const ReactNativeBlobUtil = require('react-native-blob-util').default;
461
+ const dirs = ReactNativeBlobUtil.fs.dirs;
462
+ return `${dirs.DocumentDir}/ota-updates/${label}-apk.zip`;
463
+ }
464
+ function getApkExtractDir(label) {
465
+ const ReactNativeBlobUtil = require('react-native-blob-util').default;
466
+ const dirs = ReactNativeBlobUtil.fs.dirs;
467
+ return `${dirs.DocumentDir}/ota-updates/${label}-apk`;
468
+ }
469
+ function apkAbiFromName(fileName) {
470
+ const lower = fileName.replace(/^.*[/\\]/, '').toLowerCase();
471
+ if (lower.includes('arm64-v8a') || lower.includes('arm64') || lower.includes('-v8a')) {
472
+ return 'arm64-v8a';
473
+ }
474
+ if (lower.includes('armeabi-v7a') ||
475
+ lower.includes('armeabi') ||
476
+ lower.includes('-v7a')) {
477
+ return 'armeabi-v7a';
478
+ }
479
+ if (lower.includes('x86_64')) {
480
+ return 'x86_64';
481
+ }
482
+ if (lower.includes('universal')) {
483
+ return 'universal';
484
+ }
485
+ return null;
486
+ }
487
+ async function listFilesRecursive(dir) {
488
+ const ReactNativeBlobUtil = require('react-native-blob-util').default;
489
+ const entries = await ReactNativeBlobUtil.fs.ls(dir);
490
+ const out = [];
491
+ for (const entry of entries) {
492
+ const path = `${dir}/${entry}`;
493
+ try {
494
+ const entryStat = await ReactNativeBlobUtil.fs.stat(path);
495
+ if (entryStat.type === 'directory') {
496
+ out.push(...(await listFilesRecursive(path)));
497
+ }
498
+ else {
499
+ out.push(path);
500
+ }
501
+ }
502
+ catch {
503
+ out.push(path);
504
+ }
505
+ }
506
+ return out;
507
+ }
508
+ async function pickExtractedApk(dir, deviceAbi) {
509
+ const files = await listFilesRecursive(dir);
510
+ const apks = files.filter((path) => path.toLowerCase().endsWith('.apk'));
511
+ if (apks.length === 0) {
512
+ throw new Error(`No APK found after unzipping ${dir}`);
513
+ }
514
+ if (apks.length === 1) {
515
+ return apks[0];
516
+ }
517
+ const wanted = (deviceAbi ?? (await getDeviceAbi()) ?? '').toLowerCase();
518
+ const scored = apks.map((path) => {
519
+ const abi = apkAbiFromName(path);
520
+ let score = 0;
521
+ if (abi && wanted && abi === wanted) {
522
+ score = 3;
523
+ }
524
+ else if (wanted.includes('arm64') && abi === 'arm64-v8a') {
525
+ score = 3;
526
+ }
527
+ else if ((wanted.includes('v7') || wanted.includes('armeabi')) &&
528
+ abi === 'armeabi-v7a') {
529
+ score = 2;
530
+ }
531
+ else if (abi === 'universal') {
532
+ score = 1;
533
+ }
534
+ return { path, score };
535
+ });
536
+ scored.sort((a, b) => b.score - a.score);
537
+ return scored[0].path;
538
+ }
539
+ async function extractApkUpdate(label, zipPath, deviceAbi) {
540
+ const ReactNativeBlobUtil = require('react-native-blob-util').default;
541
+ const extractDir = getApkExtractDir(label);
542
+ await removePathRecursive(extractDir);
543
+ await ReactNativeBlobUtil.fs.mkdir(extractDir);
544
+ await (0, zipExtract_1.extractZipToDirectory)(zipPath, extractDir);
545
+ const zipExists = await ReactNativeBlobUtil.fs.exists(zipPath);
546
+ if (zipExists) {
547
+ await ReactNativeBlobUtil.fs.unlink(zipPath);
548
+ }
549
+ return pickExtractedApk(extractDir, deviceAbi);
550
+ }
459
551
  function getBundleDirectory() {
460
552
  const ReactNativeBlobUtil = require('react-native-blob-util').default;
461
553
  return `${ReactNativeBlobUtil.fs.dirs.DocumentDir}/ota-updates`;
@@ -480,10 +572,13 @@ async function cleanupOldUpdates(keepLabel) {
480
572
  keepLabel,
481
573
  `${keepLabel}.zip`,
482
574
  `${keepLabel}.bundle`,
575
+ `${keepLabel}.apk`,
576
+ `${keepLabel}-apk`,
577
+ `${keepLabel}-apk.zip`,
483
578
  ]);
484
579
  const entries = await ReactNativeBlobUtil.fs.ls(dir);
485
580
  await Promise.all(entries.map(async (entry) => {
486
- if (keep.has(entry) || entry.endsWith('.apk')) {
581
+ if (keep.has(entry)) {
487
582
  return;
488
583
  }
489
584
  await removePathRecursive(`${dir}/${entry}`);
@@ -575,7 +670,7 @@ async function cleanupPartialDownload(path) {
575
670
  * bytesWritten !== Content-Length — including when Content-Length is wrong
576
671
  * but the full file actually landed. Recover those cases via size/hash checks.
577
672
  */
578
- async function tryRecoverCompletedDownload(destPath, extension, totalBytes, expectedHash, packageFormat, label) {
673
+ async function tryRecoverCompletedDownload(destPath, extension, totalBytes, expectedHash, packageFormat, label, deviceAbi) {
579
674
  try {
580
675
  const ReactNativeBlobUtil = require('react-native-blob-util').default;
581
676
  const exists = await ReactNativeBlobUtil.fs.exists(destPath);
@@ -594,6 +689,9 @@ async function tryRecoverCompletedDownload(destPath, extension, totalBytes, expe
594
689
  else if (!sizeLooksComplete) {
595
690
  return null;
596
691
  }
692
+ if (extension === 'apk' && (packageFormat === 'zip' || (await isZipArchive(destPath)))) {
693
+ return extractApkUpdate(label, destPath, deviceAbi);
694
+ }
597
695
  if (extension === 'bundle' && (await shouldExtractZipPackage(destPath, packageFormat))) {
598
696
  return extractBundleUpdate(label, destPath);
599
697
  }
@@ -606,16 +704,22 @@ async function tryRecoverCompletedDownload(destPath, extension, totalBytes, expe
606
704
  async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, onProgress, extension = 'bundle', deviceAbi, packageFormat = 'bundle', expectedHash) {
607
705
  const ReactNativeBlobUtil = require('react-native-blob-util').default;
608
706
  const isZipBundle = extension === 'bundle' && packageFormat === 'zip';
707
+ const isApkZip = extension === 'apk' && packageFormat === 'zip';
609
708
  const destPath = isZipBundle
610
709
  ? getBundleZipPath(label)
611
- : extension === 'apk'
612
- ? getApkFilePath(label)
613
- : getLegacyBundleFilePath(label);
710
+ : isApkZip
711
+ ? getApkZipPath(label)
712
+ : extension === 'apk'
713
+ ? getApkFilePath(label)
714
+ : getLegacyBundleFilePath(label);
614
715
  const totalBytes = await resolveDownloadSize(downloadUrl, deploymentKey, expectedSize, deviceAbi);
615
716
  await ensureBundleDirectory();
616
717
  if (isZipBundle) {
617
718
  await removePathRecursive(getBundleReleaseDir(label));
618
719
  }
720
+ if (isApkZip) {
721
+ await removePathRecursive(getApkExtractDir(label));
722
+ }
619
723
  let lastError;
620
724
  for (let attempt = 1; attempt <= DOWNLOAD_MAX_ATTEMPTS; attempt++) {
621
725
  await cleanupPartialDownload(destPath);
@@ -676,6 +780,9 @@ async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, o
676
780
  throw new Error('Package hash verification failed');
677
781
  }
678
782
  }
783
+ if (extension === 'apk' && (isApkZip || (await isZipArchive(destPath)))) {
784
+ return extractApkUpdate(label, destPath, deviceAbi);
785
+ }
679
786
  if (extension === 'bundle' && (await shouldExtractZipPackage(destPath, packageFormat))) {
680
787
  return extractBundleUpdate(label, destPath);
681
788
  }
@@ -684,7 +791,7 @@ async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, o
684
791
  catch (error) {
685
792
  lastError = error;
686
793
  if (isRetryableDownloadError(error)) {
687
- const recovered = await tryRecoverCompletedDownload(destPath, extension, totalBytes, expectedHash, packageFormat, label);
794
+ const recovered = await tryRecoverCompletedDownload(destPath, extension, totalBytes, expectedHash, packageFormat, label, deviceAbi);
688
795
  if (recovered) {
689
796
  if (onProgress)
690
797
  onProgress(1);
@@ -969,7 +1076,7 @@ async function performApkSync(update, showUI, messages, onProgress, changelog) {
969
1076
  label: update.label,
970
1077
  description: notes,
971
1078
  isMandatory: update.isMandatory,
972
- }), 'apk', update.deviceAbi, 'bundle', update.packageHash);
1079
+ }), 'apk', update.deviceAbi, update.packageFormat ?? 'bundle', update.packageHash);
973
1080
  onProgress?.(types_1.SyncStatus.INSTALLING, 1);
974
1081
  // Drop the OTA JS path before opening the installer so the new APK does not
975
1082
  // keep loading the previous bundle on first launch after install.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsonair.ir/react-native",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "React Native client SDK for self-hosted OTA updates",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",