@capgo/capacitor-updater 8.45.3 → 8.45.9
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.
- package/Package.swift +1 -1
- package/README.md +4 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +212 -51
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +76 -11
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +17 -1
- package/dist/docs.json +21 -4
- package/dist/esm/definitions.d.ts +17 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +164 -48
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +87 -9
- package/package.json +2 -1
package/Package.swift
CHANGED
|
@@ -11,7 +11,7 @@ let package = Package(
|
|
|
11
11
|
],
|
|
12
12
|
dependencies: [
|
|
13
13
|
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
|
|
14
|
-
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.11.
|
|
14
|
+
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.11.2")),
|
|
15
15
|
.package(url: "https://github.com/weichsel/ZIPFoundation.git", from: "0.9.20"),
|
|
16
16
|
.package(url: "https://github.com/mrackwitz/Version.git", exact: "0.8.0"),
|
|
17
17
|
.package(url: "https://github.com/attaswift/BigInt.git", from: "5.7.0")
|
package/README.md
CHANGED
|
@@ -2063,9 +2063,10 @@ If you don't use backend, you need to provide the URL and version of the bundle.
|
|
|
2063
2063
|
|
|
2064
2064
|
##### ResetOptions
|
|
2065
2065
|
|
|
2066
|
-
| Prop | Type |
|
|
2067
|
-
| ---------------------- | -------------------- |
|
|
2068
|
-
| **`toLastSuccessful`** | <code>boolean</code> |
|
|
2066
|
+
| Prop | Type | Description | Default |
|
|
2067
|
+
| ---------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
|
|
2068
|
+
| **`toLastSuccessful`** | <code>boolean</code> | Reset to the last successfully loaded bundle instead of the builtin one. | <code>false</code> |
|
|
2069
|
+
| **`usePendingBundle`** | <code>boolean</code> | Apply the pending bundle set via {@link next} while resetting. When `true`, the plugin will switch to the pending bundle immediately and clear the pending flag. If no pending bundle exists, the reset will fail. | <code>false</code> |
|
|
2069
2070
|
|
|
2070
2071
|
|
|
2071
2072
|
##### CurrentBundleResult
|
|
@@ -22,6 +22,7 @@ import android.view.View;
|
|
|
22
22
|
import android.view.ViewGroup;
|
|
23
23
|
import android.widget.FrameLayout;
|
|
24
24
|
import android.widget.ProgressBar;
|
|
25
|
+
import androidx.core.content.pm.PackageInfoCompat;
|
|
25
26
|
import com.getcapacitor.Bridge;
|
|
26
27
|
import com.getcapacitor.CapConfig;
|
|
27
28
|
import com.getcapacitor.JSArray;
|
|
@@ -87,8 +88,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
87
88
|
private static final String SPLASH_SCREEN_PLUGIN_ID = "SplashScreen";
|
|
88
89
|
private static final int SPLASH_SCREEN_RETRY_DELAY_MS = 100;
|
|
89
90
|
private static final int SPLASH_SCREEN_MAX_RETRIES = 20;
|
|
91
|
+
private static final long PENDING_BUNDLE_APP_READY_MIN_TIMEOUT_MS = 30000L;
|
|
90
92
|
|
|
91
|
-
private final String pluginVersion = "8.45.
|
|
93
|
+
private final String pluginVersion = "8.45.9";
|
|
92
94
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
93
95
|
|
|
94
96
|
private SharedPreferences.Editor editor;
|
|
@@ -133,8 +135,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
133
135
|
private volatile long downloadStartTimeMs = 0;
|
|
134
136
|
private static final long DOWNLOAD_TIMEOUT_MS = 3600000; // 1 hour timeout
|
|
135
137
|
|
|
136
|
-
|
|
137
|
-
|
|
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
|
+
};
|
|
138
144
|
|
|
139
145
|
// Lock to ensure cleanup completes before downloads start
|
|
140
146
|
private final Object cleanupLock = new Object();
|
|
@@ -191,7 +197,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
191
197
|
}
|
|
192
198
|
|
|
193
199
|
private String getVersionCode(final PackageInfo packageInfo) {
|
|
194
|
-
return Long.toString(
|
|
200
|
+
return Long.toString(PackageInfoCompat.getLongVersionCode(packageInfo));
|
|
195
201
|
}
|
|
196
202
|
|
|
197
203
|
private void notifyBreakingEvents(final String version) {
|
|
@@ -283,13 +289,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
283
289
|
|
|
284
290
|
@Override
|
|
285
291
|
public void directUpdateFinish(final BundleInfo latest) {
|
|
286
|
-
|
|
287
|
-
activity.runOnUiThread(() -> {
|
|
288
|
-
CapacitorUpdaterPlugin.this.directUpdateFinish(latest);
|
|
289
|
-
});
|
|
290
|
-
} else {
|
|
291
|
-
logger.warn("directUpdateFinish: Activity is null, skipping notification");
|
|
292
|
-
}
|
|
292
|
+
CapacitorUpdaterPlugin.this.scheduleDirectUpdateFinish(latest);
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
@Override
|
|
@@ -520,30 +520,69 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
-
private
|
|
523
|
+
private boolean semaphoreWait(final int phase, Number waitTime) {
|
|
524
524
|
try {
|
|
525
|
-
semaphoreReady.awaitAdvanceInterruptibly(
|
|
525
|
+
semaphoreReady.awaitAdvanceInterruptibly(phase, waitTime.longValue(), TimeUnit.MILLISECONDS);
|
|
526
526
|
logger.info("semaphoreReady count " + semaphoreReady.getPhase());
|
|
527
|
+
return true;
|
|
527
528
|
} catch (InterruptedException e) {
|
|
528
529
|
logger.info("semaphoreWait InterruptedException");
|
|
530
|
+
cleanupTimedOutSemaphoreWait(phase);
|
|
529
531
|
Thread.currentThread().interrupt(); // Restore interrupted status
|
|
532
|
+
return false;
|
|
530
533
|
} catch (TimeoutException e) {
|
|
531
534
|
logger.error("Semaphore timeout: " + e.getMessage());
|
|
532
|
-
|
|
535
|
+
cleanupTimedOutSemaphoreWait(phase);
|
|
536
|
+
return false;
|
|
533
537
|
}
|
|
534
538
|
}
|
|
535
539
|
|
|
536
|
-
private
|
|
540
|
+
private int semaphoreUp() {
|
|
537
541
|
logger.info("semaphoreUp");
|
|
538
|
-
semaphoreReady.register();
|
|
542
|
+
return semaphoreReady.register();
|
|
539
543
|
}
|
|
540
544
|
|
|
541
545
|
private void semaphoreDown() {
|
|
546
|
+
if (semaphoreReady.getRegisteredParties() == 0) {
|
|
547
|
+
logger.info("semaphoreDown skipped, no pending app ready wait");
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
542
550
|
logger.info("semaphoreDown");
|
|
543
551
|
logger.info("semaphoreDown count " + semaphoreReady.getPhase());
|
|
544
552
|
semaphoreReady.arriveAndDeregister();
|
|
545
553
|
}
|
|
546
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
|
+
|
|
547
586
|
private void sendReadyToJs(final BundleInfo current, final String msg) {
|
|
548
587
|
sendReadyToJs(current, msg, false);
|
|
549
588
|
}
|
|
@@ -863,6 +902,22 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
863
902
|
this.endBackGroundTaskWithNotif("test", current.getVersionName(), current, false, plannedDirectUpdate);
|
|
864
903
|
}
|
|
865
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
|
+
|
|
866
921
|
private void directUpdateFinish(final BundleInfo latest) {
|
|
867
922
|
if ("onLaunch".equals(this.directUpdateMode)) {
|
|
868
923
|
this.onLaunchDirectUpdateUsed = true;
|
|
@@ -1333,12 +1388,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1333
1388
|
this.bridge.getWebView().post(() -> this.bridge.getWebView().evaluateJavascript(script, null));
|
|
1334
1389
|
}
|
|
1335
1390
|
|
|
1336
|
-
|
|
1391
|
+
private void applyCurrentBundleToBridge() {
|
|
1337
1392
|
final String path = this.implementation.getCurrentBundlePath();
|
|
1393
|
+
final boolean usingBuiltin = this.implementation.isUsingBuiltin();
|
|
1338
1394
|
if (this.keepUrlPathAfterReload) {
|
|
1339
1395
|
this.syncKeepUrlPathFlag(true);
|
|
1340
1396
|
}
|
|
1341
|
-
this.semaphoreUp();
|
|
1342
1397
|
logger.info("Reloading: " + path);
|
|
1343
1398
|
|
|
1344
1399
|
AtomicReference<URL> url = new AtomicReference<>();
|
|
@@ -1383,7 +1438,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1383
1438
|
}
|
|
1384
1439
|
|
|
1385
1440
|
if (url.get() != null) {
|
|
1386
|
-
if (
|
|
1441
|
+
if (usingBuiltin) {
|
|
1387
1442
|
this.bridge.getLocalServer().hostAssets(path);
|
|
1388
1443
|
} else {
|
|
1389
1444
|
this.bridge.getLocalServer().hostFiles(path);
|
|
@@ -1403,14 +1458,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1403
1458
|
} catch (MalformedURLException e) {
|
|
1404
1459
|
logger.error("Cannot get finalUrl from capacitor bridge " + e.getMessage());
|
|
1405
1460
|
|
|
1406
|
-
if (
|
|
1461
|
+
if (usingBuiltin) {
|
|
1407
1462
|
this.bridge.setServerAssetPath(path);
|
|
1408
1463
|
} else {
|
|
1409
1464
|
this.bridge.setServerBasePath(path);
|
|
1410
1465
|
}
|
|
1411
1466
|
}
|
|
1412
1467
|
} else {
|
|
1413
|
-
if (
|
|
1468
|
+
if (usingBuiltin) {
|
|
1414
1469
|
this.bridge.setServerAssetPath(path);
|
|
1415
1470
|
} else {
|
|
1416
1471
|
this.bridge.setServerBasePath(path);
|
|
@@ -1426,24 +1481,63 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1426
1481
|
});
|
|
1427
1482
|
}
|
|
1428
1483
|
}
|
|
1484
|
+
}
|
|
1429
1485
|
|
|
1430
|
-
|
|
1431
|
-
this.notifyListeners("appReloaded", new JSObject());
|
|
1432
|
-
|
|
1433
|
-
// Wait for the reload to complete (until notifyAppReady is called)
|
|
1486
|
+
protected void restoreLiveBundleStateAfterFailedReload() {
|
|
1434
1487
|
try {
|
|
1435
|
-
this.
|
|
1436
|
-
} catch (Exception e) {
|
|
1437
|
-
logger.
|
|
1438
|
-
return false;
|
|
1488
|
+
this.applyCurrentBundleToBridge();
|
|
1489
|
+
} catch (final Exception e) {
|
|
1490
|
+
logger.warn("Failed to restore live bundle after rejected reload: " + e.getMessage());
|
|
1439
1491
|
}
|
|
1492
|
+
}
|
|
1440
1493
|
|
|
1441
|
-
|
|
1494
|
+
protected boolean _reload() {
|
|
1495
|
+
final int phase = this.semaphoreUp();
|
|
1496
|
+
this.applyCurrentBundleToBridge();
|
|
1497
|
+
|
|
1498
|
+
final long waitTimeMs = this.resolveAppReadyCheckTimeoutMs();
|
|
1499
|
+
this.checkAppReady(waitTimeMs);
|
|
1500
|
+
this.notifyListeners("appReloaded", new JSObject());
|
|
1501
|
+
|
|
1502
|
+
// Wait for the reload to complete (until notifyAppReady is called)
|
|
1503
|
+
return this.semaphoreWait(phase, waitTimeMs);
|
|
1442
1504
|
}
|
|
1443
1505
|
|
|
1444
1506
|
@PluginMethod
|
|
1445
1507
|
public void reload(final PluginCall call) {
|
|
1446
1508
|
try {
|
|
1509
|
+
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
1510
|
+
final BundleInfo next = this.implementation.getNextBundle();
|
|
1511
|
+
|
|
1512
|
+
if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
|
|
1513
|
+
final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
|
|
1514
|
+
final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
|
|
1515
|
+
logger.info("Applying pending bundle before reload: " + next.getVersionName());
|
|
1516
|
+
final boolean didApplyPendingBundle;
|
|
1517
|
+
if (next.isBuiltin()) {
|
|
1518
|
+
this.implementation.prepareResetStateForTransition();
|
|
1519
|
+
didApplyPendingBundle = true;
|
|
1520
|
+
} else {
|
|
1521
|
+
didApplyPendingBundle = this.implementation.stagePendingReload(next);
|
|
1522
|
+
}
|
|
1523
|
+
if (didApplyPendingBundle && this._reload()) {
|
|
1524
|
+
if (next.isBuiltin()) {
|
|
1525
|
+
this.implementation.finalizeResetTransition(previousBundleName, false);
|
|
1526
|
+
} else {
|
|
1527
|
+
this.implementation.finalizePendingReload(next, previousBundleName);
|
|
1528
|
+
}
|
|
1529
|
+
this.notifyBundleSet(next);
|
|
1530
|
+
this.implementation.setNextBundle(null);
|
|
1531
|
+
call.resolve();
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
this.implementation.restoreResetState(previousState);
|
|
1535
|
+
this.restoreLiveBundleStateAfterFailedReload();
|
|
1536
|
+
logger.error("Reload failed after applying pending bundle: " + next.getVersionName());
|
|
1537
|
+
call.reject("Reload failed after applying pending bundle: " + next.getVersionName());
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1447
1541
|
if (this._reload()) {
|
|
1448
1542
|
call.resolve();
|
|
1449
1543
|
} else {
|
|
@@ -1605,28 +1699,83 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1605
1699
|
);
|
|
1606
1700
|
}
|
|
1607
1701
|
|
|
1608
|
-
private boolean _reset(final Boolean toLastSuccessful) {
|
|
1702
|
+
private boolean _reset(final Boolean toLastSuccessful, final Boolean usePendingBundle) {
|
|
1703
|
+
return this.performReset(toLastSuccessful, usePendingBundle, false);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
private boolean performReset(final Boolean toLastSuccessful, final Boolean usePendingBundle, final boolean internal) {
|
|
1609
1707
|
final BundleInfo fallback = this.implementation.getFallbackBundle();
|
|
1610
|
-
this.implementation.
|
|
1708
|
+
final BundleInfo pending = this.implementation.getNextBundle();
|
|
1709
|
+
final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
|
|
1710
|
+
final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
|
|
1611
1711
|
|
|
1612
|
-
if (
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1712
|
+
if (Boolean.TRUE.equals(usePendingBundle)) {
|
|
1713
|
+
if (pending == null || pending.isErrorStatus()) {
|
|
1714
|
+
logger.error("No pending bundle available to reset to");
|
|
1715
|
+
return false;
|
|
1716
|
+
}
|
|
1717
|
+
if (!this.implementation.canSet(pending)) {
|
|
1718
|
+
logger.error("Pending bundle is not installable");
|
|
1719
|
+
return false;
|
|
1720
|
+
}
|
|
1721
|
+
this.implementation.prepareResetStateForTransition();
|
|
1722
|
+
logger.info("Resetting to pending bundle: " + pending.getVersionName());
|
|
1723
|
+
final boolean didApplyPendingBundle;
|
|
1724
|
+
if (pending.isBuiltin()) {
|
|
1725
|
+
didApplyPendingBundle = true;
|
|
1726
|
+
} else {
|
|
1727
|
+
didApplyPendingBundle = this.implementation.set(pending);
|
|
1728
|
+
}
|
|
1729
|
+
if (didApplyPendingBundle && this._reload()) {
|
|
1730
|
+
this.implementation.finalizeResetTransition(previousBundleName, internal);
|
|
1731
|
+
this.notifyBundleSet(pending);
|
|
1732
|
+
this.implementation.setNextBundle(null);
|
|
1616
1733
|
return true;
|
|
1617
1734
|
}
|
|
1735
|
+
this.implementation.restoreResetState(previousState);
|
|
1736
|
+
this.restoreLiveBundleStateAfterFailedReload();
|
|
1618
1737
|
return false;
|
|
1619
1738
|
}
|
|
1620
1739
|
|
|
1740
|
+
if (Boolean.TRUE.equals(toLastSuccessful) && !fallback.isBuiltin()) {
|
|
1741
|
+
if (this.implementation.canSet(fallback)) {
|
|
1742
|
+
this.implementation.prepareResetStateForTransition();
|
|
1743
|
+
logger.info("Resetting to: " + fallback);
|
|
1744
|
+
if (this.implementation.set(fallback) && this._reload()) {
|
|
1745
|
+
this.implementation.finalizeResetTransition(previousBundleName, internal);
|
|
1746
|
+
this.notifyBundleSet(fallback);
|
|
1747
|
+
return true;
|
|
1748
|
+
}
|
|
1749
|
+
if (!internal) {
|
|
1750
|
+
this.implementation.restoreResetState(previousState);
|
|
1751
|
+
this.restoreLiveBundleStateAfterFailedReload();
|
|
1752
|
+
return false;
|
|
1753
|
+
}
|
|
1754
|
+
logger.warn("Fallback reload failed during internal reset, resetting to native instead");
|
|
1755
|
+
} else {
|
|
1756
|
+
logger.warn("Fallback bundle is not installable, resetting to native instead");
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
this.implementation.prepareResetStateForTransition();
|
|
1621
1761
|
logger.info("Resetting to native.");
|
|
1622
|
-
|
|
1762
|
+
if (this._reload()) {
|
|
1763
|
+
this.implementation.finalizeResetTransition(previousBundleName, internal);
|
|
1764
|
+
return true;
|
|
1765
|
+
}
|
|
1766
|
+
if (!internal) {
|
|
1767
|
+
this.implementation.restoreResetState(previousState);
|
|
1768
|
+
this.restoreLiveBundleStateAfterFailedReload();
|
|
1769
|
+
}
|
|
1770
|
+
return false;
|
|
1623
1771
|
}
|
|
1624
1772
|
|
|
1625
1773
|
@PluginMethod
|
|
1626
1774
|
public void reset(final PluginCall call) {
|
|
1627
1775
|
try {
|
|
1628
1776
|
final Boolean toLastSuccessful = call.getBoolean("toLastSuccessful", false);
|
|
1629
|
-
|
|
1777
|
+
final Boolean usePendingBundle = call.getBoolean("usePendingBundle", false);
|
|
1778
|
+
if (this._reset(toLastSuccessful, usePendingBundle)) {
|
|
1630
1779
|
call.resolve();
|
|
1631
1780
|
return;
|
|
1632
1781
|
}
|
|
@@ -1828,11 +1977,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1828
1977
|
}
|
|
1829
1978
|
|
|
1830
1979
|
private void checkAppReady() {
|
|
1980
|
+
this.checkAppReady(this.resolveAppReadyCheckTimeoutMs());
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
private void checkAppReady(final long waitTimeMs) {
|
|
1831
1984
|
try {
|
|
1832
1985
|
if (this.appReadyCheck != null) {
|
|
1833
1986
|
this.appReadyCheck.interrupt();
|
|
1834
1987
|
}
|
|
1835
|
-
this.appReadyCheck = startNewThread(new DeferredNotifyAppReadyCheck());
|
|
1988
|
+
this.appReadyCheck = startNewThread(new DeferredNotifyAppReadyCheck(waitTimeMs));
|
|
1836
1989
|
} catch (final Exception e) {
|
|
1837
1990
|
logger.error("Failed to start " + DeferredNotifyAppReadyCheck.class.getName() + " " + e.getMessage());
|
|
1838
1991
|
}
|
|
@@ -1990,7 +2143,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1990
2143
|
);
|
|
1991
2144
|
if (directUpdateAllowedNow) {
|
|
1992
2145
|
logger.info("Direct update to builtin version");
|
|
1993
|
-
this._reset(false);
|
|
2146
|
+
this._reset(false, false);
|
|
1994
2147
|
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
1995
2148
|
"Updated to builtin version",
|
|
1996
2149
|
latestVersionName,
|
|
@@ -2224,16 +2377,18 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2224
2377
|
if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
|
|
2225
2378
|
// There is a next bundle waiting for activation
|
|
2226
2379
|
logger.debug("Next bundle is: " + next.getVersionName());
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2380
|
+
startNewThread(() -> {
|
|
2381
|
+
if (this.implementation.set(next) && this._reload()) {
|
|
2382
|
+
logger.info("Updated to bundle: " + next.getVersionName());
|
|
2383
|
+
this.notifyBundleSet(next);
|
|
2384
|
+
this.implementation.setNextBundle(null);
|
|
2385
|
+
} else {
|
|
2386
|
+
logger.error("Update to bundle: " + next.getVersionName() + " Failed!");
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2234
2389
|
}
|
|
2235
2390
|
} catch (final Exception e) {
|
|
2236
|
-
logger.error("Error during
|
|
2391
|
+
logger.error("Error during installNext " + e);
|
|
2237
2392
|
}
|
|
2238
2393
|
}
|
|
2239
2394
|
|
|
@@ -2256,7 +2411,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2256
2411
|
this.notifyListeners("updateFailed", ret);
|
|
2257
2412
|
this.implementation.sendStats("update_fail", current.getVersionName());
|
|
2258
2413
|
this.implementation.setError(current);
|
|
2259
|
-
this.
|
|
2414
|
+
this.performReset(true, false, true);
|
|
2260
2415
|
if (CapacitorUpdaterPlugin.this.autoDeleteFailed && !current.isBuiltin()) {
|
|
2261
2416
|
logger.info("Deleting failing bundle: " + current.getVersionName());
|
|
2262
2417
|
try {
|
|
@@ -2275,11 +2430,17 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2275
2430
|
|
|
2276
2431
|
private class DeferredNotifyAppReadyCheck implements Runnable {
|
|
2277
2432
|
|
|
2433
|
+
private final long waitTimeMs;
|
|
2434
|
+
|
|
2435
|
+
DeferredNotifyAppReadyCheck(final long waitTimeMs) {
|
|
2436
|
+
this.waitTimeMs = waitTimeMs;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2278
2439
|
@Override
|
|
2279
2440
|
public void run() {
|
|
2280
2441
|
try {
|
|
2281
|
-
logger.info("Wait for " +
|
|
2282
|
-
Thread.sleep(
|
|
2442
|
+
logger.info("Wait for " + this.waitTimeMs + "ms, then check for notifyAppReady");
|
|
2443
|
+
Thread.sleep(this.waitTimeMs);
|
|
2283
2444
|
CapacitorUpdaterPlugin.this.checkRevert();
|
|
2284
2445
|
CapacitorUpdaterPlugin.this.appReadyCheck = null;
|
|
2285
2446
|
} catch (final InterruptedException e) {
|
|
@@ -991,6 +991,64 @@ public class CapgoUpdater {
|
|
|
991
991
|
return (bundle.isDirectory() && bundle.exists() && new File(bundle.getPath(), "/index.html").exists() && !bundleInfo.isDeleted());
|
|
992
992
|
}
|
|
993
993
|
|
|
994
|
+
static final class ResetState {
|
|
995
|
+
|
|
996
|
+
final String currentBundlePath;
|
|
997
|
+
final String fallbackBundleId;
|
|
998
|
+
final String nextBundleId;
|
|
999
|
+
|
|
1000
|
+
ResetState(final String currentBundlePath, final String fallbackBundleId, final String nextBundleId) {
|
|
1001
|
+
this.currentBundlePath = currentBundlePath;
|
|
1002
|
+
this.fallbackBundleId = fallbackBundleId;
|
|
1003
|
+
this.nextBundleId = nextBundleId;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
ResetState captureResetState() {
|
|
1008
|
+
return new ResetState(
|
|
1009
|
+
this.getCurrentBundlePath(),
|
|
1010
|
+
this.prefs.getString(FALLBACK_VERSION, BundleInfo.ID_BUILTIN),
|
|
1011
|
+
this.prefs.getString(NEXT_VERSION, null)
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
void restoreResetState(final ResetState state) {
|
|
1016
|
+
final String currentBundlePath = state.currentBundlePath == null || state.currentBundlePath.trim().isEmpty()
|
|
1017
|
+
? "public"
|
|
1018
|
+
: state.currentBundlePath;
|
|
1019
|
+
final String fallbackBundleId = state.fallbackBundleId == null || state.fallbackBundleId.isEmpty()
|
|
1020
|
+
? BundleInfo.ID_BUILTIN
|
|
1021
|
+
: state.fallbackBundleId;
|
|
1022
|
+
|
|
1023
|
+
this.editor.putString(this.CAP_SERVER_PATH, currentBundlePath);
|
|
1024
|
+
this.editor.putString(FALLBACK_VERSION, fallbackBundleId);
|
|
1025
|
+
if (state.nextBundleId == null || state.nextBundleId.isEmpty()) {
|
|
1026
|
+
this.editor.remove(NEXT_VERSION);
|
|
1027
|
+
} else {
|
|
1028
|
+
this.editor.putString(NEXT_VERSION, state.nextBundleId);
|
|
1029
|
+
}
|
|
1030
|
+
this.editor.commit();
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
void prepareResetStateForTransition() {
|
|
1034
|
+
this.setCurrentBundle(new File("public"));
|
|
1035
|
+
this.setFallbackBundle(null);
|
|
1036
|
+
this.setNextBundle(null);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
void finalizeResetTransition(final String previousBundleName, final boolean internal) {
|
|
1040
|
+
if (this.activity != null) {
|
|
1041
|
+
DownloadWorkerManager.cancelAllDownloads(this.activity);
|
|
1042
|
+
}
|
|
1043
|
+
if (!internal) {
|
|
1044
|
+
this.sendStats("reset", this.getCurrentBundle().getVersionName(), previousBundleName);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
boolean canSet(final BundleInfo bundle) {
|
|
1049
|
+
return bundle != null && (bundle.isBuiltin() || this.bundleExists(bundle.getId()));
|
|
1050
|
+
}
|
|
1051
|
+
|
|
994
1052
|
public Boolean set(final BundleInfo bundle) {
|
|
995
1053
|
return this.set(bundle.getId());
|
|
996
1054
|
}
|
|
@@ -1015,6 +1073,21 @@ public class CapgoUpdater {
|
|
|
1015
1073
|
return false;
|
|
1016
1074
|
}
|
|
1017
1075
|
|
|
1076
|
+
boolean stagePendingReload(final BundleInfo bundle) {
|
|
1077
|
+
if (bundle == null || bundle.isBuiltin() || !this.bundleExists(bundle.getId())) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
this.setCurrentBundle(this.getBundleDirectory(bundle.getId()));
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
void finalizePendingReload(final BundleInfo bundle, final String previousBundleName) {
|
|
1085
|
+
if (bundle == null || bundle.isBuiltin()) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
this.sendStats("set", bundle.getVersionName(), previousBundleName);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1018
1091
|
public void autoReset() {
|
|
1019
1092
|
final BundleInfo currentBundle = this.getCurrentBundle();
|
|
1020
1093
|
if (!currentBundle.isBuiltin() && !this.bundleExists(currentBundle.getId())) {
|
|
@@ -1058,17 +1131,9 @@ public class CapgoUpdater {
|
|
|
1058
1131
|
|
|
1059
1132
|
public void reset(final boolean internal) {
|
|
1060
1133
|
logger.debug("reset: " + internal);
|
|
1061
|
-
|
|
1062
|
-
this.
|
|
1063
|
-
this.
|
|
1064
|
-
this.setNextBundle(null);
|
|
1065
|
-
// Cancel any ongoing downloads
|
|
1066
|
-
if (this.activity != null) {
|
|
1067
|
-
DownloadWorkerManager.cancelAllDownloads(this.activity);
|
|
1068
|
-
}
|
|
1069
|
-
if (!internal) {
|
|
1070
|
-
this.sendStats("reset", this.getCurrentBundle().getVersionName(), currentBundleName);
|
|
1071
|
-
}
|
|
1134
|
+
final String currentBundleName = this.getCurrentBundle().getVersionName();
|
|
1135
|
+
this.prepareResetStateForTransition();
|
|
1136
|
+
this.finalizeResetTransition(currentBundleName, internal);
|
|
1072
1137
|
}
|
|
1073
1138
|
|
|
1074
1139
|
private JSONObject createInfoObject() throws JSONException {
|
|
@@ -110,7 +110,23 @@ public class DownloadService extends Worker {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
private static String sanitizeUserAgentValue(String value) {
|
|
113
|
-
|
|
113
|
+
if (value == null || value.isEmpty()) {
|
|
114
|
+
return "unknown";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
StringBuilder sanitized = new StringBuilder();
|
|
118
|
+
value
|
|
119
|
+
.codePoints()
|
|
120
|
+
.forEach((cp) -> {
|
|
121
|
+
boolean isVisibleAscii = cp >= 0x20 && cp <= 0x7E;
|
|
122
|
+
boolean isIso88591 = cp >= 0xA0 && cp <= 0xFF;
|
|
123
|
+
if (isVisibleAscii || isIso88591) {
|
|
124
|
+
sanitized.appendCodePoint(cp);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
String result = sanitized.toString().trim();
|
|
129
|
+
return result.isEmpty() ? "unknown" : result;
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
// Method to update User-Agent values
|
package/dist/docs.json
CHANGED
|
@@ -343,7 +343,7 @@
|
|
|
343
343
|
},
|
|
344
344
|
{
|
|
345
345
|
"name": "link",
|
|
346
|
-
"text": "ResetOptions} to control reset behavior
|
|
346
|
+
"text": "ResetOptions} to control reset behavior.\nIf `toLastSuccessful` is `false` (or omitted), resets to builtin.\nIf `true`, resets to last successful bundle.\nIf `usePendingBundle` is `true`, applies the pending bundle set via {@link next} and clears it."
|
|
347
347
|
},
|
|
348
348
|
{
|
|
349
349
|
"name": "returns",
|
|
@@ -1872,10 +1872,27 @@
|
|
|
1872
1872
|
"properties": [
|
|
1873
1873
|
{
|
|
1874
1874
|
"name": "toLastSuccessful",
|
|
1875
|
-
"tags": [
|
|
1876
|
-
|
|
1875
|
+
"tags": [
|
|
1876
|
+
{
|
|
1877
|
+
"text": "false",
|
|
1878
|
+
"name": "default"
|
|
1879
|
+
}
|
|
1880
|
+
],
|
|
1881
|
+
"docs": "Reset to the last successfully loaded bundle instead of the builtin one.",
|
|
1877
1882
|
"complexTypes": [],
|
|
1878
|
-
"type": "boolean"
|
|
1883
|
+
"type": "boolean | undefined"
|
|
1884
|
+
},
|
|
1885
|
+
{
|
|
1886
|
+
"name": "usePendingBundle",
|
|
1887
|
+
"tags": [
|
|
1888
|
+
{
|
|
1889
|
+
"text": "false",
|
|
1890
|
+
"name": "default"
|
|
1891
|
+
}
|
|
1892
|
+
],
|
|
1893
|
+
"docs": "Apply the pending bundle set via {@link next} while resetting.\n\nWhen `true`, the plugin will switch to the pending bundle immediately and clear the pending flag.\nIf no pending bundle exists, the reset will fail.",
|
|
1894
|
+
"complexTypes": [],
|
|
1895
|
+
"type": "boolean | undefined"
|
|
1879
1896
|
}
|
|
1880
1897
|
]
|
|
1881
1898
|
},
|
|
@@ -568,7 +568,10 @@ export interface CapacitorUpdaterPlugin {
|
|
|
568
568
|
* - Testing rollback functionality
|
|
569
569
|
* - Providing users a "reset to factory" option
|
|
570
570
|
*
|
|
571
|
-
* @param options {@link ResetOptions} to control reset behavior.
|
|
571
|
+
* @param options {@link ResetOptions} to control reset behavior.
|
|
572
|
+
* If `toLastSuccessful` is `false` (or omitted), resets to builtin.
|
|
573
|
+
* If `true`, resets to last successful bundle.
|
|
574
|
+
* If `usePendingBundle` is `true`, applies the pending bundle set via {@link next} and clears it.
|
|
572
575
|
* @returns {Promise<void>} A promise that may never resolve because the app will be reloaded.
|
|
573
576
|
* @throws {Error} If the reset operation fails.
|
|
574
577
|
*/
|
|
@@ -1680,7 +1683,19 @@ export interface BundleListResult {
|
|
|
1680
1683
|
bundles: BundleInfo[];
|
|
1681
1684
|
}
|
|
1682
1685
|
export interface ResetOptions {
|
|
1683
|
-
|
|
1686
|
+
/**
|
|
1687
|
+
* Reset to the last successfully loaded bundle instead of the builtin one.
|
|
1688
|
+
* @default false
|
|
1689
|
+
*/
|
|
1690
|
+
toLastSuccessful?: boolean;
|
|
1691
|
+
/**
|
|
1692
|
+
* Apply the pending bundle set via {@link next} while resetting.
|
|
1693
|
+
*
|
|
1694
|
+
* When `true`, the plugin will switch to the pending bundle immediately and clear the pending flag.
|
|
1695
|
+
* If no pending bundle exists, the reset will fail.
|
|
1696
|
+
* @default false
|
|
1697
|
+
*/
|
|
1698
|
+
usePendingBundle?: boolean;
|
|
1684
1699
|
}
|
|
1685
1700
|
export interface ListOptions {
|
|
1686
1701
|
/**
|