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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9e5a105f360566e591e93df058b2ef3f2b014d2b
4
- data.tar.gz: 75b530f1d2ac5db181fce79e5e28772fee413e44
3
+ metadata.gz: 3662bc02fad0423af6a7a745137718af5976244a
4
+ data.tar.gz: 69331b2acf1a7c75266bd6e88adba510afb8a02c
5
5
  SHA512:
6
- metadata.gz: 04728f524b58d1b923072dbf88f2f3e028bd9e7c09b40b7e26dcf24b1311255fcf851401c1890d473d1ef43f6b35abac94be4ec71d889211d09f6ebd573f7934
7
- data.tar.gz: 40cd9292793a8a9df4f91c474deb5fe787b483d9cf7f9b76d00ef9ebe36a0d82511fdc12dcf506acd71849b5443f072110af1faf4983596006c7a96543ac3c1b
6
+ metadata.gz: 6496bb48e9e1ea773ce5ad3bd229f51458177a141d48c311fa23f1211a4f73b0de21d10ea38640d8d30686020d8ce777f83d7fe5a8b3e96ec973abc1848e8168
7
+ data.tar.gz: 66d169b4168bb2ad2f032259331c863dcaccc6295ae0e9002a6c1df4fa2e6dcaea49e784055f517160caa7c801d84390495d10b2b90f422617c9337537290ef3
@@ -1,3 +1,7 @@
1
+ ## 1.5.2
2
+
3
+ - Better support for Rails 5
4
+
1
5
  ## 1.5.1
2
6
 
3
7
  - Restored throttling after removing side effects
@@ -15,6 +15,8 @@ module Ahoy
15
15
  before_filter :verify_request_size
16
16
  end
17
17
 
18
+ protect_from_forgery with: :null_session, if: -> { Ahoy.protect_from_forgery }
19
+
18
20
  protected
19
21
 
20
22
  def ahoy
@@ -5,6 +5,8 @@ module Ahoy
5
5
  if params[:name]
6
6
  # legacy API
7
7
  [params]
8
+ elsif params[:events]
9
+ params[:events]
8
10
  else
9
11
  begin
10
12
  ActiveSupport::JSON.decode(request.body.read)
@@ -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
- ActionController::Base.send :include, Ahoy::Controller
112
- ActiveRecord::Base.send(:extend, Ahoy::Model) if defined?(ActiveRecord)
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
@@ -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
@@ -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
@@ -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(existing_visit_id || visit_token_helper)
67
+ @visit_id ||= ensure_uuid(visit_token_helper)
64
68
  end
65
69
 
66
70
  def visitor_id
67
- @visitor_id ||= ensure_uuid(existing_visitor_id || visitor_token_helper)
71
+ @visitor_id ||= ensure_uuid(visitor_token_helper)
68
72
  end
69
73
 
70
74
  def new_visit?
71
- !existing_visit_id
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
- unless existing_visitor_id
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, @options.slice(:api))
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 visit_token_helper
104
- @visit_token_helper ||= existing_visit_id || (@options[:api] && request.params["visit_token"]) || generate_id
111
+ def api?
112
+ @options[:api]
105
113
  end
106
114
 
107
- def visitor_token_helper
108
- @visitor_token_helper ||= existing_visitor_id || (@options[:api] && request.params["visitor_token"]) || generate_id
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 || (@options[:api] && !(1.minute.ago..Time.now).cover?(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 existing_visit_id
149
- @existing_visit_id ||= request && (request.headers["Ahoy-Visit"] || request.cookies["ahoy_visit"])
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 existing_visitor_id
153
- @existing_visitor_id ||= request && (request.headers["Ahoy-Visitor"] || request.cookies["ahoy_visitor"])
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)
@@ -1,3 +1,3 @@
1
1
  module Ahoy
2
- VERSION = "1.5.1"
2
+ VERSION = "1.5.2"
3
3
  end
@@ -5,7 +5,7 @@ module Ahoy
5
5
  self.table_name = "ahoy_events"
6
6
 
7
7
  belongs_to :visit
8
- belongs_to :user<% unless %w(postgresql postgresql-jsonb).include?(@database) %>
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
@@ -1,4 +1,4 @@
1
1
  class Visit < ActiveRecord::Base
2
2
  has_many :ahoy_events, class_name: "Ahoy::Event"
3
- belongs_to :user
3
+ belongs_to :user<%= Rails::VERSION::MAJOR >= 5 ? ", optional: true" : nil %>
4
4
  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
- var visitsUrl = ahoy.visitsUrl || "/ahoy/visits";
24
- var eventsUrl = ahoy.eventsUrl || "/ahoy/events";
25
- var canTrackNow = ahoy.trackNow && canStringify && typeof(window.navigator.sendBeacon) !== "undefined";
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
- if (ahoy.domain) {
39
- cookieDomain = "; domain=" + ahoy.domain;
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
- // ensure JSON is defined
104
- if (canStringify) {
105
- $.ajax({
106
- type: "POST",
107
- url: eventsUrl,
108
- data: JSON.stringify([event]),
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 payload = new Blob([JSON.stringify([event])], {type : "application/json; charset=utf-8"});
129
- navigator.sendBeacon(eventsUrl, payload)
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 ahoy.page || window.location.pathname;
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: ahoy.platform || "Web",
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
- $.post(visitsUrl, data, setReady, "json");
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
- if (canTrackNow) {
244
- trackEventNow(event);
245
- } else {
246
- eventQueue.push(event);
247
- saveEventQueue();
303
+ // wait for createVisit to log
304
+ $( function () {
305
+ log(event);
306
+ });
248
307
 
249
- // wait in case navigating to reduce duplicate events
250
- setTimeout( function () {
251
- trackEvent(event);
252
- }, 1000);
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
- createVisit();
370
+ ahoy.start = function () {
371
+ createVisit();
297
372
 
298
- // push events from queue
299
- try {
300
- eventQueue = JSON.parse(getCookie("ahoy_events") || "[]");
301
- } catch (e) {
302
- // do nothing
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
- for (var i = 0; i < eventQueue.length; i++) {
306
- trackEvent(eventQueue[i]);
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.1
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-19 00:00:00.000000000 Z
11
+ date: 2016-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties