Module: Sessions::DeviceDisplay
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/sessions/models/concerns/device_display.rb
Overview
Human, honest device presentation — shared by the registry rows (Sessions::Model) and the trail (Sessions::Event), which carry the same parsed device columns:
"Chrome 137 on macOS"
"MyApp 2.4.1 on Pixel 8 (Android 16)"
"iPhone (iOS 19.5)"
Frozen-UA tokens are never rendered as facts — versions appear only where they’re real (see Sessions::Device).
Instance Method Summary collapse
-
#auth_method_label ⇒ Object
“Google”, “GitHub”, “password”, “passkey”… for “Signed in via %method” copy.
- #bot? ⇒ Boolean
-
#country_flag ⇒ Object
“🇪🇸” — derived from country_code at render time; no column needed.
- #device_name ⇒ Object
- #hotwire_native? ⇒ Boolean
-
#location ⇒ Object
“Madrid, Spain” — or nil when geolocation is unavailable (the UI omits location cleanly).
- #native_android? ⇒ Boolean
- #native_ios? ⇒ Boolean
-
#second_factor ⇒ Object
The second factor that protected this login, when one did: “totp” (authenticator apps via devise-two-factor — detected automatically), “backup_code”, or whatever the host tagged (“webauthn” for security keys / Touch ID as a second factor — see the README’s two-factor recipes).
- #second_factor? ⇒ Boolean
-
#source_line(ip: true, separator: " · ") ⇒ Object
“🇪🇸 Madrid, Spain · IP 83.45.112.7 · Firefox 139 on Windows” — the one-line WHERE-then-WHAT of a login, ready for security emails, notification bodies, and admin lists.
- #via_oauth? ⇒ Boolean
- #via_password? ⇒ Boolean
- #web? ⇒ Boolean
Instance Method Details
#auth_method_label ⇒ Object
“Google”, “GitHub”, “password”, “passkey”… for “Signed in via %method” copy. nil when the method is unknown (the UI omits the clause).
104 105 106 107 108 109 110 |
# File 'lib/sessions/models/concerns/device_display.rb', line 104 def auth_method_label method = try(:auth_method) return nil if method.blank? || method == "unknown" return try(:auth_provider).to_s.titleize if via_oauth? && try(:auth_provider).present? I18n.t("sessions.auth_methods.#{method}", default: method.humanize.downcase) end |
#bot? ⇒ Boolean
72 73 74 |
# File 'lib/sessions/models/concerns/device_display.rb', line 72 def bot? try(:device_type) == "bot" end |
#country_flag ⇒ Object
“🇪🇸” — derived from country_code at render time; no column needed.
38 39 40 41 42 43 |
# File 'lib/sessions/models/concerns/device_display.rb', line 38 def country_flag code = try(:country_code).to_s return nil unless code.match?(/\A[A-Za-z]{2}\z/) code.upcase.each_codepoint.map { |codepoint| (codepoint + 0x1F1A5).chr(Encoding::UTF_8) }.join end |
#device_name ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/sessions/models/concerns/device_display.rb', line 17 def device_name return sessions_t("bot", default: "Bot (%{name})", name: try(:browser_name) || "unknown") if bot? return sessions_native_device_name if hotwire_native? client = [try(:browser_name), try(:browser_version)].compact.join(" ").presence platform = sessions_os_label if client && platform sessions_t("composite", default: "%{client} on %{platform}", client: client, platform: platform) else client || platform || sessions_t("unknown", default: "Unknown device") end end |
#hotwire_native? ⇒ Boolean
60 61 62 |
# File 'lib/sessions/models/concerns/device_display.rb', line 60 def hotwire_native? try(:device_type).to_s.start_with?("native_") end |
#location ⇒ Object
“Madrid, Spain” — or nil when geolocation is unavailable (the UI omits location cleanly).
33 34 35 |
# File 'lib/sessions/models/concerns/device_display.rb', line 33 def location [try(:city), try(:country_name)].compact_blank.join(", ").presence end |
#native_android? ⇒ Boolean
68 69 70 |
# File 'lib/sessions/models/concerns/device_display.rb', line 68 def native_android? try(:device_type) == "native_android" end |
#native_ios? ⇒ Boolean
64 65 66 |
# File 'lib/sessions/models/concerns/device_display.rb', line 64 def native_ios? try(:device_type) == "native_ios" end |
#second_factor ⇒ Object
The second factor that protected this login, when one did: “totp” (authenticator apps via devise-two-factor — detected automatically), “backup_code”, or whatever the host tagged (“webauthn” for security keys / Touch ID as a second factor — see the README’s two-factor recipes). nil for single-factor logins.
93 94 95 96 |
# File 'lib/sessions/models/concerns/device_display.rb', line 93 def second_factor detail = try(:auth_detail) detail.is_a?(Hash) ? detail["second_factor"].presence : nil end |
#second_factor? ⇒ Boolean
98 99 100 |
# File 'lib/sessions/models/concerns/device_display.rb', line 98 def second_factor? second_factor.present? end |
#source_line(ip: true, separator: " · ") ⇒ Object
“🇪🇸 Madrid, Spain · IP 83.45.112.7 · Firefox 139 on Windows” — the one-line WHERE-then-WHAT of a login, ready for security emails, notification bodies, and admin lists. Location leads (people recognize places; browser version numbers mean nothing to them), the IP is the verifiable fact, the device closes. Each part drops out cleanly when the record lacks it (no geo in dev, no UA on odd clients). ‘ip: false` for compact UI like notification feed rows; `separator:` for plain-text contexts.
53 54 55 56 57 58 |
# File 'lib/sessions/models/concerns/device_display.rb', line 53 def source_line(ip: true, separator: " · ") located = [country_flag, location].compact.join(" ").presence address = ip ? (try(:ip_address) || try(:last_seen_ip)).presence : nil [located, address && "IP #{address}", device_name].compact.join(separator).presence end |
#via_oauth? ⇒ Boolean
80 81 82 |
# File 'lib/sessions/models/concerns/device_display.rb', line 80 def via_oauth? try(:auth_method) == "oauth" end |
#via_password? ⇒ Boolean
84 85 86 |
# File 'lib/sessions/models/concerns/device_display.rb', line 84 def via_password? try(:auth_method) == "password" end |
#web? ⇒ Boolean
76 77 78 |
# File 'lib/sessions/models/concerns/device_display.rb', line 76 def web? !hotwire_native? && !bot? end |