keen-tracking.js
This project contains the most advanced event tracking functionality available for Keen IO, and will soon be built directly into keen-js, replacing and upgrading the current tracking capabilities of that library.
Why did we split this library out of keen-js? Tracking and Analysis+Dataviz are two distinct workflows and it rarely makes sense for these tools to be duct-taped together. Monolithic codebases bring more heartache than Nirvana.
Upgrading from keen-js? Read this.
This example setup demonstrates how to put this library to work.
Getting started:
If you haven't done so already, login to Keen IO to create a project. The Project ID and API Keys are available on the Project Overview page. You will need these for the next steps.
Helpful utilities:
- Cookies (browser-only) for persisting data from one page to the next
- Listeners (browser-only) for capturing and taking action during common DOM events like click, scroll, and submit
- Timers for tracking time before and between user or system interactions
Helpful helpers:
- Datetime index for decomposing a date object into a set of properties like "hour_of_day" or "day_of_month"
- Unique ID for generating UUIDs
- DOM node path for returning the xPath for a given DOM element
- Screen profile for generating a set of properties describing the current device screen, like "height", "availHeight", and "orientation"
- Window profile for generating a set of properties describing the current window, like "height", "scrollHeight", and "ratio" to screen dimensions
- Browser profile for generating a set of properties describing the current browser, like "useragent", "online" status, and "language", plus screen and window profiles
Upgrading from keen-js:
There are several new methods and name changes from keen-js, but fear not! We have included shims and legacy methods to make this library fully backward-compatible with the core functionality of keen-js, aside from one breaking change to the client.url()
method (detailed below). Here are the methods and their replacement methods:
Please avoid using these deprecated methods, as they will eventually get axed. Deprecation messages will be visible in the developer console if debugging is enabled.
Breaking change from keen-js: the previous implementation of client.url()
automatically included https://api.keen.io/3.0/projects/PROJECT_ID
plus a path
argument ('/events/whatever'). This design severely limited its utility, so we've revamped this method.
This method now references an internal collection of resource paths, and constructs URLs using client configuration properties like host
and projectId
:
var url = client.url('projectId');
Default resources:
- 'base': '
{protocol}
://{host}
', - 'version': '
{protocol}
://{host}
/3.0', - 'projects': '
{protocol}
://{host}
/3.0/projects', - 'projectId': '
{protocol}
://{host}
/3.0/projects/{projectId}
', - 'events': '
{protocol}
://{host}
/3.0/projects/{projectId}
/events'
Unmatching strings will be appended to the base resource, like so:
var url = client.url('/3.0/projects');
You can also pass in an object to append a serialized query string to the result, like so:
var url = client.url('events', { api_key: 'YOUR_API_KEY' });
Resources can be returned or added with the client.resources()
method, like so:
client.resources()
client.resources({
'new': '{protocol}://analytics.mydomain.com/my-custom-endpoint/{projectId}'
});
client.url('new');
Additional resources:
Support:
Need a hand with something? Shoot us an email at team@keen.io. We're always happy to help, or just hear what you're building! Here are a few other resources worth checking out:
Install the library
This library is best loaded asynchronously with the copy-paste technique outlined below, but can also be installed via npm or bower:
# via npm
$ npm install keen-tracking
# or bower
$ bower install keen-tracking
Copy/paste this snippet of JavaScript above the </head>
tag of your page to load the tracking library asynchronously. This technique sneaks the library into your page without significantly impacting page load speed.
<script>
!function(name,path,ctx){
var latest,prev=name!=='Keen'&&window.Keen?window.Keen:false;ctx[name]=ctx[name]||{ready:function(fn){var h=document.getElementsByTagName('head')[0],s=document.createElement('script'),w=window,loaded;s.onload=s.onerror=s.onreadystatechange=function(){if((s.readyState&&!(/^c|loade/.test(s.readyState)))||loaded){return}s.onload=s.onreadystatechange=null;loaded=1;latest=w.Keen;if(prev){w.Keen=prev}else{try{delete w.Keen}catch(e){w.Keen=void 0}}ctx[name]=latest;ctx[name].ready(fn)};s.async=1;s.src=path;h.parentNode.insertBefore(s,h)}}
}('Keen','https://d26b395fwzu5fz.cloudfront.net/keen-tracking-1.0.1.min.js',this);
Keen.ready(function(){
var client = new Keen({
projectId: 'YOUR_PROJECT_ID',
writeKey: 'YOUR_WRITE_KEY'
});
client.recordEvent('pageviews', {
title: document.title
});
});
</script>
This loader works a little differently than all the previous versions we have released.
Notice the last line of the asynchronous loader snippet: }('Keen', './filename.js', this);
. These three arguments can be overwritten, allowing you to customize important details about the installation process.
- Namespace: Define a custom namespace for the library, instead of the default
Keen
, like MyCustomKeenBuild
. - Script URI: Define the location of the script to load. You don't need to rely on our CDN. You can use your own, or host the file locally.
- Context: Define where the library should be installed. Global pollution is a problem. This helps you fight back.
Here's an example that uses all of these features together:
var modules = {};
!function(name,path,ctx){
}('MyKeenBuild','/assets/js/custom-keen-tracking.js', modules);
modules.MyKeenBuild.ready(function(){
var client = new modules.MyKeenBuild.Client({
projectId: 'YOUR_PROJECT_ID',
writeKey: 'YOUR_WRITE_KEY'
});
});
Important: This update brings an important change to note. In past versions of keen-js, we shimmed tracking-methods so you could begin using them immediately without the .ready()
callback wrapper. This created a lot of strange edge cases and version conflicts. Now, everything must be initialized from within the .ready(function(){ ... })
wrapper.
RequireJS
The library is published with an explicitly named module ID of 'keen-tracking'. This presents a light configuration step, but prevents anonymous define() mismatch mayhem.
To use this module, configure a paths record, like so:
<script data-main="path/to/app.js" src="require.js"></script>
requirejs.config({
paths: {
'keen-tracking': 'path/to/keen-tracking.js'
}
});
require([
'keen-tracking'
], function(KeenAMD) {
var client = new KeenAMD.Client({
projectId: "123",
writeKey: "456"
});
});
Also note a global Keen
object will still be defined. This is meant to ensure the library can initialize in environments where neighboring scripts are unknown or uncontrollable.
Connect
The client instance is the core of the library and will be required for all API-related functionality. The client
variable defined below will also be used throughout this document.
var client = new Keen({
projectId: 'YOUR_PROJECT_ID',
writeKey: 'YOUR_WRITE_KEY',
});
client.projectId('PROJECT_ID');
client.writeKey('WRITE_KEY');
Important notes about client configuration options:
host
and writePath
: these options can be overwritten to make it easier than ever to proxy events through your own intermediary host.protocol
: older versions of IE feature a fun little quirk where cross-domain requests to a secure resource (https) from an insecure host (!https) fail. In these rare cases the library will match the current host's protocol.requestType
: this option sets a default for GET requests, which is only supported when recording single events. There are limits to the URL string length of a request, so if this limit is exceeded we'll attempt to execute a POST instead, using XHR. In rare cases where XHR isn't supported, the request will fail.
Record events
These methods push single or multiple events to their respective API endpoints. Wondering what you should record? Browse our data modeling guide, and let us know if you don't find what you're looking for.
Record a single event
Here is an example for recording a "purchases" event. Note that dollar amounts are tracked in cents:
var purchaseEvent = {
item: 'golden gadget',
price: 2550,
referrer: document.referrer,
keen: {
timestamp: new Date().toISOString()
}
};
client.recordEvent('purchases', purchaseEvent, function(err, res){
if (err) {
}
else {
}
});
API response for recording a single event:
{
"created": true
}
Record multiple events
Here is an example for how to record multiple events with a single API call. Note that dollar amounts are tracked in cents:
var multipleEvents = {
purchases: [
{
item: 'golden gadget',
price: 2550,
transaction_id: 'f029342'
},
{
item: 'a different gadget',
price: 1775,
transaction_id: 'f029342'
}
],
transactions: [
{
id: 'f029342',
items: 2,
total: 4325
}
]
};
client.recordEvents(multipleEvents, function(err, res){
if (err) {
}
else {
}
});
API response for recording multiple events:
{
"purchases": [
{
"success": true
},
{
"success": true
}
],
"transactions": [
{
"success": true
}
]
}
Defer events
These methods handle an internal queue of events, which is pushed to the events API resource on a given interval (default: 15 seconds), or when the queue reaches a maximum capacity (default: 5000).
client.deferEvent('purchase', purchaseEvent);
client.deferEvents(multipleEvents);
client.recordDeferredEvents();
client.queueCapacity(5000);
client.queueCapacity();
client.queueInterval(15);
client.queueInterval();
Extend events
These methods extend the event body of every event sent through recordEvent()
or recordEvents()
, for all or specified collections, and accepts either a predefined object (static) or a function that returns an object (dynamic). This returned object is then grafted into the original event body with a deep-extend operation that seamlessly blends nested objects.
extendEvents
transforms will be applied first, followed by collection-specific extendEvent
transforms. In either case, transforms will be applied in the order that they are defined. Properties provided in the originating recordEvent/s()
call will override any matching properties (static or dynamic) returned by these methods.
client.extendEvent('transaction', {});
client.extendEvent('transaction', function(){
return {};
});
client.extendEvents({});
client.extendEvents(function(){
return {};
});
var userProps = {
full_name: 'User Dude',
email: 'name@domain.com',
id: 'f1233423h',
username: 'userdude213'
};
client.extendEvent('purchases', {
'user': userProps
});
client.extendEvents({
'user': userProps
});
client.extendEvents(function(){
return {
keen: {
timestamp: new Date().toISOString()
}
};
});
Example usage:
client.extendEvents({
page: {
href: document.location.href,
title: document.title
},
referrer: document.referrer,
user: {
email: 'name@domain.com',
id: 'f1233423h',
username: 'someuser123'
}
});
client.extendEvents(function(){
return {
keen: {
timestamp: new Date().toISOString()
}
}
});
client.recordEvent('pageviews');
Utilities
Cookies
Keen.utils.cookie(key)
finds or creates a cookie with a given key (string) value, and returns an object with several methods for managing the data contained in that cookie.
var sessionCookie = Keen.utils.cookie('visitor-stats');
sessionCookie.set('user_id', '222323843234');
sessionCookie.set({
user_id: '222323843234',
first_referrer: 'https://github.com/keen/keen-tracking.js'
})
sessionCookie.get('user_id');
sessionCookie.get();
sessionCookie.enabled();
sessionCookie.expire();
sessionCookie.options({
domain: '...',
secure: true
});
Important: Some browsers do not allow cookies to be created or accessed from a local file (file://dev/index.html
), which can make local development and testing problematic. .set()
and .get()
methods will only function correctly when cookies are enabled.
This utility uses js-cookie.
Prior to the 1.0 release, this library used Cookies.js, but incorrectly encoded the cookie data twice. Data stored in cookies by v0.1.1 or earlier can be accessed and resolved like so:
var cookies = document.cookie.split(';');
var myCookie = Keen.utils.cookie('your-cookie-name');
var badData, newData;
for (var i = 0; i < cookies.length; i++) {
if (cookies[i].indexOf('your-cookie-name=') < 0) continue;
badData = cookies[i].split('your-cookie-name=')[1];
newData = JSON.parse(
decodeURIComponent(
decodeURIComponent(badData)
)
);
myCookie.set(newData);
break;
}
Listeners
Keen.utils.listener()
helps surface common DOM element events like "click", "scroll", and "submit". There is also a Keen.listenTo()
method for quickly setting a series of listeners (below)
Important: Form submits and clicks will be delayed by 500ms, unless the event is cancelled within a given listener's callback.
var navLinks = Keen.utils.listener('.nav li > a');
navLinks.on('click', function(e){
});
myClicker.once('click', function(e){
});
function clickHandler(e){
}
myClicker.on('click', clickHandler);
myClicker.off('click', clickHandler);
myClicker.off('click');
myClicker.off();
var formListener = Keen.utils.listener('form#signup');
formListener.on('submit', function(e){
client.recordEvent('signup', {
});
});
Keen.listenTo()
This is a convenience function for quickly creating multiple listeners. These listeners are constructed with the Keen.utils.listener
utility, so the behavior will be identical to calling Keen.utils.listener(selector).on(eventType, callback);
.
Keen.listenTo({
'click .nav li > a': function(e){
},
'submit form#signup': function(e){
}
});
This technique does not return a reference to the listener, but can be deactivated by defining a listener with the same selector and calling the .off(eventType)
event:
Keen.utils.listener('.nav li > a').off('click');
Keen.utils.listener('form#signup').off('submit');
Window events
var winListener = Keen.utils.listener('window')
.once('scroll', function(e){
})
.on('hashchange', function(e){
})
.on('resize', function(e){
});
Generally supported events:
- click (see below for
<a>
clicks) - submit (see below for
<form>
submits) - keydown
- keypress
- keyup
- mousedown
- mousemove
- mouseout
- mouseover
- mouseup
Important note about <a>
and <form>
elements: <a>
tag clicks (when navigating away from the current page) and <form>
submits are deferred for 500ms to allow for quick, asynchronous API calls.
window
events:
- blur
- focus
- hashchange
- resize
- scroll
Not currently supported:
- dblclick
- error
- onload
- unload
Timers
Keen.utils.timer()
creates an object that tracks time, and can be paused, restarted, or initialized with a known value (seconds). It seems simple, but these little do-dads are excellent for recording the duration of sessions or specific interactions.
var userActivity = Keen.utils.timer();
userActivity.start();
userActivity.pause();
userActivity.value();
userActivity.clear();
var historicalActivity = Keen.utils.timer(3132).start();
historicalActivity.pause();
Helpers
These helpers are designed to generate useful properties and objects for event data models, and can be used when recording, deferring or extending events.
Datetime index
Keen.utils.getDatetimeIndex()
returns a set of properties like "hour_of_day" or "day_of_month". This helper accepts an optional Date object as an argument, otherwise it will construct and return a datetime index object based on "now".
var datetimeIndex = Keen.helpers.getDatetimeIndex();
Unique ID
Keen.helpers.getUniqueId()
returns a UUID. This is useful in conjunction with Keen.utils.cookie()
for identifying and tracking unauthenticated site visitors.
var uniqueId = Keen.helpers.getUniqueId();
DOM node path
Keen.helpers.getDomNodePath(el)
returns the xPath for a given DOM element.
var btn = document.getElementById('signup-button');
var domNodePath = Keen.helpers.getDomNodePath(btn);
Screen profile
Keen.utils.getScreenProfile()
returns a set of properties describing the current device screen, like "height", "availHeight", and "orientation".
var screenProfile = Keen.helpers.getScreenProfile();
Window profile
Keen.utils.getWindowProfile()
returns a set of properties describing the current window, like "height", "scrollHeight", and "ratio" to screen dimensions.
var windowProfile = Keen.helpers.getWindowProfile();
Browser profile
Keen.utils.getBrowserProfile()
returns a set of properties describing the current browser, like "useragent", "online" status, and "language", plus screen and window profiles.
var browserProfile = Keen.helpers.getBrowserProfile();
Example Setup
var client = new Keen({
projectId: 'MY_PROJECT_ID',
writeKey: 'MY_WRITE_KEY'
});
var sessionCookie = Keen.utils.cookie('keen-example-cookie');
if (!sessionCookie.get('user_id')) {
sessionCookie.set('user_id', Keen.helpers.getUniqueId());
}
var sessionTimer = Keen.utils.timer();
sessionTimer.start();
Keen.listenTo({
'submit form#signup': function(e){
var userEmail = document.getElementById('signup-email').value;
client.recordEvent('user signup', {
visitor: {
email: userEmail
}
});
},
'click .nav a': function(e){
client.recordEvent('leave page');
}
});
client.extendEvents(function(){
return {
page: {
title: document.title,
url: document.location.href
},
referrer: {
url: document.referrer
},
tech: {
browser: Keen.helpers.getBrowserProfile(),
ip: '${keen.ip}',
ua: '${keen.user_agent}'
},
time: Keen.helpers.getDatetimeIndex(),
visitor: {
id: sessionCookie.get('user_id'),
time_on_page: sessionTimer.value()
},
keen: {
timestamp: new Date().toISOString(),
addons: [
{
name: 'keen:ip_to_geo',
input: {
ip: 'tech.ip'
},
output: 'geo'
},
{
name: 'keen:ua_parser',
input: {
ua_string: 'tech.ua'
},
output: 'tech.info'
},
{
name: 'keen:url_parser',
input: {
url: 'page.url'
},
output: 'page.info'
},
{
name: 'keen:referrer_parser',
input: {
page_url: 'page.url',
referrer_url: 'referrer.url'
},
output: 'referrer.info'
}
]
}
};
});
client.recordEvent('pageview');
Debugging
Dev console errors and messages are turned off by default, but can be activated by setting Keen.debug = true;
. Additionally, you can disable writing events to the API by setting Keen.enabled = false;
.
Keen.debug = true;
Keen.enabled = false;
client.on('recordEvent', Keen.log);
client.on('recordEvents', Keen.log);
client.on('deferEvent', Keen.log);
client.on('deferEvents', Keen.log);
client.on('recordDeferredEvents', Keen.log);
client.on('extendEvent', Keen.log);
client.on('extendEvents', Keen.log);
Contributing
This is an open source project and we love involvement from the community! Hit us up with pull requests and issues. The more contributions the better!
TODO:
Learn more about contributing to this project.
Custom builds
Run the following commands to install and build this project:
# Clone the repo
$ git clone https://github.com/keen/keen-tracking.js.git && cd keen-tracking.js
# Install project dependencies
$ npm install
# Build project with gulp
# npm install -g gulp
$ gulp
# Build and launch to view test results
$ gulp with-tests
$ open http://localhost:9000