├── 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 |
--------------------------------------------------------------------------------