TimeonsiteTracker (TOS)

View the Project on GitHub saleemkce/timeonsite

What is TOS?
Getting Started
Getting current TOS
TOS configuration option
Setting custom data
Unsetting custom data
User sessions **
Extending User Sessions
Saving data to local storage
Real-time example
Additional Features
Backend - Examples


Reporting Dashboard in PHP/NodeJs
Buy Licence/Use it free
Release Notes

What is TimeOnSite(TOS) Tracker?

TimeOnSite(TOS) tracker is a Javascript snippet that when included in a website will track how much time user spent in the site. The goal of the project is to find time spent by user in individual pages and session as a whole.

Getting Started!

Include the timeonsitetracker.min.js in your site, like

<head> 
    <script type="text/javascript">
        var Tos;
        (function(d, s, id, file) {
            var js, fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) return;
            js = d.createElement(s);
            js.id = id;
            js.onload = function() {
                var config = {};
                if (TimeOnSiteTracker) {
                    Tos = new TimeOnSiteTracker(config);
                }
            };
            js.src = file;fjs.parentNode.insertBefore(js, fjs);
        } (document, 'script', 'TimeOnSiteTracker', 'timeonsitetracker.min.js'));
    </script> 
</head>

Then, open browser console/developer console; you will see TimeOnSiteTracker imprint. Just type following line to see current "Time on page" and related data.

console.log(Tos.getTimeOnPage());

If you could see TOS data, hooray! your TOS tracking is successful, that means, the TimeOnSiteTracker is correctly integrated in your site!

