npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@fractal-mcp/render

v0.11.4

Published

React iframe host for sandboxed widgets

Readme

@fractal-mcp/render

React components for hosting sandboxed Fractal widgets inside iframes with secure MessageChannel communication. Provides imperative APIs for sending commands and receiving events from embedded widgets.

Installation

npm install @fractal-mcp/render

Overview

The @fractal-mcp/render package provides React components and utilities for rendering Fractal widgets in secure, sandboxed iframes. It includes:

  • FractalComponent - Individual iframe-based widget renderer
  • FractalFrame - JSX-based layout renderer with multiple widgets
  • Secure Communication - MessageChannel-based widget communication
  • Automatic Resizing - Dynamic iframe sizing based on content
  • Event Handling - Comprehensive event system for widget interactions
  • DOM Manipulation - Remote DOM interaction capabilities

Quick Start

Basic Widget Rendering

import { FractalComponent } from '@fractal-mcp/render';

function MyApp() {
  return (
    <FractalComponent
      srcDoc="<div>Hello from widget!</div>"
      onEvent={(event) => console.log('Widget event:', event)}
    />
  );
}

Layout with Multiple Widgets

import { FractalFrame } from '@fractal-mcp/render';

function Dashboard() {
  const jsx = `
    <div className="dashboard">
      <Frac id="header" />
      <div className="content">
        <Frac id="sidebar" />
        <Frac id="main" />
      </div>
    </div>
  `;
  
  const map = {
    header: {
      component: { html: '<header>Dashboard Header</header>' },
      data: { title: 'My Dashboard' }
    },
    sidebar: {
      component: { html: '<nav>Navigation</nav>' },
      data: { items: ['Home', 'Profile', 'Settings'] }
    },
    main: {
      component: { html: '<main>Main Content</main>' },
      data: { content: 'Welcome to the dashboard' }
    }
  };

  return (
    <FractalFrame
      jsx={jsx}
      map={map}
      onEvent={(event) => console.log('Frame event:', event)}
    />
  );
}

API Reference

FractalComponent

A React component that renders a single Fractal widget in a sandboxed iframe.

Props

interface Props {
  src?: string;                    // URL of iframe content
  srcDoc?: string;                 // HTML content string (takes precedence over src)
  data?: unknown;                  // Arbitrary data available to the widget
  onEvent?: (e: FractalUIEvent) => void; // Event handler for widget events
  sandbox?: string;                // iframe sandbox attribute (default: 'allow-scripts allow-same-origin')
  handlers?: CommandHandlers;      // Custom command handlers
  registryUrl?: string;           // Custom registry URL for MCP calls
}

Example

import { FractalComponent, FractalComponentHandle } from '@fractal-mcp/render';
import { useRef } from 'react';

function WeatherWidget() {
  const widgetRef = useRef<FractalComponentHandle>(null);

  const handleWeatherEvent = (event) => {
    if (event.type === 'action' && event.data.name === 'refresh') {
      // Handle refresh action
      console.log('Refreshing weather data...');
    }
  };

  const clickButton = async () => {
    await widgetRef.current?.click({ id: 'refresh-btn', xpath: '' });
  };

  return (
    <div>
      <button onClick={clickButton}>Click Widget Button</button>
      <FractalComponent
        ref={widgetRef}
        srcDoc={`
          <div>
            <h2>Weather Widget</h2>
            <button id="refresh-btn">Refresh</button>
            <p>Temperature: 72°F</p>
          </div>
        `}
        onEvent={handleWeatherEvent}
      />
    </div>
  );
}

Imperative API

The component exposes an imperative API through refs:

interface FractalComponentHandle {
  queryDom: (selector: string) => Promise<unknown>;
  click: (params: {id: string, xpath: string}) => Promise<unknown>;
  enterText: (params: {id: string, xpath: string, text: string}) => Promise<unknown>;
  send: (cmd: FractalEventType, data?: { name: string; params: Record<string, unknown> }) => Promise<unknown>;
}

Methods:

  • queryDom(selector) - Query the widget's DOM structure
  • click(params) - Programmatically click elements in the widget
  • enterText(params) - Enter text into form fields
  • send(cmd, data) - Send custom commands to the widget

Example:

const widgetRef = useRef<FractalComponentHandle>(null);

// Query DOM
const html = await widgetRef.current?.queryDom('body');

