From d4e81e354706fe2cfa7998fc2165eb10c4c0ee0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Mon, 17 Aug 2026 10:57:17 +0200 Subject: [PATCH] Android: don't let download/install failures escape as hung promises downloadUpdate and installUpdate run inside AsyncTask.doInBackground, but their catch clauses only listed a subset of the exception types the code underneath can actually throw (e.g. downloadUpdate didn't catch CodePushMalformedDataException, which downloadPackage throws for a malformed download URL). Any exception type not explicitly caught escapes doInBackground uncaught, and the JS promise never settles - no resolve, no reject, just a permanent hang with nothing in logcat tying it to a cause. Add the missing CodePushMalformedDataException catch, plus a final catch (Exception e) safety net in both methods so every failure path - including ones not yet enumerated - ends in a rejected promise instead of an uncaught background-thread exception. --- .../codepush/react/CodePushNativeModule.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java index 604e60e3..c8f299e4 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java @@ -400,7 +400,13 @@ public void dispatchDownloadProgressEvent() { CodePushUtils.log(e); mSettingsManager.saveFailedUpdate(CodePushUtils.convertReadableToJsonObject(updatePackage)); promise.reject(e); - } catch (IOException | CodePushUnknownException e) { + } catch (IOException | CodePushUnknownException | CodePushMalformedDataException e) { + CodePushUtils.log(e); + promise.reject(e); + } catch (Exception e) { + // Safety net: make sure a download failure always rejects the JS promise + // instead of escaping this background task uncaught, which would leave + // the promise hanging forever with no error and no log tying it to a cause. CodePushUtils.log(e); promise.reject(e); } @@ -492,6 +498,12 @@ protected Void doInBackground(Void... params) { } catch(CodePushUnknownException e) { CodePushUtils.log(e); promise.reject(e); + } catch (Exception e) { + // Safety net: make sure a failure always rejects the JS promise + // instead of escaping this background task uncaught, which would leave + // the promise hanging forever with no error and no log tying it to a cause. + CodePushUtils.log(e); + promise.reject(e); } return null; @@ -550,6 +562,12 @@ protected Void doInBackground(Void... params) { } catch(CodePushUnknownException e) { CodePushUtils.log(e); promise.reject(e); + } catch (Exception e) { + // Safety net: make sure a failure always rejects the JS promise + // instead of escaping this background task uncaught, which would leave + // the promise hanging forever with no error and no log tying it to a cause. + CodePushUtils.log(e); + promise.reject(e); } return null; } @@ -634,7 +652,13 @@ public void onHostDestroy() { } promise.resolve(""); - } catch(CodePushUnknownException e) { + } catch (CodePushUnknownException | CodePushMalformedDataException e) { + CodePushUtils.log(e); + promise.reject(e); + } catch (Exception e) { + // Safety net: make sure an install failure always rejects the JS promise + // instead of escaping this background task uncaught, which would leave + // the promise hanging forever with no error and no log tying it to a cause. CodePushUtils.log(e); promise.reject(e); }