diff --git a/src/entries/Background/rpc.ts b/src/entries/Background/rpc.ts index fb5606a..dc20e99 100644 --- a/src/entries/Background/rpc.ts +++ b/src/entries/Background/rpc.ts @@ -528,6 +528,7 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) { websocketProxyUrl: _websocketProxyUrl, maxSentData: _maxSentData, maxRecvData: _maxRecvData, + metadata, } = request.data; const notaryUrl = _notaryUrl || (await getNotaryApi()); const websocketProxyUrl = _websocketProxyUrl || (await getProxyApi()); @@ -547,6 +548,7 @@ async function runPluginProver(request: BackgroundAction, now = Date.now()) { maxSentData, secretHeaders, secretResps, + metadata, }); await setNotaryRequestStatus(id, 'pending'); diff --git a/src/entries/SidePanel/SidePanel.tsx b/src/entries/SidePanel/SidePanel.tsx index ecda919..280a87f 100644 --- a/src/entries/SidePanel/SidePanel.tsx +++ b/src/entries/SidePanel/SidePanel.tsx @@ -7,6 +7,7 @@ import { makePlugin, PluginConfig, StepConfig, + InputFieldConfig, } from '../../utils/misc'; import DefaultPluginIcon from '../../assets/img/default-plugin-icon.png'; import logo from '../../assets/img/icon-128.png'; @@ -225,13 +226,27 @@ function StepContent( p2p = false, clientId = '', parameterValues, + inputs, } = props; const [completed, setCompleted] = useState(false); const [pending, setPending] = useState(false); const [error, setError] = useState(''); const [notarizationId, setNotarizationId] = useState(''); + const [inputValues, setInputValues] = useState>({}); const notaryRequest = useRequestHistory(notarizationId); + useEffect(() => { + if (inputs) { + const initialValues: Record = {}; + inputs.forEach((input) => { + if (input.defaultValue) { + initialValues[input.name] = input.defaultValue; + } + }); + setInputValues(initialValues); + } + }, [inputs]); + const getPlugin = useCallback(async () => { const hex = (await getPluginByUrl(url)) || _hex; const arrayBuffer = hexToArrayBuffer(hex!); @@ -243,16 +258,31 @@ function StepContent( if (!plugin) return; if (index > 0 && !lastResponse) return; + // Validate required input fields + if (inputs) { + for (const input of inputs) { + if ( + input.required && + (!inputValues[input.name] || inputValues[input.name].trim() === '') + ) { + setError(`${input.label} is required`); + return; + } + } + } + setPending(true); setError(''); try { - const out = await plugin.call( - action, - index > 0 - ? JSON.stringify(lastResponse) - : JSON.stringify(parameterValues), - ); + let stepData: any; + if (index > 0) { + stepData = lastResponse; + } else { + stepData = { ...parameterValues, ...inputValues }; + } + + const out = await plugin.call(action, JSON.stringify(stepData)); const val = JSON.parse(out!.string()); if (val && prover) { setNotarizationId(val); @@ -266,7 +296,16 @@ function StepContent( } finally { setPending(false); } - }, [action, index, lastResponse, prover, getPlugin]); + }, [ + action, + index, + lastResponse, + prover, + getPlugin, + inputs, + inputValues, + parameterValues, + ]); const onClick = useCallback(() => { if ( @@ -311,8 +350,11 @@ function StepContent( }, []); useEffect(() => { - processStep(); - }, [processStep]); + // only auto-progress if this step does need inputs + if (!inputs || inputs.length === 0) { + processStep(); + } + }, [processStep, inputs]); let btnContent = null; @@ -420,8 +462,105 @@ function StepContent( )} + {inputs && inputs.length > 0 && !completed && ( +
+ {inputs.map((input) => ( + + setInputValues((prev) => ({ ...prev, [input.name]: value })) + } + /> + ))} +
+ )} {btnContent} ); } + +interface InputFieldProps { + config: InputFieldConfig; + value: string; + onChange: (value: string) => void; + disabled?: boolean; +} + +function InputField({ + config, + value, + onChange, + disabled = false, +}: InputFieldProps): ReactElement { + const { name, label, type, placeholder, required, options } = config; + + const baseClasses = + 'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent'; + + const renderInput = () => { + switch (type) { + case 'textarea': + return ( +