Created
June 3, 2016 16:14
-
-
Save sitesbyjoe/1088f42a44f0acf0c45c47a1258cd339 to your computer and use it in GitHub Desktop.
jajo.js - home-brewed screen recording from back in 2013...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ----------------------------------------------------------- | |
* 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