@capgo/capacitor-updater 8.50.2 → 8.51.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/README.md +43 -41
- package/android/build.gradle +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +109 -13
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +161 -10
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +19 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +89 -115
- package/dist/docs.json +5 -5
- package/dist/esm/definitions.d.ts +14 -9
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +63 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +2 -2
- package/ios/Sources/CapacitorUpdaterPlugin/WebViewStatsReporter.swift +28 -0
- package/package.json +6 -2
|
@@ -13,7 +13,14 @@ import android.content.pm.PackageManager;
|
|
|
13
13
|
import android.os.Build;
|
|
14
14
|
import androidx.annotation.NonNull;
|
|
15
15
|
import androidx.lifecycle.LifecycleOwner;
|
|
16
|
+
import androidx.work.Constraints;
|
|
16
17
|
import androidx.work.Data;
|
|
18
|
+
import androidx.work.ExistingPeriodicWorkPolicy;
|
|
19
|
+
import androidx.work.ExistingWorkPolicy;
|
|
20
|
+
import androidx.work.ListenableWorker;
|
|
21
|
+
import androidx.work.NetworkType;
|
|
22
|
+
import androidx.work.OneTimeWorkRequest;
|
|
23
|
+
import androidx.work.PeriodicWorkRequest;
|
|
17
24
|
import androidx.work.WorkInfo;
|
|
18
25
|
import androidx.work.WorkManager;
|
|
19
26
|
import com.google.common.util.concurrent.Futures;
|
|
@@ -69,6 +76,7 @@ public class CapgoUpdater {
|
|
|
69
76
|
private static final String TEMP_UNZIP_PREFIX = "capgo_unzip_";
|
|
70
77
|
private static final String CAPACITOR_CONFIG_ASSET = "capacitor.config.json";
|
|
71
78
|
private static final String BACKGROUND_RUNNER_CONFIG_KEY = "BackgroundRunner";
|
|
79
|
+
private static final String BACKGROUND_RUNNER_WORKER_CLASS = "io.ionic.backgroundrunner.plugin.RunnerWorker";
|
|
72
80
|
|
|
73
81
|
public static final String TAG = "Capacitor-updater";
|
|
74
82
|
public SharedPreferences.Editor editor;
|
|
@@ -656,6 +664,9 @@ public class CapgoUpdater {
|
|
|
656
664
|
if ("low_mem_fail".equals(error)) {
|
|
657
665
|
sendStats("low_mem_fail", failedVersion);
|
|
658
666
|
}
|
|
667
|
+
if ("insufficient_disk_space".equals(error)) {
|
|
668
|
+
sendStats("insufficient_disk_space", failedVersion);
|
|
669
|
+
}
|
|
659
670
|
ret.put("error", error != null ? error : "download_fail");
|
|
660
671
|
sendStats("download_fail", failedVersion);
|
|
661
672
|
notifyListeners("downloadFailed", ret);
|
|
@@ -983,7 +994,7 @@ public class CapgoUpdater {
|
|
|
983
994
|
}
|
|
984
995
|
|
|
985
996
|
private void setCurrentBundle(final File bundle) {
|
|
986
|
-
this.
|
|
997
|
+
this.resetBackgroundRunnerWorkForBundleSwitch(bundle);
|
|
987
998
|
this.editor.putString(this.CAP_SERVER_PATH, bundle.getPath());
|
|
988
999
|
logger.info("Current bundle set to: " + bundle);
|
|
989
1000
|
this.editor.commit();
|
|
@@ -993,7 +1004,33 @@ public class CapgoUpdater {
|
|
|
993
1004
|
return bundlePath != null && !bundlePath.trim().isEmpty() && !isBuiltin && !hasStoredBundleInfo;
|
|
994
1005
|
}
|
|
995
1006
|
|
|
996
|
-
static
|
|
1007
|
+
static final class BackgroundRunnerWorkConfig {
|
|
1008
|
+
|
|
1009
|
+
final String label;
|
|
1010
|
+
final String src;
|
|
1011
|
+
final String event;
|
|
1012
|
+
final boolean autoStart;
|
|
1013
|
+
final boolean repeat;
|
|
1014
|
+
final int interval;
|
|
1015
|
+
|
|
1016
|
+
BackgroundRunnerWorkConfig(
|
|
1017
|
+
final String label,
|
|
1018
|
+
final String src,
|
|
1019
|
+
final String event,
|
|
1020
|
+
final boolean autoStart,
|
|
1021
|
+
final boolean repeat,
|
|
1022
|
+
final int interval
|
|
1023
|
+
) {
|
|
1024
|
+
this.label = label;
|
|
1025
|
+
this.src = src;
|
|
1026
|
+
this.event = event;
|
|
1027
|
+
this.autoStart = autoStart;
|
|
1028
|
+
this.repeat = repeat;
|
|
1029
|
+
this.interval = interval;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
static BackgroundRunnerWorkConfig getBackgroundRunnerWorkConfigFromConfig(final String configJson) {
|
|
997
1034
|
if (configJson == null || configJson.trim().isEmpty()) {
|
|
998
1035
|
return null;
|
|
999
1036
|
}
|
|
@@ -1011,12 +1048,30 @@ public class CapgoUpdater {
|
|
|
1011
1048
|
}
|
|
1012
1049
|
|
|
1013
1050
|
final String label = backgroundRunner.optString("label", "").trim();
|
|
1014
|
-
|
|
1051
|
+
if (label.isEmpty()) {
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
final String src = backgroundRunner.optString("src", "").trim();
|
|
1056
|
+
final String event = backgroundRunner.optString("event", "").trim();
|
|
1057
|
+
return new BackgroundRunnerWorkConfig(
|
|
1058
|
+
label,
|
|
1059
|
+
src,
|
|
1060
|
+
event,
|
|
1061
|
+
backgroundRunner.optBoolean("autoStart", false),
|
|
1062
|
+
backgroundRunner.optBoolean("repeat", false),
|
|
1063
|
+
backgroundRunner.optInt("interval", 0)
|
|
1064
|
+
);
|
|
1015
1065
|
} catch (JSONException ignored) {
|
|
1016
1066
|
return null;
|
|
1017
1067
|
}
|
|
1018
1068
|
}
|
|
1019
1069
|
|
|
1070
|
+
static String getBackgroundRunnerLabelFromConfig(final String configJson) {
|
|
1071
|
+
final BackgroundRunnerWorkConfig config = getBackgroundRunnerWorkConfigFromConfig(configJson);
|
|
1072
|
+
return config == null ? null : config.label;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1020
1075
|
private String readAssetAsString(final String assetPath) throws IOException {
|
|
1021
1076
|
final StringBuilder buffer = new StringBuilder();
|
|
1022
1077
|
try (
|
|
@@ -1032,32 +1087,128 @@ public class CapgoUpdater {
|
|
|
1032
1087
|
return buffer.toString();
|
|
1033
1088
|
}
|
|
1034
1089
|
|
|
1035
|
-
private void
|
|
1090
|
+
private void copyFileAtomically(final File source, final File dest) throws IOException {
|
|
1091
|
+
final File parent = dest.getParentFile();
|
|
1092
|
+
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
|
1093
|
+
throw new IOException("Failed to create parent directory: " + parent.getAbsolutePath());
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
final File tempFile = new File(parent, dest.getName() + ".capgo_tmp");
|
|
1097
|
+
try (final FileInputStream input = new FileInputStream(source); final FileOutputStream output = new FileOutputStream(tempFile)) {
|
|
1098
|
+
final byte[] buffer = new byte[1024 * 1024];
|
|
1099
|
+
int length;
|
|
1100
|
+
while ((length = input.read(buffer)) != -1) {
|
|
1101
|
+
output.write(buffer, 0, length);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (!tempFile.renameTo(dest)) {
|
|
1106
|
+
if (!dest.delete() || !tempFile.renameTo(dest)) {
|
|
1107
|
+
tempFile.delete();
|
|
1108
|
+
throw new IOException("Failed to replace file: " + dest.getAbsolutePath());
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
private void syncBackgroundRunnerScriptFromBundle(final File bundle, final BackgroundRunnerWorkConfig config) {
|
|
1114
|
+
if (this.activity == null || bundle == null || config == null || config.src == null || config.src.isEmpty()) {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
if (bundle.getPath().endsWith("/public") || "public".equals(bundle.getName())) {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
try {
|
|
1123
|
+
final File source = resolvePathInsideDirectory(bundle, config.src);
|
|
1124
|
+
if (!source.isFile()) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
final File publicDir = new File(this.activity.getFilesDir(), "public");
|
|
1129
|
+
final File dest = resolvePathInsideDirectory(publicDir, config.src);
|
|
1130
|
+
this.copyFileAtomically(source, dest);
|
|
1131
|
+
logger.info("Synced Background Runner script into native public storage before bundle switch.");
|
|
1132
|
+
logger.debug("Background Runner script path: " + dest.getAbsolutePath());
|
|
1133
|
+
} catch (Exception e) {
|
|
1134
|
+
logger.debug("Background Runner script sync skipped: " + e.getMessage());
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
private void resetBackgroundRunnerWorkForBundleSwitch(final File bundle) {
|
|
1036
1139
|
if (this.activity == null) {
|
|
1037
1140
|
return;
|
|
1038
1141
|
}
|
|
1039
1142
|
|
|
1040
|
-
final
|
|
1143
|
+
final BackgroundRunnerWorkConfig config;
|
|
1041
1144
|
try {
|
|
1042
|
-
|
|
1145
|
+
config = getBackgroundRunnerWorkConfigFromConfig(this.readAssetAsString(CAPACITOR_CONFIG_ASSET));
|
|
1043
1146
|
} catch (IOException ignored) {
|
|
1044
1147
|
return;
|
|
1045
1148
|
}
|
|
1046
1149
|
|
|
1047
|
-
if (
|
|
1150
|
+
if (config == null) {
|
|
1048
1151
|
return;
|
|
1049
1152
|
}
|
|
1050
1153
|
|
|
1051
1154
|
try {
|
|
1052
1155
|
final WorkManager workManager = WorkManager.getInstance(this.activity.getApplicationContext());
|
|
1053
|
-
workManager.cancelUniqueWork(label);
|
|
1054
|
-
workManager.cancelAllWorkByTag(label);
|
|
1156
|
+
workManager.cancelUniqueWork(config.label);
|
|
1157
|
+
workManager.cancelAllWorkByTag(config.label);
|
|
1055
1158
|
logger.info("Cancelled Background Runner work before bundle switch.");
|
|
1056
|
-
logger.debug("Background Runner label: " + label);
|
|
1159
|
+
logger.debug("Background Runner label: " + config.label);
|
|
1057
1160
|
} catch (Exception e) {
|
|
1058
1161
|
logger.warn("Failed to cancel Background Runner work before bundle switch.");
|
|
1059
1162
|
logger.debug("Background Runner cancellation error: " + e.getMessage());
|
|
1060
1163
|
}
|
|
1164
|
+
|
|
1165
|
+
this.syncBackgroundRunnerScriptFromBundle(bundle, config);
|
|
1166
|
+
this.rescheduleBackgroundRunnerWork(config);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
private void rescheduleBackgroundRunnerWork(final BackgroundRunnerWorkConfig config) {
|
|
1170
|
+
if (!config.autoStart || config.interval <= 0 || config.src.isEmpty()) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
try {
|
|
1175
|
+
@SuppressWarnings("unchecked")
|
|
1176
|
+
final Class<? extends ListenableWorker> workerClass = (Class<? extends ListenableWorker>) Class.forName(
|
|
1177
|
+
BACKGROUND_RUNNER_WORKER_CLASS
|
|
1178
|
+
);
|
|
1179
|
+
final Data data = new Data.Builder()
|
|
1180
|
+
.putString("label", config.label)
|
|
1181
|
+
.putString("src", config.src)
|
|
1182
|
+
.putString("event", config.event)
|
|
1183
|
+
.build();
|
|
1184
|
+
final Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();
|
|
1185
|
+
final WorkManager workManager = WorkManager.getInstance(this.activity.getApplicationContext());
|
|
1186
|
+
|
|
1187
|
+
if (!config.repeat) {
|
|
1188
|
+
final OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(workerClass)
|
|
1189
|
+
.setInitialDelay(config.interval, TimeUnit.MINUTES)
|
|
1190
|
+
.setInputData(data)
|
|
1191
|
+
.addTag(config.label)
|
|
1192
|
+
.setConstraints(constraints)
|
|
1193
|
+
.build();
|
|
1194
|
+
workManager.enqueueUniqueWork(config.label, ExistingWorkPolicy.REPLACE, work);
|
|
1195
|
+
} else {
|
|
1196
|
+
final PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(workerClass, config.interval, TimeUnit.MINUTES)
|
|
1197
|
+
.setInitialDelay(config.interval, TimeUnit.MINUTES)
|
|
1198
|
+
.setInputData(data)
|
|
1199
|
+
.addTag(config.label)
|
|
1200
|
+
.setConstraints(constraints)
|
|
1201
|
+
.build();
|
|
1202
|
+
workManager.enqueueUniquePeriodicWork(config.label, ExistingPeriodicWorkPolicy.UPDATE, work);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
logger.info("Rescheduled Background Runner work after bundle switch.");
|
|
1206
|
+
} catch (ClassNotFoundException ignored) {
|
|
1207
|
+
logger.debug("Background Runner plugin not installed, skipping reschedule.");
|
|
1208
|
+
} catch (Exception e) {
|
|
1209
|
+
logger.warn("Failed to reschedule Background Runner work after bundle switch.");
|
|
1210
|
+
logger.debug("Background Runner reschedule error: " + e.getMessage());
|
|
1211
|
+
}
|
|
1061
1212
|
}
|
|
1062
1213
|
|
|
1063
1214
|
private boolean hasStoredBundleInfo(final String id) {
|
|
@@ -174,7 +174,9 @@ public class DownloadService extends Worker {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
static File resolveManifestBuiltinFile(final File builtinFolder, final String fileName) throws IOException {
|
|
177
|
-
|
|
177
|
+
final boolean isBrotli = fileName.endsWith(".br");
|
|
178
|
+
final String resolvedName = isBrotli ? fileName.substring(0, fileName.length() - 3) : fileName;
|
|
179
|
+
return CapgoUpdater.resolvePathInsideDirectory(builtinFolder, resolvedName);
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
private String getInputString(String key, String fallback) {
|
|
@@ -642,14 +644,29 @@ public class DownloadService extends Worker {
|
|
|
642
644
|
}
|
|
643
645
|
|
|
644
646
|
private void copyFile(File source, File dest) throws IOException {
|
|
647
|
+
final File parent = dest.getParentFile();
|
|
648
|
+
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
|
649
|
+
throw new IOException("Failed to create parent directory: " + parent.getAbsolutePath());
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
final File tempFile = new File(parent, dest.getName() + ".capgo_tmp");
|
|
645
653
|
try (
|
|
646
654
|
FileInputStream inStream = new FileInputStream(source);
|
|
647
|
-
FileOutputStream outStream = new FileOutputStream(
|
|
655
|
+
FileOutputStream outStream = new FileOutputStream(tempFile);
|
|
648
656
|
FileChannel inChannel = inStream.getChannel();
|
|
649
657
|
FileChannel outChannel = outStream.getChannel()
|
|
650
658
|
) {
|
|
651
659
|
inChannel.transferTo(0, inChannel.size(), outChannel);
|
|
652
660
|
}
|
|
661
|
+
|
|
662
|
+
try {
|
|
663
|
+
Files.move(tempFile.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
|
664
|
+
} catch (IOException e) {
|
|
665
|
+
if (tempFile.exists()) {
|
|
666
|
+
tempFile.delete();
|
|
667
|
+
}
|
|
668
|
+
throw e;
|
|
669
|
+
}
|
|
653
670
|
}
|
|
654
671
|
|
|
655
672
|
private void downloadAndVerify(
|
|
@@ -13,6 +13,7 @@ import android.hardware.SensorManager;
|
|
|
13
13
|
import android.text.Editable;
|
|
14
14
|
import android.text.TextWatcher;
|
|
15
15
|
import android.widget.ArrayAdapter;
|
|
16
|
+
import android.widget.Button;
|
|
16
17
|
import android.widget.EditText;
|
|
17
18
|
import android.widget.LinearLayout;
|
|
18
19
|
import android.widget.ListView;
|
|
@@ -117,65 +118,8 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
117
118
|
isShowing = false;
|
|
118
119
|
return;
|
|
119
120
|
}
|
|
120
|
-
if (Boolean.TRUE.equals(plugin.shakeChannelSelectorEnabled)) {
|
|
121
|
-
showCombinedPreviewMenu();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
|
|
125
|
-
String title = "Preview " + appName + " Menu";
|
|
126
|
-
String message = "Reload, switch, or leave the current preview.";
|
|
127
|
-
List<String> actions = new ArrayList<>();
|
|
128
|
-
actions.add("Reload preview");
|
|
129
|
-
if (plugin.previewMenuPreviews().length() > 0) {
|
|
130
|
-
actions.add("Switch preview");
|
|
131
|
-
}
|
|
132
|
-
actions.add("Leave test app");
|
|
133
|
-
final boolean[] openingNestedSelector = { false };
|
|
134
|
-
final boolean[] previewActionRunning = { false };
|
|
135
121
|
|
|
136
|
-
|
|
137
|
-
builder.setTitle(title);
|
|
138
|
-
builder.setMessage(message);
|
|
139
|
-
builder.setItems(actions.toArray(new String[0]), (dialogInterface, which) -> {
|
|
140
|
-
AlertDialog dialog = (AlertDialog) dialogInterface;
|
|
141
|
-
String action = actions.get(which);
|
|
142
|
-
if ("Reload preview".equals(action)) {
|
|
143
|
-
previewActionRunning[0] = true;
|
|
144
|
-
logger.info("Reloading webview");
|
|
145
|
-
runPreviewMenuAction(dialog, "Could not reload the test app.", "Error reloading test app: ", () ->
|
|
146
|
-
plugin.reloadPreviewSessionFromShakeMenu()
|
|
147
|
-
);
|
|
148
|
-
} else if ("Switch preview".equals(action)) {
|
|
149
|
-
openingNestedSelector[0] = true;
|
|
150
|
-
dialog.dismiss();
|
|
151
|
-
showPreviewSelector();
|
|
152
|
-
} else {
|
|
153
|
-
previewActionRunning[0] = true;
|
|
154
|
-
runPreviewMenuAction(dialog, "Could not leave the test app.", "Error leaving test app: ", () ->
|
|
155
|
-
plugin.leavePreviewSessionFromShakeMenu()
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// Cancel button
|
|
161
|
-
builder.setNegativeButton(
|
|
162
|
-
"Close menu",
|
|
163
|
-
new DialogInterface.OnClickListener() {
|
|
164
|
-
public void onClick(DialogInterface dialog, int id) {
|
|
165
|
-
logger.info("Shake menu cancelled");
|
|
166
|
-
dialog.dismiss();
|
|
167
|
-
isShowing = false;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
AlertDialog dialog = builder.create();
|
|
173
|
-
dialog.setOnDismissListener((dialogInterface) -> {
|
|
174
|
-
if (!openingNestedSelector[0] && !previewActionRunning[0]) {
|
|
175
|
-
isShowing = false;
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
dialog.show();
|
|
122
|
+
showPreviewActionsMenu(Boolean.TRUE.equals(plugin.shakeChannelSelectorEnabled));
|
|
179
123
|
} catch (Exception e) {
|
|
180
124
|
logger.error("Error showing shake menu: " + e.getMessage());
|
|
181
125
|
isShowing = false;
|
|
@@ -183,65 +127,95 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
183
127
|
});
|
|
184
128
|
}
|
|
185
129
|
|
|
186
|
-
private void
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
130
|
+
private void showPreviewActionsMenu(boolean includeChannelSelector) {
|
|
131
|
+
String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
|
|
132
|
+
String title = "Preview " + appName + " Menu";
|
|
133
|
+
String message = "Reload, switch, or leave the current preview.";
|
|
134
|
+
final boolean[] openingNestedSelector = { false };
|
|
135
|
+
final boolean[] previewActionRunning = { false };
|
|
136
|
+
final AlertDialog[] dialogRef = { null };
|
|
137
|
+
List<Button> buttons = new ArrayList<>();
|
|
138
|
+
|
|
139
|
+
LinearLayout layout = new LinearLayout(activity);
|
|
140
|
+
layout.setOrientation(LinearLayout.VERTICAL);
|
|
141
|
+
int horizontalPadding = dpToPx(16);
|
|
142
|
+
int verticalPadding = dpToPx(8);
|
|
143
|
+
layout.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
|
|
144
|
+
|
|
145
|
+
addPreviewMenuButton(layout, buttons, "Reload preview", () -> {
|
|
146
|
+
AlertDialog dialog = dialogRef[0];
|
|
147
|
+
previewActionRunning[0] = true;
|
|
148
|
+
setPreviewMenuButtonsEnabled(buttons, false);
|
|
149
|
+
logger.info("Reloading webview");
|
|
150
|
+
runPreviewMenuAction(dialog, "Could not reload the test app.", "Error reloading test app: ", () ->
|
|
151
|
+
plugin.reloadPreviewSessionFromShakeMenu()
|
|
152
|
+
);
|
|
153
|
+
});
|
|
200
154
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
AlertDialog dialog = (AlertDialog) dialogInterface;
|
|
206
|
-
String action = actions.get(which);
|
|
207
|
-
if ("Reload preview".equals(action)) {
|
|
208
|
-
previewActionRunning[0] = true;
|
|
209
|
-
logger.info("Reloading webview");
|
|
210
|
-
runPreviewMenuAction(dialog, "Could not reload the test app.", "Error reloading test app: ", () ->
|
|
211
|
-
plugin.reloadPreviewSessionFromShakeMenu()
|
|
212
|
-
);
|
|
213
|
-
} else if ("Leave test app".equals(action)) {
|
|
214
|
-
previewActionRunning[0] = true;
|
|
215
|
-
runPreviewMenuAction(dialog, "Could not leave the test app.", "Error leaving test app: ", () ->
|
|
216
|
-
plugin.leavePreviewSessionFromShakeMenu()
|
|
217
|
-
);
|
|
218
|
-
} else if ("Switch preview".equals(action)) {
|
|
219
|
-
openingNestedSelector[0] = true;
|
|
220
|
-
dialog.dismiss();
|
|
221
|
-
showPreviewSelector();
|
|
222
|
-
} else {
|
|
223
|
-
openingNestedSelector[0] = true;
|
|
224
|
-
dialog.dismiss();
|
|
225
|
-
showChannelSelector();
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
builder.setNegativeButton("Close menu", (dialog, id) -> {
|
|
229
|
-
logger.info("Shake menu cancelled");
|
|
155
|
+
if (plugin.previewMenuPreviews().length() > 0) {
|
|
156
|
+
addPreviewMenuButton(layout, buttons, "Switch preview", () -> {
|
|
157
|
+
AlertDialog dialog = dialogRef[0];
|
|
158
|
+
openingNestedSelector[0] = true;
|
|
230
159
|
dialog.dismiss();
|
|
231
|
-
|
|
160
|
+
showPreviewSelector();
|
|
232
161
|
});
|
|
162
|
+
}
|
|
233
163
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
164
|
+
if (includeChannelSelector) {
|
|
165
|
+
addPreviewMenuButton(layout, buttons, "Switch channel", () -> {
|
|
166
|
+
AlertDialog dialog = dialogRef[0];
|
|
167
|
+
openingNestedSelector[0] = true;
|
|
168
|
+
dialog.dismiss();
|
|
169
|
+
showChannelSelector();
|
|
239
170
|
});
|
|
240
|
-
dialog.show();
|
|
241
|
-
} catch (Exception e) {
|
|
242
|
-
logger.error("Error showing combined shake menu: " + e.getMessage());
|
|
243
|
-
isShowing = false;
|
|
244
171
|
}
|
|
172
|
+
|
|
173
|
+
addPreviewMenuButton(layout, buttons, "Leave test app", () -> {
|
|
174
|
+
AlertDialog dialog = dialogRef[0];
|
|
175
|
+
previewActionRunning[0] = true;
|
|
176
|
+
setPreviewMenuButtonsEnabled(buttons, false);
|
|
177
|
+
runPreviewMenuAction(dialog, "Could not leave the test app.", "Error leaving test app: ", () ->
|
|
178
|
+
plugin.leavePreviewSessionFromShakeMenu()
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
addPreviewMenuButton(layout, buttons, "Close menu", () -> {
|
|
183
|
+
AlertDialog dialog = dialogRef[0];
|
|
184
|
+
if (dialog != null) {
|
|
185
|
+
logger.info("Shake menu cancelled");
|
|
186
|
+
dialog.dismiss();
|
|
187
|
+
isShowing = false;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
192
|
+
builder.setTitle(title);
|
|
193
|
+
builder.setMessage(message);
|
|
194
|
+
builder.setView(layout);
|
|
195
|
+
|
|
196
|
+
AlertDialog dialog = builder.create();
|
|
197
|
+
dialogRef[0] = dialog;
|
|
198
|
+
dialog.setOnDismissListener((dialogInterface) -> {
|
|
199
|
+
if (!openingNestedSelector[0] && !previewActionRunning[0]) {
|
|
200
|
+
isShowing = false;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
dialog.show();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private void addPreviewMenuButton(LinearLayout layout, List<Button> buttons, String title, Runnable action) {
|
|
207
|
+
Button button = new Button(activity);
|
|
208
|
+
button.setAllCaps(false);
|
|
209
|
+
button.setText(title);
|
|
210
|
+
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
|
211
|
+
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
212
|
+
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
213
|
+
);
|
|
214
|
+
params.setMargins(0, 0, 0, dpToPx(8));
|
|
215
|
+
button.setLayoutParams(params);
|
|
216
|
+
button.setOnClickListener((view) -> action.run());
|
|
217
|
+
buttons.add(button);
|
|
218
|
+
layout.addView(button);
|
|
245
219
|
}
|
|
246
220
|
|
|
247
221
|
private void runPreviewMenuAction(AlertDialog dialog, String failureMessage, String errorPrefix, PreviewMenuAction action) {
|
|
@@ -262,10 +236,10 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
|
|
|
262
236
|
}).start();
|
|
263
237
|
}
|
|
264
238
|
|
|
265
|
-
private void setPreviewMenuButtonsEnabled(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
239
|
+
private void setPreviewMenuButtonsEnabled(List<Button> buttons, boolean enabled) {
|
|
240
|
+
for (Button button : buttons) {
|
|
241
|
+
button.setEnabled(enabled);
|
|
242
|
+
}
|
|
269
243
|
}
|
|
270
244
|
|
|
271
245
|
private void showPreviewSelector() {
|
package/dist/docs.json
CHANGED
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
"text": "{Error} If the download fails or the bundle is invalid."
|
|
157
157
|
}
|
|
158
158
|
],
|
|
159
|
-
"docs": "Download a new bundle from the provided URL for later installation.\n\nThe downloaded bundle is stored locally but not activated. To use it:\n- Call {@link next} to set it for installation on next app backgrounding/restart\n- Call {@link set} to activate it immediately (destroys current JavaScript context)\n\nThe URL should point to a zip file containing either:\n- Your app files directly in the zip root, or\n- A single folder containing all your app files\n\nThe bundle must include an `index.html` file at the root level.\n\nFor encrypted bundles, provide the `sessionKey` and `checksum` parameters.\nFor multi-file delta updates, provide the `manifest` array.\n\n**Android Background Runner note:** `@capacitor/background-runner` loads its\nconfigured runner script from native APK assets. Live updates cannot replace\nthat runner script. Keep it stable across OTA updates and ship a native app\nupdate when the runner code changes.",
|
|
159
|
+
"docs": "Download a new bundle from the provided URL for later installation.\n\nThe downloaded bundle is stored locally but not activated. To use it:\n- Call {@link next} to set it for installation on next app backgrounding/restart\n- Call {@link set} to activate it immediately (destroys current JavaScript context)\n\nThe URL should point to a zip file containing either:\n- Your app files directly in the zip root, or\n- A single folder containing all your app files\n\nThe bundle must include an `index.html` file at the root level.\n\nFor encrypted bundles, provide the `sessionKey` and `checksum` parameters.\nFor multi-file delta updates, provide the `manifest` array.\n\n**Android Background Runner note:** `@capacitor/background-runner` loads its\nconfigured runner script from native APK assets. Live updates cannot replace\nthat runner script. Keep it stable across OTA updates and ship a native app\nupdate when the runner code changes. When a bundle switch happens, Capacitor\nUpdater cancels and reschedules configured Background Runner WorkManager jobs\nand syncs the bundled runner script into native `public/` storage when present.",
|
|
160
160
|
"complexTypes": [
|
|
161
161
|
"BundleInfo",
|
|
162
162
|
"DownloadOptions"
|
|
@@ -4623,7 +4623,7 @@
|
|
|
4623
4623
|
"name": "example"
|
|
4624
4624
|
}
|
|
4625
4625
|
],
|
|
4626
|
-
"docs": "Configure how the plugin checks for, downloads, and applies live updates.\n\nThe plugin checks for updates when the app moves to the foreground
|
|
4626
|
+
"docs": "Configure how the plugin checks for, downloads, and applies live updates.\n\nThe plugin checks for updates when the app moves to the foreground. When\n{@link periodCheckDelay} is greater than 0, it also checks on a repeating timer\nwhile the app stays open.\n\nBoolean values keep their existing behavior:\n- `true`: Same as `\"atBackground\"`.\n- `false`: Same as `\"off\"`.\n\nString values merge the previous Auto Update and Direct Update configuration:\n- `\"off\"`: Disable automatic update checks.\n- `\"atBackground\"`: Check and download automatically on each foreground check, then apply the update the next time the app moves to background.\n- `\"atInstall\"`: Apply immediately only after a fresh install or native app store update; otherwise use `\"atBackground\"` behavior.\n- `\"onLaunch\"`: Apply immediately only when the app is brought to the foreground from a killed state (cold start). After that first check, fall back to `\"atBackground\"` behavior.\n- `\"always\"`: Check on every foreground transition and apply immediately whenever an update is available.\n- `\"onlyDownload\"`: Check and download automatically, emit `updateAvailable`, and never set the next bundle or apply an update automatically.\n\nOnly available for Android and iOS.",
|
|
4627
4627
|
"complexTypes": [],
|
|
4628
4628
|
"type": "boolean | 'always' | 'off' | 'atBackground' | 'atInstall' | 'onLaunch' | 'onlyDownload' | undefined"
|
|
4629
4629
|
},
|
|
@@ -4687,7 +4687,7 @@
|
|
|
4687
4687
|
"name": "example"
|
|
4688
4688
|
}
|
|
4689
4689
|
],
|
|
4690
|
-
"docs": "Configure the URL / endpoint to which update statistics are sent.\n\nOnly available for Android and iOS. Set to \"\" to disable stats reporting.\nNative stats include update lifecycle events, app health signals such as crashes,\nAndroid ANRs, low-memory exits, iOS memory warnings, and WebView health signals\nsuch as JavaScript errors, unhandled promise rejections, resource load failures,\nWebView renderer exits,
|
|
4690
|
+
"docs": "Configure the URL / endpoint to which update statistics are sent.\n\nOnly available for Android and iOS. Set to \"\" to disable stats reporting.\nNative stats include update lifecycle events, app health signals such as crashes,\nAndroid ANRs, low-memory exits, iOS memory warnings, and WebView health signals\nsuch as JavaScript errors, unhandled promise rejections, resource load failures,\nWebView renderer exits, unclean WebView restarts, app launch readiness timing,\nand WebView load milestones when available.",
|
|
4691
4691
|
"complexTypes": [],
|
|
4692
4692
|
"type": "string | undefined"
|
|
4693
4693
|
},
|
|
@@ -4727,7 +4727,7 @@
|
|
|
4727
4727
|
"name": "directUpdate",
|
|
4728
4728
|
"tags": [
|
|
4729
4729
|
{
|
|
4730
|
-
"text": "Use {@link PluginsConfig.CapacitorUpdater.autoUpdate} string modes instead.\nWorks well for apps less than 10MB and with uploads done using --delta flag.\nZip or apps more than 10MB will be relatively slow for users to update.\n- false: Never do direct updates
|
|
4730
|
+
"text": "Use {@link PluginsConfig.CapacitorUpdater.autoUpdate} string modes instead.\nWorks well for apps less than 10MB and with uploads done using --delta flag.\nZip or apps more than 10MB will be relatively slow for users to update.\n- false: Never do direct updates\n- atInstall: Same as `\"atInstall\"` for {@link autoUpdate}\n- onLaunch: Same as `\"onLaunch\"` for {@link autoUpdate}\n- always: Same as `\"always\"` for {@link autoUpdate}\n- true: (deprecated) Same as \"always\" for backward compatibility\n\nActivate this flag will automatically make the CLI upload delta in CICD envs and will ask for confirmation in local uploads.\nOnly available for Android and iOS.",
|
|
4731
4731
|
"name": "deprecated"
|
|
4732
4732
|
},
|
|
4733
4733
|
{
|
|
@@ -4807,7 +4807,7 @@
|
|
|
4807
4807
|
"name": "example"
|
|
4808
4808
|
}
|
|
4809
4809
|
],
|
|
4810
|
-
"docs": "Configure the
|
|
4810
|
+
"docs": "Configure the interval in seconds for repeating update checks while the app stays open.\nForeground checks still run when this is 0. Values below 600 are normalized to 600.\n\nOnly available for Android and iOS.\nCannot be less than 600 seconds (10 minutes).",
|
|
4811
4811
|
"complexTypes": [],
|
|
4812
4812
|
"type": "number | undefined"
|
|
4813
4813
|
},
|
|
@@ -44,8 +44,9 @@ declare module '@capacitor/cli' {
|
|
|
44
44
|
/**
|
|
45
45
|
* Configure how the plugin checks for, downloads, and applies live updates.
|
|
46
46
|
*
|
|
47
|
-
* The plugin checks for updates when the app moves to the foreground
|
|
48
|
-
* {@link periodCheckDelay} is
|
|
47
|
+
* The plugin checks for updates when the app moves to the foreground. When
|
|
48
|
+
* {@link periodCheckDelay} is greater than 0, it also checks on a repeating timer
|
|
49
|
+
* while the app stays open.
|
|
49
50
|
*
|
|
50
51
|
* Boolean values keep their existing behavior:
|
|
51
52
|
* - `true`: Same as `"atBackground"`.
|
|
@@ -99,7 +100,8 @@ declare module '@capacitor/cli' {
|
|
|
99
100
|
* Native stats include update lifecycle events, app health signals such as crashes,
|
|
100
101
|
* Android ANRs, low-memory exits, iOS memory warnings, and WebView health signals
|
|
101
102
|
* such as JavaScript errors, unhandled promise rejections, resource load failures,
|
|
102
|
-
* WebView renderer exits,
|
|
103
|
+
* WebView renderer exits, unclean WebView restarts, app launch readiness timing,
|
|
104
|
+
* and WebView load milestones when available.
|
|
103
105
|
*
|
|
104
106
|
* @default https://plugin.capgo.app/stats
|
|
105
107
|
* @example https://example.com/api/stats
|
|
@@ -130,10 +132,10 @@ declare module '@capacitor/cli' {
|
|
|
130
132
|
* @deprecated Use {@link PluginsConfig.CapacitorUpdater.autoUpdate} string modes instead.
|
|
131
133
|
* Works well for apps less than 10MB and with uploads done using --delta flag.
|
|
132
134
|
* Zip or apps more than 10MB will be relatively slow for users to update.
|
|
133
|
-
* - false: Never do direct updates
|
|
134
|
-
* - atInstall:
|
|
135
|
-
* - onLaunch:
|
|
136
|
-
* - always:
|
|
135
|
+
* - false: Never do direct updates
|
|
136
|
+
* - atInstall: Same as `"atInstall"` for {@link autoUpdate}
|
|
137
|
+
* - onLaunch: Same as `"onLaunch"` for {@link autoUpdate}
|
|
138
|
+
* - always: Same as `"always"` for {@link autoUpdate}
|
|
137
139
|
* - true: (deprecated) Same as "always" for backward compatibility
|
|
138
140
|
*
|
|
139
141
|
* Activate this flag will automatically make the CLI upload delta in CICD envs and will ask for confirmation in local uploads.
|
|
@@ -181,7 +183,8 @@ declare module '@capacitor/cli' {
|
|
|
181
183
|
*/
|
|
182
184
|
autoSplashscreenTimeout?: number;
|
|
183
185
|
/**
|
|
184
|
-
* Configure the
|
|
186
|
+
* Configure the interval in seconds for repeating update checks while the app stays open.
|
|
187
|
+
* Foreground checks still run when this is 0. Values below 600 are normalized to 600.
|
|
185
188
|
*
|
|
186
189
|
* Only available for Android and iOS.
|
|
187
190
|
* Cannot be less than 600 seconds (10 minutes).
|
|
@@ -485,7 +488,9 @@ export interface CapacitorUpdaterPlugin {
|
|
|
485
488
|
* **Android Background Runner note:** `@capacitor/background-runner` loads its
|
|
486
489
|
* configured runner script from native APK assets. Live updates cannot replace
|
|
487
490
|
* that runner script. Keep it stable across OTA updates and ship a native app
|
|
488
|
-
* update when the runner code changes.
|
|
491
|
+
* update when the runner code changes. When a bundle switch happens, Capacitor
|
|
492
|
+
* Updater cancels and reschedules configured Background Runner WorkManager jobs
|
|
493
|
+
* and syncs the bundled runner script into native `public/` storage when present.
|
|
489
494
|
*
|
|
490
495
|
* @example
|
|
491
496
|
* const bundle = await CapacitorUpdater.download({
|