@capgo/capacitor-updater 8.50.2 → 8.51.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.
- package/README.md +44 -42
- package/android/build.gradle +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +7 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +1 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +251 -93
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +450 -69
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +19 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +90 -116
- package/dist/docs.json +13 -5
- package/dist/esm/definitions.d.ts +15 -10
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +5 -1
- package/ios/Sources/CapacitorUpdaterPlugin/BundleStatus.swift +3 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +167 -49
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +188 -34
- package/ios/Sources/CapacitorUpdaterPlugin/WebViewStatsReporter.swift +28 -0
- package/package.json +6 -2
|
@@ -68,6 +68,7 @@ import java.util.Set;
|
|
|
68
68
|
import java.util.TimeZone;
|
|
69
69
|
import java.util.Timer;
|
|
70
70
|
import java.util.TimerTask;
|
|
71
|
+
import java.util.concurrent.CountDownLatch;
|
|
71
72
|
import java.util.concurrent.Phaser;
|
|
72
73
|
import java.util.concurrent.Semaphore;
|
|
73
74
|
import java.util.concurrent.TimeUnit;
|
|
@@ -142,7 +143,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
142
143
|
static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
|
|
143
144
|
static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
|
|
144
145
|
|
|
145
|
-
private final String pluginVersion = "8.
|
|
146
|
+
private final String pluginVersion = "8.51.1";
|
|
146
147
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
147
148
|
|
|
148
149
|
private SharedPreferences.Editor editor;
|
|
@@ -215,6 +216,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
215
216
|
|
|
216
217
|
private volatile Thread backgroundDownloadTask;
|
|
217
218
|
private volatile Thread appReadyCheck;
|
|
219
|
+
// When true, sendReadyToJs should wait for notifyAppReady before hiding splash.
|
|
220
|
+
private volatile boolean pendingNotifyAppReadyWait = false;
|
|
221
|
+
private volatile int pendingNotifyAppReadyPhase = -1;
|
|
218
222
|
private volatile long downloadStartTimeMs = 0;
|
|
219
223
|
private static final long DOWNLOAD_TIMEOUT_MS = 600000; // 10 minute timeout
|
|
220
224
|
|
|
@@ -227,6 +231,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
227
231
|
|
|
228
232
|
// Lock to ensure cleanup completes before downloads start
|
|
229
233
|
private final Object cleanupLock = new Object();
|
|
234
|
+
private volatile CountDownLatch cleanupLatch = new CountDownLatch(0);
|
|
230
235
|
private volatile boolean cleanupComplete = false;
|
|
231
236
|
private volatile Thread cleanupThread = null;
|
|
232
237
|
|
|
@@ -236,6 +241,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
236
241
|
|
|
237
242
|
private ShakeMenu shakeMenu;
|
|
238
243
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
244
|
+
private final long launchStartedAtMs = System.currentTimeMillis();
|
|
245
|
+
private volatile long webViewPageStartedAtMs = 0;
|
|
246
|
+
private volatile boolean launchStartReported = false;
|
|
247
|
+
private volatile boolean launchReadyReported = false;
|
|
239
248
|
private FrameLayout splashscreenLoaderOverlay;
|
|
240
249
|
private Runnable splashscreenTimeoutRunnable;
|
|
241
250
|
private FrameLayout previewTransitionLoaderOverlay;
|
|
@@ -807,6 +816,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
807
816
|
}
|
|
808
817
|
logger.info("init for device " + this.implementation.deviceID);
|
|
809
818
|
logger.info("version native " + this.currentVersionNative.getOriginalString());
|
|
819
|
+
this.reportAppLaunchStart();
|
|
810
820
|
this.autoDeleteFailed = this.getConfig().getBoolean("autoDeleteFailed", true);
|
|
811
821
|
this.autoDeletePrevious = this.getConfig().getBoolean("autoDeletePrevious", true);
|
|
812
822
|
this.updateUrl = this.getConfig().getString("updateUrl", updateUrlDefault);
|
|
@@ -866,11 +876,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
866
876
|
this.reportPreviousAppExitReasons();
|
|
867
877
|
this.reportPreviousWebViewRenderProcessGone();
|
|
868
878
|
this.installWebViewStatsReporter();
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
879
|
+
// Downloads (including shake-menu / CapgoUpdater entry points) wait on this gate.
|
|
880
|
+
this.implementation.downloadGate = this::waitForCleanupIfNeeded;
|
|
881
|
+
// Always run async cleanup: delete obsolete bundles on native update (when enabled)
|
|
882
|
+
// and sweep orphan folders every launch. Must not block app startup.
|
|
883
|
+
if (!resetWhenUpdate) {
|
|
872
884
|
this.persistCurrentNativeBuildVersion();
|
|
873
885
|
}
|
|
886
|
+
this.cleanupObsoleteVersions(resetWhenUpdate);
|
|
874
887
|
|
|
875
888
|
// Check for 'kill' delay condition on app launch
|
|
876
889
|
// This handles cases where the app was killed by the system (onDestroy is not reliable)
|
|
@@ -904,6 +917,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
904
917
|
} else {
|
|
905
918
|
logger.info("Using activity lifecycle callbacks for foreground/background detection (Android <14)");
|
|
906
919
|
}
|
|
920
|
+
|
|
921
|
+
// Expect notifyAppReady before the first appReady/splash hide (same idea as iOS).
|
|
922
|
+
this.armPendingNotifyAppReadyWait();
|
|
907
923
|
}
|
|
908
924
|
|
|
909
925
|
private boolean semaphoreWait(final int phase, Number waitTime) {
|
|
@@ -946,6 +962,32 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
946
962
|
semaphoreReady.arriveAndDeregister();
|
|
947
963
|
}
|
|
948
964
|
|
|
965
|
+
private void armPendingNotifyAppReadyWait() {
|
|
966
|
+
this.clearPendingNotifyAppReadyWait();
|
|
967
|
+
this.pendingNotifyAppReadyPhase = this.semaphoreUp();
|
|
968
|
+
this.pendingNotifyAppReadyWait = true;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
private void clearPendingNotifyAppReadyWait() {
|
|
972
|
+
if (!this.pendingNotifyAppReadyWait) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
final int phase = this.pendingNotifyAppReadyPhase;
|
|
976
|
+
this.pendingNotifyAppReadyWait = false;
|
|
977
|
+
this.pendingNotifyAppReadyPhase = -1;
|
|
978
|
+
this.cleanupTimedOutSemaphoreWait(phase);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
private boolean consumePendingNotifyAppReadyWait(final int[] phaseOut) {
|
|
982
|
+
if (!this.pendingNotifyAppReadyWait) {
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
phaseOut[0] = this.pendingNotifyAppReadyPhase;
|
|
986
|
+
this.pendingNotifyAppReadyWait = false;
|
|
987
|
+
this.pendingNotifyAppReadyPhase = -1;
|
|
988
|
+
return true;
|
|
989
|
+
}
|
|
990
|
+
|
|
949
991
|
protected long getMinimumPendingBundleAppReadyTimeoutMs() {
|
|
950
992
|
return PENDING_BUNDLE_APP_READY_MIN_TIMEOUT_MS;
|
|
951
993
|
}
|
|
@@ -984,19 +1026,40 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
984
1026
|
|
|
985
1027
|
private void sendReadyToJs(final BundleInfo current, final String msg, final boolean isDirectUpdate) {
|
|
986
1028
|
logger.info("sendReadyToJs: " + msg);
|
|
987
|
-
final
|
|
988
|
-
|
|
989
|
-
ret.put("status", msg);
|
|
1029
|
+
final int[] pendingPhase = new int[] { -1 };
|
|
1030
|
+
final boolean shouldWait = this.consumePendingNotifyAppReadyWait(pendingPhase);
|
|
990
1031
|
|
|
991
|
-
|
|
992
|
-
|
|
1032
|
+
final Runnable emitReady = () -> {
|
|
1033
|
+
final JSObject ret = new JSObject();
|
|
1034
|
+
ret.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
|
|
1035
|
+
ret.put("status", msg);
|
|
993
1036
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1037
|
+
this.notifyListeners("appReady", ret, true);
|
|
1038
|
+
|
|
1039
|
+
// Auto hide splashscreen if enabled
|
|
1040
|
+
// We show it on background when conditions are met, so we should hide it on foreground regardless of update outcome
|
|
1041
|
+
if (this.autoSplashscreen) {
|
|
1042
|
+
this.hideSplashscreen();
|
|
1043
|
+
}
|
|
1044
|
+
this.hidePreviewTransitionLoader("app-ready");
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
if (!shouldWait) {
|
|
1048
|
+
emitReady.run();
|
|
1049
|
+
return;
|
|
998
1050
|
}
|
|
999
|
-
|
|
1051
|
+
|
|
1052
|
+
// Never block the UI thread waiting for notifyAppReady (JS needs it).
|
|
1053
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
1054
|
+
startNewThread(() -> {
|
|
1055
|
+
this.semaphoreWait(pendingPhase[0], this.appReadyTimeout);
|
|
1056
|
+
emitReady.run();
|
|
1057
|
+
});
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
this.semaphoreWait(pendingPhase[0], this.appReadyTimeout);
|
|
1062
|
+
emitReady.run();
|
|
1000
1063
|
}
|
|
1001
1064
|
|
|
1002
1065
|
private void hideSplashscreen() {
|
|
@@ -1486,26 +1549,39 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1486
1549
|
this.webViewStatsListener = new WebViewListener() {
|
|
1487
1550
|
@Override
|
|
1488
1551
|
public void onPageStarted(final android.webkit.WebView view) {
|
|
1552
|
+
CapacitorUpdaterPlugin.this.webViewPageStartedAtMs = System.currentTimeMillis();
|
|
1489
1553
|
CapacitorUpdaterPlugin.this.evaluateWebViewStatsReporterScript(view, script);
|
|
1490
1554
|
}
|
|
1491
1555
|
|
|
1492
1556
|
@Override
|
|
1493
1557
|
public void onPageLoaded(final android.webkit.WebView view) {
|
|
1558
|
+
CapacitorUpdaterPlugin.this.reportWebViewPageLoaded(view);
|
|
1494
1559
|
CapacitorUpdaterPlugin.this.evaluateWebViewStatsReporterScript(view, script);
|
|
1495
1560
|
}
|
|
1496
|
-
|
|
1497
|
-
@Override
|
|
1498
|
-
public boolean onRenderProcessGone(final android.webkit.WebView view, final RenderProcessGoneDetail detail) {
|
|
1499
|
-
final Map<String, String> metadata = CapacitorUpdaterPlugin.this.buildWebViewRenderProcessGoneMetadata(detail);
|
|
1500
|
-
CapacitorUpdaterPlugin.this.persistPendingWebViewRenderProcessGone(metadata);
|
|
1501
|
-
return false;
|
|
1502
|
-
}
|
|
1503
1561
|
};
|
|
1504
1562
|
|
|
1505
1563
|
this.bridge.addWebViewListener(this.webViewStatsListener);
|
|
1564
|
+
// Keep RenderProcessGoneDetail off the Plugin class method table and off this
|
|
1565
|
+
// listener so Android < 8 (API 26) does not crash during plugin reflection.
|
|
1566
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1567
|
+
this.installWebViewRenderProcessGoneReporter();
|
|
1568
|
+
}
|
|
1506
1569
|
this.evaluateWebViewStatsReporterScript(webView, script);
|
|
1507
1570
|
}
|
|
1508
1571
|
|
|
1572
|
+
private void installWebViewRenderProcessGoneReporter() {
|
|
1573
|
+
this.bridge.addWebViewListener(
|
|
1574
|
+
new WebViewListener() {
|
|
1575
|
+
@Override
|
|
1576
|
+
public boolean onRenderProcessGone(final android.webkit.WebView view, final RenderProcessGoneDetail detail) {
|
|
1577
|
+
final Map<String, String> metadata = CapacitorUpdaterPlugin.this.buildWebViewRenderProcessGoneMetadata(detail);
|
|
1578
|
+
CapacitorUpdaterPlugin.this.persistPendingWebViewRenderProcessGone(metadata);
|
|
1579
|
+
return false;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1509
1585
|
private void installDocumentStartWebViewStatsReporter(final android.webkit.WebView webView, final String script) {
|
|
1510
1586
|
try {
|
|
1511
1587
|
final Class<?> webViewFeature = Class.forName("androidx.webkit.WebViewFeature");
|
|
@@ -1545,16 +1621,84 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1545
1621
|
});
|
|
1546
1622
|
}
|
|
1547
1623
|
|
|
1548
|
-
private
|
|
1624
|
+
private void reportAppLaunchStart() {
|
|
1625
|
+
if (
|
|
1626
|
+
this.implementation == null ||
|
|
1627
|
+
this.implementation.statsUrl == null ||
|
|
1628
|
+
this.implementation.statsUrl.isEmpty() ||
|
|
1629
|
+
this.launchStartReported
|
|
1630
|
+
) {
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
this.launchStartReported = true;
|
|
1635
|
+
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
1636
|
+
final Map<String, String> metadata = new HashMap<>();
|
|
1637
|
+
metadata.put("launch_started_at", Long.toString(this.launchStartedAtMs));
|
|
1638
|
+
metadata.put("source", "plugin_load");
|
|
1639
|
+
this.implementation.sendStats("app_launch_start", current == null ? "" : current.getVersionName(), "", metadata);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
private void reportAppLaunchReady(final BundleInfo bundle) {
|
|
1643
|
+
if (
|
|
1644
|
+
this.implementation == null ||
|
|
1645
|
+
this.implementation.statsUrl == null ||
|
|
1646
|
+
this.implementation.statsUrl.isEmpty() ||
|
|
1647
|
+
this.launchReadyReported
|
|
1648
|
+
) {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
this.launchReadyReported = true;
|
|
1653
|
+
final Map<String, String> metadata = new HashMap<>();
|
|
1654
|
+
metadata.put("duration_ms", Long.toString(Math.max(0, System.currentTimeMillis() - this.launchStartedAtMs)));
|
|
1655
|
+
metadata.put("launch_started_at", Long.toString(this.launchStartedAtMs));
|
|
1656
|
+
metadata.put("source", "notify_app_ready");
|
|
1657
|
+
this.implementation.sendStats("app_launch_ready", bundle == null ? "" : bundle.getVersionName(), "", metadata);
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
private void reportAppLaunchTimeout(final BundleInfo bundle) {
|
|
1661
|
+
if (this.implementation == null || this.implementation.statsUrl == null || this.implementation.statsUrl.isEmpty()) {
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
final Map<String, String> metadata = new HashMap<>();
|
|
1666
|
+
metadata.put("duration_ms", Long.toString(Math.max(0, System.currentTimeMillis() - this.launchStartedAtMs)));
|
|
1667
|
+
metadata.put("launch_started_at", Long.toString(this.launchStartedAtMs));
|
|
1668
|
+
metadata.put("timeout_ms", Long.toString(this.appReadyTimeout));
|
|
1669
|
+
metadata.put("source", "app_ready_timeout");
|
|
1670
|
+
this.implementation.sendStats("app_launch_timeout", bundle == null ? "" : bundle.getVersionName(), "", metadata);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
private void reportWebViewPageLoaded(final android.webkit.WebView view) {
|
|
1674
|
+
if (this.implementation == null || this.implementation.statsUrl == null || this.implementation.statsUrl.isEmpty()) {
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
final Map<String, String> metadata = new HashMap<>();
|
|
1679
|
+
metadata.put("source", "android_webview_listener");
|
|
1680
|
+
final long pageStartedAt = this.webViewPageStartedAtMs;
|
|
1681
|
+
if (pageStartedAt > 0) {
|
|
1682
|
+
metadata.put("duration_ms", Long.toString(Math.max(0, System.currentTimeMillis() - pageStartedAt)));
|
|
1683
|
+
metadata.put("page_started_at", Long.toString(pageStartedAt));
|
|
1684
|
+
}
|
|
1685
|
+
if (view != null && view.getUrl() != null && !view.getUrl().isEmpty()) {
|
|
1686
|
+
metadata.put("href", truncateStatsMetadataValue(sanitizeStatsMetadataUrl(view.getUrl()), 512));
|
|
1687
|
+
}
|
|
1688
|
+
this.reportWebViewStats("webview_page_loaded", metadata);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
private Map<String, String> buildWebViewRenderProcessGoneMetadata(final Object detailObj) {
|
|
1549
1692
|
final Map<String, String> metadata = new HashMap<>();
|
|
1550
1693
|
metadata.put("error_type", "render_process_gone");
|
|
1551
1694
|
metadata.put("source", "android_on_render_process_gone");
|
|
1552
1695
|
metadata.put("timestamp", Long.toString(System.currentTimeMillis()));
|
|
1553
|
-
|
|
1696
|
+
// Parameter typed as Object so Android < 8 ART does not resolve
|
|
1697
|
+
// RenderProcessGoneDetail while reflecting CapacitorUpdaterPlugin methods.
|
|
1698
|
+
if (detailObj != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1699
|
+
final RenderProcessGoneDetail detail = (RenderProcessGoneDetail) detailObj;
|
|
1554
1700
|
metadata.put("did_crash", Boolean.toString(detail.didCrash()));
|
|
1555
|
-
|
|
1556
|
-
metadata.put("renderer_priority_at_exit", Integer.toString(detail.rendererPriorityAtExit()));
|
|
1557
|
-
}
|
|
1701
|
+
metadata.put("renderer_priority_at_exit", Integer.toString(detail.rendererPriorityAtExit()));
|
|
1558
1702
|
}
|
|
1559
1703
|
return metadata;
|
|
1560
1704
|
}
|
|
@@ -1652,6 +1796,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1652
1796
|
return "webview_render_process_gone";
|
|
1653
1797
|
case "web_content_process_terminated":
|
|
1654
1798
|
return "webview_content_process_terminated";
|
|
1799
|
+
case "webview_dom_content_loaded":
|
|
1800
|
+
return "webview_dom_content_loaded";
|
|
1801
|
+
case "webview_page_loaded":
|
|
1802
|
+
return "webview_page_loaded";
|
|
1655
1803
|
case "javascript_error":
|
|
1656
1804
|
default:
|
|
1657
1805
|
return "webview_javascript_error";
|
|
@@ -1670,6 +1818,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1670
1818
|
putStatsMetadataValue(metadata, "href", sanitizeStatsMetadataUrl(data.optString("href", "")), 512);
|
|
1671
1819
|
putStatsMetadataValue(metadata, "user_agent", data.optString("user_agent", ""), 256);
|
|
1672
1820
|
putStatsMetadataValue(metadata, "session_id", data.optString("session_id", ""), 128);
|
|
1821
|
+
putStatsMetadataValue(metadata, "duration_ms", data.optString("duration_ms", ""), 32);
|
|
1822
|
+
putStatsMetadataValue(metadata, "page_started_at", data.optString("page_started_at", ""), 64);
|
|
1673
1823
|
putStatsMetadataValue(metadata, "previous_session_id", data.optString("previous_session_id", ""), 128);
|
|
1674
1824
|
putStatsMetadataValue(metadata, "previous_href", sanitizeStatsMetadataUrl(data.optString("previous_href", "")), 512);
|
|
1675
1825
|
putStatsMetadataValue(metadata, "previous_started_at", data.optString("previous_started_at", ""), 64);
|
|
@@ -1802,12 +1952,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1802
1952
|
"if(previous&&previous.active){send({type:'webview_unclean_restart',message:'WebView restarted without a clean page unload',previous_session_id:s(previous.id),previous_href:s(previous.href),previous_started_at:s(previous.started_at),previous_updated_at:s(previous.updated_at)});}" +
|
|
1803
1953
|
"writeSession(true);" +
|
|
1804
1954
|
"setInterval(function(){writeSession(true);},15000);" +
|
|
1955
|
+
"function pageDuration(){var started=Number(window.__capgoWebViewSessionStartedAt||Date.now());return String(Math.max(0,Date.now()-started));}" +
|
|
1805
1956
|
"function markClean(){writeSession(false);}" +
|
|
1806
1957
|
"window.addEventListener('pagehide',markClean,true);" +
|
|
1807
1958
|
"window.addEventListener('beforeunload',markClean,true);" +
|
|
1808
1959
|
"window.addEventListener('error',function(event){var target=event&&event.target;if(target&&target!==window&&(target.src||target.href)){send({type:'resource_error',message:'Resource failed to load',source:s(target.src||target.href),tag_name:s(target.tagName)});return;}send({type:'javascript_error',message:s((event&&event.message)||(event&&event.error)),source:s(event&&event.filename),line:s(event&&event.lineno),column:s(event&&event.colno),stack:stack(event&&event.error)});},true);" +
|
|
1809
1960
|
"window.addEventListener('unhandledrejection',function(event){var reason=event&&event.reason;send({type:'unhandled_rejection',message:s(reason),stack:stack(reason)});},true);" +
|
|
1810
1961
|
"document.addEventListener('securitypolicyviolation',function(event){send({type:'security_policy_violation',message:s(event&&event.violatedDirective),source:s(event&&event.blockedURI)});},true);" +
|
|
1962
|
+
"document.addEventListener('DOMContentLoaded',function(){send({type:'webview_dom_content_loaded',message:'WebView DOM content loaded',duration_ms:pageDuration(),page_started_at:String(window.__capgoWebViewSessionStartedAt)});},true);" +
|
|
1811
1963
|
"document.addEventListener('deviceready',scheduleFlush,false);" +
|
|
1812
1964
|
"setTimeout(scheduleFlush,0);" +
|
|
1813
1965
|
"})();"
|
|
@@ -2128,67 +2280,58 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2128
2280
|
return false;
|
|
2129
2281
|
}
|
|
2130
2282
|
|
|
2131
|
-
private void cleanupObsoleteVersions() {
|
|
2283
|
+
private void cleanupObsoleteVersions(final boolean resetWhenUpdate) {
|
|
2284
|
+
// Latch created before start so waiters never race past an unstarted cleanup thread.
|
|
2285
|
+
final CountDownLatch latch = new CountDownLatch(1);
|
|
2286
|
+
this.cleanupComplete = false;
|
|
2287
|
+
this.cleanupLatch = latch;
|
|
2132
2288
|
cleanupThread = startNewThread(() -> {
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
for (final BundleInfo info : storedBundles) {
|
|
2156
|
-
if (info != null && info.getId() != null && !info.getId().isEmpty()) {
|
|
2157
|
-
allowedIds.add(info.getId());
|
|
2289
|
+
try {
|
|
2290
|
+
synchronized (cleanupLock) {
|
|
2291
|
+
try {
|
|
2292
|
+
final String previous = this.getStoredNativeBuildVersion();
|
|
2293
|
+
final boolean nativeVersionChanged = !"".equals(previous) && !Objects.equals(this.currentBuildVersion, previous);
|
|
2294
|
+
if (resetWhenUpdate && nativeVersionChanged) {
|
|
2295
|
+
logger.info("New native build version detected: " + this.currentBuildVersion);
|
|
2296
|
+
this.implementation.reset(true);
|
|
2297
|
+
final List<BundleInfo> installed = this.implementation.list(false);
|
|
2298
|
+
for (final BundleInfo bundle : installed) {
|
|
2299
|
+
try {
|
|
2300
|
+
logger.info("Deleting obsolete bundle: " + bundle.getId());
|
|
2301
|
+
this.implementation.delete(bundle.getId());
|
|
2302
|
+
} catch (final Exception e) {
|
|
2303
|
+
logger.error("Failed to delete: " + bundle.getId() + " " + e.getMessage());
|
|
2304
|
+
}
|
|
2305
|
+
try {
|
|
2306
|
+
Thread.sleep(75L);
|
|
2307
|
+
} catch (final InterruptedException ie) {
|
|
2308
|
+
Thread.currentThread().interrupt();
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2158
2311
|
}
|
|
2312
|
+
this.implementation.cleanupDeltaCache();
|
|
2159
2313
|
}
|
|
2160
|
-
this.implementation.cleanupDownloadDirectories(allowedIds, Thread.currentThread());
|
|
2161
|
-
this.implementation.cleanupOrphanedTempFolders(Thread.currentThread());
|
|
2162
2314
|
|
|
2163
|
-
//
|
|
2164
|
-
|
|
2165
|
-
logger.warn("Cleanup was cancelled before delta cache cleanup");
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
|
-
this.implementation.cleanupDeltaCache(Thread.currentThread());
|
|
2169
|
-
}
|
|
2170
|
-
this.editor.putString("LatestNativeBuildVersion", this.currentBuildVersion);
|
|
2171
|
-
this.editor.apply();
|
|
2172
|
-
} catch (Exception e) {
|
|
2173
|
-
logger.error("Error during cleanupObsoleteVersions: " + e.getMessage());
|
|
2174
|
-
} finally {
|
|
2175
|
-
cleanupComplete = true;
|
|
2176
|
-
logger.info("Cleanup complete");
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
});
|
|
2315
|
+
// Resume any DELETING leftovers from prior kills, one-by-one.
|
|
2316
|
+
this.implementation.drainPendingDeletes();
|
|
2180
2317
|
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2318
|
+
// Always sweep orphan folders so incomplete prior cleanups (or failed deletes)
|
|
2319
|
+
// cannot leave hundreds of MB behind across launches.
|
|
2320
|
+
final Set<String> allowedIds = this.implementation.allowedBundleIdsForCleanup();
|
|
2321
|
+
this.implementation.cleanupDownloadDirectories(allowedIds);
|
|
2322
|
+
this.implementation.cleanupOrphanedTempFolders(null);
|
|
2323
|
+
|
|
2324
|
+
this.editor.putString("LatestNativeBuildVersion", this.currentBuildVersion);
|
|
2325
|
+
this.editor.apply();
|
|
2326
|
+
} catch (Exception e) {
|
|
2327
|
+
logger.error("Error during cleanupObsoleteVersions: " + e.getMessage());
|
|
2328
|
+
} finally {
|
|
2329
|
+
cleanupComplete = true;
|
|
2330
|
+
logger.info("Cleanup complete");
|
|
2331
|
+
}
|
|
2189
2332
|
}
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2333
|
+
} finally {
|
|
2334
|
+
latch.countDown();
|
|
2192
2335
|
}
|
|
2193
2336
|
});
|
|
2194
2337
|
}
|
|
@@ -2212,11 +2355,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2212
2355
|
}
|
|
2213
2356
|
|
|
2214
2357
|
logger.info("Waiting for cleanup to complete before starting download...");
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2358
|
+
try {
|
|
2359
|
+
this.cleanupLatch.await();
|
|
2360
|
+
} catch (final InterruptedException e) {
|
|
2361
|
+
Thread.currentThread().interrupt();
|
|
2362
|
+
logger.warn("Interrupted while waiting for cleanup");
|
|
2363
|
+
throw new IllegalStateException("Interrupted while waiting for cleanup");
|
|
2219
2364
|
}
|
|
2365
|
+
logger.info("Cleanup finished, proceeding with download");
|
|
2220
2366
|
}
|
|
2221
2367
|
|
|
2222
2368
|
public void notifyDownload(final String id, final int percent) {
|
|
@@ -2554,6 +2700,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2554
2700
|
final String checksum,
|
|
2555
2701
|
final JSONArray manifest
|
|
2556
2702
|
) throws IOException {
|
|
2703
|
+
// Manual/preview downloads must wait too — launch orphan sweep can delete their temps.
|
|
2704
|
+
waitForCleanupIfNeeded();
|
|
2557
2705
|
if (manifest != null) {
|
|
2558
2706
|
return this.implementation.downloadManifest(url, version, sessionKey, checksum, manifest);
|
|
2559
2707
|
}
|
|
@@ -2727,6 +2875,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2727
2875
|
}
|
|
2728
2876
|
|
|
2729
2877
|
protected boolean _reload() {
|
|
2878
|
+
// Drop any launch pending wait; this reload owns notifyAppReady synchronization.
|
|
2879
|
+
this.clearPendingNotifyAppReadyWait();
|
|
2730
2880
|
final int phase = this.semaphoreUp();
|
|
2731
2881
|
this.applyCurrentBundleToBridge();
|
|
2732
2882
|
|
|
@@ -4187,6 +4337,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4187
4337
|
try {
|
|
4188
4338
|
final BundleInfo bundle = this.implementation.getCurrentBundle();
|
|
4189
4339
|
this.implementation.setSuccess(bundle, this.autoDeletePrevious);
|
|
4340
|
+
this.reportAppLaunchReady(bundle);
|
|
4190
4341
|
logger.info("Current bundle loaded successfully. ['notifyAppReady()' was called] " + bundle);
|
|
4191
4342
|
logger.info("semaphoreReady countDown");
|
|
4192
4343
|
this.semaphoreDown();
|
|
@@ -4861,19 +5012,26 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4861
5012
|
ret.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
|
|
4862
5013
|
this.persistLastFailedBundle(current);
|
|
4863
5014
|
this.notifyListeners("updateFailed", ret);
|
|
5015
|
+
this.reportAppLaunchTimeout(current);
|
|
4864
5016
|
this.implementation.sendStats("update_fail", current.getVersionName());
|
|
4865
5017
|
this.implementation.setError(current);
|
|
4866
5018
|
this.performReset(true, false, true);
|
|
4867
5019
|
if (CapacitorUpdaterPlugin.this.autoDeleteFailed && !current.isBuiltin()) {
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
5020
|
+
final String failedId = current.getId();
|
|
5021
|
+
final String failedVersion = current.getVersionName();
|
|
5022
|
+
logger.info("Deleting failing bundle: " + failedVersion);
|
|
5023
|
+
// Mark before async work so kill/OOM still resumes via drainPendingDeletes.
|
|
5024
|
+
CapacitorUpdaterPlugin.this.implementation.saveBundleInfo(failedId, current.setStatus(BundleStatus.DELETING));
|
|
5025
|
+
startNewThread(() -> {
|
|
5026
|
+
try {
|
|
5027
|
+
final Boolean res = CapacitorUpdaterPlugin.this.implementation.delete(failedId, false, false);
|
|
5028
|
+
if (Boolean.TRUE.equals(res)) {
|
|
5029
|
+
logger.info("Failed bundle deleted: " + failedVersion);
|
|
5030
|
+
}
|
|
5031
|
+
} catch (final IOException e) {
|
|
5032
|
+
logger.error("Failed to delete failed bundle: " + failedVersion + " " + e.getMessage());
|
|
4873
5033
|
}
|
|
4874
|
-
}
|
|
4875
|
-
logger.error("Failed to delete failed bundle: " + current.getVersionName() + " " + e.getMessage());
|
|
4876
|
-
}
|
|
5034
|
+
});
|
|
4877
5035
|
}
|
|
4878
5036
|
} else {
|
|
4879
5037
|
logger.info("notifyAppReady was called. This is fine: " + current.getId());
|