// Click an element by ID
await widgetRef.current?.click({ id: 'submit-btn', xpath: '' });

// Click an element by XPath
await widgetRef.current?.click({ id: '', xpath: '//button[text()="Submit"]' });

// Enter text in a form field
await widgetRef.current?.enterText({ 
  id: 'username', 
  xpath: '', 
  text: '[email protected]' 
});

// Send custom command
await widgetRef.current?.send('customAction', {
  name: 'updateData',
  params: { value: 'new data' }
});

FractalFrame

A React component that renders JSX layouts containing multiple <Frac /> elements, each resolved to a FractalComponent.

Props

interface FractalFrameProps {
  jsx: string;                                    // JSX string containing <Frac /> elements
  map: Record<string, FractalDefinition>;        // Mapping of Frac IDs to component definitions
  onEvent?: (event: FractalFrameEvent) => void;  // Event handler for frame events
}

interface FractalDefinition {
  component: { html: string };    // Component HTML content
  data: unknown;                  // Component data
  toolName?: string;              // Optional tool name for tracking
}

interface FractalFrameEvent extends FractalUIEvent {
  toolName?: string;              // Tool that generated the component
  componentId: string;            // ID of the component that emitted the event
}

Example

import { FractalFrame } from '@fractal-mcp/render';

function MultiWidgetLayout() {
  const jsx = `
    <div className="layout">
      <header>
        <Frac id="nav" />
      </header>
      <main>
        <aside><Frac id="sidebar" /></aside>
        <section><Frac id="content" /></section>
      </main>
      <footer>
        <Frac id="footer" />
      </footer>
    </div>
  `;

  const map = {
    nav: {
      component: { html: '<nav><a href="/">Home</a><a href="/about">About</a></nav>' },
      data: { currentPage: 'home' },
      toolName: 'navigation-tool'
    },
    sidebar: {
      component: { html: '<div>Sidebar content</div>' },
      data: { items: ['Item 1', 'Item 2'] },
      toolName: 'sidebar-tool'
    },
    content: {
      component: { html: '<article>Main content</article>' },
      data: { title: 'Welcome', body: 'Hello world!' },
      toolName: 'content-tool'
    },
    footer: {
      component: { html: '<footer>&copy; 2024 My App</footer>' },
      data: { year: 2024 },
      toolName: 'footer-tool'
    }
  };

  const handleFrameEvent = (event) => {
    console.log(`Event from ${event.componentId} (${event.toolName}):`, event);
    
    if (event.type === 'action') {
      switch (event.componentId) {
        case 'nav':
          handleNavigation(event.data);
          break;
        case 'sidebar':
          handleSidebarAction(event.data);
          break;
        // Handle other components...
      }
    }
  };

  return (
    <FractalFrame
      jsx={jsx}
      map={map}
      onEvent={handleFrameEvent}
    />
  );
}

Dynamic Updates

The FractalFrame component supports dynamic updates to both JSX and component mappings:

function DynamicFrame() {
  const [jsx, setJsx] = useState('<div><Frac id="widget1" /></div>');
  const [map, setMap] = useState({
    widget1: {
      component: { html: '<div>Initial content</div>' },
      data: { message: 'Hello' }
    }
  });

  const addWidget = () => {
    setJsx('<div><Frac id="widget1" /><Frac id="widget2" /></div>');
    setMap(prev => ({
      ...prev,
      widget2: {
        component: { html: '<div>New widget</div>' },
        data: { message: 'World' }
      }
    }));
  };

  return (
    <div>
      <button onClick={addWidget}>Add Widget</button>
      <FractalFrame jsx={jsx} map={map} />
    </div>
  );
}

useFractalComponent

A React hook that creates a ref for the FractalComponent imperative API.

function useFractalComponent(): React.RefObject<FractalComponentHandle>

Example:

import { useFractalComponent, FractalComponent } from '@fractal-mcp/render';

function MyWidget() {
  const widgetRef = useFractalComponent();

  const interactWithWidget = async () => {
    // Use the imperative API
    await widgetRef.current?.click({ id: 'button', xpath: '' });
    const dom = await widgetRef.current?.queryDom('body');
  };

  return (
    <FractalComponent
      ref={widgetRef}
      srcDoc="<button id='button'>Click me</button>"
    />
  );
}

Event System

Event Types

The render package handles various event types from widgets:

