@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 +1 -1
- package/android/build.gradle +12 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +36 -17
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +21 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +259 -99
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +29 -24
- package/dist/docs.json +1 -1
- package/dist/esm/definitions.d.ts +1 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +28 -83
- package/ios/Plugin/CapgoUpdater.swift +20 -2
- package/ios/Plugin/Logger.swift +22 -1
- package/package.json +5 -2
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.
|
|
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 |
|
package/android/build.gradle
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
264
|
+
Thread.currentThread().interrupt(); // Restore interrupted status
|
|
267
265
|
} catch (TimeoutException e) {
|
|
268
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|