Merge pull request #7323 from vector-im/feature/mna/device-manager-parsing-os

[Device management] Improve the parsing for OS of Desktop/Web sessions (PSG-823)
This commit is contained in:
Maxime NATUREL 2022-10-13 10:39:38 +02:00 committed by GitHub
commit f9eb6a64ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 37 deletions

1
changelog.d/7321.wip Normal file
View file

@ -0,0 +1 @@
[Device management] Improve the parsing for OS of Desktop/Web sessions

View file

@ -74,55 +74,77 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
} }
private fun parseDesktopUserAgent(userAgent: String): DeviceExtendedInfo { private fun parseDesktopUserAgent(userAgent: String): DeviceExtendedInfo {
val browserInfo = parseBrowserInfoFromDesktopUserAgent(userAgent)
val operatingSystem = parseOperatingSystemFromDesktopUserAgent(userAgent)
return DeviceExtendedInfo(
deviceType = DeviceType.DESKTOP,
deviceModel = null,
deviceOperatingSystem = operatingSystem,
clientName = browserInfo.name,
clientVersion = browserInfo.version,
)
}
private data class BrowserInfo(val name: String? = null, val version: String? = null)
private fun parseBrowserInfoFromDesktopUserAgent(userAgent: String): BrowserInfo {
val browserSegments = userAgent.split(" ") val browserSegments = userAgent.split(" ")
val (browserName, browserVersion) = when { return when {
isFirefox(browserSegments) -> { isFirefox(browserSegments) -> {
Pair("Firefox", getBrowserVersion(browserSegments, "Firefox")) BrowserInfo(BROWSER_FIREFOX, getBrowserVersion(browserSegments, BROWSER_FIREFOX))
} }
isEdge(browserSegments) -> { isEdge(browserSegments) -> {
Pair("Edge", getBrowserVersion(browserSegments, "Edge")) BrowserInfo(BROWSER_EDGE, getBrowserVersion(browserSegments, BROWSER_EDGE))
} }
isMobile(browserSegments) -> { isMobile(browserSegments) -> {
when (val name = getMobileBrowserName(browserSegments)) { when (val name = getMobileBrowserName(browserSegments)) {
null -> { null -> {
Pair(null, null) BrowserInfo()
} }
"Safari" -> { BROWSER_SAFARI -> {
Pair(name, getBrowserVersion(browserSegments, "Version")) BrowserInfo(name, getBrowserVersion(browserSegments, "Version"))
} }
else -> { else -> {
Pair(name, getBrowserVersion(browserSegments, name)) BrowserInfo(name, getBrowserVersion(browserSegments, name))
} }
} }
} }
isSafari(browserSegments) -> { isSafari(browserSegments) -> {
Pair("Safari", getBrowserVersion(browserSegments, "Version")) BrowserInfo(BROWSER_SAFARI, getBrowserVersion(browserSegments, "Version"))
} }
else -> { else -> {
when (val name = getRegularBrowserName(browserSegments)) { when (val name = getRegularBrowserName(browserSegments)) {
null -> { null -> {
Pair(null, null) BrowserInfo()
} }
else -> { else -> {
Pair(name, getBrowserVersion(browserSegments, name)) BrowserInfo(name, getBrowserVersion(browserSegments, name))
} }
} }
} }
} }
}
val deviceOperatingSystemSegments = userAgent.substringAfter("(").substringBefore(")").split("; ") private fun parseOperatingSystemFromDesktopUserAgent(userAgent: String): String? {
val deviceOperatingSystem = if (deviceOperatingSystemSegments.getOrNull(1)?.startsWith("Android").orFalse()) { val deviceOperatingSystemSegments = userAgent
deviceOperatingSystemSegments.getOrNull(1) .substringAfter("(")
} else { .substringBefore(")")
deviceOperatingSystemSegments.getOrNull(0) .split("; ")
val firstSegment = deviceOperatingSystemSegments.getOrNull(0).orEmpty()
val secondSegment = deviceOperatingSystemSegments.getOrNull(1).orEmpty()
return when {
// e.g. (Macintosh; Intel Mac OS X 10_15_7) => macOS
firstSegment.startsWith(OPERATING_SYSTEM_MAC_KEYWORD) -> OPERATING_SYSTEM_MAC
// e.g. (Windows NT 10.0; Win64; x64) => Windows
firstSegment.startsWith(OPERATING_SYSTEM_WINDOWS_KEYWORD) -> OPERATING_SYSTEM_WINDOWS_KEYWORD
// e.g. (iPad; CPU OS 8_4_1 like Mac OS X) => iOS
firstSegment.startsWith(DEVICE_IPAD_KEYWORD) || firstSegment.startsWith(DEVICE_IPHONE_KEYWORD) -> OPERATING_SYSTEM_IOS
// e.g. (Linux; Android 9; SM-G973U Build/PPR1.180610.011) => Android
secondSegment.startsWith(OPERATING_SYSTEM_ANDROID_KEYWORD) -> OPERATING_SYSTEM_ANDROID_KEYWORD
else -> null
} }
return DeviceExtendedInfo(
deviceType = DeviceType.DESKTOP,
deviceModel = null,
deviceOperatingSystem = deviceOperatingSystem,
clientName = browserName,
clientVersion = browserVersion,
)
} }
private fun parseWebUserAgent(userAgent: String): DeviceExtendedInfo { private fun parseWebUserAgent(userAgent: String): DeviceExtendedInfo {
@ -136,7 +158,7 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
} }
private fun isFirefox(browserSegments: List<String>): Boolean { private fun isFirefox(browserSegments: List<String>): Boolean {
return browserSegments.lastOrNull()?.startsWith("Firefox").orFalse() return browserSegments.lastOrNull()?.startsWith(BROWSER_FIREFOX).orFalse()
} }
private fun getBrowserVersion(browserSegments: List<String>, browserName: String): String? { private fun getBrowserVersion(browserSegments: List<String>, browserName: String): String? {
@ -148,11 +170,11 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
} }
private fun isEdge(browserSegments: List<String>): Boolean { private fun isEdge(browserSegments: List<String>): Boolean {
return browserSegments.lastOrNull()?.startsWith("Edge").orFalse() return browserSegments.lastOrNull()?.startsWith(BROWSER_EDGE).orFalse()
} }
private fun isSafari(browserSegments: List<String>): Boolean { private fun isSafari(browserSegments: List<String>): Boolean {
return browserSegments.lastOrNull()?.startsWith("Safari").orFalse() && return browserSegments.lastOrNull()?.startsWith(BROWSER_SAFARI).orFalse() &&
browserSegments.getOrNull(browserSegments.size - 2)?.startsWith("Version").orFalse() browserSegments.getOrNull(browserSegments.size - 2)?.startsWith("Version").orFalse()
} }
@ -163,7 +185,7 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
private fun getMobileBrowserName(browserSegments: List<String>): String? { private fun getMobileBrowserName(browserSegments: List<String>): String? {
val possibleBrowserName = browserSegments.getOrNull(browserSegments.size - 3)?.split("/")?.firstOrNull() val possibleBrowserName = browserSegments.getOrNull(browserSegments.size - 3)?.split("/")?.firstOrNull()
return if (possibleBrowserName == "Version") { return if (possibleBrowserName == "Version") {
"Safari" BROWSER_SAFARI
} else { } else {
possibleBrowserName possibleBrowserName
} }
@ -187,5 +209,17 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 // Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
private const val WEB_KEYWORD = "Mozilla/" private const val WEB_KEYWORD = "Mozilla/"
private const val OPERATING_SYSTEM_MAC_KEYWORD = "Macintosh"
private const val OPERATING_SYSTEM_MAC = "macOS"
private const val OPERATING_SYSTEM_IOS = "iOS"
private const val OPERATING_SYSTEM_WINDOWS_KEYWORD = "Windows"
private const val OPERATING_SYSTEM_ANDROID_KEYWORD = "Android"
private const val DEVICE_IPAD_KEYWORD = "iPad"
private const val DEVICE_IPHONE_KEYWORD = "iPhone"
private const val BROWSER_FIREFOX = "Firefox"
private const val BROWSER_SAFARI = "Safari"
private const val BROWSER_EDGE = "Edge"
} }
} }

