ahoy_matey 2.0.0 → 3.2.0

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.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +112 -37
  3. data/CONTRIBUTING.md +9 -7
  4. data/LICENSE.txt +1 -1
  5. data/README.md +377 -63
  6. data/app/controllers/ahoy/base_controller.rb +14 -10
  7. data/app/controllers/ahoy/events_controller.rb +1 -1
  8. data/app/controllers/ahoy/visits_controller.rb +1 -0
  9. data/app/jobs/ahoy/geocode_v2_job.rb +3 -4
  10. data/lib/ahoy.rb +57 -2
  11. data/lib/ahoy/base_store.rb +32 -3
  12. data/lib/ahoy/controller.rb +21 -8
  13. data/lib/ahoy/database_store.rb +33 -16
  14. data/lib/ahoy/engine.rb +3 -1
  15. data/lib/ahoy/helper.rb +40 -0
  16. data/lib/ahoy/model.rb +2 -2
  17. data/lib/ahoy/query_methods.rb +46 -1
  18. data/lib/ahoy/tracker.rb +59 -27
  19. data/lib/ahoy/utils.rb +7 -0
  20. data/lib/ahoy/version.rb +1 -1
  21. data/lib/ahoy/visit_properties.rb +73 -37
  22. data/lib/generators/ahoy/activerecord_generator.rb +17 -26
  23. data/lib/generators/ahoy/base_generator.rb +1 -1
  24. data/lib/generators/ahoy/install_generator.rb +1 -1
  25. data/lib/generators/ahoy/mongoid_generator.rb +1 -5
  26. data/lib/generators/ahoy/templates/active_record_event_model.rb.tt +10 -0
  27. data/lib/generators/ahoy/templates/{active_record_migration.rb → active_record_migration.rb.tt} +14 -7
  28. data/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +6 -0
  29. data/lib/generators/ahoy/templates/{base_store_initializer.rb → base_store_initializer.rb.tt} +8 -0
  30. data/lib/generators/ahoy/templates/database_store_initializer.rb.tt +10 -0
  31. data/lib/generators/ahoy/templates/{mongoid_event_model.rb → mongoid_event_model.rb.tt} +1 -1
  32. data/lib/generators/ahoy/templates/{mongoid_visit_model.rb → mongoid_visit_model.rb.tt} +9 -7
  33. data/vendor/assets/javascripts/ahoy.js +539 -552
  34. metadata +27 -204
  35. data/.github/ISSUE_TEMPLATE.md +0 -7
  36. data/.gitignore +0 -17
  37. data/Gemfile +0 -6
  38. data/Rakefile +0 -9
  39. data/ahoy_matey.gemspec +0 -36
  40. data/docs/Ahoy-2-Upgrade.md +0 -147
  41. data/docs/Data-Store-Examples.md +0 -240
  42. data/lib/generators/ahoy/templates/active_record_event_model.rb +0 -10
  43. data/lib/generators/ahoy/templates/active_record_visit_model.rb +0 -6
  44. data/lib/generators/ahoy/templates/database_store_initializer.rb +0 -5
  45. data/test/query_methods/mongoid_test.rb +0 -23
  46. data/test/query_methods/mysql_json_test.rb +0 -18
  47. data/test/query_methods/mysql_text_test.rb +0 -19
  48. data/test/query_methods/postgresql_hstore_test.rb +0 -20
  49. data/test/query_methods/postgresql_json_test.rb +0 -18
  50. data/test/query_methods/postgresql_jsonb_test.rb +0 -19
  51. data/test/query_methods/postgresql_text_test.rb +0 -19
  52. data/test/test_helper.rb +0 -100
