This is an automated email from the ASF dual-hosted git repository.

apratim pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/incubator-resilientdb-resvault.git


The following commit(s) were added to refs/heads/main by this push:
     new 486e2f6  Added upload transaction functionality
486e2f6 is described below

commit 486e2f6c646dec4d8ece19eb07409cb150c04b8f
Author: Apratim Shukla <apratimshuk...@gmail.com>
AuthorDate: Sun Sep 29 18:58:32 2024 -0700

    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 9f6f054..c0b775f 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 3a378bd..caf28f1 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() {
                 <Lottie options={defaultOptions} height="100%" width="100%" />
             </div>
             <div className="page page--main" data-page="buy">
-            <header className="header header--fixed">
+                <header className="header header--fixed">
                     <div className="header__inner header-container">
                         <div className="header__logo header__logo--text">
                             Res<strong>Vault</strong>
@@ -476,6 +571,40 @@ function Dashboard() {
                     </div>
                 )}
 
+                {showSuccessModal && (
+                    <div className="overlay">
+                        <div className="modal">
+                            <div className="modal-content">
+                                <h2>Transaction Submitted Successfully!</h2>
+                                {/* Extract transaction ID */}
+                                {successResponse && 
successResponse.postTransaction && successResponse.postTransaction.id ? (
+                                <div className="fieldset">
+                                    <div className="radio-option 
radio-option--full">
+                                        <input
+                                            type="radio"
+                                            name="transactionId"
+                                            id="txId"
+                                            
value={successResponse.postTransaction.id}
+                                            checked
+                                            readOnly
+                                            onClick={handleIdClick}
+                                        />
+                                        <label htmlFor="txId">
+                                            <span>{isIdCopied ? 'Copied' : 
`${successResponse.postTransaction.id.slice(0, 
5)}...${successResponse.postTransaction.id.slice(-5)}`}</span>
+                                        </label>
+                                    </div>
+                                </div>
+                                ) : (
+                                    <p>No transaction ID found.</p>
+                                )}
+                                <button onClick={() => 
setShowSuccessModal(false)} className="button-close">
+                                    Close
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                )}
+
                 <div className="page__content page__content--with-header 
page__content--with-bottom-nav">
                     <h2 className="page__title">Dashboard</h2>
 
@@ -533,6 +662,7 @@ function Dashboard() {
                                     <span className="filename">Click to Upload 
JSON File</span>
                                 )}
                             </div>
+                            {transactionError && <p 
className="error-message">{transactionError}</p>}
                         </div>
                     </div>
 
@@ -567,4 +697,4 @@ function Dashboard() {
     );
 }
 
-export default Dashboard;
+export default Dashboard;
\ No newline at end of file

Reply via email to