Skip to content

Commit ef82acf

Browse files
committed
fix(settings): correctly detect Chrome on Android in devices & sessions
Signed-off-by: Chandrika Mohan <chandrikalov@gmail.com>
1 parent 56dcfc4 commit ef82acf

File tree

6 files changed

+104
-42
lines changed

6 files changed

+104
-42
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { describe, expect, it } from 'vitest'
7+
import { userAgentMap } from '../utils/userAgentMap.ts'
8+
9+
// Helper: iterate map in order, return first match (mirrors AuthToken.vue client computed)
10+
function detect(ua: string) {
11+
for (const [id, regex] of Object.entries(userAgentMap)) {
12+
const m = ua.match(regex)
13+
if (m) {
14+
return { id, version: m[2] ?? m[1], os: m[2] ? m[1] : null }
15+
}
16+
}
17+
return null
18+
}
19+
20+
// Android Chrome
21+
22+
describe('Android Chrome detection', () => {
23+
it('modern Android Chrome (no Build/ string, post-2021) should match androidChrome', () => {
24+
const ua = 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36'
25+
const result = detect(ua)
26+
expect(result?.id).toBe('androidChrome')
27+
expect(result?.version).toBe('132')
28+
})
29+
30+
it('legacy Android Chrome (with Build/ string, pre-2021) should match androidChrome', () => {
31+
const ua = 'Mozilla/5.0 (Linux; Android 10; SM-G973F Build/QP1A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.36'
32+
const result = detect(ua)
33+
expect(result?.id).toBe('androidChrome')
34+
expect(result?.version).toBe('130')
35+
})
36+
37+
it('Android Chrome on tablet (no "Mobile" in UA) should match androidChrome', () => {
38+
const ua = 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
39+
const result = detect(ua)
40+
expect(result?.id).toBe('androidChrome')
41+
expect(result?.version).toBe('131')
42+
})
43+
})
44+
45+
// Desktop Chrome regressions
46+
47+
describe('Desktop Chrome regression tests', () => {
48+
it('Desktop Chrome on Linux should still match chrome', () => {
49+
const ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'
50+
const result = detect(ua)
51+
expect(result?.id).toBe('chrome')
52+
expect(result?.version).toBe('132')
53+
})
54+
})
55+
56+
// Desktop Firefox regressions
57+
58+
describe('Desktop Firefox regression tests', () => {
59+
it('Desktop Firefox on Linux should still match firefox', () => {
60+
const ua = 'Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0'
61+
const result = detect(ua)
62+
expect(result?.id).toBe('firefox')
63+
expect(result?.version).toBe('124')
64+
})
65+
})

