import { AfterViewInit , Component , ElementRef , Input , Renderer2 , ViewChild } from '@angular/core' ; import { Observable , Subscriber } from 'rxjs' ; import entropyCalculatorWorkerJS from '../../../assets/workers/entropyCalculatorWorker' ; import { Entropy , IEntropyGenerator } from './../../services/entropy/IEntropyGenerator' ; const blobURL = window . URL . createObjectURL ( new Blob ( [ entropyCalculatorWorkerJS ] ) ) ; const entropyCalculatorWorker = new Worker ( blobURL ) ; @ Component ( { selector : 'airgap-touch-entropy' , templateUrl : './touch-entropy.component.html' , styleUrls : [ './touch-entropy.component.scss' ] } ) export class TouchEntropyComponent implements AfterViewInit , IEntropyGenerator { @ Input ( ) public cursorSize : number = 2 ; @ Input ( ) public randomFactorInPercent : number = 10 ; @ Input ( ) public cursorColor : string = 'white' ; @ ViewChild ( 'canvas' , { static : true } ) public canvasRef : ElementRef < HTMLCanvasElement > ; private canvas : HTMLCanvasElement ; private context : CanvasRenderingContext2D ; private rectangle : ClientRect ; private handler : ( numbers : number [ ] ) => void ; private readonly entropyObservable : Observable < Entropy > ; private collectedEntropyPercentage : number = 0 ; private isDrawing : boolean = false ; constructor ( private readonly renderer ) { this . entropyObservable = new Observable ( ( observer ) : void => { entropyCalculatorWorker . onmessage = ( event ) : void => { this . collectedEntropyPercentage += event . data . entropyMeasure ; observer . next ( { entropyHex : event . data . entropyHex } ) ; } ; this . handler = ( numbers ) : void => { const buffer1 = this . arrayBufferFromIntArray ( numbers ) ; entropyCalculatorWorker . postMessage ( { entropyBuffer : buffer1 } , [ buffer1 ] ) ; } ; } ) ; } public ngAfterViewInit ( ) { this . canvas = this . canvasRef . nativeElement ; this . initCanvas ( ) ; } private initCanvas ( ) { if ( ! this . isCanvasReady ( ) ) { setTimeout ( ( ) => { this . initCanvas ( ) ; } , 50 ) ; return ; } this . canvas . setAttribute ( 'height' , `${ this . canvas . parentElement ! . getBoundingClientRect ( ) . height }px` ) ; this . canvas . setAttribute ( 'width' , `${ this . canvas . parentElement ! . getBoundingClientRect ( ) . width }px` ) ; const context = this . canvas . getContext ( '2d' ) ; if ( context ) { this . context = context ; } this . context . fillStyle = this . cursorColor ; this . rectangle = this . canvas . getBoundingClientRect ( ) ; } private isCanvasReady ( ) { if ( this . canvas . parentElement ) { return this . canvas . parentElement . getBoundingClientRect ( ) . height > 0 ; } else { return false ; } } public start ( ) { this . collectedEntropyPercentage = 0 ; return new Promise ( ( resolve ) => { this . renderer . listen ( this . canvas , 'mousedown' , ( ) => { this . isDrawing = true ; } ) ; this . renderer . listen ( this . canvas , 'touchstart' , ( ) => { this . isDrawing = true ; } ) ; this . renderer . listen ( this . canvas , 'mouseup' , ( ) => { this . isDrawing = false ; } ) ; this . renderer . listen ( this . canvas , 'touchend' , ( ) => { this . isDrawing = false ; } ) ; this . renderer . listen ( this . canvas , 'mousemove' , ( event ) => { if ( this . isDrawing ) { this . collectEntropy ( event . clientX , event . clientY ) ; } } ) ; this . renderer . listen ( this . canvas , 'touchmove' , ( event ) => { if ( this . isDrawing ) { this . collectEntropy ( event . touches [ 0 ] . clientX , event . touches [ 0 ] . clientY ) ; } } ) ; resolve ( ) ; } ) ; } public stop ( ) { return new Promise ( ( resolve ) => { this . isDrawing = false ; resolve ( ) ; } ) ; } public collectEntropy ( x , y ) { const currX = Math . ceil ( ( ( x - this . rectangle . left ) / ( this . rectangle . right - this . rectangle . left ) ) * this . canvas . width ) ; const currY = Math . ceil ( ( ( y - this . rectangle . top ) / ( this . rectangle . bottom - this . rectangle . top ) ) * this . canvas . height ) - 56 ; if ( this . getRandomIntInclusive ( 0 , 100 ) <= this . randomFactorInPercent ) { const timeStampInMs = window . performance && window . performance . now && window . performance . timing && window . performance . timing . navigationStart ? window . performance . now ( ) + window . performance . timing . navigationStart : Date . now ( ) ; if ( this . handler ) { this . handler ( [ currX , currY , timeStampInMs ] ) ; } } } public getEntropyUpdateObservable ( ) { return this . entropyObservable ; } private getRandomIntInclusive ( min , max ) { const randomBuffer = new Uint32Array ( 1 ) ; window . crypto . getRandomValues ( randomBuffer ) ; const randomNumber = randomBuffer [ 0 ] / ( 0xffffffff + 1 ) ; const roundedMin = Math . ceil ( min ) ; const roundedMax = Math . floor ( max ) ; return Math . floor ( randomNumber * ( roundedMax - roundedMin + 1 ) ) + roundedMin ; } private arrayBufferFromIntArray ( array ) { const buffer = new ArrayBuffer ( array . length * 2 ) ; const bufView = new Uint8Array ( buffer ) ; for ( let i = 0 ; i < array . length ; i ++ ) { bufView [ i ] = Math . abs ( array [ i ] * 10000 ) ; } return buffer ; } public getCollectedEntropyPercentage ( ) { return this . collectedEntropyPercentage / 10 ; } }