react-native-update 10.53.0 → 10.53.1

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.
@@ -29,9 +29,31 @@ final class ApkInstaller {
29
29
  private ApkInstaller() {
30
30
  }
31
31
 
32
+ /**
33
+ * Everything in here runs inside the module call, where a thrown exception
34
+ * would take the whole app down instead of rejecting the promise. A missing
35
+ * REQUEST_INSTALL_PACKAGES declaration must surface as a rejection the JS
36
+ * layer can report — never as a crash — so no failure is allowed to escape.
37
+ */
32
38
  static boolean ensureInstallPermission(
33
39
  ReactApplicationContext reactContext,
34
40
  Promise promise
41
+ ) {
42
+ try {
43
+ return checkInstallPermission(reactContext, promise);
44
+ } catch (Throwable error) {
45
+ promise.reject(
46
+ ErrorCodes.APK_INSTALL_PERMISSION_REQUIRED,
47
+ "Unable to verify the install-packages permission",
48
+ error
49
+ );
50
+ return false;
51
+ }
52
+ }
53
+
54
+ private static boolean checkInstallPermission(
55
+ ReactApplicationContext reactContext,
56
+ Promise promise
35
57
  ) {
36
58
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
37
59
  promise.reject(
@@ -46,6 +68,8 @@ final class ApkInstaller {
46
68
  }
47
69
 
48
70
  PackageManager packageManager = reactContext.getPackageManager();
71
+ // canRequestPackageInstalls() itself throws SecurityException when the
72
+ // permission is not declared, so the manifest check has to come first.
49
73
  if (!declaresInstallPermission(reactContext, packageManager)) {
50
74
  promise.reject(
51
75
  ErrorCodes.APK_INSTALL_PERMISSION_REQUIRED,
@@ -97,7 +121,9 @@ final class ApkInstaller {
97
121
  return true;
98
122
  }
99
123
  }
100
- } catch (PackageManager.NameNotFoundException error) {
124
+ } catch (Throwable error) {
125
+ // A package manager that cannot answer is treated as "not declared":
126
+ // the caller rejects with a code the app can act on.
101
127
  Log.e(UpdateContext.TAG, "Unable to inspect requested permissions", error);
102
128
  }
103
129
  return false;
@@ -109,37 +135,36 @@ final class ApkInstaller {
109
135
  String sourceUrl,
110
136
  Promise promise
111
137
  ) {
112
- if (!apkFile.isFile() || apkFile.length() <= 0) {
113
- promise.reject(ErrorCodes.APK_INSTALL_FAILED, "Downloaded APK is empty or missing");
114
- return;
115
- }
116
-
117
- PackageInstaller packageInstaller = reactContext
118
- .getPackageManager()
119
- .getPackageInstaller();
120
- PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
121
- PackageInstaller.SessionParams.MODE_FULL_INSTALL
122
- );
123
- sessionParams.setAppPackageName(reactContext.getPackageName());
124
- sessionParams.setSize(apkFile.length());
125
- if (sourceUrl != null && !sourceUrl.isEmpty()) {
126
- sessionParams.setOriginatingUri(Uri.parse(sourceUrl));
127
- }
128
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
129
- sessionParams.setOriginatingUid(Process.myUid());
130
- }
131
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
132
- sessionParams.setInstallReason(PackageManager.INSTALL_REASON_USER);
133
- }
134
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
135
- sessionParams.setRequireUserAction(
136
- PackageInstaller.SessionParams.USER_ACTION_REQUIRED
137
- );
138
- }
139
-
138
+ PackageInstaller packageInstaller = null;
140
139
  int sessionId = -1;
141
140
  boolean committed = false;
