@capgo/cordova-updater 8.1.4 → 8.2.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.
- package/dist/esm/definitions.d.ts +116 -86
- package/dist/esm/definitions.js +2 -2
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/exec.js +11 -1
- package/dist/esm/exec.js.map +1 -1
- package/dist/esm/history.js +7 -7
- package/dist/esm/history.js.map +1 -1
- package/dist/plugin.cjs.js +13 -3
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +13 -3
- package/dist/plugin.js.map +1 -1
- package/package.json +5 -3
- package/plugin.xml +9 -9
- package/src/android/CordovaUpdaterPlugin.java +173 -5
- package/src/android/app/capgo/cordova/updater/CapgoUpdater.java +313 -55
- package/src/android/app/capgo/cordova/updater/CryptoCipher.java +51 -18
- package/src/android/app/capgo/cordova/updater/DelayUpdateUtils.java +0 -1
- package/src/android/app/capgo/cordova/updater/DownloadService.java +402 -89
- package/src/android/app/capgo/cordova/updater/Version.java +312 -0
- package/src/android/capgo-cordova-updater.gradle +3 -5
- package/src/android/test/app/capgo/cordova/updater/BackgroundDownloadSettlementTest.java +29 -0
- package/src/android/test/app/capgo/cordova/updater/CapacitorUpdaterUnitTest.java.skip +2 -12
- package/src/android/test/app/capgo/cordova/updater/DelayUpdateUtilsTest.java +0 -1
- package/src/android/test/app/capgo/cordova/updater/RsaContractTest.java +137 -0
- package/src/android/test/app/capgo/cordova/updater/SecurityHardeningTest.java +197 -0
- package/src/android/test/app/capgo/cordova/updater/SessionKeyRequiredTest.java +344 -0
- package/src/ios/CapgoSemanticVersion.swift +181 -0
- package/src/ios/CapgoUpdater.swift +427 -114
- package/src/ios/CordovaUpdaterPlugin.swift +171 -40
- package/src/ios/CryptoCipher.swift +41 -38
- package/src/ios/DelayUpdateUtils.swift +19 -45
- package/src/ios/Logger.swift +81 -5
- package/src/ios/RedirectPolicyDelegate.swift +53 -0
- package/src/ios/WebViewStatsReporter.swift +5 -0
- package/src/ios/ZipArchiveReader.swift +263 -0
- package/src/ios/ZipCentralDirectory.swift +283 -0
|
@@ -36,7 +36,6 @@ import com.google.android.play.core.install.InstallStateUpdatedListener;
|
|
|
36
36
|
import com.google.android.play.core.install.model.AppUpdateType;
|
|
37
37
|
import com.google.android.play.core.install.model.InstallStatus;
|
|
38
38
|
import com.google.android.play.core.install.model.UpdateAvailability;
|
|
39
|
-
import io.github.g00fy2.versioncompare.Version;
|
|
40
39
|
import java.io.ByteArrayOutputStream;
|
|
41
40
|
import java.io.IOException;
|
|
42
41
|
import java.io.InputStream;
|
|
@@ -171,7 +170,7 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
171
170
|
static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
|
|
172
171
|
static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
|
|
173
172
|
|
|
174
|
-
private final String pluginVersion = "8.1
|
|
173
|
+
private final String pluginVersion = "8.2.1";
|
|
175
174
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
176
175
|
|
|
177
176
|
private SharedPreferences.Editor editor;
|
|
@@ -245,6 +244,11 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
245
244
|
private Boolean isPreviousMainActivity = true;
|
|
246
245
|
|
|
247
246
|
private volatile Thread backgroundDownloadTask;
|
|
247
|
+
private volatile long activeBackgroundDownloadGeneration = 0L;
|
|
248
|
+
private final Object readyGuardLock = new Object();
|
|
249
|
+
private Object readyGenerationScriptHandle;
|
|
250
|
+
private volatile int readyGeneration = 0;
|
|
251
|
+
private volatile boolean readyGuardArmed = false;
|
|
248
252
|
private volatile Thread appReadyCheck;
|
|
249
253
|
private volatile long downloadStartTimeMs = 0;
|
|
250
254
|
private static final long DOWNLOAD_TIMEOUT_MS = 600000; // 10 minute timeout
|
|
@@ -711,6 +715,11 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
711
715
|
CordovaUpdaterPlugin.this.scheduleDirectUpdateFinish(latest);
|
|
712
716
|
}
|
|
713
717
|
|
|
718
|
+
@Override
|
|
719
|
+
public void backgroundDownloadSettled(final BundleInfo bundle, final String status, final long settlementToken) {
|
|
720
|
+
CordovaUpdaterPlugin.this.emitLaunchDownloadReady(bundle, status, settlementToken);
|
|
721
|
+
}
|
|
722
|
+
|
|
714
723
|
@Override
|
|
715
724
|
public void notifyListeners(final String id, final Map<String, Object> res) {
|
|
716
725
|
if (activity != null) {
|
|
@@ -753,6 +762,7 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
753
762
|
CryptoCipher.setLogger(logger);
|
|
754
763
|
DownloadService.setLogger(logger);
|
|
755
764
|
DownloadWorkerManager.setLogger(logger);
|
|
765
|
+
DownloadService.setAllowHttpsToHttpRedirect(this.updaterConfig.getBoolean("allowHttpsToHttpRedirect", false));
|
|
756
766
|
|
|
757
767
|
this.implementation.appId = InternalUtils.getPackageName(getContext().getPackageManager(), getContext().getPackageName());
|
|
758
768
|
this.implementation.appId = updaterConfig.getString("appId", this.implementation.appId);
|
|
@@ -2002,6 +2012,32 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
2002
2012
|
this.logger = logger;
|
|
2003
2013
|
}
|
|
2004
2014
|
|
|
2015
|
+
static boolean shouldAcceptBackgroundDownloadSettlement(final long settlementToken, final long activeGeneration) {
|
|
2016
|
+
return settlementToken > 0L && settlementToken == activeGeneration;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
void emitLaunchDownloadReady(final BundleInfo bundle, final String status, final long settlementToken) {
|
|
2020
|
+
if (!shouldAcceptBackgroundDownloadSettlement(settlementToken, this.activeBackgroundDownloadGeneration)) {
|
|
2021
|
+
logger.info("Ignoring stale background download settlement");
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
final BundleInfo readyBundle = bundle != null ? bundle : this.implementation.getCurrentBundle();
|
|
2025
|
+
if (readyBundle == null) {
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
this.endBackGroundTaskWithNotif(
|
|
2029
|
+
status,
|
|
2030
|
+
readyBundle.getVersionName(),
|
|
2031
|
+
readyBundle,
|
|
2032
|
+
false,
|
|
2033
|
+
false,
|
|
2034
|
+
"download_fail",
|
|
2035
|
+
"downloadFailed",
|
|
2036
|
+
false,
|
|
2037
|
+
false
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2005
2041
|
void completeBackgroundTaskForTesting(final BundleInfo current, final boolean plannedDirectUpdate) {
|
|
2006
2042
|
this.endBackGroundTaskWithNotif("test", current.getVersionName(), current, false, plannedDirectUpdate);
|
|
2007
2043
|
}
|
|
@@ -2373,6 +2409,7 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
2373
2409
|
CordovaUpdaterPlugin.this.editor,
|
|
2374
2410
|
DEFAULT_CHANNEL_PREF_KEY,
|
|
2375
2411
|
configDefaultChannel,
|
|
2412
|
+
CordovaUpdaterPlugin.this.allowSetDefaultChannel,
|
|
2376
2413
|
(res) -> {
|
|
2377
2414
|
JSObject jsRes = InternalUtils.mapToJSObject(res);
|
|
2378
2415
|
if (jsRes.has("error")) {
|
|
@@ -2546,6 +2583,20 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
2546
2583
|
final String checksum,
|
|
2547
2584
|
final JSONArray manifest
|
|
2548
2585
|
) throws IOException {
|
|
2586
|
+
if (!this.implementation.publicKey.isEmpty() && !CryptoCipher.isValidSessionKey(sessionKey)) {
|
|
2587
|
+
logger.error("Public key present but no valid session key provided");
|
|
2588
|
+
this.implementation.sendStats("session_key_required");
|
|
2589
|
+
throw new IOException("Session key required when public key is present");
|
|
2590
|
+
}
|
|
2591
|
+
if (manifest != null && manifest.length() == 0) {
|
|
2592
|
+
logger.error("Empty manifest provided");
|
|
2593
|
+
throw new IOException("Manifest cannot be empty");
|
|
2594
|
+
}
|
|
2595
|
+
if (manifest == null && (checksum == null || checksum.isEmpty())) {
|
|
2596
|
+
logger.error("No checksum provided");
|
|
2597
|
+
this.implementation.sendStats("checksum_required");
|
|
2598
|
+
throw new IOException("Checksum required");
|
|
2599
|
+
}
|
|
2549
2600
|
if (manifest != null) {
|
|
2550
2601
|
return this.implementation.downloadManifest(url, version, sessionKey, checksum, manifest);
|
|
2551
2602
|
}
|
|
@@ -2621,6 +2672,7 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
2621
2672
|
this.syncKeepUrlPathFlag(true);
|
|
2622
2673
|
}
|
|
2623
2674
|
logger.info("Reloading: " + path);
|
|
2675
|
+
this.stampReadyGenerationBeforeReload();
|
|
2624
2676
|
|
|
2625
2677
|
if (pathHandler != null) {
|
|
2626
2678
|
if (usingBuiltin) {
|
|
@@ -4063,9 +4115,103 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
4063
4115
|
this.periodCheckDelay
|
|
4064
4116
|
);
|
|
4065
4117
|
}
|
|
4118
|
+
static boolean shouldAcceptReadyCall(
|
|
4119
|
+
final boolean guardArmed,
|
|
4120
|
+
final int expectedGeneration,
|
|
4121
|
+
final boolean hasGeneration,
|
|
4122
|
+
final int reportedGeneration
|
|
4123
|
+
) {
|
|
4124
|
+
if (!guardArmed) {
|
|
4125
|
+
return true;
|
|
4126
|
+
}
|
|
4127
|
+
return hasGeneration && reportedGeneration == expectedGeneration;
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
static String readyGenerationScript(final int generation) {
|
|
4131
|
+
return "(function(){window.__CAPGO_READY_GEN=" + generation + ";})();";
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
private int armReadyGuard() {
|
|
4135
|
+
synchronized (this.readyGuardLock) {
|
|
4136
|
+
this.readyGeneration = this.readyGeneration + 1;
|
|
4137
|
+
this.readyGuardArmed = true;
|
|
4138
|
+
return this.readyGeneration;
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
private void disarmReadyGuard(final int generation) {
|
|
4143
|
+
synchronized (this.readyGuardLock) {
|
|
4144
|
+
if (this.readyGeneration == generation) {
|
|
4145
|
+
this.readyGuardArmed = false;
|
|
4146
|
+
logger.warn("Could not stamp notifyAppReady for the next page. Readiness guard disabled for this reload.");
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
private void stampReadyGenerationBeforeReload() {
|
|
4152
|
+
final int generation = this.armReadyGuard();
|
|
4153
|
+
final android.webkit.WebView webView = getUpdaterWebView();
|
|
4154
|
+
if (webView == null) {
|
|
4155
|
+
this.disarmReadyGuard(generation);
|
|
4156
|
+
return;
|
|
4157
|
+
}
|
|
4158
|
+
cordova.getActivity().runOnUiThread(() -> {
|
|
4159
|
+
if (!this.installReadyGenerationScript(webView, generation)) {
|
|
4160
|
+
this.disarmReadyGuard(generation);
|
|
4161
|
+
}
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
4164
|
+
|
|
4165
|
+
private boolean installReadyGenerationScript(final android.webkit.WebView webView, final int generation) {
|
|
4166
|
+
try {
|
|
4167
|
+
final Class<?> webViewFeature = Class.forName("androidx.webkit.WebViewFeature");
|
|
4168
|
+
final String feature = (String) webViewFeature.getField("DOCUMENT_START_SCRIPT").get(null);
|
|
4169
|
+
final Boolean supported = (Boolean) webViewFeature.getMethod("isFeatureSupported", String.class).invoke(null, feature);
|
|
4170
|
+
if (!Boolean.TRUE.equals(supported)) {
|
|
4171
|
+
return false;
|
|
4172
|
+
}
|
|
4173
|
+
final Class<?> webViewCompat = Class.forName("androidx.webkit.WebViewCompat");
|
|
4174
|
+
if (this.readyGenerationScriptHandle != null) {
|
|
4175
|
+
try {
|
|
4176
|
+
this.readyGenerationScriptHandle.getClass().getMethod("remove").invoke(this.readyGenerationScriptHandle);
|
|
4177
|
+
} catch (final Exception removeError) {
|
|
4178
|
+
logger.warn("Unable to remove previous notifyAppReady generation script: " + removeError.getMessage());
|
|
4179
|
+
}
|
|
4180
|
+
this.readyGenerationScriptHandle = null;
|
|
4181
|
+
}
|
|
4182
|
+
this.readyGenerationScriptHandle = webViewCompat
|
|
4183
|
+
.getMethod("addDocumentStartJavaScript", android.webkit.WebView.class, String.class, Set.class)
|
|
4184
|
+
.invoke(null, webView, readyGenerationScript(generation), java.util.Collections.singleton("*"));
|
|
4185
|
+
return true;
|
|
4186
|
+
} catch (final Exception e) {
|
|
4187
|
+
logger.warn("Unable to stamp notifyAppReady generation: " + e.getMessage());
|
|
4188
|
+
return false;
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
private boolean acceptsReadyCall(final PluginCall call) {
|
|
4193
|
+
if (!this.readyGuardArmed) {
|
|
4194
|
+
return true;
|
|
4195
|
+
}
|
|
4196
|
+
final JSONObject data = call.getData();
|
|
4197
|
+
final boolean hasGeneration = data != null && data.has("loadGeneration");
|
|
4198
|
+
final int reported = hasGeneration ? data.optInt("loadGeneration", -1) : -1;
|
|
4199
|
+
return shouldAcceptReadyCall(this.readyGuardArmed, this.readyGeneration, hasGeneration, reported);
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4066
4202
|
public void notifyAppReady(final PluginCall call) {
|
|
4067
4203
|
ensureBridgeSet();
|
|
4068
4204
|
try {
|
|
4205
|
+
if (!this.acceptsReadyCall(call)) {
|
|
4206
|
+
logger.info("Ignoring notifyAppReady from a page that is no longer current");
|
|
4207
|
+
final BundleInfo current = this.implementation.getCurrentBundle();
|
|
4208
|
+
final app.capgo.cordova.updater.compat.JSObject ignored = new app.capgo.cordova.updater.compat.JSObject();
|
|
4209
|
+
if (current != null) {
|
|
4210
|
+
ignored.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
|
|
4211
|
+
}
|
|
4212
|
+
call.resolve(ignored);
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4069
4215
|
final BundleInfo bundle = this.implementation.getCurrentBundle();
|
|
4070
4216
|
this.implementation.setSuccess(bundle, this.autoDeletePrevious);
|
|
4071
4217
|
logger.info("Current bundle loaded successfully. ['notifyAppReady()' was called] " + bundle);
|
|
@@ -4352,6 +4498,7 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
4352
4498
|
if (this.shouldBlockAutoUpdateForPreviewSession()) {
|
|
4353
4499
|
return null;
|
|
4354
4500
|
}
|
|
4501
|
+
final long generation = ++this.activeBackgroundDownloadGeneration;
|
|
4355
4502
|
final boolean plannedDirectUpdate = this.shouldUseDirectUpdate();
|
|
4356
4503
|
final boolean initialDirectUpdateAllowed = this.isDirectUpdateCurrentlyAllowed(plannedDirectUpdate);
|
|
4357
4504
|
final String messageUpdate = initialDirectUpdateAllowed
|
|
@@ -4487,6 +4634,22 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
4487
4634
|
if (
|
|
4488
4635
|
latestVersionName != null && !latestVersionName.isEmpty() && !current.getVersionName().equals(latestVersionName)
|
|
4489
4636
|
) {
|
|
4637
|
+
final String latestSessionKey = jsRes.has("sessionKey") ? jsRes.getString("sessionKey") : "";
|
|
4638
|
+
if (
|
|
4639
|
+
!CordovaUpdaterPlugin.this.implementation.publicKey.isEmpty() &&
|
|
4640
|
+
!CryptoCipher.isValidSessionKey(latestSessionKey)
|
|
4641
|
+
) {
|
|
4642
|
+
logger.error("Public key present but no valid session key provided");
|
|
4643
|
+
CordovaUpdaterPlugin.this.implementation.sendStats("session_key_required");
|
|
4644
|
+
CordovaUpdaterPlugin.this.endBackGroundTaskWithNotif(
|
|
4645
|
+
"Session key required when public key is present",
|
|
4646
|
+
latestVersionName,
|
|
4647
|
+
current,
|
|
4648
|
+
true,
|
|
4649
|
+
plannedDirectUpdate
|
|
4650
|
+
);
|
|
4651
|
+
return;
|
|
4652
|
+
}
|
|
4490
4653
|
final BundleInfo latest = CordovaUpdaterPlugin.this.implementation.getBundleInfoByName(latestVersionName);
|
|
4491
4654
|
if (latest != null) {
|
|
4492
4655
|
final app.capgo.cordova.updater.compat.JSObject ret = new app.capgo.cordova.updater.compat.JSObject();
|
|
@@ -4502,7 +4665,10 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
4502
4665
|
);
|
|
4503
4666
|
return;
|
|
4504
4667
|
}
|
|
4505
|
-
if (
|
|
4668
|
+
if (
|
|
4669
|
+
latest.isDownloaded() &&
|
|
4670
|
+
BundleStatus.DOWNLOADING != latest.getStatus()
|
|
4671
|
+
) {
|
|
4506
4672
|
logger.info("Latest bundle already exists and download is NOT required. " + messageUpdate);
|
|
4507
4673
|
final boolean directUpdateAllowedNow = CordovaUpdaterPlugin.this.isDirectUpdateCurrentlyAllowed(
|
|
4508
4674
|
plannedDirectUpdate
|
|
@@ -4623,7 +4789,8 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
4623
4789
|
sessionKey,
|
|
4624
4790
|
checksum,
|
|
4625
4791
|
manifest,
|
|
4626
|
-
CordovaUpdaterPlugin.this.shouldAutoSetNextBundle()
|
|
4792
|
+
CordovaUpdaterPlugin.this.shouldAutoSetNextBundle(),
|
|
4793
|
+
generation
|
|
4627
4794
|
);
|
|
4628
4795
|
} else {
|
|
4629
4796
|
// Handle single file download (existing code)
|
|
@@ -4633,7 +4800,8 @@ public class CordovaUpdaterPlugin extends org.apache.cordova.CordovaPlugin imple
|
|
|
4633
4800
|
sessionKey,
|
|
4634
4801
|
checksum,
|
|
4635
4802
|
null,
|
|
4636
|
-
CordovaUpdaterPlugin.this.shouldAutoSetNextBundle()
|
|
4803
|
+
CordovaUpdaterPlugin.this.shouldAutoSetNextBundle(),
|
|
4804
|
+
generation
|
|
4637
4805
|
);
|
|
4638
4806
|
}
|
|
4639
4807
|
} catch (final Exception e) {
|