ahoy_matey 2.0.2 → 2.1.0

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