apps/settings/src/components/AuthToken.vue

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -100,35 +100,8 @@ import NcDateTime from '@nextcloud/vue/components/NcDateTime'
100100
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
101101
import NcTextField from '@nextcloud/vue/components/NcTextField'
102102
import { TokenType, useAuthTokenStore } from '../store/authtoken.ts'
103+
import { userAgentMap } from '../utils/userAgentMap.ts'
103104
104-
// When using capture groups the following parts are extracted the first is used as the version number, the second as the OS
105-
const userAgentMap = {
106-
ie: /(?:MSIE|Trident|Trident\/7.0; rv)[ :](\d+)/,
107-
// Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
108-
edge: /^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/,
109-
// Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
110-
firefox: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) Gecko\/[0-9.]+ Firefox\/(\d+)(?:\.\d)?$/,
111-
// Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
112-
chrome: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/(\d+)[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+$/,
113-
// Safari User Agent from http://www.useragentstring.com/pages/Safari/
114-
safari: /^Mozilla\/5\.0 \([^)]*(Windows|OS X)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/([0-9]+)[0-9.]+)? Safari\/[0-9.A-Z]+$/,
115-
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
116-
androidChrome: /Android.*(?:; (.*) Build\/).*Chrome\/(\d+)[0-9.]+/,
117-
iphone: / *CPU +iPhone +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
118-
ipad: /\(iPad; *CPU +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
119-
iosClient: /^Mozilla\/5\.0 \(iOS\) (?:ownCloud|Nextcloud)-iOS.*$/,
120-
androidClient: /^Mozilla\/5\.0 \(Android\) (?:ownCloud|Nextcloud)-android.*$/,
121-
iosTalkClient: /^Mozilla\/5\.0 \(iOS\) Nextcloud-Talk.*$/,
122-
androidTalkClient: /^Mozilla\/5\.0 \(Android\) Nextcloud-Talk.*$/,
123-
// DAVx5/3.3.8-beta2-gplay (2021/01/02; dav4jvm; okhttp/4.9.0) Android/10
124-
davx5: /DAV(?:droid|x5)\/([^ ]+)/,
125-
// Mozilla/5.0 (U; Linux; Maemo; Jolla; Sailfish; like Android 4.3) AppleWebKit/538.1 (KHTML, like Gecko) WebPirate/2.0 like Mobile Safari/538.1 (compatible)
126-
webPirate: /(Sailfish).*WebPirate\/(\d+)/,
127-
// Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0
128-
sailfishBrowser: /(Sailfish).*SailfishBrowser\/(\d+)/,
129-
// Neon 1.0.0+1
130-
neon: /Neon \d+\.\d+\.\d+\+\d+/,
131-
}
132105
const nameMap = {
133106
edge: 'Microsoft Edge',
134107
firefox: 'Firefox',
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
// When using capture groups the following parts are extracted
7+
// the first is used as the version number, the second as the OS
8+
// Exception: single-group regexes (ie, androidChrome) use the first group as the version.
9+
export const userAgentMap = {
10+
ie: /(?:MSIE|Trident|Trident\/7.0; rv)[ :](\d+)/,
11+
// Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
12+
edge: /^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/,
13+
// Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
14+
firefox: /^Mozilla\/5\.0 \((?![^)]*Android)[^)]*(Windows|OS X|Linux)[^)]+\) Gecko\/[0-9.]+ Firefox\/(\d+)(?:\.\d)?$/,
15+
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
16+
androidChrome: /^Mozilla\/5\.0 \(Linux; Android[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/(\d+)[0-9.]+ (?:Mobile )?Safari\/[0-9.]+$/,
17+
// Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
18+
chrome: /^Mozilla\/5\.0 \((?![^)]*Android)[^)]*(Windows|OS X|Linux)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/(\d+)[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+$/,
19+
// Safari User Agent from http://www.useragentstring.com/pages/Safari/
20+
safari: /^Mozilla\/5\.0 \([^)]*(Windows|OS X)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/([0-9]+)[0-9.]+)? Safari\/[0-9.A-Z]+$/,
21+
iphone: / *CPU +iPhone +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
22+
ipad: /\(iPad; *CPU +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
23+
iosClient: /^Mozilla\/5\.0 \(iOS\) (?:ownCloud|Nextcloud)-iOS.*$/,
24+
androidClient: /^Mozilla\/5\.0 \(Android\) (?:ownCloud|Nextcloud)-android.*$/,
25+
iosTalkClient: /^Mozilla\/5\.0 \(iOS\) Nextcloud-Talk.*$/,
26+
androidTalkClient: /^Mozilla\/5\.0 \(Android\) Nextcloud-Talk.*$/,
27+
// DAVx5/3.3.8-beta2-gplay (2021/01/02; dav4jvm; okhttp/4.9.0) Android/10
28+
davx5: /DAV(?:droid|x5)\/([^ ]+)/,
29+
// Mozilla/5.0 (U; Linux; Maemo; Jolla; Sailfish; like Android 4.3) AppleWebKit/538.1 (KHTML, like Gecko) WebPirate/2.0 like Mobile Safari/538.1 (compatible)
30+
webPirate: /(Sailfish).*WebPirate\/(\d+)/,
31+
// Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0
32+
sailfishBrowser: /(Sailfish).*SailfishBrowser\/(\d+)/,
33+
// Neon 1.0.0+1
34+
neon: /Neon \d+\.\d+\.\d+\+\d+/,
35+
}

dist/settings-vue-settings-personal-security.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/settings-vue-settings-personal-security.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 0 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)