import { AppRecord , SimpleAppRecord , AppRecordOptions , BackendContext , DevtoolsBackend , } from '@vue-devtools/app-backend-api' ; import { BridgeEvents , SharedData } from '@vue-devtools/shared-utils' ; import { App } from '@vue/devtools-api' ; import slug from 'speakingurl' ; import { JobQueue } from './util/queue' ; import { scan } from './legacy/scan' ; import { addBuiltinLayers , removeLayersForApp } from './timeline' ; import { getBackend , availableBackends } from './backend' ; const jobs = new JobQueue ( ) ; let recordId = 0 ; type AppRecordResolver = ( record : AppRecord ) => void | Promise < void > ; const appRecordPromises = new Map < App , AppRecordResolver [ ] > ( ) ; export async function registerApp ( options , ctx ) { return jobs . queue ( ( ) => registerAppJob ( options , ctx ) ) ; } async function registerAppJob ( options , ctx ) { if ( ctx . appRecords . find ( a => a . options === options ) ) { return ; } const baseFrameworkVersion = parseInt ( options . version . substr ( 0 , options . version . indexOf ( '.' ) ) ) ; for ( let i = 0 ; i < availableBackends . length ; i ++ ) { const backendOptions = availableBackends [ i ] ; if ( backendOptions . frameworkVersion === baseFrameworkVersion ) { const backend = getBackend ( backendOptions , ctx ) ; await createAppRecord ( options , backend , ctx ) ; break ; } } } async function createAppRecord ( options , backend , ctx ) { const rootInstance = await backend . api . getAppRootInstance ( options . app ) ; if ( rootInstance ) { if ( ( await backend . api . getComponentDevtoolsOptions ( rootInstance ) ) . hide ) { return ; } recordId ++ ; const name = await backend . api . getAppRecordName ( options . app , recordId . toString ( ) ) ; const id = getAppRecordId ( options . app , slug ( name ) ) ; const [ el ] = await backend . api . getComponentRootElements ( rootInstance ) ; const record = { id , name , options , backend , lastInspectedComponentId : null , instanceMap : new Map ( ) , rootInstance , perfGroupIds : new Map ( ) , iframe : document !== el . ownerDocument ? el . ownerDocument . location . pathname : null , meta : options . meta ?? { } , } ; options . app . __VUE_DEVTOOLS_APP_RECORD__ = record ; const rootId = `${ record . id }:root` ; record . instanceMap . set ( rootId , record . rootInstance ) ; record . rootInstance . __VUE_DEVTOOLS_UID__ = rootId ; addBuiltinLayers ( record , ctx ) ; ctx . appRecords . push ( record ) ; if ( backend . options . setupApp ) { backend . options . setupApp ( backend . api , record ) ; } await backend . api . registerApplication ( options . app ) ; ctx . bridge . send ( BridgeEvents . TO_FRONT_APP_ADD , { appRecord : mapAppRecord ( record ) , } ) ; if ( appRecordPromises . has ( options . app ) ) { for ( const r of appRecordPromises . get ( options . app ) ) { await r ( record ) ; } } if ( ctx . currentAppRecord == null ) { await selectApp ( record , ctx ) ; } } else { console . warn ( '[Vue devtools] No root instance found for app, it might have been unmounted' , options . app ) ; } } export async function selectApp ( record , ctx ) { ctx . currentAppRecord = record ; ctx . currentInspectedComponentId = record . lastInspectedComponentId ; ctx . bridge . send ( BridgeEvents . TO_FRONT_APP_SELECTED , { id : record . id , lastInspectedComponentId : record . lastInspectedComponentId , } ) ; } export function mapAppRecord ( record ) { return { id : record . id , name : record . name , version : record . options . version , iframe : record . iframe , } ; } const appIds = new Set ( ) ; export function getAppRecordId ( app , defaultId ? ) { if ( app . __VUE_DEVTOOLS_APP_RECORD_ID__ != null ) { return app . __VUE_DEVTOOLS_APP_RECORD_ID__ ; } let id = defaultId ?? ( recordId ++ ) . toString ( ) ; if ( defaultId && appIds . has ( id ) ) { let count = 1 ; while ( appIds . has ( `${ defaultId }:${ count }` ) ) { count ++ ; } id = `${ defaultId }_${ count }` ; } appIds . add ( id ) ; app . __VUE_DEVTOOLS_APP_RECORD_ID__ = id ; return id ; } export async function getAppRecord ( app , ctx ) { const record = ctx . appRecords . find ( ar => ar . options . app === app ) ; if ( record ) { return record ; } return new Promise ( ( resolve , reject ) => { let resolvers = appRecordPromises . get ( app ) ; let timedOut = false ; if ( ! resolvers ) { resolvers = [ ] ; appRecordPromises . set ( app , resolvers ) ; } const fn = ( record ) => { if ( ! timedOut ) { clearTimeout ( timer ) ; resolve ( record ) ; } } ; resolvers . push ( fn ) ; const timer = setTimeout ( ( ) => { timedOut = true ; const index = resolvers . indexOf ( fn ) ; if ( index !== - 1 ) resolvers . splice ( index , 1 ) ; if ( SharedData . debugInfo ) { console . log ( 'Timed out waiting for app record' , app ) ; } reject ( new Error ( `Timed out getting app record for app` ) ) ; } , 60000 ) ; } ) ; } export function waitForAppsRegistration ( ) { return jobs . queue ( async ( ) => { } ) ; } export async function sendApps ( ctx ) { const appRecords = [ ] ; for ( const appRecord of ctx . appRecords ) { appRecords . push ( appRecord ) ; } ctx . bridge . send ( BridgeEvents . TO_FRONT_APP_LIST , { apps : appRecords . map ( mapAppRecord ) , } ) ; } export async function removeApp ( app , ctx ) { try { const appRecord = await getAppRecord ( app , ctx ) ; if ( appRecord ) { appIds . delete ( appRecord . id ) ; const index = ctx . appRecords . indexOf ( appRecord ) ; if ( index !== - 1 ) ctx . appRecords . splice ( index , 1 ) ; removeLayersForApp ( app , ctx ) ; ctx . bridge . send ( BridgeEvents . TO_FRONT_APP_REMOVE , { id : appRecord . id } ) ; } } catch ( e ) { if ( SharedData . debugInfo ) { console . error ( e ) ; } } } export async function _legacy_getAndRegisterApps ( Vue , ctx ) { const apps = scan ( ) ; apps . forEach ( app => { registerApp ( { app , types : { } , version : Vue . version , meta : { Vue , } , } , ctx ) ; } ) ; }