Skip to main content
Glama

WebSee MCP Server

by 1AQuantum
component-tracker.js16.3 kB
export class ComponentTracker { page = null; componentCache = new Map(); domToComponentMap = new Map(); async initialize(page) { this.page = page; this.componentCache.clear(); this.domToComponentMap.clear(); // Inject tracking hooks into the page await this.injectTrackingHooks(); } async injectTrackingHooks() { if (!this.page) throw new Error('Page not initialized'); await this.page.evaluate(() => { // Create global tracking object window.__COMPONENT_TRACKER__ = { components: new Map(), domMap: new Map(), renderCounts: new Map(), lastRenderTimes: new Map(), }; // Hook into React DevTools if available if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) { const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; const tracker = window.__COMPONENT_TRACKER__; // Patch onCommitFiberRoot to track renders const originalOnCommit = hook.onCommitFiberRoot; hook.onCommitFiberRoot = function (...args) { if (originalOnCommit) { originalOnCommit.apply(this, args); } // Track render time const root = args[1]; if (root && root.current) { tracker.lastRenderTimes.set('react', performance.now()); } }; } // Hook into Vue 3 if available if (window.Vue || window.app) { const vue = window.Vue || window.app?.config?.globalProperties?.Vue; if (vue && vue.version?.startsWith('3')) { const tracker = window.__COMPONENT_TRACKER__; // Track Vue 3 component updates if (window.app?.__app_context__) { tracker.lastRenderTimes.set('vue', performance.now()); } } } // Hook into Angular if available if (window.ng?.probe) { const tracker = window.__COMPONENT_TRACKER__; tracker.lastRenderTimes.set('angular', performance.now()); } }); } async getComponentTree() { if (!this.page) throw new Error('Page not initialized'); const startTime = performance.now(); const components = []; // Extract React components const reactComponents = await this.extractReactComponents(); components.push(...reactComponents); // Extract Vue components const vueComponents = await this.extractVueComponents(); components.push(...vueComponents); // Extract Angular components const angularComponents = await this.extractAngularComponents(); components.push(...angularComponents); // Update cache for (const comp of components) { this.componentCache.set(comp.name, comp); for (const domId of comp.domNodes) { this.domToComponentMap.set(domId, comp.name); } } const duration = performance.now() - startTime; if (duration > 50) { console.warn(`Component tree extraction took ${duration.toFixed(2)}ms (target: <50ms)`); } return components; } async extractReactComponents() { if (!this.page) return []; return await this.page.evaluate(() => { const components = []; const tracker = window.__COMPONENT_TRACKER__; try { // Try to access React DevTools hook const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; if (!hook || !hook.renderers || hook.renderers.size === 0) { return components; } // Iterate through React renderers hook.renderers.forEach((renderer) => { if (!renderer || !renderer.getCurrentFiber) return; // Find root fiber const roots = hook.getFiberRoots?.(renderer.version) || []; roots.forEach((root) => { if (!root.current) return; const visitedFibers = new WeakSet(); const walkFiber = (fiber, parentName) => { if (!fiber || visitedFibers.has(fiber)) return; visitedFibers.add(fiber); const compInfo = extractComponentFromFiber(fiber, parentName, tracker); if (compInfo) { components.push(compInfo); // Walk children let child = fiber.child; while (child) { walkFiber(child, compInfo.name); child = child.sibling; } } else if (fiber.child) { walkFiber(fiber.child, parentName); } if (fiber.sibling) { walkFiber(fiber.sibling, parentName); } }; walkFiber(root.current); }); }); } catch (e) { console.warn('React component extraction error:', e); } function extractComponentFromFiber(fiber, parentName, tracker) { try { // Skip non-component fibers if (!fiber.type || typeof fiber.type === 'string') return null; const name = getComponentName(fiber); if (!name || name.startsWith('_') || name.startsWith('Provider') || name.startsWith('Consumer')) { return null; } const domNodes = []; const stateNode = fiber.stateNode; if (stateNode && stateNode instanceof Element) { const id = stateNode.id || `react-${Math.random().toString(36).substr(2, 9)}`; if (!stateNode.id) stateNode.id = id; domNodes.push(id); } const source = { file: 'unknown', framework: 'react', }; // Try to get source location from debug info if (fiber._debugSource) { source.file = fiber._debugSource.fileName || 'unknown'; source.line = fiber._debugSource.lineNumber; source.column = fiber._debugSource.columnNumber; } else if (fiber.type._debugSource) { source.file = fiber.type._debugSource.fileName || 'unknown'; source.line = fiber.type._debugSource.lineNumber; source.column = fiber.type._debugSource.columnNumber; } const countKey = `react:${name}`; const renderCount = tracker.renderCounts.get(countKey) || 0; tracker.renderCounts.set(countKey, renderCount + 1); return { name, type: 'react', source, props: fiber.memoizedProps || {}, state: fiber.memoizedState || undefined, parent: parentName, children: [], domNodes, renderCount: renderCount + 1, lastRenderTime: tracker.lastRenderTimes.get('react') || 0, }; } catch (e) { return null; } } function getComponentName(fiber) { if (!fiber || !fiber.type) return 'Unknown'; if (typeof fiber.type === 'function') { return fiber.type.displayName || fiber.type.name || 'Anonymous'; } if (fiber.elementType && typeof fiber.elementType === 'function') { return fiber.elementType.displayName || fiber.elementType.name || 'Anonymous'; } return 'Unknown'; } return components; }); } async extractVueComponents() { if (!this.page) return []; return await this.page.evaluate(() => { const components = []; const tracker = window.__COMPONENT_TRACKER__; try { // Check for Vue 3 const app = window.app; if (!app || !app._instance) return components; const visitedInstances = new WeakSet(); const walkInstance = (instance, parentName) => { if (!instance || visitedInstances.has(instance)) return; visitedInstances.add(instance); const name = instance.type?.name || instance.type?.__name || 'Anonymous'; if (name.startsWith('_')) return; const domNodes = []; if (instance.vnode?.el && instance.vnode.el instanceof Element) { const id = instance.vnode.el.id || `vue-${Math.random().toString(36).substr(2, 9)}`; if (!instance.vnode.el.id) instance.vnode.el.id = id; domNodes.push(id); } const source = { file: instance.type?.__file || 'unknown', framework: 'vue', }; const countKey = `vue:${name}`; const renderCount = tracker.renderCounts.get(countKey) || 0; tracker.renderCounts.set(countKey, renderCount + 1); components.push({ name, type: 'vue', source, props: instance.props || {}, state: instance.data || instance.setupState || undefined, parent: parentName, children: [], domNodes, renderCount: renderCount + 1, lastRenderTime: tracker.lastRenderTimes.get('vue') || 0, }); // Walk children const children = instance.subTree?.children || []; if (Array.isArray(children)) { children.forEach((child) => { if (child && child.component) { walkInstance(child.component, name); } }); } }; walkInstance(app._instance); } catch (e) { console.warn('Vue component extraction error:', e); } return components; }); } async extractAngularComponents() { if (!this.page) return []; return await this.page.evaluate(() => { const components = []; const tracker = window.__COMPONENT_TRACKER__; try { const ng = window.ng; if (!ng?.probe) return components; // Find all Angular components in the DOM const allElements = document.querySelectorAll('*'); const visitedComponents = new Set(); allElements.forEach((el) => { try { const debugElement = ng.probe(el); if (!debugElement || !debugElement.componentInstance) return; const instance = debugElement.componentInstance; if (visitedComponents.has(instance)) return; visitedComponents.add(instance); const name = instance.constructor?.name || 'Anonymous'; if (name === 'Object' || name.startsWith('_')) return; const id = el.id || `ng-${Math.random().toString(36).substr(2, 9)}`; if (!el.id) el.id = id; const source = { file: 'unknown', framework: 'angular', }; const countKey = `angular:${name}`; const renderCount = tracker.renderCounts.get(countKey) || 0; tracker.renderCounts.set(countKey, renderCount + 1); // Extract props (inputs) and state const props = {}; const state = {}; for (const key in instance) { if (instance.hasOwnProperty(key) && !key.startsWith('_')) { const value = instance[key]; if (typeof value !== 'function') { state[key] = value; } } } components.push({ name, type: 'angular', source, props, state, parent: undefined, children: [], domNodes: [id], renderCount: renderCount + 1, lastRenderTime: tracker.lastRenderTimes.get('angular') || 0, }); } catch (e) { // Skip elements that aren't Angular components } }); } catch (e) { console.warn('Angular component extraction error:', e); } return components; }); } async getComponentAtElement(selector) { if (!this.page) throw new Error('Page not initialized'); try { // First, ensure we have the latest component tree await this.getComponentTree(); // Get the element's ID or create one const elementId = await this.page.evaluate((sel) => { const el = document.querySelector(sel); if (!el) return null; const htmlEl = el; if (htmlEl.id) return htmlEl.id; const newId = `elem-${Math.random().toString(36).substr(2, 9)}`; htmlEl.id = newId; return newId; }, selector); if (!elementId) return null; // Look up the component in our map const componentName = this.domToComponentMap.get(elementId); if (!componentName) return null; return this.componentCache.get(componentName) || null; } catch (e) { console.error('Error getting component at element:', e); return null; } } } //# sourceMappingURL=component-tracker.js.map

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/1AQuantum/websee-mcp-server'

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