Compare commits

...

1 Commits

Author SHA1 Message Date
Chandrika Mohan 65316c3391 fix(settings): correctly detect Chrome on Android in devices & sessions
Signed-off-by: Chandrika Mohan <chandrikalov@gmail.com>

common logic to detect ua

Signed-off-by: Chandrika Mohan <chandrikalov@gmail.com>
2026-03-18 16:07:41 +01:00
9 changed files with 130 additions and 45 deletions
@@ -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',
})
})
})
+2 -40
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)
},
/**
@@ -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
}
+35
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
+1 -1
View File
@@ -1,5 +1,5 @@
/* extracted by css-entry-points-plugin */
@import './theming-theming-settings-personal-BGvZ2soH.chunk.css';
@import './theming-theming-settings-personal-Tw2nBJ_u.chunk.css';
@import './createElementId-DhjFt1I9-C_oBIsvc.chunk.css';
@import './autolink-U5pBzLgI-R3us1MM8.chunk.css';
@import './NcModal-DHryP_87-CU2wYsLf.chunk.css';
@@ -1 +0,0 @@
.theming__preview[data-v-13bf6aae]{--ratio: 16;position:relative;display:flex;justify-content:flex-start}.theming__preview[data-v-13bf6aae],.theming__preview[data-v-13bf6aae] *{-webkit-user-select:none;user-select:none}.theming__preview-image[data-v-13bf6aae]{flex-basis:calc(16px * var(--ratio));flex-shrink:0;height:calc(10px * var(--ratio));margin-inline-end:var(--gap);cursor:pointer;border-radius:var(--border-radius);background-repeat:no-repeat;background-position:top left;background-size:cover}.theming__preview-explanation[data-v-13bf6aae]{margin-bottom:10px}.theming__preview-description[data-v-13bf6aae]{display:flex;flex-direction:column}.theming__preview-description h3[data-v-13bf6aae]{font-weight:700;margin-bottom:0}.theming__preview-description label[data-v-13bf6aae]{padding:12px 0}.theming__preview-warning[data-v-13bf6aae]{background-color:var(--color-warning);color:var(--color-warning-text)}@media(max-width:682.6666666667px){.theming__preview[data-v-13bf6aae]{flex-direction:column}.theming__preview-image[data-v-13bf6aae]{margin:0}}._userSectionAppMenu__selector_1hkl7_2{margin-block:12px}._backgroundSelect_l1nhm_2{display:flex;flex-wrap:wrap;justify-content:center}._backgroundSelect_l1nhm_2 ._backgroundSelect__entry_l1nhm_7{display:flex;flex-direction:column;justify-content:center;align-items:center;overflow:hidden;height:96px;width:168px;margin:var(--default-grid-baseline);text-align:center;overflow-wrap:break-word;-webkit-hyphens:auto;hyphens:auto;border:2px solid var(--color-main-background);border-radius:var(--border-radius-large);background-position:center center;background-size:cover;--color-content: var(--color-background-plain-text)}._backgroundSelect_l1nhm_2 ._backgroundSelect__entry_l1nhm_7:hover,._backgroundSelect_l1nhm_2 ._backgroundSelect__entry_l1nhm_7:focus{outline:2px solid var(--color-main-text)!important;border-color:var(--color-main-background)!important}._backgroundSelect_l1nhm_2 ._backgroundSelect__entry_l1nhm_7>*{color:var(--color-content);opacity:1}._backgroundSelect_l1nhm_2 ._backgroundSelect__entryColor_l1nhm_38{background-color:var(--color-background-plain)}._backgroundSelect_l1nhm_2 ._backgroundSelect__entryFilePicker_l1nhm_42{--color-content: var(--color-main-text);background-color:var(--color-background-dark)}._backgroundSelect_l1nhm_2 ._backgroundSelect__entryFilePicker_l1nhm_42[aria-pressed=true]{--color-content: var(--color-background-plain-text);background-image:var(--image-background)}._backgroundSelect_l1nhm_2 ._backgroundSelect__entryDefault_l1nhm_52{background-image:linear-gradient(to bottom,#17171780,#17171780),var(--cf2ff408)}._userPrimaryColor_3oh6f_1{display:flex;flex-direction:row;flex-wrap:wrap;gap:12px}._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;margin:0!important;background-color:var(--color-primary);color:var(--color-primary-text);height:96px;width:168px;overflow-wrap:break-word;-webkit-hyphens:auto;hyphens:auto;border:2px solid var(--color-main-background);border-radius:var(--border-radius-large)}._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8:active{background-color:var(--color-primary-hover)!important}._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8:hover,._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8:focus,._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8:focus-visible{border-color:var(--color-main-background)!important;outline:2px solid var(--color-main-text)!important}.theming p[data-v-50195b4f]{max-width:800px}.theming[data-v-50195b4f] a{font-weight:700}.theming[data-v-50195b4f] a:hover,.theming[data-v-50195b4f] a:focus{text-decoration:underline}.theming__preview-list[data-v-50195b4f]{--gap: 30px;display:grid;margin-top:var(--gap);column-gap:var(--gap);row-gap:var(--gap)}.background__grid[data-v-50195b4f]{margin-top:30px}@media(max-width:1440px){.theming__preview-list[data-v-50195b4f]{display:flex;flex-direction:column}}
@@ -0,0 +1 @@
.theming__preview[data-v-13bf6aae]{--ratio: 16;position:relative;display:flex;justify-content:flex-start}.theming__preview[data-v-13bf6aae],.theming__preview[data-v-13bf6aae] *{-webkit-user-select:none;user-select:none}.theming__preview-image[data-v-13bf6aae]{flex-basis:calc(16px * var(--ratio));flex-shrink:0;height:calc(10px * var(--ratio));margin-inline-end:var(--gap);cursor:pointer;border-radius:var(--border-radius);background-repeat:no-repeat;background-position:top left;background-size:cover}.theming__preview-explanation[data-v-13bf6aae]{margin-bottom:10px}.theming__preview-description[data-v-13bf6aae]{display:flex;flex-direction:column}.theming__preview-description h3[data-v-13bf6aae]{font-weight:700;margin-bottom:0}.theming__preview-description label[data-v-13bf6aae]{padding:12px 0}.theming__preview-warning[data-v-13bf6aae]{background-color:var(--color-warning);color:var(--color-warning-text)}@media(max-width:682.6666666667px){.theming__preview[data-v-13bf6aae]{flex-direction:column}.theming__preview-image[data-v-13bf6aae]{margin:0}}._userSectionAppMenu__selector_1hkl7_2{margin-block:12px}._backgroundSelect_l1nhm_2{display:flex;flex-wrap:wrap;justify-content:center}._backgroundSelect_l1nhm_2 ._backgroundSelect__entry_l1nhm_7{display:flex;flex-direction:column;justify-content:center;align-items:center;overflow:hidden;height:96px;width:168px;margin:var(--default-grid-baseline);text-align:center;overflow-wrap:break-word;hyphens:auto;border:2px solid var(--color-main-background);border-radius:var(--border-radius-large);background-position:center center;background-size:cover;--color-content: var(--color-background-plain-text)}._backgroundSelect_l1nhm_2 ._backgroundSelect__entry_l1nhm_7:hover,._backgroundSelect_l1nhm_2 ._backgroundSelect__entry_l1nhm_7:focus{outline:2px solid var(--color-main-text)!important;border-color:var(--color-main-background)!important}._backgroundSelect_l1nhm_2 ._backgroundSelect__entry_l1nhm_7>*{color:var(--color-content);opacity:1}._backgroundSelect_l1nhm_2 ._backgroundSelect__entryColor_l1nhm_38{background-color:var(--color-background-plain)}._backgroundSelect_l1nhm_2 ._backgroundSelect__entryFilePicker_l1nhm_42{--color-content: var(--color-main-text);background-color:var(--color-background-dark)}._backgroundSelect_l1nhm_2 ._backgroundSelect__entryFilePicker_l1nhm_42[aria-pressed=true]{--color-content: var(--color-background-plain-text);background-image:var(--image-background)}._backgroundSelect_l1nhm_2 ._backgroundSelect__entryDefault_l1nhm_52{background-image:linear-gradient(to bottom,#17171780,#17171780),var(--cf2ff408)}._userPrimaryColor_3oh6f_1{display:flex;flex-direction:row;flex-wrap:wrap;gap:12px}._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;margin:0!important;background-color:var(--color-primary);color:var(--color-primary-text);height:96px;width:168px;overflow-wrap:break-word;hyphens:auto;border:2px solid var(--color-main-background);border-radius:var(--border-radius-large)}._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8:active{background-color:var(--color-primary-hover)!important}._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8:hover,._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8:focus,._userPrimaryColor_3oh6f_1 ._userPrimaryColor__trigger_3oh6f_8:focus-visible{border-color:var(--color-main-background)!important;outline:2px solid var(--color-main-text)!important}.theming p[data-v-50195b4f]{max-width:800px}.theming[data-v-50195b4f] a{font-weight:700}.theming[data-v-50195b4f] a:hover,.theming[data-v-50195b4f] a:focus{text-decoration:underline}.theming__preview-list[data-v-50195b4f]{--gap: 30px;display:grid;margin-top:var(--gap);column-gap:var(--gap);row-gap:var(--gap)}.background__grid[data-v-50195b4f]{margin-top:30px}@media(max-width:1440px){.theming__preview-list[data-v-50195b4f]{display:flex;flex-direction:column}}