@capgo/capacitor-webview-version-checker 8.0.0 → 8.2.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/README.md +88 -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 +533 -34
- package/dist/docs.json +28 -0
- package/dist/esm/definitions.d.ts +104 -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,19 @@ 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
|
+
|
|
78
|
+
private boolean isPromptShowing;
|
|
79
|
+
|
|
80
|
+
@Nullable
|
|
81
|
+
private AlertDialog activePromptDialog;
|
|
82
|
+
|
|
69
83
|
@Override
|
|
70
84
|
public void load() {
|
|
71
85
|
super.load();
|
|
@@ -123,7 +137,7 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
123
137
|
@PluginMethod
|
|
124
138
|
public void showUpdatePrompt(PluginCall call) {
|
|
125
139
|
PromptOptions promptOptions = PromptOptions.fromCall(call, runtimeOptions);
|
|
126
|
-
showUpdatePrompt(promptOptions, true, (shown, opened) -> {
|
|
140
|
+
showUpdatePrompt(promptOptions, true, runtimeOptions.autoPromptDismissible, (shown, opened) -> {
|
|
127
141
|
JSObject payload = new JSObject();
|
|
128
142
|
payload.put("shown", shown);
|
|
129
143
|
payload.put("openedUpdatePage", opened);
|
|
@@ -157,6 +171,7 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
157
171
|
lastStatus = status;
|
|
158
172
|
|
|
159
173
|
notifyStatus(status);
|
|
174
|
+
dismissAutoPromptIfUpToDate(status);
|
|
160
175
|
maybeAutoPrompt(status, options, forcePrompt);
|
|
161
176
|
|
|
162
177
|
if (call != null) {
|
|
@@ -167,7 +182,6 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
167
182
|
|
|
168
183
|
private JSObject evaluateStatus(String source, RuntimeOptions options) {
|
|
169
184
|
CurrentWebViewInfo current = resolveCurrentWebViewInfo();
|
|
170
|
-
LatestVersionResolution latestResolution = resolveLatestVersion(options);
|
|
171
185
|
|
|
172
186
|
JSObject status = new JSObject();
|
|
173
187
|
status.put("platform", "android");
|
|
@@ -181,15 +195,6 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
181
195
|
status.put("currentMajorVersion", currentMajorVersion);
|
|
182
196
|
}
|
|
183
197
|
|
|
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
198
|
String updateUrl = resolveUpdateUrl(options.updateUrl, status);
|
|
194
199
|
if (!isBlank(updateUrl)) {
|
|
195
200
|
status.put("updateUrl", updateUrl);
|
|
@@ -202,6 +207,55 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
202
207
|
return status;
|
|
203
208
|
}
|
|
204
209
|
|
|
210
|
+
String deviceShareErrorMessage = null;
|
|
211
|
+
if (options.minimumDeviceSharePercent != null) {
|
|
212
|
+
status.put("minimumDeviceSharePercent", options.minimumDeviceSharePercent);
|
|
213
|
+
if (currentMajorVersion == null) {
|
|
214
|
+
deviceShareErrorMessage =
|
|
215
|
+
"Could not evaluate device-share threshold because the installed WebView major version could not be parsed.";
|
|
216
|
+
} else {
|
|
217
|
+
VersionShareResolution versionShareResolution = resolveVersionShare(options);
|
|
218
|
+
if (!isBlank(versionShareResolution.source)) {
|
|
219
|
+
status.put("versionShareSource", versionShareResolution.source);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
Double currentVersionSharePercent = readShareForMajor(versionShareResolution.shareByMajor, currentMajorVersion);
|
|
223
|
+
if (currentVersionSharePercent != null) {
|
|
224
|
+
status.put("currentVersionSharePercent", currentVersionSharePercent);
|
|
225
|
+
boolean isLatest = currentVersionSharePercent >= options.minimumDeviceSharePercent;
|
|
226
|
+
status.put("state", isLatest ? "latest" : "outdated");
|
|
227
|
+
status.put("isLatest", isLatest);
|
|
228
|
+
status.put(
|
|
229
|
+
"reason",
|
|
230
|
+
isLatest
|
|
231
|
+
? "Installed WebView major version satisfies the configured minimum device-share threshold."
|
|
232
|
+
: "Installed WebView major version is below the configured minimum device-share threshold."
|
|
233
|
+
);
|
|
234
|
+
return status;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!isBlank(versionShareResolution.errorMessage)) {
|
|
238
|
+
deviceShareErrorMessage = versionShareResolution.errorMessage;
|
|
239
|
+
} else {
|
|
240
|
+
deviceShareErrorMessage =
|
|
241
|
+
"No version-share data was found for installed WebView major version " + currentMajorVersion + ".";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (!isBlank(deviceShareErrorMessage)) {
|
|
245
|
+
status.put("deviceShareError", deviceShareErrorMessage);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
LatestVersionResolution latestResolution = resolveLatestVersion(options);
|
|
250
|
+
String latestVersion = latestResolution.version;
|
|
251
|
+
if (!isBlank(latestVersion)) {
|
|
252
|
+
status.put("latestVersion", latestVersion);
|
|
253
|
+
Integer latestMajorVersion = versionChecker.parseMajorVersion(latestVersion);
|
|
254
|
+
if (latestMajorVersion != null) {
|
|
255
|
+
status.put("latestMajorVersion", latestMajorVersion);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
205
259
|
if (!isBlank(latestVersion)) {
|
|
206
260
|
int cmp = versionChecker.compareVersions(current.versionName, latestVersion);
|
|
207
261
|
boolean isLatest = cmp >= 0;
|
|
@@ -234,8 +288,13 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
234
288
|
status.put("isLatest", false);
|
|
235
289
|
if (!isBlank(latestResolution.errorMessage)) {
|
|
236
290
|
status.put("reason", latestResolution.errorMessage);
|
|
291
|
+
} else if (!isBlank(deviceShareErrorMessage)) {
|
|
292
|
+
status.put("reason", deviceShareErrorMessage);
|
|
237
293
|
} else {
|
|
238
|
-
status.put(
|
|
294
|
+
status.put(
|
|
295
|
+
"reason",
|
|
296
|
+
"Unable to resolve compatibility status. Configure latestVersion, minimumMajorVersion, or minimumDeviceSharePercent."
|
|
297
|
+
);
|
|
239
298
|
}
|
|
240
299
|
|
|
241
300
|
return status;
|
|
@@ -261,10 +320,15 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
261
320
|
}
|
|
262
321
|
|
|
263
322
|
PromptOptions promptOptions = PromptOptions.fromRuntime(options);
|
|
264
|
-
showUpdatePrompt(promptOptions, forcePrompt, null);
|
|
323
|
+
showUpdatePrompt(promptOptions, forcePrompt, options.autoPromptDismissible, null);
|
|
265
324
|
}
|
|
266
325
|
|
|
267
|
-
private void showUpdatePrompt(
|
|
326
|
+
private void showUpdatePrompt(
|
|
327
|
+
PromptOptions promptOptions,
|
|
328
|
+
boolean force,
|
|
329
|
+
boolean dismissible,
|
|
330
|
+
@Nullable PromptResultCallback callback
|
|
331
|
+
) {
|
|
268
332
|
Activity activity = getActivity();
|
|
269
333
|
if (activity == null || activity.isFinishing()) {
|
|
270
334
|
if (callback != null) {
|
|
@@ -282,15 +346,24 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
282
346
|
}
|
|
283
347
|
|
|
284
348
|
String fingerprint = buildPromptFingerprint(lastStatus, updateUrl);
|
|
285
|
-
if (!force && !isBlank(fingerprint) && fingerprint.equals(lastPromptFingerprint)) {
|
|
349
|
+
if (dismissible && !force && !isBlank(fingerprint) && fingerprint.equals(lastPromptFingerprint)) {
|
|
350
|
+
if (callback != null) {
|
|
351
|
+
callback.onResult(false, false);
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (isPromptShowing) {
|
|
286
357
|
if (callback != null) {
|
|
287
358
|
callback.onResult(false, false);
|
|
288
359
|
}
|
|
289
360
|
return;
|
|
290
361
|
}
|
|
291
362
|
|
|
292
|
-
if (!isBlank(fingerprint)) {
|
|
363
|
+
if (dismissible && !isBlank(fingerprint)) {
|
|
293
364
|
lastPromptFingerprint = fingerprint;
|
|
365
|
+
} else {
|
|
366
|
+
lastPromptFingerprint = null;
|
|
294
367
|
}
|
|
295
368
|
|
|
296
369
|
String title = !isBlank(promptOptions.title) ? promptOptions.title : DEFAULT_PROMPT_TITLE;
|
|
@@ -300,7 +373,7 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
300
373
|
|
|
301
374
|
bridge.executeOnMainThread(() -> {
|
|
302
375
|
final boolean[] resolved = { false };
|
|
303
|
-
AlertDialog
|
|
376
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(activity)
|
|
304
377
|
.setTitle(title)
|
|
305
378
|
.setMessage(message)
|
|
306
379
|
.setPositiveButton(updateButtonText, (dialogInterface, which) -> {
|
|
@@ -309,21 +382,64 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
309
382
|
resolved[0] = true;
|
|
310
383
|
callback.onResult(true, opened);
|
|
311
384
|
}
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
if (dismissible) {
|
|
388
|
+
builder
|
|
389
|
+
.setNegativeButton(cancelButtonText, (dialogInterface, which) -> {
|
|
390
|
+
if (callback != null && !resolved[0]) {
|
|
391
|
+
resolved[0] = true;
|
|
392
|
+
callback.onResult(true, false);
|
|
393
|
+
}
|
|
394
|
+
})
|
|
395
|
+
.setOnCancelListener((dialogInterface) -> {
|
|
396
|
+
if (callback != null && !resolved[0]) {
|
|
397
|
+
resolved[0] = true;
|
|
398
|
+
callback.onResult(true, false);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
AlertDialog dialog = builder.setCancelable(dismissible).create();
|
|
404
|
+
isPromptShowing = true;
|
|
405
|
+
activePromptDialog = dialog;
|
|
406
|
+
dialog.setOnDismissListener((dialogInterface) -> {
|
|
407
|
+
isPromptShowing = false;
|
|
408
|
+
if (activePromptDialog == dialog) {
|
|
409
|
+
activePromptDialog = null;
|
|
410
|
+
}
|
|
411
|
+
});
|
|
326
412
|
dialog.show();
|
|
413
|
+
if (!dismissible) {
|
|
414
|
+
dialog
|
|
415
|
+
.getButton(AlertDialog.BUTTON_POSITIVE)
|
|
416
|
+
.setOnClickListener((view) -> {
|
|
417
|
+
boolean opened = openUpdateUrl(updateUrl);
|
|
418
|
+
if (callback != null && !resolved[0]) {
|
|
419
|
+
resolved[0] = true;
|
|
420
|
+
callback.onResult(true, opened);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private void dismissAutoPromptIfUpToDate(JSObject status) {
|
|
428
|
+
if (!"latest".equals(status.optString("state", "unknown"))) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
AlertDialog dialog = activePromptDialog;
|
|
432
|
+
if (dialog == null) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
bridge.executeOnMainThread(() -> {
|
|
437
|
+
if (dialog.isShowing()) {
|
|
438
|
+
dialog.dismiss();
|
|
439
|
+
}
|
|
440
|
+
if (activePromptDialog == dialog) {
|
|
441
|
+
activePromptDialog = null;
|
|
442
|
+
}
|
|
327
443
|
});
|
|
328
444
|
}
|
|
329
445
|
|
|
@@ -392,6 +508,82 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
392
508
|
}
|
|
393
509
|
}
|
|
394
510
|
|
|
511
|
+
private VersionShareResolution resolveVersionShare(RuntimeOptions options) {
|
|
512
|
+
if (options.versionShareByMajor != null && options.versionShareByMajor.length() > 0) {
|
|
513
|
+
return new VersionShareResolution(copyJsonObject(options.versionShareByMajor), "versionShareByMajor", null);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (!isBlank(options.versionShareApiUrl)) {
|
|
517
|
+
String apiUrl = options.versionShareApiUrl;
|
|
518
|
+
|
|
519
|
+
if (
|
|
520
|
+
cachedVersionShareByMajor != null &&
|
|
521
|
+
!isBlank(cachedVersionShareApiUrl) &&
|
|
522
|
+
apiUrl.equals(cachedVersionShareApiUrl) &&
|
|
523
|
+
(System.currentTimeMillis() - cachedVersionShareTimestamp) < LATEST_VERSION_CACHE_TTL_MS
|
|
524
|
+
) {
|
|
525
|
+
return new VersionShareResolution(copyJsonObject(cachedVersionShareByMajor), "versionShareApiUrl", null);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
String payload = readUrl(apiUrl);
|
|
530
|
+
JSONObject shareByMajor = extractVersionShareByMajor(payload);
|
|
531
|
+
if (shareByMajor == null || shareByMajor.length() == 0) {
|
|
532
|
+
return new VersionShareResolution(null, "versionShareApiUrl", "Version-share API returned no compatible dataset.");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
cachedVersionShareByMajor = copyJsonObject(shareByMajor);
|
|
536
|
+
cachedVersionShareApiUrl = apiUrl;
|
|
537
|
+
cachedVersionShareTimestamp = System.currentTimeMillis();
|
|
538
|
+
|
|
539
|
+
return new VersionShareResolution(shareByMajor, "versionShareApiUrl", null);
|
|
540
|
+
} catch (Exception error) {
|
|
541
|
+
return new VersionShareResolution(
|
|
542
|
+
null,
|
|
543
|
+
"versionShareApiUrl",
|
|
544
|
+
"Could not resolve version-share dataset: " + error.getMessage()
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
JSONObject defaultDataset = GeneratedVersionShareData.getDefaultVersionShareByMajor();
|
|
550
|
+
if (defaultDataset != null && defaultDataset.length() > 0) {
|
|
551
|
+
return new VersionShareResolution(defaultDataset, GeneratedVersionShareData.getDataSource(), null);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return new VersionShareResolution(
|
|
555
|
+
null,
|
|
556
|
+
GeneratedVersionShareData.getDataSource(),
|
|
557
|
+
"Built-in version-share dataset is unavailable."
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
@Nullable
|
|
562
|
+
private Double readShareForMajor(@Nullable JSONObject shareByMajor, int majorVersion) {
|
|
563
|
+
if (shareByMajor == null) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
String directKey = String.valueOf(majorVersion);
|
|
568
|
+
if (shareByMajor.has(directKey) && !shareByMajor.isNull(directKey)) {
|
|
569
|
+
return parsePercentFromAny(shareByMajor.opt(directKey));
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
Iterator<String> keys = shareByMajor.keys();
|
|
573
|
+
while (keys.hasNext()) {
|
|
574
|
+
String key = keys.next();
|
|
575
|
+
Integer mappedMajor = parseMajorFromShareKey(key);
|
|
576
|
+
if (mappedMajor != null && mappedMajor == majorVersion) {
|
|
577
|
+
Double share = parsePercentFromAny(shareByMajor.opt(key));
|
|
578
|
+
if (share != null) {
|
|
579
|
+
return share;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
|
|
395
587
|
private String readUrl(String urlValue) throws IOException {
|
|
396
588
|
HttpURLConnection connection = (HttpURLConnection) new URL(urlValue).openConnection();
|
|
397
589
|
connection.setRequestMethod("GET");
|
|
@@ -417,6 +609,223 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
417
609
|
}
|
|
418
610
|
}
|
|
419
611
|
|
|
612
|
+
@Nullable
|
|
613
|
+
private JSONObject extractVersionShareByMajor(String payload) throws Exception {
|
|
614
|
+
JSONObject json = new JSONObject(payload);
|
|
615
|
+
return extractVersionShareByMajor(json);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
@Nullable
|
|
619
|
+
private JSONObject extractVersionShareByMajor(JSONObject json) {
|
|
620
|
+
JSONObject out = new JSONObject();
|
|
621
|
+
|
|
622
|
+
JSONObject explicitMap = json.optJSONObject("versionShareByMajor");
|
|
623
|
+
if (explicitMap != null) {
|
|
624
|
+
mergeVersionShareMap(out, explicitMap);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (out.length() == 0) {
|
|
628
|
+
JSONObject secondaryMap = json.optJSONObject("shareByMajor");
|
|
629
|
+
if (secondaryMap != null) {
|
|
630
|
+
mergeVersionShareMap(out, secondaryMap);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (out.length() == 0) {
|
|
635
|
+
JSONArray versionsArray = json.optJSONArray("versions");
|
|
636
|
+
if (versionsArray != null) {
|
|
637
|
+
mergeVersionShareArray(out, versionsArray);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (out.length() == 0) {
|
|
642
|
+
JSONArray dataArray = json.optJSONArray("data");
|
|
643
|
+
if (dataArray != null) {
|
|
644
|
+
mergeVersionShareArray(out, dataArray);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (out.length() == 0) {
|
|
649
|
+
JSONObject caniuseShareByMajor = extractCaniuseVersionShareByMajor(json);
|
|
650
|
+
if (caniuseShareByMajor != null) {
|
|
651
|
+
mergeVersionShareMap(out, caniuseShareByMajor);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (out.length() == 0) {
|
|
656
|
+
mergeVersionShareMap(out, json);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (out.length() == 0) {
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
return out;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
@Nullable
|
|
666
|
+
private JSONObject extractCaniuseVersionShareByMajor(JSONObject json) {
|
|
667
|
+
JSONObject agents = json.optJSONObject("agents");
|
|
668
|
+
if (agents == null) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
JSONObject out = new JSONObject();
|
|
673
|
+
|
|
674
|
+
// Prefer Android-specific buckets first.
|
|
675
|
+
mergeVersionShareMap(out, getUsageGlobalMap(agents, "and_chr"));
|
|
676
|
+
mergeVersionShareMap(out, getUsageGlobalMap(agents, "android"));
|
|
677
|
+
|
|
678
|
+
// If Android-specific buckets are sparse, complete with Chrome global data.
|
|
679
|
+
if (out.length() < 5) {
|
|
680
|
+
mergeVersionShareMap(out, getUsageGlobalMap(agents, "chrome"));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return out.length() > 0 ? out : null;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
@Nullable
|
|
687
|
+
private JSONObject getUsageGlobalMap(JSONObject agents, String agentKey) {
|
|
688
|
+
JSONObject agent = agents.optJSONObject(agentKey);
|
|
689
|
+
if (agent == null) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
return agent.optJSONObject("usage_global");
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
private void mergeVersionShareMap(JSONObject target, JSONObject source) {
|
|
696
|
+
if (source == null) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
Iterator<String> keys = source.keys();
|
|
700
|
+
while (keys.hasNext()) {
|
|
701
|
+
String key = keys.next();
|
|
702
|
+
Integer majorVersion = parseMajorFromShareKey(key);
|
|
703
|
+
Double sharePercent = parsePercentFromAny(source.opt(key));
|
|
704
|
+
putShareEntry(target, majorVersion, sharePercent);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
private void mergeVersionShareArray(JSONObject target, JSONArray source) {
|
|
709
|
+
for (int index = 0; index < source.length(); index++) {
|
|
710
|
+
JSONObject item = source.optJSONObject(index);
|
|
711
|
+
if (item == null) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
Integer majorVersion = parseMajorFromAny(item.opt("major"));
|
|
716
|
+
if (majorVersion == null) {
|
|
717
|
+
majorVersion = parseMajorFromAny(item.opt("version"));
|
|
718
|
+
}
|
|
719
|
+
if (majorVersion == null) {
|
|
720
|
+
majorVersion = parseMajorFromAny(item.opt("name"));
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
Double sharePercent = parsePercentFromAny(item.opt("share"));
|
|
724
|
+
if (sharePercent == null) {
|
|
725
|
+
sharePercent = parsePercentFromAny(item.opt("sharePercent"));
|
|
726
|
+
}
|
|
727
|
+
if (sharePercent == null) {
|
|
728
|
+
sharePercent = parsePercentFromAny(item.opt("percent"));
|
|
729
|
+
}
|
|
730
|
+
if (sharePercent == null) {
|
|
731
|
+
sharePercent = parsePercentFromAny(item.opt("usage"));
|
|
732
|
+
}
|
|
733
|
+
if (sharePercent == null) {
|
|
734
|
+
sharePercent = parsePercentFromAny(item.opt("value"));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
putShareEntry(target, majorVersion, sharePercent);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
private void putShareEntry(JSONObject target, @Nullable Integer majorVersion, @Nullable Double sharePercent) {
|
|
742
|
+
if (majorVersion == null || sharePercent == null) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
target.put(String.valueOf(majorVersion), sharePercent);
|
|
747
|
+
} catch (Exception ignored) {
|
|
748
|
+
// Ignore invalid JSON insertions for malformed input.
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
@Nullable
|
|
753
|
+
private Integer parseMajorFromShareKey(@Nullable String key) {
|
|
754
|
+
if (isBlank(key)) {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
String normalized = key.trim();
|
|
759
|
+
char first = normalized.charAt(0);
|
|
760
|
+
if (Character.isDigit(first)) {
|
|
761
|
+
return versionChecker.parseMajorVersion(normalized);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if ((first == 'v' || first == 'V') && normalized.length() > 1 && Character.isDigit(normalized.charAt(1))) {
|
|
765
|
+
return versionChecker.parseMajorVersion(normalized.substring(1));
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
@Nullable
|
|
772
|
+
private Integer parseMajorFromAny(@Nullable Object value) {
|
|
773
|
+
if (value == null) {
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (value instanceof Number) {
|
|
778
|
+
int major = ((Number) value).intValue();
|
|
779
|
+
return major >= 0 ? major : null;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
if (value instanceof String) {
|
|
783
|
+
return versionChecker.parseMajorVersion((String) value);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
@Nullable
|
|
790
|
+
private Double parsePercentFromAny(@Nullable Object value) {
|
|
791
|
+
if (value == null) {
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
Double percent = null;
|
|
796
|
+
if (value instanceof Number) {
|
|
797
|
+
percent = ((Number) value).doubleValue();
|
|
798
|
+
} else if (value instanceof String) {
|
|
799
|
+
try {
|
|
800
|
+
percent = Double.parseDouble(((String) value).trim());
|
|
801
|
+
} catch (NumberFormatException ignored) {
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return normalizePercentValue(percent);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
@Nullable
|
|
810
|
+
private Double normalizePercentValue(@Nullable Double value) {
|
|
811
|
+
if (value == null || value.isNaN() || value.isInfinite()) {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
return Math.max(0.0, Math.min(100.0, value));
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
@Nullable
|
|
818
|
+
private JSONObject copyJsonObject(@Nullable JSONObject value) {
|
|
819
|
+
if (value == null) {
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
try {
|
|
823
|
+
return new JSONObject(value.toString());
|
|
824
|
+
} catch (Exception error) {
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
420
829
|
@Nullable
|
|
421
830
|
private String extractLatestVersion(String payload) throws Exception {
|
|
422
831
|
JSONObject json = new JSONObject(payload);
|
|
@@ -584,6 +993,24 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
584
993
|
}
|
|
585
994
|
}
|
|
586
995
|
|
|
996
|
+
private static final class VersionShareResolution {
|
|
997
|
+
|
|
998
|
+
@Nullable
|
|
999
|
+
private final JSONObject shareByMajor;
|
|
1000
|
+
|
|
1001
|
+
@Nullable
|
|
1002
|
+
private final String source;
|
|
1003
|
+
|
|
1004
|
+
@Nullable
|
|
1005
|
+
private final String errorMessage;
|
|
1006
|
+
|
|
1007
|
+
private VersionShareResolution(@Nullable JSONObject shareByMajor, @Nullable String source, @Nullable String errorMessage) {
|
|
1008
|
+
this.shareByMajor = shareByMajor;
|
|
1009
|
+
this.source = source;
|
|
1010
|
+
this.errorMessage = errorMessage;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
587
1014
|
private static final class PromptOptions {
|
|
588
1015
|
|
|
589
1016
|
@Nullable
|
|
@@ -636,6 +1063,7 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
636
1063
|
private boolean autoCheckOnResume = true;
|
|
637
1064
|
private boolean autoPromptOnOutdated;
|
|
638
1065
|
private boolean showPromptOnOutdated;
|
|
1066
|
+
private boolean autoPromptDismissible = true;
|
|
639
1067
|
|
|
640
1068
|
@Nullable
|
|
641
1069
|
private String latestVersion;
|
|
@@ -643,6 +1071,15 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
643
1071
|
@Nullable
|
|
644
1072
|
private Integer minimumMajorVersion;
|
|
645
1073
|
|
|
1074
|
+
@Nullable
|
|
1075
|
+
private Double minimumDeviceSharePercent = 3.0;
|
|
1076
|
+
|
|
1077
|
+
@Nullable
|
|
1078
|
+
private JSONObject versionShareByMajor;
|
|
1079
|
+
|
|
1080
|
+
@Nullable
|
|
1081
|
+
private String versionShareApiUrl;
|
|
1082
|
+
|
|
646
1083
|
@Nullable
|
|
647
1084
|
private String latestVersionApiUrl;
|
|
648
1085
|
|
|
@@ -666,11 +1103,15 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
666
1103
|
out.autoCheckOnLoad = config.getBoolean("autoCheckOnLoad", true);
|
|
667
1104
|
out.autoCheckOnResume = config.getBoolean("autoCheckOnResume", true);
|
|
668
1105
|
out.autoPromptOnOutdated = config.getBoolean("autoPromptOnOutdated", false);
|
|
1106
|
+
out.autoPromptDismissible = config.getBoolean("autoPromptDismissible", true);
|
|
669
1107
|
out.latestVersion = emptyToNull(config.getString("latestVersion"));
|
|
670
|
-
Integer minimum = config.getConfigJSON().has("minimumMajorVersion")
|
|
671
|
-
? config.getInt("minimumMajorVersion", 0)
|
|
672
|
-
: null;
|
|
1108
|
+
Integer minimum = config.getConfigJSON().has("minimumMajorVersion") ? config.getInt("minimumMajorVersion", 0) : null;
|
|
673
1109
|
out.minimumMajorVersion = minimum;
|
|
1110
|
+
if (config.getConfigJSON().has("minimumDeviceSharePercent")) {
|
|
1111
|
+
out.minimumDeviceSharePercent = normalizePercent(readOptionalDouble(config.getConfigJSON(), "minimumDeviceSharePercent"));
|
|
1112
|
+
}
|
|
1113
|
+
out.versionShareByMajor = copyJsonObject(config.getObject("versionShareByMajor"));
|
|
1114
|
+
out.versionShareApiUrl = emptyToNull(config.getString("versionShareApiUrl"));
|
|
674
1115
|
out.latestVersionApiUrl = emptyToNull(config.getString("latestVersionApiUrl"));
|
|
675
1116
|
out.updateUrl = emptyToNull(config.getString("updateUrl"));
|
|
676
1117
|
out.promptTitle = emptyToNull(config.getString("promptTitle"));
|
|
@@ -692,6 +1133,9 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
692
1133
|
if (call.hasOption("autoPromptOnOutdated")) {
|
|
693
1134
|
out.autoPromptOnOutdated = call.getBoolean("autoPromptOnOutdated", out.autoPromptOnOutdated);
|
|
694
1135
|
}
|
|
1136
|
+
if (call.hasOption("autoPromptDismissible")) {
|
|
1137
|
+
out.autoPromptDismissible = call.getBoolean("autoPromptDismissible", out.autoPromptDismissible);
|
|
1138
|
+
}
|
|
695
1139
|
if (call.hasOption("showPromptOnOutdated")) {
|
|
696
1140
|
out.showPromptOnOutdated = call.getBoolean("showPromptOnOutdated", out.showPromptOnOutdated);
|
|
697
1141
|
}
|
|
@@ -701,6 +1145,15 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
701
1145
|
if (call.hasOption("minimumMajorVersion")) {
|
|
702
1146
|
out.minimumMajorVersion = call.getInt("minimumMajorVersion");
|
|
703
1147
|
}
|
|
1148
|
+
if (call.hasOption("minimumDeviceSharePercent")) {
|
|
1149
|
+
out.minimumDeviceSharePercent = normalizePercent(call.getDouble("minimumDeviceSharePercent"));
|
|
1150
|
+
}
|
|
1151
|
+
if (call.hasOption("versionShareByMajor")) {
|
|
1152
|
+
out.versionShareByMajor = copyJsonObject(call.getObject("versionShareByMajor"));
|
|
1153
|
+
}
|
|
1154
|
+
if (call.hasOption("versionShareApiUrl")) {
|
|
1155
|
+
out.versionShareApiUrl = emptyToNull(call.getString("versionShareApiUrl"));
|
|
1156
|
+
}
|
|
704
1157
|
if (call.hasOption("latestVersionApiUrl")) {
|
|
705
1158
|
out.latestVersionApiUrl = emptyToNull(call.getString("latestVersionApiUrl"));
|
|
706
1159
|
}
|
|
@@ -728,9 +1181,13 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
728
1181
|
out.autoCheckOnLoad = autoCheckOnLoad;
|
|
729
1182
|
out.autoCheckOnResume = autoCheckOnResume;
|
|
730
1183
|
out.autoPromptOnOutdated = autoPromptOnOutdated;
|
|
1184
|
+
out.autoPromptDismissible = autoPromptDismissible;
|
|
731
1185
|
out.showPromptOnOutdated = showPromptOnOutdated;
|
|
732
1186
|
out.latestVersion = latestVersion;
|
|
733
1187
|
out.minimumMajorVersion = minimumMajorVersion;
|
|
1188
|
+
out.minimumDeviceSharePercent = minimumDeviceSharePercent;
|
|
1189
|
+
out.versionShareByMajor = copyJsonObject(versionShareByMajor);
|
|
1190
|
+
out.versionShareApiUrl = versionShareApiUrl;
|
|
734
1191
|
out.latestVersionApiUrl = latestVersionApiUrl;
|
|
735
1192
|
out.updateUrl = updateUrl;
|
|
736
1193
|
out.promptTitle = promptTitle;
|
|
@@ -747,5 +1204,47 @@ public class WebviewVersionCheckerPlugin extends Plugin {
|
|
|
747
1204
|
}
|
|
748
1205
|
return value;
|
|
749
1206
|
}
|
|
1207
|
+
|
|
1208
|
+
@Nullable
|
|
1209
|
+
private static Double readOptionalDouble(JSONObject source, String key) {
|
|
1210
|
+
if (!source.has(key) || source.isNull(key)) {
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
Object value = source.opt(key);
|
|
1215
|
+
if (value instanceof Number) {
|
|
1216
|
+
return ((Number) value).doubleValue();
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (value instanceof String) {
|
|
1220
|
+
try {
|
|
1221
|
+
return Double.parseDouble(((String) value).trim());
|
|
1222
|
+
} catch (NumberFormatException ignored) {
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
@Nullable
|
|
1231
|
+
private static Double normalizePercent(@Nullable Double value) {
|
|
1232
|
+
if (value == null || value.isNaN() || value.isInfinite()) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
return Math.max(0.0, Math.min(100.0, value));
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
@Nullable
|
|
1239
|
+
private static JSONObject copyJsonObject(@Nullable JSONObject value) {
|
|
1240
|
+
if (value == null || value.length() == 0) {
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
1243
|
+
try {
|
|
1244
|
+
return new JSONObject(value.toString());
|
|
1245
|
+
} catch (Exception error) {
|
|
1246
|
+
return null;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
750
1249
|
}
|
|
751
1250
|
}
|