ahoy_matey 2.2.0 → 2.2.1

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
  SHA256:
3
- metadata.gz: 65d9ae15e5742baab80cca54630bd9c446dde514ab820c16163bf856c23839e6
4
- data.tar.gz: ca4d9f657de0ef24e56334ae11045ccd615012dac032a0c8e42f1acfc2973da4
3
+ metadata.gz: b7b0851dfc7538c6768d6f694d7e8356f0aef3e3ff66ba361447e000a2db5bac
4
+ data.tar.gz: c28eb30ab780037d83550d359a36d6790e3a16f60b314b5d0be170d11b3c427b
5
5
  SHA512:
6
- metadata.gz: 931116ac80eb7774c70fdfeeb4168724fc1608cb1476e9fc7568686d1c523bd4fd3ab5f023249e26b654088136f7004a8d09537d628db8610b23268270b9363a
7
- data.tar.gz: 85e15776ffb648e5385658def3525cf0dfc3c8a0d2c1060e5f14af7a50a806c17640bf6741ccc8ed34b8b98b1b3afd3894a534faa539825171b9602ba4279afa
6
+ metadata.gz: 85fb407054564104efc83ae3d425c6253db7495d6f6d4156c741156f64fcd7fe048035f78e4563bf9ec92c69c7e6dfef41e29f7d1031174705b17c595ef96572
7
+ data.tar.gz: ff03757afbb0deca1a11dc4ece02310bbd8a03f644f675c2eee32c1bf7711013f0cd38eb0fd009541b97b3a5b914a756279e88dbe0cbc8a8bd14ed064d648089
@@ -1,3 +1,9 @@
1
+ ## 2.2.1
2
+
3
+ - Updated Ahoy.js to 0.3.4
4
+ - Fixed v2 bot detection
5
+ - Added latitude and longitude to installation
6
+
1
7
  ## 2.2.0
2
8
 
3
9
  - Added `amp_event` helper
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2018 Andrew Kane
1
+ Copyright (c) 2014-2019 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -8,6 +8,8 @@ Track visits and events in Ruby, JavaScript, and native apps. Data is stored in
8
8
 
9
9
  :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
10
10
 
