import Guid from '@microsoft/sp-core-library/lib/Guid' ; import { SPHttpClient , SPHttpClientResponse } from '@microsoft/sp-http' ; import { IToast } from './IToast' ; interface IToastStatus { Id : number ; Ack : Date ; } interface IToastCache { Loaded ? : Date ; Toasts : IToast [ ] ; ToastStatuses : IToastStatus [ ] ; } /** Returns items from the Toast list and caches the results */ export class ToastService { private static readonly storageKeyBase : string = 'spfxToastr' ; private static readonly getFromListAlways : boolean = false ; /** Retrieves toasts that should be displayed for the given user*/ public static getToasts ( spHttpClient , baseUrl , webId ) { return new Promise < IToast [ ] > ( ( resolve , reject ) : void => { this . ensureToasts ( spHttpClient , baseUrl , webId ) . then ( ( toasts ) : void => { resolve ( toasts ) ; } ) . catch ( ( error ) : void => { reject ( error ) ; } ) ; } ) ; } param { number } id public static acknowledgeToast ( id , webId ) { let cachedData = ToastService . retrieveCache ( webId ) ; let index = ToastService . indexOfToastStatusById ( id , cachedData . ToastStatuses ) ; if ( index >= 0 ) { cachedData . ToastStatuses [ index ] . Ack = new Date ( ) ; } else { cachedData . ToastStatuses . push ( { Id : id , Ack : new Date ( ) } ) ; } ToastService . storeCache ( cachedData , webId ) ; } private static webStorageKey ( webId ) { return `${ ToastService . storageKeyBase }_${ webId }` ; } /** Rehydrates spfxToastr data from localStorage (or creates a new empty set) */ private static retrieveCache ( webId ) { let cachedData = localStorage ? JSON . parse ( localStorage . getItem ( this . webStorageKey ( webId ) ) ) : undefined ; if ( cachedData ) { cachedData . Loaded = new Date ( cachedData . Loaded . valueOf ( ) ) ; } else { cachedData = { Toasts : [ ] , ToastStatuses : [ ] } ; } return cachedData ; } /** Serializes spfxToastr data into localStorage */ private static storeCache ( cachedData , webId ) { if ( localStorage ) { localStorage . setItem ( this . webStorageKey ( webId ) , JSON . stringify ( cachedData ) ) ; } } /** Retrieves toasts from either the cache or the list depending on the cache's freshness */ private static ensureToasts ( spHttpClient , baseUrl , webId ) { return new Promise < IToast [ ] > ( ( resolve , reject ) : void => { let cachedData = ToastService . retrieveCache ( webId ) ; if ( cachedData . Loaded ) { let now = new Date ( ) ; let staleTime = new Date ( now . getTime ( ) + - 2 * 60000 ) ; if ( cachedData . Loaded > staleTime && ! ToastService . getFromListAlways ) { resolve ( ToastService . reduceToasts ( cachedData ) ) ; return ; } } if ( ( window as any ) . spfxToastrLoadingData ) { window . setTimeout ( ( ) : void => { ToastService . ensureToasts ( spHttpClient , baseUrl , webId ) . then ( ( toasts ) : void => { resolve ( toasts ) ; } ) ; } , 100 ) ; } else { ( window as any ) . spfxToastrLoadingData = true ; ToastService . getToastsFromList ( spHttpClient , baseUrl ) . then ( ( toasts ) : void => { cachedData . Toasts = toasts ; cachedData . Loaded = new Date ( ) ; cachedData = ToastService . processCache ( cachedData ) ; ToastService . storeCache ( cachedData , webId ) ; ( window as any ) . spfxToastrLoadingData = false ; resolve ( ToastService . reduceToasts ( cachedData ) ) ; } ) . catch ( ( error ) : void => { reject ( error ) ; } ) ; } } ) ; } private static readonly apiEndPoint : string = "_api/web/lists/getbytitle('Toast')/items" ; private static readonly select : string = "Id,Title,Severity,Frequency,Enabled,Message" ; private static readonly orderby : string = "StartDate asc" ; /** Pulls the active toast entries directly from the underlying list */ private static getToastsFromList ( spHttpClient , baseUrl ) { let now = new Date ( ) . toISOString ( ) ; let filter = `(StartDate le datetime'${ now }') and (EndDate ge datetime'${ now }')` ; return spHttpClient . get ( `${ baseUrl }/${ ToastService . apiEndPoint }?$select=${ ToastService . select }&$filter=${ filter }&$orderby=${ ToastService . orderby }` , SPHttpClient . configurations . v1 ) . then ( ( response ) : Promise < { value : IToast [ ] ; } > => { if ( ! response . ok ) { throw `Unable to get items: ${ response . status } (${ response . statusText })` ; } return response . json ( ) ; } ) . then ( ( results ) => { let toasts = [ ] ; for ( let v of results . value ) { toasts . push ( { Title : v . Title , Id : v . Id , Severity : v . Severity , Frequency : v . Frequency , Enabled : v . Enabled , Message : v . Message } ) ; } return toasts ; } ) ; } /** Helper function to return the index of an IToastStatus object by the Id property */ private static indexOfToastStatusById ( Id , toastStatuses ) { for ( let i = 0 ; i < toastStatuses . length ; i ++ ) { if ( toastStatuses [ i ] . Id == Id ) { return i ; } } return - 1 ; } /** Helper function to clean up the toast statuses by removing old toasts */ private static processCache ( cachedData ) { let activeIds = [ ] ; for ( let toast of cachedData . Toasts ) { activeIds . push ( toast . Id ) ; } cachedData . ToastStatuses = cachedData . ToastStatuses . filter ( ( value ) : boolean => { return activeIds . indexOf ( value . Id ) >= 0 ; } ) ; return cachedData ; } /** Adjusts the toasts to display based on what the user has already acknowledged and the toast's frequency value*/ private static reduceToasts ( cachedData ) { return cachedData . Toasts . filter ( ( toast ) : boolean => { if ( ! toast . Enabled ) { return false ; } let tsIndex = ToastService . indexOfToastStatusById ( toast . Id , cachedData . ToastStatuses ) ; if ( tsIndex >= 0 ) { let lastShown = new Date ( cachedData . ToastStatuses [ tsIndex ] . Ack . valueOf ( ) ) ; switch ( toast . Frequency ) { case 'Once' : return false ; case 'Always' : return true ; default : let now = new Date ( ) ; if ( now . getFullYear ( ) !== lastShown . getFullYear ( ) || now . getMonth ( ) !== lastShown . getMonth ( ) || now . getDay ( ) !== lastShown . getDay ( ) ) { return true ; } else { return false ; } } } else { return true ; } } ) ; } }