import { compare } from '../utils/patch' ; import { isAngular , isDebugMode } from './utils/app-check' ; import { instanceWithMetadata , MutableTree , Node , Path , serializePath } from '../tree' ; import { onElementFound , onFindElement } from './utils/find-element' ; import { NgModulesRegistry , parseModulesFromRootElement , parseModulesFromRouter } from './utils/parse-modules' ; import { parseNgVersion } from './utils/parse-ng-version' ; import { createTreeFromElements } from '../tree/mutable-tree-factory' ; import { ApplicationError , ApplicationErrorType , browserDispatch , browserSubscribe , Message , MessageFactory , MessageType } from '../communication' ; import { parameterTypes } from '../tree/decorators' ; import { send } from './indirect-connection' ; import { clear as clearHighlights , getNodeFromPartialPath , getNodeInstanceParent , getNodeProvider , highlight , parseRoutes , Route } from './utils' ; import { serialize } from '../utils' ; import { MessageQueue } from '../structures' ; import { SimpleOptions } from '../options' ; import { MessagePipeBackend } from 'feature-modules/.lib' ; import { highlighter } from 'feature-modules/highlighter/backend/index' ; import { ApplicationRef , NgModuleRef } from '@angular/core' ; import { Subject , Subscription , timer } from 'rxjs' ; import { filter , takeWhile } from 'rxjs/operators' ; import 'reflect-metadata' ; declare const ng ; declare const getAllAngularRootElements ; declare const treeRenderOptions ; const deltaThreshold = 512 ; const CHECK_AFTER_NG_MODULE_DESTROY_RATE_MS = 50 ; const messageBuffer = new MessageQueue < Message < any > > ( ) ; let previousTree , previousRoutes , previousCount , onMouseOver , onMouseDown , lastTreeMessage ; const parsedModulesData = { modules : { } , names : [ ] , configs : { } , tokenIdMap : { } } ; const featureModulesPipe = new MessagePipeBackend ( { messageQueue : messageBuffer , sendMessage : send , createQueueAlertMessage : ( ) => MessageFactory . push ( ) } ) ; const onUpdateNotifier = new Subject < void > ( ) ; highlighter . useComponentTreeInstance ( previousTree ) ; highlighter . useDocumentInstance ( document ) ; highlighter . useOnUpdateNotifier ( onUpdateNotifier . asObservable ( ) ) ; highlighter . useMessagePipe ( featureModulesPipe ) ; const runAndHandleUncaughtExceptions = ( fn ) => { try { return fn ( ) ; } catch ( e ) { send ( MessageFactory . uncaughtApplicationError ( { name : e . name , stack : e . stack , message : e . message } ) ) ; } } ; const sendNgVersionMessage = ( ) => { const ngVersion = parseNgVersion ( ) ; send ( MessageFactory . ngVersion ( ngVersion ) ) ; } ; const logAuguryDetected = ( ) => { const ngVersion = parseNgVersion ( ) ; console . log ( `%c Augury %c Detected Angular %c v${ ngVersion } %c` , 'background:#3e5975 ; padding: 1px; border-radius: 3px 0 0 3px;  color: #fff; font-size: 14px;' , 'background:#DD0031 ; padding: 1px; color: #fff; font-size: 14px;' , 'background:#C3002F ; padding: 1px; border-radius: 0px 3px 3px 0;  color: #fff; font-size: 14px;' , 'background:transparent' ) ; } ; const sendNgModulesMessage = ( ) => { const ngModulesMessage = { names : parsedModulesData . names , tokenIdMap : parsedModulesData . tokenIdMap , configs : parsedModulesData . configs } ; messageBuffer . enqueue ( MessageFactory . ngModules ( ngModulesMessage ) ) ; send ( MessageFactory . push ( ) ) ; } ; const updateComponentTree = async ( roots , sendUpdates = true ) => { const { tree , count } = createTreeFromElements ( roots , treeRenderOptions ) ; if ( sendUpdates ) { if ( previousTree == null || Math . abs ( previousCount - count ) > deltaThreshold ) { messageBuffer . enqueue ( MessageFactory . completeTree ( tree ) ) ; } else { const changes = previousTree . diff ( tree ) ; if ( changes . length > 0 ) { lastTreeMessage = 'diff' ; messageBuffer . enqueue ( MessageFactory . treeDiff ( changes ) ) ; } else { if ( lastTreeMessage === 'no-diff' ) { return ; } lastTreeMessage = 'no-diff' ; messageBuffer . enqueue ( MessageFactory . treeUnchanged ( ) ) ; } } send ( MessageFactory . push ( ) ) ; } previousTree = tree ; highlighter . useComponentTreeInstance ( previousTree ) ; previousCount = count ; } ; const updateLazyLoadedNgModules = ( routers ) : Promise < void > => { return Promise . resolve ( ) . then ( ( ) => { routers . forEach ( router => { parseModulesFromRouter ( router , parsedModulesData ) ; } ) ; sendNgModulesMessage ( ) ; } ) ; } ; const updateRouterTree = ( ) => { return Promise . resolve ( ) . then ( ( ) => { const routers = routerTree ( ) ; const parsedRoutes = routers . map ( parseRoutes ) ; let routesChanged = ! previousRoutes ? true : false ; if ( previousRoutes ) { const changes = compare ( previousRoutes , parsedRoutes ) ; if ( changes . length > 0 ) { routesChanged = true ; } } previousRoutes = parsedRoutes ; if ( routesChanged ) { updateLazyLoadedNgModules ( routers ) ; messageBuffer . enqueue ( MessageFactory . routerTree ( parsedRoutes ) ) ; } } ) ; } ; let ngModuleRef ; let isStableSubscription ; const collectRoots = ( ) => getAllAngularRootElements ( ) . map ( root => ng . probe ( root ) ) . filter ( debugRoot => debugRoot !== null ) ; const listenForSomeTimeAndMaybeResubscribe = ( timeMs ) => { timer ( CHECK_AFTER_NG_MODULE_DESTROY_RATE_MS , CHECK_AFTER_NG_MODULE_DESTROY_RATE_MS ) . pipe ( takeWhile ( ( _ , i ) => ! ngModuleRef && i < 100 ) ) . forEach ( ( ) => { resubscribe ( ) ; } ) ; } ; const resubscribe = ( ) => { runAndHandleUncaughtExceptions ( ( ) => { logAuguryDetected ( ) ; sendNgVersionMessage ( ) ; messageBuffer . clear ( ) ; ngModuleRef = undefined ; parsedModulesData . modules = { } ; parsedModulesData . names = [ ] ; parsedModulesData . configs = { } ; parsedModulesData . tokenIdMap = { } ; setTimeout ( ( ) => { Promise . resolve ( ) . then ( ( ) => { runAndHandleUncaughtExceptions ( ( ) => { const roots = collectRoots ( ) ; if ( roots . length ) { let sanity ; const sanityThreshold = 0.2 * 1000 ; const appRef = parseModulesFromRootElement ( roots [ 0 ] , parsedModulesData ) ; if ( isStableSubscription ) { isStableSubscription . unsubscribe ( ) ; } isStableSubscription = appRef . isStable . pipe ( filter ( ( ) => sanity === undefined || new Date ( ) . getTime ( ) - sanity > sanityThreshold ) ) . subscribe ( e => { setTimeout ( ( ) => { sanity = new Date ( ) . getTime ( ) ; updateComponentTree ( collectRoots ( ) ) ; updateRouterTree ( ) ; send ( MessageFactory . ping ( ) ) ; } ) ; } ) ; ngModuleRef = ( appRef as any ) . _injector ; ngModuleRef . onDestroy ( ( ) => { ngModuleRef = undefined ; listenForSomeTimeAndMaybeResubscribe ( 1000 ) ; } ) ; sendNgModulesMessage ( ) ; } } ) ; } ) . then ( ( ) => runAndHandleUncaughtExceptions ( ( ) => { previousRoutes = null ; updateRouterTree ( ) ; } ) ) . then ( ( ) => onUpdateNotifier . next ( ) ) ; } ) ; } ) ; } ; const selectedComponentPropertyKey = '$$el' ; const noSelectedComponentWarningText = 'There is no component selected.' ; Object . defineProperty ( window , selectedComponentPropertyKey , { value : noSelectedComponentWarningText , configurable : true } ) ; const messageHandler = ( message ) => { featureModulesPipe . handleIncomingMessage ( message ) ; return runAndHandleUncaughtExceptions ( ( ) => { switch ( message . messageType ) { case MessageType . Initialize : Object . assign ( treeRenderOptions , message . content ) ; previousTree = null ; if ( ! isAngular ( ) ) { send ( MessageFactory . notNgApp ( ) ) ; } else if ( ! isDebugMode ( ) ) { send ( MessageFactory . applicationError ( new ApplicationError ( ApplicationErrorType . ProductionMode ) ) ) ; } else { resubscribe ( ) ; } return true ; case MessageType . Refresh : resubscribe ( ) ; return true ; case MessageType . SelectComponent : const path = message . content . path ; updateComponentTree ( collectRoots ( ) , false ) ; if ( previousTree ) { const node = previousTree . traverse ( path ) ; this . consoleReference ( node ) ; return getComponentInstance ( node ) ; } return ; case MessageType . UpdateProperty : return updateProperty ( previousTree , message . content . path , message . content . newValue ) ; case MessageType . UpdateProviderProperty : return updateProviderProperty ( previousTree , message . content . path , message . content . token , message . content . propertyPath , message . content . newValue ) ; case MessageType . EmitValue : return emitValue ( previousTree , message . content . path , message . content . value ) ; } return undefined ; } ) ; } ; browserSubscribe ( messageHandler ) ; const getComponentInstance = ( node ) => { if ( node ) { const probed = ng . probe ( node . nativeElement ( ) ) ; if ( probed ) { return instanceWithMetadata ( probed , node , probed . componentInstance ) ; } } return null ; } ; const updateNode = ( tree , path , fn ) => { const node = getNodeFromPartialPath ( tree , path ) ; if ( node ) { const probed = ng . probe ( node . nativeElement ( ) ) ; if ( probed ) { const ngZone = probed . injector . get ( ng . coreTokens . NgZone ) ; setTimeout ( ( ) => ngZone . run ( ( ) => fn ( probed ) ) ) ; } } } ; const updateProperty = ( tree , path , newValue ) => { updateNode ( tree , path , probed => { const instanceParent = getNodeInstanceParent ( probed , path ) ; if ( instanceParent ) { instanceParent [ path [ path . length - 1 ] ] = newValue ; } } ) ; } ; const updateProviderProperty = ( tree , path , token , propertyPath , newValue ) => { updateNode ( tree , path , probed => { const provider = getNodeProvider ( probed , token , propertyPath ) ; if ( provider ) { provider [ propertyPath [ propertyPath . length - 1 ] ] = newValue ; } } ) ; } ; const emitValue = ( tree , path , newValue ) => { const node = getNodeFromPartialPath ( tree , path ) ; if ( node ) { const probed = ng . probe ( node . nativeElement ( ) ) ; if ( probed ) { const instanceParent = getNodeInstanceParent ( probed , path ) ; if ( instanceParent ) { const ngZone = probed . injector . get ( ng . coreTokens . NgZone ) ; setTimeout ( ( ) => { ngZone . run ( ( ) => { const emittable = instanceParent [ path [ path . length - 1 ] ] ; if ( typeof emittable . emit === 'function' ) { emittable . emit ( newValue ) ; } else if ( typeof emittable . next === 'function' ) { emittable . next ( newValue ) ; } else { throw new Error ( `Cannot emit value for ${ serializePath ( path ) }` ) ; } } ) ; } ) ; } } } } ; export const routersFromRoots = ( ) => { const routers = [ ] ; for ( const element of collectRoots ( ) ) { const routerFn = parameterTypes ( element . componentInstance ) . reduce ( ( prev , curr , idx , p ) => ( prev ? prev : p [ idx ] !== null && p [ idx ] . name === 'Router' ? p [ idx ] : null ) , null ) ; if ( routerFn && element . componentInstance . router && element . componentInstance . router instanceof routerFn ) { routers . push ( element . componentInstance . router ) ; } } return routers ; } ; export const routerTree = ( ) : Array < any > => { let routers = new Array < any > ( ) ; if ( ng . coreTokens . Router ) { for ( const rootElement of collectRoots ( ) ) { routers = routers . concat ( rootElement . injector . get ( ng . coreTokens . Router ) ) ; } } else { for ( const router of routersFromRoots ( ) ) { routers = routers . concat ( router ) ; } } return routers ; } ; export const consoleReference = ( node ) => { Object . defineProperty ( window , selectedComponentPropertyKey , { get : ( ) => { if ( node ) { return ng . probe ( node . nativeElement ( ) ) ; } return null ; } , configurable : true } ) ; } ; export const extendWindowOperations = < T > ( target , classImpl ) => { for ( const key of Object . keys ( classImpl ) ) { if ( target [ key ] != null ) { throw new Error ( `A window function or object named ${ key } would be overwritten` ) ; } } Object . assign ( target , classImpl ) ; } ; export const applicationOperations = { nodeFromPath : ( id ) : Element => { if ( previousTree == null ) { throw new Error ( 'No tree exists' ) ; } const node = previousTree . lookup ( id ) ; if ( node == null ) { console . error ( `Cannot find element associated with node ${ id }` ) ; return null ; } return node . nativeElement ( ) ; } , response : < T > ( response ) => { browserDispatch ( response ) ; } , handleImmediate : < T > ( message ) => { const result = messageHandler ( message ) ; if ( result ) { return serialize ( result ) ; } return 'null' ; } , readMessageQueue : ( ) : Array < Message < any > > => { return messageBuffer . dequeue ( ) ; } } ; const findElement = message => { let currentNode , currentHighlights ; if ( message . content . start ) { onMouseOver = e => { if ( currentHighlights ) { clearHighlights ( currentHighlights . map ) ; } currentNode = onFindElement ( e , previousTree ) ; if ( currentNode ) { currentHighlights = highlight ( [ currentNode ] ) ; } } ; onMouseDown = ( ) => { onElementFound ( currentNode , currentHighlights , messageBuffer ) ; } ; window . addEventListener ( 'mouseover' , onMouseOver , false ) ; window . addEventListener ( 'mousedown' , onMouseDown , false ) ; } if ( message . content . stop ) { window . removeEventListener ( 'mouseover' , onMouseOver , false ) ; window . removeEventListener ( 'mousedown' , onMouseDown , false ) ; } } ; extendWindowOperations ( window || global || this , { inspectedApplication : applicationOperations } ) ;