@capgo/capacitor-updater 7.23.1 → 7.23.13
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 +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +15 -15
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +146 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +8 -3
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +10 -16
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +138 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
[](https://console.algora.io/org/Capgo/bounties?status=completed)
|
|
17
17
|
|
|
18
18
|
<div align="center">
|
|
19
|
-
<h2><a href="https://capgo.app/?ref=
|
|
20
|
-
<h2><a href="https://capgo.app/consulting/?ref=
|
|
19
|
+
<h2><a href="https://capgo.app/?ref=plugin_updater_v7"> ➡️ Get Instant updates for your App with Capgo</a></h2>
|
|
20
|
+
<h2><a href="https://capgo.app/consulting/?ref=plugin_updater_v7"> Missing a feature? We’ll build the plugin for you 💪</a></h2>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
23
|
Capacitor plugin to update your app remotely in real-time.
|
|
@@ -71,7 +71,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
71
71
|
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
72
72
|
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
73
73
|
|
|
74
|
-
private final String PLUGIN_VERSION = "7.23.
|
|
74
|
+
private final String PLUGIN_VERSION = "7.23.13";
|
|
75
75
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
76
76
|
|
|
77
77
|
private SharedPreferences.Editor editor;
|
|
@@ -81,7 +81,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
81
81
|
private Boolean persistModifyUrl = false;
|
|
82
82
|
|
|
83
83
|
private Integer appReadyTimeout = 10000;
|
|
84
|
-
private Integer counterActivityCreate = 0;
|
|
85
84
|
private Integer periodCheckDelay = 0;
|
|
86
85
|
private Boolean autoDeleteFailed = true;
|
|
87
86
|
private Boolean autoDeletePrevious = true;
|
|
@@ -190,7 +189,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
190
189
|
@Override
|
|
191
190
|
public void load() {
|
|
192
191
|
super.load();
|
|
193
|
-
this.counterActivityCreate++;
|
|
194
192
|
this.prefs = this.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE);
|
|
195
193
|
this.editor = this.prefs.edit();
|
|
196
194
|
|
|
@@ -276,9 +274,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
276
274
|
}
|
|
277
275
|
logger.info("appId: " + implementation.appId);
|
|
278
276
|
|
|
279
|
-
// Update User-Agent for shared OkHttpClient
|
|
280
|
-
DownloadService.updateUserAgent(this.implementation.appId, this.PLUGIN_VERSION);
|
|
281
|
-
|
|
282
277
|
this.persistCustomId = this.getConfig().getBoolean("persistCustomId", false);
|
|
283
278
|
this.persistModifyUrl = this.getConfig().getBoolean("persistModifyUrl", false);
|
|
284
279
|
this.implementation.publicKey = this.getConfig().getString("publicKey", "");
|
|
@@ -316,6 +311,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
316
311
|
this.implementation.deviceID = this.prefs.getString("appUUID", UUID.randomUUID().toString()).toLowerCase();
|
|
317
312
|
this.editor.putString("appUUID", this.implementation.deviceID);
|
|
318
313
|
this.editor.apply();
|
|
314
|
+
|
|
315
|
+
// Update User-Agent for shared OkHttpClient with OS version
|
|
316
|
+
DownloadService.updateUserAgent(this.implementation.appId, this.PLUGIN_VERSION, this.implementation.versionOs);
|
|
317
|
+
|
|
319
318
|
if (Boolean.TRUE.equals(this.persistCustomId)) {
|
|
320
319
|
final String storedCustomId = this.prefs.getString(CUSTOM_ID_PREF_KEY, "");
|
|
321
320
|
if (storedCustomId != null && !storedCustomId.isEmpty()) {
|
|
@@ -357,6 +356,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
357
356
|
if (resetWhenUpdate) {
|
|
358
357
|
this.cleanupObsoleteVersions();
|
|
359
358
|
}
|
|
359
|
+
|
|
360
|
+
// Check for 'kill' delay condition on app launch
|
|
361
|
+
// This handles cases where the app was killed by the system (onDestroy is not reliable)
|
|
362
|
+
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.KILLED);
|
|
363
|
+
|
|
360
364
|
this.checkForUpdateAfterDelay();
|
|
361
365
|
}
|
|
362
366
|
|
|
@@ -1910,6 +1914,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1910
1914
|
) {
|
|
1911
1915
|
this.backgroundDownloadTask = this.backgroundDownload();
|
|
1912
1916
|
} else {
|
|
1917
|
+
final CapConfig config = CapConfig.loadDefault(this.getActivity());
|
|
1918
|
+
String serverUrl = config.getServerUrl();
|
|
1919
|
+
if (serverUrl != null && !serverUrl.isEmpty()) {
|
|
1920
|
+
CapacitorUpdaterPlugin.this.implementation.sendStats("blocked_by_server_url", current.getVersionName());
|
|
1921
|
+
}
|
|
1913
1922
|
logger.info("Auto update is disabled");
|
|
1914
1923
|
this.sendReadyToJs(current, "disabled");
|
|
1915
1924
|
}
|
|
@@ -1980,11 +1989,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1980
1989
|
}
|
|
1981
1990
|
}
|
|
1982
1991
|
|
|
1983
|
-
private void appKilled() {
|
|
1984
|
-
logger.debug("onActivityDestroyed: all activity destroyed");
|
|
1985
|
-
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.KILLED);
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
1992
|
@Override
|
|
1989
1993
|
public void handleOnStart() {
|
|
1990
1994
|
try {
|
|
@@ -2048,10 +2052,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2048
2052
|
try {
|
|
2049
2053
|
logger.info("onActivityDestroyed " + getActivity().getClass().getName());
|
|
2050
2054
|
this.implementation.activity = getActivity();
|
|
2051
|
-
counterActivityCreate--;
|
|
2052
|
-
if (counterActivityCreate == 0) {
|
|
2053
|
-
this.appKilled();
|
|
2054
|
-
}
|
|
2055
2055
|
|
|
2056
2056
|
// Clean up shake menu
|
|
2057
2057
|
if (shakeMenu != null) {
|
|
@@ -81,6 +81,12 @@ public class CapgoUpdater {
|
|
|
81
81
|
public String deviceID = "";
|
|
82
82
|
public int timeout = 20000;
|
|
83
83
|
|
|
84
|
+
// Flag to track if we received a 429 response - stops requests until app restart
|
|
85
|
+
private static volatile boolean rateLimitExceeded = false;
|
|
86
|
+
|
|
87
|
+
// Flag to track if we've already sent the rate limit statistic - prevents infinite loop
|
|
88
|
+
private static volatile boolean rateLimitStatisticSent = false;
|
|
89
|
+
|
|
84
90
|
private final Map<String, CompletableFuture<BundleInfo>> downloadFutures = new ConcurrentHashMap<>();
|
|
85
91
|
private final ExecutorService io = Executors.newSingleThreadExecutor();
|
|
86
92
|
|
|
@@ -776,6 +782,59 @@ public class CapgoUpdater {
|
|
|
776
782
|
return json;
|
|
777
783
|
}
|
|
778
784
|
|
|
785
|
+
/**
|
|
786
|
+
* Check if a 429 (Too Many Requests) response was received and set the flag
|
|
787
|
+
*/
|
|
788
|
+
private boolean checkAndHandleRateLimitResponse(Response response) {
|
|
789
|
+
if (response.code() == 429) {
|
|
790
|
+
// Send a statistic about the rate limit BEFORE setting the flag
|
|
791
|
+
// Only send once to prevent infinite loop if the stat request itself gets rate limited
|
|
792
|
+
if (!rateLimitExceeded && !rateLimitStatisticSent) {
|
|
793
|
+
rateLimitStatisticSent = true;
|
|
794
|
+
sendRateLimitStatistic();
|
|
795
|
+
}
|
|
796
|
+
rateLimitExceeded = true;
|
|
797
|
+
logger.warn("Rate limit exceeded (429). Stopping all stats and channel requests until app restart.");
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Send a synchronous statistic about rate limiting
|
|
805
|
+
*/
|
|
806
|
+
private void sendRateLimitStatistic() {
|
|
807
|
+
String statsUrl = this.statsUrl;
|
|
808
|
+
if (statsUrl == null || statsUrl.isEmpty()) {
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
try {
|
|
813
|
+
BundleInfo current = this.getCurrentBundle();
|
|
814
|
+
JSONObject json = this.createInfoObject();
|
|
815
|
+
json.put("version_name", current.getVersionName());
|
|
816
|
+
json.put("old_version_name", "");
|
|
817
|
+
json.put("action", "rate_limit_reached");
|
|
818
|
+
|
|
819
|
+
Request request = new Request.Builder()
|
|
820
|
+
.url(statsUrl)
|
|
821
|
+
.post(RequestBody.create(json.toString(), MediaType.get("application/json")))
|
|
822
|
+
.build();
|
|
823
|
+
|
|
824
|
+
// Send synchronously to ensure it goes out before the flag is set
|
|
825
|
+
// User-Agent header is automatically added by DownloadService.sharedClient interceptor
|
|
826
|
+
try (Response response = DownloadService.sharedClient.newCall(request).execute()) {
|
|
827
|
+
if (response.isSuccessful()) {
|
|
828
|
+
logger.info("Rate limit statistic sent");
|
|
829
|
+
} else {
|
|
830
|
+
logger.error("Error sending rate limit statistic: " + response.code());
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
} catch (final Exception e) {
|
|
834
|
+
logger.error("Failed to send rate limit statistic: " + e.getMessage());
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
779
838
|
private void makeJsonRequest(String url, JSONObject jsonBody, Callback callback) {
|
|
780
839
|
MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
|
781
840
|
RequestBody body = RequestBody.create(jsonBody.toString(), JSON);
|
|
@@ -797,6 +856,15 @@ public class CapgoUpdater {
|
|
|
797
856
|
@Override
|
|
798
857
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
799
858
|
try (ResponseBody responseBody = response.body()) {
|
|
859
|
+
// Check for 429 rate limit
|
|
860
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
861
|
+
Map<String, Object> retError = new HashMap<>();
|
|
862
|
+
retError.put("message", "Rate limit exceeded");
|
|
863
|
+
retError.put("error", "rate_limit_exceeded");
|
|
864
|
+
callback.callback(retError);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
|
|
800
868
|
if (!response.isSuccessful()) {
|
|
801
869
|
Map<String, Object> retError = new HashMap<>();
|
|
802
870
|
retError.put("message", "Server error: " + response.code());
|
|
@@ -865,6 +933,16 @@ public class CapgoUpdater {
|
|
|
865
933
|
}
|
|
866
934
|
|
|
867
935
|
public void unsetChannel(final Callback callback) {
|
|
936
|
+
// Check if rate limit was exceeded
|
|
937
|
+
if (rateLimitExceeded) {
|
|
938
|
+
logger.debug("Skipping unsetChannel due to rate limit (429). Requests will resume after app restart.");
|
|
939
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
940
|
+
retError.put("message", "Rate limit exceeded");
|
|
941
|
+
retError.put("error", "rate_limit_exceeded");
|
|
942
|
+
callback.callback(retError);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
|
|
868
946
|
String channelUrl = this.channelUrl;
|
|
869
947
|
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
870
948
|
logger.error("Channel URL is not set");
|
|
@@ -906,6 +984,15 @@ public class CapgoUpdater {
|
|
|
906
984
|
@Override
|
|
907
985
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
908
986
|
try (ResponseBody responseBody = response.body()) {
|
|
987
|
+
// Check for 429 rate limit
|
|
988
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
989
|
+
Map<String, Object> retError = new HashMap<>();
|
|
990
|
+
retError.put("message", "Rate limit exceeded");
|
|
991
|
+
retError.put("error", "rate_limit_exceeded");
|
|
992
|
+
callback.callback(retError);
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
|
|
909
996
|
if (!response.isSuccessful()) {
|
|
910
997
|
Map<String, Object> retError = new HashMap<>();
|
|
911
998
|
retError.put("message", "Server error: " + response.code());
|
|
@@ -950,6 +1037,16 @@ public class CapgoUpdater {
|
|
|
950
1037
|
}
|
|
951
1038
|
|
|
952
1039
|
public void setChannel(final String channel, final Callback callback) {
|
|
1040
|
+
// Check if rate limit was exceeded
|
|
1041
|
+
if (rateLimitExceeded) {
|
|
1042
|
+
logger.debug("Skipping setChannel due to rate limit (429). Requests will resume after app restart.");
|
|
1043
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1044
|
+
retError.put("message", "Rate limit exceeded");
|
|
1045
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1046
|
+
callback.callback(retError);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
953
1050
|
String channelUrl = this.channelUrl;
|
|
954
1051
|
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
955
1052
|
logger.error("Channel URL is not set");
|
|
@@ -976,6 +1073,16 @@ public class CapgoUpdater {
|
|
|
976
1073
|
}
|
|
977
1074
|
|
|
978
1075
|
public void getChannel(final Callback callback) {
|
|
1076
|
+
// Check if rate limit was exceeded
|
|
1077
|
+
if (rateLimitExceeded) {
|
|
1078
|
+
logger.debug("Skipping getChannel due to rate limit (429). Requests will resume after app restart.");
|
|
1079
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1080
|
+
retError.put("message", "Rate limit exceeded");
|
|
1081
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1082
|
+
callback.callback(retError);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
979
1086
|
String channelUrl = this.channelUrl;
|
|
980
1087
|
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
981
1088
|
logger.error("Channel URL is not set");
|
|
@@ -1017,6 +1124,15 @@ public class CapgoUpdater {
|
|
|
1017
1124
|
@Override
|
|
1018
1125
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
1019
1126
|
try (ResponseBody responseBody = response.body()) {
|
|
1127
|
+
// Check for 429 rate limit
|
|
1128
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
1129
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1130
|
+
retError.put("message", "Rate limit exceeded");
|
|
1131
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1132
|
+
callback.callback(retError);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1020
1136
|
if (response.code() == 400) {
|
|
1021
1137
|
assert responseBody != null;
|
|
1022
1138
|
String data = responseBody.string();
|
|
@@ -1074,6 +1190,16 @@ public class CapgoUpdater {
|
|
|
1074
1190
|
}
|
|
1075
1191
|
|
|
1076
1192
|
public void listChannels(final Callback callback) {
|
|
1193
|
+
// Check if rate limit was exceeded
|
|
1194
|
+
if (rateLimitExceeded) {
|
|
1195
|
+
logger.debug("Skipping listChannels due to rate limit (429). Requests will resume after app restart.");
|
|
1196
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1197
|
+
retError.put("message", "Rate limit exceeded");
|
|
1198
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1199
|
+
callback.callback(retError);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1077
1203
|
String channelUrl = this.channelUrl;
|
|
1078
1204
|
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
1079
1205
|
logger.error("Channel URL is not set");
|
|
@@ -1114,6 +1240,15 @@ public class CapgoUpdater {
|
|
|
1114
1240
|
@Override
|
|
1115
1241
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
1116
1242
|
try (ResponseBody responseBody = response.body()) {
|
|
1243
|
+
// Check for 429 rate limit
|
|
1244
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
1245
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1246
|
+
retError.put("message", "Rate limit exceeded");
|
|
1247
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1248
|
+
callback.callback(retError);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1117
1252
|
if (!response.isSuccessful()) {
|
|
1118
1253
|
Map<String, Object> retError = new HashMap<>();
|
|
1119
1254
|
retError.put("message", "Server error: " + response.code());
|
|
@@ -1185,6 +1320,12 @@ public class CapgoUpdater {
|
|
|
1185
1320
|
}
|
|
1186
1321
|
|
|
1187
1322
|
public void sendStats(final String action, final String versionName, final String oldVersionName) {
|
|
1323
|
+
// Check if rate limit was exceeded
|
|
1324
|
+
if (rateLimitExceeded) {
|
|
1325
|
+
logger.debug("Skipping sendStats due to rate limit (429). Stats will resume after app restart.");
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1188
1329
|
String statsUrl = this.statsUrl;
|
|
1189
1330
|
if (statsUrl == null || statsUrl.isEmpty()) {
|
|
1190
1331
|
return;
|
|
@@ -1217,6 +1358,11 @@ public class CapgoUpdater {
|
|
|
1217
1358
|
@Override
|
|
1218
1359
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
1219
1360
|
try (ResponseBody responseBody = response.body()) {
|
|
1361
|
+
// Check for 429 rate limit
|
|
1362
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1220
1366
|
if (response.isSuccessful()) {
|
|
1221
1367
|
logger.info("Stats send for \"" + action + "\", version " + versionName);
|
|
1222
1368
|
} else {
|
|
@@ -70,6 +70,7 @@ public class DownloadService extends Worker {
|
|
|
70
70
|
protected static OkHttpClient sharedClient;
|
|
71
71
|
private static String currentAppId = "unknown";
|
|
72
72
|
private static String currentPluginVersion = "unknown";
|
|
73
|
+
private static String currentVersionOs = "unknown";
|
|
73
74
|
|
|
74
75
|
// Initialize shared client with User-Agent interceptor
|
|
75
76
|
static {
|
|
@@ -82,7 +83,8 @@ public class DownloadService extends Worker {
|
|
|
82
83
|
(currentPluginVersion != null ? currentPluginVersion : "unknown") +
|
|
83
84
|
" (" +
|
|
84
85
|
(currentAppId != null ? currentAppId : "unknown") +
|
|
85
|
-
")"
|
|
86
|
+
") android/" +
|
|
87
|
+
(currentVersionOs != null ? currentVersionOs : "unknown");
|
|
86
88
|
Request requestWithUserAgent = originalRequest.newBuilder().header("User-Agent", userAgent).build();
|
|
87
89
|
return chain.proceed(requestWithUserAgent);
|
|
88
90
|
})
|
|
@@ -90,10 +92,13 @@ public class DownloadService extends Worker {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
// Method to update User-Agent values
|
|
93
|
-
public static void updateUserAgent(String appId, String pluginVersion) {
|
|
95
|
+
public static void updateUserAgent(String appId, String pluginVersion, String versionOs) {
|
|
94
96
|
currentAppId = appId != null ? appId : "unknown";
|
|
95
97
|
currentPluginVersion = pluginVersion != null ? pluginVersion : "unknown";
|
|
96
|
-
|
|
98
|
+
currentVersionOs = versionOs != null ? versionOs : "unknown";
|
|
99
|
+
logger.debug(
|
|
100
|
+
"Updated User-Agent: CapacitorUpdater/" + currentPluginVersion + " (" + currentAppId + ") android/" + currentVersionOs
|
|
101
|
+
);
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
public DownloadService(@NonNull Context context, @NonNull WorkerParameters params) {
|
|
@@ -54,7 +54,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
54
54
|
CAPPluginMethod(name: "isShakeMenuEnabled", returnType: CAPPluginReturnPromise)
|
|
55
55
|
]
|
|
56
56
|
public var implementation = CapgoUpdater()
|
|
57
|
-
private let PLUGIN_VERSION: String = "7.23.
|
|
57
|
+
private let PLUGIN_VERSION: String = "7.23.13"
|
|
58
58
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
59
59
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
60
60
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -236,7 +236,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
236
236
|
let nc = NotificationCenter.default
|
|
237
237
|
nc.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
|
|
238
238
|
nc.addObserver(self, selector: #selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
|
|
239
|
-
|
|
239
|
+
|
|
240
|
+
// Check for 'kill' delay condition on app launch
|
|
241
|
+
// This handles cases where the app was killed (willTerminateNotification is not reliable for system kills)
|
|
242
|
+
self.delayUpdateUtils.checkCancelDelay(source: .killed)
|
|
243
|
+
|
|
240
244
|
self.appMovedToForeground()
|
|
241
245
|
self.checkForUpdateAfterDelay()
|
|
242
246
|
}
|
|
@@ -1377,20 +1381,6 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1377
1381
|
}
|
|
1378
1382
|
}
|
|
1379
1383
|
|
|
1380
|
-
@objc func appKilled() {
|
|
1381
|
-
logger.info("onActivityDestroyed: all activity destroyed")
|
|
1382
|
-
self.delayUpdateUtils.checkCancelDelay(source: .killed)
|
|
1383
|
-
|
|
1384
|
-
// Clean up resources
|
|
1385
|
-
periodicUpdateTimer?.invalidate()
|
|
1386
|
-
periodicUpdateTimer = nil
|
|
1387
|
-
backgroundWork?.cancel()
|
|
1388
|
-
backgroundWork = nil
|
|
1389
|
-
|
|
1390
|
-
// Signal any waiting semaphores to prevent deadlocks
|
|
1391
|
-
semaphoreReady.signal()
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
1384
|
private func installNext() {
|
|
1395
1385
|
let delayUpdatePreferences = UserDefaults.standard.string(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES) ?? "[]"
|
|
1396
1386
|
let delayConditionList: [DelayCondition] = fromJsonArr(json: delayUpdatePreferences).map { obj -> DelayCondition in
|
|
@@ -1446,6 +1436,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1446
1436
|
if self._isAutoUpdateEnabled() {
|
|
1447
1437
|
self.backgroundDownload()
|
|
1448
1438
|
} else {
|
|
1439
|
+
let instanceDescriptor = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor()
|
|
1440
|
+
if instanceDescriptor?.serverURL != nil {
|
|
1441
|
+
self.implementation.sendStats(action: "blocked_by_server_url", versionName: current.getVersionName())
|
|
1442
|
+
}
|
|
1449
1443
|
logger.info("Auto update is disabled")
|
|
1450
1444
|
self.sendReadyToJs(current: current, msg: "disabled")
|
|
1451
1445
|
}
|
|
@@ -42,10 +42,16 @@ import UIKit
|
|
|
42
42
|
public var deviceID = ""
|
|
43
43
|
public var publicKey: String = ""
|
|
44
44
|
|
|
45
|
+
// Flag to track if we received a 429 response - stops requests until app restart
|
|
46
|
+
private static var rateLimitExceeded = false
|
|
47
|
+
|
|
48
|
+
// Flag to track if we've already sent the rate limit statistic - prevents infinite loop
|
|
49
|
+
private static var rateLimitStatisticSent = false
|
|
50
|
+
|
|
45
51
|
private var userAgent: String {
|
|
46
52
|
let safePluginVersion = PLUGIN_VERSION.isEmpty ? "unknown" : PLUGIN_VERSION
|
|
47
53
|
let safeAppId = appId.isEmpty ? "unknown" : appId
|
|
48
|
-
return "CapacitorUpdater/\(safePluginVersion) (\(safeAppId))"
|
|
54
|
+
return "CapacitorUpdater/\(safePluginVersion) (\(safeAppId)) ios/\(versionOs)"
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
private lazy var alamofireSession: Session = {
|
|
@@ -85,6 +91,58 @@ import UIKit
|
|
|
85
91
|
return !self.isDevEnvironment && !self.isAppStoreReceiptSandbox() && !self.hasEmbeddedMobileProvision()
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Check if a 429 (Too Many Requests) response was received and set the flag
|
|
96
|
+
*/
|
|
97
|
+
private func checkAndHandleRateLimitResponse(statusCode: Int?) -> Bool {
|
|
98
|
+
if statusCode == 429 {
|
|
99
|
+
// Send a statistic about the rate limit BEFORE setting the flag
|
|
100
|
+
// Only send once to prevent infinite loop if the stat request itself gets rate limited
|
|
101
|
+
if !CapgoUpdater.rateLimitExceeded && !CapgoUpdater.rateLimitStatisticSent {
|
|
102
|
+
CapgoUpdater.rateLimitStatisticSent = true
|
|
103
|
+
self.sendRateLimitStatistic()
|
|
104
|
+
}
|
|
105
|
+
CapgoUpdater.rateLimitExceeded = true
|
|
106
|
+
logger.warn("Rate limit exceeded (429). Stopping all stats and channel requests until app restart.")
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Send a synchronous statistic about rate limiting
|
|
114
|
+
*/
|
|
115
|
+
private func sendRateLimitStatistic() {
|
|
116
|
+
guard !statsUrl.isEmpty else {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let current = getCurrentBundle()
|
|
121
|
+
var parameters = createInfoObject()
|
|
122
|
+
parameters.action = "rate_limit_reached"
|
|
123
|
+
parameters.version_name = current.getVersionName()
|
|
124
|
+
parameters.old_version_name = ""
|
|
125
|
+
|
|
126
|
+
// Send synchronously to ensure it goes out before the flag is set
|
|
127
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
128
|
+
self.alamofireSession.request(
|
|
129
|
+
self.statsUrl,
|
|
130
|
+
method: .post,
|
|
131
|
+
parameters: parameters,
|
|
132
|
+
encoder: JSONParameterEncoder.default,
|
|
133
|
+
requestModifier: { $0.timeoutInterval = self.timeout }
|
|
134
|
+
).responseData { response in
|
|
135
|
+
switch response.result {
|
|
136
|
+
case .success:
|
|
137
|
+
self.logger.info("Rate limit statistic sent")
|
|
138
|
+
case let .failure(error):
|
|
139
|
+
self.logger.error("Error sending rate limit statistic: \(error.localizedDescription)")
|
|
140
|
+
}
|
|
141
|
+
semaphore.signal()
|
|
142
|
+
}
|
|
143
|
+
semaphore.wait()
|
|
144
|
+
}
|
|
145
|
+
|
|
88
146
|
// MARK: Private
|
|
89
147
|
private func hasEmbeddedMobileProvision() -> Bool {
|
|
90
148
|
guard Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") == nil else {
|
|
@@ -1011,6 +1069,15 @@ import UIKit
|
|
|
1011
1069
|
|
|
1012
1070
|
func unsetChannel() -> SetChannel {
|
|
1013
1071
|
let setChannel: SetChannel = SetChannel()
|
|
1072
|
+
|
|
1073
|
+
// Check if rate limit was exceeded
|
|
1074
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1075
|
+
logger.debug("Skipping unsetChannel due to rate limit (429). Requests will resume after app restart.")
|
|
1076
|
+
setChannel.message = "Rate limit exceeded"
|
|
1077
|
+
setChannel.error = "rate_limit_exceeded"
|
|
1078
|
+
return setChannel
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1014
1081
|
if (self.channelUrl ).isEmpty {
|
|
1015
1082
|
logger.error("Channel URL is not set")
|
|
1016
1083
|
setChannel.message = "Channel URL is not set"
|
|
@@ -1023,6 +1090,14 @@ import UIKit
|
|
|
1023
1090
|
let request = alamofireSession.request(self.channelUrl, method: .delete, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
|
|
1024
1091
|
|
|
1025
1092
|
request.validate().responseDecodable(of: SetChannelDec.self) { response in
|
|
1093
|
+
// Check for 429 rate limit
|
|
1094
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1095
|
+
setChannel.message = "Rate limit exceeded"
|
|
1096
|
+
setChannel.error = "rate_limit_exceeded"
|
|
1097
|
+
semaphore.signal()
|
|
1098
|
+
return
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1026
1101
|
switch response.result {
|
|
1027
1102
|
case .success:
|
|
1028
1103
|
if let responseValue = response.value {
|
|
@@ -1045,6 +1120,15 @@ import UIKit
|
|
|
1045
1120
|
|
|
1046
1121
|
func setChannel(channel: String) -> SetChannel {
|
|
1047
1122
|
let setChannel: SetChannel = SetChannel()
|
|
1123
|
+
|
|
1124
|
+
// Check if rate limit was exceeded
|
|
1125
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1126
|
+
logger.debug("Skipping setChannel due to rate limit (429). Requests will resume after app restart.")
|
|
1127
|
+
setChannel.message = "Rate limit exceeded"
|
|
1128
|
+
setChannel.error = "rate_limit_exceeded"
|
|
1129
|
+
return setChannel
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1048
1132
|
if (self.channelUrl ).isEmpty {
|
|
1049
1133
|
logger.error("Channel URL is not set")
|
|
1050
1134
|
setChannel.message = "Channel URL is not set"
|
|
@@ -1058,6 +1142,14 @@ import UIKit
|
|
|
1058
1142
|
let request = alamofireSession.request(self.channelUrl, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
|
|
1059
1143
|
|
|
1060
1144
|
request.validate().responseDecodable(of: SetChannelDec.self) { response in
|
|
1145
|
+
// Check for 429 rate limit
|
|
1146
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1147
|
+
setChannel.message = "Rate limit exceeded"
|
|
1148
|
+
setChannel.error = "rate_limit_exceeded"
|
|
1149
|
+
semaphore.signal()
|
|
1150
|
+
return
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1061
1153
|
switch response.result {
|
|
1062
1154
|
case .success:
|
|
1063
1155
|
if let responseValue = response.value {
|
|
@@ -1080,6 +1172,15 @@ import UIKit
|
|
|
1080
1172
|
|
|
1081
1173
|
func getChannel() -> GetChannel {
|
|
1082
1174
|
let getChannel: GetChannel = GetChannel()
|
|
1175
|
+
|
|
1176
|
+
// Check if rate limit was exceeded
|
|
1177
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1178
|
+
logger.debug("Skipping getChannel due to rate limit (429). Requests will resume after app restart.")
|
|
1179
|
+
getChannel.message = "Rate limit exceeded"
|
|
1180
|
+
getChannel.error = "rate_limit_exceeded"
|
|
1181
|
+
return getChannel
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1083
1184
|
if (self.channelUrl ).isEmpty {
|
|
1084
1185
|
logger.error("Channel URL is not set")
|
|
1085
1186
|
getChannel.message = "Channel URL is not set"
|
|
@@ -1094,6 +1195,14 @@ import UIKit
|
|
|
1094
1195
|
defer {
|
|
1095
1196
|
semaphore.signal()
|
|
1096
1197
|
}
|
|
1198
|
+
|
|
1199
|
+
// Check for 429 rate limit
|
|
1200
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1201
|
+
getChannel.message = "Rate limit exceeded"
|
|
1202
|
+
getChannel.error = "rate_limit_exceeded"
|
|
1203
|
+
return
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1097
1206
|
switch response.result {
|
|
1098
1207
|
case .success:
|
|
1099
1208
|
if let responseValue = response.value {
|
|
@@ -1125,6 +1234,14 @@ import UIKit
|
|
|
1125
1234
|
|
|
1126
1235
|
func listChannels() -> ListChannels {
|
|
1127
1236
|
let listChannels: ListChannels = ListChannels()
|
|
1237
|
+
|
|
1238
|
+
// Check if rate limit was exceeded
|
|
1239
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1240
|
+
logger.debug("Skipping listChannels due to rate limit (429). Requests will resume after app restart.")
|
|
1241
|
+
listChannels.error = "rate_limit_exceeded"
|
|
1242
|
+
return listChannels
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1128
1245
|
if (self.channelUrl).isEmpty {
|
|
1129
1246
|
logger.error("Channel URL is not set")
|
|
1130
1247
|
listChannels.error = "Channel URL is not set"
|
|
@@ -1160,6 +1277,13 @@ import UIKit
|
|
|
1160
1277
|
defer {
|
|
1161
1278
|
semaphore.signal()
|
|
1162
1279
|
}
|
|
1280
|
+
|
|
1281
|
+
// Check for 429 rate limit
|
|
1282
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1283
|
+
listChannels.error = "rate_limit_exceeded"
|
|
1284
|
+
return
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1163
1287
|
switch response.result {
|
|
1164
1288
|
case .success:
|
|
1165
1289
|
if let responseValue = response.value {
|
|
@@ -1193,6 +1317,12 @@ import UIKit
|
|
|
1193
1317
|
private let operationQueue = OperationQueue()
|
|
1194
1318
|
|
|
1195
1319
|
func sendStats(action: String, versionName: String? = nil, oldVersionName: String? = "") {
|
|
1320
|
+
// Check if rate limit was exceeded
|
|
1321
|
+
if CapgoUpdater.rateLimitExceeded {
|
|
1322
|
+
logger.debug("Skipping sendStats due to rate limit (429). Stats will resume after app restart.")
|
|
1323
|
+
return
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1196
1326
|
guard !statsUrl.isEmpty else {
|
|
1197
1327
|
return
|
|
1198
1328
|
}
|
|
@@ -1214,6 +1344,12 @@ import UIKit
|
|
|
1214
1344
|
encoder: JSONParameterEncoder.default,
|
|
1215
1345
|
requestModifier: { $0.timeoutInterval = self.timeout }
|
|
1216
1346
|
).responseData { response in
|
|
1347
|
+
// Check for 429 rate limit
|
|
1348
|
+
if self.checkAndHandleRateLimitResponse(statusCode: response.response?.statusCode) {
|
|
1349
|
+
semaphore.signal()
|
|
1350
|
+
return
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1217
1353
|
switch response.result {
|
|
1218
1354
|
case .success:
|
|
1219
1355
|
self.logger.info("Stats sent for \(action), version \(versionName)")
|
|
@@ -1222,7 +1358,7 @@ import UIKit
|
|
|
1222
1358
|
}
|
|
1223
1359
|
semaphore.signal()
|
|
1224
1360
|
}
|
|
1225
|
-
semaphore.
|
|
1361
|
+
semaphore.wait()
|
|
1226
1362
|
}
|
|
1227
1363
|
operationQueue.addOperation(operation)
|
|
1228
1364
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-updater",
|
|
3
|
-
"version": "7.23.
|
|
3
|
+
"version": "7.23.13",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Live update for capacitor apps",
|
|
6
6
|
"main": "dist/plugin.cjs.js",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"test:ios": "./scripts/test-ios.sh",
|
|
49
49
|
"test:android": "cd android && ./gradlew test && cd ..",
|
|
50
50
|
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
51
|
-
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --
|
|
51
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
52
52
|
"eslint": "eslint . --ext .ts",
|
|
53
53
|
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
54
54
|
"swiftlint": "node-swiftlint",
|