UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

15 lines 5.93 kB
{ "name": "metalens-components-metalensembed", "type": "registry:component", "dependencies": [], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/metalens/components/MetaLensEmbed.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { MetaLensEmbedProps } from \"../types/index.js\";\nimport { validateContext } from \"../utils/protocol.js\";\nimport { useMetaLensProvider } from \"./MetaLensProvider.js\";\n\n/**\n * Iframe-ready MetaLens component for embedding on external sites\n *\n * Based on patterns from:\n * - /Users/satchmo/code/tonicpow-website-static/src/components/layout/MetaLens/index.tsx\n * - /Users/satchmo/code/metalens-web/src_old/views/model_comments.html (iframe patterns)\n *\n * @example\n * ```tsx\n * <MetaLensEmbed\n * context=\"url\"\n * value=\"https://example.com\"\n * height=\"auto\"\n * onHeightChange={(height) => console.log('New height:', height)}\n * onSuccess={(txid) => console.log('Comment posted:', txid)}\n * />\n * ```\n */\nexport function MetaLensEmbed({\n\tcontext,\n\tvalue,\n\theight = \"auto\",\n\tscrolling = false,\n\ttheme,\n\tonHeightChange,\n\tonSuccess,\n}: MetaLensEmbedProps) {\n\tconst { config } = useMetaLensProvider();\n\tconst iframeRef = useRef<HTMLIFrameElement>(null);\n\tconst [currentHeight, setCurrentHeight] = useState<number>(\n\t\ttypeof height === \"number\" ? height : 400,\n\t);\n\tconst [iframeReady, setIframeReady] = useState(false);\n\n\t// Validate context\n\tconst validation = validateContext(context, value);\n\tif (!validation.valid) {\n\t\tconsole.error(\"Invalid MetaLensEmbed context:\", validation.error);\n\t}\n\n\tconst handleMessage = useCallback(\n\t\t(event: MessageEvent) => {\n\t\t\t// Security: Only accept messages from trusted origins\n\t\t\tconst trustedOrigins = [\n\t\t\t\t\"https://metalens-react.web.app\",\n\t\t\t\t\"https://metalens.app\",\n\t\t\t\tconfig.apiUrl,\n\t\t\t\t// Allow localhost for development\n\t\t\t\t...(typeof window !== \"undefined\" &&\n\t\t\t\twindow.location.hostname === \"localhost\"\n\t\t\t\t\t? [\"http://localhost:3000\", \"http://localhost:5173\"]\n\t\t\t\t\t: []),\n\t\t\t];\n\n\t\t\tif (!trustedOrigins.includes(event.origin)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle different message types\n\t\t\tswitch (event.data.type) {\n\t\t\t\tcase \"window\": {\n\t\t\t\t\t// Height adjustment message\n\t\t\t\t\tconst newHeight = Number.parseInt(event.data.height);\n\t\t\t\t\tif (!Number.isNaN(newHeight) && newHeight > 0) {\n\t\t\t\t\t\tsetCurrentHeight(newHeight);\n\t\t\t\t\t\tonHeightChange?.(newHeight);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"success\":\n\t\t\t\t\t// Comment posted successfully\n\t\t\t\t\tif (event.data.txid) {\n\t\t\t\t\t\tonSuccess?.(event.data.txid);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase \"ready\": {\n\t\t\t\t\t// Iframe loaded and ready\n\t\t\t\t\tsetIframeReady(true);\n\t\t\t\t\t// Send configuration to iframe\n\t\t\t\t\tconst mergedTheme = { ...config.theme, ...theme };\n\t\t\t\t\tiframeRef.current?.contentWindow?.postMessage(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"config\",\n\t\t\t\t\t\t\ttheme: mergedTheme,\n\t\t\t\t\t\t\tappName: config.appName,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"*\",\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase \"error\":\n\t\t\t\t\t// Handle errors\n\t\t\t\t\tconsole.error(\"MetaLens iframe error:\", event.data.error);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t},\n\t\t[\n\t\t\tconfig.apiUrl,\n\t\t\tconfig.theme,\n\t\t\tconfig.appName,\n\t\t\ttheme,\n\t\t\tonHeightChange,\n\t\t\tonSuccess,\n\t\t],\n\t);\n\n\tuseEffect(() => {\n\t\twindow.addEventListener(\"message\", handleMessage);\n\t\treturn () => window.removeEventListener(\"message\", handleMessage);\n\t}, [handleMessage]);\n\n\t// Build iframe URL with parameters\n\tconst buildIframeUrl = useCallback(() => {\n\t\t// Use the existing MetaLens embed URL (can be updated to self-host later)\n\t\tconst baseUrl = `https://metalens-react.web.app/embed/${context}/${encodeURIComponent(value)}`;\n\n\t\t// Build query parameters\n\t\tconst params = new URLSearchParams();\n\n\t\t// Add theme parameters\n\t\tconst mergedTheme = { ...config.theme, ...theme };\n\t\tfor (const [key, val] of Object.entries(mergedTheme)) {\n\t\t\tif (val && typeof val === \"string\") {\n\t\t\t\tparams.append(key, val);\n\t\t\t}\n\t\t}\n\n\t\t// Add app identifier\n\t\tparams.append(\"app\", config.appName);\n\n\t\t// Add configuration flags\n\t\tparams.append(\"realtime\", config.enableRealTime ? \"1\" : \"0\");\n\t\tparams.append(\"moderation\", config.enableModeration ? \"1\" : \"0\");\n\n\t\t// Add form visibility\n\t\tparams.append(\"showForm\", \"1\"); // Always show form in embed\n\n\t\treturn `${baseUrl}?${params.toString()}`;\n\t}, [context, value, config, theme]);\n\n\t// Show error state if context is invalid\n\tif (!validation.valid) {\n\t\treturn (\n\t\t\t<div className=\"p-5 text-center text-muted-foreground border border-border rounded min-h-[200px] flex items-center justify-center\">\n\t\t\t\t<p>Invalid MetaLens context: {validation.error}</p>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<iframe\n\t\t\tref={iframeRef}\n\t\t\tsrc={buildIframeUrl()}\n\t\t\ttitle=\"MetaLens Comments\"\n\t\t\twidth=\"100%\"\n\t\t\theight={height === \"auto\" ? currentHeight : height}\n\t\t\tscrolling={scrolling ? \"yes\" : \"no\"}\n\t\t\tframeBorder=\"0\"\n\t\t\tstyle={{\n\t\t\t\tborder: \"none\",\n\t\t\t\tminHeight: 200,\n\t\t\t\tmaxWidth: \"100%\",\n\t\t\t\ttransition: \"height 0.3s ease\",\n\t\t\t\topacity: iframeReady ? 1 : 0.5,\n\t\t\t}}\n\t\t\tsandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox\"\n\t\t\tloading=\"lazy\"\n\t\t/>\n\t);\n}\n", "target": "<%- config.aliases.components %>/metalensembed.tsx" } ] }