├── README.md ├── typingdna.js ├── typingdna.min.js └── versions ├── typingdna_2.1.js ├── typingdna_2.11.js ├── typingdna_2.12.js ├── typingdna_2.13.js ├── typingdna_2.14.js ├── typingdna_2.15.js ├── typingdna_2.5.js ├── typingdna_2.6.js ├── typingdna_2.7.js ├── typingdna_2.8.js ├── typingdna_2.9.js ├── typingdna_3.1.js └── typingdna_3.2.js /README.md: -------------------------------------------------------------------------------- 1 | # TypingDNA JavaScript recorder 2 | ##### A simple way to capture user’s typing patterns 3 | Full documentation at [api.typingdna.com](https://api.typingdna.com)* 4 | 5 | ### Usage and description 6 | First you need to import the [typingdna.js](https://typingdna.com/scripts/typingdna.js) file in the page that wants to record a typing pattern. You will need to record typing patterns when a user first creates his account and again whenever you want to authenticate that user on your platform. You can host the .js file yourself. 7 | 8 | Alternative locations from where you can include the last class: 9 | * https://typingdna.com/scripts/typingdna.js 10 | * https://api.typingdna.com/scripts/typingdna.js 11 | 12 | ### TypingDNA class 13 | 14 | Once you create an instance of the TypingDNA class, the user typing starts being recorded (as a history of keystroke events). Whenever you want to get the user's typing pattern you have to invoke .getTypingPattern method described in detail below. 15 | 16 | **Returns**: Returns the instance of the TypingDNA class (singleton) 17 | 18 | **Example** 19 | ```js 20 | var tdna = new TypingDNA(); 21 | ``` 22 | Here are the functions available in the TypingDNA class: 23 | * Main: 24 | * .getTypingPattern(optionsObject) ⇒ `String` 25 | * Optional: 26 | * .addTarget() 27 | * .removeTarget() 28 | * .start() *Automatically called by default* 29 | * .stop() 30 | * .reset() 31 | * .getQuality(typingPattern) ⇒ `Number` 32 | 33 | 34 | ### TypingDNA.getTypingPattern(optionsObject) 35 | This is the main function that outputs the user's typing pattern as a `String` 36 | 37 | **Returns**: A typing pattern in `String` form 38 | 39 | **optionsObject**: An object of the following form {type:Number, text:String, textId:Number, length: Number, targetId:String, caseSensitive:Boolean}. Detail table below. 40 | 41 | | Param | Type | Description | 42 | | --- | --- | --- | 43 | | **type** | `Number` | `0 for anytext pattern` (when you compare random typed texts of usually 120-180 chars long)
`1 for sametext pattern` (also called diagram pattern, recommended in most cases, for emails, passwords, phone numbers, credit cards, short texts)
` 2 for extended pattern` (most versatile, can replace both anytext and sametext patterns) | 44 | | **text** | `String` | (Only for type 1 and type 2) a typed string that you want the typing pattern for | 45 | | **textId** | `Number` | (Optional, only for type 1 and type 2) a personalized id for the typed text | 46 | | **length** | `Number` | (Optional) the length of the text in the history for which you want the typing pattern, for type 0 is usually 140 or more | 47 | | **targetId** | `String` | (Optional) specifies if pattern is obtain only from text typed in a certain target | 48 | | **caseSensitive** | `Boolean` | (Optional, default: false) Used if you pass a text for type 1 or type 2| 49 | 50 | **Examples** 51 | ```js 52 | //anytext pattern 53 | var typingPattern = tdna.getTypingPattern({type:0, length:160}); 54 | //sametext pattern 55 | var typingPattern = tdna.getTypingPattern({type:1, text:"Hello5g21?*"}); 56 | //extended pattern 57 | var typingPattern = tdna.getTypingPattern({type:2, text:"example@mail.com"}); 58 | ``` 59 | 60 | ### TypingDNA.addTarget(element_id) 61 | (Optional) Adds a target to the targetIds array. It has to be a text input or text area or any other HTML DOM element that has the .value property. You can add multiple targets (such as username and password fields). 62 | 63 | If you omit adding targets the typing patterns will be recorded for the entire typing session. 64 | 65 | **Example** 66 | ```js 67 | TypingDNA.addTarget(emailaddr_id) 68 | TypingDNA.addTarget(password_id) 69 | ``` 70 | 71 | ### TypingDNA.removeTarget(element_id) 72 | Remove a target from the targetIds array. 73 | 74 | ### TypingDNA.reset() 75 | Resets the history stack of recorded typing events. 76 | 77 | ### TypingDNA.start() 78 | Automatically called at initilization. It starts the recording of typing events. You only have to call .start() to resume recording after a .stop() 79 | 80 | ### TypingDNA.stop() 81 | Ends the recording of further typing events. 82 | 83 | ### TypingDNA.getQuality(typingPattern) 84 | Checks the quality of a general typing pattern (type 0), how well it is revelated, how useful the 85 | information will be for matching applications. 86 | 87 | **Returns**: `Number` - A real number between `0` and `1`. Values over `0.3` are acceptable, however a value over `0.7` shows good pattern strength. 88 | 89 | | Param | Type | Description | 90 | | --- | --- | --- | 91 | | typingPattern | `String` | The type `0` pattern string returned by the getTypingPattern() function. | 92 | 93 | **Example** 94 | ```js 95 | var patternQuality = tdna.getQuality(typingPattern); 96 | ``` 97 | 98 | ### License 99 | Apache License, [Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 100 | -------------------------------------------------------------------------------- /typingdna.min.js: -------------------------------------------------------------------------------- 1 | function TypingDNA(){if(!0===TypingDNA.initialized)return TypingDNA.instance;TypingDNA.prototype.start=function(){return TypingDNA.start.apply(this,arguments)},TypingDNA.prototype.stop=function(){return TypingDNA.stop.apply(this,arguments)},TypingDNA.prototype.reset=function(){return TypingDNA.reset.apply(this,arguments)},TypingDNA.prototype.removeEventListeners=function(){return TypingDNA.removeEventListeners.apply(this,arguments)},TypingDNA.prototype.addTarget=function(){return TypingDNA.addTarget.apply(this,arguments)},TypingDNA.prototype.removeTarget=function(){return TypingDNA.removeTarget.apply(this,arguments)},TypingDNA.prototype.getTypingPattern=function(){return TypingDNA.getTypingPattern.apply(this,arguments)},TypingDNA.prototype.getMouseDiagram=function(){return TypingDNA.getMouseDiagram.apply(this,arguments)},TypingDNA.prototype.startMouse=function(){return TypingDNA.startMouse.apply(this,arguments)},TypingDNA.prototype.stopMouse=function(){return TypingDNA.stopMouse.apply(this,arguments)},TypingDNA.prototype.getQuality=function(){return TypingDNA.getQuality.apply(this,arguments)},TypingDNA.prototype.getLength=function(){return TypingDNA.getLength.apply(this,arguments)},TypingDNA.prototype.isMobile=function(){return TypingDNA.isMobile.apply(this,arguments)},TypingDNA.prototype.getTextId=function(){return TypingDNA.getTextId.apply(this,arguments)},TypingDNA.prototype.checkEnvironment=function(){return TypingDNA.checkEnvironment.apply(this,arguments)},TypingDNA.prototype.get=function(){return TypingDNA.get.apply(this,arguments)},TypingDNA.prototype.startDiagram=function(){},TypingDNA.prototype.stopDiagram=function(){},TypingDNA.prototype.getDiagram=function(){return TypingDNA.getDiagram.apply(this,arguments)},TypingDNA.prototype.getExtendedDiagram=function(){return TypingDNA.getExtendedDiagram.apply(this,arguments)},TypingDNA.initialized=!0,TypingDNA.prototype.maxHistoryLength=TypingDNA.maxHistoryLength,TypingDNA.prototype.defaultHistoryLength=TypingDNA.defaultHistoryLength,TypingDNA.prototype.maxSeekTime=TypingDNA.maxSeekTime,TypingDNA.prototype.maxPressTime=TypingDNA.maxPressTime,TypingDNA.version=3.2,TypingDNA.cookieId=0,TypingDNA.flags=0,TypingDNA.instance=this,TypingDNA.document=document,TypingDNA.ua=window.navigator.userAgent,TypingDNA.platform=window.navigator.platform,TypingDNA.maxHistoryLength=2e3,TypingDNA.maxSeekTime=1500,TypingDNA.maxPressTime=300,TypingDNA.defaultHistoryLength=160,TypingDNA.spKeyCodes=[8,13,32],TypingDNA.spKeyCodesObj={8:1,13:1,32:1},TypingDNA.keyCodes=[65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,32,222,188,190,186,187,189,191,48,49,50,51,52,53,54,55,56,57],TypingDNA.keyCodesObj={};for(var i=TypingDNA.keyCodes.length,e=0;e=0)&&window.CSS?i.browserType="blink":window.opr&&window.opr.addons||window.opera||navigator.userAgent.indexOf(" OPR/")>=0?i.browserType="opera":"undefined"!=typeof InstallTrigger?i.browserType="firefox":document.documentMode?i.browserType="internet explorer":!document.documentMode&&window.StyleMedia?i.browserType="edge":window.chrome&&(window.chrome.webstore||window.chrome.runtime||window.chrome.loadTimes)&&-1!=navigator.userAgent.indexOf("Edg")?i.browserType="edge chromium":window.chrome&&(window.chrome.webstore||window.chrome.runtime||window.chrome.loadTimes)?i.browserType="chrome":(/constructor/i.test(window.HTMLElement)||"[object SafariRemoteNotification]"===(!window.safari||"undefined"!=typeof safari&&safari.pushNotification).toString()||navigator.userAgent.indexOf("Safari")>-1&&-1===navigator.userAgent.indexOf("Chrome"))&&(i.browserType="safari"),i.isMobile&&(i.hasMotionSensors=void 0!==window.DeviceMotionEvent,i.needsPermissionForMotionSensors=void 0!==window.DeviceMotionEvent&&"function"==typeof window.DeviceMotionEvent.requestPermission),i},TypingDNA.keyDown=function(i){if((TypingDNA.recording||TypingDNA.diagramRecording)&&TypingDNA.isTarget(i.target.id)){var e=i.keyCode;1===TypingDNA.wfk[e]||TypingDNA.dwfk[e];var n=TypingDNA.pt1;TypingDNA.pt1=(new Date).getTime();var t=TypingDNA.pt1-n,p=TypingDNA.pt1;(!0===TypingDNA.recording||TypingDNA.spKeyCodesObj[e]&&!0===TypingDNA.spKeyRecording)&&(i.shiftKey||(TypingDNA.wfk[e]=1,TypingDNA.skt[e]=t,TypingDNA.sti[e]=p)),!0===TypingDNA.diagramRecording&&(TypingDNA.dwfk[e]=1,TypingDNA.dskt[e]=t,TypingDNA.dsti[e]=p,TypingDNA.dlastDownKey=e)}},TypingDNA.keyPress=function(i){if((TypingDNA.recording||TypingDNA.diagramRecording)&&TypingDNA.isTarget(i.target.id)&&!0===TypingDNA.diagramRecording){var e=TypingDNA.dlastDownKey;TypingDNA.drkc[e]=i.charCode}},TypingDNA.keyUp=function(i){if((TypingDNA.recording||TypingDNA.diagramRecording)&&TypingDNA.isTarget(i.target.id)){var e=(new Date).getTime(),n=i.keyCode,t=0;if(!0===TypingDNA.recording||TypingDNA.spKeyCodesObj[n]&&!0===TypingDNA.spKeyRecording){if(!i.shiftKey&&1===TypingDNA.wfk[n]){t=e-TypingDNA.sti[n];var p=[n,TypingDNA.skt[n],t,TypingDNA.prevKeyCode,e,i.target.id];TypingDNA.history.add(p),TypingDNA.prevKeyCode=n}TypingDNA.wfk[n]=0}if(!0===TypingDNA.diagramRecording){if(void 0!==TypingDNA.drkc[n]&&0!==TypingDNA.drkc[n]&&1===TypingDNA.dwfk[n]){t=e-TypingDNA.dsti[n];var o=[n,TypingDNA.dskt[n],t,TypingDNA.drkc[n],e,i.target.id];TypingDNA.history.addDiagram(o)}TypingDNA.dwfk[n]=0}}},TypingDNA.MobileKeyDown=function(i){if((TypingDNA.recording||TypingDNA.diagramRecording)&&TypingDNA.isTarget(i.target.id)){var e=i.keyCode;1===TypingDNA.wfk[e]||TypingDNA.dwfk[e],TypingDNA.lastPressTime=(new Date).getTime(),(!0===TypingDNA.recording||TypingDNA.spKeyCodesObj[e]&&!0===TypingDNA.spKeyRecording)&&(TypingDNA.wfk[e]=1),!0===TypingDNA.diagramRecording&&(TypingDNA.dwfk[e]=1,TypingDNA.dlastDownKey=e)}},TypingDNA.MobileKeyPress=function(i){if((TypingDNA.recording||TypingDNA.diagramRecording)&&TypingDNA.isTarget(i.target.id)&&!0===TypingDNA.diagramRecording){var e=TypingDNA.dlastDownKey;TypingDNA.drkc[e]=i.charCode}},TypingDNA.MobileKeyUp=function(i){if((TypingDNA.recording||TypingDNA.diagramRecording)&&TypingDNA.isTarget(i.target.id)){var e=TypingDNA.ut1;TypingDNA.ut1=(new Date).getTime();var n=TypingDNA.ut1-e,t=TypingDNA.kpGetAll(),p=0!==t[0]?Math.round(TypingDNA.ut1-t[0]):0;isNaN(p)&&(p=0);var o=i.keyCode;if(!0===TypingDNA.recording||TypingDNA.spKeyCodesObj[o]&&!0===TypingDNA.spKeyRecording){if(1===TypingDNA.wfk[o]){var r=[o,n,p,TypingDNA.prevKeyCode,TypingDNA.ut1,i.target.id];TypingDNA.history.add(r),TypingDNA.prevKeyCode=o}TypingDNA.wfk[o]=0}if(!0===TypingDNA.diagramRecording){if(void 0!==TypingDNA.drkc[o]&&0!==TypingDNA.drkc[o]&&1===TypingDNA.dwfk[o]){var g=[o,n,p,TypingDNA.drkc[o],TypingDNA.ut1,i.target.id,t[1].join(","),t[2].join(","),t[3].join(","),t[4].join(",")];TypingDNA.history.addDiagram(g)}TypingDNA.dwfk[o]=0}}},TypingDNA.AndroidKeyDown=function(i){(TypingDNA.recording||TypingDNA.diagramRecording)&&TypingDNA.isTarget(i.target.id)&&(TypingDNA.lastPressTime=(new Date).getTime(),-1===TypingDNA.ACInputLengths.inputs.indexOf(i.target)&&(TypingDNA.ACInputLengths.inputs.push(i.target),TypingDNA.ACInputLengths.lastLength.push(0)))},TypingDNA.AndroidKeyUp=function(i){if(TypingDNA.recording||TypingDNA.diagramRecording){var e=TypingDNA.ut1;if(TypingDNA.ut1=(new Date).getTime(),TypingDNA.isTarget(i.target.id)){var n=TypingDNA.ut1-e,t=TypingDNA.kpGetAll(),p=0!==t[0]?Math.round(TypingDNA.ut1-t[0]):0;isNaN(p)&&(p=0);var o=i.keyCode,r=TypingDNA.ACInputLengths.inputs.indexOf(i.target);-1===r&&(TypingDNA.ACInputLengths.inputs.push(i.target),TypingDNA.ACInputLengths.lastLength.push(0),r=TypingDNA.ACInputLengths.inputs.indexOf(i.target));var g=0;if(i.target.value.length>=TypingDNA.ACInputLengths.lastLength[r]){var s=i.target.value.substr(i.target.selectionStart-1||0,1);o=s.toUpperCase().charCodeAt(0),g=s.charCodeAt(0)}TypingDNA.ACInputLengths.lastLength[r]=i.target.value.length;var a=[o,n,p,TypingDNA.prevKeyCode,TypingDNA.ut1,i.target.id];if(TypingDNA.history.add(a),TypingDNA.prevKeyCode=o,!0===TypingDNA.diagramRecording){var y=[o,n,p,g,TypingDNA.ut1,i.target.id,t[1].join(","),t[2].join(","),t[3].join(","),t[4].join(",")];TypingDNA.history.addDiagram(y)}}}},TypingDNA.mouseScroll=function(i){if(!0===TypingDNA.mouseRecording&&!0===TypingDNA.mouseMoveRecording)if(!0===TypingDNA.mouse.scrollStarted){var e=(new Date).getTime();TypingDNA.mouse.scrollTimes.push(e),TypingDNA.mouse.scrollTopArr.push(TypingDNA.document.body.scrollTop),clearInterval(TypingDNA.scrollInterval),TypingDNA.scrollInterval=setInterval(TypingDNA.mouse.checkScroll,TypingDNA.maxScrollDeltaTime)}else TypingDNA.mouse.scrollStarted=!0},TypingDNA.mouseMove=function(i){if(!0===TypingDNA.mouseRecording){var e=(new Date).getTime();!0===TypingDNA.mouseMoveRecording&&(!0===TypingDNA.mouse.started?(TypingDNA.mouse.times.push(e),TypingDNA.mouse.xPositions.push(i.screenX),TypingDNA.mouse.yPositions.push(i.screenY),clearInterval(TypingDNA.moveInterval),TypingDNA.moveInterval=setInterval(TypingDNA.mouse.checkMove,TypingDNA.maxMoveDeltaTime)):TypingDNA.mouse.started=!0),TypingDNA.lastMouseMoveTime=e}},TypingDNA.mouseDown=function(i){if(!0===TypingDNA.mouseRecording&&(TypingDNA.mouse.checkMove(),TypingDNA.mouse.checkScroll(),1===i.which)){TypingDNA.lastMouseDownTime=(new Date).getTime();var e=TypingDNA.lastMouseDownTime-TypingDNA.lastMouseMoveTime;if(e0){for(var t=0;t21&&(TypingDNA.kpTimes.shift(),TypingDNA.kpAccZ.shift(),TypingDNA.kpX.shift(),TypingDNA.kpY.shift()),TypingDNA.hasDeviceMotion||(TypingDNA.hasDeviceMotion=!0,TypingDNA.hasDeviceOrientation=!0)},1===TypingDNA.isMobile()?(TypingDNA.isAndroid()?TypingDNA.isFirefox()?(TypingDNA.document.addEventListener("input",TypingDNA.AndroidKeyUp),TypingDNA.document.addEventListener("keydown",TypingDNA.AndroidKeyDown)):(TypingDNA.document.addEventListener("keyup",TypingDNA.AndroidKeyUp),TypingDNA.document.addEventListener("keydown",TypingDNA.AndroidKeyDown)):(TypingDNA.document.addEventListener("keyup",TypingDNA.MobileKeyUp),TypingDNA.document.addEventListener("keydown",TypingDNA.MobileKeyDown),TypingDNA.document.addEventListener("keypress",TypingDNA.MobileKeyPress)),void 0!==window.DeviceMotionEvent&&window.addEventListener("devicemotion",TypingDNA.deviceMotionHandler)):TypingDNA.document.addEventListener?(TypingDNA.document.addEventListener("keyup",TypingDNA.keyUp),TypingDNA.document.addEventListener("keydown",TypingDNA.keyDown),TypingDNA.document.addEventListener("keypress",TypingDNA.keyPress),TypingDNA.document.addEventListener("mousemove",TypingDNA.mouseMove),TypingDNA.document.addEventListener("mousedown",TypingDNA.mouseDown),TypingDNA.document.addEventListener("mouseup",TypingDNA.mouseUp),TypingDNA.document.addEventListener("scroll",TypingDNA.mouseScroll)):TypingDNA.document.attachEvent?(TypingDNA.document.attachEvent("onkeyup",TypingDNA.keyUp),TypingDNA.document.attachEvent("onkeydown",TypingDNA.keyDown),TypingDNA.document.attachEvent("onkeypress",TypingDNA.keyPress),TypingDNA.document.attachEvent("onmousemove",TypingDNA.mouseMove),TypingDNA.document.attachEvent("onmousedown",TypingDNA.mouseDown),TypingDNA.document.attachEvent("onmouseup",TypingDNA.mouseUp),TypingDNA.document.attachEvent("onscroll",TypingDNA.mouseScroll)):console.log("browser not supported"),TypingDNA.kpADifArr=function(i){var e=i.length-1,n=[0];if(e<2)return[[0],[0]];for(var t=[],p=[],o=0;o0)for(e=0;e=p?(p=s,r=e):s<=o&&(o=s,g=e);else s=i[1]-i[0],t.push(s);return[t,[r-1,r,r+1,r+2,r+3,g-1,g,g+1,g+2,g+3]]},TypingDNA.kpGetAll=function(){var i=0,n=[];if(TypingDNA.kpAccZ.length<2)return[i=TypingDNA.hasDeviceMotion&&TypingDNA.hasDeviceOrientation?0:TypingDNA.lastPressTime,n=[0,0,0,0,0,0,TypingDNA.kpLastPitch,TypingDNA.kpLastRoll],[0],[0],[0]];[kpza,kpzaAbs]=TypingDNA.kpADifArr(TypingDNA.kpAccZ),[kpXR,kpxPos]=TypingDNA.kpRDifArr(TypingDNA.kpX),[kpYR,kpyPos]=TypingDNA.kpRDifArr(TypingDNA.kpY),TypingDNA.kpX.shift(),TypingDNA.kpY.shift(),TypingDNA.kpAccZ.shift(),TypingDNA.kpTimes.shift();var t=kpxPos.concat(kpyPos);t=t.sort();var p=[];for(e=1;e(kpzaAbs.length>8?2:kpzaAbs.length>4?1:0)&&void 0!==kpzaAbs[g]&&kpzaAbs[g]>o&&(o=kpzaAbs[g],r=TypingDNA.kpTimes[g])}return i=r,n=[TypingDNA.kpLastAccX,TypingDNA.kpLastAccY,TypingDNA.kpAccZ.pop(),TypingDNA.kpX.pop(),TypingDNA.kpY.pop(),TypingDNA.kpLastZ,TypingDNA.kpLastPitch,TypingDNA.kpLastRoll],TypingDNA.kpX=[],TypingDNA.kpY=[],TypingDNA.kpAccZ=[],TypingDNA.kpTimes=[],TypingDNA.pressCalculated||(TypingDNA.pressCalculated=!0),[i,n,kpza,kpXR,kpYR]},TypingDNA.mouse={},TypingDNA.mouse.times=[],TypingDNA.mouse.xPositions=[],TypingDNA.mouse.yPositions=[],TypingDNA.mouse.scrollTimes=[],TypingDNA.mouse.scrollTopArr=[],TypingDNA.mouse.history={},TypingDNA.mouse.history.stack=[],TypingDNA.mouse.getDistance=function(i,e){return Math.sqrt(i*i+e*e)},TypingDNA.mouse.getTotalDistance=function(i,n){var t=0,p=i.length;for(e=1;e=0?n?180+Math.round(Math.atan(Math.abs(i)/(Math.abs(e)+1e-7))/.01745329251):90-Math.round(Math.atan(Math.abs(i)/(Math.abs(e)+1e-7))/.01745329251)+270:n?90-Math.round(Math.atan(Math.abs(i)/(Math.abs(e)+1e-7))/.01745329251)+90:Math.round(Math.atan(Math.abs(i)/(Math.abs(e)+1e-7))/.01745329251)},TypingDNA.mouse.recordMoveAction=function(i){var e=TypingDNA.mouse.times.length;if(!(e<3)){for(var n=TypingDNA.mouse.times[e-1]-TypingDNA.mouse.times[0],t=TypingDNA.mouse.xPositions[e-1]-TypingDNA.mouse.xPositions[0],p=TypingDNA.mouse.yPositions[e-1]-TypingDNA.mouse.yPositions[0],o=Math.round(TypingDNA.mouse.getDistance(t,p)),r=Math.round(TypingDNA.mouse.getTotalDistance(TypingDNA.mouse.xPositions,TypingDNA.mouse.yPositions)),g=Math.round(100*r/o),s=[!0===i?5:1,n,o,Math.round(100*o/n),TypingDNA.mouse.getAngle(t,p),g],a=s.length,y=0;yTypingDNA.maxMouseHistoryLength&&this.stack.shift()},TypingDNA.mouse.history.getDiagram=function(){var i=this.stack.join("|");return[String(TypingDNA.isMobile())+","+String(TypingDNA.version)+","+TypingDNA.flags+",9,0,0,"+TypingDNA.getSpecialKeys()+","+TypingDNA.getDeviceSignature(),i].join("|")},TypingDNA.mouse.clearLastMove=function(){TypingDNA.mouse.times=[],TypingDNA.mouse.xPositions=[],TypingDNA.mouse.yPositions=[]},TypingDNA.mouse.checkMove=function(i){clearInterval(TypingDNA.moveInterval),!0===TypingDNA.mouse.started&&(TypingDNA.mouse.started=!1,TypingDNA.mouse.recordMoveAction(i),TypingDNA.mouse.clearLastMove())},TypingDNA.mouse.clearLastScroll=function(){TypingDNA.mouse.scrollTimes=[],TypingDNA.mouse.scrollTopArr=[]},TypingDNA.mouse.checkScroll=function(){clearInterval(TypingDNA.scrollInterval),!0===TypingDNA.mouse.scrollStarted&&(TypingDNA.mouse.scrollStarted=!1,TypingDNA.mouse.recordScrollAction(),TypingDNA.mouse.clearLastScroll())},TypingDNA.addTarget=function(i){var e=TypingDNA.targetIds.length,n=!1;if(e>0){for(var t=0;t0)for(var n=0;nn&&(i=n);var t={},p=TypingDNA.history.get(i,"",e);t.arr=p[0];var o=p[1];void 0!==e&&""!==e&&(i=o);var r=TypingDNA.zl,g=i,s=TypingDNA.math.fo(TypingDNA.history.get(i,"seek",e)),a=TypingDNA.math.fo(TypingDNA.history.get(i,"press",e)),y=Math.round(TypingDNA.math.avg(a)),T=Math.round(TypingDNA.math.avg(s)),D=Math.round(TypingDNA.math.sd(a)),A=Math.round(TypingDNA.math.sd(s)),N=T+y,d=TypingDNA.math.rd((y+r)/(N+r),4),u=TypingDNA.math.rd((1-d)/d,4),c=TypingDNA.math.rd((D+r)/(y+r),4),m=TypingDNA.math.rd((A+r)/(y+r),4),h=Math.round(6e4/(N+r));0===g&&(h=0);var l=[];for(var v in t.arr){var f=t.arr[v][1].length,k=0,w=0,M=0,b=0,x=0,L=0;switch(t.arr[v][0].length){case 0:break;case 1:k=TypingDNA.math.rd((t.arr[v][0][0]+r)/(T+r),4);break;default:l=TypingDNA.math.fo(t.arr[v][0]),k=TypingDNA.math.rd((TypingDNA.math.avg(l)+r)/(T+r),4),b=TypingDNA.math.rd((TypingDNA.math.sd(l)+r)/(A+r),4)}switch(t.arr[v][1].length){case 0:break;case 1:w=TypingDNA.math.rd((t.arr[v][1][0]+r)/(y+r),4);break;default:l=TypingDNA.math.fo(t.arr[v][1]),w=TypingDNA.math.rd((TypingDNA.math.avg(l)+r)/(y+r),4),x=TypingDNA.math.rd((TypingDNA.math.sd(l)+r)/(D+r),4)}switch(t.arr[v][2].length){case 0:break;case 1:M=TypingDNA.math.rd((t.arr[v][2][0]+r)/(T+r),4);break;default:l=TypingDNA.math.fo(t.arr[v][2]),M=TypingDNA.math.rd((TypingDNA.math.avg(l)+r)/(T+r),4),L=TypingDNA.math.rd((TypingDNA.math.sd(l)+r)/(A+r),4)}delete t.arr[v][2],delete t.arr[v][1],delete t.arr[v][0],t.arr[v][0]=f,t.arr[v][1]=k,t.arr[v][2]=w,t.arr[v][3]=M,t.arr[v][4]=b,t.arr[v][5]=x,t.arr[v][6]=L}l=[],TypingDNA.apu(l,g),TypingDNA.apu(l,h),TypingDNA.apu(l,N),TypingDNA.apu(l,d),TypingDNA.apu(l,u),TypingDNA.apu(l,c),TypingDNA.apu(l,m),TypingDNA.apu(l,y),TypingDNA.apu(l,T),TypingDNA.apu(l,D),TypingDNA.apu(l,A);for(var E=0;E<=6;E++)for(v=0;v<44;v++){var C=TypingDNA.keyCodes[v],S=t.arr[C][E];0===S&&E>0&&(S=1),TypingDNA.apu(l,S)}return TypingDNA.apu(l,TypingDNA.isMobile()),TypingDNA.apu(l,TypingDNA.version),TypingDNA.apu(l,TypingDNA.flags),TypingDNA.apu(l,-1),TypingDNA.apu(l,g),TypingDNA.apu(l,0),l.push(TypingDNA.getSpecialKeys()),l.push(TypingDNA.getDeviceSignature()),l.join(",")},TypingDNA.apu=function(i,e){"NaN"===String(e)&&(e=0),i.push(e)},TypingDNA.math={},TypingDNA.math.rd=function(i,e){return Number(i.toFixed(e))},TypingDNA.math.avg=function(i){var e=i.length;if(e>0){for(var n=0,t=0;t1){var e=i.concat(),n=i.length;e.sort((function(i,e){return i-e}));var t=this.sd(e),p=e[Math.ceil(i.length/2)],o=p+2*t,r=p-2*t;n<20&&(r=0);for(var g=[],s=0;sr&&g.push(a)}return g}return i},TypingDNA.math.fnv1aHash=function(i){if(void 0===i||"string"!=typeof i)return 0;var e,n,t=1914395348;for(e=0,n=(i=i.toLowerCase()).length;e>>0},TypingDNA.history={},TypingDNA.history.stack=[],TypingDNA.history.stackDiagram=[],TypingDNA.history.add=function(i){this.stack.push(i),this.stack.length>TypingDNA.maxHistoryLength&&this.stack.shift()},TypingDNA.history.addDiagram=function(i){this.stackDiagram.push(i)},TypingDNA.history.getDiagram=function(i,e,n,t,p){p=void 0!==p?p:void 0===e||""===e;var o=[],r=[],g=[],s=[],a=[],y=Boolean(TypingDNA.isMobile()),T=!0===i?1:0,D=this.stackDiagram,A={};if(void 0!==t&&""!==t&&D.length>0)D=TypingDNA.sliceStackByTargetId(D,t),void 0!==e&&""!==e||null!=(A=TypingDNA.document.getElementById(t))&&(e=A.value);else{var N=TypingDNA.targetIds.length;if(void 0===e||""===e)if(N>0){e="";for(var d=0;d0&&"string"==typeof e){var M,b,x=e.toLowerCase(),L=e.toUpperCase(),E=[],C=0;for(d=0;d1&&E[j-1]===E[j-2]+1&&(C=K+1,E=[])}v=l[0],k=l[1],w=l[2],i?o.push([f,k,w,v]):o.push([k,w]),!0===y&&void 0!==l[6]&&l[6].length>0&&TypingDNA.hasDeviceMotion&&TypingDNA.hasDeviceOrientation&&(!0===TypingDNA.motionFixedData&&r.push(l[6]),!0===TypingDNA.motionArrayData&&(g.push(l[7]),s.push(l[8]),a.push(l[9])));break}if(!1===R)if(0!==I)I=0,P=C;else if(R=!0,TypingDNA.replaceMissingKeys){if(u++,"object"!=typeof TypingDNA.savedMissingAvgValues||TypingDNA.savedMissingAvgValues.historyLength!==c){var O=TypingDNA.math.fo(TypingDNA.history.get(0,"seek")),z=TypingDNA.math.fo(TypingDNA.history.get(0,"press"));k=Math.round(TypingDNA.math.avg(O)),w=Math.round(TypingDNA.math.avg(z)),TypingDNA.savedMissingAvgValues={seekTime:k,pressTime:w,historyLength:c}}else k=TypingDNA.savedMissingAvgValues.seekTime,w=TypingDNA.savedMissingAvgValues.pressTime;i?o.push([S,k,w,S,1]):o.push([k,w,1]),!0===y&&(!0===TypingDNA.motionFixedData&&r.push(""),!0===TypingDNA.motionArrayData&&(g.push(""),s.push(""),a.push("")));break}}if(TypingDNA.replaceMissingKeysPerc<100*u/m)return null}}else{var F=0;for("number"==typeof e&&(F=c-e),F<0&&(F=0),d=F;d0&&(!0===TypingDNA.motionFixedData&&r.push(l[6]),!0===TypingDNA.motionArrayData&&(g.push(l[7]),s.push(l[8]),a.push(l[9])))}var H=o.join("|");return!0===y&&(!0===TypingDNA.motionFixedData&&(H+="#"+r.join("|")),!0===TypingDNA.motionArrayData&&(H+="#"+g.join("|"),H+="/"+s.join("|"),H+="/"+a.join("|"))),H},TypingDNA.sliceStackByTargetId=function(i,n){var t=i.length,p=[];for(e=0;e0&&(p=TypingDNA.sliceStackByTargetId(p,t));var o=p.length;0!==e&&void 0!==e||(e=TypingDNA.defaultHistoryLength),e>o&&(e=o);var r=0,g=0;switch(n){case"seek":for(var s=[],a=1;a<=e;a++)(r=p[o-a][1])<=TypingDNA.maxSeekTime&&s.push(r);return s;case"press":var y=[];for(a=1;a<=e;a++)(g=p[o-a][2])<=TypingDNA.maxPressTime&&y.push(g);return y;default:var T={};for(a=0;a1?(i.push(Math.round(TypingDNA.math.avg(p))),i.push(Math.round(TypingDNA.math.sd(p)))):1===s?i.push([p[0],-1]):i.push([-1,-1])}var a=TypingDNA.clickTimes.length;i.push(a),a>1?(i.push(Math.round(TypingDNA.math.avg(TypingDNA.clickTimes))),i.push(Math.round(TypingDNA.math.sd(TypingDNA.clickTimes)))):1===a?i.push(TypingDNA.clickTimes[0],-1):i.push([-1,-1]);var y=TypingDNA.stopTimes.length;return i.push(y),y>1?(i.push(Math.round(TypingDNA.math.avg(TypingDNA.stopTimes))),i.push(Math.round(TypingDNA.math.sd(TypingDNA.stopTimes)))):1===y?i.push(TypingDNA.stopTimes[0],-1):i.push([-1,-1]),i},TypingDNA.getOSBrowserMobile=function(){var i=TypingDNA.ua,e=TypingDNA.platform,n=screen.height>=screen.width,t=0,p=0,o=0,r=0,g=1;/MSIE/.test(i)?(o=4,/IEMobile/.test(i)&&(g=2),/MSIE \d+[.]\d+/.test(i)&&(r=/MSIE \d+[.]\d+/.exec(i)[0].split(" ")[1].split(".")[0])):/Edge/.test(i)?(o=6,/Edge\/[\d\.]+/.test(i)&&(r=/Edge\/[\d\.]+/.exec(i)[0].split("/")[1].split(".")[0])):/Chrome/.test(i)?(/CrOS/.test(i)&&(e="CrOS"),o=1,/Chrome\/[\d\.]+/.test(i)&&(r=/Chrome\/[\d\.]+/.exec(i)[0].split("/")[1].split(".")[0])):/Opera/.test(i)?(o=3,(/mini/.test(i)||/Mobile/.test(i))&&(g=2)):/Android/.test(i)?(o=7,g=2,t=6):/Firefox/.test(i)?(o=2,/Fennec/.test(i)&&(g=2),/Firefox\/[\.\d]+/.test(i)&&(r=/Firefox\/[\.\d]+/.exec(i)[0].split("/")[1].split(".")[0])):/Safari/.test(i)&&(o=5,(/iPhone/.test(i)||/iPad/.test(i)||/iPod/.test(i))&&(t=5,g=/iPad/.test(i)?3:2)),r||(/Version\/[\.\d]+/.test(i)&&(r=/Version\/[\.\d]+/.exec(i)),r?r=r[0].split("/")[1].split(".")[0]:/Opera\/[\.\d]+/.test(i)&&(r=/Opera\/[\.\d]+/.exec(i)[0].split("/")[1].split(".")[0])),"MacIntel"===e||"MacPPC"===e?(t=2,/10[\.\_\d]+/.test(i)&&(p=/10[\.\_\d]+/.exec(i)[0].split(".",2).join("")),/[\_]/.test(p)&&(p=p.split("_").slice(0,2).join(""))):"CrOS"===e?t=4:"Win32"===e||"Win64"===e?t=1:!t&&/Android/.test(i)?t=6:!t&&/Linux/.test(e)?t=3:!t&&/Windows/.test(i)&&(t=1),3===g&&2!==g||1!==TypingDNA.isMobile()||(/(ipad|tablet|(android(?!.*mobile))|(windows(?!.*phone)(.*touch))|kindle|playbook|silk|(puffin(?!.*(IP|AP|WP))))/i.test(i)?g=3:6!==t&&0!==t||!(n&&screen.height>767&&screen.width>480||!n&&screen.width>767&&screen.height>480)?1===t?window.navigator.msPointerEnabled&&navigator.msMaxTouchPoints>0&&(g=3):g=2:g=3);var s=Number(void 0!==window.orientation||-1!==i.indexOf("IEMobile"))+1,a=Number("ontouchstart"in window||navigator.maxTouchPoints||window.DocumentTouch&&document instanceof DocumentTouch||!1)+1;return[Number(t),Number(p),Number(o),Number(r),Number(g),s,Number(n),a]},TypingDNA.getDeviceSignature=function(){var i=TypingDNA.getOSBrowserMobile(),e=i[4],n=0,t=0,p=i[5],o=i[0],r=1,g=TypingDNA.math.fnv1aHash(navigator.language),s=i[7],a=TypingDNA.getPressType(),y=0,T=0,D=0,A=i[2],N=screen.width||0,d=screen.height||0,u=i[6]?1:2,c=i[1],m=i[3],h=TypingDNA.cookieId,l=TypingDNA.math.fnv1aHash([e,n,t,p,o,r,g,s,a,y,T,D,A,N,d,u,c,m,h].join("-"));return[e,n,t,p,o,r,g,s,a,y,T,D,A,N,d,u,c,m,h,l]},TypingDNA.getPressType=function(){return 0===TypingDNA.isMobile()?(TypingDNA.pressRecorded,1):!0===TypingDNA.pressCalculated?!0===TypingDNA.pressRecorded?3:2:!0===TypingDNA.pressRecorded?1:0},TypingDNA.getQuality=function(i){for(var e=i.split(","),n=0;n0),t+=Number(r[n]>4),o+=Number(r[n]>g);var a=Math.sqrt(p*t*o)/75;return a>1?1:a},TypingDNA.checkMobileValidity=function(i){var e=i.split(","),n=e[0];if(0===n)return 0;for(var t=0,p=e.slice(11,55),o=p.length,r=0;r0?Number(i.substring(0,e).split(",")[4])||0:Number(i.split(",")[0])||0}return 0},TypingDNA.getTextId=function(i){return TypingDNA.math.fnv1aHash(i)}} -------------------------------------------------------------------------------- /versions/typingdna_2.1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TypingDNA - Typing Biometrics JavaScript API v2.1 3 | * http://api.typingdna.com/scripts/typingdna.js 4 | * http://typingdna.com/scripts/typingdna.js (alternative) 5 | * 6 | * @version 2.1 7 | * @author Raul Popa 8 | * @copyright SC TypingDNA SRL, http://typingdna.com 9 | * @license http://www.apache.org/licenses/LICENSE-2.0 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | * Typical usage: 22 | * var tdna = new TypingDNA(); // creates a new TypingDNA object and starts recording 23 | * var typingPattern = tdna.get(); // returns a typing pattern (and continues recording afterwards), 24 | * optionally you can pass a length, tdna.get(200) will return the pattern based on the last 200 key events. 25 | * 26 | * Optional: 27 | * tdna.stop(); // ends recording and clears history stack (returns recording flag: false) 28 | * tdna.start(); // restarts the recording after a stop (returns recording flag: true) 29 | * tdna.reset(); // restarts the recording anytime, clears history stack and starts from scratch (returns nothing) 30 | * var typingPatternQuality = TypingDNA.getQuality(typingPattern); //returns the quality/strength of any typing pattern 31 | * (there is no need to initialize the class to do pattern quality checking) 32 | */ 33 | 34 | /** 35 | * Creates a single instance (or a reference) of the TypingDNA class 36 | * @param {Number} maxHistoryLength Optional: The maximum length of the 37 | * history stack, default:2000; optimal lengths needed for good quality 38 | * typing patterns are between 500 and 5000, you can also pass 0 for default. 39 | * @return {Object} Returns the single instance of the TypingDNA class. 40 | * @example var tdna = new TypingDNA(); 41 | * @example var tdna = new TypingDNA(0); 42 | * @example var tdna = new TypingDNA(300); 43 | */ 44 | function TypingDNA(maxHistoryLength) { 45 | if (TypingDNA.initialized != true) { 46 | TypingDNA.prototype.start = function() { 47 | return TypingDNA.start.apply(this, arguments); 48 | } 49 | TypingDNA.prototype.stop = function() { 50 | return TypingDNA.stop.apply(this, arguments); 51 | } 52 | TypingDNA.prototype.reset = function() { 53 | return TypingDNA.reset.apply(this, arguments); 54 | } 55 | TypingDNA.prototype.get = function(args) { 56 | return TypingDNA.get.apply(this, arguments); 57 | } 58 | TypingDNA.prototype.getQuality = function(args) { 59 | return TypingDNA.getQuality.apply(this, arguments); 60 | } 61 | TypingDNA.prototype.getLength = function(args) { 62 | return TypingDNA.getLength.apply(this, arguments); 63 | } 64 | TypingDNA.prototype.isMobile = function(args) { 65 | return TypingDNA.isMobile.apply(this, arguments); 66 | } 67 | TypingDNA.initialized = true; 68 | TypingDNA.prototype.maxHistoryLength = TypingDNA.maxHistoryLength; 69 | TypingDNA.prototype.defaultHistoryLength = TypingDNA.defaultHistoryLength; 70 | TypingDNA.prototype.maxSeekTime = TypingDNA.maxSeekTime; 71 | TypingDNA.prototype.maxPressTime = TypingDNA.maxPressTime; 72 | TypingDNA.instance = this; 73 | TypingDNA.element = document; 74 | TypingDNA.maxHistoryLength = 2000; 75 | TypingDNA.maxSeekTime = 1500; 76 | TypingDNA.maxPressTime = 300; 77 | TypingDNA.defaultHistoryLength = 500; 78 | TypingDNA.keyCodes = [65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,32,222,188,190,186,187,189,191,48,49,50,51,52,53,54,55,56,57]; 79 | TypingDNA.keyCodesObj = {65:1,66:1,67:1,68:1,69:1,70:1,71:1,72:1,73:1,74:1,75:1,76:1,77:1,78:1,79:1,80:1,81:1,82:1,83:1,84:1,85:1,86:1,87:1,88:1,89:1,90:1,32:1,222:1,188:1,190:1,186:1,187:1,189:1,191:1,48:1,49:1,50:1,51:1,52:1,53:1,54:1,55:1,56:1,57:1} 80 | TypingDNA.pt1 = TypingDNA.ut1 = (new Date).getTime(); 81 | TypingDNA.wfk = []; 82 | TypingDNA.sti = []; 83 | TypingDNA.skt = []; 84 | TypingDNA.rkc = []; 85 | TypingDNA.lastDownKey; 86 | TypingDNA.prevKeyCode = 0; 87 | TypingDNA.zl = 0.0000001; 88 | 89 | TypingDNA.keydown = function(e) { 90 | var t0 = TypingDNA.pt1; 91 | if (!e.shiftKey || TypingDNA.isMobile()) { 92 | TypingDNA.pt1 = (new Date).getTime(); 93 | var keyCode = e.keyCode; 94 | var seekTotal = TypingDNA.pt1 - t0; 95 | var startTime = TypingDNA.pt1; 96 | TypingDNA.wfk[keyCode] = 1; 97 | TypingDNA.skt[keyCode] = seekTotal; 98 | TypingDNA.sti[keyCode] = startTime; 99 | } 100 | } 101 | TypingDNA.keyup = function(e) { 102 | var ut = (new Date).getTime(); 103 | if (!e.shiftKey || TypingDNA.isMobile()) { 104 | var keyCode = e.keyCode; 105 | if (TypingDNA.wfk[keyCode] == 1) { 106 | var pressTime = ut - TypingDNA.sti[keyCode]; 107 | var seekTime = TypingDNA.skt[keyCode]; 108 | var arr = [keyCode, seekTime, pressTime, TypingDNA.prevKeyCode, ut]; 109 | TypingDNA.history.add(arr); 110 | TypingDNA.prevKeyCode = keyCode; 111 | } 112 | } 113 | TypingDNA.wfk[keyCode] = 0; 114 | } 115 | if (TypingDNA.element.addEventListener) { 116 | TypingDNA.element.addEventListener("keydown", TypingDNA.keydown); 117 | TypingDNA.element.addEventListener("keyup", TypingDNA.keyup); 118 | } else if (TypingDNA.element.attachEvent) { 119 | TypingDNA.element.attachEvent("onkeydown", TypingDNA.keydown); 120 | TypingDNA.element.attachEvent("onkeyup", TypingDNA.keyup); 121 | } else { 122 | console.log("browser not supported"); 123 | } 124 | 125 | /** 126 | * Resets the history stack 127 | */ 128 | TypingDNA.reset = function() { 129 | TypingDNA.history.stack = []; 130 | } 131 | 132 | /** 133 | * Automatically called at initilization. It starts the recording of keystrokes. 134 | */ 135 | TypingDNA.start = function() { 136 | if (TypingDNA.recording == true) { 137 | TypingDNA.stop(); 138 | } 139 | TypingDNA.reset(); 140 | return TypingDNA.recording = true; 141 | } 142 | 143 | /** 144 | * Ends the recording of further keystrokes. To restart recording afterwards you can 145 | * either call TypingDNA.start() or create a new TypingDNA object again, not recommended. 146 | */ 147 | TypingDNA.stop = function() { 148 | TypingDNA.reset(); 149 | return TypingDNA.recording = false; 150 | } 151 | 152 | /** 153 | * This function outputs the typing pattern as a String, in a new basic structure for 154 | * easy storage and usage in any kind of keystroke dynamics applications (e.g. typing 155 | * pattern matching, user recognition) 156 | * @param {Number} length Optional: The amount of history keystrokes to use for the 157 | * typing pattern. By default it will use the last 500 recorded keystrokes (or as many 158 | * available if less than 500). 159 | * @return {String} The TypingDNA typing pattern, comma separated. 160 | * A fixed vector of only numeric values separated by commas. 161 | * @example var typingPattern = tdna.get(); 162 | * @example var typingPattern = tdna.get(200); 163 | */ 164 | TypingDNA.get = function(length) { 165 | var historyTotalLength = TypingDNA.history.stack.length; 166 | if (length == undefined) { 167 | length = TypingDNA.defaultHistoryLength; 168 | } 169 | if (length > historyTotalLength) { 170 | length = historyTotalLength; 171 | } 172 | var obj = {}; 173 | obj.arr = TypingDNA.history.get(length); 174 | var zl = TypingDNA.zl; 175 | var histRev = length; 176 | var histSktF = TypingDNA.math.fo(TypingDNA.history.get(length, "seek")); 177 | var histPrtF = TypingDNA.math.fo(TypingDNA.history.get(length, "press")); 178 | var histRevPF = histPrtF.length; 179 | var histRevSF = histSktF.length; 180 | var pressHistMean = Math.round(TypingDNA.math.avg(histPrtF)); 181 | var seekHistMean = Math.round(TypingDNA.math.avg(histSktF)); 182 | var pressHistSD = Math.round(TypingDNA.math.sd(histPrtF)); 183 | var seekHistSD = Math.round(TypingDNA.math.sd(histSktF)); 184 | var charMeanTime = seekHistMean + pressHistMean; 185 | var pressRatio = TypingDNA.math.rd((pressHistMean + zl) / (charMeanTime + zl), 4); 186 | var seekToPressRatio = TypingDNA.math.rd((1 - pressRatio)/pressRatio, 4); 187 | var pressSDToPressRatio = TypingDNA.math.rd((pressHistSD + zl) / (pressHistMean + zl), 4); 188 | var seekSDToPressRatio = TypingDNA.math.rd((seekHistSD + zl) / (pressHistMean + zl), 4); 189 | var cpm = Math.round(6E4 / (charMeanTime + zl)); 190 | for (var i in obj.arr) { 191 | var rev = obj.arr[i][1].length; 192 | var seekMean = 0; 193 | var pressMean = 0; 194 | var postMean = 0; 195 | var seekSD = 0; 196 | var pressSD = 0; 197 | var postSD = 0; 198 | switch (obj.arr[i][0].length) { 199 | case 0: 200 | break; 201 | case 1: 202 | var seekMean = TypingDNA.math.rd((obj.arr[i][0][0] + zl) / (seekHistMean + zl), 4); 203 | break; 204 | default: 205 | var arr = TypingDNA.math.fo(obj.arr[i][0]); 206 | seekMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (seekHistMean + zl), 4); 207 | seekSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (seekHistSD + zl), 4); 208 | } 209 | switch (obj.arr[i][1].length) { 210 | case 0: 211 | break; 212 | case 1: 213 | var pressMean = TypingDNA.math.rd((obj.arr[i][1][0] + zl) / (pressHistMean + zl), 4); 214 | break; 215 | default: 216 | var arr = TypingDNA.math.fo(obj.arr[i][1]); 217 | pressMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (pressHistMean + zl), 4); 218 | pressSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (pressHistSD + zl), 4); 219 | } 220 | switch (obj.arr[i][2].length) { 221 | case 0: 222 | break; 223 | case 1: 224 | var postMean = TypingDNA.math.rd((obj.arr[i][2][0] + zl) / (seekHistMean + zl), 4); 225 | break; 226 | default: 227 | var arr = TypingDNA.math.fo(obj.arr[i][2]); 228 | postMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (seekHistMean + zl), 4); 229 | postSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (seekHistSD + zl), 4); 230 | } 231 | delete obj.arr[i][2]; 232 | delete obj.arr[i][1]; 233 | delete obj.arr[i][0]; 234 | obj.arr[i][0] = rev; 235 | obj.arr[i][1] = seekMean; 236 | obj.arr[i][2] = pressMean; 237 | obj.arr[i][3] = postMean; 238 | obj.arr[i][4] = seekSD; 239 | obj.arr[i][5] = pressSD; 240 | obj.arr[i][6] = postSD; 241 | } 242 | var arr = []; 243 | TypingDNA.apu(arr, histRev); 244 | TypingDNA.apu(arr, cpm); 245 | TypingDNA.apu(arr, charMeanTime); 246 | TypingDNA.apu(arr, pressRatio); 247 | TypingDNA.apu(arr, seekToPressRatio); 248 | TypingDNA.apu(arr, pressSDToPressRatio); 249 | TypingDNA.apu(arr, seekSDToPressRatio); 250 | TypingDNA.apu(arr, pressHistMean); 251 | TypingDNA.apu(arr, seekHistMean); 252 | TypingDNA.apu(arr, pressHistSD); 253 | TypingDNA.apu(arr, seekHistSD); 254 | for (var c = 0; c <= 6; c++) { 255 | for (var i = 0; i < 44; i++) { 256 | var keyCode = TypingDNA.keyCodes[i]; 257 | var val = obj.arr[keyCode][c]; 258 | if (val == 0 && c > 0) { 259 | val = 1; 260 | } 261 | TypingDNA.apu(arr, val); 262 | } 263 | } 264 | TypingDNA.apu(arr, TypingDNA.isMobile()); 265 | return arr.join(","); 266 | } 267 | TypingDNA.apu = function(arr, val) { 268 | "NaN" == String(val) && (val = 0); 269 | arr.push(val); 270 | } 271 | TypingDNA.math = {}; 272 | TypingDNA.math.rd = function(val, dec) { 273 | return Number(val.toFixed(dec)); 274 | } 275 | TypingDNA.math.avg = function(arr) { 276 | var len = arr.length; 277 | var sum = 0; 278 | for (var i = 0; i < len; i++) { 279 | sum += arr[i]; 280 | } 281 | return this.rd(sum / len, 4); 282 | } 283 | TypingDNA.math.sd = function(arr) { 284 | var len = arr.length; 285 | if (len < 2) { 286 | return 0; 287 | } else { 288 | var sumVS = 0; 289 | var mean = this.avg(arr); 290 | for (var i = 0; i < len; i++) { 291 | sumVS += (arr[i] - mean) * (arr[i] - mean); 292 | } 293 | var sd = Math.sqrt(sumVS / len); 294 | return sd; 295 | } 296 | } 297 | TypingDNA.math.fo = function(arr) { 298 | var values = arr.concat(); 299 | var len = arr.length; 300 | values.sort(function(a, b) { 301 | return a - b; 302 | }); 303 | var asd = this.sd(values); 304 | var aMean = values[Math.ceil(arr.length / 2)]; 305 | var multiplier = 2; 306 | var maxVal = aMean + multiplier * asd; 307 | var minVal = aMean - multiplier * asd; 308 | if (len < 20) { 309 | minVal = 0; 310 | } 311 | var fVal = []; 312 | for (var i = 0; i < len; i++) { 313 | var tempval = values[i]; 314 | if (tempval < maxVal && tempval > minVal) { 315 | fVal.push(tempval); 316 | } 317 | } 318 | return fVal; 319 | } 320 | TypingDNA.history = {}; 321 | TypingDNA.history.stack = []; 322 | TypingDNA.history.add = function(arr) { 323 | this.stack.push(arr); 324 | if (this.stack.length > TypingDNA.maxHistoryLength) { 325 | this.stack.shift(); 326 | } 327 | } 328 | TypingDNA.history.get = function(length, type) { 329 | var historyTotalLength = this.stack.length; 330 | if (length == 0 || length == undefined) { 331 | length = TypingDNA.defaultHistoryLength; 332 | } 333 | if (length > historyTotalLength) { 334 | length = historyTotalLength; 335 | } 336 | switch (type) { 337 | case "seek": 338 | var seekArr = []; 339 | for (i = 1; i <= length; i++) { 340 | var seekTime = this.stack[historyTotalLength - i][1]; 341 | if (seekTime <= TypingDNA.maxSeekTime) { 342 | seekArr.push(seekTime); 343 | } 344 | }; 345 | return seekArr; 346 | break; 347 | case "press": 348 | var pressArr = []; 349 | for (i = 1; i <= length; i++) { 350 | var pressTime = this.stack[historyTotalLength - i][2]; 351 | if (pressTime <= TypingDNA.maxPressTime) { 352 | pressArr.push(pressTime); 353 | } 354 | }; 355 | return pressArr; 356 | break; 357 | default: 358 | var historyStackObj = {}; 359 | for (var i in TypingDNA.keyCodes) { 360 | historyStackObj[TypingDNA.keyCodes[i]] = [ 361 | [], 362 | [], 363 | [] 364 | ]; 365 | } 366 | for (i = 1; i <= length; i++) { 367 | var arr = this.stack[historyTotalLength - i]; 368 | var keyCode = arr[0]; 369 | var seekTime = arr[1]; 370 | var pressTime = arr[2]; 371 | var prevKeyCode = arr[3]; 372 | if (TypingDNA.keyCodesObj[keyCode]) { 373 | if (seekTime <= TypingDNA.maxSeekTime) { 374 | historyStackObj[keyCode][0].push(seekTime); 375 | if (prevKeyCode != 0 && TypingDNA.keyCodesObj[prevKeyCode]) { 376 | historyStackObj[prevKeyCode][2].push(seekTime); 377 | } 378 | } 379 | if (pressTime <= TypingDNA.maxPressTime) { 380 | historyStackObj[keyCode][1].push(pressTime); 381 | } 382 | } 383 | }; 384 | return historyStackObj; 385 | } 386 | } 387 | 388 | /** 389 | * Checks the quality of a typing pattern, how well it is revelated, how useful the 390 | * information will be for matching applications. It returns a value between 0 and 1. 391 | * Values over 0.3 are acceptable, however a value over 0.7 shows good pattern strength. 392 | * @param {String} typingPattern The typing pattern string returned by the get() function. 393 | * @return {Number} A real number between 0 and 1. A close to 1 value means a stronger pattern. 394 | * @example var quality = tdna.getQuality(typingPattern); 395 | */ 396 | TypingDNA.getQuality = function(typingPattern) { 397 | var obj = typingPattern.split(","); 398 | for (i = 0; i < obj.length; i++){ 399 | obj[i] = Number(obj[i]); 400 | } 401 | var totalEvents = obj[0]; 402 | var acc = rec = avgAcc = 0; 403 | var avg = TypingDNA.math.avg(obj); 404 | var revs = obj.slice(11, 55); 405 | for (var i in revs) { 406 | rec += Number(revs[i] > 0); 407 | acc += Number(revs[i] > 4); 408 | avgAcc += Number(revs[i] > avg); 409 | } 410 | var tReturn = Math.sqrt(rec * acc * avgAcc) / 80; 411 | return tReturn > 1 ? 1 : tReturn; 412 | } 413 | 414 | TypingDNA.getLength = function(typingPattern) { 415 | return Number(typingPattern.split(",")[1]); 416 | } 417 | 418 | TypingDNA.isMobile = function() { 419 | var check = 0; 420 | (function(a) { 421 | if ( 422 | /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i 423 | .test(a) || 424 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i 425 | .test(a.substr(0, 4))) { 426 | check = 1 427 | } 428 | })(navigator.userAgent || navigator.vendor || window.opera); 429 | return check; 430 | } 431 | } else { 432 | // TypingDNA is a static class, currently doesn't support actual multiple instances (Singleton implementation) 433 | return TypingDNA.instance; 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /versions/typingdna_2.5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TypingDNA - Typing Biometrics JavaScript API 3 | * http://api.typingdna.com/scripts/typingdna.js 4 | * http://typingdna.com/scripts/typingdna.js (alternative) 5 | * 6 | * @version 2.5 7 | * @author Raul Popa 8 | * @copyright SC TypingDNA SRL, http://typingdna.com 9 | * @license http://www.apache.org/licenses/LICENSE-2.0 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | * Typical usage: 22 | * var tdna = new TypingDNA(); // creates a new TypingDNA object and starts recording 23 | * var typingPattern = tdna.get(); // returns a typing pattern (and continues recording afterwards), 24 | * optionally you can pass a length, tdna.get(200) will return the pattern based on the last 200 key events. 25 | * 26 | * Optional: 27 | * tdna.stop(); // ends recording and clears history stack (returns recording flag: false) 28 | * tdna.start(); // restarts the recording after a stop (returns recording flag: true) 29 | * tdna.reset(); // restarts the recording anytime, clears history stack and starts from scratch (returns nothing) 30 | * var typingPatternQuality = TypingDNA.getQuality(typingPattern); //returns the quality/strength of any typing pattern 31 | * (there is no need to initialize the class to do pattern quality checking) 32 | */ 33 | 34 | /** 35 | * Creates a single instance (or a reference) of the TypingDNA class 36 | * @param {Number} maxHistoryLength Optional: The maximum length of the 37 | * history stack, default:2000; optimal lengths needed for good quality 38 | * typing patterns are between 500 and 5000, you can also pass 0 for default. 39 | * @return {Object} Returns the single instance of the TypingDNA class. 40 | * @example var tdna = new TypingDNA(); 41 | * @example var tdna = new TypingDNA(0); 42 | * @example var tdna = new TypingDNA(300); 43 | */ 44 | function TypingDNA(maxHistoryLength) { 45 | if (TypingDNA.initialized != true) { 46 | TypingDNA.prototype.start = function() { 47 | return TypingDNA.start.apply(this, arguments); 48 | } 49 | TypingDNA.prototype.stop = function() { 50 | return TypingDNA.stop.apply(this, arguments); 51 | } 52 | TypingDNA.prototype.reset = function() { 53 | return TypingDNA.reset.apply(this, arguments); 54 | } 55 | TypingDNA.prototype.addTarget = function() { 56 | return TypingDNA.addTarget.apply(this, arguments); 57 | } 58 | TypingDNA.prototype.removeTarget = function() { 59 | return TypingDNA.removeTarget.apply(this, arguments); 60 | } 61 | TypingDNA.prototype.get = function(args) { 62 | return TypingDNA.get.apply(this, arguments); 63 | } 64 | TypingDNA.prototype.startDiagram = function() { 65 | return TypingDNA.startDiagram.apply(this, arguments); 66 | } 67 | TypingDNA.prototype.stopDiagram = function() { 68 | return TypingDNA.stopDiagram.apply(this, arguments); 69 | } 70 | TypingDNA.prototype.getDiagram = function(args) { 71 | return TypingDNA.getDiagram.apply(this, arguments); 72 | } 73 | TypingDNA.prototype.getExtendedDiagram = function(args) { 74 | return TypingDNA.getExtendedDiagram.apply(this, arguments); 75 | } 76 | TypingDNA.prototype.startMouse = function() { 77 | return TypingDNA.startMouse.apply(this, arguments); 78 | } 79 | TypingDNA.prototype.stopMouse = function() { 80 | return TypingDNA.stopMouse.apply(this, arguments); 81 | } 82 | TypingDNA.prototype.getQuality = function(args) { 83 | return TypingDNA.getQuality.apply(this, arguments); 84 | } 85 | TypingDNA.prototype.getLength = function(args) { 86 | return TypingDNA.getLength.apply(this, arguments); 87 | } 88 | TypingDNA.prototype.isMobile = function(args) { 89 | return TypingDNA.isMobile.apply(this, arguments); 90 | } 91 | TypingDNA.initialized = true; 92 | TypingDNA.prototype.maxHistoryLength = TypingDNA.maxHistoryLength; 93 | TypingDNA.prototype.defaultHistoryLength = TypingDNA.defaultHistoryLength; 94 | TypingDNA.prototype.maxSeekTime = TypingDNA.maxSeekTime; 95 | TypingDNA.prototype.maxPressTime = TypingDNA.maxPressTime; 96 | TypingDNA.version = 2.5; 97 | TypingDNA.flags = 0; 98 | TypingDNA.instance = this; 99 | TypingDNA.element = document; 100 | TypingDNA.maxHistoryLength = 2000; 101 | TypingDNA.maxSeekTime = 1500; 102 | TypingDNA.maxPressTime = 300; 103 | TypingDNA.defaultHistoryLength = 500; 104 | TypingDNA.spKeyCodes = [8,13]; 105 | TypingDNA.spKeyCodesObj = {8:1,13:1}; 106 | TypingDNA.keyCodes = [65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,32,222,188,190,186,187,189,191,48,49,50,51,52,53,54,55,56,57]; 107 | TypingDNA.keyCodesObj = {65:1,66:1,67:1,68:1,69:1,70:1,71:1,72:1,73:1,74:1,75:1,76:1,77:1,78:1,79:1,80:1,81:1,82:1,83:1,84:1,85:1,86:1,87:1,88:1,89:1,90:1,32:1,222:1,188:1,190:1,186:1,187:1,189:1,191:1,48:1,49:1,50:1,51:1,52:1,53:1,54:1,55:1,56:1,57:1} 108 | TypingDNA.pt1 = TypingDNA.ut1 = (new Date).getTime(); 109 | TypingDNA.wfk = []; 110 | TypingDNA.sti = []; 111 | TypingDNA.skt = []; 112 | TypingDNA.recording = true; 113 | TypingDNA.mouseRecording = true; 114 | TypingDNA.spKeyRecording = true; 115 | TypingDNA.diagramRecording = false; 116 | TypingDNA.dwfk = []; 117 | TypingDNA.dsti = []; 118 | TypingDNA.dskt = []; 119 | TypingDNA.drkc = []; 120 | TypingDNA.dlastDownKey; 121 | TypingDNA.prevKeyCode = 0; 122 | TypingDNA.lastMouseMoveTime = TypingDNA.lastMouseDownTime = (new Date).getTime(); 123 | TypingDNA.maxStopTime = 1500; 124 | TypingDNA.maxClickTime = 600; 125 | TypingDNA.stopTimes = []; 126 | TypingDNA.clickTimes = []; 127 | TypingDNA.zl = 0.0000001; 128 | TypingDNA.isAndroidChrome = false; 129 | TypingDNA.AClastTime = (new Date).getTime(); 130 | TypingDNA.ACseekTime = 0; 131 | TypingDNA.ACpressTime = 0; 132 | TypingDNA.AClastCharCode = 0; 133 | TypingDNA.AClastKeyCode = 0; 134 | TypingDNA.ACflagDownTI = 0; 135 | TypingDNA.ACpreviousKeyCode = 0; 136 | TypingDNA.targetIds = []; 137 | TypingDNA.lastTarget = ""; 138 | TypingDNA.lastTargetFound = false; 139 | 140 | TypingDNA.keydown = function(e) { 141 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 142 | return; 143 | } 144 | if (!TypingDNA.isTarget(e.target.id)) { 145 | return; 146 | } 147 | var keyCode = e.keyCode; 148 | if (keyCode == 229 && TypingDNA.isMobile() && !TypingDNA.isAndroidChrome) { 149 | TypingDNA.isAndroidChrome = true; 150 | 151 | TypingDNA.ACseekTime = (new Date).getTime() - TypingDNA.AClastTime; 152 | TypingDNA.AClastTime = (new Date).getTime(); 153 | TypingDNA.ACflagDownTI = 1; 154 | 155 | TypingDNA.element.removeEventListener("keydown", TypingDNA.keydown); 156 | TypingDNA.element.removeEventListener("keyup", TypingDNA.keyup); 157 | TypingDNA.element.removeEventListener("keypress", TypingDNA.keypress); 158 | 159 | // if android 160 | TypingDNA.element.addEventListener("textInput", TypingDNA.ACtextInput); 161 | TypingDNA.element.addEventListener("keydown", TypingDNA.ACkeydown); 162 | TypingDNA.element.addEventListener("keyup", TypingDNA.ACkeyup); 163 | } else { 164 | var t0 = TypingDNA.pt1; 165 | TypingDNA.pt1 = (new Date).getTime(); 166 | var seekTotal = TypingDNA.pt1 - t0; 167 | var startTime = TypingDNA.pt1; 168 | if (TypingDNA.recording == true || (TypingDNA.spKeyCodesObj[keyCode] && TypingDNA.spKeyRecording == true)) { 169 | if (!e.shiftKey || TypingDNA.isMobile()) { 170 | TypingDNA.wfk[keyCode] = 1; 171 | TypingDNA.skt[keyCode] = seekTotal; 172 | TypingDNA.sti[keyCode] = startTime; 173 | } 174 | } 175 | if (TypingDNA.diagramRecording == true) { 176 | TypingDNA.dwfk[keyCode] = 1; 177 | TypingDNA.dskt[keyCode] = seekTotal; 178 | TypingDNA.dsti[keyCode] = startTime; 179 | TypingDNA.dlastDownKey = keyCode; 180 | } 181 | } 182 | } 183 | 184 | TypingDNA.keypress = function(e) { 185 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 186 | return; 187 | } 188 | if (!TypingDNA.isTarget(e.target.id)) { 189 | return; 190 | } 191 | if (TypingDNA.diagramRecording == true) { 192 | var keyCode = TypingDNA.dlastDownKey; 193 | TypingDNA.drkc[keyCode] = e.charCode; 194 | } 195 | } 196 | 197 | TypingDNA.keyup = function(e) { 198 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 199 | return; 200 | } 201 | if (!TypingDNA.isTarget(e.target.id)) { 202 | return; 203 | } 204 | var ut = (new Date).getTime(); 205 | var keyCode = e.keyCode; 206 | if (TypingDNA.recording == true || (TypingDNA.spKeyCodesObj[keyCode] && TypingDNA.spKeyRecording == true)) { 207 | if (!e.shiftKey || TypingDNA.isMobile()) { 208 | if (TypingDNA.wfk[keyCode] == 1) { 209 | var pressTime = ut - TypingDNA.sti[keyCode]; 210 | var seekTime = TypingDNA.skt[keyCode]; 211 | var arr = [keyCode, seekTime, pressTime, TypingDNA.prevKeyCode, ut]; 212 | TypingDNA.history.add(arr); 213 | TypingDNA.prevKeyCode = keyCode; 214 | } 215 | } 216 | TypingDNA.wfk[keyCode] = 0; 217 | } 218 | if (TypingDNA.diagramRecording == true) { 219 | if (TypingDNA.drkc[keyCode] != undefined && TypingDNA.drkc[keyCode] != 0) { 220 | if (TypingDNA.dwfk[keyCode] == 1) { 221 | var pressTime = ut - TypingDNA.dsti[keyCode]; 222 | var seekTime = TypingDNA.dskt[keyCode]; 223 | var realKeyCode = TypingDNA.drkc[keyCode]; 224 | var arrD = [keyCode, seekTime, pressTime, realKeyCode]; 225 | TypingDNA.history.addDiagram(arrD); 226 | } 227 | } 228 | TypingDNA.dwfk[keyCode] = 0; 229 | } 230 | } 231 | 232 | TypingDNA.mousemove = function(e) { 233 | if (TypingDNA.mouseRecording == true) { 234 | TypingDNA.lastMouseMoveTime = (new Date).getTime(); 235 | } 236 | } 237 | 238 | TypingDNA.mousedown = function(e) { 239 | if (TypingDNA.mouseRecording == true) { 240 | if (e.which == 1) { 241 | TypingDNA.lastMouseDownTime = (new Date).getTime(); 242 | var stopTime = TypingDNA.lastMouseDownTime - TypingDNA.lastMouseMoveTime; 243 | if (stopTime < TypingDNA.maxStopTime) { 244 | TypingDNA.stopTimes.push(stopTime); 245 | } 246 | } 247 | } 248 | } 249 | 250 | TypingDNA.mouseup = function(e) { 251 | if (TypingDNA.mouseRecording == true) { 252 | if (e.which == 1) { 253 | var clickTime = (new Date).getTime() - TypingDNA.lastMouseDownTime; 254 | if (clickTime < TypingDNA.maxClickTime && TypingDNA.lastMouseDownTime > TypingDNA.lastMouseMoveTime) { 255 | TypingDNA.clickTimes.push(clickTime); 256 | } 257 | } 258 | } 259 | } 260 | 261 | TypingDNA.ACkeydown = function(e) { 262 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 263 | return; 264 | } 265 | TypingDNA.ACseekTime = (new Date).getTime() - TypingDNA.AClastTime; 266 | TypingDNA.AClastTime = (new Date).getTime(); 267 | if (!TypingDNA.isTarget(e.target.id)) { 268 | return; 269 | } 270 | if (e.keyCode == 229) { 271 | TypingDNA.ACflagDownTI = 1; 272 | } 273 | } 274 | 275 | TypingDNA.ACtextInput = function(e) { 276 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 277 | return; 278 | } 279 | if (TypingDNA.ACflagDownTI = 1) { 280 | TypingDNA.AClastCharCode = e.data.charCodeAt(0); 281 | TypingDNA.AClastKeyCode = e.data.toUpperCase().charCodeAt(0); 282 | TypingDNA.ACflagDownTI = 0; 283 | } 284 | } 285 | 286 | TypingDNA.ACkeyup = function(e) { 287 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 288 | return; 289 | } 290 | TypingDNA.ACpressTime = (new Date).getTime() - TypingDNA.AClastTime; 291 | TypingDNA.AClastTime = (new Date).getTime(); 292 | if (!TypingDNA.isTarget(e.target.id)) { 293 | return; 294 | } 295 | var keyCode = e.keyCode; 296 | if (keyCode == 229 && TypingDNA.ACflagDownTI == 0) { 297 | keyCode = TypingDNA.AClastKeyCode; 298 | } 299 | var arr = [keyCode, TypingDNA.ACseekTime, TypingDNA.ACpressTime, TypingDNA.ACpreviousKeyCode, TypingDNA.AClastTime]; 300 | TypingDNA.history.add(arr); 301 | TypingDNA.ACpreviousKeyCode = keyCode; 302 | if (TypingDNA.diagramRecording == true && TypingDNA.ACflagDownTI == 0) { 303 | var arrD = [keyCode, TypingDNA.ACseekTime, TypingDNA.ACpressTime, TypingDNA.AClastCharCode]; 304 | TypingDNA.history.addDiagram(arrD); 305 | } 306 | } 307 | 308 | TypingDNA.isTarget = function(target) { 309 | if (TypingDNA.lastTarget == target && TypingDNA.lastTargetFound) { 310 | return true; 311 | } else { 312 | var targetLength = TypingDNA.targetIds.length; 313 | var targetFound = false; 314 | if (targetLength > 0) { 315 | for (var i = 0; i < targetLength; i++) { 316 | if (TypingDNA.targetIds[i] == target) { 317 | targetFound = true; 318 | break; 319 | } 320 | } 321 | TypingDNA.lastTarget = target; 322 | TypingDNA.lastTargetFound = targetFound; 323 | return targetFound; 324 | } else { 325 | TypingDNA.lastTarget = target; 326 | TypingDNA.lastTargetFound = true; 327 | return true; 328 | } 329 | } 330 | } 331 | 332 | if (TypingDNA.element.addEventListener) { 333 | TypingDNA.element.addEventListener("keydown", TypingDNA.keydown); 334 | TypingDNA.element.addEventListener("keyup", TypingDNA.keyup); 335 | TypingDNA.element.addEventListener("keypress", TypingDNA.keypress); 336 | TypingDNA.element.addEventListener("mousemove", TypingDNA.mousemove); 337 | TypingDNA.element.addEventListener("mousedown", TypingDNA.mousedown); 338 | TypingDNA.element.addEventListener("mouseup", TypingDNA.mouseup); 339 | } else if (TypingDNA.element.attachEvent) { 340 | TypingDNA.element.attachEvent("onkeydown", TypingDNA.keydown); 341 | TypingDNA.element.attachEvent("onkeyup", TypingDNA.keyup); 342 | TypingDNA.element.attachEvent("onkeypress", TypingDNA.keypress); 343 | TypingDNA.element.attachEvent("onmousemove", TypingDNA.mousemove); 344 | TypingDNA.element.attachEvent("onmousedown", TypingDNA.mousedown); 345 | TypingDNA.element.attachEvent("onmouseup", TypingDNA.mouseup); 346 | } else { 347 | console.log("browser not supported"); 348 | } 349 | 350 | /** 351 | * Adds a target to the targetIds array. 352 | */ 353 | TypingDNA.addTarget = function(target) { 354 | var targetLength = TypingDNA.targetIds.length; 355 | var targetFound = false; 356 | if (targetLength > 0) { 357 | for (var i = 0; i < targetLength; i++) { 358 | if (TypingDNA.targetIds[i] == target) { 359 | targetFound = true; 360 | break; 361 | } 362 | } 363 | if (!targetFound) { 364 | TypingDNA.targetIds.push(target); 365 | } 366 | } else { 367 | TypingDNA.targetIds.push(target); 368 | } 369 | } 370 | 371 | /** 372 | * Adds a target to the targetIds array. 373 | */ 374 | TypingDNA.removeTarget = function(target) { 375 | var targetLength = TypingDNA.targetIds.length; 376 | if (targetLength > 0) { 377 | for (var i = 0; i < targetLength; i++) { 378 | if (TypingDNA.targetIds[i] == target) { 379 | TypingDNA.targetIds.splice(i, 1); 380 | break; 381 | } 382 | } 383 | } 384 | } 385 | 386 | /** 387 | * Resets the history stack 388 | */ 389 | TypingDNA.reset = function() { 390 | TypingDNA.history.stack = []; 391 | TypingDNA.history.stackDiagram = []; 392 | TypingDNA.clickTimes = []; 393 | TypingDNA.stopTimes = []; 394 | } 395 | 396 | /** 397 | * Automatically called at initilization. It starts the recording of keystrokes. 398 | */ 399 | TypingDNA.start = function() { 400 | return TypingDNA.recording = true; 401 | } 402 | 403 | /** 404 | * Ends the recording of further keystrokes. To restart recording afterwards you can 405 | * either call TypingDNA.start() or create a new TypingDNA object again, not recommended. 406 | */ 407 | TypingDNA.stop = function() { 408 | return TypingDNA.recording = false; 409 | } 410 | 411 | /** 412 | * Starts the recording of mouse activity. 413 | */ 414 | TypingDNA.startMouse = function() { 415 | return TypingDNA.mouseRecording = true; 416 | } 417 | 418 | /** 419 | * Stops the recording of mouse activity. 420 | */ 421 | TypingDNA.stopMouse = function() { 422 | return TypingDNA.mouseRecording = false; 423 | } 424 | 425 | /** 426 | * Starts the recording of a non-fixed diagram typing pattern. 427 | */ 428 | TypingDNA.startDiagram = function() { 429 | return TypingDNA.diagramRecording = true; 430 | } 431 | 432 | /** 433 | * Ends the recording of a non-fixed diagram typing pattern. 434 | */ 435 | TypingDNA.stopDiagram = function() { 436 | return TypingDNA.diagramRecording = false; 437 | } 438 | 439 | /** 440 | * This function outputs the linear diagram typing pattern as a String 441 | * @param {String} str Optional: The string represented by the diagram 442 | * The function checks for the exact string (with minor typos) in the recorded 443 | * history. Any letters that are not included in the string will be ommited from 444 | * the output diagram typing pattern. 445 | * @return {String} The TypingDNA linear diagram typing pattern, comma separated. 446 | * A non-fixed vector of only numeric values separated by commas. 447 | * @example var typingPattern = tdna.getDiagram(); 448 | * @example var typingPattern = tdna.getDiagram("Hello5g21?*"); 449 | */ 450 | TypingDNA.getDiagram = function(str) { 451 | return TypingDNA.history.getDiagram(false, str); 452 | } 453 | 454 | /** 455 | * This function outputs the extended linear diagram typing pattern as a String 456 | * Compared to getDiagram, it includes char codes (not safe for storage) 457 | * @param {String} str Optional: The string represented by the diagram 458 | * The function checks for the exact string (with minor typos) in the recorded 459 | * history. Any letters that are not included in the string will be ommited from 460 | * the output diagram typing pattern. 461 | * @return {String} The TypingDNA linear diagram typing pattern, comma separated. 462 | * A non-fixed vector of only numeric values separated by commas. 463 | * @example var typingPattern = tdna.getExtendedDiagram(); 464 | * @example var typingPattern = tdna.getExtendedDiagram("Hello5g21?*"); 465 | */ 466 | TypingDNA.getExtendedDiagram = function(str) { 467 | return TypingDNA.history.getDiagram(true, str); 468 | } 469 | 470 | /** 471 | * This function outputs the typing pattern as a String, in a new basic structure for 472 | * easy storage and usage in any kind of keystroke dynamics applications (e.g. typing 473 | * pattern matching, user recognition) 474 | * @param {Number} length Optional: The amount of history keystrokes to use for the 475 | * typing pattern. By default it will use the last 500 recorded keystrokes (or as many 476 | * available if less than 500). 477 | * @return {String} The TypingDNA typing pattern, comma separated. 478 | * A fixed vector of only numeric values separated by commas. 479 | * @example var typingPattern = tdna.get(); 480 | * @example var typingPattern = tdna.get(200); 481 | */ 482 | TypingDNA.get = function(length) { 483 | var historyTotalLength = TypingDNA.history.stack.length; 484 | if (length == undefined) { 485 | length = TypingDNA.defaultHistoryLength; 486 | } 487 | if (length > historyTotalLength) { 488 | length = historyTotalLength; 489 | } 490 | var obj = {}; 491 | obj.arr = TypingDNA.history.get(length); 492 | var zl = TypingDNA.zl; 493 | var histRev = length; 494 | var histSktF = TypingDNA.math.fo(TypingDNA.history.get(length, "seek")); 495 | var histPrtF = TypingDNA.math.fo(TypingDNA.history.get(length, "press")); 496 | var histRevPF = histPrtF.length; 497 | var histRevSF = histSktF.length; 498 | var pressHistMean = Math.round(TypingDNA.math.avg(histPrtF)); 499 | var seekHistMean = Math.round(TypingDNA.math.avg(histSktF)); 500 | var pressHistSD = Math.round(TypingDNA.math.sd(histPrtF)); 501 | var seekHistSD = Math.round(TypingDNA.math.sd(histSktF)); 502 | var charMeanTime = seekHistMean + pressHistMean; 503 | var pressRatio = TypingDNA.math.rd((pressHistMean + zl) / (charMeanTime + zl), 4); 504 | var seekToPressRatio = TypingDNA.math.rd((1 - pressRatio)/pressRatio, 4); 505 | var pressSDToPressRatio = TypingDNA.math.rd((pressHistSD + zl) / (pressHistMean + zl), 4); 506 | var seekSDToPressRatio = TypingDNA.math.rd((seekHistSD + zl) / (pressHistMean + zl), 4); 507 | var cpm = Math.round(6E4 / (charMeanTime + zl)); 508 | for (var i in obj.arr) { 509 | var rev = obj.arr[i][1].length; 510 | var seekMean = 0; 511 | var pressMean = 0; 512 | var postMean = 0; 513 | var seekSD = 0; 514 | var pressSD = 0; 515 | var postSD = 0; 516 | switch (obj.arr[i][0].length) { 517 | case 0: 518 | break; 519 | case 1: 520 | var seekMean = TypingDNA.math.rd((obj.arr[i][0][0] + zl) / (seekHistMean + zl), 4); 521 | break; 522 | default: 523 | var arr = TypingDNA.math.fo(obj.arr[i][0]); 524 | seekMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (seekHistMean + zl), 4); 525 | seekSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (seekHistSD + zl), 4); 526 | } 527 | switch (obj.arr[i][1].length) { 528 | case 0: 529 | break; 530 | case 1: 531 | var pressMean = TypingDNA.math.rd((obj.arr[i][1][0] + zl) / (pressHistMean + zl), 4); 532 | break; 533 | default: 534 | var arr = TypingDNA.math.fo(obj.arr[i][1]); 535 | pressMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (pressHistMean + zl), 4); 536 | pressSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (pressHistSD + zl), 4); 537 | } 538 | switch (obj.arr[i][2].length) { 539 | case 0: 540 | break; 541 | case 1: 542 | var postMean = TypingDNA.math.rd((obj.arr[i][2][0] + zl) / (seekHistMean + zl), 4); 543 | break; 544 | default: 545 | var arr = TypingDNA.math.fo(obj.arr[i][2]); 546 | postMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (seekHistMean + zl), 4); 547 | postSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (seekHistSD + zl), 4); 548 | } 549 | delete obj.arr[i][2]; 550 | delete obj.arr[i][1]; 551 | delete obj.arr[i][0]; 552 | obj.arr[i][0] = rev; 553 | obj.arr[i][1] = seekMean; 554 | obj.arr[i][2] = pressMean; 555 | obj.arr[i][3] = postMean; 556 | obj.arr[i][4] = seekSD; 557 | obj.arr[i][5] = pressSD; 558 | obj.arr[i][6] = postSD; 559 | } 560 | var arr = []; 561 | TypingDNA.apu(arr, histRev); 562 | TypingDNA.apu(arr, cpm); 563 | TypingDNA.apu(arr, charMeanTime); 564 | TypingDNA.apu(arr, pressRatio); 565 | TypingDNA.apu(arr, seekToPressRatio); 566 | TypingDNA.apu(arr, pressSDToPressRatio); 567 | TypingDNA.apu(arr, seekSDToPressRatio); 568 | TypingDNA.apu(arr, pressHistMean); 569 | TypingDNA.apu(arr, seekHistMean); 570 | TypingDNA.apu(arr, pressHistSD); 571 | TypingDNA.apu(arr, seekHistSD); 572 | for (var c = 0; c <= 6; c++) { 573 | for (var i = 0; i < 44; i++) { 574 | var keyCode = TypingDNA.keyCodes[i]; 575 | var val = obj.arr[keyCode][c]; 576 | if (val == 0 && c > 0) { 577 | val = 1; 578 | } 579 | TypingDNA.apu(arr, val); 580 | } 581 | } 582 | TypingDNA.apu(arr, TypingDNA.isMobile()); 583 | TypingDNA.apu(arr, TypingDNA.version); 584 | TypingDNA.apu(arr, TypingDNA.flags); 585 | arr.push(TypingDNA.history.getSpecialKeys()); 586 | return arr.join(","); 587 | } 588 | 589 | TypingDNA.apu = function(arr, val) { 590 | "NaN" == String(val) && (val = 0); 591 | arr.push(val); 592 | } 593 | 594 | TypingDNA.math = {}; 595 | 596 | TypingDNA.math.rd = function(val, dec) { 597 | return Number(val.toFixed(dec)); 598 | } 599 | 600 | TypingDNA.math.avg = function(arr) { 601 | var len = arr.length; 602 | var sum = 0; 603 | for (var i = 0; i < len; i++) { 604 | sum += arr[i]; 605 | } 606 | return this.rd(sum / len, 4); 607 | } 608 | 609 | TypingDNA.math.sd = function(arr) { 610 | var len = arr.length; 611 | if (len < 2) { 612 | return 0; 613 | } else { 614 | var sumVS = 0; 615 | var mean = this.avg(arr); 616 | for (var i = 0; i < len; i++) { 617 | sumVS += (arr[i] - mean) * (arr[i] - mean); 618 | } 619 | var sd = Math.sqrt(sumVS / len); 620 | return sd; 621 | } 622 | } 623 | 624 | TypingDNA.math.fo = function(arr) { 625 | if (arr.length > 1) { 626 | var values = arr.concat(); 627 | var len = arr.length; 628 | values.sort(function(a, b) { 629 | return a - b; 630 | }); 631 | var asd = this.sd(values); 632 | var aMean = values[Math.ceil(arr.length / 2)]; 633 | var multiplier = 2; 634 | var maxVal = aMean + multiplier * asd; 635 | var minVal = aMean - multiplier * asd; 636 | if (len < 20) { 637 | minVal = 0; 638 | } 639 | var fVal = []; 640 | for (var i = 0; i < len; i++) { 641 | var tempval = values[i]; 642 | if (tempval < maxVal && tempval > minVal) { 643 | fVal.push(tempval); 644 | } 645 | } 646 | return fVal; 647 | } else { 648 | return arr; 649 | } 650 | } 651 | 652 | TypingDNA.history = {}; 653 | TypingDNA.history.stack = []; 654 | TypingDNA.history.stackDiagram = []; 655 | 656 | TypingDNA.history.add = function(arr) { 657 | this.stack.push(arr); 658 | if (this.stack.length > TypingDNA.maxHistoryLength) { 659 | this.stack.shift(); 660 | } 661 | } 662 | 663 | TypingDNA.history.addDiagram = function(arr) { 664 | this.stackDiagram.push(arr); 665 | } 666 | 667 | TypingDNA.history.getDiagram = function(extended, str) { 668 | var returnArr = []; 669 | returnArr.push(TypingDNA.isMobile()); 670 | returnArr.push(TypingDNA.version); 671 | returnArr.push(TypingDNA.flags); 672 | returnArr.push(TypingDNA.history.getSpecialKeys()); 673 | var diagramType = (extended) ? 1 : 0; 674 | returnArr.push(diagramType); 675 | var diagramHistoryLength = this.stackDiagram.length; 676 | var targetLength = TypingDNA.targetIds.length; 677 | if (str == undefined && targetLength > 0) { 678 | str = ""; 679 | for (var i = 0; i < targetLength; i++) { 680 | str += document.getElementById(TypingDNA.targetIds[i]).value; 681 | } 682 | } 683 | if (str != undefined) { 684 | var solvedAstr = []; 685 | var lastPos = 0; 686 | for (var i = 0; i < str.length; i++) { 687 | var currentCharCode = str.charCodeAt(i); 688 | for (var j = lastPos; j < diagramHistoryLength; j++) { 689 | var arr = this.stackDiagram[j]; 690 | var charCode = arr[3]; 691 | if (charCode == currentCharCode) { 692 | if (j == lastPos) { 693 | lastPos++; 694 | } 695 | var seekTime = arr[1]; 696 | var pressTime = arr[2]; 697 | if (extended) { 698 | returnArr.push(charCode, seekTime, pressTime); 699 | } else { 700 | returnArr.push(seekTime, pressTime); 701 | } 702 | break; 703 | } 704 | } 705 | } 706 | } else { 707 | for (var i = 0; i < diagramHistoryLength; i++) { 708 | var arr = this.stackDiagram[i]; 709 | var seekTime = arr[1]; 710 | var pressTime = arr[2]; 711 | if (extended) { 712 | var charCode = arr[3]; 713 | returnArr.push(charCode, seekTime, pressTime); 714 | } else { 715 | returnArr.push(seekTime, pressTime); 716 | } 717 | } 718 | } 719 | return returnArr.join(","); 720 | } 721 | 722 | TypingDNA.history.get = function(length, type) { 723 | var historyTotalLength = this.stack.length; 724 | if (length == 0 || length == undefined) { 725 | length = TypingDNA.defaultHistoryLength; 726 | } 727 | if (length > historyTotalLength) { 728 | length = historyTotalLength; 729 | } 730 | switch (type) { 731 | case "seek": 732 | var seekArr = []; 733 | for (i = 1; i <= length; i++) { 734 | var seekTime = this.stack[historyTotalLength - i][1]; 735 | if (seekTime <= TypingDNA.maxSeekTime) { 736 | seekArr.push(seekTime); 737 | } 738 | }; 739 | return seekArr; 740 | break; 741 | case "press": 742 | var pressArr = []; 743 | for (i = 1; i <= length; i++) { 744 | var pressTime = this.stack[historyTotalLength - i][2]; 745 | if (pressTime <= TypingDNA.maxPressTime) { 746 | pressArr.push(pressTime); 747 | } 748 | }; 749 | return pressArr; 750 | break; 751 | default: 752 | var historyStackObj = {}; 753 | for (var i in TypingDNA.keyCodes) { 754 | historyStackObj[TypingDNA.keyCodes[i]] = [ 755 | [], 756 | [], 757 | [] 758 | ]; 759 | } 760 | for (i = 1; i <= length; i++) { 761 | var arr = this.stack[historyTotalLength - i]; 762 | var keyCode = arr[0]; 763 | var seekTime = arr[1]; 764 | var pressTime = arr[2]; 765 | var prevKeyCode = arr[3]; 766 | if (TypingDNA.keyCodesObj[keyCode]) { 767 | if (seekTime <= TypingDNA.maxSeekTime) { 768 | historyStackObj[keyCode][0].push(seekTime); 769 | if (prevKeyCode != 0 && TypingDNA.keyCodesObj[prevKeyCode]) { 770 | historyStackObj[prevKeyCode][2].push(seekTime); 771 | } 772 | } 773 | if (pressTime <= TypingDNA.maxPressTime) { 774 | historyStackObj[keyCode][1].push(pressTime); 775 | } 776 | } 777 | }; 778 | return historyStackObj; 779 | } 780 | } 781 | 782 | TypingDNA.history.getSpecialKeys = function() { 783 | var returnArr = []; 784 | var length = this.stack.length; 785 | var historyStackObj = {}; 786 | for (var i in TypingDNA.spKeyCodes) { 787 | historyStackObj[TypingDNA.spKeyCodes[i]] = [ 788 | [], 789 | ]; 790 | } 791 | for (i = 1; i <= length; i++) { 792 | var arr = this.stack[length - i]; 793 | if (TypingDNA.spKeyCodesObj[arr[0]]) { 794 | var keyCode = arr[0]; 795 | var pressTime = arr[2]; 796 | if (pressTime <= TypingDNA.maxPressTime) { 797 | historyStackObj[keyCode][0].push(pressTime); 798 | } 799 | } 800 | } 801 | for (var i in TypingDNA.spKeyCodes) { 802 | var arr = TypingDNA.math.fo(historyStackObj[TypingDNA.spKeyCodes[i]][0]); 803 | var arrLen = arr.length; 804 | returnArr.push(arrLen); 805 | if (arrLen > 1) { 806 | returnArr.push(Math.round(TypingDNA.math.avg(arr))); 807 | returnArr.push(Math.round(TypingDNA.math.sd(arr))); 808 | } else if (arrLen == 1) { 809 | returnArr.push([arr[0],-1]); 810 | } else { 811 | returnArr.push([-1,-1]); 812 | } 813 | } 814 | var clicksArrLen = TypingDNA.clickTimes.length; 815 | returnArr.push(clicksArrLen); 816 | if (clicksArrLen > 1) { 817 | returnArr.push(Math.round(TypingDNA.math.avg(TypingDNA.clickTimes))); 818 | returnArr.push(Math.round(TypingDNA.math.sd(TypingDNA.clickTimes))); 819 | } else if (clicksArrLen == 1) { 820 | returnArr.push(TypingDNA.clickTimes[0],-1); 821 | } else { 822 | returnArr.push([-1,-1]); 823 | } 824 | var stopArrLen = TypingDNA.stopTimes.length; 825 | returnArr.push(stopArrLen); 826 | if (stopArrLen > 1) { 827 | returnArr.push(Math.round(TypingDNA.math.avg(TypingDNA.stopTimes))); 828 | returnArr.push(Math.round(TypingDNA.math.sd(TypingDNA.stopTimes))); 829 | } else if (stopArrLen == 1) { 830 | returnArr.push(TypingDNA.stopTimes[0],-1); 831 | } else { 832 | returnArr.push([-1,-1]); 833 | } 834 | return returnArr; 835 | } 836 | 837 | /** 838 | * Checks the quality of a typing pattern, how well it is revelated, how useful the 839 | * information will be for matching applications. It returns a value between 0 and 1. 840 | * Values over 0.3 are acceptable, however a value over 0.7 shows good pattern strength. 841 | * @param {String} typingPattern The typing pattern string returned by the get() function. 842 | * @return {Number} A real number between 0 and 1. A close to 1 value means a stronger pattern. 843 | * @example var quality = tdna.getQuality(typingPattern); 844 | */ 845 | TypingDNA.getQuality = function(typingPattern) { 846 | var obj = typingPattern.split(","); 847 | for (i = 0; i < obj.length; i++) { 848 | obj[i] = Number(obj[i]); 849 | } 850 | var totalEvents = obj[0]; 851 | var acc = rec = avgAcc = 0; 852 | var avg = TypingDNA.math.avg(obj); 853 | var revs = obj.slice(11, 55); 854 | for (var i in revs) { 855 | rec += Number(revs[i] > 0); 856 | acc += Number(revs[i] > 4); 857 | avgAcc += Number(revs[i] > avg); 858 | } 859 | var tReturn = Math.sqrt(rec * acc * avgAcc) / 80; 860 | return tReturn > 1 ? 1 : tReturn; 861 | } 862 | 863 | TypingDNA.getLength = function(typingPattern) { 864 | return Number(typingPattern.split(",")[1]); 865 | } 866 | 867 | TypingDNA.isMobile = function() { 868 | if (TypingDNA.mobile != undefined) { 869 | return TypingDNA.mobile; 870 | } else { 871 | var check = 0; 872 | (function(a) { 873 | if ( 874 | /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i 875 | .test(a) || 876 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i 877 | .test(a.substr(0, 4))) { 878 | check = 1 879 | } 880 | })(navigator.userAgent || navigator.vendor || window.opera); 881 | TypingDNA.mobile = check; 882 | return check; 883 | } 884 | } 885 | } else { 886 | // TypingDNA is a static class, currently doesn't support actual multiple instances (Singleton implementation) 887 | return TypingDNA.instance; 888 | } 889 | } 890 | -------------------------------------------------------------------------------- /versions/typingdna_2.6.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TypingDNA - Typing Biometrics JavaScript API 3 | * https://api.typingdna.com/scripts/typingdna.js 4 | * https://typingdna.com/scripts/typingdna.js (alternative) 5 | * 6 | * @version 2.6 7 | * @author Raul Popa 8 | * @copyright SC TypingDNA SRL, http://typingdna.com 9 | * @license http://www.apache.org/licenses/LICENSE-2.0 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | * Typical usage: 22 | * var tdna = new TypingDNA(); // creates a new TypingDNA object and starts recording 23 | * var typingPattern = tdna.get(); // returns a typing pattern (and continues recording afterwards), 24 | * optionally you can pass a length, tdna.get(200) will return the pattern based on the last 200 key events. 25 | * 26 | * Optional: 27 | * tdna.stop(); // ends recording and clears history stack (returns recording flag: false) 28 | * tdna.start(); // restarts the recording after a stop (returns recording flag: true) 29 | * tdna.reset(); // restarts the recording anytime, clears history stack and starts from scratch (returns nothing) 30 | * var typingPatternQuality = TypingDNA.getQuality(typingPattern); //returns the quality/strength of any typing pattern 31 | * (there is no need to initialize the class to do pattern quality checking) 32 | */ 33 | 34 | /** 35 | * Creates a single instance (or a reference) of the TypingDNA class 36 | * @return {Object} Returns the single instance of the TypingDNA class. 37 | * @example var tdna = new TypingDNA(); 38 | */ 39 | function TypingDNA() { 40 | if (TypingDNA.initialized != true) { 41 | TypingDNA.prototype.start = function() { 42 | return TypingDNA.start.apply(this, arguments); 43 | } 44 | TypingDNA.prototype.stop = function() { 45 | return TypingDNA.stop.apply(this, arguments); 46 | } 47 | TypingDNA.prototype.reset = function() { 48 | return TypingDNA.reset.apply(this, arguments); 49 | } 50 | TypingDNA.prototype.addTarget = function() { 51 | return TypingDNA.addTarget.apply(this, arguments); 52 | } 53 | TypingDNA.prototype.removeTarget = function() { 54 | return TypingDNA.removeTarget.apply(this, arguments); 55 | } 56 | TypingDNA.prototype.get = function(args) { 57 | return TypingDNA.get.apply(this, arguments); 58 | } 59 | TypingDNA.prototype.startDiagram = function() { 60 | return TypingDNA.startDiagram.apply(this, arguments); 61 | } 62 | TypingDNA.prototype.stopDiagram = function() { 63 | return TypingDNA.stopDiagram.apply(this, arguments); 64 | } 65 | TypingDNA.prototype.getDiagram = function(args) { 66 | return TypingDNA.getDiagram.apply(this, arguments); 67 | } 68 | TypingDNA.prototype.getExtendedDiagram = function(args) { 69 | return TypingDNA.getExtendedDiagram.apply(this, arguments); 70 | } 71 | TypingDNA.prototype.getMouseDiagram = function(args) { 72 | return TypingDNA.getMouseDiagram.apply(this, arguments); 73 | } 74 | TypingDNA.prototype.startMouse = function() { 75 | return TypingDNA.startMouse.apply(this, arguments); 76 | } 77 | TypingDNA.prototype.stopMouse = function() { 78 | return TypingDNA.stopMouse.apply(this, arguments); 79 | } 80 | TypingDNA.prototype.getQuality = function(args) { 81 | return TypingDNA.getQuality.apply(this, arguments); 82 | } 83 | TypingDNA.prototype.getLength = function(args) { 84 | return TypingDNA.getLength.apply(this, arguments); 85 | } 86 | TypingDNA.prototype.isMobile = function(args) { 87 | return TypingDNA.isMobile.apply(this, arguments); 88 | } 89 | TypingDNA.initialized = true; 90 | TypingDNA.prototype.maxHistoryLength = TypingDNA.maxHistoryLength; 91 | TypingDNA.prototype.defaultHistoryLength = TypingDNA.defaultHistoryLength; 92 | TypingDNA.prototype.maxSeekTime = TypingDNA.maxSeekTime; 93 | TypingDNA.prototype.maxPressTime = TypingDNA.maxPressTime; 94 | TypingDNA.version = 2.6; 95 | TypingDNA.flags = 0; 96 | TypingDNA.instance = this; 97 | TypingDNA.document = document; 98 | TypingDNA.maxHistoryLength = 2000; 99 | TypingDNA.maxSeekTime = 1500; 100 | TypingDNA.maxPressTime = 300; 101 | TypingDNA.defaultHistoryLength = 500; 102 | TypingDNA.spKeyCodes = [8, 13]; 103 | TypingDNA.spKeyCodesObj = {8: 1, 13: 1}; 104 | TypingDNA.keyCodes = [65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 32, 222, 188, 190, 186, 187, 189, 191, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]; 105 | TypingDNA.keyCodesObj = {} 106 | for (var i in TypingDNA.keyCodes) { 107 | TypingDNA.keyCodesObj[TypingDNA.keyCodes[i]] = 1; 108 | } 109 | TypingDNA.pt1 = TypingDNA.ut1 = (new Date).getTime(); 110 | TypingDNA.wfk = []; 111 | TypingDNA.sti = []; 112 | TypingDNA.skt = []; 113 | TypingDNA.recording = true; 114 | TypingDNA.mouseRecording = true; 115 | TypingDNA.mouseMoveRecording = false; 116 | TypingDNA.spKeyRecording = true; 117 | TypingDNA.diagramRecording = false; 118 | TypingDNA.dwfk = []; 119 | TypingDNA.dsti = []; 120 | TypingDNA.dskt = []; 121 | TypingDNA.drkc = []; 122 | TypingDNA.dlastDownKey; 123 | TypingDNA.prevKeyCode = 0; 124 | TypingDNA.maxMoveDeltaTime = 600; 125 | TypingDNA.maxScrollDeltaTime = 800; 126 | TypingDNA.maxStopTime = 1500; 127 | TypingDNA.maxClickTime = 600; 128 | TypingDNA.maxMouseHistoryLength = 500; 129 | TypingDNA.lastMouseMoveTime = TypingDNA.lastMouseDownTime = (new Date).getTime(); 130 | TypingDNA.stopTimes = []; 131 | TypingDNA.clickTimes = []; 132 | TypingDNA.lastMouseStop = true; 133 | TypingDNA.zl = 0.0000001; 134 | TypingDNA.isAndroidChrome = false; 135 | TypingDNA.ACLastTime = (new Date).getTime(); 136 | TypingDNA.ACSeekTime = 0; 137 | TypingDNA.ACPressTime = 0; 138 | TypingDNA.ACLastCharCode = 0; 139 | TypingDNA.ACLastKeyCode = 0; 140 | TypingDNA.ACFlagDownTI = 0; 141 | TypingDNA.ACPreviousKeyCode = 0; 142 | TypingDNA.targetIds = []; 143 | TypingDNA.lastTarget = ""; 144 | TypingDNA.lastTargetFound = false; 145 | 146 | TypingDNA.keyDown = function(e) { 147 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 148 | return; 149 | } 150 | if (!TypingDNA.isTarget(e.target.id)) { 151 | return; 152 | } 153 | var keyCode = e.keyCode; 154 | if (keyCode == 229 && TypingDNA.isMobile() && !TypingDNA.isAndroidChrome) { 155 | TypingDNA.isAndroidChrome = true; 156 | 157 | TypingDNA.ACSeekTime = (new Date).getTime() - TypingDNA.ACLastTime; 158 | TypingDNA.ACLastTime = (new Date).getTime(); 159 | TypingDNA.ACFlagDownTI = 1; 160 | 161 | TypingDNA.document.removeEventListener("keydown", TypingDNA.keyDown); 162 | TypingDNA.document.removeEventListener("keyup", TypingDNA.keyUp); 163 | TypingDNA.document.removeEventListener("keypress", TypingDNA.keyPress); 164 | 165 | // if android 166 | TypingDNA.document.addEventListener("textInput", TypingDNA.ACTextInput); 167 | TypingDNA.document.addEventListener("keydown", TypingDNA.ACKeyDown); 168 | TypingDNA.document.addEventListener("keyup", TypingDNA.ACKeyUp); 169 | } else { 170 | var t0 = TypingDNA.pt1; 171 | TypingDNA.pt1 = (new Date).getTime(); 172 | var seekTotal = TypingDNA.pt1 - t0; 173 | var startTime = TypingDNA.pt1; 174 | if (TypingDNA.recording == true || (TypingDNA.spKeyCodesObj[keyCode] && TypingDNA.spKeyRecording == true)) { 175 | if (!e.shiftKey || TypingDNA.isMobile()) { 176 | TypingDNA.wfk[keyCode] = 1; 177 | TypingDNA.skt[keyCode] = seekTotal; 178 | TypingDNA.sti[keyCode] = startTime; 179 | } 180 | } 181 | if (TypingDNA.diagramRecording == true) { 182 | TypingDNA.dwfk[keyCode] = 1; 183 | TypingDNA.dskt[keyCode] = seekTotal; 184 | TypingDNA.dsti[keyCode] = startTime; 185 | TypingDNA.dlastDownKey = keyCode; 186 | } 187 | } 188 | } 189 | 190 | TypingDNA.keyPress = function(e) { 191 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 192 | return; 193 | } 194 | if (!TypingDNA.isTarget(e.target.id)) { 195 | return; 196 | } 197 | if (TypingDNA.diagramRecording == true) { 198 | var keyCode = TypingDNA.dlastDownKey; 199 | TypingDNA.drkc[keyCode] = e.charCode; 200 | } 201 | } 202 | 203 | TypingDNA.keyUp = function(e) { 204 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 205 | return; 206 | } 207 | if (!TypingDNA.isTarget(e.target.id)) { 208 | return; 209 | } 210 | var ut = (new Date).getTime(); 211 | var keyCode = e.keyCode; 212 | if (TypingDNA.recording == true || (TypingDNA.spKeyCodesObj[keyCode] && TypingDNA.spKeyRecording == true)) { 213 | if (!e.shiftKey || TypingDNA.isMobile()) { 214 | if (TypingDNA.wfk[keyCode] == 1) { 215 | var pressTime = ut - TypingDNA.sti[keyCode]; 216 | var seekTime = TypingDNA.skt[keyCode]; 217 | var arr = [keyCode, seekTime, pressTime, TypingDNA.prevKeyCode, ut]; 218 | TypingDNA.history.add(arr); 219 | TypingDNA.prevKeyCode = keyCode; 220 | } 221 | } 222 | TypingDNA.wfk[keyCode] = 0; 223 | } 224 | if (TypingDNA.diagramRecording == true) { 225 | if (TypingDNA.drkc[keyCode] != undefined && TypingDNA.drkc[keyCode] != 0) { 226 | if (TypingDNA.dwfk[keyCode] == 1) { 227 | var pressTime = ut - TypingDNA.dsti[keyCode]; 228 | var seekTime = TypingDNA.dskt[keyCode]; 229 | var realKeyCode = TypingDNA.drkc[keyCode]; 230 | var arrD = [keyCode, seekTime, pressTime, realKeyCode]; 231 | TypingDNA.history.addDiagram(arrD); 232 | } 233 | } 234 | TypingDNA.dwfk[keyCode] = 0; 235 | } 236 | } 237 | 238 | TypingDNA.mouseScroll = function(e) { 239 | if (TypingDNA.mouseRecording == true) { 240 | if (TypingDNA.mouseMoveRecording == true) { 241 | if (TypingDNA.mouse.scrollStarted == true) { 242 | var currentTime = (new Date).getTime(); 243 | TypingDNA.mouse.scrollTimes.push(currentTime); 244 | TypingDNA.mouse.scrollTopArr.push(TypingDNA.document.body.scrollTop); 245 | clearInterval(TypingDNA.scrollInterval); 246 | TypingDNA.scrollInterval = setInterval(TypingDNA.mouse.checkScroll, TypingDNA.maxScrollDeltaTime); 247 | } else { 248 | TypingDNA.mouse.scrollStarted = true 249 | } 250 | } 251 | } 252 | } 253 | 254 | TypingDNA.mouseMove = function(e) { 255 | if (TypingDNA.mouseRecording == true) { 256 | var currentTime = (new Date).getTime(); 257 | if (TypingDNA.mouseMoveRecording == true) { 258 | if (TypingDNA.mouse.started == true) { 259 | TypingDNA.mouse.times.push(currentTime); 260 | TypingDNA.mouse.xPositions.push(e.screenX); 261 | TypingDNA.mouse.yPositions.push(e.screenY); 262 | clearInterval(TypingDNA.moveInterval); 263 | TypingDNA.moveInterval = setInterval(TypingDNA.mouse.checkMove, TypingDNA.maxMoveDeltaTime); 264 | } else { 265 | TypingDNA.mouse.started = true; 266 | } 267 | } 268 | TypingDNA.lastMouseMoveTime = currentTime; 269 | } 270 | } 271 | 272 | TypingDNA.mouseDown = function(e) { 273 | if (TypingDNA.mouseRecording == true) { 274 | TypingDNA.mouse.checkMove(); 275 | TypingDNA.mouse.checkScroll(); 276 | if (e.which == 1) { 277 | TypingDNA.lastMouseDownTime = (new Date).getTime(); 278 | var stopTime = TypingDNA.lastMouseDownTime - TypingDNA.lastMouseMoveTime; 279 | if (stopTime < TypingDNA.maxStopTime && TypingDNA.lastMouseStop == false) { 280 | TypingDNA.stopTimes.push(stopTime); 281 | if (TypingDNA.mouseMoveRecording == true) { 282 | var eventType = 3; 283 | var arr = [eventType, stopTime]; 284 | TypingDNA.mouse.history.add(arr); 285 | TypingDNA.lastMouseStop = true; 286 | } 287 | } 288 | } 289 | } 290 | } 291 | 292 | TypingDNA.mouseUp = function(e) { 293 | if (TypingDNA.mouseRecording == true) { 294 | if (e.which == 1) { 295 | var clickTime = (new Date).getTime() - TypingDNA.lastMouseDownTime; 296 | if (clickTime < TypingDNA.maxClickTime && TypingDNA.lastMouseDownTime > TypingDNA.lastMouseMoveTime) { 297 | TypingDNA.clickTimes.push(clickTime); 298 | if (TypingDNA.mouseMoveRecording == true) { 299 | var eventType = 4; 300 | var arr = [eventType, clickTime]; 301 | TypingDNA.mouse.history.add(arr); 302 | } 303 | } 304 | } 305 | } 306 | } 307 | 308 | TypingDNA.ACKeyDown = function(e) { 309 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 310 | return; 311 | } 312 | TypingDNA.ACSeekTime = (new Date).getTime() - TypingDNA.ACLastTime; 313 | TypingDNA.ACLastTime = (new Date).getTime(); 314 | if (!TypingDNA.isTarget(e.target.id)) { 315 | return; 316 | } 317 | if (e.keyCode == 229) { 318 | TypingDNA.ACFlagDownTI = 1; 319 | } 320 | } 321 | 322 | TypingDNA.ACTextInput = function(e) { 323 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 324 | return; 325 | } 326 | if (TypingDNA.ACFlagDownTI = 1) { 327 | TypingDNA.ACLastCharCode = e.data.charCodeAt(0); 328 | TypingDNA.ACLastKeyCode = e.data.toUpperCase().charCodeAt(0); 329 | TypingDNA.ACFlagDownTI = 0; 330 | } 331 | } 332 | 333 | TypingDNA.ACKeyUp = function(e) { 334 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 335 | return; 336 | } 337 | TypingDNA.ACPressTime = (new Date).getTime() - TypingDNA.ACLastTime; 338 | TypingDNA.ACLastTime = (new Date).getTime(); 339 | if (!TypingDNA.isTarget(e.target.id)) { 340 | return; 341 | } 342 | var keyCode = e.keyCode; 343 | if (keyCode == 229 && TypingDNA.ACFlagDownTI == 0) { 344 | keyCode = TypingDNA.ACLastKeyCode; 345 | } 346 | var arr = [keyCode, TypingDNA.ACSeekTime, TypingDNA.ACPressTime, TypingDNA.ACPreviousKeyCode, TypingDNA.ACLastTime]; 347 | TypingDNA.history.add(arr); 348 | TypingDNA.ACPreviousKeyCode = keyCode; 349 | if (TypingDNA.diagramRecording == true && TypingDNA.ACFlagDownTI == 0) { 350 | var arrD = [keyCode, TypingDNA.ACSeekTime, TypingDNA.ACPressTime, TypingDNA.ACLastCharCode]; 351 | TypingDNA.history.addDiagram(arrD); 352 | } 353 | } 354 | 355 | TypingDNA.isTarget = function(target) { 356 | if (TypingDNA.lastTarget == target && TypingDNA.lastTargetFound) { 357 | return true; 358 | } else { 359 | var targetLength = TypingDNA.targetIds.length; 360 | var targetFound = false; 361 | if (targetLength > 0) { 362 | for (var i = 0; i < targetLength; i++) { 363 | if (TypingDNA.targetIds[i] == target) { 364 | targetFound = true; 365 | break; 366 | } 367 | } 368 | TypingDNA.lastTarget = target; 369 | TypingDNA.lastTargetFound = targetFound; 370 | return targetFound; 371 | } else { 372 | TypingDNA.lastTarget = target; 373 | TypingDNA.lastTargetFound = true; 374 | return true; 375 | } 376 | } 377 | } 378 | 379 | if (TypingDNA.document.addEventListener) { 380 | TypingDNA.document.addEventListener("keydown", TypingDNA.keyDown); 381 | TypingDNA.document.addEventListener("keyup", TypingDNA.keyUp); 382 | TypingDNA.document.addEventListener("keypress", TypingDNA.keyPress); 383 | TypingDNA.document.addEventListener("mousemove", TypingDNA.mouseMove); 384 | TypingDNA.document.addEventListener("mousedown", TypingDNA.mouseDown); 385 | TypingDNA.document.addEventListener("mouseup", TypingDNA.mouseUp); 386 | TypingDNA.document.addEventListener("scroll", TypingDNA.mouseScroll); 387 | } else if (TypingDNA.document.attachEvent) { 388 | TypingDNA.document.attachEvent("onkeydown", TypingDNA.keyDown); 389 | TypingDNA.document.attachEvent("onkeyup", TypingDNA.keyUp); 390 | TypingDNA.document.attachEvent("onkeypress", TypingDNA.keyPress); 391 | TypingDNA.document.attachEvent("onmousemove", TypingDNA.mouseMove); 392 | TypingDNA.document.attachEvent("onmousedown", TypingDNA.mouseDown); 393 | TypingDNA.document.attachEvent("onmouseup", TypingDNA.mouseUp); 394 | TypingDNA.document.attachEvent("onscroll", TypingDNA.mouseScroll); 395 | } else { 396 | console.log("browser not supported"); 397 | } 398 | 399 | TypingDNA.mouse = {}; 400 | TypingDNA.mouse.times = []; 401 | TypingDNA.mouse.xPositions = []; 402 | TypingDNA.mouse.yPositions = []; 403 | TypingDNA.mouse.scrollTimes = []; 404 | TypingDNA.mouse.scrollTopArr = []; 405 | TypingDNA.mouse.history = {}; 406 | TypingDNA.mouse.history.stack = []; 407 | 408 | TypingDNA.mouse.getDistance = function(a, b) { 409 | return Math.sqrt((a * a) + (b * b)); 410 | } 411 | 412 | TypingDNA.mouse.getTotalDistance = function(xPositions, yPositions) { 413 | var totalDistance = 0; 414 | var length = xPositions.length; 415 | for (i = 1; i < length - 1; i++) { 416 | var a = xPositions[i] - xPositions[i - 1]; 417 | var b = yPositions[i] - yPositions[i - 1]; 418 | totalDistance += TypingDNA.mouse.getDistance(a, b); 419 | } 420 | return totalDistance; 421 | } 422 | 423 | TypingDNA.mouse.getAngle = function(xPosDelta, yPosDelta) { 424 | var angle = 0; 425 | var leftRight = (xPosDelta >= 0); // 1 if left, 0 if right 426 | var downUp = (yPosDelta < 0); // 1 if down, 0 if up 427 | if (leftRight) { 428 | if (downUp) { 429 | angle = 180 + (Math.round(Math.atan(Math.abs(xPosDelta) / (Math.abs(yPosDelta) + 0.0000001)) / 0.01745329251)) // 0.01745329251 = pi/180 430 | } else { 431 | angle = 270 + (90 - Math.round(Math.atan(Math.abs(xPosDelta) / (Math.abs(yPosDelta) + 0.0000001)) / 0.01745329251)) 432 | } 433 | } else { 434 | if (downUp) { 435 | angle = 90 + (90 - Math.round(Math.atan(Math.abs(xPosDelta) / (Math.abs(yPosDelta) + 0.0000001)) / 0.01745329251)) 436 | } else { 437 | angle = Math.round(Math.atan(Math.abs(xPosDelta) / (Math.abs(yPosDelta) + 0.0000001)) / 0.01745329251) 438 | } 439 | } 440 | return angle; 441 | } 442 | 443 | TypingDNA.mouse.recordMoveAction = function() { 444 | var length = TypingDNA.mouse.times.length; 445 | if (length < 3) { 446 | return; 447 | } 448 | var deltaTime = TypingDNA.mouse.times[length - 1] - TypingDNA.mouse.times[0]; 449 | var xPosDelta = TypingDNA.mouse.xPositions[length - 1] - TypingDNA.mouse.xPositions[0]; 450 | var yPosDelta = TypingDNA.mouse.yPositions[length - 1] - TypingDNA.mouse.yPositions[0]; 451 | var directDistance = Math.round(TypingDNA.mouse.getDistance(xPosDelta, yPosDelta)); 452 | var totalDistance = Math.round(TypingDNA.mouse.getTotalDistance(TypingDNA.mouse.xPositions, TypingDNA.mouse.yPositions)); 453 | var ratioDistance = Math.round(totalDistance * 100 / directDistance); 454 | var speed = Math.round(directDistance * 100 / deltaTime); 455 | var angle = TypingDNA.mouse.getAngle(xPosDelta, yPosDelta); 456 | var eventType = 1; 457 | var arr = [eventType, deltaTime, directDistance, speed, angle, ratioDistance]; 458 | for (i in arr) { 459 | if (isNaN(arr[i])) { 460 | return; 461 | } 462 | } 463 | TypingDNA.mouse.history.add(arr); 464 | TypingDNA.lastMouseStop = false; 465 | } 466 | 467 | TypingDNA.mouse.recordScrollAction = function() { 468 | var length = TypingDNA.mouse.scrollTimes.length; 469 | if (length < 2) { 470 | return; 471 | } 472 | var deltaTime = TypingDNA.mouse.scrollTimes[length - 1] - TypingDNA.mouse.scrollTimes[0]; 473 | var directDistance = TypingDNA.mouse.scrollTopArr[length - 1] - TypingDNA.mouse.scrollTopArr[0]; 474 | var speed = Math.round(directDistance * 100 / deltaTime); 475 | var eventType = 2; 476 | var arr = [eventType, deltaTime, directDistance, speed]; 477 | for (i in arr) { 478 | if (isNaN(arr[i]) && !isFinite(arr[i])) { 479 | return; 480 | } 481 | } 482 | TypingDNA.mouse.history.add(arr); 483 | } 484 | 485 | TypingDNA.mouse.history.add = function(arr) { 486 | this.stack.push(arr); 487 | if (this.stack.length > TypingDNA.maxMouseHistoryLength) { 488 | this.stack.shift(); 489 | } 490 | } 491 | 492 | TypingDNA.mouse.history.getDiagram = function() { 493 | var mouseDiagram = this.stack.join("|"); 494 | return [String(TypingDNA.isMobile()) + "," + String(TypingDNA.version), mouseDiagram].join("|"); 495 | } 496 | 497 | TypingDNA.mouse.clearLastMove = function() { 498 | TypingDNA.mouse.times = []; 499 | TypingDNA.mouse.xPositions = []; 500 | TypingDNA.mouse.yPositions = []; 501 | } 502 | 503 | TypingDNA.mouse.checkMove = function() { 504 | clearInterval(TypingDNA.moveInterval); 505 | if (TypingDNA.mouse.started == true) { 506 | TypingDNA.mouse.started = false; 507 | TypingDNA.mouse.recordMoveAction(); 508 | TypingDNA.mouse.clearLastMove(); 509 | } 510 | } 511 | 512 | TypingDNA.mouse.clearLastScroll = function() { 513 | TypingDNA.mouse.scrollTimes = []; 514 | TypingDNA.mouse.scrollTopArr = []; 515 | } 516 | 517 | TypingDNA.mouse.checkScroll = function() { 518 | clearInterval(TypingDNA.scrollInterval); 519 | if (TypingDNA.mouse.scrollStarted == true) { 520 | TypingDNA.mouse.scrollStarted = false; 521 | TypingDNA.mouse.recordScrollAction(); 522 | TypingDNA.mouse.clearLastScroll(); 523 | } 524 | } 525 | 526 | /** 527 | * Adds a target to the targetIds array. 528 | */ 529 | TypingDNA.addTarget = function(target) { 530 | var targetLength = TypingDNA.targetIds.length; 531 | var targetFound = false; 532 | if (targetLength > 0) { 533 | for (var i = 0; i < targetLength; i++) { 534 | if (TypingDNA.targetIds[i] == target) { 535 | targetFound = true; 536 | break; 537 | } 538 | } 539 | if (!targetFound) { 540 | TypingDNA.targetIds.push(target); 541 | } 542 | } else { 543 | TypingDNA.targetIds.push(target); 544 | } 545 | } 546 | 547 | /** 548 | * Adds a target to the targetIds array. 549 | */ 550 | TypingDNA.removeTarget = function(target) { 551 | var targetLength = TypingDNA.targetIds.length; 552 | if (targetLength > 0) { 553 | for (var i = 0; i < targetLength; i++) { 554 | if (TypingDNA.targetIds[i] == target) { 555 | TypingDNA.targetIds.splice(i, 1); 556 | break; 557 | } 558 | } 559 | } 560 | } 561 | 562 | /** 563 | * Resets the history stack 564 | */ 565 | TypingDNA.reset = function(all) { 566 | TypingDNA.history.stack = []; 567 | TypingDNA.history.stackDiagram = []; 568 | TypingDNA.clickTimes = []; 569 | TypingDNA.stopTimes = []; 570 | if (all == true) { 571 | TypingDNA.mouse.history.stack = []; 572 | } 573 | } 574 | 575 | /** 576 | * Automatically called at initilization. It starts the recording of keystrokes. 577 | */ 578 | TypingDNA.start = function() { 579 | return TypingDNA.recording = true; 580 | } 581 | 582 | /** 583 | * Ends the recording of further keystrokes. To restart recording afterwards you can 584 | * either call TypingDNA.start() or create a new TypingDNA object again, not recommended. 585 | */ 586 | TypingDNA.stop = function() { 587 | return TypingDNA.recording = false; 588 | } 589 | 590 | /** 591 | * Starts the recording of mouse activity. 592 | */ 593 | TypingDNA.startMouse = function() { 594 | return TypingDNA.mouseRecording = TypingDNA.mouseMoveRecording = true; 595 | } 596 | 597 | /** 598 | * Stops the recording of mouse activity. 599 | */ 600 | TypingDNA.stopMouse = function() { 601 | return TypingDNA.mouseRecording = TypingDNA.mouseMoveRecording = false; 602 | } 603 | 604 | /** 605 | * Starts the recording of a non-fixed diagram typing pattern. 606 | */ 607 | TypingDNA.startDiagram = function() { 608 | return TypingDNA.diagramRecording = true; 609 | } 610 | 611 | /** 612 | * Ends the recording of a non-fixed diagram typing pattern. 613 | */ 614 | TypingDNA.stopDiagram = function() { 615 | return TypingDNA.diagramRecording = false; 616 | } 617 | 618 | /** 619 | * This function outputs the linear diagram typing pattern as a String 620 | * @param {String} str Optional: The string represented by the diagram 621 | * The function checks for the exact string (with minor typos) in the recorded 622 | * history. Any letters that are not included in the string will be ommited from 623 | * the output diagram typing pattern. 624 | * @return {String} The TypingDNA linear diagram typing pattern, comma separated. 625 | * A non-fixed vector of only numeric values separated by commas. 626 | * @example var typingPattern = tdna.getDiagram(); 627 | * @example var typingPattern = tdna.getDiagram("Hello5g21?*"); 628 | */ 629 | TypingDNA.getDiagram = function(str) { 630 | return TypingDNA.history.getDiagram(false, str); 631 | } 632 | 633 | /** 634 | * This function outputs the extended linear diagram typing pattern as a String 635 | * Compared to getDiagram, it includes char codes (not safe for storage) 636 | * @param {String} str Optional: The string represented by the diagram 637 | * The function checks for the exact string (with minor typos) in the recorded 638 | * history. Any letters that are not included in the string will be ommited from 639 | * the output diagram typing pattern. 640 | * @return {String} The TypingDNA linear diagram typing pattern, comma separated. 641 | * A non-fixed vector of only numeric values separated by commas. 642 | * @example var typingPattern = tdna.getExtendedDiagram(); 643 | * @example var typingPattern = tdna.getExtendedDiagram("Hello5g21?*"); 644 | */ 645 | TypingDNA.getExtendedDiagram = function(str) { 646 | return TypingDNA.history.getDiagram(true, str); 647 | } 648 | 649 | TypingDNA.getMouseDiagram = function(str) { 650 | return TypingDNA.mouse.history.getDiagram(); 651 | } 652 | 653 | /** 654 | * This function outputs the typing pattern as a String, in a new basic structure for 655 | * easy storage and usage in any kind of keystroke dynamics applications (e.g. typing 656 | * pattern matching, user recognition) 657 | * @param {Number} length Optional: The amount of history keystrokes to use for the 658 | * typing pattern. By default it will use the last 500 recorded keystrokes (or as many 659 | * available if less than 500). 660 | * @return {String} The TypingDNA typing pattern, comma separated. 661 | * A fixed vector of only numeric values separated by commas. 662 | * @example var typingPattern = tdna.get(); 663 | * @example var typingPattern = tdna.get(200); 664 | */ 665 | TypingDNA.get = function(length) { 666 | var historyTotalLength = TypingDNA.history.stack.length; 667 | if (length == undefined) { 668 | length = TypingDNA.defaultHistoryLength; 669 | } 670 | if (length > historyTotalLength) { 671 | length = historyTotalLength; 672 | } 673 | var obj = {}; 674 | obj.arr = TypingDNA.history.get(length); 675 | var zl = TypingDNA.zl; 676 | var histRev = length; 677 | var histSktF = TypingDNA.math.fo(TypingDNA.history.get(length, "seek")); 678 | var histPrtF = TypingDNA.math.fo(TypingDNA.history.get(length, "press")); 679 | var histRevPF = histPrtF.length; 680 | var histRevSF = histSktF.length; 681 | var pressHistMean = Math.round(TypingDNA.math.avg(histPrtF)); 682 | var seekHistMean = Math.round(TypingDNA.math.avg(histSktF)); 683 | var pressHistSD = Math.round(TypingDNA.math.sd(histPrtF)); 684 | var seekHistSD = Math.round(TypingDNA.math.sd(histSktF)); 685 | var charMeanTime = seekHistMean + pressHistMean; 686 | var pressRatio = TypingDNA.math.rd((pressHistMean + zl) / (charMeanTime + zl), 4); 687 | var seekToPressRatio = TypingDNA.math.rd((1 - pressRatio) / pressRatio, 4); 688 | var pressSDToPressRatio = TypingDNA.math.rd((pressHistSD + zl) / (pressHistMean + zl), 4); 689 | var seekSDToPressRatio = TypingDNA.math.rd((seekHistSD + zl) / (pressHistMean + zl), 4); 690 | var cpm = Math.round(6E4 / (charMeanTime + zl)); 691 | for (var i in obj.arr) { 692 | var rev = obj.arr[i][1].length; 693 | var seekMean = 0; 694 | var pressMean = 0; 695 | var postMean = 0; 696 | var seekSD = 0; 697 | var pressSD = 0; 698 | var postSD = 0; 699 | switch (obj.arr[i][0].length) { 700 | case 0: 701 | break; 702 | case 1: 703 | var seekMean = TypingDNA.math.rd((obj.arr[i][0][0] + zl) / (seekHistMean + zl), 4); 704 | break; 705 | default: 706 | var arr = TypingDNA.math.fo(obj.arr[i][0]); 707 | seekMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (seekHistMean + zl), 4); 708 | seekSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (seekHistSD + zl), 4); 709 | } 710 | switch (obj.arr[i][1].length) { 711 | case 0: 712 | break; 713 | case 1: 714 | var pressMean = TypingDNA.math.rd((obj.arr[i][1][0] + zl) / (pressHistMean + zl), 4); 715 | break; 716 | default: 717 | var arr = TypingDNA.math.fo(obj.arr[i][1]); 718 | pressMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (pressHistMean + zl), 4); 719 | pressSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (pressHistSD + zl), 4); 720 | } 721 | switch (obj.arr[i][2].length) { 722 | case 0: 723 | break; 724 | case 1: 725 | var postMean = TypingDNA.math.rd((obj.arr[i][2][0] + zl) / (seekHistMean + zl), 4); 726 | break; 727 | default: 728 | var arr = TypingDNA.math.fo(obj.arr[i][2]); 729 | postMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (seekHistMean + zl), 4); 730 | postSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (seekHistSD + zl), 4); 731 | } 732 | delete obj.arr[i][2]; 733 | delete obj.arr[i][1]; 734 | delete obj.arr[i][0]; 735 | obj.arr[i][0] = rev; 736 | obj.arr[i][1] = seekMean; 737 | obj.arr[i][2] = pressMean; 738 | obj.arr[i][3] = postMean; 739 | obj.arr[i][4] = seekSD; 740 | obj.arr[i][5] = pressSD; 741 | obj.arr[i][6] = postSD; 742 | } 743 | var arr = []; 744 | TypingDNA.apu(arr, histRev); 745 | TypingDNA.apu(arr, cpm); 746 | TypingDNA.apu(arr, charMeanTime); 747 | TypingDNA.apu(arr, pressRatio); 748 | TypingDNA.apu(arr, seekToPressRatio); 749 | TypingDNA.apu(arr, pressSDToPressRatio); 750 | TypingDNA.apu(arr, seekSDToPressRatio); 751 | TypingDNA.apu(arr, pressHistMean); 752 | TypingDNA.apu(arr, seekHistMean); 753 | TypingDNA.apu(arr, pressHistSD); 754 | TypingDNA.apu(arr, seekHistSD); 755 | for (var c = 0; c <= 6; c++) { 756 | for (var i = 0; i < 44; i++) { 757 | var keyCode = TypingDNA.keyCodes[i]; 758 | var val = obj.arr[keyCode][c]; 759 | if (val == 0 && c > 0) { 760 | val = 1; 761 | } 762 | TypingDNA.apu(arr, val); 763 | } 764 | } 765 | TypingDNA.apu(arr, TypingDNA.isMobile()); 766 | TypingDNA.apu(arr, TypingDNA.version); 767 | TypingDNA.apu(arr, TypingDNA.flags); 768 | arr.push(TypingDNA.history.getSpecialKeys()); 769 | return arr.join(","); 770 | } 771 | 772 | TypingDNA.apu = function(arr, val) { 773 | "NaN" == String(val) && (val = 0); 774 | arr.push(val); 775 | } 776 | 777 | TypingDNA.math = {}; 778 | 779 | TypingDNA.math.rd = function(val, dec) { 780 | return Number(val.toFixed(dec)); 781 | } 782 | 783 | TypingDNA.math.avg = function(arr) { 784 | var len = arr.length; 785 | var sum = 0; 786 | for (var i = 0; i < len; i++) { 787 | sum += arr[i]; 788 | } 789 | return this.rd(sum / len, 4); 790 | } 791 | 792 | TypingDNA.math.sd = function(arr) { 793 | var len = arr.length; 794 | if (len < 2) { 795 | return 0; 796 | } else { 797 | var sumVS = 0; 798 | var mean = this.avg(arr); 799 | for (var i = 0; i < len; i++) { 800 | sumVS += (arr[i] - mean) * (arr[i] - mean); 801 | } 802 | var sd = Math.sqrt(sumVS / len); 803 | return sd; 804 | } 805 | } 806 | 807 | TypingDNA.math.fo = function(arr) { 808 | if (arr.length > 1) { 809 | var values = arr.concat(); 810 | var len = arr.length; 811 | values.sort(function(a, b) { 812 | return a - b; 813 | }); 814 | var asd = this.sd(values); 815 | var aMean = values[Math.ceil(arr.length / 2)]; 816 | var multiplier = 2; 817 | var maxVal = aMean + multiplier * asd; 818 | var minVal = aMean - multiplier * asd; 819 | if (len < 20) { 820 | minVal = 0; 821 | } 822 | var fVal = []; 823 | for (var i = 0; i < len; i++) { 824 | var tempval = values[i]; 825 | if (tempval < maxVal && tempval > minVal) { 826 | fVal.push(tempval); 827 | } 828 | } 829 | return fVal; 830 | } else { 831 | return arr; 832 | } 833 | } 834 | 835 | TypingDNA.history = {}; 836 | TypingDNA.history.stack = []; 837 | TypingDNA.history.stackDiagram = []; 838 | 839 | TypingDNA.history.add = function(arr) { 840 | this.stack.push(arr); 841 | if (this.stack.length > TypingDNA.maxHistoryLength) { 842 | this.stack.shift(); 843 | } 844 | } 845 | 846 | TypingDNA.history.addDiagram = function(arr) { 847 | this.stackDiagram.push(arr); 848 | } 849 | 850 | TypingDNA.history.getDiagram = function(extended, str) { 851 | var returnArr = []; 852 | var diagramType = (extended) ? 1 : 0; 853 | returnArr.push([TypingDNA.isMobile(), TypingDNA.version, TypingDNA.flags, TypingDNA.history.getSpecialKeys(), diagramType]); 854 | var diagramHistoryLength = this.stackDiagram.length; 855 | var targetLength = TypingDNA.targetIds.length; 856 | if (str == undefined && targetLength > 0) { 857 | str = ""; 858 | for (var i = 0; i < targetLength; i++) { 859 | var element = TypingDNA.document.getElementById(TypingDNA.targetIds[i]); 860 | if (element != null) { 861 | str += element.value; 862 | } 863 | } 864 | } 865 | if (str != undefined) { 866 | var solvedAstr = []; 867 | var lastPos = 0; 868 | for (var i = 0; i < str.length; i++) { 869 | var currentCharCode = str.charCodeAt(i); 870 | for (var j = lastPos; j < diagramHistoryLength; j++) { 871 | var arr = this.stackDiagram[j]; 872 | var charCode = arr[3]; 873 | if (charCode == currentCharCode) { 874 | if (j == lastPos) { 875 | lastPos++; 876 | } 877 | var seekTime = arr[1]; 878 | var pressTime = arr[2]; 879 | if (extended) { 880 | returnArr.push([charCode, seekTime, pressTime]); 881 | } else { 882 | returnArr.push([seekTime, pressTime]); 883 | } 884 | break; 885 | } 886 | } 887 | } 888 | } else { 889 | for (var i = 0; i < diagramHistoryLength; i++) { 890 | var arr = this.stackDiagram[i]; 891 | var seekTime = arr[1]; 892 | var pressTime = arr[2]; 893 | if (extended) { 894 | var charCode = arr[3]; 895 | returnArr.push([charCode, seekTime, pressTime]); 896 | } else { 897 | returnArr.push([seekTime, pressTime]); 898 | } 899 | } 900 | } 901 | return returnArr.join("|"); 902 | } 903 | 904 | TypingDNA.history.get = function(length, type) { 905 | var historyTotalLength = this.stack.length; 906 | if (length == 0 || length == undefined) { 907 | length = TypingDNA.defaultHistoryLength; 908 | } 909 | if (length > historyTotalLength) { 910 | length = historyTotalLength; 911 | } 912 | switch (type) { 913 | case "seek": 914 | var seekArr = []; 915 | for (i = 1; i <= length; i++) { 916 | var seekTime = this.stack[historyTotalLength - i][1]; 917 | if (seekTime <= TypingDNA.maxSeekTime) { 918 | seekArr.push(seekTime); 919 | } 920 | }; 921 | return seekArr; 922 | break; 923 | case "press": 924 | var pressArr = []; 925 | for (i = 1; i <= length; i++) { 926 | var pressTime = this.stack[historyTotalLength - i][2]; 927 | if (pressTime <= TypingDNA.maxPressTime) { 928 | pressArr.push(pressTime); 929 | } 930 | }; 931 | return pressArr; 932 | break; 933 | default: 934 | var historyStackObj = {}; 935 | for (var i in TypingDNA.keyCodes) { 936 | historyStackObj[TypingDNA.keyCodes[i]] = [ 937 | [], 938 | [], 939 | [] 940 | ]; 941 | } 942 | for (i = 1; i <= length; i++) { 943 | var arr = this.stack[historyTotalLength - i]; 944 | var keyCode = arr[0]; 945 | var seekTime = arr[1]; 946 | var pressTime = arr[2]; 947 | var prevKeyCode = arr[3]; 948 | if (TypingDNA.keyCodesObj[keyCode]) { 949 | if (seekTime <= TypingDNA.maxSeekTime) { 950 | historyStackObj[keyCode][0].push(seekTime); 951 | if (prevKeyCode != 0 && TypingDNA.keyCodesObj[prevKeyCode]) { 952 | historyStackObj[prevKeyCode][2].push(seekTime); 953 | } 954 | } 955 | if (pressTime <= TypingDNA.maxPressTime) { 956 | historyStackObj[keyCode][1].push(pressTime); 957 | } 958 | } 959 | }; 960 | return historyStackObj; 961 | } 962 | } 963 | 964 | TypingDNA.history.getSpecialKeys = function() { 965 | var returnArr = []; 966 | var length = this.stack.length; 967 | var historyStackObj = {}; 968 | for (var i in TypingDNA.spKeyCodes) { 969 | historyStackObj[TypingDNA.spKeyCodes[i]] = [ 970 | [], 971 | ]; 972 | } 973 | for (i = 1; i <= length; i++) { 974 | var arr = this.stack[length - i]; 975 | if (TypingDNA.spKeyCodesObj[arr[0]]) { 976 | var keyCode = arr[0]; 977 | var pressTime = arr[2]; 978 | if (pressTime <= TypingDNA.maxPressTime) { 979 | historyStackObj[keyCode][0].push(pressTime); 980 | } 981 | } 982 | } 983 | for (var i in TypingDNA.spKeyCodes) { 984 | var arr = TypingDNA.math.fo(historyStackObj[TypingDNA.spKeyCodes[i]][0]); 985 | var arrLen = arr.length; 986 | returnArr.push(arrLen); 987 | if (arrLen > 1) { 988 | returnArr.push(Math.round(TypingDNA.math.avg(arr))); 989 | returnArr.push(Math.round(TypingDNA.math.sd(arr))); 990 | } else if (arrLen == 1) { 991 | returnArr.push([arr[0], -1]); 992 | } else { 993 | returnArr.push([-1, -1]); 994 | } 995 | } 996 | var clicksArrLen = TypingDNA.clickTimes.length; 997 | returnArr.push(clicksArrLen); 998 | if (clicksArrLen > 1) { 999 | returnArr.push(Math.round(TypingDNA.math.avg(TypingDNA.clickTimes))); 1000 | returnArr.push(Math.round(TypingDNA.math.sd(TypingDNA.clickTimes))); 1001 | } else if (clicksArrLen == 1) { 1002 | returnArr.push(TypingDNA.clickTimes[0], -1); 1003 | } else { 1004 | returnArr.push([-1, -1]); 1005 | } 1006 | var stopArrLen = TypingDNA.stopTimes.length; 1007 | returnArr.push(stopArrLen); 1008 | if (stopArrLen > 1) { 1009 | returnArr.push(Math.round(TypingDNA.math.avg(TypingDNA.stopTimes))); 1010 | returnArr.push(Math.round(TypingDNA.math.sd(TypingDNA.stopTimes))); 1011 | } else if (stopArrLen == 1) { 1012 | returnArr.push(TypingDNA.stopTimes[0], -1); 1013 | } else { 1014 | returnArr.push([-1, -1]); 1015 | } 1016 | return returnArr; 1017 | } 1018 | 1019 | /** 1020 | * Checks the quality of a typing pattern, how well it is revelated, how useful the 1021 | * information will be for matching applications. It returns a value between 0 and 1. 1022 | * Values over 0.3 are acceptable, however a value over 0.7 shows good pattern strength. 1023 | * @param {String} typingPattern The typing pattern string returned by the get() function. 1024 | * @return {Number} A real number between 0 and 1. A close to 1 value means a stronger pattern. 1025 | * @example var quality = tdna.getQuality(typingPattern); 1026 | */ 1027 | TypingDNA.getQuality = function(typingPattern) { 1028 | var obj = typingPattern.split(","); 1029 | for (i = 0; i < obj.length; i++) { 1030 | obj[i] = Number(obj[i]); 1031 | } 1032 | var totalEvents = obj[0]; 1033 | var acc = rec = avgAcc = 0; 1034 | var avg = TypingDNA.math.avg(obj); 1035 | var revs = obj.slice(11, 55); 1036 | for (var i in revs) { 1037 | rec += Number(revs[i] > 0); 1038 | acc += Number(revs[i] > 4); 1039 | avgAcc += Number(revs[i] > avg); 1040 | } 1041 | var tReturn = Math.sqrt(rec * acc * avgAcc) / 80; 1042 | return tReturn > 1 ? 1 : tReturn; 1043 | } 1044 | 1045 | TypingDNA.getLength = function(typingPattern) { 1046 | return Number(typingPattern.split(",")[1]); 1047 | } 1048 | 1049 | TypingDNA.isMobile = function() { 1050 | if (TypingDNA.mobile != undefined) { 1051 | return TypingDNA.mobile; 1052 | } else { 1053 | var check = 0; 1054 | (function(a) { 1055 | if ( 1056 | /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i 1057 | .test(a) || 1058 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i 1059 | .test(a.substr(0, 4))) { 1060 | check = 1 1061 | } 1062 | })(navigator.userAgent || navigator.vendor || window.opera); 1063 | TypingDNA.mobile = check; 1064 | return check; 1065 | } 1066 | } 1067 | } else { 1068 | // TypingDNA is a static class, currently doesn't support actual multiple instances (Singleton implementation) 1069 | return TypingDNA.instance; 1070 | } 1071 | } 1072 | -------------------------------------------------------------------------------- /versions/typingdna_2.7.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TypingDNA - Typing Biometrics JavaScript API 3 | * https://api.typingdna.com/scripts/typingdna.js 4 | * https://typingdna.com/scripts/typingdna.js (alternative) 5 | * 6 | * @version 2.7 7 | * @author Raul Popa 8 | * @copyright SC TypingDNA SRL, http://typingdna.com 9 | * @license http://www.apache.org/licenses/LICENSE-2.0 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | * Typical usage: 22 | * var tdna = new TypingDNA(); // creates a new TypingDNA object and starts recording 23 | * var typingPattern = tdna.get(); // returns a typing pattern (and continues recording afterwards), 24 | * optionally you can pass a length, tdna.get(200) will return the pattern based on the last 200 key events. 25 | * 26 | * Optional: 27 | * tdna.stop(); // ends recording and clears history stack (returns recording flag: false) 28 | * tdna.start(); // restarts the recording after a stop (returns recording flag: true) 29 | * tdna.reset(); // restarts the recording anytime, clears history stack and starts from scratch (returns nothing) 30 | * var typingPatternQuality = TypingDNA.getQuality(typingPattern); //returns the quality/strength of any typing pattern 31 | * (there is no need to initialize the class to do pattern quality checking) 32 | */ 33 | 34 | /** 35 | * Creates a single instance (or a reference) of the TypingDNA class 36 | * @return {Object} Returns the single instance of the TypingDNA class. 37 | * @example var tdna = new TypingDNA(); 38 | */ 39 | function TypingDNA() { 40 | if (TypingDNA.initialized != true) { 41 | TypingDNA.prototype.start = function() { 42 | return TypingDNA.start.apply(this, arguments); 43 | } 44 | TypingDNA.prototype.stop = function() { 45 | return TypingDNA.stop.apply(this, arguments); 46 | } 47 | TypingDNA.prototype.reset = function() { 48 | return TypingDNA.reset.apply(this, arguments); 49 | } 50 | TypingDNA.prototype.addTarget = function() { 51 | return TypingDNA.addTarget.apply(this, arguments); 52 | } 53 | TypingDNA.prototype.removeTarget = function() { 54 | return TypingDNA.removeTarget.apply(this, arguments); 55 | } 56 | TypingDNA.prototype.get = function(args) { 57 | return TypingDNA.get.apply(this, arguments); 58 | } 59 | TypingDNA.prototype.startDiagram = function() { 60 | return TypingDNA.startDiagram.apply(this, arguments); 61 | } 62 | TypingDNA.prototype.stopDiagram = function() { 63 | return TypingDNA.stopDiagram.apply(this, arguments); 64 | } 65 | TypingDNA.prototype.getDiagram = function(args) { 66 | return TypingDNA.getDiagram.apply(this, arguments); 67 | } 68 | TypingDNA.prototype.getExtendedDiagram = function(args) { 69 | return TypingDNA.getExtendedDiagram.apply(this, arguments); 70 | } 71 | TypingDNA.prototype.getMouseDiagram = function(args) { 72 | return TypingDNA.getMouseDiagram.apply(this, arguments); 73 | } 74 | TypingDNA.prototype.startMouse = function() { 75 | return TypingDNA.startMouse.apply(this, arguments); 76 | } 77 | TypingDNA.prototype.stopMouse = function() { 78 | return TypingDNA.stopMouse.apply(this, arguments); 79 | } 80 | TypingDNA.prototype.getQuality = function(args) { 81 | return TypingDNA.getQuality.apply(this, arguments); 82 | } 83 | TypingDNA.prototype.getLength = function(args) { 84 | return TypingDNA.getLength.apply(this, arguments); 85 | } 86 | TypingDNA.prototype.isMobile = function(args) { 87 | return TypingDNA.isMobile.apply(this, arguments); 88 | } 89 | TypingDNA.initialized = true; 90 | TypingDNA.prototype.maxHistoryLength = TypingDNA.maxHistoryLength; 91 | TypingDNA.prototype.defaultHistoryLength = TypingDNA.defaultHistoryLength; 92 | TypingDNA.prototype.maxSeekTime = TypingDNA.maxSeekTime; 93 | TypingDNA.prototype.maxPressTime = TypingDNA.maxPressTime; 94 | TypingDNA.version = 2.7; 95 | TypingDNA.flags = 0; 96 | TypingDNA.instance = this; 97 | TypingDNA.document = document; 98 | TypingDNA.maxHistoryLength = 2000; 99 | TypingDNA.maxSeekTime = 1500; 100 | TypingDNA.maxPressTime = 300; 101 | TypingDNA.defaultHistoryLength = 500; 102 | TypingDNA.spKeyCodes = [8, 13, 32]; 103 | TypingDNA.spKeyCodesObj = {8: 1, 13: 1, 32: 1}; 104 | TypingDNA.keyCodes = [65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 32, 222, 188, 190, 186, 187, 189, 191, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]; 105 | TypingDNA.keyCodesObj = {} 106 | for (var i in TypingDNA.keyCodes) { 107 | TypingDNA.keyCodesObj[TypingDNA.keyCodes[i]] = 1; 108 | } 109 | TypingDNA.pt1 = TypingDNA.ut1 = (new Date).getTime(); 110 | TypingDNA.wfk = []; 111 | TypingDNA.sti = []; 112 | TypingDNA.skt = []; 113 | TypingDNA.recording = true; 114 | TypingDNA.mouseRecording = true; 115 | TypingDNA.mouseMoveRecording = false; 116 | TypingDNA.spKeyRecording = true; 117 | TypingDNA.diagramRecording = false; 118 | TypingDNA.dwfk = []; 119 | TypingDNA.dsti = []; 120 | TypingDNA.dskt = []; 121 | TypingDNA.drkc = []; 122 | TypingDNA.dlastDownKey; 123 | TypingDNA.prevKeyCode = 0; 124 | TypingDNA.maxMoveDeltaTime = 600; 125 | TypingDNA.maxScrollDeltaTime = 800; 126 | TypingDNA.maxStopTime = 1500; 127 | TypingDNA.maxClickTime = 600; 128 | TypingDNA.maxMouseHistoryLength = 500; 129 | TypingDNA.lastMouseMoveTime = TypingDNA.lastMouseDownTime = (new Date).getTime(); 130 | TypingDNA.stopTimes = []; 131 | TypingDNA.clickTimes = []; 132 | TypingDNA.lastMouseStop = true; 133 | TypingDNA.zl = 0.0000001; 134 | TypingDNA.isAndroidChrome = false; 135 | TypingDNA.ACLastTime = (new Date).getTime(); 136 | TypingDNA.ACSeekTime = 0; 137 | TypingDNA.ACPressTime = 0; 138 | TypingDNA.ACLastCharCode = 0; 139 | TypingDNA.ACLastKeyCode = 0; 140 | TypingDNA.ACFlagDownTI = 0; 141 | TypingDNA.ACPreviousKeyCode = 0; 142 | TypingDNA.targetIds = []; 143 | TypingDNA.lastTarget = ""; 144 | TypingDNA.lastTargetFound = false; 145 | 146 | TypingDNA.keyDown = function(e) { 147 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 148 | return; 149 | } 150 | if (!TypingDNA.isTarget(e.target.id)) { 151 | return; 152 | } 153 | var keyCode = e.keyCode; 154 | if (keyCode == 229 && TypingDNA.isMobile() && !TypingDNA.isAndroidChrome) { 155 | TypingDNA.isAndroidChrome = true; 156 | 157 | TypingDNA.ACSeekTime = (new Date).getTime() - TypingDNA.ACLastTime; 158 | TypingDNA.ACLastTime = (new Date).getTime(); 159 | TypingDNA.ACFlagDownTI = 1; 160 | 161 | TypingDNA.document.removeEventListener("keydown", TypingDNA.keyDown); 162 | TypingDNA.document.removeEventListener("keyup", TypingDNA.keyUp); 163 | TypingDNA.document.removeEventListener("keypress", TypingDNA.keyPress); 164 | 165 | // if android 166 | TypingDNA.document.addEventListener("textInput", TypingDNA.ACTextInput); 167 | TypingDNA.document.addEventListener("keydown", TypingDNA.ACKeyDown); 168 | TypingDNA.document.addEventListener("keyup", TypingDNA.ACKeyUp); 169 | } else { 170 | var t0 = TypingDNA.pt1; 171 | TypingDNA.pt1 = (new Date).getTime(); 172 | var seekTotal = TypingDNA.pt1 - t0; 173 | var startTime = TypingDNA.pt1; 174 | if (TypingDNA.recording == true || (TypingDNA.spKeyCodesObj[keyCode] && TypingDNA.spKeyRecording == true)) { 175 | if (!e.shiftKey || TypingDNA.isMobile()) { 176 | TypingDNA.wfk[keyCode] = 1; 177 | TypingDNA.skt[keyCode] = seekTotal; 178 | TypingDNA.sti[keyCode] = startTime; 179 | } 180 | } 181 | if (TypingDNA.diagramRecording == true) { 182 | TypingDNA.dwfk[keyCode] = 1; 183 | TypingDNA.dskt[keyCode] = seekTotal; 184 | TypingDNA.dsti[keyCode] = startTime; 185 | TypingDNA.dlastDownKey = keyCode; 186 | } 187 | } 188 | } 189 | 190 | TypingDNA.keyPress = function(e) { 191 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 192 | return; 193 | } 194 | if (!TypingDNA.isTarget(e.target.id)) { 195 | return; 196 | } 197 | if (TypingDNA.diagramRecording == true) { 198 | var keyCode = TypingDNA.dlastDownKey; 199 | TypingDNA.drkc[keyCode] = e.charCode; 200 | } 201 | } 202 | 203 | TypingDNA.keyUp = function(e) { 204 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 205 | return; 206 | } 207 | if (!TypingDNA.isTarget(e.target.id)) { 208 | return; 209 | } 210 | var ut = (new Date).getTime(); 211 | var keyCode = e.keyCode; 212 | if (TypingDNA.recording == true || (TypingDNA.spKeyCodesObj[keyCode] && TypingDNA.spKeyRecording == true)) { 213 | if (!e.shiftKey || TypingDNA.isMobile()) { 214 | if (TypingDNA.wfk[keyCode] == 1) { 215 | var pressTime = ut - TypingDNA.sti[keyCode]; 216 | var seekTime = TypingDNA.skt[keyCode]; 217 | var arr = [keyCode, seekTime, pressTime, TypingDNA.prevKeyCode, ut]; 218 | TypingDNA.history.add(arr); 219 | TypingDNA.prevKeyCode = keyCode; 220 | } 221 | } 222 | TypingDNA.wfk[keyCode] = 0; 223 | } 224 | if (TypingDNA.diagramRecording == true) { 225 | if (TypingDNA.drkc[keyCode] != undefined && TypingDNA.drkc[keyCode] != 0) { 226 | if (TypingDNA.dwfk[keyCode] == 1) { 227 | var pressTime = ut - TypingDNA.dsti[keyCode]; 228 | var seekTime = TypingDNA.dskt[keyCode]; 229 | var realKeyCode = TypingDNA.drkc[keyCode]; 230 | var arrD = [keyCode, seekTime, pressTime, realKeyCode]; 231 | TypingDNA.history.addDiagram(arrD); 232 | } 233 | } 234 | TypingDNA.dwfk[keyCode] = 0; 235 | } 236 | } 237 | 238 | TypingDNA.mouseScroll = function(e) { 239 | if (TypingDNA.mouseRecording == true) { 240 | if (TypingDNA.mouseMoveRecording == true) { 241 | if (TypingDNA.mouse.scrollStarted == true) { 242 | var currentTime = (new Date).getTime(); 243 | TypingDNA.mouse.scrollTimes.push(currentTime); 244 | TypingDNA.mouse.scrollTopArr.push(TypingDNA.document.body.scrollTop); 245 | clearInterval(TypingDNA.scrollInterval); 246 | TypingDNA.scrollInterval = setInterval(TypingDNA.mouse.checkScroll, TypingDNA.maxScrollDeltaTime); 247 | } else { 248 | TypingDNA.mouse.scrollStarted = true 249 | } 250 | } 251 | } 252 | } 253 | 254 | TypingDNA.mouseMove = function(e) { 255 | if (TypingDNA.mouseRecording == true) { 256 | var currentTime = (new Date).getTime(); 257 | if (TypingDNA.mouseMoveRecording == true) { 258 | if (TypingDNA.mouse.started == true) { 259 | TypingDNA.mouse.times.push(currentTime); 260 | TypingDNA.mouse.xPositions.push(e.screenX); 261 | TypingDNA.mouse.yPositions.push(e.screenY); 262 | clearInterval(TypingDNA.moveInterval); 263 | TypingDNA.moveInterval = setInterval(TypingDNA.mouse.checkMove, TypingDNA.maxMoveDeltaTime); 264 | } else { 265 | TypingDNA.mouse.started = true; 266 | } 267 | } 268 | TypingDNA.lastMouseMoveTime = currentTime; 269 | } 270 | } 271 | 272 | TypingDNA.mouseDown = function(e) { 273 | if (TypingDNA.mouseRecording == true) { 274 | TypingDNA.mouse.checkMove(); 275 | TypingDNA.mouse.checkScroll(); 276 | if (e.which == 1) { 277 | TypingDNA.lastMouseDownTime = (new Date).getTime(); 278 | var stopTime = TypingDNA.lastMouseDownTime - TypingDNA.lastMouseMoveTime; 279 | if (stopTime < TypingDNA.maxStopTime && TypingDNA.lastMouseStop == false) { 280 | TypingDNA.stopTimes.push(stopTime); 281 | if (TypingDNA.mouseMoveRecording == true) { 282 | var eventType = 3; 283 | var arr = [eventType, stopTime]; 284 | TypingDNA.mouse.history.add(arr); 285 | TypingDNA.lastMouseStop = true; 286 | } 287 | } 288 | } 289 | } 290 | } 291 | 292 | TypingDNA.mouseUp = function(e) { 293 | if (TypingDNA.mouseRecording == true) { 294 | if (e.which == 1) { 295 | var clickTime = (new Date).getTime() - TypingDNA.lastMouseDownTime; 296 | if (clickTime < TypingDNA.maxClickTime && TypingDNA.lastMouseDownTime > TypingDNA.lastMouseMoveTime) { 297 | TypingDNA.clickTimes.push(clickTime); 298 | if (TypingDNA.mouseMoveRecording == true) { 299 | var eventType = 4; 300 | var arr = [eventType, clickTime]; 301 | TypingDNA.mouse.history.add(arr); 302 | } 303 | } 304 | } 305 | } 306 | } 307 | 308 | TypingDNA.ACKeyDown = function(e) { 309 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 310 | return; 311 | } 312 | TypingDNA.ACSeekTime = (new Date).getTime() - TypingDNA.ACLastTime; 313 | TypingDNA.ACLastTime = (new Date).getTime(); 314 | if (!TypingDNA.isTarget(e.target.id)) { 315 | return; 316 | } 317 | if (e.keyCode == 229) { 318 | TypingDNA.ACFlagDownTI = 1; 319 | } 320 | } 321 | 322 | TypingDNA.ACTextInput = function(e) { 323 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 324 | return; 325 | } 326 | if (TypingDNA.ACFlagDownTI = 1) { 327 | TypingDNA.ACLastCharCode = e.data.charCodeAt(0); 328 | TypingDNA.ACLastKeyCode = e.data.toUpperCase().charCodeAt(0); 329 | TypingDNA.ACFlagDownTI = 0; 330 | } 331 | } 332 | 333 | TypingDNA.ACKeyUp = function(e) { 334 | if (!TypingDNA.recording && !TypingDNA.diagramRecording) { 335 | return; 336 | } 337 | TypingDNA.ACPressTime = (new Date).getTime() - TypingDNA.ACLastTime; 338 | TypingDNA.ACLastTime = (new Date).getTime(); 339 | if (!TypingDNA.isTarget(e.target.id)) { 340 | return; 341 | } 342 | var keyCode = e.keyCode; 343 | if (keyCode == 229 && TypingDNA.ACFlagDownTI == 0) { 344 | keyCode = TypingDNA.ACLastKeyCode; 345 | } 346 | var arr = [keyCode, TypingDNA.ACSeekTime, TypingDNA.ACPressTime, TypingDNA.ACPreviousKeyCode, TypingDNA.ACLastTime]; 347 | TypingDNA.history.add(arr); 348 | TypingDNA.ACPreviousKeyCode = keyCode; 349 | if (TypingDNA.diagramRecording == true && TypingDNA.ACFlagDownTI == 0) { 350 | var arrD = [keyCode, TypingDNA.ACSeekTime, TypingDNA.ACPressTime, TypingDNA.ACLastCharCode]; 351 | TypingDNA.history.addDiagram(arrD); 352 | } 353 | } 354 | 355 | TypingDNA.isTarget = function(target) { 356 | if (TypingDNA.lastTarget == target && TypingDNA.lastTargetFound) { 357 | return true; 358 | } else { 359 | var targetLength = TypingDNA.targetIds.length; 360 | var targetFound = false; 361 | if (targetLength > 0) { 362 | for (var i = 0; i < targetLength; i++) { 363 | if (TypingDNA.targetIds[i] == target) { 364 | targetFound = true; 365 | break; 366 | } 367 | } 368 | TypingDNA.lastTarget = target; 369 | TypingDNA.lastTargetFound = targetFound; 370 | return targetFound; 371 | } else { 372 | TypingDNA.lastTarget = target; 373 | TypingDNA.lastTargetFound = true; 374 | return true; 375 | } 376 | } 377 | } 378 | 379 | if (TypingDNA.document.addEventListener) { 380 | TypingDNA.document.addEventListener("keydown", TypingDNA.keyDown); 381 | TypingDNA.document.addEventListener("keyup", TypingDNA.keyUp); 382 | TypingDNA.document.addEventListener("keypress", TypingDNA.keyPress); 383 | TypingDNA.document.addEventListener("mousemove", TypingDNA.mouseMove); 384 | TypingDNA.document.addEventListener("mousedown", TypingDNA.mouseDown); 385 | TypingDNA.document.addEventListener("mouseup", TypingDNA.mouseUp); 386 | TypingDNA.document.addEventListener("scroll", TypingDNA.mouseScroll); 387 | } else if (TypingDNA.document.attachEvent) { 388 | TypingDNA.document.attachEvent("onkeydown", TypingDNA.keyDown); 389 | TypingDNA.document.attachEvent("onkeyup", TypingDNA.keyUp); 390 | TypingDNA.document.attachEvent("onkeypress", TypingDNA.keyPress); 391 | TypingDNA.document.attachEvent("onmousemove", TypingDNA.mouseMove); 392 | TypingDNA.document.attachEvent("onmousedown", TypingDNA.mouseDown); 393 | TypingDNA.document.attachEvent("onmouseup", TypingDNA.mouseUp); 394 | TypingDNA.document.attachEvent("onscroll", TypingDNA.mouseScroll); 395 | } else { 396 | console.log("browser not supported"); 397 | } 398 | 399 | TypingDNA.mouse = {}; 400 | TypingDNA.mouse.times = []; 401 | TypingDNA.mouse.xPositions = []; 402 | TypingDNA.mouse.yPositions = []; 403 | TypingDNA.mouse.scrollTimes = []; 404 | TypingDNA.mouse.scrollTopArr = []; 405 | TypingDNA.mouse.history = {}; 406 | TypingDNA.mouse.history.stack = []; 407 | 408 | TypingDNA.mouse.getDistance = function(a, b) { 409 | return Math.sqrt((a * a) + (b * b)); 410 | } 411 | 412 | TypingDNA.mouse.getTotalDistance = function(xPositions, yPositions) { 413 | var totalDistance = 0; 414 | var length = xPositions.length; 415 | for (i = 1; i < length - 1; i++) { 416 | var a = xPositions[i] - xPositions[i - 1]; 417 | var b = yPositions[i] - yPositions[i - 1]; 418 | totalDistance += TypingDNA.mouse.getDistance(a, b); 419 | } 420 | return totalDistance; 421 | } 422 | 423 | TypingDNA.mouse.getAngle = function(xPosDelta, yPosDelta) { 424 | var angle = 0; 425 | var leftRight = (xPosDelta >= 0); // 1 if left, 0 if right 426 | var downUp = (yPosDelta < 0); // 1 if down, 0 if up 427 | if (leftRight) { 428 | if (downUp) { 429 | angle = 180 + (Math.round(Math.atan(Math.abs(xPosDelta) / (Math.abs(yPosDelta) + 0.0000001)) / 0.01745329251)) // 0.01745329251 = pi/180 430 | } else { 431 | angle = 270 + (90 - Math.round(Math.atan(Math.abs(xPosDelta) / (Math.abs(yPosDelta) + 0.0000001)) / 0.01745329251)) 432 | } 433 | } else { 434 | if (downUp) { 435 | angle = 90 + (90 - Math.round(Math.atan(Math.abs(xPosDelta) / (Math.abs(yPosDelta) + 0.0000001)) / 0.01745329251)) 436 | } else { 437 | angle = Math.round(Math.atan(Math.abs(xPosDelta) / (Math.abs(yPosDelta) + 0.0000001)) / 0.01745329251) 438 | } 439 | } 440 | return angle; 441 | } 442 | 443 | TypingDNA.mouse.recordMoveAction = function() { 444 | var length = TypingDNA.mouse.times.length; 445 | if (length < 3) { 446 | return; 447 | } 448 | var deltaTime = TypingDNA.mouse.times[length - 1] - TypingDNA.mouse.times[0]; 449 | var xPosDelta = TypingDNA.mouse.xPositions[length - 1] - TypingDNA.mouse.xPositions[0]; 450 | var yPosDelta = TypingDNA.mouse.yPositions[length - 1] - TypingDNA.mouse.yPositions[0]; 451 | var directDistance = Math.round(TypingDNA.mouse.getDistance(xPosDelta, yPosDelta)); 452 | var totalDistance = Math.round(TypingDNA.mouse.getTotalDistance(TypingDNA.mouse.xPositions, TypingDNA.mouse.yPositions)); 453 | var ratioDistance = Math.round(totalDistance * 100 / directDistance); 454 | var speed = Math.round(directDistance * 100 / deltaTime); 455 | var angle = TypingDNA.mouse.getAngle(xPosDelta, yPosDelta); 456 | var eventType = 1; 457 | var arr = [eventType, deltaTime, directDistance, speed, angle, ratioDistance]; 458 | for (i in arr) { 459 | if (isNaN(arr[i])) { 460 | return; 461 | } 462 | } 463 | TypingDNA.mouse.history.add(arr); 464 | TypingDNA.lastMouseStop = false; 465 | } 466 | 467 | TypingDNA.mouse.recordScrollAction = function() { 468 | var length = TypingDNA.mouse.scrollTimes.length; 469 | if (length < 2) { 470 | return; 471 | } 472 | var deltaTime = TypingDNA.mouse.scrollTimes[length - 1] - TypingDNA.mouse.scrollTimes[0]; 473 | var directDistance = TypingDNA.mouse.scrollTopArr[length - 1] - TypingDNA.mouse.scrollTopArr[0]; 474 | var speed = Math.round(directDistance * 100 / deltaTime); 475 | var eventType = 2; 476 | var arr = [eventType, deltaTime, directDistance, speed]; 477 | for (i in arr) { 478 | if (isNaN(arr[i]) && !isFinite(arr[i])) { 479 | return; 480 | } 481 | } 482 | TypingDNA.mouse.history.add(arr); 483 | } 484 | 485 | TypingDNA.mouse.history.add = function(arr) { 486 | this.stack.push(arr); 487 | if (this.stack.length > TypingDNA.maxMouseHistoryLength) { 488 | this.stack.shift(); 489 | } 490 | } 491 | 492 | TypingDNA.mouse.history.getDiagram = function() { 493 | var mouseDiagram = this.stack.join("|"); 494 | return [String(TypingDNA.isMobile()) + "," + String(TypingDNA.version), mouseDiagram].join("|"); 495 | } 496 | 497 | TypingDNA.mouse.clearLastMove = function() { 498 | TypingDNA.mouse.times = []; 499 | TypingDNA.mouse.xPositions = []; 500 | TypingDNA.mouse.yPositions = []; 501 | } 502 | 503 | TypingDNA.mouse.checkMove = function() { 504 | clearInterval(TypingDNA.moveInterval); 505 | if (TypingDNA.mouse.started == true) { 506 | TypingDNA.mouse.started = false; 507 | TypingDNA.mouse.recordMoveAction(); 508 | TypingDNA.mouse.clearLastMove(); 509 | } 510 | } 511 | 512 | TypingDNA.mouse.clearLastScroll = function() { 513 | TypingDNA.mouse.scrollTimes = []; 514 | TypingDNA.mouse.scrollTopArr = []; 515 | } 516 | 517 | TypingDNA.mouse.checkScroll = function() { 518 | clearInterval(TypingDNA.scrollInterval); 519 | if (TypingDNA.mouse.scrollStarted == true) { 520 | TypingDNA.mouse.scrollStarted = false; 521 | TypingDNA.mouse.recordScrollAction(); 522 | TypingDNA.mouse.clearLastScroll(); 523 | } 524 | } 525 | 526 | /** 527 | * Adds a target to the targetIds array. 528 | */ 529 | TypingDNA.addTarget = function(target) { 530 | var targetLength = TypingDNA.targetIds.length; 531 | var targetFound = false; 532 | if (targetLength > 0) { 533 | for (var i = 0; i < targetLength; i++) { 534 | if (TypingDNA.targetIds[i] == target) { 535 | targetFound = true; 536 | break; 537 | } 538 | } 539 | if (!targetFound) { 540 | TypingDNA.targetIds.push(target); 541 | } 542 | } else { 543 | TypingDNA.targetIds.push(target); 544 | } 545 | } 546 | 547 | /** 548 | * Adds a target to the targetIds array. 549 | */ 550 | TypingDNA.removeTarget = function(target) { 551 | var targetLength = TypingDNA.targetIds.length; 552 | if (targetLength > 0) { 553 | for (var i = 0; i < targetLength; i++) { 554 | if (TypingDNA.targetIds[i] == target) { 555 | TypingDNA.targetIds.splice(i, 1); 556 | break; 557 | } 558 | } 559 | } 560 | } 561 | 562 | /** 563 | * Resets the history stack 564 | */ 565 | TypingDNA.reset = function(all) { 566 | TypingDNA.history.stack = []; 567 | TypingDNA.history.stackDiagram = []; 568 | TypingDNA.clickTimes = []; 569 | TypingDNA.stopTimes = []; 570 | if (all == true) { 571 | TypingDNA.mouse.history.stack = []; 572 | } 573 | } 574 | 575 | /** 576 | * Automatically called at initilization. It starts the recording of keystrokes. 577 | */ 578 | TypingDNA.start = function() { 579 | return TypingDNA.recording = true; 580 | } 581 | 582 | /** 583 | * Ends the recording of further keystrokes. To restart recording afterwards you can 584 | * either call TypingDNA.start() or create a new TypingDNA object again, not recommended. 585 | */ 586 | TypingDNA.stop = function() { 587 | return TypingDNA.recording = false; 588 | } 589 | 590 | /** 591 | * Starts the recording of mouse activity. 592 | */ 593 | TypingDNA.startMouse = function() { 594 | return TypingDNA.mouseRecording = TypingDNA.mouseMoveRecording = true; 595 | } 596 | 597 | /** 598 | * Stops the recording of mouse activity. 599 | */ 600 | TypingDNA.stopMouse = function() { 601 | return TypingDNA.mouseRecording = TypingDNA.mouseMoveRecording = false; 602 | } 603 | 604 | /** 605 | * Starts the recording of a non-fixed diagram typing pattern. 606 | */ 607 | TypingDNA.startDiagram = function() { 608 | return TypingDNA.diagramRecording = true; 609 | } 610 | 611 | /** 612 | * Ends the recording of a non-fixed diagram typing pattern. 613 | */ 614 | TypingDNA.stopDiagram = function() { 615 | return TypingDNA.diagramRecording = false; 616 | } 617 | 618 | /** 619 | * This function outputs the linear diagram typing pattern as a String 620 | * @param {String} str Optional: The string represented by the diagram 621 | * The function checks for the exact string (with minor typos) in the recorded 622 | * history. Any letters that are not included in the string will be ommited from 623 | * the output diagram typing pattern. 624 | * @return {String} The TypingDNA linear diagram typing pattern, comma separated. 625 | * A non-fixed vector of only numeric values separated by commas. 626 | * @example var typingPattern = tdna.getDiagram(); 627 | * @example var typingPattern = tdna.getDiagram("Hello5g21?*"); 628 | */ 629 | TypingDNA.getDiagram = function(str) { 630 | return TypingDNA.history.getDiagram(false, str); 631 | } 632 | 633 | /** 634 | * This function outputs the extended linear diagram typing pattern as a String 635 | * Compared to getDiagram, it includes char codes (not safe for storage) 636 | * @param {String} str Optional: The string represented by the diagram 637 | * The function checks for the exact string (with minor typos) in the recorded 638 | * history. Any letters that are not included in the string will be ommited from 639 | * the output diagram typing pattern. 640 | * @return {String} The TypingDNA linear diagram typing pattern, comma separated. 641 | * A non-fixed vector of only numeric values separated by commas. 642 | * @example var typingPattern = tdna.getExtendedDiagram(); 643 | * @example var typingPattern = tdna.getExtendedDiagram("Hello5g21?*"); 644 | */ 645 | TypingDNA.getExtendedDiagram = function(str) { 646 | return TypingDNA.history.getDiagram(true, str); 647 | } 648 | 649 | TypingDNA.getMouseDiagram = function(str) { 650 | return TypingDNA.mouse.history.getDiagram(); 651 | } 652 | 653 | /** 654 | * This function outputs the typing pattern as a String, in a new basic structure for 655 | * easy storage and usage in any kind of keystroke dynamics applications (e.g. typing 656 | * pattern matching, user recognition) 657 | * @param {Number} length Optional: The amount of history keystrokes to use for the 658 | * typing pattern. By default it will use the last 500 recorded keystrokes (or as many 659 | * available if less than 500). 660 | * @return {String} The TypingDNA typing pattern, comma separated. 661 | * A fixed vector of only numeric values separated by commas. 662 | * @example var typingPattern = tdna.get(); 663 | * @example var typingPattern = tdna.get(200); 664 | */ 665 | TypingDNA.get = function(length) { 666 | var historyTotalLength = TypingDNA.history.stack.length; 667 | if (length == undefined) { 668 | length = TypingDNA.defaultHistoryLength; 669 | } 670 | if (length > historyTotalLength) { 671 | length = historyTotalLength; 672 | } 673 | var obj = {}; 674 | obj.arr = TypingDNA.history.get(length); 675 | var zl = TypingDNA.zl; 676 | var histRev = length; 677 | var histSktF = TypingDNA.math.fo(TypingDNA.history.get(length, "seek")); 678 | var histPrtF = TypingDNA.math.fo(TypingDNA.history.get(length, "press")); 679 | var pressHistMean = Math.round(TypingDNA.math.avg(histPrtF)); 680 | var seekHistMean = Math.round(TypingDNA.math.avg(histSktF)); 681 | var pressHistSD = Math.round(TypingDNA.math.sd(histPrtF)); 682 | var seekHistSD = Math.round(TypingDNA.math.sd(histSktF)); 683 | var charMeanTime = seekHistMean + pressHistMean; 684 | var pressRatio = TypingDNA.math.rd((pressHistMean + zl) / (charMeanTime + zl), 4); 685 | var seekToPressRatio = TypingDNA.math.rd((1 - pressRatio) / pressRatio, 4); 686 | var pressSDToPressRatio = TypingDNA.math.rd((pressHistSD + zl) / (pressHistMean + zl), 4); 687 | var seekSDToPressRatio = TypingDNA.math.rd((seekHistSD + zl) / (pressHistMean + zl), 4); 688 | var cpm = Math.round(6E4 / (charMeanTime + zl)); 689 | for (var i in obj.arr) { 690 | var rev = obj.arr[i][1].length; 691 | var seekMean = 0; 692 | var pressMean = 0; 693 | var postMean = 0; 694 | var seekSD = 0; 695 | var pressSD = 0; 696 | var postSD = 0; 697 | switch (obj.arr[i][0].length) { 698 | case 0: 699 | break; 700 | case 1: 701 | var seekMean = TypingDNA.math.rd((obj.arr[i][0][0] + zl) / (seekHistMean + zl), 4); 702 | break; 703 | default: 704 | var arr = TypingDNA.math.fo(obj.arr[i][0]); 705 | seekMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (seekHistMean + zl), 4); 706 | seekSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (seekHistSD + zl), 4); 707 | } 708 | switch (obj.arr[i][1].length) { 709 | case 0: 710 | break; 711 | case 1: 712 | var pressMean = TypingDNA.math.rd((obj.arr[i][1][0] + zl) / (pressHistMean + zl), 4); 713 | break; 714 | default: 715 | var arr = TypingDNA.math.fo(obj.arr[i][1]); 716 | pressMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (pressHistMean + zl), 4); 717 | pressSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (pressHistSD + zl), 4); 718 | } 719 | switch (obj.arr[i][2].length) { 720 | case 0: 721 | break; 722 | case 1: 723 | var postMean = TypingDNA.math.rd((obj.arr[i][2][0] + zl) / (seekHistMean + zl), 4); 724 | break; 725 | default: 726 | var arr = TypingDNA.math.fo(obj.arr[i][2]); 727 | postMean = TypingDNA.math.rd((TypingDNA.math.avg(arr) + zl) / (seekHistMean + zl), 4); 728 | postSD = TypingDNA.math.rd((TypingDNA.math.sd(arr) + zl) / (seekHistSD + zl), 4); 729 | } 730 | delete obj.arr[i][2]; 731 | delete obj.arr[i][1]; 732 | delete obj.arr[i][0]; 733 | obj.arr[i][0] = rev; 734 | obj.arr[i][1] = seekMean; 735 | obj.arr[i][2] = pressMean; 736 | obj.arr[i][3] = postMean; 737 | obj.arr[i][4] = seekSD; 738 | obj.arr[i][5] = pressSD; 739 | obj.arr[i][6] = postSD; 740 | } 741 | var arr = []; 742 | TypingDNA.apu(arr, histRev); 743 | TypingDNA.apu(arr, cpm); 744 | TypingDNA.apu(arr, charMeanTime); 745 | TypingDNA.apu(arr, pressRatio); 746 | TypingDNA.apu(arr, seekToPressRatio); 747 | TypingDNA.apu(arr, pressSDToPressRatio); 748 | TypingDNA.apu(arr, seekSDToPressRatio); 749 | TypingDNA.apu(arr, pressHistMean); 750 | TypingDNA.apu(arr, seekHistMean); 751 | TypingDNA.apu(arr, pressHistSD); 752 | TypingDNA.apu(arr, seekHistSD); 753 | for (var c = 0; c <= 6; c++) { 754 | for (var i = 0; i < 44; i++) { 755 | var keyCode = TypingDNA.keyCodes[i]; 756 | var val = obj.arr[keyCode][c]; 757 | if (val == 0 && c > 0) { 758 | val = 1; 759 | } 760 | TypingDNA.apu(arr, val); 761 | } 762 | } 763 | TypingDNA.apu(arr, TypingDNA.isMobile()); 764 | TypingDNA.apu(arr, TypingDNA.version); 765 | TypingDNA.apu(arr, TypingDNA.flags); 766 | arr.push(TypingDNA.history.getSpecialKeys()); 767 | return arr.join(","); 768 | } 769 | 770 | TypingDNA.apu = function(arr, val) { 771 | "NaN" == String(val) && (val = 0); 772 | arr.push(val); 773 | } 774 | 775 | TypingDNA.math = {}; 776 | 777 | TypingDNA.math.rd = function(val, dec) { 778 | return Number(val.toFixed(dec)); 779 | } 780 | 781 | TypingDNA.math.avg = function(arr) { 782 | var len = arr.length; 783 | if (len > 0) { 784 | var sum = 0; 785 | for (var i = 0; i < len; i++) { 786 | sum += arr[i]; 787 | } 788 | return this.rd(sum / len, 4); 789 | } else { 790 | return 0; 791 | } 792 | } 793 | 794 | TypingDNA.math.sd = function(arr) { 795 | var len = arr.length; 796 | if (len < 2) { 797 | return 0; 798 | } else { 799 | var sumVS = 0; 800 | var mean = this.avg(arr); 801 | for (var i = 0; i < len; i++) { 802 | sumVS += (arr[i] - mean) * (arr[i] - mean); 803 | } 804 | var sd = Math.sqrt(sumVS / len); 805 | return sd; 806 | } 807 | } 808 | 809 | TypingDNA.math.fo = function(arr) { 810 | if (arr.length > 1) { 811 | var values = arr.concat(); 812 | var len = arr.length; 813 | values.sort(function(a, b) { 814 | return a - b; 815 | }); 816 | var asd = this.sd(values); 817 | var aMean = values[Math.ceil(arr.length / 2)]; 818 | var multiplier = 2; 819 | var maxVal = aMean + multiplier * asd; 820 | var minVal = aMean - multiplier * asd; 821 | if (len < 20) { 822 | minVal = 0; 823 | } 824 | var fVal = []; 825 | for (var i = 0; i < len; i++) { 826 | var tempval = values[i]; 827 | if (tempval < maxVal && tempval > minVal) { 828 | fVal.push(tempval); 829 | } 830 | } 831 | return fVal; 832 | } else { 833 | return arr; 834 | } 835 | } 836 | 837 | TypingDNA.history = {}; 838 | TypingDNA.history.stack = []; 839 | TypingDNA.history.stackDiagram = []; 840 | 841 | TypingDNA.history.add = function(arr) { 842 | this.stack.push(arr); 843 | if (this.stack.length > TypingDNA.maxHistoryLength) { 844 | this.stack.shift(); 845 | } 846 | } 847 | 848 | TypingDNA.history.addDiagram = function(arr) { 849 | this.stackDiagram.push(arr); 850 | } 851 | 852 | TypingDNA.history.getDiagram = function(extended, str) { 853 | var returnArr = []; 854 | var diagramType = (extended) ? 1 : 0; 855 | returnArr.push([TypingDNA.isMobile(), TypingDNA.version, TypingDNA.flags, TypingDNA.history.getSpecialKeys(), diagramType]); 856 | var diagramHistoryLength = this.stackDiagram.length; 857 | var targetLength = TypingDNA.targetIds.length; 858 | if (str == undefined && targetLength > 0) { 859 | str = ""; 860 | for (var i = 0; i < targetLength; i++) { 861 | var element = TypingDNA.document.getElementById(TypingDNA.targetIds[i]); 862 | if (element != null) { 863 | str += element.value; 864 | } 865 | } 866 | } 867 | if (str != undefined) { 868 | var solvedAstr = []; 869 | var lastPos = 0; 870 | for (var i = 0; i < str.length; i++) { 871 | var currentCharCode = str.charCodeAt(i); 872 | for (var j = lastPos; j < diagramHistoryLength; j++) { 873 | var arr = this.stackDiagram[j]; 874 | var charCode = arr[3]; 875 | if (charCode == currentCharCode) { 876 | if (j == lastPos) { 877 | lastPos++; 878 | } 879 | var keyCode = arr[0]; 880 | var seekTime = arr[1]; 881 | var pressTime = arr[2]; 882 | if (extended) { 883 | returnArr.push([charCode, seekTime, pressTime, keyCode]); 884 | } else { 885 | returnArr.push([seekTime, pressTime]); 886 | } 887 | break; 888 | } 889 | } 890 | } 891 | } else { 892 | for (var i = 0; i < diagramHistoryLength; i++) { 893 | var arr = this.stackDiagram[i]; 894 | var keyCode = arr[0]; 895 | var seekTime = arr[1]; 896 | var pressTime = arr[2]; 897 | if (extended) { 898 | var charCode = arr[3]; 899 | returnArr.push([charCode, seekTime, pressTime, keyCode]); 900 | } else { 901 | returnArr.push([seekTime, pressTime]); 902 | } 903 | } 904 | } 905 | return returnArr.join("|"); 906 | } 907 | 908 | TypingDNA.history.get = function(length, type) { 909 | var historyTotalLength = this.stack.length; 910 | if (length == 0 || length == undefined) { 911 | length = TypingDNA.defaultHistoryLength; 912 | } 913 | if (length > historyTotalLength) { 914 | length = historyTotalLength; 915 | } 916 | switch (type) { 917 | case "seek": 918 | var seekArr = []; 919 | for (i = 1; i <= length; i++) { 920 | var seekTime = this.stack[historyTotalLength - i][1]; 921 | if (seekTime <= TypingDNA.maxSeekTime) { 922 | seekArr.push(seekTime); 923 | } 924 | }; 925 | return seekArr; 926 | break; 927 | case "press": 928 | var pressArr = []; 929 | for (i = 1; i <= length; i++) { 930 | var pressTime = this.stack[historyTotalLength - i][2]; 931 | if (pressTime <= TypingDNA.maxPressTime) { 932 | pressArr.push(pressTime); 933 | } 934 | }; 935 | return pressArr; 936 | break; 937 | default: 938 | var historyStackObj = {}; 939 | for (var i in TypingDNA.keyCodes) { 940 | historyStackObj[TypingDNA.keyCodes[i]] = [ 941 | [], 942 | [], 943 | [] 944 | ]; 945 | } 946 | for (i = 1; i <= length; i++) { 947 | var arr = this.stack[historyTotalLength - i]; 948 | var keyCode = arr[0]; 949 | var seekTime = arr[1]; 950 | var pressTime = arr[2]; 951 | var prevKeyCode = arr[3]; 952 | if (TypingDNA.keyCodesObj[keyCode]) { 953 | if (seekTime <= TypingDNA.maxSeekTime) { 954 | historyStackObj[keyCode][0].push(seekTime); 955 | if (prevKeyCode != 0 && TypingDNA.keyCodesObj[prevKeyCode]) { 956 | historyStackObj[prevKeyCode][2].push(seekTime); 957 | } 958 | } 959 | if (pressTime <= TypingDNA.maxPressTime) { 960 | historyStackObj[keyCode][1].push(pressTime); 961 | } 962 | } 963 | }; 964 | return historyStackObj; 965 | } 966 | } 967 | 968 | TypingDNA.history.getSpecialKeys = function() { 969 | var returnArr = []; 970 | var length = this.stack.length; 971 | var historyStackObj = {}; 972 | for (var i in TypingDNA.spKeyCodes) { 973 | historyStackObj[TypingDNA.spKeyCodes[i]] = [ 974 | [], 975 | ]; 976 | } 977 | for (i = 1; i <= length; i++) { 978 | var arr = this.stack[length - i]; 979 | if (TypingDNA.spKeyCodesObj[arr[0]]) { 980 | var keyCode = arr[0]; 981 | var pressTime = arr[2]; 982 | if (pressTime <= TypingDNA.maxPressTime) { 983 | historyStackObj[keyCode][0].push(pressTime); 984 | } 985 | } 986 | } 987 | for (var i in TypingDNA.spKeyCodes) { 988 | var arr = TypingDNA.math.fo(historyStackObj[TypingDNA.spKeyCodes[i]][0]); 989 | var arrLen = arr.length; 990 | returnArr.push(arrLen); 991 | if (arrLen > 1) { 992 | returnArr.push(Math.round(TypingDNA.math.avg(arr))); 993 | returnArr.push(Math.round(TypingDNA.math.sd(arr))); 994 | } else if (arrLen == 1) { 995 | returnArr.push([arr[0], -1]); 996 | } else { 997 | returnArr.push([-1, -1]); 998 | } 999 | } 1000 | var clicksArrLen = TypingDNA.clickTimes.length; 1001 | returnArr.push(clicksArrLen); 1002 | if (clicksArrLen > 1) { 1003 | returnArr.push(Math.round(TypingDNA.math.avg(TypingDNA.clickTimes))); 1004 | returnArr.push(Math.round(TypingDNA.math.sd(TypingDNA.clickTimes))); 1005 | } else if (clicksArrLen == 1) { 1006 | returnArr.push(TypingDNA.clickTimes[0], -1); 1007 | } else { 1008 | returnArr.push([-1, -1]); 1009 | } 1010 | var stopArrLen = TypingDNA.stopTimes.length; 1011 | returnArr.push(stopArrLen); 1012 | if (stopArrLen > 1) { 1013 | returnArr.push(Math.round(TypingDNA.math.avg(TypingDNA.stopTimes))); 1014 | returnArr.push(Math.round(TypingDNA.math.sd(TypingDNA.stopTimes))); 1015 | } else if (stopArrLen == 1) { 1016 | returnArr.push(TypingDNA.stopTimes[0], -1); 1017 | } else { 1018 | returnArr.push([-1, -1]); 1019 | } 1020 | return returnArr; 1021 | } 1022 | 1023 | /** 1024 | * Checks the quality of a typing pattern, how well it is revelated, how useful the 1025 | * information will be for matching applications. It returns a value between 0 and 1. 1026 | * Values over 0.3 are acceptable, however a value over 0.7 shows good pattern strength. 1027 | * @param {String} typingPattern The typing pattern string returned by the get() function. 1028 | * @return {Number} A real number between 0 and 1. A close to 1 value means a stronger pattern. 1029 | * @example var quality = tdna.getQuality(typingPattern); 1030 | */ 1031 | TypingDNA.getQuality = function(typingPattern) { 1032 | var obj = typingPattern.split(","); 1033 | for (i = 0; i < obj.length; i++) { 1034 | obj[i] = Number(obj[i]); 1035 | } 1036 | var totalEvents = obj[0]; 1037 | var acc = rec = avgAcc = 0; 1038 | var avg = TypingDNA.math.avg(obj); 1039 | var revs = obj.slice(11, 55); 1040 | for (var i in revs) { 1041 | rec += Number(revs[i] > 0); 1042 | acc += Number(revs[i] > 4); 1043 | avgAcc += Number(revs[i] > avg); 1044 | } 1045 | var tReturn = Math.sqrt(rec * acc * avgAcc) / 80; 1046 | return tReturn > 1 ? 1 : tReturn; 1047 | } 1048 | 1049 | /** 1050 | * Checks the validity of a typing pattern if recorded on mobile. 1051 | * @param {String} typingPattern The typing pattern string returned by the get() function. 1052 | * @return {Number} A real number between 0 and 1. A number larger than 0.7 usually means a valid pattern. 1053 | * @example var quality = tdna.checkMobileValidity(typingPattern); 1054 | */ 1055 | TypingDNA.checkMobileValidity = function(typingPattern) { 1056 | var obj = typingPattern.split(","); 1057 | var totalEvents = obj[0]; 1058 | if (totalEvents == 0) { 1059 | return 0; 1060 | } 1061 | var rec = 0; 1062 | var revs = obj.slice(11, 55); 1063 | for (var i in revs) { 1064 | rec += Number(revs[i]); 1065 | } 1066 | return rec/totalEvents; 1067 | } 1068 | 1069 | TypingDNA.getLength = function(typingPattern) { 1070 | return Number(typingPattern.split(",")[1]); 1071 | } 1072 | 1073 | TypingDNA.isMobile = function() { 1074 | if (TypingDNA.mobile != undefined) { 1075 | return TypingDNA.mobile; 1076 | } else { 1077 | var check = 0; 1078 | (function(a) { 1079 | if ( 1080 | /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i 1081 | .test(a) || 1082 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i 1083 | .test(a.substr(0, 4))) { 1084 | check = 1 1085 | } 1086 | })(navigator.userAgent || navigator.vendor || window.opera); 1087 | TypingDNA.mobile = check; 1088 | return check; 1089 | } 1090 | } 1091 | } else { 1092 | // TypingDNA is a static class, currently doesn't support actual multiple instances (Singleton implementation) 1093 | return TypingDNA.instance; 1094 | } 1095 | } 1096 | --------------------------------------------------------------------------------