diff --git a/app/controllers/api/hackatime/v1/hackatime_controller.rb b/app/controllers/api/hackatime/v1/hackatime_controller.rb index 50eef0e..1ca1c30 100644 --- a/app/controllers/api/hackatime/v1/hackatime_controller.rb +++ b/app/controllers/api/hackatime/v1/hackatime_controller.rb @@ -234,6 +234,10 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController heartbeat_array.each do |heartbeat| source_type = :direct_entry + # Fallback to :plugin if :user_agent is not set + fallback_value = heartbeat.delete(:plugin) + heartbeat[:user_agent] ||= fallback_value + parsed_ua = WakatimeService.parse_user_agent(heartbeat[:user_agent]) # special case: if the entity is "test.txt", this is a test heartbeat @@ -318,7 +322,8 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController :project_root_count, :time, :type, - :user_agent + :user_agent, + :plugin ] end @@ -328,8 +333,9 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController { heartbeats: params.permit(_json: [ *heartbeat_keys ])[:_json] } elsif request.content_type&.include?("text/plain") && request.raw_post.present? # Handle text/plain requests by parsing JSON directly - parsed_json = JSON.parse(request.raw_post) rescue [] - { heartbeats: parsed_json } + parsed_json = JSON.parse(request.raw_post, symbolize_names: true) rescue [] + filtered_json = parsed_json.map { |hb| hb.slice(*heartbeat_keys) } + { heartbeats: filtered_json } else params.require(:hackatime).permit( heartbeats: [ @@ -345,7 +351,7 @@ class Api::Hackatime::V1::HackatimeController < ApplicationController params[:_json].first.permit(*heartbeat_keys) elsif request.content_type&.include?("text/plain") && request.raw_post.present? # Handle text/plain requests by parsing JSON directly - parsed_json = JSON.parse(request.raw_post) rescue {} + parsed_json = JSON.parse(request.raw_post, symbolize_names: true) rescue {} parsed_json = [ parsed_json ] unless parsed_json.is_a?(Array) parsed_json.first&.with_indifferent_access&.slice(*heartbeat_keys) || {} elsif params[:hackatime].present? diff --git a/lib/wakatime_service.rb b/lib/wakatime_service.rb index bc69b69..1d49062 100644 --- a/lib/wakatime_service.rb +++ b/lib/wakatime_service.rb @@ -87,7 +87,18 @@ class WakatimeService else # Try parsing as browser user agent as fallback if browser_ua = user_agent.match(/^([^\/]+)\/([^\/\s]+)/) - { os: browser_ua[1], editor: browser_ua[2], err: nil } + # If "wakatime" is present, assume it's the browser extension + if user_agent.include?("wakatime") then + full_os = user_agent.split(" ")[1] + if full_os.present? + os = full_os.include?("_") ? full_os.split("_")[0] : full_os + { os: os, editor: browser_ua[1].downcase, err: nil } + else + { os: "", editor: "", err: "failed to parse user agent string" } + end + else + { os: browser_ua[1], editor: browser_ua[2], err: nil } + end else { os: "", editor: "", err: "failed to parse user agent string" } end diff --git a/test/lib/wakatime_service_test.rb b/test/lib/wakatime_service_test.rb index e46b074..cb5d07e 100644 --- a/test/lib/wakatime_service_test.rb +++ b/test/lib/wakatime_service_test.rb @@ -63,6 +63,14 @@ class WakatimeServiceTest < Minitest::Test assert_nil result[:error] end + def test_parse_user_agent_with_Firefox + user_agent = "Firefox/139.0 linux_x86-64 firefox-wakatime/4.1.0" + result = WakatimeService.parse_user_agent(user_agent) + assert_equal "linux", result[:os] + assert_equal "firefox", result[:editor] + assert_nil result[:error] + end + def test_parse_user_agent_with_invalid_user_agent user_agent = "invalid-user-agent" result = WakatimeService.parse_user_agent(user_agent)