import { env } from './environment' ; import { Icon } from './interfaces' ; type IconDimension = { width : number ; height : number ; } ; type ImageFormat = { exts : [ string , string ? ] ; mime : string ; } ; param icons param purpose param mimeType param desiredHeight param desiredWidth export function findSuitableIcon ( icons , purpose , desiredWidth , desiredHeight , mimeType ) { if ( ! icons || icons . length === 0 ) { return null ; } const iconInfos = isIconInfos ( icons ) ? icons : icons . map ( i => new IconInfo ( i ) ) ; const exactMatch = iconInfos . find ( i => i . isExactMatch ( purpose , desiredWidth , desiredHeight , mimeType ) ) ; if ( exactMatch ) { return exactMatch . getIcon ( ) ; } var largerMatch = iconInfos . find ( i => i . isSuitableIcon ( purpose , desiredWidth , desiredHeight , mimeType ) ) ; return largerMatch ?. getIcon ( ) || null ; } param icons returns export function findBestAppIcon ( icons ) { const iconInfos = ( icons || [ ] ) . map ( i => new IconInfo ( i ) ) ; return findSuitableIcon ( iconInfos , 'any' , 512 , 512 , 'image/png' ) || findSuitableIcon ( iconInfos , 'maskable' , 512 , 512 , 'image/png' ) || findSuitableIcon ( iconInfos , 'any' , 192 , 192 , 'image/png' ) || findSuitableIcon ( iconInfos , 'maskable' , 192 , 192 , 'image/png' ) || findSuitableIcon ( iconInfos , 'any' , 512 , 512 , 'image/jpeg' ) || findSuitableIcon ( iconInfos , 'maskable' , 512 , 512 , 'image/jpeg' ) || findSuitableIcon ( iconInfos , 'any' , 192 , 192 , 'image/jpeg' ) || findSuitableIcon ( iconInfos , 'maskable' , 192 , 192 , 'image/jpeg' ) || findSuitableIcon ( iconInfos , 'any' , 512 , 512 , undefined ) || findSuitableIcon ( iconInfos , 'maskable' , 512 , 512 , undefined ) || findSuitableIcon ( iconInfos , 'any' , 192 , 192 , undefined ) || findSuitableIcon ( iconInfos , 'maskable' , 192 , 192 , undefined ) || findSuitableIcon ( iconInfos , 'any' , 0 , 0 , 'image/png' ) || findSuitableIcon ( iconInfos , 'maskable' , 0 , 0 , 'image/png' ) || findSuitableIcon ( iconInfos , 'any' , 0 , 0 , 'image/jpeg' ) || findSuitableIcon ( iconInfos , 'maskable' , 0 , 0 , 'image/jpeg' ) || findSuitableIcon ( iconInfos , 'any' , 0 , 0 , undefined ) || findSuitableIcon ( iconInfos , 'maskable' , 0 , 0 , undefined ) ; } function isIconInfos ( icons ) { const firstIcon = icons [ 0 ] ; if ( firstIcon instanceof IconInfo ) { return true ; } return false ; } /**
 * Wraps a manifest icon and provides information about it.
 */ export class IconInfo { static readonly pngFormat : ImageFormat = { exts : [ 'png' ] , mime : 'image/png' } ; static readonly jpgFormat : ImageFormat = { exts : [ 'jpg' , 'jpeg' ] , mime : 'image/jpeg' } ; static readonly formats : ImageFormat [ ] = [ IconInfo . pngFormat , IconInfo . jpgFormat , { exts : [ 'webp' ] , mime : 'image/webp' } , { exts : [ 'gif' ] , mime : 'image/gif' } , { exts : [ 'ico' ] , mime : 'image/x-icon' } , { exts : [ 'tiff' ] , mime : 'image/tiff' } , { exts : [ 'bmp' ] , mime : 'image/bmp' } , { exts : [ 'svg' ] , mime : 'image/svg+xml' } , ] ; constructor ( private readonly icon ) { } getProbableFileExtension ( ) { if ( ! this . icon . type ) { return this . getFileExtensionFromSrc ( ) ; } const format = this . getFormat ( ) ; if ( format ) { return format . exts [ 0 ] ; } const lastSlashIndex = this . icon . type . lastIndexOf ( '/' ) ; if ( lastSlashIndex != - 1 ) { return this . icon . type . substring ( lastSlashIndex + 1 ) ; } return null ; } isAtLeast ( width , height ) { const dimensions = this . getDimensions ( ) ; return dimensions . some ( i => i . width >= width && i . height >= height ) ; } get isPng ( ) : boolean { return this . getMimeTypeOrGuessFromSrc ( ) === IconInfo . pngFormat . mime ; } get isJpg ( ) : boolean { return this . getMimeTypeOrGuessFromSrc ( ) === IconInfo . jpgFormat . mime ; } get isSquare ( ) : boolean { const dimensions = this . getDimensions ( ) ; return dimensions . some ( d => d . width === d . height ) ; } get isEmbedded ( ) : boolean { return this . icon . src . includes ( 'data:image' ) ; } hasPurpose ( purpose ) { if ( ! purpose ) { return true ; } return ( this . icon . purpose || 'any' ) . split ( ' ' ) . some ( p => p . toLowerCase ( ) === purpose . toLowerCase ( ) ) ; } hasSize ( size ) { return ( this . icon . sizes || '0x0' ) . split ( ' ' ) . some ( s => s === size ) ; } param icon param newSrc returns createIconWithoutUrlEncodedSrc ( newSrc ) { const clone = { ... this . icon } ; if ( clone . src ?. startsWith ( 'data:image' ) ) { clone . src = newSrc ; } return clone ; } getIcon ( ) { return this . icon ; } getDimensions ( ) { return ( this . icon . sizes || '0x0' ) . split ( ' ' ) . map ( size => { const dimensions = size . split ( 'x' ) ; return { width : Number . parseInt ( dimensions [ 0 ] || '0' , 10 ) , height : Number . parseInt ( dimensions [ 1 ] || '0' , 10 ) , } ; } ) ; } hasMimeType ( mimeType ? ) { if ( ! mimeType ) { return true ; } return this . getMimeTypeOrGuessFromSrc ( ) === mimeType . toLowerCase ( ) ; } isExactMatch ( purpose , desiredWidth , desiredHeight , mimeType ) { const desiredSize = `${ desiredWidth }x${ desiredHeight }` ; return this . hasPurpose ( purpose ) && this . hasSize ( desiredSize ) && ! this . isEmbedded && this . hasMimeType ( mimeType ) ; } param purpose param desiredWidth param desiredHeight param mimeType isSuitableIcon ( purpose , desiredWidth , desiredHeight , mimeType ) { const exactMatch = this . isExactMatch ( purpose , desiredWidth , desiredHeight , mimeType ) ; if ( exactMatch ) { return true ; } if ( ! purpose ) { const withoutPurpose = this . isExactMatch ( null , desiredWidth , desiredHeight , mimeType ) ; if ( withoutPurpose ) { return true ; } } const isExpectingSquare = desiredWidth === desiredHeight ; const matchesSquareRequirement = ! isExpectingSquare || this . isSquare ; const largerIcon = this . hasPurpose ( purpose ) && this . isAtLeast ( desiredWidth , desiredHeight ) && ! this . isEmbedded && this . hasMimeType ( mimeType ) && matchesSquareRequirement ; return ! ! largerIcon ; } param manifestUrl returns async resolvesSuccessfully ( manifestUrl ) { if ( ! this . icon . src ) { return Promise . resolve ( false ) ; } return new Promise ( resolve => { const imageEl = new Image ( ) ; const imgUrl = new URL ( this . icon . src , manifestUrl ) ; imageEl . src = `${ env . safeUrlFetcher }?checkExistsOnly=false&url=${ encodeURIComponent ( imgUrl . toString ( ) ) }` ; imageEl . onload = ( ) => { if ( imageEl . complete && imageEl . naturalHeight > 0 ) { resolve ( true ) ; } else { resolve ( false ) ; } } ; imageEl . onerror = ( ) => { resolve ( false ) ; } ; } ) ; } private getFileExtensionFromSrc ( ) { const format = this . getFormat ( ) ; return format ?. exts [ 0 ] || null ; } private getFormat ( ) { const formatByMimeType = IconInfo . formats . find ( f => f . mime === this . icon . type ) ; if ( formatByMimeType ) { return formatByMimeType ; } const srcLower = this . icon . src ?. toLowerCase ( ) || '' ; const guessedFormat = IconInfo . formats . find ( f => f . exts . some ( ext => srcLower . endsWith ( `.${ ext }` ) ) ) ; return guessedFormat ?? null ; } private getMimeTypeOrGuessFromSrc ( ) { if ( this . icon . type ) { return this . icon . type ; } const guessFormat = this . getFormat ( ) ; return guessFormat ?. mime || null ; } }