@capgo/capacitor-webview-version-checker 8.0.0 → 8.1.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/README.md +86 -17
- package/android/build.gradle +1 -1
- package/android/src/main/java/app/capgo/webviewversionchecker/GeneratedVersionShareData.java +28 -0
- package/android/src/main/java/app/capgo/webviewversionchecker/WebviewVersionCheckerPlugin.java +445 -15
- package/dist/docs.json +28 -0
- package/dist/esm/definitions.d.ts +95 -4
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/generated-version-share.d.ts +9 -0
- package/dist/esm/generated-version-share.js +106 -0
- package/dist/esm/generated-version-share.js.map +1 -0
- package/dist/esm/web.d.ts +3 -0
- package/dist/esm/web.js +86 -10
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +190 -10
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +190 -10
- package/dist/plugin.js.map +1 -1
- package/package.json +6 -3
package/android/src/main/java/app/capgo/webviewversionchecker/WebviewVersionCheckerPlugin.java
CHANGED
|
@@ -25,6 +25,7 @@ import java.net.URL;
|
|
|
25
25
|
import java.nio.charset.StandardCharsets;
|
|
26
26
|
import java.text.SimpleDateFormat;
|
|
27
27
|
import java.util.Date;
|
|
28
|
+
import java.util.Iterator;
|
|
28
29
|
import java.util.Locale;
|
|
29
30
|
import java.util.TimeZone;
|
|
30
31
|
import org.json.JSONArray;
|
|
@@ -66,6 +67,14 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
66
67
|
|
|
67
68
|
private long cachedLatestVersionTimestamp;
|
|
68
69
|
|
|
70
|
+
@Nullable
|
|
71
|
+
private JSONObject cachedVersionShareByMajor;
|
|
72
|
+
|
|
73
|
+
@Nullable
|
|
74
|
+
private String cachedVersionShareApiUrl;
|
|
75
|
+
|
|
76
|
+
private long cachedVersionShareTimestamp;
|
|
77
|
+
|
|
69
78
|
@Override
|
|
70
79
|
public void load() {
|
|
71
80
|
super.load();
|
|
@@ -167,7 +176,6 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
167
176
|
|
|
168
177
|
private JSObject evaluateStatus(String source, RuntimeOptions options) {
|
|
169
178
|
CurrentWebViewInfo current = resolveCurrentWebViewInfo();
|
|
170
|
-
LatestVersionResolution latestResolution = resolveLatestVersion(options);
|
|
171
179
|
|
|
172
180
|
JSObject status = new JSObject();
|
|
173
181
|
status.put("platform", "android");
|
|
@@ -181,15 +189,6 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
181
189
|
status.put("currentMajorVersion", currentMajorVersion);
|
|
182
190
|
}
|
|
183
191
|
|
|
184
|
-
String latestVersion = latestResolution.version;
|
|
185
|
-
if (!isBlank(latestVersion)) {
|
|
186
|
-
status.put("latestVersion", latestVersion);
|
|
187
|
-
Integer latestMajorVersion = versionChecker.parseMajorVersion(latestVersion);
|
|
188
|
-
if (latestMajorVersion != null) {
|
|
189
|
-
status.put("latestMajorVersion", latestMajorVersion);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
192
|
String updateUrl = resolveUpdateUrl(options.updateUrl, status);
|
|
194
193
|
if (!isBlank(updateUrl)) {
|
|
195
194
|
status.put("updateUrl", updateUrl);
|
|
@@ -202,6 +201,55 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
202
201
|
return status;
|
|
203
202
|
}
|
|
204
203
|
|
|
204
|
+
String deviceShareErrorMessage = null;
|
|
205
|
+
if (options.minimumDeviceSharePercent != null) {
|
|
206
|
+
status.put("minimumDeviceSharePercent", options.minimumDeviceSharePercent);
|
|
207
|
+
if (currentMajorVersion == null) {
|
|
208
|
+
deviceShareErrorMessage =
|
|
209
|
+
"Could not evaluate device-share threshold because the installed WebView major version could not be parsed.";
|
|
210
|
+
} else {
|
|
211
|
+
VersionShareResolution versionShareResolution = resolveVersionShare(options);
|
|
212
|
+
if (!isBlank(versionShareResolution.source)) {
|
|
213
|
+
status.put("versionShareSource", versionShareResolution.source);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Double currentVersionSharePercent = readShareForMajor(versionShareResolution.shareByMajor, currentMajorVersion);
|
|
217
|
+
if (currentVersionSharePercent != null) {
|
|
218
|
+
status.put("currentVersionSharePercent", currentVersionSharePercent);
|
|
219
|
+
boolean isLatest = currentVersionSharePercent >= options.minimumDeviceSharePercent;
|
|
220
|
+
status.put("state", isLatest ? "latest" : "outdated");
|
|
221
|
+
status.put("isLatest", isLatest);
|
|
222
|
+
status.put(
|
|
223
|
+
"reason",
|
|
224
|
+
isLatest
|
|
225
|
+
? "Installed WebView major version satisfies the configured minimum device-share threshold."
|
|
226
|
+
: "Installed WebView major version is below the configured minimum device-share threshold."
|
|
227
|
+
);
|
|
228
|
+
return status;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!isBlank(versionShareResolution.errorMessage)) {
|
|
232
|
+
deviceShareErrorMessage = versionShareResolution.errorMessage;
|
|
233
|
+
} else {
|
|
234
|
+
deviceShareErrorMessage =
|
|
235
|
+
"No version-share data was found for installed WebView major version " + currentMajorVersion + ".";
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!isBlank(deviceShareErrorMessage)) {
|
|
239
|
+
status.put("deviceShareError", deviceShareErrorMessage);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
LatestVersionResolution latestResolution = resolveLatestVersion(options);
|
|
244
|
+
String latestVersion = latestResolution.version;
|
|
245
|
+
if (!isBlank(latestVersion)) {
|
|
246
|
+
status.put("latestVersion", latestVersion);
|
|
247
|
+
Integer latestMajorVersion = versionChecker.parseMajorVersion(latestVersion);
|
|
248
|
+
if (latestMajorVersion != null) {
|
|
249
|
+
status.put("latestMajorVersion", latestMajorVersion);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
205
253
|
if (!isBlank(latestVersion)) {
|
|
206
254
|
int cmp = versionChecker.compareVersions(current.versionName, latestVersion);
|
|
207
255
|
boolean isLatest = cmp >= 0;
|
|
@@ -234,8 +282,13 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
234
282
|
status.put("isLatest", false);
|
|
235
283
|
if (!isBlank(latestResolution.errorMessage)) {
|
|
236
284
|
status.put("reason", latestResolution.errorMessage);
|
|
285
|
+
} else if (!isBlank(deviceShareErrorMessage)) {
|
|
286
|
+
status.put("reason", deviceShareErrorMessage);
|
|
237
287
|
} else {
|
|
238
|
-
status.put(
|
|
288
|
+
status.put(
|
|
289
|
+
"reason",
|
|
290
|
+
"Unable to resolve compatibility status. Configure latestVersion, minimumMajorVersion, or minimumDeviceSharePercent."
|
|
291
|
+
);
|
|
239
292
|
}
|
|
240
293
|
|
|
241
294
|
return status;
|
|
@@ -316,7 +369,7 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
316
369
|
callback.onResult(true, false);
|
|
317
370
|
}
|
|
318
371
|
})
|
|
319
|
-
.setOnCancelListener(dialogInterface -> {
|
|
372
|
+
.setOnCancelListener((dialogInterface) -> {
|
|
320
373
|
if (callback != null && !resolved[0]) {
|
|
321
374
|
resolved[0] = true;
|
|
322
375
|
callback.onResult(true, false);
|
|
@@ -392,6 +445,82 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
392
445
|
}
|
|
393
446
|
}
|
|
394
447
|
|
|
448
|
+
private VersionShareResolution resolveVersionShare(RuntimeOptions options) {
|
|
449
|
+
if (options.versionShareByMajor != null && options.versionShareByMajor.length() > 0) {
|
|
450
|
+
return new VersionShareResolution(copyJsonObject(options.versionShareByMajor), "versionShareByMajor", null);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!isBlank(options.versionShareApiUrl)) {
|
|
454
|
+
String apiUrl = options.versionShareApiUrl;
|
|
455
|
+
|
|
456
|
+
if (
|
|
457
|
+
cachedVersionShareByMajor != null &&
|
|
458
|
+
!isBlank(cachedVersionShareApiUrl) &&
|
|
459
|
+
apiUrl.equals(cachedVersionShareApiUrl) &&
|
|
460
|
+
(System.currentTimeMillis() - cachedVersionShareTimestamp) < LATEST_VERSION_CACHE_TTL_MS
|
|
461
|
+
) {
|
|
462
|
+
return new VersionShareResolution(copyJsonObject(cachedVersionShareByMajor), "versionShareApiUrl", null);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
String payload = readUrl(apiUrl);
|
|
467
|
+
JSONObject shareByMajor = extractVersionShareByMajor(payload);
|
|
468
|
+
if (shareByMajor == null || shareByMajor.length() == 0) {
|
|
469
|
+
return new VersionShareResolution(null, "versionShareApiUrl", "Version-share API returned no compatible dataset.");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
cachedVersionShareByMajor = copyJsonObject(shareByMajor);
|
|
473
|
+
cachedVersionShareApiUrl = apiUrl;
|
|
474
|
+
cachedVersionShareTimestamp = System.currentTimeMillis();
|
|
475
|
+
|
|
476
|
+
return new VersionShareResolution(shareByMajor, "versionShareApiUrl", null);
|
|
477
|
+
} catch (Exception error) {
|
|
478
|
+
return new VersionShareResolution(
|
|
479
|
+
null,
|
|
480
|
+
"versionShareApiUrl",
|
|
481
|
+
"Could not resolve version-share dataset: " + error.getMessage()
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
JSONObject defaultDataset = GeneratedVersionShareData.getDefaultVersionShareByMajor();
|
|
487
|
+
if (defaultDataset != null && defaultDataset.length() > 0) {
|
|
488
|
+
return new VersionShareResolution(defaultDataset, GeneratedVersionShareData.getDataSource(), null);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return new VersionShareResolution(
|
|
492
|
+
null,
|
|
493
|
+
GeneratedVersionShareData.getDataSource(),
|
|
494
|
+
"Built-in version-share dataset is unavailable."
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
@Nullable
|
|
499
|
+
private Double readShareForMajor(@Nullable JSONObject shareByMajor, int majorVersion) {
|
|
500
|
+
if (shareByMajor == null) {
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
String directKey = String.valueOf(majorVersion);
|
|
505
|
+
if (shareByMajor.has(directKey) && !shareByMajor.isNull(directKey)) {
|
|
506
|
+
return parsePercentFromAny(shareByMajor.opt(directKey));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
Iterator<String> keys = shareByMajor.keys();
|
|
510
|
+
while (keys.hasNext()) {
|
|
511
|
+
String key = keys.next();
|
|
512
|
+
Integer mappedMajor = parseMajorFromShareKey(key);
|
|
513
|
+
if (mappedMajor != null && mappedMajor == majorVersion) {
|
|
514
|
+
Double share = parsePercentFromAny(shareByMajor.opt(key));
|
|
515
|
+
if (share != null) {
|
|
516
|
+
return share;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
|
|
395
524
|
private String readUrl(String urlValue) throws IOException {
|
|
396
525
|
HttpURLConnection connection = (HttpURLConnection) new URL(urlValue).openConnection();
|
|
397
526
|
connection.setRequestMethod("GET");
|
|
@@ -417,6 +546,223 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
417
546
|
}
|
|
418
547
|
}
|
|
419
548
|
|
|
549
|
+
@Nullable
|
|
550
|
+
private JSONObject extractVersionShareByMajor(String payload) throws Exception {
|
|
551
|
+
JSONObject json = new JSONObject(payload);
|
|
552
|
+
return extractVersionShareByMajor(json);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
@Nullable
|
|
556
|
+
private JSONObject extractVersionShareByMajor(JSONObject json) {
|
|
557
|
+
JSONObject out = new JSONObject();
|
|
558
|
+
|
|
559
|
+
JSONObject explicitMap = json.optJSONObject("versionShareByMajor");
|
|
560
|
+
if (explicitMap != null) {
|
|
561
|
+
mergeVersionShareMap(out, explicitMap);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (out.length() == 0) {
|
|
565
|
+
JSONObject secondaryMap = json.optJSONObject("shareByMajor");
|
|
566
|
+
if (secondaryMap != null) {
|
|
567
|
+
mergeVersionShareMap(out, secondaryMap);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (out.length() == 0) {
|
|
572
|
+
JSONArray versionsArray = json.optJSONArray("versions");
|
|
573
|
+
if (versionsArray != null) {
|
|
574
|
+
mergeVersionShareArray(out, versionsArray);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (out.length() == 0) {
|
|
579
|
+
JSONArray dataArray = json.optJSONArray("data");
|
|
580
|
+
if (dataArray != null) {
|
|
581
|
+
mergeVersionShareArray(out, dataArray);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (out.length() == 0) {
|
|
586
|
+
JSONObject caniuseShareByMajor = extractCaniuseVersionShareByMajor(json);
|
|
587
|
+
if (caniuseShareByMajor != null) {
|
|
588
|
+
mergeVersionShareMap(out, caniuseShareByMajor);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (out.length() == 0) {
|
|
593
|
+
mergeVersionShareMap(out, json);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (out.length() == 0) {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
return out;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
@Nullable
|
|
603
|
+
private JSONObject extractCaniuseVersionShareByMajor(JSONObject json) {
|
|
604
|
+
JSONObject agents = json.optJSONObject("agents");
|
|
605
|
+
if (agents == null) {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
JSONObject out = new JSONObject();
|
|
610
|
+
|
|
611
|
+
// Prefer Android-specific buckets first.
|
|
612
|
+
mergeVersionShareMap(out, getUsageGlobalMap(agents, "and_chr"));
|
|
613
|
+
mergeVersionShareMap(out, getUsageGlobalMap(agents, "android"));
|
|
614
|
+
|
|
615
|
+
// If Android-specific buckets are sparse, complete with Chrome global data.
|
|
616
|
+
if (out.length() < 5) {
|
|
617
|
+
mergeVersionShareMap(out, getUsageGlobalMap(agents, "chrome"));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return out.length() > 0 ? out : null;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
@Nullable
|
|
624
|
+
private JSONObject getUsageGlobalMap(JSONObject agents, String agentKey) {
|
|
625
|
+
JSONObject agent = agents.optJSONObject(agentKey);
|
|
626
|
+
if (agent == null) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
return agent.optJSONObject("usage_global");
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
private void mergeVersionShareMap(JSONObject target, JSONObject source) {
|
|
633
|
+
if (source == null) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
Iterator<String> keys = source.keys();
|
|
637
|
+
while (keys.hasNext()) {
|
|
638
|
+
String key = keys.next();
|
|
639
|
+
Integer majorVersion = parseMajorFromShareKey(key);
|
|
640
|
+
Double sharePercent = parsePercentFromAny(source.opt(key));
|
|
641
|
+
putShareEntry(target, majorVersion, sharePercent);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private void mergeVersionShareArray(JSONObject target, JSONArray source) {
|
|
646
|
+
for (int index = 0; index < source.length(); index++) {
|
|
647
|
+
JSONObject item = source.optJSONObject(index);
|
|
648
|
+
if (item == null) {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
Integer majorVersion = parseMajorFromAny(item.opt("major"));
|
|
653
|
+
if (majorVersion == null) {
|
|
654
|
+
majorVersion = parseMajorFromAny(item.opt("version"));
|
|
655
|
+
}
|
|
656
|
+
if (majorVersion == null) {
|
|
657
|
+
majorVersion = parseMajorFromAny(item.opt("name"));
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
Double sharePercent = parsePercentFromAny(item.opt("share"));
|
|
661
|
+
if (sharePercent == null) {
|
|
662
|
+
sharePercent = parsePercentFromAny(item.opt("sharePercent"));
|
|
663
|
+
}
|
|
664
|
+
if (sharePercent == null) {
|
|
665
|
+
sharePercent = parsePercentFromAny(item.opt("percent"));
|
|
666
|
+
}
|
|
667
|
+
if (sharePercent == null) {
|
|
668
|
+
sharePercent = parsePercentFromAny(item.opt("usage"));
|
|
669
|
+
}
|
|
670
|
+
if (sharePercent == null) {
|
|
671
|
+
sharePercent = parsePercentFromAny(item.opt("value"));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
putShareEntry(target, majorVersion, sharePercent);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private void putShareEntry(JSONObject target, @Nullable Integer majorVersion, @Nullable Double sharePercent) {
|
|
679
|
+
if (majorVersion == null || sharePercent == null) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
target.put(String.valueOf(majorVersion), sharePercent);
|
|
684
|
+
} catch (Exception ignored) {
|
|
685
|
+
// Ignore invalid JSON insertions for malformed input.
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
@Nullable
|
|
690
|
+
private Integer parseMajorFromShareKey(@Nullable String key) {
|
|
691
|
+
if (isBlank(key)) {
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
String normalized = key.trim();
|
|
696
|
+
char first = normalized.charAt(0);
|
|
697
|
+
if (Character.isDigit(first)) {
|
|
698
|
+
return versionChecker.parseMajorVersion(normalized);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if ((first == 'v' || first == 'V') && normalized.length() > 1 && Character.isDigit(normalized.charAt(1))) {
|
|
702
|
+
return versionChecker.parseMajorVersion(normalized.substring(1));
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
@Nullable
|
|
709
|
+
private Integer parseMajorFromAny(@Nullable Object value) {
|
|
710
|
+
if (value == null) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (value instanceof Number) {
|
|
715
|
+
int major = ((Number) value).intValue();
|
|
716
|
+
return major >= 0 ? major : null;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (value instanceof String) {
|
|
720
|
+
return versionChecker.parseMajorVersion((String) value);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
@Nullable
|
|
727
|
+
private Double parsePercentFromAny(@Nullable Object value) {
|
|
728
|
+
if (value == null) {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
Double percent = null;
|
|
733
|
+
if (value instanceof Number) {
|
|
734
|
+
percent = ((Number) value).doubleValue();
|
|
735
|
+
} else if (value instanceof String) {
|
|
736
|
+
try {
|
|
737
|
+
percent = Double.parseDouble(((String) value).trim());
|
|
738
|
+
} catch (NumberFormatException ignored) {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return normalizePercentValue(percent);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
@Nullable
|
|
747
|
+
private Double normalizePercentValue(@Nullable Double value) {
|
|
748
|
+
if (value == null || value.isNaN() || value.isInfinite()) {
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
return Math.max(0.0, Math.min(100.0, value));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
@Nullable
|
|
755
|
+
private JSONObject copyJsonObject(@Nullable JSONObject value) {
|
|
756
|
+
if (value == null) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
return new JSONObject(value.toString());
|
|
761
|
+
} catch (Exception error) {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
420
766
|
@Nullable
|
|
421
767
|
private String extractLatestVersion(String payload) throws Exception {
|
|
422
768
|
JSONObject json = new JSONObject(payload);
|
|
@@ -584,6 +930,24 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
584
930
|
}
|
|
585
931
|
}
|
|
586
932
|
|
|
933
|
+
private static final class VersionShareResolution {
|
|
934
|
+
|
|
935
|
+
@Nullable
|
|
936
|
+
private final JSONObject shareByMajor;
|
|
937
|
+
|
|
938
|
+
@Nullable
|
|
939
|
+
private final String source;
|
|
940
|
+
|
|
941
|
+
@Nullable
|
|
942
|
+
private final String errorMessage;
|
|
943
|
+
|
|
944
|
+
private VersionShareResolution(@Nullable JSONObject shareByMajor, @Nullable String source, @Nullable String errorMessage) {
|
|
945
|
+
this.shareByMajor = shareByMajor;
|
|
946
|
+
this.source = source;
|
|
947
|
+
this.errorMessage = errorMessage;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
587
951
|
private static final class PromptOptions {
|
|
588
952
|
|
|
589
953
|
@Nullable
|
|
@@ -643,6 +1007,15 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
643
1007
|
@Nullable
|
|
644
1008
|
private Integer minimumMajorVersion;
|
|
645
1009
|
|
|
1010
|
+
@Nullable
|
|
1011
|
+
private Double minimumDeviceSharePercent = 3.0;
|
|
1012
|
+
|
|
1013
|
+
@Nullable
|
|
1014
|
+
private JSONObject versionShareByMajor;
|
|
1015
|
+
|
|
1016
|
+
@Nullable
|
|
1017
|
+
private String versionShareApiUrl;
|
|
1018
|
+
|
|
646
1019
|
@Nullable
|
|
647
1020
|
private String latestVersionApiUrl;
|
|
648
1021
|
|
|
@@ -667,10 +1040,13 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
667
1040
|
out.autoCheckOnResume = config.getBoolean("autoCheckOnResume", true);
|
|
668
1041
|
out.autoPromptOnOutdated = config.getBoolean("autoPromptOnOutdated", false);
|
|
669
1042
|
out.latestVersion = emptyToNull(config.getString("latestVersion"));
|
|
670
|
-
Integer minimum = config.getConfigJSON().has("minimumMajorVersion")
|
|
671
|
-
? config.getInt("minimumMajorVersion", 0)
|
|
672
|
-
: null;
|
|
1043
|
+
Integer minimum = config.getConfigJSON().has("minimumMajorVersion") ? config.getInt("minimumMajorVersion", 0) : null;
|
|
673
1044
|
out.minimumMajorVersion = minimum;
|
|
1045
|
+
if (config.getConfigJSON().has("minimumDeviceSharePercent")) {
|
|
1046
|
+
out.minimumDeviceSharePercent = normalizePercent(readOptionalDouble(config.getConfigJSON(), "minimumDeviceSharePercent"));
|
|
1047
|
+
}
|
|
1048
|
+
out.versionShareByMajor = copyJsonObject(config.getObject("versionShareByMajor"));
|
|
1049
|
+
out.versionShareApiUrl = emptyToNull(config.getString("versionShareApiUrl"));
|
|
674
1050
|
out.latestVersionApiUrl = emptyToNull(config.getString("latestVersionApiUrl"));
|
|
675
1051
|
out.updateUrl = emptyToNull(config.getString("updateUrl"));
|
|
676
1052
|
out.promptTitle = emptyToNull(config.getString("promptTitle"));
|
|
@@ -701,6 +1077,15 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
701
1077
|
if (call.hasOption("minimumMajorVersion")) {
|
|
702
1078
|
out.minimumMajorVersion = call.getInt("minimumMajorVersion");
|
|
703
1079
|
}
|
|
1080
|
+
if (call.hasOption("minimumDeviceSharePercent")) {
|
|
1081
|
+
out.minimumDeviceSharePercent = normalizePercent(call.getDouble("minimumDeviceSharePercent"));
|
|
1082
|
+
}
|
|
1083
|
+
if (call.hasOption("versionShareByMajor")) {
|
|
1084
|
+
out.versionShareByMajor = copyJsonObject(call.getObject("versionShareByMajor"));
|
|
1085
|
+
}
|
|
1086
|
+
if (call.hasOption("versionShareApiUrl")) {
|
|
1087
|
+
out.versionShareApiUrl = emptyToNull(call.getString("versionShareApiUrl"));
|
|
1088
|
+
}
|
|
704
1089
|
if (call.hasOption("latestVersionApiUrl")) {
|
|
705
1090
|
out.latestVersionApiUrl = emptyToNull(call.getString("latestVersionApiUrl"));
|
|
706
1091
|
}
|
|
@@ -731,6 +1116,9 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
731
1116
|
out.showPromptOnOutdated = showPromptOnOutdated;
|
|
732
1117
|
out.latestVersion = latestVersion;
|
|
733
1118
|
out.minimumMajorVersion = minimumMajorVersion;
|
|
1119
|
+
out.minimumDeviceSharePercent = minimumDeviceSharePercent;
|
|
1120
|
+
out.versionShareByMajor = copyJsonObject(versionShareByMajor);
|
|
1121
|
+
out.versionShareApiUrl = versionShareApiUrl;
|
|
734
1122
|
out.latestVersionApiUrl = latestVersionApiUrl;
|
|
735
1123
|
out.updateUrl = updateUrl;
|
|
736
1124
|
out.promptTitle = promptTitle;
|
|
@@ -747,5 +1135,47 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
747
1135
|
}
|
|
748
1136
|
return value;
|
|
749
1137
|
}
|
|
1138
|
+
|
|
1139
|
+
@Nullable
|
|
1140
|
+
private static Double readOptionalDouble(JSONObject source, String key) {
|
|
1141
|
+
if (!source.has(key) || source.isNull(key)) {
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
Object value = source.opt(key);
|
|
1146
|
+
if (value instanceof Number) {
|
|
1147
|
+
return ((Number) value).doubleValue();
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
if (value instanceof String) {
|
|
1151
|
+
try {
|
|
1152
|
+
return Double.parseDouble(((String) value).trim());
|
|
1153
|
+
} catch (NumberFormatException ignored) {
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
@Nullable
|
|
1162
|
+
private static Double normalizePercent(@Nullable Double value) {
|
|
1163
|
+
if (value == null || value.isNaN() || value.isInfinite()) {
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
return Math.max(0.0, Math.min(100.0, value));
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
@Nullable
|
|
1170
|
+
private static JSONObject copyJsonObject(@Nullable JSONObject value) {
|
|
1171
|
+
if (value == null || value.length() == 0) {
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
try {
|
|
1175
|
+
return new JSONObject(value.toString());
|
|
1176
|
+
} catch (Exception error) {
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
750
1180
|
}
|
|
751
1181
|
}
|
package/dist/docs.json
CHANGED
|
@@ -255,6 +255,34 @@
|
|
|
255
255
|
"complexTypes": [],
|
|
256
256
|
"type": "number | undefined"
|
|
257
257
|
},
|
|
258
|
+
{
|
|
259
|
+
"name": "currentVersionSharePercent",
|
|
260
|
+
"tags": [],
|
|
261
|
+
"docs": "Device-share percentage for the installed WebView major version.\n\nPresent only when a version-share dataset is available and includes the current major version.",
|
|
262
|
+
"complexTypes": [],
|
|
263
|
+
"type": "number | undefined"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"name": "minimumDeviceSharePercent",
|
|
267
|
+
"tags": [],
|
|
268
|
+
"docs": "Configured threshold used by compatibility-threshold mode.\n\nPresent only when `minimumDeviceSharePercent` was requested for that check.",
|
|
269
|
+
"complexTypes": [],
|
|
270
|
+
"type": "number | undefined"
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"name": "versionShareSource",
|
|
274
|
+
"tags": [],
|
|
275
|
+
"docs": "Source used for version-share data.\n\nValues:\n- `versionShareByMajor` (inline dataset)\n- `versionShareApiUrl` (remote dataset)\n- `generatedVersionShareByMajor` (built-in dataset generated at build time)",
|
|
276
|
+
"complexTypes": [],
|
|
277
|
+
"type": "string | undefined"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"name": "deviceShareError",
|
|
281
|
+
"tags": [],
|
|
282
|
+
"docs": "Diagnostic message for compatibility-threshold mode.\n\nPresent when `minimumDeviceSharePercent` was requested but the threshold check\ncould not be fully evaluated.",
|
|
283
|
+
"complexTypes": [],
|
|
284
|
+
"type": "string | undefined"
|
|
285
|
+
},
|
|
258
286
|
{
|
|
259
287
|
"name": "providerPackage",
|
|
260
288
|
"tags": [],
|