@capgo/capacitor-updater 8.51.25 → 8.52.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.
- package/CapgoCapacitorUpdater.podspec +0 -3
- package/Package.swift +3 -11
- package/README.md +4 -1
- package/android/build.gradle +0 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +182 -31
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +60 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +0 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +80 -21
- package/android/src/main/java/ee/forgr/capacitor_updater/Version.java +306 -0
- package/dist/docs.json +16 -0
- package/dist/esm/definitions.d.ts +14 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +90 -34
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoSemanticVersion.swift +162 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +218 -71
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +3 -4
- package/ios/Sources/CapacitorUpdaterPlugin/RedirectPolicyDelegate.swift +53 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ZipArchiveReader.swift +247 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ZipCentralDirectory.swift +280 -0
- package/package.json +1 -1
|
@@ -15,7 +15,6 @@ import java.io.*;
|
|
|
15
15
|
import java.io.FileInputStream;
|
|
16
16
|
import java.net.HttpURLConnection;
|
|
17
17
|
import java.net.MalformedURLException;
|
|
18
|
-
import java.net.URL;
|
|
19
18
|
import java.nio.channels.FileChannel;
|
|
20
19
|
import java.security.MessageDigest;
|
|
21
20
|
import java.util.ArrayList;
|
|
@@ -35,7 +34,9 @@ import java.util.function.BooleanSupplier;
|
|
|
35
34
|
import java.util.function.LongConsumer;
|
|
36
35
|
import okhttp3.Call;
|
|
37
36
|
import okhttp3.Callback;
|
|
37
|
+
import okhttp3.CookieJar;
|
|
38
38
|
import okhttp3.Dispatcher;
|
|
39
|
+
import okhttp3.HttpUrl;
|
|
39
40
|
import okhttp3.Interceptor;
|
|
40
41
|
import okhttp3.MediaType;
|
|
41
42
|
import okhttp3.OkHttpClient;
|
|
@@ -50,6 +51,14 @@ import org.json.JSONObject;
|
|
|
50
51
|
|
|
51
52
|
public class DownloadService extends Worker {
|
|
52
53
|
|
|
54
|
+
/** A refused HTTPS to HTTP redirect. Permanent for the given URL, so downloads must not retry it. */
|
|
55
|
+
static final class BlockedRedirectException extends IOException {
|
|
56
|
+
|
|
57
|
+
BlockedRedirectException() {
|
|
58
|
+
super("Blocked HTTPS to HTTP redirect; set allowHttpsToHttpRedirect to true to allow it");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
53
62
|
static final class DownloadRetryException extends RuntimeException {
|
|
54
63
|
|
|
55
64
|
DownloadRetryException(String message) {
|
|
@@ -126,6 +135,8 @@ public class DownloadService extends Worker {
|
|
|
126
135
|
// Match CapgoUpdater.timeout / responseTimeout default (20s). OkHttp's 10s
|
|
127
136
|
// defaults were unused by the plugin config and aborted slow manifest GETs.
|
|
128
137
|
private static volatile int httpTimeoutMs = 20_000;
|
|
138
|
+
// Off by default: a redirect must never downgrade updater traffic from HTTPS to plain HTTP.
|
|
139
|
+
private static volatile boolean allowHttpsToHttpRedirect = false;
|
|
129
140
|
private static String currentAppId = "unknown";
|
|
130
141
|
private static String currentPluginVersion = "unknown";
|
|
131
142
|
private static String currentVersionOs = "unknown";
|
|
@@ -138,6 +149,9 @@ public class DownloadService extends Worker {
|
|
|
138
149
|
sharedClient = new OkHttpClient.Builder()
|
|
139
150
|
.dispatcher(dispatcher)
|
|
140
151
|
.protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
|
|
152
|
+
// Never share cookies with Capgo endpoints: the app's java.net.CookieHandler default is
|
|
153
|
+
// Capacitor's WebView cookie manager, and it must not see (or be fed by) plugin traffic.
|
|
154
|
+
.cookieJar(CookieJar.NO_COOKIES)
|
|
141
155
|
.connectTimeout(httpTimeoutMs, TimeUnit.MILLISECONDS)
|
|
142
156
|
.readTimeout(httpTimeoutMs, TimeUnit.MILLISECONDS)
|
|
143
157
|
.writeTimeout(httpTimeoutMs, TimeUnit.MILLISECONDS)
|
|
@@ -147,9 +161,33 @@ public class DownloadService extends Worker {
|
|
|
147
161
|
Request requestWithUserAgent = originalRequest.newBuilder().header("User-Agent", userAgent).build();
|
|
148
162
|
return chain.proceed(requestWithUserAgent);
|
|
149
163
|
})
|
|
164
|
+
.addNetworkInterceptor((chain) -> {
|
|
165
|
+
Response response = chain.proceed(chain.request());
|
|
166
|
+
if (!allowHttpsToHttpRedirect && isHttpsToHttpRedirect(response)) {
|
|
167
|
+
response.close();
|
|
168
|
+
throw new BlockedRedirectException();
|
|
169
|
+
}
|
|
170
|
+
return response;
|
|
171
|
+
})
|
|
150
172
|
.build();
|
|
151
173
|
}
|
|
152
174
|
|
|
175
|
+
static void setAllowHttpsToHttpRedirect(boolean allow) {
|
|
176
|
+
allowHttpsToHttpRedirect = allow;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static boolean isHttpsToHttpRedirect(Response response) {
|
|
180
|
+
if (!response.isRedirect() || !response.request().isHttps()) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
String location = response.header("Location");
|
|
184
|
+
if (location == null) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
HttpUrl target = response.request().url().resolve(location);
|
|
188
|
+
return target != null && !target.isHttps();
|
|
189
|
+
}
|
|
190
|
+
|
|
153
191
|
static int httpTimeoutMs() {
|
|
154
192
|
return httpTimeoutMs;
|
|
155
193
|
}
|
|
@@ -768,20 +806,16 @@ public class DownloadService extends Worker {
|
|
|
768
806
|
throw new RuntimeException("insufficient_disk_space");
|
|
769
807
|
}
|
|
770
808
|
|
|
771
|
-
|
|
809
|
+
Response response = null;
|
|
772
810
|
InputStream inputStream = null;
|
|
773
811
|
BufferedReader reader = null;
|
|
774
812
|
BufferedWriter writer = null;
|
|
775
813
|
|
|
776
814
|
try {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
// responseTimeout cannot shrink large-bundle downloads.
|
|
782
|
-
int zipTimeoutMs = Math.max(httpTimeoutMs, 60_000);
|
|
783
|
-
httpConn.setConnectTimeout(zipTimeoutMs);
|
|
784
|
-
httpConn.setReadTimeout(zipTimeoutMs);
|
|
815
|
+
HttpUrl zipUrl = url != null ? HttpUrl.parse(url) : null;
|
|
816
|
+
if (zipUrl == null) {
|
|
817
|
+
throw new MalformedURLException("Expected an http or https URL");
|
|
818
|
+
}
|
|
785
819
|
|
|
786
820
|
// Reading progress file (if exist)
|
|
787
821
|
long downloadedBytes = 0;
|
|
@@ -810,15 +844,16 @@ public class DownloadService extends Worker {
|
|
|
810
844
|
throw new DownloadRetryException("download_stopped");
|
|
811
845
|
}
|
|
812
846
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
int responseCode = httpConn.getResponseCode();
|
|
847
|
+
response = executeZipRequest(zipUrl, downloadedBytes);
|
|
848
|
+
int responseCode = response.code();
|
|
818
849
|
|
|
819
850
|
if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_PARTIAL) {
|
|
820
|
-
|
|
821
|
-
|
|
851
|
+
ResponseBody responseBody = response.body();
|
|
852
|
+
if (responseBody == null) {
|
|
853
|
+
throw new IOException("Response body is null");
|
|
854
|
+
}
|
|
855
|
+
ZipWritePlan writePlan = planZipResumeWrite(responseCode, downloadedBytes, response.header("Content-Range"));
|
|
856
|
+
long responseBodyLength = responseBody.contentLength();
|
|
822
857
|
long contentLength = shouldAppendHttpBody(writePlan.statusCode, writePlan.writeOffset)
|
|
823
858
|
? (responseBodyLength >= 0 ? responseBodyLength + writePlan.writeOffset : -1)
|
|
824
859
|
: responseBodyLength;
|
|
@@ -829,7 +864,7 @@ public class DownloadService extends Worker {
|
|
|
829
864
|
}
|
|
830
865
|
|
|
831
866
|
try {
|
|
832
|
-
inputStream =
|
|
867
|
+
inputStream = responseBody.byteStream();
|
|
833
868
|
|
|
834
869
|
if (writePlan.writeOffset == 0 && downloadedBytes == 0) {
|
|
835
870
|
writer = new BufferedWriter(new FileWriter(infoFile));
|
|
@@ -840,7 +875,7 @@ public class DownloadService extends Worker {
|
|
|
840
875
|
|
|
841
876
|
final int[] lastNotifiedPercent = { 0 };
|
|
842
877
|
final long totalContentLength = contentLength;
|
|
843
|
-
String contentRangeHeader =
|
|
878
|
+
String contentRangeHeader = response.header("Content-Range");
|
|
844
879
|
writeHttpBody(tempFile, inputStream, writePlan.statusCode, writePlan.writeOffset, this::isStopped, (written) -> {
|
|
845
880
|
int percent = calcTotalPercent(written, totalContentLength);
|
|
846
881
|
if (percent >= lastNotifiedPercent[0] + 10) {
|
|
@@ -908,6 +943,9 @@ public class DownloadService extends Worker {
|
|
|
908
943
|
} catch (MalformedURLException e) {
|
|
909
944
|
logger.error("Invalid download URL: " + e.getMessage());
|
|
910
945
|
throw new RuntimeException("invalid_url: " + e.getMessage());
|
|
946
|
+
} catch (BlockedRedirectException e) {
|
|
947
|
+
logger.error("Download error: " + e.getMessage());
|
|
948
|
+
throw new RuntimeException("blocked_redirect: " + e.getMessage());
|
|
911
949
|
} catch (IOException e) {
|
|
912
950
|
logger.error("Download error: " + e.getMessage());
|
|
913
951
|
throw new DownloadRetryException(e.getMessage(), e);
|
|
@@ -916,14 +954,35 @@ public class DownloadService extends Worker {
|
|
|
916
954
|
throw new RuntimeException(e.getMessage());
|
|
917
955
|
} finally {
|
|
918
956
|
// Ensure connection is closed
|
|
919
|
-
if (
|
|
957
|
+
if (response != null) {
|
|
920
958
|
try {
|
|
921
|
-
|
|
959
|
+
response.close();
|
|
922
960
|
} catch (Exception ignored) {}
|
|
923
961
|
}
|
|
924
962
|
}
|
|
925
963
|
}
|
|
926
964
|
|
|
965
|
+
/**
|
|
966
|
+
* Zip bundle request on the shared client (no cookies, same User-Agent), resuming from
|
|
967
|
+
* {@code downloadedBytes} with a Range header. The caller closes the response.
|
|
968
|
+
*/
|
|
969
|
+
static Response executeZipRequest(HttpUrl url, long downloadedBytes) throws IOException {
|
|
970
|
+
// Zip can stall longer than a JSON API call; keep a floor so
|
|
971
|
+
// responseTimeout cannot shrink large-bundle downloads.
|
|
972
|
+
int zipTimeoutMs = Math.max(httpTimeoutMs, 60_000);
|
|
973
|
+
OkHttpClient zipClient = sharedClient
|
|
974
|
+
.newBuilder()
|
|
975
|
+
.connectTimeout(zipTimeoutMs, TimeUnit.MILLISECONDS)
|
|
976
|
+
.readTimeout(zipTimeoutMs, TimeUnit.MILLISECONDS)
|
|
977
|
+
.writeTimeout(zipTimeoutMs, TimeUnit.MILLISECONDS)
|
|
978
|
+
.build();
|
|
979
|
+
Request.Builder builder = new Request.Builder().url(url);
|
|
980
|
+
if (downloadedBytes > 0) {
|
|
981
|
+
builder.header("Range", "bytes=" + downloadedBytes + "-");
|
|
982
|
+
}
|
|
983
|
+
return zipClient.newCall(builder.build()).execute();
|
|
984
|
+
}
|
|
985
|
+
|
|
927
986
|
static boolean isRetryableHttpStatus(int responseCode) {
|
|
928
987
|
return responseCode >= 500 || responseCode == 408 || responseCode == 429;
|
|
929
988
|
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Parsing and comparison logic ported from version-compare 1.5.0
|
|
7
|
+
* (https://github.com/G00fY2/version-compare), Copyright G00fY2,
|
|
8
|
+
* licensed under the Apache License, Version 2.0
|
|
9
|
+
* (https://www.apache.org/licenses/LICENSE-2.0).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
package ee.forgr.capacitor_updater;
|
|
13
|
+
|
|
14
|
+
import androidx.annotation.NonNull;
|
|
15
|
+
import androidx.annotation.Nullable;
|
|
16
|
+
import java.util.ArrayList;
|
|
17
|
+
import java.util.List;
|
|
18
|
+
import java.util.Locale;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Lenient version parser and comparator.
|
|
22
|
+
*
|
|
23
|
+
* <p>Behavior-compatible port of {@code io.github.g00fy2:versioncompare:1.5.0} (Apache-2.0), kept in-repo so the
|
|
24
|
+
* plugin does not ship an extra third-party dependency. Parsing never throws for non-null input that starts with a
|
|
25
|
+
* digit; unparsable input (null or not starting with a digit) yields an empty version equal to {@code 0}.
|
|
26
|
+
*
|
|
27
|
+
* <p>Pre-release suffix ordering: {@code snapshot < pre-alpha < alpha < beta < rc < stable}, with an optional number
|
|
28
|
+
* right after the qualifier ({@code 1.0.0-rc2 > 1.0.0-rc1}).
|
|
29
|
+
*/
|
|
30
|
+
public class Version implements Comparable<Version> {
|
|
31
|
+
|
|
32
|
+
enum ReleaseType {
|
|
33
|
+
SNAPSHOT,
|
|
34
|
+
PRE_ALPHA,
|
|
35
|
+
ALPHA,
|
|
36
|
+
BETA,
|
|
37
|
+
RC,
|
|
38
|
+
STABLE
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private static final String SNAPSHOT_STRING = "snapshot";
|
|
42
|
+
private static final String PRE_STRING = "pre";
|
|
43
|
+
private static final String ALPHA_STRING = "alpha";
|
|
44
|
+
private static final String BETA_STRING = "beta";
|
|
45
|
+
private static final String RC_STRING = "rc";
|
|
46
|
+
|
|
47
|
+
@Nullable
|
|
48
|
+
private final String originalString;
|
|
49
|
+
|
|
50
|
+
private final List<Long> subversionNumbers = new ArrayList<>();
|
|
51
|
+
private final List<Long> trimmedSubversionNumbers = new ArrayList<>();
|
|
52
|
+
private final String suffix;
|
|
53
|
+
private final ReleaseType releaseType;
|
|
54
|
+
private final long preReleaseVersion;
|
|
55
|
+
|
|
56
|
+
public Version(@Nullable String versionString) {
|
|
57
|
+
originalString = versionString;
|
|
58
|
+
if (originalString != null && startsNumeric(originalString)) {
|
|
59
|
+
String[] versionTokens = originalString.replaceAll("\\s", "").split("\\.");
|
|
60
|
+
boolean suffixFound = false;
|
|
61
|
+
StringBuilder suffixSb = null;
|
|
62
|
+
|
|
63
|
+
for (String versionToken : versionTokens) {
|
|
64
|
+
if (suffixFound) {
|
|
65
|
+
suffixSb.append(".");
|
|
66
|
+
suffixSb.append(versionToken);
|
|
67
|
+
} else if (isNumeric(versionToken)) {
|
|
68
|
+
subversionNumbers.add(safeParseLong(versionToken));
|
|
69
|
+
} else {
|
|
70
|
+
for (int i = 0; i < versionToken.length(); i++) {
|
|
71
|
+
if (!Character.isDigit(versionToken.charAt(i))) {
|
|
72
|
+
suffixSb = new StringBuilder();
|
|
73
|
+
if (i > 0) {
|
|
74
|
+
subversionNumbers.add(safeParseLong(versionToken.substring(0, i)));
|
|
75
|
+
suffixSb.append(versionToken.substring(i));
|
|
76
|
+
} else {
|
|
77
|
+
suffixSb.append(versionToken);
|
|
78
|
+
}
|
|
79
|
+
suffixFound = true;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
suffix = (suffixSb != null) ? suffixSb.toString() : "";
|
|
86
|
+
trimmedSubversionNumbers.addAll(subversionNumbers);
|
|
87
|
+
while (!trimmedSubversionNumbers.isEmpty() && trimmedSubversionNumbers.get(trimmedSubversionNumbers.size() - 1) == 0L) {
|
|
88
|
+
trimmedSubversionNumbers.remove(trimmedSubversionNumbers.size() - 1);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
suffix = "";
|
|
92
|
+
}
|
|
93
|
+
releaseType = qualifierToReleaseType(suffix);
|
|
94
|
+
preReleaseVersion = preReleaseVersion(suffix, releaseType);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public long getMajor() {
|
|
98
|
+
return trimmedSubversionNumbers.size() > 0 ? trimmedSubversionNumbers.get(0) : 0L;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public long getMinor() {
|
|
102
|
+
return trimmedSubversionNumbers.size() > 1 ? trimmedSubversionNumbers.get(1) : 0L;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public long getPatch() {
|
|
106
|
+
return trimmedSubversionNumbers.size() > 2 ? trimmedSubversionNumbers.get(2) : 0L;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@NonNull
|
|
110
|
+
public List<Long> getSubversionNumbers() {
|
|
111
|
+
return subversionNumbers;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@NonNull
|
|
115
|
+
public String getSuffix() {
|
|
116
|
+
return suffix;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@Nullable
|
|
120
|
+
public String getOriginalString() {
|
|
121
|
+
return originalString;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public boolean isHigherThan(String otherVersion) {
|
|
125
|
+
return isHigherThan(new Version(otherVersion));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public boolean isHigherThan(Version otherVersion) {
|
|
129
|
+
return compareTo(otherVersion) > 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public boolean isLowerThan(String otherVersion) {
|
|
133
|
+
return isLowerThan(new Version(otherVersion));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public boolean isLowerThan(Version otherVersion) {
|
|
137
|
+
return compareTo(otherVersion) < 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public boolean isEqual(String otherVersion) {
|
|
141
|
+
return isEqual(new Version(otherVersion));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public boolean isEqual(Version otherVersion) {
|
|
145
|
+
return compareTo(otherVersion) == 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public boolean isAtLeast(String otherVersion) {
|
|
149
|
+
return isAtLeast(new Version(otherVersion));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public boolean isAtLeast(Version otherVersion) {
|
|
153
|
+
return compareTo(otherVersion) >= 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public boolean isAtLeast(String otherVersion, boolean ignoreSuffix) {
|
|
157
|
+
return isAtLeast(new Version(otherVersion), ignoreSuffix);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public boolean isAtLeast(Version otherVersion, boolean ignoreSuffix) {
|
|
161
|
+
return compareTo(otherVersion, ignoreSuffix) >= 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@Override
|
|
165
|
+
public final int compareTo(@NonNull Version version) {
|
|
166
|
+
return compareTo(version, false);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private int compareTo(@NonNull Version version, boolean ignoreSuffix) {
|
|
170
|
+
int versionNumberResult = compareSubversionNumbers(trimmedSubversionNumbers, version.trimmedSubversionNumbers);
|
|
171
|
+
if (versionNumberResult != 0 || ignoreSuffix) {
|
|
172
|
+
return versionNumberResult;
|
|
173
|
+
}
|
|
174
|
+
int releaseTypeResult = releaseType.compareTo(version.releaseType);
|
|
175
|
+
if (releaseTypeResult != 0) {
|
|
176
|
+
return releaseTypeResult;
|
|
177
|
+
}
|
|
178
|
+
return Long.compare(preReleaseVersion, version.preReleaseVersion);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@Override
|
|
182
|
+
public final boolean equals(Object o) {
|
|
183
|
+
return o instanceof Version && isEqual((Version) o);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@Override
|
|
187
|
+
public final int hashCode() {
|
|
188
|
+
int result = trimmedSubversionNumbers.hashCode();
|
|
189
|
+
result = 31 * result + releaseType.hashCode();
|
|
190
|
+
result = 31 * result + (int) (preReleaseVersion ^ (preReleaseVersion >>> 32));
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@NonNull
|
|
195
|
+
@Override
|
|
196
|
+
public String toString() {
|
|
197
|
+
return String.valueOf(originalString);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// helpers
|
|
201
|
+
|
|
202
|
+
private static int compareSubversionNumbers(@NonNull final List<Long> versionNumbersA, @NonNull final List<Long> versionNumbersB) {
|
|
203
|
+
final int numbersSizeA = versionNumbersA.size();
|
|
204
|
+
final int numbersSizeB = versionNumbersB.size();
|
|
205
|
+
final int maxSize = Math.max(numbersSizeA, numbersSizeB);
|
|
206
|
+
|
|
207
|
+
for (int i = 0; i < maxSize; i++) {
|
|
208
|
+
final long a = i < numbersSizeA ? versionNumbersA.get(i) : 0L;
|
|
209
|
+
final long b = i < numbersSizeB ? versionNumbersB.get(i) : 0L;
|
|
210
|
+
if (a > b) {
|
|
211
|
+
return 1;
|
|
212
|
+
} else if (a < b) {
|
|
213
|
+
return -1;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static ReleaseType qualifierToReleaseType(@NonNull String suffix) {
|
|
220
|
+
if (suffix.length() > 0) {
|
|
221
|
+
suffix = suffix.toLowerCase(Locale.ROOT);
|
|
222
|
+
if (suffix.contains(RC_STRING)) return ReleaseType.RC;
|
|
223
|
+
if (suffix.contains(BETA_STRING)) return ReleaseType.BETA;
|
|
224
|
+
if (suffix.contains(ALPHA_STRING)) {
|
|
225
|
+
if (suffix.substring(0, suffix.indexOf(ALPHA_STRING)).contains(PRE_STRING)) {
|
|
226
|
+
return ReleaseType.PRE_ALPHA;
|
|
227
|
+
}
|
|
228
|
+
return ReleaseType.ALPHA;
|
|
229
|
+
}
|
|
230
|
+
if (suffix.contains(SNAPSHOT_STRING)) return ReleaseType.SNAPSHOT;
|
|
231
|
+
}
|
|
232
|
+
return ReleaseType.STABLE;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private static long preReleaseVersion(@NonNull final String suffix, final ReleaseType releaseType) {
|
|
236
|
+
if (releaseType == ReleaseType.STABLE || releaseType == ReleaseType.SNAPSHOT) return 0;
|
|
237
|
+
|
|
238
|
+
final int startIndex = indexOfQualifier(suffix, releaseType);
|
|
239
|
+
if (startIndex < suffix.length()) {
|
|
240
|
+
final int maxStartIndex = Math.min(startIndex + 2, suffix.length());
|
|
241
|
+
if (containsNumeric(suffix.substring(startIndex, maxStartIndex))) {
|
|
242
|
+
final StringBuilder versionNumber = new StringBuilder();
|
|
243
|
+
for (int i = startIndex; i < suffix.length(); i++) {
|
|
244
|
+
final char c = suffix.charAt(i);
|
|
245
|
+
if (Character.isDigit(c)) {
|
|
246
|
+
versionNumber.append(c);
|
|
247
|
+
} else if (i != startIndex) {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return safeParseLong(versionNumber.toString());
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private static int indexOfQualifier(@NonNull String suffix, final ReleaseType releaseType) {
|
|
258
|
+
suffix = suffix.toLowerCase(Locale.ROOT);
|
|
259
|
+
switch (releaseType) {
|
|
260
|
+
case RC:
|
|
261
|
+
return suffix.indexOf(RC_STRING) + RC_STRING.length();
|
|
262
|
+
case BETA:
|
|
263
|
+
return suffix.indexOf(BETA_STRING) + BETA_STRING.length();
|
|
264
|
+
case ALPHA:
|
|
265
|
+
case PRE_ALPHA:
|
|
266
|
+
return suffix.indexOf(ALPHA_STRING) + ALPHA_STRING.length();
|
|
267
|
+
default:
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private static boolean startsNumeric(@NonNull String str) {
|
|
273
|
+
str = str.trim();
|
|
274
|
+
return str.length() > 0 && Character.isDigit(str.charAt(0));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private static long safeParseLong(@NonNull String numbers) {
|
|
278
|
+
if (numbers.length() > 19) {
|
|
279
|
+
numbers = numbers.substring(0, 19);
|
|
280
|
+
}
|
|
281
|
+
return Long.parseLong(numbers);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private static boolean isNumeric(@NonNull final CharSequence cs) {
|
|
285
|
+
final int sz = cs.length();
|
|
286
|
+
if (sz > 0) {
|
|
287
|
+
for (int i = 0; i < sz; i++) {
|
|
288
|
+
if (!Character.isDigit(cs.charAt(i))) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private static boolean containsNumeric(@NonNull final CharSequence cs) {
|
|
298
|
+
final int sz = cs.length();
|
|
299
|
+
for (int i = 0; i < sz; i++) {
|
|
300
|
+
if (Character.isDigit(cs.charAt(i))) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
package/dist/docs.json
CHANGED
|
@@ -4947,6 +4947,22 @@
|
|
|
4947
4947
|
"complexTypes": [],
|
|
4948
4948
|
"type": "boolean | undefined"
|
|
4949
4949
|
},
|
|
4950
|
+
{
|
|
4951
|
+
"name": "allowHttpsToHttpRedirect",
|
|
4952
|
+
"tags": [
|
|
4953
|
+
{
|
|
4954
|
+
"text": "false",
|
|
4955
|
+
"name": "default"
|
|
4956
|
+
},
|
|
4957
|
+
{
|
|
4958
|
+
"text": "8.52.0",
|
|
4959
|
+
"name": "since"
|
|
4960
|
+
}
|
|
4961
|
+
],
|
|
4962
|
+
"docs": "Allow the plugin to follow redirects from HTTPS to plain HTTP for its own network requests\n(update checks, stats, channel calls, and bundle/manifest downloads).\n\nBlocked by default so a redirect can never downgrade updater traffic to an unencrypted connection.\nOnly enable this if your self-hosted update server or CDN must redirect to an HTTP URL.\nDirect HTTP URLs (for example `localApi` during development) are not affected.\n\nOnly available for Android and iOS.",
|
|
4963
|
+
"complexTypes": [],
|
|
4964
|
+
"type": "boolean | undefined"
|
|
4965
|
+
},
|
|
4950
4966
|
{
|
|
4951
4967
|
"name": "allowModifyAppId",
|
|
4952
4968
|
"tags": [
|
|
@@ -264,6 +264,20 @@ declare module '@capacitor/cli' {
|
|
|
264
264
|
* @since 5.4.0
|
|
265
265
|
*/
|
|
266
266
|
allowModifyUrl?: boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Allow the plugin to follow redirects from HTTPS to plain HTTP for its own network requests
|
|
269
|
+
* (update checks, stats, channel calls, and bundle/manifest downloads).
|
|
270
|
+
*
|
|
271
|
+
* Blocked by default so a redirect can never downgrade updater traffic to an unencrypted connection.
|
|
272
|
+
* Only enable this if your self-hosted update server or CDN must redirect to an HTTP URL.
|
|
273
|
+
* Direct HTTP URLs (for example `localApi` during development) are not affected.
|
|
274
|
+
*
|
|
275
|
+
* Only available for Android and iOS.
|
|
276
|
+
*
|
|
277
|
+
* @default false
|
|
278
|
+
* @since 8.52.0
|
|
279
|
+
*/
|
|
280
|
+
allowHttpsToHttpRedirect?: boolean;
|
|
267
281
|
/**
|
|
268
282
|
* Allow the plugin to modify the appId dynamically from the JavaScript side.
|
|
269
283
|
*
|