Skip to content

Instantly share code, notes, and snippets.

@sitesbyjoe
Created June 3, 2016 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sitesbyjoe/1088f42a44f0acf0c45c47a1258cd339 to your computer and use it in GitHub Desktop.
Save sitesbyjoe/1088f42a44f0acf0c45c47a1258cd339 to your computer and use it in GitHub Desktop.
jajo.js - home-brewed screen recording from back in 2013...
/* -----------------------------------------------------------
* Super user tracking script - jajo pronounced (ha-ho)
* -----------------------------------------------------------
* A generic script to track what a user does on a webpage,
* storing it and having the ability to replay each session.
* -----------------------------------------------------------
*
* User Tracking:
* - Capturing the scrolling, mouse movements and clicks of a
* - desktop user.
*
* Storing the tracking data:
*
* -----------------------------------------------------------
* - PageLoad:
* -----------------------------------------------------------
* -- send as a packet:
* -- {pageLoad: timestamp, userID: userID, url: currentURL, sessionKey: sessionKey, html: html}
*
* -- Send once the page loads so this is the starting point
* -- of the tracking - this will let us know how long it took
* -- the user to start scrolling and interacting.
*
* -- The session key will generate on any pageload so users
* -- inidivual page interactions stay segregated.
*
* -----------------------------------------------------------
* - Scrolling:
* -----------------------------------------------------------
* -- When the user begins to scroll, store a timestamp, an
* -- event named scrollStart, and the current scroll position.
*
* -- When the user stops scrolling, store a timestamp, an
* -- event named scrollEnd and the current scroll position.
*
* -- then store the whole event as a packet like:
*
* -- [{scrolls: {
* -- scrollStart: {userID: userID, sessionKey: sessionKey, startTimestamp: XXXXXXX, startPosition: Ycoordinate},
* -- scrollEnd: {userID: userID, sessionKey: sessionKey, endTimestamp: XXXXXXX, endPosition: Ycoordinate},
* -- ... (we may need to store more than once scroll setper packet based on how often we can send ajax requests)
* -- }}];
*
* ----------------------------------------------------------
* - Mousing:
* ----------------------------------------------------------
* -- when the user beings to move the mouse, store the data
* -- similar to the scrolling:
*
* -- [{mousing: {
* -- mouseStart: {userID: userID, url: currentURL, startTimestamp: XXXXXXX, startPositionX: Xcoordinate, startPositionY: Ycoordinate},
* -- mouseEnd: {userID: userID, url: currentURL, endTimestamp: XXXXXXX, endPositionX: Xcoordinate, endPositionY: Ycoordinate},
* -- ... (we may need to store more than once mousing per packet based on how often we can send ajax requests)
* -- }}];
*
* ----------------------------------------------------------
* - Clicking:
* ----------------------------------------------------------
*
*
* ----------------------------------------------------------
* User replaying:
* ----------------------------------------------------------
* - taking the data we collect from the user to "replay" what
* - the user did while being tracked.
*
* - Thoughts:
* - Use the timestamps and coordinates to build a CSS set of
* - animation keyframes etc to move the page for scrolling etc
*
* - Have to test CSS animation vs. JS animation (JS might be better for scrolling at least)
* - ...
*
*/
// code I grabbed to more properly track sc
var jajo = {
// some application settings
settings: {
storeUser: true, // whether or not we're storing users on this page
userUrl: '', // url that'll return the userID of our user as json packet like: {userID: 1234}
track: true // if true it'll track, if not it'll just play
},
// object to manage the session
session: {
// empty data model for our session we'll need to populate
data: {
pageLoad: null,
userID: null,
sessionKey: null,
url: null,
screenWidth: window.innerWidth,
screenHeight: window.innerHeight
},
// generate a sessionKey
generateKey: function() {
var length = 32;
var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
var key = '';
for (var i=0; i<length; i++) {
key += chars[Math.round(Math.random() * (chars.length - 1))];
//key += key;
}
return key;
},
// get the current url the user is on
getURL: function() {
return document.URL;
},
//function to get our userID - if applicable
getUserID: function() {
// hard coding in my own id for testing
return '2371435';
},
// initialize the session
init: function() {
// set our starting timestamp
jajo.session.data.pageLoad = jajo.tracking.getTimestamp();
// get the userID (if applicable)
if (jajo.settings.storeUser) {
jajo.session.data.userID = jajo.session.getUserID();
}
// set the sessionKey
jajo.session.data.sessionKey = jajo.session.generateKey();
// store the url
jajo.session.data.url = jajo.session.getURL();
// send the packet to the server
jajo.delivery.sendPageLoad();
}
},
// object that stores user events
tracking: {
// generate a timestamp relative to the user's own time
getTimestamp: function() {
return new Date().getTime();
},
adjustTime: function(start, end) {
var value = 0;
value = parseInt(end) - parseInt(start);
// console.log('adjusted value:' + value);
return parseInt(value);
},
// our scrolling tracking object
scroll: {
// variable to handle our done scrolling event - we start with "true" and change it to false during a scroll
doneScrolling: true,
// packet of data for a scroll start/end
packet: {
startPosition: null,
startTimestamp: null,
endPosition: null,
endTimestamp: null
},
// tracking the start of a scroll action
trackScrollStart: function() {
if (jajo.settings.track) {
// add our current scroll position and timestamp to the packet
jajo.tracking.scroll.packet.startPosition = window.scrollY;
jajo.tracking.scroll.packet.startTimestamp = jajo.tracking.getTimestamp();
// set that we're currently scrolling
jajo.tracking.scroll.doneScrolling = false;
}
},
// tracking the end of a scroll action
trackScrollEnd: function() {
if (jajo.settings.track) {
// add our ending position and timestamp to the packet
jajo.tracking.scroll.packet.endPosition = window.scrollY;
jajo.tracking.scroll.packet.endTimestamp = jajo.tracking.getTimestamp();
// set that we're done scrolling
jajo.tracking.scroll.doneScrolling = true;
// if a real scroll has been done start the packet delivery
if (jajo.tracking.scroll.packet.startPosition !== jajo.tracking.scroll.packet.endPosition) {
// build the packet to send
eventData = JSON.stringify(jajo.tracking.scroll.packet);
// send the event
jajo.delivery.sendPageEvent('scroll', eventData);
}
}
}
},
// our mouse tracking object
mouse: {
// flag for mouse movement tracking
doneMousing: true,
// packet we send to the server
packet: {
startX: null,
startY: null,
startTimestamp: null,
endX: null,
endY: null,
endTimestamp: null
},
// function to catch the beginning of the mouse
trackMouseStart: function(e) {
if (jajo.settings.track) {
jajo.tracking.mouse.packet.startX = e.pageX;
jajo.tracking.mouse.packet.startY = e.pageY;
jajo.tracking.mouse.packet.startTimestamp = jajo.tracking.getTimestamp();
jajo.tracking.mouse.doneMousing = false;
}
},
// function to catch the end of the mouse motion
trackMouseEnd: function(e) {
if (jajo.settings.track) {
jajo.tracking.mouse.packet.endX = e.pageX;
jajo.tracking.mouse.packet.endY = e.pageY;
jajo.tracking.mouse.packet.endTimestamp = jajo.tracking.getTimestamp();
jajo.tracking.mouse.doneMousing = true;
// build the packet to send
eventData = JSON.stringify(jajo.tracking.mouse.packet);
// send the event
jajo.delivery.sendPageEvent('mouse', eventData);
}
}
},
// our click tracking object
click: {
// packet that we send to the server
packet: {
clickTimestamp: null,
clickX: null,
clickY: null,
},
// function that sends the click to the server
trackClick: function(e) {
jajo.tracking.click.packet.clickTimestamp = jajo.tracking.getTimestamp();
jajo.tracking.click.packet.clickX = e.pageX;
jajo.tracking.click.packet.clickY = e.pageY;
// build the packet to send
eventData = JSON.stringify(jajo.tracking.click.packet);
// send the event
jajo.delivery.sendPageEvent('click', eventData);
}
}
},
// object that sends stored data to server
delivery: {
// send pageload
sendPageLoad: function() {
$.ajax({
url: 'https://www.scholarshippoints.com/jajo/setPageLoad/?pageLoad=' + jajo.session.data.pageLoad + '&userID=' + jajo.session.data.userID + '&url=' + jajo.session.data.url + '&sessionKey=' + jajo.session.data.sessionKey + '&screenWidth=' + jajo.session.data.screenWidth + '&screenHeight=' + jajo.session.data.screenHeight,
statusCode: {
200: function(data) {
console.log(data);
}
}
});
},
// send an event packet
sendPageEvent: function(eventType, eventData) {
$.ajax({
url: 'https://www.scholarshippoints.com/jajo/setEvent/?sessionKey=' + jajo.session.data.sessionKey + '&eventType=' + eventType + '&eventData=' + eventData,
statusCode: {
200: function(data) {
console.log(data);
}
}
});
}
},
// object that replays sessions
player: {
// array of animations to run
animationSequences: [],
// loads all the sessions we've stored
getSessionList: function() {
$.ajax({
url: 'https://www.scholarshippoints.com/jajo/sessionList/',
dataType: 'json',
statusCode: {
200: function(data) {
console.log(data);
}
}
})
},
// loads a session to replay
getSession: function(sessionKey) {
// were gonna run the player so turn tracking off
jajo.settings.track = false;
// has a sessionID been passed?
if (typeof(sessionKey) !== 'undefined') {
var url = 'https://www.scholarshippoints.com/jajo/getSession/?sessionKey=' + sessionKey;
} else {
var url = 'https://www.scholarshippoints.com/jajo/getSession/';
}
// call our session from the server
$.ajax({
url: url,
dataType: 'json',
statusCode: {
200: function(data) {
//console.log(data.session);
var session = {};
session.events = []
for (var i=0; i<data.session.length; i++) {
//console.log(data.session[i]);
if (i == 0) {
session.startTime = data.session[0].pageLoad;
// console.log('startTime');
// console.log(session.startTime);
session.screenWidth = data.session[0].screenWidth;
session.screenHeight = data.session[0].screenHeight;
}
session.events.push({eventType: data.session[i].eventType, eventData: $.parseJSON(data.session[i].eventData)});
}
//scrollAnimation = [];
//mouseAnimation = [];
var firstScroll = true;
var firstMouse = true;
var firstClick = true;
var lastScrollTimestamp = null;
var lastMouseTimestamp = null;
var lastClickTimestamp = null;
for (var i=0; i<session.events.length; i++) {
var event = session.events[i];
var singleScrollAnimation = [];
var singleMouseAnimation = [];
var clickAnimation = [];
if (event.eventType == 'scroll') {
if (firstScroll) {
//scrollAnimation.push({'delay':jajo.tracking.adjustTime(session.startTime, event.eventData.startTimestamp)});
singleScrollAnimation.push({'delay':jajo.tracking.adjustTime(session.startTime, event.eventData.startTimestamp)});
firstScroll = false
} else {
//scrollAnimation.push({'delay':jajo.tracking.adjustTime(lastScrollTimestamp, event.eventData.startTimestamp)});
singleScrollAnimation.push({'delay':jajo.tracking.adjustTime(lastScrollTimestamp, event.eventData.startTimestamp)});
}
//scrollAnimation.push({'scrollTop':event.eventData.endPosition});
singleScrollAnimation.push({'scrollTop':event.eventData.endPosition});
//scrollAnimation.push({'animationLength':jajo.tracking.adjustTime(event.eventData.startTimestamp, event.eventData.endTimestamp)});
singleScrollAnimation.push({'animationLength':jajo.tracking.adjustTime(event.eventData.startTimestamp, event.eventData.endTimestamp)});
lastScrollTimestamp = event.eventData.endTimestamp;
// add this to our set of animations to run
jajo.player.animationSequences.push(jajo.player.buildAnimationSequence('scroll', singleScrollAnimation));
}
if (event.eventType == 'mouse') {
//mouseAnimation.push({'left':event.eventData.startX});
//mouseAnimation.push({'top':event.eventData.startY});
if (firstMouse) {
// console.log('firstMouse');
// console.log(Number(event.eventData.startTimestamp));
//mouseAnimation.push({'delay':jajo.tracking.adjustTime(session.startTime, event.eventData.startTimestamp)});
singleMouseAnimation.push({'delay':jajo.tracking.adjustTime(session.startTime, event.eventData.startTimestamp)});
firstMouse = false;
} else {
//mouseAnimation.push({'delay':jajo.tracking.adjustTime(lastMouseTimestamp, event.eventData.startTimestamp)});
singleMouseAnimation.push({'delay':jajo.tracking.adjustTime(lastMouseTimestamp, event.eventData.startTimestamp)});
}
//mouseAnimation.push({'left':event.eventData.endX});
singleMouseAnimation.push({'left':event.eventData.endX});
//mouseAnimation.push({'top':event.eventData.endY});
singleMouseAnimation.push({'top':event.eventData.endY});
//mouseAnimation.push({'animationLength':jajo.tracking.adjustTime(event.eventData.startTimestamp, event.eventData.endTimestamp)});
singleMouseAnimation.push({'animationLength':jajo.tracking.adjustTime(event.eventData.startTimestamp, event.eventData.endTimestamp)});
lastMouseTimestamp = event.eventData.endTimestamp;
jajo.player.animationSequences.push(jajo.player.buildAnimationSequence('mouse', singleMouseAnimation));
}
if (event.eventType == 'click') {
//console.log('click event');
if (firstClick) {
clickAnimation.push({'clickDelay':jajo.tracking.adjustTime(session.startTime, event.eventData.clickTimestamp)});
lastClickTimestamp = event.eventData.clickTimestamp;
} else {
clickAnimation.push({'clickDelay':jajo.tracking.adjustTime(lastClickTimestamp, event.eventData.clickTimestamp)});
}
clickAnimation.push({'clickX':event.eventData.clickX});
clickAnimation.push({'clickY':event.eventData.clickY});
jajo.player.animationSequences.push(jajo.player.buildAnimationSequence('click', clickAnimation));
}
}
for (var i=0; i<jajo.player.animationSequences.length; i++) {
eval(jajo.player.animationSequences[i]);
}
}
}
});
},
// builds a single animation
buildAnimationSequence: function(type, animation) {
// now build our big scroll animation chain
var sequence = '';
// scrolls are animated on the body
if (type == 'scroll') {
sequence += '$(\'body\')';
}
// mouse animates the little cursor image
if (type == 'mouse') {
sequence += '$(\'img#cursor\')';
}
var clickDelay = 0;
for (var i=0; i<animation.length; i++) {
// console.log(animation[i]);
$.each(animation[i], function(key, value) {
if (key == 'delay') {
sequence += '.delay(' + value + ')';
}
if (key == 'scrollTop') {
sequence += '.animate({\'scrollTop\':' + value + '}, ';
}
if (key == 'animationLength') {
sequence += value + ')';
}
if (key == 'left') {
sequence += '.animate({left:' + value + ',';
}
if (key == 'top') {
sequence += 'top:' + value + '},';
}
if (key == 'clickDelay') {
sequence += 'setTimeout(function(){';
clickDelay = value;
}
if (key == 'clickX') {
sequence += '$(\'body\').append(\'<img src="https://www.scholarshippoints.com/images/click.png" style="position:absolute; left:' + (value - 32) + 'px; ';
//// $('body').append('<img src="https://www.scholarshippoints.com/images/click.png" style="position:absolute; left:' + (e.pageX -32) + 'px; top:' + (e.pageY - 32) + 'px; z-index:9999;">');
}
if (key == 'clickY') {
sequence += 'top:' + (value - 32) + 'px; z-index:9999;">\')';
if (clickDelay > 0) {
sequence += '}, ' + clickDelay + ')';
}
}
});
}
sequence += ';';
//console.log(sequence);
return sequence;
}
},
// main init function
init: function() {
// starting jajo
jajo.session.init();
// tracking mode
if (jajo.settings.track) {
// how to tracking scrolling
$(window).scroll(function() {
// start a new scroll track if we're not in the middle of another one
if (jajo.tracking.scroll.doneScrolling) {
jajo.tracking.scroll.trackScrollStart();
}
clearTimeout($.data(this, 'scrollTimer'));
$.data(this, 'scrollTimer', setTimeout(function() {
jajo.tracking.scroll.trackScrollEnd();
}, 300));
});
// add our mouse cursor to the screen
$('body').append('<img src="https://www.scholarshippoints.com/images/cursor.png" id="cursor" style="position:absolute; left:0; top:0; z-index:9999;">');
// mouse tracking
$(window).mousemove(function(e) {
// start a new mouse movement if we're not in the middle of another one
if (jajo.tracking.mouse.doneMousing) {
jajo.tracking.mouse.trackMouseStart(e);
}
clearTimeout($.data(this, 'mouseTimer'));
$.data(this, 'mouseTimer', setTimeout(function() {
jajo.tracking.mouse.trackMouseEnd(e);
}, 300));
});
// click tracking
$(window).click(function(e) {
// add our click marker
jajo.tracking.click.trackClick(e);
});
}
}
};
//
$().ready(function() {
jajo.init();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment