@capgo/capacitor-updater 6.25.0 → 6.25.3

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.
@@ -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 pluginVersion = "6.25.0";
74
+ private final String pluginVersion = "6.25.3";
75
75
  private static final String DELAY_CONDITION_PREFERENCES = "";
76
76
 
77
77
  private SharedPreferences.Editor editor;
@@ -355,7 +355,15 @@ public class CapgoUpdater {
355
355
  manifest != null,
356
356
  this.isEmulator(),
357
357
  this.appId,
358
- this.pluginVersion
358
+ this.pluginVersion,
359
+ this.isProd(),
360
+ this.statsUrl,
361
+ this.deviceID,
362
+ this.versionBuild,
363
+ this.versionCode,
364
+ this.versionOs,
365
+ this.customId,
366
+ this.defaultChannel
359
367
  );
360
368
 
361
369
  if (manifest != null) {
@@ -28,10 +28,14 @@ import java.util.concurrent.Future;
28
28
  import java.util.concurrent.TimeUnit;
29
29
  import java.util.concurrent.atomic.AtomicBoolean;
30
30
  import java.util.concurrent.atomic.AtomicLong;
31
+ import okhttp3.Call;
32
+ import okhttp3.Callback;
31
33
  import okhttp3.Interceptor;
34
+ import okhttp3.MediaType;
32
35
  import okhttp3.OkHttpClient;
33
36
  import okhttp3.Protocol;
34
37
  import okhttp3.Request;
38
+ import okhttp3.RequestBody;
35
39
  import okhttp3.Response;
36
40
  import okhttp3.ResponseBody;
37
41
  import okio.Buffer;
@@ -64,6 +68,15 @@ public class DownloadService extends Worker {
64
68
  public static final String IS_MANIFEST = "is_manifest";
65
69
  public static final String APP_ID = "app_id";
66
70
  public static final String pluginVersion = "plugin_version";
71
+ public static final String STATS_URL = "stats_url";
72
+ public static final String DEVICE_ID = "device_id";
73
+ public static final String CUSTOM_ID = "custom_id";
74
+ public static final String VERSION_BUILD = "version_build";
75
+ public static final String VERSION_CODE = "version_code";
76
+ public static final String VERSION_OS = "version_os";
77
+ public static final String DEFAULT_CHANNEL = "default_channel";
78
+ public static final String IS_PROD = "is_prod";
79
+ public static final String IS_EMULATOR = "is_emulator";
67
80
  private static final String UPDATE_FILE = "update.dat";
68
81
 
69
82
  // Shared OkHttpClient to prevent resource leaks
@@ -130,6 +143,11 @@ public class DownloadService extends Worker {
130
143
  return Result.success(output);
131
144
  }
132
145
 
146
+ private String getInputString(String key, String fallback) {
147
+ String value = getInputData().getString(key);
148
+ return value != null ? value : fallback;
149
+ }
150
+
133
151
  @NonNull
134
152
  @Override
135
153
  public Result doWork() {
@@ -174,6 +192,62 @@ public class DownloadService extends Worker {
174
192
  return percent;
175
193
  }
176
194
 
195
+ private void sendStatsAsync(String action, String version) {
196
+ try {
197
+ String statsUrl = getInputData().getString(STATS_URL);
198
+ if (statsUrl == null || statsUrl.isEmpty()) {
199
+ return;
200
+ }
201
+
202
+ JSONObject json = new JSONObject();
203
+ json.put("platform", "android");
204
+ json.put("app_id", getInputString(APP_ID, "unknown"));
205
+ json.put("plugin_version", getInputString(pluginVersion, "unknown"));
206
+ json.put("version_name", version != null ? version : "");
207
+ json.put("old_version_name", "");
208
+ json.put("action", action);
209
+ json.put("device_id", getInputString(DEVICE_ID, ""));
210
+ json.put("custom_id", getInputString(CUSTOM_ID, ""));
211
+ json.put("version_build", getInputString(VERSION_BUILD, ""));
212
+ json.put("version_code", getInputString(VERSION_CODE, ""));
213
+ json.put("version_os", getInputString(VERSION_OS, currentVersionOs));
214
+ json.put("defaultChannel", getInputString(DEFAULT_CHANNEL, ""));
215
+ json.put("is_prod", getInputData().getBoolean(IS_PROD, true));
216
+ json.put("is_emulator", getInputData().getBoolean(IS_EMULATOR, false));
217
+
218
+ Request request = new Request.Builder()
219
+ .url(statsUrl)
220
+ .post(RequestBody.create(json.toString(), MediaType.get("application/json")))
221
+ .build();
222
+
223
+ sharedClient
224
+ .newCall(request)
225
+ .enqueue(
226
+ new Callback() {
227
+ @Override
228
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
229
+ if (logger != null) {
230
+ logger.error("Failed to send stats: " + e.getMessage());
231
+ }
232
+ }
233
+
234
+ @Override
235
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
236
+ try (ResponseBody body = response.body()) {
237
+ // nothing else to do, just closing body
238
+ } catch (Exception ignored) {} finally {
239
+ response.close();
240
+ }
241
+ }
242
+ }
243
+ );
244
+ } catch (Exception e) {
245
+ if (logger != null) {
246
+ logger.error("sendStatsAsync error: " + e.getMessage());
247
+ }
248
+ }
249
+ }
250
+
177
251
  private void handleManifestDownload(
178
252
  String id,
179
253
  String documentsDir,
@@ -185,6 +259,10 @@ public class DownloadService extends Worker {
185
259
  ) {
186
260
  try {
187
261
  logger.debug("handleManifestDownload");
262
+
263
+ // Send stats for manifest download start
264
+ sendStatsAsync("download_manifest_start", version);
265
+
188
266
  JSONArray manifest = new JSONArray(manifestString);
189
267
  File destFolder = new File(documentsDir, dest);
190
268
  File cacheFolder = new File(getApplicationContext().getCacheDir(), "capgo_downloads");
@@ -252,6 +330,7 @@ public class DownloadService extends Worker {
252
330
  setProgress(percent);
253
331
  } catch (Exception e) {
254
332
  logger.error("Error processing file: " + fileName + " " + e.getMessage());
333
+ sendStatsAsync("download_manifest_file_fail", version + ":" + fileName);
255
334
  hasError.set(true);
256
335
  }
257
336
  });
@@ -282,6 +361,9 @@ public class DownloadService extends Worker {
282
361
  logger.error("One or more files failed to download");
283
362
  throw new IOException("One or more files failed to download");
284
363
  }
364
+
365
+ // Send stats for manifest download complete
366
+ sendStatsAsync("download_manifest_complete", version);
285
367
  } catch (Exception e) {
286
368
  logger.error("Error in handleManifestDownload " + e.getMessage());
287
369
  throw new RuntimeException(e.getLocalizedMessage());
@@ -297,6 +379,9 @@ public class DownloadService extends Worker {
297
379
  String sessionKey,
298
380
  String checksum
299
381
  ) {
382
+ // Send stats for zip download start
383
+ sendStatsAsync("download_zip_start", version);
384
+
300
385
  File target = new File(documentsDir, dest);
301
386
  File infoFile = new File(documentsDir, UPDATE_FILE);
302
387
  File tempFile = new File(documentsDir, "temp" + ".tmp");
@@ -404,6 +489,9 @@ public class DownloadService extends Worker {
404
489
  throw new RuntimeException("Failed to rename temp file to final destination");
405
490
  }
406
491
  infoFile.delete();
492
+
493
+ // Send stats for zip download complete
494
+ sendStatsAsync("download_zip_complete", version);
407
495
  } catch (OutOfMemoryError e) {
408
496
  logger.error("Out of memory during download: " + e.getMessage());
409
497
  // Try to free some memory
@@ -503,6 +591,7 @@ public class DownloadService extends Worker {
503
591
 
504
592
  try (Response response = sharedClient.newCall(request).execute()) {
505
593
  if (!response.isSuccessful()) {
594
+ sendStatsAsync("download_manifest_file_fail", getInputData().getString(VERSION) + ":" + finalTargetFile.getName());
506
595
  throw new IOException("Unexpected response code: " + response.code());
507
596
  }
508
597
 
@@ -526,7 +615,16 @@ public class DownloadService extends Worker {
526
615
  try (FileInputStream fis = new FileInputStream(compressedFile)) {
527
616
  byte[] compressedData = new byte[(int) compressedFile.length()];
528
617
  fis.read(compressedData);
529
- byte[] decompressedData = decompressBrotli(compressedData, targetFile.getName());
618
+ byte[] decompressedData;
619
+ try {
620
+ decompressedData = decompressBrotli(compressedData, targetFile.getName());
621
+ } catch (IOException e) {
622
+ sendStatsAsync(
623
+ "download_manifest_brotli_fail",
624
+ getInputData().getString(VERSION) + ":" + finalTargetFile.getName()
625
+ );
626
+ throw e;
627
+ }
530
628
 
531
629
  // Write decompressed data atomically
532
630
  try (java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(decompressedData)) {
@@ -552,6 +650,7 @@ public class DownloadService extends Worker {
552
650
  }
553
651
  } else {
554
652
  finalTargetFile.delete();
653
+ sendStatsAsync("download_manifest_checksum_fail", getInputData().getString(VERSION) + ":" + finalTargetFile.getName());
555
654
  throw new IOException(
556
655
  "Checksum verification failed for: " +
557
656
  downloadUrl +
@@ -61,7 +61,15 @@ public class DownloadWorkerManager {
61
61
  boolean isManifest,
62
62
  boolean isEmulator,
63
63
  String appId,
64
- String pluginVersion
64
+ String pluginVersion,
65
+ boolean isProd,
66
+ String statsUrl,
67
+ String deviceId,
68
+ String versionBuild,
69
+ String versionCode,
70
+ String versionOs,
71
+ String customId,
72
+ String defaultChannel
65
73
  ) {
66
74
  initializeIfNeeded(context.getApplicationContext());
67
75
 
@@ -81,6 +89,15 @@ public class DownloadWorkerManager {
81
89
  .putString(DownloadService.PUBLIC_KEY, publicKey)
82
90
  .putString(DownloadService.APP_ID, appId)
83
91
  .putString(DownloadService.pluginVersion, pluginVersion)
92
+ .putString(DownloadService.STATS_URL, statsUrl)
93
+ .putString(DownloadService.DEVICE_ID, deviceId)
94
+ .putString(DownloadService.VERSION_BUILD, versionBuild)
95
+ .putString(DownloadService.VERSION_CODE, versionCode)
96
+ .putString(DownloadService.VERSION_OS, versionOs)
97
+ .putString(DownloadService.CUSTOM_ID, customId)
98
+ .putString(DownloadService.DEFAULT_CHANNEL, defaultChannel)
99
+ .putBoolean(DownloadService.IS_PROD, isProd)
100
+ .putBoolean(DownloadService.IS_EMULATOR, isEmulator)
84
101
  .build();
85
102
 
86
103
  // Create network constraints - be more lenient on emulators
@@ -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 pluginVersion: String = "6.25.0"
57
+ private let pluginVersion: String = "6.25.3"
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"
@@ -411,6 +411,9 @@ import UIKit
411
411
  let bundleInfo = BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: "")
412
412
  self.saveBundleInfo(id: id, bundle: bundleInfo)
413
413
 
414
+ // Send stats for manifest download start
415
+ self.sendStats(action: "download_manifest_start", versionName: version)
416
+
414
417
  // Notify the start of the download process
415
418
  self.notifyDownload(id: id, percent: 0, ignoreMultipleOfTen: true)
416
419
 
@@ -473,6 +476,7 @@ import UIKit
473
476
  do {
474
477
  let statusCode = response.response?.statusCode ?? 200
475
478
  if statusCode < 200 || statusCode >= 300 {
479
+ self.sendStats(action: "download_manifest_file_fail", versionName: "\(version):\(fileName)")
476
480
  if let stringData = String(data: data, encoding: .utf8) {
477
481
  throw NSError(domain: "StatusCodeError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to fetch. Status code (\(statusCode)) invalid. Data: \(stringData) for file \(fileName) at url \(downloadUrl)"])
478
482
  } else {
@@ -515,6 +519,7 @@ import UIKit
515
519
  if isBrotli {
516
520
  // Decompress the Brotli data
517
521
  guard let decompressedData = self.decompressBrotli(data: finalData, fileName: fileName) else {
522
+ self.sendStats(action: "download_manifest_brotli_fail", versionName: "\(version):\(finalFileName)")
518
523
  throw NSError(domain: "BrotliDecompressionError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to decompress Brotli data for file \(fileName) at url \(downloadUrl)"])
519
524
  }
520
525
  finalData = decompressedData
@@ -525,6 +530,7 @@ import UIKit
525
530
  // assume that calcChecksum != null
526
531
  let calculatedChecksum = CryptoCipherV2.calcChecksum(filePath: destFilePath)
527
532
  if calculatedChecksum != fileHash {
533
+ self.sendStats(action: "download_manifest_checksum_fail", versionName: "\(version):\(finalFileName)")
528
534
  throw NSError(domain: "ChecksumError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Computed checksum is not equal to required checksum (\(calculatedChecksum) != \(fileHash)) for file \(fileName) at url \(downloadUrl)"])
529
535
  }
530
536
  }
@@ -540,6 +546,8 @@ import UIKit
540
546
  self.logger.error("downloadManifest \(id) \(fileName) error: \(error.localizedDescription)")
541
547
  }
542
548
  case .failure(let error):
549
+ downloadError = error
550
+ self.sendStats(action: "download_manifest_file_fail", versionName: "\(version):\(fileName)")
543
551
  self.logger.error("downloadManifest \(id) \(fileName) download error: \(error.localizedDescription). Debug response: \(response.debugDescription).")
544
552
  }
545
553
  }
@@ -559,6 +567,9 @@ import UIKit
559
567
  let updatedBundle = bundleInfo.setStatus(status: BundleStatus.PENDING.localizedString)
560
568
  self.saveBundleInfo(id: id, bundle: updatedBundle)
561
569
 
570
+ // Send stats for manifest download complete
571
+ self.sendStats(action: "download_manifest_complete", versionName: version)
572
+
562
573
  logger.info("downloadManifest done \(id)")
563
574
  return updatedBundle
564
575
  }
@@ -689,6 +700,10 @@ import UIKit
689
700
  var lastSentProgress = 0
690
701
  var totalReceivedBytes: Int64 = loadDownloadProgress() // Retrieving the amount of already downloaded data if exist, defined at 0 otherwise
691
702
  let requestHeaders: HTTPHeaders = ["Range": "bytes=\(totalReceivedBytes)-"]
703
+
704
+ // Send stats for zip download start
705
+ self.sendStats(action: "download_zip_start", versionName: version)
706
+
692
707
  // Opening connection for streaming the bytes
693
708
  if totalReceivedBytes == 0 {
694
709
  self.notifyDownload(id: id, percent: 0, ignoreMultipleOfTen: true)
@@ -803,6 +818,10 @@ import UIKit
803
818
  let info = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
804
819
  self.saveBundleInfo(id: id, bundle: info)
805
820
  self.cleanDownloadData()
821
+
822
+ // Send stats for zip download complete
823
+ self.sendStats(action: "download_zip_complete", versionName: version)
824
+
806
825
  self.notifyDownload(id: id, percent: 100)
807
826
  logger.info("Downloading: 100% (complete)")
808
827
  return info
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "6.25.0",
3
+ "version": "6.25.3",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",