@capgo/capacitor-updater 7.2.14 → 7.2.17
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/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +8 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +30 -118
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +222 -0
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +75 -118
- package/ios/Plugin/DelayUpdateUtils.swift +220 -0
- package/package.json +1 -1
|
@@ -322,6 +322,14 @@ public class CapacitorUpdater {
|
|
|
322
322
|
|
|
323
323
|
if (!isManifest) {
|
|
324
324
|
String checksumDecrypted = Objects.requireNonNullElse(checksumRes, "");
|
|
325
|
+
|
|
326
|
+
// If public key is present but no checksum provided, refuse installation
|
|
327
|
+
if (!this.publicKey.isEmpty() && checksumDecrypted.isEmpty()) {
|
|
328
|
+
Log.e(CapacitorUpdater.TAG, "Public key present but no checksum provided");
|
|
329
|
+
this.sendStats("checksum_required");
|
|
330
|
+
throw new IOException("Checksum required when public key is present: " + id);
|
|
331
|
+
}
|
|
332
|
+
|
|
325
333
|
if (!sessionKey.isEmpty()) {
|
|
326
334
|
CryptoCipherV2.decryptFile(downloaded, publicKey, sessionKey);
|
|
327
335
|
checksumDecrypted = CryptoCipherV2.decryptChecksum(checksumRes, publicKey);
|
|
@@ -49,6 +49,7 @@ import okhttp3.OkHttpClient;
|
|
|
49
49
|
import okhttp3.Protocol;
|
|
50
50
|
import org.json.JSONArray;
|
|
51
51
|
import org.json.JSONException;
|
|
52
|
+
import org.json.JSONObject;
|
|
52
53
|
|
|
53
54
|
@CapacitorPlugin(name = "CapacitorUpdater")
|
|
54
55
|
public class CapacitorUpdaterPlugin extends Plugin {
|
|
@@ -57,7 +58,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
57
58
|
private static final String statsUrlDefault = "https://plugin.capgo.app/stats";
|
|
58
59
|
private static final String channelUrlDefault = "https://plugin.capgo.app/channel_self";
|
|
59
60
|
|
|
60
|
-
private final String PLUGIN_VERSION = "7.2.
|
|
61
|
+
private final String PLUGIN_VERSION = "7.2.17";
|
|
61
62
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
62
63
|
|
|
63
64
|
private SharedPreferences.Editor editor;
|
|
@@ -86,6 +87,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
86
87
|
|
|
87
88
|
private int lastNotifiedStatPercent = 0;
|
|
88
89
|
|
|
90
|
+
private DelayUpdateUtils delayUpdateUtils;
|
|
91
|
+
|
|
89
92
|
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
90
93
|
Thread bgTask = new Thread(() -> {
|
|
91
94
|
try {
|
|
@@ -140,9 +143,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
140
143
|
.readTimeout(this.implementation.timeout, TimeUnit.MILLISECONDS)
|
|
141
144
|
.writeTimeout(this.implementation.timeout, TimeUnit.MILLISECONDS)
|
|
142
145
|
.build();
|
|
143
|
-
|
|
144
146
|
this.implementation.directUpdate = this.getConfig().getBoolean("directUpdate", false);
|
|
145
147
|
this.currentVersionNative = new Version(this.getConfig().getString("version", pInfo.versionName));
|
|
148
|
+
this.delayUpdateUtils = new DelayUpdateUtils(
|
|
149
|
+
this.prefs,
|
|
150
|
+
this.editor,
|
|
151
|
+
this.currentVersionNative,
|
|
152
|
+
CapacitorUpdaterPlugin.this::installNext
|
|
153
|
+
);
|
|
146
154
|
} catch (final PackageManager.NameNotFoundException e) {
|
|
147
155
|
Log.e(CapacitorUpdater.TAG, "Error instantiating implementation", e);
|
|
148
156
|
return;
|
|
@@ -831,13 +839,21 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
831
839
|
@PluginMethod
|
|
832
840
|
public void setMultiDelay(final PluginCall call) {
|
|
833
841
|
try {
|
|
834
|
-
final
|
|
842
|
+
final JSONArray delayConditions = call.getData().optJSONArray("delayConditions");
|
|
835
843
|
if (delayConditions == null) {
|
|
836
844
|
Log.e(CapacitorUpdater.TAG, "setMultiDelay called without delayCondition");
|
|
837
845
|
call.reject("setMultiDelay called without delayCondition");
|
|
838
846
|
return;
|
|
839
847
|
}
|
|
840
|
-
|
|
848
|
+
for (int i = 0; i < delayConditions.length(); i++) {
|
|
849
|
+
final JSONObject object = delayConditions.optJSONObject(i);
|
|
850
|
+
if (object != null && object.optString("kind").equals("background") && object.optString("value").isEmpty()) {
|
|
851
|
+
object.put("value", "0");
|
|
852
|
+
delayConditions.put(i, object);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (this.delayUpdateUtils.setMultiDelay(delayConditions.toString())) {
|
|
841
857
|
call.resolve();
|
|
842
858
|
} else {
|
|
843
859
|
call.reject("Failed to delay update");
|
|
@@ -848,95 +864,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
848
864
|
}
|
|
849
865
|
}
|
|
850
866
|
|
|
851
|
-
private Boolean _setMultiDelay(String delayConditions) {
|
|
852
|
-
try {
|
|
853
|
-
this.editor.putString(DELAY_CONDITION_PREFERENCES, delayConditions);
|
|
854
|
-
this.editor.commit();
|
|
855
|
-
Log.i(CapacitorUpdater.TAG, "Delay update saved");
|
|
856
|
-
return true;
|
|
857
|
-
} catch (final Exception e) {
|
|
858
|
-
Log.e(CapacitorUpdater.TAG, "Failed to delay update, [Error calling '_setMultiDelay()']", e);
|
|
859
|
-
return false;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
private boolean _cancelDelay(String source) {
|
|
864
|
-
try {
|
|
865
|
-
this.editor.remove(DELAY_CONDITION_PREFERENCES);
|
|
866
|
-
this.editor.commit();
|
|
867
|
-
Log.i(CapacitorUpdater.TAG, "All delays canceled from " + source);
|
|
868
|
-
return true;
|
|
869
|
-
} catch (final Exception e) {
|
|
870
|
-
Log.e(CapacitorUpdater.TAG, "Failed to cancel update delay", e);
|
|
871
|
-
return false;
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
867
|
@PluginMethod
|
|
876
868
|
public void cancelDelay(final PluginCall call) {
|
|
877
|
-
if (this.
|
|
869
|
+
if (this.delayUpdateUtils.cancelDelay("JS")) {
|
|
878
870
|
call.resolve();
|
|
879
871
|
} else {
|
|
880
872
|
call.reject("Failed to cancel delay");
|
|
881
873
|
}
|
|
882
874
|
}
|
|
883
875
|
|
|
884
|
-
private void _checkCancelDelay(Boolean killed) {
|
|
885
|
-
Gson gson = new Gson();
|
|
886
|
-
String delayUpdatePreferences = prefs.getString(DELAY_CONDITION_PREFERENCES, "[]");
|
|
887
|
-
Type type = new TypeToken<ArrayList<DelayCondition>>() {}.getType();
|
|
888
|
-
ArrayList<DelayCondition> delayConditionList = gson.fromJson(delayUpdatePreferences, type);
|
|
889
|
-
for (DelayCondition condition : delayConditionList) {
|
|
890
|
-
String kind = condition.getKind().toString();
|
|
891
|
-
String value = condition.getValue();
|
|
892
|
-
if (!kind.isEmpty()) {
|
|
893
|
-
switch (kind) {
|
|
894
|
-
case "background":
|
|
895
|
-
if (!killed) {
|
|
896
|
-
this._cancelDelay("background check");
|
|
897
|
-
}
|
|
898
|
-
break;
|
|
899
|
-
case "kill":
|
|
900
|
-
if (killed) {
|
|
901
|
-
this._cancelDelay("kill check");
|
|
902
|
-
this.installNext();
|
|
903
|
-
}
|
|
904
|
-
break;
|
|
905
|
-
case "date":
|
|
906
|
-
if (!"".equals(value)) {
|
|
907
|
-
try {
|
|
908
|
-
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
|
|
909
|
-
Date date = sdf.parse(value);
|
|
910
|
-
assert date != null;
|
|
911
|
-
if (new Date().compareTo(date) > 0) {
|
|
912
|
-
this._cancelDelay("date expired");
|
|
913
|
-
}
|
|
914
|
-
} catch (final Exception e) {
|
|
915
|
-
this._cancelDelay("date parsing issue");
|
|
916
|
-
}
|
|
917
|
-
} else {
|
|
918
|
-
this._cancelDelay("delayVal absent");
|
|
919
|
-
}
|
|
920
|
-
break;
|
|
921
|
-
case "nativeVersion":
|
|
922
|
-
if (!"".equals(value)) {
|
|
923
|
-
try {
|
|
924
|
-
final Version versionLimit = new Version(value);
|
|
925
|
-
if (this.currentVersionNative.isAtLeast(versionLimit)) {
|
|
926
|
-
this._cancelDelay("nativeVersion above limit");
|
|
927
|
-
}
|
|
928
|
-
} catch (final Exception e) {
|
|
929
|
-
this._cancelDelay("nativeVersion parsing issue");
|
|
930
|
-
}
|
|
931
|
-
} else {
|
|
932
|
-
this._cancelDelay("delayVal absent");
|
|
933
|
-
}
|
|
934
|
-
break;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
|
|
940
876
|
private Boolean _isAutoUpdateEnabled() {
|
|
941
877
|
final CapConfig config = CapConfig.loadDefault(this.getActivity());
|
|
942
878
|
String serverUrl = config.getServerUrl();
|
|
@@ -1107,7 +1043,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1107
1043
|
);
|
|
1108
1044
|
if (CapacitorUpdaterPlugin.this.implementation.directUpdate) {
|
|
1109
1045
|
Gson gson = new Gson();
|
|
1110
|
-
String delayUpdatePreferences = prefs.getString(DELAY_CONDITION_PREFERENCES, "[]");
|
|
1046
|
+
String delayUpdatePreferences = prefs.getString(DelayUpdateUtils.DELAY_CONDITION_PREFERENCES, "[]");
|
|
1111
1047
|
Type type = new TypeToken<ArrayList<DelayCondition>>() {}.getType();
|
|
1112
1048
|
ArrayList<DelayCondition> delayConditionList = gson.fromJson(delayUpdatePreferences, type);
|
|
1113
1049
|
if (delayConditionList != null && !delayConditionList.isEmpty()) {
|
|
@@ -1221,7 +1157,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1221
1157
|
private void installNext() {
|
|
1222
1158
|
try {
|
|
1223
1159
|
Gson gson = new Gson();
|
|
1224
|
-
String delayUpdatePreferences = prefs.getString(DELAY_CONDITION_PREFERENCES, "[]");
|
|
1160
|
+
String delayUpdatePreferences = prefs.getString(DelayUpdateUtils.DELAY_CONDITION_PREFERENCES, "[]");
|
|
1225
1161
|
Type type = new TypeToken<ArrayList<DelayCondition>>() {}.getType();
|
|
1226
1162
|
ArrayList<DelayCondition> delayConditionList = gson.fromJson(delayUpdatePreferences, type);
|
|
1227
1163
|
if (delayConditionList != null && !delayConditionList.isEmpty()) {
|
|
@@ -1302,7 +1238,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1302
1238
|
public void appMovedToForeground() {
|
|
1303
1239
|
final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
|
|
1304
1240
|
CapacitorUpdaterPlugin.this.implementation.sendStats("app_moved_to_foreground", current.getVersionName());
|
|
1305
|
-
this.
|
|
1241
|
+
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.FOREGROUND);
|
|
1242
|
+
this.delayUpdateUtils.unsetBackgroundTimestamp();
|
|
1306
1243
|
if (
|
|
1307
1244
|
CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() &&
|
|
1308
1245
|
(this.backgroundDownloadTask == null || !this.backgroundDownloadTask.isAlive())
|
|
@@ -1320,35 +1257,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1320
1257
|
CapacitorUpdaterPlugin.this.implementation.sendStats("app_moved_to_background", current.getVersionName());
|
|
1321
1258
|
Log.i(CapacitorUpdater.TAG, "Checking for pending update");
|
|
1322
1259
|
try {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
String backgroundValue = null;
|
|
1328
|
-
for (DelayCondition delayCondition : delayConditionList) {
|
|
1329
|
-
if (delayCondition.getKind().toString().equals("background")) {
|
|
1330
|
-
String value = delayCondition.getValue();
|
|
1331
|
-
backgroundValue = (value != null && !value.isEmpty()) ? value : "0";
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
if (backgroundValue != null) {
|
|
1335
|
-
taskRunning = true;
|
|
1336
|
-
final Long timeout = Long.parseLong(backgroundValue);
|
|
1337
|
-
if (backgroundTask != null) {
|
|
1338
|
-
backgroundTask.interrupt();
|
|
1339
|
-
}
|
|
1340
|
-
backgroundTask = startNewThread(
|
|
1341
|
-
() -> {
|
|
1342
|
-
taskRunning = false;
|
|
1343
|
-
_checkCancelDelay(false);
|
|
1344
|
-
installNext();
|
|
1345
|
-
},
|
|
1346
|
-
timeout
|
|
1347
|
-
);
|
|
1348
|
-
} else {
|
|
1349
|
-
this._checkCancelDelay(false);
|
|
1350
|
-
this.installNext();
|
|
1351
|
-
}
|
|
1260
|
+
// We need to set "backgrounded time"
|
|
1261
|
+
this.delayUpdateUtils.setBackgroundTimestamp(System.currentTimeMillis());
|
|
1262
|
+
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.BACKGROUND);
|
|
1263
|
+
this.installNext();
|
|
1352
1264
|
} catch (final Exception e) {
|
|
1353
1265
|
Log.e(CapacitorUpdater.TAG, "Error during onActivityStopped", e);
|
|
1354
1266
|
}
|
|
@@ -1379,7 +1291,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1379
1291
|
|
|
1380
1292
|
private void appKilled() {
|
|
1381
1293
|
Log.d(CapacitorUpdater.TAG, "onActivityDestroyed: all activity destroyed");
|
|
1382
|
-
this.
|
|
1294
|
+
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.KILLED);
|
|
1383
1295
|
}
|
|
1384
1296
|
|
|
1385
1297
|
@Override
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
package ee.forgr.capacitor_updater;
|
|
2
|
+
|
|
3
|
+
import android.content.SharedPreferences;
|
|
4
|
+
import android.util.Log;
|
|
5
|
+
import com.google.common.reflect.TypeToken;
|
|
6
|
+
import com.google.gson.Gson;
|
|
7
|
+
import io.github.g00fy2.versioncompare.Version;
|
|
8
|
+
import java.lang.reflect.Type;
|
|
9
|
+
import java.text.SimpleDateFormat;
|
|
10
|
+
import java.util.ArrayList;
|
|
11
|
+
import java.util.Date;
|
|
12
|
+
|
|
13
|
+
public class DelayUpdateUtils {
|
|
14
|
+
|
|
15
|
+
public static final String DELAY_CONDITION_PREFERENCES = "DELAY_CONDITION_PREFERENCES_CAPGO";
|
|
16
|
+
public static final String BACKGROUND_TIMESTAMP_KEY = "BACKGROUND_TIMESTAMP_KEY_CAPGO";
|
|
17
|
+
|
|
18
|
+
private final SharedPreferences prefs;
|
|
19
|
+
private final SharedPreferences.Editor editor;
|
|
20
|
+
private final Version currentVersionNative;
|
|
21
|
+
private final Runnable installNext;
|
|
22
|
+
|
|
23
|
+
public DelayUpdateUtils(SharedPreferences prefs, SharedPreferences.Editor editor, Version currentVersionNative, Runnable installNext) {
|
|
24
|
+
this.prefs = prefs;
|
|
25
|
+
this.editor = editor;
|
|
26
|
+
this.currentVersionNative = currentVersionNative;
|
|
27
|
+
this.installNext = installNext;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public enum CancelDelaySource {
|
|
31
|
+
KILLED,
|
|
32
|
+
BACKGROUND,
|
|
33
|
+
FOREGROUND
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public void checkCancelDelay(CancelDelaySource source) {
|
|
37
|
+
Gson gson = new Gson();
|
|
38
|
+
String delayUpdatePreferences = prefs.getString(DELAY_CONDITION_PREFERENCES, "[]");
|
|
39
|
+
Type type = new TypeToken<ArrayList<DelayCondition>>() {}.getType();
|
|
40
|
+
ArrayList<DelayCondition> delayConditionList = gson.fromJson(delayUpdatePreferences, type);
|
|
41
|
+
ArrayList<DelayCondition> delayConditionListToKeep = new ArrayList<>(delayConditionList.size());
|
|
42
|
+
int index = 0;
|
|
43
|
+
|
|
44
|
+
for (DelayCondition condition : delayConditionList) {
|
|
45
|
+
DelayUntilNext kind = condition.getKind();
|
|
46
|
+
String value = condition.getValue();
|
|
47
|
+
switch (kind) {
|
|
48
|
+
case DelayUntilNext.background:
|
|
49
|
+
if (source == CancelDelaySource.FOREGROUND) {
|
|
50
|
+
long backgroundedAt = getBackgroundTimestamp();
|
|
51
|
+
long now = System.currentTimeMillis();
|
|
52
|
+
long delta = Math.max(0, now - backgroundedAt);
|
|
53
|
+
long longValue = 0L;
|
|
54
|
+
try {
|
|
55
|
+
longValue = Long.parseLong(value);
|
|
56
|
+
} catch (NumberFormatException e) {
|
|
57
|
+
Log.e(
|
|
58
|
+
CapacitorUpdater.TAG,
|
|
59
|
+
"Background condition (value: " +
|
|
60
|
+
value +
|
|
61
|
+
") had an invalid value at index " +
|
|
62
|
+
index +
|
|
63
|
+
". We will likely remove it."
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (delta > longValue) {
|
|
68
|
+
Log.i(
|
|
69
|
+
CapacitorUpdater.TAG,
|
|
70
|
+
"Background condition (value: " +
|
|
71
|
+
value +
|
|
72
|
+
") deleted at index " +
|
|
73
|
+
index +
|
|
74
|
+
". Delta: " +
|
|
75
|
+
delta +
|
|
76
|
+
", longValue: " +
|
|
77
|
+
longValue
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
delayConditionListToKeep.add(condition);
|
|
82
|
+
Log.i(
|
|
83
|
+
CapacitorUpdater.TAG,
|
|
84
|
+
"Background delay (value: " +
|
|
85
|
+
value +
|
|
86
|
+
") condition kept at index " +
|
|
87
|
+
index +
|
|
88
|
+
" (source: " +
|
|
89
|
+
source.toString() +
|
|
90
|
+
")"
|
|
91
|
+
);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case DelayUntilNext.kill:
|
|
95
|
+
if (source == CancelDelaySource.KILLED) {
|
|
96
|
+
this.installNext.run();
|
|
97
|
+
} else {
|
|
98
|
+
delayConditionListToKeep.add(condition);
|
|
99
|
+
Log.i(
|
|
100
|
+
CapacitorUpdater.TAG,
|
|
101
|
+
"Kill delay (value: " + value + ") condition kept at index " + index + " (source: " + source.toString() + ")"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case DelayUntilNext.date:
|
|
106
|
+
if (!"".equals(value)) {
|
|
107
|
+
try {
|
|
108
|
+
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
|
|
109
|
+
Date date = sdf.parse(value);
|
|
110
|
+
assert date != null;
|
|
111
|
+
if (new Date().compareTo(date) > 0) {
|
|
112
|
+
Log.i(
|
|
113
|
+
CapacitorUpdater.TAG,
|
|
114
|
+
"Date delay (value: " + value + ") condition removed due to expired date at index " + index
|
|
115
|
+
);
|
|
116
|
+
} else {
|
|
117
|
+
delayConditionListToKeep.add(condition);
|
|
118
|
+
Log.i(CapacitorUpdater.TAG, "Date delay (value: " + value + ") condition kept at index " + index);
|
|
119
|
+
}
|
|
120
|
+
} catch (final Exception e) {
|
|
121
|
+
Log.e(
|
|
122
|
+
CapacitorUpdater.TAG,
|
|
123
|
+
"Date delay (value: " + value + ") condition removed due to parsing issue at index " + index,
|
|
124
|
+
e
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
Log.d(
|
|
129
|
+
CapacitorUpdater.TAG,
|
|
130
|
+
"Date delay (value: " + value + ") condition removed due to empty value at index " + index
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
case DelayUntilNext.nativeVersion:
|
|
135
|
+
if (!"".equals(value)) {
|
|
136
|
+
try {
|
|
137
|
+
final Version versionLimit = new Version(value);
|
|
138
|
+
if (this.currentVersionNative.isAtLeast(versionLimit)) {
|
|
139
|
+
Log.i(
|
|
140
|
+
CapacitorUpdater.TAG,
|
|
141
|
+
"Native version delay (value: " + value + ") condition removed due to above limit at index " + index
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
delayConditionListToKeep.add(condition);
|
|
145
|
+
Log.i(CapacitorUpdater.TAG, "Native version delay (value: " + value + ") condition kept at index " + index);
|
|
146
|
+
}
|
|
147
|
+
} catch (final Exception e) {
|
|
148
|
+
Log.e(
|
|
149
|
+
CapacitorUpdater.TAG,
|
|
150
|
+
"Native version delay (value: " + value + ") condition removed due to parsing issue at index " + index,
|
|
151
|
+
e
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
Log.d(
|
|
156
|
+
CapacitorUpdater.TAG,
|
|
157
|
+
"Native version delay (value: " + value + ") condition removed due to empty value at index " + index
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
index++;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!delayConditionListToKeep.isEmpty()) {
|
|
166
|
+
this.setMultiDelay(gson.toJson(delayConditionListToKeep));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public Boolean setMultiDelay(String delayConditions) {
|
|
171
|
+
try {
|
|
172
|
+
this.editor.putString(DELAY_CONDITION_PREFERENCES, delayConditions);
|
|
173
|
+
this.editor.commit();
|
|
174
|
+
Log.i(CapacitorUpdater.TAG, "Delay update saved");
|
|
175
|
+
return true;
|
|
176
|
+
} catch (final Exception e) {
|
|
177
|
+
Log.e(CapacitorUpdater.TAG, "Failed to delay update, [Error calling '_setMultiDelay()']", e);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public void setBackgroundTimestamp(long backgroundTimestamp) {
|
|
183
|
+
try {
|
|
184
|
+
this.editor.putLong(BACKGROUND_TIMESTAMP_KEY, backgroundTimestamp);
|
|
185
|
+
this.editor.commit();
|
|
186
|
+
Log.i(CapacitorUpdater.TAG, "Delay update saved");
|
|
187
|
+
} catch (final Exception e) {
|
|
188
|
+
Log.e(CapacitorUpdater.TAG, "Failed to delay update, [Error calling '_setBackgroundTimestamp()']", e);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public void unsetBackgroundTimestamp() {
|
|
193
|
+
try {
|
|
194
|
+
this.editor.remove(BACKGROUND_TIMESTAMP_KEY);
|
|
195
|
+
this.editor.commit();
|
|
196
|
+
Log.i(CapacitorUpdater.TAG, "Delay update saved");
|
|
197
|
+
} catch (final Exception e) {
|
|
198
|
+
Log.e(CapacitorUpdater.TAG, "Failed to delay update, [Error calling '_unsetBackgroundTimestamp()']", e);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private long getBackgroundTimestamp() {
|
|
203
|
+
try {
|
|
204
|
+
return this.prefs.getLong(BACKGROUND_TIMESTAMP_KEY, 0);
|
|
205
|
+
} catch (final Exception e) {
|
|
206
|
+
Log.e(CapacitorUpdater.TAG, "Failed to delay update, [Error calling '_getBackgroundTimestamp()']", e);
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public boolean cancelDelay(String source) {
|
|
212
|
+
try {
|
|
213
|
+
this.editor.remove(DELAY_CONDITION_PREFERENCES);
|
|
214
|
+
this.editor.commit();
|
|
215
|
+
Log.i(CapacitorUpdater.TAG, "All delays canceled from " + source);
|
|
216
|
+
return true;
|
|
217
|
+
} catch (final Exception e) {
|
|
218
|
+
Log.e(CapacitorUpdater.TAG, "Failed to cancel update delay", e);
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -45,11 +45,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
45
45
|
CAPPluginMethod(name: "getNextBundle", returnType: CAPPluginReturnPromise)
|
|
46
46
|
]
|
|
47
47
|
public var implementation = CapacitorUpdater()
|
|
48
|
-
private let PLUGIN_VERSION: String = "7.2.
|
|
48
|
+
private let PLUGIN_VERSION: String = "7.2.17"
|
|
49
49
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
50
50
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
51
51
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
52
|
-
|
|
52
|
+
// Note: DELAY_CONDITION_PREFERENCES is now defined in DelayUpdateUtils.DELAY_CONDITION_PREFERENCES
|
|
53
53
|
private var updateUrl = ""
|
|
54
54
|
private var statsUrl = ""
|
|
55
55
|
private var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
|
|
@@ -67,6 +67,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
67
67
|
private var periodCheckDelay = 0
|
|
68
68
|
let semaphoreReady = DispatchSemaphore(value: 0)
|
|
69
69
|
|
|
70
|
+
private var delayUpdateUtils: DelayUpdateUtils!
|
|
71
|
+
|
|
70
72
|
override public func load() {
|
|
71
73
|
#if targetEnvironment(simulator)
|
|
72
74
|
print("\(CapacitorUpdater.TAG) ::::: SIMULATOR :::::")
|
|
@@ -109,6 +111,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
109
111
|
implementation.publicKey = getConfig().getString("publicKey", "")!
|
|
110
112
|
implementation.notifyDownloadRaw = notifyDownload
|
|
111
113
|
implementation.PLUGIN_VERSION = self.PLUGIN_VERSION
|
|
114
|
+
|
|
115
|
+
// Initialize DelayUpdateUtils
|
|
116
|
+
self.delayUpdateUtils = DelayUpdateUtils(currentVersionNative: currentVersionNative, installNext: { [weak self] in
|
|
117
|
+
self?.installNext()
|
|
118
|
+
})
|
|
112
119
|
let config = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor().legacyConfig
|
|
113
120
|
implementation.appId = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? ""
|
|
114
121
|
implementation.appId = config?["appId"] as? String ?? implementation.appId
|
|
@@ -291,6 +298,18 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
291
298
|
DispatchQueue.global(qos: .background).async {
|
|
292
299
|
do {
|
|
293
300
|
let next = try self.implementation.download(url: url!, version: version, sessionKey: sessionKey)
|
|
301
|
+
// If public key is present but no checksum provided, refuse installation
|
|
302
|
+
if self.implementation.publicKey != "" && checksum == "" {
|
|
303
|
+
print("\(CapacitorUpdater.TAG) Public key present but no checksum provided")
|
|
304
|
+
self.implementation.sendStats(action: "checksum_required", versionName: next.getVersionName())
|
|
305
|
+
let id = next.getId()
|
|
306
|
+
let resDel = self.implementation.delete(id: id)
|
|
307
|
+
if !resDel {
|
|
308
|
+
print("\(CapacitorUpdater.TAG) Delete failed, id \(id) doesn't exist")
|
|
309
|
+
}
|
|
310
|
+
throw ObjectSavableError.checksum
|
|
311
|
+
}
|
|
312
|
+
|
|
294
313
|
checksum = try CryptoCipherV2.decryptChecksum(checksum: checksum, publicKey: self.implementation.publicKey)
|
|
295
314
|
if (checksum != "" || self.implementation.publicKey != "") && next.getChecksum() != checksum {
|
|
296
315
|
print("\(CapacitorUpdater.TAG) Error checksum", next.getChecksum(), checksum)
|
|
@@ -549,96 +568,45 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
549
568
|
call.reject("setMultiDelay called without delayCondition")
|
|
550
569
|
return
|
|
551
570
|
}
|
|
552
|
-
let delayConditions: String = toJson(object: delayConditionList)
|
|
553
|
-
if _setMultiDelay(delayConditions: delayConditions) {
|
|
554
|
-
call.resolve()
|
|
555
|
-
} else {
|
|
556
|
-
call.reject("Failed to delay update")
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
571
|
|
|
560
|
-
|
|
561
|
-
if
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
572
|
+
// Handle background conditions with empty value (set to "0")
|
|
573
|
+
if var modifiableList = delayConditionList as? [[String: Any]] {
|
|
574
|
+
for i in 0..<modifiableList.count {
|
|
575
|
+
if let kind = modifiableList[i]["kind"] as? String,
|
|
576
|
+
kind == "background",
|
|
577
|
+
let value = modifiableList[i]["value"] as? String,
|
|
578
|
+
value.isEmpty {
|
|
579
|
+
modifiableList[i]["value"] = "0"
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
let delayConditions: String = toJson(object: modifiableList)
|
|
583
|
+
if delayUpdateUtils.setMultiDelay(delayConditions: delayConditions) {
|
|
584
|
+
call.resolve()
|
|
585
|
+
} else {
|
|
586
|
+
call.reject("Failed to delay update")
|
|
587
|
+
}
|
|
566
588
|
} else {
|
|
567
|
-
|
|
568
|
-
|
|
589
|
+
let delayConditions: String = toJson(object: delayConditionList)
|
|
590
|
+
if delayUpdateUtils.setMultiDelay(delayConditions: delayConditions) {
|
|
591
|
+
call.resolve()
|
|
592
|
+
} else {
|
|
593
|
+
call.reject("Failed to delay update")
|
|
594
|
+
}
|
|
569
595
|
}
|
|
570
596
|
}
|
|
571
597
|
|
|
572
|
-
|
|
573
|
-
print("\(CapacitorUpdater.TAG) delay Canceled from \(source)")
|
|
574
|
-
UserDefaults.standard.removeObject(forKey: DELAY_CONDITION_PREFERENCES)
|
|
575
|
-
UserDefaults.standard.synchronize()
|
|
576
|
-
}
|
|
598
|
+
// Note: _setMultiDelay and _cancelDelay methods have been moved to DelayUpdateUtils class
|
|
577
599
|
|
|
578
600
|
@objc func cancelDelay(_ call: CAPPluginCall) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
private func _checkCancelDelay(killed: Bool) {
|
|
584
|
-
let delayUpdatePreferences = UserDefaults.standard.string(forKey: DELAY_CONDITION_PREFERENCES) ?? "[]"
|
|
585
|
-
let delayConditionList: [DelayCondition] = fromJsonArr(json: delayUpdatePreferences).map { obj -> DelayCondition in
|
|
586
|
-
let kind: String = obj.value(forKey: "kind") as! String
|
|
587
|
-
let value: String? = obj.value(forKey: "value") as? String
|
|
588
|
-
return DelayCondition(kind: kind, value: value)
|
|
589
|
-
}
|
|
590
|
-
for condition in delayConditionList {
|
|
591
|
-
let kind: String? = condition.getKind()
|
|
592
|
-
let value: String? = condition.getValue()
|
|
593
|
-
if kind != nil {
|
|
594
|
-
switch kind {
|
|
595
|
-
case "background":
|
|
596
|
-
if !killed {
|
|
597
|
-
self._cancelDelay(source: "background check")
|
|
598
|
-
}
|
|
599
|
-
case "kill":
|
|
600
|
-
if killed {
|
|
601
|
-
self._cancelDelay(source: "kill check")
|
|
602
|
-
// instant install for kill action
|
|
603
|
-
self.installNext()
|
|
604
|
-
}
|
|
605
|
-
case "date":
|
|
606
|
-
if value != nil && value != "" {
|
|
607
|
-
let dateFormatter = ISO8601DateFormatter()
|
|
608
|
-
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
609
|
-
guard let ExpireDate = dateFormatter.date(from: value!) else {
|
|
610
|
-
self._cancelDelay(source: "date parsing issue")
|
|
611
|
-
return
|
|
612
|
-
}
|
|
613
|
-
if ExpireDate < Date() {
|
|
614
|
-
self._cancelDelay(source: "date expired")
|
|
615
|
-
}
|
|
616
|
-
} else {
|
|
617
|
-
self._cancelDelay(source: "delayVal absent")
|
|
618
|
-
}
|
|
619
|
-
case "nativeVersion":
|
|
620
|
-
if value != nil && value != "" {
|
|
621
|
-
do {
|
|
622
|
-
let versionLimit = try Version(value!)
|
|
623
|
-
if self.currentVersionNative >= versionLimit {
|
|
624
|
-
self._cancelDelay(source: "nativeVersion above limit")
|
|
625
|
-
}
|
|
626
|
-
} catch {
|
|
627
|
-
self._cancelDelay(source: "nativeVersion parsing issue")
|
|
628
|
-
}
|
|
629
|
-
} else {
|
|
630
|
-
self._cancelDelay(source: "delayVal absent")
|
|
631
|
-
}
|
|
632
|
-
case .none:
|
|
633
|
-
print("\(CapacitorUpdater.TAG) _checkCancelDelay switch case none error")
|
|
634
|
-
case .some:
|
|
635
|
-
print("\(CapacitorUpdater.TAG) _checkCancelDelay switch case some error")
|
|
636
|
-
}
|
|
637
|
-
}
|
|
601
|
+
if delayUpdateUtils.cancelDelay(source: "JS") {
|
|
602
|
+
call.resolve()
|
|
603
|
+
} else {
|
|
604
|
+
call.reject("Failed to cancel delay")
|
|
638
605
|
}
|
|
639
|
-
// self.checkAppReady() why this here?
|
|
640
606
|
}
|
|
641
607
|
|
|
608
|
+
// Note: _checkCancelDelay method has been moved to DelayUpdateUtils class
|
|
609
|
+
|
|
642
610
|
private func _isAutoUpdateEnabled() -> Bool {
|
|
643
611
|
let instanceDescriptor = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor()
|
|
644
612
|
if instanceDescriptor?.serverURL != nil {
|
|
@@ -818,10 +786,20 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
818
786
|
return
|
|
819
787
|
}
|
|
820
788
|
if self.directUpdate {
|
|
789
|
+
let delayUpdatePreferences = UserDefaults.standard.string(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES) ?? "[]"
|
|
790
|
+
let delayConditionList: [DelayCondition] = self.fromJsonArr(json: delayUpdatePreferences).map { obj -> DelayCondition in
|
|
791
|
+
let kind: String = obj.value(forKey: "kind") as! String
|
|
792
|
+
let value: String? = obj.value(forKey: "value") as? String
|
|
793
|
+
return DelayCondition(kind: kind, value: value)
|
|
794
|
+
}
|
|
795
|
+
if !delayConditionList.isEmpty {
|
|
796
|
+
print("\(CapacitorUpdater.TAG) Update delayed until delay conditions met")
|
|
797
|
+
self.endBackGroundTaskWithNotif(msg: "Update delayed until delay conditions met", latestVersionName: latestVersionName, current: next, error: false)
|
|
798
|
+
return
|
|
799
|
+
}
|
|
821
800
|
_ = self.implementation.set(bundle: next)
|
|
822
801
|
_ = self._reload()
|
|
823
|
-
self.
|
|
824
|
-
self.endBackGroundTaskWithNotif(msg: "update installed", latestVersionName: latestVersionName, current: current, error: false)
|
|
802
|
+
self.endBackGroundTaskWithNotif(msg: "update installed", latestVersionName: latestVersionName, current: next, error: false)
|
|
825
803
|
} else {
|
|
826
804
|
self.notifyListeners("updateAvailable", data: ["bundle": next.toJSON()])
|
|
827
805
|
_ = self.implementation.setNextBundle(next: next.getId())
|
|
@@ -844,17 +822,17 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
844
822
|
|
|
845
823
|
@objc func appKilled() {
|
|
846
824
|
print("\(CapacitorUpdater.TAG) onActivityDestroyed: all activity destroyed")
|
|
847
|
-
self.
|
|
825
|
+
self.delayUpdateUtils.checkCancelDelay(source: .killed)
|
|
848
826
|
}
|
|
849
827
|
|
|
850
828
|
private func installNext() {
|
|
851
|
-
let delayUpdatePreferences = UserDefaults.standard.string(forKey: DELAY_CONDITION_PREFERENCES) ?? "[]"
|
|
852
|
-
let delayConditionList: [DelayCondition]
|
|
829
|
+
let delayUpdatePreferences = UserDefaults.standard.string(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES) ?? "[]"
|
|
830
|
+
let delayConditionList: [DelayCondition] = fromJsonArr(json: delayUpdatePreferences).map { obj -> DelayCondition in
|
|
853
831
|
let kind: String = obj.value(forKey: "kind") as! String
|
|
854
832
|
let value: String? = obj.value(forKey: "value") as? String
|
|
855
833
|
return DelayCondition(kind: kind, value: value)
|
|
856
834
|
}
|
|
857
|
-
if delayConditionList
|
|
835
|
+
if !delayConditionList.isEmpty {
|
|
858
836
|
print("\(CapacitorUpdater.TAG) Update delayed until delay conditions met")
|
|
859
837
|
return
|
|
860
838
|
}
|
|
@@ -893,6 +871,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
893
871
|
@objc func appMovedToForeground() {
|
|
894
872
|
let current: BundleInfo = self.implementation.getCurrentBundle()
|
|
895
873
|
self.implementation.sendStats(action: "app_moved_to_foreground", versionName: current.getVersionName())
|
|
874
|
+
self.delayUpdateUtils.checkCancelDelay(source: .foreground)
|
|
875
|
+
self.delayUpdateUtils.unsetBackgroundTimestamp()
|
|
896
876
|
if backgroundWork != nil && taskRunning {
|
|
897
877
|
backgroundWork!.cancel()
|
|
898
878
|
print("\(CapacitorUpdater.TAG) Background Timer Task canceled, Activity resumed before timer completes")
|
|
@@ -929,38 +909,15 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
929
909
|
}
|
|
930
910
|
|
|
931
911
|
@objc func appMovedToBackground() {
|
|
932
|
-
self.implementation.
|
|
912
|
+
let current: BundleInfo = self.implementation.getCurrentBundle()
|
|
913
|
+
self.implementation.sendStats(action: "app_moved_to_background", versionName: current.getVersionName())
|
|
933
914
|
print("\(CapacitorUpdater.TAG) Check for pending update")
|
|
934
|
-
let delayUpdatePreferences = UserDefaults.standard.string(forKey: DELAY_CONDITION_PREFERENCES) ?? "[]"
|
|
935
|
-
|
|
936
|
-
let delayConditionList: [DelayCondition] = fromJsonArr(json: delayUpdatePreferences).map { obj -> DelayCondition in
|
|
937
|
-
let kind: String = obj.value(forKey: "kind") as! String
|
|
938
|
-
let value: String? = obj.value(forKey: "value") as? String
|
|
939
|
-
return DelayCondition(kind: kind, value: value)
|
|
940
|
-
}
|
|
941
|
-
var backgroundValue: String?
|
|
942
|
-
for delayCondition in delayConditionList {
|
|
943
|
-
if delayCondition.getKind() == "background" {
|
|
944
|
-
let value: String? = delayCondition.getValue()
|
|
945
|
-
backgroundValue = (value != nil && value != "") ? value! : "0"
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
if backgroundValue != nil {
|
|
949
|
-
self.taskRunning = true
|
|
950
|
-
let interval: Double = (Double(backgroundValue!) ?? 0.0) / 1000
|
|
951
|
-
self.backgroundWork?.cancel()
|
|
952
|
-
self.backgroundWork = DispatchWorkItem(block: {
|
|
953
|
-
// IOS never executes this task in background
|
|
954
|
-
self.taskRunning = false
|
|
955
|
-
self._checkCancelDelay(killed: false)
|
|
956
|
-
self.installNext()
|
|
957
|
-
})
|
|
958
|
-
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + interval, execute: self.backgroundWork!)
|
|
959
|
-
} else {
|
|
960
|
-
self._checkCancelDelay(killed: false)
|
|
961
|
-
self.installNext()
|
|
962
|
-
}
|
|
963
915
|
|
|
916
|
+
// Set background timestamp
|
|
917
|
+
let backgroundTimestamp = Int64(Date().timeIntervalSince1970 * 1000) // Convert to milliseconds
|
|
918
|
+
self.delayUpdateUtils.setBackgroundTimestamp(backgroundTimestamp)
|
|
919
|
+
self.delayUpdateUtils.checkCancelDelay(source: .background)
|
|
920
|
+
self.installNext()
|
|
964
921
|
}
|
|
965
922
|
|
|
966
923
|
@objc func getNextBundle(_ call: CAPPluginCall) {
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
//
|
|
8
|
+
// DelayUpdateUtils.swift
|
|
9
|
+
// Plugin
|
|
10
|
+
//
|
|
11
|
+
// Created by Auto-generated based on Android implementation
|
|
12
|
+
// Copyright © 2024 Capgo. All rights reserved.
|
|
13
|
+
//
|
|
14
|
+
|
|
15
|
+
import Foundation
|
|
16
|
+
import Version
|
|
17
|
+
|
|
18
|
+
public class DelayUpdateUtils {
|
|
19
|
+
|
|
20
|
+
static let DELAY_CONDITION_PREFERENCES = "DELAY_CONDITION_PREFERENCES_CAPGO"
|
|
21
|
+
static let BACKGROUND_TIMESTAMP_KEY = "BACKGROUND_TIMESTAMP_KEY_CAPGO"
|
|
22
|
+
|
|
23
|
+
private let currentVersionNative: Version
|
|
24
|
+
private let installNext: () -> Void
|
|
25
|
+
|
|
26
|
+
public enum CancelDelaySource {
|
|
27
|
+
case killed
|
|
28
|
+
case background
|
|
29
|
+
case foreground
|
|
30
|
+
|
|
31
|
+
var description: String {
|
|
32
|
+
switch self {
|
|
33
|
+
case .killed: return "KILLED"
|
|
34
|
+
case .background: return "BACKGROUND"
|
|
35
|
+
case .foreground: return "FOREGROUND"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public init(currentVersionNative: Version, installNext: @escaping () -> Void) {
|
|
41
|
+
self.currentVersionNative = currentVersionNative
|
|
42
|
+
self.installNext = installNext
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public func checkCancelDelay(source: CancelDelaySource) {
|
|
46
|
+
let delayUpdatePreferences = UserDefaults.standard.string(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES) ?? "[]"
|
|
47
|
+
let delayConditionList: [DelayCondition] = fromJsonArr(json: delayUpdatePreferences).map { obj -> DelayCondition in
|
|
48
|
+
let kind: String = obj.value(forKey: "kind") as! String
|
|
49
|
+
let value: String? = obj.value(forKey: "value") as? String
|
|
50
|
+
return DelayCondition(kind: kind, value: value)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
var delayConditionListToKeep: [DelayCondition] = []
|
|
54
|
+
var index = 0
|
|
55
|
+
|
|
56
|
+
for condition in delayConditionList {
|
|
57
|
+
let kind = condition.getKind()
|
|
58
|
+
let value = condition.getValue()
|
|
59
|
+
|
|
60
|
+
switch kind {
|
|
61
|
+
case "background":
|
|
62
|
+
if source == .foreground {
|
|
63
|
+
let backgroundedAt = getBackgroundTimestamp()
|
|
64
|
+
let now = Int64(Date().timeIntervalSince1970 * 1000) // Convert to milliseconds
|
|
65
|
+
let delta = max(0, now - backgroundedAt)
|
|
66
|
+
|
|
67
|
+
var longValue: Int64 = 0
|
|
68
|
+
if let value = value, !value.isEmpty {
|
|
69
|
+
longValue = Int64(value) ?? 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if delta > longValue {
|
|
73
|
+
print("\(CapacitorUpdater.TAG) Background condition (value: \(value ?? "")) deleted at index \(index). Delta: \(delta), longValue: \(longValue)")
|
|
74
|
+
} else {
|
|
75
|
+
delayConditionListToKeep.append(condition)
|
|
76
|
+
print("\(CapacitorUpdater.TAG) Background delay (value: \(value ?? "")) condition kept at index \(index) (source: \(source.description))")
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
delayConditionListToKeep.append(condition)
|
|
80
|
+
print("\(CapacitorUpdater.TAG) Background delay (value: \(value ?? "")) condition kept at index \(index) (source: \(source.description))")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case "kill":
|
|
84
|
+
if source == .killed {
|
|
85
|
+
self.installNext()
|
|
86
|
+
} else {
|
|
87
|
+
delayConditionListToKeep.append(condition)
|
|
88
|
+
print("\(CapacitorUpdater.TAG) Kill delay (value: \(value ?? "")) condition kept at index \(index) (source: \(source.description))")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
case "date":
|
|
92
|
+
if let value = value, !value.isEmpty {
|
|
93
|
+
do {
|
|
94
|
+
let dateFormatter = ISO8601DateFormatter()
|
|
95
|
+
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
96
|
+
|
|
97
|
+
if let date = dateFormatter.date(from: value) {
|
|
98
|
+
if Date() > date {
|
|
99
|
+
print("\(CapacitorUpdater.TAG) Date delay (value: \(value)) condition removed due to expired date at index \(index)")
|
|
100
|
+
} else {
|
|
101
|
+
delayConditionListToKeep.append(condition)
|
|
102
|
+
print("\(CapacitorUpdater.TAG) Date delay (value: \(value)) condition kept at index \(index)")
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
print("\(CapacitorUpdater.TAG) Date delay (value: \(value)) condition removed due to parsing issue at index \(index)")
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
print("\(CapacitorUpdater.TAG) Date delay (value: \(value)) condition removed due to parsing issue at index \(index): \(error)")
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
print("\(CapacitorUpdater.TAG) Date delay (value: \(value ?? "")) condition removed due to empty value at index \(index)")
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
case "nativeVersion":
|
|
115
|
+
if let value = value, !value.isEmpty {
|
|
116
|
+
do {
|
|
117
|
+
let versionLimit = try Version(value)
|
|
118
|
+
if currentVersionNative >= versionLimit {
|
|
119
|
+
print("\(CapacitorUpdater.TAG) Native version delay (value: \(value)) condition removed due to above limit at index \(index)")
|
|
120
|
+
} else {
|
|
121
|
+
delayConditionListToKeep.append(condition)
|
|
122
|
+
print("\(CapacitorUpdater.TAG) Native version delay (value: \(value)) condition kept at index \(index)")
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
print("\(CapacitorUpdater.TAG) Native version delay (value: \(value)) condition removed due to parsing issue at index \(index): \(error)")
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
print("\(CapacitorUpdater.TAG) Native version delay (value: \(value ?? "")) condition removed due to empty value at index \(index)")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
default:
|
|
132
|
+
print("\(CapacitorUpdater.TAG) Unknown delay condition kind: \(kind) at index \(index)")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
index += 1
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if !delayConditionListToKeep.isEmpty {
|
|
139
|
+
let json = toJson(object: delayConditionListToKeep.map { $0.toJSON() })
|
|
140
|
+
_ = setMultiDelay(delayConditions: json)
|
|
141
|
+
} else {
|
|
142
|
+
// Clear all delay conditions if none are left to keep
|
|
143
|
+
_ = cancelDelay(source: "checkCancelDelay")
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public func setMultiDelay(delayConditions: String) -> Bool {
|
|
148
|
+
do {
|
|
149
|
+
UserDefaults.standard.set(delayConditions, forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES)
|
|
150
|
+
UserDefaults.standard.synchronize()
|
|
151
|
+
print("\(CapacitorUpdater.TAG) Delay update saved")
|
|
152
|
+
return true
|
|
153
|
+
} catch {
|
|
154
|
+
print("\(CapacitorUpdater.TAG) Failed to delay update, [Error calling 'setMultiDelay()']: \(error)")
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public func setBackgroundTimestamp(_ backgroundTimestamp: Int64) {
|
|
160
|
+
do {
|
|
161
|
+
UserDefaults.standard.set(backgroundTimestamp, forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY)
|
|
162
|
+
UserDefaults.standard.synchronize()
|
|
163
|
+
print("\(CapacitorUpdater.TAG) Background timestamp saved")
|
|
164
|
+
} catch {
|
|
165
|
+
print("\(CapacitorUpdater.TAG) Failed to save background timestamp, [Error calling 'setBackgroundTimestamp()']: \(error)")
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public func unsetBackgroundTimestamp() {
|
|
170
|
+
do {
|
|
171
|
+
UserDefaults.standard.removeObject(forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY)
|
|
172
|
+
UserDefaults.standard.synchronize()
|
|
173
|
+
print("\(CapacitorUpdater.TAG) Background timestamp removed")
|
|
174
|
+
} catch {
|
|
175
|
+
print("\(CapacitorUpdater.TAG) Failed to remove background timestamp, [Error calling 'unsetBackgroundTimestamp()']: \(error)")
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private func getBackgroundTimestamp() -> Int64 {
|
|
180
|
+
do {
|
|
181
|
+
let timestamp = UserDefaults.standard.object(forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY) as? Int64 ?? 0
|
|
182
|
+
return timestamp
|
|
183
|
+
} catch {
|
|
184
|
+
print("\(CapacitorUpdater.TAG) Failed to get background timestamp, [Error calling 'getBackgroundTimestamp()']: \(error)")
|
|
185
|
+
return 0
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public func cancelDelay(source: String) -> Bool {
|
|
190
|
+
do {
|
|
191
|
+
UserDefaults.standard.removeObject(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES)
|
|
192
|
+
UserDefaults.standard.synchronize()
|
|
193
|
+
print("\(CapacitorUpdater.TAG) All delays canceled from \(source)")
|
|
194
|
+
return true
|
|
195
|
+
} catch {
|
|
196
|
+
print("\(CapacitorUpdater.TAG) Failed to cancel update delay: \(error)")
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// MARK: - Helper methods
|
|
202
|
+
|
|
203
|
+
private func toJson(object: Any) -> String {
|
|
204
|
+
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
|
|
205
|
+
return ""
|
|
206
|
+
}
|
|
207
|
+
return String(data: data, encoding: String.Encoding.utf8) ?? ""
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private func fromJsonArr(json: String) -> [NSObject] {
|
|
211
|
+
guard let jsonData = json.data(using: .utf8) else {
|
|
212
|
+
return []
|
|
213
|
+
}
|
|
214
|
+
let object = try? JSONSerialization.jsonObject(
|
|
215
|
+
with: jsonData,
|
|
216
|
+
options: .mutableContainers
|
|
217
|
+
) as? [NSObject]
|
|
218
|
+
return object ?? []
|
|
219
|
+
}
|
|
220
|
+
}
|