@capgo/capacitor-updater 7.8.11 → 7.9.1

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.
@@ -59,7 +59,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
59
59
  private static final String statsUrlDefault = "https://plugin.capgo.app/stats";
60
60
  private static final String channelUrlDefault = "https://plugin.capgo.app/channel_self";
61
61
 
62
- private final String PLUGIN_VERSION = "7.8.11";
62
+ private final String PLUGIN_VERSION = "7.9.1";
63
63
  private static final String DELAY_CONDITION_PREFERENCES = "";
64
64
 
65
65
  private SharedPreferences.Editor editor;
@@ -233,7 +233,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
233
233
  this.implementation.versionOs = Build.VERSION.RELEASE;
234
234
  this.implementation.deviceID = this.prefs.getString("appUUID", UUID.randomUUID().toString()).toLowerCase();
235
235
  this.editor.putString("appUUID", this.implementation.deviceID);
236
- this.editor.commit();
236
+ this.editor.apply();
237
237
  logger.info("init for device " + this.implementation.deviceID);
238
238
  logger.info("version native " + this.currentVersionNative.getOriginalString());
239
239
  this.autoDeleteFailed = this.getConfig().getBoolean("autoDeleteFailed", true);
@@ -455,7 +455,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
455
455
  logger.error("Error calculating previous native version " + e.getMessage());
456
456
  }
457
457
  this.editor.putString("LatestVersionNative", this.currentVersionNative.toString());
458
- this.editor.commit();
458
+ this.editor.apply();
459
459
  }
460
460
 
