@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.
package/README.md ADDED
@@ -0,0 +1,393 @@
1
+ # @capgo/capacitor-webview-version-checker
2
+ <a href="https://capgo.app/"><img src='https://raw.githubusercontent.com/Cap-go/capgo/main/assets/capgo_banner.png' alt='Capgo - Instant updates for capacitor'/></a>
3
+
4
+ <div align="center">
5
+ <h2><a href="https://capgo.app/?ref=plugin_webview_version_checker"> ➡️ Get Instant updates for your App with Capgo</a></h2>
6
+ <h2><a href="https://capgo.app/consulting/?ref=plugin_webview_version_checker"> Missing a feature? We’ll build the plugin for you 💪</a></h2>
7
+ </div>
8
+
9
+ Capacitor plugin to detect whether the app WebView engine is up to date, emit realtime status events, and optionally show a native update prompt that redirects users to the proper update destination.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ bun add @capgo/capacitor-webview-version-checker
15
+ bunx cap sync
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Simple setup (config only):
21
+
22
+ ```ts
23
+ import type { CapacitorConfig } from '@capacitor/cli';
24
+
25
+ const config: CapacitorConfig = {
26
+ plugins: {
27
+ WebviewVersionChecker: {
28
+ autoCheckOnLoad: true,
29
+ autoCheckOnResume: true,
30
+ autoPromptOnOutdated: true,
31
+ },
32
+ },
33
+ };
34
+
35
+ export default config;
36
+ ```
37
+
38
+ Advanced usage with JavaScript (manual check, listeners, custom prompt):
39
+
40
+ ```ts
41
+ import { WebviewVersionChecker } from '@capgo/capacitor-webview-version-checker';
42
+
43
+ const listener = await WebviewVersionChecker.addListener('webViewOutdated', (status) => {
44
+ console.log('Outdated WebView detected', status);
45
+ });
46
+
47
+ const status = await WebviewVersionChecker.check({
48
+ latestVersionApiUrl:
49
+ 'https://versionhistory.googleapis.com/v1/chrome/platforms/android/channels/stable/versions?page_size=1',
50
+ minimumMajorVersion: 124,
51
+ showPromptOnOutdated: true,
52
+ promptTitle: 'Update WebView',
53
+ promptMessage: 'Your WebView is outdated. Please update to continue safely.',
54
+ promptUpdateButtonText: 'Update now',
55
+ promptCancelButtonText: 'Later',
56
+ });
57
+
58
+ console.log('WebView status', status);
59
+
60
+ // later
61
+ listener.remove();
62
+ ```
63
+
64
+ ## Why WebView Version Checker?
65
+
66
+ Capacitor already provides a built-in WebView minimum check through config:
67
+
68
+ ```ts
69
+ import type { CapacitorConfig } from '@capacitor/cli';
70
+
71
+ const config: CapacitorConfig = {
72
+ android: {
73
+ minWebViewVersion: 124,
74
+ },
75
+ server: {
76
+ errorPath: 'unsupported-webview.html',
77
+ },
78
+ };
79
+
80
+ export default config;
81
+ ```
82
+
83
+ ### Why we're doing this instead
84
+
85
+ - `android.minWebViewVersion` is static config; it does not give runtime plugin events like `webViewOutdated`.
86
+ - With `server.errorPath`, users are redirected to a static error page (hard block UX) instead of seeing a native modal in your normal app flow.
87
+ - Without `server.errorPath`, Capacitor only logs the issue, but does not provide a native update prompt + store deep link UX.
88
+ - This plugin gives soft enforcement: users can still open/use the app, get a native update modal, and you decide whether to require update later.
89
+
90
+ ## Android Provider Handling
91
+
92
+ The plugin resolves the active WebView provider package and version, then routes update links to the correct package:
93
+ - Android 5-6 and 10+: Android System WebView (`com.google.android.webview`)
94
+ - Android 7-9: Google Chrome (`com.android.chrome`)
95
+
96
+ When provider detection is unavailable, fallback routing follows the same API-range rule above.
97
+
98
+ ## API
99
+
100
+ <docgen-index>
101
+
102
+ * [`check(...)`](#check)
103
+ * [`startMonitoring(...)`](#startmonitoring)
104
+ * [`stopMonitoring()`](#stopmonitoring)
105
+ * [`getLastStatus()`](#getlaststatus)
106
+ * [`showUpdatePrompt(...)`](#showupdateprompt)
107
+ * [`openUpdatePage(...)`](#openupdatepage)
108
+ * [`addListener('statusChanged', ...)`](#addlistenerstatuschanged-)
109
+ * [`addListener('webViewLatest', ...)`](#addlistenerwebviewlatest-)
110
+ * [`addListener('webViewOutdated', ...)`](#addlistenerwebviewoutdated-)
111
+ * [Interfaces](#interfaces)
112
+ * [Type Aliases](#type-aliases)
113
+
114
+ </docgen-index>
115
+
116
+ <docgen-api>
117
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
118
+
119
+ Public API for checking WebView freshness and guiding users to updates.
120
+
121
+ ### check(...)
122
+
123
+ ```typescript
124
+ check(options?: CheckWebViewOptions | undefined) => Promise<WebViewVersionStatus>
125
+ ```
126
+
127
+ Runs a version check and returns the latest known status.
128
+
129
+ | Param | Type |
130
+ | ------------- | ------------------------------------------------------------------- |
131
+ | **`options`** | <code><a href="#checkwebviewoptions">CheckWebViewOptions</a></code> |
132
+
133
+ **Returns:** <code>Promise&lt;<a href="#webviewversionstatus">WebViewVersionStatus</a>&gt;</code>
134
+
135
+ --------------------
136
+
137
+
138
+ ### startMonitoring(...)
139
+
140
+ ```typescript
141
+ startMonitoring(options?: StartMonitoringOptions | undefined) => Promise<MonitoringStateResult>
142
+ ```
143
+
144
+ Enables background monitoring (typically on app resume).
145
+
146
+ | Param | Type |
147
+ | ------------- | ------------------------------------------------------------------------- |
148
+ | **`options`** | <code><a href="#startmonitoringoptions">StartMonitoringOptions</a></code> |
149
+
150
+ **Returns:** <code>Promise&lt;<a href="#monitoringstateresult">MonitoringStateResult</a>&gt;</code>
151
+
152
+ --------------------
153
+
154
+
155
+ ### stopMonitoring()
156
+
157
+ ```typescript
158
+ stopMonitoring() => Promise<MonitoringStateResult>
159
+ ```
160
+
161
+ Disables monitoring.
162
+
163
+ **Returns:** <code>Promise&lt;<a href="#monitoringstateresult">MonitoringStateResult</a>&gt;</code>
164
+
165
+ --------------------
166
+
167
+
168
+ ### getLastStatus()
169
+
170
+ ```typescript
171
+ getLastStatus() => Promise<LastStatusResult>
172
+ ```
173
+
174
+ Returns the last resolved status, or `null` if no check was run yet.
175
+
176
+ **Returns:** <code>Promise&lt;<a href="#laststatusresult">LastStatusResult</a>&gt;</code>
177
+
178
+ --------------------
179
+
180
+
181
+ ### showUpdatePrompt(...)
182
+
183
+ ```typescript
184
+ showUpdatePrompt(options?: ShowUpdatePromptOptions | undefined) => Promise<ShowUpdatePromptResult>
185
+ ```
186
+
187
+ Shows a native prompt asking the user to update the WebView.
188
+
189
+ | Param | Type |
190
+ | ------------- | --------------------------------------------------------------------------- |
191
+ | **`options`** | <code><a href="#showupdatepromptoptions">ShowUpdatePromptOptions</a></code> |
192
+
193
+ **Returns:** <code>Promise&lt;<a href="#showupdatepromptresult">ShowUpdatePromptResult</a>&gt;</code>
194
+
195
+ --------------------
196
+
197
+
198
+ ### openUpdatePage(...)
199
+
200
+ ```typescript
201
+ openUpdatePage(options?: OpenUpdatePageOptions | undefined) => Promise<OpenUpdatePageResult>
202
+ ```
203
+
204
+ Opens the configured update page directly.
205
+
206
+ | Param | Type |
207
+ | ------------- | ----------------------------------------------------------------------- |
208
+ | **`options`** | <code><a href="#openupdatepageoptions">OpenUpdatePageOptions</a></code> |
209
+
210
+ **Returns:** <code>Promise&lt;<a href="#openupdatepageresult">OpenUpdatePageResult</a>&gt;</code>
211
+
212
+ --------------------
213
+
214
+
215
+ ### addListener('statusChanged', ...)
216
+
217
+ ```typescript
218
+ addListener(eventName: 'statusChanged', listenerFunc: (status: WebViewVersionStatus) => void) => Promise<PluginListenerHandle>
219
+ ```
220
+
221
+ Fired for every successful status evaluation.
222
+
223
+ | Param | Type |
224
+ | ------------------ | ------------------------------------------------------------------------------------------ |
225
+ | **`eventName`** | <code>'statusChanged'</code> |
226
+ | **`listenerFunc`** | <code>(status: <a href="#webviewversionstatus">WebViewVersionStatus</a>) =&gt; void</code> |
227
+
228
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
229
+
230
+ --------------------
231
+
232
+
233
+ ### addListener('webViewLatest', ...)
234
+
235
+ ```typescript
236
+ addListener(eventName: 'webViewLatest', listenerFunc: (status: WebViewVersionStatus) => void) => Promise<PluginListenerHandle>
237
+ ```
238
+
239
+ Fired when the state resolves to `latest`.
240
+
241
+ | Param | Type |
242
+ | ------------------ | ------------------------------------------------------------------------------------------ |
243
+ | **`eventName`** | <code>'webViewLatest'</code> |
244
+ | **`listenerFunc`** | <code>(status: <a href="#webviewversionstatus">WebViewVersionStatus</a>) =&gt; void</code> |
245
+
246
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
247
+
248
+ --------------------
249
+
250
+
251
+ ### addListener('webViewOutdated', ...)
252
+
253
+ ```typescript
254
+ addListener(eventName: 'webViewOutdated', listenerFunc: (status: WebViewVersionStatus) => void) => Promise<PluginListenerHandle>
255
+ ```
256
+
257
+ Fired when the state resolves to `outdated`.
258
+
259
+ | Param | Type |
260
+ | ------------------ | ------------------------------------------------------------------------------------------ |
261
+ | **`eventName`** | <code>'webViewOutdated'</code> |
262
+ | **`listenerFunc`** | <code>(status: <a href="#webviewversionstatus">WebViewVersionStatus</a>) =&gt; void</code> |
263
+
264
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
265
+
266
+ --------------------
267
+
268
+
269
+ ### Interfaces
270
+
271
+
272
+ #### WebViewVersionStatus
273
+
274
+ Snapshot of the currently detected WebView status.
275
+
276
+ | Prop | Type | Description |
277
+ | ------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------- |
278
+ | **`platform`** | <code><a href="#webviewplatform">WebViewPlatform</a></code> | Native platform that generated the status. |
279
+ | **`state`** | <code><a href="#webviewversionstate">WebViewVersionState</a></code> | Resolved version state. |
280
+ | **`isLatest`** | <code>boolean</code> | Convenience boolean equivalent to `state === 'latest'`. |
281
+ | **`checkedAt`** | <code>string</code> | ISO-8601 timestamp of the check. |
282
+ | **`reason`** | <code>string</code> | Human-readable explanation for the reported state. |
283
+ | **`currentVersion`** | <code>string</code> | Current WebView (or iOS system WebKit) version string. |
284
+ | **`currentMajorVersion`** | <code>number</code> | Current detected major version (if parseable). |
285
+ | **`latestVersion`** | <code>string</code> | Resolved latest version used for comparison. |
286
+ | **`latestMajorVersion`** | <code>number</code> | Resolved latest major version (if parseable). |
287
+ | **`providerPackage`** | <code>string</code> | Android package name of the active WebView provider. |
288
+ | **`updateUrl`** | <code>string</code> | URL that should be opened to update when outdated. |
289
+ | **`source`** | <code>string</code> | Internal source identifier for the check implementation. |
290
+
291
+
292
+ #### CheckWebViewOptions
293
+
294
+ Options for running a WebView version check.
295
+
296
+ | Prop | Type | Description | Default |
297
+ | -------------------------- | -------------------- | --------------------------------------------------------------------------------- | --------------------- |
298
+ | **`showPromptOnOutdated`** | <code>boolean</code> | Force showing a native prompt if an outdated WebView is detected. | <code>false</code> |
299
+ | **`source`** | <code>string</code> | Optional tag included in the status payload so you can identify the check origin. | <code>"manual"</code> |
300
+
301
+
302
+ #### MonitoringStateResult
303
+
304
+ Result payload for monitor controls.
305
+
306
+ | Prop | Type | Description |
307
+ | -------------------------- | -------------------- | ------------------------------------------------------------ |
308
+ | **`monitoring`** | <code>boolean</code> | Whether monitoring is currently active. |
309
+ | **`checkOnResume`** | <code>boolean</code> | Whether checks run on app resume while monitoring is active. |
310
+ | **`autoPromptOnOutdated`** | <code>boolean</code> | Whether outdated checks auto-trigger the native prompt. |
311
+
312
+
313
+ #### StartMonitoringOptions
314
+
315
+ Options for starting monitor mode.
316
+
317
+ | Prop | Type | Description | Default |
318
+ | ------------------- | -------------------- | ----------------------------------------------------------------- | ----------------- |
319
+ | **`checkOnStart`** | <code>boolean</code> | Run a check immediately when monitoring starts. | <code>true</code> |
320
+ | **`checkOnResume`** | <code>boolean</code> | Whether foreground checks should run while monitoring is enabled. | <code>true</code> |
321
+
322
+
323
+ #### LastStatusResult
324
+
325
+ Last known check snapshot.
326
+
327
+ | Prop | Type | Description |
328
+ | ------------ | ----------------------------------------------------------------------------- | ------------------------------------- |
329
+ | **`status`** | <code><a href="#webviewversionstatus">WebViewVersionStatus</a> \| null</code> | Null until the first check completes. |
330
+
331
+
332
+ #### ShowUpdatePromptResult
333
+
334
+ Result payload for `showUpdatePrompt()`.
335
+
336
+ | Prop | Type | Description |
337
+ | ---------------------- | -------------------- | ---------------------------------------------------------- |
338
+ | **`shown`** | <code>boolean</code> | Whether a native prompt was actually shown. |
339
+ | **`openedUpdatePage`** | <code>boolean</code> | Whether the update page was opened from the prompt action. |
340
+
341
+
342
+ #### ShowUpdatePromptOptions
343
+
344
+ Options for showing the native update prompt.
345
+
346
+ | Prop | Type | Description |
347
+ | ---------------------- | ------------------- | ------------------------------------------------------ |
348
+ | **`title`** | <code>string</code> | Prompt title. |
349
+ | **`message`** | <code>string</code> | Prompt message. |
350
+ | **`updateButtonText`** | <code>string</code> | Update CTA label. |
351
+ | **`cancelButtonText`** | <code>string</code> | Cancel CTA label. |
352
+ | **`updateUrl`** | <code>string</code> | Optional URL to open if the update action is selected. |
353
+
354
+
355
+ #### OpenUpdatePageResult
356
+
357
+ Result payload for `openUpdatePage()`.
358
+
359
+ | Prop | Type | Description |
360
+ | ------------ | -------------------- | ----------------------------------------- |
361
+ | **`opened`** | <code>boolean</code> | Whether opening the update URL succeeded. |
362
+ | **`url`** | <code>string</code> | URL that was attempted. |
363
+
364
+
365
+ #### OpenUpdatePageOptions
366
+
367
+ Options for opening the update page directly.
368
+
369
+ | Prop | Type | Description |
370
+ | --------------- | ------------------- | ---------------------- |
371
+ | **`updateUrl`** | <code>string</code> | Optional URL override. |
372
+
373
+
374
+ #### PluginListenerHandle
375
+
376
+ | Prop | Type |
377
+ | ------------ | ----------------------------------------- |
378
+ | **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
379
+
380
+
381
+ ### Type Aliases
382
+
383
+
384
+ #### WebViewPlatform
385
+
386
+ <code>'android' | 'ios' | 'web'</code>
387
+
388
+
389
+ #### WebViewVersionState
390
+
391
+ <code>'latest' | 'outdated' | 'unknown'</code>
392
+
393
+ </docgen-api>
@@ -0,0 +1,61 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
4
+ androidxWebkitVersion = project.hasProperty('androidxWebkitVersion') ? rootProject.ext.androidxWebkitVersion : '1.14.0'
5
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
6
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
7
+ }
8
+
9
+ buildscript {
10
+ repositories {
11
+ google()
12
+ mavenCentral()
13
+ }
14
+ dependencies {
15
+ classpath 'com.android.tools.build:gradle:8.13.0'
16
+ }
17
+ }
18
+
19
+ apply plugin: 'com.android.library'
20
+
21
+ android {
22
+ namespace = "app.capgo.webviewversionchecker"
23
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
24
+ defaultConfig {
25
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
26
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
27
+ versionCode 1
28
+ versionName "1.0"
29
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
30
+ }
31
+ buildTypes {
32
+ release {
33
+ minifyEnabled false
34
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
35
+ }
36
+ }
37
+ lintOptions {
38
+ abortOnError = false
39
+ }
40
+ compileOptions {
41
+ sourceCompatibility JavaVersion.VERSION_21
42
+ targetCompatibility JavaVersion.VERSION_21
43
+ }
44
+ }
45
+
46
+ repositories {
47
+ google()
48
+ mavenCentral()
49
+ }
50
+
51
+
52
+ dependencies {
53
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
54
+ implementation project(':capacitor-android')
55
+ annotationProcessor project(':capacitor-android')
56
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
57
+ implementation "androidx.webkit:webkit:$androidxWebkitVersion"
58
+ testImplementation "junit:junit:$junitVersion"
59
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
60
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
61
+ }
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.INTERNET" />
3
+ </manifest>
@@ -0,0 +1,75 @@
1
+ package app.capgo.webviewversionchecker;
2
+
3
+ import androidx.annotation.Nullable;
4
+ import java.util.ArrayList;
5
+ import java.util.List;
6
+
7
+ final class WebviewVersionChecker {
8
+
9
+ @Nullable
10
+ Integer parseMajorVersion(@Nullable String version) {
11
+ if (version == null || version.isEmpty()) {
12
+ return null;
13
+ }
14
+
15
+ StringBuilder digits = new StringBuilder();
16
+ for (int i = 0; i < version.length(); i++) {
17
+ char c = version.charAt(i);
18
+ if (Character.isDigit(c)) {
19
+ digits.append(c);
20
+ } else if (digits.length() > 0) {
21
+ break;
22
+ }
23
+ }
24
+
25
+ if (digits.length() == 0) {
26
+ return null;
27
+ }
28
+
29
+ try {
30
+ return Integer.parseInt(digits.toString());
31
+ } catch (NumberFormatException error) {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ int compareVersions(@Nullable String left, @Nullable String right) {
37
+ List<Integer> leftParts = tokenize(left);
38
+ List<Integer> rightParts = tokenize(right);
39
+
40
+ int max = Math.max(leftParts.size(), rightParts.size());
41
+ for (int index = 0; index < max; index++) {
42
+ int a = index < leftParts.size() ? leftParts.get(index) : 0;
43
+ int b = index < rightParts.size() ? rightParts.get(index) : 0;
44
+ if (a > b) {
45
+ return 1;
46
+ }
47
+ if (a < b) {
48
+ return -1;
49
+ }
50
+ }
51
+
52
+ return 0;
53
+ }
54
+
55
+ private List<Integer> tokenize(@Nullable String value) {
56
+ List<Integer> out = new ArrayList<>();
57
+ if (value == null || value.isEmpty()) {
58
+ return out;
59
+ }
60
+
61
+ String[] rawParts = value.split("[^0-9]+");
62
+ for (String part : rawParts) {
63
+ if (part == null || part.isEmpty()) {
64
+ continue;
65
+ }
66
+ try {
67
+ out.add(Integer.parseInt(part));
68
+ } catch (NumberFormatException ignored) {
69
+ out.add(0);
70
+ }
71
+ }
72
+
73
+ return out;
74
+ }
75
+ }