# frozen_string_literal: true module Sessions # Normalizza e applica fingerprint del client (OS, app, device, operatore) # sulla sessione, a create e/o a ogni telemetry. class ApplyClientInfo OS_VALUES = %w[android ios].freeze MAX_LEN = 80 ATTRS = %i[ client_os app_version app_build device_manufacturer device_model os_version carrier ].freeze def self.call(session, raw) new(session, raw).call end def initialize(session, raw) @session = session @raw = normalize_hash(raw) end def call attrs = extract_attrs return @session if attrs.empty? @session.assign_attributes(attrs) @session.save! if @session.persisted? && @session.changed? @session end private def normalize_hash(raw) return {} if raw.blank? data = raw.respond_to?(:to_unsafe_h) ? raw.to_unsafe_h : raw data = data.to_h if data.respond_to?(:to_h) data.with_indifferent_access rescue StandardError {} end def extract_attrs attrs = {} os = @raw[:os].presence || @raw[:client_os].presence os = os.to_s.downcase.strip attrs[:client_os] = os if OS_VALUES.include?(os) { app_version: %i[app_version version], app_build: %i[app_build build build_number], device_manufacturer: %i[device_manufacturer manufacturer], device_model: %i[device_model model], os_version: %i[os_version system_version], carrier: %i[carrier network_operator operator] }.each do |column, keys| value = keys.map { |k| @raw[k] }.find(&:present?) next if value.blank? attrs[column] = truncate(value.to_s.strip) end attrs end def truncate(value) value.bytesize <= MAX_LEN ? value : value.byteslice(0, MAX_LEN) end end end