ahoy_matey 2.2.1 → 3.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|