142
141
  try {
142
+ if (!apkFile.isFile() || apkFile.length() <= 0) {
143
+ promise.reject(ErrorCodes.APK_INSTALL_FAILED, "Downloaded APK is empty or missing");
144
+ return;
145
+ }
146
+
147
+ packageInstaller = reactContext.getPackageManager().getPackageInstaller();
148
+ PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
149
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL
150
+ );
151
+ sessionParams.setAppPackageName(reactContext.getPackageName());
152
+ sessionParams.setSize(apkFile.length());
153
+ if (sourceUrl != null && !sourceUrl.isEmpty()) {
154
+ sessionParams.setOriginatingUri(Uri.parse(sourceUrl));
155
+ }
156
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
157
+ sessionParams.setOriginatingUid(Process.myUid());
158
+ }
159
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
160
+ sessionParams.setInstallReason(PackageManager.INSTALL_REASON_USER);
161
+ }
162
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
163
+ sessionParams.setRequireUserAction(
164
+ PackageInstaller.SessionParams.USER_ACTION_REQUIRED
165
+ );
166
+ }
167
+
143
168
  sessionId = packageInstaller.createSession(sessionParams);
144
169
  try (
145
170
  PackageInstaller.Session session = packageInstaller.openSession(sessionId);
@@ -173,9 +198,16 @@ final class ApkInstaller {
173
198
  committed = true;
174
199
  }
175
200
  } catch (Throwable error) {
201
+ if (committed) {
202
+ // The session is already in the system's hands (only session
203
+ // close failed): the status receiver owns the promise now, so
204
+ // settling here would be a second, contradictory outcome.
205
+ Log.w(UpdateContext.TAG, "Install session reported an error after commit", error);
206
+ return;
207
+ }
176
208
  if (sessionId != -1) {
177
209
  pendingPromises.remove(sessionId);
178
- if (!committed) {
210
+ if (packageInstaller != null) {
179
211
  try {
180
212
  packageInstaller.abandonSession(sessionId);
181
213
  } catch (Throwable abandonError) {
@@ -61,23 +61,31 @@ public class UpdateModuleImpl {
61
61
  final ReadableMap options,
62
62
  final Promise promise
63
63
  ) {
64
- final String url = options.getString("url");
65
- String hash = options.getString("hash");
66
- String target = options.getString("target");
67
- if (!ApkInstaller.ensureInstallPermission(reactContext, promise)) {
68
- return;
69
- }
70
- updateContext.downloadFile(url, hash, target, new UpdateContext.DownloadFileListener() {
71
- @Override
72
- public void onDownloadCompleted(DownloadTaskParams params) {
73
- ApkInstaller.install(reactContext, params.targetFile, url, promise);
64
+ // A module call runs on the native modules thread: anything thrown here
65
+ // reaches the bridge's exception handler and kills the app. Every
66
+ // failure of this path — a missing REQUEST_INSTALL_PACKAGES declaration
67
+ // above all — has to come back as a promise rejection instead.
68
+ try {
69
+ final String url = options.getString("url");
70
+ String hash = options.getString("hash");
71
+ String target = options.getString("target");
72
+ if (!ApkInstaller.ensureInstallPermission(reactContext, promise)) {
73
+ return;
74
74
  }
75
+ updateContext.downloadFile(url, hash, target, new UpdateContext.DownloadFileListener() {
76
+ @Override
77
+ public void onDownloadCompleted(DownloadTaskParams params) {
78
+ ApkInstaller.install(reactContext, params.targetFile, url, promise);
79
+ }
75
80
 
76
- @Override
77
- public void onDownloadFailed(Throwable error) {
78
- promise.reject(ErrorCodes.DOWNLOAD_FAILED, error);
79
- }
80
- });
81
+ @Override
82
+ public void onDownloadFailed(Throwable error) {
83
+ promise.reject(ErrorCodes.DOWNLOAD_FAILED, error);
84
+ }
85
+ });
86
+ } catch (Throwable error) {
87
+ promise.reject(ErrorCodes.APK_INSTALL_FAILED, error);
88
+ }
81
89
  }
82
90
 
83
91
  public static void downloadPatchFromPackage(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.53.0",
3
+ "version": "10.53.1",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "types": "src/index.ts",