"use client"; import React, { useEffect, useRef, useState } from "react"; import { Puzzle, AlertTriangle, ExternalLink } from "lucide-react"; interface PluginBlockProps { pluginId: string; name: string; componentUrl: string; config?: Record; } /** * Renderiza un Web Component externo dentro de un iframe sandboxed. * El sandbox permite scripts y same-origin pero bloquea navegación superior, * formularios externos y acceso a cámara/micrófono sin permiso explícito. */ export default function PluginBlock({ pluginId, name, componentUrl, config = {} }: PluginBlockProps) { const iframeRef = useRef(null); const [error, setError] = useState(null); const [loaded, setLoaded] = useState(false); // Solo permitir HTTPS const isSecure = componentUrl.startsWith("https://"); useEffect(() => { if (!isSecure) { setError("Este plugin no puede cargarse: la URL debe usar HTTPS."); return; } // Enviar config al iframe cuando cargue vía postMessage const handleLoad = () => { setLoaded(true); iframeRef.current?.contentWindow?.postMessage( { type: "OPENCCB_PLUGIN_CONFIG", pluginId, config }, new URL(componentUrl).origin ); }; const iframe = iframeRef.current; if (iframe) { iframe.addEventListener("load", handleLoad); return () => iframe.removeEventListener("load", handleLoad); } }, [componentUrl, config, isSecure, pluginId]); if (!isSecure) { return (
El plugin {name} no puede cargarse: URL no segura.
); } return (
{/* Header */}
{name}
Abrir
{/* Loading state */} {!loaded && (
Cargando plugin…
)} {/* Iframe sandboxed */}