Compare commits

..

No commits in common. "feature/initial-conversion" and "v0.5.4-pre" have entirely different histories.

61 changed files with 1001 additions and 4185 deletions

View File

@ -14,7 +14,6 @@ dependencies {
implementation project(':capacitor-filesystem') implementation project(':capacitor-filesystem')
implementation project(':capacitor-local-notifications') implementation project(':capacitor-local-notifications')
implementation project(':capacitor-preferences') implementation project(':capacitor-preferences')
implementation project(':capacitor-screen-orientation')
implementation project(':capacitor-splash-screen') implementation project(':capacitor-splash-screen')
implementation project(':capawesome-capacitor-file-picker') implementation project(':capawesome-capacitor-file-picker')
implementation project(':evva-capacitor-secure-storage-plugin') implementation project(':evva-capacitor-secure-storage-plugin')

View File

@ -13,7 +13,6 @@
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
<activity <activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:screenOrientation="unspecified"
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/title_activity_main" android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch" android:theme="@style/AppTheme.NoActionBarLaunch"

View File

@ -4,8 +4,6 @@ import com.getcapacitor.BridgeActivity;
import com.github.Qortal.qortalMobile.NativeBcrypt; import com.github.Qortal.qortalMobile.NativeBcrypt;
import com.github.Qortal.qortalMobile.NativePOW; import com.github.Qortal.qortalMobile.NativePOW;
import android.os.Bundle; import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
public class MainActivity extends BridgeActivity { public class MainActivity extends BridgeActivity {
@Override @Override
@ -14,9 +12,6 @@ public class MainActivity extends BridgeActivity {
registerPlugin(NativePOW.class); registerPlugin(NativePOW.class);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Enable mixed content mode for WebView
WebView webView = this.bridge.getWebView();
WebSettings webSettings = webView.getSettings();
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
} }
} }

View File

@ -17,9 +17,6 @@ project(':capacitor-local-notifications').projectDir = new File('../node_modules
include ':capacitor-preferences' include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android') project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
include ':capacitor-screen-orientation'
project(':capacitor-screen-orientation').projectDir = new File('../node_modules/@capacitor/screen-orientation/android')
include ':capacitor-splash-screen' include ':capacitor-splash-screen'
project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android') project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android')

View File

@ -19,7 +19,7 @@ const config: CapacitorConfig = {
"splashImmersive": true "splashImmersive": true
}, },
CapacitorHttp: { CapacitorHttp: {
enabled: false, enabled: true,
} }
} }
}; };

