import { inject , injectable } from 'inversify' ; import { GlobalLogger } from 'logger' ; import { AxeScanResults } from 'scanner-global-library' ; import { OnDemandPageScanRunResultProvider , WebsiteScanResultProvider } from 'service-library' ; import { OnDemandPageScanReport , OnDemandPageScanResult , OnDemandPageScanRunState , OnDemandScanResult , ScanError , WebsiteScanResult , } from 'storage-documents' ; import { System , ServiceConfiguration } from 'common' ; import _ from 'lodash' ; import { ReportGenerator } from '../report-generator/report-generator' ; import { ScanMetadataConfig } from '../scan-metadata-config' ; import { ScanRunnerTelemetryManager } from '../scan-runner-telemetry-manager' ; import { CombinedScanResultProcessor } from '../combined-result/combined-scan-result-processor' ; import { PageScanProcessor } from '../scanner/page-scan-processor' ; import { ReportWriter } from '../report-generator/report-writer' ; import { ScanMetadata } from '../types/scan-metadata' ; import { ScanNotificationProcessor } from '../sender/scan-notification-processor' ; @ injectable ( ) export class Runner { constructor ( @ inject ( ScanMetadataConfig ) private readonly scanMetadataConfig , @ inject ( OnDemandPageScanRunResultProvider ) private readonly onDemandPageScanRunResultProvider , @ inject ( WebsiteScanResultProvider ) protected readonly websiteScanResultProvider , @ inject ( PageScanProcessor ) private readonly pageScanProcessor , @ inject ( ReportWriter ) protected readonly reportWriter , @ inject ( ReportGenerator ) private readonly reportGenerator , @ inject ( CombinedScanResultProcessor ) private readonly combinedScanResultProcessor , @ inject ( ScanNotificationProcessor ) protected readonly scanNotificationProcessor , @ inject ( ScanRunnerTelemetryManager ) private readonly telemetryManager , @ inject ( ServiceConfiguration ) protected readonly serviceConfig , @ inject ( GlobalLogger ) private readonly logger ) { } public async run ( ) { const scanMetadata = this . scanMetadataConfig . getConfig ( ) ; scanMetadata . url = decodeURI ( scanMetadata . url ) ; this . logger . setCommonProperties ( { scanId : scanMetadata . id , url : scanMetadata . url } ) ; this . logger . logInfo ( 'Starting page scan task.' ) ; const pageScanResult = await this . updateScanRunState ( scanMetadata . id ) ; if ( pageScanResult === undefined ) { return ; } this . telemetryManager . trackScanStarted ( scanMetadata . id ) ; try { const axeScanResults = await this . pageScanProcessor . scan ( scanMetadata , pageScanResult ) ; await this . processScanResult ( axeScanResults , pageScanResult ) ; } catch ( error ) { const errorMessage = System . serializeError ( error ) ; this . setRunResult ( pageScanResult , 'failed' , errorMessage ) ; this . logger . logError ( `The scanner failed to scan a page.` , { error : errorMessage } ) ; this . telemetryManager . trackScanTaskFailed ( ) ; } finally { this . telemetryManager . trackScanCompleted ( ) ; } const websiteScanResult = await this . updateScanResult ( scanMetadata , pageScanResult ) ; await this . scanNotificationProcessor . sendScanCompletionNotification ( scanMetadata , pageScanResult , websiteScanResult ) ; this . logger . logInfo ( 'Page scan task completed.' ) ; } private async updateScanRunState ( scanId ) { this . logger . logInfo ( `Updating page scan run state to 'running'.` ) ; const partialPageScanResult = { id : scanId , run : { state : 'running' , timestamp : new Date ( ) . toJSON ( ) , error : null , } , scanResult : null , reports : null , } ; const response = await this . onDemandPageScanRunResultProvider . tryUpdateScanRun ( partialPageScanResult ) ; if ( ! response . succeeded ) { this . logger . logWarn ( `Update page scan run state to 'running' failed due to merge conflict with other process. Exiting page scan task.` ) ; return undefined ; } return response . result ; } private async processScanResult ( axeScanResults , pageScanResult ) { if ( _ . isNil ( axeScanResults . error ) ) { this . setRunResult ( pageScanResult , 'completed' ) ; pageScanResult . scanResult = this . getScanStatus ( axeScanResults ) ; pageScanResult . reports = await this . generateScanReports ( axeScanResults ) ; if ( axeScanResults . scannedUrl !== undefined ) { pageScanResult . scannedUrl = axeScanResults . scannedUrl ; } await this . combinedScanResultProcessor . generateCombinedScanResults ( axeScanResults , pageScanResult ) ; } else { this . setRunResult ( pageScanResult , 'failed' , axeScanResults . error ) ; this . logger . logError ( 'Browser has failed to scan a page.' , { error : JSON . stringify ( axeScanResults . error ) } ) ; this . telemetryManager . trackBrowserScanFailed ( ) ; } pageScanResult . run . pageTitle = axeScanResults . pageTitle ; pageScanResult . run . pageResponseCode = axeScanResults . pageResponseCode ; return axeScanResults . error ? undefined : axeScanResults ; } private async updateScanResult ( scanMetadata , pageScanResult ) { await this . onDemandPageScanRunResultProvider . updateScanRun ( pageScanResult ) ; const websiteScanRef = pageScanResult . websiteScanRefs ?. find ( ( ref ) => ref . scanGroupType === 'deep-scan' ) ; if ( websiteScanRef ) { const scanConfig = await this . serviceConfig . getConfigValue ( 'scanConfig' ) ; const runState = pageScanResult . run . state === 'completed' || pageScanResult . run . retryCount >= scanConfig . maxFailedScanRetryCount ? pageScanResult . run . state : undefined ; const updatedWebsiteScanResult = { id : websiteScanRef . id , pageScans : [ { scanId : scanMetadata . id , url : scanMetadata . url , scanState : pageScanResult . scanResult ?. state , runState , timestamp : new Date ( ) . toJSON ( ) , } , ] , } ; return this . websiteScanResultProvider . mergeOrCreate ( scanMetadata . id , updatedWebsiteScanResult , true ) ; } return undefined ; } private async generateScanReports ( axeResults ) { this . logger . logInfo ( `Generating reports from scan results.` ) ; const reports = this . reportGenerator . generateReports ( axeResults ) ; return this . reportWriter . writeBatch ( reports ) ; } private setRunResult ( pageScanResult , state , error ? ) { pageScanResult . run = { ... pageScanResult . run , state , timestamp : new Date ( ) . toJSON ( ) , error : _ . isString ( error ) ? error . substring ( 0 , 2048 ) : error , } ; } private getScanStatus ( axeResults ) { if ( axeResults ?. results ?. violations !== undefined && axeResults . results . violations . length > 0 ) { return { state : 'fail' , issueCount : axeResults . results . violations . reduce ( ( a , b ) => a + b . nodes . length , 0 ) , } ; } else { return { state : 'pass' , } ; } } }