import * as JSZip from 'jszip' ; import { Logger } from '../../logger' ; import { UrlParser } from '../UrlParser/UrlParser' ; import { EXTERNAL_RESOURCES_REGEXP_SECOND_GROUP } from './ExternalResources.constants' ; export class ExternalResources { content : string ; jszip : JSZip ; constructor ( content , jszip ) { this . content = content ; this . jszip = jszip ; } public async getReplacedContent ( ) { return this . replaceExternalStyles ( this . content ) . then ( ( content ) => this . replaceExternalBackgroundUrl ( content ) ) . then ( ( content ) => this . replaceExternalImage ( content ) ) . then ( ( content ) => this . replaceExternalScript ( content ) ) ; } private async replaceExternalStyles ( content , base = '/' ) { const pattern = this . getSearchPattern ( 'style' ) ; return await this . replacer ( content , pattern , async ( substring ) : Promise < string > => { const path = await this . getReplacedFilePath ( substring , pattern , EXTERNAL_RESOURCES_REGEXP_SECOND_GROUP , base ) ; if ( ! path ) { return substring ; } const content = await this . jszip . file ( path ) . async ( 'text' ) ; const relative = path . split ( '/' ) . splice ( - 1 , 1 ) . join ( '/' ) ; const result = await this . replaceExternalBackgroundUrl ( content , relative ) ; return '<style type="text/css">' + result . replace ( '\n' , '' ) + '</style>' ; } ) ; } private async replaceExternalBackgroundUrl ( content , base = '/' ) { const pattern = this . getSearchPattern ( 'backgroundUrl' ) ; return await this . replacer ( content , pattern , async ( substring ) : Promise < string > => { const path = await this . getReplacedFilePath ( substring , pattern , EXTERNAL_RESOURCES_REGEXP_SECOND_GROUP , base ) ; if ( ! path ) { return substring ; } const content = await this . jszip . file ( path ) . async ( 'base64' ) ; const extension = path . split ( '.' ) . pop ( ) ; return `url("data:image/${ ( extension === 'svg' ? 'svg+xml' : extension ) };base64,${ content }")` ; } ) ; } private async replaceExternalImage ( content , base = '/' ) { const pattern = this . getSearchPattern ( 'image' ) ; return await this . replacer ( content , pattern , async ( substring ) : Promise < string > => { const path = await this . getReplacedFilePath ( substring , pattern , EXTERNAL_RESOURCES_REGEXP_SECOND_GROUP , base ) ; if ( ! path ) { return substring ; } const content = await this . jszip . file ( path ) . async ( 'base64' ) ; const extension = path . split ( '.' ) . pop ( ) ; return '<img src="data:image/' + ( extension === 'svg' ? 'svg+xml' : extension ) + ';base64,' + content + '" alt="">' ; } ) ; } private async replaceExternalScript ( content , base = '/' ) { const pattern = this . getSearchPattern ( 'script' ) ; return await this . replacer ( content , pattern , async ( substring ) : Promise < string > => { const path = await this . getReplacedFilePath ( substring , pattern , EXTERNAL_RESOURCES_REGEXP_SECOND_GROUP , base ) ; if ( ! path ) { return substring ; } const content = await this . jszip . file ( path ) . async ( 'text' ) ; return '<script>' + content + '</script>' ; } ) ; } private getSearchPattern ( type ) { switch ( type ) { case 'style' : return '<link[^>]*href=(\'|")([^>]*\.(css))(\'|")[^>]*>' ; case 'backgroundUrl' : return 'url\\s*\\((\'|")?([^)]*\\.(gif|jpg|jpeg|png|svg))(\'|")?\\s*\\)' ; case 'image' : return '<img[^\\/?>]*src=(\'|")([^>]*\\.(gif|jpg|jpeg|png|svg))(\'|")[^\\/?>]*>' ; case 'script' : return '<script[^>]*src=(\'|")(.*\\.js)(\'|")[^>]*>(\\n|\\r|\\s)*<\\/script>' ; } } private async replacer ( content , pattern , callback ) { const arPromises = [ ] ; const regexp = new RegExp ( pattern , 'g' ) ; content . replace ( regexp , ( substring ) : string => { arPromises . push ( callback ( substring ) ) ; return substring ; } ) ; const replaces = await Promise . all ( arPromises ) ; return content . replace ( regexp , ( ) => { const replace = replaces . shift ( ) ; return replace || '' ; } ) ; } private async getReplacedFilePath ( substring , pattern , matchPosition , base = '/' ) { const regexp = new RegExp ( pattern ) ; const matches = substring . match ( regexp ) ; if ( ! matches || ! matches . length ) { return ; } const match = matches [ matchPosition ] ; const url = new UrlParser ( match , base ) ; if ( url . checkIsAbsolutePath ( ) ) { return ; } const path = url . getRelativePath ( ) ; if ( ! this . jszip . files [ path ] ) { Logger . write ( 'error' , 'Not found file: "' + path + '" for replace.' ) ; return ; } return path ; } }