import { useState, useEffect } from 'react'; import { getChromeApi } from '../utils/chromeApi'; import { Box, Typography, Button, TextField, Paper, Alert, CircularProgress, Divider, Tabs, Tab, List, ListItem, ListItemText, ListItemSecondaryAction, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, Chip } from '@mui/material'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; import VisibilityIcon from '@mui/icons-material/Visibility'; // DeleteIcon removed as it's not used import { useNavigate } from 'react-router-dom'; import { useSessionStore } from '../store/sessionStore'; interface ScriptResult { id: string; timestamp: number; script: string; result: string; success: boolean; } interface PendingScript { id: string; title: string; description: string; script: string; tags: string[]; timestamp: number; } const ScriptPage = () => { const navigate = useNavigate(); const { isSessionUnlocked, currentKeypair } = useSessionStore(); const [tabValue, setTabValue] = useState(0); const [scriptInput, setScriptInput] = useState(''); const [isExecuting, setIsExecuting] = useState(false); const [executionResult, setExecutionResult] = useState(null); const [executionSuccess, setExecutionSuccess] = useState(null); const [scriptResults, setScriptResults] = useState([]); const [pendingScripts, setPendingScripts] = useState([]); const [selectedPendingScript, setSelectedPendingScript] = useState(null); const [scriptDialogOpen, setScriptDialogOpen] = useState(false); const [error, setError] = useState(null); // Redirect if not unlocked useEffect(() => { if (!isSessionUnlocked) { navigate('/'); } }, [isSessionUnlocked, navigate]); // Load pending scripts from storage useEffect(() => { const loadPendingScripts = async () => { try { const chromeApi = getChromeApi(); const data = await chromeApi.storage.local.get('pendingScripts'); if (data.pendingScripts) { setPendingScripts(data.pendingScripts); } } catch (err) { console.error('Failed to load pending scripts:', err); } }; if (isSessionUnlocked) { loadPendingScripts(); } }, [isSessionUnlocked]); // Load script history from storage useEffect(() => { const loadScriptResults = async () => { try { const chromeApi = getChromeApi(); const data = await chromeApi.storage.local.get('scriptResults'); if (data.scriptResults) { setScriptResults(data.scriptResults); } } catch (err) { console.error('Failed to load script results:', err); } }; if (isSessionUnlocked) { loadScriptResults(); } }, [isSessionUnlocked]); const handleTabChange = (_: React.SyntheticEvent, newValue: number) => { setTabValue(newValue); }; const handleExecuteScript = async () => { if (!scriptInput.trim()) return; setIsExecuting(true); setError(null); setExecutionResult(null); setExecutionSuccess(null); try { // Call the WASM run_rhai function via our store const result = await useSessionStore.getState().executeScript(scriptInput); setExecutionResult(result); setExecutionSuccess(true); // Save to history const newResult: ScriptResult = { id: `script-${Date.now()}`, timestamp: Date.now(), script: scriptInput, result, success: true }; const updatedResults = [newResult, ...scriptResults].slice(0, 20); // Keep last 20 setScriptResults(updatedResults); // Save to storage const chromeApi = getChromeApi(); await chromeApi.storage.local.set({ scriptResults: updatedResults }); } catch (err) { setError((err as Error).message || 'Failed to execute script'); setExecutionSuccess(false); setExecutionResult('Execution failed'); } finally { setIsExecuting(false); } }; const handleViewPendingScript = (script: PendingScript) => { setSelectedPendingScript(script); setScriptDialogOpen(true); }; const handleApprovePendingScript = async () => { if (!selectedPendingScript) return; setScriptDialogOpen(false); setScriptInput(selectedPendingScript.script); setTabValue(0); // Switch to execute tab // Remove from pending list const updatedPendingScripts = pendingScripts.filter( script => script.id !== selectedPendingScript.id ); setPendingScripts(updatedPendingScripts); const chromeApi = getChromeApi(); await chromeApi.storage.local.set({ pendingScripts: updatedPendingScripts }); setSelectedPendingScript(null); }; const handleRejectPendingScript = async () => { if (!selectedPendingScript) return; // Remove from pending list const updatedPendingScripts = pendingScripts.filter( script => script.id !== selectedPendingScript.id ); setPendingScripts(updatedPendingScripts); const chromeApi = getChromeApi(); await chromeApi.storage.local.set({ pendingScripts: updatedPendingScripts }); setScriptDialogOpen(false); setSelectedPendingScript(null); }; const handleClearHistory = async () => { setScriptResults([]); const chromeApi = getChromeApi(); await chromeApi.storage.local.set({ scriptResults: [] }); }; if (!isSessionUnlocked) { return null; // Will redirect via useEffect } return ( Pending {pendingScripts.length > 0 && ( )} } sx={{ minHeight: '48px', py: 0 }} /> {/* Execute Tab */} {tabValue === 0 && ( {!currentKeypair && ( No keypair selected. Select a keypair to enable script execution with signing capabilities. )} {error && ( {error} )} setScriptInput(e.target.value)} fullWidth variant="outlined" placeholder="Enter your Rhai script here..." sx={{ mb: 2 }} disabled={isExecuting} /> {executionResult && ( Execution Result: {executionResult} )} )} {/* Pending Scripts Tab */} {tabValue === 1 && ( {pendingScripts.length === 0 ? ( No pending scripts. Incoming scripts from connected WebSocket servers will appear here. ) : ( {pendingScripts.map((script, index) => ( {index > 0 && } {script.description || 'No description'} {script.tags.map(tag => ( ))} } /> handleViewPendingScript(script)} aria-label="view script" > ))} )} )} {/* History Tab */} {tabValue === 2 && ( {scriptResults.length === 0 ? ( No script execution history yet. ) : ( {scriptResults.map((result, index) => ( {index > 0 && } {new Date(result.timestamp).toLocaleString()} } secondary={ {result.script} } /> { setScriptInput(result.script); setTabValue(0); }} aria-label="reuse script" > ))} )} )} {/* Pending Script Dialog */} setScriptDialogOpen(false)} maxWidth="md" fullWidth > {selectedPendingScript?.title || 'Script Details'} {selectedPendingScript && ( <> Description: {selectedPendingScript.description || 'No description provided'} {selectedPendingScript.tags.map(tag => ( ))} Script Content: {selectedPendingScript.script} {selectedPendingScript.tags.includes('remote') ? 'This is a remote script. If approved, your signature will be sent to the server and the script may execute remotely.' : 'This script will execute locally in your browser extension if approved.'} )} ); }; export default ScriptPage;