@capgo/capacitor-updater 6.14.36 → 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 PLUGIN_VERSION = "6.14.31";
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;
@@ -220,7 +220,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
220
220
  this.implementation.activity = this.getActivity();
221
221
  this.implementation.versionBuild = this.getConfig().getString("version", pInfo.versionName);
222
222
  this.implementation.CAP_SERVER_PATH = WebView.CAP_SERVER_PATH;
223
- this.implementation.PLUGIN_VERSION = this.PLUGIN_VERSION;
223
+ this.implementation.pluginVersion = this.pluginVersion;
224
224
  this.implementation.versionCode = Integer.toString(pInfo.versionCode);
225
225
  // Removed unused OkHttpClient creation - using shared client in DownloadService instead
226
226
  // Handle directUpdate configuration - support string values and backward compatibility
@@ -334,7 +334,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
334
334
  this.implementation.deviceID = DeviceIdHelper.getOrCreateDeviceId(this.getContext(), this.prefs);
335
335
 
336
336
  // Update User-Agent for shared OkHttpClient with OS version
337
- DownloadService.updateUserAgent(this.implementation.appId, this.PLUGIN_VERSION, this.implementation.versionOs);
337
+ DownloadService.updateUserAgent(this.implementation.appId, this.pluginVersion, this.implementation.versionOs);
338
338
 
339
339
  if (Boolean.TRUE.equals(this.persistCustomId)) {
340
340
  final String storedCustomId = this.prefs.getString(CUSTOM_ID_PREF_KEY, "");
@@ -841,7 +841,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
841
841
  public void getPluginVersion(final PluginCall call) {
842
842
  try {
843
843
  final JSObject ret = new JSObject();
844
- ret.put("version", this.PLUGIN_VERSION);
844
+ ret.put("version", this.pluginVersion);
845
845
  call.resolve(ret);
846
846
  } catch (final Exception e) {
847
847
  logger.error("Could not get plugin version " + e.getMessage());
@@ -66,7 +66,7 @@ public class CapgoUpdater {
66
66
  public File documentsDir;
67
67
  public Boolean directUpdate = false;
68
68
  public Activity activity;
69
- public String PLUGIN_VERSION = "";
69
+ public String pluginVersion = "";
70
70
  public String versionBuild = "";
71
71
  public String versionCode = "";
72
72
  public String versionOs = "";
@@ -355,7 +355,15 @@ public class CapgoUpdater {
355
355
  manifest != null,
356
356
  this.isEmulator(),
357
357
  this.appId,
358
- this.PLUGIN_VERSION
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) {
@@ -782,7 +790,7 @@ public class CapgoUpdater {
782
790
  json.put("version_code", this.versionCode);
783
791
  json.put("version_os", this.versionOs);
784
792
  json.put("version_name", this.getCurrentBundle().getVersionName());
785
- json.put("plugin_version", this.PLUGIN_VERSION);
793
+ json.put("plugin_version", this.pluginVersion);
786
794
  json.put("is_emulator", this.isEmulator());
787
795
  json.put("is_prod", this.isProd());
788
796
  json.put("defaultChannel", this.defaultChannel);
@@ -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;
@@ -63,7 +67,16 @@ public class DownloadService extends Worker {
63
67
  public static final String PUBLIC_KEY = "publickey";
64
68
  public static final String IS_MANIFEST = "is_manifest";
65
69
  public static final String APP_ID = "app_id";
66
- public static final String PLUGIN_VERSION = "plugin_version";
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
 
@@ -80,7 +88,16 @@ public class DownloadWorkerManager {
80
88
  .putBoolean(DownloadService.IS_MANIFEST, isManifest)
81
89
  .putString(DownloadService.PUBLIC_KEY, publicKey)
82
90
  .putString(DownloadService.APP_ID, appId)
83
- .putString(DownloadService.PLUGIN_VERSION, pluginVersion)
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 PLUGIN_VERSION: String = "6.14.31"
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"
@@ -193,7 +193,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
193
193
  implementation.hasOldPrivateKeyPropertyInConfig = true
194
194
  }
195
195
  implementation.notifyDownloadRaw = notifyDownload
196
- implementation.PLUGIN_VERSION = self.PLUGIN_VERSION
196
+ implementation.pluginVersion = self.pluginVersion
197
197
 
198
198
  // Set logger for shared classes
199
199
  implementation.setLogger(logger)
@@ -448,7 +448,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
448
448
  }
449
449
 
450
450
  @objc func getPluginVersion(_ call: CAPPluginCall) {
451
- call.resolve(["version": self.PLUGIN_VERSION])
451
+ call.resolve(["version": self.pluginVersion])
452
452
  }
453
453
 
454
454
  @objc func download(_ call: CAPPluginCall) {
@@ -33,7 +33,7 @@ import UIKit
33
33
  public let CAP_SERVER_PATH: String = "serverBasePath"
34
34
  public var versionBuild: String = ""
35
35
  public var customId: String = ""
36
- public var PLUGIN_VERSION: String = ""
36
+ public var pluginVersion: String = ""
37
37
  public var timeout: Double = 20
38
38
  public var statsUrl: String = ""
39
39
  public var channelUrl: String = ""
@@ -51,7 +51,7 @@ import UIKit
51
51
  private static var rateLimitStatisticSent = false
52
52
 
53
53
  private var userAgent: String {
54
- let safePluginVersion = PLUGIN_VERSION.isEmpty ? "unknown" : PLUGIN_VERSION
54
+ let safePluginVersion = pluginVersion.isEmpty ? "unknown" : pluginVersion
55
55
  let safeAppId = appId.isEmpty ? "unknown" : appId
56
56
  return "CapacitorUpdater/\(safePluginVersion) (\(safeAppId)) ios/\(versionOs)"
57
57
  }
@@ -315,7 +315,7 @@ import UIKit
315
315
  version_code: self.versionCode,
316
316
  version_os: self.versionOs,
317
317
  version_name: self.getCurrentBundle().getVersionName(),
318
- plugin_version: self.PLUGIN_VERSION,
318
+ plugin_version: self.pluginVersion,
319
319
  is_emulator: self.isEmulator(),
320
320
  is_prod: self.isProd(),
321
321
  action: nil,
@@ -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.14.36",
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",