@capgo/capacitor-updater 7.9.1 → 7.11.6

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/README.md CHANGED
@@ -249,7 +249,7 @@ CapacitorUpdater can be configured with these options:
249
249
  | **`localApi`** | <code>string</code> | Configure the CLI to use a local api for testing. | <code>undefined</code> | 6.3.3 |
250
250
  | **`localApiFiles`** | <code>string</code> | Configure the CLI to use a local file api for testing. | <code>undefined</code> | 6.3.3 |
251
251
  | **`allowModifyUrl`** | <code>boolean</code> | Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side. | <code>false</code> | 5.4.0 |
252
- | **`defaultChannel`** | <code>string</code> | Set the default channel for the app in the config. Case sensitive. This will setting will override the default channel set in the cloud, but will still respect overrides made in the cloud. | <code>undefined</code> | 5.5.0 |
252
+ | **`defaultChannel`** | <code>string</code> | Set the default channel for the app in the config. Case sensitive. This will setting will override the default channel set in the cloud, but will still respect overrides made in the cloud. This requires the channel to allow devices to self dissociate/associate in the channel settings. https://capgo.app/docs/public-api/channels/#channel-configuration-options | <code>undefined</code> | 5.5.0 |
253
253
  | **`appId`** | <code>string</code> | Configure the app id for the app in the config. | <code>undefined</code> | 6.0.0 |
254
254
  | **`keepUrlPathAfterReload`** | <code>boolean</code> | Configure the plugin to keep the URL path after a reload. WARNING: When a reload is triggered, 'window.history' will be cleared. | <code>false</code> | 6.8.0 |
255
255
  | **`disableJSLogging`** | <code>boolean</code> | Disable the JavaScript logging of the plugin. if true, the plugin will not log to the JavaScript console. only the native log will be done | <code>false</code> | 7.3.0 |
@@ -59,8 +59,20 @@ dependencies {
59
59
  implementation 'io.github.g00fy2:versioncompare:1.5.0'
60
60
  implementation 'com.google.code.gson:gson:2.13.1'
61
61
  testImplementation "junit:junit:$junitVersion"
62
+ testImplementation 'org.mockito:mockito-core:5.14.2'
63
+ testImplementation 'org.robolectric:robolectric:4.13'
62
64
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
63
65
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
64
66
  implementation 'org.brotli:dec:0.1.2'
65
67
  implementation 'com.squareup.okhttp3:okhttp:4.12.0'
66
68
  }
69
+
70
+ tasks.withType(Test) {
71
+ testLogging {
72
+ events "passed", "skipped", "failed", "standardOut", "standardError"
73
+ exceptionFormat "full"
74
+ showStandardStreams = true
75
+ showCauses = true
76
+ showStackTraces = true
77
+ }
78
+ }
@@ -44,8 +44,7 @@ import java.util.concurrent.Semaphore;
44
44
  import java.util.concurrent.TimeUnit;
45
45
  import java.util.concurrent.TimeoutException;
46
46
  import java.util.concurrent.atomic.AtomicReference;
47
- import okhttp3.OkHttpClient;
48
- import okhttp3.Protocol;
47
+ // Removed OkHttpClient and Protocol imports - using shared client in DownloadService instead
49
48
  import org.json.JSONArray;
50
49
  import org.json.JSONException;
51
50
  import org.json.JSONObject;
@@ -59,7 +58,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
59
58
  private static final String statsUrlDefault = "https://plugin.capgo.app/stats";
60
59
  private static final String channelUrlDefault = "https://plugin.capgo.app/channel_self";
61
60
 
62
- private final String PLUGIN_VERSION = "7.9.1";
61
+ private final String PLUGIN_VERSION = "7.11.6";
63
62
  private static final String DELAY_CONDITION_PREFERENCES = "";
64
63
 
65
64
  private SharedPreferences.Editor editor;
@@ -153,12 +152,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
153
152
  this.implementation.CAP_SERVER_PATH = WebView.CAP_SERVER_PATH;
154
153
  this.implementation.PLUGIN_VERSION = this.PLUGIN_VERSION;
155
154
  this.implementation.versionCode = Integer.toString(pInfo.versionCode);
156
- this.implementation.client = new OkHttpClient.Builder()
157
- .protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
158
- .connectTimeout(this.implementation.timeout, TimeUnit.MILLISECONDS)
159
- .readTimeout(this.implementation.timeout, TimeUnit.MILLISECONDS)
160
- .writeTimeout(this.implementation.timeout, TimeUnit.MILLISECONDS)
161
- .build();
155
+ // Removed unused OkHttpClient creation - using shared client in DownloadService instead
162
156
  // Handle directUpdate configuration - support string values and backward compatibility
163
157
  String directUpdateConfig = this.getConfig().getString("directUpdate", null);