461
461
  public void notifyDownload(final String id, final int percent) {
@@ -304,7 +304,9 @@ public class CapgoUpdater {
304
304
  checksum,
305
305
  this.publicKey,
306
306
  manifest != null,
307
- this.isEmulator()
307
+ this.isEmulator(),
308
+ this.appId,
309
+ this.PLUGIN_VERSION
308
310
  );
309
311
 
310
312
  if (manifest != null) {
@@ -27,6 +27,7 @@ import java.util.concurrent.Future;
27
27
  import java.util.concurrent.TimeUnit;
28
28
  import java.util.concurrent.atomic.AtomicBoolean;
29
29
  import java.util.concurrent.atomic.AtomicLong;
30
+ import okhttp3.Interceptor;
30
31
  import okhttp3.OkHttpClient;
31
32
  import okhttp3.Protocol;
32
33
  import okhttp3.Request;
@@ -55,12 +56,31 @@ public class DownloadService extends Worker {
55
56
  public static final String CHECKSUM = "checksum";
56
57
  public static final String PUBLIC_KEY = "publickey";
57
58
  public static final String IS_MANIFEST = "is_manifest";
59
+ public static final String APP_ID = "app_id";
60
+ public static final String PLUGIN_VERSION = "plugin_version";
58
61
  private static final String UPDATE_FILE = "update.dat";
59
62
 
60
- private final OkHttpClient client = new OkHttpClient.Builder().protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1)).build();
63
+ private final OkHttpClient client;
61
64
 
62
65
  public DownloadService(@NonNull Context context, @NonNull WorkerParameters params) {
63
66
  super(context, params);
67
+ // Get appId and plugin version from input data
68
+ String appId = getInputData().getString(APP_ID);
69
+ String pluginVersion = getInputData().getString(PLUGIN_VERSION);
70
+
71
+ // Build user agent with appId and plugin version
72
+ String userAgent =
73
+ "CapacitorUpdater/" + (pluginVersion != null ? pluginVersion : "unknown") + " (" + (appId != null ? appId : "unknown") + ")";
74
+
75
+ // Create OkHttpClient with custom user agent
76
+ this.client = new OkHttpClient.Builder()
77
+ .protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
78
+ .addInterceptor(chain -> {
79
+ Request originalRequest = chain.request();
80
+ Request requestWithUserAgent = originalRequest.newBuilder().header("User-Agent", userAgent).build();
81
+ return chain.proceed(requestWithUserAgent);
82
+ })
83
+ .build();
64
84
  }
65
85
 
66
86
  private void setProgress(int percent) {
@@ -466,14 +486,14 @@ public class DownloadService extends Worker {
466
486
 
467
487
  private String calculateFileHash(File file) throws Exception {
468
488
  MessageDigest digest = MessageDigest.getInstance("SHA-256");
469
- FileInputStream fis = new FileInputStream(file);
470
489
  byte[] byteArray = new byte[1024];
471
490
  int bytesCount = 0;
472
491
 
473
- while ((bytesCount = fis.read(byteArray)) != -1) {
474
- digest.update(byteArray, 0, bytesCount);
492
+ try (FileInputStream fis = new FileInputStream(file)) {
493
+ while ((bytesCount = fis.read(byteArray)) != -1) {
494
+ digest.update(byteArray, 0, bytesCount);
495
+ }
475
496
  }
476
- fis.close();
477
497
 
478
498
  byte[] bytes = digest.digest();
479
499
  StringBuilder sb = new StringBuilder();
@@ -23,6 +23,7 @@ public class DownloadWorkerManager {
23
23
 
24
24
  private static volatile boolean isInitialized = false;
25
25
  private static final Set<String> activeVersions = new HashSet<>();
26
+ private static final Object activeVersionsLock = new Object();
26
27
 
27
28
  private static synchronized void initializeIfNeeded(Context context) {
28
29
  if (!isInitialized) {
@@ -36,8 +37,10 @@ public class DownloadWorkerManager {
36
37
  }
37
38
  }
38
39
 
39
- public static synchronized boolean isVersionDownloading(String version) {
40
- return activeVersions.contains(version);
40
+ public static boolean isVersionDownloading(String version) {
41
+ synchronized (activeVersionsLock) {
42
+ return activeVersions.contains(version);
43
+ }
41
44
  }
42
45
 
43
46
  public static void enqueueDownload(
@@ -51,16 +54,20 @@ public class DownloadWorkerManager {
51
54
  String checksum,
52
55
  String publicKey,
53
56
  boolean isManifest,
54
- boolean isEmulator
57
+ boolean isEmulator,
58
+ String appId,
59
+ String pluginVersion
55
60
  ) {
56
61
  initializeIfNeeded(context.getApplicationContext());
57
62
 
58
63
  // If version is already downloading, don't start another one
59
- if (isVersionDownloading(version)) {
60
- logger.info("Version " + version + " is already downloading");
61
- return;
64
+ synchronized (activeVersionsLock) {
65
+ if (activeVersions.contains(version)) {
66
+ logger.info("Version " + version + " is already downloading");
67
+ return;
68
+ }
69
+ activeVersions.add(version);
62
70
  }
63
- activeVersions.add(version);
64
71
 
65
72
  // Create input data
66
73
  Data inputData = new Data.Builder()
@@ -73,6 +80,8 @@ public class DownloadWorkerManager {
73
80
  .putString(DownloadService.CHECKSUM, checksum)
74
81
  .putBoolean(DownloadService.IS_MANIFEST, isManifest)
75
82
  .putString(DownloadService.PUBLIC_KEY, publicKey)
83
+ .putString(DownloadService.APP_ID, appId)
84
+ .putString(DownloadService.PLUGIN_VERSION, pluginVersion)
76
85
  .build();
77
86
 
78
87
  // Create network constraints - be more lenient on emulators
@@ -110,12 +119,16 @@ public class DownloadWorkerManager {
110
119
  public static void cancelVersionDownload(Context context, String version) {
111
120
  initializeIfNeeded(context.getApplicationContext());
112
121
  WorkManager.getInstance(context).cancelAllWorkByTag(version);
113
- activeVersions.remove(version);
122
+ synchronized (activeVersionsLock) {
123
+ activeVersions.remove(version);
124
+ }
114
125
  }
115
126
 
116
127
  public static void cancelAllDownloads(Context context) {
117
128
  initializeIfNeeded(context.getApplicationContext());
118
129
  WorkManager.getInstance(context).cancelAllWorkByTag("capacitor_updater_download");
119
- activeVersions.clear();
130
+ synchronized (activeVersionsLock) {
131
+ activeVersions.clear();
132
+ }
120
133
  }
121
134
  }
@@ -13,12 +13,12 @@ import WebKit
13
13
  class WebViewLoadDelegate: NSObject, WKNavigationDelegate {
14
14
  private let completion: (Bool) -> Void
15
15
  private var hasCompleted = false
16
-
16
+
17
17
  init(completion: @escaping (Bool) -> Void) {
18
18
  self.completion = completion
19
19
  super.init()
20
20
  }
21
-
21
+
22
22
  func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
23
23
  if !hasCompleted {
24
24
  hasCompleted = true
@@ -26,7 +26,7 @@ class WebViewLoadDelegate: NSObject, WKNavigationDelegate {
26
26
  completion(true)
27
27
  }
28
28
  }
29
-
29
+
30
30
  func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
31
31
  if !hasCompleted {
32
32
  hasCompleted = true
@@ -34,7 +34,7 @@ class WebViewLoadDelegate: NSObject, WKNavigationDelegate {
34
34
  completion(false)
35
35
  }
36
36
  }
37
-
37
+
38
38
  func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
39
39
  if !hasCompleted {
40
40
  hasCompleted = true
@@ -86,7 +86,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
86
86
  CAPPluginMethod(name: "isShakeMenuEnabled", returnType: CAPPluginReturnPromise)
87
87
  ]
88
88
  public var implementation = CapgoUpdater()
89
- private let PLUGIN_VERSION: String = "7.8.11"
89
+ private let PLUGIN_VERSION: String = "7.9.1"
90
90
  static let updateUrlDefault = "https://plugin.capgo.app/updates"
91
91
  static let statsUrlDefault = "https://plugin.capgo.app/stats"
92
92
  static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
@@ -445,11 +445,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
445
445
  logger.error("Cannot get capBridge")
446
446
  return false
447
447
  }
448
-
448
+
449
449
  // Use semaphore to wait for WebView load completion
450
450
  let loadSemaphore = DispatchSemaphore(value: 0)
451
451
  var loadSuccess = false
452
-
452
+
453
453
  // Set up navigation delegate to detect when loading completes
454
454
  let navigationDelegate = WebViewLoadDelegate { [weak self] success in
455
455
  loadSuccess = success
@@ -459,7 +459,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
459
459
  }
460
460
  loadSemaphore.signal()
461
461
  }
462
-
462
+
463
463
  if keepUrlPathAfterReload {
464
464
  DispatchQueue.main.async {
465
465
  guard let url = vc.webView?.url else {
@@ -488,10 +488,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
488
488
  vc.webView?.reload()
489
489
  }
490
490
  }
491
-
491
+
492
492
  // Wait for load completion with timeout
493
493
  let result = loadSemaphore.wait(timeout: .now() + 10) // 10 second timeout
494
-
494
+
495
495
  if result == .timedOut {
496
496
  logger.error("Reload timed out after 10 seconds")
497
497
  DispatchQueue.main.async {
@@ -499,7 +499,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
499
499
  }
500
500
  return false
501
501
  }
502
-
502
+
503
503
  return loadSuccess
504
504
  }
505
505
  return false
@@ -1150,6 +1150,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1150
1150
  self.checkAppReady()
1151
1151
  }
1152
1152
 
1153
+ private var periodicUpdateTimer: Timer?
1154
+
1153
1155
  @objc func checkForUpdateAfterDelay() {
1154
1156
  if periodCheckDelay == 0 || !self._isAutoUpdateEnabled() {
1155
1157
  return
@@ -1158,7 +1160,12 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1158
1160
  logger.error("Error no url or wrong format")
1159
1161
  return
1160
1162
  }
1161
- let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(periodCheckDelay), repeats: true) { _ in
1163
+
1164
+ // Clean up any existing timer
1165
+ periodicUpdateTimer?.invalidate()
1166
+
1167
+ periodicUpdateTimer = Timer.scheduledTimer(withTimeInterval: TimeInterval(periodCheckDelay), repeats: true) { [weak self] _ in
1168
+ guard let self = self else { return }
1162
1169
  DispatchQueue.global(qos: .background).async {
1163
1170
  let res = self.implementation.getLatest(url: url, channel: nil)
1164
1171
  let current = self.implementation.getCurrentBundle()
@@ -1169,7 +1176,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1169
1176
  }
1170
1177
  }
1171
1178
  }
1172
- RunLoop.current.add(timer, forMode: .default)
1179
+ RunLoop.current.add(periodicUpdateTimer!, forMode: .default)
1173
1180
  }
1174
1181
 
1175
1182
  @objc func appMovedToBackground() {
@@ -42,6 +42,16 @@ import UIKit
42
42
  public var deviceID = ""
43
43
  public var publicKey: String = ""
44
44
 
45
+ private var userAgent: String {
46
+ return "CapacitorUpdater/\(PLUGIN_VERSION) (\(appId))"
47
+ }
48
+
49
+ private lazy var alamofireSession: Session = {
50
+ let configuration = URLSessionConfiguration.default
51
+ configuration.httpAdditionalHeaders = ["User-Agent": self.userAgent]
52
+ return Session(configuration: configuration)
53
+ }()
54
+
45
55
  public var notifyDownloadRaw: (String, Int, Bool) -> Void = { _, _, _ in }
46
56
  public func notifyDownload(id: String, percent: Int, ignoreMultipleOfTen: Bool = false) {
47
57
  notifyDownloadRaw(id, percent, ignoreMultipleOfTen)
@@ -250,7 +260,7 @@ import UIKit
250
260
  parameters.defaultChannel = channel
251
261
  }
252
262
  logger.info("Auto-update parameters: \(parameters)")
253
- let request = AF.request(url, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
263
+ let request = alamofireSession.request(url, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
254
264
 
255
265
  request.validate().responseDecodable(of: AppVersionDec.self) { response in
256
266
  switch response.result {
@@ -376,7 +386,7 @@ import UIKit
376
386
  dispatchGroup.leave()
377
387
  } else {
378
388
  // File not in cache, download, decompress, and save to both cache and destination
379
- AF.download(downloadUrl).responseData { response in
389
+ self.alamofireSession.download(downloadUrl).responseData { response in
380
390
  defer { dispatchGroup.leave() }
381
391
 
382
392
  switch response.result {
@@ -601,7 +611,9 @@ import UIKit
601
611
  mainError = error as NSError?
602
612
  }
603
613
  }
604
- let session = Session(eventMonitors: [monitor])
614
+ let configuration = URLSessionConfiguration.default
615
+ configuration.httpAdditionalHeaders = ["User-Agent": self.userAgent]
616
+ let session = Session(configuration: configuration, eventMonitors: [monitor])
605
617
 
606
618
  let request = session.streamRequest(url, headers: requestHeaders).validate().onHTTPResponse(perform: { response in
607
619
  if let contentLength = response.headers.value(for: "Content-Length") {
@@ -950,7 +962,7 @@ import UIKit
950
962
  let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
951
963
  let parameters: InfoObject = self.createInfoObject()
952
964
 
953
- let request = AF.request(self.channelUrl, method: .delete, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
965
+ let request = alamofireSession.request(self.channelUrl, method: .delete, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
954
966
 
955
967
  request.validate().responseDecodable(of: SetChannelDec.self) { response in
956
968
  switch response.result {
@@ -985,7 +997,7 @@ import UIKit
985
997
  var parameters: InfoObject = self.createInfoObject()
986
998
  parameters.channel = channel
987
999
 
988
- let request = AF.request(self.channelUrl, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
1000
+ let request = alamofireSession.request(self.channelUrl, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
989
1001
 
990
1002
  request.validate().responseDecodable(of: SetChannelDec.self) { response in
991
1003
  switch response.result {
@@ -1018,7 +1030,7 @@ import UIKit
1018
1030
  }
1019
1031
  let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
1020
1032
  let parameters: InfoObject = self.createInfoObject()
1021
- let request = AF.request(self.channelUrl, method: .put, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
1033
+ let request = alamofireSession.request(self.channelUrl, method: .put, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
1022
1034
 
1023
1035
  request.validate().responseDecodable(of: GetChannelDec.self) { response in
1024
1036
  defer {
@@ -1084,7 +1096,7 @@ import UIKit
1084
1096
  return listChannels
1085
1097
  }
1086
1098
 
1087
- let request = AF.request(url, method: .get, requestModifier: { $0.timeoutInterval = self.timeout })
1099
+ let request = alamofireSession.request(url, method: .get, requestModifier: { $0.timeoutInterval = self.timeout })
1088
1100
 
1089
1101
  request.validate().responseDecodable(of: ListChannelsDec.self) { response in
1090
1102
  defer {
@@ -1137,7 +1149,7 @@ import UIKit
1137
1149
 
1138
1150
  let operation = BlockOperation {
1139
1151
  let semaphore = DispatchSemaphore(value: 0)
1140
- AF.request(
1152
+ self.alamofireSession.request(
1141
1153
  self.statsUrl,
1142
1154
  method: .post,
1143
1155
  parameters: parameters,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "7.8.11",
3
+ "version": "7.9.1",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",