import request from 'request' ; import dataUriToBuffer from 'data-uri-to-buffer' ; import async , { AsyncResultArrayCallback , AsyncResultCallback , AsyncFunction } from 'async' ; import { SourceMapConsumer , SourceMapGenerator , RawSourceMap , BasicSourceMapConsumer } from 'source-map' ; import validateMappings from './validateMappings' ; import Report from './report' ; import { ReportCallback } from './interfaces' ; import { resolveSourceMapSource } from './utils' ; import { UnableToFetchSourceMapError , UnableToFetchSourceError , InvalidSourceMapFormatError , InvalidJSONError , BadContentError , ResourceTimeoutError } from './errors' ; import { MAX_TIMEOUT } from './constants' ; /**
 * Wrapper around request except it handles source maps contained in data-uris
 */ function requestSourceMap ( sourceMapUrl , options , callback ) { if ( sourceMapUrl . startsWith ( 'data:' ) ) { const body = dataUriToBuffer ( sourceMapUrl ) ; callback ( null , { statusCode : 200 } , body . toString ( ) ) ; } else { request ( sourceMapUrl , options , callback as request . RequestCallback ) ; } } param sourceMapUrl param generatedContent param reportCallback export default function validateSourceMap ( sourceMapUrl , generatedContent , reportCallback ) { let report = new Report ( ) ; report . sourceMap = sourceMapUrl ; requestSourceMap ( sourceMapUrl , { timeout : MAX_TIMEOUT } , ( error , response , body ) => { if ( error ) { if ( error . message === 'ESOCKETTIMEDOUT' ) { report . pushError ( new ResourceTimeoutError ( sourceMapUrl , MAX_TIMEOUT ) ) ; return void reportCallback ( report ) ; } return void console . error ( error ) ; } if ( response && response . statusCode !== 200 ) { report . pushError ( new UnableToFetchSourceMapError ( sourceMapUrl ) ) ; reportCallback ( report ) ; return ; } let rawSourceMap ; try { rawSourceMap = JSON . parse ( body ) ; } catch ( err ) { report . pushError ( new InvalidJSONError ( sourceMapUrl , err ) ) ; reportCallback ( report ) ; return ; } new SourceMapConsumer ( rawSourceMap ) . then ( ( sourceMapConsumer ) => { const resolvedSources = sourceMapConsumer . sources . map ( sourceUrl => { return [ sourceUrl , resolveSourceMapSource ( sourceUrl , sourceMapUrl , rawSourceMap ) ] ; } ) ; const validateMappingsCallback = ( _sourceMapConsumer , fetchesReport = new Report ( ) ) => { const generatedLines = generatedContent . split ( '\n' ) ; const mappingsReport = validateMappings ( _sourceMapConsumer , generatedLines ) ; report = report . concat ( fetchesReport ) . concat ( mappingsReport ) . pushSource ( ... resolvedSources . map ( ( [ , resolvedUrl ] ) => resolvedUrl ) ) ; reportCallback ( report ) ; } ; if ( sourceMapConsumer . hasContentsOfAllSources ( ) ) { return void validateMappingsCallback ( sourceMapConsumer ) ; } fetchSources ( sourceMapConsumer , resolvedSources , validateMappingsCallback ) ; } ) . catch ( err => { report . pushError ( new InvalidSourceMapFormatError ( sourceMapUrl , err ) ) ; reportCallback ( report ) ; } ) ; } ) ; } param sourceMapConsumer param resolvedSources param validateMappingsCallback function fetchSources ( sourceMapConsumer , resolvedSources , validateMappingsCallback ) { const report = new Report ( ) ; const generator = SourceMapGenerator . fromSourceMap ( sourceMapConsumer ) ; const missingSources = resolvedSources . filter ( ( [ sourceUrl ] ) => { return ! sourceMapConsumer . sourceContentFor ( sourceUrl , true ) ; } ) ; const requests = missingSources . map ( ( [ sourceUrl , resolvedUrl ] ) => { return ( cb ) => { request ( resolvedUrl , { timeout : MAX_TIMEOUT } , ( err , response , body ) => { if ( err ) { console . warn ( 'Fetch failure:' , err ) ; } if ( ! response || response . statusCode !== 200 ) { cb ( null , new UnableToFetchSourceError ( resolvedUrl ) ) ; return void 0 ; } const bodyStart = body . slice ( 0 , 200 ) . trim ( ) ; if ( /^<!doctype/i . test ( bodyStart ) ) { return void cb ( null , new BadContentError ( resolvedUrl ) ) ; } generator . setSourceContent ( sourceUrl , body ) ; cb ( null , undefined ) ; } ) ; } ; } ) ; async . series ( requests , ( err ? , validationErrors ? ) => { if ( err || ! validationErrors ) { return void console . error ( err ) ; } new SourceMapConsumer ( generator . toJSON ( ) ) . then ( fullSourceMapConsumer => { report . pushError ( ... validationErrors . filter ( ( _err ) : _err is Error => _err !== undefined ) ) ; validateMappingsCallback ( fullSourceMapConsumer , report ) ; } ) ; } ) ; }