ahoy_matey 2.0.0 → 3.2.0

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