ahoy_matey 1.5.1 → 1.5.2

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