Skip to content

Commit 48c49c1

Browse files
authored
feat(device): add browser/webview version to getInfo() (#109)
1 parent 911ae71 commit 48c49c1

File tree

8 files changed

+216
-6
lines changed

8 files changed

+216
-6
lines changed

device/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ Get the device's current language locale code.
7474

7575
#### DeviceInfo
7676

77-
| Prop | Type | Description | Since |
78-
| --------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
77+
| Prop | Type | Description | Since |
78+
| --------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
7979
| **`name`** | <code>string</code> | The name of the device. For example, "John's iPhone". This is only supported on iOS and Android 7.1 or above. | 1.0.0 |
8080
| **`model`** | <code>string</code> | The device model. For example, "iPhone". | 1.0.0 |
8181
| **`platform`** | <code>'ios' \| 'android' \| 'web'</code> | The device platform (lowercase). | 1.0.0 |
@@ -86,7 +86,8 @@ Get the device's current language locale code.
8686
| **`isVirtual`** | <code>boolean</code> | Whether the app is running in a simulator/emulator. | 1.0.0 |
8787
| **`memUsed`** | <code>number</code> | Approximate memory used by the current app, in bytes. Divide by 1048576 to get the number of MBs used. | 1.0.0 |
8888
| **`diskFree`** | <code>number</code> | How much free disk space is available on the the normal data storage. path for the os, in bytes | 1.0.0 |
89-
| **`diskTotal`** | <code>number</code> | The total size of the normal data storage path for the OS, in bytes. | 1.0.0 |
89+
| **`diskTotal`** | <code>number</code> | The total size of the normal data storage path for the OS, in bytes. | 1.0.0 |
90+
| **`webViewVersion`** | <code>string</code> | The web view browser version | 1.0.0 |
9091

9192

9293
#### DeviceBatteryInfo

device/android/src/main/java/com/capacitorjs/plugins/device/Device.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
import android.content.Context;
44
import android.content.Intent;
55
import android.content.IntentFilter;
6+
import android.content.pm.PackageInfo;
7+
import android.content.pm.PackageManager;
68
import android.os.BatteryManager;
79
import android.os.Build;
810
import android.os.Environment;
911
import android.os.StatFs;
1012
import android.provider.Settings;
13+
import android.webkit.WebView;
1114

1215
public class Device {
1316

@@ -78,4 +81,27 @@ public String getName() {
7881

7982
return null;
8083
}
84+
85+
public String getWebViewVersion() {
86+
PackageInfo info = null;
87+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
88+
info = WebView.getCurrentWebViewPackage();
89+
} else {
90+
String webViewPackage = "com.google.android.webview";
91+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
92+
webViewPackage = "com.android.chrome";
93+
}
94+
PackageManager pm = this.context.getPackageManager();
95+
try {
96+
info = pm.getPackageInfo(webViewPackage, 0);
97+
} catch (PackageManager.NameNotFoundException e) {
98+
e.printStackTrace();
99+
}
100+
}
101+
if (info != null) {
102+
return info.versionName;
103+
}
104+
105+
return android.os.Build.VERSION.RELEASE;
106+
}
81107
}

device/android/src/main/java/com/capacitorjs/plugins/device/DevicePlugin.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public void getInfo(PluginCall call) {
3232
r.put("uuid", implementation.getUuid());
3333
r.put("isVirtual", implementation.isVirtual());
3434
r.put("name", implementation.getName());
35+
r.put("webViewVersion", implementation.getWebViewVersion());
3536

3637
call.resolve(r);
3738
}

device/ios/Plugin/DevicePlugin.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public class DevicePlugin: CAPPlugin {
2626
"platform": "ios",
2727
"manufacturer": "Apple",
2828
"uuid": UIDevice.current.identifierForVendor!.uuidString,
29-
"isVirtual": isSimulator
29+
"isVirtual": isSimulator,
30+
"webViewVersion": UIDevice.current.systemVersion
3031
])
3132
}
3233

device/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727
"native"
2828
],
2929
"scripts": {
30+
"test": "uvu -r esm -r ts-node/register src/__tests__",
3031
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
3132
"verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin && cd ..",
3233
"verify:android": "cd android && ./gradlew clean build test && cd ..",
33-
"verify:web": "npm run build",
34+
"verify:web": "npm run build && npm test",
3435
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
3536
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- autocorrect --format",
3637
"eslint": "eslint . --ext ts",
@@ -51,12 +52,15 @@
5152
"@ionic/prettier-config": "~1.0.1",
5253
"@ionic/swiftlint-config": "^1.1.2",
5354
"eslint": "^7.11.0",
55+
"esm": "^3.2.25",
5456
"prettier": "~2.2.0",
5557
"prettier-plugin-java": "~1.0.0",
5658
"rimraf": "^3.0.0",
5759
"rollup": "^2.29.0",
5860
"swiftlint": "^1.0.1",
59-
"typescript": "~4.0.3"
61+
"ts-node": "^9.1.1",
62+
"typescript": "~4.0.3",
63+
"uvu": "^0.5.1"
6064
},
6165
"peerDependencies": {
6266
"@capacitor/core": "^3.0.0-alpha.9"
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import type { WebPluginConfig } from '@capacitor/core';
2+
import { test } from 'uvu';
3+
import * as assert from 'uvu/assert';
4+
5+
import { DeviceWeb } from '../web';
6+
7+
const config: WebPluginConfig = { name: 'DevicePlugin' };
8+
const web = new DeviceWeb(config);
9+
10+
test('Chrome', () => {
11+
// Mock empty navigator/window objects
12+
(global as any).navigator = {};
13+
(global as any).window = { chrome: true };
14+
15+
const userAgents = [
16+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', // Chrome 87 on Windows
17+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', // Chrome 87 MacOS
18+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', // Chrome 87 on Linux
19+
'Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.86 Mobile Safari/537.36', // Chrome 87 Android
20+
'Mozilla/5.0 (Linux; Android 10; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.86 Mobile Safari/537.36', // Chrome 87 on Android (Samsung)
21+
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/87.0.4280.77 Mobile/15E148 Safari/604.1', // "ChromeRevision" 87 on iPhone
22+
];
23+
24+
const expected = [
25+
'87.0.4280.88',
26+
'87.0.4280.88',
27+
'87.0.4280.88',
28+
'87.0.4280.86',
29+
'87.0.4280.86',
30+
'87.0.4280.77',
31+
];
32+
33+
for (const [index, ua] of userAgents.entries()) {
34+
const parsed = web.parseUa(ua);
35+
const actual = parsed.browserVersion;
36+
assert.is(actual, expected[index]);
37+
}
38+
});
39+
40+
test('Firefox', () => {
41+
(global as any).navigator = {};
42+
(global as any).window = { InstallTrigger: true };
43+
const userAgents = [
44+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0', // Firefox 83 on Windows
45+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11.0; rv:83.0) Gecko/20100101 Firefox/83.0', // Firefox 83 on MacOS
46+
'Mozilla/5.0 (X11; Linux i686; rv:83.0) Gecko/20100101 Firefox/83.0', // Firefox 83 on Linux
47+
'Mozilla/5.0 (Android 11; Mobile; rv:68.0) Gecko/68.0 Firefox/83.0', // Firefox 83 on Android
48+
'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/29.0 Mobile/15E148 Safari/605.1.15', // Firefox Mobile 29 on iPhone
49+
];
50+
51+
const expected = ['83.0', '83.0', '83.0', '83.0', '29.0'];
52+
53+
for (const [index, ua] of userAgents.entries()) {
54+
const parsed = web.parseUa(ua);
55+
const actual = parsed.browserVersion;
56+
assert.is(actual, expected[index]);
57+
}
58+
});
59+
60+
test('Safari', () => {
61+
(global as any).navigator = {};
62+
(global as any).window = { ApplePaySession: true };
63+
const userAgents = [
64+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15', // Safari 14 on MacOS
65+
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1', // Safari 14 on iPhone
66+
];
67+
68+
const expected = ['14.0.1', '14.0'];
69+
70+
for (const [index, ua] of userAgents.entries()) {
71+
const parsed = web.parseUa(ua);
72+
const actual = parsed.browserVersion;
73+
assert.is(actual, expected[index]);
74+
}
75+
});
76+
77+
test('Edge', () => {
78+
// this is true on early chromium edge, and false for older and latest versions. It should pass for both true or false
79+
(global as any).window = { chrome: true };
80+
(global as any).navigator = {};
81+
82+
const userAgents = [
83+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/18.17763', // Edge 18 on Windows 10
84+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134', // Edge 17 on Windows 10
85+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.63', // Edge 85 on Windows 10
86+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.57', // Edge 87 on Windows 10
87+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.57', // Edge 87 on MacOS
88+
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 EdgiOS/45.11.1 Mobile/15E148 Safari/605.1.15', // Edge 45 on iOS
89+
];
90+
91+
const expected = [
92+
'18.17763',
93+
'17.17134',
94+
'85.0.564.63',
95+
'87.0.664.57',
96+
'87.0.664.57',
97+
'45.11.1',
98+
];
99+
100+
for (const [index, ua] of userAgents.entries()) {
101+
const parsed = web.parseUa(ua);
102+
const actual = parsed.browserVersion;
103+
assert.is(actual, expected[index]);
104+
}
105+
106+
(global as any).window = { chrome: false };
107+
(global as any).navigator = {};
108+
for (const [index, ua] of userAgents.entries()) {
109+
const parsed = web.parseUa(ua);
110+
const actual = parsed.browserVersion;
111+
assert.is(actual, expected[index]);
112+
}
113+
});
114+
115+
test.run();

device/src/definitions.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ export interface DeviceInfo {
8282
* @since 1.0.0
8383
*/
8484
diskTotal?: number;
85+
86+
/**
87+
* The web view browser version
88+
*
89+
* @since 1.0.0
90+
*/
91+
webViewVersion: string;
8592
}
8693

8794
export interface DeviceBatteryInfo {

device/src/web.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ declare global {
1212
getBattery: any;
1313
oscpu: any;
1414
}
15+
16+
interface Window {
17+
InstallTrigger?: any;
18+
ApplePaySession?: any;
19+
chrome?: any;
20+
}
1521
}
1622

1723
export class DeviceWeb extends WebPlugin implements DevicePlugin {
@@ -31,6 +37,7 @@ export class DeviceWeb extends WebPlugin implements DevicePlugin {
3137
manufacturer: navigator.vendor,
3238
isVirtual: false,
3339
uuid: this.getUid(),
40+
webViewVersion: uaFields.browserVersion,
3441
};
3542
}
3643

@@ -106,6 +113,54 @@ export class DeviceWeb extends WebPlugin implements DevicePlugin {
106113
uaFields.operatingSystem = 'unknown';
107114
}
108115

116+
// Check for browsers based on non-standard javascript apis, only not user agent
117+
const isFirefox = !!window.InstallTrigger;
118+
const isSafari = !!window.ApplePaySession;
119+
const isChrome = !!window.chrome;
120+
const isEdge = /Edg/.test(ua);
121+
const isFirefoxIOS = /FxiOS/.test(ua);
122+
const isChromeIOS = /CriOS/.test(ua);
123+
const isEdgeIOS = /EdgiOS/.test(ua);
124+
125+
// FF and Edge User Agents both end with "/MAJOR.MINOR"
126+
if (
127+
isSafari ||
128+
(isChrome && !isEdge) ||
129+
isFirefoxIOS ||
130+
isChromeIOS ||
131+
isEdgeIOS
132+
) {
133+
// Safari version comes as "... Version/MAJOR.MINOR ..."
134+
// Chrome version comes as "... Chrome/MAJOR.MINOR ..."
135+
// FirefoxIOS version comes as "... FxiOS/MAJOR.MINOR ..."
136+
// ChromeIOS version comes as "... CriOS/MAJOR.MINOR ..."
137+
let searchWord: string;
138+
if (isFirefoxIOS) {
139+
searchWord = 'FxiOS';
140+
} else if (isChromeIOS) {
141+
searchWord = 'CriOS';
142+
} else if (isEdgeIOS) {
143+
searchWord = 'EdgiOS';
144+
} else if (isSafari) {
145+
searchWord = 'Version';
146+
} else {
147+
searchWord = 'Chrome';
148+
}
149+
150+
const words = ua.split(' ');
151+
for (const word of words) {
152+
if (word.includes(searchWord)) {
153+
const version = word.split('/')[1];
154+
uaFields.browserVersion = version;
155+
}
156+
}
157+
} else if (isFirefox || isEdge) {
158+
const reverseUA = ua.split('').reverse().join('');
159+
const reverseVersion = reverseUA.split('/')[0];
160+
const version = reverseVersion.split('').reverse().join('');
161+
uaFields.browserVersion = version;
162+
}
163+
109164
return uaFields;
110165
}
111166

0 commit comments

Comments
 (0)