Skip to main content
Glama

Curupira

by drzln
devtools-injector.ts9.13 kB
/** * React DevTools Injector for Production Builds * * Injects a minimal React DevTools hook to enable inspection * of production React applications */ import type { RuntimeDomain } from '../../chrome/domains/runtime.js'; import { logger } from '../../config/logger.js'; export class ReactDevToolsInjector { constructor(private runtime: RuntimeDomain) {} /** * Inject React DevTools hook for production builds */ async inject(): Promise<boolean> { try { // First check if hook already exists const hasHook = await this.checkExistingHook(); if (hasHook) { logger.debug('React DevTools hook already present'); return true; } // Inject the hook const injected = await this.injectHook(); if (!injected) { logger.warn('Failed to inject React DevTools hook'); return false; } // Try to capture existing React instances await this.captureExistingReactInstances(); // Set up mutation observer for new React roots await this.setupMutationObserver(); logger.info('React DevTools hook injected successfully'); return true; } catch (error) { logger.error('Error injecting React DevTools hook', error); return false; } } private async checkExistingHook(): Promise<boolean> { const result = await this.runtime.evaluate<boolean>(` !!window.__REACT_DEVTOOLS_GLOBAL_HOOK__ `); return result.value === true; } private async injectHook(): Promise<boolean> { const result = await this.runtime.evaluate<boolean>(` (() => { try { // Create a comprehensive hook that mimics React DevTools const hook = { renderers: new Map(), supportsFiber: true, _fiberRoots: new Map(), _rootIDCounter: 1, // Core hook methods inject(renderer) { const id = this._rootIDCounter++; this.renderers.set(id, { ...renderer, version: renderer.version || '18.0.0', // Assume modern React renderer: 'react-dom', rendererPackageName: 'react-dom' }); return id; }, onCommitFiberRoot(id, root, priorityLevel) { if (!root) return; // Store the fiber root this._fiberRoots.set(id, root); // Emit event for tools if (this._listeners && this._listeners.size > 0) { this._listeners.forEach(listener => { listener({ type: 'commit', id, root }); }); } }, onCommitFiberUnmount(id, fiber) { // Handle unmounting if (this._listeners && this._listeners.size > 0) { this._listeners.forEach(listener => { listener({ type: 'unmount', id, fiber }); }); } }, // DevTools specific methods getFiberRoots() { return Array.from(this._fiberRoots.values()); }, hasDetectedReact() { return this.renderers.size > 0 || this._fiberRoots.size > 0; }, // Event subscription _listeners: new Set(), sub(listener) { this._listeners.add(listener); return () => this._listeners.delete(listener); }, // Helper methods for inspection inspectElement(id, path) { const root = this._fiberRoots.get(id); if (!root) return null; let current = root.current || root; for (const key of path || []) { current = current[key]; if (!current) return null; } return { type: current.type, props: current.memoizedProps || current.props, state: current.memoizedState || current.state, hooks: this._extractHooks(current) }; }, _extractHooks(fiber) { if (!fiber.memoizedState) return null; const hooks = []; let hook = fiber.memoizedState; while (hook) { hooks.push({ state: hook.memoizedState, next: !!hook.next }); hook = hook.next; } return hooks; } }; // Install the hook Object.defineProperty(window, '__REACT_DEVTOOLS_GLOBAL_HOOK__', { value: hook, writable: false, enumerable: false, configurable: false }); // Dispatch event to notify React window.dispatchEvent(new Event('ReactDevToolsHookInit')); return true; } catch (error) { console.error('Failed to inject React DevTools hook:', error); return false; } })() `); return result.value === true; } private async captureExistingReactInstances(): Promise<void> { await this.runtime.evaluate(` (() => { const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; if (!hook) return; // Find all elements with React properties const elements = document.querySelectorAll('*'); const foundRoots = new Set(); for (const element of elements) { const keys = Object.keys(element); // Look for React Fiber const fiberKey = keys.find(key => key.startsWith('__reactFiber')); if (fiberKey && element[fiberKey]) { const fiber = element[fiberKey]; // Walk up to find root let current = fiber; while (current.return) { current = current.return; } // Register the root if not already found const rootId = current.stateNode || current; if (!foundRoots.has(rootId)) { foundRoots.add(rootId); hook.onCommitFiberRoot(hook._rootIDCounter++, current); } } // Look for React Root Container const containerKey = keys.find(key => key.startsWith('_reactRootContainer')); if (containerKey && element[containerKey]) { const container = element[containerKey]; const root = container._internalRoot || container; if (!foundRoots.has(root)) { foundRoots.add(root); hook.onCommitFiberRoot(hook._rootIDCounter++, root.current || root); } } } console.log('Captured', foundRoots.size, 'React root(s)'); })() `); } private async setupMutationObserver(): Promise<void> { await this.runtime.evaluate(` (() => { const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; if (!hook || hook._mutationObserver) return; // Set up observer for new React roots const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { // Check if this element has React properties setTimeout(() => { const keys = Object.keys(node); const fiberKey = keys.find(key => key.startsWith('__reactFiber')); if (fiberKey && node[fiberKey]) { // New React element detected console.log('New React element detected:', node); } }, 100); // Small delay to let React initialize } } } }); observer.observe(document.body, { childList: true, subtree: true }); hook._mutationObserver = observer; })() `); } /** * Check if injection was successful */ async verify(): Promise<boolean> { const result = await this.runtime.evaluate<{ hasHook: boolean; hasReact: boolean; rootCount: number; }>(` (() => { const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; return { hasHook: !!hook, hasReact: hook ? hook.hasDetectedReact() : false, rootCount: hook ? hook._fiberRoots.size : 0 }; })() `); if (result.error || !result.value) { return false; } const { hasHook, hasReact, rootCount } = result.value; logger.debug('DevTools injection verification', { hasHook, hasReact, rootCount }); return hasHook && (hasReact || rootCount > 0); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/drzln/curupira'

If you have feedback or need assistance with the MCP directory API, please join our Discord server