@capgo/capacitor-updater 8.45.11 → 8.46.0
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 +40 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +558 -12
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +45 -0
- package/dist/docs.json +50 -1
- package/dist/esm/definitions.d.ts +46 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -1
- package/dist/esm/web.js +4 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +4 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +4 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AppHealthTracker.swift +82 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +66 -8
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +9 -0
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +1 -0
- package/ios/Sources/CapacitorUpdaterPlugin/WebViewStatsReporter.swift +276 -0
- package/package.json +10 -1
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
package ee.forgr.capacitor_updater;
|
|
8
8
|
|
|
9
9
|
import android.app.Activity;
|
|
10
|
+
import android.app.ActivityManager;
|
|
11
|
+
import android.app.ApplicationExitInfo;
|
|
10
12
|
import android.content.Context;
|
|
11
13
|
import android.content.Intent;
|
|
12
14
|
import android.content.SharedPreferences;
|
|
@@ -20,6 +22,7 @@ import android.os.Looper;
|
|
|
20
22
|
import android.view.Gravity;
|
|
21
23
|
import android.view.View;
|
|
22
24
|
import android.view.ViewGroup;
|
|
25
|
+
import android.webkit.RenderProcessGoneDetail;
|
|
23
26
|
import android.widget.FrameLayout;
|
|
24
27
|
import android.widget.ProgressBar;
|
|
25
28
|
import androidx.core.content.pm.PackageInfoCompat;
|
|
@@ -32,6 +35,7 @@ import com.getcapacitor.PluginCall;
|
|
|
32
35
|
import com.getcapacitor.PluginHandle;
|
|
33
36
|
import com.getcapacitor.PluginMethod;
|
|
34
37
|
import com.getcapacitor.PluginResult;
|
|
38
|
+
import com.getcapacitor.WebViewListener;
|
|
35
39
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
36
40
|
import com.getcapacitor.plugin.WebView;
|
|
37
41
|
import com.google.android.gms.tasks.Task;
|
|
@@ -51,6 +55,7 @@ import java.net.MalformedURLException;
|
|
|
51
55
|
import java.net.URL;
|
|
52
56
|
import java.util.ArrayList;
|
|
53
57
|
import java.util.Date;
|
|
58
|
+
import java.util.HashMap;
|
|
54
59
|
import java.util.HashSet;
|
|
55
60
|
import java.util.List;
|
|
56
61
|
import java.util.Map;
|
|
@@ -84,12 +89,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
84
89
|
private static final String DEFAULT_CHANNEL_PREF_KEY = "CapacitorUpdater.defaultChannel";
|
|
85
90
|
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
86
91
|
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
92
|
+
private static final String LAST_REPORTED_APP_EXIT_TIMESTAMP_PREF_KEY = "CapacitorUpdater.lastReportedAppExitTimestamp";
|
|
93
|
+
private static final String LAST_WEBVIEW_RENDER_PROCESS_GONE_PREF_KEY = "CapacitorUpdater.lastWebViewRenderProcessGone";
|
|
87
94
|
private static final String SPLASH_SCREEN_PLUGIN_ID = "SplashScreen";
|
|
88
95
|
private static final int SPLASH_SCREEN_RETRY_DELAY_MS = 100;
|
|
89
96
|
private static final int SPLASH_SCREEN_MAX_RETRIES = 20;
|
|
90
97
|
private static final long PENDING_BUNDLE_APP_READY_MIN_TIMEOUT_MS = 30000L;
|
|
91
98
|
|
|
92
|
-
private final String pluginVersion = "8.
|
|
99
|
+
private final String pluginVersion = "8.46.0";
|
|
93
100
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
94
101
|
|
|
95
102
|
private SharedPreferences.Editor editor;
|
|
@@ -154,6 +161,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
154
161
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
155
162
|
private FrameLayout splashscreenLoaderOverlay;
|
|
156
163
|
private Runnable splashscreenTimeoutRunnable;
|
|
164
|
+
private WebViewListener webViewStatsListener;
|
|
157
165
|
|
|
158
166
|
private static final class FireAndForgetPluginCall extends PluginCall {
|
|
159
167
|
|
|
@@ -210,6 +218,26 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
220
|
|
|
221
|
+
private boolean shouldNotifyBreakingEvents(final JSObject response) {
|
|
222
|
+
if (response == null) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (response.optBoolean("breaking", false)) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
final String error = response.optString("error", "");
|
|
231
|
+
final String message = response.optString("message", "");
|
|
232
|
+
return "disable_auto_update_to_major".equals(error) || "store_update_required".equals(message);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private void notifyBreakingEventsIfNeeded(final JSObject response, final String version) {
|
|
236
|
+
if (shouldNotifyBreakingEvents(response)) {
|
|
237
|
+
notifyBreakingEvents(version);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
213
241
|
private void persistLastFailedBundle(BundleInfo bundle) {
|
|
214
242
|
if (this.prefs == null) {
|
|
215
243
|
return;
|
|
@@ -426,13 +454,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
426
454
|
this.implementation.defaultChannel = this.getConfig().getString("defaultChannel", "");
|
|
427
455
|
}
|
|
428
456
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (userValue >= 0 && userValue <= 600) {
|
|
432
|
-
this.periodCheckDelay = 600 * 1000;
|
|
433
|
-
} else if (userValue > 600) {
|
|
434
|
-
this.periodCheckDelay = userValue * 1000;
|
|
435
|
-
}
|
|
457
|
+
this.periodCheckDelay = normalizedPeriodCheckDelayMs(this.getConfig().getInt("periodCheckDelay", 0));
|
|
436
458
|
|
|
437
459
|
this.implementation.documentsDir = this.getContext().getFilesDir();
|
|
438
460
|
this.implementation.prefs = this.prefs;
|
|
@@ -482,9 +504,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
482
504
|
// Check if app was recently installed/updated BEFORE cleanupObsoleteVersions updates LatestVersionNative
|
|
483
505
|
this.wasRecentlyInstalledOrUpdated = this.checkIfRecentlyInstalledOrUpdated();
|
|
484
506
|
|
|
485
|
-
this.implementation.autoReset();
|
|
507
|
+
this.implementation.autoReset(this.currentBuildVersion, resetWhenUpdate);
|
|
508
|
+
this.reportPreviousAppExitReasons();
|
|
509
|
+
this.reportPreviousWebViewRenderProcessGone();
|
|
510
|
+
this.installWebViewStatsReporter();
|
|
486
511
|
if (resetWhenUpdate) {
|
|
487
512
|
this.cleanupObsoleteVersions();
|
|
513
|
+
} else {
|
|
514
|
+
this.persistCurrentNativeBuildVersion();
|
|
488
515
|
}
|
|
489
516
|
|
|
490
517
|
// Check for 'kill' delay condition on app launch
|
|
@@ -810,7 +837,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
810
837
|
|
|
811
838
|
private boolean checkIfRecentlyInstalledOrUpdated() {
|
|
812
839
|
String currentVersion = this.currentBuildVersion;
|
|
813
|
-
String lastKnownVersion = this.
|
|
840
|
+
String lastKnownVersion = this.getStoredNativeBuildVersion();
|
|
814
841
|
|
|
815
842
|
if (lastKnownVersion.isEmpty()) {
|
|
816
843
|
// First time running, consider it as recently installed
|
|
@@ -823,6 +850,456 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
823
850
|
return false;
|
|
824
851
|
}
|
|
825
852
|
|
|
853
|
+
private void reportPreviousAppExitReasons() {
|
|
854
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || this.implementation == null || this.implementation.statsUrl.isEmpty()) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
try {
|
|
859
|
+
final ActivityManager activityManager = (ActivityManager) this.getContext().getSystemService(Context.ACTIVITY_SERVICE);
|
|
860
|
+
if (activityManager == null) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
final List<ApplicationExitInfo> exitReasons = activityManager.getHistoricalProcessExitReasons(
|
|
865
|
+
this.getContext().getPackageName(),
|
|
866
|
+
0,
|
|
867
|
+
8
|
|
868
|
+
);
|
|
869
|
+
if (exitReasons == null || exitReasons.isEmpty()) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
final long lastReportedTimestamp = this.prefs.getLong(LAST_REPORTED_APP_EXIT_TIMESTAMP_PREF_KEY, 0L);
|
|
874
|
+
long newestReportedTimestamp = lastReportedTimestamp;
|
|
875
|
+
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
876
|
+
final String versionName = current == null ? "" : current.getVersionName();
|
|
877
|
+
|
|
878
|
+
for (final ApplicationExitInfo exitInfo : exitReasons) {
|
|
879
|
+
if (exitInfo == null || exitInfo.getTimestamp() <= lastReportedTimestamp) {
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
final String action = statsActionForApplicationExitReason(exitInfo.getReason());
|
|
884
|
+
if (action == null) {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
this.implementation.sendStats(action, versionName, "", buildApplicationExitMetadata(exitInfo));
|
|
889
|
+
newestReportedTimestamp = Math.max(newestReportedTimestamp, exitInfo.getTimestamp());
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (newestReportedTimestamp > lastReportedTimestamp) {
|
|
893
|
+
this.prefs.edit().putLong(LAST_REPORTED_APP_EXIT_TIMESTAMP_PREF_KEY, newestReportedTimestamp).apply();
|
|
894
|
+
}
|
|
895
|
+
} catch (final Exception e) {
|
|
896
|
+
logger.warn("Unable to report previous app exit reason: " + e.getMessage());
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
static String statsActionForApplicationExitReason(final int reason) {
|
|
901
|
+
switch (reason) {
|
|
902
|
+
case ApplicationExitInfo.REASON_CRASH:
|
|
903
|
+
return "app_crash";
|
|
904
|
+
case ApplicationExitInfo.REASON_CRASH_NATIVE:
|
|
905
|
+
return "app_crash_native";
|
|
906
|
+
case ApplicationExitInfo.REASON_ANR:
|
|
907
|
+
return "app_anr";
|
|
908
|
+
case ApplicationExitInfo.REASON_LOW_MEMORY:
|
|
909
|
+
return "app_killed_low_memory";
|
|
910
|
+
case ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE:
|
|
911
|
+
return "app_killed_excessive_resource_usage";
|
|
912
|
+
case ApplicationExitInfo.REASON_INITIALIZATION_FAILURE:
|
|
913
|
+
return "app_initialization_failure";
|
|
914
|
+
default:
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
static String applicationExitReasonName(final int reason) {
|
|
920
|
+
switch (reason) {
|
|
921
|
+
case ApplicationExitInfo.REASON_EXIT_SELF:
|
|
922
|
+
return "exit_self";
|
|
923
|
+
case ApplicationExitInfo.REASON_SIGNALED:
|
|
924
|
+
return "signaled";
|
|
925
|
+
case ApplicationExitInfo.REASON_LOW_MEMORY:
|
|
926
|
+
return "low_memory";
|
|
927
|
+
case ApplicationExitInfo.REASON_CRASH:
|
|
928
|
+
return "crash";
|
|
929
|
+
case ApplicationExitInfo.REASON_CRASH_NATIVE:
|
|
930
|
+
return "crash_native";
|
|
931
|
+
case ApplicationExitInfo.REASON_ANR:
|
|
932
|
+
return "anr";
|
|
933
|
+
case ApplicationExitInfo.REASON_INITIALIZATION_FAILURE:
|
|
934
|
+
return "initialization_failure";
|
|
935
|
+
case ApplicationExitInfo.REASON_PERMISSION_CHANGE:
|
|
936
|
+
return "permission_change";
|
|
937
|
+
case ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE:
|
|
938
|
+
return "excessive_resource_usage";
|
|
939
|
+
case ApplicationExitInfo.REASON_USER_REQUESTED:
|
|
940
|
+
return "user_requested";
|
|
941
|
+
case ApplicationExitInfo.REASON_DEPENDENCY_DIED:
|
|
942
|
+
return "dependency_died";
|
|
943
|
+
default:
|
|
944
|
+
return "unknown";
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
private static Map<String, String> buildApplicationExitMetadata(final ApplicationExitInfo exitInfo) {
|
|
949
|
+
final Map<String, String> metadata = new HashMap<>();
|
|
950
|
+
metadata.put("exit_reason", applicationExitReasonName(exitInfo.getReason()));
|
|
951
|
+
metadata.put("exit_reason_code", Integer.toString(exitInfo.getReason()));
|
|
952
|
+
metadata.put("exit_status", Integer.toString(exitInfo.getStatus()));
|
|
953
|
+
metadata.put("exit_importance", Integer.toString(exitInfo.getImportance()));
|
|
954
|
+
metadata.put("exit_timestamp", Long.toString(exitInfo.getTimestamp()));
|
|
955
|
+
metadata.put("pid", Integer.toString(exitInfo.getPid()));
|
|
956
|
+
metadata.put("pss_kb", Long.toString(exitInfo.getPss()));
|
|
957
|
+
metadata.put("rss_kb", Long.toString(exitInfo.getRss()));
|
|
958
|
+
|
|
959
|
+
final String processName = exitInfo.getProcessName();
|
|
960
|
+
if (processName != null && !processName.isEmpty()) {
|
|
961
|
+
metadata.put("process_name", truncateStatsMetadataValue(processName, 128));
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
final String description = exitInfo.getDescription();
|
|
965
|
+
if (description != null && !description.isEmpty()) {
|
|
966
|
+
metadata.put("exit_description", truncateStatsMetadataValue(description, 512));
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return metadata;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
private static String truncateStatsMetadataValue(final String value, final int maxLength) {
|
|
973
|
+
return value.length() <= maxLength ? value : value.substring(0, maxLength);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
private void installWebViewStatsReporter() {
|
|
977
|
+
if (this.bridge == null || this.bridge.getWebView() == null || this.webViewStatsListener != null) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
final android.webkit.WebView webView = this.bridge.getWebView();
|
|
982
|
+
final String script = buildWebViewStatsReporterScript();
|
|
983
|
+
this.installDocumentStartWebViewStatsReporter(webView, script);
|
|
984
|
+
|
|
985
|
+
this.webViewStatsListener = new WebViewListener() {
|
|
986
|
+
@Override
|
|
987
|
+
public void onPageStarted(final android.webkit.WebView view) {
|
|
988
|
+
CapacitorUpdaterPlugin.this.evaluateWebViewStatsReporterScript(view, script);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
@Override
|
|
992
|
+
public void onPageLoaded(final android.webkit.WebView view) {
|
|
993
|
+
CapacitorUpdaterPlugin.this.evaluateWebViewStatsReporterScript(view, script);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
@Override
|
|
997
|
+
public boolean onRenderProcessGone(final android.webkit.WebView view, final RenderProcessGoneDetail detail) {
|
|
998
|
+
final Map<String, String> metadata = CapacitorUpdaterPlugin.this.buildWebViewRenderProcessGoneMetadata(detail);
|
|
999
|
+
CapacitorUpdaterPlugin.this.persistPendingWebViewRenderProcessGone(metadata);
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
this.bridge.addWebViewListener(this.webViewStatsListener);
|
|
1005
|
+
this.evaluateWebViewStatsReporterScript(webView, script);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
private void installDocumentStartWebViewStatsReporter(final android.webkit.WebView webView, final String script) {
|
|
1009
|
+
try {
|
|
1010
|
+
final Class<?> webViewFeature = Class.forName("androidx.webkit.WebViewFeature");
|
|
1011
|
+
final String feature = (String) webViewFeature.getField("DOCUMENT_START_SCRIPT").get(null);
|
|
1012
|
+
final Boolean supported = (Boolean) webViewFeature.getMethod("isFeatureSupported", String.class).invoke(null, feature);
|
|
1013
|
+
if (!Boolean.TRUE.equals(supported)) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
final String allowedOrigin = Uri.parse(this.bridge.getAppUrl())
|
|
1018
|
+
.buildUpon()
|
|
1019
|
+
.path(null)
|
|
1020
|
+
.fragment(null)
|
|
1021
|
+
.clearQuery()
|
|
1022
|
+
.build()
|
|
1023
|
+
.toString();
|
|
1024
|
+
final Class<?> webViewCompat = Class.forName("androidx.webkit.WebViewCompat");
|
|
1025
|
+
webViewCompat
|
|
1026
|
+
.getMethod("addDocumentStartJavaScript", android.webkit.WebView.class, String.class, Set.class)
|
|
1027
|
+
.invoke(null, webView, script, java.util.Collections.singleton(allowedOrigin));
|
|
1028
|
+
} catch (final Exception e) {
|
|
1029
|
+
logger.debug("Unable to install document-start WebView stats reporter: " + e.getMessage());
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
private void evaluateWebViewStatsReporterScript(final android.webkit.WebView webView, final String script) {
|
|
1034
|
+
if (webView == null) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
this.mainHandler.post(() -> {
|
|
1039
|
+
try {
|
|
1040
|
+
webView.evaluateJavascript(script, null);
|
|
1041
|
+
} catch (final Exception e) {
|
|
1042
|
+
logger.debug("Unable to evaluate WebView stats reporter: " + e.getMessage());
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
private Map<String, String> buildWebViewRenderProcessGoneMetadata(final RenderProcessGoneDetail detail) {
|
|
1048
|
+
final Map<String, String> metadata = new HashMap<>();
|
|
1049
|
+
metadata.put("error_type", "render_process_gone");
|
|
1050
|
+
metadata.put("source", "android_on_render_process_gone");
|
|
1051
|
+
metadata.put("timestamp", Long.toString(System.currentTimeMillis()));
|
|
1052
|
+
if (detail != null) {
|
|
1053
|
+
metadata.put("did_crash", Boolean.toString(detail.didCrash()));
|
|
1054
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1055
|
+
metadata.put("renderer_priority_at_exit", Integer.toString(detail.rendererPriorityAtExit()));
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return metadata;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
private void persistPendingWebViewRenderProcessGone(final Map<String, String> metadata) {
|
|
1062
|
+
try {
|
|
1063
|
+
final JSONObject json = new JSONObject(metadata);
|
|
1064
|
+
this.prefs.edit().putString(LAST_WEBVIEW_RENDER_PROCESS_GONE_PREF_KEY, json.toString()).commit();
|
|
1065
|
+
} catch (final Exception e) {
|
|
1066
|
+
logger.debug("Unable to persist WebView render process crash metadata: " + e.getMessage());
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
private void reportPreviousWebViewRenderProcessGone() {
|
|
1071
|
+
if (this.implementation == null || this.implementation.statsUrl.isEmpty()) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
final String rawMetadata = this.prefs.getString(LAST_WEBVIEW_RENDER_PROCESS_GONE_PREF_KEY, "");
|
|
1076
|
+
if (rawMetadata == null || rawMetadata.isEmpty()) {
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
try {
|
|
1081
|
+
final Map<String, String> metadata = jsonObjectToStringMap(new JSONObject(rawMetadata));
|
|
1082
|
+
metadata.put("reported_after_restart", "true");
|
|
1083
|
+
this.reportWebViewStats("webview_render_process_gone", metadata);
|
|
1084
|
+
this.prefs.edit().remove(LAST_WEBVIEW_RENDER_PROCESS_GONE_PREF_KEY).apply();
|
|
1085
|
+
} catch (final JSONException e) {
|
|
1086
|
+
this.prefs.edit().remove(LAST_WEBVIEW_RENDER_PROCESS_GONE_PREF_KEY).apply();
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
private static Map<String, String> jsonObjectToStringMap(final JSONObject json) throws JSONException {
|
|
1091
|
+
final Map<String, String> map = new HashMap<>();
|
|
1092
|
+
final JSONArray names = json.names();
|
|
1093
|
+
if (names == null) {
|
|
1094
|
+
return map;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
for (int i = 0; i < names.length(); i++) {
|
|
1098
|
+
final String key = names.getString(i);
|
|
1099
|
+
final String value = json.optString(key, "");
|
|
1100
|
+
if (!value.isEmpty()) {
|
|
1101
|
+
map.put(key, value);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return map;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
@PluginMethod
|
|
1108
|
+
public void reportWebViewError(final PluginCall call) {
|
|
1109
|
+
final JSObject data = call.getData();
|
|
1110
|
+
this.reportWebViewStats(
|
|
1111
|
+
statsActionForWebViewErrorType(data.optString("type", "javascript_error")),
|
|
1112
|
+
buildWebViewErrorMetadata(data)
|
|
1113
|
+
);
|
|
1114
|
+
call.resolve();
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
private void reportWebViewStats(final String action, final Map<String, String> metadata) {
|
|
1118
|
+
if (this.implementation == null) {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
1123
|
+
final String versionName = current == null ? "" : current.getVersionName();
|
|
1124
|
+
this.implementation.sendStats(action, versionName, "", metadata);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
static String statsActionForWebViewErrorType(final String type) {
|
|
1128
|
+
switch (type) {
|
|
1129
|
+
case "unhandled_rejection":
|
|
1130
|
+
return "webview_unhandled_rejection";
|
|
1131
|
+
case "resource_error":
|
|
1132
|
+
return "webview_resource_error";
|
|
1133
|
+
case "security_policy_violation":
|
|
1134
|
+
return "webview_security_policy_violation";
|
|
1135
|
+
case "webview_unclean_restart":
|
|
1136
|
+
return "webview_unclean_restart";
|
|
1137
|
+
case "render_process_gone":
|
|
1138
|
+
return "webview_render_process_gone";
|
|
1139
|
+
case "web_content_process_terminated":
|
|
1140
|
+
return "webview_content_process_terminated";
|
|
1141
|
+
case "javascript_error":
|
|
1142
|
+
default:
|
|
1143
|
+
return "webview_javascript_error";
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
static Map<String, String> buildWebViewErrorMetadata(final JSObject data) {
|
|
1148
|
+
final Map<String, String> metadata = new HashMap<>();
|
|
1149
|
+
putStatsMetadataValue(metadata, "error_type", data.optString("type", "javascript_error"), 64);
|
|
1150
|
+
putStatsMetadataValue(metadata, "message", data.optString("message", ""), 1024);
|
|
1151
|
+
putStatsMetadataValue(metadata, "source", sanitizeStatsMetadataUrl(data.optString("source", "")), 512);
|
|
1152
|
+
putStatsMetadataValue(metadata, "line", data.optString("line", data.optString("lineno", "")), 32);
|
|
1153
|
+
putStatsMetadataValue(metadata, "column", data.optString("column", data.optString("colno", "")), 32);
|
|
1154
|
+
putStatsMetadataValue(metadata, "stack", data.optString("stack", ""), 2048);
|
|
1155
|
+
putStatsMetadataValue(metadata, "tag_name", data.optString("tag_name", ""), 64);
|
|
1156
|
+
putStatsMetadataValue(metadata, "href", sanitizeStatsMetadataUrl(data.optString("href", "")), 512);
|
|
1157
|
+
putStatsMetadataValue(metadata, "user_agent", data.optString("user_agent", ""), 256);
|
|
1158
|
+
putStatsMetadataValue(metadata, "session_id", data.optString("session_id", ""), 128);
|
|
1159
|
+
putStatsMetadataValue(metadata, "previous_session_id", data.optString("previous_session_id", ""), 128);
|
|
1160
|
+
putStatsMetadataValue(metadata, "previous_href", sanitizeStatsMetadataUrl(data.optString("previous_href", "")), 512);
|
|
1161
|
+
putStatsMetadataValue(metadata, "previous_started_at", data.optString("previous_started_at", ""), 64);
|
|
1162
|
+
putStatsMetadataValue(metadata, "previous_updated_at", data.optString("previous_updated_at", ""), 64);
|
|
1163
|
+
return metadata;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
private static void putStatsMetadataValue(
|
|
1167
|
+
final Map<String, String> metadata,
|
|
1168
|
+
final String key,
|
|
1169
|
+
final String value,
|
|
1170
|
+
final int maxLength
|
|
1171
|
+
) {
|
|
1172
|
+
if (value == null || value.isEmpty()) {
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
metadata.put(key, truncateStatsMetadataValue(value, maxLength));
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
static String sanitizeStatsMetadataUrl(final String value) {
|
|
1180
|
+
if (value == null || value.isEmpty()) {
|
|
1181
|
+
return "";
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
try {
|
|
1185
|
+
final java.net.URI uri = new java.net.URI(value);
|
|
1186
|
+
if (uri.getScheme() != null && uri.getHost() != null) {
|
|
1187
|
+
final String path = sanitizeStatsMetadataUrlPath(uri.getPath());
|
|
1188
|
+
return new java.net.URI(
|
|
1189
|
+
uri.getScheme(),
|
|
1190
|
+
null,
|
|
1191
|
+
uri.getHost(),
|
|
1192
|
+
uri.getPort(),
|
|
1193
|
+
path.isEmpty() ? null : path,
|
|
1194
|
+
null,
|
|
1195
|
+
null
|
|
1196
|
+
).toString();
|
|
1197
|
+
}
|
|
1198
|
+
} catch (Exception ignored) {}
|
|
1199
|
+
|
|
1200
|
+
try {
|
|
1201
|
+
final Uri uri = Uri.parse(value);
|
|
1202
|
+
if (uri.getScheme() != null && uri.getHost() != null) {
|
|
1203
|
+
final String host = stripUrlUserInfo(uri.getHost());
|
|
1204
|
+
if (host.isEmpty()) {
|
|
1205
|
+
return stripUrlQueryAndFragment(value);
|
|
1206
|
+
}
|
|
1207
|
+
final StringBuilder authority = new StringBuilder(host);
|
|
1208
|
+
if (uri.getPort() != -1) {
|
|
1209
|
+
authority.append(':').append(uri.getPort());
|
|
1210
|
+
}
|
|
1211
|
+
final Uri.Builder builder = new Uri.Builder().scheme(uri.getScheme()).authority(authority.toString());
|
|
1212
|
+
final String path = sanitizeStatsMetadataUrlPath(uri.getPath());
|
|
1213
|
+
if (!path.isEmpty()) {
|
|
1214
|
+
builder.path(path);
|
|
1215
|
+
}
|
|
1216
|
+
return builder.build().toString();
|
|
1217
|
+
}
|
|
1218
|
+
} catch (Exception ignored) {}
|
|
1219
|
+
|
|
1220
|
+
return stripUrlQueryAndFragment(value);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
private static String sanitizeStatsMetadataUrlPath(final String path) {
|
|
1224
|
+
if (path == null || path.isEmpty()) {
|
|
1225
|
+
return "";
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
final String[] segments = path.split("/", -1);
|
|
1229
|
+
for (int index = 0; index < segments.length; index++) {
|
|
1230
|
+
if (isSensitiveUrlPathSegment(segments[index])) {
|
|
1231
|
+
segments[index] = "redacted";
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return String.join("/", segments);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
private static String stripUrlUserInfo(final String host) {
|
|
1238
|
+
if (host == null || host.isEmpty()) {
|
|
1239
|
+
return "";
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
final int userInfoIndex = host.lastIndexOf('@');
|
|
1243
|
+
if (userInfoIndex < 0) {
|
|
1244
|
+
return host;
|
|
1245
|
+
}
|
|
1246
|
+
return host.substring(userInfoIndex + 1);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
private static boolean isSensitiveUrlPathSegment(final String segment) {
|
|
1250
|
+
return (
|
|
1251
|
+
segment.matches("[0-9]{6,}") ||
|
|
1252
|
+
segment.matches("[0-9a-fA-F]{16,}") ||
|
|
1253
|
+
segment.matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
private static String stripUrlQueryAndFragment(final String value) {
|
|
1258
|
+
int end = value.length();
|
|
1259
|
+
final int queryIndex = value.indexOf('?');
|
|
1260
|
+
final int fragmentIndex = value.indexOf('#');
|
|
1261
|
+
if (queryIndex >= 0) {
|
|
1262
|
+
end = Math.min(end, queryIndex);
|
|
1263
|
+
}
|
|
1264
|
+
if (fragmentIndex >= 0) {
|
|
1265
|
+
end = Math.min(end, fragmentIndex);
|
|
1266
|
+
}
|
|
1267
|
+
return value.substring(0, end);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
static String buildWebViewStatsReporterScript() {
|
|
1271
|
+
return (
|
|
1272
|
+
"(function(){" +
|
|
1273
|
+
"if(window.__capgoWebViewErrorReporterInstalled){return;}" +
|
|
1274
|
+
"window.__capgoWebViewErrorReporterInstalled=true;" +
|
|
1275
|
+
"var maxReports=20,sentReports=0,queue=[],seen={};" +
|
|
1276
|
+
"var sessionKey='CapacitorUpdater.webViewSession';" +
|
|
1277
|
+
"var sessionId=String(Date.now())+'-'+Math.random().toString(36).slice(2);" +
|
|
1278
|
+
"function s(value){try{if(value===undefined){return '';}if(value===null){return 'null';}if(typeof value==='string'){return value;}if(value&&typeof value.message==='string'){return value.message;}return String(value);}catch(_){return '';}}" +
|
|
1279
|
+
"function stack(value){try{return value&&value.stack?String(value.stack):'';}catch(_){return '';}}" +
|
|
1280
|
+
"function updater(){var cap=window.Capacitor;if(!cap||!cap.Plugins){return null;}return cap.Plugins.CapacitorUpdater||null;}" +
|
|
1281
|
+
"function flush(){var plugin=updater();if(!plugin||typeof plugin.reportWebViewError!=='function'){return false;}while(queue.length){var payload=queue.shift();try{var result=plugin.reportWebViewError(payload);if(result&&typeof result.catch==='function'){result.catch(function(){});}}catch(_){}}return true;}" +
|
|
1282
|
+
"var retries=0;function scheduleFlush(){if(flush()){return;}if(retries++<40){setTimeout(scheduleFlush,250);}}" +
|
|
1283
|
+
"function send(payload){try{if(sentReports>=maxReports){return;}payload.href=payload.href||location.href||'';payload.user_agent=navigator.userAgent||'';payload.session_id=sessionId;var key=[payload.type,payload.message,payload.source,payload.line,payload.column,payload.tag_name].join('|');if(seen[key]){return;}seen[key]=true;sentReports+=1;queue.push(payload);scheduleFlush();}catch(_){}}" +
|
|
1284
|
+
"function readSession(){try{return JSON.parse(localStorage.getItem(sessionKey)||'null')||null;}catch(_){return null;}}" +
|
|
1285
|
+
"function writeSession(active){try{localStorage.setItem(sessionKey,JSON.stringify({id:sessionId,active:active,href:location.href||'',started_at:window.__capgoWebViewSessionStartedAt,updated_at:String(Date.now())}));}catch(_){}}" +
|
|
1286
|
+
"window.__capgoWebViewSessionStartedAt=String(Date.now());" +
|
|
1287
|
+
"var previous=readSession();" +
|
|
1288
|
+
"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)});}" +
|
|
1289
|
+
"writeSession(true);" +
|
|
1290
|
+
"setInterval(function(){writeSession(true);},15000);" +
|
|
1291
|
+
"function markClean(){writeSession(false);}" +
|
|
1292
|
+
"window.addEventListener('pagehide',markClean,true);" +
|
|
1293
|
+
"window.addEventListener('beforeunload',markClean,true);" +
|
|
1294
|
+
"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);" +
|
|
1295
|
+
"window.addEventListener('unhandledrejection',function(event){var reason=event&&event.reason;send({type:'unhandled_rejection',message:s(reason),stack:stack(reason)});},true);" +
|
|
1296
|
+
"document.addEventListener('securitypolicyviolation',function(event){send({type:'security_policy_violation',message:s(event&&event.violatedDirective),source:s(event&&event.blockedURI)});},true);" +
|
|
1297
|
+
"document.addEventListener('deviceready',scheduleFlush,false);" +
|
|
1298
|
+
"setTimeout(scheduleFlush,0);" +
|
|
1299
|
+
"})();"
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
826
1303
|
private boolean shouldUseDirectUpdate() {
|
|
827
1304
|
if (Boolean.TRUE.equals(this.autoSplashscreenTimedOut)) {
|
|
828
1305
|
return false;
|
|
@@ -862,6 +1339,22 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
862
1339
|
return plannedDirectUpdate && "onLaunch".equals(directUpdateMode);
|
|
863
1340
|
}
|
|
864
1341
|
|
|
1342
|
+
static int normalizedPeriodCheckDelayMs(final int valueSeconds) {
|
|
1343
|
+
final int normalizedSeconds = normalizedPeriodCheckDelaySeconds(valueSeconds);
|
|
1344
|
+
if (normalizedSeconds <= 0) {
|
|
1345
|
+
return 0;
|
|
1346
|
+
}
|
|
1347
|
+
final long delayMs = (long) normalizedSeconds * 1000L;
|
|
1348
|
+
return delayMs > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) delayMs;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
static int normalizedPeriodCheckDelaySeconds(final int valueSeconds) {
|
|
1352
|
+
if (valueSeconds <= 0) {
|
|
1353
|
+
return 0;
|
|
1354
|
+
}
|
|
1355
|
+
return Math.max(600, valueSeconds);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
865
1358
|
private void consumeOnLaunchDirectUpdateAttempt(final boolean plannedDirectUpdate) {
|
|
866
1359
|
if (!shouldConsumeOnLaunchDirectUpdate(this.directUpdateMode, plannedDirectUpdate)) {
|
|
867
1360
|
return;
|
|
@@ -932,7 +1425,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
932
1425
|
cleanupThread = startNewThread(() -> {
|
|
933
1426
|
synchronized (cleanupLock) {
|
|
934
1427
|
try {
|
|
935
|
-
final String previous = this.
|
|
1428
|
+
final String previous = this.getStoredNativeBuildVersion();
|
|
936
1429
|
if (!"".equals(previous) && !Objects.equals(this.currentBuildVersion, previous)) {
|
|
937
1430
|
logger.info("New native build version detected: " + this.currentBuildVersion);
|
|
938
1431
|
this.implementation.reset(true);
|
|
@@ -993,6 +1486,19 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
993
1486
|
});
|
|
994
1487
|
}
|
|
995
1488
|
|
|
1489
|
+
String getStoredNativeBuildVersion() {
|
|
1490
|
+
String previous = this.prefs.getString("LatestNativeBuildVersion", "");
|
|
1491
|
+
if (previous == null || previous.isEmpty()) {
|
|
1492
|
+
previous = this.prefs.getString("LatestVersionNative", "");
|
|
1493
|
+
}
|
|
1494
|
+
return previous == null ? "" : previous;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
void persistCurrentNativeBuildVersion() {
|
|
1498
|
+
this.editor.putString("LatestNativeBuildVersion", this.currentBuildVersion);
|
|
1499
|
+
this.editor.apply();
|
|
1500
|
+
}
|
|
1501
|
+
|
|
996
1502
|
private void waitForCleanupIfNeeded() {
|
|
997
1503
|
if (cleanupComplete) {
|
|
998
1504
|
return; // Already done, no need to wait
|
|
@@ -1707,7 +2213,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1707
2213
|
String error = jsRes.has("error") ? jsRes.getString("error") : "";
|
|
1708
2214
|
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
|
|
1709
2215
|
String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(jsRes.has("kind") ? jsRes.getString("kind") : null);
|
|
2216
|
+
String latestVersion = jsRes.has("version") ? jsRes.getString("version") : "";
|
|
1710
2217
|
jsRes.put("kind", kind);
|
|
2218
|
+
CapacitorUpdaterPlugin.this.notifyBreakingEventsIfNeeded(jsRes, latestVersion);
|
|
1711
2219
|
if ("failed".equals(kind)) {
|
|
1712
2220
|
logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
|
|
1713
2221
|
call.reject(error.isEmpty() ? errorMessage : error);
|
|
@@ -1720,6 +2228,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1720
2228
|
}
|
|
1721
2229
|
return;
|
|
1722
2230
|
} else if (jsRes.has("message")) {
|
|
2231
|
+
String latestVersion = jsRes.has("version") ? jsRes.getString("version") : "";
|
|
2232
|
+
CapacitorUpdaterPlugin.this.notifyBreakingEventsIfNeeded(jsRes, latestVersion);
|
|
1723
2233
|
call.reject(jsRes.getString("message"));
|
|
1724
2234
|
return;
|
|
1725
2235
|
} else {
|
|
@@ -1729,6 +2239,28 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1729
2239
|
);
|
|
1730
2240
|
}
|
|
1731
2241
|
|
|
2242
|
+
public String triggerBackgroundUpdateCheck() {
|
|
2243
|
+
if (this.updateUrl == null || this.updateUrl.isEmpty() || !this.isValidURL(this.updateUrl)) {
|
|
2244
|
+
logger.error("Error no url or wrong format");
|
|
2245
|
+
return "unavailable";
|
|
2246
|
+
}
|
|
2247
|
+
if (this.isDownloadStuckOrTimedOut()) {
|
|
2248
|
+
logger.info("Download already in progress, skipping duplicate download request");
|
|
2249
|
+
return "already_running";
|
|
2250
|
+
}
|
|
2251
|
+
this.backgroundDownload();
|
|
2252
|
+
return "queued";
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
@PluginMethod
|
|
2256
|
+
public void triggerUpdateCheck(final PluginCall call) {
|
|
2257
|
+
final String status = this.triggerBackgroundUpdateCheck();
|
|
2258
|
+
final JSObject ret = new JSObject();
|
|
2259
|
+
ret.put("status", status);
|
|
2260
|
+
ret.put("queued", "queued".equals(status));
|
|
2261
|
+
call.resolve(ret);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
1732
2264
|
private boolean _reset(final Boolean toLastSuccessful, final Boolean usePendingBundle) {
|
|
1733
2265
|
return this.performReset(toLastSuccessful, usePendingBundle, false);
|
|
1734
2266
|
}
|
|
@@ -2065,13 +2597,17 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2065
2597
|
}
|
|
2066
2598
|
}
|
|
2067
2599
|
|
|
2068
|
-
|
|
2600
|
+
static String normalizedUpdateResponseKind(final String kind) {
|
|
2069
2601
|
if ("up_to_date".equals(kind) || "blocked".equals(kind) || "failed".equals(kind)) {
|
|
2070
2602
|
return kind;
|
|
2071
2603
|
}
|
|
2072
2604
|
return "failed";
|
|
2073
2605
|
}
|
|
2074
2606
|
|
|
2607
|
+
private String getUpdateResponseKind(final String kind) {
|
|
2608
|
+
return normalizedUpdateResponseKind(kind);
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2075
2611
|
private void notifyUpdateCheckResult(
|
|
2076
2612
|
final String kind,
|
|
2077
2613
|
final String error,
|
|
@@ -2206,6 +2742,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2206
2742
|
String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(jsRes.has("kind") ? jsRes.getString("kind") : null);
|
|
2207
2743
|
String latestVersion = jsRes.has("version") ? jsRes.getString("version") : current.getVersionName();
|
|
2208
2744
|
CapacitorUpdaterPlugin.this.notifyUpdateCheckResult(kind, error, errorMessage, statusCode, latestVersion, current);
|
|
2745
|
+
CapacitorUpdaterPlugin.this.notifyBreakingEventsIfNeeded(
|
|
2746
|
+
jsRes,
|
|
2747
|
+
jsRes.has("version") ? jsRes.getString("version") : ""
|
|
2748
|
+
);
|
|
2209
2749
|
|
|
2210
2750
|
if ("up_to_date".equals(kind)) {
|
|
2211
2751
|
logger.info("No new version available");
|
|
@@ -2268,6 +2808,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2268
2808
|
}
|
|
2269
2809
|
|
|
2270
2810
|
if (!jsRes.has("url") || !CapacitorUpdaterPlugin.this.isValidURL(jsRes.getString("url"))) {
|
|
2811
|
+
CapacitorUpdaterPlugin.this.notifyBreakingEventsIfNeeded(jsRes, latestVersionName);
|
|
2271
2812
|
logger.error("Error no url or wrong format");
|
|
2272
2813
|
CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
2273
2814
|
"Error no url or wrong format",
|
|
@@ -3174,6 +3715,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
3174
3715
|
logger.error("Failed to clean up AppLifecycleObserver: " + e.getMessage());
|
|
3175
3716
|
}
|
|
3176
3717
|
}
|
|
3718
|
+
|
|
3719
|
+
if (webViewStatsListener != null && bridge != null) {
|
|
3720
|
+
bridge.removeWebViewListener(webViewStatsListener);
|
|
3721
|
+
webViewStatsListener = null;
|
|
3722
|
+
}
|
|
3177
3723
|
} catch (Exception e) {
|
|
3178
3724
|
logger.error("Failed to run handleOnDestroy: " + e.getMessage());
|
|
3179
3725
|
}
|