const threshold = 0.97
const testSampleCount = 2;
const minSampleReq = 4;
var counter = 0;
var recording = false;
var sessionSamples = [];
var sessionLabels = [];
var buffer = [];
function foo() {
for(var i = 0; i < 10000000; i++);
}
function bar() {
for(var i = 0; i < 100000000; i++);
}
function getPrefix() {
var prefix = null;
if (window.performance !== undefined) {
if (window.performance.now !== undefined)
prefix = "";
else {
var browserPrefixes = ["webkit","moz","ms","o"];
// Test all vendor prefixes
for(var i = 0; i < browserPrefixes.length; i++) {
if (window.performance[browserPrefixes[i] + "Now"] != undefined) {
prefix = browserPrefixes[i];
break;
}
}
}
}
return prefix;
}
function getTime() {
return (prefix === "") ? window.performance.now() : window.performance[prefix + "Now"]();
}
function doBenchmark() {
if (prefix === null){
document.getElementById("displayRes").style.display = 'block';
document.getElementById("displayRes").textContent = "Your browser does not support High Resolution Time API";
}
// else {
// var startTime = getTime();
// foo();
// var test1 = getTime();
// bar();
// var test2 = getTime();
// document.getElementById("log").innerHTML += "
Browser hi-res time support test:
";
// document.getElementById("log").innerHTML += "Foo time: " + (test1 - startTime) + "
";
// document.getElementById("log").innerHTML += "Bar time: " + (test2 - test1) + "
";
// }
}
var prefix = getPrefix();
window.onload = doBenchmark;
function showMatchTab(){
document.getElementById("createTab").style.display = "none";
document.getElementById("matchTab").style.display = "block";
document.getElementById('displayRes').style.display = "none";
}
function showCreateTab(){
counter = 0;
recording = false;
sessionSamples = [];
sessionLabels = [];
buffer = [];
document.getElementById("createTab").style.display = "block";
document.getElementById("matchTab").style.display = "none";
document.getElementById('displayRes').style.display = "none";
setWords();
}
function startRecording(){
if(recording){
recording=false;
//console.log('Stopped recording');
document.getElementById("recBttn").style = 'display: none';
document.getElementById("wrongBttn").style = 'display: none;';
document.getElementById("correctBttn").style = 'display: static;width: 20px; padding-bottom: 30px; margin-left: -30px;';
if (counter == minSampleReq-1){
document.getElementById("saveBttn").style = 'display: block; width: 20px; cursor: pointer;';
// document.getElementById('showAccuracy').style = 'display: static';
document.getElementById('matchFingerprint').style = 'display: static';
document.getElementById('textRecord').setAttribute("disabled", true);
showAccuracy();
}
extractFeatures();
showKDEGraph();
} else {
buffer = [];
document.getElementById("textRecord").value = '';
document.getElementById("recBttn").style = 'display: static;width: 20px; padding-bottom: 30px; margin-left: -30px;';
document.getElementById("wrongBttn").style = 'display: none;';
document.getElementById("correctBttn").style = 'display: none;';
// document.getElementById('showAccuracy').style = 'display: none';
document.getElementById('matchFingerprint').style = 'display: none';
recording=true;
//console.log('Started recording');
}
}
function showAccuracy(){
document.getElementById('displayRes').textContent = '';
document.getElementById('displayRes').style = "display: block;";
var testSamples = sessionSamples.slice(Math.max(sessionSamples.length - testSampleCount, 0));
var testResult = [];
for(var i=0; i < testSamples.length;i++){
var allSimilarities = [];
for (var m=0; m < sessionSamples.length; m++){
var pred = cosineSimilarity(sessionSamples[m], testSamples[i]);
allSimilarities.push(pred);
}
var med = calculateMedian(allSimilarities);
var res = med > threshold ? 0 : 1;
testResult.push(res);
}
var wins = 0;
for(var n=0; n < testResult.length;n++){
if(testResult[n] == 0) {
wins++;
}
}
var proc = (wins/testResult.length) *100;
console.log([proc,wins,testResult.length])
document.getElementById('displayRes').textContent = 'Consistancy: ' + proc + '%';
}
async function stopRecordingTest(){
recording=false;
var testSample = extractOneFeature();
var allSimilarities = [];
for (var i=0; i < sessionSamples.length; i++){
var pred = cosineSimilarity(sessionSamples[i], testSample);
allSimilarities.push(pred);
}
var med = calculateMedian(allSimilarities);
var res = med > threshold ? "It really is you ❤️" : "👮 Intruder!!!";
var rest = med > threshold ? 0 : 1;
if (rest == 0){
document.getElementById('correctBttn1').style = 'display: static;width: 20px; padding-bottom: 30px; margin-left: -30px;';
document.getElementById('wrongBttn1').style = "display: none";
} else if (rest == 1) {
document.getElementById('correctBttn1').style = "display: none";
document.getElementById('wrongBttn1').style= 'display: static; width: 20px; padding-bottom: 30px; margin-left: -30px;';
}
showKDEGraph();
document.getElementById('displayRes').style = "display: block;";
document.getElementById('displayRes').textContent = res;
}
function setWords(){
document.getElementById('randomWords').textContent = '';
for(var i=0; i< 4;i++){
var ttx = words();
document.getElementById('randomWords').textContent += ttx + ' ';
}
}
function startRecordingTest(){
buffer = [];
document.getElementById("testRecord").value = '';
recording=true;
//console.log('Started recording');
}
function calculateMedian(values) {
values.sort( function(a,b) {return a - b;} );
var half = Math.floor(values.length/2);
if(values.length % 2)
return values[half];
else
return (values[half-1] + values[half]) / 2.0;
}
function calculateAverage(array) {
var total = 0;
var count = 0;
array.forEach(function(item, index) {
total += item;
count++;
});
return total / count;
}
function extractFeatures(){
var uppArr = [];
var downArr = [];
var holdKeyTime = [];
var ddKeyTime = [];
var udKeyTime = [];
for(var i = 0; i < buffer.length;i++){
if(buffer[i].event == "up"){
uppArr.push(buffer[i]);
}
if (buffer[i].event == "down"){
downArr.push(buffer[i]);
}
}
if(uppArr.length != downArr.length || uppArr.length == 0 || downArr.length == 0) {
document.getElementById("wrongBttn").style = 'display: static; width: 20px; padding-bottom: 30px; margin-left: -30px;';
document.getElementById("correctBttn").style = 'display: none';
return [];
}
for(var i=0; i < downArr.length;i++){
var timed = uppArr[i].time - downArr[i].time;
holdKeyTime.push({key: 'H.'+ uppArr[i].key, time: timed });
if(i == downArr.length-1) break;
var pkey = ('DD.'+ downArr[i].key + '.' + downArr[i+1].key);
ddKeyTime.push({key: pkey, time: downArr[i+1].time - downArr[i].time});
var skey = 'UD.'+ uppArr[i].key + '.' + downArr[i+1].key;
udKeyTime.push({ key: skey, time: downArr[i+1].time -uppArr[i].time});
}
var featureReady = [];
var labels = []
// var curUser = document.getElementById("nameField").value =='' ? 'NoName' : document.getElementById("nameField").value;
for (var i=0; i < holdKeyTime.length; i++) {
var totalTime = buffer[buffer.length-1].time - buffer[0].time;
// var avrgSpeed = totalTime/holdKeyTime.length;
if(i == holdKeyTime.length-1){
labels.push(holdKeyTime[i].key);
featureReady.push(holdKeyTime[i].time/1000);
break;
}
labels.push(holdKeyTime[i].key);
labels.push(ddKeyTime[i].key);
labels.push(udKeyTime[i].key);
var hk_ = holdKeyTime[i].time/1000;
var ddk_ = ddKeyTime[i].time/1000;
var udk_ = udKeyTime[i].time/1000;
var hk = hk_ < 0 ? (hk_ * -1) : hk_;
var ddk = ddk_ < 0 ? (ddk_ * -1) : ddk_;
var udk = udk_ < 0 ? (udk_ * -1) : udk_;
featureReady.push(hk);
featureReady.push(ddk);
featureReady.push(udk);
}
// labels.push('avgSpeed')
sessionSamples.push(featureReady);
sessionLabels = labels;
counter++;
document.getElementById("counterView").textContent = counter;
}
function extractOneFeature(){
var uppArr = [];
var downArr = [];
var holdKeyTime = [];
var ddKeyTime = [];
var udKeyTime = [];
for(var i = 0; i < buffer.length;i++){
if(buffer[i].event == "up"){
uppArr.push(buffer[i]);
}
if (buffer[i].event == "down"){
downArr.push(buffer[i]);
}
}
if(uppArr.length != downArr.length || uppArr.length == 0 || downArr.length == 0) {
document.getElementById("wrongBttn").style = 'display: static; width: 20px; padding-bottom: 30px; margin-left: -30px;';
document.getElementById("correctBttn").style = 'display: none';
return [];
}
for(var i=0; i < downArr.length;i++){
var timed = uppArr[i].time - downArr[i].time;
holdKeyTime.push({key: 'H.'+ uppArr[i].key, time: timed });
if(i == downArr.length-1) break;
var pkey = ('DD.'+ downArr[i].key + '.' + downArr[i+1].key);
ddKeyTime.push({key: pkey, time: downArr[i+1].time - downArr[i].time});
var skey = 'UD.'+ uppArr[i].key + '.' + downArr[i+1].key;
udKeyTime.push({ key: skey, time: downArr[i+1].time -uppArr[i].time});
}
var featureReady = [];
var labels = []
for (var i=0; i < holdKeyTime.length; i++) {
if(i == holdKeyTime.length-1){
labels.push(holdKeyTime[i].key);
featureReady.push(holdKeyTime[i].time/1000);
break;
}
labels.push(holdKeyTime[i].key);
labels.push(ddKeyTime[i].key);
labels.push(udKeyTime[i].key);
var hk_ = holdKeyTime[i].time/1000;
var ddk_ = ddKeyTime[i].time/1000;
var udk_ = udKeyTime[i].time/1000;
var hk = hk_ < 0 ? (hk_ * -1) : hk_;
var ddk = ddk_ < 0 ? (ddk_ * -1) : ddk_;
var udk = udk_ < 0 ? (udk_ * -1) : udk_;
featureReady.push(hk);
featureReady.push(ddk);
featureReady.push(udk);
}
return featureReady;
}
function saveSamples(){
//merege and convert samples to CSV
var csv = sessionLabels +'\n';
sessionSamples.forEach(function(row) {
csv += row.join(',');
csv += "\n";
});
//console.log(csv);
var hiddenElement = document.createElement('a');
hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
hiddenElement.target = '_blank';
hiddenElement.download = 'signature.sms';
hiddenElement.click();
}
//graph KDE
function showKDEGraph(){
d3.select("svg").selectAll("*").remove();
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
margin = {top: 20, right: 30, bottom: 30, left: 40};
var x = d3.scaleLinear()
.domain([0, 1])
.range([margin.left, width - margin.right]);
var y = d3.scaleLinear()
.domain([0, 1])
.range([height - margin.bottom, margin.top]);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x))
.append("text")
.attr("x", width - margin.right)
.attr("y", -6)
.attr("fill", "#000")
.attr("text-anchor", "end")
.attr("font-weight", "bold")
.text("Time in seconds)");
svg.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y).ticks(null));
var faithful = sessionSamples[sessionSamples.length-1];
var n = faithful.length,
bins = d3.histogram().domain(x.domain()).thresholds(sessionLabels.length)(faithful),
density = kernelDensityEstimator(kernelEpanechnikov(0.0155), x.ticks(sessionLabels.length))(faithful);
svg.insert("g", "*")
.attr("fill", "#bbb")
.selectAll("rect")
.data(bins)
.enter().append("rect")
.attr("x", function(d) { return x(d.x0) + 1; })
.attr("y", function(d) { return y(d.length / n); })
.attr("width", function(d) { return x(d.x1) - x(d.x0) - 1; })
.attr("height", function(d) { return y(0) - y(d.length / n); });
svg.append("path")
.datum(density)
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", 1.5)
.attr("stroke-linejoin", "round")
.attr("d", d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); }));
}
function kernelDensityEstimator(kernel, X) {
return function(V) {
return X.map(function(x) {
return [x, d3.mean(V, function(v) { return kernel(x - v); })];
});
};
}
function kernelEpanechnikov(k) {
return function(v) {
return Math.abs(v /= k) <= 1 ? 0.0199 * (1 - v * v) / k : 0;
};
}
function dotp(x, y) {
function dotp_sum(a, b) {
return a + b;
}
function dotp_times(a, i) {
return x[i] * y[i];
}
return x.map(dotp_times).reduce(dotp_sum, 0);
}
function cosineSimilarity(A,B){
var similarity = dotp(A, B) / (Math.sqrt(dotp(A,A)) * Math.sqrt(dotp(B,B)));
return similarity;
}
window.addEventListener('load', () => {
'use strict';
document.getElementById("textRecord").value = '';
document.getElementById("testRecord").value = '';
// document.getElementById("nameField").value = '';
document.getElementById("counterView").value = counter;
document.getElementById("minSampleView").textContent = minSampleReq;
const charList = 'abcdefghijklmnopqrstuvwxyz@shift0123456789period!?';
document.addEventListener('keydown', event => {
if (!recording) return;
var key = event.key;
if (key == '.') key = 'period';
// we are only interested in
if (charList.indexOf(key) === -1) return;
const el = {
key: key.toLowerCase(),
event:'down',
time: performance.now()
}
buffer.push(el);
});
document.addEventListener('keyup', event => {
if (!recording) return;
var key = event.key;
if (key == '.') key = 'period';
// we are only interested in
if (charList.indexOf(key) === -1) return;
const el = {
key: key.toLowerCase(),
event:'up',
time: performance.now()
}
buffer.push(el);
});
});