@capgo/capacitor-updater 8.45.8 → 8.45.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.
@@ -54,7 +54,7 @@ dependencies {
54
54
  implementation "androidx.work:work-runtime:$work_version"
55
55
  implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
56
56
  implementation "com.google.android.gms:play-services-tasks:18.4.1"
57
- implementation "com.google.guava:guava:33.5.0-android"
57
+ implementation "com.google.guava:guava:33.6.0-android"
58
58
  implementation fileTree(dir: 'libs', include: ['*.jar'])
59
59
  implementation project(':capacitor-android')
60
60
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
@@ -88,8 +88,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
88
88
  private static final String SPLASH_SCREEN_PLUGIN_ID = "SplashScreen";
89
89
  private static final int SPLASH_SCREEN_RETRY_DELAY_MS = 100;
90
90
  private static final int SPLASH_SCREEN_MAX_RETRIES = 20;
91
+ private static final long PENDING_BUNDLE_APP_READY_MIN_TIMEOUT_MS = 30000L;
91
92
 
92
- private final String pluginVersion = "8.45.8";
93
+ private final String pluginVersion = "8.45.10";
93
94
  private static final String DELAY_CONDITION_PREFERENCES = "";
94
95
 
95
96
  private SharedPreferences.Editor editor;
@@ -134,8 +135,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
134
135
  private volatile long downloadStartTimeMs = 0;
135
136
  private static final long DOWNLOAD_TIMEOUT_MS = 3600000; // 1 hour timeout
136
137
 
137
- // private static final CountDownLatch semaphoreReady = new CountDownLatch(1);
138
- private static final Phaser semaphoreReady = new Phaser(1);
138
+ private final Phaser semaphoreReady = new Phaser(0) {
139
+ @Override
140
+ protected boolean onAdvance(final int phase, final int registeredParties) {
141
+ return false;
142
+ }
143
+ };
139
144
 
140
145
  // Lock to ensure cleanup completes before downloads start
141
146
  private final Object cleanupLock = new Object();
@@ -284,13 +289,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
284
289
 
285
290
  @Override
286
291
  public void directUpdateFinish(final BundleInfo latest) {
287
- if (activity != null) {
288
- activity.runOnUiThread(() -> {
289
- CapacitorUpdaterPlugin.this.directUpdateFinish(latest);
290
- });
291
- } else {
292
- logger.warn("directUpdateFinish: Activity is null, skipping notification");
293
- }
292
+ CapacitorUpdaterPlugin.this.scheduleDirectUpdateFinish(latest);
294
293
  }
295
294
 
296
295
  @Override
@@ -521,30 +520,69 @@ public class CapacitorUpdaterPlugin extends Plugin {
521
520
  }
522
521
  }
523
522
 
524
- private void semaphoreWait(Number waitTime) {
523
+ private boolean semaphoreWait(final int phase, Number waitTime) {
525
524
  try {
526
- semaphoreReady.awaitAdvanceInterruptibly(semaphoreReady.getPhase(), waitTime.longValue(), TimeUnit.SECONDS);
525
+ semaphoreReady.awaitAdvanceInterruptibly(phase, waitTime.longValue(), TimeUnit.MILLISECONDS);
527
526
  logger.info("semaphoreReady count " + semaphoreReady.getPhase());
527
+ return true;
528
528
  } catch (InterruptedException e) {
529
529
  logger.info("semaphoreWait InterruptedException");
530
+ cleanupTimedOutSemaphoreWait(phase);
530
531
  Thread.currentThread().interrupt(); // Restore interrupted status
532
+ return false;
531
533
  } catch (TimeoutException e) {
532
534
  logger.error("Semaphore timeout: " + e.getMessage());
533
- // Don't throw runtime exception, just log and continue
535
+ cleanupTimedOutSemaphoreWait(phase);
536
+ return false;
534
537
  }
535
538
  }
536
539
 
537
- private void semaphoreUp() {
540
+ private int semaphoreUp() {
538
541
  logger.info("semaphoreUp");
539
- semaphoreReady.register();
542
+ return semaphoreReady.register();
540
543
  }
541
544
 
542
545
  private void semaphoreDown() {
546
+ if (semaphoreReady.getRegisteredParties() == 0) {
547
+ logger.info("semaphoreDown skipped, no pending app ready wait");
548
+ return;
549
+ }
543
550
  logger.info("semaphoreDown");
544
551
  logger.info("semaphoreDown count " + semaphoreReady.getPhase());
545
552
  semaphoreReady.arriveAndDeregister();
546
553
  }
547
554
 
555
+ private void cleanupTimedOutSemaphoreWait(final int phase) {
556
+ if (semaphoreReady.getPhase() != phase || semaphoreReady.getRegisteredParties() == 0) {
557
+ return;
558
+ }
559
+ logger.info("Cleaning up stale app ready wait for phase " + phase);
560
+ semaphoreReady.arriveAndDeregister();
561
+ }
562
+
563
+ protected long getMinimumPendingBundleAppReadyTimeoutMs() {
564
+ return PENDING_BUNDLE_APP_READY_MIN_TIMEOUT_MS;
565
+ }
566
+
567
+ private long resolveAppReadyCheckTimeoutMs() {
568
+ long configuredTimeoutMs = this.appReadyTimeout.longValue();
569
+ try {
570
+ if (this.implementation == null) {
571
+ return configuredTimeoutMs;
572
+ }
573
+
574
+ final BundleInfo current = this.implementation.getCurrentBundle();
575
+ if (current == null || BundleStatus.SUCCESS == current.getStatus()) {
576
+ return configuredTimeoutMs;
577
+ }
578
+
579
+ return Math.max(configuredTimeoutMs, this.getMinimumPendingBundleAppReadyTimeoutMs());
580
+ } catch (final Exception e) {
581
+ logger.warn("Falling back to configured appReadyTimeout: " + e.getMessage());
582
+ return configuredTimeoutMs;
583
+ }
584
+ }
585
+
548
586
  private void sendReadyToJs(final BundleInfo current, final String msg) {
549
587
  sendReadyToJs(current, msg, false);
550
588
  }
@@ -864,6 +902,22 @@ public class CapacitorUpdaterPlugin extends Plugin {
864
902
  this.endBackGroundTaskWithNotif("test", current.getVersionName(), current, false, plannedDirectUpdate);
865
903
  }
866
904
 
905
+ void scheduleDirectUpdateFinish(final BundleInfo latest) {
906
+ startNewThread(() -> {
907
+ try {
908
+ Activity currentActivity = this.getActivity();
909
+ if (currentActivity != null) {
910
+ this.implementation.activity = currentActivity;
911
+ } else {
912
+ logger.warn("directUpdateFinish: Activity is null, proceeding without refreshing the activity reference");
913
+ }
914
+ this.directUpdateFinish(latest);
915
+ } catch (final Exception e) {
916
+ logger.error("directUpdateFinish failed: " + e.getMessage());
917
+ }
918
+ });
919
+ }
920
+
867
921
  private void directUpdateFinish(final BundleInfo latest) {
868
922
  if ("onLaunch".equals(this.directUpdateMode)) {
869
923
  this.onLaunchDirectUpdateUsed = true;
@@ -1438,68 +1492,64 @@ public class CapacitorUpdaterPlugin extends Plugin {
1438
1492
  }
1439
1493
 
1440
1494
  protected boolean _reload() {
1441
- this.semaphoreUp();
1495
+ final int phase = this.semaphoreUp();
1442
1496
  this.applyCurrentBundleToBridge();
1443
1497
 
1444
- this.checkAppReady();
1498
+ final long waitTimeMs = this.resolveAppReadyCheckTimeoutMs();
1499
+ this.checkAppReady(waitTimeMs);
1445
1500
  this.notifyListeners("appReloaded", new JSObject());
1446
1501
 
1447
1502
  // Wait for the reload to complete (until notifyAppReady is called)
1448
- try {
1449
- this.semaphoreWait(this.appReadyTimeout);
1450
- } catch (Exception e) {
1451
- logger.error("Error waiting for app ready: " + e.getMessage());
1452
- return false;
1453
- }
1454
-
1455
- return true;
1503
+ return this.semaphoreWait(phase, waitTimeMs);
1456
1504
  }
1457
1505
 
1458
1506
  @PluginMethod
1459
1507
  public void reload(final PluginCall call) {
1460
- try {
1461
- final BundleInfo current = this.implementation.getCurrentBundle();
1462
- final BundleInfo next = this.implementation.getNextBundle();
1463
-
1464
- if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
1465
- final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
1466
- final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
1467
- logger.info("Applying pending bundle before reload: " + next.getVersionName());
1468
- final boolean didApplyPendingBundle;
1469
- if (next.isBuiltin()) {
1470
- this.implementation.prepareResetStateForTransition();
1471
- didApplyPendingBundle = true;
1472
- } else {
1473
- didApplyPendingBundle = this.implementation.stagePendingReload(next);
1474
- }
1475
- if (didApplyPendingBundle && this._reload()) {
1508
+ startNewThread(() -> {
1509
+ try {
1510
+ final BundleInfo current = this.implementation.getCurrentBundle();
1511
+ final BundleInfo next = this.implementation.getNextBundle();
1512
+
1513
+ if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
1514
+ final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
1515
+ final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
1516
+ logger.info("Applying pending bundle before reload: " + next.getVersionName());
1517
+ final boolean didApplyPendingBundle;
1476
1518
  if (next.isBuiltin()) {
1477
- this.implementation.finalizeResetTransition(previousBundleName, false);
1519
+ this.implementation.prepareResetStateForTransition();
1520
+ didApplyPendingBundle = true;
1478
1521
  } else {
1479
- this.implementation.finalizePendingReload(next, previousBundleName);
1522
+ didApplyPendingBundle = this.implementation.stagePendingReload(next);
1480
1523
  }
1481
- this.notifyBundleSet(next);
1482
- this.implementation.setNextBundle(null);
1483
- call.resolve();
1524
+ if (didApplyPendingBundle && this._reload()) {
1525
+ if (next.isBuiltin()) {
1526
+ this.implementation.finalizeResetTransition(previousBundleName, false);
1527
+ } else {
1528
+ this.implementation.finalizePendingReload(next, previousBundleName);
1529
+ }
1530
+ this.notifyBundleSet(next);
1531
+ this.implementation.setNextBundle(null);
1532
+ call.resolve();
1533
+ return;
1534
+ }
1535
+ this.implementation.restoreResetState(previousState);
1536
+ this.restoreLiveBundleStateAfterFailedReload();
1537
+ logger.error("Reload failed after applying pending bundle: " + next.getVersionName());
1538
+ call.reject("Reload failed after applying pending bundle: " + next.getVersionName());
1484
1539
  return;
1485
1540
  }
1486
- this.implementation.restoreResetState(previousState);
1487
- this.restoreLiveBundleStateAfterFailedReload();
1488
- logger.error("Reload failed after applying pending bundle: " + next.getVersionName());
1489
- call.reject("Reload failed after applying pending bundle: " + next.getVersionName());
1490
- return;
1491
- }
1492
1541
 
1493
- if (this._reload()) {
1494
- call.resolve();
1495
- } else {
1496
- logger.error("Reload failed");
1497
- call.reject("Reload failed");
1542
+ if (this._reload()) {
1543
+ call.resolve();
1544
+ } else {
1545
+ logger.error("Reload failed");
1546
+ call.reject("Reload failed");
1547
+ }
1548
+ } catch (final Exception e) {
1549
+ logger.error("Could not reload " + e.getMessage());
1550
+ call.reject("Could not reload", e);
1498
1551
  }
1499
- } catch (final Exception e) {
1500
- logger.error("Could not reload " + e.getMessage());
1501
- call.reject("Could not reload", e);
1502
- }
1552
+ });
1503
1553
  }
1504
1554
 
1505
1555
  @PluginMethod
@@ -1929,11 +1979,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
1929
1979
  }
1930
1980
 
1931
1981
  private void checkAppReady() {
1982
+ this.checkAppReady(this.resolveAppReadyCheckTimeoutMs());
1983
+ }
1984
+
1985
+ private void checkAppReady(final long waitTimeMs) {
1932
1986
  try {
1933
1987
  if (this.appReadyCheck != null) {
1934
1988
  this.appReadyCheck.interrupt();
1935
1989
  }
1936
- this.appReadyCheck = startNewThread(new DeferredNotifyAppReadyCheck());
1990
+ this.appReadyCheck = startNewThread(new DeferredNotifyAppReadyCheck(waitTimeMs));
1937
1991
  } catch (final Exception e) {
1938
1992
  logger.error("Failed to start " + DeferredNotifyAppReadyCheck.class.getName() + " " + e.getMessage());
1939
1993
  }
@@ -2325,16 +2379,18 @@ public class CapacitorUpdaterPlugin extends Plugin {
2325
2379
  if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
2326
2380
  // There is a next bundle waiting for activation
2327
2381
  logger.debug("Next bundle is: " + next.getVersionName());
2328
- if (this.implementation.set(next) && this._reload()) {
2329
- logger.info("Updated to bundle: " + next.getVersionName());
2330
- this.notifyBundleSet(next);
2331
- this.implementation.setNextBundle(null);
2332
- } else {
2333
- logger.error("Update to bundle: " + next.getVersionName() + " Failed!");
2334
- }
2382
+ startNewThread(() -> {
2383
+ if (this.implementation.set(next) && this._reload()) {
2384
+ logger.info("Updated to bundle: " + next.getVersionName());
2385
+ this.notifyBundleSet(next);
2386
+ this.implementation.setNextBundle(null);
2387
+ } else {
2388
+ logger.error("Update to bundle: " + next.getVersionName() + " Failed!");
2389
+ }
2390
+ });
2335
2391
  }
2336
2392
  } catch (final Exception e) {
2337
- logger.error("Error during onActivityStopped " + e.getMessage());
2393
+ logger.error("Error during installNext " + e);
2338
2394
  }
2339
2395
  }
2340
2396
 
@@ -2376,11 +2432,17 @@ public class CapacitorUpdaterPlugin extends Plugin {
2376
2432
 
2377
2433
  private class DeferredNotifyAppReadyCheck implements Runnable {
2378
2434
 
2435
+ private final long waitTimeMs;
2436
+
2437
+ DeferredNotifyAppReadyCheck(final long waitTimeMs) {
2438
+ this.waitTimeMs = waitTimeMs;
2439
+ }
2440
+
2379
2441
  @Override
2380
2442
  public void run() {
2381
2443
  try {
2382
- logger.info("Wait for " + CapacitorUpdaterPlugin.this.appReadyTimeout + "ms, then check for notifyAppReady");
2383
- Thread.sleep(CapacitorUpdaterPlugin.this.appReadyTimeout);
2444
+ logger.info("Wait for " + this.waitTimeMs + "ms, then check for notifyAppReady");
2445
+ Thread.sleep(this.waitTimeMs);
2384
2446
  CapacitorUpdaterPlugin.this.checkRevert();
2385
2447
  CapacitorUpdaterPlugin.this.appReadyCheck = null;
2386
2448
  } catch (final InterruptedException e) {
@@ -72,7 +72,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
72
72
  CAPPluginMethod(name: "completeFlexibleUpdate", returnType: CAPPluginReturnPromise)
73
73
  ]
74
74
  public var implementation = CapgoUpdater()
75
- private let pluginVersion: String = "8.45.8"
75
+ private let pluginVersion: String = "8.45.10"
76
76
  static let updateUrlDefault = "https://plugin.capgo.app/updates"
77
77
  static let statsUrlDefault = "https://plugin.capgo.app/stats"
78
78
  static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "8.45.8",
3
+ "version": "8.45.10",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",
@@ -48,6 +48,7 @@
48
48
  "test": "bun run test:ios && bun run test:android",
49
49
  "test:ios": "./scripts/test-ios.sh",
50
50
  "test:android": "cd android && ./gradlew test && cd ..",
51
+ "test:maestro": "./scripts/maestro/run-android-live-update.sh",
51
52
  "test:maestro:android": "./scripts/test-maestro-android.sh",
52
53
  "test:maestro:ios": "./scripts/test-maestro-ios.sh",
53
54
  "lint": "bun run eslint && bun run prettier -- --check && bun run swiftlint -- lint",