ahoy_matey 2.2.1 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -42
- data/README.md +76 -39
- data/app/controllers/ahoy/base_controller.rb +5 -8
- data/lib/ahoy.rb +11 -4
- data/lib/ahoy/base_store.rb +4 -2
- data/lib/ahoy/controller.rb +8 -4
- data/lib/ahoy/database_store.rb +2 -2
- data/lib/ahoy/engine.rb +2 -0
- data/lib/ahoy/model.rb +2 -4
- data/lib/ahoy/tracker.rb +16 -13
- data/lib/ahoy/version.rb +1 -1
- data/lib/ahoy/visit_properties.rb +16 -11
- data/lib/generators/ahoy/activerecord_generator.rb +6 -23
- data/lib/generators/ahoy/base_generator.rb +1 -1
- data/lib/generators/ahoy/install_generator.rb +1 -1
- data/lib/generators/ahoy/mongoid_generator.rb +1 -5
- data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +2 -2
- data/lib/generators/ahoy/templates/active_record_migration.rb.tt +5 -4
- data/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +2 -2
- data/lib/generators/ahoy/templates/base_store_initializer.rb.tt +0 -6
- data/lib/generators/ahoy/templates/database_store_initializer.rb.tt +0 -6
- data/lib/generators/ahoy/templates/mongoid_event_model.rb.tt +1 -1
- data/lib/generators/ahoy/templates/mongoid_visit_model.rb.tt +1 -1
- data/vendor/assets/javascripts/ahoy.js +21 -112
- metadata +20 -62
@@ -1,13 +1,10 @@
|
|
1
1
|
module Ahoy
|
2
2
|
class BaseController < ApplicationController
|
3
3
|
filters = _process_action_callbacks.map(&:filter) - Ahoy.preserve_callbacks
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
else
|
9
|
-
skip_action_callback(*filters)
|
10
|
-
end
|
4
|
+
skip_before_action(*filters, raise: false)
|
5
|
+
skip_after_action(*filters, raise: false)
|
6
|
+
skip_around_action(*filters, raise: false)
|
7
|
+
|
11
8
|
before_action :verify_request_size
|
12
9
|
before_action :renew_cookies
|
13
10
|
|
@@ -31,7 +28,7 @@ module Ahoy
|
|
31
28
|
def verify_request_size
|
32
29
|
if request.content_length > Ahoy.max_content_length
|
33
30
|
logger.info "[ahoy] Payload too large"
|
34
|
-
render
|
31
|
+
render plain: "Payload too large\n", status: 413
|
35
32
|
end
|
36
33
|
end
|
37
34
|
end
|
data/lib/ahoy.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
require "ipaddr"
|
2
2
|
|
3
|
+
# dependencies
|
3
4
|
require "active_support"
|
4
5
|
require "active_support/core_ext"
|
5
|
-
require "addressable/uri"
|
6
6
|
require "geocoder"
|
7
7
|
require "safely/core"
|
8
8
|
|
9
|
+
# modules
|
9
10
|
require "ahoy/utils"
|
10
11
|
require "ahoy/base_store"
|
11
12
|
require "ahoy/controller"
|
@@ -29,8 +30,12 @@ module Ahoy
|
|
29
30
|
mattr_accessor :cookies
|
30
31
|
self.cookies = true
|
31
32
|
|
33
|
+
# TODO deprecate in favor of cookie_options
|
32
34
|
mattr_accessor :cookie_domain
|
33
35
|
|
36
|
+
mattr_accessor :cookie_options
|
37
|
+
self.cookie_options = {}
|
38
|
+
|
34
39
|
mattr_accessor :server_side_visits
|
35
40
|
self.server_side_visits = true
|
36
41
|
|
@@ -72,7 +77,7 @@ module Ahoy
|
|
72
77
|
self.track_bots = false
|
73
78
|
|
74
79
|
mattr_accessor :bot_detection_version
|
75
|
-
self.bot_detection_version =
|
80
|
+
self.bot_detection_version = 2
|
76
81
|
|
77
82
|
mattr_accessor :token_generator
|
78
83
|
self.token_generator = -> { SecureRandom.uuid }
|
@@ -81,10 +86,12 @@ module Ahoy
|
|
81
86
|
self.mask_ips = false
|
82
87
|
|
83
88
|
mattr_accessor :user_agent_parser
|
84
|
-
self.user_agent_parser = :
|
89
|
+
self.user_agent_parser = :device_detector
|
90
|
+
|
91
|
+
mattr_accessor :logger
|
85
92
|
|
86
93
|
def self.log(message)
|
87
|
-
|
94
|
+
logger.info { "[ahoy] #{message}" } if logger
|
88
95
|
end
|
89
96
|
|
90
97
|
def self.mask_ip(ip)
|
data/lib/ahoy/base_store.rb
CHANGED
@@ -26,7 +26,7 @@ module Ahoy
|
|
26
26
|
if Ahoy.user_method.respond_to?(:call)
|
27
27
|
Ahoy.user_method.call(controller)
|
28
28
|
else
|
29
|
-
controller.send(Ahoy.user_method)
|
29
|
+
controller.send(Ahoy.user_method) if controller.respond_to?(Ahoy.user_method, true)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -52,11 +52,13 @@ module Ahoy
|
|
52
52
|
if Ahoy.user_agent_parser == :device_detector
|
53
53
|
detector = DeviceDetector.new(request.user_agent)
|
54
54
|
if Ahoy.bot_detection_version == 2
|
55
|
-
detector.bot? || detector.device_type.nil?
|
55
|
+
detector.bot? || (detector.device_type.nil? && detector.os_name.nil?)
|
56
56
|
else
|
57
57
|
detector.bot?
|
58
58
|
end
|
59
59
|
else
|
60
|
+
# no need to throw friendly error if browser isn't defined
|
61
|
+
# since will error in visit_properties
|
60
62
|
Browser.new(request.user_agent).bot?
|
61
63
|
end
|
62
64
|
else
|
data/lib/ahoy/controller.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require "request_store"
|
2
|
-
|
3
1
|
module Ahoy
|
4
2
|
module Controller
|
5
3
|
def self.included(base)
|
@@ -9,7 +7,7 @@ module Ahoy
|
|
9
7
|
end
|
10
8
|
base.before_action :set_ahoy_cookies, unless: -> { Ahoy.api_only }
|
11
9
|
base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
|
12
|
-
base.
|
10
|
+
base.around_action :set_ahoy_request_store
|
13
11
|
end
|
14
12
|
|
15
13
|
def ahoy
|
@@ -41,7 +39,13 @@ module Ahoy
|
|
41
39
|
end
|
42
40
|
|
43
41
|
def set_ahoy_request_store
|
44
|
-
|
42
|
+
previous_value = Thread.current[:ahoy]
|
43
|
+
begin
|
44
|
+
Thread.current[:ahoy] = ahoy
|
45
|
+
yield
|
46
|
+
ensure
|
47
|
+
Thread.current[:ahoy] = previous_value
|
48
|
+
end
|
45
49
|
end
|
46
50
|
end
|
47
51
|
end
|
data/lib/ahoy/database_store.rb
CHANGED
@@ -34,7 +34,7 @@ module Ahoy
|
|
34
34
|
# upsert since visit might not be found due to eventual consistency
|
35
35
|
visit_model.where(visit_token: visit_token).find_one_and_update({"$set": data}, {upsert: true})
|
36
36
|
elsif visit
|
37
|
-
visit.
|
37
|
+
visit.update!(data)
|
38
38
|
else
|
39
39
|
Ahoy.log "Visit for geocode not found: #{visit_token}"
|
40
40
|
end
|
@@ -76,7 +76,7 @@ module Ahoy
|
|
76
76
|
|
77
77
|
def slice_data(model, data)
|
78
78
|
column_names = model.try(:column_names) || model.attribute_names
|
79
|
-
data.slice(*column_names.map(&:to_sym)).select { |_, v| v }
|
79
|
+
data.slice(*column_names.map(&:to_sym)).select { |_, v| !v.nil? }
|
80
80
|
end
|
81
81
|
|
82
82
|
def unique_exception?(e)
|
data/lib/ahoy/engine.rb
CHANGED
data/lib/ahoy/model.rb
CHANGED
@@ -2,14 +2,12 @@ module Ahoy
|
|
2
2
|
module Model
|
3
3
|
def visitable(name = :visit, **options)
|
4
4
|
class_eval do
|
5
|
-
|
6
|
-
safe_options[:optional] = true if Rails::VERSION::MAJOR >= 5
|
7
|
-
belongs_to(name, class_name: "Ahoy::Visit", **safe_options)
|
5
|
+
belongs_to(name, class_name: "Ahoy::Visit", optional: true, **options)
|
8
6
|
before_create :set_ahoy_visit
|
9
7
|
end
|
10
8
|
class_eval %{
|
11
9
|
def set_ahoy_visit
|
12
|
-
self.#{name} ||=
|
10
|
+
self.#{name} ||= Thread.current[:ahoy].try(:visit_or_create)
|
13
11
|
end
|
14
12
|
}
|
15
13
|
end
|
data/lib/ahoy/tracker.rb
CHANGED
@@ -11,6 +11,7 @@ module Ahoy
|
|
11
11
|
@controller = options[:controller]
|
12
12
|
@request = options[:request] || @controller.try(:request)
|
13
13
|
@visit_token = options[:visit_token]
|
14
|
+
@user = options[:user]
|
14
15
|
@options = options
|
15
16
|
end
|
16
17
|
|
@@ -57,7 +58,7 @@ module Ahoy
|
|
57
58
|
|
58
59
|
@store.track_visit(data)
|
59
60
|
|
60
|
-
Ahoy::GeocodeV2Job.perform_later(visit_token, data[:ip]) if Ahoy.geocode
|
61
|
+
Ahoy::GeocodeV2Job.perform_later(visit_token, data[:ip]) if Ahoy.geocode && data[:ip]
|
61
62
|
end
|
62
63
|
end
|
63
64
|
true
|
@@ -127,9 +128,8 @@ module Ahoy
|
|
127
128
|
@user ||= @store.user
|
128
129
|
end
|
129
130
|
|
130
|
-
# TODO better name
|
131
131
|
def visit_properties
|
132
|
-
@visit_properties ||= Ahoy::VisitProperties.new(request, api: api?).generate
|
132
|
+
@visit_properties ||= request ? Ahoy::VisitProperties.new(request, api: api?).generate : {}
|
133
133
|
end
|
134
134
|
|
135
135
|
def visit_token
|
@@ -169,24 +169,23 @@ module Ahoy
|
|
169
169
|
|
170
170
|
def set_cookie(name, value, duration = nil, use_domain = true)
|
171
171
|
# safety net
|
172
|
-
return unless Ahoy.cookies
|
172
|
+
return unless Ahoy.cookies && request
|
173
173
|
|
174
|
-
cookie =
|
175
|
-
value: value
|
176
|
-
}
|
174
|
+
cookie = Ahoy.cookie_options.merge(value: value)
|
177
175
|
cookie[:expires] = duration.from_now if duration
|
178
|
-
domain
|
179
|
-
cookie[:domain]
|
176
|
+
# prefer cookie_options[:domain] over cookie_domain
|
177
|
+
cookie[:domain] ||= Ahoy.cookie_domain if Ahoy.cookie_domain
|
178
|
+
cookie.delete(:domain) unless use_domain
|
180
179
|
request.cookie_jar[name] = cookie
|
181
180
|
end
|
182
181
|
|
183
182
|
def delete_cookie(name)
|
184
|
-
request.cookie_jar.delete(name) if request.cookie_jar[name]
|
183
|
+
request.cookie_jar.delete(name) if request && request.cookie_jar[name]
|
185
184
|
end
|
186
185
|
|
187
186
|
def trusted_time(time = nil)
|
188
187
|
if !time || (api? && !(1.minute.ago..Time.now).cover?(time))
|
189
|
-
Time.
|
188
|
+
Time.current
|
190
189
|
else
|
191
190
|
time
|
192
191
|
end
|
@@ -197,8 +196,12 @@ module Ahoy
|
|
197
196
|
end
|
198
197
|
|
199
198
|
def report_exception(e)
|
200
|
-
|
201
|
-
|
199
|
+
if defined?(ActionDispatch::RemoteIp::IpSpoofAttackError) && e.is_a?(ActionDispatch::RemoteIp::IpSpoofAttackError)
|
200
|
+
debug "Tracking excluded due to IP spoofing"
|
201
|
+
else
|
202
|
+
raise e if !defined?(Rails) || Rails.env.development? || Rails.env.test?
|
203
|
+
Safely.report_exception(e)
|
204
|
+
end
|
202
205
|
end
|
203
206
|
|
204
207
|
def generate_id
|
data/lib/ahoy/version.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
require "
|
1
|
+
require "cgi"
|
2
2
|
require "device_detector"
|
3
|
-
require "
|
4
|
-
require "user_agent_parser"
|
3
|
+
require "uri"
|
5
4
|
|
6
5
|
module Ahoy
|
7
6
|
class VisitProperties
|
@@ -21,23 +20,26 @@ module Ahoy
|
|
21
20
|
private
|
22
21
|
|
23
22
|
def utm_properties
|
24
|
-
|
25
|
-
|
23
|
+
landing_params = {}
|
24
|
+
begin
|
25
|
+
landing_uri = URI.parse(landing_page)
|
26
|
+
# could also use Rack::Utils.parse_nested_query
|
27
|
+
landing_params = CGI.parse(landing_uri.query) if landing_uri
|
28
|
+
rescue
|
29
|
+
# do nothing
|
30
|
+
end
|
26
31
|
|
27
32
|
props = {}
|
28
33
|
%w(utm_source utm_medium utm_term utm_content utm_campaign).each do |name|
|
29
|
-
props[name.to_sym] = params[name] || landing_params[name]
|
34
|
+
props[name.to_sym] = params[name] || landing_params[name].try(:first)
|
30
35
|
end
|
31
36
|
props
|
32
37
|
end
|
33
38
|
|
34
39
|
def traffic_properties
|
35
|
-
|
36
|
-
@@referrer_parser ||= RefererParser::Parser.new
|
37
|
-
|
40
|
+
uri = URI.parse(referrer) rescue nil
|
38
41
|
{
|
39
|
-
referring_domain:
|
40
|
-
search_keyword: (@@referrer_parser.parse(@referrer)[:term].first(255) rescue nil).presence
|
42
|
+
referring_domain: uri.try(:host).try(:first, 255)
|
41
43
|
}
|
42
44
|
end
|
43
45
|
|
@@ -60,6 +62,9 @@ module Ahoy
|
|
60
62
|
device_type: device_type
|
61
63
|
}
|
62
64
|
else
|
65
|
+
raise "Add browser to your Gemfile to use legacy user agent parsing" unless defined?(Browser)
|
66
|
+
raise "Add user_agent_parser to your Gemfile to use legacy user agent parsing" unless defined?(UserAgentParser)
|
67
|
+
|
63
68
|
# cache for performance
|
64
69
|
@@user_agent_parser ||= UserAgentParser::Parser.new
|
65
70
|
|
@@ -1,34 +1,19 @@
|
|
1
|
-
# taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
|
2
|
-
require "rails/generators"
|
3
|
-
require "rails/generators/migration"
|
4
|
-
require "active_record"
|
5
1
|
require "rails/generators/active_record"
|
6
2
|
|
7
3
|
module Ahoy
|
8
4
|
module Generators
|
9
5
|
class ActiverecordGenerator < Rails::Generators::Base
|
10
|
-
include
|
11
|
-
source_root File.
|
6
|
+
include ActiveRecord::Generators::Migration
|
7
|
+
source_root File.join(__dir__, "templates")
|
12
8
|
|
13
9
|
class_option :database, type: :string, aliases: "-d"
|
14
10
|
|
15
|
-
# Implement the required interface for Rails::Generators::Migration.
|
16
|
-
def self.next_migration_number(dirname) #:nodoc:
|
17
|
-
next_migration_number = current_migration_number(dirname) + 1
|
18
|
-
if ::ActiveRecord::Base.timestamped_migrations
|
19
|
-
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
20
|
-
else
|
21
|
-
"%.3d" % next_migration_number
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
11
|
def copy_templates
|
26
12
|
template "database_store_initializer.rb", "config/initializers/ahoy.rb"
|
27
13
|
template "active_record_visit_model.rb", "app/models/ahoy/visit.rb"
|
28
14
|
template "active_record_event_model.rb", "app/models/ahoy/event.rb"
|
29
15
|
migration_template "active_record_migration.rb", "db/migrate/create_ahoy_visits_and_events.rb", migration_version: migration_version
|
30
|
-
|
31
|
-
puts "\nAlmost set! Last, run:\n\n #{migrate_command} db:migrate"
|
16
|
+
puts "\nAlmost set! Last, run:\n\n rails db:migrate"
|
32
17
|
end
|
33
18
|
|
34
19
|
def properties_type
|
@@ -44,14 +29,12 @@ module Ahoy
|
|
44
29
|
end
|
45
30
|
end
|
46
31
|
|
47
|
-
def
|
48
|
-
|
32
|
+
def rails52?
|
33
|
+
ActiveRecord::VERSION::STRING >= "5.2"
|
49
34
|
end
|
50
35
|
|
51
36
|
def migration_version
|
52
|
-
|
53
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
54
|
-
end
|
37
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
55
38
|
end
|
56
39
|
end
|
57
40
|
end
|
@@ -3,7 +3,7 @@ require "rails/generators"
|
|
3
3
|
module Ahoy
|
4
4
|
module Generators
|
5
5
|
class BaseGenerator < Rails::Generators::Base
|
6
|
-
source_root File.
|
6
|
+
source_root File.join(__dir__, "templates")
|
7
7
|
|
8
8
|
def copy_templates
|
9
9
|
template "base_store_initializer.rb", "config/initializers/ahoy.rb"
|
@@ -3,7 +3,7 @@ require "rails/generators"
|
|
3
3
|
module Ahoy
|
4
4
|
module Generators
|
5
5
|
class InstallGenerator < Rails::Generators::Base
|
6
|
-
source_root File.
|
6
|
+
source_root File.join(__dir__, "templates")
|
7
7
|
|
8
8
|
def copy_templates
|
9
9
|
activerecord = defined?(ActiveRecord)
|
@@ -3,7 +3,7 @@ require "rails/generators"
|
|
3
3
|
module Ahoy
|
4
4
|
module Generators
|
5
5
|
class MongoidGenerator < Rails::Generators::Base
|
6
|
-
source_root File.
|
6
|
+
source_root File.join(__dir__, "templates")
|
7
7
|
|
8
8
|
def copy_templates
|
9
9
|
template "database_store_initializer.rb", "config/initializers/ahoy.rb"
|
@@ -11,10 +11,6 @@ module Ahoy
|
|
11
11
|
template "mongoid_event_model.rb", "app/models/ahoy/event.rb"
|
12
12
|
puts "\nAlmost set! Last, run:\n\n rake db:mongoid:create_indexes"
|
13
13
|
end
|
14
|
-
|
15
|
-
def rails5?
|
16
|
-
Rails::VERSION::MAJOR >= 5
|
17
|
-
end
|
18
14
|
end
|
19
15
|
end
|
20
16
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
class Ahoy::Event <
|
1
|
+
class Ahoy::Event < ApplicationRecord
|
2
2
|
include Ahoy::QueryMethods
|
3
3
|
|
4
4
|
self.table_name = "ahoy_events"
|
5
5
|
|
6
6
|
belongs_to :visit
|
7
|
-
belongs_to :user
|
7
|
+
belongs_to :user, optional: true<% if properties_type == "text" %>
|
8
8
|
|
9
9
|
serialize :properties, JSON<% end %>
|
10
10
|
end
|
@@ -26,8 +26,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
|
|
26
26
|
t.string :country
|
27
27
|
t.string :region
|
28
28
|
t.string :city
|
29
|
-
t.
|
30
|
-
t.
|
29
|
+
t.float :latitude
|
30
|
+
t.float :longitude
|
31
31
|
|
32
32
|
# utm parameters
|
33
33
|
t.string :utm_source
|
@@ -55,7 +55,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
|
|
55
55
|
t.timestamp :time
|
56
56
|
end
|
57
57
|
|
58
|
-
add_index :ahoy_events, [:name, :time]<% if properties_type == "jsonb"
|
59
|
-
add_index :ahoy_events,
|
58
|
+
add_index :ahoy_events, [:name, :time]<% if properties_type == "jsonb" %><% if rails52? %>
|
59
|
+
add_index :ahoy_events, :properties, using: :gin, opclass: :jsonb_path_ops<% else %>
|
60
|
+
add_index :ahoy_events, "properties jsonb_path_ops", using: "gin"<% end %><% end %>
|
60
61
|
end
|
61
62
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
class Ahoy::Visit <
|
1
|
+
class Ahoy::Visit < ApplicationRecord
|
2
2
|
self.table_name = "ahoy_visits"
|
3
3
|
|
4
4
|
has_many :events, class_name: "Ahoy::Event"
|
5
|
-
belongs_to :user
|
5
|
+
belongs_to :user, optional: true
|
6
6
|
end
|