11
+ [![Build Status](https://travis-ci.org/ankane/ahoy.svg?branch=master)](https://travis-ci.org/ankane/ahoy)
12
+
11
13
  ## Installation
12
14
 
13
15
  Add this line to your application’s Gemfile:
@@ -42,13 +44,25 @@ Ahoy.api = true
42
44
 
43
45
  And restart your web server.
44
46
 
45
- For JavaScript, add to `app/assets/javascripts/application.js`:
47
+ For JavaScript and Rails 6 / Webpacker, run:
48
+
49
+ ```sh
50
+ yarn add ahoy.js
51
+ ```
52
+
53
+ And add to `app/javascript/packs/application.js`:
54
+
55
+ ```javascript
56
+ import ahoy from "ahoy.js";
57
+ ```
58
+
59
+ For JavaScript and Rails 5 / Sprockets, add to `app/assets/javascripts/application.js`:
46
60
 
47
61
  ```javascript
48
62
  //= require ahoy
49
63
  ```
50
64
 
51
- And track an event with:
65
+ Track an event with:
52
66
 
53
67
  ```javascript
54
68
  ahoy.track("My second event", {language: "JavaScript"});
@@ -85,7 +99,11 @@ Prevent certain Rails actions from creating visits with:
85
99
  skip_before_action :track_ahoy_visit
86
100
  ```
87
101
 
88
- This is typically useful for APIs.
102
+ This is typically useful for APIs. If your entire Rails app is an API, you can use:
103
+
104
+ ```ruby
105
+ Ahoy.api_only = true
106
+ ```
89
107
 
90
108
  You can also defer visit tracking to JavaScript. This is useful for preventing bots (that aren’t detected by their user agent) and users with cookies disabled from creating a new visit on each request. `:when_needed` will create visits server-side only when needed by events, and `false` will disable server-side creation completely, discarding events without a visit.
91
109
 
@@ -154,34 +172,34 @@ Say we want to associate orders with visits. Just add `visitable` to the model.
154
172
 
155
173
  ```ruby
156
174
  class Order < ApplicationRecord
157
- visitable
175
+ visitable :ahoy_visit
158
176
  end
159
177
  ```
160
178
 
161
- When a visitor places an order, the `visit_id` column is automatically set :tada:
179
+ When a visitor places an order, the `ahoy_visit_id` column is automatically set :tada:
162
180
 
163
181
  See where orders are coming from with simple joins:
164
182
 
165
183
  ```ruby
166
- Order.joins(:visit).group("referring_domain").count
167
- Order.joins(:visit).group("city").count
168
- Order.joins(:visit).group("device_type").count
184
+ Order.joins(:ahoy_visit).group("referring_domain").count
185
+ Order.joins(:ahoy_visit).group("city").count
186
+ Order.joins(:ahoy_visit).group("device_type").count
169
187
  ```
170
188
 
171
- Here’s what the migration to add the `visit_id` column should look like:
189
+ Here’s what the migration to add the `ahoy_visit_id` column should look like:
172
190
 
173
191
  ```ruby
174
- class AddVisitIdToOrders < ActiveRecord::Migration[5.1]
192
+ class AddVisitIdToOrders < ActiveRecord::Migration[5.2]
175
193
  def change
176
- add_column :orders, :visit_id, :bigint
194
+ add_column :orders, :ahoy_visit_id, :bigint
177
195
  end
178
196
  end
179
197
  ```
180
198
 
181
- Customize the column and class name with:
199
+ Customize the column with:
182
200
 
183
201
  ```ruby
184
- visitable :sign_up_visit, class_name: "Ahoy::Visit"
202
+ visitable :sign_up_visit
185
203
  ```
186
204
 
187
205
  ### Users
@@ -236,6 +254,28 @@ class ApplicationController < ActionController::Base
236
254
  end
237
255
  ```
238
256
 
257
+ #### Knock
258
+
259
+ To attach the user with [Knock](https://github.com/nsarno/knock), either include `Knock::Authenticable`in `ApplicationController`:
260
+
261
+ ```ruby
262
+ class ApplicationController < ActionController::API
263
+ include Knock::Authenticable
264
+ end
265
+ ```
266
+
267
+ Or include it in Ahoy:
268
+
269
+ ```ruby
270
+ Ahoy::BaseController.include Knock::Authenticable
271
+ ```
272
+
273
+ And use:
274
+
275
+ ```ruby
276
+ Ahoy.user_method = ->(controller) { controller.send(:authenticate_entity, "user") }
277
+ ```
278
+
239
279
  ### Exclusions
240
280
 
241
281
  Bots are excluded from tracking by default. To include them, use:
@@ -303,6 +343,8 @@ Geocoder.configure(
303
343
  )
304
344
  ```
305
345
 
346
+ If you use Heroku, you can use an unofficial buildpack like [this one](https://github.com/temedica/heroku-buildpack-maxmind-geolite2) to avoid including the database in your repo.
347
+
306
348
  ### Token Generation
307
349
 
308
350
  Ahoy uses random UUIDs for visit and visitor tokens by default, but you can use your own generator like [Druuid](https://github.com/recurly/druuid).
@@ -465,6 +507,23 @@ end
465
507
 
466
508
  Two useful methods you can use are `request` and `controller`.
467
509
 
510
+ You can pass additional visit data from JavaScript with: [master]
511
+
512
+ ```javascript
513
+ ahoy.configure({visitParams: {referral_code: 123}});
514
+ ```
515
+
516
+ And use:
517
+
518
+ ```ruby
519
+ class Ahoy::Store < Ahoy::DatabaseStore
520
+ def track_visit(data)
521
+ data[:referral_code] = request.parameters[:referral_code]
522
+ super(data)
523
+ end
524
+ end
525
+ ```
526
+
468
527
  ### Use Different Models
469
528
 
470
529
  ```ruby
@@ -570,20 +629,6 @@ Send a `POST` request to `/ahoy/events` with `Content-Type: application/json` an
570
629
  }
571
630
  ```
572
631
 
573
- ## Webpacker
574
-
575
- For Webpacker, use Yarn to install the JavaScript library:
576
-
577
- ```sh
578
- yarn add ahoy.js
579
- ```
580
-
581
- Then include it in your pack.
582
-
583
- ```es6
584
- import ahoy from "ahoy.js";
585
- ```
586
-
587
632
  ## Upgrading
588
633
 
589
634
  ### 2.2
@@ -63,7 +63,7 @@ module Ahoy
63
63
 
64
64
  mattr_accessor :user_method
65
65
  self.user_method = lambda do |controller|
66
- (controller.respond_to?(:current_user) && controller.current_user) || (controller.respond_to?(:current_resource_owner, true) && controller.send(:current_resource_owner)) || nil
66
+ (controller.respond_to?(:current_user, true) && controller.send(:current_user)) || (controller.respond_to?(:current_resource_owner, true) && controller.send(:current_resource_owner)) || nil
67
67
  end
68
68
 
69
69
  mattr_accessor :exclude_method
@@ -50,10 +50,11 @@ module Ahoy
50
50
  @bot = begin
51
51
  if request
52
52
  if Ahoy.user_agent_parser == :device_detector
53
+ detector = DeviceDetector.new(request.user_agent)
53
54
  if Ahoy.bot_detection_version == 2
54
- DeviceDetector.new(request.user_agent).device_type.nil?
55
+ detector.bot? || detector.device_type.nil?
55
56
  else
56
- DeviceDetector.new(request.user_agent).bot?
57
+ detector.bot?
57
58
  end
58
59
  else
59
60
  Browser.new(request.user_agent).bot?
@@ -1,3 +1,3 @@
1
1
  module Ahoy
2
- VERSION = "2.2.0"
2
+ VERSION = "2.2.1"
3
3
  end
@@ -26,6 +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.decimal :latitude, precision: 10, scale: 8
30
+ t.decimal :longitude, precision: 11, scale: 8
29
31
 
30
32
  # utm parameters
31
33
  t.string :utm_source
@@ -16,23 +16,20 @@ class Ahoy::Visit
16
16
  field :ip, type: String
17
17
  field :user_agent, type: String
18
18
  field :referrer, type: String
19
- field :landing_page, type: String
20
-
21
- # traffic source
22
19
  field :referring_domain, type: String
23
- field :search_keyword, type: String
20
+ field :landing_page, type: String
24
21
 
25
22
  # technology
26
23
  field :browser, type: String
27
24
  field :os, type: String
28
25
  field :device_type, type: String
29
- field :screen_height, type: Integer
30
- field :screen_width, type: Integer
31
26
 
32
27
  # location
33
28
  field :country, type: String
34
29
  field :region, type: String
35
30
  field :city, type: String
31
+ field :latitude, type: Float
32
+ field :longitude, type: Float
36
33
 
37
34
  # utm parameters
38
35
  field :utm_source, type: String
@@ -2,7 +2,7 @@
2
2
  * Ahoy.js
3
3
  * Simple, powerful JavaScript analytics
4
4
  * https://github.com/ankane/ahoy.js
5
- * v0.3.3
5
+ * v0.3.4
6
6
  * MIT License
7
7
  */
8
8
 
@@ -12,48 +12,82 @@
12
12
  (global.ahoy = factory());
13
13
  }(this, (function () { 'use strict';
14
14
 
15
- function isUndefined (value) {
16
- return value === undefined
15
+ function isUndefined(value) {
16
+ return value === undefined;
17
17
  }
18
18
 
19
- function isObject (value) {
20
- return value === Object(value)
19
+ function isNull(value) {
20
+ return value === null;
21
21
  }
22
22
 
23
- function isArray (value) {
24
- return Array.isArray(value)
23
+ function isObject(value) {
24
+ return value === Object(value);
25
25
  }
26
26
 
27
- function isBlob (value) {
28
- return value != null &&
29
- typeof value.size === 'number' &&
30
- typeof value.type === 'string' &&
31
- typeof value.slice === 'function'
27
+ function isArray(value) {
28
+ return Array.isArray(value);
32
29
  }
33
30
 
34
- function isFile (value) {
35
- return isBlob(value) &&
36
- typeof value.lastModified === 'number' &&
37
- typeof value.name === 'string'
31
+ function isDate(value) {
32
+ return value instanceof Date;
38
33
  }
39
34
 
40
- function isDate (value) {
41
- return value instanceof Date
35
+ function isBlob(value) {
36
+ return (
37
+ value &&
38
+ typeof value.size === 'number' &&
39
+ typeof value.type === 'string' &&
40
+ typeof value.slice === 'function'
41
+ );
42
42
  }
43
43
 
44
- function objectToFormData (obj, fd, pre) {
44
+ function isFile(value) {
45
+ return (
46
+ isBlob(value) &&
47
+ (typeof value.lastModifiedDate === 'object' ||
48
+ typeof value.lastModified === 'number') &&
49
+ typeof value.name === 'string'
50
+ );
51
+ }
52
+
53
+ function isFormData(value) {
54
+ return value instanceof FormData;
55
+ }
56
+
57
+ function objectToFormData(obj, cfg, fd, pre) {
58
+ if (isFormData(cfg)) {
59
+ pre = fd;
60
+ fd = cfg;
61
+ cfg = null;
62
+ }
63
+
64
+ cfg = cfg || {};
65
+ cfg.indices = isUndefined(cfg.indices) ? false : cfg.indices;
66
+ cfg.nulls = isUndefined(cfg.nulls) ? true : cfg.nulls;
45
67
  fd = fd || new FormData();
46
68
 
47
69
  if (isUndefined(obj)) {
48
- return fd
70
+ return fd;
71
+ } else if (isNull(obj)) {
72
+ if (cfg.nulls) {
73
+ fd.append(pre, '');
74
+ }
49
75
  } else if (isArray(obj)) {
50
- obj.forEach(function (value) {
76
+ if (!obj.length) {
51
77
  var key = pre + '[]';
52
78
 
53
- objectToFormData(value, fd, key);
54
- });
55
- } else if (isObject(obj) && !isFile(obj) && !isDate(obj)) {
56
- Object.keys(obj).forEach(function (prop) {
79
+ fd.append(key, '');
80
+ } else {
81
+ obj.forEach(function(value, index) {
82
+ var key = pre + '[' + (cfg.indices ? index : '') + ']';
83
+
84
+ objectToFormData(value, cfg, fd, key);
85
+ });
86
+ }
87
+ } else if (isDate(obj)) {
88
+ fd.append(pre, obj.toISOString());
89
+ } else if (isObject(obj) && !isFile(obj) && !isBlob(obj)) {
90
+ Object.keys(obj).forEach(function(prop) {
57
91
  var value = obj[prop];
58
92
 
59
93
  if (isArray(value)) {
@@ -62,20 +96,20 @@
62
96
  }
63
97
  }
64
98
 
65
- var key = pre ? (pre + '[' + prop + ']') : prop;
99
+ var key = pre ? pre + '[' + prop + ']' : prop;
66
100
 
67
- objectToFormData(value, fd, key);
101
+ objectToFormData(value, cfg, fd, key);
68
102
  });
69
103
  } else {
70
104
  fd.append(pre, obj);
71
105
  }
72
106
 
73
- return fd
107
+ return fd;
74
108
  }
75
109
 
76
110
  var objectToFormdata = objectToFormData;
77
111
 
78
- // http://www.quirksmode.org/js/cookies.html
112
+ // https://www.quirksmode.org/js/cookies.html
79
113
 
80
114
  var Cookies = {
81
115
  set: function (name, value, ttl, domain) {
@@ -112,13 +146,16 @@
112
146
  urlPrefix: "",
113
147
  visitsUrl: "/ahoy/visits",
114
148
  eventsUrl: "/ahoy/events",
115
- cookieDomain: null,
116
149
  page: null,
117
150
  platform: "Web",
118
151
  useBeacon: true,
119
152
  startOnReady: true,
120
153
  trackVisits: true,
121
- cookies: true
154
+ cookies: true,
155
+ cookieDomain: null,
156
+ headers: {},
157
+ visitParams: {},
158
+ withCredentials: false
122
159
  };
123
160
 
124
161
  var ahoy = window.ahoy || window.Ahoy || {};
@@ -151,8 +188,12 @@
151
188
  return config.urlPrefix + config.eventsUrl;
152
189
  }
153
190
 
191
+ function isEmpty(obj) {
192
+ return Object.keys(obj).length === 0;
193
+ }
194
+
154
195
  function canTrackNow() {
155
- return (config.useBeacon || config.trackNow) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined";
196
+ return (config.useBeacon || config.trackNow) && isEmpty(config.headers) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined" && !config.withCredentials;
156
197
  }
157
198
 
158
199
  // cookies
@@ -220,7 +261,7 @@
220
261
  document.readyState === "interactive" || document.readyState === "complete" ? callback() : document.addEventListener("DOMContentLoaded", callback);
221
262
  }
222
263
 
223
- // http://stackoverflow.com/a/2117523/1177228
264
+ // https://stackoverflow.com/a/2117523/1177228
224
265
  function generateId() {
225
266
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
226
267
  var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
@@ -261,12 +302,22 @@
261
302
  contentType: "application/json; charset=utf-8",
262
303
  dataType: "json",
263
304
  beforeSend: CSRFProtection,
264
- success: success
305
+ success: success,
306
+ headers: config.headers,
307
+ xhrFields: {
308
+ withCredentials: config.withCredentials
309
+ }
265
310
  });
266
311
  } else {
267
312
  var xhr = new XMLHttpRequest();
268
313
  xhr.open("POST", url, true);
314
+ xhr.withCredentials = config.withCredentials;
269
315
  xhr.setRequestHeader("Content-Type", "application/json");
316
+ for (var header in config.headers) {
317
+ if (config.headers.hasOwnProperty(header)) {
318
+ xhr.setRequestHeader(header, config.headers[header]);
319
+ }
320
+ }
270
321
  xhr.onload = function() {
271
322
  if (xhr.status === 200) {
272
323
  success();
@@ -285,7 +336,8 @@
285
336
  if (config.cookies) {
286
337
  data.visit_token = event.visit_token;
287
338
  data.visitor_token = event.visitor_token;
288
- } delete event.visit_token;
339
+ }
340
+ delete event.visit_token;
289
341
  delete event.visitor_token;
290
342
  return data;
291
343
  }
@@ -402,6 +454,12 @@
402
454
  data.referrer = document.referrer;
403
455
  }
404
456
 
457
+ for (var key in config.visitParams) {
458
+ if (config.visitParams.hasOwnProperty(key)) {
459
+ data[key] = config.visitParams[key];
460
+ }
461
+ }
462
+
405
463
  log(data);
406
464
 
407
465
  sendRequest(visitsUrl(), data, function () {
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: 2.2.0
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-04 00:00:00.000000000 Z
11
+ date: 2019-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -295,8 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
295
295
  - !ruby/object:Gem::Version
296
296
  version: '0'
297
297
  requirements: []
298
- rubyforge_project:
299
- rubygems_version: 2.7.6
298
+ rubygems_version: 3.0.3
300
299
  signing_key:
301
300
  specification_version: 4
302
301
  summary: Simple, powerful analytics for Rails