@appsonair.ir/react-native 1.0.7 → 1.0.10

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.
@@ -21,6 +21,23 @@ object OTAApkInstallHelper {
21
21
  throw IllegalArgumentException("APK file is missing or invalid: ${file.absolutePath}")
22
22
  }
23
23
 
24
+ if (file.name.endsWith(".zip", ignoreCase = true)) {
25
+ throw IllegalArgumentException(
26
+ "Downloaded zip was not extracted to an APK before install: ${file.name}",
27
+ )
28
+ }
29
+
30
+ val archiveInfo =
31
+ reactContext.packageManager.getPackageArchiveInfo(
32
+ file.absolutePath,
33
+ 0,
34
+ )
35
+ if (archiveInfo == null) {
36
+ throw IllegalArgumentException(
37
+ "Wrong package: file is not a valid Android APK (${file.name})",
38
+ )
39
+ }
40
+
24
41
  if (needsInstallPermission(reactContext) &&
25
42
  !reactContext.packageManager.canRequestPackageInstalls()) {
26
43
  OTAUpdaterStorage.setPendingApkPath(reactContext, file.absolutePath)
@@ -1,9 +1,9 @@
1
1
  package com.otaupdater
2
2
 
3
+ import java.io.BufferedInputStream
3
4
  import java.io.File
4
- import java.io.FileInputStream
5
5
  import java.io.FileOutputStream
6
- import java.util.zip.ZipInputStream
6
+ import java.util.zip.ZipFile
7
7
 
8
8
  object OTAZipExtractHelper {
9
9
  fun extract(zipPath: String, destDirPath: String) {
@@ -19,28 +19,43 @@ object OTAZipExtractHelper {
19
19
  }
20
20
  destDir.mkdirs()
21
21
 
22
- ZipInputStream(FileInputStream(zipFile)).use { zipInput ->
23
- var entry = zipInput.nextEntry
24
- while (entry != null) {
25
- val outFile = safeOutputFile(destDir, entry.name)
22
+ // ZipFile handles Info-ZIP / macOS ditto archives more reliably than ZipInputStream.
23
+ ZipFile(zipFile).use { zip ->
24
+ val entries = zip.entries()
25
+ while (entries.hasMoreElements()) {
26
+ val entry = entries.nextElement()
27
+ val normalized = entry.name.replace('\\', '/').trimStart('/')
28
+ val baseName = normalized.substringAfterLast('/')
29
+ if (
30
+ normalized.split('/').contains("__MACOSX") ||
31
+ baseName.startsWith("._") ||
32
+ baseName == ".DS_Store"
33
+ ) {
34
+ continue
35
+ }
36
+
37
+ val outFile = safeOutputFile(destDir, normalized)
26
38
 
27
39
  if (entry.isDirectory) {
28
40
  outFile.mkdirs()
29
- } else {
30
- outFile.parentFile?.mkdirs()
31
- FileOutputStream(outFile).use { output ->
32
- zipInput.copyTo(output)
33
- }
41
+ continue
34
42
  }
35
43
 
36
- zipInput.closeEntry()
37
- entry = zipInput.nextEntry
44
+ outFile.parentFile?.mkdirs()
45
+ zip.getInputStream(entry).use { input ->
46
+ BufferedInputStream(input).use { buffered ->
47
+ FileOutputStream(outFile).use { output ->
48
+ buffered.copyTo(output)
49
+ }
50
+ }
51
+ }
38
52
  }
39
53
  }
40
54
  }
41
55
 
42
56
  private fun safeOutputFile(destDir: File, entryName: String): File {
43
- val outFile = File(destDir, entryName)
57
+ val normalized = entryName.replace('\\', '/').trimStart('/')
58
+ val outFile = File(destDir, normalized)
44
59
  val destPath = destDir.canonicalPath
45
60
  val outPath = outFile.canonicalPath
46
61
  if (outPath != destPath && !outPath.startsWith("$destPath${File.separator}")) {
package/lib/index.d.ts CHANGED
@@ -18,6 +18,8 @@ export { setUserInfo, clearUserInfo, getUserInfo } from './userInfo';
18
18
  export type { OTAUserInfo } from './userInfo';
19
19
  export { OTAUpdateModal } from './ui/OTAUpdateModal';
20
20
  export { OTAUpdateProvider } from './ui/OTAUpdateProvider';
21
+ export { formatBytes, getProgressState, subscribeProgress } from './progress';
22
+ export type { OTAProgressState } from './sync';
21
23
  export declare const OTAUpdater: {
22
24
  configure: typeof configure;
23
25
  getConfig: typeof getConfig;
package/lib/index.js CHANGED
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.OTAUpdater = exports.OTAUpdateProvider = exports.OTAUpdateModal = exports.getUserInfo = exports.clearUserInfo = exports.setUserInfo = exports.logEvent = exports.stopTelemetry = exports.startTelemetry = exports.ping = exports.respondToUpdatePrompt = exports.isSyncInProgress = exports.notifyAppReady = exports.getInstalledBundlePath = exports.clearUpdate = exports.resumePendingApkInstall = exports.installApk = exports.restartApp = exports.sync = exports.resolveUpdaterConfig = exports.isOtaConfigured = exports.getDeploymentKey = exports.configureFromProject = exports.DEFAULT_SERVER_URL = exports.getServerUrl = exports.getConfig = exports.configure = void 0;
20
+ exports.OTAUpdater = exports.subscribeProgress = exports.getProgressState = exports.formatBytes = exports.OTAUpdateProvider = exports.OTAUpdateModal = exports.getUserInfo = exports.clearUserInfo = exports.setUserInfo = exports.logEvent = exports.stopTelemetry = exports.startTelemetry = exports.ping = exports.respondToUpdatePrompt = exports.isSyncInProgress = exports.notifyAppReady = exports.getInstalledBundlePath = exports.clearUpdate = exports.resumePendingApkInstall = exports.installApk = exports.restartApp = exports.sync = exports.resolveUpdaterConfig = exports.isOtaConfigured = exports.getDeploymentKey = exports.configureFromProject = exports.DEFAULT_SERVER_URL = exports.getServerUrl = exports.getConfig = exports.configure = void 0;
21
21
  exports.getCurrentLabel = getCurrentLabel;
22
22
  exports.checkUpdate = checkUpdate;
23
23
  const react_native_device_info_1 = __importDefault(require("react-native-device-info"));
@@ -77,6 +77,10 @@ var OTAUpdateModal_1 = require("./ui/OTAUpdateModal");
77
77
  Object.defineProperty(exports, "OTAUpdateModal", { enumerable: true, get: function () { return OTAUpdateModal_1.OTAUpdateModal; } });
78
78
  var OTAUpdateProvider_1 = require("./ui/OTAUpdateProvider");
79
79
  Object.defineProperty(exports, "OTAUpdateProvider", { enumerable: true, get: function () { return OTAUpdateProvider_1.OTAUpdateProvider; } });
80
+ var progress_1 = require("./progress");
81
+ Object.defineProperty(exports, "formatBytes", { enumerable: true, get: function () { return progress_1.formatBytes; } });
82
+ Object.defineProperty(exports, "getProgressState", { enumerable: true, get: function () { return progress_1.getProgressState; } });
83
+ Object.defineProperty(exports, "subscribeProgress", { enumerable: true, get: function () { return progress_1.subscribeProgress; } });
80
84
  exports.OTAUpdater = {
81
85
  configure: config_1.configure,
82
86
  getConfig: config_1.getConfig,
package/lib/progress.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export type { OTAProgressState } from './sync';
2
- export { defaultMessages, delay, getMessageForStatus, getModalTitle, getProgressState, hideProgress, subscribeProgress, } from './sync';
2
+ export { defaultMessages, delay, formatBytes, getMessageForStatus, getModalTitle, getProgressState, hideProgress, subscribeProgress, } from './sync';
package/lib/progress.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.subscribeProgress = exports.hideProgress = exports.getProgressState = exports.getModalTitle = exports.getMessageForStatus = exports.delay = exports.defaultMessages = void 0;
3
+ exports.subscribeProgress = exports.hideProgress = exports.getProgressState = exports.getModalTitle = exports.getMessageForStatus = exports.formatBytes = exports.delay = exports.defaultMessages = void 0;
4
4
  var sync_1 = require("./sync");
5
5
  Object.defineProperty(exports, "defaultMessages", { enumerable: true, get: function () { return sync_1.defaultMessages; } });
6
6
  Object.defineProperty(exports, "delay", { enumerable: true, get: function () { return sync_1.delay; } });
7
+ Object.defineProperty(exports, "formatBytes", { enumerable: true, get: function () { return sync_1.formatBytes; } });
7
8
  Object.defineProperty(exports, "getMessageForStatus", { enumerable: true, get: function () { return sync_1.getMessageForStatus; } });
8
9
  Object.defineProperty(exports, "getModalTitle", { enumerable: true, get: function () { return sync_1.getModalTitle; } });
9
10
  Object.defineProperty(exports, "getProgressState", { enumerable: true, get: function () { return sync_1.getProgressState; } });
package/lib/sync.d.ts CHANGED
@@ -13,6 +13,22 @@ export interface OTAProgressState {
13
13
  showActions?: boolean;
14
14
  canSkip?: boolean;
15
15
  error?: string;
16
+ /** Whether this update is a JS bundle or native APK */
17
+ updateType?: 'bundle' | 'apk';
18
+ /** Target release app version (from server) */
19
+ appVersion?: string;
20
+ /** Target APK versionCode (from server) */
21
+ buildNumber?: number;
22
+ /** Currently installed native app version */
23
+ currentAppVersion?: string;
24
+ /** Currently installed native build / versionCode */
25
+ currentBuildNumber?: string;
26
+ /** Currently installed JS bundle label, if any */
27
+ currentLabel?: string;
28
+ /** Total download size in bytes (0 if unknown) */
29
+ totalBytes?: number;
30
+ /** Bytes downloaded so far */
31
+ downloadedBytes?: number;
16
32
  }
17
33
  type ProgressListener = (state: OTAProgressState) => void;
18
34
  export declare function getProgressState(): OTAProgressState;
@@ -20,6 +36,8 @@ export declare function subscribeProgress(listener: ProgressListener): () => voi
20
36
  export declare function hideProgress(): void;
21
37
  export declare const defaultMessages: {
22
38
  title: string;
39
+ titleBundle: string;
40
+ titleApk: string;
23
41
  checking: string;
24
42
  downloading: string;
25
43
  installing: string;
@@ -28,15 +46,20 @@ export declare const defaultMessages: {
28
46
  downloadInterrupted: string;
29
47
  upToDate: string;
30
48
  updateAvailable: string;
49
+ updateAvailableBundle: string;
50
+ updateAvailableApk: string;
31
51
  changelogTitle: string;
32
52
  updateNow: string;
33
53
  skip: string;
34
54
  dismiss: string;
35
55
  appliedNextLaunch: string;
36
56
  mandatoryHint: string;
57
+ versionBundle: string;
58
+ versionApk: string;
37
59
  };
38
60
  export declare function getMessageForStatus(status: SyncStatus, messages?: Partial<typeof defaultMessages>): string;
39
- export declare function getModalTitle(messages?: Partial<typeof defaultMessages>): string;
61
+ export declare function getModalTitle(messages?: Partial<typeof defaultMessages>, updateType?: 'bundle' | 'apk'): string;
62
+ export declare function formatBytes(bytes: number | undefined): string;
40
63
  export declare function delay(ms: number): Promise<void>;
41
64
  type UpdatePromptAction = 'update' | 'skip' | 'dismiss';
42
65
  /** Resolve the Update / Skip / Dismiss prompt shown by OTAUpdateModal. */
@@ -54,7 +77,7 @@ export declare function checkForUpdate(serverUrl: string, deploymentKey: string,
54
77
  export declare function getBundleReleaseDir(label: string): string;
55
78
  export declare function getBundleFilePath(label: string): string;
56
79
  export declare function getApkFilePath(label: string): string;
57
- export declare function downloadBundle(downloadUrl: string, deploymentKey: string, label: string, expectedSize?: number, onProgress?: (progress: number) => void, extension?: 'bundle' | 'apk', deviceAbi?: string, packageFormat?: 'bundle' | 'zip', expectedHash?: string): Promise<string>;
80
+ export declare function downloadBundle(downloadUrl: string, deploymentKey: string, label: string, expectedSize?: number, onProgress?: (progress: number, received?: number, total?: number) => void, extension?: 'bundle' | 'apk', deviceAbi?: string, packageFormat?: 'bundle' | 'zip', expectedHash?: string): Promise<string>;
58
81
  export declare function verifyBundleHash(filePath: string, expectedHash: string): Promise<boolean>;
59
82
  type ProgressCallback = (status: SyncStatus, progress?: number) => void;
60
83
  export declare function isSyncInProgress(): boolean;
package/lib/sync.js CHANGED
@@ -42,6 +42,7 @@ exports.subscribeProgress = subscribeProgress;
42
42
  exports.hideProgress = hideProgress;
43
43
  exports.getMessageForStatus = getMessageForStatus;
44
44
  exports.getModalTitle = getModalTitle;
45
+ exports.formatBytes = formatBytes;
45
46
  exports.delay = delay;
46
47
  exports.respondToUpdatePrompt = respondToUpdatePrompt;
47
48
  exports.getStoredBundleMeta = getStoredBundleMeta;
@@ -105,6 +106,8 @@ function shouldShowModalForStatus(status) {
105
106
  }
106
107
  exports.defaultMessages = {
107
108
  title: 'به\u200cروزرسانی برنامه',
109
+ titleBundle: 'به\u200cروزرسانی محتوا',
110
+ titleApk: 'به\u200cروزرسانی نسخه برنامه',
108
111
  checking: 'در حال بررسی به\u200cروزرسانی...',
109
112
  downloading: 'در حال دانلود به\u200cروزرسانی...',
110
113
  installing: 'در حال نصب به\u200cروزرسانی...',
@@ -113,12 +116,16 @@ exports.defaultMessages = {
113
116
  downloadInterrupted: 'دانلود قطع شد. لطفاً دوباره تلاش کنید',
114
117
  upToDate: 'برنامه به\u200cروز است',
115
118
  updateAvailable: 'نسخه جدیدی در دسترس است',
119
+ updateAvailableBundle: 'به\u200cروزرسانی محتوا آماده است',
120
+ updateAvailableApk: 'نسخه جدید برنامه آماده نصب است',
116
121
  changelogTitle: 'تغییرات این نسخه',
117
122
  updateNow: 'به\u200cروزرسانی',
118
123
  skip: 'فعلاً نه',
119
124
  dismiss: 'باشه',
120
125
  appliedNextLaunch: 'به\u200cروزرسانی نصب شد و در اجرای بعدی برنامه اعمال می\u200cشود',
121
126
  mandatoryHint: 'این به\u200cروزرسانی الزامی است',
127
+ versionBundle: 'باندل',
128
+ versionApk: 'نسخه برنامه',
122
129
  };
123
130
  function getMessageForStatus(status, messages = {}) {
124
131
  const m = { ...exports.defaultMessages, ...messages };
@@ -141,10 +148,28 @@ function getMessageForStatus(status, messages = {}) {
141
148
  return m.upToDate;
142
149
  }
143
150
  }
144
- function getModalTitle(messages = {}) {
151
+ function getModalTitle(messages = {}, updateType) {
145
152
  const m = { ...exports.defaultMessages, ...messages };
153
+ if (updateType === 'apk') {
154
+ return m.titleApk ?? m.title;
155
+ }
156
+ if (updateType === 'bundle') {
157
+ return m.titleBundle ?? m.title;
158
+ }
146
159
  return m.title;
147
160
  }
161
+ function formatBytes(bytes) {
162
+ if (bytes == null || !Number.isFinite(bytes) || bytes <= 0) {
163
+ return '—';
164
+ }
165
+ if (bytes < 1024) {
166
+ return `${Math.round(bytes)} B`;
167
+ }
168
+ if (bytes < 1024 * 1024) {
169
+ return `${(bytes / 1024).toFixed(1)} KB`;
170
+ }
171
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
172
+ }
148
173
  function delay(ms) {
149
174
  return new Promise((resolve) => setTimeout(resolve, ms));
150
175
  }
@@ -401,6 +426,14 @@ async function isZipArchive(filePath) {
401
426
  return false;
402
427
  }
403
428
  }
429
+ async function shouldExtractApkZip(downloadedPath, packageFormat) {
430
+ // APK files are themselves zip archives (PK magic). Only unzip when the
431
+ // server said this package is a zip *of* an APK, or the download is named .zip.
432
+ if (packageFormat === 'zip') {
433
+ return true;
434
+ }
435
+ return downloadedPath.toLowerCase().endsWith('.zip');
436
+ }
404
437
  async function shouldExtractZipPackage(downloadedPath, packageFormat) {
405
438
  if (packageFormat === 'zip') {
406
439
  return true;
@@ -539,14 +572,33 @@ async function pickExtractedApk(dir, deviceAbi) {
539
572
  async function extractApkUpdate(label, zipPath, deviceAbi) {
540
573
  const ReactNativeBlobUtil = require('react-native-blob-util').default;
541
574
  const extractDir = getApkExtractDir(label);
575
+ const finalApkPath = getApkFilePath(label);
542
576
  await removePathRecursive(extractDir);
543
577
  await ReactNativeBlobUtil.fs.mkdir(extractDir);
544
578
  await (0, zipExtract_1.extractZipToDirectory)(zipPath, extractDir);
579
+ const extractedApk = await pickExtractedApk(extractDir, deviceAbi);
580
+ if (!extractedApk.toLowerCase().endsWith('.apk')) {
581
+ throw new Error('Zip did not contain a valid APK');
582
+ }
583
+ // Never hand the zip (or a nested extract path) to the installer.
584
+ if (extractedApk !== finalApkPath) {
585
+ const finalExists = await ReactNativeBlobUtil.fs.exists(finalApkPath);
586
+ if (finalExists) {
587
+ await ReactNativeBlobUtil.fs.unlink(finalApkPath);
588
+ }
589
+ await ReactNativeBlobUtil.fs.cp(extractedApk, finalApkPath);
590
+ }
545
591
  const zipExists = await ReactNativeBlobUtil.fs.exists(zipPath);
546
592
  if (zipExists) {
547
593
  await ReactNativeBlobUtil.fs.unlink(zipPath);
548
594
  }
549
- return pickExtractedApk(extractDir, deviceAbi);
595
+ // Drop extract clutter; keep only the installable APK.
596
+ await removePathRecursive(extractDir);
597
+ const apkExists = await ReactNativeBlobUtil.fs.exists(finalApkPath);
598
+ if (!apkExists) {
599
+ throw new Error('Failed to prepare APK for install');
600
+ }
601
+ return finalApkPath;
550
602
  }
551
603
  function getBundleDirectory() {
552
604
  const ReactNativeBlobUtil = require('react-native-blob-util').default;
@@ -689,7 +741,7 @@ async function tryRecoverCompletedDownload(destPath, extension, totalBytes, expe
689
741
  else if (!sizeLooksComplete) {
690
742
  return null;
691
743
  }
692
- if (extension === 'apk' && (packageFormat === 'zip' || (await isZipArchive(destPath)))) {
744
+ if (extension === 'apk' && (await shouldExtractApkZip(destPath, packageFormat))) {
693
745
  return extractApkUpdate(label, destPath, deviceAbi);
694
746
  }
695
747
  if (extension === 'bundle' && (await shouldExtractZipPackage(destPath, packageFormat))) {
@@ -736,9 +788,11 @@ async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, o
736
788
  if (!onProgress)
737
789
  return;
738
790
  const resolvedTotal = total > 0 ? total : totalBytes;
739
- if (resolvedTotal <= 0)
791
+ if (resolvedTotal <= 0) {
792
+ onProgress(0, Math.max(received, 0), 0);
740
793
  return;
741
- onProgress(Math.min(Math.max(received / resolvedTotal, 0), 0.99));
794
+ }
795
+ onProgress(Math.min(Math.max(received / resolvedTotal, 0), 0.99), Math.max(received, 0), resolvedTotal);
742
796
  };
743
797
  let pollTimer = null;
744
798
  if (onProgress && totalBytes > 0) {
@@ -762,9 +816,6 @@ async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, o
762
816
  }
763
817
  try {
764
818
  const response = await task;
765
- if (onProgress) {
766
- onProgress(1);
767
- }
768
819
  const downloadedPath = String(response.path()).replace(/^file:\/\//, '');
769
820
  if (downloadedPath !== destPath) {
770
821
  await ReactNativeBlobUtil.fs.cp(downloadedPath, destPath);
@@ -774,13 +825,16 @@ async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, o
774
825
  if (!stat || stat.size < minSize) {
775
826
  throw new Error(`Downloaded file is too small (${stat?.size ?? 0} bytes)`);
776
827
  }
828
+ if (onProgress) {
829
+ onProgress(1, stat.size, totalBytes > 0 ? totalBytes : stat.size);
830
+ }
777
831
  if (expectedHash) {
778
832
  const valid = await verifyBundleHash(destPath, expectedHash);
779
833
  if (!valid) {
780
- throw new Error('Package hash verification failed');
834
+ throw new Error('Wrong package: hash verification failed');
781
835
  }
782
836
  }
783
- if (extension === 'apk' && (isApkZip || (await isZipArchive(destPath)))) {
837
+ if (extension === 'apk' && (await shouldExtractApkZip(destPath, packageFormat))) {
784
838
  return extractApkUpdate(label, destPath, deviceAbi);
785
839
  }
786
840
  if (extension === 'bundle' && (await shouldExtractZipPackage(destPath, packageFormat))) {
@@ -839,8 +893,17 @@ function reportProgress(showUI, messages, onProgress, status, progress = 0, extr
839
893
  status === types_1.SyncStatus.APK_INSTALL_PENDING) {
840
894
  return;
841
895
  }
896
+ const updateType = extra?.updateType;
897
+ let statusMessage = extra?.message ?? getMessageForStatus(status, messages);
898
+ if (!extra?.message && status === types_1.SyncStatus.UPDATE_AVAILABLE && updateType) {
899
+ const m = { ...exports.defaultMessages, ...messages };
900
+ statusMessage =
901
+ updateType === 'apk'
902
+ ? (m.updateAvailableApk ?? m.updateAvailable)
903
+ : (m.updateAvailableBundle ?? m.updateAvailable);
904
+ }
842
905
  if (status === types_1.SyncStatus.ERROR) {
843
- showProgress(status, progress, extra?.error ?? getMessageForStatus(status, messages), {
906
+ showProgress(status, progress, extra?.error ?? statusMessage, {
844
907
  error: extra?.error,
845
908
  showActions: true,
846
909
  canSkip: false,
@@ -849,24 +912,36 @@ function reportProgress(showUI, messages, onProgress, status, progress = 0, extr
849
912
  return;
850
913
  }
851
914
  if (shouldShowModalForStatus(status)) {
852
- showProgress(status, progress, extra?.message ?? getMessageForStatus(status, messages), {
915
+ showProgress(status, progress, statusMessage, {
853
916
  showActions: false,
854
917
  canSkip: false,
855
918
  ...extra,
856
919
  });
857
920
  }
858
921
  }
859
- async function promptForUpdate(showUI, messages, onProgress, update, installMode) {
860
- const changelog = getUpdateChangelog(update);
922
+ function buildUpdateProgressExtra(update, installMode, currentLabel) {
923
+ return {
924
+ label: update.label,
925
+ description: getUpdateChangelog(update),
926
+ isMandatory: update.isMandatory,
927
+ installMode,
928
+ updateType: update.updateType ?? 'bundle',
929
+ appVersion: update.appVersion,
930
+ buildNumber: update.buildNumber,
931
+ currentAppVersion: react_native_device_info_1.default.getVersion(),
932
+ currentBuildNumber: react_native_1.Platform.OS === 'android' ? react_native_device_info_1.default.getBuildNumber() : undefined,
933
+ currentLabel: currentLabel ?? undefined,
934
+ totalBytes: update.size && update.size > 0 ? update.size : undefined,
935
+ downloadedBytes: 0,
936
+ };
937
+ }
938
+ async function promptForUpdate(showUI, messages, onProgress, update, installMode, currentLabel) {
861
939
  const isMandatory = update.isMandatory === true;
862
940
  if (!showUI) {
863
941
  return 'update';
864
942
  }
865
943
  reportProgress(showUI, messages, onProgress, types_1.SyncStatus.UPDATE_AVAILABLE, 0, {
866
- label: update.label,
867
- description: changelog,
868
- isMandatory,
869
- installMode,
944
+ ...buildUpdateProgressExtra(update, installMode, currentLabel),
870
945
  showActions: true,
871
946
  canSkip: !isMandatory,
872
947
  });
@@ -963,7 +1038,7 @@ async function performSync(options = {}, onProgress, messages) {
963
1038
  else {
964
1039
  await clearSkippedUpdate();
965
1040
  }
966
- const decision = await promptForUpdate(showUI, messages, onProgress, update, installMode);
1041
+ const decision = await promptForUpdate(showUI, messages, onProgress, update, installMode, meta?.label);
967
1042
  if (decision === 'skip') {
968
1043
  await markUpdateSkipped(update.label);
969
1044
  if (showUI)
@@ -977,19 +1052,14 @@ async function performSync(options = {}, onProgress, messages) {
977
1052
  }
978
1053
  await clearSkippedUpdate();
979
1054
  if (update.updateType === 'apk') {
980
- return performApkSync(update, showUI, messages, onProgress, changelog);
1055
+ return performApkSync(update, showUI, messages, onProgress, changelog, meta?.label);
981
1056
  }
982
- reportProgress(showUI, messages, onProgress, types_1.SyncStatus.DOWNLOADING, 0, {
983
- label: update.label,
984
- description: changelog,
985
- isMandatory: update.isMandatory,
986
- installMode,
987
- });
988
- const bundlePath = await downloadBundle(update.downloadUrl, config.deploymentKey, update.label, update.size, (p) => reportProgress(showUI, messages, onProgress, types_1.SyncStatus.DOWNLOADING, p, {
989
- label: update.label,
990
- description: changelog,
991
- isMandatory: update.isMandatory,
992
- installMode,
1057
+ const progressExtra = buildUpdateProgressExtra(update, installMode, meta?.label);
1058
+ reportProgress(showUI, messages, onProgress, types_1.SyncStatus.DOWNLOADING, 0, progressExtra);
1059
+ const bundlePath = await downloadBundle(update.downloadUrl, config.deploymentKey, update.label, update.size, (p, received, total) => reportProgress(showUI, messages, onProgress, types_1.SyncStatus.DOWNLOADING, p, {
1060
+ ...progressExtra,
1061
+ downloadedBytes: received,
1062
+ totalBytes: total && total > 0 ? total : progressExtra.totalBytes,
993
1063
  }), 'bundle', undefined, update.packageFormat ?? 'bundle', update.packageHash);
994
1064
  onProgress?.(types_1.SyncStatus.INSTALLING, 1);
995
1065
  const ReactNativeBlobUtil = require('react-native-blob-util').default;
@@ -1011,10 +1081,8 @@ async function performSync(options = {}, onProgress, messages) {
1011
1081
  const shouldRestartNow = installMode === 'immediate' || update.isMandatory === true;
1012
1082
  if (shouldRestartNow) {
1013
1083
  reportProgress(showUI, messages, onProgress, types_1.SyncStatus.RESTARTING, 1, {
1014
- label: update.label,
1015
- description: changelog,
1016
- isMandatory: update.isMandatory,
1017
- installMode,
1084
+ ...progressExtra,
1085
+ downloadedBytes: progressExtra.totalBytes,
1018
1086
  });
1019
1087
  await markRestartPending();
1020
1088
  await delay(300);
@@ -1027,10 +1095,8 @@ async function performSync(options = {}, onProgress, messages) {
1027
1095
  }
1028
1096
  if (showUI) {
1029
1097
  reportProgress(showUI, messages, onProgress, types_1.SyncStatus.UPDATE_INSTALLED, 1, {
1030
- label: update.label,
1031
- description: changelog,
1032
- isMandatory: update.isMandatory,
1033
- installMode,
1098
+ ...progressExtra,
1099
+ downloadedBytes: progressExtra.totalBytes,
1034
1100
  showActions: true,
1035
1101
  canSkip: false,
1036
1102
  message: getMessageForStatus(types_1.SyncStatus.UPDATE_INSTALLED, messages),
@@ -1061,22 +1127,23 @@ async function performSync(options = {}, onProgress, messages) {
1061
1127
  };
1062
1128
  }
1063
1129
  }
1064
- async function performApkSync(update, showUI, messages, onProgress, changelog) {
1130
+ async function performApkSync(update, showUI, messages, onProgress, changelog, currentLabel) {
1065
1131
  if (!update.downloadUrl || !update.label || !update.packageHash) {
1066
1132
  return { status: types_1.SyncStatus.UP_TO_DATE };
1067
1133
  }
1068
1134
  const notes = changelog ?? getUpdateChangelog(update);
1135
+ const progressExtra = buildUpdateProgressExtra(update, undefined, currentLabel);
1136
+ const packageFormat = update.packageFormat ?? 'zip';
1069
1137
  try {
1070
- reportProgress(showUI, messages, onProgress, types_1.SyncStatus.DOWNLOADING, 0, {
1071
- label: update.label,
1072
- description: notes,
1073
- isMandatory: update.isMandatory,
1074
- });
1075
- const apkPath = await downloadBundle(update.downloadUrl, (0, config_1.getConfig)().deploymentKey, update.label, update.size, (p) => reportProgress(showUI, messages, onProgress, types_1.SyncStatus.DOWNLOADING, p, {
1076
- label: update.label,
1077
- description: notes,
1078
- isMandatory: update.isMandatory,
1079
- }), 'apk', update.deviceAbi, update.packageFormat ?? 'bundle', update.packageHash);
1138
+ reportProgress(showUI, messages, onProgress, types_1.SyncStatus.DOWNLOADING, 0, progressExtra);
1139
+ const apkPath = await downloadBundle(update.downloadUrl, (0, config_1.getConfig)().deploymentKey, update.label, update.size, (p, received, total) => reportProgress(showUI, messages, onProgress, types_1.SyncStatus.DOWNLOADING, p, {
1140
+ ...progressExtra,
1141
+ downloadedBytes: received,
1142
+ totalBytes: total && total > 0 ? total : progressExtra.totalBytes,
1143
+ }), 'apk', update.deviceAbi, packageFormat, update.packageHash);
1144
+ if (!apkPath.toLowerCase().endsWith('.apk')) {
1145
+ throw new Error('Wrong package: expected an APK after zip extract');
1146
+ }
1080
1147
  onProgress?.(types_1.SyncStatus.INSTALLING, 1);
1081
1148
  // Drop the OTA JS path before opening the installer so the new APK does not
1082
1149
  // keep loading the previous bundle on first launch after install.
@@ -1084,9 +1151,8 @@ async function performApkSync(update, showUI, messages, onProgress, changelog) {
1084
1151
  const installResult = await installApk(apkPath);
1085
1152
  if (installResult === 'permission_required') {
1086
1153
  reportProgress(showUI, messages, onProgress, types_1.SyncStatus.INSTALLING, 1, {
1087
- label: update.label,
1088
- description: notes,
1089
- isMandatory: update.isMandatory,
1154
+ ...progressExtra,
1155
+ downloadedBytes: progressExtra.totalBytes,
1090
1156
  });
1091
1157
  return {
1092
1158
  status: types_1.SyncStatus.APK_INSTALL_PENDING,
@@ -1106,6 +1172,7 @@ async function performApkSync(update, showUI, messages, onProgress, changelog) {
1106
1172
  catch (error) {
1107
1173
  const err = error instanceof Error ? error : new Error(String(error));
1108
1174
  reportProgress(showUI, messages, onProgress, types_1.SyncStatus.ERROR, 0, {
1175
+ ...progressExtra,
1109
1176
  error: err.message,
1110
1177
  showActions: true,
1111
1178
  canSkip: false,
@@ -1143,6 +1210,9 @@ async function clearUpdate() {
1143
1210
  await removePathRecursive(getBundleReleaseDir(meta.label));
1144
1211
  await cleanupPartialDownload(getBundleZipPath(meta.label));
1145
1212
  await cleanupPartialDownload(getLegacyBundleFilePath(meta.label));
1213
+ await cleanupPartialDownload(getApkFilePath(meta.label));
1214
+ await cleanupPartialDownload(getApkZipPath(meta.label));
1215
+ await removePathRecursive(getApkExtractDir(meta.label));
1146
1216
  }
1147
1217
  }
1148
1218
  async function notifyAppReady() {
package/lib/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from 'react';
1
+ import type { ComponentType, ReactNode } from 'react';
2
2
  /** When to apply a downloaded JS bundle update. */
3
3
  export type InstallMode = 'immediate' | 'next-launch'
4
4
  /** @deprecated use `next-launch` */
@@ -35,6 +35,8 @@ export interface OTAUpdaterConfig {
35
35
  }
36
36
  export interface OTAProgressMessages {
37
37
  title?: string;
38
+ titleBundle?: string;
39
+ titleApk?: string;
38
40
  checking?: string;
39
41
  downloading?: string;
40
42
  installing?: string;
@@ -43,12 +45,43 @@ export interface OTAProgressMessages {
43
45
  upToDate?: string;
44
46
  /** Prompt when an update is available */
45
47
  updateAvailable?: string;
48
+ updateAvailableBundle?: string;
49
+ updateAvailableApk?: string;
46
50
  changelogTitle?: string;
47
51
  updateNow?: string;
48
52
  skip?: string;
49
53
  dismiss?: string;
50
54
  appliedNextLaunch?: string;
51
55
  mandatoryHint?: string;
56
+ versionBundle?: string;
57
+ versionApk?: string;
58
+ }
59
+ export interface OTAUpdateUIProps {
60
+ visible: boolean;
61
+ status: SyncStatus;
62
+ progress: number;
63
+ message: string;
64
+ label?: string;
65
+ description?: string;
66
+ isMandatory?: boolean;
67
+ installMode?: InstallMode;
68
+ showActions?: boolean;
69
+ canSkip?: boolean;
70
+ error?: string;
71
+ updateType?: 'bundle' | 'apk';
72
+ appVersion?: string;
73
+ buildNumber?: number;
74
+ currentAppVersion?: string;
75
+ currentBuildNumber?: string;
76
+ currentLabel?: string;
77
+ totalBytes?: number;
78
+ downloadedBytes?: number;
79
+ title?: string;
80
+ fontFamily?: string;
81
+ messages?: OTAProgressMessages;
82
+ onUpdate?: () => void;
83
+ onSkip?: () => void;
84
+ onDismiss?: () => void;
52
85
  }
53
86
  export interface OTAUpdateProviderProps {
54
87
  children: ReactNode;
@@ -64,6 +97,13 @@ export interface OTAUpdateProviderProps {
64
97
  fontFamily?: string;
65
98
  /** Passed to sync() */
66
99
  syncOptions?: SyncOptions;
100
+ /**
101
+ * Replace the built-in update modal.
102
+ * Receive progress props + action handlers; return null to hide.
103
+ */
104
+ renderUpdateUI?: (props: OTAUpdateUIProps) => ReactNode;
105
+ /** Component alternative to renderUpdateUI */
106
+ UpdateComponent?: ComponentType<OTAUpdateUIProps>;
67
107
  }
68
108
  export interface UpdateCheckResult {
69
109
  updateAvailable: boolean;
@@ -1,9 +1,4 @@
1
1
  import React from 'react';
2
- import { defaultMessages, OTAProgressState } from '../sync';
3
- interface OTAUpdateModalProps extends OTAProgressState {
4
- title?: string;
5
- fontFamily?: string;
6
- messages?: Partial<typeof defaultMessages>;
7
- }
8
- export declare function OTAUpdateModal({ visible, status, progress, message, description, isMandatory, error, showActions, canSkip, title, fontFamily, messages, }: OTAUpdateModalProps): React.JSX.Element;
9
- export {};
2
+ import { OTAUpdateUIProps } from '../types';
3
+ export type OTAUpdateModalProps = OTAUpdateUIProps;
4
+ export declare function OTAUpdateModal({ visible, status, progress, message, description, isMandatory, error, showActions, canSkip, updateType, appVersion, buildNumber, currentAppVersion, currentBuildNumber, currentLabel, label, totalBytes, downloadedBytes, title, fontFamily, messages, onUpdate, onSkip, onDismiss, }: OTAUpdateModalProps): React.JSX.Element;
@@ -38,8 +38,17 @@ const react_1 = __importStar(require("react"));
38
38
  const react_native_1 = require("react-native");
39
39
  const sync_1 = require("../sync");
40
40
  const types_1 = require("../types");
41
- function OTAUpdateModal({ visible, status, progress, message, description, isMandatory, error, showActions, canSkip, title = sync_1.defaultMessages.title, fontFamily, messages, }) {
41
+ function OTAUpdateModal({ visible, status, progress, message, description, isMandatory, error, showActions, canSkip, updateType, appVersion, buildNumber, currentAppVersion, currentBuildNumber, currentLabel, label, totalBytes, downloadedBytes, title, fontFamily, messages, onUpdate, onSkip, onDismiss, }) {
42
42
  const copy = (0, react_1.useMemo)(() => ({ ...sync_1.defaultMessages, ...messages }), [messages]);
43
+ const resolvedTitle = (0, react_1.useMemo)(() => {
44
+ if (title)
45
+ return title;
46
+ if (updateType === 'apk')
47
+ return copy.titleApk ?? copy.title;
48
+ if (updateType === 'bundle')
49
+ return copy.titleBundle ?? copy.title;
50
+ return copy.title;
51
+ }, [title, updateType, copy]);
43
52
  const customFontStyle = (0, react_1.useMemo)(() => fontFamily
44
53
  ? {
45
54
  fontFamily,
@@ -60,19 +69,58 @@ function OTAUpdateModal({ visible, status, progress, message, description, isMan
60
69
  const showSpinner = status === types_1.SyncStatus.RESTARTING ||
61
70
  status === types_1.SyncStatus.INSTALLING ||
62
71
  (!showProgressBar && !isPrompt && !isDeferredDone && !isError);
72
+ const typeLabel = updateType === 'apk'
73
+ ? copy.versionApk
74
+ : updateType === 'bundle'
75
+ ? copy.versionBundle
76
+ : null;
77
+ const currentVersionText = updateType === 'apk'
78
+ ? [currentAppVersion, currentBuildNumber ? `(${currentBuildNumber})` : null]
79
+ .filter(Boolean)
80
+ .join(' ')
81
+ : currentLabel || currentAppVersion || null;
82
+ const nextVersionText = updateType === 'apk'
83
+ ? [appVersion, buildNumber != null ? `(${buildNumber})` : null]
84
+ .filter(Boolean)
85
+ .join(' ')
86
+ : label || appVersion || null;
87
+ const handleUpdate = () => (onUpdate ? onUpdate() : (0, sync_1.respondToUpdatePrompt)('update'));
88
+ const handleSkip = () => (onSkip ? onSkip() : (0, sync_1.respondToUpdatePrompt)('skip'));
89
+ const handleDismiss = () => onDismiss ? onDismiss() : (0, sync_1.respondToUpdatePrompt)('dismiss');
90
+ const downloaded = downloadedBytes ?? 0;
91
+ const total = totalBytes ?? 0;
92
+ const showSizes = showProgressBar && (total > 0 || downloaded > 0);
63
93
  return (<react_native_1.Modal visible transparent animationType="fade" statusBarTranslucent onRequestClose={() => {
64
94
  if (canSkip)
65
- (0, sync_1.respondToUpdatePrompt)('skip');
95
+ handleSkip();
66
96
  else if (showActions && (isDeferredDone || isError)) {
67
- (0, sync_1.respondToUpdatePrompt)('dismiss');
97
+ handleDismiss();
68
98
  }
69
99
  }}>
70
100
  <react_native_1.View style={styles.overlay}>
71
101
  <react_native_1.View style={styles.card}>
72
102
  <react_native_1.Text style={[styles.title, !fontFamily && styles.titleBold, textStyle]}>
73
- {title}
103
+ {resolvedTitle}
74
104
  </react_native_1.Text>
75
105
 
106
+ {typeLabel ? (<react_native_1.View style={styles.badge}>
107
+ <react_native_1.Text style={[styles.badgeText, textStyle]}>{typeLabel}</react_native_1.Text>
108
+ </react_native_1.View>) : null}
109
+
110
+ {(currentVersionText || nextVersionText) && !error ? (<react_native_1.View style={styles.versionBox}>
111
+ {currentVersionText ? (<react_native_1.Text style={[styles.versionLine, textStyle]}>
112
+ فعلی: {currentVersionText}
113
+ </react_native_1.Text>) : null}
114
+ {nextVersionText ? (<react_native_1.Text style={[
115
+ styles.versionLine,
116
+ styles.versionNext,
117
+ !fontFamily && styles.titleBold,
118
+ textStyle,
119
+ ]}>
120
+ جدید: {nextVersionText}
121
+ </react_native_1.Text>) : null}
122
+ </react_native_1.View>) : null}
123
+
76
124
  <react_native_1.Text style={[styles.message, textStyle]}>{error ?? message}</react_native_1.Text>
77
125
 
78
126
  {isMandatory && isPrompt ? (<react_native_1.Text style={[styles.mandatory, textStyle]}>{copy.mandatoryHint}</react_native_1.Text>) : null}
@@ -93,13 +141,20 @@ function OTAUpdateModal({ visible, status, progress, message, description, isMan
93
141
  <react_native_1.Text style={[styles.percent, !fontFamily && styles.percentBold, textStyle]}>
94
142
  {progressPercent}٪
95
143
  </react_native_1.Text>
144
+ {showSizes ? (<react_native_1.Text style={[styles.sizeText, textStyle]}>
145
+ {(0, sync_1.formatBytes)(downloaded)} / {(0, sync_1.formatBytes)(total > 0 ? total : undefined)}
146
+ </react_native_1.Text>) : null}
96
147
  </>) : null}
97
148
 
149
+ {!showProgressBar && total > 0 && (isPrompt || isError) ? (<react_native_1.Text style={[styles.sizeText, textStyle]}>
150
+ حجم دانلود: {(0, sync_1.formatBytes)(total)}
151
+ </react_native_1.Text>) : null}
152
+
98
153
  {showSpinner ? (<react_native_1.ActivityIndicator size="large" color="#2563eb" style={styles.spinner}/>) : null}
99
154
 
100
155
  {showActions ? (<react_native_1.View style={styles.actions}>
101
156
  {isPrompt ? (<>
102
- <react_native_1.Pressable style={[styles.button, styles.buttonPrimary]} onPress={() => (0, sync_1.respondToUpdatePrompt)('update')}>
157
+ <react_native_1.Pressable style={[styles.button, styles.buttonPrimary]} onPress={handleUpdate}>
103
158
  <react_native_1.Text style={[
104
159
  styles.buttonPrimaryText,
105
160
  !fontFamily && styles.titleBold,
@@ -108,10 +163,10 @@ function OTAUpdateModal({ visible, status, progress, message, description, isMan
108
163
  {copy.updateNow}
109
164
  </react_native_1.Text>
110
165
  </react_native_1.Pressable>
111
- {canSkip ? (<react_native_1.Pressable style={[styles.button, styles.buttonGhost]} onPress={() => (0, sync_1.respondToUpdatePrompt)('skip')}>
166
+ {canSkip ? (<react_native_1.Pressable style={[styles.button, styles.buttonGhost]} onPress={handleSkip}>
112
167
  <react_native_1.Text style={[styles.buttonGhostText, textStyle]}>{copy.skip}</react_native_1.Text>
113
168
  </react_native_1.Pressable>) : null}
114
- </>) : (<react_native_1.Pressable style={[styles.button, styles.buttonPrimary]} onPress={() => (0, sync_1.respondToUpdatePrompt)('dismiss')}>
169
+ </>) : (<react_native_1.Pressable style={[styles.button, styles.buttonPrimary]} onPress={handleDismiss}>
115
170
  <react_native_1.Text style={[
116
171
  styles.buttonPrimaryText,
117
172
  !fontFamily && styles.titleBold,
@@ -150,6 +205,35 @@ const styles = react_native_1.StyleSheet.create({
150
205
  titleBold: {
151
206
  fontWeight: '700',
152
207
  },
208
+ badge: {
209
+ alignSelf: 'center',
210
+ backgroundColor: '#eff6ff',
211
+ borderRadius: 999,
212
+ paddingHorizontal: 10,
213
+ paddingVertical: 4,
214
+ marginBottom: 8,
215
+ },
216
+ badgeText: {
217
+ fontSize: 12,
218
+ color: '#1d4ed8',
219
+ textAlign: 'center',
220
+ },
221
+ versionBox: {
222
+ backgroundColor: '#f9fafb',
223
+ borderRadius: 10,
224
+ paddingVertical: 8,
225
+ paddingHorizontal: 12,
226
+ marginBottom: 8,
227
+ gap: 4,
228
+ },
229
+ versionLine: {
230
+ fontSize: 13,
231
+ color: '#6b7280',
232
+ textAlign: 'center',
233
+ },
234
+ versionNext: {
235
+ color: '#111827',
236
+ },
153
237
  message: {
154
238
  fontSize: 15,
155
239
  color: '#374151',
@@ -211,6 +295,12 @@ const styles = react_native_1.StyleSheet.create({
211
295
  percentBold: {
212
296
  fontWeight: '600',
213
297
  },
298
+ sizeText: {
299
+ marginTop: 4,
300
+ fontSize: 12,
301
+ color: '#9ca3af',
302
+ textAlign: 'center',
303
+ },
214
304
  spinner: {
215
305
  marginTop: 12,
216
306
  alignSelf: 'center',
@@ -1,3 +1,3 @@
1
1
  import React from 'react';
2
2
  import { OTAUpdateProviderProps } from '../types';
3
- export declare function OTAUpdateProvider({ children, projectConfig, autoSync, skipInDev, messages, fontFamily, syncOptions, }: OTAUpdateProviderProps): React.JSX.Element;
3
+ export declare function OTAUpdateProvider({ children, projectConfig, autoSync, skipInDev, messages, fontFamily, syncOptions, renderUpdateUI, UpdateComponent, }: OTAUpdateProviderProps): React.JSX.Element;
@@ -43,12 +43,12 @@ const telemetry_1 = require("../telemetry");
43
43
  const OTAUpdateModal_1 = require("./OTAUpdateModal");
44
44
  const SYNC_DELAY_MS = 2500;
45
45
  const FOREGROUND_SYNC_DEBOUNCE_MS = 30000;
46
- function OTAUpdateProvider({ children, projectConfig, autoSync = true, skipInDev = true, messages, fontFamily, syncOptions, }) {
46
+ function OTAUpdateProvider({ children, projectConfig, autoSync = true, skipInDev = true, messages, fontFamily, syncOptions, renderUpdateUI, UpdateComponent, }) {
47
47
  const [progress, setProgress] = (0, react_1.useState)((0, progress_1.getProgressState)());
48
48
  const lastSyncAtRef = (0, react_1.useRef)(0);
49
49
  const foregroundTimerRef = (0, react_1.useRef)(null);
50
50
  const hasMountedSyncRef = (0, react_1.useRef)(false);
51
- const modalTitle = (0, progress_1.getModalTitle)(messages);
51
+ const modalTitle = (0, progress_1.getModalTitle)(messages, progress.updateType);
52
52
  (0, react_1.useEffect)(() => {
53
53
  if (projectConfig) {
54
54
  (0, projectConfig_1.configureFromProject)(projectConfig);
@@ -101,8 +101,27 @@ function OTAUpdateProvider({ children, projectConfig, autoSync = true, skipInDev
101
101
  };
102
102
  // eslint-disable-next-line react-hooks/exhaustive-deps
103
103
  }, []);
104
+ const uiProps = (0, react_1.useMemo)(() => ({
105
+ ...progress,
106
+ title: modalTitle,
107
+ fontFamily,
108
+ messages,
109
+ onUpdate: () => (0, sync_1.respondToUpdatePrompt)('update'),
110
+ onSkip: () => (0, sync_1.respondToUpdatePrompt)('skip'),
111
+ onDismiss: () => (0, sync_1.respondToUpdatePrompt)('dismiss'),
112
+ }), [progress, modalTitle, fontFamily, messages]);
113
+ let updateUI = null;
114
+ if (renderUpdateUI) {
115
+ updateUI = renderUpdateUI(uiProps);
116
+ }
117
+ else if (UpdateComponent) {
118
+ updateUI = <UpdateComponent {...uiProps}/>;
119
+ }
120
+ else {
121
+ updateUI = <OTAUpdateModal_1.OTAUpdateModal {...uiProps}/>;
122
+ }
104
123
  return (<>
105
124
  {children}
106
- <OTAUpdateModal_1.OTAUpdateModal {...progress} title={modalTitle} fontFamily={fontFamily} messages={messages}/>
125
+ {updateUI}
107
126
  </>);
108
127
  }
package/lib/zipExtract.js CHANGED
@@ -88,6 +88,12 @@ async function extractZipToDirectoryJs(zipPath, destDir) {
88
88
  await mkdirRecursive(`${destDir}/${normalized.slice(0, -1)}`);
89
89
  continue;
90
90
  }
91
+ const base = normalized.split('/').pop() ?? normalized;
92
+ if (normalized.split('/').includes('__MACOSX') ||
93
+ base.startsWith('._') ||
94
+ base === '.DS_Store') {
95
+ continue;
96
+ }
91
97
  const content = rawContent instanceof Uint8Array
92
98
  ? rawContent
93
99
  : new Uint8Array(rawContent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsonair.ir/react-native",
3
- "version": "1.0.7",
3
+ "version": "1.0.10",
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",