@@ -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.expand_path("../templates", __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.expand_path("../templates", __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
@@ -0,0 +1,10 @@
1
+ class Ahoy::Event < ApplicationRecord
2
+ include Ahoy::QueryMethods
3
+
4
+ self.table_name = "ahoy_events"
5
+
6
+ belongs_to :visit
7
+ belongs_to :user, optional: true<% if properties_type == "text" %>
8
+
9
+ serialize :properties, JSON<% end %>
10
+ end
@@ -1,6 +1,6 @@
1
1
  class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
- create_table :ahoy_visits do |t|
3
+ create_table :ahoy_visits do |t|
4
4
  t.string :visit_token
5
5
  t.string :visitor_token
6
6
 
@@ -15,7 +15,6 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
15
15
  t.text :user_agent
16
16
  t.text :referrer
17
17
  t.string :referring_domain
18
- t.string :search_keyword
19
18
  t.text :landing_page
20
19
 
21
20
  # technology
@@ -27,6 +26,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
27
26
  t.string :country
28
27
  t.string :region
29
28
  t.string :city
29
+ t.float :latitude
30
+ t.float :longitude
30
31
 
31
32
  # utm parameters
32
33
  t.string :utm_source
@@ -35,10 +36,15 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
35
36
  t.string :utm_content
36
37
  t.string :utm_campaign
37
38
 
38
- t.timestamp :started_at
39
+ # native apps
40
+ t.string :app_version
41
+ t.string :os_version
42
+ t.string :platform
43
+
44
+ t.datetime :started_at
39
45
  end
40
46
 
41
- add_index :ahoy_visits, [:visit_token], unique: true
47
+ add_index :ahoy_visits, :visit_token, unique: true
42
48
 
43
49
  create_table :ahoy_events do |t|
44
50
  t.references :visit
@@ -46,10 +52,11 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
46
52
 
47
53
  t.string :name
48
54
  t.<%= properties_type %> :properties
49
- t.timestamp :time
55
+ t.datetime :time
50
56
  end
51
57
 
52
- add_index :ahoy_events, [:name, :time]<% if properties_type == "jsonb" && rails5? %>
53
- add_index :ahoy_events, "properties jsonb_path_ops", using: "gin"<% end %>
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 %>
54
61
  end
55
62
  end
@@ -0,0 +1,6 @@
1
+ class Ahoy::Visit < ApplicationRecord
2
+ self.table_name = "ahoy_visits"
3
+
4
+ has_many :events, class_name: "Ahoy::Event"
5
+ belongs_to :user, optional: true
6
+ end
@@ -15,3 +15,11 @@ class Ahoy::Store < Ahoy::BaseStore
15
15
  # !!!
16
16
  end
17
17
  end
18
+
19
+ # set to true for JavaScript tracking
20
+ Ahoy.api = false
21
+
22
+ # set to true for geocoding
23
+ # we recommend configuring local geocoding first
24
+ # see https://github.com/ankane/ahoy#geocoding
25
+ Ahoy.geocode = false
@@ -0,0 +1,10 @@
1
+ class Ahoy::Store < Ahoy::DatabaseStore
2
+ end
3
+
4
+ # set to true for JavaScript tracking
5
+ Ahoy.api = false
6
+
7
+ # set to true for geocoding
8
+ # we recommend configuring local geocoding first
9
+ # see https://github.com/ankane/ahoy#geocoding
10
+ Ahoy.geocode = false
@@ -3,7 +3,7 @@ class Ahoy::Event
3
3
 
4
4
  # associations
5
5
  belongs_to :visit, index: true
6
- belongs_to :user, index: true<%= rails5? ? ", optional: true" : nil %>
6
+ belongs_to :user, index: true, optional: true
7
7
 
8
8
  # fields
9
9
  field :name, type: String
@@ -3,7 +3,7 @@ class Ahoy::Visit
3
3
 
4
4
  # associations
5
5
  has_many :events, class_name: "Ahoy::Event"
6
- belongs_to :user, index: true<%= rails5? ? ", optional: true" : nil %>
6
+ belongs_to :user, index: true, optional: true
7
7
 
8
8
  # required
9
9
  field :visit_token, type: String
@@ -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
@@ -41,6 +38,11 @@ class Ahoy::Visit
41
38
  field :utm_content, type: String
42
39
  field :utm_campaign, type: String
43
40
 
41
+ # native apps
42
+ field :app_version, type: String
43
+ field :os_version, type: String
44
+ field :platform, type: String
45
+
44
46
  field :started_at, type: Time
45
47
 
46
48
  index({visit_token: 1}, {unique: true})
@@ -1,628 +1,615 @@
1
- (function webpackUniversalModuleDefinition(root, factory) {
2
- if(typeof exports === 'object' && typeof module === 'object')
3
- module.exports = factory();
4
- else if(typeof define === 'function' && define.amd)
5
- define([], factory);
6
- else if(typeof exports === 'object')
7
- exports["ahoy"] = factory();
8
- else
9
- root["ahoy"] = factory();
10
- })(typeof self !== 'undefined' ? self : this, function() {
11
- return /******/ (function(modules) { // webpackBootstrap
12
- /******/ // The module cache
13
- /******/ var installedModules = {};
14
- /******/
15
- /******/ // The require function
16
- /******/ function __webpack_require__(moduleId) {
17
- /******/
18
- /******/ // Check if module is in cache
19
- /******/ if(installedModules[moduleId]) {
20
- /******/ return installedModules[moduleId].exports;
21
- /******/ }
22
- /******/ // Create a new module (and put it into the cache)
23
- /******/ var module = installedModules[moduleId] = {
24
- /******/ i: moduleId,
25
- /******/ l: false,
26
- /******/ exports: {}
27
- /******/ };
28
- /******/
29
- /******/ // Execute the module function
30
- /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31
- /******/
32
- /******/ // Flag the module as loaded
33
- /******/ module.l = true;
34
- /******/
35
- /******/ // Return the exports of the module
36
- /******/ return module.exports;
37
- /******/ }
38
- /******/
39
- /******/
40
- /******/ // expose the modules object (__webpack_modules__)
41
- /******/ __webpack_require__.m = modules;
42
- /******/
43
- /******/ // expose the module cache
44
- /******/ __webpack_require__.c = installedModules;
45
- /******/
46
- /******/ // define getter function for harmony exports
47
- /******/ __webpack_require__.d = function(exports, name, getter) {
48
- /******/ if(!__webpack_require__.o(exports, name)) {
49
- /******/ Object.defineProperty(exports, name, {
50
- /******/ configurable: false,
51
- /******/ enumerable: true,
52
- /******/ get: getter
53
- /******/ });
54
- /******/ }
55
- /******/ };
56
- /******/
57
- /******/ // getDefaultExport function for compatibility with non-harmony modules
58
- /******/ __webpack_require__.n = function(module) {
59
- /******/ var getter = module && module.__esModule ?
60
- /******/ function getDefault() { return module['default']; } :
61
- /******/ function getModuleExports() { return module; };
62
- /******/ __webpack_require__.d(getter, 'a', getter);
63
- /******/ return getter;
64
- /******/ };
65
- /******/
66
- /******/ // Object.prototype.hasOwnProperty.call
67
- /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
68
- /******/
69
- /******/ // __webpack_public_path__
70
- /******/ __webpack_require__.p = "";
71
- /******/
72
- /******/ // Load entry module and return exports
73
- /******/ return __webpack_require__(__webpack_require__.s = 0);
74
- /******/ })
75
- /************************************************************************/
76
- /******/ ([
77
- /* 0 */
78
- /***/ (function(module, exports, __webpack_require__) {
79
-
80
- "use strict";
81
-
82
-
83
- Object.defineProperty(exports, "__esModule", {
84
- value: true
85
- });
86
-
87
- var _objectToFormdata = __webpack_require__(1);
88
-
89
- var _objectToFormdata2 = _interopRequireDefault(_objectToFormdata);
90
-
91
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
92
-
93
- var config = {
94
- urlPrefix: "",
95
- visitsUrl: "/ahoy/visits",
96
- eventsUrl: "/ahoy/events",
97
- cookieDomain: null,
98
- page: null,
99
- platform: "Web",
100
- useBeacon: true,
101
- startOnReady: true
102
- }; /*
103
- * Ahoy.js
104
- * Simple, powerful JavaScript analytics
105
- * https://github.com/ankane/ahoy.js
106
- * v0.3.0
107
- * MIT License
108
- */
109
-
110
- var ahoy = window.ahoy || window.Ahoy || {};
111
-
112
- ahoy.configure = function (options) {
113
- for (var key in options) {
114
- if (options.hasOwnProperty(key)) {
115
- config[key] = options[key];
1
+ /*
2
+ * Ahoy.js
3
+ * Simple, powerful JavaScript analytics
4
+ * https://github.com/ankane/ahoy.js
5
+ * v0.3.8
6
+ * MIT License
7
+ */
8
+
9
+ (function (global, factory) {
10
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
11
+ typeof define === 'function' && define.amd ? define(factory) :
12
+ (global = global || self, global.ahoy = factory());
13
+ }(this, (function () { 'use strict';
14
+
15
+ var isUndefined = function (value) { return value === undefined; };
16
+
17
+ var isNull = function (value) { return value === null; };
18
+
19
+ var isBoolean = function (value) { return typeof value === 'boolean'; };
20
+
21
+ var isObject = function (value) { return value === Object(value); };
22
+
23
+ var isArray = function (value) { return Array.isArray(value); };
24
+
25
+ var isDate = function (value) { return value instanceof Date; };
26
+
27
+ var isBlob = function (value) { return value &&
28
+ typeof value.size === 'number' &&
29
+ typeof value.type === 'string' &&
30
+ typeof value.slice === 'function'; };
31
+
32
+ var isFile = function (value) { return isBlob(value) &&
33
+ typeof value.name === 'string' &&
34
+ (typeof value.lastModifiedDate === 'object' ||
35
+ typeof value.lastModified === 'number'); };
36
+
37
+ var serialize = function (obj, cfg, fd, pre) {
38
+ cfg = cfg || {};
39
+
40
+ cfg.indices = isUndefined(cfg.indices) ? false : cfg.indices;
41
+
42
+ cfg.nullsAsUndefineds = isUndefined(cfg.nullsAsUndefineds)
43
+ ? false
44
+ : cfg.nullsAsUndefineds;
45
+
46
+ cfg.booleansAsIntegers = isUndefined(cfg.booleansAsIntegers)
47
+ ? false
48
+ : cfg.booleansAsIntegers;
49
+
50
+ cfg.allowEmptyArrays = isUndefined(cfg.allowEmptyArrays)
51
+ ? false
52
+ : cfg.allowEmptyArrays;
53
+
54
+ fd = fd || new FormData();
55
+
56
+ if (isUndefined(obj)) {
57
+ return fd;
58
+ } else if (isNull(obj)) {
59
+ if (!cfg.nullsAsUndefineds) {
60
+ fd.append(pre, '');
61
+ }
62
+ } else if (isBoolean(obj)) {
63
+ if (cfg.booleansAsIntegers) {
64
+ fd.append(pre, obj ? 1 : 0);
65
+ } else {
66
+ fd.append(pre, obj);
67
+ }
68
+ } else if (isArray(obj)) {
69
+ if (obj.length) {
70
+ obj.forEach(function (value, index) {
71
+ var key = pre + '[' + (cfg.indices ? index : '') + ']';
72
+
73
+ serialize(value, cfg, fd, key);
74
+ });
75
+ } else if (cfg.allowEmptyArrays) {
76
+ fd.append(pre + '[]', '');
77
+ }
78
+ } else if (isDate(obj)) {
79
+ fd.append(pre, obj.toISOString());
80
+ } else if (isObject(obj) && !isFile(obj) && !isBlob(obj)) {
81
+ Object.keys(obj).forEach(function (prop) {
82
+ var value = obj[prop];
83
+
84
+ if (isArray(value)) {
85
+ while (prop.length > 2 && prop.lastIndexOf('[]') === prop.length - 2) {
86
+ prop = prop.substring(0, prop.length - 2);
87
+ }
88
+ }
89
+
90
+ var key = pre ? pre + '[' + prop + ']' : prop;
91
+
92
+ serialize(value, cfg, fd, key);
93
+ });
94
+ } else {
95
+ fd.append(pre, obj);
116
96
  }
117
- }
118
- };
119
-
120
- // legacy
121
- ahoy.configure(ahoy);
122
-
123
- var $ = window.jQuery || window.Zepto || window.$;
124
- var visitId = void 0,
125
- visitorId = void 0,
126
- track = void 0;
127
- var visitTtl = 4 * 60; // 4 hours
128
- var visitorTtl = 2 * 365 * 24 * 60; // 2 years
129
- var isReady = false;
130
- var queue = [];
131
- var canStringify = typeof JSON !== "undefined" && typeof JSON.stringify !== "undefined";
132
- var eventQueue = [];
133
-
134
- function visitsUrl() {
135
- return config.urlPrefix + config.visitsUrl;
136
- }
137
-
138
- function eventsUrl() {
139
- return config.urlPrefix + config.eventsUrl;
140
- }
141
-
142
- function canTrackNow() {
143
- return (config.useBeacon || config.trackNow) && canStringify && typeof window.navigator.sendBeacon !== "undefined";
144
- }
145
-
146
- // cookies
147
-
148
- // http://www.quirksmode.org/js/cookies.html
149
- function setCookie(name, value, ttl) {
150
- var expires = "";
151
- var cookieDomain = "";
152
- if (ttl) {
153
- var date = new Date();
154
- date.setTime(date.getTime() + ttl * 60 * 1000);
155
- expires = "; expires=" + date.toGMTString();
156
- }
157
- var domain = config.cookieDomain || config.domain;
158
- if (domain) {
159
- cookieDomain = "; domain=" + domain;
160
- }
161
- document.cookie = name + "=" + escape(value) + expires + cookieDomain + "; path=/";
162
- }
163
-
164
- function getCookie(name) {
165
- var i = void 0,
166
- c = void 0;
167
- var nameEQ = name + "=";
168
- var ca = document.cookie.split(';');
169
- for (i = 0; i < ca.length; i++) {
170
- c = ca[i];
171
- while (c.charAt(0) === ' ') {
172
- c = c.substring(1, c.length);
97
+
98
+ return fd;
99
+ };
100
+
101
+ var index_module = {
102
+ serialize: serialize,
103
+ };
104
+ var index_module_1 = index_module.serialize;
105
+
106
+ // https://www.quirksmode.org/js/cookies.html
107
+
108
+ var Cookies = {
109
+ set: function (name, value, ttl, domain) {
110
+ var expires = "";
111
+ var cookieDomain = "";
112
+ if (ttl) {
113
+ var date = new Date();
114
+ date.setTime(date.getTime() + (ttl * 60 * 1000));
115
+ expires = "; expires=" + date.toGMTString();
116
+ }
117
+ if (domain) {
118
+ cookieDomain = "; domain=" + domain;
119
+ }
120
+ document.cookie = name + "=" + escape(value) + expires + cookieDomain + "; path=/";
121
+ },
122
+ get: function (name) {
123
+ var i, c;
124
+ var nameEQ = name + "=";
125
+ var ca = document.cookie.split(';');
126
+ for (i = 0; i < ca.length; i++) {
127
+ c = ca[i];
128
+ while (c.charAt(0) === ' ') {
129
+ c = c.substring(1, c.length);
130
+ }
131
+ if (c.indexOf(nameEQ) === 0) {
132
+ return unescape(c.substring(nameEQ.length, c.length));
133
+ }
134
+ }
135
+ return null;
173
136
  }
174
- if (c.indexOf(nameEQ) === 0) {
175
- return unescape(c.substring(nameEQ.length, c.length));
137
+ };
138
+
139
+ var config = {
140
+ urlPrefix: "",
141
+ visitsUrl: "/ahoy/visits",
142
+ eventsUrl: "/ahoy/events",
143
+ page: null,
144
+ platform: "Web",
145
+ useBeacon: true,
146
+ startOnReady: true,
147
+ trackVisits: true,
148
+ cookies: true,
149
+ cookieDomain: null,
150
+ headers: {},
151
+ visitParams: {},
152
+ withCredentials: false,
153
+ visitDuration: 4 * 60, // default 4 hours
154
+ visitorDuration: 2 * 365 * 24 * 60 // default 2 years
155
+ };
156
+
157
+ var ahoy = window.ahoy || window.Ahoy || {};
158
+
159
+ ahoy.configure = function (options) {
160
+ for (var key in options) {
161
+ if (options.hasOwnProperty(key)) {
162
+ config[key] = options[key];
163
+ }
176
164
  }
165
+ };
166
+
167
+ // legacy
168
+ ahoy.configure(ahoy);
169
+
170
+ var $ = window.jQuery || window.Zepto || window.$;
171
+ var visitId, visitorId, track;
172
+ var isReady = false;
173
+ var queue = [];
174
+ var canStringify = typeof(JSON) !== "undefined" && typeof(JSON.stringify) !== "undefined";
175
+ var eventQueue = [];
176
+
177
+ function visitsUrl() {
178
+ return config.urlPrefix + config.visitsUrl;
177
179
  }
178
- return null;
179
- }
180
180
 
181
- function destroyCookie(name) {
182
- setCookie(name, "", -1);
183
- }
181
+ function eventsUrl() {
182
+ return config.urlPrefix + config.eventsUrl;
183
+ }
184
+
185
+ function isEmpty(obj) {
186
+ return Object.keys(obj).length === 0;
187
+ }
184
188
 
185
- function log(message) {
186
- if (getCookie("ahoy_debug")) {
187
- window.console.log(message);
189
+ function canTrackNow() {
190
+ return (config.useBeacon || config.trackNow) && isEmpty(config.headers) && canStringify && typeof(window.navigator.sendBeacon) !== "undefined" && !config.withCredentials;
188
191
  }
189
- }
190
192
 
191
- function setReady() {
192
- var callback = void 0;
193
- while (callback = queue.shift()) {
194
- callback();
193
+ // cookies
194
+
195
+ function setCookie(name, value, ttl) {
196
+ Cookies.set(name, value, ttl, config.cookieDomain || config.domain);
195
197
  }
196
- isReady = true;
197
- }
198
-
199
- function ready(callback) {
200
- if (isReady) {
201
- callback();
202
- } else {
203
- queue.push(callback);
198
+
199
+ function getCookie(name) {
200
+ return Cookies.get(name);
204
201
  }
205
- }
206
202
 
207
- function matchesSelector(element, selector) {
208
- if (element.matches) {
209
- return element.matches(selector);
210
- } else {
211
- return element.msMatchesSelector(selector);
203
+ function destroyCookie(name) {
204
+ Cookies.set(name, "", -1);
212
205
  }
213
- }
214
206
 
215
- function onEvent(eventName, selector, callback) {
216
- document.addEventListener(eventName, function (e) {
217
- if (matchesSelector(e.target, selector)) {
218
- callback(e);
207
+ function log(message) {
208
+ if (getCookie("ahoy_debug")) {
209
+ window.console.log(message);
219
210
  }
220
- });
221
- }
222
-
223
- // http://beeker.io/jquery-document-ready-equivalent-vanilla-javascript
224
- function documentReady(callback) {
225
- document.readyState === "interactive" || document.readyState === "complete" ? callback() : document.addEventListener("DOMContentLoaded", callback);
226
- }
227
-
228
- // http://stackoverflow.com/a/2117523/1177228
229
- function generateId() {
230
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
231
- var r = Math.random() * 16 | 0,
232
- v = c == 'x' ? r : r & 0x3 | 0x8;
233
- return v.toString(16);
234
- });
235
- }
211
+ }
236
212
 
237
- function saveEventQueue() {
238
- // TODO add stringify method for IE 7 and under
239
- if (canStringify) {
240
- setCookie("ahoy_events", JSON.stringify(eventQueue), 1);
213
+ function setReady() {
214
+ var callback;
215
+ while ((callback = queue.shift())) {
216
+ callback();
217
+ }
218
+ isReady = true;
241
219
  }
242
- }
243
-
244
- // from rails-ujs
245
-
246
- function csrfToken() {
247
- var meta = document.querySelector("meta[name=csrf-token]");
248
- return meta && meta.content;
249
- }
250
-
251
- function csrfParam() {
252
- var meta = document.querySelector("meta[name=csrf-param]");
253
- return meta && meta.content;
254
- }
255
-
256
- function CSRFProtection(xhr) {
257
- var token = csrfToken();
258
- if (token) xhr.setRequestHeader("X-CSRF-Token", token);
259
- }
260
-
261
- function sendRequest(url, data, success) {
262
- if (canStringify) {
263
- if ($) {
264
- $.ajax({
265
- type: "POST",
266
- url: url,
267
- data: JSON.stringify(data),
268
- contentType: "application/json; charset=utf-8",
269
- dataType: "json",
270
- beforeSend: CSRFProtection,
271
- success: success
272
- });
220
+
221
+ ahoy.ready = function (callback) {
222
+ if (isReady) {
223
+ callback();
273
224
  } else {
274
- var xhr = new XMLHttpRequest();
275
- xhr.open("POST", url, true);
276
- xhr.setRequestHeader("Content-Type", "application/json");
277
- xhr.onload = function () {
278
- if (xhr.status === 200) {
279
- success();
280
- }
281
- };
282
- CSRFProtection(xhr);
283
- xhr.send(JSON.stringify(data));
225
+ queue.push(callback);
226
+ }
227
+ };
228
+
229
+ function matchesSelector(element, selector) {
230
+ var matches = element.matches ||
231
+ element.matchesSelector ||
232
+ element.mozMatchesSelector ||
233
+ element.msMatchesSelector ||
234
+ element.oMatchesSelector ||
235
+ element.webkitMatchesSelector;
236
+
237
+ if (matches) {
238
+ if (matches.apply(element, [selector])) {
239
+ return element;
240
+ } else if (element.parentElement) {
241
+ return matchesSelector(element.parentElement, selector)
242
+ }
243
+ return null;
244
+ } else {
245
+ log("Unable to match");
246
+ return null;
284
247
  }
285
248
  }
286
- }
287
249
 
288
- function eventData(event) {
289
- var data = {
290
- events: [event],
291
- visit_token: event.visit_token,
292
- visitor_token: event.visitor_token
293
- };
294
- delete event.visit_token;
295
- delete event.visitor_token;
296
- return data;
297
- }
298
-
299
- function trackEvent(event) {
300
- ready(function () {
301
- sendRequest(eventsUrl(), eventData(event), function () {
302
- // remove from queue
303
- for (var i = 0; i < eventQueue.length; i++) {
304
- if (eventQueue[i].id == event.id) {
305
- eventQueue.splice(i, 1);
306
- break;
307
- }
250
+ function onEvent(eventName, selector, callback) {
251
+ document.addEventListener(eventName, function (e) {
252
+ var matchedElement = matchesSelector(e.target, selector);
253
+ if (matchedElement) {
254
+ callback.call(matchedElement, e);
308
255
  }
309
- saveEventQueue();
310
256
  });
311
- });
312
- }
313
-
314
- function trackEventNow(event) {
315
- ready(function () {
316
- var data = eventData(event);
317
- var param = csrfParam();
318
- var token = csrfToken();
319
- if (param && token) data[param] = token;
320
- // stringify so we keep the type
321
- data.events_json = JSON.stringify(data.events);
322
- delete data.events;
323
- window.navigator.sendBeacon(eventsUrl(), (0, _objectToFormdata2.default)(data));
324
- });
325
- }
326
-
327
- function page() {
328
- return config.page || window.location.pathname;
329
- }
330
-
331
- function presence(str) {
332
- return str && str.length > 0 ? str : null;
333
- }
257
+ }
334
258
 
335
- function cleanObject(obj) {
336
- for (var key in obj) {
337
- if (obj.hasOwnProperty(key)) {
338
- if (obj[key] === null) {
339
- delete obj[key];
340
- }
259
+ // http://beeker.io/jquery-document-ready-equivalent-vanilla-javascript
260
+ function documentReady(callback) {
261
+ if (document.readyState === "interactive" || document.readyState === "complete") {
262
+ setTimeout(callback, 0);
263
+ } else {
264
+ document.addEventListener("DOMContentLoaded", callback);
341
265
  }
342
266
  }
343
- return obj;
344
- }
345
-
346
- function eventProperties(e) {
347
- var target = e.target;
348
- return cleanObject({
349
- tag: target.tagName.toLowerCase(),
350
- id: presence(target.id),
351
- "class": presence(target.className),
352
- page: page(),
353
- section: getClosestSection(target)
354
- });
355
- }
356
267
 
357
- function getClosestSection(element) {
358
- for (; element && element !== document; element = element.parentNode) {
359
- if (element.hasAttribute('data-section')) {
360
- return element.getAttribute('data-section');
268
+ // https://stackoverflow.com/a/2117523/1177228
269
+ function generateId() {
270
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
271
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
272
+ return v.toString(16);
273
+ });
274
+ }
275
+
276
+ function saveEventQueue() {
277
+ if (config.cookies && canStringify) {
278
+ setCookie("ahoy_events", JSON.stringify(eventQueue), 1);
361
279
  }
362
280
  }
363
281
 
364
- return null;
365
- }
282
+ // from rails-ujs
366
283
 
367
- function createVisit() {
368
- isReady = false;
284
+ function csrfToken() {
285
+ var meta = document.querySelector("meta[name=csrf-token]");
286
+ return meta && meta.content;
287
+ }
288
+
289
+ function csrfParam() {
290
+ var meta = document.querySelector("meta[name=csrf-param]");
291
+ return meta && meta.content;
292
+ }
369
293
 
370
- visitId = ahoy.getVisitId();
371
- visitorId = ahoy.getVisitorId();
372
- track = getCookie("ahoy_track");
294
+ function CSRFProtection(xhr) {
295
+ var token = csrfToken();
296
+ if (token) { xhr.setRequestHeader("X-CSRF-Token", token); }
297
+ }
373
298
 
374
- if (visitId && visitorId && !track) {
375
- // TODO keep visit alive?
376
- log("Active visit");
377
- setReady();
378
- } else {
379
- if (track) {
380
- destroyCookie("ahoy_track");
299
+ function sendRequest(url, data, success) {
300
+ if (canStringify) {
301
+ if ($ && $.ajax) {
302
+ $.ajax({
303
+ type: "POST",
304
+ url: url,
305
+ data: JSON.stringify(data),
306
+ contentType: "application/json; charset=utf-8",
307
+ dataType: "json",
308
+ beforeSend: CSRFProtection,
309
+ success: success,
310
+ headers: config.headers,
311
+ xhrFields: {
312
+ withCredentials: config.withCredentials
313
+ }
314
+ });
315
+ } else {
316
+ var xhr = new XMLHttpRequest();
317
+ xhr.open("POST", url, true);
318
+ xhr.withCredentials = config.withCredentials;
319
+ xhr.setRequestHeader("Content-Type", "application/json");
320
+ for (var header in config.headers) {
321
+ if (config.headers.hasOwnProperty(header)) {
322
+ xhr.setRequestHeader(header, config.headers[header]);
323
+ }
324
+ }
325
+ xhr.onload = function() {
326
+ if (xhr.status === 200) {
327
+ success();
328
+ }
329
+ };
330
+ CSRFProtection(xhr);
331
+ xhr.send(JSON.stringify(data));
332
+ }
381
333
  }
334
+ }
382
335
 
383
- if (!visitId) {
384
- visitId = generateId();
385
- setCookie("ahoy_visit", visitId, visitTtl);
336
+ function eventData(event) {
337
+ var data = {
338
+ events: [event]
339
+ };
340
+ if (config.cookies) {
341
+ data.visit_token = event.visit_token;
342
+ data.visitor_token = event.visitor_token;
386
343
  }
344
+ delete event.visit_token;
345
+ delete event.visitor_token;
346
+ return data;
347
+ }
387
348
 
388
- // make sure cookies are enabled
389
- if (getCookie("ahoy_visit")) {
390
- log("Visit started");
349
+ function trackEvent(event) {
350
+ ahoy.ready( function () {
351
+ sendRequest(eventsUrl(), eventData(event), function() {
352
+ // remove from queue
353
+ for (var i = 0; i < eventQueue.length; i++) {
354
+ if (eventQueue[i].id == event.id) {
355
+ eventQueue.splice(i, 1);
356
+ break;
357
+ }
358
+ }
359
+ saveEventQueue();
360
+ });
361
+ });
362
+ }
391
363
 
392
- if (!visitorId) {
393
- visitorId = generateId();
394
- setCookie("ahoy_visitor", visitorId, visitorTtl);
395
- }
364
+ function trackEventNow(event) {
365
+ ahoy.ready( function () {
366
+ var data = eventData(event);
367
+ var param = csrfParam();
368
+ var token = csrfToken();
369
+ if (param && token) { data[param] = token; }
370
+ // stringify so we keep the type
371
+ data.events_json = JSON.stringify(data.events);
372
+ delete data.events;
373
+ window.navigator.sendBeacon(eventsUrl(), index_module_1(data));
374
+ });
375
+ }
396
376
 
397
- var data = {
398
- visit_token: visitId,
399
- visitor_token: visitorId,
400
- platform: config.platform,
401
- landing_page: window.location.href,
402
- screen_width: window.screen.width,
403
- screen_height: window.screen.height
404
- };
405
-
406
- // referrer
407
- if (document.referrer.length > 0) {
408
- data.referrer = document.referrer;
409
- }
377
+ function page() {
378
+ return config.page || window.location.pathname;
379
+ }
410
380
 
411
- log(data);
381
+ function presence(str) {
382
+ return (str && str.length > 0) ? str : null;
383
+ }
412
384
 
413
- sendRequest(visitsUrl(), data, setReady);
414
- } else {
415
- log("Cookies disabled");
416
- setReady();
385
+ function cleanObject(obj) {
386
+ for (var key in obj) {
387
+ if (obj.hasOwnProperty(key)) {
388
+ if (obj[key] === null) {
389
+ delete obj[key];
390
+ }
391
+ }
417
392
  }
393
+ return obj;
418
394
  }
419
- }
420
-
421
- ahoy.getVisitId = ahoy.getVisitToken = function () {
422
- return getCookie("ahoy_visit");
423
- };
424
-
425
- ahoy.getVisitorId = ahoy.getVisitorToken = function () {
426
- return getCookie("ahoy_visitor");
427
- };
428
-
429
- ahoy.reset = function () {
430
- destroyCookie("ahoy_visit");
431
- destroyCookie("ahoy_visitor");
432
- destroyCookie("ahoy_events");
433
- destroyCookie("ahoy_track");
434
- return true;
435
- };
436
-
437
- ahoy.debug = function (enabled) {
438
- if (enabled === false) {
439
- destroyCookie("ahoy_debug");
440
- } else {
441
- setCookie("ahoy_debug", "t", 365 * 24 * 60); // 1 year
442
- }
443
- return true;
444
- };
445
-
446
- ahoy.track = function (name, properties) {
447
- // generate unique id
448
- var event = {
449
- name: name,
450
- properties: properties || {},
451
- time: new Date().getTime() / 1000.0,
452
- id: generateId()
453
- };
454
395
 
455
- // wait for createVisit to log
456
- documentReady(function () {
457
- log(event);
458
- });
396
+ function eventProperties(e) {
397
+ return cleanObject({
398
+ tag: this.tagName.toLowerCase(),
399
+ id: presence(this.id),
400
+ "class": presence(this.className),
401
+ page: page(),
402
+ section: getClosestSection(this)
403
+ });
404
+ }
459
405
 
460
- ready(function () {
461
- if (!ahoy.getVisitId()) {
462
- createVisit();
406
+ function getClosestSection(element) {
407
+ for ( ; element && element !== document; element = element.parentNode) {
408
+ if (element.hasAttribute('data-section')) {
409
+ return element.getAttribute('data-section');
410
+ }
463
411
  }
464
412
 
465
- event.visit_token = ahoy.getVisitId();
466
- event.visitor_token = ahoy.getVisitorId();
413
+ return null;
414
+ }
467
415
 
468
- if (canTrackNow()) {
469
- trackEventNow(event);
416
+ function createVisit() {
417
+ isReady = false;
418
+
419
+ visitId = ahoy.getVisitId();
420
+ visitorId = ahoy.getVisitorId();
421
+ track = getCookie("ahoy_track");
422
+
423
+ if (config.cookies === false || config.trackVisits === false) {
424
+ log("Visit tracking disabled");
425
+ setReady();
426
+ } else if (visitId && visitorId && !track) {
427
+ // TODO keep visit alive?
428
+ log("Active visit");
429
+ setReady();
470
430
  } else {
471
- eventQueue.push(event);
472
- saveEventQueue();
431
+ if (!visitId) {
432
+ visitId = generateId();
433
+ setCookie("ahoy_visit", visitId, config.visitDuration);
434
+ }
473
435
 
474
- // wait in case navigating to reduce duplicate events
475
- setTimeout(function () {
476
- trackEvent(event);
477
- }, 1000);
478
- }
479
- });
480
- };
436
+ // make sure cookies are enabled
437
+ if (getCookie("ahoy_visit")) {
438
+ log("Visit started");
481
439
 
482
- ahoy.trackView = function (additionalProperties) {
483
- var properties = {
484
- url: window.location.href,
485
- title: document.title,
486
- page: page()
487
- };
440
+ if (!visitorId) {
441
+ visitorId = generateId();
442
+ setCookie("ahoy_visitor", visitorId, config.visitorDuration);
443
+ }
488
444
 
489
- if (additionalProperties) {
490
- for (var propName in additionalProperties) {
491
- if (additionalProperties.hasOwnProperty(propName)) {
492
- properties[propName] = additionalProperties[propName];
445
+ var data = {
446
+ visit_token: visitId,
447
+ visitor_token: visitorId,
448
+ platform: config.platform,
449
+ landing_page: window.location.href,
450
+ screen_width: window.screen.width,
451
+ screen_height: window.screen.height,
452
+ js: true
453
+ };
454
+
455
+ // referrer
456
+ if (document.referrer.length > 0) {
457
+ data.referrer = document.referrer;
458
+ }
459
+
460
+ for (var key in config.visitParams) {
461
+ if (config.visitParams.hasOwnProperty(key)) {
462
+ data[key] = config.visitParams[key];
463
+ }
464
+ }
465
+
466
+ log(data);
467
+
468
+ sendRequest(visitsUrl(), data, function () {
469
+ // wait until successful to destroy
470
+ destroyCookie("ahoy_track");
471
+ setReady();
472
+ });
473
+ } else {
474
+ log("Cookies disabled");
475
+ setReady();
493
476
  }
494
477
  }
495
478
  }
496
- ahoy.track("$view", properties);
497
- };
498
-
499
- ahoy.trackClicks = function () {
500
- onEvent("click", "a, button, input[type=submit]", function (e) {
501
- var target = e.target;
502
- var properties = eventProperties(e);
503
- properties.text = properties.tag == "input" ? target.value : (target.textContent || target.innerText || target.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
504
- properties.href = target.href;
505
- ahoy.track("$click", properties);
506
- });
507
- };
508
-
509
- ahoy.trackSubmits = function () {
510
- onEvent("submit", "form", function (e) {
511
- var properties = eventProperties(e);
512
- ahoy.track("$submit", properties);
513
- });
514
- };
515
479
 
516
- ahoy.trackChanges = function () {
517
- onEvent("change", "input, textarea, select", function (e) {
518
- var properties = eventProperties(e);
519
- ahoy.track("$change", properties);
520
- });
521
- };
522
-
523
- ahoy.trackAll = function () {
524
- ahoy.trackView();
525
- ahoy.trackClicks();
526
- ahoy.trackSubmits();
527
- ahoy.trackChanges();
528
- };
529
-
530
- // push events from queue
531
- try {
532
- eventQueue = JSON.parse(getCookie("ahoy_events") || "[]");
533
- } catch (e) {
534
- // do nothing
535
- }
536
-
537
- for (var i = 0; i < eventQueue.length; i++) {
538
- trackEvent(eventQueue[i]);
539
- }
540
-
541
- ahoy.start = function () {
542
- createVisit();
543
-
544
- ahoy.start = function () {};
545
- };
546
-
547
- documentReady(function () {
548
- if (config.startOnReady) {
549
- ahoy.start();
550
- }
551
- });
480
+ ahoy.getVisitId = ahoy.getVisitToken = function () {
481
+ return getCookie("ahoy_visit");
482
+ };
552
483
 
553
- exports.default = ahoy;
484
+ ahoy.getVisitorId = ahoy.getVisitorToken = function () {
485
+ return getCookie("ahoy_visitor");
486
+ };
554
487
 
555
- /***/ }),
556
- /* 1 */
557
- /***/ (function(module, exports, __webpack_require__) {
488
+ ahoy.reset = function () {
489
+ destroyCookie("ahoy_visit");
490
+ destroyCookie("ahoy_visitor");
491
+ destroyCookie("ahoy_events");
492
+ destroyCookie("ahoy_track");
493
+ return true;
494
+ };
558
495
 
559
- "use strict";
496
+ ahoy.debug = function (enabled) {
497
+ if (enabled === false) {
498
+ destroyCookie("ahoy_debug");
499
+ } else {
500
+ setCookie("ahoy_debug", "t", 365 * 24 * 60); // 1 year
501
+ }
502
+ return true;
503
+ };
560
504
 
505
+ ahoy.track = function (name, properties) {
506
+ // generate unique id
507
+ var event = {
508
+ name: name,
509
+ properties: properties || {},
510
+ time: (new Date()).getTime() / 1000.0,
511
+ id: generateId(),
512
+ js: true
513
+ };
514
+
515
+ ahoy.ready( function () {
516
+ if (config.cookies && !ahoy.getVisitId()) {
517
+ createVisit();
518
+ }
561
519
 
562
- function isUndefined (value) {
563
- return value === undefined
564
- }
520
+ ahoy.ready( function () {
521
+ log(event);
565
522
 
566
- function isObject (value) {
567
- return value === Object(value)
568
- }
523
+ event.visit_token = ahoy.getVisitId();
524
+ event.visitor_token = ahoy.getVisitorId();
569
525
 
570
- function isArray (value) {
571
- return Array.isArray(value)
572
- }
526
+ if (canTrackNow()) {
527
+ trackEventNow(event);
528
+ } else {
529
+ eventQueue.push(event);
530
+ saveEventQueue();
573
531
 
574
- function isBlob (value) {
575
- return value != null &&
576
- typeof value.size === 'number' &&
577
- typeof value.type === 'string' &&
578
- typeof value.slice === 'function'
579
- }
532
+ // wait in case navigating to reduce duplicate events
533
+ setTimeout( function () {
534
+ trackEvent(event);
535
+ }, 1000);
536
+ }
537
+ });
538
+ });
580
539
 
581
- function isFile (value) {
582
- return isBlob(value) &&
583
- typeof value.lastModified === 'number' &&
584
- typeof value.name === 'string'
585
- }
540
+ return true;
541
+ };
586
542
 
587
- function isDate (value) {
588
- return value instanceof Date
589
- }
543
+ ahoy.trackView = function (additionalProperties) {
544
+ var properties = {
545
+ url: window.location.href,
546
+ title: document.title,
547
+ page: page()
548
+ };
549
+
550
+ if (additionalProperties) {
551
+ for(var propName in additionalProperties) {
552
+ if (additionalProperties.hasOwnProperty(propName)) {
553
+ properties[propName] = additionalProperties[propName];
554
+ }
555
+ }
556
+ }
557
+ ahoy.track("$view", properties);
558
+ };
590
559
 
591
- function objectToFormData (obj, fd, pre) {
592
- fd = fd || new FormData()
560
+ ahoy.trackClicks = function () {
561
+ onEvent("click", "a, button, input[type=submit]", function (e) {
562
+ var properties = eventProperties.call(this, e);
563
+ properties.text = properties.tag == "input" ? this.value : (this.textContent || this.innerText || this.innerHTML).replace(/[\s\r\n]+/g, " ").trim();
564
+ properties.href = this.href;
565
+ ahoy.track("$click", properties);
566
+ });
567
+ };
593
568
 
594
- if (isUndefined(obj)) {
595
- return fd
596
- } else if (isArray(obj)) {
597
- obj.forEach(function (value) {
598
- var key = pre + '[]'
569
+ ahoy.trackSubmits = function () {
570
+ onEvent("submit", "form", function (e) {
571
+ var properties = eventProperties.call(this, e);
572
+ ahoy.track("$submit", properties);
573
+ });
574
+ };
599
575
 
600
- objectToFormData(value, fd, key)
601
- })
602
- } else if (isObject(obj) && !isFile(obj) && !isDate(obj)) {
603
- Object.keys(obj).forEach(function (prop) {
604
- var value = obj[prop]
576
+ ahoy.trackChanges = function () {
577
+ onEvent("change", "input, textarea, select", function (e) {
578
+ var properties = eventProperties.call(this, e);
579
+ ahoy.track("$change", properties);
580
+ });
581
+ };
605
582
 
606
- if (isArray(value)) {
607
- while (prop.length > 2 && prop.lastIndexOf('[]') === prop.length - 2) {
608
- prop = prop.substring(0, prop.length - 2)
609
- }
610
- }
583
+ ahoy.trackAll = function() {
584
+ ahoy.trackView();
585
+ ahoy.trackClicks();
586
+ ahoy.trackSubmits();
587
+ ahoy.trackChanges();
588
+ };
611
589
 
612
- var key = pre ? (pre + '[' + prop + ']') : prop
590
+ // push events from queue
591
+ try {
592
+ eventQueue = JSON.parse(getCookie("ahoy_events") || "[]");
593
+ } catch (e) {
594
+ // do nothing
595
+ }
613
596
 
614
- objectToFormData(value, fd, key)
615
- })
616
- } else {
617
- fd.append(pre, obj)
597
+ for (var i = 0; i < eventQueue.length; i++) {
598
+ trackEvent(eventQueue[i]);
618
599
  }
619
600
 
620
- return fd
621
- }
601
+ ahoy.start = function () {
602
+ createVisit();
622
603
 
623
- module.exports = objectToFormData
604
+ ahoy.start = function () {};
605
+ };
606
+
607
+ documentReady(function() {
608
+ if (config.startOnReady) {
609
+ ahoy.start();
610
+ }
611
+ });
624
612
 
613
+ return ahoy;
625
614
 
626
- /***/ })
627
- /******/ ])["default"];
628
- });
615
+ })));