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