@capgo/capacitor-updater 8.0.1 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoCapacitorUpdater.podspec +7 -5
- package/Package.swift +9 -7
- package/README.md +984 -215
- package/android/build.gradle +24 -12
- package/android/proguard-rules.pro +22 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +110 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1316 -489
- package/android/src/main/java/ee/forgr/capacitor_updater/{CapacitorUpdater.java → CapgoUpdater.java} +662 -203
- package/android/src/main/java/ee/forgr/capacitor_updater/{CryptoCipherV2.java → CryptoCipher.java} +138 -33
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +0 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +260 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +221 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +497 -133
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +80 -25
- package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +338 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +169 -0
- package/dist/docs.json +873 -154
- package/dist/esm/definitions.d.ts +881 -114
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/history.d.ts +1 -0
- package/dist/esm/history.js +283 -0
- package/dist/esm/history.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +12 -1
- package/dist/esm/web.js +29 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +311 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +311 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +69 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleInfo.swift +37 -10
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +1610 -0
- package/ios/{Plugin/CapacitorUpdater.swift → Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift} +541 -231
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +286 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +220 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/InternalUtils.swift +54 -0
- package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +112 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +0 -2
- package/package.json +21 -19
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -975
- package/ios/Plugin/CryptoCipherV2.swift +0 -310
- /package/{LICENCE → LICENSE} +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
package/android/src/main/java/ee/forgr/capacitor_updater/{CapacitorUpdater.java → CapgoUpdater.java}
RENAMED
|
@@ -10,14 +10,11 @@ import android.app.Activity;
|
|
|
10
10
|
import android.content.Context;
|
|
11
11
|
import android.content.SharedPreferences;
|
|
12
12
|
import android.os.Build;
|
|
13
|
-
import android.util.Log;
|
|
14
13
|
import androidx.annotation.NonNull;
|
|
15
14
|
import androidx.lifecycle.LifecycleOwner;
|
|
16
15
|
import androidx.work.Data;
|
|
17
16
|
import androidx.work.WorkInfo;
|
|
18
17
|
import androidx.work.WorkManager;
|
|
19
|
-
import com.getcapacitor.JSObject;
|
|
20
|
-
import com.getcapacitor.plugin.WebView;
|
|
21
18
|
import com.google.common.util.concurrent.Futures;
|
|
22
19
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
23
20
|
import java.io.BufferedInputStream;
|
|
@@ -30,17 +27,28 @@ import java.io.IOException;
|
|
|
30
27
|
import java.security.SecureRandom;
|
|
31
28
|
import java.util.ArrayList;
|
|
32
29
|
import java.util.Date;
|
|
30
|
+
import java.util.HashMap;
|
|
33
31
|
import java.util.Iterator;
|
|
34
32
|
import java.util.List;
|
|
33
|
+
import java.util.Map;
|
|
35
34
|
import java.util.Objects;
|
|
35
|
+
import java.util.Set;
|
|
36
|
+
import java.util.concurrent.CompletableFuture;
|
|
37
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
38
|
+
import java.util.concurrent.ExecutorService;
|
|
39
|
+
import java.util.concurrent.Executors;
|
|
40
|
+
import java.util.concurrent.TimeUnit;
|
|
36
41
|
import java.util.zip.ZipEntry;
|
|
37
42
|
import java.util.zip.ZipInputStream;
|
|
38
43
|
import okhttp3.*;
|
|
44
|
+
import okhttp3.HttpUrl;
|
|
39
45
|
import org.json.JSONArray;
|
|
40
46
|
import org.json.JSONException;
|
|
41
47
|
import org.json.JSONObject;
|
|
42
48
|
|
|
43
|
-
public class
|
|
49
|
+
public class CapgoUpdater {
|
|
50
|
+
|
|
51
|
+
private final Logger logger;
|
|
44
52
|
|
|
45
53
|
private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
46
54
|
private static final SecureRandom rnd = new SecureRandom();
|
|
@@ -55,15 +63,14 @@ public class CapacitorUpdater {
|
|
|
55
63
|
public SharedPreferences.Editor editor;
|
|
56
64
|
public SharedPreferences prefs;
|
|
57
65
|
|
|
58
|
-
public OkHttpClient client;
|
|
59
|
-
|
|
60
66
|
public File documentsDir;
|
|
61
67
|
public Boolean directUpdate = false;
|
|
62
68
|
public Activity activity;
|
|
63
|
-
public String
|
|
69
|
+
public String pluginVersion = "";
|
|
64
70
|
public String versionBuild = "";
|
|
65
71
|
public String versionCode = "";
|
|
66
72
|
public String versionOs = "";
|
|
73
|
+
public String CAP_SERVER_PATH = "";
|
|
67
74
|
|
|
68
75
|
public String customId = "";
|
|
69
76
|
public String statsUrl = "";
|
|
@@ -74,6 +81,22 @@ public class CapacitorUpdater {
|
|
|
74
81
|
public String deviceID = "";
|
|
75
82
|
public int timeout = 20000;
|
|
76
83
|
|
|
84
|
+
// Cached key ID calculated once from publicKey
|
|
85
|
+
private String cachedKeyId = "";
|
|
86
|
+
|
|
87
|
+
// Flag to track if we received a 429 response - stops requests until app restart
|
|
88
|
+
private static volatile boolean rateLimitExceeded = false;
|
|
89
|
+
|
|
90
|
+
// Flag to track if we've already sent the rate limit statistic - prevents infinite loop
|
|
91
|
+
private static volatile boolean rateLimitStatisticSent = false;
|
|
92
|
+
|
|
93
|
+
private final Map<String, CompletableFuture<BundleInfo>> downloadFutures = new ConcurrentHashMap<>();
|
|
94
|
+
private final ExecutorService io = Executors.newSingleThreadExecutor();
|
|
95
|
+
|
|
96
|
+
public CapgoUpdater(Logger logger) {
|
|
97
|
+
this.logger = logger;
|
|
98
|
+
}
|
|
99
|
+
|
|
77
100
|
private final FilenameFilter filter = (f, name) -> {
|
|
78
101
|
// ignore directories generated by mac os x
|
|
79
102
|
return (!name.startsWith("__MACOSX") && !name.startsWith(".") && !name.startsWith(".DS_Store"));
|
|
@@ -117,14 +140,27 @@ public class CapacitorUpdater {
|
|
|
117
140
|
|
|
118
141
|
void directUpdateFinish(final BundleInfo latest) {}
|
|
119
142
|
|
|
120
|
-
void notifyListeners(final String id, final
|
|
143
|
+
void notifyListeners(final String id, final Map<String, Object> res) {}
|
|
121
144
|
|
|
122
|
-
|
|
145
|
+
public String randomString() {
|
|
123
146
|
final StringBuilder sb = new StringBuilder(10);
|
|
124
147
|
for (int i = 0; i < 10; i++) sb.append(AB.charAt(rnd.nextInt(AB.length())));
|
|
125
148
|
return sb.toString();
|
|
126
149
|
}
|
|
127
150
|
|
|
151
|
+
public void setPublicKey(String publicKey) {
|
|
152
|
+
this.publicKey = publicKey;
|
|
153
|
+
if (!publicKey.isEmpty()) {
|
|
154
|
+
this.cachedKeyId = CryptoCipher.calcKeyId(publicKey);
|
|
155
|
+
} else {
|
|
156
|
+
this.cachedKeyId = "";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public String getKeyId() {
|
|
161
|
+
return this.cachedKeyId;
|
|
162
|
+
}
|
|
163
|
+
|
|
128
164
|
private File unzip(final String id, final File zipFile, final String dest) throws IOException {
|
|
129
165
|
final File targetDirectory = new File(this.documentsDir, dest);
|
|
130
166
|
try (
|
|
@@ -142,7 +178,7 @@ public class CapacitorUpdater {
|
|
|
142
178
|
ZipEntry entry;
|
|
143
179
|
while ((entry = zis.getNextEntry()) != null) {
|
|
144
180
|
if (entry.getName().contains("\\")) {
|
|
145
|
-
|
|
181
|
+
logger.error("unzip: Windows path is not supported, please use unix path as require by zip RFC: " + entry.getName());
|
|
146
182
|
this.sendStats("windows_path_fail");
|
|
147
183
|
}
|
|
148
184
|
final File file = new File(targetDirectory, entry.getName());
|
|
@@ -207,14 +243,14 @@ public class CapacitorUpdater {
|
|
|
207
243
|
|
|
208
244
|
private void observeWorkProgress(Context context, String id) {
|
|
209
245
|
if (!(context instanceof LifecycleOwner)) {
|
|
210
|
-
|
|
246
|
+
logger.error("Context is not a LifecycleOwner, cannot observe work progress");
|
|
211
247
|
return;
|
|
212
248
|
}
|
|
213
249
|
|
|
214
250
|
activity.runOnUiThread(() -> {
|
|
215
251
|
WorkManager.getInstance(context)
|
|
216
252
|
.getWorkInfosByTagLiveData(id)
|
|
217
|
-
.observe((LifecycleOwner) context, workInfos -> {
|
|
253
|
+
.observe((LifecycleOwner) context, (workInfos) -> {
|
|
218
254
|
if (workInfos == null || workInfos.isEmpty()) return;
|
|
219
255
|
|
|
220
256
|
WorkInfo workInfo = workInfos.get(0);
|
|
@@ -226,6 +262,7 @@ public class CapacitorUpdater {
|
|
|
226
262
|
notifyDownload(id, percent);
|
|
227
263
|
break;
|
|
228
264
|
case SUCCEEDED:
|
|
265
|
+
logger.info("Download succeeded: " + workInfo.getState());
|
|
229
266
|
Data outputData = workInfo.getOutputData();
|
|
230
267
|
String dest = outputData.getString(DownloadService.FILEDEST);
|
|
231
268
|
String version = outputData.getString(DownloadService.VERSION);
|
|
@@ -233,35 +270,71 @@ public class CapacitorUpdater {
|
|
|
233
270
|
String checksum = outputData.getString(DownloadService.CHECKSUM);
|
|
234
271
|
boolean isManifest = outputData.getBoolean(DownloadService.IS_MANIFEST, false);
|
|
235
272
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
273
|
+
io.execute(() -> {
|
|
274
|
+
boolean success = finishDownload(id, dest, version, sessionKey, checksum, true, isManifest);
|
|
275
|
+
BundleInfo resultBundle;
|
|
276
|
+
if (!success) {
|
|
277
|
+
logger.error("Finish download failed: " + version);
|
|
278
|
+
resultBundle = new BundleInfo(
|
|
279
|
+
id,
|
|
280
|
+
version,
|
|
281
|
+
BundleStatus.ERROR,
|
|
282
|
+
new Date(System.currentTimeMillis()),
|
|
283
|
+
""
|
|
284
|
+
);
|
|
285
|
+
saveBundleInfo(id, resultBundle);
|
|
286
|
+
// Cleanup download tracking
|
|
287
|
+
DownloadWorkerManager.cancelBundleDownload(activity, id, version);
|
|
288
|
+
Map<String, Object> ret = new HashMap<>();
|
|
289
|
+
ret.put("version", version);
|
|
290
|
+
ret.put("error", "finish_download_fail");
|
|
291
|
+
sendStats("finish_download_fail", version);
|
|
292
|
+
notifyListeners("downloadFailed", ret);
|
|
293
|
+
} else {
|
|
294
|
+
// Successful download - cleanup tracking
|
|
295
|
+
DownloadWorkerManager.cancelBundleDownload(activity, id, version);
|
|
296
|
+
resultBundle = getBundleInfo(id);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Complete the future if it exists
|
|
300
|
+
CompletableFuture<BundleInfo> future = downloadFutures.remove(id);
|
|
301
|
+
if (future != null) {
|
|
302
|
+
future.complete(resultBundle);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
248
305
|
break;
|
|
249
306
|
case FAILED:
|
|
250
307
|
Data failedData = workInfo.getOutputData();
|
|
251
308
|
String error = failedData.getString(DownloadService.ERROR);
|
|
309
|
+
logger.error("Download failed: " + error + " " + workInfo.getState());
|
|
252
310
|
String failedVersion = failedData.getString(DownloadService.VERSION);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
311
|
+
|
|
312
|
+
io.execute(() -> {
|
|
313
|
+
BundleInfo failedBundle = new BundleInfo(
|
|
314
|
+
id,
|
|
315
|
+
failedVersion,
|
|
316
|
+
BundleStatus.ERROR,
|
|
317
|
+
new Date(System.currentTimeMillis()),
|
|
318
|
+
""
|
|
319
|
+
);
|
|
320
|
+
saveBundleInfo(id, failedBundle);
|
|
321
|
+
// Cleanup download tracking for failed downloads
|
|
322
|
+
DownloadWorkerManager.cancelBundleDownload(activity, id, failedVersion);
|
|
323
|
+
Map<String, Object> ret = new HashMap<>();
|
|
324
|
+
ret.put("version", failedVersion);
|
|
325
|
+
if ("low_mem_fail".equals(error)) {
|
|
326
|
+
sendStats("low_mem_fail", failedVersion);
|
|
327
|
+
}
|
|
328
|
+
ret.put("error", error != null ? error : "download_fail");
|
|
329
|
+
sendStats("download_fail", failedVersion);
|
|
330
|
+
notifyListeners("downloadFailed", ret);
|
|
331
|
+
|
|
332
|
+
// Complete the future with error status
|
|
333
|
+
CompletableFuture<BundleInfo> failedFuture = downloadFutures.remove(id);
|
|
334
|
+
if (failedFuture != null) {
|
|
335
|
+
failedFuture.complete(failedBundle);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
265
338
|
break;
|
|
266
339
|
}
|
|
267
340
|
});
|
|
@@ -278,7 +351,7 @@ public class CapacitorUpdater {
|
|
|
278
351
|
final JSONArray manifest
|
|
279
352
|
) {
|
|
280
353
|
if (this.activity == null) {
|
|
281
|
-
|
|
354
|
+
logger.error("Activity is null, cannot observe work progress");
|
|
282
355
|
return;
|
|
283
356
|
}
|
|
284
357
|
observeWorkProgress(this.activity, id);
|
|
@@ -293,7 +366,18 @@ public class CapacitorUpdater {
|
|
|
293
366
|
sessionKey,
|
|
294
367
|
checksum,
|
|
295
368
|
this.publicKey,
|
|
296
|
-
manifest != null
|
|
369
|
+
manifest != null,
|
|
370
|
+
this.isEmulator(),
|
|
371
|
+
this.appId,
|
|
372
|
+
this.pluginVersion,
|
|
373
|
+
this.isProd(),
|
|
374
|
+
this.statsUrl,
|
|
375
|
+
this.deviceID,
|
|
376
|
+
this.versionBuild,
|
|
377
|
+
this.versionCode,
|
|
378
|
+
this.versionOs,
|
|
379
|
+
this.customId,
|
|
380
|
+
this.defaultChannel
|
|
297
381
|
);
|
|
298
382
|
|
|
299
383
|
if (manifest != null) {
|
|
@@ -311,6 +395,7 @@ public class CapacitorUpdater {
|
|
|
311
395
|
Boolean isManifest
|
|
312
396
|
) {
|
|
313
397
|
File downloaded = null;
|
|
398
|
+
File extractedDir = null;
|
|
314
399
|
String checksum = "";
|
|
315
400
|
|
|
316
401
|
try {
|
|
@@ -319,68 +404,94 @@ public class CapacitorUpdater {
|
|
|
319
404
|
|
|
320
405
|
if (!isManifest) {
|
|
321
406
|
String checksumDecrypted = Objects.requireNonNullElse(checksumRes, "");
|
|
407
|
+
|
|
408
|
+
// If public key is present but no checksum provided, refuse installation
|
|
409
|
+
if (!this.publicKey.isEmpty() && checksumDecrypted.isEmpty()) {
|
|
410
|
+
logger.error("Public key present but no checksum provided");
|
|
411
|
+
this.sendStats("checksum_required");
|
|
412
|
+
throw new IOException("Checksum required when public key is present: " + id);
|
|
413
|
+
}
|
|
414
|
+
|
|
322
415
|
if (!sessionKey.isEmpty()) {
|
|
323
|
-
|
|
324
|
-
checksumDecrypted =
|
|
325
|
-
checksum =
|
|
416
|
+
CryptoCipher.decryptFile(downloaded, publicKey, sessionKey);
|
|
417
|
+
checksumDecrypted = CryptoCipher.decryptChecksum(checksumRes, publicKey);
|
|
418
|
+
checksum = CryptoCipher.calcChecksum(downloaded);
|
|
419
|
+
} else {
|
|
420
|
+
checksum = CryptoCipher.calcChecksum(downloaded);
|
|
326
421
|
}
|
|
422
|
+
CryptoCipher.logChecksumInfo("Calculated checksum", checksum);
|
|
423
|
+
CryptoCipher.logChecksumInfo("Expected checksum", checksumDecrypted);
|
|
327
424
|
if ((!checksumDecrypted.isEmpty() || !this.publicKey.isEmpty()) && !checksumDecrypted.equals(checksum)) {
|
|
328
|
-
|
|
425
|
+
logger.error("Error checksum '" + checksumDecrypted + "' '" + checksum + "' '");
|
|
329
426
|
this.sendStats("checksum_fail");
|
|
330
427
|
throw new IOException("Checksum failed: " + id);
|
|
331
428
|
}
|
|
332
429
|
}
|
|
333
430
|
// Remove the decryption for manifest downloads
|
|
334
|
-
} catch (
|
|
431
|
+
} catch (Exception e) {
|
|
432
|
+
if (!isManifest) {
|
|
433
|
+
safeDelete(downloaded);
|
|
434
|
+
}
|
|
335
435
|
final Boolean res = this.delete(id);
|
|
336
436
|
if (!res) {
|
|
337
|
-
|
|
437
|
+
logger.info("Double error, cannot cleanup: " + version);
|
|
338
438
|
}
|
|
339
439
|
|
|
340
|
-
final
|
|
341
|
-
ret.put("version",
|
|
440
|
+
final Map<String, Object> ret = new HashMap<>();
|
|
441
|
+
ret.put("version", version);
|
|
342
442
|
|
|
343
|
-
|
|
344
|
-
|
|
443
|
+
CapgoUpdater.this.notifyListeners("downloadFailed", ret);
|
|
444
|
+
CapgoUpdater.this.sendStats("download_fail");
|
|
345
445
|
return false;
|
|
346
446
|
}
|
|
347
447
|
|
|
348
448
|
try {
|
|
349
449
|
if (!isManifest) {
|
|
350
|
-
|
|
450
|
+
extractedDir = this.unzip(id, downloaded, this.randomString());
|
|
351
451
|
this.notifyDownload(id, 91);
|
|
352
452
|
final String idName = bundleDirectory + "/" + id;
|
|
353
|
-
this.flattenAssets(
|
|
453
|
+
this.flattenAssets(extractedDir, idName);
|
|
354
454
|
} else {
|
|
355
455
|
this.notifyDownload(id, 91);
|
|
356
456
|
final String idName = bundleDirectory + "/" + id;
|
|
357
457
|
this.flattenAssets(downloaded, idName);
|
|
358
458
|
downloaded.delete();
|
|
359
459
|
}
|
|
360
|
-
|
|
460
|
+
// Remove old bundle info and set new one
|
|
361
461
|
this.saveBundleInfo(id, null);
|
|
362
462
|
BundleInfo next = new BundleInfo(id, version, BundleStatus.PENDING, new Date(System.currentTimeMillis()), checksum);
|
|
363
463
|
this.saveBundleInfo(id, next);
|
|
464
|
+
this.notifyDownload(id, 100);
|
|
364
465
|
|
|
365
|
-
final
|
|
366
|
-
ret.put("bundle", next.
|
|
367
|
-
|
|
466
|
+
final Map<String, Object> ret = new HashMap<>();
|
|
467
|
+
ret.put("bundle", next.toJSONMap());
|
|
468
|
+
logger.info("updateAvailable: " + ret);
|
|
469
|
+
CapgoUpdater.this.notifyListeners("updateAvailable", ret);
|
|
470
|
+
logger.info("setNext: " + setNext);
|
|
368
471
|
if (setNext) {
|
|
472
|
+
logger.info("directUpdate: " + this.directUpdate);
|
|
369
473
|
if (this.directUpdate) {
|
|
370
|
-
|
|
474
|
+
CapgoUpdater.this.directUpdateFinish(next);
|
|
371
475
|
this.directUpdate = false;
|
|
372
476
|
} else {
|
|
373
477
|
this.setNextBundle(next.getId());
|
|
374
478
|
}
|
|
375
479
|
}
|
|
376
480
|
} catch (IOException e) {
|
|
481
|
+
if (!isManifest) {
|
|
482
|
+
safeDelete(extractedDir);
|
|
483
|
+
safeDelete(downloaded);
|
|
484
|
+
}
|
|
377
485
|
e.printStackTrace();
|
|
378
|
-
final
|
|
379
|
-
ret.put("version",
|
|
380
|
-
|
|
381
|
-
|
|
486
|
+
final Map<String, Object> ret = new HashMap<>();
|
|
487
|
+
ret.put("version", version);
|
|
488
|
+
CapgoUpdater.this.notifyListeners("downloadFailed", ret);
|
|
489
|
+
CapgoUpdater.this.sendStats("download_fail");
|
|
382
490
|
return false;
|
|
383
491
|
}
|
|
492
|
+
if (!isManifest) {
|
|
493
|
+
safeDelete(downloaded);
|
|
494
|
+
}
|
|
384
495
|
return true;
|
|
385
496
|
}
|
|
386
497
|
|
|
@@ -398,9 +509,76 @@ public class CapacitorUpdater {
|
|
|
398
509
|
}
|
|
399
510
|
}
|
|
400
511
|
|
|
512
|
+
public void cleanupDeltaCache() {
|
|
513
|
+
if (this.activity == null) {
|
|
514
|
+
logger.warn("Activity is null, skipping delta cache cleanup");
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
final File cacheFolder = new File(this.activity.getCacheDir(), "capgo_downloads");
|
|
518
|
+
if (!cacheFolder.exists()) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
this.deleteDirectory(cacheFolder);
|
|
523
|
+
logger.info("Cleaned up delta cache folder");
|
|
524
|
+
} catch (IOException e) {
|
|
525
|
+
logger.error("Failed to cleanup delta cache: " + e.getMessage());
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
public void cleanupDownloadDirectories(final Set<String> allowedIds) {
|
|
530
|
+
if (this.documentsDir == null) {
|
|
531
|
+
logger.warn("Documents directory is null, skipping download cleanup");
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
final File bundleRoot = new File(this.documentsDir, bundleDirectory);
|
|
536
|
+
if (!bundleRoot.exists() || !bundleRoot.isDirectory()) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
final File[] entries = bundleRoot.listFiles();
|
|
541
|
+
if (entries != null) {
|
|
542
|
+
for (final File entry : entries) {
|
|
543
|
+
if (!entry.isDirectory()) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
final String id = entry.getName();
|
|
548
|
+
|
|
549
|
+
if (allowedIds != null && allowedIds.contains(id)) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
this.deleteDirectory(entry);
|
|
555
|
+
this.removeBundleInfo(id);
|
|
556
|
+
logger.info("Deleted orphan bundle directory: " + id);
|
|
557
|
+
} catch (IOException e) {
|
|
558
|
+
logger.error("Failed to delete orphan bundle directory: " + id + " " + e.getMessage());
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
private void safeDelete(final File target) {
|
|
565
|
+
if (target == null || !target.exists()) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
try {
|
|
569
|
+
if (target.isDirectory()) {
|
|
570
|
+
this.deleteDirectory(target);
|
|
571
|
+
} else if (!target.delete()) {
|
|
572
|
+
logger.warn("Failed to delete file: " + target.getAbsolutePath());
|
|
573
|
+
}
|
|
574
|
+
} catch (IOException cleanupError) {
|
|
575
|
+
logger.warn("Cleanup failed for " + target.getAbsolutePath() + ": " + cleanupError.getMessage());
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
401
579
|
private void setCurrentBundle(final File bundle) {
|
|
402
|
-
this.editor.putString(
|
|
403
|
-
|
|
580
|
+
this.editor.putString(this.CAP_SERVER_PATH, bundle.getPath());
|
|
581
|
+
logger.info("Current bundle set to: " + bundle);
|
|
404
582
|
this.editor.commit();
|
|
405
583
|
}
|
|
406
584
|
|
|
@@ -413,13 +591,21 @@ public class CapacitorUpdater {
|
|
|
413
591
|
) {
|
|
414
592
|
final String id = this.randomString();
|
|
415
593
|
|
|
416
|
-
// Check if version is already downloading
|
|
417
|
-
if (this.activity != null && DownloadWorkerManager.isVersionDownloading(version)) {
|
|
418
|
-
|
|
419
|
-
|
|
594
|
+
// Check if version is already downloading, but allow retry if previous download failed
|
|
595
|
+
if (this.activity != null && DownloadWorkerManager.isVersionDownloading(this.activity, version)) {
|
|
596
|
+
// Check if there's an existing bundle with error status that we can retry
|
|
597
|
+
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
598
|
+
if (existingBundle != null && existingBundle.isErrorStatus()) {
|
|
599
|
+
// Cancel the failed download and allow retry
|
|
600
|
+
DownloadWorkerManager.cancelVersionDownload(this.activity, version);
|
|
601
|
+
logger.info("Retrying failed download for version: " + version);
|
|
602
|
+
} else {
|
|
603
|
+
logger.info("Version already downloading: " + version);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
420
606
|
}
|
|
421
607
|
|
|
422
|
-
|
|
608
|
+
saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
|
|
423
609
|
this.notifyDownload(id, 0);
|
|
424
610
|
this.notifyDownload(id, 5);
|
|
425
611
|
|
|
@@ -427,42 +613,43 @@ public class CapacitorUpdater {
|
|
|
427
613
|
}
|
|
428
614
|
|
|
429
615
|
public BundleInfo download(final String url, final String version, final String sessionKey, final String checksum) throws IOException {
|
|
616
|
+
// Check for existing bundle with same version and clean up if in error state
|
|
617
|
+
BundleInfo existingBundle = this.getBundleInfoByName(version);
|
|
618
|
+
if (existingBundle != null && (existingBundle.isErrorStatus() || existingBundle.isDeleted())) {
|
|
619
|
+
logger.info("Found existing failed bundle for version " + version + ", deleting before retry");
|
|
620
|
+
this.delete(existingBundle.getId(), true);
|
|
621
|
+
}
|
|
622
|
+
|
|
430
623
|
final String id = this.randomString();
|
|
431
|
-
|
|
624
|
+
saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
|
|
432
625
|
this.notifyDownload(id, 0);
|
|
433
626
|
this.notifyDownload(id, 5);
|
|
434
627
|
final String dest = this.randomString();
|
|
435
628
|
|
|
436
|
-
//
|
|
629
|
+
// Create a CompletableFuture to track download completion
|
|
630
|
+
CompletableFuture<BundleInfo> downloadFuture = new CompletableFuture<>();
|
|
631
|
+
downloadFutures.put(id, downloadFuture);
|
|
632
|
+
|
|
633
|
+
// Start the download
|
|
437
634
|
this.download(id, url, dest, version, sessionKey, checksum, null);
|
|
438
635
|
|
|
439
|
-
// Wait for completion
|
|
636
|
+
// Wait for completion without timeout
|
|
440
637
|
try {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (workInfos != null && !workInfos.isEmpty()) {
|
|
446
|
-
WorkInfo workInfo = workInfos.get(0);
|
|
447
|
-
while (!workInfo.getState().isFinished()) {
|
|
448
|
-
Thread.sleep(100);
|
|
449
|
-
workInfos = Futures.getChecked(WorkManager.getInstance(activity).getWorkInfosByTag(id), IOException.class);
|
|
450
|
-
if (workInfos != null && !workInfos.isEmpty()) {
|
|
451
|
-
workInfo = workInfos.get(0);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (workInfo.getState() != WorkInfo.State.SUCCEEDED) {
|
|
456
|
-
Data outputData = workInfo.getOutputData();
|
|
457
|
-
String error = outputData.getString(DownloadService.ERROR);
|
|
458
|
-
throw new IOException(error != null ? error : "Download failed: " + workInfo.getState());
|
|
459
|
-
}
|
|
638
|
+
BundleInfo result = downloadFuture.get();
|
|
639
|
+
if (result.isErrorStatus()) {
|
|
640
|
+
throw new IOException("Download failed with status: " + result.getStatus());
|
|
460
641
|
}
|
|
461
|
-
return
|
|
642
|
+
return result;
|
|
462
643
|
} catch (Exception e) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
644
|
+
// Clean up on failure
|
|
645
|
+
downloadFutures.remove(id);
|
|
646
|
+
logger.error("Error waiting for download: " + e.getMessage());
|
|
647
|
+
BundleInfo errorBundle = new BundleInfo(id, version, BundleStatus.ERROR, new Date(System.currentTimeMillis()), "");
|
|
648
|
+
saveBundleInfo(id, errorBundle);
|
|
649
|
+
if (e instanceof IOException) {
|
|
650
|
+
throw (IOException) e;
|
|
651
|
+
}
|
|
652
|
+
throw new IOException("Error waiting for download: " + e.getMessage(), e);
|
|
466
653
|
}
|
|
467
654
|
}
|
|
468
655
|
|
|
@@ -470,14 +657,14 @@ public class CapacitorUpdater {
|
|
|
470
657
|
if (!rawList) {
|
|
471
658
|
final List<BundleInfo> res = new ArrayList<>();
|
|
472
659
|
final File destHot = new File(this.documentsDir, bundleDirectory);
|
|
473
|
-
|
|
660
|
+
logger.debug("list File : " + destHot.getPath());
|
|
474
661
|
if (destHot.exists()) {
|
|
475
662
|
for (final File i : Objects.requireNonNull(destHot.listFiles())) {
|
|
476
663
|
final String id = i.getName();
|
|
477
664
|
res.add(this.getBundleInfo(id));
|
|
478
665
|
}
|
|
479
666
|
} else {
|
|
480
|
-
|
|
667
|
+
logger.info("No versions available to list" + destHot);
|
|
481
668
|
}
|
|
482
669
|
return res;
|
|
483
670
|
} else {
|
|
@@ -496,12 +683,12 @@ public class CapacitorUpdater {
|
|
|
496
683
|
public Boolean delete(final String id, final Boolean removeInfo) throws IOException {
|
|
497
684
|
final BundleInfo deleted = this.getBundleInfo(id);
|
|
498
685
|
if (deleted.isBuiltin() || this.getCurrentBundleId().equals(id)) {
|
|
499
|
-
|
|
686
|
+
logger.error("Cannot delete " + id);
|
|
500
687
|
return false;
|
|
501
688
|
}
|
|
502
689
|
final BundleInfo next = this.getNextBundle();
|
|
503
690
|
if (next != null && !next.isDeleted() && !next.isErrorStatus() && next.getId().equals(id)) {
|
|
504
|
-
|
|
691
|
+
logger.error("Cannot delete the next bundle" + id);
|
|
505
692
|
return false;
|
|
506
693
|
}
|
|
507
694
|
// Cancel download for this version if active
|
|
@@ -518,7 +705,7 @@ public class CapacitorUpdater {
|
|
|
518
705
|
}
|
|
519
706
|
return true;
|
|
520
707
|
}
|
|
521
|
-
|
|
708
|
+
logger.error("bundle removed: " + deleted.getVersionName());
|
|
522
709
|
// perhaps we did not find the bundle in the files, but if the user requested a delete, we delete
|
|
523
710
|
if (removeInfo) {
|
|
524
711
|
this.removeBundleInfo(id);
|
|
@@ -532,7 +719,7 @@ public class CapacitorUpdater {
|
|
|
532
719
|
return this.delete(id, true);
|
|
533
720
|
} catch (IOException e) {
|
|
534
721
|
e.printStackTrace();
|
|
535
|
-
|
|
722
|
+
logger.info("Failed to delete bundle (" + id + ")" + "\nError:\n" + e.toString());
|
|
536
723
|
return false;
|
|
537
724
|
}
|
|
538
725
|
}
|
|
@@ -558,7 +745,7 @@ public class CapacitorUpdater {
|
|
|
558
745
|
return true;
|
|
559
746
|
}
|
|
560
747
|
final File bundle = this.getBundleDirectory(id);
|
|
561
|
-
|
|
748
|
+
logger.info("Setting next active bundle: " + id);
|
|
562
749
|
if (this.bundleExists(id)) {
|
|
563
750
|
var currentBundleName = this.getCurrentBundle().getVersionName();
|
|
564
751
|
this.setCurrentBundle(bundle);
|
|
@@ -574,7 +761,7 @@ public class CapacitorUpdater {
|
|
|
574
761
|
public void autoReset() {
|
|
575
762
|
final BundleInfo currentBundle = this.getCurrentBundle();
|
|
576
763
|
if (!currentBundle.isBuiltin() && !this.bundleExists(currentBundle.getId())) {
|
|
577
|
-
|
|
764
|
+
logger.info("Folder at bundle path does not exist. Triggering reset.");
|
|
578
765
|
this.reset();
|
|
579
766
|
}
|
|
580
767
|
}
|
|
@@ -586,12 +773,17 @@ public class CapacitorUpdater {
|
|
|
586
773
|
public void setSuccess(final BundleInfo bundle, Boolean autoDeletePrevious) {
|
|
587
774
|
this.setBundleStatus(bundle.getId(), BundleStatus.SUCCESS);
|
|
588
775
|
final BundleInfo fallback = this.getFallbackBundle();
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
776
|
+
logger.debug("Fallback bundle is: " + fallback);
|
|
777
|
+
logger.info("Version successfully loaded: " + bundle.getVersionName());
|
|
778
|
+
// Only attempt to delete when the fallback is a different bundle than the
|
|
779
|
+
// currently loaded one. Otherwise we spam logs with "Cannot delete <id>"
|
|
780
|
+
// because delete() protects the current bundle from removal.
|
|
781
|
+
if (autoDeletePrevious && !fallback.isBuiltin() && fallback.getId() != null && !fallback.getId().equals(bundle.getId())) {
|
|
592
782
|
final Boolean res = this.delete(fallback.getId());
|
|
593
783
|
if (res) {
|
|
594
|
-
|
|
784
|
+
logger.info("Deleted previous bundle: " + fallback.getVersionName());
|
|
785
|
+
} else {
|
|
786
|
+
logger.debug("Skip deleting previous bundle (same as current or protected): " + fallback.getId());
|
|
595
787
|
}
|
|
596
788
|
}
|
|
597
789
|
this.setFallbackBundle(bundle);
|
|
@@ -602,7 +794,7 @@ public class CapacitorUpdater {
|
|
|
602
794
|
}
|
|
603
795
|
|
|
604
796
|
public void reset(final boolean internal) {
|
|
605
|
-
|
|
797
|
+
logger.debug("reset: " + internal);
|
|
606
798
|
var currentBundleName = this.getCurrentBundle().getVersionName();
|
|
607
799
|
this.setCurrentBundle(new File("public"));
|
|
608
800
|
this.setFallbackBundle(null);
|
|
@@ -626,26 +818,85 @@ public class CapacitorUpdater {
|
|
|
626
818
|
json.put("version_code", this.versionCode);
|
|
627
819
|
json.put("version_os", this.versionOs);
|
|
628
820
|
json.put("version_name", this.getCurrentBundle().getVersionName());
|
|
629
|
-
json.put("plugin_version", this.
|
|
821
|
+
json.put("plugin_version", this.pluginVersion);
|
|
630
822
|
json.put("is_emulator", this.isEmulator());
|
|
631
823
|
json.put("is_prod", this.isProd());
|
|
632
824
|
json.put("defaultChannel", this.defaultChannel);
|
|
825
|
+
|
|
826
|
+
// Add encryption key ID if encryption is enabled (use cached value)
|
|
827
|
+
if (!this.cachedKeyId.isEmpty()) {
|
|
828
|
+
json.put("key_id", this.cachedKeyId);
|
|
829
|
+
}
|
|
830
|
+
|
|
633
831
|
return json;
|
|
634
832
|
}
|
|
635
833
|
|
|
834
|
+
/**
|
|
835
|
+
* Check if a 429 (Too Many Requests) response was received and set the flag
|
|
836
|
+
*/
|
|
837
|
+
private boolean checkAndHandleRateLimitResponse(Response response) {
|
|
838
|
+
if (response.code() == 429) {
|
|
839
|
+
// Send a statistic about the rate limit BEFORE setting the flag
|
|
840
|
+
// Only send once to prevent infinite loop if the stat request itself gets rate limited
|
|
841
|
+
if (!rateLimitExceeded && !rateLimitStatisticSent) {
|
|
842
|
+
rateLimitStatisticSent = true;
|
|
843
|
+
sendRateLimitStatistic();
|
|
844
|
+
}
|
|
845
|
+
rateLimitExceeded = true;
|
|
846
|
+
logger.warn("Rate limit exceeded (429). Stopping all stats and channel requests until app restart.");
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Send a synchronous statistic about rate limiting
|
|
854
|
+
*/
|
|
855
|
+
private void sendRateLimitStatistic() {
|
|
856
|
+
String statsUrl = this.statsUrl;
|
|
857
|
+
if (statsUrl == null || statsUrl.isEmpty()) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
try {
|
|
862
|
+
BundleInfo current = this.getCurrentBundle();
|
|
863
|
+
JSONObject json = this.createInfoObject();
|
|
864
|
+
json.put("version_name", current.getVersionName());
|
|
865
|
+
json.put("old_version_name", "");
|
|
866
|
+
json.put("action", "rate_limit_reached");
|
|
867
|
+
|
|
868
|
+
Request request = new Request.Builder()
|
|
869
|
+
.url(statsUrl)
|
|
870
|
+
.post(RequestBody.create(json.toString(), MediaType.get("application/json")))
|
|
871
|
+
.build();
|
|
872
|
+
|
|
873
|
+
// Send synchronously to ensure it goes out before the flag is set
|
|
874
|
+
// User-Agent header is automatically added by DownloadService.sharedClient interceptor
|
|
875
|
+
try (Response response = DownloadService.sharedClient.newCall(request).execute()) {
|
|
876
|
+
if (response.isSuccessful()) {
|
|
877
|
+
logger.info("Rate limit statistic sent");
|
|
878
|
+
} else {
|
|
879
|
+
logger.error("Error sending rate limit statistic: " + response.code());
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
} catch (final Exception e) {
|
|
883
|
+
logger.error("Failed to send rate limit statistic: " + e.getMessage());
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
636
887
|
private void makeJsonRequest(String url, JSONObject jsonBody, Callback callback) {
|
|
637
888
|
MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
|
638
889
|
RequestBody body = RequestBody.create(jsonBody.toString(), JSON);
|
|
639
890
|
|
|
640
891
|
Request request = new Request.Builder().url(url).post(body).build();
|
|
641
892
|
|
|
642
|
-
|
|
893
|
+
DownloadService.sharedClient
|
|
643
894
|
.newCall(request)
|
|
644
895
|
.enqueue(
|
|
645
896
|
new okhttp3.Callback() {
|
|
646
897
|
@Override
|
|
647
898
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
648
|
-
|
|
899
|
+
Map<String, Object> retError = new HashMap<>();
|
|
649
900
|
retError.put("message", "Request failed: " + e.getMessage());
|
|
650
901
|
retError.put("error", "network_error");
|
|
651
902
|
callback.callback(retError);
|
|
@@ -654,10 +905,22 @@ public class CapacitorUpdater {
|
|
|
654
905
|
@Override
|
|
655
906
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
656
907
|
try (ResponseBody responseBody = response.body()) {
|
|
908
|
+
final int statusCode = response.code();
|
|
909
|
+
// Check for 429 rate limit
|
|
910
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
911
|
+
Map<String, Object> retError = new HashMap<>();
|
|
912
|
+
retError.put("message", "Rate limit exceeded");
|
|
913
|
+
retError.put("error", "rate_limit_exceeded");
|
|
914
|
+
retError.put("statusCode", statusCode);
|
|
915
|
+
callback.callback(retError);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
657
919
|
if (!response.isSuccessful()) {
|
|
658
|
-
|
|
920
|
+
Map<String, Object> retError = new HashMap<>();
|
|
659
921
|
retError.put("message", "Server error: " + response.code());
|
|
660
922
|
retError.put("error", "response_error");
|
|
923
|
+
retError.put("statusCode", statusCode);
|
|
661
924
|
callback.callback(retError);
|
|
662
925
|
return;
|
|
663
926
|
}
|
|
@@ -665,7 +928,23 @@ public class CapacitorUpdater {
|
|
|
665
928
|
assert responseBody != null;
|
|
666
929
|
String responseData = responseBody.string();
|
|
667
930
|
JSONObject jsonResponse = new JSONObject(responseData);
|
|
668
|
-
|
|
931
|
+
|
|
932
|
+
// Check for server-side errors first
|
|
933
|
+
if (jsonResponse.has("error")) {
|
|
934
|
+
Map<String, Object> retError = new HashMap<>();
|
|
935
|
+
retError.put("error", jsonResponse.getString("error"));
|
|
936
|
+
if (jsonResponse.has("message")) {
|
|
937
|
+
retError.put("message", jsonResponse.getString("message"));
|
|
938
|
+
} else {
|
|
939
|
+
retError.put("message", "server did not provide a message");
|
|
940
|
+
}
|
|
941
|
+
retError.put("statusCode", statusCode);
|
|
942
|
+
callback.callback(retError);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
Map<String, Object> ret = new HashMap<>();
|
|
947
|
+
ret.put("statusCode", statusCode);
|
|
669
948
|
|
|
670
949
|
Iterator<String> keys = jsonResponse.keys();
|
|
671
950
|
while (keys.hasNext()) {
|
|
@@ -680,7 +959,7 @@ public class CapacitorUpdater {
|
|
|
680
959
|
}
|
|
681
960
|
callback.callback(ret);
|
|
682
961
|
} catch (JSONException e) {
|
|
683
|
-
|
|
962
|
+
Map<String, Object> retError = new HashMap<>();
|
|
684
963
|
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
685
964
|
retError.put("error", "parse_error");
|
|
686
965
|
callback.callback(retError);
|
|
@@ -698,24 +977,68 @@ public class CapacitorUpdater {
|
|
|
698
977
|
json.put("defaultChannel", channel);
|
|
699
978
|
}
|
|
700
979
|
} catch (JSONException e) {
|
|
701
|
-
|
|
702
|
-
final
|
|
980
|
+
logger.error("Error getLatest JSONException " + e.getMessage());
|
|
981
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
703
982
|
retError.put("message", "Cannot get info: " + e);
|
|
704
983
|
retError.put("error", "json_error");
|
|
705
984
|
callback.callback(retError);
|
|
706
985
|
return;
|
|
707
986
|
}
|
|
708
987
|
|
|
709
|
-
|
|
988
|
+
logger.info("Auto-update parameters: " + json);
|
|
710
989
|
|
|
711
990
|
makeJsonRequest(updateUrl, json, callback);
|
|
712
991
|
}
|
|
713
992
|
|
|
714
|
-
public void unsetChannel(
|
|
993
|
+
public void unsetChannel(
|
|
994
|
+
final SharedPreferences.Editor editor,
|
|
995
|
+
final String defaultChannelKey,
|
|
996
|
+
final String configDefaultChannel,
|
|
997
|
+
final Callback callback
|
|
998
|
+
) {
|
|
999
|
+
// Clear persisted defaultChannel and revert to config value
|
|
1000
|
+
editor.remove(defaultChannelKey);
|
|
1001
|
+
editor.apply();
|
|
1002
|
+
this.defaultChannel = configDefaultChannel;
|
|
1003
|
+
logger.info("Persisted defaultChannel cleared, reverted to config value: " + configDefaultChannel);
|
|
1004
|
+
|
|
1005
|
+
Map<String, Object> ret = new HashMap<>();
|
|
1006
|
+
ret.put("status", "ok");
|
|
1007
|
+
ret.put("message", "Channel override removed");
|
|
1008
|
+
callback.callback(ret);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
public void setChannel(
|
|
1012
|
+
final String channel,
|
|
1013
|
+
final SharedPreferences.Editor editor,
|
|
1014
|
+
final String defaultChannelKey,
|
|
1015
|
+
final boolean allowSetDefaultChannel,
|
|
1016
|
+
final Callback callback
|
|
1017
|
+
) {
|
|
1018
|
+
// Check if setting defaultChannel is allowed
|
|
1019
|
+
if (!allowSetDefaultChannel) {
|
|
1020
|
+
logger.error("setChannel is disabled by allowSetDefaultChannel config");
|
|
1021
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1022
|
+
retError.put("message", "setChannel is disabled by configuration");
|
|
1023
|
+
retError.put("error", "disabled_by_config");
|
|
1024
|
+
callback.callback(retError);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Check if rate limit was exceeded
|
|
1029
|
+
if (rateLimitExceeded) {
|
|
1030
|
+
logger.debug("Skipping setChannel due to rate limit (429). Requests will resume after app restart.");
|
|
1031
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1032
|
+
retError.put("message", "Rate limit exceeded");
|
|
1033
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1034
|
+
callback.callback(retError);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
715
1038
|
String channelUrl = this.channelUrl;
|
|
716
1039
|
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
717
|
-
|
|
718
|
-
final
|
|
1040
|
+
logger.error("Channel URL is not set");
|
|
1041
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
719
1042
|
retError.put("message", "channelUrl missing");
|
|
720
1043
|
retError.put("error", "missing_config");
|
|
721
1044
|
callback.callback(retError);
|
|
@@ -724,9 +1047,56 @@ public class CapacitorUpdater {
|
|
|
724
1047
|
JSONObject json;
|
|
725
1048
|
try {
|
|
726
1049
|
json = this.createInfoObject();
|
|
1050
|
+
json.put("channel", channel);
|
|
727
1051
|
} catch (JSONException e) {
|
|
728
|
-
|
|
729
|
-
final
|
|
1052
|
+
logger.error("Error setChannel JSONException " + e.getMessage());
|
|
1053
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1054
|
+
retError.put("message", "Cannot get info: " + e);
|
|
1055
|
+
retError.put("error", "json_error");
|
|
1056
|
+
callback.callback(retError);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
makeJsonRequest(channelUrl, json, (res) -> {
|
|
1061
|
+
if (res.containsKey("error")) {
|
|
1062
|
+
callback.callback(res);
|
|
1063
|
+
} else {
|
|
1064
|
+
// Success - persist defaultChannel
|
|
1065
|
+
this.defaultChannel = channel;
|
|
1066
|
+
editor.putString(defaultChannelKey, channel);
|
|
1067
|
+
editor.apply();
|
|
1068
|
+
logger.info("defaultChannel persisted locally: " + channel);
|
|
1069
|
+
callback.callback(res);
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
public void getChannel(final Callback callback) {
|
|
1075
|
+
// Check if rate limit was exceeded
|
|
1076
|
+
if (rateLimitExceeded) {
|
|
1077
|
+
logger.debug("Skipping getChannel due to rate limit (429). Requests will resume after app restart.");
|
|
1078
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1079
|
+
retError.put("message", "Rate limit exceeded");
|
|
1080
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1081
|
+
callback.callback(retError);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
String channelUrl = this.channelUrl;
|
|
1086
|
+
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
1087
|
+
logger.error("Channel URL is not set");
|
|
1088
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1089
|
+
retError.put("message", "Channel URL is not set");
|
|
1090
|
+
retError.put("error", "missing_config");
|
|
1091
|
+
callback.callback(retError);
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
JSONObject json;
|
|
1095
|
+
try {
|
|
1096
|
+
json = this.createInfoObject();
|
|
1097
|
+
} catch (JSONException e) {
|
|
1098
|
+
logger.error("Error getChannel JSONException " + e.getMessage());
|
|
1099
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
730
1100
|
retError.put("message", "Cannot get info: " + e);
|
|
731
1101
|
retError.put("error", "json_error");
|
|
732
1102
|
callback.callback(retError);
|
|
@@ -735,16 +1105,16 @@ public class CapacitorUpdater {
|
|
|
735
1105
|
|
|
736
1106
|
Request request = new Request.Builder()
|
|
737
1107
|
.url(channelUrl)
|
|
738
|
-
.
|
|
1108
|
+
.put(RequestBody.create(json.toString(), MediaType.get("application/json")))
|
|
739
1109
|
.build();
|
|
740
1110
|
|
|
741
|
-
|
|
1111
|
+
DownloadService.sharedClient
|
|
742
1112
|
.newCall(request)
|
|
743
1113
|
.enqueue(
|
|
744
1114
|
new okhttp3.Callback() {
|
|
745
1115
|
@Override
|
|
746
1116
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
747
|
-
|
|
1117
|
+
Map<String, Object> retError = new HashMap<>();
|
|
748
1118
|
retError.put("message", "Request failed: " + e.getMessage());
|
|
749
1119
|
retError.put("error", "network_error");
|
|
750
1120
|
callback.callback(retError);
|
|
@@ -753,8 +1123,30 @@ public class CapacitorUpdater {
|
|
|
753
1123
|
@Override
|
|
754
1124
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
755
1125
|
try (ResponseBody responseBody = response.body()) {
|
|
1126
|
+
// Check for 429 rate limit
|
|
1127
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
1128
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1129
|
+
retError.put("message", "Rate limit exceeded");
|
|
1130
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1131
|
+
callback.callback(retError);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if (response.code() == 400) {
|
|
1136
|
+
assert responseBody != null;
|
|
1137
|
+
String data = responseBody.string();
|
|
1138
|
+
if (data.contains("channel_not_found") && !defaultChannel.isEmpty()) {
|
|
1139
|
+
Map<String, Object> ret = new HashMap<>();
|
|
1140
|
+
ret.put("channel", defaultChannel);
|
|
1141
|
+
ret.put("status", "default");
|
|
1142
|
+
logger.info("Channel get to \"" + ret);
|
|
1143
|
+
callback.callback(ret);
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
756
1148
|
if (!response.isSuccessful()) {
|
|
757
|
-
|
|
1149
|
+
Map<String, Object> retError = new HashMap<>();
|
|
758
1150
|
retError.put("message", "Server error: " + response.code());
|
|
759
1151
|
retError.put("error", "response_error");
|
|
760
1152
|
callback.callback(retError);
|
|
@@ -764,7 +1156,21 @@ public class CapacitorUpdater {
|
|
|
764
1156
|
assert responseBody != null;
|
|
765
1157
|
String responseData = responseBody.string();
|
|
766
1158
|
JSONObject jsonResponse = new JSONObject(responseData);
|
|
767
|
-
|
|
1159
|
+
|
|
1160
|
+
// Check for server-side errors first
|
|
1161
|
+
if (jsonResponse.has("error")) {
|
|
1162
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1163
|
+
retError.put("error", jsonResponse.getString("error"));
|
|
1164
|
+
if (jsonResponse.has("message")) {
|
|
1165
|
+
retError.put("message", jsonResponse.getString("message"));
|
|
1166
|
+
} else {
|
|
1167
|
+
retError.put("message", "server did not provide a message");
|
|
1168
|
+
}
|
|
1169
|
+
callback.callback(retError);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
Map<String, Object> ret = new HashMap<>();
|
|
768
1174
|
|
|
769
1175
|
Iterator<String> keys = jsonResponse.keys();
|
|
770
1176
|
while (keys.hasNext()) {
|
|
@@ -773,10 +1179,10 @@ public class CapacitorUpdater {
|
|
|
773
1179
|
ret.put(key, jsonResponse.get(key));
|
|
774
1180
|
}
|
|
775
1181
|
}
|
|
776
|
-
|
|
1182
|
+
logger.info("Channel get to \"" + ret);
|
|
777
1183
|
callback.callback(ret);
|
|
778
1184
|
} catch (JSONException e) {
|
|
779
|
-
|
|
1185
|
+
Map<String, Object> retError = new HashMap<>();
|
|
780
1186
|
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
781
1187
|
retError.put("error", "parse_error");
|
|
782
1188
|
callback.callback(retError);
|
|
@@ -786,66 +1192,63 @@ public class CapacitorUpdater {
|
|
|
786
1192
|
);
|
|
787
1193
|
}
|
|
788
1194
|
|
|
789
|
-
public void
|
|
790
|
-
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
final
|
|
794
|
-
retError.put("message", "
|
|
795
|
-
retError.put("error", "
|
|
1195
|
+
public void listChannels(final Callback callback) {
|
|
1196
|
+
// Check if rate limit was exceeded
|
|
1197
|
+
if (rateLimitExceeded) {
|
|
1198
|
+
logger.debug("Skipping listChannels due to rate limit (429). Requests will resume after app restart.");
|
|
1199
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
1200
|
+
retError.put("message", "Rate limit exceeded");
|
|
1201
|
+
retError.put("error", "rate_limit_exceeded");
|
|
796
1202
|
callback.callback(retError);
|
|
797
1203
|
return;
|
|
798
1204
|
}
|
|
799
|
-
JSONObject json;
|
|
800
|
-
try {
|
|
801
|
-
json = this.createInfoObject();
|
|
802
|
-
json.put("channel", channel);
|
|
803
|
-
} catch (JSONException e) {
|
|
804
|
-
Log.e(TAG, "Error setChannel JSONException", e);
|
|
805
|
-
final JSObject retError = new JSObject();
|
|
806
|
-
retError.put("message", "Cannot get info: " + e);
|
|
807
|
-
retError.put("error", "json_error");
|
|
808
|
-
callback.callback(retError);
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
makeJsonRequest(channelUrl, json, callback);
|
|
813
|
-
}
|
|
814
1205
|
|
|
815
|
-
public void getChannel(final Callback callback) {
|
|
816
1206
|
String channelUrl = this.channelUrl;
|
|
817
1207
|
if (channelUrl == null || channelUrl.isEmpty()) {
|
|
818
|
-
|
|
819
|
-
final
|
|
1208
|
+
logger.error("Channel URL is not set");
|
|
1209
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
820
1210
|
retError.put("message", "Channel URL is not set");
|
|
821
1211
|
retError.put("error", "missing_config");
|
|
822
1212
|
callback.callback(retError);
|
|
823
1213
|
return;
|
|
824
1214
|
}
|
|
1215
|
+
|
|
825
1216
|
JSONObject json;
|
|
826
1217
|
try {
|
|
827
1218
|
json = this.createInfoObject();
|
|
828
1219
|
} catch (JSONException e) {
|
|
829
|
-
|
|
830
|
-
final
|
|
1220
|
+
logger.error("Error creating info object: " + e.getMessage());
|
|
1221
|
+
final Map<String, Object> retError = new HashMap<>();
|
|
831
1222
|
retError.put("message", "Cannot get info: " + e);
|
|
832
1223
|
retError.put("error", "json_error");
|
|
833
1224
|
callback.callback(retError);
|
|
834
1225
|
return;
|
|
835
1226
|
}
|
|
836
1227
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
.
|
|
1228
|
+
// Build URL with query parameters from JSON
|
|
1229
|
+
HttpUrl.Builder urlBuilder = HttpUrl.parse(channelUrl).newBuilder();
|
|
1230
|
+
try {
|
|
1231
|
+
Iterator<String> keys = json.keys();
|
|
1232
|
+
while (keys.hasNext()) {
|
|
1233
|
+
String key = keys.next();
|
|
1234
|
+
Object value = json.get(key);
|
|
1235
|
+
if (value != null) {
|
|
1236
|
+
urlBuilder.addQueryParameter(key, value.toString());
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
} catch (JSONException e) {
|
|
1240
|
+
logger.error("Error adding query parameters: " + e.getMessage());
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
Request request = new Request.Builder().url(urlBuilder.build()).get().build();
|
|
841
1244
|
|
|
842
|
-
|
|
1245
|
+
DownloadService.sharedClient
|
|
843
1246
|
.newCall(request)
|
|
844
1247
|
.enqueue(
|
|
845
1248
|
new okhttp3.Callback() {
|
|
846
1249
|
@Override
|
|
847
1250
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
848
|
-
|
|
1251
|
+
Map<String, Object> retError = new HashMap<>();
|
|
849
1252
|
retError.put("message", "Request failed: " + e.getMessage());
|
|
850
1253
|
retError.put("error", "network_error");
|
|
851
1254
|
callback.callback(retError);
|
|
@@ -854,21 +1257,17 @@ public class CapacitorUpdater {
|
|
|
854
1257
|
@Override
|
|
855
1258
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
856
1259
|
try (ResponseBody responseBody = response.body()) {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
String
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
Log.i(TAG, "Channel get to \"" + ret);
|
|
865
|
-
callback.callback(ret);
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
1260
|
+
// Check for 429 rate limit
|
|
1261
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
1262
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1263
|
+
retError.put("message", "Rate limit exceeded");
|
|
1264
|
+
retError.put("error", "rate_limit_exceeded");
|
|
1265
|
+
callback.callback(retError);
|
|
1266
|
+
return;
|
|
868
1267
|
}
|
|
869
1268
|
|
|
870
1269
|
if (!response.isSuccessful()) {
|
|
871
|
-
|
|
1270
|
+
Map<String, Object> retError = new HashMap<>();
|
|
872
1271
|
retError.put("message", "Server error: " + response.code());
|
|
873
1272
|
retError.put("error", "response_error");
|
|
874
1273
|
callback.callback(retError);
|
|
@@ -876,24 +1275,57 @@ public class CapacitorUpdater {
|
|
|
876
1275
|
}
|
|
877
1276
|
|
|
878
1277
|
assert responseBody != null;
|
|
879
|
-
String
|
|
880
|
-
|
|
881
|
-
|
|
1278
|
+
String data = responseBody.string();
|
|
1279
|
+
|
|
1280
|
+
try {
|
|
1281
|
+
Map<String, Object> ret = new HashMap<>();
|
|
1282
|
+
|
|
1283
|
+
try {
|
|
1284
|
+
// Try to parse as direct array first
|
|
1285
|
+
JSONArray channelsJson = new JSONArray(data);
|
|
1286
|
+
List<Map<String, Object>> channelsList = new ArrayList<>();
|
|
1287
|
+
|
|
1288
|
+
for (int i = 0; i < channelsJson.length(); i++) {
|
|
1289
|
+
JSONObject channelJson = channelsJson.getJSONObject(i);
|
|
1290
|
+
Map<String, Object> channel = new HashMap<>();
|
|
1291
|
+
channel.put("id", channelJson.optString("id", ""));
|
|
1292
|
+
channel.put("name", channelJson.optString("name", ""));
|
|
1293
|
+
channel.put("public", channelJson.optBoolean("public", false));
|
|
1294
|
+
channel.put("allow_self_set", channelJson.optBoolean("allow_self_set", false));
|
|
1295
|
+
channelsList.add(channel);
|
|
1296
|
+
}
|
|
882
1297
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1298
|
+
// Wrap in channels object for JS API
|
|
1299
|
+
ret.put("channels", channelsList);
|
|
1300
|
+
|
|
1301
|
+
logger.info("Channels listed successfully");
|
|
1302
|
+
callback.callback(ret);
|
|
1303
|
+
} catch (JSONException arrayException) {
|
|
1304
|
+
// If not an array, try to parse as error object
|
|
1305
|
+
try {
|
|
1306
|
+
JSONObject json = new JSONObject(data);
|
|
1307
|
+
if (json.has("error")) {
|
|
1308
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1309
|
+
retError.put("error", json.getString("error"));
|
|
1310
|
+
if (json.has("message")) {
|
|
1311
|
+
retError.put("message", json.getString("message"));
|
|
1312
|
+
} else {
|
|
1313
|
+
retError.put("message", "server did not provide a message");
|
|
1314
|
+
}
|
|
1315
|
+
callback.callback(retError);
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
} catch (JSONException objException) {
|
|
1319
|
+
// If neither array nor object, throw parse error
|
|
1320
|
+
throw arrayException;
|
|
1321
|
+
}
|
|
888
1322
|
}
|
|
1323
|
+
} catch (JSONException e) {
|
|
1324
|
+
Map<String, Object> retError = new HashMap<>();
|
|
1325
|
+
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
1326
|
+
retError.put("error", "parse_error");
|
|
1327
|
+
callback.callback(retError);
|
|
889
1328
|
}
|
|
890
|
-
Log.i(TAG, "Channel get to \"" + ret);
|
|
891
|
-
callback.callback(ret);
|
|
892
|
-
} catch (JSONException e) {
|
|
893
|
-
JSObject retError = new JSObject();
|
|
894
|
-
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
895
|
-
retError.put("error", "parse_error");
|
|
896
|
-
callback.callback(retError);
|
|
897
1329
|
}
|
|
898
1330
|
}
|
|
899
1331
|
}
|
|
@@ -909,6 +1341,12 @@ public class CapacitorUpdater {
|
|
|
909
1341
|
}
|
|
910
1342
|
|
|
911
1343
|
public void sendStats(final String action, final String versionName, final String oldVersionName) {
|
|
1344
|
+
// Check if rate limit was exceeded
|
|
1345
|
+
if (rateLimitExceeded) {
|
|
1346
|
+
logger.debug("Skipping sendStats due to rate limit (429). Stats will resume after app restart.");
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
912
1350
|
String statsUrl = this.statsUrl;
|
|
913
1351
|
if (statsUrl == null || statsUrl.isEmpty()) {
|
|
914
1352
|
return;
|
|
@@ -920,7 +1358,7 @@ public class CapacitorUpdater {
|
|
|
920
1358
|
json.put("old_version_name", oldVersionName);
|
|
921
1359
|
json.put("action", action);
|
|
922
1360
|
} catch (JSONException e) {
|
|
923
|
-
|
|
1361
|
+
logger.error("Error sendStats JSONException " + e.getMessage());
|
|
924
1362
|
return;
|
|
925
1363
|
}
|
|
926
1364
|
|
|
@@ -929,21 +1367,28 @@ public class CapacitorUpdater {
|
|
|
929
1367
|
.post(RequestBody.create(json.toString(), MediaType.get("application/json")))
|
|
930
1368
|
.build();
|
|
931
1369
|
|
|
932
|
-
|
|
1370
|
+
DownloadService.sharedClient
|
|
933
1371
|
.newCall(request)
|
|
934
1372
|
.enqueue(
|
|
935
1373
|
new okhttp3.Callback() {
|
|
936
1374
|
@Override
|
|
937
1375
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
938
|
-
|
|
1376
|
+
logger.error("Failed to send stats: " + e.getMessage());
|
|
939
1377
|
}
|
|
940
1378
|
|
|
941
1379
|
@Override
|
|
942
1380
|
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1381
|
+
try (ResponseBody responseBody = response.body()) {
|
|
1382
|
+
// Check for 429 rate limit
|
|
1383
|
+
if (checkAndHandleRateLimitResponse(response)) {
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (response.isSuccessful()) {
|
|
1388
|
+
logger.info("Stats send for \"" + action + "\", version " + versionName);
|
|
1389
|
+
} else {
|
|
1390
|
+
logger.error("Error sending stats: " + response.code());
|
|
1391
|
+
}
|
|
947
1392
|
}
|
|
948
1393
|
}
|
|
949
1394
|
}
|
|
@@ -963,13 +1408,26 @@ public class CapacitorUpdater {
|
|
|
963
1408
|
} else {
|
|
964
1409
|
try {
|
|
965
1410
|
String stored = this.prefs.getString(trueId + INFO_SUFFIX, "");
|
|
966
|
-
|
|
1411
|
+
if (stored.isEmpty()) {
|
|
1412
|
+
result = new BundleInfo(trueId, null, BundleStatus.PENDING, "", "");
|
|
1413
|
+
} else {
|
|
1414
|
+
result = BundleInfo.fromJSON(stored);
|
|
1415
|
+
}
|
|
967
1416
|
} catch (JSONException e) {
|
|
968
|
-
|
|
969
|
-
|
|
1417
|
+
logger.error(
|
|
1418
|
+
"Failed to parse info for bundle [" +
|
|
1419
|
+
trueId +
|
|
1420
|
+
"] stored value: '" +
|
|
1421
|
+
this.prefs.getString(trueId + INFO_SUFFIX, "") +
|
|
1422
|
+
"' error: " +
|
|
1423
|
+
e.getMessage()
|
|
1424
|
+
);
|
|
1425
|
+
// Clear corrupted data
|
|
1426
|
+
this.editor.remove(trueId + INFO_SUFFIX);
|
|
1427
|
+
this.editor.commit();
|
|
1428
|
+
result = new BundleInfo(trueId, null, BundleStatus.ERROR, "", "");
|
|
970
1429
|
}
|
|
971
1430
|
}
|
|
972
|
-
// Log.d(TAG, "Returning info [" + trueId + "] " + result);
|
|
973
1431
|
return result;
|
|
974
1432
|
}
|
|
975
1433
|
|
|
@@ -989,17 +1447,18 @@ public class CapacitorUpdater {
|
|
|
989
1447
|
|
|
990
1448
|
public void saveBundleInfo(final String id, final BundleInfo info) {
|
|
991
1449
|
if (id == null || (info != null && (info.isBuiltin() || info.isUnknown()))) {
|
|
992
|
-
|
|
1450
|
+
logger.debug("Not saving info for bundle: [" + id + "] " + info);
|
|
993
1451
|
return;
|
|
994
1452
|
}
|
|
995
1453
|
|
|
996
1454
|
if (info == null) {
|
|
997
|
-
|
|
1455
|
+
logger.debug("Removing info for bundle [" + id + "]");
|
|
998
1456
|
this.editor.remove(id + INFO_SUFFIX);
|
|
999
1457
|
} else {
|
|
1000
1458
|
final BundleInfo update = info.setId(id);
|
|
1001
|
-
|
|
1002
|
-
|
|
1459
|
+
String jsonString = update.toString();
|
|
1460
|
+
logger.debug("Storing info for bundle [" + id + "] " + update.getClass().getName() + " -> " + jsonString);
|
|
1461
|
+
this.editor.putString(id + INFO_SUFFIX, jsonString);
|
|
1003
1462
|
}
|
|
1004
1463
|
this.editor.commit();
|
|
1005
1464
|
}
|
|
@@ -1007,7 +1466,7 @@ public class CapacitorUpdater {
|
|
|
1007
1466
|
private void setBundleStatus(final String id, final BundleStatus status) {
|
|
1008
1467
|
if (id != null && status != null) {
|
|
1009
1468
|
BundleInfo info = this.getBundleInfo(id);
|
|
1010
|
-
|
|
1469
|
+
logger.debug("Setting status for bundle [" + id + "] to " + status);
|
|
1011
1470
|
this.saveBundleInfo(id, info.setStatus(status));
|
|
1012
1471
|
}
|
|
1013
1472
|
}
|
|
@@ -1026,7 +1485,7 @@ public class CapacitorUpdater {
|
|
|
1026
1485
|
}
|
|
1027
1486
|
|
|
1028
1487
|
public String getCurrentBundlePath() {
|
|
1029
|
-
String path = this.prefs.getString(
|
|
1488
|
+
String path = this.prefs.getString(this.CAP_SERVER_PATH, "public");
|
|
1030
1489
|
if (path.trim().isEmpty()) {
|
|
1031
1490
|
return "public";
|
|
1032
1491
|
}
|