import { Logger } from '../utils/Logger.js';
import { ComponentManager, ComponentInstance, HookInstance } from '../managers/ComponentManager.js';
import { EventEmitter } from 'events';
/**
* React DevTools fiber node representation
*/
export interface ReactFiberNode {
id: number;
displayName: string;
type: string;
key: string | null;
props: Record<string, any>;
state: Record<string, any>;
hooks: any[];
children: ReactFiberNode[];
parent: ReactFiberNode | null;
source?: {
fileName: string;
lineNumber: number;
};
}
/**
* React DevTools profiler data
*/
export interface ReactProfilerData {
componentName: string;
phase: 'mount' | 'update';
actualDuration: number;
baseDuration: number;
startTime: number;
commitTime: number;
}
/**
* Bridge to React DevTools for component inspection and profiling
*/
export class ReactDevToolsBridge extends EventEmitter {
private logger: Logger;
private componentManager: ComponentManager;
private isConnected: boolean = false;
private devToolsHook: any = null;
private fiberRoots: Set<any> = new Set();
constructor(componentManager: ComponentManager) {
super();
this.logger = new Logger('ReactDevToolsBridge');
this.componentManager = componentManager;
}
/**
* Initialize connection to React DevTools
*/
async initialize(): Promise<void> {
try {
// In a real browser environment, we would access the React DevTools hook
// For now, we'll simulate the connection
this.logger.info('Initializing React DevTools bridge...');
// Check if React DevTools hook is available
if (typeof window !== 'undefined' && (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__) {
this.devToolsHook = (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
this.setupDevToolsListeners();
this.isConnected = true;
this.logger.info('Connected to React DevTools');
} else {
this.logger.warn('React DevTools hook not found - running in simulation mode');
this.isConnected = false;
}
this.emit('connected', this.isConnected);
} catch (error) {
this.logger.error('Failed to initialize React DevTools bridge', { error });
throw error;
}
}
/**
* Setup listeners for React DevTools events
*/
private setupDevToolsListeners(): void {
if (!this.devToolsHook) return;
// Listen for fiber root additions
this.devToolsHook.onCommitFiberRoot = (id: number, root: any) => {
this.fiberRoots.add(root);
this.processFiberRoot(root);
};
// Listen for fiber unmounts
this.devToolsHook.onCommitFiberUnmount = (id: number, fiber: any) => {
this.processFiberUnmount(fiber);
};
this.logger.debug('React DevTools listeners setup complete');
}
/**
* Process a fiber root and extract component information
*/
private processFiberRoot(root: any): void {
try {
const components = this.traverseFiberTree(root.current);
components.forEach(component => {
this.componentManager.registerComponent(component);
});
} catch (error) {
this.logger.error('Error processing fiber root', { error });
}
}
/**
* Process fiber unmount
*/
private processFiberUnmount(fiber: any): void {
try {
const componentId = this.getFiberComponentId(fiber);
if (componentId) {
this.componentManager.removeComponent(componentId);
}
} catch (error) {
this.logger.error('Error processing fiber unmount', { error });
}
}
/**
* Traverse React fiber tree and extract component instances
*/
private traverseFiberTree(fiber: any, parent?: ComponentInstance): ComponentInstance[] {
const components: ComponentInstance[] = [];
if (!fiber) return components;
// Check if this fiber represents a component
if (this.isComponentFiber(fiber)) {
const component = this.createComponentInstance(fiber, parent);
if (component) {
components.push(component);
// Traverse children with this component as parent
let child = fiber.child;
while (child) {
components.push(...this.traverseFiberTree(child, component));
child = child.sibling;
}
}
} else {
// Not a component, continue traversing
let child = fiber.child;
while (child) {
components.push(...this.traverseFiberTree(child, parent));
child = child.sibling;
}
}
return components;
}
/**
* Check if a fiber represents a React component
*/
private isComponentFiber(fiber: any): boolean {
return fiber.type && (
typeof fiber.type === 'function' ||
(typeof fiber.type === 'object' && fiber.type.render)
);
}
/**
* Create component instance from fiber
*/
private createComponentInstance(fiber: any, parent?: ComponentInstance): ComponentInstance | null {
try {
const id = this.getFiberComponentId(fiber);
const name = this.getFiberComponentName(fiber);
const filePath = this.getFiberSourcePath(fiber);
const component: ComponentInstance = {
id,
name,
filePath,
props: fiber.memoizedProps || {},
state: fiber.memoizedState ? this.extractStateFromFiber(fiber) : {},
hooks: this.extractHooksFromFiber(fiber),
renderCount: 0,
lastRenderTime: 0,
renderDuration: 0,
totalRenderTime: 0,
averageRenderTime: 0,
parent: parent?.id,
children: [],
renderHistory: []
};
// Add to parent's children
if (parent) {
parent.children.push(id);
}
return component;
} catch (error) {
this.logger.error('Error creating component instance from fiber', { error });
return null;
}
}
/**
* Get unique component ID from fiber
*/
private getFiberComponentId(fiber: any): string {
return `component_${fiber._debugID || Math.random().toString(36).substr(2, 9)}`;
}
/**
* Get component name from fiber
*/
private getFiberComponentName(fiber: any): string {
if (fiber.type) {
if (typeof fiber.type === 'function') {
return fiber.type.displayName || fiber.type.name || 'Anonymous';
}
if (typeof fiber.type === 'object' && fiber.type.render) {
return fiber.type.displayName || fiber.type.render.name || 'Anonymous';
}
if (typeof fiber.type === 'string') {
return fiber.type;
}
}
return 'Unknown';
}
/**
* Get source file path from fiber
*/
private getFiberSourcePath(fiber: any): string {
if (fiber._debugSource) {
return fiber._debugSource.fileName || 'unknown';
}
if (fiber.type && fiber.type._source) {
return fiber.type._source.fileName || 'unknown';
}
return 'unknown';
}
/**
* Extract state from fiber
*/
private extractStateFromFiber(fiber: any): Record<string, any> {
const state: Record<string, any> = {};
if (fiber.memoizedState) {
// For class components
if (typeof fiber.memoizedState === 'object' && !Array.isArray(fiber.memoizedState)) {
return fiber.memoizedState;
}
// For functional components with hooks
let hook = fiber.memoizedState;
let index = 0;
while (hook) {
if (hook.memoizedState !== undefined) {
state[`hook_${index}`] = hook.memoizedState;
}
hook = hook.next;
index++;
}
}
return state;
}
/**
* Extract hooks from fiber
*/
private extractHooksFromFiber(fiber: any): HookInstance[] {
const hooks: HookInstance[] = [];
if (fiber.memoizedState) {
let hook = fiber.memoizedState;
let index = 0;
while (hook) {
hooks.push({
type: this.getHookType(hook),
index,
value: hook.memoizedState,
dependencies: hook.deps
});
hook = hook.next;
index++;
}
}
return hooks;
}
/**
* Determine hook type from hook object
*/
private getHookType(hook: any): string {
// This is a simplified implementation
// Real React DevTools has more sophisticated hook type detection
if (hook.queue) {
return 'useState';
}
if (hook.deps) {
return 'useEffect';
}
if (hook.memoizedState && typeof hook.memoizedState === 'function') {
return 'useCallback';
}
return 'unknown';
}
/**
* Get current component tree
*/
async getComponentTree(): Promise<ReactFiberNode[]> {
if (!this.isConnected) {
this.logger.warn('React DevTools not connected');
return [];
}
const trees: ReactFiberNode[] = [];
for (const root of this.fiberRoots) {
const tree = this.fiberToNode(root.current);
if (tree) {
trees.push(tree);
}
}
return trees;
}
/**
* Convert fiber to ReactFiberNode
*/
private fiberToNode(fiber: any): ReactFiberNode | null {
if (!fiber) return null;
const children: ReactFiberNode[] = [];
let child = fiber.child;
while (child) {
const childNode = this.fiberToNode(child);
if (childNode) {
children.push(childNode);
}
child = child.sibling;
}
return {
id: fiber._debugID || 0,
displayName: this.getFiberComponentName(fiber),
type: typeof fiber.type === 'string' ? fiber.type : 'component',
key: fiber.key,
props: fiber.memoizedProps || {},
state: this.extractStateFromFiber(fiber),
hooks: this.extractHooksFromFiber(fiber),
children,
parent: null, // Will be set by parent
source: fiber._debugSource
};
}
/**
* Inspect specific component by ID
*/
async inspectComponent(componentId: string): Promise<ComponentInstance | null> {
return this.componentManager.getComponent(componentId) || null;
}
/**
* Start profiling React components
*/
async startProfiling(): Promise<void> {
if (!this.isConnected) {
this.logger.warn('Cannot start profiling - React DevTools not connected');
return;
}
// Enable React profiler
if (this.devToolsHook && this.devToolsHook.profilerStore) {
this.devToolsHook.profilerStore.isProfiling = true;
this.logger.info('React profiling started');
}
}
/**
* Stop profiling and get results
*/
async stopProfiling(): Promise<ReactProfilerData[]> {
if (!this.isConnected) {
this.logger.warn('Cannot stop profiling - React DevTools not connected');
return [];
}
// Disable React profiler and collect data
if (this.devToolsHook && this.devToolsHook.profilerStore) {
this.devToolsHook.profilerStore.isProfiling = false;
// In a real implementation, we would extract profiling data here
this.logger.info('React profiling stopped');
}
return [];
}
/**
* Check if React DevTools is connected
*/
isReactDevToolsConnected(): boolean {
return this.isConnected;
}
/**
* Disconnect from React DevTools
*/
disconnect(): void {
this.isConnected = false;
this.devToolsHook = null;
this.fiberRoots.clear();
this.logger.info('Disconnected from React DevTools');
this.emit('disconnected');
}
}