interface FractalUIEvent {
  type: 'action' | 'navigate' | 'resize' | 'click' | 'enterText' | 'queryDom';
  data: {
    name: string;
    params: Record<string, unknown>;
  };
}

interface FractalFrameEvent extends FractalUIEvent {
  toolName?: string;      // Tool that generated the component
  componentId: string;    // ID of the component that emitted the event
}

Event Handling Examples

function EventHandlingExample() {
  const handleWidgetEvent = (event: FractalUIEvent) => {
    switch (event.type) {
      case 'action':
        console.log('Action:', event.data.name, event.data.params);
        // Handle widget actions (button clicks, form submissions, etc.)
        break;
        
      case 'navigate':
        console.log('Navigation:', event.data.name, event.data.params);
        // Handle navigation requests from widgets
        break;
        
      case 'resize':
        console.log('Resize:', event.data.params);
        // Widget size changed (handled automatically)
        break;
    }
  };

  const handleFrameEvent = (event: FractalFrameEvent) => {
    console.log(`Event from component ${event.componentId}:`, event);
    
    // Route events based on component ID or tool name
    if (event.toolName === 'user-profile-tool') {
      handleUserProfileEvent(event);
    } else if (event.componentId === 'navigation') {
      handleNavigationEvent(event);
    }
  };

  return (
    <div>
      {/* Single component */}
      <FractalComponent
        srcDoc="<button onclick='parent.postMessage({type: \"action\", data: {name: \"click\", params: {}}}, \"*\")'>Click</button>"
        onEvent={handleWidgetEvent}
      />
      
      {/* Multiple components */}
      <FractalFrame
        jsx="<div><Frac id='comp1' /><Frac id='comp2' /></div>"
        map={{
          comp1: { component: { html: '...' }, data: {} },
          comp2: { component: { html: '...' }, data: {} }
        }}
        onEvent={handleFrameEvent}
      />
    </div>
  );
}

Security Features

Sandboxing

All widgets run in sandboxed iframes with restricted permissions:

// Default sandbox settings
<FractalComponent
  sandbox="allow-scripts allow-same-origin"
  srcDoc="<div>Sandboxed content</div>"
/>

// Custom sandbox settings
<FractalComponent
  sandbox="allow-scripts allow-forms allow-popups"
  srcDoc="<div>Custom sandbox</div>"
/>

MessageChannel Communication

Secure communication between parent and widget using MessageChannel:

  • No direct postMessage calls required
  • Private communication channel per widget
  • Automatic message routing and handling
  • Type-safe event system

Content Security

// Safe: HTML content is sandboxed
<FractalComponent
  srcDoc="<script>console.log('Safe in sandbox')</script>"
/>

// External content with restrictions
<FractalComponent
  src="https://trusted-widget.example.com"
  sandbox="allow-scripts allow-same-origin"
/>

Advanced Usage

Custom Command Handlers

interface CommandHandlers {
  [command: string]: (data: unknown, reply: (resp: unknown) => void) => void;
}

function AdvancedWidget() {
  const customHandlers: CommandHandlers = {
    customCommand: (data, reply) => {
      console.log('Custom command received:', data);
      reply({ status: 'success', result: 'processed' });
    },
    
    fetchData: async (data, reply) => {
      try {
        const result = await fetch('/api/data');
        const json = await result.json();
        reply({ data: json });
      } catch (error) {
        reply({ error: error.message });
      }
    }
  };

  return (
    <FractalComponent
      srcDoc="<div>Widget with custom handlers</div>"
      handlers={customHandlers}
    />
  );
}

Integration with MCP Tools

import { FractalFrame, callMcpTool } from '@fractal-mcp/render';

function McpIntegration() {
  const [layout, setLayout] = useState('');
  const [componentMap, setComponentMap] = useState({});

  const executeToolAndRender = async (toolName: string, args: any) => {
    try {
      const result = await callMcpTool(toolName, args);
      
      if (result.component && result.data) {
        const componentId = `tool-${Date.now()}`;
        setLayout(`<div><Frac id="${componentId}" /></div>`);
        setComponentMap({
          [componentId]: {
            component: result.component,
            data: result.data,
            toolName
          }
        });
      }
    } catch (error) {
      console.error('Tool execution failed:', error);
    }
  };

  return (
    <div>
      <button onClick={() => executeToolAndRender('weather-tool', { location: 'NYC' })}>
        Get Weather
      </button>
      
      {layout && (
        <FractalFrame
          jsx={layout}
          map={componentMap}
          onEvent={(event) => {
            if (event.type === 'action' && event.data.name === 'refresh') {
              executeToolAndRender(event.toolName!, event.data.params);
            }
          }}
        />
      )}
    </div>
  );
}

