Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Device management] Improve the parsing for OS of Desktop/Web sessions (PSG-823) #7323

Merged
merged 6 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/7321.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Device management] Improve the parsing for OS of Desktop/Web sessions
Original file line number Diff line number Diff line change
Expand Up @@ -74,55 +74,77 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
}

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 (browserName, browserVersion) = when {
return when {
isFirefox(browserSegments) -> {
Pair("Firefox", getBrowserVersion(browserSegments, "Firefox"))
BrowserInfo(BROWSER_FIREFOX, getBrowserVersion(browserSegments, BROWSER_FIREFOX))
}
isEdge(browserSegments) -> {
Pair("Edge", getBrowserVersion(browserSegments, "Edge"))
BrowserInfo(BROWSER_EDGE, getBrowserVersion(browserSegments, BROWSER_EDGE))
}
isMobile(browserSegments) -> {
when (val name = getMobileBrowserName(browserSegments)) {
null -> {
Pair(null, null)
BrowserInfo()
}
"Safari" -> {
Pair(name, getBrowserVersion(browserSegments, "Version"))
BROWSER_SAFARI -> {
BrowserInfo(name, getBrowserVersion(browserSegments, "Version"))
}
else -> {
Pair(name, getBrowserVersion(browserSegments, name))
BrowserInfo(name, getBrowserVersion(browserSegments, name))
}
}
}
isSafari(browserSegments) -> {
Pair("Safari", getBrowserVersion(browserSegments, "Version"))
BrowserInfo(BROWSER_SAFARI, getBrowserVersion(browserSegments, "Version"))
}
else -> {
when (val name = getRegularBrowserName(browserSegments)) {
null -> {
Pair(null, null)
BrowserInfo()
}
else -> {
Pair(name, getBrowserVersion(browserSegments, name))
BrowserInfo(name, getBrowserVersion(browserSegments, name))
}
}
}
}
}

val deviceOperatingSystemSegments = userAgent.substringAfter("(").substringBefore(")").split("; ")
val deviceOperatingSystem = if (deviceOperatingSystemSegments.getOrNull(1)?.startsWith("Android").orFalse()) {
deviceOperatingSystemSegments.getOrNull(1)
} else {
deviceOperatingSystemSegments.getOrNull(0)
private fun parseOperatingSystemFromDesktopUserAgent(userAgent: String): String? {
val deviceOperatingSystemSegments = userAgent
.substringAfter("(")
.substringBefore(")")
.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 {
Expand All @@ -136,7 +158,7 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
}

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? {
Expand All @@ -148,11 +170,11 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
}

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 {
return browserSegments.lastOrNull()?.startsWith("Safari").orFalse() &&
return browserSegments.lastOrNull()?.startsWith(BROWSER_SAFARI).orFalse() &&
browserSegments.getOrNull(browserSegments.size - 2)?.startsWith("Version").orFalse()
}

Expand All @@ -163,7 +185,7 @@ class ParseDeviceUserAgentUseCase @Inject constructor() {
private fun getMobileBrowserName(browserSegments: List<String>): String? {
val possibleBrowserName = browserSegments.getOrNull(browserSegments.size - 3)?.split("/")?.firstOrNull()
return if (possibleBrowserName == "Version") {
"Safari"
BROWSER_SAFARI
} else {
possibleBrowserName
}
Expand All @@ -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
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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
private val AN_EXPECTED_RESULT_LIST_FOR_DESKTOP = listOf(
DeviceExtendedInfo(DeviceType.DESKTOP, null, "Macintosh", "Electron", "20.1.1"),
DeviceExtendedInfo(DeviceType.DESKTOP, null, "Windows NT 10.0", "Electron", "20.1.1"),
DeviceExtendedInfo(DeviceType.DESKTOP, null, "macOS", "Electron", "20.1.1"),
DeviceExtendedInfo(DeviceType.DESKTOP, null, "Windows", "Electron", "20.1.1"),
)

private val A_USER_AGENT_LIST_FOR_WEB = listOf(
Expand All @@ -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",
)
private val AN_EXPECTED_RESULT_LIST_FOR_WEB = listOf(
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Chrome", "104.0.5112.102"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 10.0", "Chrome", "104.0.5112.102"),
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Firefox", "39.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Macintosh", "Safari", "8.0.3"),
DeviceExtendedInfo(DeviceType.WEB, null, "Android 9", "Chrome", "69.0.3497.100"),
DeviceExtendedInfo(DeviceType.WEB, null, "iPad", "Safari", "8.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "iPhone", "Safari", "8.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 6.0", "Firefox", "40.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows NT 10.0", "Edge", "12.246"),
DeviceExtendedInfo(DeviceType.WEB, null, "macOS", "Chrome", "104.0.5112.102"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows", "Chrome", "104.0.5112.102"),
DeviceExtendedInfo(DeviceType.WEB, null, "macOS", "Firefox", "39.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "macOS", "Safari", "8.0.3"),
DeviceExtendedInfo(DeviceType.WEB, null, "Android", "Chrome", "69.0.3497.100"),
DeviceExtendedInfo(DeviceType.WEB, null, "iOS", "Safari", "8.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "iOS", "Safari", "8.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows", "Firefox", "40.0"),
DeviceExtendedInfo(DeviceType.WEB, null, "Windows", "Edge", "12.246"),
)

private val AN_UNKNOWN_USER_AGENT_LIST = listOf(
Expand Down