declare var System ; import * as AngularCore from '@angular/core' ; import * as Toolkit from 'chipmunk.client.toolkit' ; import { Subscription } from 'rxjs' ; import { Compiler , Injector } from '@angular/core' ; import { IPC } from './service.electron.ipc' ; import { IService } from '../interfaces/interface.service' ; import { ControllerPluginGate } from '../controller/controller.plugin.gate' ; import { IAPI } from 'chipmunk.client.toolkit' ; import { getAvailablePluginModules } from '../controller/controller.plugin.deps' ; import ElectronIpcService from './service.electron.ipc' ; import PluginsIPCService from './service.plugins.ipc' ; import LogsService from './service.logs' ; import OutputParsersService from './standalone/service.output.parsers' ; import SelectionParsersService from './standalone/service.selection.parsers' ; import ControllerPluginIPC from '../controller/controller.plugin.ipc' ; import ControllerPluginsManager from '../controller/controller.plugins.manager' ; export type TRowParser = ( str : string ) => string ; export interface IPluginControllers { sessions : Toolkit . ControllerSessionsEvents ; } export interface IPluginData { name : string ; token : string ; exports : Toolkit . IPluginExports ; ipc : ControllerPluginIPC ; controllers : IPluginControllers ; id : number ; displayName : string ; factories : { [ key ] : any ; } ; mwcf ? : AngularCore . ModuleWithComponentFactories < any > ; } const CPluginEvents = { onSessionChange : 'onSessionChange' , onSessionOpen : 'onSessionOpen' , onSessionClose : 'onSessionClose' , } ; export class PluginsService extends Toolkit . Emitter implements IService { public Events = { pluginsLoaded : 'pluginsLoaded' , onTaskBarPlugin : 'onTaskBarPlugin' , } ; private _logger : Toolkit . Logger = new Toolkit . Logger ( 'PluginsService' ) ; private _compiler ! : Compiler ; private _injector ! : Injector ; private _plugins : Map < string , IPluginData > = new Map ( ) ; private _subscriptions : { [ key ] : Subscription | Toolkit . Subscription ; } = { } ; private _idsCache : { [ key ] : IPluginData ; } = { } ; private _factories : AngularCore . ComponentFactory < any > [ ] = [ ] ; private _getPluginAPIByID ? : ( pluginId : number ) => IAPI ; private _manager : ControllerPluginsManager ; private _jitCompiler : any ; constructor ( ) { super ( ) ; this . _manager = new ControllerPluginsManager ( ) ; this . _subscriptions . mountPlugin = ElectronIpcService . subscribe ( IPC . RenderMountPlugin , this . _ipc_onRenderMountPlugin . bind ( this ) ) ; } public init ( ) { return new Promise ( ( resolve ) => { System . import ( './compiler.umd.js' ) . then ( ( mod ) => { this . _jitCompiler = mod ; this . _logger . debug ( `Angular JIT compiler is loaded` ) ; resolve ( ) ; } ) . catch ( ( err ) => { this . _logger . warn ( `Fail to load Angular JIT compiler. Angular based plugins would not be available. Error: ${ err . message }` ) ; resolve ( ) ; } ) ; } ) ; } public getName ( ) { return 'PluginsService' ; } public defineCompilerAndInjector ( compiler , injector ) { this . _compiler = compiler ; this . _injector = injector ; } public destroy ( ) { Object . keys ( this . _subscriptions ) . forEach ( ( key ) => { this . _subscriptions [ key ] . unsubscribe ( ) ; } ) ; this . _manager . destroy ( ) ; } public getPlugin ( name ) { return this . _plugins . get ( name ) ; } public getPluginById ( id ) { if ( this . _idsCache [ id ] !== undefined ) { return this . _idsCache [ id ] ; } let name ; this . _plugins . forEach ( ( plugin , pluginName ) => { if ( plugin . id === id ) { name = pluginName ; } } ) ; if ( name === undefined ) { this . _logger . warn ( `Fail to find plugin by ID: ${ id }` ) ; return undefined ; } let plugin = this . _plugins . get ( name ) ; if ( plugin !== undefined ) { this . _idsCache [ id ] = plugin ; } return this . _idsCache [ id ] ; } public getPluginFactory ( id , selector ) { const plugin = this . getPluginById ( id ) ; if ( plugin === undefined ) { return undefined ; } if ( plugin . mwcf === undefined ) { return ; } return plugin . mwcf . componentFactories . find ( ( e ) => e . selector === selector ) ; } public fire ( ) { return { onSessionChange : this . _fire . bind ( this , CPluginEvents . onSessionChange ) , onSessionOpen : this . _fire . bind ( this , CPluginEvents . onSessionOpen ) , onSessionClose : this . _fire . bind ( this , CPluginEvents . onSessionClose ) , } ; } public getStoredFactoryBySelector ( selector ) { return this . _factories . find ( ( e ) => e . selector === selector ) ; } public getStoredFactoryByName ( name ) { return this . _factories . find ( ( e ) => e . componentType . name === name ) ; } public setPluginAPIGetter ( getter ) { this . _getPluginAPIByID = getter ; } public getAvailablePlugins ( ) { return Array . from ( this . _plugins . values ( ) ) ; } public getManager ( ) { return this . _manager ; } private _fire ( event , ... args ) { this . _plugins . forEach ( ( plugin ) => { const emitter = plugin . controllers . sessions . emit ( ) ; if ( typeof ( emitter as any ) [ event ] !== 'function' ) { return ; } else { } ( emitter as any ) [ event ] ( ... args ) ; } ) ; } private _loadAndInit ( name , displayName , token , id , location ) { return new Promise ( ( resolve , reject ) => { Toolkit . sequences ( [ this . _loadAndInit_FetchPlugin . bind ( this , name , token , id , location ) , this . _loadAndInit_InitPlugin . bind ( this , name , token , id , location ) , this . _loadAndInit_CompilePlugin . bind ( this , name , token , id , displayName ) , this . _loadAndInit_SetupPluginService . bind ( this , name , token , id , location ) , ] ) . then ( ( pluginData ) => { resolve ( pluginData ) ; } ) . catch ( ( error ) => { reject ( error ) ; } ) ; } ) ; } private _loadAndInit_FetchPlugin ( name , token , id , location ) { return new Promise ( ( resolve , reject ) => { fetch ( location ) . then ( ( response ) => { response . text ( ) . then ( ( source ) => { resolve ( source ) ; } ) . catch ( ( responseError ) => { reject ( new Error ( this . _logger . error ( `Response of plugin "${ name }" wasn't parsed correctly due error: ${ responseError . message }` ) ) ) ; } ) ; } ) . catch ( ( fetchError ) => { reject ( new Error ( this . _logger . error ( `Plugin "${ name }" wasn't loaded due error: ${ fetchError . message }` ) ) ) ; } ) ; } ) ; } private _loadAndInit_InitPlugin ( name , token , id , location , code ) { return new Promise ( ( resolve , reject ) => { this . _logger . env ( `Sources of plugin "${ name }" was fetch correctly.` ) ; const modules = getAvailablePluginModules ( ) ; const require = ( module ) => modules [ module ] ; let exports = { } ; const gate = new ControllerPluginGate ( modules , require ) ; ( window as any ) . chipmunk = gate ; ( window as any ) . logviewer = gate ; if ( ( window as any ) . global === undefined ) { ( window as any ) . global = { } ; } if ( ( window as any ) . global . ng === undefined && this . _jitCompiler !== undefined ) { ( window as any ) . global . ng = { ɵcompilerFacade : this . _jitCompiler . default . ɵcompilerFacade , compiler : this . _jitCompiler . default . compiler , } ; } if ( ! LogsService . isProduction ( ) ) { code = `console.log('Reference to code of plugin "${ name . replace ( /'/gi , '"' ) }"');\n${ code }` ; } try { eval ( code ) ; } catch ( executeError ) { return reject ( new Error ( this . _logger . error ( `Fail to execute plugin "${ name }" due error: ${ executeError instanceof Error ? executeError . message : executeError }` ) ) ) ; } if ( Object . keys ( exports ) . length === 0 ) { exports = gate . getPluginExports ( ) ; } delete ( window as any ) . logviewer ; resolve ( exports ) ; } ) ; } private _loadAndInit_CompilePlugin ( name , token , id , displayName , exports ) { return new Promise ( ( resolve , reject ) => { const ngModule = this . _getNgModule ( exports ) ; if ( ngModule === undefined ) { const pluginData = { name : name , displayName : displayName , token : token , exports : exports , ipc : new ControllerPluginIPC ( name , token ) , controllers : { sessions : new Toolkit . ControllerSessionsEvents ( ) , } , id : id , factories : { } , } ; OutputParsersService . setParsers ( exports , id ) ; SelectionParsersService . setParsers ( exports , id ) ; resolve ( pluginData ) ; } else { this . _compiler . compileModuleAndAllComponentsAsync < any > ( ngModule ) . then ( ( mwcf ) => { try { const module = mwcf . ngModuleFactory . create ( this . _injector ) ; if ( ! ( module . instance instanceof ngModule ) ) { return reject ( new Error ( this . _logger . error ( `Fail to compile main module of plugin "${ name }".` ) ) ) ; } const pluginData = { name : name , displayName : displayName , token : token , exports : exports , ipc : new ControllerPluginIPC ( name , token ) , controllers : { sessions : new Toolkit . ControllerSessionsEvents ( ) , } , id : id , factories : { } , mwcf : mwcf , } ; OutputParsersService . setParsers ( exports , id , mwcf ) ; SelectionParsersService . setParsers ( exports , id ) ; const selectors = Object . keys ( Toolkit . EViewsTypes ) . map ( ( key ) => { return ( Toolkit . EViewsTypes as any ) [ key ] ; } ) ; this . _factories . push ( ... mwcf . componentFactories . filter ( ( factory ) => { if ( selectors . indexOf ( factory . selector ) !== - 1 ) { return false ; } if ( factory . selector . indexOf ( 'lib-primitive' ) !== - 1 || factory . selector . indexOf ( 'lib-complex' ) !== - 1 || factory . selector . indexOf ( 'lib-containers' ) !== - 1 ) { return false ; } return ! this . _isComponentFactoryStored ( factory ) ; } ) ) ; Object . keys ( Toolkit . EViewsTypes ) . forEach ( ( alias ) => { const selector = ( Toolkit . EViewsTypes as any ) [ alias ] ; const componentFactory = mwcf . componentFactories . find ( ( e ) => e . selector === selector ) ; if ( componentFactory ) { pluginData . factories [ selector ] = componentFactory ; } } ) ; resolve ( pluginData ) ; } catch ( moduleCompileError ) { return reject ( new Error ( this . _logger . error ( `Fail to compile main module of plugin "${ name }" due error: ${ moduleCompileError instanceof Error ? moduleCompileError . message : moduleCompileError }.` ) ) ) ; } } ) . catch ( ( compileError ) => { reject ( new Error ( this . _logger . error ( `Fail to compile plugin "${ name }" due error: ${ compileError . message }` ) ) ) ; } ) ; } } ) ; } private _loadAndInit_SetupPluginService ( name , token , id , location , pluginData ) { return new Promise ( ( resolve ) => { const service = this . _getPluginService ( pluginData . exports ) ; if ( service === undefined ) { return resolve ( pluginData ) ; } service . setAPIGetter ( this . _getPluginAPI . bind ( this , id ) ) ; resolve ( pluginData ) ; } ) ; } private _getPluginAPI ( id ) { if ( this . _getPluginAPIByID === undefined ) { return undefined ; } return this . _getPluginAPIByID ( id ) ; } private _getNgModule ( exports ) { let module ; Object . keys ( exports ) . forEach ( ( key ) => { if ( module === undefined && Toolkit . PluginNgModule . isInstance ( exports [ key ] ) ) { module = ( exports as any ) [ key ] as Toolkit . PluginNgModule ; } } ) ; return module as any ; } private _getPluginService ( exports ) { let service ; Object . keys ( exports ) . forEach ( ( key ) => { if ( service === undefined && Toolkit . PluginService . isInstance ( exports [ key ] ) ) { service = exports [ key ] as Toolkit . PluginService ; } } ) ; return service ; } private _inspectComponentsOfPlugins ( ) { this . _plugins . forEach ( ( plugin ) => { if ( plugin . factories [ Toolkit . EViewsTypes . tasksBar ] !== undefined ) { this . emit ( this . Events . onTaskBarPlugin , plugin . id , plugin . factories [ Toolkit . EViewsTypes . tasksBar ] , plugin . ipc ) ; } } ) ; } private _ipc_onRenderMountPlugin ( event ) { let left = event . plugins . length ; const done = ( ) => { left -= 1 ; if ( left === 0 ) { setTimeout ( ( ) => { this . emit ( this . Events . pluginsLoaded ) ; this . _inspectComponentsOfPlugins ( ) ; } , 50 ) ; } } ; if ( event . plugins . length === 0 ) { this . emit ( this . Events . pluginsLoaded ) ; return ; } event . plugins . forEach ( ( pluginInfo ) => { this . _logger . env ( `Information about plugin "${ pluginInfo . name }" has been gotten. Starting loading & initialization.` ) ; this . _loadAndInit ( pluginInfo . name , pluginInfo . displayName , pluginInfo . token , pluginInfo . id , pluginInfo . location ) . then ( ( pluginData ) => { PluginsIPCService . addPlugin ( pluginInfo . token , pluginData . ipc ) ; this . _plugins . set ( pluginData . name , pluginData ) ; this . _logger . env ( `Plugin "${ pluginInfo . name }" is successfully mount.` ) ; done ( ) ; } ) . catch ( ( loadError ) => { this . _logger . error ( `Fail to load and initialize plugin "${ pluginInfo . name }" due error: ${ loadError . message }` ) ; done ( ) ; } ) ; } ) ; } private _isComponentFactoryStored ( factory ) { return this . _factories . find ( ( e ) => e . selector === factory . selector ) !== undefined ; } } export default new PluginsService ( ) ;