import { withTimeout , isPromise } from '@kbn/std' ; import { PluginName , PluginOpaqueId } from '../../server' ; import { CoreService } from '../../types' ; import { CoreContext } from '../core_system' ; import { PluginWrapper } from './plugin' ; import { createPluginInitializerContext , createPluginSetupContext , createPluginStartContext , } from './plugin_context' ; import { InternalCoreSetup , InternalCoreStart } from '../core_system' ; import { InjectedPluginMetadata } from '../injected_metadata' ; const Sec = 1000 ; internal export type PluginsServiceSetupDeps = InternalCoreSetup ; internal export type PluginsServiceStartDeps = InternalCoreStart ; internal export interface PluginsServiceSetup { contracts : ReadonlyMap < string , unknown > ; } internal export interface PluginsServiceStart { contracts : ReadonlyMap < string , unknown > ; } internal export class PluginsService implements CoreService < PluginsServiceSetup , PluginsServiceStart > { /** Plugin wrappers in topological order. */ private readonly plugins = new Map < PluginName , PluginWrapper < unknown , unknown > > ( ) ; private readonly pluginDependencies = new Map < PluginName , PluginName [ ] > ( ) ; private readonly satupPlugins : PluginName [ ] = [ ] ; constructor ( private readonly coreContext , plugins ) { const opaqueIds = new Map < PluginName , PluginOpaqueId > ( plugins . map ( ( p ) => [ p . id , Symbol ( p . id ) ] ) ) ; plugins . forEach ( ( { id , plugin , config = { } } ) => { this . pluginDependencies . set ( id , [ ... plugin . requiredPlugins , ... plugin . optionalPlugins . filter ( ( optPlugin ) => opaqueIds . has ( optPlugin ) ) , ] ) ; this . plugins . set ( id , new PluginWrapper ( plugin , opaqueIds . get ( id ) ! , createPluginInitializerContext ( this . coreContext , opaqueIds . get ( id ) ! , plugin , config ) ) ) ; } ) ; } public getOpaqueIds ( ) { return new Map ( [ ... this . pluginDependencies ] . map ( ( [ id , deps ] ) => [ this . plugins . get ( id ) ! . opaqueId , deps . map ( ( depId ) => this . plugins . get ( depId ) ! . opaqueId ) , ] ) ) ; } public async setup ( deps ) { const contracts = new Map < string , unknown > ( ) ; for ( const [ pluginName , plugin ] of this . plugins . entries ( ) ) { const pluginDepContracts = [ ... this . pluginDependencies . get ( pluginName ) ! ] . reduce ( ( depContracts , dependencyName ) => { if ( contracts . has ( dependencyName ) ) { depContracts [ dependencyName ] = contracts . get ( dependencyName ) ; } return depContracts ; } , { } as Record < PluginName , unknown > ) ; let contract ; const contractOrPromise = plugin . setup ( createPluginSetupContext ( this . coreContext , deps , plugin ) , pluginDepContracts ) ; if ( isPromise ( contractOrPromise ) ) { if ( this . coreContext . env . mode . dev ) { console . log ( `Plugin ${ pluginName } is using asynchronous setup lifecycle. Asynchronous plugins support will be removed in a later version.` ) ; } const contractMaybe = await withTimeout ( { promise : contractOrPromise , timeoutMs : 10 * Sec , } ) ; if ( contractMaybe . timedout ) { throw new Error ( `Setup lifecycle of "${ pluginName }" plugin wasn't completed in 10sec. Consider disabling the plugin and re-start.` ) ; } else { contract = contractMaybe . value ; } } else { contract = contractOrPromise ; } contracts . set ( pluginName , contract ) ; this . satupPlugins . push ( pluginName ) ; } return { contracts } ; } public async start ( deps ) { const contracts = new Map < string , unknown > ( ) ; for ( const [ pluginName , plugin ] of this . plugins . entries ( ) ) { const pluginDepContracts = [ ... this . pluginDependencies . get ( pluginName ) ! ] . reduce ( ( depContracts , dependencyName ) => { if ( contracts . has ( dependencyName ) ) { depContracts [ dependencyName ] = contracts . get ( dependencyName ) ; } return depContracts ; } , { } as Record < PluginName , unknown > ) ; let contract ; const contractOrPromise = plugin . start ( createPluginStartContext ( this . coreContext , deps , plugin ) , pluginDepContracts ) ; if ( isPromise ( contractOrPromise ) ) { if ( this . coreContext . env . mode . dev ) { console . log ( `Plugin ${ pluginName } is using asynchronous start lifecycle. Asynchronous plugins support will be removed in a later version.` ) ; } const contractMaybe = await withTimeout ( { promise : contractOrPromise , timeoutMs : 10 * Sec , } ) ; if ( contractMaybe . timedout ) { throw new Error ( `Start lifecycle of "${ pluginName }" plugin wasn't completed in 10sec. Consider disabling the plugin and re-start.` ) ; } else { contract = contractMaybe . value ; } } else { contract = contractOrPromise ; } contracts . set ( pluginName , contract ) ; } return { contracts } ; } public async stop ( ) { for ( const pluginName of this . satupPlugins . reverse ( ) ) { this . plugins . get ( pluginName ) ! . stop ( ) ; } } }