View file

@ -62,8 +62,8 @@ private val A_USER_AGENT_LIST_FOR_DESKTOP = listOf(
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36",
) )
private val AN_EXPECTED_RESULT_LIST_FOR_DESKTOP = listOf( private val AN_EXPECTED_RESULT_LIST_FOR_DESKTOP = listOf(
DeviceExtendedInfo(DeviceType.DESKTOP, null, "Macintosh", "Electron", "20.1.1"), DeviceExtendedInfo(DeviceType.DESKTOP, null, "macOS", "Electron", "20.1.1"),
DeviceExtendedInfo(DeviceType.DESKTOP, null, "Windows NT 10.0", "Electron", "20.1.1"), DeviceExtendedInfo(DeviceType.DESKTOP, null, "Windows", "Electron", "20.1.1"),
) )
private val A_USER_AGENT_LIST_FOR_WEB = listOf( private val A_USER_AGENT_LIST_FOR_WEB = listOf(
@ -78,15 +78,15 @@ private val A_USER_AGENT_LIST_FOR_WEB = listOf(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246",
) )
private val AN_EXPECTED_RESULT_LIST_FOR_WEB = listOf( private val AN_EXPECTED_RESULT_LIST_FOR_WEB = listOf(
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Chrome", "104.0.5112.102"), DeviceExtendedInfo(DeviceType.WEB, null, "macOS", "Chrome", "104.0.5112.102"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 10.0", "Chrome", "104.0.5112.102"), DeviceExtendedInfo(DeviceType.WEB, null, "Windows", "Chrome", "104.0.5112.102"),
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Firefox", "39.0"), DeviceExtendedInfo(DeviceType.WEB, null, "macOS", "Firefox", "39.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Safari", "8.0.3"), DeviceExtendedInfo(DeviceType.WEB, null, "macOS", "Safari", "8.0.3"),
DeviceExtendedInfo(DeviceType.WEB, null, "Android 9", "Chrome", "69.0.3497.100"), DeviceExtendedInfo(DeviceType.WEB, null, "Android", "Chrome", "69.0.3497.100"),
DeviceExtendedInfo(DeviceType.WEB, null, "iPad", "Safari", "8.0"), DeviceExtendedInfo(DeviceType.WEB, null, "iOS", "Safari", "8.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "iPhone", "Safari", "8.0"), DeviceExtendedInfo(DeviceType.WEB, null, "iOS", "Safari", "8.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 6.0", "Firefox", "40.0"), DeviceExtendedInfo(DeviceType.WEB, null, "Windows", "Firefox", "40.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 10.0", "Edge", "12.246"), DeviceExtendedInfo(DeviceType.WEB, null, "Windows", "Edge", "12.246"),
) )
private val AN_UNKNOWN_USER_AGENT_LIST = listOf( private val AN_UNKNOWN_USER_AGENT_LIST = listOf(