From 486e2f6c646dec4d8ece19eb07409cb150c04b8f Mon Sep 17 00:00:00 2001 From: Apratim Shukla Date: Sun, 29 Sep 2024 18:58:32 -0700 Subject: [PATCH] Added upload transaction functionality - The user can now upload a transaction in a specific format and submit it to the connected net. - A modal will then display the returned callback and allow the user to easily copy the ID. --- public/background.js | 156 +++++++++++++++++++------------------- src/pages/Dashboard.jsx | 164 +++++++++++++++++++++++++++++++++++----- 2 files changed, 225 insertions(+), 95 deletions(-) diff --git a/public/background.js b/public/background.js index 9f6f0543a..c0b775fe4 100644 --- a/public/background.js +++ b/public/background.js @@ -236,95 +236,95 @@ chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { })(); return true; // Keep the message channel open for async sendResponse - } else if (request.action === 'submitTransaction') { + } else if (request.action === 'submitTransactionFromDashboard') { (async function() { - const senderUrl = sender.tab ? sender.tab.url : sender.url || null; - console.log('Sender URL:', senderUrl); - const domain = getBaseDomain(senderUrl); - console.log('Domain:', domain); + // Retrieve the necessary data + const transactionData = request.transactionData; + const domain = request.domain; + const net = request.net; + + // Validate transactionData + if (!transactionData || !transactionData.asset || !transactionData.recipientAddress || !transactionData.amount) { + sendResponse({ success: false, error: 'Invalid transaction data.' }); + return; + } + + // Retrieve the signer's keys and URL from storage chrome.storage.local.get(['keys', 'connectedNets'], async function (result) { const keys = result.keys || {}; const connectedNets = result.connectedNets || {}; - console.log('ConnectedNets:', connectedNets); - const net = connectedNets[domain]; - console.log('Net for domain:', domain, 'is', net); - - if (keys[domain] && keys[domain][net]) { - const { publicKey, privateKey, url, exportedKey } = keys[domain][net]; - - try { - // Import the key material from JWK format - const keyMaterial = await crypto.subtle.importKey( - 'jwk', - exportedKey, - { name: 'AES-GCM' }, - true, - ['encrypt', 'decrypt'] - ); - - const decryptedPublicKey = await decryptData(publicKey.ciphertext, publicKey.iv, keyMaterial); - const decryptedPrivateKey = await decryptData(privateKey.ciphertext, privateKey.iv, keyMaterial); - const decryptedUrl = await decryptData(url.ciphertext, url.iv, keyMaterial); - // Check if required fields are defined - if (!decryptedPublicKey || !decryptedPrivateKey || !request.recipient) { - console.error('Missing required fields for transaction submission'); - sendResponse({ success: false, error: 'Missing required fields for transaction' }); - return; - } + if (!connectedNets[domain] || connectedNets[domain] !== net) { + sendResponse({ success: false, error: 'Not connected to the specified net for this domain.' }); + return; + } - // Prepare asset data as a JSON string - const assetData = JSON.stringify({ - data: request.data || {} - }); + if (!keys[domain] || !keys[domain][net]) { + sendResponse({ success: false, error: 'Keys not found for the specified domain and net.' }); + return; + } - // Construct the GraphQL mutation - const mutation = ` - mutation { - postTransaction(data: { - operation: "CREATE", - amount: ${parseInt(request.amount)}, - signerPublicKey: "${escapeGraphQLString(decryptedPublicKey)}", - signerPrivateKey: "${escapeGraphQLString(decryptedPrivateKey)}", - recipientPublicKey: "${escapeGraphQLString(request.recipient)}", - asset: """${assetData}""" - }) { - id - } + const { publicKey, privateKey, url, exportedKey } = keys[domain][net]; + + try { + // Import the key material from JWK format + const keyMaterial = await crypto.subtle.importKey( + 'jwk', + exportedKey, + { name: 'AES-GCM', + }, + true, + ['encrypt', 'decrypt'] + ); + + const decryptedPublicKey = await decryptData(publicKey.ciphertext, publicKey.iv, keyMaterial); + const decryptedPrivateKey = await decryptData(privateKey.ciphertext, privateKey.iv, keyMaterial); + const decryptedUrl = await decryptData(url.ciphertext, url.iv, keyMaterial); + + // Prepare the asset data + const assetData = JSON.stringify(transactionData.asset); + + // Construct the GraphQL mutation + const mutation = ` + mutation { + postTransaction(data: { + operation: "CREATE", + amount: ${parseInt(transactionData.amount)}, + signerPublicKey: "${escapeGraphQLString(decryptedPublicKey)}", + signerPrivateKey: "${escapeGraphQLString(decryptedPrivateKey)}", + recipientPublicKey: "${escapeGraphQLString(transactionData.recipientAddress)}", + asset: """${assetData}""" + }) { + id } - `; - - // Log the mutation for debugging - console.log('Mutation:', mutation); - - const response = await fetch(decryptedUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ query: mutation }), - }); - - if (!response.ok) { - throw new Error(`Network response was not ok: ${response.statusText}`); } + `; + + // Send the mutation + const response = await fetch(decryptedUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ query: mutation }), + }); - const resultData = await response.json(); - if (resultData.errors) { - console.error('GraphQL errors:', resultData.errors); - sendResponse({ success: false, errors: resultData.errors }); - } else { - console.log('Transaction submitted successfully:', resultData.data); - sendResponse({ success: true, data: resultData.data }); - } - } catch (error) { - console.error('Error submitting transaction:', error); - sendResponse({ success: false, error: error.message }); + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.statusText}`); } - } else { - console.error('No keys found for domain:', domain, 'and net:', net); - console.log('Available keys:', keys); - sendResponse({ error: "No keys found for domain and net" }); + + const resultData = await response.json(); + if (resultData.errors) { + console.error('GraphQL errors:', resultData.errors); + sendResponse({ success: false, error: 'GraphQL errors occurred.', errors: resultData.errors }); + } else { + console.log('Transaction submitted successfully:', resultData.data); + sendResponse({ success: true, data: resultData.data }); + } + + } catch (error) { + console.error('Error submitting transaction:', error); + sendResponse({ success: false, error: error.message }); } }); })(); diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 3a378bdfc..caf28f1c6 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -11,8 +11,8 @@ * * 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 +* "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. */ @@ -49,6 +49,15 @@ function Dashboard() { const inputRef = useRef(null); const navigate = useNavigate(); + // New state variables for transaction data and handling + const [transactionData, setTransactionData] = useState(null); + const [transactionError, setTransactionError] = useState(''); + const [showSuccessModal, setShowSuccessModal] = useState(false); + const [successResponse, setSuccessResponse] = useState(null); + + // New state for copying transaction ID + const [isIdCopied, setIsIdCopied] = useState(false); + useEffect(() => { console.log('publicKey in Dashboard:', publicKey); console.log('privateKey in Dashboard:', privateKey); @@ -109,7 +118,7 @@ function Dashboard() { useEffect(() => { chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { - if (tabs.length > 0) { + if (tabs.length > 0 && tabs[0].url) { const currentTab = tabs[0]; setTabId(currentTab.id); @@ -123,7 +132,15 @@ function Dashboard() { }); } } else { - setIsConnected(false); + // If no active tab or no URL, set domain to 'extension' + setDomain('extension'); + + // Ensure selectedNet is set before checking connection status + if (selectedNet) { + getDomainConnectionStatus('extension', selectedNet, (connected) => { + setIsConnected(connected); + }); + } } }); }, [selectedNet]); @@ -219,10 +236,6 @@ function Dashboard() { return; } - // Log keys for debugging (Remove in production) - console.log('Connecting with publicKey:', publicKey); - console.log('Connecting with privateKey:', privateKey); - const newConnectionStatus = !isConnected; setIsConnected(newConnectionStatus); @@ -351,18 +364,34 @@ function Dashboard() { } }; - const [isJSONInvalid, setIsJSONInvalid] = useState(false); - - const showError = () => { - setIsJSONInvalid(true); - }; - const handleFileUpload = (e) => { const file = e.target.files[0]; if (file && file.type === 'application/json') { setJsonFileName(file.name); // Show file name once uploaded + + const reader = new FileReader(); + reader.onload = (event) => { + try { + const json = JSON.parse(event.target.result); + // Validate JSON data + if (json.asset && json.recipientAddress && json.amount) { + setTransactionData(json); + setTransactionError(''); // Clear any previous error + } else { + setTransactionData(null); + setTransactionError('Invalid JSON format: Missing required fields.'); + } + } catch (err) { + console.error('Error parsing JSON:', err); + setTransactionData(null); + setTransactionError('Invalid JSON format.'); + } + }; + reader.readAsText(file); } else { setJsonFileName(''); // Clear if the file is not JSON + setTransactionData(null); + setTransactionError('Please upload a JSON file.'); } }; @@ -382,8 +411,30 @@ function Dashboard() { const file = e.dataTransfer.files[0]; if (file && file.type === 'application/json') { setJsonFileName(file.name); + + const reader = new FileReader(); + reader.onload = (event) => { + try { + const json = JSON.parse(event.target.result); + // Validate JSON data + if (json.asset && json.recipientAddress && json.amount) { + setTransactionData(json); + setTransactionError(''); // Clear any previous error + } else { + setTransactionData(null); + setTransactionError('Invalid JSON format: Missing required fields.'); + } + } catch (err) { + console.error('Error parsing JSON:', err); + setTransactionData(null); + setTransactionError('Invalid JSON format.'); + } + }; + reader.readAsText(file); } else { setJsonFileName(''); + setTransactionData(null); + setTransactionError('Please upload a JSON file.'); } }; @@ -392,7 +443,51 @@ function Dashboard() { }; const handleSubmit = () => { - setJsonFileName(''); // Clear the file name on submit + if (!transactionData) { + setTransactionError('No valid transaction data found.'); + return; + } + if (!isConnected) { + setTransactionError('Please connect to a net before submitting a transaction.'); + return; + } + + // Send transaction data to background script + chrome.runtime.sendMessage({ + action: 'submitTransactionFromDashboard', + transactionData: transactionData, + domain: domain, + net: selectedNet, + }, (response) => { + if (response.success) { + setSuccessResponse(response.data); + setShowSuccessModal(true); + setTransactionError(''); + setJsonFileName(''); // Clear the file name after successful submission + setTransactionData(null); + } else { + setTransactionError(response.error || 'Transaction submission failed.'); + } + }); + }; + + // New function to handle transaction ID click + const handleIdClick = () => { + try { + const transactionId = (successResponse && successResponse.postTransaction && successResponse.postTransaction.id) || ''; + const tempInput = document.createElement('input'); + tempInput.value = transactionId; + document.body.appendChild(tempInput); + tempInput.select(); + document.execCommand('copy'); + document.body.removeChild(tempInput); + setIsIdCopied(true); + setTimeout(() => { + setIsIdCopied(false); + }, 1500); + } catch (err) { + console.error('Unable to copy text: ', err); + } }; return ( @@ -401,7 +496,7 @@ function Dashboard() {
-
+
ResVault @@ -476,6 +571,40 @@ function Dashboard() {
)} + {showSuccessModal && ( +
+
+
+

Transaction Submitted Successfully!

+ {/* Extract transaction ID */} + {successResponse && successResponse.postTransaction && successResponse.postTransaction.id ? ( +
+
+ + +
+
+ ) : ( +

No transaction ID found.

+ )} + +
+
+
+ )} +

Dashboard

@@ -533,6 +662,7 @@ function Dashboard() { Click to Upload JSON File )}
+ {transactionError &&

{transactionError}

}
@@ -567,4 +697,4 @@ function Dashboard() { ); } -export default Dashboard; +export default Dashboard; \ No newline at end of file