ahoy_matey 1.5.1 → 1.5.2
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 +4 -0
- data/app/controllers/ahoy/base_controller.rb +2 -0
- data/app/controllers/ahoy/events_controller.rb +2 -0
- data/lib/ahoy.rb +13 -2
- data/lib/ahoy/controller.rb +4 -4
- data/lib/ahoy/throttle.rb +0 -8
- data/lib/ahoy/tracker.rb +78 -16
- data/lib/ahoy/version.rb +1 -1
- data/lib/generators/ahoy/stores/templates/active_record_event_model.rb +1 -1
- data/lib/generators/ahoy/stores/templates/active_record_visit_model.rb +1 -1
- data/vendor/assets/javascripts/ahoy.js +137 -57
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3662bc02fad0423af6a7a745137718af5976244a
|
4
|
+
data.tar.gz: 69331b2acf1a7c75266bd6e88adba510afb8a02c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6496bb48e9e1ea773ce5ad3bd229f51458177a141d48c311fa23f1211a4f73b0de21d10ea38640d8d30686020d8ce777f83d7fe5a8b3e96ec973abc1848e8168
|
7
|
+
data.tar.gz: 66d169b4168bb2ad2f032259331c863dcaccc6295ae0e9002a6c1df4fa2e6dcaea49e784055f517160caa7c801d84390495d10b2b90f422617c9337537290ef3
|
data/CHANGELOG.md
CHANGED
data/lib/ahoy.rb
CHANGED
@@ -84,6 +84,12 @@ module Ahoy
|
|
84
84
|
mattr_accessor :job_queue
|
85
85
|
self.job_queue = :ahoy
|
86
86
|
|
87
|
+
mattr_accessor :api_only
|
88
|
+
self.api_only = false
|
89
|
+
|
90
|
+
mattr_accessor :protect_from_forgery
|
91
|
+
self.protect_from_forgery = false
|
92
|
+
|
87
93
|
def self.ensure_uuid(id)
|
88
94
|
valid = UUIDTools::UUID.parse(id) rescue nil
|
89
95
|
if valid
|
@@ -108,8 +114,13 @@ module Ahoy
|
|
108
114
|
end
|
109
115
|
|
110
116
|
if defined?(Rails)
|
111
|
-
|
112
|
-
|
117
|
+
ActiveSupport.on_load(:action_controller) do
|
118
|
+
ActionController::Base.send :include, Ahoy::Controller
|
119
|
+
end
|
120
|
+
|
121
|
+
ActiveSupport.on_load(:active_record) do
|
122
|
+
ActiveRecord::Base.send(:extend, Ahoy::Model)
|
123
|
+
end
|
113
124
|
|
114
125
|
# ensure logger silence will not be added by activerecord-session_store
|
115
126
|
# otherwise, we get SystemStackError: stack level too deep
|
data/lib/ahoy/controller.rb
CHANGED
@@ -6,12 +6,12 @@ module Ahoy
|
|
6
6
|
base.helper_method :current_visit
|
7
7
|
base.helper_method :ahoy
|
8
8
|
if base.respond_to?(:before_action)
|
9
|
-
base.before_action :set_ahoy_cookies
|
10
|
-
base.before_action :track_ahoy_visit
|
9
|
+
base.before_action :set_ahoy_cookies, unless: -> { Ahoy.api_only }
|
10
|
+
base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
|
11
11
|
base.before_action :set_ahoy_request_store
|
12
12
|
else
|
13
|
-
base.before_filter :set_ahoy_cookies
|
14
|
-
base.before_filter :track_ahoy_visit
|
13
|
+
base.before_filter :set_ahoy_cookies, unless: -> { Ahoy.api_only }
|
14
|
+
base.before_filter :track_ahoy_visit, unless: -> { Ahoy.api_only }
|
15
15
|
base.before_filter :set_ahoy_request_store
|
16
16
|
end
|
17
17
|
end
|
data/lib/ahoy/throttle.rb
CHANGED
@@ -13,13 +13,5 @@ module Ahoy
|
|
13
13
|
def self.throttled_response
|
14
14
|
Rack::Attack.throttled_response
|
15
15
|
end
|
16
|
-
|
17
|
-
def self.blacklisted_response
|
18
|
-
Rack::Attack.blacklisted_response
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.blocklisted_response
|
22
|
-
Rack::Attack.blocklisted_response
|
23
|
-
end
|
24
16
|
end
|
25
17
|
end
|
data/lib/ahoy/tracker.rb
CHANGED
@@ -12,6 +12,8 @@ module Ahoy
|
|
12
12
|
def track(name, properties = {}, options = {})
|
13
13
|
if exclude?
|
14
14
|
debug "Event excluded"
|
15
|
+
elsif missing_params?
|
16
|
+
debug "Missing required parameters"
|
15
17
|
else
|
16
18
|
options = options.dup
|
17
19
|
|
@@ -28,6 +30,8 @@ module Ahoy
|
|
28
30
|
def track_visit(options = {})
|
29
31
|
if exclude?
|
30
32
|
debug "Visit excluded"
|
33
|
+
elsif missing_params?
|
34
|
+
debug "Missing required parameters"
|
31
35
|
else
|
32
36
|
if options[:defer]
|
33
37
|
set_cookie("ahoy_track", true, nil, false)
|
@@ -60,15 +64,19 @@ module Ahoy
|
|
60
64
|
end
|
61
65
|
|
62
66
|
def visit_id
|
63
|
-
@visit_id ||= ensure_uuid(
|
67
|
+
@visit_id ||= ensure_uuid(visit_token_helper)
|
64
68
|
end
|
65
69
|
|
66
70
|
def visitor_id
|
67
|
-
@visitor_id ||= ensure_uuid(
|
71
|
+
@visitor_id ||= ensure_uuid(visitor_token_helper)
|
68
72
|
end
|
69
73
|
|
70
74
|
def new_visit?
|
71
|
-
!
|
75
|
+
!existing_visit_token
|
76
|
+
end
|
77
|
+
|
78
|
+
def new_visitor?
|
79
|
+
!existing_visitor_token
|
72
80
|
end
|
73
81
|
|
74
82
|
def set_visit_cookie
|
@@ -76,7 +84,7 @@ module Ahoy
|
|
76
84
|
end
|
77
85
|
|
78
86
|
def set_visitor_cookie
|
79
|
-
|
87
|
+
if new_visitor?
|
80
88
|
set_cookie("ahoy_visitor", visitor_id, Ahoy.visitor_duration)
|
81
89
|
end
|
82
90
|
end
|
@@ -87,7 +95,7 @@ module Ahoy
|
|
87
95
|
|
88
96
|
# TODO better name
|
89
97
|
def visit_properties
|
90
|
-
@visit_properties ||= Ahoy::VisitProperties.new(request,
|
98
|
+
@visit_properties ||= Ahoy::VisitProperties.new(request, api: api?)
|
91
99
|
end
|
92
100
|
|
93
101
|
def visit_token
|
@@ -100,12 +108,16 @@ module Ahoy
|
|
100
108
|
|
101
109
|
protected
|
102
110
|
|
103
|
-
def
|
104
|
-
@
|
111
|
+
def api?
|
112
|
+
@options[:api]
|
105
113
|
end
|
106
114
|
|
107
|
-
def
|
108
|
-
|
115
|
+
def missing_params?
|
116
|
+
if api? && Ahoy.protect_from_forgery
|
117
|
+
!(existing_visit_token && existing_visitor_token)
|
118
|
+
else
|
119
|
+
false
|
120
|
+
end
|
109
121
|
end
|
110
122
|
|
111
123
|
def set_cookie(name, value, duration = nil, use_domain = true)
|
@@ -119,7 +131,7 @@ module Ahoy
|
|
119
131
|
end
|
120
132
|
|
121
133
|
def trusted_time(time)
|
122
|
-
if !time || (
|
134
|
+
if !time || (api? && !(1.minute.ago..Time.now).cover?(time))
|
123
135
|
Time.zone.now
|
124
136
|
else
|
125
137
|
time
|
@@ -145,20 +157,70 @@ module Ahoy
|
|
145
157
|
@store.generate_id
|
146
158
|
end
|
147
159
|
|
148
|
-
def
|
149
|
-
@
|
160
|
+
def visit_token_helper
|
161
|
+
@visit_token_helper ||= begin
|
162
|
+
token = existing_visit_token
|
163
|
+
token ||= generate_id unless api?
|
164
|
+
token
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def visitor_token_helper
|
169
|
+
@visitor_token_helper ||= begin
|
170
|
+
token = existing_visitor_token
|
171
|
+
token ||= generate_id unless api?
|
172
|
+
token
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def existing_visit_token
|
177
|
+
@existing_visit_token ||= begin
|
178
|
+
token = visit_header
|
179
|
+
token ||= visit_cookie unless api? && Ahoy.protect_from_forgery
|
180
|
+
token ||= visit_param if api?
|
181
|
+
token
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def existing_visitor_token
|
186
|
+
@existing_visitor_token ||= begin
|
187
|
+
token = visitor_header
|
188
|
+
token ||= visitor_cookie unless api? && Ahoy.protect_from_forgery
|
189
|
+
token ||= visitor_param if api?
|
190
|
+
token
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def visit_cookie
|
195
|
+
@visit_cookie ||= request && request.cookies["ahoy_visit"]
|
196
|
+
end
|
197
|
+
|
198
|
+
def visitor_cookie
|
199
|
+
@visitor_cookie ||= request && request.cookies["ahoy_visitor"]
|
200
|
+
end
|
201
|
+
|
202
|
+
def visit_header
|
203
|
+
@visit_header ||= request && request.headers["Ahoy-Visit"]
|
204
|
+
end
|
205
|
+
|
206
|
+
def visitor_header
|
207
|
+
@visitor_header ||= request && request.headers["Ahoy-Visitor"]
|
208
|
+
end
|
209
|
+
|
210
|
+
def visit_param
|
211
|
+
@visit_param ||= request && request.params["visit_token"]
|
150
212
|
end
|
151
213
|
|
152
|
-
def
|
153
|
-
@
|
214
|
+
def visitor_param
|
215
|
+
@visitor_param ||= request && request.params["visitor_token"]
|
154
216
|
end
|
155
217
|
|
156
218
|
def ensure_uuid(id)
|
157
|
-
Ahoy.ensure_uuid(id)
|
219
|
+
Ahoy.ensure_uuid(id) if id
|
158
220
|
end
|
159
221
|
|
160
222
|
def ensure_token(token)
|
161
|
-
token.to_s.gsub(/[^a-z0-9\-]/i, "").first(64)
|
223
|
+
token.to_s.gsub(/[^a-z0-9\-]/i, "").first(64) if token
|
162
224
|
end
|
163
225
|
|
164
226
|
def debug(message)
|
data/lib/ahoy/version.rb
CHANGED
@@ -5,7 +5,7 @@ module Ahoy
|
|
5
5
|
self.table_name = "ahoy_events"
|
6
6
|
|
7
7
|
belongs_to :visit
|
8
|
-
belongs_to :user
|
8
|
+
belongs_to :user<%= Rails::VERSION::MAJOR >= 5 ? ", optional: true" : nil %><% unless %w(postgresql postgresql-jsonb).include?(@database) %>
|
9
9
|
|
10
10
|
serialize :properties, JSON<% end %>
|
11
11
|
end
|
@@ -11,7 +11,30 @@
|
|
11
11
|
(function (window) {
|
12
12
|
"use strict";
|
13
13
|
|
14
|
+
var config = {
|
15
|
+
urlPrefix: "",
|
16
|
+
visitsUrl: "/ahoy/visits",
|
17
|
+
eventsUrl: "/ahoy/events",
|
18
|
+
cookieDomain: null,
|
19
|
+
page: null,
|
20
|
+
platform: "Web",
|
21
|
+
useBeacon: false,
|
22
|
+
startOnReady: true
|
23
|
+
};
|
24
|
+
|
14
25
|
var ahoy = window.ahoy || window.Ahoy || {};
|
26
|
+
|
27
|
+
ahoy.configure = function (options) {
|
28
|
+
for (var key in options) {
|
29
|
+
if (options.hasOwnProperty(key)) {
|
30
|
+
config[key] = options[key];
|
31
|
+
}
|
32
|
+
}
|
33
|
+
};
|
34
|
+
|
35
|
+
// legacy
|
36
|
+
ahoy.configure(ahoy);
|
37
|
+
|
15
38
|
var $ = window.jQuery || window.Zepto || window.$;
|
16
39
|
var visitId, visitorId, track;
|
17
40
|
var visitTtl = 4 * 60; // 4 hours
|
@@ -20,9 +43,18 @@
|
|
20
43
|
var queue = [];
|
21
44
|
var canStringify = typeof(JSON) !== "undefined" && typeof(JSON.stringify) !== "undefined";
|
22
45
|
var eventQueue = [];
|
23
|
-
|
24
|
-
|
25
|
-
|
46
|
+
|
47
|
+
function visitsUrl() {
|
48
|
+
return config.urlPrefix + config.visitsUrl;
|
49
|
+
}
|
50
|
+
|
51
|
+
function eventsUrl() {
|
52
|
+
return config.urlPrefix + config.eventsUrl;
|
53
|
+
}
|
54
|
+
|
55
|
+
function canTrackNow() {
|
56
|
+
return (config.useBeacon || config.trackNow) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined";
|
57
|
+
}
|
26
58
|
|
27
59
|
// cookies
|
28
60
|
|
@@ -35,8 +67,9 @@
|
|
35
67
|
date.setTime(date.getTime() + (ttl * 60 * 1000));
|
36
68
|
expires = "; expires=" + date.toGMTString();
|
37
69
|
}
|
38
|
-
|
39
|
-
|
70
|
+
var domain = config.cookieDomain || config.domain;
|
71
|
+
if (domain) {
|
72
|
+
cookieDomain = "; domain=" + domain;
|
40
73
|
}
|
41
74
|
document.cookie = name + "=" + escape(value) + expires + cookieDomain + "; path=/";
|
42
75
|
}
|
@@ -98,40 +131,74 @@
|
|
98
131
|
}
|
99
132
|
}
|
100
133
|
|
134
|
+
// from jquery-ujs
|
135
|
+
|
136
|
+
function csrfToken() {
|
137
|
+
return $("meta[name=csrf-token]").attr("content");
|
138
|
+
}
|
139
|
+
|
140
|
+
function csrfParam() {
|
141
|
+
return $("meta[name=csrf-param]").attr("content");
|
142
|
+
}
|
143
|
+
|
144
|
+
function CSRFProtection(xhr) {
|
145
|
+
var token = csrfToken();
|
146
|
+
if (token) xhr.setRequestHeader("X-CSRF-Token", token);
|
147
|
+
}
|
148
|
+
|
149
|
+
function sendRequest(url, data, success) {
|
150
|
+
if (canStringify) {
|
151
|
+
$.ajax({
|
152
|
+
type: "POST",
|
153
|
+
url: url,
|
154
|
+
data: JSON.stringify(data),
|
155
|
+
contentType: "application/json; charset=utf-8",
|
156
|
+
dataType: "json",
|
157
|
+
beforeSend: CSRFProtection,
|
158
|
+
success: success
|
159
|
+
});
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
function eventData(event) {
|
164
|
+
var data = {
|
165
|
+
events: [event],
|
166
|
+
visit_token: event.visit_token,
|
167
|
+
visitor_token: event.visitor_token
|
168
|
+
};
|
169
|
+
delete event.visit_token;
|
170
|
+
delete event.visitor_token;
|
171
|
+
return data;
|
172
|
+
}
|
173
|
+
|
101
174
|
function trackEvent(event) {
|
102
175
|
ready( function () {
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
contentType: "application/json; charset=utf-8",
|
110
|
-
dataType: "json",
|
111
|
-
success: function() {
|
112
|
-
// remove from queue
|
113
|
-
for (var i = 0; i < eventQueue.length; i++) {
|
114
|
-
if (eventQueue[i].id == event.id) {
|
115
|
-
eventQueue.splice(i, 1);
|
116
|
-
break;
|
117
|
-
}
|
118
|
-
}
|
119
|
-
saveEventQueue();
|
176
|
+
sendRequest(eventsUrl(), eventData(event), function() {
|
177
|
+
// remove from queue
|
178
|
+
for (var i = 0; i < eventQueue.length; i++) {
|
179
|
+
if (eventQueue[i].id == event.id) {
|
180
|
+
eventQueue.splice(i, 1);
|
181
|
+
break;
|
120
182
|
}
|
121
|
-
}
|
122
|
-
|
183
|
+
}
|
184
|
+
saveEventQueue();
|
185
|
+
});
|
123
186
|
});
|
124
187
|
}
|
125
188
|
|
126
189
|
function trackEventNow(event) {
|
127
190
|
ready( function () {
|
128
|
-
var
|
129
|
-
|
191
|
+
var data = eventData(event);
|
192
|
+
var param = csrfParam();
|
193
|
+
var token = csrfToken();
|
194
|
+
if (param && token) data[param] = token;
|
195
|
+
var payload = new Blob([JSON.stringify(data)], {type : "application/json; charset=utf-8"});
|
196
|
+
navigator.sendBeacon(eventsUrl(), payload);
|
130
197
|
});
|
131
198
|
}
|
132
199
|
|
133
200
|
function page() {
|
134
|
-
return
|
201
|
+
return config.page || window.location.pathname;
|
135
202
|
}
|
136
203
|
|
137
204
|
function eventProperties(e) {
|
@@ -178,7 +245,7 @@
|
|
178
245
|
var data = {
|
179
246
|
visit_token: visitId,
|
180
247
|
visitor_token: visitorId,
|
181
|
-
platform:
|
248
|
+
platform: config.platform,
|
182
249
|
landing_page: window.location.href,
|
183
250
|
screen_width: window.screen.width,
|
184
251
|
screen_height: window.screen.height
|
@@ -191,7 +258,7 @@
|
|
191
258
|
|
192
259
|
log(data);
|
193
260
|
|
194
|
-
|
261
|
+
sendRequest(visitsUrl(), data, setReady);
|
195
262
|
} else {
|
196
263
|
log("Cookies disabled");
|
197
264
|
setReady();
|
@@ -225,32 +292,39 @@
|
|
225
292
|
};
|
226
293
|
|
227
294
|
ahoy.track = function (name, properties) {
|
228
|
-
if (!ahoy.getVisitId()) {
|
229
|
-
createVisit();
|
230
|
-
}
|
231
|
-
|
232
295
|
// generate unique id
|
233
296
|
var event = {
|
234
297
|
id: generateId(),
|
235
|
-
visit_token: ahoy.getVisitId(),
|
236
|
-
visitor_token: ahoy.getVisitorId(),
|
237
298
|
name: name,
|
238
|
-
properties: properties,
|
299
|
+
properties: properties || {},
|
239
300
|
time: (new Date()).getTime() / 1000.0
|
240
301
|
};
|
241
|
-
log(event);
|
242
302
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
saveEventQueue();
|
303
|
+
// wait for createVisit to log
|
304
|
+
$( function () {
|
305
|
+
log(event);
|
306
|
+
});
|
248
307
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
}
|
253
|
-
|
308
|
+
ready( function () {
|
309
|
+
if (!ahoy.getVisitId()) {
|
310
|
+
createVisit();
|
311
|
+
}
|
312
|
+
|
313
|
+
event.visit_token = ahoy.getVisitId();
|
314
|
+
event.visitor_token = ahoy.getVisitorId();
|
315
|
+
|
316
|
+
if (canTrackNow()) {
|
317
|
+
trackEventNow(event);
|
318
|
+
} else {
|
319
|
+
eventQueue.push(event);
|
320
|
+
saveEventQueue();
|
321
|
+
|
322
|
+
// wait in case navigating to reduce duplicate events
|
323
|
+
setTimeout( function () {
|
324
|
+
trackEvent(event);
|
325
|
+
}, 1000);
|
326
|
+
}
|
327
|
+
});
|
254
328
|
};
|
255
329
|
|
256
330
|
ahoy.trackView = function () {
|
@@ -293,18 +367,24 @@
|
|
293
367
|
ahoy.trackChanges();
|
294
368
|
};
|
295
369
|
|
296
|
-
|
370
|
+
ahoy.start = function () {
|
371
|
+
createVisit();
|
297
372
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
373
|
+
// push events from queue
|
374
|
+
try {
|
375
|
+
eventQueue = JSON.parse(getCookie("ahoy_events") || "[]");
|
376
|
+
} catch (e) {
|
377
|
+
// do nothing
|
378
|
+
}
|
304
379
|
|
305
|
-
|
306
|
-
|
307
|
-
|
380
|
+
for (var i = 0; i < eventQueue.length; i++) {
|
381
|
+
trackEvent(eventQueue[i]);
|
382
|
+
}
|
383
|
+
|
384
|
+
ahoy.start = function () {};
|
385
|
+
};
|
386
|
+
|
387
|
+
if (config.startOnReady) { $(ahoy.start); }
|
308
388
|
|
309
389
|
window.ahoy = ahoy;
|
310
390
|
}(window));
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ahoy_matey
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|