import { PluginPermission, SharedData, hasPluginPermission } from '@vue-devtools/shared-utils' import type { HookHandler, HookPayloads, Hookable } from '@vue/devtools-api' import { Hooks } from '@vue/devtools-api' import type { BackendContext } from './backend-context' import type { Plugin } from './plugin' type Handler = HookHandler export interface HookHandlerData { handler: Handler plugin: Plugin } export class DevtoolsHookable implements Hookable { private handlers: Partial<{ [eventType in Hooks]: HookHandlerData[] }> = {} private ctx: BackendContext private plugin: Plugin constructor(ctx: BackendContext, plugin: Plugin = null) { this.ctx = ctx this.plugin = plugin } private hook(eventType: T, handler: Handler, pluginPermision: PluginPermission = null) { const handlers = (this.handlers[eventType] = this.handlers[eventType] || []) as HookHandlerData[] if (this.plugin) { const originalHandler = handler handler = (...args) => { // Plugin permission if (!hasPluginPermission(this.plugin.descriptor.id, PluginPermission.ENABLED) || (pluginPermision && !hasPluginPermission(this.plugin.descriptor.id, pluginPermision)) ) { return } // App scope if (!this.plugin.descriptor.disableAppScope && this.ctx.currentAppRecord?.options.app !== this.plugin.descriptor.app) { return } // Plugin scope if (!this.plugin.descriptor.disablePluginScope && (args[0] as any).pluginId != null && (args[0] as any).pluginId !== this.plugin.descriptor.id) { return } return originalHandler(...args) } } handlers.push({ handler, plugin: this.ctx.currentPlugin, }) } async callHandlers(eventType: T, payload: HookPayloads[T], ctx: BackendContext) { if (this.handlers[eventType]) { const handlers = this.handlers[eventType] as HookHandlerData[] for (let i = 0; i < handlers.length; i++) { const { handler, plugin } = handlers[i] try { await handler(payload, ctx) } catch (e) { if (SharedData.debugInfo) { console.error(`An error occurred in hook '${eventType}'${plugin ? ` registered by plugin '${plugin.descriptor.id}'` : ''} with payload:`, payload) console.error(e) } } } } return payload } transformCall(handler: Handler) { this.hook(Hooks.TRANSFORM_CALL, handler) } getAppRecordName(handler: Handler) { this.hook(Hooks.GET_APP_RECORD_NAME, handler) } getAppRootInstance(handler: Handler) { this.hook(Hooks.GET_APP_ROOT_INSTANCE, handler) } registerApplication(handler: Handler) { this.hook(Hooks.REGISTER_APPLICATION, handler) } walkComponentTree(handler: Handler) { this.hook(Hooks.WALK_COMPONENT_TREE, handler, PluginPermission.COMPONENTS) } visitComponentTree(handler: Handler) { this.hook(Hooks.VISIT_COMPONENT_TREE, handler, PluginPermission.COMPONENTS) } walkComponentParents(handler: Handler) { this.hook(Hooks.WALK_COMPONENT_PARENTS, handler, PluginPermission.COMPONENTS) } inspectComponent(handler: Handler) { this.hook(Hooks.INSPECT_COMPONENT, handler, PluginPermission.COMPONENTS) } getComponentBounds(handler: Handler) { this.hook(Hooks.GET_COMPONENT_BOUNDS, handler, PluginPermission.COMPONENTS) } getComponentName(handler: Handler) { this.hook(Hooks.GET_COMPONENT_NAME, handler, PluginPermission.COMPONENTS) } getComponentInstances(handler: Handler) { this.hook(Hooks.GET_COMPONENT_INSTANCES, handler, PluginPermission.COMPONENTS) } getElementComponent(handler: Handler) { this.hook(Hooks.GET_ELEMENT_COMPONENT, handler, PluginPermission.COMPONENTS) } getComponentRootElements(handler: Handler) { this.hook(Hooks.GET_COMPONENT_ROOT_ELEMENTS, handler, PluginPermission.COMPONENTS) } editComponentState(handler: Handler) { this.hook(Hooks.EDIT_COMPONENT_STATE, handler, PluginPermission.COMPONENTS) } getComponentDevtoolsOptions(handler: Handler) { this.hook(Hooks.GET_COMPONENT_DEVTOOLS_OPTIONS, handler, PluginPermission.COMPONENTS) } getComponentRenderCode(handler: Handler) { this.hook(Hooks.GET_COMPONENT_RENDER_CODE, handler, PluginPermission.COMPONENTS) } inspectTimelineEvent(handler: Handler) { this.hook(Hooks.INSPECT_TIMELINE_EVENT, handler, PluginPermission.TIMELINE) } timelineCleared(handler: Handler) { this.hook(Hooks.TIMELINE_CLEARED, handler, PluginPermission.TIMELINE) } getInspectorTree(handler: Handler) { this.hook(Hooks.GET_INSPECTOR_TREE, handler, PluginPermission.CUSTOM_INSPECTOR) } getInspectorState(handler: Handler) { this.hook(Hooks.GET_INSPECTOR_STATE, handler, PluginPermission.CUSTOM_INSPECTOR) } editInspectorState(handler: Handler) { this.hook(Hooks.EDIT_INSPECTOR_STATE, handler, PluginPermission.CUSTOM_INSPECTOR) } setPluginSettings(handler: Handler) { this.hook(Hooks.SET_PLUGIN_SETTINGS, handler) } }