Performance Optimization

import { memo, useMemo } from 'react';
import { FractalFrame } from '@fractal-mcp/render';

// Memoize FractalFrame to prevent unnecessary re-renders
const MemoizedFractalFrame = memo(FractalFrame);

function OptimizedLayout({ widgets, onEvent }) {
  // Memoize expensive computations
  const jsx = useMemo(() => {
    return widgets.map(w => `<Frac id="${w.id}" />`).join('');
  }, [widgets]);

  const componentMap = useMemo(() => {
    return widgets.reduce((map, widget) => {
      map[widget.id] = {
        component: widget.component,
        data: widget.data,
        toolName: widget.toolName
      };
      return map;
    }, {});
  }, [widgets]);

  return (
    <MemoizedFractalFrame
      jsx={jsx}
      map={componentMap}
      onEvent={onEvent}
    />
  );
}

Error Handling

Component Error Boundaries

import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert" style={{ padding: '20px', border: '1px solid red' }}>
      <h2>Widget Error</h2>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function SafeWidgetRenderer() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <FractalComponent
        srcDoc="<div>Potentially problematic widget</div>"
        onEvent={(event) => {
          // Handle events safely
          try {
            processEvent(event);
          } catch (error) {
            console.error('Event handling error:', error);
          }
        }}
      />
    </ErrorBoundary>
  );
}

Widget Communication Errors

function RobustWidget() {
  const widgetRef = useRef<FractalComponentHandle>(null);

  const safeInteraction = async () => {
    try {
      await widgetRef.current?.click({ id: 'button', xpath: '' });
    } catch (error) {
      console.error('Widget interaction failed:', error);
      // Handle gracefully - maybe show user feedback
    }
  };

  return (
    <FractalComponent
      ref={widgetRef}
      srcDoc="<button id='button'>Click me</button>"
      onEvent={(event) => {
        // Validate event structure
        if (!event || !event.type || !event.data) {
          console.warn('Invalid event received:', event);
          return;
        }
        
        // Process valid events
        handleValidEvent(event);
      }}
    />
  );
}

Testing

Unit Testing Components

import { render, screen } from '@testing-library/react';
import { FractalComponent } from '@fractal-mcp/render';

describe('FractalComponent', () => {
  it('renders iframe with srcDoc content', () => {
    const onEvent = jest.fn();
    
    render(
      <FractalComponent
        srcDoc="<div>Test content</div>"
        onEvent={onEvent}
      />
    );
    
    const iframe = screen.getByTitle('sandboxed-widget');
    expect(iframe).toBeInTheDocument();
  });

  it('handles events from widget', async () => {
    const onEvent = jest.fn();
    
    render(
      <FractalComponent
        srcDoc="<button onclick='parent.postMessage({type: \"action\"}, \"*\")'>Click</button>"
        onEvent={onEvent}
      />
    );
    
    // Simulate event from widget
    // Note: Testing iframe communication requires special setup
  });
});

Integration Testing

import { renderHook } from '@testing-library/react';
import { useFractalComponent } from '@fractal-mcp/render';

describe('useFractalComponent', () => {
  it('returns a ref object', () => {
    const { result } = renderHook(() => useFractalComponent());
    
    expect(result.current).toHaveProperty('current');
    expect(result.current.current).toBeNull(); // Initially null
  });
});

Dependencies

  • @fractal-mcp/shared-ui - Shared messaging utilities and types
  • react-jsx-parser - JSX string parsing for FractalFrame
  • react & react-dom - React framework (peer dependencies)

Requirements

  • React 19.0+
  • Modern browser with MessageChannel support
  • ES2020+ JavaScript environment

Best Practices

  1. Always use error boundaries around Fractal components
  2. Memoize expensive computations for large component maps
  3. Handle events gracefully with proper error checking
  4. Use TypeScript for better type safety with events
  5. Test widget communication in isolation
  6. Validate event data before processing
  7. Implement loading states for async widget operations
  8. Use refs sparingly - prefer event-driven communication