@capgo/capacitor-webview-version-checker 8.0.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.
@@ -0,0 +1,751 @@
1
+ package app.capgo.webviewversionchecker;
2
+
3
+ import android.app.Activity;
4
+ import android.content.ActivityNotFoundException;
5
+ import android.content.Intent;
6
+ import android.content.pm.PackageInfo;
7
+ import android.net.Uri;
8
+ import android.os.Build;
9
+ import android.webkit.WebView;
10
+ import androidx.annotation.Nullable;
11
+ import androidx.appcompat.app.AlertDialog;
12
+ import androidx.webkit.WebViewCompat;
13
+ import com.getcapacitor.JSObject;
14
+ import com.getcapacitor.Plugin;
15
+ import com.getcapacitor.PluginCall;
16
+ import com.getcapacitor.PluginConfig;
17
+ import com.getcapacitor.PluginMethod;
18
+ import com.getcapacitor.annotation.CapacitorPlugin;
19
+ import java.io.BufferedReader;
20
+ import java.io.IOException;
21
+ import java.io.InputStream;
22
+ import java.io.InputStreamReader;
23
+ import java.net.HttpURLConnection;
24
+ import java.net.URL;
25
+ import java.nio.charset.StandardCharsets;
26
+ import java.text.SimpleDateFormat;
27
+ import java.util.Date;
28
+ import java.util.Locale;
29
+ import java.util.TimeZone;
30
+ import org.json.JSONArray;
31
+ import org.json.JSONObject;
32
+
33
+ @CapacitorPlugin(name = "WebviewVersionChecker")
34
+ public class WebviewVersionCheckerPlugin extends Plugin {
35
+
36
+ private static final String DEFAULT_ANDROID_LATEST_VERSION_API_URL =
37
+ "https://versionhistory.googleapis.com/v1/chrome/platforms/android/channels/stable/versions?page_size=1";
38
+ private static final String DEFAULT_PROMPT_TITLE = "WebView update available";
39
+ private static final String DEFAULT_PROMPT_UPDATE_BUTTON = "Update";
40
+ private static final String DEFAULT_PROMPT_CANCEL_BUTTON = "Later";
41
+ private static final long LATEST_VERSION_CACHE_TTL_MS = 6L * 60L * 60L * 1000L;
42
+
43
+ private static final ThreadLocal<SimpleDateFormat> ISO_FORMAT = ThreadLocal.withInitial(() -> {
44
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
45
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
46
+ return sdf;
47
+ });
48
+
49
+ private final WebviewVersionChecker versionChecker = new WebviewVersionChecker();
50
+
51
+ private RuntimeOptions runtimeOptions = new RuntimeOptions();
52
+ private boolean monitoringEnabled;
53
+ private boolean checkOnResume = true;
54
+
55
+ @Nullable
56
+ private volatile JSObject lastStatus;
57
+
58
+ @Nullable
59
+ private String lastPromptFingerprint;
60
+
61
+ @Nullable
62
+ private String cachedLatestVersion;
63
+
64
+ @Nullable
65
+ private String cachedLatestVersionUrl;
66
+
67
+ private long cachedLatestVersionTimestamp;
68
+
69
+ @Override
70
+ public void load() {
71
+ super.load();
72
+ runtimeOptions = RuntimeOptions.fromConfig(getConfig());
73
+ monitoringEnabled = runtimeOptions.autoCheckOnResume;
74
+ checkOnResume = runtimeOptions.autoCheckOnResume;
75
+
76
+ if (runtimeOptions.autoCheckOnLoad) {
77
+ scheduleCheck("load", runtimeOptions, null, false);
78
+ }
79
+ }
80
+
81
+ @Override
82
+ protected void handleOnResume() {
83
+ super.handleOnResume();
84
+ if (!monitoringEnabled || !checkOnResume) {
85
+ return;
86
+ }
87
+ scheduleCheck("resume", runtimeOptions, null, false);
88
+ }
89
+
90
+ @PluginMethod
91
+ public void check(PluginCall call) {
92
+ RuntimeOptions merged = runtimeOptions.merge(call);
93
+ String source = call.getString("source", "manual");
94
+ scheduleCheck(source, merged, call, merged.showPromptOnOutdated);
95
+ }
96
+
97
+ @PluginMethod
98
+ public void startMonitoring(PluginCall call) {
99
+ runtimeOptions = runtimeOptions.merge(call);
100
+ monitoringEnabled = true;
101
+ checkOnResume = call.getBoolean("checkOnResume", runtimeOptions.autoCheckOnResume);
102
+
103
+ if (call.getBoolean("checkOnStart", true)) {
104
+ scheduleCheck("startMonitoring", runtimeOptions, null, runtimeOptions.showPromptOnOutdated);
105
+ }
106
+
107
+ call.resolve(buildMonitoringStatePayload());
108
+ }
109
+
110
+ @PluginMethod
111
+ public void stopMonitoring(PluginCall call) {
112
+ monitoringEnabled = false;
113
+ call.resolve(buildMonitoringStatePayload());
114
+ }
115
+
116
+ @PluginMethod
117
+ public void getLastStatus(PluginCall call) {
118
+ JSObject payload = new JSObject();
119
+ payload.put("status", lastStatus != null ? lastStatus : JSONObject.NULL);
120
+ call.resolve(payload);
121
+ }
122
+
123
+ @PluginMethod
124
+ public void showUpdatePrompt(PluginCall call) {
125
+ PromptOptions promptOptions = PromptOptions.fromCall(call, runtimeOptions);
126
+ showUpdatePrompt(promptOptions, true, (shown, opened) -> {
127
+ JSObject payload = new JSObject();
128
+ payload.put("shown", shown);
129
+ payload.put("openedUpdatePage", opened);
130
+ call.resolve(payload);
131
+ });
132
+ }
133
+
134
+ @PluginMethod
135
+ public void openUpdatePage(PluginCall call) {
136
+ String url = resolveUpdateUrl(call.getString("updateUrl"), lastStatus);
137
+ if (isBlank(url)) {
138
+ JSObject payload = new JSObject();
139
+ payload.put("opened", false);
140
+ payload.put("url", "");
141
+ call.resolve(payload);
142
+ return;
143
+ }
144
+
145
+ bridge.executeOnMainThread(() -> {
146
+ boolean opened = openUpdateUrl(url);
147
+ JSObject payload = new JSObject();
148
+ payload.put("opened", opened);
149
+ payload.put("url", url);
150
+ call.resolve(payload);
151
+ });
152
+ }
153
+
154
+ private void scheduleCheck(String source, RuntimeOptions options, @Nullable PluginCall call, boolean forcePrompt) {
155
+ execute(() -> {
156
+ JSObject status = evaluateStatus(source, options);
157
+ lastStatus = status;
158
+
159
+ notifyStatus(status);
160
+ maybeAutoPrompt(status, options, forcePrompt);
161
+
162
+ if (call != null) {
163
+ bridge.executeOnMainThread(() -> call.resolve(status));
164
+ }
165
+ });
166
+ }
167
+
168
+ private JSObject evaluateStatus(String source, RuntimeOptions options) {
169
+ CurrentWebViewInfo current = resolveCurrentWebViewInfo();
170
+ LatestVersionResolution latestResolution = resolveLatestVersion(options);
171
+
172
+ JSObject status = new JSObject();
173
+ status.put("platform", "android");
174
+ status.put("checkedAt", isoNow());
175
+ status.put("source", source);
176
+ status.put("providerPackage", current.packageName);
177
+ status.put("currentVersion", current.versionName);
178
+
179
+ Integer currentMajorVersion = versionChecker.parseMajorVersion(current.versionName);
180
+ if (currentMajorVersion != null) {
181
+ status.put("currentMajorVersion", currentMajorVersion);
182
+ }
183
+
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
+ String updateUrl = resolveUpdateUrl(options.updateUrl, status);
194
+ if (!isBlank(updateUrl)) {
195
+ status.put("updateUrl", updateUrl);
196
+ }
197
+
198
+ if (isBlank(current.versionName)) {
199
+ status.put("state", "unknown");
200
+ status.put("isLatest", false);
201
+ status.put("reason", "Could not detect the active Android WebView package version.");
202
+ return status;
203
+ }
204
+
205
+ if (!isBlank(latestVersion)) {
206
+ int cmp = versionChecker.compareVersions(current.versionName, latestVersion);
207
+ boolean isLatest = cmp >= 0;
208
+ status.put("state", isLatest ? "latest" : "outdated");
209
+ status.put("isLatest", isLatest);
210
+ status.put(
211
+ "reason",
212
+ isLatest
213
+ ? "Installed WebView version is at or above the latest resolved version."
214
+ : "Installed WebView version is behind the latest resolved version."
215
+ );
216
+ return status;
217
+ }
218
+
219
+ Integer minimumMajorVersion = options.minimumMajorVersion;
220
+ if (minimumMajorVersion != null && currentMajorVersion != null) {
221
+ boolean isLatest = currentMajorVersion >= minimumMajorVersion;
222
+ status.put("state", isLatest ? "latest" : "outdated");
223
+ status.put("isLatest", isLatest);
224
+ status.put(
225
+ "reason",
226
+ isLatest
227
+ ? "Installed WebView major version satisfies the minimum configured major version."
228
+ : "Installed WebView major version is below the configured minimum major version."
229
+ );
230
+ return status;
231
+ }
232
+
233
+ status.put("state", "unknown");
234
+ status.put("isLatest", false);
235
+ if (!isBlank(latestResolution.errorMessage)) {
236
+ status.put("reason", latestResolution.errorMessage);
237
+ } else {
238
+ status.put("reason", "Unable to resolve latest WebView version. Configure latestVersion or minimumMajorVersion.");
239
+ }
240
+
241
+ return status;
242
+ }
243
+
244
+ private void notifyStatus(JSObject status) {
245
+ bridge.executeOnMainThread(() -> {
246
+ notifyListeners("statusChanged", status);
247
+ String state = status.optString("state", "unknown");
248
+ if ("latest".equals(state)) {
249
+ notifyListeners("webViewLatest", status);
250
+ } else if ("outdated".equals(state)) {
251
+ notifyListeners("webViewOutdated", status);
252
+ }
253
+ });
254
+ }
255
+
256
+ private void maybeAutoPrompt(JSObject status, RuntimeOptions options, boolean forcePrompt) {
257
+ String state = status.optString("state", "unknown");
258
+ boolean shouldPrompt = "outdated".equals(state) && (forcePrompt || options.autoPromptOnOutdated);
259
+ if (!shouldPrompt) {
260
+ return;
261
+ }
262
+
263
+ PromptOptions promptOptions = PromptOptions.fromRuntime(options);
264
+ showUpdatePrompt(promptOptions, forcePrompt, null);
265
+ }
266
+
267
+ private void showUpdatePrompt(PromptOptions promptOptions, boolean force, @Nullable PromptResultCallback callback) {
268
+ Activity activity = getActivity();
269
+ if (activity == null || activity.isFinishing()) {
270
+ if (callback != null) {
271
+ callback.onResult(false, false);
272
+ }
273
+ return;
274
+ }
275
+
276
+ String updateUrl = resolveUpdateUrl(promptOptions.updateUrl, lastStatus);
277
+ if (isBlank(updateUrl)) {
278
+ if (callback != null) {
279
+ callback.onResult(false, false);
280
+ }
281
+ return;
282
+ }
283
+
284
+ String fingerprint = buildPromptFingerprint(lastStatus, updateUrl);
285
+ if (!force && !isBlank(fingerprint) && fingerprint.equals(lastPromptFingerprint)) {
286
+ if (callback != null) {
287
+ callback.onResult(false, false);
288
+ }
289
+ return;
290
+ }
291
+
292
+ if (!isBlank(fingerprint)) {
293
+ lastPromptFingerprint = fingerprint;
294
+ }
295
+
296
+ String title = !isBlank(promptOptions.title) ? promptOptions.title : DEFAULT_PROMPT_TITLE;
297
+ String message = !isBlank(promptOptions.message) ? promptOptions.message : buildDefaultPromptMessage(lastStatus);
298
+ String updateButtonText = !isBlank(promptOptions.updateButtonText) ? promptOptions.updateButtonText : DEFAULT_PROMPT_UPDATE_BUTTON;
299
+ String cancelButtonText = !isBlank(promptOptions.cancelButtonText) ? promptOptions.cancelButtonText : DEFAULT_PROMPT_CANCEL_BUTTON;
300
+
301
+ bridge.executeOnMainThread(() -> {
302
+ final boolean[] resolved = { false };
303
+ AlertDialog dialog = new AlertDialog.Builder(activity)
304
+ .setTitle(title)
305
+ .setMessage(message)
306
+ .setPositiveButton(updateButtonText, (dialogInterface, which) -> {
307
+ boolean opened = openUpdateUrl(updateUrl);
308
+ if (callback != null && !resolved[0]) {
309
+ resolved[0] = true;
310
+ callback.onResult(true, opened);
311
+ }
312
+ })
313
+ .setNegativeButton(cancelButtonText, (dialogInterface, which) -> {
314
+ if (callback != null && !resolved[0]) {
315
+ resolved[0] = true;
316
+ callback.onResult(true, false);
317
+ }
318
+ })
319
+ .setOnCancelListener(dialogInterface -> {
320
+ if (callback != null && !resolved[0]) {
321
+ resolved[0] = true;
322
+ callback.onResult(true, false);
323
+ }
324
+ })
325
+ .create();
326
+ dialog.show();
327
+ });
328
+ }
329
+
330
+ private JSObject buildMonitoringStatePayload() {
331
+ JSObject payload = new JSObject();
332
+ payload.put("monitoring", monitoringEnabled);
333
+ payload.put("checkOnResume", checkOnResume);
334
+ payload.put("autoPromptOnOutdated", runtimeOptions.autoPromptOnOutdated);
335
+ return payload;
336
+ }
337
+
338
+ private CurrentWebViewInfo resolveCurrentWebViewInfo() {
339
+ try {
340
+ PackageInfo packageInfo = WebViewCompat.getCurrentWebViewPackage(getContext());
341
+ if (packageInfo != null) {
342
+ return new CurrentWebViewInfo(packageInfo.packageName, packageInfo.versionName);
343
+ }
344
+ } catch (Throwable ignored) {
345
+ // Fall through to platform APIs.
346
+ }
347
+
348
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
349
+ try {
350
+ PackageInfo packageInfo = WebView.getCurrentWebViewPackage();
351
+ if (packageInfo != null) {
352
+ return new CurrentWebViewInfo(packageInfo.packageName, packageInfo.versionName);
353
+ }
354
+ } catch (Throwable ignored) {
355
+ // Fall through to unknown state.
356
+ }
357
+ }
358
+
359
+ return new CurrentWebViewInfo("", "");
360
+ }
361
+
362
+ private LatestVersionResolution resolveLatestVersion(RuntimeOptions options) {
363
+ if (!isBlank(options.latestVersion)) {
364
+ return new LatestVersionResolution(options.latestVersion, null);
365
+ }
366
+
367
+ String apiUrl = !isBlank(options.latestVersionApiUrl) ? options.latestVersionApiUrl : DEFAULT_ANDROID_LATEST_VERSION_API_URL;
368
+
369
+ if (
370
+ !isBlank(cachedLatestVersion) &&
371
+ !isBlank(cachedLatestVersionUrl) &&
372
+ apiUrl.equals(cachedLatestVersionUrl) &&
373
+ (System.currentTimeMillis() - cachedLatestVersionTimestamp) < LATEST_VERSION_CACHE_TTL_MS
374
+ ) {
375
+ return new LatestVersionResolution(cachedLatestVersion, null);
376
+ }
377
+
378
+ try {
379
+ String payload = readUrl(apiUrl);
380
+ String version = extractLatestVersion(payload);
381
+ if (isBlank(version)) {
382
+ return new LatestVersionResolution(null, "Latest version API returned no version string.");
383
+ }
384
+
385
+ cachedLatestVersion = version;
386
+ cachedLatestVersionUrl = apiUrl;
387
+ cachedLatestVersionTimestamp = System.currentTimeMillis();
388
+
389
+ return new LatestVersionResolution(version, null);
390
+ } catch (Exception error) {
391
+ return new LatestVersionResolution(null, "Could not resolve latest WebView version: " + error.getMessage());
392
+ }
393
+ }
394
+
395
+ private String readUrl(String urlValue) throws IOException {
396
+ HttpURLConnection connection = (HttpURLConnection) new URL(urlValue).openConnection();
397
+ connection.setRequestMethod("GET");
398
+ connection.setConnectTimeout(5000);
399
+ connection.setReadTimeout(5000);
400
+ connection.setRequestProperty("Accept", "application/json");
401
+
402
+ try {
403
+ InputStream stream = connection.getResponseCode() >= 400 ? connection.getErrorStream() : connection.getInputStream();
404
+ if (stream == null) {
405
+ throw new IOException("No response body.");
406
+ }
407
+
408
+ BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
409
+ StringBuilder out = new StringBuilder();
410
+ String line;
411
+ while ((line = reader.readLine()) != null) {
412
+ out.append(line);
413
+ }
414
+ return out.toString();
415
+ } finally {
416
+ connection.disconnect();
417
+ }
418
+ }
419
+
420
+ @Nullable
421
+ private String extractLatestVersion(String payload) throws Exception {
422
+ JSONObject json = new JSONObject(payload);
423
+
424
+ String version = normalizeVersion(json.optString("version", null));
425
+ if (!isBlank(version)) {
426
+ return version;
427
+ }
428
+
429
+ version = normalizeVersion(json.optString("latestVersion", null));
430
+ if (!isBlank(version)) {
431
+ return version;
432
+ }
433
+
434
+ JSONArray versions = json.optJSONArray("versions");
435
+ if (versions != null && versions.length() > 0) {
436
+ JSONObject first = versions.optJSONObject(0);
437
+ if (first != null) {
438
+ version = normalizeVersion(first.optString("version", null));
439
+ if (!isBlank(version)) {
440
+ return version;
441
+ }
442
+
443
+ String name = first.optString("name", null);
444
+ if (!isBlank(name)) {
445
+ int slash = name.lastIndexOf('/');
446
+ if (slash >= 0 && slash + 1 < name.length()) {
447
+ version = normalizeVersion(name.substring(slash + 1));
448
+ if (!isBlank(version)) {
449
+ return version;
450
+ }
451
+ }
452
+ }
453
+ }
454
+ }
455
+
456
+ return null;
457
+ }
458
+
459
+ @Nullable
460
+ private String normalizeVersion(@Nullable String value) {
461
+ if (isBlank(value)) {
462
+ return null;
463
+ }
464
+ return value.trim();
465
+ }
466
+
467
+ private String resolveUpdateUrl(@Nullable String explicitUrl, @Nullable JSObject status) {
468
+ if (!isBlank(explicitUrl)) {
469
+ return explicitUrl;
470
+ }
471
+
472
+ if (status != null) {
473
+ String statusUrl = status.optString("updateUrl", null);
474
+ if (!isBlank(statusUrl)) {
475
+ return statusUrl;
476
+ }
477
+
478
+ String providerPackage = status.optString("providerPackage", defaultProviderPackageForSdk());
479
+ return buildStoreUrl(providerPackage);
480
+ }
481
+
482
+ return buildStoreUrl(defaultProviderPackageForSdk());
483
+ }
484
+
485
+ private String buildStoreUrl(@Nullable String packageName) {
486
+ String resolvedPackage = isBlank(packageName) ? defaultProviderPackageForSdk() : packageName;
487
+ return "market://details?id=" + resolvedPackage;
488
+ }
489
+
490
+ private String defaultProviderPackageForSdk() {
491
+ int sdk = Build.VERSION.SDK_INT;
492
+ if (sdk >= Build.VERSION_CODES.N && sdk <= Build.VERSION_CODES.P) {
493
+ return "com.android.chrome";
494
+ }
495
+ return "com.google.android.webview";
496
+ }
497
+
498
+ private boolean openUpdateUrl(String url) {
499
+ Activity activity = getActivity();
500
+ if (activity == null) {
501
+ return false;
502
+ }
503
+
504
+ try {
505
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
506
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
507
+ activity.startActivity(intent);
508
+ return true;
509
+ } catch (ActivityNotFoundException error) {
510
+ if (url.startsWith("market://")) {
511
+ Uri uri = Uri.parse(url);
512
+ String packageName = uri.getQueryParameter("id");
513
+ if (!isBlank(packageName)) {
514
+ String fallbackUrl = "https://play.google.com/store/apps/details?id=" + packageName;
515
+ try {
516
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(fallbackUrl));
517
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
518
+ activity.startActivity(intent);
519
+ return true;
520
+ } catch (ActivityNotFoundException ignored) {
521
+ return false;
522
+ }
523
+ }
524
+ }
525
+ return false;
526
+ }
527
+ }
528
+
529
+ private String buildDefaultPromptMessage(@Nullable JSObject status) {
530
+ if (status == null) {
531
+ return "A newer WebView version is available. Please update for best compatibility.";
532
+ }
533
+
534
+ String current = status.optString("currentVersion", "current version");
535
+ String latest = status.optString("latestVersion", "the latest available version");
536
+ return "Your WebView version (" + current + ") is outdated. Please update to " + latest + ".";
537
+ }
538
+
539
+ private String buildPromptFingerprint(@Nullable JSObject status, String updateUrl) {
540
+ if (status == null) {
541
+ return updateUrl;
542
+ }
543
+
544
+ String current = status.optString("currentVersion", "");
545
+ String latest = status.optString("latestVersion", "");
546
+ String provider = status.optString("providerPackage", "");
547
+ return current + "|" + latest + "|" + provider + "|" + updateUrl;
548
+ }
549
+
550
+ private boolean isBlank(@Nullable String value) {
551
+ return value == null || value.trim().isEmpty();
552
+ }
553
+
554
+ private String isoNow() {
555
+ return ISO_FORMAT.get().format(new Date());
556
+ }
557
+
558
+ private interface PromptResultCallback {
559
+ void onResult(boolean shown, boolean openedUpdatePage);
560
+ }
561
+
562
+ private static final class CurrentWebViewInfo {
563
+
564
+ private final String packageName;
565
+ private final String versionName;
566
+
567
+ private CurrentWebViewInfo(String packageName, String versionName) {
568
+ this.packageName = packageName != null ? packageName : "";
569
+ this.versionName = versionName != null ? versionName : "";
570
+ }
571
+ }
572
+
573
+ private static final class LatestVersionResolution {
574
+
575
+ @Nullable
576
+ private final String version;
577
+
578
+ @Nullable
579
+ private final String errorMessage;
580
+
581
+ private LatestVersionResolution(@Nullable String version, @Nullable String errorMessage) {
582
+ this.version = version;
583
+ this.errorMessage = errorMessage;
584
+ }
585
+ }
586
+
587
+ private static final class PromptOptions {
588
+
589
+ @Nullable
590
+ private String title;
591
+
592
+ @Nullable
593
+ private String message;
594
+
595
+ @Nullable
596
+ private String updateButtonText;
597
+
598
+ @Nullable
599
+ private String cancelButtonText;
600
+
601
+ @Nullable
602
+ private String updateUrl;
603
+
604
+ static PromptOptions fromRuntime(RuntimeOptions options) {
605
+ PromptOptions out = new PromptOptions();
606
+ out.title = options.promptTitle;
607
+ out.message = options.promptMessage;
608
+ out.updateButtonText = options.promptUpdateButtonText;
609
+ out.cancelButtonText = options.promptCancelButtonText;
610
+ out.updateUrl = options.updateUrl;
611
+ return out;
612
+ }
613
+
614
+ static PromptOptions fromCall(PluginCall call, RuntimeOptions defaults) {
615
+ PromptOptions out = fromRuntime(defaults);
616
+ out.title = emptyToNull(call.getString("title", out.title));
617
+ out.message = emptyToNull(call.getString("message", out.message));
618
+ out.updateButtonText = emptyToNull(call.getString("updateButtonText", out.updateButtonText));
619
+ out.cancelButtonText = emptyToNull(call.getString("cancelButtonText", out.cancelButtonText));
620
+ out.updateUrl = emptyToNull(call.getString("updateUrl", out.updateUrl));
621
+ return out;
622
+ }
623
+
624
+ @Nullable
625
+ private static String emptyToNull(@Nullable String value) {
626
+ if (value == null || value.trim().isEmpty()) {
627
+ return null;
628
+ }
629
+ return value;
630
+ }
631
+ }
632
+
633
+ private static final class RuntimeOptions {
634
+
635
+ private boolean autoCheckOnLoad = true;
636
+ private boolean autoCheckOnResume = true;
637
+ private boolean autoPromptOnOutdated;
638
+ private boolean showPromptOnOutdated;
639
+
640
+ @Nullable
641
+ private String latestVersion;
642
+
643
+ @Nullable
644
+ private Integer minimumMajorVersion;
645
+
646
+ @Nullable
647
+ private String latestVersionApiUrl;
648
+
649
+ @Nullable
650
+ private String updateUrl;
651
+
652
+ @Nullable
653
+ private String promptTitle;
654
+
655
+ @Nullable
656
+ private String promptMessage;
657
+
658
+ @Nullable
659
+ private String promptUpdateButtonText;
660
+
661
+ @Nullable
662
+ private String promptCancelButtonText;
663
+
664
+ static RuntimeOptions fromConfig(PluginConfig config) {
665
+ RuntimeOptions out = new RuntimeOptions();
666
+ out.autoCheckOnLoad = config.getBoolean("autoCheckOnLoad", true);
667
+ out.autoCheckOnResume = config.getBoolean("autoCheckOnResume", true);
668
+ out.autoPromptOnOutdated = config.getBoolean("autoPromptOnOutdated", false);
669
+ out.latestVersion = emptyToNull(config.getString("latestVersion"));
670
+ Integer minimum = config.getConfigJSON().has("minimumMajorVersion")
671
+ ? config.getInt("minimumMajorVersion", 0)
672
+ : null;
673
+ out.minimumMajorVersion = minimum;
674
+ out.latestVersionApiUrl = emptyToNull(config.getString("latestVersionApiUrl"));
675
+ out.updateUrl = emptyToNull(config.getString("updateUrl"));
676
+ out.promptTitle = emptyToNull(config.getString("promptTitle"));
677
+ out.promptMessage = emptyToNull(config.getString("promptMessage"));
678
+ out.promptUpdateButtonText = emptyToNull(config.getString("promptUpdateButtonText"));
679
+ out.promptCancelButtonText = emptyToNull(config.getString("promptCancelButtonText"));
680
+ return out;
681
+ }
682
+
683
+ RuntimeOptions merge(PluginCall call) {
684
+ RuntimeOptions out = copy();
685
+
686
+ if (call.hasOption("autoCheckOnLoad")) {
687
+ out.autoCheckOnLoad = call.getBoolean("autoCheckOnLoad", out.autoCheckOnLoad);
688
+ }
689
+ if (call.hasOption("autoCheckOnResume")) {
690
+ out.autoCheckOnResume = call.getBoolean("autoCheckOnResume", out.autoCheckOnResume);
691
+ }
692
+ if (call.hasOption("autoPromptOnOutdated")) {
693
+ out.autoPromptOnOutdated = call.getBoolean("autoPromptOnOutdated", out.autoPromptOnOutdated);
694
+ }
695
+ if (call.hasOption("showPromptOnOutdated")) {
696
+ out.showPromptOnOutdated = call.getBoolean("showPromptOnOutdated", out.showPromptOnOutdated);
697
+ }
698
+ if (call.hasOption("latestVersion")) {
699
+ out.latestVersion = emptyToNull(call.getString("latestVersion"));
700
+ }
701
+ if (call.hasOption("minimumMajorVersion")) {
702
+ out.minimumMajorVersion = call.getInt("minimumMajorVersion");
703
+ }
704
+ if (call.hasOption("latestVersionApiUrl")) {
705
+ out.latestVersionApiUrl = emptyToNull(call.getString("latestVersionApiUrl"));
706
+ }
707
+ if (call.hasOption("updateUrl")) {
708
+ out.updateUrl = emptyToNull(call.getString("updateUrl"));
709
+ }
710
+ if (call.hasOption("promptTitle")) {
711
+ out.promptTitle = emptyToNull(call.getString("promptTitle"));
712
+ }
713
+ if (call.hasOption("promptMessage")) {
714
+ out.promptMessage = emptyToNull(call.getString("promptMessage"));
715
+ }
716
+ if (call.hasOption("promptUpdateButtonText")) {
717
+ out.promptUpdateButtonText = emptyToNull(call.getString("promptUpdateButtonText"));
718
+ }
719
+ if (call.hasOption("promptCancelButtonText")) {
720
+ out.promptCancelButtonText = emptyToNull(call.getString("promptCancelButtonText"));
721
+ }
722
+
723
+ return out;
724
+ }
725
+
726
+ private RuntimeOptions copy() {
727
+ RuntimeOptions out = new RuntimeOptions();
728
+ out.autoCheckOnLoad = autoCheckOnLoad;
729
+ out.autoCheckOnResume = autoCheckOnResume;
730
+ out.autoPromptOnOutdated = autoPromptOnOutdated;
731
+ out.showPromptOnOutdated = showPromptOnOutdated;
732
+ out.latestVersion = latestVersion;
733
+ out.minimumMajorVersion = minimumMajorVersion;
734
+ out.latestVersionApiUrl = latestVersionApiUrl;
735
+ out.updateUrl = updateUrl;
736
+ out.promptTitle = promptTitle;
737
+ out.promptMessage = promptMessage;
738
+ out.promptUpdateButtonText = promptUpdateButtonText;
739
+ out.promptCancelButtonText = promptCancelButtonText;
740
+ return out;
741
+ }
742
+
743
+ @Nullable
744
+ private static String emptyToNull(@Nullable String value) {
745
+ if (value == null || value.trim().isEmpty()) {
746
+ return null;
747
+ }
748
+ return value;
749
+ }
750
+ }
751
+ }