/** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import React, { useState, useEffect, useCallback } from 'react'; import { createRoot } from 'react-dom/client'; import { Button, TabItem } from './tabItem.js'; import type { TabInfo } from './tabItem.js'; type StatusType = 'connected' | 'error' | 'connecting'; const ConnectApp: React.FC = () => { const [tabs, setTabs] = useState([]); const [status, setStatus] = useState<{ type: StatusType; message: string } | null>(null); const [showButtons, setShowButtons] = useState(true); const [showTabList, setShowTabList] = useState(true); const [clientInfo, setClientInfo] = useState('unknown'); const [mcpRelayUrl, setMcpRelayUrl] = useState(''); useEffect(() => { const params = new URLSearchParams(window.location.search); const relayUrl = params.get('mcpRelayUrl'); if (!relayUrl) { setShowButtons(false); setStatus({ type: 'error', message: 'Missing mcpRelayUrl parameter in URL.' }); return; } setMcpRelayUrl(relayUrl); try { const client = JSON.parse(params.get('client') || '{}'); const info = `${client.name}/${client.version}`; setClientInfo(info); setStatus({ type: 'connecting', message: `🎭 Playwright MCP started from "${info}" is trying to connect. Do you want to continue?` }); } catch (e) { setStatus({ type: 'error', message: 'Failed to parse client version.' }); return; } void connectToMCPRelay(relayUrl); void loadTabs(); }, []); const connectToMCPRelay = useCallback(async (mcpRelayUrl: string) => { const response = await chrome.runtime.sendMessage({ type: 'connectToMCPRelay', mcpRelayUrl }); if (!response.success) setStatus({ type: 'error', message: 'Failed to connect to MCP relay: ' + response.error }); }, []); const loadTabs = useCallback(async () => { const response = await chrome.runtime.sendMessage({ type: 'getTabs' }); if (response.success) setTabs(response.tabs); else setStatus({ type: 'error', message: 'Failed to load tabs: ' + response.error }); }, []); const handleConnectToTab = useCallback(async (tab: TabInfo) => { setShowButtons(false); setShowTabList(false); try { const response = await chrome.runtime.sendMessage({ type: 'connectToTab', mcpRelayUrl, tabId: tab.id, windowId: tab.windowId, }); if (response?.success) { setStatus({ type: 'connected', message: `MCP client "${clientInfo}" connected.` }); } else { setStatus({ type: 'error', message: response?.error || `MCP client "${clientInfo}" failed to connect.` }); } } catch (e) { setStatus({ type: 'error', message: `MCP client "${clientInfo}" failed to connect: ${e}` }); } }, [clientInfo, mcpRelayUrl]); const handleReject = useCallback(() => { setShowButtons(false); setShowTabList(false); setStatus({ type: 'error', message: 'Connection rejected. This tab can be closed.' }); }, []); useEffect(() => { const listener = (message: any) => { if (message.type === 'connectionTimeout') handleReject(); }; chrome.runtime.onMessage.addListener(listener); return () => { chrome.runtime.onMessage.removeListener(listener); }; }, []); return (
{status && (
{showButtons && ( )}
)} {showTabList && (
Select page to expose to MCP server:
{tabs.map(tab => ( handleConnectToTab(tab)}> Connect } /> ))}
)}
); }; const StatusBanner: React.FC<{ type: StatusType; message: string }> = ({ type, message }) => { return
{message}
; }; // Initialize the React app const container = document.getElementById('root'); if (container) { const root = createRoot(container); root.render(); }