@capgo/capacitor-updater 7.25.0 → 7.27.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.
@@ -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 = "7.25.0";
74
+ private final String pluginVersion = "7.27.0";
75
75
  private static final String DELAY_CONDITION_PREFERENCES = "";
76
76
 
77
77
  private SharedPreferences.Editor editor;
@@ -1304,6 +1304,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
1304
1304
  CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, channel, (res) -> {
1305
1305
  JSObject jsRes = mapToJSObject(res);
1306
1306
  if (jsRes.has("error")) {
1307
+ String error = jsRes.getString("error");
1308
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
1309
+ logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
1307
1310
  call.reject(jsRes.getString("error"));
1308
1311
  return;
1309
1312
  } else if (jsRes.has("message")) {
@@ -1409,7 +1412,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
1409
1412
  CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, null, (res) -> {
1410
1413
  JSObject jsRes = mapToJSObject(res);
1411
1414
  if (jsRes.has("error")) {
1412
- logger.error(Objects.requireNonNull(jsRes.getString("error")));
1415
+ String error = jsRes.getString("error");
1416
+ String errorMessage = jsRes.has("message")
1417
+ ? jsRes.getString("message")
1418
+ : "server did not provide a message";
1419
+ logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
1413
1420
  } else if (jsRes.has("version")) {
1414
1421
  String newVersion = jsRes.getString("version");
1415
1422
  String currentVersion = String.valueOf(CapacitorUpdaterPlugin.this.implementation.getCurrentBundle());
@@ -1613,7 +1620,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
1613
1620
  // Handle network errors and other failures first
1614
1621
  if (jsRes.has("error")) {
1615
1622
  String error = jsRes.getString("error");
1616
- logger.error("getLatest failed with error: " + error);
1623
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
1624
+ logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
1617
1625
  String latestVersion = jsRes.has("version") ? jsRes.getString("version") : current.getVersionName();
1618
1626
  if ("response_error".equals(error)) {
1619
1627
  CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
@@ -353,7 +353,15 @@ public class CapgoUpdater {
353
353
  manifest != null,
354
354
  this.isEmulator(),
355
355
  this.appId,
356
- this.pluginVersion
356
+ this.pluginVersion,
357
+ this.isProd(),
358
+ this.statsUrl,
359
+ this.deviceID,
360
+ this.versionBuild,
361
+ this.versionCode,
362
+ this.versionOs,
363
+ this.customId,
364
+ this.defaultChannel
357
365
  );
358
366
 
359
367
  if (manifest != null) {
@@ -880,8 +888,12 @@ public class CapgoUpdater {
880
888
  // Check for server-side errors first
881
889
  if (jsonResponse.has("error")) {
882
890
  Map<String, Object> retError = new HashMap<>();
883
- retError.put("message", jsonResponse.getString("error"));
884
- retError.put("error", "server_error");
891
+ retError.put("error", jsonResponse.getString("error"));
892
+ if (jsonResponse.has("message")) {
893
+ retError.put("message", jsonResponse.getString("message"));
894
+ } else {
895
+ retError.put("message", "server did not provide a message");
896
+ }
885
897
  callback.callback(retError);
886
898
  return;
887
899
  }
@@ -1008,8 +1020,12 @@ public class CapgoUpdater {
1008
1020
  // Check for server-side errors first
1009
1021
  if (jsonResponse.has("error")) {
1010
1022
  Map<String, Object> retError = new HashMap<>();
1011
- retError.put("message", jsonResponse.getString("error"));
1012
- retError.put("error", "server_error");
1023
+ retError.put("error", jsonResponse.getString("error"));
1024
+ if (jsonResponse.has("message")) {
1025
+ retError.put("message", jsonResponse.getString("message"));
1026
+ } else {
1027
+ retError.put("message", "server did not provide a message");
1028
+ }
1013
1029
  callback.callback(retError);
1014
1030
  return;
1015
1031
  }
@@ -1161,8 +1177,12 @@ public class CapgoUpdater {
1161
1177
  // Check for server-side errors first
1162
1178
  if (jsonResponse.has("error")) {
1163
1179
  Map<String, Object> retError = new HashMap<>();
1164
- retError.put("message", jsonResponse.getString("error"));
1165
- retError.put("error", "server_error");
1180
+ retError.put("error", jsonResponse.getString("error"));
1181
+ if (jsonResponse.has("message")) {
1182
+ retError.put("message", jsonResponse.getString("message"));
1183
+ } else {
1184
+ retError.put("message", "server did not provide a message");
1185
+ }
1166
1186
  callback.callback(retError);
1167
1187
  return;
1168
1188
  }
@@ -1289,8 +1309,12 @@ public class CapgoUpdater {
1289
1309
  JSONObject json = new JSONObject(data);
1290
1310
  if (json.has("error")) {
1291
1311
  Map<String, Object> retError = new HashMap<>();
1292
- retError.put("message", json.getString("error"));
1293
- retError.put("error", "server_error");
1312
+ retError.put("error", json.getString("error"));
1313
+ if (json.has("message")) {
1314
+ retError.put("message", json.getString("message"));
1315
+ } else {
1316
+ retError.put("message", "server did not provide a message");
1317
+ }
1294
1318
  callback.callback(retError);
1295
1319
  return;
1296
1320
  }
@@ -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 = "7.25.0"
57
+ private let pluginVersion: String = "7.27.0"
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"
@@ -409,6 +409,9 @@ import UIKit
409
409
  let bundleInfo = BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: "")
410
410
  self.saveBundleInfo(id: id, bundle: bundleInfo)
411
411
 
412
+ // Send stats for manifest download start
413
+ self.sendStats(action: "download_manifest_start", versionName: version)
414
+
412
415
  // Notify the start of the download process
413
416
  self.notifyDownload(id: id, percent: 0, ignoreMultipleOfTen: true)
414
417
 
@@ -467,6 +470,7 @@ import UIKit
467
470
  do {
468
471
  let statusCode = response.response?.statusCode ?? 200
469
472
  if statusCode < 200 || statusCode >= 300 {
473
+ self.sendStats(action: "download_manifest_file_fail", versionName: "\(version):\(fileName)")
470
474
  if let stringData = String(data: data, encoding: .utf8) {
471
475
  throw NSError(domain: "StatusCodeError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to fetch. Status code (\(statusCode)) invalid. Data: \(stringData) for file \(fileName) at url \(downloadUrl)"])
472
476
  } else {
@@ -498,6 +502,7 @@ import UIKit
498
502
  if isBrotli {
499
503
  // Decompress the Brotli data
500
504
  guard let decompressedData = self.decompressBrotli(data: finalData, fileName: fileName) else {
505
+ self.sendStats(action: "download_manifest_brotli_fail", versionName: "\(version):\(finalFileName)")
501
506
  throw NSError(domain: "BrotliDecompressionError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to decompress Brotli data for file \(fileName) at url \(downloadUrl)"])
502
507
  }
503
508
  finalData = decompressedData
@@ -508,6 +513,7 @@ import UIKit
508
513
  // assume that calcChecksum != null
509
514
  let calculatedChecksum = CryptoCipher.calcChecksum(filePath: destFilePath)
510
515
  if calculatedChecksum != fileHash {
516
+ self.sendStats(action: "download_manifest_checksum_fail", versionName: "\(version):\(finalFileName)")
511
517
  throw NSError(domain: "ChecksumError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Computed checksum is not equal to required checksum (\(calculatedChecksum) != \(fileHash)) for file \(fileName) at url \(downloadUrl)"])
512
518
  }
513
519
  }
@@ -523,6 +529,8 @@ import UIKit
523
529
  self.logger.error("downloadManifest \(id) \(fileName) error: \(error.localizedDescription)")
524
530
  }
525
531
  case .failure(let error):
532
+ downloadError = error
533
+ self.sendStats(action: "download_manifest_file_fail", versionName: "\(version):\(fileName)")
526
534
  self.logger.error("downloadManifest \(id) \(fileName) download error: \(error.localizedDescription). Debug response: \(response.debugDescription).")
527
535
  }
528
536
  }
@@ -542,6 +550,9 @@ import UIKit
542
550
  let updatedBundle = bundleInfo.setStatus(status: BundleStatus.PENDING.localizedString)
543
551
  self.saveBundleInfo(id: id, bundle: updatedBundle)
544
552
 
553
+ // Send stats for manifest download complete
554
+ self.sendStats(action: "download_manifest_complete", versionName: version)
555
+
545
556
  logger.info("downloadManifest done \(id)")
546
557
  return updatedBundle
547
558
  }
@@ -672,6 +683,10 @@ import UIKit
672
683
  var lastSentProgress = 0
673
684
  var totalReceivedBytes: Int64 = loadDownloadProgress() // Retrieving the amount of already downloaded data if exist, defined at 0 otherwise
674
685
  let requestHeaders: HTTPHeaders = ["Range": "bytes=\(totalReceivedBytes)-"]
686
+
687
+ // Send stats for zip download start
688
+ self.sendStats(action: "download_zip_start", versionName: version)
689
+
675
690
  // Opening connection for streaming the bytes
676
691
  if totalReceivedBytes == 0 {
677
692
  self.notifyDownload(id: id, percent: 0, ignoreMultipleOfTen: true)
@@ -780,6 +795,10 @@ import UIKit
780
795
  let info = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
781
796
  self.saveBundleInfo(id: id, bundle: info)
782
797
  self.cleanDownloadData()
798
+
799
+ // Send stats for zip download complete
800
+ self.sendStats(action: "download_zip_complete", versionName: version)
801
+
783
802
  self.notifyDownload(id: id, percent: 100)
784
803
  logger.info("Downloading: 100% (complete)")
785
804
  return info
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "7.25.0",
3
+ "version": "7.27.0",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",