import { BaseComponentContext } from '@microsoft/sp-component-base' ; import { SPHttpClient } from "@microsoft/sp-http" ; import { ISearchResult , BingQuerySearchParams , IRecentFile } from "./FilesSearchService.types" ; import { find } from "office-ui-fabric-react/lib/Utilities" ; import { GeneralHelper } from "../common/utilities/GeneralHelper" ; /**
 * Maximum file size when searching
 */ const MAXFILESIZE = 52428800 ; /**
 * Maximum number of search results
 */ const MAXRESULTS = 100 ; export class FilesSearchService { private context : BaseComponentContext ; private bingAPIKey : string ; constructor ( context , bingAPIKey ) { this . context = context ; this . bingAPIKey = bingAPIKey ; } /**
     * Checks if file exists
     */ public checkFileExists = async ( fileUrl ) : Promise < boolean > => { try { const fetchFileResult = await this . context . httpClient . fetch ( fileUrl , SPHttpClient . configurations . v1 , { headers : new Headers ( ) , method : 'HEAD' , mode : 'cors' } ) ; if ( ! fetchFileResult || ! fetchFileResult . ok ) { throw new Error ( `Something went wrong when executing search query. Status='${ fetchFileResult . status }'` ) ; } return true ; } catch ( err ) { console . error ( `[BingFilesService.fetchFile]: Err='${ err . message }'` ) ; return false ; } } ; /**
     * Executes Recent files search.
     */ public executeRecentSearch = async ( accepts ? ) => { try { const webId = this . context . pageContext . web . id . toString ( ) ; const siteId = this . context . pageContext . site . id . toString ( ) ; const fileFilter = this . _getFileFilter ( accepts ) ; const queryTemplate = `((SiteID:${ siteId } OR SiteID: {${ siteId }}) AND (WebId: ${ webId } OR WebId: {${ webId }})) AND LastModifiedTime < {Today} AND -Title:OneNote_DeletedPages AND -Title:OneNote_RecycleBin${ fileFilter }` ; const queryData = { __metadata : { "type" : "Microsoft.Office.Server.Search.REST.SearchRequest" } , QueryTemplate : queryTemplate , RowLimit : 20 , SelectProperties : { results : [ "Title" , "Path" , "Filename" , "FileExtension" , "FileType" , "Created" , "Author" , "LastModifiedTime" , "EditorOwsUser" , "ModifiedBy" , "LinkingUrl" , "SiteTitle" , "ParentLink" , "DocumentPreviewMetadata" , "ListID" , "ListItemID" , "SPSiteURL" , "SiteID" , "WebId" , "UniqueID" , "SPWebUrl" , "DefaultEncodingURL" , "PictureThumbnailURL" , ] } , SortList : { results : [ { "Property" : "LastModifiedTime" , "Direction" : 1 } ] } } ; const searchApi = `${ this . context . pageContext . web . absoluteUrl }/_api/search/postquery` ; const recentSearchDataResult = await this . context . spHttpClient . post ( searchApi , SPHttpClient . configurations . v1 , { headers : { 'accept' : 'application/json;odata=nometadata' , 'content-type' : 'application/json;odata=verbose' , "odata-version" : "" } , body : JSON . stringify ( { request : queryData } ) } ) ; if ( ! recentSearchDataResult || ! recentSearchDataResult . ok ) { throw new Error ( `Something went wrong when executing search query. Status='${ recentSearchDataResult . status }'` ) ; } const recentSearchData = await recentSearchDataResult . json ( ) ; if ( ! recentSearchData || ! recentSearchData . PrimaryQueryResult . RelevantResults . Table . Rows ) { throw new Error ( `Cannot read JSON result` ) ; } const recentFilesResult = recentSearchData . PrimaryQueryResult . RelevantResults . Table . Rows . map ( ( row ) => { return this . parseRecentSearchResult ( row . Cells ) ; } ) ; return recentFilesResult ; } catch ( err ) { console . error ( `[BingFilesService.executeRecentSearch]: Err='${ err . message }'` ) ; return null ; } } ; /**
     * Executes bing search for a file.
     */ public executeBingSearch = async ( queryParams ) : Promise < ISearchResult [ ] > => { try { const aspect = queryParams . aspect ? queryParams . aspect : 'All' ; const size = queryParams . size ? queryParams . size : 'All' ; const license = queryParams . license ? queryParams . license : 'Any' ; const query = queryParams . query ; const maxResults = queryParams . maxResults ? queryParams . maxResults : MAXRESULTS ; const maxFileSize = queryParams . maxFileSize ? queryParams . maxFileSize : MAXFILESIZE ; if ( query === undefined ) { return ; } const apiUrl = `https://www.bingapis.com/api/v7/images/search?appid=${ this . bingAPIKey }&traffictype=Internal_monitor&q=${ encodeURIComponent ( query ) }&count=${ maxResults }&aspect=${ aspect }&maxFileSize=${ maxFileSize }&size=${ size }&mkt=en-US&license=${ license }` ; const searchDataResponse = await this . context . httpClient . get ( apiUrl , SPHttpClient . configurations . v1 , { method : 'GET' , mode : 'cors' } ) ; if ( ! searchDataResponse || ! searchDataResponse . ok ) { throw new Error ( `Something went wrong when executing search query. Status='${ searchDataResponse . statusMessage }'` ) ; } const searchData = await searchDataResponse . json ( ) ; if ( ! searchData || ! searchData . value ) { throw new Error ( `Cannot read JSON result` ) ; } const bingResults = searchData . value ; const searchResults = bingResults . map ( item => this . parseBingSearchResult ( item ) ) ; return searchResults ; } catch ( err ) { console . error ( `[BingFilesService.executeSearch]: Err='${ err . message }'` ) ; return null ; } } ; /**
     * Downloads document content from SP location.
     */ public downloadSPFileContent = async ( absoluteFileUrl , fileName ) : Promise < File > => { try { const fileDownloadResult = await this . context . spHttpClient . get ( absoluteFileUrl , SPHttpClient . configurations . v1 ) ; if ( ! fileDownloadResult || ! fileDownloadResult . ok ) { throw new Error ( `Something went wrong when downloading the file. Status='${ fileDownloadResult . status }'` ) ; } const blob = await fileDownloadResult . blob ( ) ; return GeneralHelper . getFileFromBlob ( blob , fileName ) ; } catch ( err ) { console . error ( `[FileSearchService.fetchFileContent] Err='${ err . message }'` ) ; return null ; } } ; /**
     * Downloads document content from Remote location.
     */ public downloadBingContent = async ( absoluteFileUrl , fileName ) : Promise < File > => { try { const fileDownloadResult = await this . context . httpClient . get ( absoluteFileUrl , SPHttpClient . configurations . v1 , { method : "GET" , mode : "cors" } ) ; if ( ! fileDownloadResult || ! fileDownloadResult . ok ) { throw new Error ( `Something went wrong when downloading the file. Status='${ fileDownloadResult . status }'` ) ; } const blob = await fileDownloadResult . blob ( ) ; return GeneralHelper . getFileFromBlob ( blob , fileName ) ; } catch ( err ) { console . error ( `[FileSearchService.fetchFileContent] Err='${ err . message }'` ) ; return null ; } } ; /**
     * Parses Recent Search results.
     */ private parseRecentSearchResult = ( cells ) => { const titleCell = find ( cells , x => x . Key == "Title" ) ; const keyCell = find ( cells , x => x . Key == "UniqueID" ) ; const fileUrlCell = find ( cells , x => x . Key == "DefaultEncodingURL" ) ; const editedByCell = find ( cells , x => x . Key == "ModifiedBy" ) ; const recentFile = { key : keyCell ? keyCell . Value : null , name : titleCell ? titleCell . Value : null , fileUrl : fileUrlCell ? fileUrlCell . Value : null , editedBy : editedByCell ? editedByCell . Value : null } ; return recentFile ; } ; /**
     * Parses Bing search results.
     */ private parseBingSearchResult = ( bingResult ) : ISearchResult => { const width = bingResult ! . thumbnail ! . width ? bingResult ! . thumbnail ! . width : bingResult ! . width ; const height = bingResult ! . thumbnail ! . height ? bingResult ! . thumbnail ! . height : bingResult ! . height ; const searchResult = { thumbnailUrl : bingResult . thumbnailUrl , contentUrl : bingResult . contentUrl , displayUrl : this . getDisplayUrl ( bingResult . hostPageDisplayUrl ) , key : bingResult . imageId , width : width , height : height , } ; return searchResult ; } ; /**
     * Builds a file filter using the accepted file extensions
     */ private _getFileFilter ( accepts ? ) { let fileFilter = undefined ; if ( accepts ) { fileFilter = " AND (" ; accepts . forEach ( ( fileType , index ) => { fileType = fileType . replace ( "." , "" ) ; if ( index > 0 ) { fileFilter = fileFilter + " OR " ; } fileFilter = fileFilter + `FileExtension:${ fileType } OR SecondaryFileExtension:${ fileType }` ; } ) ; fileFilter = fileFilter + ")" ; } return fileFilter ; } param url private getDisplayUrl = ( url ) : string => { if ( url . indexOf ( '://' ) > - 1 ) { const urlParts = url . split ( '://' ) ; url = urlParts . pop ( ) ; } const splitUrl = url . split ( '/' ) ; return splitUrl [ 0 ] ; } ; }