@capgo/capacitor-updater 7.42.9 → 7.45.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Package.swift +5 -2
- package/README.md +250 -77
- package/android/build.gradle +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +682 -213
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +222 -35
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +49 -13
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +38 -13
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +13 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +476 -2
- package/dist/docs.json +402 -10
- package/dist/esm/definitions.d.ts +183 -22
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +3 -2
- package/dist/esm/web.js +6 -4
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +6 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +6 -4
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +653 -140
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +213 -50
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +37 -16
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +2 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +347 -2
- package/package.json +12 -8
|
@@ -345,7 +345,7 @@ public class CapgoUpdater {
|
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
private void observeWorkProgress(Context context, String id) {
|
|
348
|
+
private void observeWorkProgress(Context context, String id, boolean setNext) {
|
|
349
349
|
if (!(context instanceof LifecycleOwner)) {
|
|
350
350
|
logger.error("Context is not a LifecycleOwner, cannot observe work progress");
|
|
351
351
|
return;
|
|
@@ -375,7 +375,7 @@ public class CapgoUpdater {
|
|
|
375
375
|
boolean isManifest = outputData.getBoolean(DownloadService.IS_MANIFEST, false);
|
|
376
376
|
|
|
377
377
|
io.execute(() -> {
|
|
378
|
-
boolean success = finishDownload(id, dest, version, sessionKey, checksum,
|
|
378
|
+
boolean success = finishDownload(id, dest, version, sessionKey, checksum, setNext, isManifest);
|
|
379
379
|
BundleInfo resultBundle;
|
|
380
380
|
if (!success) {
|
|
381
381
|
logger.error("Finish download failed");
|
|
@@ -454,13 +454,14 @@ public class CapgoUpdater {
|
|
|
454
454
|
final String version,
|
|
455
455
|
final String sessionKey,
|
|
456
456
|
final String checksum,
|
|
457
|
-
final JSONArray manifest
|
|
457
|
+
final JSONArray manifest,
|
|
458
|
+
final boolean setNext
|
|
458
459
|
) {
|
|
459
460
|
if (this.activity == null) {
|
|
460
461
|
logger.error("Activity is null, cannot observe work progress");
|
|
461
462
|
return;
|
|
462
463
|
}
|
|
463
|
-
observeWorkProgress(this.activity, id);
|
|
464
|
+
observeWorkProgress(this.activity, id, setNext);
|
|
464
465
|
|
|
465
466
|
DownloadWorkerManager.enqueueDownload(
|
|
466
467
|
this.activity,
|
|
@@ -573,7 +574,7 @@ public class CapgoUpdater {
|
|
|
573
574
|
this.notifyDownload(id, 100);
|
|
574
575
|
|
|
575
576
|
final Map<String, Object> ret = new HashMap<>();
|
|
576
|
-
ret.put("bundle", next.toJSONMap());
|
|
577
|
+
ret.put("bundle", InternalUtils.mapToJSObject(next.toJSONMap()));
|
|
577
578
|
logger.info("updateAvailable: " + ret);
|
|
578
579
|
CapgoUpdater.this.notifyListeners("updateAvailable", ret);
|
|
579
580
|
logger.info("setNext: " + setNext);
|
|
@@ -757,12 +758,37 @@ public class CapgoUpdater {
|
|
|
757
758
|
this.editor.commit();
|
|
758
759
|
}
|
|
759
760
|
|
|
761
|
+
static boolean shouldResetForForeignBundle(final String bundlePath, final boolean isBuiltin, final boolean hasStoredBundleInfo) {
|
|
762
|
+
return bundlePath != null && !bundlePath.trim().isEmpty() && !isBuiltin && !hasStoredBundleInfo;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
private boolean hasStoredBundleInfo(final String id) {
|
|
766
|
+
return (
|
|
767
|
+
id != null &&
|
|
768
|
+
!id.isEmpty() &&
|
|
769
|
+
!BundleInfo.ID_BUILTIN.equals(id) &&
|
|
770
|
+
!BundleInfo.VERSION_UNKNOWN.equals(id) &&
|
|
771
|
+
this.prefs.contains(id + INFO_SUFFIX)
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
760
775
|
public void downloadBackground(
|
|
761
776
|
final String url,
|
|
762
777
|
final String version,
|
|
763
778
|
final String sessionKey,
|
|
764
779
|
final String checksum,
|
|
765
780
|
final JSONArray manifest
|
|
781
|
+
) {
|
|
782
|
+
downloadBackground(url, version, sessionKey, checksum, manifest, true);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
public void downloadBackground(
|
|
786
|
+
final String url,
|
|
787
|
+
final String version,
|
|
788
|
+
final String sessionKey,
|
|
789
|
+
final String checksum,
|
|
790
|
+
final JSONArray manifest,
|
|
791
|
+
final boolean setNext
|
|
766
792
|
) {
|
|
767
793
|
final String id = this.randomString();
|
|
768
794
|
|
|
@@ -784,7 +810,7 @@ public class CapgoUpdater {
|
|
|
784
810
|
this.notifyDownload(id, 0);
|
|
785
811
|
this.notifyDownload(id, 5);
|
|
786
812
|
|
|
787
|
-
this.download(id, url, this.randomString(), version, sessionKey, checksum, manifest);
|
|
813
|
+
this.download(id, url, this.randomString(), version, sessionKey, checksum, manifest, setNext);
|
|
788
814
|
}
|
|
789
815
|
|
|
790
816
|
public BundleInfo download(final String url, final String version, final String sessionKey, final String checksum) throws IOException {
|
|
@@ -806,7 +832,59 @@ public class CapgoUpdater {
|
|
|
806
832
|
downloadFutures.put(id, downloadFuture);
|
|
807
833
|
|
|
808
834
|
// Start the download
|
|
809
|
-
this.download(id, url, dest, version, sessionKey, checksum, null);
|
|
835
|
+
this.download(id, url, dest, version, sessionKey, checksum, null, false);
|
|
836
|
+
|
|
837
|
+
// Wait for completion without timeout
|
|
838
|
+
try {
|
|
839
|
+
BundleInfo result = downloadFuture.get();
|
|
840
|
+
if (result.isErrorStatus()) {
|
|
841
|
+
throw new IOException("Download failed with status: " + result.getStatus());
|
|
842
|
+
}
|
|
843
|
+
return result;
|
|
844
|
+
} catch (Exception e) {
|
|
845
|
+
// Clean up on failure
|
|
846
|
+
downloadFutures.remove(id);
|
|
847
|
+
logger.error("Error waiting for download");
|
|
848
|
+
logger.debug("Error: " + e.getMessage());
|
|
849
|
+
BundleInfo errorBundle = new BundleInfo(id, version, BundleStatus.ERROR, new Date(System.currentTimeMillis()), "");
|
|
850
|
+
saveBundleInfo(id, errorBundle);
|
|
851
|
+
if (e instanceof IOException) {
|
|
852
|
+
throw (IOException) e;
|
|
853
|
+
}
|
|
854
|
+
throw new IOException("Error waiting for download: " + e.getMessage(), e);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
public BundleInfo downloadManifest(
|
|
859
|
+
final String url,
|
|
860
|
+
final String version,
|
|
861
|
+
final String sessionKey,
|
|
862
|
+
final String checksum,
|
|
863
|
+
final JSONArray manifest
|
|
864
|
+
) throws IOException {
|
|
865
|
+
if (manifest == null) {
|
|
866
|
+
return download(url, version, sessionKey, checksum);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Check for existing bundle with same version and clean up if in error state
|
|
870
|
+
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
871
|
+
if (existingBundle != null && (existingBundle.isErrorStatus() || existingBundle.isDeleted())) {
|
|
872
|
+
logger.info("Found existing failed bundle for version " + version + ", deleting before retry");
|
|
873
|
+
this.delete(existingBundle.getId(), true);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
final String id = this.randomString();
|
|
877
|
+
saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
|
|
878
|
+
this.notifyDownload(id, 0);
|
|
879
|
+
this.notifyDownload(id, 5);
|
|
880
|
+
final String dest = this.randomString();
|
|
881
|
+
|
|
882
|
+
// Create a CompletableFuture to track download completion
|
|
883
|
+
CompletableFuture<BundleInfo> downloadFuture = new CompletableFuture<>();
|
|
884
|
+
downloadFutures.put(id, downloadFuture);
|
|
885
|
+
|
|
886
|
+
// Start the download
|
|
887
|
+
this.download(id, url, dest, version, sessionKey, checksum, manifest, false);
|
|
810
888
|
|
|
811
889
|
// Wait for completion without timeout
|
|
812
890
|
try {
|
|
@@ -913,6 +991,64 @@ public class CapgoUpdater {
|
|
|
913
991
|
return (bundle.isDirectory() && bundle.exists() && new File(bundle.getPath(), "/index.html").exists() && !bundleInfo.isDeleted());
|
|
914
992
|
}
|
|
915
993
|
|
|
994
|
+
static final class ResetState {
|
|
995
|
+
|
|
996
|
+
final String currentBundlePath;
|
|
997
|
+
final String fallbackBundleId;
|
|
998
|
+
final String nextBundleId;
|
|
999
|
+
|
|
1000
|
+
ResetState(final String currentBundlePath, final String fallbackBundleId, final String nextBundleId) {
|
|
1001
|
+
this.currentBundlePath = currentBundlePath;
|
|
1002
|
+
this.fallbackBundleId = fallbackBundleId;
|
|
1003
|
+
this.nextBundleId = nextBundleId;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
ResetState captureResetState() {
|
|
1008
|
+
return new ResetState(
|
|
1009
|
+
this.getCurrentBundlePath(),
|
|
1010
|
+
this.prefs.getString(FALLBACK_VERSION, BundleInfo.ID_BUILTIN),
|
|
1011
|
+
this.prefs.getString(NEXT_VERSION, null)
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
void restoreResetState(final ResetState state) {
|
|
1016
|
+
final String currentBundlePath = state.currentBundlePath == null || state.currentBundlePath.trim().isEmpty()
|
|
1017
|
+
? "public"
|
|
1018
|
+
: state.currentBundlePath;
|
|
1019
|
+
final String fallbackBundleId = state.fallbackBundleId == null || state.fallbackBundleId.isEmpty()
|
|
1020
|
+
? BundleInfo.ID_BUILTIN
|
|
1021
|
+
: state.fallbackBundleId;
|
|
1022
|
+
|
|
1023
|
+
this.editor.putString(this.CAP_SERVER_PATH, currentBundlePath);
|
|
1024
|
+
this.editor.putString(FALLBACK_VERSION, fallbackBundleId);
|
|
1025
|
+
if (state.nextBundleId == null || state.nextBundleId.isEmpty()) {
|
|
1026
|
+
this.editor.remove(NEXT_VERSION);
|
|
1027
|
+
} else {
|
|
1028
|
+
this.editor.putString(NEXT_VERSION, state.nextBundleId);
|
|
1029
|
+
}
|
|
1030
|
+
this.editor.commit();
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
void prepareResetStateForTransition() {
|
|
1034
|
+
this.setCurrentBundle(new File("public"));
|
|
1035
|
+
this.setFallbackBundle(null);
|
|
1036
|
+
this.setNextBundle(null);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
void finalizeResetTransition(final String previousBundleName, final boolean internal) {
|
|
1040
|
+
if (this.activity != null) {
|
|
1041
|
+
DownloadWorkerManager.cancelAllDownloads(this.activity);
|
|
1042
|
+
}
|
|
1043
|
+
if (!internal) {
|
|
1044
|
+
this.sendStats("reset", this.getCurrentBundle().getVersionName(), previousBundleName);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
boolean canSet(final BundleInfo bundle) {
|
|
1049
|
+
return bundle != null && (bundle.isBuiltin() || this.bundleExists(bundle.getId()));
|
|
1050
|
+
}
|
|
1051
|
+
|
|
916
1052
|
public Boolean set(final BundleInfo bundle) {
|
|
917
1053
|
return this.set(bundle.getId());
|
|
918
1054
|
}
|
|
@@ -937,11 +1073,32 @@ public class CapgoUpdater {
|
|
|
937
1073
|
return false;
|
|
938
1074
|
}
|
|
939
1075
|
|
|
1076
|
+
boolean stagePendingReload(final BundleInfo bundle) {
|
|
1077
|
+
if (bundle == null || bundle.isBuiltin() || !this.bundleExists(bundle.getId())) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
this.setCurrentBundle(this.getBundleDirectory(bundle.getId()));
|
|
1081
|
+
return true;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
void finalizePendingReload(final BundleInfo bundle, final String previousBundleName) {
|
|
1085
|
+
if (bundle == null || bundle.isBuiltin()) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
this.sendStats("set", bundle.getVersionName(), previousBundleName);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
940
1091
|
public void autoReset() {
|
|
941
1092
|
final BundleInfo currentBundle = this.getCurrentBundle();
|
|
942
1093
|
if (!currentBundle.isBuiltin() && !this.bundleExists(currentBundle.getId())) {
|
|
943
1094
|
logger.info("Folder at bundle path does not exist. Triggering reset.");
|
|
944
1095
|
this.reset();
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
String bundlePath = this.prefs.getString(this.CAP_SERVER_PATH, null);
|
|
1099
|
+
if (shouldResetForForeignBundle(bundlePath, currentBundle.isBuiltin(), this.hasStoredBundleInfo(currentBundle.getId()))) {
|
|
1100
|
+
logger.info("Current bundle id is not one of the bundle ids stored by this plugin. Triggering reset.");
|
|
1101
|
+
this.reset();
|
|
945
1102
|
}
|
|
946
1103
|
}
|
|
947
1104
|
|
|
@@ -974,17 +1131,9 @@ public class CapgoUpdater {
|
|
|
974
1131
|
|
|
975
1132
|
public void reset(final boolean internal) {
|
|
976
1133
|
logger.debug("reset: " + internal);
|
|
977
|
-
|
|
978
|
-
this.
|
|
979
|
-
this.
|
|
980
|
-
this.setNextBundle(null);
|
|
981
|
-
// Cancel any ongoing downloads
|
|
982
|
-
if (this.activity != null) {
|
|
983
|
-
DownloadWorkerManager.cancelAllDownloads(this.activity);
|
|
984
|
-
}
|
|
985
|
-
if (!internal) {
|
|
986
|
-
this.sendStats("reset", this.getCurrentBundle().getVersionName(), currentBundleName);
|
|
987
|
-
}
|
|
1134
|
+
final String currentBundleName = this.getCurrentBundle().getVersionName();
|
|
1135
|
+
this.prepareResetStateForTransition();
|
|
1136
|
+
this.finalizeResetTransition(currentBundleName, internal);
|
|
988
1137
|
}
|
|
989
1138
|
|
|
990
1139
|
private JSONObject createInfoObject() throws JSONException {
|
|
@@ -1080,6 +1229,7 @@ public class CapgoUpdater {
|
|
|
1080
1229
|
Map<String, Object> retError = new HashMap<>();
|
|
1081
1230
|
retError.put("message", "Request failed: " + e.getMessage());
|
|
1082
1231
|
retError.put("error", "network_error");
|
|
1232
|
+
retError.put("kind", "failed");
|
|
1083
1233
|
callback.callback(retError);
|
|
1084
1234
|
}
|
|
1085
1235
|
|
|
@@ -1087,11 +1237,46 @@ public class CapgoUpdater {
|
|
|
1087
1237
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
1088
1238
|
try (ResponseBody responseBody = response.body()) {
|
|
1089
1239
|
final int statusCode = response.code();
|
|
1240
|
+
final String responseData = responseBody != null ? responseBody.string() : "";
|
|
1241
|
+
JSONObject jsonResponse = null;
|
|
1242
|
+
if (!responseData.isEmpty()) {
|
|
1243
|
+
try {
|
|
1244
|
+
jsonResponse = new JSONObject(responseData);
|
|
1245
|
+
} catch (JSONException ignored) {
|
|
1246
|
+
// Non-JSON responses are handled as response or parse errors below.
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if (jsonResponse != null && (jsonResponse.has("error") || jsonResponse.has("kind"))) {
|
|
1251
|
+
if (statusCode == 429) {
|
|
1252
|
+
checkAndHandleRateLimitResponse(response);
|
|
1253
|
+
}
|
|
1254
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1255
|
+
if (jsonResponse.has("error") && !jsonResponse.isNull("error")) {
|
|
1256
|
+
retError.put("error", jsonResponse.getString("error"));
|
|
1257
|
+
}
|
|
1258
|
+
if (jsonResponse.has("kind") && !jsonResponse.isNull("kind")) {
|
|
1259
|
+
retError.put("kind", jsonResponse.getString("kind"));
|
|
1260
|
+
}
|
|
1261
|
+
if (jsonResponse.has("message") && !jsonResponse.isNull("message")) {
|
|
1262
|
+
retError.put("message", jsonResponse.getString("message"));
|
|
1263
|
+
} else {
|
|
1264
|
+
retError.put("message", "server did not provide a message");
|
|
1265
|
+
}
|
|
1266
|
+
if (jsonResponse.has("version") && !jsonResponse.isNull("version")) {
|
|
1267
|
+
retError.put("version", jsonResponse.getString("version"));
|
|
1268
|
+
}
|
|
1269
|
+
retError.put("statusCode", statusCode);
|
|
1270
|
+
callback.callback(retError);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1090
1274
|
// Check for 429 rate limit
|
|
1091
1275
|
if (checkAndHandleRateLimitResponse(response)) {
|
|
1092
1276
|
Map<String, Object> retError = new HashMap<>();
|
|
1093
1277
|
retError.put("message", "Rate limit exceeded");
|
|
1094
1278
|
retError.put("error", "rate_limit_exceeded");
|
|
1279
|
+
retError.put("kind", "failed");
|
|
1095
1280
|
retError.put("statusCode", statusCode);
|
|
1096
1281
|
callback.callback(retError);
|
|
1097
1282
|
return;
|
|
@@ -1101,27 +1286,14 @@ public class CapgoUpdater {
|
|
|
1101
1286
|
Map<String, Object> retError = new HashMap<>();
|
|
1102
1287
|
retError.put("message", "Server error: " + response.code());
|
|
1103
1288
|
retError.put("error", "response_error");
|
|
1289
|
+
retError.put("kind", "failed");
|
|
1104
1290
|
retError.put("statusCode", statusCode);
|
|
1105
1291
|
callback.callback(retError);
|
|
1106
1292
|
return;
|
|
1107
1293
|
}
|
|
1108
1294
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
JSONObject jsonResponse = new JSONObject(responseData);
|
|
1112
|
-
|
|
1113
|
-
// Check for server-side errors first
|
|
1114
|
-
if (jsonResponse.has("error")) {
|
|
1115
|
-
Map<String, Object> retError = new HashMap<>();
|
|
1116
|
-
retError.put("error", jsonResponse.getString("error"));
|
|
1117
|
-
if (jsonResponse.has("message")) {
|
|
1118
|
-
retError.put("message", jsonResponse.getString("message"));
|
|
1119
|
-
} else {
|
|
1120
|
-
retError.put("message", "server did not provide a message");
|
|
1121
|
-
}
|
|
1122
|
-
retError.put("statusCode", statusCode);
|
|
1123
|
-
callback.callback(retError);
|
|
1124
|
-
return;
|
|
1295
|
+
if (jsonResponse == null) {
|
|
1296
|
+
throw new JSONException("Response is not a JSON object");
|
|
1125
1297
|
}
|
|
1126
1298
|
|
|
1127
1299
|
Map<String, Object> ret = new HashMap<>();
|
|
@@ -1143,6 +1315,7 @@ public class CapgoUpdater {
|
|
|
1143
1315
|
Map<String, Object> retError = new HashMap<>();
|
|
1144
1316
|
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
1145
1317
|
retError.put("error", "parse_error");
|
|
1318
|
+
retError.put("kind", "failed");
|
|
1146
1319
|
callback.callback(retError);
|
|
1147
1320
|
}
|
|
1148
1321
|
}
|
|
@@ -1643,7 +1816,13 @@ public class CapgoUpdater {
|
|
|
1643
1816
|
}
|
|
1644
1817
|
BundleInfo result;
|
|
1645
1818
|
if (BundleInfo.ID_BUILTIN.equals(trueId)) {
|
|
1646
|
-
result = new BundleInfo(
|
|
1819
|
+
result = new BundleInfo(
|
|
1820
|
+
trueId,
|
|
1821
|
+
this.versionBuild == null || this.versionBuild.isEmpty() ? null : this.versionBuild,
|
|
1822
|
+
BundleStatus.SUCCESS,
|
|
1823
|
+
"",
|
|
1824
|
+
""
|
|
1825
|
+
);
|
|
1647
1826
|
} else if (BundleInfo.VERSION_UNKNOWN.equals(trueId)) {
|
|
1648
1827
|
result = new BundleInfo(trueId, null, BundleStatus.ERROR, "", "");
|
|
1649
1828
|
} else {
|
|
@@ -1748,6 +1927,7 @@ public class CapgoUpdater {
|
|
|
1748
1927
|
}
|
|
1749
1928
|
|
|
1750
1929
|
public boolean setNextBundle(final String next) {
|
|
1930
|
+
BundleInfo bundleToNotify = null;
|
|
1751
1931
|
if (next == null) {
|
|
1752
1932
|
this.editor.remove(NEXT_VERSION);
|
|
1753
1933
|
} else {
|
|
@@ -1757,8 +1937,15 @@ public class CapgoUpdater {
|
|
|
1757
1937
|
}
|
|
1758
1938
|
this.editor.putString(NEXT_VERSION, next);
|
|
1759
1939
|
this.setBundleStatus(next, BundleStatus.PENDING);
|
|
1940
|
+
bundleToNotify = newBundle;
|
|
1760
1941
|
}
|
|
1761
1942
|
this.editor.commit();
|
|
1943
|
+
if (bundleToNotify != null) {
|
|
1944
|
+
this.sendStats("set_next", bundleToNotify.getVersionName(), this.getCurrentBundle().getVersionName());
|
|
1945
|
+
final Map<String, Object> payload = new HashMap<>();
|
|
1946
|
+
payload.put("bundle", bundleToNotify.toJSONMap());
|
|
1947
|
+
this.notifyListeners("setNext", payload);
|
|
1948
|
+
}
|
|
1762
1949
|
return true;
|
|
1763
1950
|
}
|
|
1764
1951
|
|
|
@@ -2,9 +2,12 @@ package ee.forgr.capacitor_updater;
|
|
|
2
2
|
|
|
3
3
|
import android.content.SharedPreferences;
|
|
4
4
|
import io.github.g00fy2.versioncompare.Version;
|
|
5
|
+
import java.text.ParsePosition;
|
|
5
6
|
import java.text.SimpleDateFormat;
|
|
6
7
|
import java.util.ArrayList;
|
|
7
8
|
import java.util.Date;
|
|
9
|
+
import java.util.Locale;
|
|
10
|
+
import java.util.TimeZone;
|
|
8
11
|
import org.json.JSONArray;
|
|
9
12
|
import org.json.JSONException;
|
|
10
13
|
import org.json.JSONObject;
|
|
@@ -98,25 +101,16 @@ public class DelayUpdateUtils {
|
|
|
98
101
|
break;
|
|
99
102
|
case DelayUntilNext.date:
|
|
100
103
|
if (!"".equals(value)) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Date date = sdf.parse(value);
|
|
104
|
-
assert date != null;
|
|
104
|
+
Date date = parseDateCondition(value);
|
|
105
|
+
if (date != null) {
|
|
105
106
|
if (new Date().compareTo(date) > 0) {
|
|
106
107
|
logger.info("Date delay (value: " + value + ") condition removed due to expired date at index " + index);
|
|
107
108
|
} else {
|
|
108
109
|
delayConditionListToKeep.add(condition);
|
|
109
110
|
logger.info("Date delay (value: " + value + ") condition kept at index " + index);
|
|
110
111
|
}
|
|
111
|
-
}
|
|
112
|
-
logger.error(
|
|
113
|
-
"Date delay (value: " +
|
|
114
|
-
value +
|
|
115
|
-
") condition removed due to parsing issue at index " +
|
|
116
|
-
index +
|
|
117
|
-
" " +
|
|
118
|
-
e.getMessage()
|
|
119
|
-
);
|
|
112
|
+
} else {
|
|
113
|
+
logger.error("Date delay (value: " + value + ") condition removed due to parsing issue at index " + index);
|
|
120
114
|
}
|
|
121
115
|
} else {
|
|
122
116
|
logger.debug("Date delay (value: " + value + ") condition removed due to empty value at index " + index);
|
|
@@ -190,6 +184,48 @@ public class DelayUpdateUtils {
|
|
|
190
184
|
return conditions;
|
|
191
185
|
}
|
|
192
186
|
|
|
187
|
+
private Date parseDateCondition(String value) {
|
|
188
|
+
String[] patterns = {
|
|
189
|
+
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
|
|
190
|
+
"yyyy-MM-dd'T'HH:mm:ssXXX",
|
|
191
|
+
"yyyy-MM-dd'T'HH:mm:ss.SSSXX",
|
|
192
|
+
"yyyy-MM-dd'T'HH:mm:ssXX",
|
|
193
|
+
"yyyy-MM-dd'T'HH:mm:ss.SSSX",
|
|
194
|
+
"yyyy-MM-dd'T'HH:mm:ssX",
|
|
195
|
+
"yyyy-MM-dd'T'HH:mm:ss.SSS",
|
|
196
|
+
"yyyy-MM-dd'T'HH:mm:ss"
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
for (String pattern : patterns) {
|
|
200
|
+
Date parsed = parseDateWithPattern(value, pattern);
|
|
201
|
+
if (parsed != null) {
|
|
202
|
+
return parsed;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private Date parseDateWithPattern(String value, String pattern) {
|
|
210
|
+
try {
|
|
211
|
+
SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.US);
|
|
212
|
+
sdf.setLenient(false);
|
|
213
|
+
|
|
214
|
+
// If no timezone is provided, keep historical behavior and interpret as local time.
|
|
215
|
+
if (!pattern.contains("X")) {
|
|
216
|
+
sdf.setTimeZone(TimeZone.getDefault());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
ParsePosition position = new ParsePosition(0);
|
|
220
|
+
Date parsed = sdf.parse(value, position);
|
|
221
|
+
if (parsed != null && position.getIndex() == value.length()) {
|
|
222
|
+
return parsed;
|
|
223
|
+
}
|
|
224
|
+
} catch (Exception ignored) {}
|
|
225
|
+
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
193
229
|
private String convertDelayConditionsToJson(ArrayList<DelayCondition> conditions) {
|
|
194
230
|
JSONArray array = new JSONArray();
|
|
195
231
|
for (DelayCondition condition : conditions) {
|
|
@@ -91,27 +91,52 @@ public class DownloadService extends Worker {
|
|
|
91
91
|
.protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
|
|
92
92
|
.addInterceptor((chain) -> {
|
|
93
93
|
Request originalRequest = chain.request();
|
|
94
|
-
String userAgent =
|
|
95
|
-
"CapacitorUpdater/" +
|
|
96
|
-
(currentPluginVersion != null ? currentPluginVersion : "unknown") +
|
|
97
|
-
" (" +
|
|
98
|
-
(currentAppId != null ? currentAppId : "unknown") +
|
|
99
|
-
") android/" +
|
|
100
|
-
(currentVersionOs != null ? currentVersionOs : "unknown");
|
|
94
|
+
String userAgent = buildUserAgent(currentAppId, currentPluginVersion, currentVersionOs);
|
|
101
95
|
Request requestWithUserAgent = originalRequest.newBuilder().header("User-Agent", userAgent).build();
|
|
102
96
|
return chain.proceed(requestWithUserAgent);
|
|
103
97
|
})
|
|
104
98
|
.build();
|
|
105
99
|
}
|
|
106
100
|
|
|
101
|
+
static String buildUserAgent(String appId, String pluginVersion, String versionOs) {
|
|
102
|
+
return (
|
|
103
|
+
"CapacitorUpdater/" +
|
|
104
|
+
sanitizeUserAgentValue(pluginVersion) +
|
|
105
|
+
" (" +
|
|
106
|
+
sanitizeUserAgentValue(appId) +
|
|
107
|
+
") android/" +
|
|
108
|
+
sanitizeUserAgentValue(versionOs)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private static String sanitizeUserAgentValue(String value) {
|
|
113
|
+
if (value == null || value.isEmpty()) {
|
|
114
|
+
return "unknown";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
StringBuilder sanitized = new StringBuilder();
|
|
118
|
+
value
|
|
119
|
+
.codePoints()
|
|
120
|
+
.forEach((cp) -> {
|
|
121
|
+
boolean isVisibleAscii = cp >= 0x20 && cp <= 0x7E;
|
|
122
|
+
boolean isIso88591 = cp >= 0xA0 && cp <= 0xFF;
|
|
123
|
+
if (isVisibleAscii || isIso88591) {
|
|
124
|
+
sanitized.appendCodePoint(cp);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
String result = sanitized.toString().trim();
|
|
129
|
+
return result.isEmpty() ? "unknown" : result;
|
|
130
|
+
}
|
|
131
|
+
|
|
107
132
|
// Method to update User-Agent values
|
|
108
133
|
public static void updateUserAgent(String appId, String pluginVersion, String versionOs) {
|
|
109
|
-
currentAppId = appId
|
|
110
|
-
currentPluginVersion = pluginVersion
|
|
111
|
-
currentVersionOs = versionOs
|
|
112
|
-
logger
|
|
113
|
-
"Updated User-Agent:
|
|
114
|
-
|
|
134
|
+
currentAppId = sanitizeUserAgentValue(appId);
|
|
135
|
+
currentPluginVersion = sanitizeUserAgentValue(pluginVersion);
|
|
136
|
+
currentVersionOs = sanitizeUserAgentValue(versionOs);
|
|
137
|
+
if (logger != null) {
|
|
138
|
+
logger.debug("Updated User-Agent: " + buildUserAgent(currentAppId, currentPluginVersion, currentVersionOs));
|
|
139
|
+
}
|
|
115
140
|
}
|
|
116
141
|
|
|
117
142
|
public DownloadService(@NonNull Context context, @NonNull WorkerParameters params) {
|
|
@@ -3,9 +3,22 @@ package ee.forgr.capacitor_updater;
|
|
|
3
3
|
import android.content.pm.PackageInfo;
|
|
4
4
|
import android.content.pm.PackageManager;
|
|
5
5
|
import android.os.Build;
|
|
6
|
+
import com.getcapacitor.JSObject;
|
|
7
|
+
import java.util.Map;
|
|
6
8
|
|
|
7
9
|
public class InternalUtils {
|
|
8
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Converts a Map to JSObject for proper bridge serialization.
|
|
13
|
+
*/
|
|
14
|
+
public static JSObject mapToJSObject(Map<String, Object> map) {
|
|
15
|
+
JSObject jsObject = new JSObject();
|
|
16
|
+
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
|
17
|
+
jsObject.put(entry.getKey(), entry.getValue());
|
|
18
|
+
}
|
|
19
|
+
return jsObject;
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
public static String getPackageName(PackageManager pm, String packageName) {
|
|
10
23
|
try {
|
|
11
24
|
PackageInfo pInfo = getPackageInfoInternal(pm, packageName);
|