## Description Sessions created by logins from the Chatwoot Mobile app currently render as "Unknown Device" in the dashboard sessions UI. The mobile app's HTTP layer sends: - Android: `User-Agent: okhttp/4.9.2` - iOS: `User-Agent: Chatwoot/<build> CFNetwork/<v> Darwin/<v>` Neither pattern is classifiable by the `browser` gem, so \`browser_name\`, \`platform_name\`, and \`device_name\` all end up "Unknown". This change adds a backend-only fallback in \`UserSessionTrackingService\`. When \`Browser.new(ua)\` returns "Unknown Browser" and the UA matches a known Chatwoot Mobile pattern, the labels are overridden to \`Chatwoot Mobile\` + \`Android\` / \`iPhone\`. The Vue sessions list (\`ActiveSessions.vue\`) then renders "Chatwoot Mobile on Android" or "Chatwoot Mobile on iPhone" with the smartphone icon. This is the immediate floor. A follow-up will add structured \`X-Chatwoot-*\` headers from the mobile app so we can render full version + device model. Fixes INF-75. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - Added specs ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules
64 lines
1.8 KiB
Ruby
64 lines
1.8 KiB
Ruby
class UserSessionTrackingService
|
|
# CFNetwork UAs cannot distinguish iPhone from iPad; both get labelled iPhone here.
|
|
LEGACY_MOBILE_UAS = [
|
|
{ match: %r{\Aokhttp/}, platform: 'Android', device: 'Android' },
|
|
{ match: %r{\AChatwoot/.*CFNetwork.*Darwin}, platform: 'iPhone', device: 'iPhone' }
|
|
].freeze
|
|
private_constant :LEGACY_MOBILE_UAS
|
|
|
|
def initialize(user:, request:, client_id:)
|
|
@user = user
|
|
@request = request
|
|
@client_id = client_id
|
|
end
|
|
|
|
def create_or_update!
|
|
session = @user.user_sessions.find_or_initialize_by(client_id: @client_id)
|
|
session.assign_attributes(session_attributes)
|
|
session.last_activity_at = Time.current
|
|
session.save!
|
|
UserSessionIpLookupJob.perform_later(session) if session.ip_address.present?
|
|
session
|
|
end
|
|
|
|
def update_activity!
|
|
session = @user.user_sessions.find_by(client_id: @client_id)
|
|
return unless session&.should_update_activity?
|
|
|
|
session.update_columns(last_activity_at: Time.current) # rubocop:disable Rails/SkipsModelValidations
|
|
end
|
|
|
|
private
|
|
|
|
def session_attributes
|
|
browser = Browser.new(@request.user_agent)
|
|
|
|
attrs = {
|
|
ip_address: @request.remote_ip,
|
|
user_agent: @request.user_agent,
|
|
browser_name: browser.name,
|
|
browser_version: browser.full_version,
|
|
device_name: browser.device.name,
|
|
platform_name: browser.platform.name,
|
|
platform_version: browser.platform.version
|
|
}
|
|
|
|
patch_for_legacy_mobile(attrs)
|
|
end
|
|
|
|
def patch_for_legacy_mobile(attrs)
|
|
return attrs unless attrs[:browser_name] == 'Unknown Browser'
|
|
|
|
hit = LEGACY_MOBILE_UAS.find { |m| @request.user_agent.to_s.match?(m[:match]) }
|
|
return attrs unless hit
|
|
|
|
attrs.merge(
|
|
browser_name: 'Chatwoot Mobile',
|
|
browser_version: nil,
|
|
platform_name: hit[:platform],
|
|
platform_version: nil,
|
|
device_name: hit[:device]
|
|
)
|
|
end
|
|
end
|