import { getResourceForMime } from '@remusao/small' ; import { StaticDataView , sizeOfASCII , sizeOfByte } from './data-view' ; function btoaPolyfill ( buffer ) { if ( typeof btoa !== 'undefined' ) { return btoa ( buffer ) ; } else if ( typeof Buffer !== 'undefined' ) { return Buffer . from ( buffer ) . toString ( 'base64' ) ; } return buffer ; } interface Resource { contentType : string ; body : string ; } /**
 * Abstraction on top of resources.txt used for redirections as well as script
 * injections. It contains logic to parse, serialize and get resources by name
 * for use in the engine.
 */ export default class Resources { public static deserialize ( buffer ) { const checksum = buffer . getASCII ( ) ; const resources = new Map ( ) ; const numberOfResources = buffer . getUint16 ( ) ; for ( let i = 0 ; i < numberOfResources ; i += 1 ) { resources . set ( buffer . getASCII ( ) , { contentType : buffer . getASCII ( ) , body : buffer . getASCII ( ) , } ) ; } const js = new Map ( ) ; resources . forEach ( ( { contentType , body } , name ) => { if ( contentType === 'application/javascript' ) { js . set ( name , body ) ; } } ) ; return new Resources ( { checksum , js , resources , } ) ; } public static parse ( data , { checksum } ) { const typeToResource = new Map ( ) ; const trimComments = ( str ) => str . replace ( /^\s*#.*$/gm , '' ) ; const chunks = data . split ( '\n\n' ) ; for ( const chunk of chunks ) { const resource = trimComments ( chunk ) . trim ( ) ; if ( resource . length !== 0 ) { const firstNewLine = resource . indexOf ( '\n' ) ; const split = resource . slice ( 0 , firstNewLine ) . split ( /\s+/ ) ; const name = split [ 0 ] ; const type = split [ 1 ] ; const body = resource . slice ( firstNewLine + 1 ) ; if ( name === undefined || type === undefined || body === undefined ) { continue ; } let resources = typeToResource . get ( type ) ; if ( resources === undefined ) { resources = new Map ( ) ; typeToResource . set ( type , resources ) ; } resources . set ( name , body ) ; } } const js = typeToResource . get ( 'application/javascript' ) || new Map ( ) ; for ( const [ key , value ] of js . entries ( ) ) { if ( key . endsWith ( '.js' ) ) { js . set ( key . slice ( 0 , - 3 ) , value ) ; } } const resourcesByName = new Map ( ) ; typeToResource . forEach ( ( resources , contentType ) => { resources . forEach ( ( resource , name ) => { resourcesByName . set ( name , { contentType , body : resource , } ) ; } ) ; } ) ; return new Resources ( { checksum , js , resources : resourcesByName , } ) ; } public readonly checksum : string ; public readonly js : Map < string , string > ; public readonly resources : Map < string , Resource > ; constructor ( { checksum = '' , js = new Map ( ) , resources = new Map ( ) } = { } ) { this . checksum = checksum ; this . js = js ; this . resources = resources ; } public getResource ( name ) { const { body , contentType } = this . resources . get ( name ) || getResourceForMime ( name ) ; let dataUrl ; if ( contentType . indexOf ( ';' ) !== - 1 ) { dataUrl = `data:${ contentType },${ body }` ; } else { dataUrl = `data:${ contentType };base64,${ btoaPolyfill ( body ) }` ; } return { body , contentType , dataUrl } ; } public getSerializedSize ( ) { let estimatedSize = sizeOfASCII ( this . checksum ) + 2 * sizeOfByte ( ) ; this . resources . forEach ( ( { contentType , body } , name ) => { estimatedSize += sizeOfASCII ( name ) + sizeOfASCII ( contentType ) + sizeOfASCII ( body ) ; } ) ; return estimatedSize ; } public serialize ( buffer ) { buffer . pushASCII ( this . checksum ) ; buffer . pushUint16 ( this . resources . size ) ; this . resources . forEach ( ( { contentType , body } , name ) => { buffer . pushASCII ( name ) ; buffer . pushASCII ( contentType ) ; buffer . pushASCII ( body ) ; } ) ; } }