164
158
  if (directUpdateConfig != null) {
@@ -209,12 +203,16 @@ public class CapacitorUpdaterPlugin extends Plugin {
209
203
  this.implementation.appId = config.getString("appId", this.implementation.appId);
210
204
  this.implementation.appId = this.getConfig().getString("appId", this.implementation.appId);
211
205
  if (this.implementation.appId == null || this.implementation.appId.isEmpty()) {
212
- // crash the app
206
+ // crash the app on purpose it should not happen
213
207
  throw new RuntimeException(
214
208
  "appId is missing in capacitor.config.json or plugin config, and cannot be retrieved from the native app, please add it globally or in the plugin config"
215
209
  );
216
210
  }
217
211
  logger.info("appId: " + implementation.appId);
212
+
213
+ // Update User-Agent for shared OkHttpClient
214
+ DownloadService.updateUserAgent(this.implementation.appId, this.PLUGIN_VERSION);
215
+
218
216
  this.implementation.publicKey = this.getConfig().getString("publicKey", "");
219
217
  this.implementation.statsUrl = this.getConfig().getString("statsUrl", statsUrlDefault);
220
218
  this.implementation.channelUrl = this.getConfig().getString("channelUrl", channelUrlDefault);
@@ -263,9 +261,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
263
261
  logger.info("semaphoreReady count " + semaphoreReady.getPhase());
264
262
  } catch (InterruptedException e) {
265
263
  logger.info("semaphoreWait InterruptedException");
266
- e.printStackTrace();
264
+ Thread.currentThread().interrupt(); // Restore interrupted status
267
265
  } catch (TimeoutException e) {
268
- throw new RuntimeException(e);
266
+ logger.error("Semaphore timeout: " + e.getMessage());
267
+ // Don't throw runtime exception, just log and continue
269
268
  }
270
269
  }
271
270
 
@@ -291,7 +290,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
291
290
  ret.put("status", msg);
292
291
 
293
292
  // No need to wait for semaphore anymore since _reload() has already waited
294
- this.notifyListeners("appReady", ret);
293
+ this.notifyListeners("appReady", ret, true);
295
294
 
296
295
  // Auto hide splashscreen if enabled
297
296
  // We show it on background when conditions are met, so we should hide it on foreground regardless of update outcome
@@ -782,22 +781,37 @@ public class CapacitorUpdaterPlugin extends Plugin {
782
781
  Semaphore mainThreadSemaphore = new Semaphore(0);
783
782
  this.bridge.executeOnMainThread(() -> {
784
783
  try {
785
- url.set(new URL(this.bridge.getWebView().getUrl()));
784
+ if (this.bridge != null && this.bridge.getWebView() != null) {
785
+ String currentUrl = this.bridge.getWebView().getUrl();
786
+ if (currentUrl != null) {
787
+ url.set(new URL(currentUrl));
788
+ }
789
+ }
786
790
  } catch (Exception e) {
787
791
  logger.error("Error executing on main thread " + e.getMessage());
788
792
  }
789
793
  mainThreadSemaphore.release();
790
794
  });
791
- mainThreadSemaphore.acquire();
795
+
796
+ // Add timeout to prevent indefinite blocking
797
+ if (!mainThreadSemaphore.tryAcquire(10, TimeUnit.SECONDS)) {
798
+ logger.error("Timeout waiting for main thread operation");
799
+ }
792
800
  } else {
793
801
  try {
794
- url.set(new URL(this.bridge.getWebView().getUrl()));
802
+ if (this.bridge != null && this.bridge.getWebView() != null) {
803
+ String currentUrl = this.bridge.getWebView().getUrl();
804
+ if (currentUrl != null) {
805
+ url.set(new URL(currentUrl));
806
+ }
807
+ }
795
808
  } catch (Exception e) {
796
809
  logger.error("Error executing on main thread " + e.getMessage());
797
810
  }
798
811
  }
799
812
  } catch (InterruptedException e) {
800
813
  logger.error("Error waiting for main thread or getting the current URL from webview " + e.getMessage());
814
+ Thread.currentThread().interrupt(); // Restore interrupted status
801
815
  }
802
816
  }
803
817
 
@@ -839,7 +853,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
839
853
  this.notifyListeners("appReloaded", new JSObject());
840
854
 
841
855
  // Wait for the reload to complete (until notifyAppReady is called)
842
- this.semaphoreWait(this.appReadyTimeout);
856
+ try {
857
+ this.semaphoreWait(this.appReadyTimeout);
858
+ } catch (Exception e) {
859
+ logger.error("Error waiting for app ready: " + e.getMessage());
860
+ return false;
861
+ }
843
862
 
844
863
  return true;
845
864
  }
@@ -32,6 +32,7 @@ import java.util.Iterator;
32
32
  import java.util.List;
33
33
  import java.util.Map;
34
34
  import java.util.Objects;
35
+ import java.util.concurrent.TimeUnit;
35
36
  import java.util.zip.ZipEntry;
36
37
  import java.util.zip.ZipInputStream;
37
38
  import okhttp3.*;