14
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "qortal-go", "name": "qortal-go",
"version": "0.5.3", "version": "0.5.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "qortal-go", "name": "qortal-go",
"version": "0.5.3", "version": "0.5.2",
"dependencies": { "dependencies": {
"@capacitor/android": "^6.1.2", "@capacitor/android": "^6.1.2",
"@capacitor/app": "^6.0.1", "@capacitor/app": "^6.0.1",
@ -16,7 +16,6 @@
"@capacitor/filesystem": "^6.0.1", "@capacitor/filesystem": "^6.0.1",
"@capacitor/local-notifications": "^6.1.0", "@capacitor/local-notifications": "^6.1.0",
"@capacitor/preferences": "^6.0.3", "@capacitor/preferences": "^6.0.3",
"@capacitor/screen-orientation": "^6.0.3",
"@capacitor/splash-screen": "^6.0.2", "@capacitor/splash-screen": "^6.0.2",
"@capawesome/capacitor-file-picker": "^6.1.0", "@capawesome/capacitor-file-picker": "^6.1.0",
"@chatscope/chat-ui-kit-react": "^2.0.3", "@chatscope/chat-ui-kit-react": "^2.0.3",
@ -1701,15 +1700,6 @@
"@capacitor/core": "^6.0.0" "@capacitor/core": "^6.0.0"
} }
}, },
"node_modules/@capacitor/screen-orientation": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@capacitor/screen-orientation/-/screen-orientation-6.0.3.tgz",
"integrity": "sha512-5R+tf+twRNnkZFGSWsQkEBz1MFyP1kzZDyqOA9rtXJlTQYNcFJWouSXEuNa+Ba6i6nEi4X83BuXVzEFJ7zDrgQ==",
"license": "MIT",
"peerDependencies": {
"@capacitor/core": "^6.0.0"
}
},
"node_modules/@capacitor/splash-screen": { "node_modules/@capacitor/splash-screen": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-6.0.2.tgz", "resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-6.0.2.tgz",

View File

@ -20,7 +20,6 @@
"@capacitor/filesystem": "^6.0.1", "@capacitor/filesystem": "^6.0.1",
"@capacitor/local-notifications": "^6.1.0", "@capacitor/local-notifications": "^6.1.0",
"@capacitor/preferences": "^6.0.3", "@capacitor/preferences": "^6.0.3",
"@capacitor/screen-orientation": "^6.0.3",
"@capacitor/splash-screen": "^6.0.2", "@capacitor/splash-screen": "^6.0.2",
"@capawesome/capacitor-file-picker": "^6.1.0", "@capawesome/capacitor-file-picker": "^6.1.0",
"@chatscope/chat-ui-kit-react": "^2.0.3", "@chatscope/chat-ui-kit-react": "^2.0.3",

View File

@ -21,18 +21,14 @@ import {
DialogContentText, DialogContentText,
DialogTitle, DialogTitle,
Divider, Divider,
FormControlLabel,
Input, Input,
InputLabel, InputLabel,
Popover, Popover,
Tooltip, Tooltip,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import { ScreenOrientation } from '@capacitor/screen-orientation';
import { decryptStoredWallet } from "./utils/decryptWallet"; import { decryptStoredWallet } from "./utils/decryptWallet";
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet';
import PriorityHighIcon from '@mui/icons-material/PriorityHigh';
import { JsonView, allExpanded, darkStyles } from 'react-json-view-lite'; import { JsonView, allExpanded, darkStyles } from 'react-json-view-lite';
import 'react-json-view-lite/dist/index.css'; import 'react-json-view-lite/dist/index.css';
@ -131,7 +127,6 @@ import {
isUsingImportExportSettingsAtom, isUsingImportExportSettingsAtom,
lastEnteredGroupIdAtom, lastEnteredGroupIdAtom,
mailsAtom, mailsAtom,
myGroupsWhereIAmAdminAtom,
oldPinnedAppsAtom, oldPinnedAppsAtom,
qMailLastEnteredTimestampAtom, qMailLastEnteredTimestampAtom,
settingsLocalLastUpdatedAtom, settingsLocalLastUpdatedAtom,
@ -158,7 +153,6 @@ import { BuyQortInformation } from "./components/BuyQortInformation";
import { InstallPWA } from "./components/InstallPWA"; import { InstallPWA } from "./components/InstallPWA";
import { QortPayment } from "./components/QortPayment"; import { QortPayment } from "./components/QortPayment";
import { PdfViewer } from "./common/PdfViewer"; import { PdfViewer } from "./common/PdfViewer";
import { DownloadWallet } from "./components/Auth/DownloadWallet";
type extStates = type extStates =
@ -445,7 +439,7 @@ function App() {
const { isShow, onCancel, onOk, show, message } = useModal(); const { isShow, onCancel, onOk, show, message } = useModal();
const {isUserBlocked, const {isUserBlocked,
addToBlockList, addToBlockList,
removeBlockFromList, getAllBlockedUsers} = useBlockedAddresses(extState === 'authenticated') removeBlockFromList, getAllBlockedUsers} = useBlockedAddresses()
const { const {
isShow: isShowUnsavedChanges, isShow: isShowUnsavedChanges,
onCancel: onCancelUnsavedChanges, onCancel: onCancelUnsavedChanges,
@ -492,8 +486,6 @@ function App() {
url: "http://127.0.0.1:12391", url: "http://127.0.0.1:12391",
}); });
const [useLocalNode, setUseLocalNode] = useState(false); const [useLocalNode, setUseLocalNode] = useState(false);
const [confirmRequestRead, setConfirmRequestRead] = useState(false);
const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [showSeed, setShowSeed] = useState(false) const [showSeed, setShowSeed] = useState(false)
const [creationStep, setCreationStep] = useState(1) const [creationStep, setCreationStep] = useState(1)
@ -520,15 +512,6 @@ function App() {
} }
} }
useEffect(() => {
try {
ScreenOrientation.lock({ orientation: 'portrait' });
} catch (error) {
console.error(error)
}
}, []);
useEffect(()=> { useEffect(()=> {
if(!shownTutorialsInitiated) return if(!shownTutorialsInitiated) return
if(extState === 'not-authenticated'){ if(extState === 'not-authenticated'){
@ -575,9 +558,6 @@ function App() {
const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(qMailLastEnteredTimestampAtom) const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(qMailLastEnteredTimestampAtom)
const resetAtomMailsAtom = useResetRecoilState(mailsAtom) const resetAtomMailsAtom = useResetRecoilState(mailsAtom)
const resetLastEnteredGroupIdAtom = useResetRecoilState(lastEnteredGroupIdAtom) const resetLastEnteredGroupIdAtom = useResetRecoilState(lastEnteredGroupIdAtom)
const resetMyGroupsWhereIAmAdminAtom = useResetRecoilState(
myGroupsWhereIAmAdminAtom
);
const resetAllRecoil = () => { const resetAllRecoil = () => {
resetAtomSortablePinnedAppsAtom(); resetAtomSortablePinnedAppsAtom();
resetAtomCanSaveSettingToQdnAtom(); resetAtomCanSaveSettingToQdnAtom();
@ -589,7 +569,6 @@ function App() {
resetAtomMailsAtom() resetAtomMailsAtom()
resetGroupPropertiesAtom() resetGroupPropertiesAtom()
resetLastEnteredGroupIdAtom() resetLastEnteredGroupIdAtom()
resetMyGroupsWhereIAmAdminAtom()
}; };
useEffect(() => { useEffect(() => {
if (!isMobile) return; if (!isMobile) return;
@ -871,24 +850,6 @@ function App() {
}); });
balanceSetInterval() balanceSetInterval()
}; };
const refetchUserInfo = () => {
window
.sendMessage('userInfo')
.then((response) => {
if (response && !response.error) {
setUserInfo(response);
}
})
.catch((error) => {
console.error('Failed to get user info:', error);
});
};
const getBalanceAndUserInfoFunc = () => {
getBalanceFunc();
refetchUserInfo();
};
const getLtcBalanceFunc = () => { const getLtcBalanceFunc = () => {
setLtcBalanceLoading(true); setLtcBalanceLoading(true);
window window
@ -917,8 +878,6 @@ function App() {
if(message?.payload?.checkbox1){ if(message?.payload?.checkbox1){
qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1?.value || false qortalRequestCheckbox1Ref.current = message?.payload?.checkbox1?.value || false
} }
setConfirmRequestRead(false)
await showQortalRequestExtension(message?.payload); await showQortalRequestExtension(message?.payload);
if (qortalRequestCheckbox1Ref.current) { if (qortalRequestCheckbox1Ref.current) {
event.source.postMessage( event.source.postMessage(
@ -1673,7 +1632,7 @@ function App() {
{balance?.toFixed(2)} QORT {balance?.toFixed(2)} QORT
</TextP> </TextP>
<RefreshIcon <RefreshIcon
onClick={getBalanceAndUserInfoFunc} onClick={getBalanceFunc}
sx={{ sx={{
fontSize: "16px", fontSize: "16px",
color: "white", color: "white",
@ -2619,14 +2578,87 @@ function App() {
)} )}
{extState === "download-wallet" && ( {extState === "download-wallet" && (
<> <>
<DownloadWallet <Spacer height="22px" />
returnToMain={returnToMain} <Box
setIsLoading={setIsLoading} sx={{
showInfo={showInfo} display: "flex",
rawWallet={rawWallet} width: "100%",
setWalletToBeDownloaded={setWalletToBeDownloaded} justifyContent: "flex-start",
walletToBeDownloaded={walletToBeDownloaded} paddingLeft: "22px",
/> boxSizing: "border-box",
}}
>
<img
style={{
cursor: "pointer",
}}
onClick={returnToMain}
src={Return}
/>
</Box>
<Spacer height="10px" />
<div
className="image-container"
style={{
width: "136px",
height: "154px",
}}
>
<img src={Logo1Dark} className="base-image" />
</div>
<Spacer height="35px" />
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
}}
>
<TextP
sx={{
textAlign: "start",
lineHeight: "24px",
fontSize: "20px",
fontWeight: 600,
}}
>
Download Account
</TextP>
</Box>
<Spacer height="35px" />
{!walletToBeDownloaded && (
<>
<CustomLabel htmlFor="standard-adornment-password">
Confirm Wallet Password
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={walletToBeDownloadedPassword}
onChange={(e) =>
setWalletToBeDownloadedPassword(e.target.value)
}
/>
<Spacer height="20px" />
<CustomButton onClick={confirmPasswordToDownload}>
Confirm password
</CustomButton>
<ErrorText>{walletToBeDownloadedError}</ErrorText>
</>
)}
{walletToBeDownloaded && (
<>
<CustomButton onClick={async ()=> {
await saveFileToDiskFunc()
await showInfo({
message: isNative ? `Your account file was saved to internal storage, in the document folder. Keep that file secure.` : `Your account file was downloaded by your browser. Keep that file secure.` ,
})
}}>
Download account
</CustomButton>
</>
)}
</> </>
)} )}
{extState === "create-wallet" && ( {extState === "create-wallet" && (
@ -3078,7 +3110,7 @@ function App() {
> >
<CountdownCircleTimer <CountdownCircleTimer
isPlaying isPlaying
duration={60} duration={30}
colors={["#004777", "#F7B801", "#A30000", "#A30000"]} colors={["#004777", "#F7B801", "#A30000", "#A30000"]}
colorsTime={[7, 5, 2, 0]} colorsTime={[7, 5, 2, 0]}
onComplete={() => { onComplete={() => {
@ -3159,14 +3191,12 @@ function App() {
> >
{messageQortalRequestExtension?.text3} {messageQortalRequestExtension?.text3}
</TextP> </TextP>
<Spacer height="15px" />
</Box> </Box>
<Spacer height="15px" />
</> </>
)} )}
{messageQortalRequestExtension?.text4 && ( {messageQortalRequestExtension?.text4 && (
<>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -3184,8 +3214,6 @@ function App() {
{messageQortalRequestExtension?.text4} {messageQortalRequestExtension?.text4}
</TextP> </TextP>
</Box> </Box>
<Spacer height="15px" />
</>
)} )}
{messageQortalRequestExtension?.html && ( {messageQortalRequestExtension?.html && (
@ -3313,35 +3341,6 @@ function App() {
</Typography> </Typography>
</Box> </Box>
)} )}
{messageQortalRequestExtension?.confirmCheckbox && (
<FormControlLabel
control={
<Checkbox
onChange={(e) => setConfirmRequestRead(e.target.checked)}
checked={confirmRequestRead}
edge="start"
tabIndex={-1}
disableRipple
sx={{
"&.Mui-checked": {
color: "white",
},
"& .MuiSvgIcon-root": {
color: "white",
},
}}
/>
}
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography sx={{ fontSize: "14px" }}>
I have read this request
</Typography>
<PriorityHighIcon color="warning" />
</Box>
}
/>
)}
<Spacer height="29px" /> <Spacer height="29px" />
<Box <Box
@ -3351,21 +3350,13 @@ function App() {
gap: "14px", gap: "14px",
}} }}
> >
<CustomButtonAccept <CustomButtonAccept
color="black" color="black"
bgColor="var(--green)" bgColor="var(--green)"
sx={{ sx={{
minWidth: "102px", minWidth: "102px",
opacity: messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead ? 0.1 : 0.7,
cursor: messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead ? 'default' : 'pointer',
"&:hover": {
opacity: messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead ? 0.1 : 1,
}
}}
onClick={() => {
if(messageQortalRequestExtension?.confirmCheckbox && !confirmRequestRead) return
onOkQortalRequestExtension("accepted")
}} }}
onClick={() => onOkQortalRequestExtension("accepted")}
> >
accept accept
</CustomButtonAccept> </CustomButtonAccept>

View File

@ -41,14 +41,6 @@ export const sortablePinnedAppsAtom = atom({
{ {
name: 'Q-Wallets', name: 'Q-Wallets',
service: 'APP' service: 'APP'
},
{
name: 'Q-Search',
service: 'APP'
},
{
name: 'Q-Nodecontrol',
service: 'APP'
} }
], ],
}); });
@ -188,8 +180,3 @@ export const lastPaymentSeenTimestampAtom = atom<null | number>({
key: 'lastPaymentSeenTimestampAtom', key: 'lastPaymentSeenTimestampAtom',
default: null, default: null,
}); });
export const isOpenBlockedModalAtom = atom({
key: 'isOpenBlockedModalAtom',
default: false,
});

View File

@ -358,12 +358,11 @@ export async function sendCoinCase(request, event) {
export async function inviteToGroupCase(request, event) { export async function inviteToGroupCase(request, event) {
try { try {
const { groupId, qortalAddress, inviteTime, txGroupId = 0 } = request.payload; const { groupId, qortalAddress, inviteTime } = request.payload;
const response = await inviteToGroup({ const response = await inviteToGroup({
groupId, groupId,
qortalAddress, qortalAddress,
inviteTime, inviteTime,
txGroupId
}); });
event.source.postMessage( event.source.postMessage(
@ -484,8 +483,8 @@ export async function createGroupCase(request, event) {
export async function cancelInvitationToGroupCase(request, event) { export async function cancelInvitationToGroupCase(request, event) {
try { try {
const { groupId, qortalAddress,txGroupId = 0 } = request.payload; const { groupId, qortalAddress } = request.payload;
const response = await cancelInvitationToGroup({ groupId, qortalAddress, txGroupId }); const response = await cancelInvitationToGroup({ groupId, qortalAddress });
event.source.postMessage( event.source.postMessage(
{ {
@ -565,12 +564,11 @@ export async function joinGroupCase(request, event) {
export async function kickFromGroupCase(request, event) { export async function kickFromGroupCase(request, event) {
try { try {
const { groupId, qortalAddress, rBanReason, txGroupId = 0 } = request.payload; const { groupId, qortalAddress, rBanReason } = request.payload;
const response = await kickFromGroup({ const response = await kickFromGroup({
groupId, groupId,
qortalAddress, qortalAddress,
rBanReason, rBanReason,
txGroupId
}); });
event.source.postMessage( event.source.postMessage(
@ -597,13 +595,12 @@ export async function kickFromGroupCase(request, event) {
export async function banFromGroupCase(request, event) { export async function banFromGroupCase(request, event) {
try { try {
const { groupId, qortalAddress, rBanReason, rBanTime, txGroupId = 0 } = request.payload; const { groupId, qortalAddress, rBanReason, rBanTime } = request.payload;
const response = await banFromGroup({ const response = await banFromGroup({
groupId, groupId,
qortalAddress, qortalAddress,
rBanReason, rBanReason,
rBanTime, rBanTime,
txGroupId
}); });
event.source.postMessage( event.source.postMessage(
@ -737,8 +734,8 @@ export async function getUserSettingsCase(request, event) {
export async function cancelBanCase(request, event) { export async function cancelBanCase(request, event) {
try { try {
const { groupId, qortalAddress, txGroupId = 0 } = request.payload; const { groupId, qortalAddress } = request.payload;
const response = await cancelBan({ groupId, qortalAddress, txGroupId }); const response = await cancelBan({ groupId, qortalAddress });
event.source.postMessage( event.source.postMessage(
{ {
@ -791,8 +788,8 @@ export async function registerNameCase(request, event) {
export async function makeAdminCase(request, event) { export async function makeAdminCase(request, event) {
try { try {
const { groupId, qortalAddress,txGroupId = 0 } = request.payload; const { groupId, qortalAddress } = request.payload;
const response = await makeAdmin({ groupId, qortalAddress, txGroupId }); const response = await makeAdmin({ groupId, qortalAddress });
event.source.postMessage( event.source.postMessage(
{ {
@ -818,8 +815,8 @@ export async function makeAdminCase(request, event) {
export async function removeAdminCase(request, event) { export async function removeAdminCase(request, event) {
try { try {
const { groupId, qortalAddress, txGroupId = 0 } = request.payload; const { groupId, qortalAddress } = request.payload;
const response = await removeAdmin({ groupId, qortalAddress, txGroupId }); const response = await removeAdmin({ groupId, qortalAddress });
event.source.postMessage( event.source.postMessage(
{ {
@ -1332,7 +1329,6 @@ export async function publishOnQDNCase(request, event) {
try { try {
const { const {
data, data,
name = "",
identifier, identifier,
service, service,
title, title,
@ -1350,7 +1346,6 @@ export async function publishOnQDNCase(request, event) {
identifier, identifier,
service, service,
title, title,
name,
description, description,
category, category,
tag1, tag1,

View File

@ -795,35 +795,33 @@ export async function getNameInfo() {
const wallet = await getSaveWallet(); const wallet = await getSaveWallet();
const address = wallet.address0; const address = wallet.address0;
const validApi = await getBaseApi(); const validApi = await getBaseApi();
const response = await fetch(validApi + '/names/primary/' + address); const response = await fetch(validApi + "/names/address/" + address);
const nameData = await response.json(); const nameData = await response.json();
if (nameData?.name) { if (nameData?.length > 0) {
return nameData.name; return nameData[0].name;
} else { } else {
return ''; return "";
} }
} }
export async function getNameInfoForOthers(address) { export async function getNameInfoForOthers(address) {
if (!address) return '';
const validApi = await getBaseApi(); const validApi = await getBaseApi();
const response = await fetch(validApi + '/names/primary/' + address); const response = await fetch(validApi + "/names/address/" + address);
const nameData = await response.json(); const nameData = await response.json();
if (nameData?.name) { if (nameData?.length > 0) {
return nameData?.name; return nameData[0].name;
} else { } else {
return ''; return "";
} }
} }
export async function getAddressInfo(address) { export async function getAddressInfo(address) {
const validApi = await getBaseApi(); const validApi = await getBaseApi();
const response = await fetch(validApi + "/addresses/" + address); const response = await fetch(validApi + "/addresses/" + address);
const data = await response.json(); const data = await response.json();
if (!response?.ok && data?.error !== 124) if (!response?.ok && data?.error !== 124)
throw new Error("Cannot retrieve address info"); throw new Error("Cannot fetch address info");
if (data?.error === 124) { if (data?.error === 124) {
return { return {
address, address,
@ -930,59 +928,6 @@ export async function getBalanceInfo() {
const data = await response.json(); const data = await response.json();
return data; return data;
} }
export async function getAssetBalanceInfo(assetId: number) {
const wallet = await getSaveWallet();
const address = wallet.address0;
const validApi = await getBaseApi();
const response = await fetch(validApi + `/assets/balances?address=${address}&assetid=${assetId}&ordering=ASSET_BALANCE_ACCOUNT&limit=1`);
if (!response?.ok) throw new Error("Cannot fetch asset balance");
const data = await response.json();
return +data?.[0]?.balance
}
export async function getAssetInfo(assetId: number) {
const validApi = await getBaseApi();
const response = await fetch(validApi + `/assets/info?assetId=${assetId}`);
if (!response?.ok) throw new Error("Cannot fetch asset info");
const data = await response.json();
return data
}
export async function transferAsset({
amount,
recipient,
assetId,
}) {
const lastReference = await getLastRef();
const resKeyPair = await getKeyPair();
const parsedData = resKeyPair;
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
const feeres = await getFee("TRANSFER_ASSET");
const tx = await createTransaction(12, keyPair, {
fee: feeres.fee,
recipient: recipient,
amount: amount,
assetId: assetId,
lastReference: lastReference,
});
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
if (!res?.signature)
throw new Error(res?.message || "Transaction was not able to be processed");
return res;
}
export async function getLTCBalance() { export async function getLTCBalance() {
const wallet = await getSaveWallet(); const wallet = await getSaveWallet();
let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`; let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`;
@ -2034,7 +1979,7 @@ export async function joinGroup({ groupId }) {
return res; return res;
} }
export async function cancelInvitationToGroup({ groupId, qortalAddress, txGroupId = 0 }) { export async function cancelInvitationToGroup({ groupId, qortalAddress }) {
const lastReference = await getLastRef(); const lastReference = await getLastRef();
const resKeyPair = await getKeyPair(); const resKeyPair = await getKeyPair();
const parsedData = resKeyPair; const parsedData = resKeyPair;
@ -2051,7 +1996,6 @@ export async function cancelInvitationToGroup({ groupId, qortalAddress, txGroupI
recipient: qortalAddress, recipient: qortalAddress,
rGroupId: groupId, rGroupId: groupId,
lastReference: lastReference, lastReference: lastReference,
groupID: txGroupId
}); });
const signedBytes = Base58.encode(tx.signedBytes); const signedBytes = Base58.encode(tx.signedBytes);
@ -2062,7 +2006,7 @@ export async function cancelInvitationToGroup({ groupId, qortalAddress, txGroupI
return res; return res;
} }
export async function cancelBan({ groupId, qortalAddress, txGroupId = 0 }) { export async function cancelBan({ groupId, qortalAddress }) {
const lastReference = await getLastRef(); const lastReference = await getLastRef();
const resKeyPair = await getKeyPair(); const resKeyPair = await getKeyPair();
const parsedData = resKeyPair; const parsedData = resKeyPair;
@ -2079,7 +2023,6 @@ export async function cancelBan({ groupId, qortalAddress, txGroupId = 0 }) {
recipient: qortalAddress, recipient: qortalAddress,
rGroupId: groupId, rGroupId: groupId,
lastReference: lastReference, lastReference: lastReference,
groupID: txGroupId
}); });
const signedBytes = Base58.encode(tx.signedBytes); const signedBytes = Base58.encode(tx.signedBytes);
@ -2144,7 +2087,7 @@ export async function updateName({ newName, oldName, description }) {
return res; return res;
} }
export async function makeAdmin({ groupId, qortalAddress, txGroupId = 0 }) { export async function makeAdmin({ groupId, qortalAddress }) {
const lastReference = await getLastRef(); const lastReference = await getLastRef();
const resKeyPair = await getKeyPair(); const resKeyPair = await getKeyPair();
const parsedData = resKeyPair; const parsedData = resKeyPair;
@ -2161,7 +2104,6 @@ export async function makeAdmin({ groupId, qortalAddress, txGroupId = 0 }) {
recipient: qortalAddress, recipient: qortalAddress,
rGroupId: groupId, rGroupId: groupId,
lastReference: lastReference, lastReference: lastReference,
groupID: txGroupId
}); });
const signedBytes = Base58.encode(tx.signedBytes); const signedBytes = Base58.encode(tx.signedBytes);
@ -2172,7 +2114,7 @@ export async function makeAdmin({ groupId, qortalAddress, txGroupId = 0 }) {
return res; return res;
} }
export async function removeAdmin({ groupId, qortalAddress, txGroupId = 0 }) { export async function removeAdmin({ groupId, qortalAddress }) {
const lastReference = await getLastRef(); const lastReference = await getLastRef();
const resKeyPair = await getKeyPair(); const resKeyPair = await getKeyPair();
const parsedData = resKeyPair; const parsedData = resKeyPair;
@ -2189,7 +2131,6 @@ export async function removeAdmin({ groupId, qortalAddress, txGroupId = 0 }) {
recipient: qortalAddress, recipient: qortalAddress,
rGroupId: groupId, rGroupId: groupId,
lastReference: lastReference, lastReference: lastReference,
groupID: txGroupId
}); });
const signedBytes = Base58.encode(tx.signedBytes); const signedBytes = Base58.encode(tx.signedBytes);
@ -2205,7 +2146,6 @@ export async function banFromGroup({
qortalAddress, qortalAddress,
rBanReason = "", rBanReason = "",
rBanTime, rBanTime,
txGroupId = 0
}) { }) {
const lastReference = await getLastRef(); const lastReference = await getLastRef();
const resKeyPair = await getKeyPair(); const resKeyPair = await getKeyPair();
@ -2225,7 +2165,6 @@ export async function banFromGroup({
rBanReason: rBanReason, rBanReason: rBanReason,
rBanTime, rBanTime,
lastReference: lastReference, lastReference: lastReference,
groupID: txGroupId
}); });
const signedBytes = Base58.encode(tx.signedBytes); const signedBytes = Base58.encode(tx.signedBytes);
@ -2240,7 +2179,6 @@ export async function kickFromGroup({
groupId, groupId,
qortalAddress, qortalAddress,
rBanReason = "", rBanReason = "",
txGroupId = 0
}) { }) {
const lastReference = await getLastRef(); const lastReference = await getLastRef();
const resKeyPair = await getKeyPair(); const resKeyPair = await getKeyPair();
@ -2259,7 +2197,6 @@ export async function kickFromGroup({
rGroupId: groupId, rGroupId: groupId,
rBanReason: rBanReason, rBanReason: rBanReason,
lastReference: lastReference, lastReference: lastReference,
groupID: txGroupId
}); });
const signedBytes = Base58.encode(tx.signedBytes); const signedBytes = Base58.encode(tx.signedBytes);
@ -2310,153 +2247,7 @@ export async function createGroup({
if (!res?.signature) throw new Error(res?.message || "Transaction was not able to be processed"); if (!res?.signature) throw new Error(res?.message || "Transaction was not able to be processed");
return res; return res;
} }
export async function inviteToGroup({ groupId, qortalAddress, inviteTime }) {
export async function sellName({
name,
sellPrice
}) {
const wallet = await getSaveWallet();
const address = wallet.address0;
if (!address) throw new Error("Cannot find user");
const lastReference = await getLastRef();
const feeres = await getFee("SELL_NAME");
const resKeyPair = await getKeyPair();
const parsedData = resKeyPair;
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
const tx = await createTransaction(5, keyPair, {
fee: feeres.fee,
name,
sellPrice: sellPrice,
lastReference: lastReference,
});
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
if (!res?.signature)
throw new Error(res?.message || "Transaction was not able to be processed");
return res;
}
export async function cancelSellName({
name
}) {
const wallet = await getSaveWallet();
const address = wallet.address0;
if (!address) throw new Error("Cannot find user");
const lastReference = await getLastRef();
const feeres = await getFee("SELL_NAME");
const resKeyPair = await getKeyPair();
const parsedData = resKeyPair;
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
const tx = await createTransaction(6, keyPair, {
fee: feeres.fee,
name,
lastReference: lastReference,
});
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
if (!res?.signature)
throw new Error(res?.message || "Transaction was not able to be processed");
return res;
}
export async function buyName({
name,
sellerAddress,
sellPrice
}) {
const wallet = await getSaveWallet();
const address = wallet.address0;
if (!address) throw new Error("Cannot find user");
const lastReference = await getLastRef();
const feeres = await getFee("BUY_NAME");
const resKeyPair = await getKeyPair();
const parsedData = resKeyPair;
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
const tx = await createTransaction(7, keyPair, {
fee: feeres.fee,
name,
sellPrice,
recipient: sellerAddress,
lastReference: lastReference,
});
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
if (!res?.signature)
throw new Error(res?.message || "Transaction was not able to be processed");
return res;
}
export async function updateGroup({
groupId,
newOwner,
newIsOpen,
newDescription,
newApprovalThreshold,
newMinimumBlockDelay,
newMaximumBlockDelay,
txGroupId = 0
}) {
const wallet = await getSaveWallet();
const address = wallet.address0;
if (!address) throw new Error("Cannot find user");
const lastReference = await getLastRef();
const feeres = await getFee("UPDATE_GROUP");
const resKeyPair = await getKeyPair();
const parsedData = resKeyPair;
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
const tx = await createTransaction(23, keyPair, {
fee: feeres.fee,
_groupId: groupId,
newOwner,
newIsOpen,
newDescription,
newApprovalThreshold,
newMinimumBlockDelay,
newMaximumBlockDelay,
lastReference: lastReference,
groupID: txGroupId
});
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
if (!res?.signature)
throw new Error(res?.message || "Transaction was not able to be processed");
return res;
}
export async function inviteToGroup({ groupId, qortalAddress, inviteTime, txGroupId = 0 }) {
const address = await getNameOrAddress(qortalAddress); const address = await getNameOrAddress(qortalAddress);
if (!address) throw new Error("Cannot find user"); if (!address) throw new Error("Cannot find user");
const lastReference = await getLastRef(); const lastReference = await getLastRef();
@ -2476,14 +2267,13 @@ export async function inviteToGroup({ groupId, qortalAddress, inviteTime, txGrou
rGroupId: groupId, rGroupId: groupId,
rInviteTime: inviteTime, rInviteTime: inviteTime,
lastReference: lastReference, lastReference: lastReference,
groupID: txGroupId
}); });
const signedBytes = Base58.encode(tx.signedBytes); const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes); const res = await processTransactionVersion2(signedBytes);
if (!res?.signature) if (!res?.signature)
throw new Error(res?.message || "Transaction was not able to be processed"); throw new Error("Transaction was not able to be processed");
return res; return res;
} }
@ -3198,7 +2988,6 @@ function setupMessageListener() {
break; break;
case "updateThreadActivity": case "updateThreadActivity":
updateThreadActivityCase(request, event); updateThreadActivityCase(request, event);
break;
case "decryptGroupEncryption": case "decryptGroupEncryption":
decryptGroupEncryptionCase(request, event); decryptGroupEncryptionCase(request, event);
break; break;

View File

@ -47,29 +47,18 @@ async function getSaveWallet() {
throw new Error("No wallet saved"); throw new Error("No wallet saved");
} }
} }
export async function getNameInfo() { export async function getNameInfo() {
const wallet = await getSaveWallet(); const wallet = await getSaveWallet();
const address = wallet.address0; const address = wallet.address0;
const validApi = await getBaseApi(); const validApi = await getBaseApi()
const response = await fetch(validApi + '/names/primary/' + address); const response = await fetch(validApi + "/names/address/" + address);
const nameData = await response.json(); const nameData = await response.json();
if (nameData?.name) { if (nameData?.length > 0) {
return nameData?.name; return nameData[0].name;
} else { } else {
return ''; return "";
} }
} }
export async function getAllUserNames() {
const wallet = await getSaveWallet();
const address = wallet.address0;
const validApi = await getBaseApi();
const response = await fetch(validApi + '/names/address/' + address);
const nameData = await response.json();
return nameData.map((item) => item.name);
}
async function getKeyPair() { async function getKeyPair() {
const res = await getData<any>("keyPair").catch(() => null); const res = await getData<any>("keyPair").catch(() => null);
if (res) { if (res) {
@ -162,7 +151,7 @@ async function getKeyPair() {
if(encryptedData){ if(encryptedData){
const registeredName = await getNameInfo() const registeredName = await getNameInfo()
const data = await publishData({ const data = await publishData({
registeredName, data: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, uploadType: 'base64', withFee: true registeredName, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true
}) })
return { return {
data, data,
@ -213,7 +202,7 @@ export const encryptAndPublishSymmetricKeyGroupChat = async ({groupId, previousD
if(encryptedData){ if(encryptedData){
const registeredName = await getNameInfo() const registeredName = await getNameInfo()
const data = await publishData({ const data = await publishData({
registeredName, data: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, uploadType: 'base64', withFee: true registeredName, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true
}) })
return { return {
data, data,
@ -234,7 +223,7 @@ export const publishGroupEncryptedResource = async ({encryptedData, identifier})
const registeredName = await getNameInfo() const registeredName = await getNameInfo()
if(!registeredName) throw new Error('You need a name to publish') if(!registeredName) throw new Error('You need a name to publish')
const data = await publishData({ const data = await publishData({
registeredName, data: encryptedData, service: 'DOCUMENT', identifier, uploadType: 'base64', withFee: true registeredName, file: encryptedData, service: 'DOCUMENT', identifier, uploadType: 'file', isBase64: true, withFee: true
}) })
return data return data
@ -253,16 +242,15 @@ export const publishOnQDN = async ({data, identifier, service, title,
tag3, tag3,
tag4, tag4,
tag5, tag5,
name,
uploadType = 'file' uploadType = 'file'
}) => { }) => {
if(data && service){ if(data && service){
const registeredName = name || await getNameInfo() const registeredName = await getNameInfo()
if(!registeredName) throw new Error('You need a name to publish') if(!registeredName) throw new Error('You need a name to publish')
const res = await publishData({ const res = await publishData({
registeredName, data, service, identifier, uploadType, withFee: true, title, registeredName, file: data, service, identifier, uploadType, isBase64: true, withFee: true, title,
description, description,
category, category,
tag1, tag1,

View File

@ -130,17 +130,12 @@ export const BoundedNumericTextField = ({
...props?.InputProps, ...props?.InputProps,
endAdornment: addIconButtons ? ( endAdornment: addIconButtons ? (
<InputAdornment position="end"> <InputAdornment position="end">
<IconButton size="small" onClick={() => <IconButton size="small" onClick={() => changeValueWithIncDecButton(1)}>
changeValueWithIncDecButton(1)
} onTouchStart={(e)=> e.stopPropagation()}>
<AddIcon sx={{ <AddIcon sx={{
color: 'white' color: 'white'
}} />{" "} }} />{" "}
</IconButton> </IconButton>
<IconButton onTouchStart={(e)=> e.stopPropagation()} size="small" onClick={() => <IconButton size="small" onClick={() => changeValueWithIncDecButton(-1)}>
changeValueWithIncDecButton(-1)
}>
<RemoveIcon sx={{ <RemoveIcon sx={{
color: 'white' color: 'white'
}} />{" "} }} />{" "}

View File

@ -48,7 +48,7 @@ export const useModal = () => {
const onCancel = () => { const onCancel = () => {
const { reject } = promiseConfig.current; const { reject } = promiseConfig.current;
hide(); hide();
reject('Declined'); reject();
setMessage({ setMessage({
publishFee: "", publishFee: "",
message: "" message: ""

View File

@ -1,4 +1,4 @@
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; import React, { useContext, useEffect, useMemo, useState } from "react";
import { import {
AppCircle, AppCircle,
AppCircleContainer, AppCircleContainer,
@ -49,7 +49,6 @@ import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
import { CustomizedSnackbars } from "../Snackbar/Snackbar"; import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { getFee } from "../../background"; import { getFee } from "../../background";
import { fileToBase64 } from "../../utils/fileReading"; import { fileToBase64 } from "../../utils/fileReading";
import { useSortedMyNames } from "../../hooks/useSortedMyNames";
const CustomSelect = styled(Select)({ const CustomSelect = styled(Select)({
border: "0.5px solid var(--50-white, #FFFFFF80)", border: "0.5px solid var(--50-white, #FFFFFF80)",
@ -83,8 +82,7 @@ const CustomMenuItem = styled(MenuItem)({
}, },
}); });
export const AppPublish = ({ categories, myAddress, myName }) => { export const AppPublish = ({ names, categories }) => {
const [names, setNames] = useState([]);
const [name, setName] = useState(""); const [name, setName] = useState("");
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
@ -101,8 +99,6 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null); const [infoSnack, setInfoSnack] = useState(null);
const [isLoading, setIsLoading] = useState(""); const [isLoading, setIsLoading] = useState("");
const mySortedNames = useSortedMyNames(names, myName);
const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
accept: { accept: {
@ -166,25 +162,6 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
getQapp(name, appType); getQapp(name, appType);
}, [name, appType]); }, [name, appType]);
const getNames = useCallback(async () => {
if (!myAddress) return;
try {
setIsLoading('Loading names');
const res = await fetch(
`${getBaseApiReact()}/names/address/${myAddress}?limit=0`
);
const data = await res.json();
setNames(data?.map((item) => item.name));
} catch (error) {
console.error(error);
} finally {
setIsLoading('');
}
}, [myAddress]);
useEffect(() => {
getNames();
}, [getNames]);
const publishApp = async () => { const publishApp = async () => {
try { try {
const data = { const data = {
@ -222,10 +199,10 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
publishFee: fee.fee + " QORT", publishFee: fee.fee + " QORT",
}); });
setIsLoading("Publishing... Please wait."); setIsLoading("Publishing... Please wait.");
const fileBase64 = await fileToBase64(file);
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("publishOnQDN", { window.sendMessage("publishOnQDN", {
data: file, data: fileBase64,
service: appType, service: appType,
title, title,
description, description,
@ -236,7 +213,6 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
tag4, tag4,
tag5, tag5,
uploadType: "zip", uploadType: "zip",
name
}) })
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
@ -311,7 +287,7 @@ export const AppPublish = ({ categories, myAddress, myName }) => {
</em>{" "} </em>{" "}
{/* This is the placeholder item */} {/* This is the placeholder item */}
</CustomMenuItem> </CustomMenuItem>
{mySortedNames.map((name) => { {names.map((name) => {
return <CustomMenuItem value={name}>{name}</CustomMenuItem>; return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
})} })}
</CustomSelect> </CustomSelect>

View File

@ -1,4 +1,4 @@
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; import React, { useContext, useEffect, useMemo, useState } from "react";
import { Avatar, Box, } from "@mui/material"; import { Avatar, Box, } from "@mui/material";
import { Add } from "@mui/icons-material"; import { Add } from "@mui/icons-material";
@ -100,57 +100,6 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode, skipAuth}, i
}; };
}, [app, path]); }, [app, path]);
const receiveChunksFunc = useCallback(
(e) => {
const iframe = iframeRef?.current;
if (!iframe || !iframe?.src) return;
if (app?.tabId !== e.detail?.tabId) return;
const publishLocation = e.detail?.publishLocation;
const chunksSubmitted = e.detail?.chunksSubmitted;
const totalChunks = e.detail?.totalChunks;
const retry = e.detail?.retry;
const filename = e.detail?.filename;
try {
if (publishLocation === undefined || publishLocation === null) return;
const dataToBeSent = {};
if (chunksSubmitted !== undefined && chunksSubmitted !== null) {
dataToBeSent.chunks = chunksSubmitted;
}
if (totalChunks !== undefined && totalChunks !== null) {
dataToBeSent.totalChunks = totalChunks;
}
if (retry !== undefined && retry !== null) {
dataToBeSent.retry = retry;
}
if (filename !== undefined && filename !== null) {
dataToBeSent.filename = filename;
}
const targetOrigin = new URL(iframe.src).origin;
iframe.contentWindow?.postMessage(
{
action: 'PUBLISH_STATUS',
publishLocation,
...dataToBeSent,
requestedHandler: 'UI',
processed: e.detail?.processed || false,
},
targetOrigin
);
} catch (err) {
console.error('Failed to send theme change to iframe:', err);
}
},
[iframeRef, app?.tabId]
);
useEffect(() => {
subscribeToEvent('receiveChunks', receiveChunksFunc);
return () => {
unsubscribeFromEvent('receiveChunks', receiveChunksFunc);
};
}, [receiveChunksFunc]);
// Function to navigate back in iframe // Function to navigate back in iframe
const navigateBackInIframe = async () => { const navigateBackInIframe = async () => {
if (iframeRef.current && iframeRef.current.contentWindow && history?.currentIndex > 0) { if (iframeRef.current && iframeRef.current.contentWindow && history?.currentIndex > 0) {
@ -245,7 +194,7 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode, skipAuth}, i
height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`, height: !isMobile ? '100vh' : `calc(${rootHeight} - 60px - 45px )`,
border: 'none', border: 'none',
width: '100%' width: '100%'
}} id="browser-iframe" src={defaultUrl} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals allow-orientation-lock" }} id="browser-iframe" src={defaultUrl} sandbox="allow-scripts allow-same-origin allow-forms allow-downloads allow-modals"
allow="fullscreen; clipboard-read; clipboard-write"> allow="fullscreen; clipboard-read; clipboard-write">
</iframe> </iframe>

View File

@ -17,7 +17,7 @@ import { AppsLibrary } from "./AppsLibrary";
const uid = new ShortUniqueId({ length: 8 }); const uid = new ShortUniqueId({ length: 8 });
export const Apps = ({ mode, setMode, show , myName, myAddress}) => { export const Apps = ({ mode, setMode, show , myName}) => {
const [availableQapps, setAvailableQapps] = useState([]); const [availableQapps, setAvailableQapps] = useState([]);
const [selectedAppInfo, setSelectedAppInfo] = useState(null); const [selectedAppInfo, setSelectedAppInfo] = useState(null);
const [selectedCategory, setSelectedCategory] = useState(null) const [selectedCategory, setSelectedCategory] = useState(null)
@ -298,7 +298,7 @@ export const Apps = ({ mode, setMode, show , myName, myAddress}) => {
> >
{mode !== "viewer" && !selectedTab && <Spacer height="30px" />} {mode !== "viewer" && !selectedTab && <Spacer height="30px" />}
{mode === "home" && ( {mode === "home" && (
<AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myAddress={myAddress} /> <AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
)} )}
<AppsLibrary <AppsLibrary
@ -314,7 +314,7 @@ export const Apps = ({ mode, setMode, show , myName, myAddress}) => {
{mode === "appInfo" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />} {mode === "appInfo" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
{mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />} {mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
<AppsCategory availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} /> <AppsCategory availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} />
{mode === "publish" && !selectedTab && <AppPublish categories={categories} myAddress={myAddress} myName={myName} />} {mode === "publish" && !selectedTab && <AppPublish names={myName ? [myName] : []} categories={categories} />}
{tabs.map((tab) => { {tabs.map((tab) => {
if (!iframeRefs.current[tab.tabId]) { if (!iframeRefs.current[tab.tabId]) {
@ -335,7 +335,7 @@ export const Apps = ({ mode, setMode, show , myName, myAddress}) => {
{isNewTabWindow && mode === "viewer" && ( {isNewTabWindow && mode === "viewer" && (
<> <>
<Spacer height="30px" /> <Spacer height="30px" />
<AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myAddress={myAddress} /> <AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
</> </>
)} )}
{mode !== "viewer" && !selectedTab && <Spacer height="180px" />} {mode !== "viewer" && !selectedTab && <Spacer height="180px" />}

View File

@ -41,9 +41,7 @@ const officialAppList = [
"q-trade", "q-trade",
"q-support", "q-support",
"q-manager", "q-manager",
"q-wallets", "q-wallets"
"q-search",
"q-nodecontrol"
]; ];
const ScrollerStyled = styled('div')({ const ScrollerStyled = styled('div')({

View File

@ -47,9 +47,7 @@ const officialAppList = [
"q-fund", "q-fund",
"q-shop", "q-shop",
"q-manager", "q-manager",
"q-wallets", "q-wallets"
"q-search",
"q-nodecontrol"
]; ];
const ScrollerStyled = styled("div")({ const ScrollerStyled = styled("div")({

View File

@ -20,7 +20,7 @@ import HelpIcon from '@mui/icons-material/Help';
import { useHandleTutorials } from "../Tutorials/useHandleTutorials"; import { useHandleTutorials } from "../Tutorials/useHandleTutorials";
import { AppsPrivate } from "./AppsPrivate"; import { AppsPrivate } from "./AppsPrivate";
export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps, myName, myAddress }) => { export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps, myName }) => {
const [qortalUrl, setQortalUrl] = useState('') const [qortalUrl, setQortalUrl] = useState('')
const { showTutorial } = useContext(GlobalContext); const { showTutorial } = useContext(GlobalContext);
@ -146,7 +146,7 @@ export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps, myName, m
<AppCircleLabel>Library</AppCircleLabel> <AppCircleLabel>Library</AppCircleLabel>
</AppCircleContainer> </AppCircleContainer>
</ButtonBase> </ButtonBase>
<AppsPrivate myName={myName} myAddress={myAddress} /> <AppsPrivate myName={myName} />
<SortablePinnedApps availableQapps={availableQapps} myWebsite={myWebsite} myApp={myApp} /> <SortablePinnedApps availableQapps={availableQapps} myWebsite={myWebsite} myApp={myApp} />

View File

@ -45,9 +45,7 @@ const officialAppList = [
"q-support", "q-support",
"q-manager", "q-manager",
"q-mintership", "q-mintership",
"q-wallets", "q-wallets"
"q-search",
"q-nodecontrol"
]; ];
const ScrollerStyled = styled('div')({ const ScrollerStyled = styled('div')({

View File

@ -56,9 +56,7 @@ const officialAppList = [
"q-shop", "q-shop",
"q-manager", "q-manager",
"q-mintership", "q-mintership",
"q-wallets", "q-wallets"
"q-search",
"q-nodecontrol"
]; ];
const ScrollerStyled = styled("div")({ const ScrollerStyled = styled("div")({

View File

@ -1,4 +1,4 @@
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; import React, { useContext, useMemo, useState } from "react";
import { import {
Avatar, Avatar,
Box, Box,
@ -30,18 +30,15 @@ import {
PublishQAppInfo, PublishQAppInfo,
} from "./Apps-styles"; } from "./Apps-styles";
import ImageUploader from "../../common/ImageUploader"; import ImageUploader from "../../common/ImageUploader";
import { getBaseApiReact, isMobile, MyContext } from "../../App"; import { isMobile, MyContext } from "../../App";
import { fileToBase64 } from "../../utils/fileReading"; import { fileToBase64 } from "../../utils/fileReading";
import { objectToBase64 } from "../../qdn/encryption/group-encryption"; import { objectToBase64 } from "../../qdn/encryption/group-encryption";
import { getFee } from "../../background"; import { getFee } from "../../background";
import { useSortedMyNames } from "../../hooks/useSortedMyNames";
const maxFileSize = 50 * 1024 * 1024; // 50MB const maxFileSize = 50 * 1024 * 1024; // 50MB
export const AppsPrivate = ({myName, myAddress}) => { export const AppsPrivate = ({myName}) => {
const { openApp } = useHandlePrivateApps(); const { openApp } = useHandlePrivateApps();
const [names, setNames] = useState([]);
const [name, setName] = useState(0);
const [file, setFile] = useState(null); const [file, setFile] = useState(null);
const [logo, setLogo] = useState(null); const [logo, setLogo] = useState(null);
const [qortalUrl, setQortalUrl] = useState(""); const [qortalUrl, setQortalUrl] = useState("");
@ -51,7 +48,6 @@ export const AppsPrivate = ({myName, myAddress}) => {
const [myGroupsWhereIAmAdminFromGlobal] = useRecoilState( const [myGroupsWhereIAmAdminFromGlobal] = useRecoilState(
myGroupsWhereIAmAdminAtom myGroupsWhereIAmAdminAtom
); );
const mySortedNames = useSortedMyNames(names, myName);
const myGroupsWhereIAmAdmin = useMemo(()=> { const myGroupsWhereIAmAdmin = useMemo(()=> {
return myGroupsWhereIAmAdminFromGlobal?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false) return myGroupsWhereIAmAdminFromGlobal?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
@ -169,8 +165,6 @@ export const AppsPrivate = ({myName, myAddress}) => {
data: decryptedData, data: decryptedData,
identifier: newPrivateAppValues?.identifier, identifier: newPrivateAppValues?.identifier,
service: newPrivateAppValues?.service, service: newPrivateAppValues?.service,
uploadType: 'base64',
name,
}) })
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {
@ -187,7 +181,7 @@ export const AppsPrivate = ({myName, myAddress}) => {
{ {
identifier: newPrivateAppValues?.identifier, identifier: newPrivateAppValues?.identifier,
service: newPrivateAppValues?.service, service: newPrivateAppValues?.service,
name, name: myName,
groupId: selectedGroup, groupId: selectedGroup,
}, },
true true
@ -202,24 +196,6 @@ export const AppsPrivate = ({myName, myAddress}) => {
} }
}; };
const getNames = useCallback(async () => {
if (!myAddress) return;
try {
const res = await fetch(
`${getBaseApiReact()}/names/address/${myAddress}?limit=0`
);
const data = await res.json();
setNames(data?.map((item) => item.name));
} catch (error) {
console.error(error);
}
}, [myAddress]);
useEffect(() => {
if (isOpenPrivateModal) {
getNames();
}
}, [getNames, isOpenPrivateModal]);
const handleChange = (event: React.SyntheticEvent, newValue: number) => { const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValueTabPrivateApp(newValue); setValueTabPrivateApp(newValue);
}; };
@ -456,34 +432,6 @@ export const AppsPrivate = ({myName, myAddress}) => {
{file ? "Change" : "Choose"} File {file ? "Change" : "Choose"} File
</PublishQAppChoseFile> </PublishQAppChoseFile>
<Spacer height="20px" /> <Spacer height="20px" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: '5px',
}}
>
<Label>Select a Qortal name</Label>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={name}
label="Groups where you are an admin"
onChange={(e) => setName(e.target.value)}
>
<MenuItem value={0}>No name selected</MenuItem>
{mySortedNames.map((name) => {
return (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
);
})}
</Select>
</Box>
<Spacer height="20px" />
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",

View File

@ -10,99 +10,9 @@ import { MyContext } from '../../App';
import FileSaver from 'file-saver'; import FileSaver from 'file-saver';
import { Capacitor } from '@capacitor/core'; import { Capacitor } from '@capacitor/core';
import { createEndpoint } from '../../background';
import { uint8ArrayToBase64 } from '../../backgroundFunctions/encryption';
export const isNative = Capacitor.isNativePlatform(); export const isNative = Capacitor.isNativePlatform();
export const saveFileInChunksFromUrl = async (
location,
) => {
let fileName = location.filename
let locationUrl = `/arbitrary/${location.service}/${location.name}`;
if (location.identifier) {
locationUrl = locationUrl + `/${location.identifier}`;
}
const endpoint = await createEndpoint(
locationUrl +
`?attachment=true&attachmentFilename=${location?.filename}`
);
const response = await fetch(endpoint);
if (!response.ok || !response.body) {
throw new Error('Failed to fetch file or no readable stream');
}
const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
const base64Prefix = `data:${contentType};base64,`;
const getExtensionFromFileName = (name: string): string => {
const lastDotIndex = name.lastIndexOf('.');
return lastDotIndex !== -1 ? name.substring(lastDotIndex) : '';
};
const existingExtension = getExtensionFromFileName(fileName);
if (existingExtension) {
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
}
const mimeTypeToExtension = (mimeType: string): string => {
return mimeToExtensionMap[mimeType] || existingExtension || '';
};
const extension = mimeTypeToExtension(contentType);
const fullFileName = `${fileName}_${Date.now()}${extension}`;
const reader = response.body.getReader();
let isFirstChunk = true;
let done = false;
let buffer = new Uint8Array(0);
const preferredChunkSize = 1024 * 1024; // 1MB
while (!done) {
const result = await reader.read();
done = result.done;
if (result.value) {
// Combine new value with existing buffer
const newBuffer = new Uint8Array(buffer.length + result.value.length);
newBuffer.set(buffer);
newBuffer.set(result.value, buffer.length);
buffer = newBuffer;
// While we have enough data, process 1MB chunks
while (buffer.length >= preferredChunkSize) {
const chunk = buffer.slice(0, preferredChunkSize);
buffer = buffer.slice(preferredChunkSize);
const base64Chunk = uint8ArrayToBase64(chunk);
await Filesystem.writeFile({
path: fullFileName,
data: isFirstChunk ? base64Prefix + base64Chunk : base64Chunk,
directory: Directory.Documents,
recursive: true,
append: !isFirstChunk,
});
isFirstChunk = false;
}
}
}
// Write remaining buffer (if any)
if (buffer.length > 0) {
const base64Chunk = uint8ArrayToBase64(buffer);
await Filesystem.writeFile({
path: fullFileName,
data: isFirstChunk ? base64Prefix + base64Chunk : base64Chunk,
directory: Directory.Documents,
recursive: true,
append: !isFirstChunk,
});
}
};
export const saveFileInChunks = async ( export const saveFileInChunks = async (
blob: Blob, blob: Blob,
@ -345,15 +255,7 @@ export function openIndexedDB() {
'GET_NODE_INFO', 'GET_NODE_INFO',
'GET_NODE_STATUS', 'GET_NODE_STATUS',
'GET_ARRR_SYNC_STATUS', 'GET_ARRR_SYNC_STATUS',
'SHOW_PDF_READER', 'SHOW_PDF_READER'
'UPDATE_GROUP',
'SELL_NAME',
'CANCEL_SELL_NAME',
'BUY_NAME', 'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
'TRANSFER_ASSET',
'SIGN_FOREIGN_FEES',
'GET_PRIMARY_NAME',
'SCREEN-ORIENTATION'
] ]
@ -367,13 +269,7 @@ const UIQortalRequests = [
'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER', 'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER',
'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER', 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY', 'CREATE_TRADE_BUY_ORDER',
'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_PUBLIC_NODE', 'SIGN_TRANSACTION', 'ADMIN_ACTION', 'OPEN_NEW_TAB', 'CREATE_AND_COPY_EMBED_LINK', 'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_DATA_WITH_SHARING_KEY', 'DELETE_HOSTED_DATA', 'GET_HOSTED_DATA', 'SHOW_ACTIONS', 'REGISTER_NAME', 'UPDATE_NAME', 'LEAVE_GROUP', 'INVITE_TO_GROUP', 'KICK_FROM_GROUP', 'BAN_FROM_GROUP', 'CANCEL_GROUP_BAN', 'ADD_GROUP_ADMIN', 'REMOVE_GROUP_ADMIN','DECRYPT_AESGCM', 'CANCEL_GROUP_INVITE', 'CREATE_GROUP', 'GET_USER_WALLET_TRANSACTIONS', 'GET_NODE_INFO', 'CREATE_TRADE_SELL_ORDER', 'CANCEL_TRADE_SELL_ORDER', 'IS_USING_PUBLIC_NODE', 'SIGN_TRANSACTION', 'ADMIN_ACTION', 'OPEN_NEW_TAB', 'CREATE_AND_COPY_EMBED_LINK', 'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_DATA_WITH_SHARING_KEY', 'DELETE_HOSTED_DATA', 'GET_HOSTED_DATA', 'SHOW_ACTIONS', 'REGISTER_NAME', 'UPDATE_NAME', 'LEAVE_GROUP', 'INVITE_TO_GROUP', 'KICK_FROM_GROUP', 'BAN_FROM_GROUP', 'CANCEL_GROUP_BAN', 'ADD_GROUP_ADMIN', 'REMOVE_GROUP_ADMIN','DECRYPT_AESGCM', 'CANCEL_GROUP_INVITE', 'CREATE_GROUP', 'GET_USER_WALLET_TRANSACTIONS', 'GET_NODE_INFO',
'GET_NODE_STATUS', 'GET_ARRR_SYNC_STATUS', 'SHOW_PDF_READER', 'UPDATE_GROUP', 'SELL_NAME', 'GET_NODE_STATUS', 'GET_ARRR_SYNC_STATUS', 'SHOW_PDF_READER'
'CANCEL_SELL_NAME',
'BUY_NAME', 'MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA',
'TRANSFER_ASSET',
'SIGN_FOREIGN_FEES',
'GET_PRIMARY_NAME',
'SCREEN_ORIENTATION'
]; ];
@ -646,18 +542,8 @@ isDOMContentLoaded: false
if (event?.data?.requestedHandler !== 'UI') return; if (event?.data?.requestedHandler !== 'UI') return;
const sendMessageToRuntime = (message, eventPort) => { const sendMessageToRuntime = (message, eventPort) => {
let timeout: number = 300000; window.sendMessage(message.action, message.payload, 300000, message.isExtension, {
if ( name: appName, service: appService
message?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' &&
message?.payload?.resources?.length > 0
) {
timeout = message?.payload?.resources?.length * 1200000;
} else if (message?.action === 'PUBLISH_QDN_RESOURCE') {
timeout = 1200000;
}
window.sendMessage(message.action, message.payload, timeout, message.isExtension, {
name: appName, service: appService, tabId,
}, skipAuth) }, skipAuth)
.then((response) => { .then((response) => {
if (response.error) { if (response.error) {
@ -665,7 +551,7 @@ isDOMContentLoaded: false
result: null, result: null,
error: { error: {
error: response.error, error: response.error,
message: typeof response?.error === 'string' ? response?.error : typeof response?.message === 'string' ? response?.message : 'An error has occurred' message: typeof response?.error === 'string' ? response.error : 'An error has occurred'
}, },
}); });
} else { } else {
@ -690,26 +576,38 @@ isDOMContentLoaded: false
} else if(event?.data?.action === 'SAVE_FILE' } else if(event?.data?.action === 'SAVE_FILE'
){ ){
try { try {
await saveFile(event.data, null, true, { const res = await saveFile( event.data, null, true, {
openSnackGlobal, openSnackGlobal,
setOpenSnackGlobal, setOpenSnackGlobal,
infoSnackCustom, infoSnackCustom,
setInfoSnackCustom, setInfoSnackCustom
});
event.ports[0].postMessage({
result: true,
error: null,
}); });
} catch (error) { } catch (error) {
event.ports[0].postMessage({
result: null,
error: error?.message || 'Failed to save file',
});
} }
} else if ( } else if (
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
event?.data?.action === 'PUBLISH_QDN_RESOURCE' ||
event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'ENCRYPT_DATA_WITH_SHARING_KEY' || event?.data?.action === 'ENCRYPT_QORTAL_GROUP_DATA' event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'ENCRYPT_DATA_WITH_SHARING_KEY' || event?.data?.action === 'ENCRYPT_QORTAL_GROUP_DATA'
) { ) {
if (
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
event?.data?.action === 'PUBLISH_QDN_RESOURCE'
){
try {
checkMobileSizeConstraints(event.data)
} catch (error) {
event.ports[0].postMessage({
result: null,
error: error?.message,
});
return;
}
}
let data; let data;
try { try {
data = await storeFilesInIndexedDB(event.data); data = await storeFilesInIndexedDB(event.data);
@ -732,29 +630,6 @@ isDOMContentLoaded: false
error: 'Failed to prepare data for publishing', error: 'Failed to prepare data for publishing',
}); });
} }
} else if (
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
event?.data?.action === 'PUBLISH_QDN_RESOURCE'
) {
const data = event.data;
if (data) {
sendMessageToRuntime(
{
action: event.data.action,
type: 'qortalRequest',
payload: data,
isExtension: true,
},
event.ports[0]
);
} else {
event.ports[0].postMessage({
result: null,
error: 'Failed to prepare data for publishing',
});
}
} else if(event?.data?.action === 'LINK_TO_QDN_RESOURCE' || } else if(event?.data?.action === 'LINK_TO_QDN_RESOURCE' ||
event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){ event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){
const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null
@ -812,7 +687,7 @@ isDOMContentLoaded: false
}; };
}, [appName, appService, tabId]); // Empty dependency array to run once when the component mounts }, [appName, appService]); // Empty dependency array to run once when the component mounts

View File

@ -1,248 +0,0 @@
import {
Box,
Checkbox,
FormControlLabel,
Typography,
useTheme,
} from '@mui/material';
import { Spacer } from '../../common/Spacer';
import { PasswordField } from '../PasswordField/PasswordField';
import { ErrorText } from '../ErrorText/ErrorText';
import Logo1Dark from '../../assets/svgs/Logo1Dark.svg';
import { saveFileToDisk } from '../../utils/generateWallet/generateWallet';
import { useState } from 'react';
import { decryptStoredWallet } from '../../utils/decryptWallet';
import PhraseWallet from '../../utils/generateWallet/phrase-wallet';
import { crypto, walletVersion } from '../../constants/decryptWallet';
import Return from "../../assets/svgs/Return.svg";
import { CustomButton, CustomLabel, TextP } from '../../App-styles';
export const DownloadWallet = ({
returnToMain,
setIsLoading,
showInfo,
rawWallet,
setWalletToBeDownloaded,
walletToBeDownloaded,
}) => {
const [walletToBeDownloadedPassword, setWalletToBeDownloadedPassword] =
useState<string>('');
const [newPassword, setNewPassword] = useState<string>('');
const [keepCurrentPassword, setKeepCurrentPassword] = useState<boolean>(true);
const theme = useTheme();
const [walletToBeDownloadedError, setWalletToBeDownloadedError] =
useState<string>('');
const saveFileToDiskFunc = async () => {
try {
await saveFileToDisk(
walletToBeDownloaded.wallet,
walletToBeDownloaded.qortAddress
);
} catch (error: any) {
setWalletToBeDownloadedError(error?.message);
}
};
const saveWalletFunc = async (password: string, newPassword) => {
let wallet = structuredClone(rawWallet);
const res = await decryptStoredWallet(password, wallet);
const wallet2 = new PhraseWallet(res, wallet?.version || walletVersion);
const passwordToUse = newPassword || password;
wallet = await wallet2.generateSaveWalletData(
passwordToUse,
crypto.kdfThreads,
() => {}
);
setWalletToBeDownloaded({
wallet,
qortAddress: rawWallet.address0,
});
return {
wallet,
qortAddress: rawWallet.address0,
};
};
const confirmPasswordToDownload = async () => {
try {
setWalletToBeDownloadedError('');
if (!keepCurrentPassword && !newPassword) {
setWalletToBeDownloadedError(
'Please enter a new password'
);
return;
}
if (!walletToBeDownloadedPassword) {
setWalletToBeDownloadedError(
'Please enter your password'
);
return;
}
setIsLoading(true);
await new Promise<void>((res) => {
setTimeout(() => {
res();
}, 250);
});
const newPasswordForWallet = !keepCurrentPassword ? newPassword : null;
const res = await saveWalletFunc(
walletToBeDownloadedPassword,
newPasswordForWallet
);
} catch (error: any) {
setWalletToBeDownloadedError(error?.message);
} finally {
setIsLoading(false);
}
};
return (
<>
<Spacer height="22px" />
<Box
sx={{
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'flex-start',
maxWidth: '700px',
paddingLeft: '22px',
width: '100%',
}}
>
<img
style={{
cursor: "pointer",
height: '24px'
}}
onClick={returnToMain}
src={Return}
/>
</Box>
<Spacer height="10px" />
<div
className="image-container"
style={{
width: '136px',
height: '154px',
}}
>
<img src={Logo1Dark} className="base-image" />
</div>
<Spacer height="35px" />
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<TextP
sx={{
textAlign: 'start',
lineHeight: '24px',
fontSize: '20px',
fontWeight: 600,
}}
>
Download account
</TextP>
</Box>
<Spacer height="35px" />
{!walletToBeDownloaded && (
<>
<CustomLabel htmlFor="standard-adornment-password">
Confirm password
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={walletToBeDownloadedPassword}
onChange={(e) => setWalletToBeDownloadedPassword(e.target.value)}
/>
<Spacer height="20px" />
<FormControlLabel
sx={{
margin: 0,
}}
control={
<Checkbox
onChange={(e) => setKeepCurrentPassword(e.target.checked)}
checked={keepCurrentPassword}
edge="start"
tabIndex={-1}
disableRipple
sx={{
'&.Mui-checked': {
color: theme.palette.text.secondary,
},
'& .MuiSvgIcon-root': {
color: theme.palette.text.secondary,
},
}}
/>
}
label={
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography sx={{ fontSize: '14px' }}>
Keep current password
</Typography>
</Box>
}
/>
<Spacer height="20px" />
{!keepCurrentPassword && (
<>
<CustomLabel htmlFor="standard-adornment-password">
New password
</CustomLabel>
<Spacer height="5px" />
<PasswordField
id="standard-adornment-password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
<Spacer height="20px" />
</>
)}
<CustomButton onClick={confirmPasswordToDownload}>
Confirm wallet password
</CustomButton>
<ErrorText>{walletToBeDownloadedError}</ErrorText>
</>
)}
{walletToBeDownloaded && (
<>
<CustomButton
onClick={async () => {
await saveFileToDiskFunc();
await showInfo({
message: 'Keep your account file secure',
});
}}
>
Download account
</CustomButton>
</>
)}
</>
);
};

View File

@ -79,7 +79,7 @@ export const AdminSpaceInner = ({
const res = await fetch( const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${ `${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${
getLatestPublish.name getLatestPublish.name
}/${getLatestPublish.identifier}?encoding=base64&rebuild=true` }/${getLatestPublish.identifier}?encoding=base64`
); );
data = await res.text(); data = await res.text();

View File

@ -15,12 +15,12 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar'
import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes' import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/codes'
import { useMessageQueue } from '../../MessageQueueContext' import { useMessageQueue } from '../../MessageQueueContext'
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events' import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'
import { Box, ButtonBase, Divider, IconButton, Tooltip, Typography } from '@mui/material' import { Box, ButtonBase, Divider, Typography } from '@mui/material'
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import { ReplyPreview } from './MessageItem' import { ReplyPreview } from './MessageItem'
import { ExitIcon } from '../../assets/Icons/ExitIcon' import { ExitIcon } from '../../assets/Icons/ExitIcon'
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../../constants/resourceTypes' import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../../constants/resourceTypes'
import { getFee, isExtMsg } from '../../background' import { isExtMsg } from '../../background'
import MentionList from './MentionList' import MentionList from './MentionList'
import { ChatOptions } from './ChatOptions' import { ChatOptions } from './ChatOptions'
import { isFocusedParentGroupAtom } from '../../atoms/global' import { isFocusedParentGroupAtom } from '../../atoms/global'
@ -28,10 +28,6 @@ import { useRecoilState } from 'recoil'
import AppViewerContainer from '../Apps/AppViewerContainer' import AppViewerContainer from '../Apps/AppViewerContainer'
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { throttle } from 'lodash' import { throttle } from 'lodash'
import ImageIcon from '@mui/icons-material/Image';
import { messageHasImage } from '../../utils/chat'
const uidImages = new ShortUniqueId({ length: 12 });
const uid = new ShortUniqueId({ length: 5 }); const uid = new ShortUniqueId({ length: 5 });
@ -59,9 +55,8 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey,
const editorRef = useRef(null); const editorRef = useRef(null);
const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue(); const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue();
const handleUpdateRef = useRef(null); const handleUpdateRef = useRef(null);
const {isUserBlocked, show} = useContext(MyContext) const {isUserBlocked} = useContext(MyContext)
const [chatImagesToSave, setChatImagesToSave] = useState([]);
const [isDeleteImage, setIsDeleteImage] = useState(false);
const lastReadTimestamp = useRef(null) const lastReadTimestamp = useRef(null)
@ -629,8 +624,6 @@ if(isFocusedParent === false){
setReplyMessage(null) setReplyMessage(null)
setOnEditMessage(null) setOnEditMessage(null)
clearEditorContent() clearEditorContent()
setIsDeleteImage(false);
setChatImagesToSave([]);
} }
}, [isFocusedParent]) }, [isFocusedParent])
const clearEditorContent = () => { const clearEditorContent = () => {
@ -651,193 +644,88 @@ const clearEditorContent = () => {
const sendMessage = async () => { const sendMessage = async ()=> {
try { try {
if (messageSize > 4000) return; // TODO magic number if(messageSize > 4000) return
if (isPrivate === null) if(isPrivate === null) throw new Error('Unable to determine if group is private')
throw new Error( if(isSending) return
"Onable to determine if group is private" if(+balance < 4) throw new Error('You need at least 4 QORT to send a message')
); pauseAllQueues()
if (isSending) return;
if (+balance < 4)
// TODO magic number
throw new Error(
"You need at least 4 QORT to send a message"
);
pauseAllQueues();
if (editorRef.current) { if (editorRef.current) {
let htmlContent = editorRef.current.getHTML(); const htmlContent = editorRef.current.getHTML();
const deleteImage =
onEditMessage && isDeleteImage && messageHasImage(onEditMessage);
const hasImage = if(!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') return
chatImagesToSave?.length > 0 || onEditMessage?.images?.length > 0;
if (
(!htmlContent?.trim() || htmlContent?.trim() === '<p></p>') &&
!hasImage &&
!deleteImage
)
return;
if (htmlContent?.trim() === '<p></p>') {
htmlContent = null;
}
setIsSending(true);
const message =
isPrivate === false
? !htmlContent
? '<p></p>'
: editorRef.current.getJSON()
: htmlContent;
const secretKeyObject = await getSecretKey(false, true);
let repliedTo = replyMessage?.signature;
if (replyMessage?.chatReference) { setIsSending(true)
repliedTo = replyMessage?.chatReference; const message = isPrivate === false ? editorRef.current.getJSON() : htmlContent
} const secretKeyObject = await getSecretKey(false, true)
const chatReference = onEditMessage?.signature; let repliedTo = replyMessage?.signature
const publicData = isPrivate if (replyMessage?.chatReference) {
? {} repliedTo = replyMessage?.chatReference
: { }
isEdited: chatReference ? true : false, let chatReference = onEditMessage?.signature
};
interface ImageToPublish { const publicData = isPrivate ? {} : {
service: string; isEdited : chatReference ? true : false,
identifier: string; }
name: string; const otherData = {
base64: string; repliedTo,
} ...(onEditMessage?.decryptedData || {}),
type: chatReference ? 'edit' : '',
specialId: uid.rnd(),
...publicData
}
const objectMessage = {
...(otherData || {}),
[isPrivate ? 'message' : 'messageText']: message,
version: 3
}
const message64: any = await objectToBase64(objectMessage)
const imagesToPublish: ImageToPublish[] = []; const encryptSingle = isPrivate === false ? JSON.stringify(objectMessage) : await encryptChatMessage(message64, secretKeyObject)
// const res = await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle})
if (deleteImage) { const sendMessageFunc = async () => {
const fee = await getFee('ARBITRARY'); return await sendChatGroup({groupId: selectedGroup,messageText: encryptSingle, chatReference})
await show({ };
publishFee: fee.fee + ' QORT',
message: "Would you like to delete your previous chat image?",
});
// TODO magic string // Add the function to the queue
await window.sendMessage('publishOnQDN', { const messageObj = {
data: 'RA==', message: {
identifier: onEditMessage?.images[0]?.identifier, text: htmlContent,
service: onEditMessage?.images[0]?.service, timestamp: Date.now(),
uploadType: 'base64', senderName: myName,
}); sender: myAddress,
} ...(otherData || {})
},
if (chatImagesToSave?.length > 0) { chatReference
const imageToSave = chatImagesToSave[0]; }
addToQueue(sendMessageFunc, messageObj, 'chat',
const base64ToSave = isPrivate selectedGroup );
? await encryptChatMessage(imageToSave, secretKeyObject) setTimeout(() => {
: imageToSave; executeEvent("sent-new-message-group", {})
}, 150);
// 1 represents public group, 0 is private clearEditorContent()
const identifier = `grp-q-manager_${isPrivate ? 0 : 1}_group_${selectedGroup}_${uidImages.rnd()}`; setReplyMessage(null)
imagesToPublish.push({ setOnEditMessage(null)
service: 'IMAGE',
identifier,
name: myName,
base64: base64ToSave,
});
const res = await window.sendMessage(
'PUBLISH_MULTIPLE_QDN_RESOURCES',
{
resources: imagesToPublish,
},
240000,
true
);
if (res?.error)
throw new Error(
"Unable to publish image"
);
}
const images =
imagesToPublish?.length > 0
? imagesToPublish.map((item) => {
return {
name: item.name,
identifier: item.identifier,
service: item.service,
timestamp: Date.now(),
};
})
: chatReference
? isDeleteImage
? []
: onEditMessage?.images || []
: [];
const otherData = {
repliedTo,
...(onEditMessage?.decryptedData || {}),
type: chatReference ? 'edit' : '',
specialId: uid.rnd(),
images: images,
...publicData,
};
const objectMessage = {
...(otherData || {}),
[isPrivate ? 'message' : 'messageText']: message,
version: 3,
};
const message64: any = await objectToBase64(objectMessage);
const encryptSingle =
isPrivate === false
? JSON.stringify(objectMessage)
: await encryptChatMessage(message64, secretKeyObject);
const sendMessageFunc = async () => {
return await sendChatGroup({
groupId: selectedGroup,
messageText: encryptSingle,
chatReference,
});
};
// Add the function to the queue
const messageObj = {
message: {
text: htmlContent,
timestamp: Date.now(),
senderName: myName,
sender: myAddress,
...(otherData || {}),
},
chatReference,
};
addToQueue(sendMessageFunc, messageObj, 'chat', selectedGroup);
setTimeout(() => {
executeEvent('sent-new-message-group', {});
}, 150);
clearEditorContent();
setReplyMessage(null);
setOnEditMessage(null);
setIsDeleteImage(false);
setChatImagesToSave([]);
} }
// send chat message // send chat message
} catch (error) { } catch (error) {
const errorMsg = error?.message || error; const errorMsg = error?.message || error
setInfoSnack({ setInfoSnack({
type: 'error', type: "error",
message: errorMsg, message: errorMsg,
}); });
setOpenSnack(true); setOpenSnack(true);
console.error(error); console.error(error)
} finally { } finally {
setIsSending(false); setIsSending(false)
resumeAllQueues(); resumeAllQueues()
} }
}; }
useEffect(() => { useEffect(() => {
if (hide) { if (hide) {
@ -854,8 +742,7 @@ const sendMessage = async () => {
setReplyMessage(message) setReplyMessage(message)
setOnEditMessage(null) setOnEditMessage(null)
setIsFocusedParent(true); setIsFocusedParent(true);
setIsDeleteImage(false);
setChatImagesToSave([]);
setTimeout(() => { setTimeout(() => {
editorRef?.current?.chain().focus() editorRef?.current?.chain().focus()
@ -868,7 +755,7 @@ const sendMessage = async () => {
setReplyMessage(null) setReplyMessage(null)
setIsFocusedParent(true); setIsFocusedParent(true);
setTimeout(() => { setTimeout(() => {
editorRef?.current?.chain().focus().setContent(message?.messageText || message?.text || '<p></p>').run(); editorRef.current.chain().focus().setContent(message?.messageText || message?.text).run();
}, 250); }, 250);
}, []) }, [])
@ -938,24 +825,6 @@ const sendMessage = async () => {
} }
}, [isPrivate]) }, [isPrivate])
const insertImage = useCallback(
(img) => {
if (
chatImagesToSave?.length > 0 ||
(messageHasImage(onEditMessage) && !isDeleteImage)
) {
setInfoSnack({
type: 'error',
message: 'This message already has an image',
});
setOpenSnack(true);
return;
}
setChatImagesToSave((prev) => [...prev, img]);
},
[chatImagesToSave, onEditMessage?.images, isDeleteImage]
);
return ( return (
<div style={{ <div style={{
height: isMobile ? '100%' : '100%', height: isMobile ? '100%' : '100%',
@ -995,117 +864,6 @@ const sendMessage = async () => {
overflow: !isMobile && "auto", overflow: !isMobile && "auto",
flexShrink: 0 flexShrink: 0
}}> }}>
<Box
sx={{
alignItems: 'flex-start',
display: 'flex',
width: '100%',
gap: '10px',
flexWrap: 'wrap',
}}
>
{!isDeleteImage &&
onEditMessage &&
messageHasImage(onEditMessage) &&
onEditMessage?.images?.map((_, index) => (
<div
key={index}
style={{
position: 'relative',
height: '50px',
width: '50px',
}}
>
<ImageIcon
sx={{
height: '100%',
width: '100%',
borderRadius: '3px',
color:'white'
}}
/>
<Tooltip title="Delete image">
<IconButton
onClick={() => setIsDeleteImage(true)}
size="small"
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: (theme) =>
theme.palette.background.paper,
color: (theme) => theme.palette.text.primary,
borderRadius: '50%',
opacity: 0,
transition: 'opacity 0.2s',
boxShadow: (theme) => theme.shadows[2],
'&:hover': {
backgroundColor: (theme) =>
theme.palette.background.default,
opacity: 1,
},
pointerEvents: 'auto',
}}
>
<CloseIcon fontSize="small" />
</IconButton>
</Tooltip>
</div>
))}
{chatImagesToSave.map((imgBase64, index) => (
<div
key={index}
style={{
position: 'relative',
height: '50px',
width: '50px',
}}
>
<img
src={`data:image/webp;base64,${imgBase64}`}
style={{
height: '100%',
width: '100%',
objectFit: 'contain',
borderRadius: '3px',
}}
/>
<Tooltip title="Remove image">
<IconButton
onClick={() =>
setChatImagesToSave((prev) =>
prev.filter((_, i) => i !== index)
)
}
size="small"
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: (theme) =>
theme.palette.background.paper,
color: (theme) => theme.palette.text.primary,
borderRadius: '50%',
opacity: 0,
transition: 'opacity 0.2s',
boxShadow: (theme) => theme.shadows[2],
'&:hover': {
backgroundColor: (theme) =>
theme.palette.background.default,
opacity: 1,
},
pointerEvents: 'auto',
}}
>
<CloseIcon fontSize="small" />
</IconButton>
</Tooltip>
</div>
))}
</Box>
{replyMessage && ( {replyMessage && (
<Box sx={{ <Box sx={{
display: 'flex', display: 'flex',
@ -1137,7 +895,7 @@ const sendMessage = async () => {
}}> }}>
<Tiptap isReply={onEditMessage || replyMessage} enableMentions setEditorRef={setEditorRef} onEnter={sendMessage} isChat disableEnter={isMobile ? true : false} isFocusedParent={isFocusedParent} setIsFocusedParent={setIsFocusedParent} membersWithNames={members} insertImage={insertImage} /> <Tiptap isReply={onEditMessage || replyMessage} enableMentions setEditorRef={setEditorRef} onEnter={sendMessage} isChat disableEnter={isMobile ? true : false} isFocusedParent={isFocusedParent} setIsFocusedParent={setIsFocusedParent} membersWithNames={members} />

View File

@ -261,18 +261,17 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR
if (chatReferences?.[message.signature]) { if (chatReferences?.[message.signature]) {
reactions = chatReferences[message.signature]?.reactions || null; reactions = chatReferences[message.signature]?.reactions || null;
if (chatReferences[message.signature]?.edit) { if (chatReferences[message.signature]?.edit?.message && message?.text) {
message.text = message.text = chatReferences[message.signature]?.edit?.message;
chatReferences[message.signature]?.edit?.message; message.isEdit = true
message.messageText = message.editTimestamp = chatReferences[message.signature]?.edit?.timestamp
chatReferences[message.signature]?.edit?.messageText; }
message.images = if (chatReferences[message.signature]?.edit?.messageText && message?.messageText) {
chatReferences[message.signature]?.edit?.images; message.messageText = chatReferences[message.signature]?.edit?.messageText;
message.isEdit = true
message.isEdit = true; message.editTimestamp = chatReferences[message.signature]?.edit?.timestamp
message.editTimestamp =
chatReferences[message.signature]?.edit?.timestamp;
} }
} }
// Check if message is updating // Check if message is updating

View File

@ -39,7 +39,6 @@ import StarterKit from "@tiptap/starter-kit";
import Underline from "@tiptap/extension-underline"; import Underline from "@tiptap/extension-underline";
import { generateHTML } from "@tiptap/react"; import { generateHTML } from "@tiptap/react";
import ErrorBoundary from "../../common/ErrorBoundary"; import ErrorBoundary from "../../common/ErrorBoundary";
import { isHtmlString } from "../../utils/chat";
const extractTextFromHTML = (htmlString = '') => { const extractTextFromHTML = (htmlString = '') => {
return convert(htmlString, { return convert(htmlString, {
@ -60,30 +59,27 @@ export const ChatOptions = ({ messages : untransformedMessages, goToMessage, mem
const parentRefMentions = useRef(); const parentRefMentions = useRef();
const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null) const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null)
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
const messages = useMemo(() => { const messages = useMemo(()=> {
return untransformedMessages?.map((item) => { return untransformedMessages?.map((item)=> {
if (item?.messageText) { if(item?.messageText){
let transformedMessage = item?.messageText; let transformedMessage = item?.messageText
const isHtml = isHtmlString(item?.messageText);
try { try {
transformedMessage = isHtml transformedMessage = generateHTML(item?.messageText, [
? item?.messageText StarterKit,
: generateHTML(item?.messageText, [ Underline,
StarterKit, Highlight,
Underline, Mention
Highlight, ])
Mention, return {
]); ...item,
return { messageText: transformedMessage
...item, }
messageText: transformedMessage,
};
} catch (error) { } catch (error) {
console.log(error); // error
} }
} else return item; } else return item
}); })
}, [untransformedMessages]); }, [untransformedMessages])
const getTimestampMention = async () => { const getTimestampMention = async () => {
try { try {

View File

@ -66,7 +66,7 @@ export const CreateCommonSecret = ({groupId, secretKey, isOwner, myAddress, sec
const res = await fetch( const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ `${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
publish.identifier publish.identifier
}?encoding=base64&rebuild=true` }?encoding=base64`
); );
const data = await res.text(); const data = await res.text();

View File

@ -33,9 +33,6 @@ import level7Img from "../../assets/badges/level-7.png"
import level8Img from "../../assets/badges/level-8.png" import level8Img from "../../assets/badges/level-8.png"
import level9Img from "../../assets/badges/level-9.png" import level9Img from "../../assets/badges/level-9.png"
import level10Img from "../../assets/badges/level-10.png" import level10Img from "../../assets/badges/level-10.png"
import { Embed } from "../Embeds/Embed";
import { buildImageEmbedLink, isHtmlString, messageHasImage } from "../../utils/chat";
import CommentsDisabledIcon from '@mui/icons-material/CommentsDisabled';
const getBadgeImg = (level)=> { const getBadgeImg = (level)=> {
switch(level?.toString()){ switch(level?.toString()){
@ -105,33 +102,35 @@ useEffect(()=> {
getInfo() getInfo()
}, [message?.sender, getIndividualUserInfo]) }, [message?.sender, getIndividualUserInfo])
const htmlText = useMemo(() => { const htmlText = useMemo(()=> {
if (message?.messageText) {
const isHtml = isHtmlString(message?.messageText); if(message?.messageText){
if (isHtml) return message?.messageText;
return generateHTML(message?.messageText, [ return generateHTML(message?.messageText, [
StarterKit, StarterKit,
Underline, Underline,
Highlight, Highlight,
Mention, Mention,
TextStyle, TextStyle
]); ])
} }
}, [message?.editTimestamp]);
const htmlReply = useMemo(() => { }, [message?.editTimestamp])
if (reply?.messageText) {
const isHtml = isHtmlString(reply?.messageText);
if (isHtml) return reply?.messageText;
const htmlReply = useMemo(()=> {
if(reply?.messageText){
return generateHTML(reply?.messageText, [ return generateHTML(reply?.messageText, [
StarterKit, StarterKit,
Underline, Underline,
Highlight, Highlight,
Mention, Mention,
TextStyle, TextStyle
]); ])
} }
}, [reply?.editTimestamp]);
}, [reply?.editTimestamp])
const userAvatarUrl = useMemo(()=> { const userAvatarUrl = useMemo(()=> {
return message?.senderName ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ return message?.senderName ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
@ -143,13 +142,6 @@ const onSeenFunc = useCallback(()=> {
onSeen(message.id); onSeen(message.id);
}, [message?.id]) }, [message?.id])
const hasNoMessage =
(!message.decryptedData?.data?.message ||
message.decryptedData?.data?.message === '<p></p>') &&
(message?.images || [])?.length === 0 &&
(!message?.messageText || message?.messageText === '<p></p>') &&
(!message?.text || message?.text === '<p></p>');
return ( return (
<MessageWragger lastMessage={lastSignature === message?.signature} isLast={isLast} onSeen={onSeenFunc}> <MessageWragger lastMessage={lastSignature === message?.signature} isLast={isLast} onSeen={onSeenFunc}>
{message?.divide && ( {message?.divide && (
@ -343,7 +335,7 @@ const hasNoMessage =
</Box> </Box>
</> </>
)} )}
{htmlText && !hasNoMessage && ( {message?.messageText && (
<MessageDisplay <MessageDisplay
htmlContent={htmlText} htmlContent={htmlText}
setMobileViewModeKeepOpen={setMobileViewModeKeepOpen} setMobileViewModeKeepOpen={setMobileViewModeKeepOpen}
@ -351,30 +343,9 @@ const hasNoMessage =
)} )}
{message?.decryptedData?.type === "notification" ? ( {message?.decryptedData?.type === "notification" ? (
<MessageDisplay htmlContent={message.decryptedData?.data?.message} /> <MessageDisplay htmlContent={message.decryptedData?.data?.message} />
) : hasNoMessage ? null : ( ) : (
<MessageDisplay setMobileViewModeKeepOpen={setMobileViewModeKeepOpen} htmlContent={message.text} /> <MessageDisplay setMobileViewModeKeepOpen={setMobileViewModeKeepOpen} htmlContent={message.text} />
)} )}
{hasNoMessage && (
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: '10px',
}}
>
<CommentsDisabledIcon sx={{
color: 'white'
}} />
<Typography sx={{
color: 'white'
}}>
No Message
</Typography>
</Box>
)}
{message?.images && messageHasImage(message) && (
<Embed embedLink={buildImageEmbedLink(message.images[0])} />
)}
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -548,19 +519,6 @@ const hasNoMessage =
export const ReplyPreview = ({message, isEdit})=> { export const ReplyPreview = ({message, isEdit})=> {
const replyMessageText = useMemo(() => {
if (!message?.messageText) return null;
const isHtml = isHtmlString(message?.messageText);
if (isHtml) return message?.messageText;
return generateHTML(message?.messageText, [
StarterKit,
Underline,
Highlight,
Mention,
TextStyle,
]);
}, [message?.messageText]);
return ( return (
<Box <Box
sx={{ sx={{
@ -595,9 +553,15 @@ export const ReplyPreview = ({message, isEdit})=> {
}}>Replied to {message?.senderName || message?.senderAddress}</Typography> }}>Replied to {message?.senderName || message?.senderAddress}</Typography>
)} )}
{replyMessageText && ( {message?.messageText && (
<MessageDisplay <MessageDisplay
htmlContent={replyMessageText} htmlContent={generateHTML(message?.messageText, [
StarterKit,
Underline,
Highlight,
Mention,
TextStyle
])}
/> />
)} )}
{message?.decryptedData?.type === "notification" ? ( {message?.decryptedData?.type === "notification" ? (

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import React, { useEffect, useMemo, useRef, useState } from "react";
import { EditorProvider, useCurrentEditor, useEditor } from "@tiptap/react"; import { EditorProvider, useCurrentEditor, useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit"; import StarterKit from "@tiptap/starter-kit";
import { Color } from "@tiptap/extension-color"; import { Color } from "@tiptap/extension-color";
@ -34,7 +34,6 @@ import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText'; import ListItemText from '@mui/material/ListItemText';
import { ReactRenderer } from '@tiptap/react' import { ReactRenderer } from '@tiptap/react'
import MentionList from './MentionList.jsx' import MentionList from './MentionList.jsx'
import { fileToBase64 } from "../../utils/fileReading/index.js";
function textMatcher(doc, from) { function textMatcher(doc, from) {
const textBeforeCursor = doc.textBetween(0, from, ' ', ' '); const textBeforeCursor = doc.textBetween(0, from, ' ', ' ');
@ -111,13 +110,13 @@ const MenuBar = ({ setEditorRef, isChat }) => {
}; };
useEffect(() => { useEffect(() => {
if (editor && !isChat) { if (editor) {
editor.view.dom.addEventListener("paste", handlePaste); editor.view.dom.addEventListener("paste", handlePaste);
return () => { return () => {
editor.view.dom.removeEventListener("paste", handlePaste); editor.view.dom.removeEventListener("paste", handlePaste);
}; };
} }
}, [editor, isChat]); }, [editor]);
return ( return (
<div className="control-group"> <div className="control-group">
@ -300,8 +299,7 @@ export default ({
customEditorHeight, customEditorHeight,
membersWithNames, membersWithNames,
enableMentions, enableMentions,
isReply, isReply
insertImage,
}) => { }) => {
const extensionsFiltered = isChat const extensionsFiltered = isChat
@ -331,35 +329,7 @@ export default ({
}, [membersWithNames]) }, [membersWithNames])
const handleImageUpload = useCallback(async (file) => {
try {
if (!file.type.includes('image')) return;
let compressedFile = file;
if (file.type !== 'image/gif') {
await new Promise<void>((resolve) => {
new Compressor(file, {
quality: 0.6,
maxWidth: 1200,
mimeType: 'image/webp',
success(result) {
compressedFile = result;
resolve();
},
error(err) {
console.error('Image compression error:', err);
},
});
});
}
if (compressedFile) {
const toBase64 = await fileToBase64(compressedFile);
insertImage(toBase64);
}
} catch (error) {
console.error(error);
}
}, [insertImage]);
const usersRef = useRef([]); const usersRef = useRef([]);
@ -500,25 +470,6 @@ export default ({
} }
return false; return false;
}, },
handlePaste(view, event) {
if(!handleImageUpload) return
if (!isChat) return;
const items = event.clipboardData?.items;
if (!items) return false;
for (const item of items) {
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
if (file) {
event.preventDefault(); // Block the default paste
handleImageUpload(file); // Custom handler
return true; // Let ProseMirror know we handled it
}
}
}
return false; // fallback to default behavior otherwise
},
}} }}
/> />
</div> </div>

View File

@ -1,9 +1,10 @@
import React, { useCallback, useEffect, useRef } from "react"; import React, { useCallback, useEffect, useRef } from "react";
import { getBaseApiReact } from "../../App";
import { truncate } from "lodash";
export const useBlockedAddresses = () => {
export const useBlockedAddresses = (isAuthenticated: boolean) => {
const userBlockedRef = useRef({}) const userBlockedRef = useRef({})
const userNamesBlockedRef = useRef({}) const userNamesBlockedRef = useRef({})
@ -18,7 +19,7 @@ export const useBlockedAddresses = (isAuthenticated: boolean) => {
const isUserBlocked = useCallback((address, name)=> { const isUserBlocked = useCallback((address, name)=> {
try { try {
if(!address) return false if(!address) return false
if(userBlockedRef.current[address]) return true if(userBlockedRef.current[address] || userNamesBlockedRef.current[name]) return true
return false return false
@ -28,9 +29,6 @@ export const useBlockedAddresses = (isAuthenticated: boolean) => {
}, []) }, [])
useEffect(()=> { useEffect(()=> {
if (!isAuthenticated) return;
userBlockedRef.current = {};
userNamesBlockedRef.current = {};
const fetchBlockedList = async ()=> { const fetchBlockedList = async ()=> {
try { try {
const response = await new Promise((res, rej) => { const response = await new Promise((res, rej) => {
@ -89,16 +87,46 @@ export const useBlockedAddresses = (isAuthenticated: boolean) => {
} }
} }
fetchBlockedList() fetchBlockedList()
}, [isAuthenticated]) }, [])
const removeBlockFromList = useCallback(async (address, name)=> { const removeBlockFromList = useCallback(async (address, name)=> {
if(name){ await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'remove',
items: name ? [name] : [address],
listName: name ? 'blockedNames' : 'blockedAddresses'
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
if(!name){
const copyObject = {...userBlockedRef.current}
delete copyObject[address]
userBlockedRef.current = copyObject
} else {
const copyObject = {...userNamesBlockedRef.current}
delete copyObject[name]
userNamesBlockedRef.current = copyObject
}
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
if(name && userBlockedRef.current[address]){
await new Promise((res, rej) => { await new Promise((res, rej) => {
window.sendMessage("listActions", { window.sendMessage("listActions", {
type: 'remove', type: 'remove',
items: [name] , items: !name ? [name] : [address],
listName: 'blockedNames' listName: !name ? 'blockedNames' : 'blockedAddresses'
}) })
.then((response) => { .then((response) => {
@ -106,12 +134,9 @@ export const useBlockedAddresses = (isAuthenticated: boolean) => {
rej(response?.message); rej(response?.message);
return; return;
} else { } else {
const copyObject = {...userBlockedRef.current}
const copyObject = {...userNamesBlockedRef.current} delete copyObject[address]
delete copyObject[name] userBlockedRef.current = copyObject
userNamesBlockedRef.current = copyObject
res(response); res(response);
} }
}) })
@ -121,94 +146,41 @@ export const useBlockedAddresses = (isAuthenticated: boolean) => {
}) })
} }
if(address){
await new Promise((res, rej) => {
window.sendMessage("listActions", {
type: 'remove',
items: [address],
listName: 'blockedAddresses'
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userBlockedRef.current}
delete copyObject[address]
userBlockedRef.current = copyObject
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
}
}, []) }, [])
const addToBlockList = useCallback(async (address, name)=> { const addToBlockList = useCallback(async (address, name)=> {
if(name){ await new Promise((res, rej) => {
await new Promise((res, rej) => { window.sendMessage("listActions", {
window.sendMessage("listActions", {
type: 'add', type: 'add',
items: [name], items: name ? [name] : [address],
listName: 'blockedNames' listName: name ? 'blockedNames' : 'blockedAddresses'
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userNamesBlockedRef.current}
copyObject[name] = true
userNamesBlockedRef.current = copyObject
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
}) })
} .then((response) => {
if(address){ if (response.error) {
await new Promise((res, rej) => { rej(response?.message);
window.sendMessage("listActions", { return;
} else {
if(name){
type: 'add', const copyObject = {...userNamesBlockedRef.current}
items: [address], copyObject[name] = true
listName: 'blockedAddresses' userNamesBlockedRef.current = copyObject
}else {
const copyObject = {...userBlockedRef.current}
copyObject[address] = true
userBlockedRef.current = copyObject
})
.then((response) => {
if (response.error) {
rej(response?.message);
return;
} else {
const copyObject = {...userBlockedRef.current}
copyObject[address] = true
userBlockedRef.current = copyObject
res(response);
} }
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
}
res(response);
}
})
.catch((error) => {
console.error("Failed qortalRequest", error);
});
})
}, []) }, [])
return { return {

View File

@ -52,8 +52,6 @@ export const ImageCard = ({
backgroundColor: "#1F2023", backgroundColor: "#1F2023",
height: height, height: height,
transition: "height 0.6s ease-in-out", transition: "height 0.6s ease-in-out",
display: 'flex',
flexDirection: 'column',
}} }}
> >
<Box <Box
@ -172,18 +170,8 @@ export const ImageCard = ({
)} )}
</Box> </Box>
<Box <Box>
sx={{ <CardContent>
maxHeight: '100%',
flexGrow: 1,
overflow: 'hidden',
}}
>
<CardContent
sx={{
height: '100%',
}}
>
<ImageViewer src={image} /> <ImageViewer src={image} />
</CardContent> </CardContent>
</Box> </Box>
@ -215,7 +203,6 @@ export const ImageCard = ({
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
cursor: "pointer", cursor: "pointer",
height: '100%',
}} }}
onClick={handleOpenFullscreen} onClick={handleOpenFullscreen}
> >
@ -252,9 +239,6 @@ export const ImageCard = ({
position: "relative", position: "relative",
width: "100%", width: "100%",
height: "100%", height: "100%",
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
backgroundColor: "#000", backgroundColor: "#000",
}} }}
> >

View File

@ -6,30 +6,19 @@ import {
DialogContent, DialogContent,
DialogContentText, DialogContentText,
DialogTitle, DialogTitle,
IconButton,
TextField, TextField,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import React, { useContext, useEffect, useState } from "react"; import React, { useContext, useEffect, useState } from "react";
import { getBaseApiReact, MyContext } from "../../App"; import { MyContext } from "../../App";
import { Spacer } from "../../common/Spacer"; import { Spacer } from "../../common/Spacer";
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; import { executeEvent } from "../../utils/events";
import { validateAddress } from "../../utils/validateAddress";
import { getNameInfo, requestQueueMemberNames } from "./Group";
import { useModal } from "../../common/useModal";
import { useRecoilState } from "recoil";
import { isOpenBlockedModalAtom } from "../../atoms/global";
import CloseIcon from '@mui/icons-material/Close';
import InfoIcon from '@mui/icons-material/Info'; export const BlockedUsersModal = ({ close }) => {
export const BlockedUsersModal = () => {
const [isOpenBlockedModal, setIsOpenBlockedModal] = useRecoilState(isOpenBlockedModalAtom)
const [hasChanged, setHasChanged] = useState(false); const [hasChanged, setHasChanged] = useState(false);
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const [addressesWithNames, setAddressesWithNames] = useState({})
const { isShow, onCancel, onOk, show, message } = useModal(); const { getAllBlockedUsers, removeBlockFromList, addToBlockList } = useContext(MyContext);
const { getAllBlockedUsers, removeBlockFromList, addToBlockList, setOpenSnackGlobal, setInfoSnackCustom } =
useContext(MyContext);
const [blockedUsers, setBlockedUsers] = useState({ const [blockedUsers, setBlockedUsers] = useState({
addresses: {}, addresses: {},
names: {}, names: {},
@ -39,162 +28,60 @@ export const BlockedUsersModal = () => {
}; };
useEffect(() => { useEffect(() => {
if(!isOpenBlockedModal) return
fetchBlockedUsers(); fetchBlockedUsers();
}, [isOpenBlockedModal]); }, []);
const getNames = async () => {
// const validApi = await findUsableApi();
const addresses = Object.keys(blockedUsers?.addresses)
const addressNames = {}
const getMemNames = addresses.map(async (address) => {
const name = await requestQueueMemberNames.enqueue(() => {
return getNameInfo(address);
});
if (name) {
addressNames[address] = name
}
return true;
});
await Promise.all(getMemNames);
setAddressesWithNames(addressNames)
};
const blockUser = async (e, user?: string) => {
try {
const valUser = user || value
if (!valUser) return;
const isAddress = validateAddress(valUser);
let userName = null;
let userAddress = null;
if (isAddress) {
userAddress = valUser;
const name = await getNameInfo(valUser);
if (name) {
userName = name;
}
}
if (!isAddress) {
const response = await fetch(`${getBaseApiReact()}/names/${valUser}`);
const data = await response.json();
if (!data?.owner) throw new Error("Name does not exist");
if (data?.owner) {
userAddress = data.owner;
userName = valUser;
}
}
if(!userName){
await addToBlockList(userAddress, null);
fetchBlockedUsers();
setHasChanged(true);
executeEvent('updateChatMessagesWithBlocks', true)
setValue('')
return
}
const responseModal = await show({
userName,
userAddress,
});
if (responseModal === "both") {
await addToBlockList(userAddress, userName);
} else if (responseModal === "address") {
await addToBlockList(userAddress, null);
} else if (responseModal === "name") {
await addToBlockList(null, userName);
}
fetchBlockedUsers();
setHasChanged(true);
setValue('')
if(user){
setIsOpenBlockedModal(false)
}
if(responseModal === 'both' || responseModal === 'address'){
executeEvent('updateChatMessagesWithBlocks', true)
}
} catch (error) {
setOpenSnackGlobal(true);
setInfoSnackCustom({
type: "error",
message: error?.message || "Unable to block user",
});
}
};
const blockUserFromOutsideModalFunc = (e) => {
const user = e.detail?.user;
setIsOpenBlockedModal(true)
blockUser(null, user)
};
useEffect(() => {
subscribeToEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
return () => {
unsubscribeFromEvent("blockUserFromOutside", blockUserFromOutsideModalFunc);
};
}, []);
return ( return (
<Dialog <Dialog
open={isOpenBlockedModal} open={true}
aria-labelledby="alert-dialog-title" aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description" aria-describedby="alert-dialog-description"
> >
<DialogTitle>Blocked Users</DialogTitle> <DialogTitle>Blocked Users</DialogTitle>
<DialogContent <DialogContent sx={{
sx={{ padding: '20px'
padding: "20px", }}>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
}} }}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
}}
> >
<TextField <TextField
placeholder="Name or address" placeholder="Name"
value={value} value={value}
onChange={(e) => { onChange={(e) => {
setValue(e.target.value); setValue(e.target.value);
}} }}
/> />
<Button <Button variant="contained" onClick={async ()=> {
sx={{ try {
flexShrink: 0, if(!value) return
}} await addToBlockList(undefined, value)
variant="contained" fetchBlockedUsers()
onClick={blockUser} setHasChanged(true)
> } catch (error) {
Block console.error(error)
</Button> }
</Box> }}>Block</Button>
</Box>
{Object.entries(blockedUsers?.addresses).length > 0 && ( {Object.entries(blockedUsers?.addresses).length > 0 && (
<> <>
<Spacer height="20px" /> <Spacer height="20px" />
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Blocked addresses- blocks processing of txs Blocked Users for Chat ( addresses )
</DialogContentText> </DialogContentText>
<Spacer height="10px" /> <Spacer height="10px" />
<Button variant="contained" size="small" onClick={getNames}>Fetch names</Button>
<Spacer height="10px" />
</> </>
)} )}
<Box <Box sx={{
sx={{ display: 'flex',
display: "flex", flexDirection: 'column',
flexDirection: "column", gap: '10px'
gap: "10px", }}>
}}
>
{Object.entries(blockedUsers?.addresses || {})?.map( {Object.entries(blockedUsers?.addresses || {})?.map(
([key, value]) => { ([key, value]) => {
return ( return (
@ -203,22 +90,18 @@ export const BlockedUsersModal = () => {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "10px", gap: "10px",
width: "100%", width: '100%',
justifyContent: "space-between", justifyContent: 'space-between'
}} }}
> >
<Typography>{addressesWithNames[key] || key}</Typography> <Typography>{key}</Typography>
<Button <Button
sx={{
flexShrink: 0,
}}
size="small"
variant="contained" variant="contained"
onClick={async () => { onClick={async () => {
try { try {
await removeBlockFromList(key, undefined); await removeBlockFromList(key, undefined);
setHasChanged(true); setHasChanged(true);
setValue(""); setValue('')
fetchBlockedUsers(); fetchBlockedUsers();
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -236,19 +119,17 @@ export const BlockedUsersModal = () => {
<> <>
<Spacer height="20px" /> <Spacer height="20px" />
<DialogContentText id="alert-dialog-description"> <DialogContentText id="alert-dialog-description">
Blocked names for QDN Blocked Users for QDN and Chat (names)
</DialogContentText> </DialogContentText>
<Spacer height="10px" /> <Spacer height="10px" />
</> </>
)} )}
<Box <Box sx={{
sx={{ display: 'flex',
display: "flex", flexDirection: 'column',
flexDirection: "column", gap: '10px'
gap: "10px", }}>
}}
>
{Object.entries(blockedUsers?.names || {})?.map(([key, value]) => { {Object.entries(blockedUsers?.names || {})?.map(([key, value]) => {
return ( return (
<Box <Box
@ -256,16 +137,12 @@ export const BlockedUsersModal = () => {
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "10px", gap: "10px",
width: "100%", width: '100%',
justifyContent: "space-between", justifyContent: 'space-between'
}} }}
> >
<Typography>{key}</Typography> <Typography>{key}</Typography>
<Button <Button
size="small"
sx={{
flexShrink: 0,
}}
variant="contained" variant="contained"
onClick={async () => { onClick={async () => {
try { try {
@ -298,78 +175,16 @@ export const BlockedUsersModal = () => {
}, },
}} }}
variant="contained" variant="contained"
onClick={() => { onClick={()=> {
if (hasChanged) { if(hasChanged){
executeEvent("updateChatMessagesWithBlocks", true); executeEvent('updateChatMessagesWithBlocks', true)
} }
setIsOpenBlockedModal(false); close()
}} }}
> >
close close
</Button> </Button>
</DialogActions> </DialogActions>
<Dialog
open={isShow}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Decide what to block"}
</DialogTitle>
<IconButton
onClick={onCancel}
sx={{
position: 'absolute',
right: 8,
top: 8,
color: 'white',
}}
>
<CloseIcon />
</IconButton>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Blocking {message?.userName || message?.userAddress}
</DialogContentText>
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: '10px',
marginTop: '20px'
}}>
<InfoIcon sx={{
color: 'fff'
}}/> <Typography>Choose "block txs" or "all" to block chat messages </Typography>
</Box>
</DialogContent>
<DialogActions>
<Button
variant="contained"
onClick={() => {
onOk("address");
}}
>
Block txs
</Button>
<Button
variant="contained"
onClick={() => {
onOk("name");
}}
>
Block QDN data
</Button>
<Button
variant="contained"
onClick={() => {
onOk("both");
}}
>
Block All
</Button>
</DialogActions>
</Dialog>
</Dialog> </Dialog>
); );
}; };

View File

@ -19,8 +19,7 @@ import React, {
useRef, useRef,
useState, useState,
} from "react"; } from "react";
import PersonOffIcon from '@mui/icons-material/PersonOff'; import BlockIcon from '@mui/icons-material/Block';
import { WalletsAppWrapper } from "./WalletsAppWrapper"; import { WalletsAppWrapper } from "./WalletsAppWrapper";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
@ -67,7 +66,7 @@ import HomeIcon from "@mui/icons-material/Home";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { ThingsToDoInitial } from "./ThingsToDoInitial"; import { ThingsToDoInitial } from "./ThingsToDoInitial";
import { GroupJoinRequests, requestQueueGroupJoinRequests } from "./GroupJoinRequests"; import { GroupJoinRequests } from "./GroupJoinRequests";
import { GroupForum } from "../Chat/GroupForum"; import { GroupForum } from "../Chat/GroupForum";
import { GroupInvites } from "./GroupInvites"; import { GroupInvites } from "./GroupInvites";
import { import {
@ -100,7 +99,7 @@ import { formatEmailDate } from "./QMailMessages";
import { useHandleMobileNativeBack } from "../../hooks/useHandleMobileNativeBack"; import { useHandleMobileNativeBack } from "../../hooks/useHandleMobileNativeBack";
import { AdminSpace } from "../Chat/AdminSpace"; import { AdminSpace } from "../Chat/AdminSpace";
import { useRecoilState, useSetRecoilState } from "recoil"; import { useRecoilState, useSetRecoilState } from "recoil";
import { addressInfoControllerAtom, groupsPropertiesAtom, isOpenBlockedModalAtom, lastEnteredGroupIdAtom, myGroupsWhereIAmAdminAtom, selectedGroupIdAtom } from "../../atoms/global"; import { addressInfoControllerAtom, groupsPropertiesAtom, lastEnteredGroupIdAtom, selectedGroupIdAtom } from "../../atoms/global";
import { sortArrayByTimestampAndGroupName } from "../../utils/time"; import { sortArrayByTimestampAndGroupName } from "../../utils/time";
import { BlockedUsersModal } from "./BlockedUsersModal"; import { BlockedUsersModal } from "./BlockedUsersModal";
import { GlobalTouchMenu } from "../GlobalTouchMenu"; import { GlobalTouchMenu } from "../GlobalTouchMenu";
@ -330,17 +329,16 @@ export const getDataPublishesFunc = async (groupId, type) => {
}; };
export async function getNameInfo(address: string) { export async function getNameInfo(address: string) {
const response = await fetch(`${getBaseApiReact()}/names/primary/` + address); const response = await fetch(`${getBaseApiReact()}/names/address/` + address);
const nameData = await response.json(); const nameData = await response.json();
if (nameData?.name) { if (nameData?.length > 0) {
return nameData?.name; return nameData[0]?.name;
} else { } else {
return ''; return "";
} }
} }
export const getGroupAdmins = async (groupNumber: number) => { export const getGroupAdmins = async (groupNumber: number) => {
// const validApi = await findUsableApi(); // const validApi = await findUsableApi();
@ -472,9 +470,6 @@ export const Group = ({
const { setMemberGroups, memberGroups, rootHeight, isRunningPublicNode } = useContext(MyContext); const { setMemberGroups, memberGroups, rootHeight, isRunningPublicNode } = useContext(MyContext);
const lastGroupNotification = useRef<null | number>(null); const lastGroupNotification = useRef<null | number>(null);
const [timestampEnterData, setTimestampEnterData] = useState({}); const [timestampEnterData, setTimestampEnterData] = useState({});
const groupsPropertiesRef = useRef({});
const setMyGroupsWhereIAmAdmin = useSetRecoilState(myGroupsWhereIAmAdminAtom);
const [chatMode, setChatMode] = useState("groups"); const [chatMode, setChatMode] = useState("groups");
const [newChat, setNewChat] = useState(false); const [newChat, setNewChat] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false); const [openSnack, setOpenSnack] = React.useState(false);
@ -488,8 +483,6 @@ export const Group = ({
const [groupAnnouncements, setGroupAnnouncements] = React.useState({}); const [groupAnnouncements, setGroupAnnouncements] = React.useState({});
const [defaultThread, setDefaultThread] = React.useState(null); const [defaultThread, setDefaultThread] = React.useState(null);
const [isOpenDrawer, setIsOpenDrawer] = React.useState(false); const [isOpenDrawer, setIsOpenDrawer] = React.useState(false);
const setIsOpenBlockedUserModal = useSetRecoilState(isOpenBlockedModalAtom)
const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false); const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false);
const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState(""); const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState("");
const [drawerMode, setDrawerMode] = React.useState("groups"); const [drawerMode, setDrawerMode] = React.useState("groups");
@ -514,6 +507,7 @@ export const Group = ({
const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false) const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false)
const [groupsProperties, setGroupsProperties] = useRecoilState(groupsPropertiesAtom) const [groupsProperties, setGroupsProperties] = useRecoilState(groupsPropertiesAtom)
const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom); const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom);
const [isOpenBlockedUserModal, setIsOpenBlockedUserModal] = React.useState(false);
const setLastEnteredGroupIdAtom = useSetRecoilState(lastEnteredGroupIdAtom) const setLastEnteredGroupIdAtom = useSetRecoilState(lastEnteredGroupIdAtom)
const isPrivate = useMemo(()=> { const isPrivate = useMemo(()=> {
if(selectedGroup?.groupId === '0') return false if(selectedGroup?.groupId === '0') return false
@ -539,9 +533,6 @@ export const Group = ({
useEffect(()=> { useEffect(()=> {
timestampEnterDataRef.current = timestampEnterData timestampEnterDataRef.current = timestampEnterData
}, [timestampEnterData]) }, [timestampEnterData])
useEffect(() => {
groupsPropertiesRef.current = groupsProperties;
}, [groupsProperties]);
useEffect(() => { useEffect(() => {
isFocusedRef.current = isFocused; isFocusedRef.current = isFocused;
@ -581,7 +572,7 @@ export const Group = ({
}); });
} catch (error) { } catch (error) {
console.error(error); console.log("error", error);
} }
}; };
@ -848,7 +839,7 @@ export const Group = ({
const res = await fetch( const res = await fetch(
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ `${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${
publish.identifier publish.identifier
}?encoding=base64&rebuild=true` }?encoding=base64`
); );
data = await res.text(); data = await res.text();
} }
@ -994,50 +985,15 @@ export const Group = ({
} }
}, []) }, [])
const getGroupsWhereIAmAMember = useCallback(async (groups) => {
try {
let groupsAsAdmin = [];
const getAllGroupsAsAdmin = groups
.filter((item) => item.groupId !== '0')
.map(async (group) => {
const isAdminResponse = await requestQueueGroupJoinRequests.enqueue(
() => {
return fetch(
`${getBaseApiReact()}/groups/members/${group.groupId}?limit=0&onlyAdmins=true`
);
}
);
const isAdminData = await isAdminResponse.json();
const findMyself = isAdminData?.members?.find( useEffect(()=> {
(member) => member.member === myAddress if(!myAddress) return
); if(areKeysEqual(groups?.map((grp)=> grp?.groupId), Object.keys(groupsProperties))){
} else {
if (findMyself) { getGroupsProperties(myAddress)
groupsAsAdmin.push(group);
}
return true;
});
await Promise.all(getAllGroupsAsAdmin);
setMyGroupsWhereIAmAdmin(groupsAsAdmin);
} catch (error) {
console.error();
} }
}, []); }, [groups, myAddress])
useEffect(() => {
if (!myAddress) return;
if (
!areKeysEqual(
groups?.map((grp) => grp?.groupId),
Object.keys(groupsPropertiesRef.current)
)
) {
getGroupsProperties(myAddress);
getGroupsWhereIAmAMember(groups);
}
}, [groups, myAddress]);
useEffect(() => { useEffect(() => {
// Handler function for incoming messages // Handler function for incoming messages
@ -1949,7 +1905,6 @@ export const Group = ({
width: "100%", width: "100%",
justifyContent: "center", justifyContent: "center",
padding: "10px", padding: "10px",
gap: '10px'
}} }}
> >
<CustomButton <CustomButton
@ -1967,23 +1922,6 @@ export const Group = ({
/> />
New Chat New Chat
</CustomButton> </CustomButton>
{!isRunningPublicNode && (
<CustomButton
onClick={() => {
setIsOpenBlockedUserModal(true);
}}
sx={{
minWidth: 'unset',
padding: '10px',
}}
>
<PersonOffIcon
sx={{
color: 'white',
}}
/>
</CustomButton>
)}
</div> </div>
</div> </div>
); );
@ -2221,7 +2159,7 @@ export const Group = ({
padding: '10px' padding: '10px'
}} }}
> >
<PersonOffIcon <BlockIcon
sx={{ sx={{
color: "white", color: "white",
}} }}
@ -2718,9 +2656,11 @@ export const Group = ({
)} )}
</> </>
)} )}
{isOpenBlockedUserModal && (
<BlockedUsersModal /> <BlockedUsersModal close={()=> {
setIsOpenBlockedUserModal(false)
}} />
)}
{selectedDirect && !newChat && ( {selectedDirect && !newChat && (
<> <>
<Box <Box
@ -2816,7 +2756,7 @@ export const Group = ({
/> />
)} )}
{isMobile && ( {isMobile && (
<Apps mode={appsMode} setMode={setAppsMode} show={mobileViewMode === "apps"} myName={userInfo?.name} myAddress={userInfo?.address} /> <Apps mode={appsMode} setMode={setAppsMode} show={mobileViewMode === "apps"} myName={userInfo?.name} />
)} )}
{!isMobile && ( {!isMobile && (
<AppsDesktop toggleSideViewGroups={toggleSideViewGroups} toggleSideViewDirects={toggleSideViewDirects} goToHome={goToHome} mode={appsMode} setMode={setAppsMode} setDesktopSideView={setDesktopSideView} hasUnreadDirects={directChatHasUnread} show={desktopViewMode === "apps"} myName={userInfo?.name} isGroups={isOpenSideViewGroups} <AppsDesktop toggleSideViewGroups={toggleSideViewGroups} toggleSideViewDirects={toggleSideViewDirects} goToHome={goToHome} mode={appsMode} setMode={setAppsMode} setDesktopSideView={setDesktopSideView} hasUnreadDirects={directChatHasUnread} show={desktopViewMode === "apps"} myName={userInfo?.name} isGroups={isOpenSideViewGroups}

View File

@ -17,7 +17,7 @@ import { CustomLoader } from "../../common/CustomLoader";
import { getBaseApi } from "../../background"; import { getBaseApi } from "../../background";
import { MyContext, getBaseApiReact, isMobile } from "../../App"; import { MyContext, getBaseApiReact, isMobile } from "../../App";
import { myGroupsWhereIAmAdminAtom } from "../../atoms/global"; import { myGroupsWhereIAmAdminAtom } from "../../atoms/global";
import { useRecoilState, useSetRecoilState } from "recoil"; import { useSetRecoilState } from "recoil";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2) export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2)
@ -28,44 +28,66 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([]) const [groupsWithJoinRequests, setGroupsWithJoinRequests] = React.useState([])
const [loading, setLoading] = React.useState(true) const [loading, setLoading] = React.useState(true)
const {txList, setTxList} = React.useContext(MyContext) const {txList, setTxList} = React.useContext(MyContext)
const [myGroupsWhereIAmAdmin] = useRecoilState(myGroupsWhereIAmAdminAtom); const setMyGroupsWhereIAmAdmin = useSetRecoilState(
myGroupsWhereIAmAdminAtom
);
const getJoinRequests = async ()=> {
const getJoinRequests = async () => {
try { try {
setLoading(true); setLoading(true)
const res = await Promise.all(
myGroupsWhereIAmAdmin.map(async (group) => {
const joinRequestResponse =
await requestQueueGroupJoinRequests.enqueue(() => {
return fetch(
`${getBaseApiReact()}/groups/joinrequests/${group.groupId}`
);
});
const joinRequestData = await joinRequestResponse.json(); let groupsAsAdmin = []
return { const getAllGroupsAsAdmin = groups.filter((item)=> item.groupId !== '0').map(async (group)=> {
group,
data: joinRequestData, const isAdminResponse = await requestQueueGroupJoinRequests.enqueue(()=> {
}; return fetch(
`${getBaseApiReact()}/groups/members/${group.groupId}?limit=0&onlyAdmins=true`
);
}) })
); const isAdminData = await isAdminResponse.json()
setGroupsWithJoinRequests(res);
const findMyself = isAdminData?.members?.find((member)=> member.member === myAddress)
if(findMyself){
groupsAsAdmin.push(group)
}
return true
})
await Promise.all(getAllGroupsAsAdmin)
setMyGroupsWhereIAmAdmin(groupsAsAdmin)
const res = await Promise.all(groupsAsAdmin.map(async (group)=> {
const joinRequestResponse = await requestQueueGroupJoinRequests.enqueue(()=> {
return fetch(
`${getBaseApiReact()}/groups/joinrequests/${group.groupId}`
);
})
const joinRequestData = await joinRequestResponse.json()
return {
group,
data: joinRequestData
}
}))
setGroupsWithJoinRequests(res)
} catch (error) { } catch (error) {
console.log(error);
} finally { } finally {
setLoading(false); setLoading(false)
} }
}; }
React.useEffect(() => { React.useEffect(() => {
if (myAddress && myGroupsWhereIAmAdmin.length > 0) { if (myAddress && groups.length > 0) {
getJoinRequests(); getJoinRequests()
} else { } else {
setLoading(false); setLoading(false)
} }
}, [myAddress, myGroupsWhereIAmAdmin]); }, [myAddress, groups]);
const filteredJoinRequests = React.useMemo(()=> { const filteredJoinRequests = React.useMemo(()=> {
return groupsWithJoinRequests.map((group)=> { return groupsWithJoinRequests.map((group)=> {

View File

@ -226,7 +226,6 @@ export const ListOfGroupPromotions = () => {
data: data, data: data,
identifier: identifier, identifier: identifier,
service: "DOCUMENT", service: "DOCUMENT",
uploadType: 'base64',
}) })
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {

View File

@ -54,19 +54,35 @@ export const NewUsersCTA = ({ balance }) => {
textDecoration: "underline", textDecoration: "underline",
}} }}
onClick={() => { onClick={() => {
window.open("https://link.qortal.dev/support", '_system') if (chrome && chrome.tabs) {
chrome.tabs.create({ url: "https://link.qortal.dev/telegram-invite" }, (tab) => {
if (chrome.runtime.lastError) {
console.error("Error opening tab:", chrome.runtime.lastError);
} else {
console.log("Tab opened successfully:", tab);
}
});
}
}} }}
> >
Nextcloud Telegram
</ButtonBase> </ButtonBase>
<ButtonBase <ButtonBase
sx={{ sx={{
textDecoration: "underline", textDecoration: "underline",
}} }}
onClick={() => { onClick={() => {
window.open("https://link.qortal.dev/discord-invite", '_system') if (chrome && chrome.tabs) {
chrome.tabs.create({ url: "https://link.qortal.dev/discord-invite" }, (tab) => {
if (chrome.runtime.lastError) {
console.error("Error opening tab:", chrome.runtime.lastError);
} else {
console.log("Tab opened successfully:", tab);
}
});
}
}} }}
> >
Discord Discord
</ButtonBase> </ButtonBase>

View File

@ -67,7 +67,6 @@ const [isLoading, setIsLoading] = useState(false)
data: avatarBase64, data: avatarBase64,
identifier: "qortal_avatar", identifier: "qortal_avatar",
service: "THUMBNAIL", service: "THUMBNAIL",
uploadType: 'base64',
}) })
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {

View File

@ -89,14 +89,14 @@ export const Minting = ({
const getName = async (address) => { const getName = async (address) => {
try { try {
const response = await fetch( const response = await fetch(
`${getBaseApiReact()}/names/primary/${address}` `${getBaseApiReact()}/names/address/${address}`
); );
const nameData = await response.json(); const nameData = await response.json();
if (nameData?.name) { if (nameData?.length > 0) {
setNames((prev) => { setNames((prev) => {
return { return {
...prev, ...prev,
[address]: nameData?.name, [address]: nameData[0].name,
}; };
}); });
} else { } else {
@ -108,7 +108,7 @@ export const Minting = ({
}); });
} }
} catch (error) { } catch (error) {
console.log(error); // error
} }
}; };

View File

@ -57,13 +57,12 @@ export const RegisterName = ({setOpenSnack, setInfoSnack, userInfo, show, setTxL
try { try {
const res = await fetch(`${getBaseApiReact()}/names/` + name); const res = await fetch(`${getBaseApiReact()}/names/` + name);
const data = await res.json() const data = await res.json()
if(data?.message === 'name unknown' || data?.error){ if(data?.message === 'name unknown'){
setIsNameAvailable(Availability.AVAILABLE) setIsNameAvailable(Availability.AVAILABLE)
} else { } else {
setIsNameAvailable(Availability.NOT_AVAILABLE) setIsNameAvailable(Availability.NOT_AVAILABLE)
} }
} catch (error) { } catch (error) {
setIsNameAvailable(Availability.AVAILABLE)
console.error(error) console.error(error)
} finally { } finally {
} }

View File

@ -155,7 +155,6 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
data: encryptData, data: encryptData,
identifier: "ext_saved_settings", identifier: "ext_saved_settings",
service: "DOCUMENT_PRIVATE", service: "DOCUMENT_PRIVATE",
uploadType: 'base64',
}) })
.then((response) => { .then((response) => {
if (!response?.error) { if (!response?.error) {

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { DrawerUserLookup } from "../Drawer/DrawerUserLookup"; import { DrawerUserLookup } from "../Drawer/DrawerUserLookup";
import { import {
Avatar, Avatar,
@ -16,7 +16,6 @@ import {
Typography, Typography,
Table, Table,
CircularProgress, CircularProgress,
Autocomplete,
} from "@mui/material"; } from "@mui/material";
import { getAddressInfo, getNameOrAddress } from "../../background"; import { getAddressInfo, getNameOrAddress } from "../../background";
import { getBaseApiReact } from "../../App"; import { getBaseApiReact } from "../../App";
@ -27,8 +26,6 @@ import { formatTimestamp } from "../../utils/time";
import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen'; import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import { useNameSearch } from "../../hooks/useNameSearch";
import { validateAddress } from "../../utils/validateAddress";
function formatAddress(str) { function formatAddress(str) {
if (str.length <= 12) return str; if (str.length <= 12) return str;
@ -41,13 +38,6 @@ function formatAddress(str) {
export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
const [nameOrAddress, setNameOrAddress] = useState(""); const [nameOrAddress, setNameOrAddress] = useState("");
const [inputValue, setInputValue] = useState('');
const { results, isLoading } = useNameSearch(inputValue);
const options = useMemo(() => {
const isAddress = validateAddress(inputValue);
if (isAddress) return [inputValue];
return results?.map((item) => item.name);
}, [results, inputValue]);
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState("");
const [addressInfo, setAddressInfo] = useState(null); const [addressInfo, setAddressInfo] = useState(null);
const [isLoadingUser, setIsLoadingUser] = useState(false); const [isLoadingUser, setIsLoadingUser] = useState(false);
@ -68,10 +58,7 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
if (!addressInfoRes?.publicKey) { if (!addressInfoRes?.publicKey) {
throw new Error("Address does not exist on blockchain"); throw new Error("Address does not exist on blockchain");
} }
const isAddress = validateAddress(messageAddressOrName); const name = await getNameInfo(owner);
const name = !isAddress
? messageAddressOrName
: await getNameInfo(owner);
const balanceRes = await fetch( const balanceRes = await fetch(
`${getBaseApiReact()}/addresses/balance/${owner}` `${getBaseApiReact()}/addresses/balance/${owner}`
); );
@ -119,7 +106,6 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
setIsOpenDrawerLookup(false) setIsOpenDrawerLookup(false)
setNameOrAddress('') setNameOrAddress('')
setErrorMessage('') setErrorMessage('')
setInputValue('');
setPayments([]) setPayments([])
setIsLoadingUser(false) setIsLoadingUser(false)
setIsLoadingPayments(false) setIsLoadingPayments(false)
@ -148,66 +134,27 @@ export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => {
flexShrink: 0, flexShrink: 0,
}} }}
> >
<Autocomplete <TextField
autoFocus
value={nameOrAddress} value={nameOrAddress}
onChange={(event: any, newValue: string | null) => { onChange={(e) => setNameOrAddress(e.target.value)}
if (!newValue) {
setNameOrAddress('');
return;
}
setNameOrAddress(newValue);
lookupFunc(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
loading={isLoading}
options={options}
sx={{ width: 300 }}
size="small" size="small"
renderInput={(params) => ( placeholder="Address or Name"
<TextField autoComplete="off"
autoFocus onKeyDown={(e) => {
autoComplete="off" if (e.key === "Enter" && nameOrAddress) {
{...params} lookupFunc();
label="Address or Name" }
onKeyDown={(e) => { }}
if (e.key === 'Enter' && inputValue) {
lookupFunc(inputValue);
}
}}
sx={{
'& .MuiOutlinedInput-root': {
'& fieldset': {
borderColor: 'white',
},
'&:hover fieldset': {
borderColor: 'white',
},
'&.Mui-focused fieldset': {
borderColor: 'white',
},
'& input': {
color: 'white',
},
},
'& .MuiInputLabel-root': {
color: 'white',
},
'& .MuiInputLabel-root.Mui-focused': {
color: 'white',
},
'& .MuiAutocomplete-endAdornment svg': {
color: 'white',
},
}}
/>
)}
/> />
<ButtonBase onClick={()=> {
lookupFunc();
}} >
<SearchIcon sx={{
color: 'white',
marginRight: '20px'
}} />
</ButtonBase>
<ButtonBase sx={{ <ButtonBase sx={{
marginLeft: 'auto', marginLeft: 'auto',

View File

@ -167,9 +167,12 @@ useEffect(()=> {
onClick={async () => { onClick={async () => {
try { try {
setIsLoading(true) setIsLoading(true)
executeEvent("blockUserFromOutside", { if(isAlreadyBlocked === true){
user: address await removeBlockFromList(address, name)
}) } else if(isAlreadyBlocked === false) {
await addToBlockList(address, name)
}
executeEvent('updateChatMessagesWithBlocks', true)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally { } finally {

View File

@ -173,6 +173,3 @@ const STATIC_BCRYPT_SALT = `$${BCRYPT_VERSION}$${BCRYPT_ROUNDS}$IxVE941tXVUD4cW0
const KDF_THREADS = 16 const KDF_THREADS = 16
export { TX_TYPES, ERROR_CODES, QORT_DECIMALS, PROXY_URL, STATIC_SALT, ADDRESS_VERSION, KDF_THREADS, STATIC_BCRYPT_SALT, CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP, DYNAMIC_FEE_TIMESTAMP } export { TX_TYPES, ERROR_CODES, QORT_DECIMALS, PROXY_URL, STATIC_SALT, ADDRESS_VERSION, KDF_THREADS, STATIC_BCRYPT_SALT, CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP, DYNAMIC_FEE_TIMESTAMP }
export const MAX_SIZE_PUBLIC_NODE = 500 * 1024 * 1024; // 500mb
export const MAX_SIZE_PUBLISH = 2000 * 1024 * 1024; // 2GB

View File

@ -1,55 +0,0 @@
import { useCallback, useEffect, useState } from 'react';
import { getBaseApiReact } from '../App';
interface NameListItem {
name: string;
address: string;
}
export const useNameSearch = (value: string, limit = 20) => {
const [nameList, setNameList] = useState<NameListItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
const checkIfNameExisits = useCallback(
async (name: string, listLimit: number) => {
try {
if (!name) {
setNameList([]);
return;
}
const res = await fetch(
`${getBaseApiReact()}/names/search?query=${name}&prefix=true&limit=${listLimit}`
);
const data = await res.json();
setNameList(
data?.map((item: any) => {
return {
name: item.name,
address: item.owner,
};
})
);
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
},
[]
);
// Debounce logic
useEffect(() => {
setIsLoading(true);
const handler = setTimeout(() => {
checkIfNameExisits(value, limit);
}, 500);
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
clearTimeout(handler);
};
}, [value, limit, checkIfNameExisits]);
return {
isLoading,
results: nameList,
};
};

View File

@ -1,11 +0,0 @@
import { useMemo } from 'react';
export function useSortedMyNames(names, myName) {
return useMemo(() => {
return [...names].sort((a, b) => {
if (a === myName) return -1;
if (b === myName) return 1;
return 0;
});
}, [names, myName]);
}

View File

@ -24,7 +24,7 @@ window.addEventListener("message", (event) => {
} }
}); });
export const sendMessageBackground = (action, data = {}, timeout = 600000, isExtension, appInfo, skipAuth) => { export const sendMessageBackground = (action, data = {}, timeout = 180000, isExtension, appInfo, skipAuth) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const requestId = generateRequestId(); // Unique ID for each request const requestId = generateRequestId(); // Unique ID for each request
callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks

View File

@ -1,474 +1,265 @@
// @ts-nocheck // @ts-nocheck
import { Buffer } from 'buffer'; 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 } from "../../background";
import { getData } from '../../utils/chromeStorage'; import { getData } from "../../utils/chromeStorage";
import { executeEvent } from '../../utils/events';
export async function reusableGet(endpoint) { export async function reusableGet(endpoint){
const validApi = await getBaseApi(); const validApi = await getBaseApi();
const response = await fetch(validApi + endpoint); const response = await fetch(validApi + endpoint);
const data = await response.json(); const data = await response.json();
return data; return data
}
async function reusablePost(endpoint, _body) {
// const validApi = await findUsableApi();
const url = await createEndpoint(endpoint);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: _body,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText);
} }
let data;
async function reusablePost(endpoint, _body){
// const validApi = await findUsableApi();
const url = await createEndpoint(endpoint)
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: _body
});
let data
try { try {
data = await response.clone().json(); data = await response.clone().json()
} catch (e) { } catch (e) {
data = await response.text(); data = await response.text()
} }
return data; return data
}
async function reusablePostStream(endpoint, _body) {
const url = await createEndpoint(endpoint);
const headers = {};
const response = await fetch(url, {
method: 'POST',
headers,
body: _body,
});
return response; // return the actual response so calling code can use response.ok
}
async function uploadChunkWithRetry(endpoint, formData, index, maxRetries = 3) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await reusablePostStream(endpoint, formData);
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText);
}
return; // Success
} catch (err) {
attempt++;
console.warn(
`Chunk ${index} failed (attempt ${attempt}): ${err.message}`
);
if (attempt >= maxRetries) {
throw new Error(`Chunk ${index} failed after ${maxRetries} attempts`);
}
// Wait 10 seconds before next retry
await new Promise((res) => setTimeout(res, 25_000));
}
} }
}
async function resuablePostRetry(
endpoint,
body,
maxRetries = 3,
appInfo,
resourceInfo
) {
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await reusablePost(endpoint, body);
return response;
} catch (err) {
attempt++;
if (attempt >= maxRetries) {
throw new Error(
err instanceof Error
? err?.message || `Failed to make request`
: `Failed to make request`
);
}
if (appInfo?.tabId && resourceInfo) {
executeEvent('receiveChunks', {
tabId: appInfo.tabId,
publishLocation: {
name: resourceInfo?.name,
identifier: resourceInfo?.identifier,
service: resourceInfo?.service,
},
retry: true,
});
}
// Wait 10 seconds before next retry
await new Promise((res) => setTimeout(res, 25_000));
}
}
}
async function getKeyPair() { async function getKeyPair() {
const res = await getData<any>('keyPair').catch(() => null); const res = await getData<any>("keyPair").catch(() => null);
if (res) { if (res) {
return res; return res
} else { } else {
throw new Error('Wallet not authenticated'); throw new Error("Wallet not authenticated");
}
} }
}
export const publishData = async ({ export const publishData = async ({
registeredName, registeredName,
data, file,
service, service,
identifier, identifier,
uploadType, uploadType,
filename, isBase64,
withFee, filename,
title, withFee,
description, title,
category, description,
tag1, category,
tag2, tag1,
tag3, tag2,
tag4, tag3,
tag5, tag4,
feeAmount, tag5,
appInfo feeAmount
}: any) => { }: any) => {
const validateName = async (receiverName: string) => {
return await reusableGet(`/names/${receiverName}`);
};
const convertBytesForSigning = async (transactionBytesBase58: string) => { const validateName = async (receiverName: string) => {
return await resuablePostRetry( return await reusableGet(`/names/${receiverName}`)
'/transactions/convert', }
transactionBytesBase58,
3,
appInfo,
{ identifier, name: registeredName, service }
);
};
const getArbitraryFee = async () => { const convertBytesForSigning = async (transactionBytesBase58: string) => {
const timestamp = Date.now(); return await reusablePost('/transactions/convert', transactionBytesBase58)
}
let fee = await reusableGet( const getArbitraryFee = async () => {
`/transactions/unitfee?txType=ARBITRARY&timestamp=${timestamp}` const timestamp = Date.now()
);
return { let fee = await reusableGet(`/transactions/unitfee?txType=ARBITRARY&timestamp=${timestamp}`)
timestamp,
fee: Number(fee),
feeToShow: (Number(fee) / 1e8).toFixed(8),
};
};
const signArbitraryWithFee = ( return {
arbitraryBytesBase58, timestamp,
arbitraryBytesForSigningBase58, fee: Number(fee),
keyPair feeToShow: (Number(fee) / 1e8).toFixed(8)
) => { }
if (!arbitraryBytesBase58) { }
throw new Error('ArbitraryBytesBase58 not defined');
const signArbitraryWithFee = (arbitraryBytesBase58, arbitraryBytesForSigningBase58, keyPair) => {
if (!arbitraryBytesBase58) {
throw new Error('ArbitraryBytesBase58 not defined')
}
if (!keyPair) {
throw new Error('keyPair not defined')
}
const arbitraryBytes = Base58.decode(arbitraryBytesBase58)
const _arbitraryBytesBuffer = Object.keys(arbitraryBytes).map(function (key) { return arbitraryBytes[key]; })
const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer)
const arbitraryBytesForSigning = Base58.decode(arbitraryBytesForSigningBase58)
const _arbitraryBytesForSigningBuffer = Object.keys(arbitraryBytesForSigning).map(function (key) { return arbitraryBytesForSigning[key]; })
const arbitraryBytesForSigningBuffer = new Uint8Array(_arbitraryBytesForSigningBuffer)
const signature = nacl.sign.detached(arbitraryBytesForSigningBuffer, keyPair.privateKey)
return utils.appendBuffer(arbitraryBytesBuffer, signature)
} }
if (!keyPair) { const processTransactionVersion2 = async (bytes) => {
throw new Error('keyPair not defined');
}
const arbitraryBytes = Base58.decode(arbitraryBytesBase58); return await reusablePost('/transactions/process?apiVersion=2', Base58.encode(bytes))
const _arbitraryBytesBuffer = Object.keys(arbitraryBytes).map( }
function (key) {
return arbitraryBytes[key];
}
);
const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer);
const arbitraryBytesForSigning = Base58.decode(
arbitraryBytesForSigningBase58
);
const _arbitraryBytesForSigningBuffer = Object.keys(
arbitraryBytesForSigning
).map(function (key) {
return arbitraryBytesForSigning[key];
});
const arbitraryBytesForSigningBuffer = new Uint8Array(
_arbitraryBytesForSigningBuffer
);
const signature = nacl.sign.detached(
arbitraryBytesForSigningBuffer,
keyPair.privateKey
);
return utils.appendBuffer(arbitraryBytesBuffer, signature); const signAndProcessWithFee = async (transactionBytesBase58: string) => {
}; let convertedBytesBase58 = await convertBytesForSigning(
transactionBytesBase58
)
const processTransactionVersion2 = async (bytes) => { if (convertedBytesBase58.error) {
return await resuablePostRetry( throw new Error('Error when signing')
'/transactions/process?apiVersion=2', }
Base58.encode(bytes),
3,
appInfo,
{ identifier, name: registeredName, service }
);
};
const signAndProcessWithFee = async (transactionBytesBase58: string) => {
let convertedBytesBase58 = await convertBytesForSigning(
transactionBytesBase58
);
if (convertedBytesBase58.error) { const resKeyPair = await getKeyPair()
throw new Error('Error when signing'); const parsedData = resKeyPair
} const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey
};
const resKeyPair = await getKeyPair(); let signedArbitraryBytes = signArbitraryWithFee(transactionBytesBase58, convertedBytesBase58, keyPair)
const parsedData = resKeyPair; const response = await processTransactionVersion2(signedArbitraryBytes)
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
const uint8PublicKey = Base58.decode(parsedData.publicKey);
const keyPair = {
privateKey: uint8PrivateKey,
publicKey: uint8PublicKey,
};
let signedArbitraryBytes = signArbitraryWithFee( let myResponse = { error: '' }
transactionBytesBase58,
convertedBytesBase58,
keyPair
);
const response = await processTransactionVersion2(signedArbitraryBytes);
let myResponse = { error: '' }; if (response === false) {
throw new Error('Error when signing')
} else {
myResponse = response
}
if (response === false) { return myResponse
throw new Error('Error when signing'); }
} else {
myResponse = response;
}
if (appInfo?.tabId) {
executeEvent('receiveChunks', {
tabId: appInfo.tabId,
publishLocation: {
name: registeredName,
identifier,
service,
},
processed: true,
});
}
return myResponse;
};
const validate = async () => { const validate = async () => {
let validNameRes = await validateName(registeredName); let validNameRes = await validateName(registeredName)
if (validNameRes.error) { if (validNameRes.error) {
throw new Error('Name not found'); throw new Error('Name not found')
} }
let fee = null; let fee = null
if (withFee && feeAmount) { if (withFee && feeAmount) {
fee = feeAmount; fee = feeAmount
} else if (withFee) { } else if (withFee) {
const res = await getArbitraryFee(); const res = await getArbitraryFee()
if (res.fee) { if (res.fee) {
fee = res.fee; fee = res.fee
} else { } else {
throw new Error('unable to get fee'); throw new Error('unable to get fee')
} }
} }
let transactionBytes = await uploadData(registeredName, data, fee); let transactionBytes = await uploadData(registeredName, file, fee)
if (!transactionBytes || transactionBytes.error) { if (!transactionBytes || transactionBytes.error) {
throw new Error(transactionBytes?.message || 'Error when uploading'); throw new Error(transactionBytes?.message || 'Error when uploading')
} else if (transactionBytes.includes('Error 500 Internal Server Error')) { } else if (transactionBytes.includes('Error 500 Internal Server Error')) {
throw new Error('Error when uploading'); throw new Error('Error when uploading')
} }
let signAndProcessRes; let signAndProcessRes
if (withFee) { if (withFee) {
signAndProcessRes = await signAndProcessWithFee(transactionBytes); signAndProcessRes = await signAndProcessWithFee(transactionBytes)
} }
if (signAndProcessRes?.error) { if (signAndProcessRes?.error) {
throw new Error('Error when signing'); throw new Error('Error when signing')
} }
return signAndProcessRes; return signAndProcessRes
}; }
const uploadData = async (registeredName: string, data: any, fee: number) => { const uploadData = async (registeredName: string, file:any, fee: number) => {
let postBody = '';
let urlSuffix = '';
if (data != null) { let postBody = ''
if (uploadType === 'base64') { let urlSuffix = ''
urlSuffix = '/base64';
}
if (uploadType === 'base64') { if (file != null) {
postBody = data; // If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API
} if (uploadType === 'zip') {
} else { urlSuffix = '/zip'
throw new Error('No data provided'); }
}
let uploadDataUrl = `/arbitrary/${service}/${registeredName}`; // If we're sending file data, use the /base64 version of the POST /arbitrary/* API
let paramQueries = ''; else if (uploadType === 'file') {
if (identifier?.trim().length > 0) { urlSuffix = '/base64'
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`; }
}
paramQueries = paramQueries + `?fee=${fee}`; // Base64 encode the file to work around compatibility issues between javascript and java byte arrays
if (isBase64) {
postBody = file
}
if (filename != null && filename != 'undefined') { if (!isBase64) {
paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename); let fileBuffer = new Uint8Array(await file.arrayBuffer())
} postBody = Buffer.from(fileBuffer).toString("base64")
}
if (title != null && title != 'undefined') { }
paramQueries = paramQueries + '&title=' + encodeURIComponent(title);
}
if (description != null && description != 'undefined') { let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}`
paramQueries = if (identifier?.trim().length > 0) {
paramQueries + '&description=' + encodeURIComponent(description); uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}`
} }
if (category != null && category != 'undefined') { uploadDataUrl = uploadDataUrl + `?fee=${fee}`
paramQueries = paramQueries + '&category=' + encodeURIComponent(category);
}
if (tag1 != null && tag1 != 'undefined') {
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1);
}
if (tag2 != null && tag2 != 'undefined') { if (filename != null && filename != 'undefined') {
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2); uploadDataUrl = uploadDataUrl + '&filename=' + encodeURIComponent(filename)
} }
if (tag3 != null && tag3 != 'undefined') { if (title != null && title != 'undefined') {
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3); uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title)
} }
if (tag4 != null && tag4 != 'undefined') { if (description != null && description != 'undefined') {
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4); uploadDataUrl = uploadDataUrl + '&description=' + encodeURIComponent(description)
} }
if (tag5 != null && tag5 != 'undefined') { if (category != null && category != 'undefined') {
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag5); uploadDataUrl = uploadDataUrl + '&category=' + encodeURIComponent(category)
} }
if (uploadType === 'zip') {
paramQueries = paramQueries + '&isZip=' + true;
}
if (uploadType === 'base64') { if (tag1 != null && tag1 != 'undefined') {
if (urlSuffix) { uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1)
uploadDataUrl = uploadDataUrl + urlSuffix; }
}
uploadDataUrl = uploadDataUrl + paramQueries;
if (appInfo?.tabId) {
executeEvent('receiveChunks', {
tabId: appInfo.tabId,
publishLocation: {
name: registeredName,
identifier,
service,
},
chunksSubmitted: 1,
totalChunks: 1,
processed: false,
filename: filename || title || `${service}-${identifier || ''}`,
});
}
return await resuablePostRetry(uploadDataUrl, postBody, 3, appInfo, {
identifier,
name: registeredName,
service,
});
}
const file = data; if (tag2 != null && tag2 != 'undefined') {
const urlCheck = `/arbitrary/check/tmp?totalSize=${file.size}`; uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2)
}
const checkEndpoint = await createEndpoint(urlCheck); if (tag3 != null && tag3 != 'undefined') {
const checkRes = await fetch(checkEndpoint); uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3)
if (!checkRes.ok) { }
throw new Error('Not enough space on your hard drive');
}
const chunkUrl = uploadDataUrl + `/chunk`; if (tag4 != null && tag4 != 'undefined') {
const chunkSize = 5 * 1024 * 1024; // 5MB uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4)
}
const totalChunks = Math.ceil(file.size / chunkSize); if (tag5 != null && tag5 != 'undefined') {
if (appInfo?.tabId) { uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5)
executeEvent('receiveChunks', { }
tabId: appInfo.tabId,
publishLocation: {
name: registeredName,
identifier,
service,
},
chunksSubmitted: 0,
totalChunks,
processed: false,
filename:
file?.name || filename || title || `${service}-${identifier || ''}`,
});
}
for (let index = 0; index < totalChunks; index++) {
const start = index * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('chunk', chunk, file.name); // Optional: include filename
formData.append('index', index);
await uploadChunkWithRetry(chunkUrl, formData, index); return await reusablePost(uploadDataUrl, postBody)
if (appInfo?.tabId) {
executeEvent('receiveChunks', {
tabId: appInfo.tabId,
publishLocation: {
name: registeredName,
identifier,
service,
},
chunksSubmitted: index + 1,
totalChunks,
});
}
} }
const finalizeUrl = uploadDataUrl + `/finalize` + paramQueries;
const finalizeEndpoint = await createEndpoint(finalizeUrl); try {
return await validate()
const response = await fetch(finalizeEndpoint, { } catch (error: any) {
method: 'POST', throw new Error(error?.message)
headers: {}, }
}); }
if (!response?.ok) {
const errorText = await response.text();
throw new Error(`Finalize failed: ${errorText}`);
}
const result = await response.text(); // Base58-encoded unsigned transaction
return result;
};
try {
return await validate();
} catch (error: any) {
throw new Error(error?.message);
}
};

View File

@ -1,10 +1,9 @@
import { gateways, getApiKeyFromStorage, getNameInfoForOthers } from "./background"; import { gateways, getApiKeyFromStorage } from "./background";
import { listOfAllQortalRequests } from "./components/Apps/useQortalMessageListener"; import { listOfAllQortalRequests } from "./components/Apps/useQortalMessageListener";
import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, buyNameRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellNameRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, createSellOrder, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getArrrSyncStatus, getCrossChainServerInfo, getDaySummary, getForeignFee, getHostedData, getListItems, getNodeInfo, getNodeStatus, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, multiPaymentWithPrivateData, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sellNameRequest, sendChatMessage, sendCoin, setCurrentForeignServer, signForeignFees, signTransaction, transferAssetRequest, updateForeignFee, updateGroupRequest, updateNameRequest, voteOnPoll } from "./qortalRequests/get"; import { addForeignServer, addGroupAdminRequest, addListItems, adminAction, banFromGroupRequest, cancelGroupBanRequest, cancelGroupInviteRequest, cancelSellOrder, createAndCopyEmbedLink, createBuyOrder, createGroupRequest, createPoll, decryptAESGCMRequest, decryptData, decryptDataWithSharingKey, decryptQortalGroupData, deleteHostedData, deleteListItems, deployAt, encryptData, encryptDataWithSharingKey, encryptQortalGroupData, getArrrSyncStatus, getCrossChainServerInfo, getDaySummary, getForeignFee, getHostedData, getListItems, getNodeInfo, getNodeStatus, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getUserWalletTransactions, getWalletBalance, inviteToGroupRequest, joinGroup, kickFromGroupRequest, leaveGroupRequest, openNewTab, publishMultipleQDNResources, publishQDNResource, registerNameRequest, removeForeignServer, removeGroupAdminRequest, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, signTransaction, updateForeignFee, updateNameRequest, voteOnPoll } from "./qortalRequests/get";
import { getData, storeData } from "./utils/chromeStorage"; import { getData, storeData } from "./utils/chromeStorage";
import { executeEvent } from "./utils/events"; import { executeEvent } from "./utils/events";
import { ScreenOrientation } from '@capacitor/screen-orientation';
function getLocalStorage(key) { function getLocalStorage(key) {
@ -201,7 +200,7 @@ export const isRunningGateway = async ()=> {
case "PUBLISH_QDN_RESOURCE": { case "PUBLISH_QDN_RESOURCE": {
try { try {
const res = await publishQDNResource(request.payload, event.source, isFromExtension, appInfo); const res = await publishQDNResource(request.payload, event.source, isFromExtension);
event.source.postMessage({ event.source.postMessage({
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -221,7 +220,7 @@ export const isRunningGateway = async ()=> {
case "PUBLISH_MULTIPLE_QDN_RESOURCES": { case "PUBLISH_MULTIPLE_QDN_RESOURCES": {
try { try {
const res = await publishMultipleQDNResources(request.payload, event.source, isFromExtension, appInfo); const res = await publishMultipleQDNResources(request.payload, event.source, isFromExtension);
event.source.postMessage({ event.source.postMessage({
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -463,7 +462,7 @@ export const isRunningGateway = async ()=> {
case "UPDATE_FOREIGN_FEE": { case "UPDATE_FOREIGN_FEE": {
try { try {
const res = await updateForeignFee(request.payload, isFromExtension); const res = await updateForeignFee(request.payload);
event.source.postMessage({ event.source.postMessage({
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -503,7 +502,7 @@ export const isRunningGateway = async ()=> {
case "SET_CURRENT_FOREIGN_SERVER": { case "SET_CURRENT_FOREIGN_SERVER": {
try { try {
const res = await setCurrentForeignServer(request.payload, isFromExtension); const res = await setCurrentForeignServer(request.payload);
event.source.postMessage({ event.source.postMessage({
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -523,7 +522,7 @@ export const isRunningGateway = async ()=> {
case "ADD_FOREIGN_SERVER": { case "ADD_FOREIGN_SERVER": {
try { try {
const res = await addForeignServer(request.payload, isFromExtension); const res = await addForeignServer(request.payload);
event.source.postMessage({ event.source.postMessage({
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -543,7 +542,7 @@ export const isRunningGateway = async ()=> {
case "REMOVE_FOREIGN_SERVER": { case "REMOVE_FOREIGN_SERVER": {
try { try {
const res = await removeForeignServer(request.payload, isFromExtension); const res = await removeForeignServer(request.payload);
event.source.postMessage({ event.source.postMessage({
requestId: request.requestId, requestId: request.requestId,
action: request.action, action: request.action,
@ -621,32 +620,6 @@ export const isRunningGateway = async ()=> {
break; break;
} }
case 'CREATE_TRADE_SELL_ORDER': {
try {
const res = await createSellOrder(request.payload, isFromExtension);
event.source.postMessage(
{
requestId: request.requestId,
action: request.action,
payload: res,
type: 'backgroundMessageResponse',
},
event.origin
);
} catch (error) {
event.source.postMessage(
{
requestId: request.requestId,
action: request.action,
error: error.message,
type: 'backgroundMessageResponse',
},
event.origin
);
}
break;
}
case "CANCEL_TRADE_SELL_ORDER": { case "CANCEL_TRADE_SELL_ORDER": {
try { try {
const res = await cancelSellOrder(request.payload, isFromExtension); const res = await cancelSellOrder(request.payload, isFromExtension);
@ -1233,206 +1206,6 @@ export const isRunningGateway = async ()=> {
} }
break; break;
} }
case "UPDATE_GROUP" : {
try {
const res = await updateGroupRequest(request.payload, isFromExtension)
event.source.postMessage({
requestId: request.requestId,
action: request.action,
payload: res,
type: "backgroundMessageResponse",
}, event.origin);
} catch (error) {
event.source.postMessage({
requestId: request.requestId,
action: request.action,
error: error?.message,
type: "backgroundMessageResponse",
}, event.origin);
}
break;
}
case "BUY_NAME": {
try {
const res = await buyNameRequest(request.payload, isFromExtension);
event.source.postMessage({
requestId: request.requestId,
action: request.action,
payload: res,
type: "backgroundMessageResponse",
}, event.origin);
} catch (error) {
event.source.postMessage({
requestId: request.requestId,
action: request.action,
error: error.message,
type: "backgroundMessageResponse",
}, event.origin);
}
break;
}
case "SELL_NAME": {
try {
const res = await sellNameRequest(request.payload, isFromExtension);
event.source.postMessage({
requestId: request.requestId,
action: request.action,
payload: res,
type: "backgroundMessageResponse",
}, event.origin);
} catch (error) {
event.source.postMessage({
requestId: request.requestId,
action: request.action,
error: error.message,
type: "backgroundMessageResponse",
}, event.origin);
}
break;
}
case "CANCEL_SELL_NAME": {
try {
const res = await cancelSellNameRequest(request.payload, isFromExtension);
event.source.postMessage({
requestId: request.requestId,
action: request.action,
payload: res,
type: "backgroundMessageResponse",
}, event.origin);
} catch (error) {
event.source.postMessage({
requestId: request.requestId,
action: request.action,
error: error.message,
type: "backgroundMessageResponse",
}, event.origin);
}
break;
}
case "MULTI_ASSET_PAYMENT_WITH_PRIVATE_DATA" : {
try {
const res = await multiPaymentWithPrivateData(request.payload, isFromExtension)
event.source.postMessage({
requestId: request.requestId,
action: request.action,
payload: res,
type: "backgroundMessageResponse",
}, event.origin);
} catch (error) {
event.source.postMessage({
requestId: request.requestId,
action: request.action,
error: error?.message,
type: "backgroundMessageResponse",
}, event.origin);
}
break;
}
case "TRANSFER_ASSET" : {
try {
const res = await transferAssetRequest(request.payload, isFromExtension)
event.source.postMessage({
requestId: request.requestId,
action: request.action,
payload: res,
type: "backgroundMessageResponse",
}, event.origin);
} catch (error) {
event.source.postMessage({
requestId: request.requestId,
action: request.action,
error: error?.message,
type: "backgroundMessageResponse",
}, event.origin);
}
break;
}
case 'SIGN_FOREIGN_FEES': {
try {
const res = await signForeignFees(request.payload, isFromExtension);
event.source.postMessage(
{
requestId: request.requestId,
action: request.action,
payload: res,
type: 'backgroundMessageResponse',
},
event.origin
);
} catch (error) {
event.source.postMessage(
{
requestId: request.requestId,
action: request.action,
error: error.message,
type: 'backgroundMessageResponse',
},
event.origin
);
}
break;
}
case 'GET_PRIMARY_NAME': {
try {
const res = await getNameInfoForOthers(request.payload?.address);
const resData = res ? res : "";
event.source.postMessage(
{
requestId: request.requestId,
action: request.action,
payload: resData,
type: 'backgroundMessageResponse',
},
event.origin
);
} catch (error) {
event.source.postMessage(
{
requestId: request.requestId,
action: request.action,
error: error.message,
type: 'backgroundMessageResponse',
},
event.origin
);
}
break;
}
case 'SCREEN_ORIENTATION': {
try {
const mode = request.payload?.mode
if(mode === 'unlock'){
await ScreenOrientation.unlock();
} else {
await ScreenOrientation.lock({ orientation: mode });
}
event.source.postMessage(
{
requestId: request.requestId,
action: request.action,
payload: true,
type: 'backgroundMessageResponse',
},
event.origin
);
} catch (error) {
event.source.postMessage(
{
requestId: request.requestId,
action: request.action,
error: error.message,
type: 'backgroundMessageResponse',
},
event.origin
);
}
break;
}
default: default:
break; break;
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +0,0 @@
// @ts-nocheck
import { QORT_DECIMALS } from "../constants/constants"
import TransactionBase from "./TransactionBase"
export default class BuyNameTransacion extends TransactionBase {
constructor() {
super()
this.type = 7
}
set fee(fee) {
this._fee = fee * QORT_DECIMALS
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
}
set name(name) {
this.nameText = name
this._nameBytes = this.constructor.utils.stringtoUTF8Array(name)
this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length)
}
set sellPrice(sellPrice) {
this._sellPrice = sellPrice * QORT_DECIMALS
this._sellPriceBytes = this.constructor.utils.int64ToBytes(this._sellPrice)
}
set recipient(recipient) {
this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient)
this.theRecipient = recipient
}
get params() {
const params = super.params
params.push(
this._nameLength,
this._nameBytes,
this._sellPriceBytes,
this._recipient,
this._feeBytes
)
return params
}
}

View File

@ -1,33 +0,0 @@
// @ts-nocheck
import { QORT_DECIMALS } from "../constants/constants"
import TransactionBase from "./TransactionBase"
export default class CancelSellNameTransacion extends TransactionBase {
constructor() {
super()
this.type = 6
}
set fee(fee) {
this._fee = fee * QORT_DECIMALS
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
}
set name(name) {
this.nameText = name
this._nameBytes = this.constructor.utils.stringtoUTF8Array(name)
this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length)
}
get params() {
const params = super.params
params.push(
this._nameLength,
this._nameBytes,
this._feeBytes
)
return params
}
}

View File

@ -1,40 +0,0 @@
// @ts-nocheck
import { QORT_DECIMALS } from "../constants/constants"
import TransactionBase from "./TransactionBase"
export default class SellNameTransacion extends TransactionBase {
constructor() {
super()
this.type = 5
}
set fee(fee) {
this._fee = fee * QORT_DECIMALS
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
}
set name(name) {
this.nameText = name
this._nameBytes = this.constructor.utils.stringtoUTF8Array(name)
this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length)
}
set sellPrice(sellPrice) {
this.showSellPrice = sellPrice
this._sellPrice = sellPrice * QORT_DECIMALS
this._sellPriceBytes = this.constructor.utils.int64ToBytes(this._sellPrice)
}
get params() {
const params = super.params
params.push(
this._nameLength,
this._nameBytes,
this._sellPriceBytes,
this._feeBytes
)
return params
}
}

View File

@ -1,35 +0,0 @@
// @ts-nocheck
import { QORT_DECIMALS } from '../constants/constants'
import TransactionBase from './TransactionBase'
export default class TransferAssetTransaction extends TransactionBase {
constructor() {
super()
this.type = 12
}
set recipient(recipient) {
this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient)
}
set amount(amount) {
this._amount = Math.round(amount * QORT_DECIMALS)
this._amountBytes = this.constructor.utils.int64ToBytes(this._amount)
}
set assetId(assetId) {
this._assetId = this.constructor.utils.int64ToBytes(assetId)
}
get params() {
const params = super.params
params.push(
this._recipient,
this._assetId,
this._amountBytes,
this._feeBytes
)
return params
}
}

View File

@ -1,62 +0,0 @@
// @ts-nocheck
import { QORT_DECIMALS } from "../constants/constants";
import TransactionBase from "./TransactionBase";
export default class UpdateGroupTransaction extends TransactionBase {
constructor() {
super()
this.type = 23
}
set fee(fee) {
this._fee = fee * QORT_DECIMALS
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
}
set newOwner(newOwner) {
this._newOwner = newOwner instanceof Uint8Array ? newOwner : this.constructor.Base58.decode(newOwner)
}
set newIsOpen(newIsOpen) {
this._rGroupType = new Uint8Array(1)
this._rGroupType[0] = newIsOpen
}
set newDescription(newDescription) {
this._rGroupDescBytes = this.constructor.utils.stringtoUTF8Array(newDescription.toLocaleLowerCase())
this._rGroupDescLength = this.constructor.utils.int32ToBytes(this._rGroupDescBytes.length)
}
set newApprovalThreshold(newApprovalThreshold) {
this._rGroupApprovalThreshold = new Uint8Array(1)
this._rGroupApprovalThreshold[0] = newApprovalThreshold;
}
set newMinimumBlockDelay(newMinimumBlockDelay) {
this._rGroupMinimumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMinimumBlockDelay)
}
set newMaximumBlockDelay(newMaximumBlockDelay) {
this._rGroupMaximumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMaximumBlockDelay)
}
set _groupId(_groupId){
this._groupBytes = this.constructor.utils.int32ToBytes(_groupId)
}
get params() {
const params = super.params
params.push(
this._groupBytes,
this._newOwner,
this._rGroupDescLength,
this._rGroupDescBytes,
this._rGroupType,
this._rGroupApprovalThreshold,
this._rGroupMinimumBlockDelayBytes,
this._rGroupMaximumBlockDelayBytes,
this._feeBytes
)
return params
}
}

View File

@ -20,27 +20,17 @@ import DeployAtTransaction from './DeployAtTransaction.js'
import RewardShareTransaction from './RewardShareTransaction.js' import RewardShareTransaction from './RewardShareTransaction.js'
import RemoveRewardShareTransaction from './RemoveRewardShareTransaction.js' import RemoveRewardShareTransaction from './RemoveRewardShareTransaction.js'
import UpdateNameTransaction from './UpdateNameTransaction.js' import UpdateNameTransaction from './UpdateNameTransaction.js'
import UpdateGroupTransaction from './UpdateGroupTransaction.js'
import SellNameTransacion from './SellNameTransacion.js'
import CancelSellNameTransacion from './CancelSellNameTransacion.js'
import BuyNameTransacion from './BuyNameTransacion.js'
import TransferAssetTransaction from './TransferAssetTransaction.js'
export const transactionTypes = { export const transactionTypes = {
3: RegisterNameTransaction, 3: RegisterNameTransaction,
4: UpdateNameTransaction, 4: UpdateNameTransaction,
2: PaymentTransaction, 2: PaymentTransaction,
5: SellNameTransacion,
6: CancelSellNameTransacion,
7: BuyNameTransacion,
8: CreatePollTransaction, 8: CreatePollTransaction,
9: VoteOnPollTransaction, 9: VoteOnPollTransaction,
12: TransferAssetTransaction,
16: DeployAtTransaction, 16: DeployAtTransaction,
18: ChatTransaction, 18: ChatTransaction,
181: GroupChatTransaction, 181: GroupChatTransaction,
22: CreateGroupTransaction, 22: CreateGroupTransaction,
23: UpdateGroupTransaction,
24: AddGroupAdminTransaction, 24: AddGroupAdminTransaction,
25: RemoveGroupAdminTransaction, 25: RemoveGroupAdminTransaction,
26: GroupBanTransaction, 26: GroupBanTransaction,

View File

@ -1,26 +0,0 @@
export function buildImageEmbedLink(image?: {
name?: string;
identifier?: string;
service?: string;
timestamp?: number;
}): string | null {
if (!image?.name || !image.identifier || !image.service) return null;
const base = `qortal://use-embed/IMAGE?name=${image.name}&identifier=${image.identifier}&service=${image.service}&mimeType=image%2Fpng&timestamp=${image?.timestamp || ''}`;
const isEncrypted = image.identifier.startsWith('grp-q-manager_0');
return isEncrypted ? `${base}&encryptionType=group` : base;
}
export const messageHasImage = (message) => {
return (
Array.isArray(message?.images) &&
message.images[0]?.identifier &&
message.images[0]?.name &&
message.images[0]?.service
);
};
export function isHtmlString(value) {
return typeof value === 'string' && /<[^>]+>/.test(value.trim());
}

View File

@ -14,18 +14,3 @@ export function decodeIfEncoded(input) {
// Return input as-is if not URI-encoded // Return input as-is if not URI-encoded
return input; return input;
} }
export const isValidBase64 = (str: string): boolean => {
if (typeof str !== "string" || str.length % 4 !== 0) return false;
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
return base64Regex.test(str);
};
export const isValidBase64WithDecode = (str: string): boolean => {
try {
return isValidBase64(str) && Boolean(atob(str));
} catch {
return false;
}
};

View File

@ -43,15 +43,14 @@ export function formatTimestamp(timestamp: number): string {
// Both have timestamp, sort by timestamp descending // Both have timestamp, sort by timestamp descending
return b.timestamp - a.timestamp; return b.timestamp - a.timestamp;
} else if (a.timestamp) { } else if (a.timestamp) {
// Only `a` has timestamp, it comes first
return -1; return -1;
} else if (b.timestamp) { } else if (b.timestamp) {
// Only `b` has timestamp, it comes first
return 1; return 1;
} else { } else {
// Neither has timestamp, sort alphabetically by groupName (with fallback) // Neither has timestamp, sort alphabetically by groupName
const nameA = a.groupName || ''; return a.groupName.localeCompare(b.groupName);
const nameB = b.groupName || '';
return nameA.localeCompare(nameB);
} }
}); });
} }