Note: Time on page and Time on site are captured in milliseconds (Javascript's Date function) by default.

Getting Current TOS data

You could call Tos anytime in the page by simple accessing Tos.getTimeOnPage() API in browser console provided that you initialized TOS correctly.

// It's assumed you correctly initialized Tos in the page as, var Tos = new TimeOnSite();
Tos.getTimeOnPage();
    //Response ->
    {
        TOSId: 1129620185532,
        TOSSessionKey: "14802525481391382263",
        TOSUserId: "anonymous",
        title: "Test application - TimeOnSiteTracker",
        URL: "http://tos-localdata.chennai/home.php"
        entryTime: "2016-11-27 13:15:48.663",
        currentTime: "2016-11-27 13:17:31.663",
        timeOnPage: 103,
        timeOnSite: 0,
        timeOnPageTrackedBy: "second",
        timeOnPageByDuration: "0d 00h 01m 43s",
        timeOnSiteByDuration: "0d 00h 00m 00s",
        trackingType: "tos",
    }

Parameter Type Description
TOSId Integer unique TOS id that is created when Tos.getTimeOnPage() is called
TOSSessionKey String It tracks particular user session. If user clicks 5 different pages in the same site, then all TOSSessionKey will refer to same session
TOSUserId String It is usually "anonymous" unless you modify it by calling Tos.startSession('username'); immediately after user authentication
title String Title of the web page
URL String Page URL
entryTime String The time when user enters the site and TOS Tracker is initialized
timeOnPage Integer Time spent by user in current page
timeOnPageByDuration String Time duration in more readable format
timeOnPageTrackedBy String It denotes whether page is being tracked by "second" or "millisecond"
timeOnSite Integer Time spent by user in the session(sum of TimeOnPage(TOP))
timeOnSiteByDuration String Time duration in more readable format (sum of TimeOnPage(TOP))
trackingType String It can be "tos" or "activity" which refers to either time on site tracking or activity tracking
* Note Check this example for data types to be used in various databases like MySql/Maria DB, MongoDB, Oracle etc.

TOS configuration option

When initializing timeonsitetracker.js in your site, the first parameter requires a "config" parameter which is optional and it controls the way TOS data is tracked. For example, look at the following option.

var config = {
    
    // track page by seconds. Default tracking is by milliseconds
    trackBy: 'seconds',

    // pages to blacklist; give URLs in array eg. ['URL1', 'URL2', 'URL3']
    blacklistUrl: ['http://tos-localdata.chennai/blog/id/1'],

    //this parameter helps track pageviews for single-page apps that use hash-based URL routing
    trackHistoryChange: true, 

    //this parameter helps track user's TOS data separately for two different sub-domins of a site or a domain and its sub-domain. It works just like normal cookie for domain and path but is associated with TOS Tracking specifically
    TOSCookie: {
        path: '/',
        domain: 'blog.example.com'
    }

    //save to local storage. If you give callback along with this option, callback will take precedence
    request: {
        url: 'http://localhost:4500/tos', //your endpoint URL to which data would be sent via POST method in API call
        headers: [
            {'App-userID': 234982348923}, //optional headers if any like authentication, token etc.
            {'App-session': 'x2872034ULT'} //optional headers if any
        ]
    },

    // if you give callback, it becomes real-time tracking because data is sent to server immediately on page close
    callback: function(data) {
        window.open('http://www.example.com', 'hi', 'height=200,width=200');
}};

// this is just for illustration, give only the options that you need.

* Seconds option

To capture all TOS data in seconds format instead of milliseconds, give the "trackBy" option as given below:

// use call back data "time on site" data
var config = {
    trackBy: 'seconds', 
    callback: function(data) {  // if you give callback, it becomes real-time tracking
    console.log(data);
    // give your end-point URL to save data to DB
}};

* Blacklisting URL from getting tracked (Advanced & optional setting)

You may want to block specifc page from getting tracked. This is possible in normal web sites (pages loaded with full rendering) but not in single-page apps. To make use of this option, you need to give "blacklistUrl" parameter,

var config = {
    trackBy: 'seconds',
    blacklistUrl: ['http://tos-localdata.chennai/home.php'], // pages to blacklist; give 
    //URLs in arrary eg. ['URL1', 'URL2', 'URL3']

    callback: function(data) { // if you give callback, it becomes real-time tracking
        console.log(data);

}};

* Developer mode setting for debugging (Advanced & optional setting)

This option is very useful in debugging TOS Tracker data. It also helps to point out any incorrect input you might give while initializing timeonsitetracker.js. These are all visible in browser console window (Open browser console and look at real-time TOS Tracker data, you may see a lot of data in browser console when this option is enabled). Also this setting may be useful only for development or test environments and not recommended for production. This is enabled by "developerMode" as given below.

var config = {
    trackBy: 'seconds',
    developerMode: true,
    request: { // presence of request object denotes that data is to be saved in local storage and processed on subsequent page access
        url: 'http://localhost:4500/tos',
        headers: [
            // optional headers if any
        ]
    }
};

This is a very important setting when working with domains and sub-domains for TOS Tracking. This cookie is used to determine if current TOS tracking applies to whole domain or a sub domain in the given site. If you are going to track entire site without differentiating domain and sub domain, you may leave this option. For this, you need to provide "TOSCookie" in config object as given below so that tracking works as per your needs,

//Setting custom data for TOS tracking
var config = {
    trackBy: 'seconds',
    TOSCookie: {
        path: '/',
        domain: 'example.com'
    }
};

In the above line, TOS Tracker is going to track all domain and sub-domain as one thing. For example, consider TOS Tracking is implemented in example.com. If the above cookie is set for path => '/' and domain => 'example.com' in example.com, then TOS tracking session ID/key is same for both blog.example.com and about.example.com. But, blog.example.com is actually different from about.example.com, each serves its own purpose and their usage should be treated differently. Since we gave domain as "example.com", TOS timing parameter gets mingled if a user tries to access blog.example.com and about.example.com one by one at the same time. To avoid data mixing and keep each thing different, domain parameter helps us do that

//Setting custom data for TOS tracking

//Example 1: setting for blog.exmaple.com
var config = {
    trackBy: 'seconds',
    TOSCookie: {
        path: '/',
        domain: 'blog.example.com'
    }
};


//Example 2: setting for about.exmaple.com
var config = {
    trackBy: 'seconds',
    TOSCookie: {
        path: '/',
        domain: 'about.example.com'
    }
};

When the setting is changed as above, user simultaneously accessing 'blog' and 'about' sub-domains is protected from mixing TOS session data of both sub domains. To describe, you will find two session IDs created for accessing each sub-domain and each session ID uniquely identifies time on site of sub-domains separately. It works just like domain cookies in sites but for tracking TOS.

Tracking single-page apps (Mandatory setting only if your app is single page or uses hash-based routing)

TimeOnSite tracker captures TOS information for normal web pages when page is closed, reloaded or navigated to another page using beforeunload browser API. This is not the case with single-page apps where navigation to different pages does not trigger beforeunload window event and in this case TOS is not tracked. To overcome the problem, fortunately, browsers support window.history API which gets called when page navigation occurs in single-page apps. For example, to navigate from example.com/#home -> example.com/#contact page in single-page app. When you go from "home" page to "contact" page, history API helps track the page navigation correctly. To make use of it, give config trackHistoryChange as,

var config = {
    trackBy: 'seconds',

    trackHistoryChange: true,

    callback: function(data) { // if you give callback, it becomes real-time tracking
        console.log(data);

}};
Remember that failing to configure this trackHistoryChange setting may not identify different pages correctly in tracked TOS data.

Additionally, if certain web applications/browsers are not working correctly even with "trackHistoryChange" setting. You may consider using trackPageNavigation() API manually. This is like manually tracking page navigation when everything else fails. To use this, when a page navigation occurs, call the API manually like Tos.trackPageNavigation().

Setting custom data (Advanced & optional setting)

When implementing TOS in your website or application, you may want to track additional details along with TOS data. For example, you see "timeOnPage", "URL", "title" etc. in the object returned by TOS on page close. What if you want to set userId, userName, userType or userActivity? This is where Tos.setCustomData() API comes into action. It helps you track additional data in your webpage. Look at the example given below,

//Setting custom data for TOS tracking
var custom = {
    userId: 'u23842238924',
    userName: 'Khan', 
    actions: ['view', 'audio-start', 'read']
};
Tos.setCustomData(custom); //before the page is closed, this data is appended to TOS result.

Now if you call Tos.getTimeOnPage() from browser console, you could see this custom data along with usual TOS data

//Response ->
{
    TOSId: 1129620185532,
    TOSSessionKey: "14802525481391382263",
    TOSUserId: "anonymous",
    title: "Test application - TimeOnSiteTracker",
    URL: "http://tos-localdata.chennai/home.php"
    entryTime: "2016-11-27 13:15:48.136",
    timeOnPage: 103,
    timeOnSite: 0,
    timeOnPageTrackedBy: "second",
    timeOnPageByDuration: "0d 00h 01m 43s",
    timeOnSiteByDuration: "0d 00h 00m 00s",
    trackingType: "tos",

    //custom data added to TOS response. It would be saved to DB on page close. Wow!
    userId: 'u23842238924',
    userName: 'Khan', 
    actions: ['view', 'audio-start', 'read']
}

Unsetting custom data

To remove the custom data already set, simply call Tos.unsetCustomData() API and you are done. It will only remove any custom data set by you.

User sessions (Advanced & optional setting)

You have learnt a lot about TimeOnSiteTracker and its configuration options. Now, it's time to set user ID to track and understand the behaviour of authenticated users in your application. For this, you may use Tos.startSession() API as given in example below. Moreover, this is optional setting but highly recommended in case your application makes use of authentication/login routine. If not set, all TOS tracking will be tracked as "anonymous" users in your application.

    // when user is logged into your site, call startSession.
    Tos.startSession('u2439ULasxv'); //"u2439ULasxv" is user id as available in your application

    //when user logs out of your application, call endSession.
    Tos.endSession(); 

Remember, you may call Tos.startSession() API only when user logs into your application and call Tos.endSession() only when user logs out. Calling Tos.endSession() too many times or unnecessarily may create inconsistent identity data in your DB. Failing to call Tos.endSession(), when user logs out in your application, may result in loss of that session data.

Extending user session

In section "User sessions" above, we saw how to capture TOS data for authenticated users in your application by calling Tos.startSession('testUser1'). Once started, this session information is stored in cookie and stays there for 1 day until Tos.endSession() is called. When Tos.endSession() is called, this authenticated user session is destroyed from cookie. For some applications, they may set smaller lifetime for authenticated sessions. TimeOnSite tracker needs to be in sync with such applications for capturing TOS data of authenticated users. In TimeOnSite tracker, fortunately, there is a way to modify default lifetime of cookies for authenticated users. This is done by calling Tos.extendSession() methed after starting TOS session.

    // when user is logged into your site, call startSession.
    Tos.startSession('testUserId1'); //"testUserId1" stays in cookie for next 1 day but your application preference is to auto-destroy authenticated user session every one hour. In this case, you are forced to auto-destory TOS authenticated session at the same time.

    //Just call extend session API.
    Tos.extendSession(3600); // This modifies current authenticated session lifetime to 1 hour instead of default 1 day.

    // or give any value
    Tos.extendSession(900); // This modifies current authenticated session lifetime to 15 minutes instead of default 1 day.

    // or give even bigger value
    Tos.extendSession(604800); // This modifies current authenticated session lifetime to 7 days (1 week) instead of default 1 day.

The method Tos.extendSession(3600) accepts first parameter as time period in seconds. It is of type number. In this snippet, 3600 seconds which is 1 hour. Also be aware that the value given to this API modifies the lifetime of cookie instead of adding to existing default 1 day lifetime of cookie. If you give incorrect value or very less time period like 10 seconds, it may not work correctly. This API is applicable only for authenticated user sessions.

Saving data to local storage for performance

When you give callback TOS option, TimeOnSiteTracker tries to send data immediately on page/tab close. Since it makes a HTTP request at this point and it is synchronous, it slows down the tab close action otherwise the navigation would be a bit slower. But, this is real-time tracking. On the other hand, if you give local storage option, data is saved to local storage and processed later. This won't be real-time but it doesn't affect user's site navigation.

// save to local storage; don't give callback when saving to local storage
var config = {
    trackBy: 'seconds',
    request: {  // presence of request object denotes that data is to be saved in local storage and processed on subsequent page access
        url: 'http://localhost:4500/tos', //your endpoint URL to which data would be sent via POST method in API call
        headers: [
            {'App-userID': 234982348923}, //optional headers if any like authentication, token etc.
            {'App-session': 'x2872034ULT'} //optional headers if any
        ]   
}};

Real-time Tracking Example

// save with sendBeacon or XMLHttpRequest
var config = {
    trackBy: 'seconds',
    callback: function(data) {
        console.log(data);

        // give your endpoint URL/ server-side URL that is going to handle your TOS data which is of POST method. Eg. PHP, nodejs or python URL which saves this data to your DB

        var endPointUrl = 'http://localhost:4500/tos'; // replace with your endpoint URL

        if (data && data.trackingType) {
            if (data.trackingType == 'tos') {
                if (Tos.verifyData(data) != 'valid') {
                    console.log('Data abolished!');
                    return; 
                }
            }
            
            // makes use of sendBeacon API in your browser.
            if (navigator && typeof navigator.sendBeacon === 'function') {
                var blob = new Blob([JSON.stringify(data)], {type : 'application/json'});
                navigator.sendBeacon(endPointUrl, blob);
            }
            /*else {
                // XMLHttpRequest begins..
                var url = endPointUrl,
                    params = JSON.stringify(data),
                    xhr;
                xhr = new XMLHttpRequest();
                xhr.open('POST', url, false); //synchronous call; changing this to true will make it asynchronous request and data may not be saved in your DB.

                //Send the proper header information along with the request
                xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');

                // IOS/Safari ajax POST issue fixer without no-cache header
                xhr.setRequestHeader('cache-control', 'no-cache');
                xhr.onreadystatechange = function() {//Call a function when the state changes.
                    if (xhr.readyState == 4 && xhr.status == 200) {
                        if (xhr.responseText == 'success') {
                            console.log('Data saved successfully!');
                            // Warning: But you should not do more operations here since it will //block user from closing the application or slow down site navigation
                        }
                    }
                }
                xhr.send(params);
                // XMLHttpRequest ends..
            }*/
        }
    }};

Code explained:

Since this is real-time tracking, we don't specify local storage option. We denote this as real-time by giving "callback" option. Since code is simple and self-explanatory, we only explain the high-level details here.
    if (data && data.trackingType) {
    
we check if there is data from TimeOnSiteTracker.js during page close.
    if (navigator && typeof navigator.sendBeacon === 'function') {
    
we check if browser supports sendBeacon() API which could send data asynchronously and safely to server without blocking user interface. If this API is supported by browser, we make use of it.
    xhr.open('POST', url, false); //synchronous call; changing this to true will make it asynchronous request and data may not be saved in your DB.
    

In sendBeacon's else loop, you could see this. If sendBeacon() API is not supported by browser, we use normal XMLHTTPRequest to send data synchronously to server. Note the 3rd parameter "false" which is responsible for sending data to server before page gets closed.

TOS real-time tracking - Key considerations

Additional Features

Activity Tracking

TimeOnSiteTracker also helps you track a particular interaction or activity in the site. The goal of this feature is to capture time taken by the user to complete the activity. You have to manually trigger Tos.startActivity() and Tos.endActivity() APIs to capture start and end time of activity. starActivity() and endActivity() API methods accept optional first parameter as Javascript object through which you can set custom data.

// Usual tracker initialization at header. Only once!
var config = {
    trackBy: 'seconds'
}};

// After initializing the TimeOnSiteTracker on page load, call startActivity() as
Tos.startActivity(); // this will start tracking the activity

// .
// .
// .
// after a while,

Tos.endActivity();
// calling .endActivity() will give back response

Gives response as,
{
    TOSId: 585872449448,
    TOSSessionKey: "14802525481391382263",
    TOSUserId: "anonymous",
    title: "Test application - TimeOnSiteTracker",
    URL: "http://tos-localdata.chennai/home.php",
    activityStart: "2016-11-27 13:20:46.707",
    activityEnd: "2016-11-27 13:20:50.213",
    timeTaken:4,
    timeTakenByDuration: "0d 00h 00m 04s"
    timeTakenTrackedBy: "second",
    trackingType: "activity",
}


// Another scenario with optional first parameter
Tos.startActivity({name: 'foo', city: 'bar'}); // this will start activity tracking with custom data set
It has different parameters in response like "activityStart", "activityEnd", "timeTaken" etc. It's independent of normal TOS tracking which starts at page load and ends at page close.
Note: If callback had been given, it will send data to server immediately. On the other hand, if local storage option had been given, it saves the data to storage for later processing. If neither callback nor local storage option is given, it just returns the activity data back to variable. For instance,
var manualProcess = true;
var activityData = Tos.endActivity({}, manualProcess); 
/**
 * Notice variable "manualProcess" which is boolean and is set to true. This indicates that data will not be handled by callback function or local storage. Instead, activityData is simply retured to variable for you to do anything manually.

 * You may add any additional data to this "activityData" object and save it to database as you wish.
 * 
 * activityData.userPreferences = ['social', 'journal', 'history'];
 * activityData.userId = 2427032032u;
 * 
 * Now save "activityData" object to database for understanding user preferences.
 */

Parameter Type Description
TOSId Integer unique TOS id that is created when Tos.getTimeOnPage() is called
TOSSessionKey String It tracks particular user session. If user clicks 5 different pages in the same site, then all TOSSessionKey will refer to same session
TOSUserId String It is usually "anonymous" unless you modify it by calling Tos.startSession('username'); immediately after user authentication
title String Title of the web page
URL String Page URL
timeTaken Integer The time duration it takes for user to complete the activity/interaction
timeTakenByDuration String Activity duration in more readable format
timeTakenTrackedBy String It denotes whether page is being tracked by "second" or "millisecond"
activityStart String The time when user starts the activity
activityEnd String The time when user finishes the activity
trackingType String It can be "tos" or "activity" which refers to either time on site tracking or activity tracking
* Note Check this example for data types to be used in various databases like MySql/Maria DB, MongoDB, Oracle etc.

Developed with love for web & analytics at Chennai, remember Marina? All praise be to Almighty God.
TimeonsiteTracker.js, the commercial software.