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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +8 -0
- data/Gemfile +1 -1
- data/README.md +102 -4
- data/ahoy_matey.gemspec +1 -0
- data/lib/ahoy.rb +22 -0
- data/lib/ahoy/base_store.rb +15 -1
- data/lib/ahoy/controller.rb +7 -2
- data/lib/ahoy/model.rb +3 -1
- data/lib/ahoy/tracker.rb +22 -5
- data/lib/ahoy/version.rb +1 -1
- data/lib/ahoy/visit_properties.rb +64 -27
- data/lib/generators/ahoy/templates/active_record_migration.rb +1 -2
- data/lib/generators/ahoy/templates/database_store_initializer.rb +3 -0
- data/vendor/assets/javascripts/ahoy.js +458 -561
- metadata +17 -3
@@ -1,6 +1,6 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
|
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
|
@@ -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.
|
5
|
+
* v0.3.3
|
102
6
|
* MIT License
|
103
7
|
*/
|
104
8
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
127
|
-
|
15
|
+
function isUndefined (value) {
|
16
|
+
return value === undefined
|
17
|
+
}
|
128
18
|
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
141
|
-
|
142
|
-
}
|
23
|
+
function isArray (value) {
|
24
|
+
return Array.isArray(value)
|
25
|
+
}
|
143
26
|
|
144
|
-
function
|
145
|
-
|
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
|
149
|
-
|
150
|
-
|
34
|
+
function isFile (value) {
|
35
|
+
return isBlob(value) &&
|
36
|
+
typeof value.lastModified === 'number' &&
|
37
|
+
typeof value.name === 'string'
|
38
|
+
}
|
151
39
|
|
152
|
-
|
40
|
+
function isDate (value) {
|
41
|
+
return value instanceof Date
|
42
|
+
}
|
153
43
|
|
154
|
-
function
|
155
|
-
|
156
|
-
}
|
44
|
+
function objectToFormData (obj, fd, pre) {
|
45
|
+
fd = fd || new FormData();
|
157
46
|
|
158
|
-
|
159
|
-
|
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
|
-
|
163
|
-
|
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
|
-
|
167
|
-
if (getCookie("ahoy_debug")) {
|
168
|
-
window.console.log(message);
|
169
|
-
}
|
170
|
-
}
|
65
|
+
var key = pre ? (pre + '[' + prop + ']') : prop;
|
171
66
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
isReady = true;
|
178
|
-
}
|
67
|
+
objectToFormData(value, fd, key);
|
68
|
+
});
|
69
|
+
} else {
|
70
|
+
fd.append(pre, obj);
|
71
|
+
}
|
179
72
|
|
180
|
-
|
181
|
-
if (isReady) {
|
182
|
-
callback();
|
183
|
-
} else {
|
184
|
-
queue.push(callback);
|
73
|
+
return fd
|
185
74
|
}
|
186
|
-
}
|
187
75
|
|
188
|
-
|
189
|
-
var matches = element.matches || element.matchesSelector || element.mozMatchesSelector || element.msMatchesSelector || element.oMatchesSelector || element.webkitMatchesSelector;
|
76
|
+
var objectToFormdata = objectToFormData;
|
190
77
|
|
191
|
-
|
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
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
}
|
211
|
-
|
212
|
-
|
213
|
-
function
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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
|
-
|
266
|
-
xhr.send(JSON.stringify(data));
|
106
|
+
}
|
107
|
+
return null;
|
267
108
|
}
|
268
|
-
}
|
269
|
-
}
|
109
|
+
};
|
270
110
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
-
|
293
|
-
|
294
|
-
});
|
295
|
-
}
|
131
|
+
}
|
132
|
+
};
|
296
133
|
|
297
|
-
|
298
|
-
|
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
|
-
|
311
|
-
|
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
|
315
|
-
|
316
|
-
}
|
146
|
+
function visitsUrl() {
|
147
|
+
return config.urlPrefix + config.visitsUrl;
|
148
|
+
}
|
317
149
|
|
318
|
-
function
|
319
|
-
|
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
|
341
|
-
|
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
|
-
|
348
|
-
}
|
158
|
+
// cookies
|
349
159
|
|
350
|
-
function
|
351
|
-
|
160
|
+
function setCookie(name, value, ttl) {
|
161
|
+
Cookies.set(name, value, ttl, config.cookieDomain || config.domain);
|
162
|
+
}
|
352
163
|
|
353
|
-
|
354
|
-
|
355
|
-
|
164
|
+
function getCookie(name) {
|
165
|
+
return Cookies.get(name);
|
166
|
+
}
|
356
167
|
|
357
|
-
|
358
|
-
|
359
|
-
|
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
|
-
|
368
|
-
if (getCookie("
|
369
|
-
log(
|
172
|
+
function log(message) {
|
173
|
+
if (getCookie("ahoy_debug")) {
|
174
|
+
window.console.log(message);
|
175
|
+
}
|
176
|
+
}
|
370
177
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
178
|
+
function setReady() {
|
179
|
+
var callback;
|
180
|
+
while ((callback = queue.shift())) {
|
181
|
+
callback();
|
182
|
+
}
|
183
|
+
isReady = true;
|
184
|
+
}
|
375
185
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
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
|
-
|
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
|
-
|
394
|
-
|
395
|
-
destroyCookie("ahoy_track");
|
396
|
-
setReady();
|
397
|
-
});
|
202
|
+
if (matches) {
|
203
|
+
return matches.apply(element, [selector]);
|
398
204
|
} else {
|
399
|
-
log("
|
400
|
-
|
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
|
-
|
441
|
-
|
442
|
-
|
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
|
-
|
446
|
-
log(event);
|
237
|
+
// from rails-ujs
|
447
238
|
|
448
|
-
|
449
|
-
|
239
|
+
function csrfToken() {
|
240
|
+
var meta = document.querySelector("meta[name=csrf-token]");
|
241
|
+
return meta && meta.content;
|
242
|
+
}
|
450
243
|
|
451
|
-
|
452
|
-
|
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
|
-
|
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
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
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
|
-
|
466
|
-
|
321
|
+
function page() {
|
322
|
+
return config.page || window.location.pathname;
|
323
|
+
}
|
467
324
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
title: document.title,
|
472
|
-
page: page()
|
473
|
-
};
|
325
|
+
function presence(str) {
|
326
|
+
return (str && str.length > 0) ? str : null;
|
327
|
+
}
|
474
328
|
|
475
|
-
|
476
|
-
for (var
|
477
|
-
if (
|
478
|
-
|
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
|
-
|
486
|
-
onEvent("click", "a, button, input[type=submit]", function (e) {
|
340
|
+
function eventProperties(e) {
|
487
341
|
var target = e.target;
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
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
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
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
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
};
|
358
|
+
return null;
|
359
|
+
}
|
360
|
+
|
361
|
+
function createVisit() {
|
362
|
+
isReady = false;
|
508
363
|
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
ahoy.trackSubmits();
|
513
|
-
ahoy.trackChanges();
|
514
|
-
};
|
364
|
+
visitId = ahoy.getVisitId();
|
365
|
+
visitorId = ahoy.getVisitorId();
|
366
|
+
track = getCookie("ahoy_track");
|
515
367
|
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
}
|
520
|
-
|
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
|
-
|
524
|
-
|
525
|
-
|
381
|
+
// make sure cookies are enabled
|
382
|
+
if (getCookie("ahoy_visit")) {
|
383
|
+
log("Visit started");
|
526
384
|
|
527
|
-
|
528
|
-
|
385
|
+
if (!visitorId) {
|
386
|
+
visitorId = generateId();
|
387
|
+
setCookie("ahoy_visitor", visitorId, visitorTtl);
|
388
|
+
}
|
529
389
|
|
530
|
-
|
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
|
-
|
534
|
-
if (config.startOnReady) {
|
535
|
-
ahoy.start();
|
536
|
-
}
|
537
|
-
});
|
405
|
+
log(data);
|
538
406
|
|
539
|
-
|
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
|
-
|
543
|
-
|
419
|
+
ahoy.getVisitId = ahoy.getVisitToken = function () {
|
420
|
+
return getCookie("ahoy_visit");
|
421
|
+
};
|
544
422
|
|
545
|
-
|
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
|
549
|
-
|
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
|
553
|
-
|
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
|
557
|
-
|
558
|
-
}
|
459
|
+
ready( function () {
|
460
|
+
log(event);
|
559
461
|
|
560
|
-
|
561
|
-
|
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
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
465
|
+
if (canTrackNow()) {
|
466
|
+
trackEventNow(event);
|
467
|
+
} else {
|
468
|
+
eventQueue.push(event);
|
469
|
+
saveEventQueue();
|
572
470
|
|
573
|
-
|
574
|
-
|
575
|
-
|
471
|
+
// wait in case navigating to reduce duplicate events
|
472
|
+
setTimeout( function () {
|
473
|
+
trackEvent(event);
|
474
|
+
}, 1000);
|
475
|
+
}
|
476
|
+
});
|
477
|
+
});
|
576
478
|
|
577
|
-
|
578
|
-
|
479
|
+
return true;
|
480
|
+
};
|
579
481
|
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
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
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
614
|
-
|
530
|
+
// push events from queue
|
531
|
+
try {
|
532
|
+
eventQueue = JSON.parse(getCookie("ahoy_events") || "[]");
|
533
|
+
} catch (e) {
|
534
|
+
// do nothing
|
535
|
+
}
|
615
536
|
|
616
|
-
|
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
|
-
|
620
|
-
|
621
|
-
});
|
622
|
-
// http://www.quirksmode.org/js/cookies.html
|
544
|
+
ahoy.start = function () {};
|
545
|
+
};
|
623
546
|
|
624
|
-
|
625
|
-
|
626
|
-
|
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
|
-
|
653
|
-
|
654
|
-
|
551
|
+
});
|
552
|
+
|
553
|
+
return ahoy;
|
655
554
|
|
656
|
-
|
657
|
-
/******/ ])["default"];
|
658
|
-
});
|
555
|
+
})));
|