mirror of
https://github.com/Qortal/chrome-extension.git
synced 2025-02-11 17:55:49 +00:00
batch of updates 6
This commit is contained in:
parent
7a56f3c803
commit
a4c1fd564b
48
src/App.tsx
48
src/App.tsx
@ -115,6 +115,7 @@ import { Tutorials } from "./components/Tutorials/Tutorials";
|
|||||||
import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials";
|
import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials";
|
||||||
import { CoreSyncStatus } from "./components/CoreSyncStatus";
|
import { CoreSyncStatus } from "./components/CoreSyncStatus";
|
||||||
import BoundedNumericTextField from "./common/BoundedNumericTextField";
|
import BoundedNumericTextField from "./common/BoundedNumericTextField";
|
||||||
|
import { Wallets } from "./Wallets";
|
||||||
|
|
||||||
type extStates =
|
type extStates =
|
||||||
| "not-authenticated"
|
| "not-authenticated"
|
||||||
@ -919,7 +920,12 @@ function App() {
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
setExtstate("authenticated");
|
if(response?.hasKeyPair){
|
||||||
|
setExtstate("authenticated");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setExtstate("wallet-dropped");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1069,7 +1075,7 @@ function App() {
|
|||||||
try {
|
try {
|
||||||
if(hasSettingsChanged){
|
if(hasSettingsChanged){
|
||||||
await showUnsavedChanges({message: 'Your settings have changed. If you logout you will lose your changes. Click on the save button in the header to keep your changed settings.'})
|
await showUnsavedChanges({message: 'Your settings have changed. If you logout you will lose your changes. Click on the save button in the header to keep your changed settings.'})
|
||||||
} if(extState === 'authenticated') {
|
} else if(extState === 'authenticated') {
|
||||||
await showUnsavedChanges({
|
await showUnsavedChanges({
|
||||||
message:
|
message:
|
||||||
"Are you sure you would like to logout?",
|
"Are you sure you would like to logout?",
|
||||||
@ -1831,7 +1837,7 @@ function App() {
|
|||||||
value={paymentPassword}
|
value={paymentPassword}
|
||||||
onChange={(e) => setPaymentPassword(e.target.value)}
|
onChange={(e) => setPaymentPassword(e.target.value)}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
ref={passwordRef}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Spacer height="10px" />
|
<Spacer height="10px" />
|
||||||
@ -2321,6 +2327,34 @@ function App() {
|
|||||||
Create account
|
Create account
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
{extState === "wallets" && (
|
||||||
|
<>
|
||||||
|
<Spacer height="22px" />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
paddingLeft: "22px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setRawWallet(null);
|
||||||
|
setExtstate("not-authenticated");
|
||||||
|
logoutFunc();
|
||||||
|
}}
|
||||||
|
src={Return}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Wallets setRawWallet={setRawWallet} setExtState={setExtstate} rawWallet={rawWallet} />
|
||||||
|
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{rawWallet && extState === "wallet-dropped" && (
|
{rawWallet && extState === "wallet-dropped" && (
|
||||||
<>
|
<>
|
||||||
@ -2340,7 +2374,8 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRawWallet(null);
|
setRawWallet(null);
|
||||||
setExtstate("not-authenticated");
|
setExtstate("wallets");
|
||||||
|
logoutFunc();
|
||||||
}}
|
}}
|
||||||
src={Return}
|
src={Return}
|
||||||
/>
|
/>
|
||||||
@ -2361,9 +2396,11 @@ function App() {
|
|||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "flex-start",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Typography>{rawWallet?.name ? rawWallet?.name : rawWallet?.address0}</Typography>
|
||||||
|
<Spacer height="10px" />
|
||||||
<TextP
|
<TextP
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: "start",
|
textAlign: "start",
|
||||||
@ -2391,6 +2428,7 @@ function App() {
|
|||||||
authenticateWallet();
|
authenticateWallet();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
ref={passwordRef}
|
||||||
/>
|
/>
|
||||||
<Spacer height="20px" />
|
<Spacer height="20px" />
|
||||||
<CustomButton onClick={authenticateWallet}>
|
<CustomButton onClick={authenticateWallet}>
|
||||||
|
@ -251,16 +251,11 @@ export const NotAuthenticated = ({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginLeft: "28px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomButton {...getRootProps()}>
|
<CustomButton onClick={()=> setExtstate('wallets')}>
|
||||||
<input {...getInputProps()} />
|
Wallets
|
||||||
Authenticate
|
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
<Tooltip title="Authenticate by importing your Qortal JSON file" arrow>
|
|
||||||
<img src={Info} />
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Spacer height="6px" />
|
<Spacer height="6px" />
|
||||||
@ -269,7 +264,6 @@ export const NotAuthenticated = ({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginLeft: "28px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
@ -280,12 +274,7 @@ export const NotAuthenticated = ({
|
|||||||
Create account
|
Create account
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
|
|
||||||
<img
|
|
||||||
src={Info}
|
|
||||||
style={{
|
|
||||||
visibility: "hidden",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Spacer height="15px" />
|
<Spacer height="15px" />
|
||||||
|
|
||||||
|
482
src/Wallets.tsx
Normal file
482
src/Wallets.tsx
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import List from "@mui/material/List";
|
||||||
|
import ListItem from "@mui/material/ListItem";
|
||||||
|
import Divider from "@mui/material/Divider";
|
||||||
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
|
import ListItemAvatar from "@mui/material/ListItemAvatar";
|
||||||
|
import Avatar from "@mui/material/Avatar";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import { Box, Button, ButtonBase, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Input } from "@mui/material";
|
||||||
|
import { CustomButton } from "./App-styles";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
|
import { Label } from "./components/Group/AddGroup";
|
||||||
|
import { Spacer } from "./common/Spacer";
|
||||||
|
import { getWallets, storeWallets } from "./background";
|
||||||
|
import { useModal } from "./common/useModal";
|
||||||
|
import PhraseWallet from "./utils/generateWallet/phrase-wallet";
|
||||||
|
import { decryptStoredWalletFromSeedPhrase } from "./utils/decryptWallet";
|
||||||
|
import { crypto, walletVersion } from "./constants/decryptWallet";
|
||||||
|
import { LoadingButton } from "@mui/lab";
|
||||||
|
import { PasswordField } from "./components";
|
||||||
|
|
||||||
|
const parsefilenameQortal = (filename)=> {
|
||||||
|
return filename.startsWith("qortal_backup_") ? filename.slice(14) : filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => {
|
||||||
|
const [wallets, setWallets] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [seedValue, setSeedValue] = useState("");
|
||||||
|
const [seedName, setSeedName] = useState("");
|
||||||
|
const [seedError, setSeedError] = useState("");
|
||||||
|
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [isOpenSeedModal, setIsOpenSeedModal] = useState(false);
|
||||||
|
const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false);
|
||||||
|
|
||||||
|
const { isShow, onCancel, onOk, show, } = useModal();
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
|
accept: {
|
||||||
|
"application/json": [".json"], // Only accept JSON files
|
||||||
|
},
|
||||||
|
onDrop: async (acceptedFiles) => {
|
||||||
|
const files: any = acceptedFiles;
|
||||||
|
let importedWallets: any = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const fileContents = await new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onabort = () => reject("File reading was aborted");
|
||||||
|
reader.onerror = () => reject("File reading has failed");
|
||||||
|
reader.onload = () => {
|
||||||
|
// Resolve the promise with the reader result when reading completes
|
||||||
|
resolve(reader.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read the file as text
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
if (typeof fileContents !== "string") continue;
|
||||||
|
const parsedData = JSON.parse(fileContents)
|
||||||
|
importedWallets.push({...parsedData, filename: file?.name});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let error: any = null;
|
||||||
|
let uniqueInitialMap = new Map();
|
||||||
|
|
||||||
|
// Only add a message if it doesn't already exist in the Map
|
||||||
|
importedWallets.forEach((wallet) => {
|
||||||
|
if (!wallet?.address0) return;
|
||||||
|
if (!uniqueInitialMap.has(wallet?.address0)) {
|
||||||
|
uniqueInitialMap.set(wallet?.address0, wallet);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = Array.from(uniqueInitialMap.values());
|
||||||
|
if (data && data?.length > 0) {
|
||||||
|
const uniqueNewWallets = data.filter(
|
||||||
|
(newWallet) =>
|
||||||
|
!wallets.some(
|
||||||
|
(existingWallet) =>
|
||||||
|
existingWallet?.address0 === newWallet?.address0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setWallets([...wallets, ...uniqueNewWallets]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateWalletItem = (idx, wallet) => {
|
||||||
|
setWallets((prev) => {
|
||||||
|
let copyPrev = [...prev];
|
||||||
|
if (wallet === null) {
|
||||||
|
copyPrev.splice(idx, 1); // Use splice to remove the item
|
||||||
|
return copyPrev;
|
||||||
|
} else {
|
||||||
|
copyPrev[idx] = wallet; // Update the wallet at the specified index
|
||||||
|
return copyPrev;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetSeedValue = async ()=> {
|
||||||
|
try {
|
||||||
|
setIsOpenSeedModal(true)
|
||||||
|
const {seedValue, seedName, password} = await show({
|
||||||
|
message: "",
|
||||||
|
publishFee: "",
|
||||||
|
});
|
||||||
|
setIsLoadingEncryptSeed(true)
|
||||||
|
const res = await decryptStoredWalletFromSeedPhrase(seedValue)
|
||||||
|
const wallet2 = new PhraseWallet(res, walletVersion);
|
||||||
|
const wallet = await wallet2.generateSaveWalletData(
|
||||||
|
password,
|
||||||
|
crypto.kdfThreads,
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
if(wallet?.address0){
|
||||||
|
setWallets([...wallets, {
|
||||||
|
...wallet,
|
||||||
|
name: seedName
|
||||||
|
}]);
|
||||||
|
setIsOpenSeedModal(false)
|
||||||
|
setSeedValue('')
|
||||||
|
setSeedName('')
|
||||||
|
setPassword('')
|
||||||
|
setSeedError('')
|
||||||
|
} else {
|
||||||
|
setSeedError('Could not create wallet.')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setSeedError(error?.message || 'Could not create wallet.')
|
||||||
|
} finally {
|
||||||
|
setIsLoadingEncryptSeed(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedWalletFunc = (wallet) => {
|
||||||
|
setRawWallet(wallet);
|
||||||
|
setExtState("wallet-dropped");
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
setIsLoading(true)
|
||||||
|
getWallets().then((res)=> {
|
||||||
|
|
||||||
|
if(res && Array.isArray(res)){
|
||||||
|
setWallets(res)
|
||||||
|
}
|
||||||
|
setIsLoading(false)
|
||||||
|
}).catch((error)=> {
|
||||||
|
console.error(error)
|
||||||
|
setIsLoading(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
if(!isLoading && wallets && Array.isArray(wallets)){
|
||||||
|
storeWallets(wallets)
|
||||||
|
}
|
||||||
|
}, [wallets, isLoading])
|
||||||
|
|
||||||
|
if(isLoading) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{(wallets?.length === 0 ||
|
||||||
|
!wallets) ? (
|
||||||
|
<>
|
||||||
|
<Typography>No wallets saved</Typography>
|
||||||
|
<Spacer height="75px" />
|
||||||
|
</>
|
||||||
|
): (
|
||||||
|
<>
|
||||||
|
<Typography>Your saved wallets</Typography>
|
||||||
|
<Spacer height="30px" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rawWallet && (
|
||||||
|
<Box>
|
||||||
|
<Typography>Selected Wallet:</Typography>
|
||||||
|
{rawWallet?.name && <Typography>{rawWallet.name}</Typography>}
|
||||||
|
{rawWallet?.address0 && (
|
||||||
|
<Typography>{rawWallet?.address0}</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{wallets?.length > 0 && (
|
||||||
|
<List
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "500px",
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
maxHeight: "60vh",
|
||||||
|
overflow: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wallets?.map((wallet, idx) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WalletItem
|
||||||
|
setSelectedWallet={selectedWalletFunc}
|
||||||
|
key={wallet?.address0}
|
||||||
|
wallet={wallet}
|
||||||
|
idx={idx}
|
||||||
|
updateWalletItem={updateWalletItem}
|
||||||
|
/>
|
||||||
|
<Divider variant="inset" component="li" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
alignItems: "center",
|
||||||
|
position: wallets?.length === 0 ? 'relative' : 'fixed',
|
||||||
|
bottom: wallets?.length === 0 ? 'unset' : '20px',
|
||||||
|
right: wallets?.length === 0 ? 'unset' : '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomButton onClick={handleSetSeedValue} sx={{
|
||||||
|
padding: '10px'
|
||||||
|
}} >
|
||||||
|
|
||||||
|
Add seed-phrase
|
||||||
|
</CustomButton>
|
||||||
|
<CustomButton sx={{
|
||||||
|
padding: '10px'
|
||||||
|
}} {...getRootProps()}>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
Add wallets
|
||||||
|
</CustomButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={isOpenSeedModal}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && seedValue && seedName && password) {
|
||||||
|
onOk({seedValue, seedName, password});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
Type or paste in your seed-phrase
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>Name</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Name"
|
||||||
|
value={seedName}
|
||||||
|
onChange={(e) => setSeedName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Spacer height="7px" />
|
||||||
|
<Label>Seed-phrase</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Seed-phrase"
|
||||||
|
value={seedValue}
|
||||||
|
onChange={(e) => setSeedValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Spacer height="7px" />
|
||||||
|
|
||||||
|
<Label>Choose new password</Label>
|
||||||
|
<PasswordField
|
||||||
|
id="standard-adornment-password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button disabled={isLoadingEncryptSeed} variant="contained" onClick={()=> {
|
||||||
|
setIsOpenSeedModal(false)
|
||||||
|
setSeedValue('')
|
||||||
|
setSeedName('')
|
||||||
|
setPassword('')
|
||||||
|
setSeedError('')
|
||||||
|
}}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<LoadingButton
|
||||||
|
loading={isLoadingEncryptSeed}
|
||||||
|
disabled={!seedValue || !seedName || !password}
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
if(!seedValue || !seedName || !password) return
|
||||||
|
onOk({seedValue, seedName, password});
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</LoadingButton>
|
||||||
|
<Typography sx={{
|
||||||
|
fontSize: '14px',
|
||||||
|
visibility: seedError ? 'visible' : 'hidden'
|
||||||
|
}}>{seedError}</Typography>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [note, setNote] = useState("");
|
||||||
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (wallet?.name) {
|
||||||
|
setName(wallet.name);
|
||||||
|
}
|
||||||
|
if (wallet?.note) {
|
||||||
|
setNote(wallet.note);
|
||||||
|
}
|
||||||
|
}, [wallet]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ButtonBase
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedWallet(wallet);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItem
|
||||||
|
|
||||||
|
secondaryAction={
|
||||||
|
<IconButton
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsEdit(true);
|
||||||
|
}}
|
||||||
|
edge="end"
|
||||||
|
aria-label="edit"
|
||||||
|
>
|
||||||
|
<EditIcon
|
||||||
|
sx={{
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
alignItems="flex-start"
|
||||||
|
>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar alt="" src="/static/images/avatar/1.jpg" />
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={wallet?.name ? wallet.name : wallet?.filename ? parsefilenameQortal(wallet?.filename) : "No name"}
|
||||||
|
secondary={
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
variant="body2"
|
||||||
|
sx={{ color: "text.primary", display: "inline" }}
|
||||||
|
>
|
||||||
|
{wallet?.address0}
|
||||||
|
</Typography>
|
||||||
|
{wallet?.note}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</ButtonBase>
|
||||||
|
{isEdit && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label>Name</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Name"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Spacer height="10px" />
|
||||||
|
<Label>Note</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="Note"
|
||||||
|
value={note}
|
||||||
|
onChange={(e) => setNote(e.target.value)}
|
||||||
|
inputProps={{
|
||||||
|
maxLength: 100,
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Spacer height="10px" />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "20px",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button size="small" variant="contained" onClick={() => setIsEdit(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
backgroundColor: 'var(--danger)',
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "var(--danger)",
|
||||||
|
},
|
||||||
|
"&:focus": {
|
||||||
|
backgroundColor: "var(--danger)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => updateWalletItem(idx, null)}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#5EB049",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#5EB049",
|
||||||
|
},
|
||||||
|
"&:focus": {
|
||||||
|
backgroundColor: "#5EB049",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
onClick={() => {
|
||||||
|
updateWalletItem(idx, {
|
||||||
|
...wallet,
|
||||||
|
name,
|
||||||
|
note,
|
||||||
|
});
|
||||||
|
setIsEdit(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
50
src/assets/Icons/AppsIcon.tsx
Normal file
50
src/assets/Icons/AppsIcon.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const AppsIcon = ({ color, height = 31, width = 31 }) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
viewBox="0 0 30 30"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3.76596 7.53192C5.84584 7.53192 7.53192 5.84584 7.53192 3.76596C7.53192 1.68608 5.84584 0 3.76596 0C1.68608 0 0 1.68608 0 3.76596C0 5.84584 1.68608 7.53192 3.76596 7.53192Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15 7.53192C17.0799 7.53192 18.766 5.84584 18.766 3.76596C18.766 1.68608 17.0799 0 15 0C12.9201 0 11.2341 1.68608 11.2341 3.76596C11.2341 5.84584 12.9201 7.53192 15 7.53192Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M26.234 7.53192C28.3139 7.53192 30 5.84584 30 3.76596C30 1.68608 28.3139 0 26.234 0C24.1542 0 22.4681 1.68608 22.4681 3.76596C22.4681 5.84584 24.1542 7.53192 26.234 7.53192Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M3.76596 30.0001C5.84584 30.0001 7.53192 28.314 7.53192 26.2341C7.53192 24.1542 5.84584 22.4681 3.76596 22.4681C1.68608 22.4681 0 24.1542 0 26.2341C0 28.314 1.68608 30.0001 3.76596 30.0001Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15 30.0002C17.0799 30.0002 18.766 28.3141 18.766 26.2342C18.766 24.1543 17.0799 22.4683 15 22.4683C12.9201 22.4683 11.2341 24.1543 11.2341 26.2342C11.2341 28.3141 12.9201 30.0002 15 30.0002Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M26.234 30.0002C28.3139 30.0002 30 28.3141 30 26.2342C30 24.1543 28.3139 22.4683 26.234 22.4683C24.1542 22.4683 22.4681 24.1543 22.4681 26.2342C22.4681 28.3141 24.1542 30.0002 26.234 30.0002Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M3.76596 18.766C5.84584 18.766 7.53192 17.08 7.53192 15.0001C7.53192 12.9202 5.84584 11.2341 3.76596 11.2341C1.68608 11.2341 0 12.9202 0 15.0001C0 17.08 1.68608 18.766 3.76596 18.766Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M15 18.766C17.0799 18.766 18.766 17.08 18.766 15.0001C18.766 12.9202 17.0799 11.2341 15 11.2341C12.9201 11.2341 11.2341 12.9202 11.2341 15.0001C11.2341 17.08 12.9201 18.766 15 18.766Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M26.234 18.766C28.3139 18.766 30 17.08 30 15.0001C30 12.9202 28.3139 11.2341 26.234 11.2341C24.1542 11.2341 22.4681 12.9202 22.4681 15.0001C22.4681 17.08 24.1542 18.766 26.234 18.766Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
@ -33,6 +33,47 @@ import { TradeBotRespondMultipleRequest } from "./transactions/TradeBotRespondMu
|
|||||||
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from "./constants/resourceTypes";
|
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from "./constants/resourceTypes";
|
||||||
import TradeBotRespondRequest from './transactions/TradeBotRespondRequest';
|
import TradeBotRespondRequest from './transactions/TradeBotRespondRequest';
|
||||||
|
|
||||||
|
|
||||||
|
let inMemoryKey: CryptoKey | null = null;
|
||||||
|
let inMemoryIV: Uint8Array | null = null;
|
||||||
|
|
||||||
|
|
||||||
|
async function initializeKeyAndIV() {
|
||||||
|
if (!inMemoryKey) {
|
||||||
|
inMemoryKey = await generateKey(); // Generates the key in memory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateKey(): Promise<CryptoKey> {
|
||||||
|
return await crypto.subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
length: 256
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["encrypt", "decrypt"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function encryptData(data: string, key: CryptoKey): Promise<{ iv: Uint8Array; encryptedData: ArrayBuffer }> {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const encodedData = encoder.encode(data);
|
||||||
|
|
||||||
|
// Generate a random IV each time you encrypt
|
||||||
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||||
|
|
||||||
|
const encryptedData = await crypto.subtle.encrypt(
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
iv: iv
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
encodedData
|
||||||
|
);
|
||||||
|
|
||||||
|
return { iv, encryptedData };
|
||||||
|
}
|
||||||
|
|
||||||
export function cleanUrl(url) {
|
export function cleanUrl(url) {
|
||||||
return url?.replace(/^(https?:\/\/)?(www\.)?/, '');
|
return url?.replace(/^(https?:\/\/)?(www\.)?/, '');
|
||||||
}
|
}
|
||||||
@ -997,11 +1038,41 @@ async function getAddressInfo(address) {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
async function decryptData(encryptedData: ArrayBuffer, key: CryptoKey, iv: Uint8Array): Promise<string> {
|
||||||
|
const decryptedData = await crypto.subtle.decrypt(
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
iv: iv
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
encryptedData
|
||||||
|
);
|
||||||
|
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
return decoder.decode(decryptedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64ToJson(base64) {
|
||||||
|
const binary = atob(base64);
|
||||||
|
const bytes = new Uint8Array(binary.length);
|
||||||
|
for (let i = 0; i < binary.length; i++) {
|
||||||
|
bytes[i] = binary.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return JSON.parse(new TextDecoder().decode(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
export async function getKeyPair() {
|
export async function getKeyPair() {
|
||||||
const res = await chrome.storage.local.get(["keyPair"]);
|
const res = await chrome.storage.local.get(["keyPair"]);
|
||||||
if (res?.keyPair) {
|
if (res?.keyPair) {
|
||||||
return res.keyPair;
|
const combinedData = atob(res?.keyPair)
|
||||||
|
.split("")
|
||||||
|
.map((c) => c.charCodeAt(0));
|
||||||
|
|
||||||
|
const iv = new Uint8Array(combinedData.slice(0, 12)); // First 12 bytes are the IV
|
||||||
|
const encryptedData = new Uint8Array(combinedData.slice(12)).buffer;
|
||||||
|
|
||||||
|
const decryptedBase64Data = await decryptData(encryptedData, inMemoryKey, iv);
|
||||||
|
return decryptedBase64Data
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Wallet not authenticated");
|
throw new Error("Wallet not authenticated");
|
||||||
}
|
}
|
||||||
@ -1016,6 +1087,25 @@ export async function getSaveWallet() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getWallets() {
|
||||||
|
const res = await chrome.storage.local.get(["wallets"]);
|
||||||
|
if (res['wallets']) {
|
||||||
|
return res['wallets'];
|
||||||
|
} else {
|
||||||
|
throw new Error("No wallet saved");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function storeWallets(wallets) {
|
||||||
|
chrome.storage.local.set({ wallets: wallets }, () => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
reject(new Error(chrome.runtime.lastError.message));
|
||||||
|
} else {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function clearAllNotifications() {
|
async function clearAllNotifications() {
|
||||||
const notifications = await chrome.notifications.getAll();
|
const notifications = await chrome.notifications.getAll();
|
||||||
for (const notificationId of Object.keys(notifications)) {
|
for (const notificationId of Object.keys(notifications)) {
|
||||||
@ -1483,8 +1573,14 @@ async function decryptWallet({ password, wallet, walletVersion }) {
|
|||||||
rvnPrivateKey: wallet2._addresses[0].rvnWallet.derivedMasterPrivateKey
|
rvnPrivateKey: wallet2._addresses[0].rvnWallet.derivedMasterPrivateKey
|
||||||
};
|
};
|
||||||
const dataString = JSON.stringify(toSave);
|
const dataString = JSON.stringify(toSave);
|
||||||
|
await initializeKeyAndIV();
|
||||||
|
const { iv, encryptedData } = await encryptData(dataString, inMemoryKey);
|
||||||
|
|
||||||
|
// Combine IV and encrypted data into a single Uint8Array
|
||||||
|
const combinedData = new Uint8Array([...iv, ...new Uint8Array(encryptedData)]);
|
||||||
|
const encryptedBase64Data = btoa(String.fromCharCode(...combinedData));
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
chrome.storage.local.set({ keyPair: dataString }, () => {
|
chrome.storage.local.set({ keyPair: encryptedBase64Data }, () => {
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
reject(new Error(chrome.runtime.lastError.message));
|
reject(new Error(chrome.runtime.lastError.message));
|
||||||
} else {
|
} else {
|
||||||
@ -3209,14 +3305,22 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => {
|
|||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
sendResponse({ error: chrome.runtime.lastError.message });
|
sendResponse({ error: chrome.runtime.lastError.message });
|
||||||
} else if (result.walletInfo) {
|
} else if (result.walletInfo) {
|
||||||
sendResponse({ walletInfo: result.walletInfo });
|
sendResponse({ walletInfo: result.walletInfo, hasKeyPair: true });
|
||||||
} else {
|
} else {
|
||||||
sendResponse({ error: "No wallet info found" });
|
sendResponse({ error: "No wallet info found" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
sendResponse({ error: error.message });
|
chrome.storage.local.get(["walletInfo"], (result) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
sendResponse({ error: chrome.runtime.lastError.message });
|
||||||
|
} else if (result.walletInfo) {
|
||||||
|
sendResponse({ walletInfo: result.walletInfo, hasKeyPair: false });
|
||||||
|
} else {
|
||||||
|
sendResponse({ error: "Wallet not authenticated" });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getBaseApi } from "../background";
|
import { getBaseApi, getKeyPair } from "../background";
|
||||||
import { createSymmetricKeyAndNonce, decryptGroupData, encryptDataGroup, objectToBase64 } from "../qdn/encryption/group-encryption";
|
import { createSymmetricKeyAndNonce, decryptGroupData, encryptDataGroup, objectToBase64 } from "../qdn/encryption/group-encryption";
|
||||||
import { publishData } from "../qdn/publish/pubish";
|
import { publishData } from "../qdn/publish/pubish";
|
||||||
|
|
||||||
@ -55,14 +55,14 @@ export async function getNameInfo() {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getKeyPair() {
|
// async function getKeyPair() {
|
||||||
const res = await chrome.storage.local.get(["keyPair"]);
|
// const res = await chrome.storage.local.get(["keyPair"]);
|
||||||
if (res?.keyPair) {
|
// if (res?.keyPair) {
|
||||||
return res.keyPair;
|
// return res.keyPair;
|
||||||
} else {
|
// } else {
|
||||||
throw new Error("Wallet not authenticated");
|
// throw new Error("Wallet not authenticated");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
const getPublicKeys = async (groupNumber: number) => {
|
const getPublicKeys = async (groupNumber: number) => {
|
||||||
const validApi = await getBaseApi()
|
const validApi = await getBaseApi()
|
||||||
const response = await fetch(`${validApi}/groups/members/${groupNumber}?limit=0`);
|
const response = await fetch(`${validApi}/groups/members/${groupNumber}?limit=0`);
|
||||||
|
@ -4,7 +4,7 @@ import { Buffer } from "buffer"
|
|||||||
import Base58 from "../../deps/Base58"
|
import Base58 from "../../deps/Base58"
|
||||||
import nacl from "../../deps/nacl-fast"
|
import nacl from "../../deps/nacl-fast"
|
||||||
import utils from "../../utils/utils"
|
import utils from "../../utils/utils"
|
||||||
import { createEndpoint, getBaseApi } from "../../background";
|
import { createEndpoint, getBaseApi, getKeyPair } from "../../background";
|
||||||
|
|
||||||
export async function reusableGet(endpoint){
|
export async function reusableGet(endpoint){
|
||||||
const validApi = await getBaseApi();
|
const validApi = await getBaseApi();
|
||||||
@ -33,14 +33,14 @@ export async function reusableGet(endpoint){
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getKeyPair() {
|
// async function getKeyPair() {
|
||||||
const res = await chrome.storage.local.get(["keyPair"]);
|
// const res = await chrome.storage.local.get(["keyPair"]);
|
||||||
if (res?.keyPair) {
|
// if (res?.keyPair) {
|
||||||
return res.keyPair;
|
// return res.keyPair;
|
||||||
} else {
|
// } else {
|
||||||
throw new Error("Wallet not authenticated");
|
// throw new Error("Wallet not authenticated");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const publishData = async ({
|
export const publishData = async ({
|
||||||
registeredName,
|
registeredName,
|
||||||
|
@ -21,4 +21,13 @@ export const decryptStoredWallet = async (password, wallet) => {
|
|||||||
}
|
}
|
||||||
const decryptedBytes = AES_CBC.decrypt(encryptedSeedBytes, encryptionKey, false, iv)
|
const decryptedBytes = AES_CBC.decrypt(encryptedSeedBytes, encryptionKey, false, iv)
|
||||||
return decryptedBytes
|
return decryptedBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
export const decryptStoredWalletFromSeedPhrase = async (password) => {
|
||||||
|
const threads = doInitWorkers(crypto.kdfThreads)
|
||||||
|
const salt = new Uint8Array(void 0)
|
||||||
|
|
||||||
|
|
||||||
|
const seed = await kdf(password, salt, threads)
|
||||||
|
return seed
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user