import { Injectable , NgZone } from '@angular/core' ; import { BarcodeScanner , BarcodeScannerOptions , BarcodeScanResult } from '@fttx/barcode-scanner' ; import { FirebaseAnalytics } from '@ionic-native/firebase-analytics' ; import { InAppBrowser } from '@ionic-native/in-app-browser/ngx' ; import { NativeAudio } from '@ionic-native/native-audio' ; import { Alert , AlertController , Events , Platform } from 'ionic-angular' ; import { AlertInputOptions } from 'ionic-angular/components/alert/alert-options' ; import moment from 'moment' ; import { Observable , Subscriber , Subscription } from 'rxjs' ; import { isNumeric } from 'rxjs/util/isNumeric' ; import { lt , SemVer } from 'semver' ; import * as Supplant from 'supplant' ; import { KeyboardInputComponent } from '../components/keyboard-input/keyboard-input' ; import { OutputBlockModel } from '../models/output-block.model' ; import { OutputProfileModel } from '../models/output-profile.model' ; import { requestModelRemoteComponent , requestModelUndoInfiniteLoop } from '../models/request.model' ; import { responseModel , responseModelRemoteComponentResponse , responseModelUpdateSettings } from '../models/response.model' ; import { ScanModel } from '../models/scan.model' ; import { SelectScanningModePage } from '../pages/scan-session/select-scanning-mode/select-scanning-mode' ; import { Config } from './config' ; import { ServerProvider } from './server' ; import { Settings } from './settings' ; import { AlertButtonType , Utils } from './utils' ; /**
 * The job of this class is to generate a ScanModel by talking with the native
 * barcode scanner plugin and/or by asking the user the required data to fill
 * the data of the selected OutputProfile.
 *
 * The only public method is scan
 */ @ Injectable ( ) export class ScanProvider { public awaitingForBarcode : boolean ; private pluginOptions : BarcodeScannerOptions ; public acqusitionMode : 'manual' | 'single' | 'mixed_continue' | 'continue' = 'manual' ; private barcodeFormats : any [ ] ; private outputProfile : OutputProfileModel ; private outputProfileIndex : number ; private deviceName : string ; private settingsUpdatedDialog : Alert = null ; deprecated private quantityType : 'number' | 'text' ; private keyboardInput : KeyboardInputComponent ; private remoteComponentErrorDialog : Alert = null ; private static lastBarcode : string = null ; private acceptDuplicateBarcodeDialog : Alert = null ; private rememberDuplicatedBarcodeChoiceDialog : Alert = null ; public static INFINITE_LOOP_DETECT_THRESHOLD = 30 ; constructor ( private alertCtrl , private barcodeScanner , private platform , private ngZone , private firebaseAnalytics , public nativeAudio , private settings , public serverProvider , public events , private iab , private utils ) { this . events . subscribe ( responseModel . ACTION_UPDATE_SETTINGS , async ( responseModelUpdateSettings ) => { this . outputProfile = responseModelUpdateSettings . outputProfiles [ this . outputProfileIndex ] ; if ( this . settingsUpdatedDialog != null ) this . settingsUpdatedDialog . dismiss ( ) ; this . settingsUpdatedDialog = this . alertCtrl . create ( { title : await this . utils . text ( 'settingsUpdatedDialogTitle' ) , message : await this . utils . text ( 'settingsUpdatedDialogMessage' ) , buttons : [ await this . utils . text ( 'settingsUpdatedDialogOkButton' ) ] , } ) ; this . settingsUpdatedDialog . present ( ) ; } ) ; } private lastObserver : Subscriber < ScanModel > = null ; private _scanCallId : number = null ; param scanMode param outputProfileIndex param scanSession param keyboardInput scan ( scanMode , outputProfileIndex , scanSession , keyboardInput ) { if ( this . lastObserver ) { this . lastObserver . complete ( ) ; } this . _scanCallId = new Date ( ) . getTime ( ) ; ScanProvider . lastBarcode = null ; this . keyboardInput = keyboardInput ; this . outputProfileIndex = outputProfileIndex ; return new Observable ( observer => { this . lastObserver = observer ; Promise . all ( [ this . settings . getPreferFrontCamera ( ) , this . settings . getEnableLimitBarcodeFormats ( ) , this . settings . getBarcodeFormats ( ) , this . settings . getQuantityType ( ) , this . settings . getContinueModeTimeout ( ) , this . settings . getDeviceName ( ) , this . getOutputProfile ( outputProfileIndex ) , this . settings . getTorchOn ( ) , this . settings . getEnableBeep ( ) , this . settings . getEnableVibrationFeedback ( ) , ] ) . then ( async ( result ) => { let preferFrontCamera = result [ 0 ] ; let enableLimitBarcodeFormats = result [ 1 ] ; this . barcodeFormats = result [ 2 ] ; let quantityType = result [ 3 ] ; let continueModeTimeout = result [ 4 ] ; this . deviceName = result [ 5 ] ; this . outputProfile = result [ 6 ] ; let torchOn = result [ 7 ] ; let enableBeep = result [ 8 ] ; let enableVibrationFeedback = result [ 9 ] ; const blockingComponents = OutputProfileModel . ContainsBlockingComponents ( this . outputProfile ) ; const containsMultipleBarcodeFormats = OutputProfileModel . ContainsMultipleBarcodeFormats ( this . outputProfile ) ; if ( quantityType && quantityType == 'text' ) { this . quantityType = 'text' ; } else { this . quantityType = 'number' ; } switch ( scanMode ) { case SelectScanningModePage . SCAN_MODE_ENTER_MAUALLY : this . acqusitionMode = 'manual' ; break ; case SelectScanningModePage . SCAN_MODE_SINGLE : this . acqusitionMode = 'single' ; break ; case SelectScanningModePage . SCAN_MODE_CONTINUE : { this . acqusitionMode = 'continue' ; if ( blockingComponents || ! this . platform . is ( 'android' ) || continueModeTimeout || containsMultipleBarcodeFormats ) { this . acqusitionMode = 'mixed_continue' ; } break ; } } let initialPluginOptions = { showFlipCameraButton : true , prompt : Config . DEFAULT_ACQUISITION_LABEL , showTorchButton : true , preferFrontCamera : preferFrontCamera , torchOn : torchOn , continuousMode : this . acqusitionMode == 'continue' , disableSuccessBeep : ! enableBeep , vibrationFeedback : enableVibrationFeedback , resultDisplayDuration : 0 } ; if ( enableLimitBarcodeFormats ) { initialPluginOptions . formats = this . barcodeFormats . filter ( barcodeFormat => barcodeFormat . enabled ) . map ( barcodeFormat => barcodeFormat . name ) . join ( ',' ) ; } this . pluginOptions = initialPluginOptions ; let resetAgainCountTimer ; let againCount = 0 ; let prevScanDisplayValue = '' ; let _scanCallId = this . _scanCallId ; let again = async ( ) => { if ( _scanCallId != this . _scanCallId ) { observer . complete ( ) ; return ; } if ( againCount > ScanProvider . INFINITE_LOOP_DETECT_THRESHOLD ) { let wantToContinue = await this . showPreventInfiniteLoopDialog ( ) ; if ( ! wantToContinue ) { observer . complete ( ) ; return ; } } let scan = new ScanModel ( ) ; /**
                     * Warning: DO NOT use this variable below.
                     *
                     * The Output template always neeeds fresh Date values, since it may
                     * get stuck with a blocking component such as BARCODE.
                     *
                     */ let now = new Date ( ) . getTime ( ) ; scan . outputBlocks = JSON . parse ( JSON . stringify ( this . outputProfile . outputBlocks ) ) ; scan . id = now ; scan . repeated = false ; scan . date = now ; let variables = { barcodes : [ ] , barcode : '' , quantity : null , number : null , text : null , /**
                          * We always force the variable to exists, since it is a 'variable'
                          * type and can't be assigned from the output template.
                          *
                          * This is not optimal since the now variable can be outdated at
                          * the time it's injected.
                          *
                          * Note that in the CSV file path injection (server side) it's
                          * handled differently, using the scan session date instead.
                         */ timestamp : ( now * 1000 ) , date : new Date ( now ) . toLocaleDateString ( ) , time : new Date ( now ) . toLocaleTimeString ( ) , date_time : null , scan_session_name : scanSession . name , device_name : this . deviceName , select_option : null , http : null , run : null , csv_lookup : null , csv_update : null , javascript_function : null , } ; for ( let i = 0 ; i < scan . outputBlocks . length ; i ++ ) { let outputBlock = scan . outputBlocks [ i ] ; if ( outputBlock . label ) outputBlock . label = new Supplant ( ) . text ( outputBlock . label , variables ) ; if ( outputBlock . errorMessage ) outputBlock . errorMessage = new Supplant ( ) . text ( outputBlock . errorMessage , variables ) ; if ( outputBlock . value && ! isNumeric ( outputBlock . value ) ) outputBlock . value = new Supplant ( ) . text ( outputBlock . value , variables ) ; if ( outputBlock . defaultValue ) outputBlock . defaultValue = new Supplant ( ) . text ( outputBlock . defaultValue , variables ) ; switch ( outputBlock . type ) { case 'key' : break ; case 'text' : break ; case 'variable' : { switch ( outputBlock . value ) { case 'timestamp' : outputBlock . value = ( new Date ( ) . getTime ( ) * 1000 ) + '' ; break ; case 'date' : outputBlock . value = new Date ( ) . toLocaleDateString ( ) ; break ; case 'time' : outputBlock . value = new Date ( ) . toLocaleTimeString ( ) ; break ; case 'date_time' : outputBlock . value = new Date ( ) . toLocaleTimeString ( ) + ' ' + new Date ( ) . toLocaleDateString ( ) ; break ; case 'deviceName' : outputBlock . value = this . deviceName ; break ; case 'scan_session_name' : outputBlock . value = scanSession . name ; break ; case 'quantity' : case 'number' : { try { outputBlock . value = await this . getField ( outputBlock . label , 'number' , outputBlock . defaultValue , outputBlock . filter , outputBlock . errorMessage ) ; } catch ( err ) { observer . complete ( ) ; return ; } variables . number = outputBlock . value ; variables . quantity = outputBlock . value ; scan . quantity = outputBlock . value ; break ; } case 'text' : { try { outputBlock . value = await this . getField ( outputBlock . label , 'text' , outputBlock . defaultValue , outputBlock . filter , outputBlock . errorMessage ) ; } catch ( err ) { observer . complete ( ) ; return ; } variables . text = outputBlock . value ; break ; } } break ; } case 'date_time' : { outputBlock . value = moment ( new Date ( ) ) . format ( outputBlock . format ) ; variables . date_time = outputBlock . value ; break ; } case 'select_option' : { if ( outputBlock . message ) outputBlock . message = new Supplant ( ) . text ( outputBlock . message , variables ) ; if ( outputBlock . title ) outputBlock . title = new Supplant ( ) . text ( outputBlock . title , variables ) ; outputBlock . value = await this . showSelectOption ( outputBlock ) ; variables . select_option = outputBlock . value ; break ; } case 'function' : { try { outputBlock . value = await this . evalCode ( outputBlock . value , variables ) ; } catch ( error ) { outputBlock . value = '' ; } variables . javascript_function = outputBlock . value ; break ; } case 'barcode' : { try { if ( outputBlock . filter ) outputBlock . filter = new Supplant ( ) . text ( outputBlock . filter , variables ) ; this . pluginOptions . prompt = outputBlock . label || Config . DEFAULT_ACQUISITION_LABEL ; this . keyboardInput . setError ( false ) ; if ( outputBlock . enabledFormats && outputBlock . enabledFormats . length != 0 ) { this . pluginOptions . formats = outputBlock . enabledFormats . join ( ',' ) ; } else { this . pluginOptions . formats = initialPluginOptions . formats ; } let barcode = await this . getBarcode ( outputBlock . label , outputBlock . filter , outputBlock . errorMessage ) ; ScanProvider . lastBarcode = barcode ; if ( _scanCallId != this . _scanCallId ) { observer . complete ( ) ; return ; } delete outputBlock [ 'enabledFormats' ] ; delete outputBlock [ 'filter' ] ; delete outputBlock [ 'errorMessage' ] ; variables . barcode = barcode ; variables . barcodes . push ( barcode ) ; outputBlock . value = barcode ; } catch ( err ) { observer . complete ( ) ; return ; } } case 'delay' : break ; case 'http' : case 'run' : case 'csv_lookup' : case 'csv_update' : { if ( outputBlock . notFoundValue ) outputBlock . notFoundValue = new Supplant ( ) . text ( outputBlock . notFoundValue , variables ) ; if ( outputBlock . newValue ) outputBlock . newValue = new Supplant ( ) . text ( outputBlock . newValue , variables ) ; if ( outputBlock . httpData ) outputBlock . httpData = new Supplant ( ) . text ( outputBlock . httpData , variables ) ; if ( outputBlock . httpHeaders ) outputBlock . httpHeaders = new Supplant ( ) . text ( outputBlock . httpHeaders , variables ) ; if ( outputBlock . httpParams ) outputBlock . httpParams = new Supplant ( ) . text ( outputBlock . httpParams , variables ) ; if ( ! this . serverProvider . isConnected ( ) ) { outputBlock . value = outputBlock . notFoundValue ; break ; } if ( this . serverProvider . serverVersion != null && lt ( this . serverProvider . serverVersion , new SemVer ( '3.12.0' ) ) ) break ; this . keyboardInput . lock ( 'Executing ' + outputBlock . name . toUpperCase ( ) + ', please wait...' ) ; try { let newOutputBlock = await this . remoteComponent ( outputBlock ) ; this . keyboardInput . unlock ( ) ; Object . assign ( outputBlock , newOutputBlock ) ; variables [ outputBlock . type ] = outputBlock . value ; } catch { this . keyboardInput . unlock ( ) ; if ( this . acqusitionMode == 'manual' ) again ( ) ; return ; } break ; } case 'beep' : { let beepSpeed ; switch ( outputBlock . beepSpeed ) { case 'low' : beepSpeed = 700 ; break ; case 'medium' : beepSpeed = 450 ; break ; case 'fast' : beepSpeed = 250 ; break ; } let beep = ( ) => { return new Promise < void > ( ( resolve , reject ) => { this . nativeAudio . play ( outputBlock . value ) ; setTimeout ( ( ) => { resolve ( ) ; } , beepSpeed ) ; } ) ; } ; for ( let i = 0 ; i < outputBlock . beepsNumber ; i ++ ) { await beep ( ) ; } break ; } case 'if' : { let condition = false ; try { condition = await this . evalCode ( outputBlock . value , variables ) ; } catch ( error ) { observer . complete ( ) ; return ; } let endIfIndex = OutputBlockModel . FindEndIfIndex ( scan . outputBlocks , i + 1 ) ; if ( condition == true ) { scan . outputBlocks . splice ( i , 1 ) ; scan . outputBlocks . splice ( endIfIndex - 1 , 1 ) ; } else { let count = endIfIndex - i + 1 ; scan . outputBlocks . splice ( i , count ) ; } i -- ; break ; } case 'alert' : { if ( outputBlock . alertTitle ) outputBlock . alertTitle = new Supplant ( ) . text ( outputBlock . alertTitle , variables ) ; let pressedButton = await this . showAlert ( outputBlock ) ; switch ( pressedButton ) { case 'discard_scan' : { if ( this . acqusitionMode == 'manual' ) again ( ) ; return ; } case 'scan_again' : { again ( ) ; return ; } case 'ok' : { break ; } } break ; } } } deprecated scan . text = scan . outputBlocks . map ( outputBlock => { if ( outputBlock . type == 'barcode' ) { return outputBlock . value ; } else { return '' ; } } ) . filter ( x => x != '' ) . join ( ' ' ) ; scan . displayValue = ScanModel . ToString ( scan ) ; if ( scan . displayValue == prevScanDisplayValue ) { againCount ++ ; } prevScanDisplayValue = scan . displayValue ; if ( resetAgainCountTimer ) clearTimeout ( resetAgainCountTimer ) ; resetAgainCountTimer = setTimeout ( ( ) => againCount = 0 , 500 ) ; observer . next ( scan ) ; switch ( this . acqusitionMode ) { case 'continue' : again ( ) ; break ; case 'mixed_continue' : this . showAddMoreDialog ( continueModeTimeout ) . then ( ( addMore ) => { if ( addMore ) { again ( ) ; } else { observer . complete ( ) ; } } ) ; break ; case 'manual' : again ( ) ; break ; case 'single' : observer . complete ( ) ; break ; default : observer . complete ( ) ; break ; } } ; again ( ) ; } ) ; } ) ; } private lastResolve ; private lastReject ; private continuosScanSubscription : Subscription = null ; private getBarcode ( label = null , filter = null , errorMessage = null ) { this . awaitingForBarcode = true ; let promise = new Promise < string > ( ( resolve , reject ) => { let again = async ( showFilterError = false ) => { if ( showFilterError && errorMessage ) { this . pluginOptions . prompt = 'Error: ' + errorMessage ; } switch ( this . acqusitionMode ) { case 'single' : case 'mixed_continue' : { let barcodeScanResult = await this . barcodeScanner . scan ( this . pluginOptions ) . first ( ) . toPromise ( ) ; if ( ! barcodeScanResult || barcodeScanResult . cancelled ) { this . showIsPDADeviceDialog ( ) ; reject ( 'cancelled' ) ; return ; } if ( barcodeScanResult . text && barcodeScanResult . format == 'CODE_39' && this . barcodeFormats . findIndex ( x => x . enabled && x . name == 'CODE_32' ) != - 1 ) { barcodeScanResult . text = Utils . convertCode39ToCode32 ( barcodeScanResult . text ) ; } if ( ScanProvider . lastBarcode && barcodeScanResult . text == ScanProvider . lastBarcode ) { let acceptBarcode = await this . showAcceptDuplicateDetectedDialog ( ) ; if ( ! acceptBarcode ) { again ( ) ; return ; } } if ( filter != null && ! barcodeScanResult . text . match ( filter ) ) { again ( true ) ; return ; } else { ScanProvider . lastBarcode = barcodeScanResult . text ; resolve ( barcodeScanResult . text ) ; } break ; } case 'continue' : { this . lastResolve = resolve ; this . lastReject = reject ; if ( this . continuosScanSubscription == null ) { this . continuosScanSubscription = this . barcodeScanner . scan ( this . pluginOptions ) . subscribe ( async ( barcodeScanResult ) => { if ( ! barcodeScanResult || barcodeScanResult . cancelled ) { this . continuosScanSubscription . unsubscribe ( ) ; this . continuosScanSubscription = null ; this . showIsPDADeviceDialog ( ) ; this . lastReject ( ) ; return ; } if ( barcodeScanResult . text && barcodeScanResult . format == 'CODE_39' && this . barcodeFormats . findIndex ( x => x . enabled && x . name == 'CODE_32' ) != - 1 ) { barcodeScanResult . text = Utils . convertCode39ToCode32 ( barcodeScanResult . text ) ; } if ( ScanProvider . lastBarcode && barcodeScanResult . text == ScanProvider . lastBarcode ) { let acceptBarcode = await this . showAcceptDuplicateDetectedDialog ( ) ; if ( ! acceptBarcode ) { return ; } } if ( filter == null || ( filter != null && barcodeScanResult . text . match ( filter ) ) ) { ScanProvider . lastBarcode = barcodeScanResult . text ; this . lastResolve ( barcodeScanResult . text ) ; } } , error => { } , ( ) => { } ) ; } break ; } case 'manual' : { this . keyboardInput . focus ( true ) ; this . keyboardInput . setPlaceholder ( label ) ; if ( showFilterError && errorMessage ) this . keyboardInput . setError ( errorMessage ) ; let barcode = await this . keyboardInput . onSubmit . first ( ) . toPromise ( ) ; if ( ScanProvider . lastBarcode && barcode == ScanProvider . lastBarcode ) { let acceptBarcode = await this . showAcceptDuplicateDetectedDialog ( ) ; if ( ! acceptBarcode ) { again ( ) ; return ; } } if ( filter != null && ! barcode . match ( filter ) ) { again ( true ) ; return ; } else { ScanProvider . lastBarcode = barcode ; resolve ( barcode ) ; } break ; } } } ; again ( ) ; } ) ; promise . then ( value => { this . awaitingForBarcode = false ; } ) . catch ( err => { this . awaitingForBarcode = false ; } ) ; return promise ; } private async getOutputProfile ( i ) { let profiles = await this . settings . getOutputProfiles ( ) ; return new Promise < OutputProfileModel > ( ( resolve , reject ) => { if ( i >= profiles . length ) i = profiles . length - 1 ; if ( i < 0 ) i = 0 ; resolve ( profiles [ i ] ) ; } ) ; } private showSelectOption ( outputBlock ) { return new Promise ( async ( resolve , reject ) => { let options = outputBlock . value . split ( ',' ) ; let optionIndex = 0 ; let inputs = options . map ( option => { let input = { type : 'radio' , label : option , value : option , } ; if ( optionIndex == 0 ) { input . checked = true ; } optionIndex ++ ; return input ; } ) ; let title = null ; let message = null ; if ( outputBlock . title && outputBlock . title . length >= 1 ) title = outputBlock . title ; if ( outputBlock . message && outputBlock . message . length >= 1 ) message = outputBlock . message ; let alert = this . alertCtrl . create ( { title : title , message : message , inputs : inputs , enableBackdropDismiss : false , buttons : [ { text : await this . utils . text ( 'showSelectOptionDialogOkButton' ) , handler : ( data ) => { resolve ( data ) ; } } ] } ) ; alert . setLeavingOpts ( { keyboardClose : false , animate : false } ) ; alert . present ( { keyboardClose : false , animate : false } ) ; } ) ; } /**
     * Shows a dialog to acquire a value that can be number or text
     */ private getField ( label = null , fieldType = 'number' , defaultValue = null , filter = null , errorMessage = null ) { if ( label == null ) { if ( fieldType == 'number' ) { label = 'Insert a number' ; } else { label = 'Insert text' ; } } return new Promise ( ( resolve , reject ) => { let again = async ( showError = false ) => { if ( this . quantityType && fieldType == 'number' ) { fieldType = this . quantityType ; } let placeholder = `(Default is ${ defaultValue } press OK to insert it)` ; if ( defaultValue == null ) placeholder = 'Eg. damaged item' ; let alert = this . alertCtrl . create ( { title : label , message : showError ? errorMessage : null , enableBackdropDismiss : false , cssClass : this . platform . is ( 'android' ) ? 'alert-get-field alert-big-buttons' : null , inputs : [ { name : 'value' , type : fieldType , placeholder : placeholder } ] , buttons : [ { role : 'cancel' , text : await this . utils . text ( 'getFieldDialogCancelButton' ) , handler : ( ) => { reject ( 'cancelled' ) ; } , cssClass : this . platform . is ( 'android' ) ? 'button-outline-md' : null , } , { text : await this . utils . text ( 'getFieldDialogOkButton' ) , handler : data => { if ( data . value ) { if ( filter != null && ! data . value . match ( filter ) ) { setTimeout ( ( ) => { again ( true ) ; } , 500 ) ; return ; } resolve ( data . value ) ; } else { resolve ( defaultValue || '' ) ; } } , cssClass : this . platform . is ( 'android' ) ? 'button-outline-md button-ok' : null , } ] } ) ; alert . setLeavingOpts ( { keyboardClose : false , animate : false } ) ; alert . present ( { keyboardClose : false , animate : false } ) ; } ; again ( ) ; } ) ; } private remoteComponent ( outputBlock ) { return new Promise ( async ( resolve , reject ) => { if ( ! this . serverProvider . isConnected ( ) ) { this . alertCtrl . create ( { title : await this . utils . text ( 'remoteComponentDialogTitle' ) , message : await this . utils . text ( 'remoteComponentDialogMessage' , { "outputBlockType" : outputBlock . type . toUpperCase ( ) } ) , buttons : [ { text : await this . utils . text ( 'remoteComponentDialogOkButton' ) , handler : ( ) => { } } ] } ) . present ( ) ; reject ( ) ; return ; } let id = new Date ( ) . getTime ( ) ; let wsRequest = new requestModelRemoteComponent ( ) . fromObject ( { id : id , outputBlock : outputBlock } ) ; let ackSubscription = this . serverProvider . onMessage ( ) . subscribe ( async ( message ) => { if ( message . action == responseModel . ACTION_REMOTE_COMPONENT_RESPONSE ) { let response = message ; if ( id == response . id ) { if ( response . errorMessage == null ) { ackSubscription . unsubscribe ( ) ; resolve ( response . outputBlock ) ; } else { if ( this . remoteComponentErrorDialog != null ) this . remoteComponentErrorDialog . dismiss ( ) ; this . remoteComponentErrorDialog = this . alertCtrl . create ( { title : await this . utils . text ( 'remoteComponentErrorDialogTitle' ) , message : response . errorMessage , buttons : [ { text : await this . utils . text ( 'remoteComponentErrorDialogOkButton' ) , handler : ( ) => { } } ] , } ) ; reject ( ) ; this . remoteComponentErrorDialog . present ( ) ; } } } } ) ; this . serverProvider . send ( wsRequest ) ; } ) ; } private showAlert ( outputBlock ) { return new Promise < AlertButtonType > ( ( resolve , reject ) => { let buttons = [ ] ; let pressedButton = 'ok' ; if ( outputBlock . alertDiscardScanButton ) buttons . push ( { text : outputBlock . alertDiscardScanButton , cssClass : this . platform . is ( 'android' ) ? 'button-outline-md button-alert' : null , handler : ( ) => { pressedButton = 'discard_scan' ; } } ) ; if ( outputBlock . alertScanAgainButton ) buttons . push ( { text : outputBlock . alertScanAgainButton , cssClass : this . platform . is ( 'android' ) ? 'button-outline-md button-alert' : null , handler : ( ) => { pressedButton = 'scan_again' ; } } ) ; if ( outputBlock . alertOkButton ) buttons . push ( { text : outputBlock . alertOkButton , cssClass : this . platform . is ( 'android' ) ? 'button-outline-md button-alert button-ok' : null , handler : ( ) => { pressedButton = 'ok' ; } } ) ; let alert = this . alertCtrl . create ( { title : outputBlock . alertTitle , message : outputBlock . value , buttons : buttons , cssClass : this . platform . is ( 'android' ) ? 'alert-big-buttons' : null , } ) ; alert . onDidDismiss ( ( ) => { resolve ( pressedButton ) ; } ) ; alert . present ( ) ; } ) ; } private showAddMoreDialog ( timeoutSeconds ) { return new Promise ( async ( resolve , reject ) => { if ( timeoutSeconds == 0 ) { resolve ( true ) ; return ; } let interval = null ; let alert = this . alertCtrl . create ( { title : await this . utils . text ( 'addMoreDialogTitle' ) , message : await this . utils . text ( 'addMoreDialogMessage' ) , buttons : [ { text : await this . utils . text ( 'addMoreDialogStopButton' ) , role : 'cancel' , handler : ( ) => { if ( interval ) clearInterval ( interval ) ; resolve ( false ) ; } } , { text : await this . utils . text ( 'addMoreDialogContinueButton' ) , handler : ( ) => { if ( interval ) clearInterval ( interval ) ; resolve ( true ) ; } } ] } ) ; alert . present ( ) ; this . firebaseAnalytics . logEvent ( 'custom_timeout' , { } ) ; interval = setInterval ( ( ) => { this . ngZone . run ( ( ) => { alert . setSubTitle ( 'Timeout: ' + timeoutSeconds ) ; } ) ; if ( ! timeoutSeconds || timeoutSeconds <= 0 ) { if ( interval ) clearInterval ( interval ) ; alert . dismiss ( ) ; resolve ( true ) ; } timeoutSeconds -- ; } , 1000 ) ; } ) ; } private showPreventInfiniteLoopDialog ( ) { return new Promise ( async ( resolve , reject ) => { this . alertCtrl . create ( { title : await this . utils . text ( 'preventInfiniteLoopDialogTitle' ) , message : await this . utils . text ( 'preventInfiniteLoopDialogMessage' ) , buttons : [ { text : await this . utils . text ( 'preventInfiniteLoopStopButton' ) , role : 'cancel' , handler : ( ) => { let wsRequest = new requestModelUndoInfiniteLoop ( ) . fromObject ( { count : ScanProvider . INFINITE_LOOP_DETECT_THRESHOLD } ) ; this . serverProvider . send ( wsRequest ) ; resolve ( false ) ; } } , { text : 'Continue' , handler : ( ) => { resolve ( true ) ; } } ] } ) . present ( ) ; } ) ; } private showIsPDADeviceDialog ( ) { return new Promise ( async ( resolve , reject ) => { let isPDADevicedialogShown = await this . settings . getIsPDADeviceDialogShown ( ) ; let noRunnings = await this . settings . getNoRunnings ( ) ; if ( this . platform . is ( 'android' ) && ! isPDADevicedialogShown && noRunnings < Config . NO_RUNNINGS_MAX_TO_SHOW_IS_PDA_DEVICE_DIALOG ) { this . alertCtrl . create ( { title : await this . utils . text ( 'isPDADeviceDialogTitle' ) , message : await this . utils . text ( 'isPDADeviceDialogMessage' ) , buttons : [ { text : await this . utils . text ( 'isPDADeviceDialogNoButton' ) , handler : ( ) => { this . settings . setIsPDADeviceDialogShown ( true ) ; resolve ( ) ; } } , { text : await this . utils . text ( 'isPDADeviceDialogShowLaterButton' ) , role : 'cancel' , handler : ( ) => { resolve ( ) ; } } , { text : await this . utils . text ( 'isPDADeviceDialogMoreInfoButton' ) , handler : ( ) => { this . settings . setIsPDADeviceDialogShown ( true ) ; this . iab . create ( Config . URL_ANDROID_PDA , '_system' ) ; resolve ( ) ; } } ] } ) . present ( ) ; } else { resolve ( ) ; } } ) ; } private showRememberDuplicatedBarcodeChoiceDialog ( acceptDuplicated ) { return new Promise < boolean > ( async ( resolve , reject ) => { let duplicateBarcodeSaveChoiceShown = await this . settings . getDuplicateBarcodeSaveChoiceShown ( ) ; if ( duplicateBarcodeSaveChoiceShown ) { resolve ( true ) ; return ; } if ( this . rememberDuplicatedBarcodeChoiceDialog != null ) this . rememberDuplicatedBarcodeChoiceDialog . dismiss ( ) ; this . rememberDuplicatedBarcodeChoiceDialog = this . alertCtrl . create ( { title : await this . utils . text ( 'saveAsDefaultChoiceDialogTitle' ) , message : await this . utils . text ( 'saveAsDefaultChoiceDialogMessage' ) , buttons : [ { text : await this . utils . text ( 'saveAsDefaultChoiceDialogAlwaysAskButton' ) , role : 'cancel' , handler : ( ) => { resolve ( false ) ; } , cssClass : this . platform . is ( 'android' ) ? 'button-outline-md button-alert' : null } , { text : await this . utils . text ( 'saveAsDefaultChoiceDialogSaveButton' ) , handler : ( ) => { this . settings . setDuplicateBarcodeChoice ( acceptDuplicated ? 'accept' : 'discard' ) ; resolve ( true ) ; } , cssClass : this . platform . is ( 'android' ) ? 'button-outline-md button-alert button-ok' : null } , ] } ) ; this . rememberDuplicatedBarcodeChoiceDialog . onDidDismiss ( ( ) => { this . settings . setDuplicateBarcodeSaveChoiceShown ( true ) ; } ) ; this . rememberDuplicatedBarcodeChoiceDialog . present ( ) ; } ) ; } ; private acceptDuplicateBarcodeDialogVisible = false ; private showAcceptDuplicateDetectedDialog ( ) { return new Promise < boolean > ( async ( resolve , reject ) => { let defaultChoice = await this . settings . getDuplicateBarcodeChoice ( ) ; if ( defaultChoice != 'ask' ) { resolve ( defaultChoice == 'accept' ) ; return ; } if ( this . acceptDuplicateBarcodeDialog != null ) { if ( this . acceptDuplicateBarcodeDialogVisible ) { return ; } } if ( this . rememberDuplicatedBarcodeChoiceDialog != null ) this . rememberDuplicatedBarcodeChoiceDialog . dismiss ( ) ; this . acceptDuplicateBarcodeDialog = this . alertCtrl . create ( { title : await this . utils . text ( 'duplicatedBarcodesDialogTitle' ) , message : await this . utils . text ( 'duplicatedBarcodesDialogMessage' ) , buttons : [ { text : await this . utils . text ( 'duplicatedBarcodesAcceptButton' ) , handler : ( ) => { resolve ( true ) ; this . showRememberDuplicatedBarcodeChoiceDialog ( true ) ; } , cssClass : this . platform . is ( 'android' ) ? 'button-outline-md button-alert button-ok' : null } , { text : await this . utils . text ( 'duplicatedBarcodesDiscardButton' ) , role : 'cancel' , handler : ( ) => { resolve ( false ) ; this . showRememberDuplicatedBarcodeChoiceDialog ( false ) ; } , cssClass : this . platform . is ( 'android' ) ? 'button-outline-md button-alert' : null } , ] } ) ; this . acceptDuplicateBarcodeDialog . onDidDismiss ( ( data ) => { this . acceptDuplicateBarcodeDialogVisible = false ; } ) ; this . acceptDuplicateBarcodeDialog . present ( ) ; this . acceptDuplicateBarcodeDialogVisible = true ; } ) ; } /**
     * Injects variables like barcode, device_name, date and evaluates
     * the string parameter
     */ private async evalCode ( code , variables ) { let randomInt = this . getRandomInt ( ) + '' ; Object . defineProperty ( window , randomInt , { value : { } , writable : true } ) ; Object . keys ( variables ) . forEach ( key => { let hashedKey = btoa ( key ) ; window [ randomInt ] [ hashedKey ] = variables [ key ] ; code = code . replace ( new RegExp ( key , 'g' ) , 'window["' + randomInt + '"]["' + hashedKey + '"]' ) ; } ) ; try { return eval ( code ) ; } catch ( error ) { this . alertCtrl . create ( { title : await this . utils . text ( 'evalCodeDialogTitle' ) , message : await this . utils . text ( 'evalCodeDialogMessage' , { "error" : error } ) , buttons : [ { text : await this . utils . text ( 'evalCodeDialogOkButton' ) , role : 'cancel' , } ] } ) . present ( ) ; throw new Error ( error ) ; } finally { delete window [ randomInt ] ; } } getRandomInt ( max = Number . MAX_SAFE_INTEGER ) { return Math . floor ( Math . random ( ) * Math . floor ( max ) ) ; } }