From eb4b3442a4cc6d05455698b83528b1db079631ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 2 Nov 2026 22:58:49 +0400 Subject: [PATCH] Determine device names with device_detector Replace the hand-rolled user-agent regexes with the device_detector gem. Its UA database recognises many more clients (and, e.g., GNOME Web when its UA carries the Epiphany token). WebKit "Safari" user agents on Linux (WebKitGTK, which GNOME Web sends) are labelled "WebKit on Linux", since Safari never runs on Linux. Part of #254 --- Gemfile | 1 + Gemfile.lock | 2 ++ app/models/remote_storage_authorization.rb | 26 +++++++------------ .../remote_storage_authorization_spec.rb | 22 ++++++++++++++-- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/Gemfile b/Gemfile index 5ec961f..e41a186 100644 --- a/Gemfile +++ b/Gemfile @@ -43,6 +43,7 @@ gem 'flipper-ui' gem 'gpgme', '~> 2.0.24' gem 'zbase32', '~> 0.1.1' gem 'kramdown' +gem 'device_detector' # HTTP requests gem 'faraday' diff --git a/Gemfile.lock b/Gemfile.lock index 07a76dc..9838525 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -136,6 +136,7 @@ GEM database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) date (3.5.1) + device_detector (1.1.3) devise (5.0.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -543,6 +544,7 @@ DEPENDENCIES capybara cssbundling-rails database_cleaner + device_detector devise (~> 5.0) devise_ldap_authenticatable discourse_api diff --git a/app/models/remote_storage_authorization.rb b/app/models/remote_storage_authorization.rb index e5d4fc8..7965ecb 100644 --- a/app/models/remote_storage_authorization.rb +++ b/app/models/remote_storage_authorization.rb @@ -47,25 +47,17 @@ class RemoteStorageAuthorization < ApplicationRecord def device_name return nil if user_agent.blank? - browser = - case user_agent - when /Firefox/ then "Firefox" - when /Edg\// then "Edge" - when /Chrome/ then "Chrome" - when /Safari/ then "Safari" - else "Unknown browser" - end + client = DeviceDetector.new(user_agent) + name = client.name.presence + os = client.os_name.presence + os = "Linux" if os == "GNU/Linux" + os = "macOS" if os == "Mac" - os = - case user_agent - when /Android/ then "Android" - when /iPhone|iPad|iPod/ then "iOS" - when /Mac OS X/ then "macOS" - when /Windows/ then "Windows" - when /Linux/ then "Linux" - end + # Safari never runs on Linux. A WebKit "Safari" user agent on Linux is a + # WebKitGTK browser (e.g. GNOME Web), which sends a Safari-compatible UA. + name = "WebKit" if name == "Safari" && os == "Linux" - os ? "#{browser} on #{os}" : browser + [name, os].compact.join(" on ").presence end def delete_token_from_redis diff --git a/spec/models/remote_storage_authorization_spec.rb b/spec/models/remote_storage_authorization_spec.rb index 620cd41..4ce3b2d 100644 --- a/spec/models/remote_storage_authorization_spec.rb +++ b/spec/models/remote_storage_authorization_spec.rb @@ -352,10 +352,28 @@ RSpec.describe RemoteStorageAuthorization, type: :model do expect(auth_with_user_agent(ua).device_name).to eq("Chrome on macOS") end - it "recognizes Safari on iOS" do + it "recognizes Mobile Safari on iOS" do ua = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) " \ "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1" - expect(auth_with_user_agent(ua).device_name).to eq("Safari on iOS") + expect(auth_with_user_agent(ua).device_name).to eq("Mobile Safari on iOS") + end + + it "recognizes GNOME Web on Linux (Epiphany's Safari-like user agent)" do + ua = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) " \ + "Version/16.0 Safari/605.1.15 Epiphany/45.0" + expect(auth_with_user_agent(ua).device_name).to eq("GNOME Web on Linux") + end + + it "treats a WebKit Safari user agent on Linux as WebKit (WebKitGTK)" do + ua = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/605.1.15 (KHTML, like Gecko) " \ + "Version/60.5 Safari/605.1.15" + expect(auth_with_user_agent(ua).device_name).to eq("WebKit on Linux") + end + + it "keeps Safari on macOS as Safari" do + ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 " \ + "(KHTML, like Gecko) Version/17.1 Safari/605.1.15" + expect(auth_with_user_agent(ua).device_name).to eq("Safari on macOS") end it "falls back to the browser when the OS is unknown" do