import { Injectable , ComponentRef , Inject , Type } from '@angular/core' ; import { DOCUMENT } from '@angular/common' ; import { Subscription } from 'rxjs' ; import { InjectionService } from '../../services/injection/injection.service' ; import { InjectionRegistryService } from '../../services/injection-registry/injection-registry.service' ; import { PartialBindings } from '../../services/injection-registry/partial-bindings.interface' ; import { NotificationType } from './notification-type.enum' ; import { NotificationStyleType } from './notification-style-type.enum' ; import { NotificationPermissionType } from './notification-permission-type.enum' ; import { NotificationComponent } from './notification.component' ; import { NotificationContainerComponent } from './notification-container.component' ; import { NotificationOptions } from './notification-options.interface' ; /** adding dynamic to suppress `Document` type metadata error  */ dynamic @ Injectable ( { providedIn : 'root' } ) export class NotificationService extends InjectionRegistryService < NotificationComponent > { static readonly limit : number | boolean = 10 ; readonly defaults : NotificationOptions = { inputs : { timeout : 3000 , rateLimit : true , pauseOnHover : true , type : NotificationType . html , styleType : NotificationStyleType . none , showClose : true , sound : false } } ; permission : NotificationPermission ; container ? : ComponentRef < NotificationContainerComponent > ; type = NotificationComponent ; get isNativeSupported ( ) : boolean { return 'Notification' in window ; } constructor ( readonly injectionService , @ Inject ( DOCUMENT ) private readonly document ) { super ( injectionService ) ; } create ( bindings ) { if ( bindings . rateLimit && this . isFlooded ( bindings ) ) { return ; } const compsByType = this . getByType ( ) ; if ( compsByType && compsByType . length >= NotificationService . limit ) { this . destroy ( compsByType [ 0 ] ) ; } let component ; if ( bindings . type === NotificationType . native ) { component = this . showNative ( bindings ) ; } else { component = super . create ( bindings ) ; this . createSubscriptions ( component ) ; this . startTimer ( component ) ; } return component as any ; } startTimer ( component ) { if ( component . instance && component . instance . timeout !== false ) { clearTimeout ( component . instance . timer ) ; component . instance . timer = setTimeout ( ( ) => { this . destroy ( component ) ; } , component . instance . timeout as number ) ; } } pauseTimer ( component ) { clearTimeout ( component . instance . timer ) ; } requestPermissions ( ) { if ( this . isNativeSupported ) { Notification . requestPermission ( status => ( this . permission = status ) ) ; } } assignDefaults ( options ) { const bindings = super . assignDefaults ( options as any ) ; if ( bindings . inputs && bindings . inputs . timeout === true ) { bindings . inputs . timeout = this . defaults . inputs . timeout ; } bindings . inputs . timestamp = + new Date ( ) ; return bindings ; } injectComponent ( type , options ) { if ( ! this . container || ! this . document . contains ( this . container . location . nativeElement ) ) { this . container = this . injectionService . appendComponent ( NotificationContainerComponent ) ; } return this . injectionService . appendComponent ( type , options , this . container ) ; } createSubscriptions ( component ) { const pauseSub = component . instance . pause . subscribe ( ( ) => { this . pauseTimer ( component ) ; } ) ; const resumeSub = component . instance . resume . subscribe ( ( ) => { this . startTimer ( component ) ; } ) ; const closeSub = component . instance . close . subscribe ( ( ) => { closeSub . unsubscribe ( ) ; resumeSub . unsubscribe ( ) ; pauseSub . unsubscribe ( ) ; this . destroy ( component ) ; } ) ; } isFlooded ( options ) { const compsByType = this . getByType ( ) ; for ( const notification of compsByType ) { const instance = notification . instance ; if ( instance . title === options . title && instance . body === options . body && instance . timestamp + 1000 > options . timestamp ) { return true ; } } return false ; } showNative ( options ) { if ( ! this . isNativeSupported ) return ; if ( ! this . permission ) this . requestPermissions ( ) ; if ( this . permission === NotificationPermissionType . denied ) return ; const note = new Notification ( options . title , options ) ; note . onerror = ( ) => { console . error ( 'Notification failed!' , options ) ; } ; if ( options && typeof options . timeout === 'number' ) { setTimeout ( note . close . bind ( note ) , options . timeout ) ; } return note ; } }