Merge pull request #58739 from chandrika1993/fix/issue-50502

fix(settings): correctly detect Chrome on Android in devices & sessions
This commit is contained in:
Andy Scherzinger 2026-03-22 10:24:09 +01:00 committed by GitHub
commit 4a88f9316b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 128 additions and 43 deletions

View file

@ -0,0 +1,55 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { describe, expect, it } from 'vitest'
import { detect } from '../utils/userAgentDetect.ts'
describe('Android Chrome detection', () => {
it('modern Android Chrome (no Build/ string, post-2021) should match androidChrome', () => {
const ua = 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Mobile Safari/537.36'
expect(detect(ua)).toEqual({
id: 'androidChrome',
version: '132',
})
})
it('legacy Android Chrome (with Build/ string, pre-2021) should match androidChrome', () => {
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'
expect(detect(ua)).toEqual({
id: 'androidChrome',
version: '130',
})
})
it('Android Chrome on tablet (no "Mobile" in UA) should match androidChrome', () => {
const ua = 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
expect(detect(ua)).toEqual({
id: 'androidChrome',
version: '131',
})
})
})
describe('Desktop Chrome regression tests', () => {
it('Desktop Chrome on Linux should still match chrome', () => {
const ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'
expect(detect(ua)).toEqual({
id: 'chrome',
version: '132',
os: 'Linux',
})
})
})
describe('Desktop Firefox regression tests', () => {
it('Desktop Firefox on Linux should still match firefox', () => {
const ua = 'Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0'
expect(detect(ua)).toEqual({
id: 'firefox',
version: '124',
os: 'Linux',
})
})
})

View file

@ -100,35 +100,8 @@ import NcDateTime from '@nextcloud/vue/components/NcDateTime'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import { TokenType, useAuthTokenStore } from '../store/authtoken.ts'
import { detect } from '../utils/userAgentDetect.ts'
// When using capture groups the following parts are extracted the first is used as the version number, the second as the OS
const userAgentMap = {
ie: /(?:MSIE|Trident|Trident\/7.0; rv)[ :](\d+)/,
// Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
edge: /^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/,
// Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
firefox: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) Gecko\/[0-9.]+ Firefox\/(\d+)(?:\.\d)?$/,
// Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
chrome: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/(\d+)[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+$/,
// Safari User Agent from http://www.useragentstring.com/pages/Safari/
safari: /^Mozilla\/5\.0 \([^)]*(Windows|OS X)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/([0-9]+)[0-9.]+)? Safari\/[0-9.A-Z]+$/,
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
androidChrome: /Android.*(?:; (.*) Build\/).*Chrome\/(\d+)[0-9.]+/,
iphone: / *CPU +iPhone +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
ipad: /\(iPad; *CPU +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
iosClient: /^Mozilla\/5\.0 \(iOS\) (?:ownCloud|Nextcloud)-iOS.*$/,
androidClient: /^Mozilla\/5\.0 \(Android\) (?:ownCloud|Nextcloud)-android.*$/,
iosTalkClient: /^Mozilla\/5\.0 \(iOS\) Nextcloud-Talk.*$/,
androidTalkClient: /^Mozilla\/5\.0 \(Android\) Nextcloud-Talk.*$/,
// DAVx5/3.3.8-beta2-gplay (2021/01/02; dav4jvm; okhttp/4.9.0) Android/10
davx5: /DAV(?:droid|x5)\/([^ ]+)/,
// 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)
webPirate: /(Sailfish).*WebPirate\/(\d+)/,
// Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0
sailfishBrowser: /(Sailfish).*SailfishBrowser\/(\d+)/,
// Neon 1.0.0+1
neon: /Neon \d+\.\d+\.\d+\+\d+/,
}
const nameMap = {
edge: 'Microsoft Edge',
firefox: 'Firefox',
@ -203,18 +176,7 @@ export default defineComponent({
}
}
for (const client in userAgentMap) {
const matches = this.token.name.match(userAgentMap[client])
if (matches) {
return {
id: client,
os: matches[2] && matches[1],
version: matches[2] ?? matches[1],
}
}
}
return null
return detect(this.token.name)
},
/**

View file

@ -0,0 +1,33 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { userAgentMap } from './userAgentMap.ts'
export interface DetectedUserAgent {
id: string
version?: string
os?: string
}
/**
* Detect the client from a user agent string.
*
* @param ua Raw user agent string
* @return Detected client information or null if unknown
*/
export function detect(ua: string): DetectedUserAgent | null {
for (const id in userAgentMap) {
const matches = ua.match(userAgentMap[id])
if (matches) {
return {
id,
version: matches[2] ?? matches[1],
os: matches[2] && matches[1],
}
}
}
return null
}

View file

@ -0,0 +1,35 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
// When using capture groups the following parts are extracted
// the first is used as the version number, the second as the OS
// Exception: single-group regexes (ie, androidChrome) use the first group as the version.
export const userAgentMap = {
ie: /(?:MSIE|Trident|Trident\/7.0; rv)[ :](\d+)/,
// Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
edge: /^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/,
// Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
firefox: /^Mozilla\/5\.0 \((?![^)]*Android)[^)]*(Windows|OS X|Linux)[^)]+\) Gecko\/[0-9.]+ Firefox\/(\d+)(?:\.\d)?$/,
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
androidChrome: /^Mozilla\/5\.0 \(Linux; Android[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/(\d+)[0-9.]+ (?:Mobile )?Safari\/[0-9.]+$/,
// Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
chrome: /^Mozilla\/5\.0 \((?![^)]*Android)[^)]*(Windows|OS X|Linux)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/(\d+)[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+$/,
// Safari User Agent from http://www.useragentstring.com/pages/Safari/
safari: /^Mozilla\/5\.0 \([^)]*(Windows|OS X)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/([0-9]+)[0-9.]+)? Safari\/[0-9.A-Z]+$/,
iphone: / *CPU +iPhone +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
ipad: /\(iPad; *CPU +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
iosClient: /^Mozilla\/5\.0 \(iOS\) (?:ownCloud|Nextcloud)-iOS.*$/,
androidClient: /^Mozilla\/5\.0 \(Android\) (?:ownCloud|Nextcloud)-android.*$/,
iosTalkClient: /^Mozilla\/5\.0 \(iOS\) Nextcloud-Talk.*$/,
androidTalkClient: /^Mozilla\/5\.0 \(Android\) Nextcloud-Talk.*$/,
// DAVx5/3.3.8-beta2-gplay (2021/01/02; dav4jvm; okhttp/4.9.0) Android/10
davx5: /DAV(?:droid|x5)\/([^ ]+)/,
// 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)
webPirate: /(Sailfish).*WebPirate\/(\d+)/,
// Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0
sailfishBrowser: /(Sailfish).*SailfishBrowser\/(\d+)/,
// Neon 1.0.0+1
neon: /Neon \d+\.\d+\.\d+\+\d+/,
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long