@@ -57,7 +58,7 @@ public class CapgoUpdater {
57
58
  public SharedPreferences.Editor editor;
58
59
  public SharedPreferences prefs;
59
60
 
60
- public OkHttpClient client;
61
+ private OkHttpClient client;
61
62
 
62
63
  public File documentsDir;
63
64
  public Boolean directUpdate = false;
@@ -79,6 +80,8 @@ public class CapgoUpdater {
79
80
 
80
81
  public CapgoUpdater(Logger logger) {
81
82
  this.logger = logger;
83
+ // Simple OkHttpClient - actual configuration happens in plugin
84
+ this.client = new OkHttpClient.Builder().connectTimeout(30, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).build();
82
85
  }
83
86
 
84
87
  private final FilenameFilter filter = (f, name) -> {
@@ -248,11 +251,16 @@ public class CapgoUpdater {
248
251
  id,
249
252
  new BundleInfo(id, version, BundleStatus.ERROR, new Date(System.currentTimeMillis()), "")
250
253
  );
254
+ // Cleanup download tracking
255
+ DownloadWorkerManager.cancelBundleDownload(activity, id, version);
251
256
  Map<String, Object> ret = new HashMap<>();
252
257
  ret.put("version", getCurrentBundle().getVersionName());
253
258
  ret.put("error", "finish_download_fail");
254
259
  sendStats("finish_download_fail", version);
255
260
  notifyListeners("downloadFailed", ret);
261
+ } else {
262
+ // Successful download - cleanup tracking
263
+ DownloadWorkerManager.cancelBundleDownload(activity, id, version);
256
264
  }
257
265
  break;
258
266
  case FAILED:
@@ -264,6 +272,8 @@ public class CapgoUpdater {
264
272
  id,
265
273
  new BundleInfo(id, failedVersion, BundleStatus.ERROR, new Date(System.currentTimeMillis()), "")
266
274
  );
275
+ // Cleanup download tracking for failed downloads
276
+ DownloadWorkerManager.cancelBundleDownload(activity, id, failedVersion);
267
277
  Map<String, Object> ret = new HashMap<>();
268
278
  ret.put("version", getCurrentBundle().getVersionName());
269
279
  if ("low_mem_fail".equals(error)) {
@@ -381,6 +391,7 @@ public class CapgoUpdater {
381
391
  downloaded.delete();
382
392
  }
383
393
  this.notifyDownload(id, 100);
394
+ // Remove old bundle info and set new one
384
395
  this.saveBundleInfo(id, null);
385
396
  BundleInfo next = new BundleInfo(id, version, BundleStatus.PENDING, new Date(System.currentTimeMillis()), checksum);
386
397
  this.saveBundleInfo(id, next);
@@ -440,7 +451,7 @@ public class CapgoUpdater {
440
451
  final String id = this.randomString();
441
452
 
442
453
  // Check if version is already downloading, but allow retry if previous download failed
443
- if (this.activity != null && DownloadWorkerManager.isVersionDownloading(version)) {
454
+ if (this.activity != null && DownloadWorkerManager.isVersionDownloading(this.activity, version)) {
444
455
  // Check if there's an existing bundle with error status that we can retry
445
456
  BundleInfo existingBundle = this.getBundleInfoByName(version);
446
457
  if (existingBundle != null && existingBundle.isErrorStatus()) {
@@ -453,7 +464,7 @@ public class CapgoUpdater {
453
464
  }
454
465
  }
455
466
 
456
- this.saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
467
+ saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
457
468
  this.notifyDownload(id, 0);
458
469
  this.notifyDownload(id, 5);
459
470
 
@@ -469,7 +480,7 @@ public class CapgoUpdater {
469
480
  }
470
481
 
471
482
  final String id = this.randomString();
472
- this.saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
483
+ saveBundleInfo(id, new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), ""));
473
484
  this.notifyDownload(id, 0);
474
485
  this.notifyDownload(id, 5);
475
486
  final String dest = this.randomString();
@@ -629,10 +640,15 @@ public class CapgoUpdater {
629
640
  final BundleInfo fallback = this.getFallbackBundle();
630
641
  logger.debug("Fallback bundle is: " + fallback);
631
642
  logger.info("Version successfully loaded: " + bundle.getVersionName());
632
- if (autoDeletePrevious && !fallback.isBuiltin()) {
643
+ // Only attempt to delete when the fallback is a different bundle than the
644
+ // currently loaded one. Otherwise we spam logs with "Cannot delete <id>"
645
+ // because delete() protects the current bundle from removal.
646
+ if (autoDeletePrevious && !fallback.isBuiltin() && fallback.getId() != null && !fallback.getId().equals(bundle.getId())) {
633
647
  final Boolean res = this.delete(fallback.getId());
634
648
  if (res) {
635
649
  logger.info("Deleted previous bundle: " + fallback.getVersionName());
650
+ } else {
651
+ logger.debug("Skip deleting previous bundle (same as current or protected): " + fallback.getId());
636
652
  }
637
653
  }
638
654
  this.setFallbackBundle(bundle);