mirror of
https://github.com/Qortal/qortal-mobile.git
synced 2025-06-05 07:56:58 +00:00
handle large uploads
This commit is contained in:
parent
0cfdd5cbc9
commit
0b9f32fd8c
@ -4,6 +4,8 @@ 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
|
||||||
@ -12,6 +14,9 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ const config: CapacitorConfig = {
|
|||||||
"splashImmersive": true
|
"splashImmersive": true
|
||||||
},
|
},
|
||||||
CapacitorHttp: {
|
CapacitorHttp: {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1329,6 +1329,7 @@ export async function publishOnQDNCase(request, event) {
|
|||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
|
name = "",
|
||||||
identifier,
|
identifier,
|
||||||
service,
|
service,
|
||||||
title,
|
title,
|
||||||
@ -1346,6 +1347,7 @@ export async function publishOnQDNCase(request, event) {
|
|||||||
identifier,
|
identifier,
|
||||||
service,
|
service,
|
||||||
title,
|
title,
|
||||||
|
name,
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
tag1,
|
tag1,
|
||||||
|
@ -59,6 +59,16 @@ export async function getNameInfo() {
|
|||||||
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) {
|
||||||
@ -151,7 +161,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, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true
|
registeredName, data: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, uploadType: 'base64', withFee: true
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
@ -202,7 +212,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, file: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, uploadType: 'file', isBase64: true, withFee: true
|
registeredName, data: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, uploadType: 'base64', withFee: true
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
data,
|
data,
|
||||||
@ -223,7 +233,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, file: encryptedData, service: 'DOCUMENT', identifier, uploadType: 'file', isBase64: true, withFee: true
|
registeredName, data: encryptedData, service: 'DOCUMENT', identifier, uploadType: 'base64', withFee: true
|
||||||
})
|
})
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -242,15 +252,16 @@ 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 = await getNameInfo()
|
const registeredName = name || 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, file: data, service, identifier, uploadType, isBase64: true, withFee: true, title,
|
registeredName, data, service, identifier, uploadType, withFee: true, title,
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
tag1,
|
tag1,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
AppCircle,
|
AppCircle,
|
||||||
AppCircleContainer,
|
AppCircleContainer,
|
||||||
@ -49,6 +49,7 @@ 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)",
|
||||||
@ -82,7 +83,8 @@ const CustomMenuItem = styled(MenuItem)({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AppPublish = ({ names, categories }) => {
|
export const AppPublish = ({ categories, myAddress, myName }) => {
|
||||||
|
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("");
|
||||||
@ -99,6 +101,8 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
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: {
|
||||||
@ -162,6 +166,25 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
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 = {
|
||||||
@ -199,10 +222,10 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
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: fileBase64,
|
data: file,
|
||||||
service: appType,
|
service: appType,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@ -213,6 +236,7 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
tag4,
|
tag4,
|
||||||
tag5,
|
tag5,
|
||||||
uploadType: "zip",
|
uploadType: "zip",
|
||||||
|
name
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response?.error) {
|
if (!response?.error) {
|
||||||
@ -287,7 +311,7 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
</em>{" "}
|
</em>{" "}
|
||||||
{/* This is the placeholder item */}
|
{/* This is the placeholder item */}
|
||||||
</CustomMenuItem>
|
</CustomMenuItem>
|
||||||
{names.map((name) => {
|
{mySortedNames.map((name) => {
|
||||||
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
|
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
|
||||||
})}
|
})}
|
||||||
</CustomSelect>
|
</CustomSelect>
|
||||||
|
@ -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}) => {
|
export const Apps = ({ mode, setMode, show , myName, myAddress}) => {
|
||||||
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}) => {
|
|||||||
>
|
>
|
||||||
{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} />
|
<AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myAddress={myAddress} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<AppsLibrary
|
<AppsLibrary
|
||||||
@ -314,7 +314,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
|||||||
{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 names={myName ? [myName] : []} categories={categories} />}
|
{mode === "publish" && !selectedTab && <AppPublish categories={categories} myAddress={myAddress} />}
|
||||||
|
|
||||||
{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}) => {
|
|||||||
{isNewTabWindow && mode === "viewer" && (
|
{isNewTabWindow && mode === "viewer" && (
|
||||||
<>
|
<>
|
||||||
<Spacer height="30px" />
|
<Spacer height="30px" />
|
||||||
<AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
<AppsHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myAddress={myAddress} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{mode !== "viewer" && !selectedTab && <Spacer height="180px" />}
|
{mode !== "viewer" && !selectedTab && <Spacer height="180px" />}
|
||||||
|
@ -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 }) => {
|
export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps, myName, myAddress }) => {
|
||||||
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 }
|
|||||||
<AppCircleLabel>Library</AppCircleLabel>
|
<AppCircleLabel>Library</AppCircleLabel>
|
||||||
</AppCircleContainer>
|
</AppCircleContainer>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<AppsPrivate myName={myName} />
|
<AppsPrivate myName={myName} myAddress={myAddress} />
|
||||||
|
|
||||||
<SortablePinnedApps availableQapps={availableQapps} myWebsite={myWebsite} myApp={myApp} />
|
<SortablePinnedApps availableQapps={availableQapps} myWebsite={myWebsite} myApp={myApp} />
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useMemo, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
@ -30,15 +30,18 @@ import {
|
|||||||
PublishQAppInfo,
|
PublishQAppInfo,
|
||||||
} from "./Apps-styles";
|
} from "./Apps-styles";
|
||||||
import ImageUploader from "../../common/ImageUploader";
|
import ImageUploader from "../../common/ImageUploader";
|
||||||
import { isMobile, MyContext } from "../../App";
|
import { getBaseApiReact, 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}) => {
|
export const AppsPrivate = ({myName, myAddress}) => {
|
||||||
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("");
|
||||||
@ -48,6 +51,7 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
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)
|
||||||
@ -165,6 +169,8 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
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) {
|
||||||
@ -181,7 +187,7 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
{
|
{
|
||||||
identifier: newPrivateAppValues?.identifier,
|
identifier: newPrivateAppValues?.identifier,
|
||||||
service: newPrivateAppValues?.service,
|
service: newPrivateAppValues?.service,
|
||||||
name: myName,
|
name,
|
||||||
groupId: selectedGroup,
|
groupId: selectedGroup,
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
@ -196,6 +202,22 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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(() => {
|
||||||
|
getNames();
|
||||||
|
}, [getNames]);
|
||||||
|
|
||||||
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||||
setValueTabPrivateApp(newValue);
|
setValueTabPrivateApp(newValue);
|
||||||
};
|
};
|
||||||
@ -432,6 +454,34 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
{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",
|
||||||
|
@ -10,9 +10,99 @@ 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,
|
||||||
@ -586,38 +676,26 @@ isDOMContentLoaded: false
|
|||||||
} else if(event?.data?.action === 'SAVE_FILE'
|
} else if(event?.data?.action === 'SAVE_FILE'
|
||||||
){
|
){
|
||||||
try {
|
try {
|
||||||
const res = await saveFile( event.data, null, true, {
|
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);
|
||||||
@ -640,6 +718,29 @@ 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
|
||||||
|
@ -574,7 +574,7 @@ export const Group = ({
|
|||||||
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error", error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2756,7 +2756,7 @@ export const Group = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<Apps mode={appsMode} setMode={setAppsMode} show={mobileViewMode === "apps"} myName={userInfo?.name} />
|
<Apps mode={appsMode} setMode={setAppsMode} show={mobileViewMode === "apps"} myName={userInfo?.name} myAddress={userInfo?.address} />
|
||||||
)}
|
)}
|
||||||
{!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}
|
||||||
|
@ -226,6 +226,7 @@ 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) {
|
||||||
|
@ -67,6 +67,7 @@ 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) {
|
||||||
|
@ -155,6 +155,7 @@ 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) {
|
||||||
|
11
src/hooks/useSortedMyNames.tsx
Normal file
11
src/hooks/useSortedMyNames.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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]);
|
||||||
|
}
|
@ -1,265 +1,365 @@
|
|||||||
// @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';
|
||||||
|
|
||||||
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){
|
async function reusablePost(endpoint, _body) {
|
||||||
// const validApi = await findUsableApi();
|
// const validApi = await findUsableApi();
|
||||||
const url = await createEndpoint(endpoint)
|
const url = await createEndpoint(endpoint);
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: _body
|
body: _body,
|
||||||
});
|
});
|
||||||
let data
|
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, 10_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,
|
||||||
file,
|
data,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
uploadType,
|
uploadType,
|
||||||
isBase64,
|
filename,
|
||||||
filename,
|
withFee,
|
||||||
withFee,
|
title,
|
||||||
title,
|
description,
|
||||||
description,
|
category,
|
||||||
category,
|
tag1,
|
||||||
tag1,
|
tag2,
|
||||||
tag2,
|
tag3,
|
||||||
tag3,
|
tag4,
|
||||||
tag4,
|
tag5,
|
||||||
tag5,
|
feeAmount,
|
||||||
feeAmount
|
|
||||||
}: any) => {
|
}: any) => {
|
||||||
|
const validateName = async (receiverName: string) => {
|
||||||
|
return await reusableGet(`/names/${receiverName}`);
|
||||||
|
};
|
||||||
|
|
||||||
const validateName = async (receiverName: string) => {
|
const convertBytesForSigning = async (transactionBytesBase58: string) => {
|
||||||
return await reusableGet(`/names/${receiverName}`)
|
return await reusablePost('/transactions/convert', transactionBytesBase58);
|
||||||
}
|
};
|
||||||
|
|
||||||
const convertBytesForSigning = async (transactionBytesBase58: string) => {
|
const getArbitraryFee = async () => {
|
||||||
return await reusablePost('/transactions/convert', transactionBytesBase58)
|
const timestamp = Date.now();
|
||||||
}
|
|
||||||
|
|
||||||
const getArbitraryFee = async () => {
|
let fee = await reusableGet(
|
||||||
const timestamp = Date.now()
|
`/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}`
|
||||||
|
);
|
||||||
|
|
||||||
let fee = await reusableGet(`/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}`)
|
return {
|
||||||
|
timestamp,
|
||||||
|
fee: Number(fee),
|
||||||
|
feeToShow: (Number(fee) / 1e8).toFixed(8),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
const signArbitraryWithFee = (
|
||||||
timestamp,
|
arbitraryBytesBase58,
|
||||||
fee: Number(fee),
|
arbitraryBytesForSigningBase58,
|
||||||
feeToShow: (Number(fee) / 1e8).toFixed(8)
|
keyPair
|
||||||
}
|
) => {
|
||||||
}
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const processTransactionVersion2 = async (bytes) => {
|
if (!keyPair) {
|
||||||
|
throw new Error('keyPair not defined');
|
||||||
|
}
|
||||||
|
|
||||||
return await reusablePost('/transactions/process?apiVersion=2', Base58.encode(bytes))
|
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
|
||||||
|
);
|
||||||
|
|
||||||
const signAndProcessWithFee = async (transactionBytesBase58: string) => {
|
return utils.appendBuffer(arbitraryBytesBuffer, signature);
|
||||||
let convertedBytesBase58 = await convertBytesForSigning(
|
};
|
||||||
transactionBytesBase58
|
|
||||||
)
|
|
||||||
|
|
||||||
if (convertedBytesBase58.error) {
|
const processTransactionVersion2 = async (bytes) => {
|
||||||
throw new Error('Error when signing')
|
return await reusablePost(
|
||||||
}
|
'/transactions/process?apiVersion=2',
|
||||||
|
Base58.encode(bytes)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const signAndProcessWithFee = async (transactionBytesBase58: string) => {
|
||||||
|
let convertedBytesBase58 = await convertBytesForSigning(
|
||||||
|
transactionBytesBase58
|
||||||
|
);
|
||||||
|
|
||||||
const resKeyPair = await getKeyPair()
|
if (convertedBytesBase58.error) {
|
||||||
const parsedData = resKeyPair
|
throw new Error('Error when signing');
|
||||||
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
}
|
||||||
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
|
||||||
const keyPair = {
|
|
||||||
privateKey: uint8PrivateKey,
|
|
||||||
publicKey: uint8PublicKey
|
|
||||||
};
|
|
||||||
|
|
||||||
let signedArbitraryBytes = signArbitraryWithFee(transactionBytesBase58, convertedBytesBase58, keyPair)
|
const resKeyPair = await getKeyPair();
|
||||||
const response = await processTransactionVersion2(signedArbitraryBytes)
|
const parsedData = resKeyPair;
|
||||||
|
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||||
|
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||||
|
const keyPair = {
|
||||||
|
privateKey: uint8PrivateKey,
|
||||||
|
publicKey: uint8PublicKey,
|
||||||
|
};
|
||||||
|
|
||||||
let myResponse = { error: '' }
|
let signedArbitraryBytes = signArbitraryWithFee(
|
||||||
|
transactionBytesBase58,
|
||||||
|
convertedBytesBase58,
|
||||||
|
keyPair
|
||||||
|
);
|
||||||
|
const response = await processTransactionVersion2(signedArbitraryBytes);
|
||||||
|
|
||||||
if (response === false) {
|
let myResponse = { error: '' };
|
||||||
throw new Error('Error when signing')
|
|
||||||
} else {
|
|
||||||
myResponse = response
|
|
||||||
}
|
|
||||||
|
|
||||||
return myResponse
|
if (response === false) {
|
||||||
}
|
throw new Error('Error when signing');
|
||||||
|
} else {
|
||||||
|
myResponse = response;
|
||||||
|
}
|
||||||
|
|
||||||
const validate = async () => {
|
return myResponse;
|
||||||
let validNameRes = await validateName(registeredName)
|
};
|
||||||
|
|
||||||
if (validNameRes.error) {
|
const validate = async () => {
|
||||||
throw new Error('Name not found')
|
let validNameRes = await validateName(registeredName);
|
||||||
}
|
|
||||||
|
|
||||||
let fee = null
|
if (validNameRes.error) {
|
||||||
|
throw new Error('Name not found');
|
||||||
|
}
|
||||||
|
|
||||||
if (withFee && feeAmount) {
|
let fee = null;
|
||||||
fee = feeAmount
|
|
||||||
} else if (withFee) {
|
|
||||||
const res = await getArbitraryFee()
|
|
||||||
if (res.fee) {
|
|
||||||
fee = res.fee
|
|
||||||
} else {
|
|
||||||
throw new Error('unable to get fee')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let transactionBytes = await uploadData(registeredName, file, fee)
|
if (withFee && feeAmount) {
|
||||||
if (!transactionBytes || transactionBytes.error) {
|
fee = feeAmount;
|
||||||
throw new Error(transactionBytes?.message || 'Error when uploading')
|
} else if (withFee) {
|
||||||
} else if (transactionBytes.includes('Error 500 Internal Server Error')) {
|
const res = await getArbitraryFee();
|
||||||
throw new Error('Error when uploading')
|
if (res.fee) {
|
||||||
}
|
fee = res.fee;
|
||||||
|
} else {
|
||||||
|
throw new Error('unable to get fee');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let signAndProcessRes
|
let transactionBytes = await uploadData(registeredName, data, fee);
|
||||||
|
if (!transactionBytes || transactionBytes.error) {
|
||||||
|
throw new Error(transactionBytes?.message || 'Error when uploading');
|
||||||
|
} else if (transactionBytes.includes('Error 500 Internal Server Error')) {
|
||||||
|
throw new Error('Error when uploading');
|
||||||
|
}
|
||||||
|
|
||||||
if (withFee) {
|
let signAndProcessRes;
|
||||||
signAndProcessRes = await signAndProcessWithFee(transactionBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signAndProcessRes?.error) {
|
if (withFee) {
|
||||||
throw new Error('Error when signing')
|
signAndProcessRes = await signAndProcessWithFee(transactionBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return signAndProcessRes
|
if (signAndProcessRes?.error) {
|
||||||
}
|
throw new Error('Error when signing');
|
||||||
|
}
|
||||||
|
|
||||||
const uploadData = async (registeredName: string, file:any, fee: number) => {
|
return signAndProcessRes;
|
||||||
|
};
|
||||||
|
|
||||||
let postBody = ''
|
const uploadData = async (registeredName: string, data: any, fee: number) => {
|
||||||
let urlSuffix = ''
|
let postBody = '';
|
||||||
|
let urlSuffix = '';
|
||||||
|
|
||||||
if (file != null) {
|
if (data != null) {
|
||||||
// If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API
|
if (uploadType === 'base64') {
|
||||||
if (uploadType === 'zip') {
|
urlSuffix = '/base64';
|
||||||
urlSuffix = '/zip'
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If we're sending file data, use the /base64 version of the POST /arbitrary/* API
|
if (uploadType === 'base64') {
|
||||||
else if (uploadType === 'file') {
|
postBody = data;
|
||||||
urlSuffix = '/base64'
|
}
|
||||||
}
|
} else {
|
||||||
|
throw new Error('No data provided');
|
||||||
|
}
|
||||||
|
|
||||||
// Base64 encode the file to work around compatibility issues between javascript and java byte arrays
|
let uploadDataUrl = `/arbitrary/${service}/${registeredName}`;
|
||||||
if (isBase64) {
|
let paramQueries = '';
|
||||||
postBody = file
|
if (identifier?.trim().length > 0) {
|
||||||
}
|
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isBase64) {
|
paramQueries = paramQueries + `?fee=${fee}`;
|
||||||
let fileBuffer = new Uint8Array(await file.arrayBuffer())
|
|
||||||
postBody = Buffer.from(fileBuffer).toString("base64")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
if (filename != null && filename != 'undefined') {
|
||||||
|
paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename);
|
||||||
|
}
|
||||||
|
|
||||||
let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}`
|
if (title != null && title != 'undefined') {
|
||||||
if (identifier?.trim().length > 0) {
|
paramQueries = paramQueries + '&title=' + encodeURIComponent(title);
|
||||||
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}`
|
}
|
||||||
}
|
|
||||||
|
|
||||||
uploadDataUrl = uploadDataUrl + `?fee=${fee}`
|
if (description != null && description != 'undefined') {
|
||||||
|
paramQueries =
|
||||||
|
paramQueries + '&description=' + encodeURIComponent(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category != null && category != 'undefined') {
|
||||||
|
paramQueries = paramQueries + '&category=' + encodeURIComponent(category);
|
||||||
|
}
|
||||||
|
|
||||||
if (filename != null && filename != 'undefined') {
|
if (tag1 != null && tag1 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&filename=' + encodeURIComponent(filename)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (title != null && title != 'undefined') {
|
if (tag2 != null && tag2 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (description != null && description != 'undefined') {
|
if (tag3 != null && tag3 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&description=' + encodeURIComponent(description)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (category != null && category != 'undefined') {
|
if (tag4 != null && tag4 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&category=' + encodeURIComponent(category)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag1 != null && tag1 != 'undefined') {
|
if (tag5 != null && tag5 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag5);
|
||||||
}
|
}
|
||||||
|
if (uploadType === 'zip') {
|
||||||
|
paramQueries = paramQueries + '&isZip=' + true;
|
||||||
|
}
|
||||||
|
|
||||||
if (tag2 != null && tag2 != 'undefined') {
|
if (uploadType === 'base64') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2)
|
if (urlSuffix) {
|
||||||
}
|
uploadDataUrl = uploadDataUrl + urlSuffix;
|
||||||
|
}
|
||||||
|
uploadDataUrl = uploadDataUrl + paramQueries;
|
||||||
|
return await reusablePost(uploadDataUrl, postBody);
|
||||||
|
}
|
||||||
|
|
||||||
if (tag3 != null && tag3 != 'undefined') {
|
const file = data;
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3)
|
const urlCheck = `/arbitrary/check-tmp-space?totalSize=${file.size}`;
|
||||||
}
|
|
||||||
|
|
||||||
if (tag4 != null && tag4 != 'undefined') {
|
const checkEndpoint = await createEndpoint(urlCheck);
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4)
|
const checkRes = await fetch(checkEndpoint);
|
||||||
}
|
if (!checkRes.ok) {
|
||||||
|
throw new Error('Not enough space on your hard drive');
|
||||||
|
}
|
||||||
|
|
||||||
if (tag5 != null && tag5 != 'undefined') {
|
const chunkUrl = uploadDataUrl + `/chunk`;
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5)
|
const chunkSize = 1 * 1024 * 1024; // 1MB
|
||||||
}
|
|
||||||
|
|
||||||
return await reusablePost(uploadDataUrl, postBody)
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
|
||||||
}
|
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);
|
||||||
|
|
||||||
try {
|
await uploadChunkWithRetry(chunkUrl, formData, index);
|
||||||
return await validate()
|
}
|
||||||
} catch (error: any) {
|
const finalizeUrl = uploadDataUrl + `/finalize` + paramQueries;
|
||||||
throw new Error(error?.message)
|
|
||||||
}
|
const finalizeEndpoint = await createEndpoint(finalizeUrl);
|
||||||
}
|
|
||||||
|
const response = await fetch(finalizeEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
@ -37,10 +37,11 @@ import {
|
|||||||
getNameOrAddress,
|
getNameOrAddress,
|
||||||
getAssetInfo,
|
getAssetInfo,
|
||||||
transferAsset,
|
transferAsset,
|
||||||
getPublicKey
|
getPublicKey,
|
||||||
|
isNative
|
||||||
} from "../background";
|
} from "../background";
|
||||||
import { getNameInfo, uint8ArrayToObject } from "../backgroundFunctions/encryption";
|
import { getNameInfo, uint8ArrayToObject,getAllUserNames } from "../backgroundFunctions/encryption";
|
||||||
import { showSaveFilePicker } from "../components/Apps/useQortalMessageListener";
|
import { saveFileInChunksFromUrl, showSaveFilePicker } from "../components/Apps/useQortalMessageListener";
|
||||||
import { QORT_DECIMALS } from "../constants/constants";
|
import { QORT_DECIMALS } from "../constants/constants";
|
||||||
import Base58 from "../deps/Base58";
|
import Base58 from "../deps/Base58";
|
||||||
import {
|
import {
|
||||||
@ -74,6 +75,7 @@ import ed2curve from "../deps/ed2curve";
|
|||||||
import { Sha256 } from "asmcrypto.js";
|
import { Sha256 } from "asmcrypto.js";
|
||||||
import { isValidBase64WithDecode } from "../utils/decode";
|
import { isValidBase64WithDecode } from "../utils/decode";
|
||||||
import ShortUniqueId from "short-unique-id";
|
import ShortUniqueId from "short-unique-id";
|
||||||
|
import { fileToBase64 } from "../utils/fileReading";
|
||||||
|
|
||||||
const uid = new ShortUniqueId({ length: 6 });
|
const uid = new ShortUniqueId({ length: 6 });
|
||||||
|
|
||||||
@ -914,7 +916,7 @@ export const publishQDNResource = async (
|
|||||||
sender,
|
sender,
|
||||||
isFromExtension
|
isFromExtension
|
||||||
) => {
|
) => {
|
||||||
const requiredFields = ["service"];
|
const requiredFields = ['service'];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (!data[field]) {
|
if (!data[field]) {
|
||||||
@ -922,25 +924,26 @@ export const publishQDNResource = async (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (missingFields.length > 0) {
|
if (missingFields.length > 0) {
|
||||||
const missingFieldsString = missingFields.join(", ");
|
const missingFieldsString = missingFields.join(', ');
|
||||||
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
||||||
throw new Error(errorMsg);
|
throw new Error(errorMsg);
|
||||||
}
|
}
|
||||||
if (!data.fileId && !data.data64 && !data.base64) {
|
if (!data.file && !data.data64 && !data.base64) {
|
||||||
throw new Error("No data or file was submitted");
|
throw new Error('No data or file was submitted');
|
||||||
}
|
}
|
||||||
// Use "default" if user hasn't specified an identifer
|
// Use "default" if user hasn't specified an identifier
|
||||||
const service = data.service;
|
const service = data.service;
|
||||||
const appFee = data?.appFee ? +data.appFee : undefined
|
const appFee = data?.appFee ? +data.appFee : undefined;
|
||||||
const appFeeRecipient = data?.appFeeRecipient
|
const appFeeRecipient = data?.appFeeRecipient;
|
||||||
let hasAppFee = false
|
let hasAppFee = false;
|
||||||
if(appFee && appFee > 0 && appFeeRecipient){
|
if (appFee && appFee > 0 && appFeeRecipient) {
|
||||||
hasAppFee = true
|
hasAppFee = true;
|
||||||
}
|
}
|
||||||
const registeredName = await getNameInfo();
|
|
||||||
|
const registeredName = data?.name || (await getNameInfo());
|
||||||
const name = registeredName;
|
const name = registeredName;
|
||||||
if(!name){
|
if (!name) {
|
||||||
throw new Error('User has no Qortal name')
|
throw new Error('User has no Qortal name');
|
||||||
}
|
}
|
||||||
let identifier = data.identifier;
|
let identifier = data.identifier;
|
||||||
let data64 = data.data64 || data.base64;
|
let data64 = data.data64 || data.base64;
|
||||||
@ -948,7 +951,7 @@ export const publishQDNResource = async (
|
|||||||
const title = data.title;
|
const title = data.title;
|
||||||
const description = data.description;
|
const description = data.description;
|
||||||
const category = data.category;
|
const category = data.category;
|
||||||
|
const file = data?.file || data?.blob;
|
||||||
const tags = data?.tags || [];
|
const tags = data?.tags || [];
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
@ -961,26 +964,26 @@ export const publishQDNResource = async (
|
|||||||
const { tag1, tag2, tag3, tag4, tag5 } = result;
|
const { tag1, tag2, tag3, tag4, tag5 } = result;
|
||||||
|
|
||||||
if (data.identifier == null) {
|
if (data.identifier == null) {
|
||||||
identifier = "default";
|
identifier = 'default';
|
||||||
}
|
|
||||||
if (data.fileId) {
|
|
||||||
data64 = await getFileFromContentScript(data.fileId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
data.encrypt &&
|
data.encrypt &&
|
||||||
(!data.publicKeys ||
|
(!data.publicKeys ||
|
||||||
(Array.isArray(data.publicKeys) && data.publicKeys.length === 0))
|
(Array.isArray(data.publicKeys) && data.publicKeys.length === 0))
|
||||||
) {
|
) {
|
||||||
throw new Error("Encrypting data requires public keys");
|
throw new Error('Encrypting data requires public keys');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (data.encrypt) {
|
if (data.encrypt) {
|
||||||
try {
|
try {
|
||||||
const resKeyPair = await getKeyPair();
|
const resKeyPair = await getKeyPair();
|
||||||
const parsedData = resKeyPair;
|
const parsedData = resKeyPair;
|
||||||
const privateKey = parsedData.privateKey;
|
const privateKey = parsedData.privateKey;
|
||||||
const userPublicKey = parsedData.publicKey;
|
const userPublicKey = parsedData.publicKey;
|
||||||
|
if (data?.file || data?.blob) {
|
||||||
|
data64 = await fileToBase64(data?.file || data?.blob);
|
||||||
|
}
|
||||||
const encryptDataResponse = encryptDataGroup({
|
const encryptDataResponse = encryptDataGroup({
|
||||||
data64,
|
data64,
|
||||||
publicKeys: data.publicKeys,
|
publicKeys: data.publicKeys,
|
||||||
@ -992,49 +995,46 @@ export const publishQDNResource = async (
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
error.message || "Upload failed due to failed encryption"
|
error.message || 'Upload failed due to failed encryption'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fee = await getFee("ARBITRARY");
|
const fee = await getFee('ARBITRARY');
|
||||||
|
|
||||||
const handleDynamicValues = {}
|
const handleDynamicValues = {};
|
||||||
if(hasAppFee){
|
if (hasAppFee) {
|
||||||
const feePayment = await getFee("PAYMENT");
|
const feePayment = await getFee('PAYMENT');
|
||||||
|
|
||||||
handleDynamicValues['appFee'] = +appFee + +feePayment.fee,
|
(handleDynamicValues['appFee'] = +appFee + +feePayment.fee),
|
||||||
handleDynamicValues['checkbox1'] = {
|
(handleDynamicValues['checkbox1'] = {
|
||||||
value: true,
|
value: true,
|
||||||
label: "accept app fee",
|
label: 'accept app fee',
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
if(data?.encrypt){
|
if (!!data?.encrypt) {
|
||||||
handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`
|
handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resPermission = await getUserPermission(
|
const resPermission = await getUserPermission(
|
||||||
{
|
{
|
||||||
text1: "Do you give this application permission to publish to QDN?",
|
text1: 'Do you give this application permission to publish to QDN?',
|
||||||
text2: `service: ${service}`,
|
text2: `service: ${service}`,
|
||||||
text3: `identifier: ${identifier || null}`,
|
text3: `identifier: ${identifier || null}`,
|
||||||
|
text4: `name: ${registeredName}`,
|
||||||
fee: fee.fee,
|
fee: fee.fee,
|
||||||
...handleDynamicValues
|
...handleDynamicValues,
|
||||||
},
|
},
|
||||||
isFromExtension
|
isFromExtension
|
||||||
);
|
);
|
||||||
const { accepted, checkbox1 = false } = resPermission;
|
const { accepted, checkbox1 = false } = resPermission;
|
||||||
|
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resPublish = await publishData({
|
const resPublish = await publishData({
|
||||||
registeredName: encodeURIComponent(name),
|
registeredName: encodeURIComponent(name),
|
||||||
file: data64,
|
data: data64 ? data64 : file,
|
||||||
service: service,
|
service: service,
|
||||||
identifier: encodeURIComponent(identifier),
|
identifier: encodeURIComponent(identifier),
|
||||||
uploadType: "file",
|
uploadType: data64 ? 'base64' : 'file',
|
||||||
isBase64: true,
|
|
||||||
filename: filename,
|
filename: filename,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@ -1047,18 +1047,21 @@ export const publishQDNResource = async (
|
|||||||
apiVersion: 2,
|
apiVersion: 2,
|
||||||
withFee: true,
|
withFee: true,
|
||||||
});
|
});
|
||||||
if(resPublish?.signature && hasAppFee && checkbox1){
|
if (resPublish?.signature && hasAppFee && checkbox1) {
|
||||||
sendCoinFunc({
|
sendCoinFunc(
|
||||||
amount: appFee,
|
{
|
||||||
receiver: appFeeRecipient
|
amount: appFee,
|
||||||
}, true)
|
receiver: appFeeRecipient,
|
||||||
}
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
return resPublish;
|
return resPublish;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error?.message || "Upload failed");
|
throw new Error(error?.message || 'Upload failed');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("User declined request");
|
throw new Error('User declined request');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1096,8 +1099,12 @@ export const checkArrrSyncStatus = async (seed) => {
|
|||||||
throw new Error("Failed to synchronize after 36 attempts");
|
throw new Error("Failed to synchronize after 36 attempts");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const publishMultipleQDNResources = async (data: any, sender, isFromExtension) => {
|
export const publishMultipleQDNResources = async (
|
||||||
const requiredFields = ["resources"];
|
data: any,
|
||||||
|
sender,
|
||||||
|
isFromExtension
|
||||||
|
) => {
|
||||||
|
const requiredFields = ['resources'];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
let feeAmount = null;
|
let feeAmount = null;
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
@ -1106,64 +1113,74 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (missingFields.length > 0) {
|
if (missingFields.length > 0) {
|
||||||
const missingFieldsString = missingFields.join(", ");
|
const missingFieldsString = missingFields.join(', ');
|
||||||
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
||||||
throw new Error(errorMsg);
|
throw new Error(errorMsg);
|
||||||
}
|
}
|
||||||
const resources = data.resources;
|
const resources = data.resources;
|
||||||
if (!Array.isArray(resources)) {
|
if (!Array.isArray(resources)) {
|
||||||
throw new Error("Invalid data");
|
throw new Error('Invalid data');
|
||||||
}
|
}
|
||||||
if (resources.length === 0) {
|
if (resources.length === 0) {
|
||||||
throw new Error("No resources to publish");
|
throw new Error('No resources to publish');
|
||||||
}
|
}
|
||||||
const encrypt = data?.encrypt
|
|
||||||
|
const encrypt = data?.encrypt;
|
||||||
|
|
||||||
for (const resource of resources) {
|
for (const resource of resources) {
|
||||||
const resourceEncrypt = encrypt && resource?.disableEncrypt !== true
|
const resourceEncrypt = encrypt && resource?.disableEncrypt !== true;
|
||||||
if (!resourceEncrypt && resource?.service.endsWith("_PRIVATE")) {
|
if (!resourceEncrypt && resource?.service.endsWith('_PRIVATE')) {
|
||||||
const errorMsg = "Only encrypted data can go into private services";
|
const errorMsg = 'Only encrypted data can go into private services';
|
||||||
throw new Error(errorMsg)
|
throw new Error(errorMsg);
|
||||||
} else if(resourceEncrypt && !resource?.service.endsWith("_PRIVATE")){
|
} else if (resourceEncrypt && !resource?.service.endsWith('_PRIVATE')) {
|
||||||
const errorMsg = "For an encrypted publish please use a service that ends with _PRIVATE";
|
const errorMsg =
|
||||||
throw new Error(errorMsg)
|
'For an encrypted publish please use a service that ends with _PRIVATE';
|
||||||
|
throw new Error(errorMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const fee = await getFee("ARBITRARY");
|
|
||||||
|
const fee = await getFee('ARBITRARY');
|
||||||
const registeredName = await getNameInfo();
|
const registeredName = await getNameInfo();
|
||||||
|
|
||||||
const name = registeredName;
|
const name = registeredName;
|
||||||
if(!name){
|
if (!name) {
|
||||||
throw new Error('You need a Qortal name to publish.')
|
throw new Error('You need a Qortal name to publish.');
|
||||||
}
|
}
|
||||||
const appFee = data?.appFee ? +data.appFee : undefined
|
const userNames = await getAllUserNames();
|
||||||
const appFeeRecipient = data?.appFeeRecipient
|
data.resources?.forEach((item) => {
|
||||||
let hasAppFee = false
|
if (item?.name && !userNames?.includes(item.name))
|
||||||
if(appFee && appFee > 0 && appFeeRecipient){
|
throw new Error(
|
||||||
hasAppFee = true
|
`The name ${item.name}, does not belong to the publisher.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const appFee = data?.appFee ? +data.appFee : undefined;
|
||||||
|
const appFeeRecipient = data?.appFeeRecipient;
|
||||||
|
let hasAppFee = false;
|
||||||
|
if (appFee && appFee > 0 && appFeeRecipient) {
|
||||||
|
hasAppFee = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDynamicValues = {}
|
const handleDynamicValues = {};
|
||||||
if(hasAppFee){
|
if (hasAppFee) {
|
||||||
const feePayment = await getFee("PAYMENT");
|
const feePayment = await getFee('PAYMENT');
|
||||||
|
|
||||||
handleDynamicValues['appFee'] = +appFee + +feePayment.fee,
|
(handleDynamicValues['appFee'] = +appFee + +feePayment.fee),
|
||||||
handleDynamicValues['checkbox1'] = {
|
(handleDynamicValues['checkbox1'] = {
|
||||||
value: true,
|
value: true,
|
||||||
label: "accept app fee",
|
label: 'accept app fee',
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
if(data?.encrypt){
|
if (data?.encrypt) {
|
||||||
handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`
|
handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`;
|
||||||
}
|
}
|
||||||
const resPermission = await getUserPermission({
|
const resPermission = await getUserPermission(
|
||||||
text1: "Do you give this application permission to publish to QDN?",
|
{
|
||||||
html: `
|
text1: 'Do you give this application permission to publish to QDN?',
|
||||||
|
html: `
|
||||||
<div style="max-height: 30vh; overflow-y: auto;">
|
<div style="max-height: 30vh; overflow-y: auto;">
|
||||||
<style>
|
<style>
|
||||||
body {
|
|
||||||
background-color: #121212;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-container {
|
.resource-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -1172,7 +1189,7 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: #1e1e1e;
|
background-color: var(--background-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-detail {
|
.resource-detail {
|
||||||
@ -1181,7 +1198,7 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
|
|
||||||
.resource-detail span {
|
.resource-detail span {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #bb86fc;
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
@ -1204,34 +1221,34 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
<div class="resource-detail"><span>Service:</span> ${
|
<div class="resource-detail"><span>Service:</span> ${
|
||||||
resource.service
|
resource.service
|
||||||
}</div>
|
}</div>
|
||||||
<div class="resource-detail"><span>Name:</span> ${name}</div>
|
<div class="resource-detail"><span>Name:</span> ${resource?.name || name}</div>
|
||||||
<div class="resource-detail"><span>Identifier:</span> ${
|
<div class="resource-detail"><span>Identifier:</span> ${
|
||||||
resource.identifier
|
resource.identifier
|
||||||
}</div>
|
}</div>
|
||||||
${
|
${
|
||||||
resource.filename
|
resource.filename
|
||||||
? `<div class="resource-detail"><span>Filename:</span> ${resource.filename}</div>`
|
? `<div class="resource-detail"><span>Filename:</span> ${resource.filename}</div>`
|
||||||
: ""
|
: ''
|
||||||
}
|
}
|
||||||
</div>`
|
</div>`
|
||||||
)
|
)
|
||||||
.join("")}
|
.join('')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
`,
|
`,
|
||||||
fee: +fee.fee * resources.length,
|
fee: +fee.fee * resources.length,
|
||||||
...handleDynamicValues
|
...handleDynamicValues,
|
||||||
}, isFromExtension);
|
},
|
||||||
|
isFromExtension
|
||||||
const { accepted, checkbox1 = false } = resPermission;
|
);
|
||||||
|
const { accepted, checkbox1 = false } = resPermission;
|
||||||
if (!accepted) {
|
if (!accepted) {
|
||||||
throw new Error("User declined request");
|
throw new Error('User declined request');
|
||||||
}
|
}
|
||||||
let failedPublishesIdentifiers = [];
|
let failedPublishesIdentifiers = [];
|
||||||
for (const resource of resources) {
|
for (const resource of resources) {
|
||||||
try {
|
try {
|
||||||
const requiredFields = ["service"];
|
const requiredFields = ['service'];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (!resource[field]) {
|
if (!resource[field]) {
|
||||||
@ -1239,34 +1256,35 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (missingFields.length > 0) {
|
if (missingFields.length > 0) {
|
||||||
const missingFieldsString = missingFields.join(", ");
|
const missingFieldsString = missingFields.join(', ');
|
||||||
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
||||||
failedPublishesIdentifiers.push({
|
failedPublishesIdentifiers.push({
|
||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!resource.fileId && !resource.data64 && !resource?.base64) {
|
if (!resource.file && !resource.data64 && !resource?.base64) {
|
||||||
const errorMsg = "No data or file was submitted";
|
const errorMsg = 'No data or file was submitted';
|
||||||
failedPublishesIdentifiers.push({
|
failedPublishesIdentifiers.push({
|
||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const service = resource.service;
|
const service = resource.service;
|
||||||
let identifier = resource.identifier;
|
let identifier = resource.identifier;
|
||||||
let data64 = resource?.data64 || resource?.base64;
|
let rawData = resource?.data64 || resource?.base64;
|
||||||
const filename = resource.filename;
|
const filename = resource.filename;
|
||||||
const title = resource.title;
|
const title = resource.title;
|
||||||
const description = resource.description;
|
const description = resource.description;
|
||||||
const category = resource.category;
|
const category = resource.category;
|
||||||
const tags = resource?.tags || [];
|
const tags = resource?.tags || [];
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
// Fill tags dynamically while maintaining backward compatibility
|
// Fill tags dynamically while maintaining backward compatibility
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
result[`tag${i + 1}`] = tags[i] || resource[`tag${i + 1}`] || undefined;
|
result[`tag${i + 1}`] = tags[i] || resource[`tag${i + 1}`] || undefined;
|
||||||
@ -1274,108 +1292,126 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
|
|
||||||
// Access tag1 to tag5 from result
|
// Access tag1 to tag5 from result
|
||||||
const { tag1, tag2, tag3, tag4, tag5 } = result;
|
const { tag1, tag2, tag3, tag4, tag5 } = result;
|
||||||
const resourceEncrypt = encrypt && resource?.disableEncrypt !== true
|
const resourceEncrypt = encrypt && resource?.disableEncrypt !== true;
|
||||||
|
|
||||||
if (resource.identifier == null) {
|
if (resource.identifier == null) {
|
||||||
identifier = "default";
|
identifier = 'default';
|
||||||
}
|
}
|
||||||
if (!resourceEncrypt && service.endsWith("_PRIVATE")) {
|
if (!resourceEncrypt && service.endsWith('_PRIVATE')) {
|
||||||
const errorMsg = "Only encrypted data can go into private services";
|
const errorMsg = 'Only encrypted data can go into private services';
|
||||||
failedPublishesIdentifiers.push({
|
failedPublishesIdentifiers.push({
|
||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (resource.fileId) {
|
if (resource.file) {
|
||||||
data64 = await getFileFromContentScript(resource.fileId);
|
rawData = resource.file;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceEncrypt) {
|
if (resourceEncrypt) {
|
||||||
try {
|
try {
|
||||||
const resKeyPair = await getKeyPair()
|
if (resource?.file) {
|
||||||
const parsedData = resKeyPair
|
rawData = await fileToBase64(resource.file);
|
||||||
const privateKey = parsedData.privateKey
|
}
|
||||||
const userPublicKey = parsedData.publicKey
|
const resKeyPair = await getKeyPair();
|
||||||
|
const parsedData = resKeyPair;
|
||||||
|
const privateKey = parsedData.privateKey;
|
||||||
|
const userPublicKey = parsedData.publicKey;
|
||||||
const encryptDataResponse = encryptDataGroup({
|
const encryptDataResponse = encryptDataGroup({
|
||||||
data64,
|
data64: rawData,
|
||||||
publicKeys: data.publicKeys,
|
publicKeys: data.publicKeys,
|
||||||
privateKey,
|
privateKey,
|
||||||
userPublicKey
|
userPublicKey,
|
||||||
});
|
});
|
||||||
if (encryptDataResponse) {
|
if (encryptDataResponse) {
|
||||||
data64 = encryptDataResponse;
|
rawData = encryptDataResponse;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg =
|
const errorMsg =
|
||||||
error?.message || "Upload failed due to failed encryption";
|
error?.message || 'Upload failed due to failed encryption';
|
||||||
failedPublishesIdentifiers.push({
|
failedPublishesIdentifiers.push({
|
||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await retryTransaction(publishData, [
|
const dataType =
|
||||||
{
|
resource?.base64 || resource?.data64 || resourceEncrypt
|
||||||
registeredName: encodeURIComponent(name),
|
? 'base64'
|
||||||
file: data64,
|
: 'file';
|
||||||
service: service,
|
await retryTransaction(
|
||||||
identifier: encodeURIComponent(identifier),
|
publishData,
|
||||||
uploadType: "file",
|
[
|
||||||
isBase64: true,
|
{
|
||||||
filename: filename,
|
data: rawData,
|
||||||
title,
|
registeredName: encodeURIComponent(resource?.name || name),
|
||||||
description,
|
service: service,
|
||||||
category,
|
identifier: encodeURIComponent(identifier),
|
||||||
tag1,
|
uploadType: dataType,
|
||||||
tag2,
|
// isBase64: true,
|
||||||
tag3,
|
filename: filename,
|
||||||
tag4,
|
title,
|
||||||
tag5,
|
description,
|
||||||
apiVersion: 2,
|
category,
|
||||||
withFee: true,
|
tag1,
|
||||||
},
|
tag2,
|
||||||
], true);
|
tag3,
|
||||||
|
tag4,
|
||||||
|
tag5,
|
||||||
|
apiVersion: 2,
|
||||||
|
withFee: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
true
|
||||||
|
);
|
||||||
await new Promise((res) => {
|
await new Promise((res) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
res();
|
res();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = error.message || "Upload failed";
|
const errorMsg = error.message || 'Upload failed';
|
||||||
failedPublishesIdentifiers.push({
|
failedPublishesIdentifiers.push({
|
||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
failedPublishesIdentifiers.push({
|
failedPublishesIdentifiers.push({
|
||||||
reason: error?.message || "Unknown error",
|
reason: error?.message || 'Unknown error',
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (failedPublishesIdentifiers.length > 0) {
|
if (failedPublishesIdentifiers.length > 0) {
|
||||||
const obj = {
|
const obj = {
|
||||||
message: "Some resources have failed to publish.",
|
message: 'Some resources have failed to publish.',
|
||||||
};
|
};
|
||||||
obj["error"] = {
|
obj['error'] = {
|
||||||
unsuccessfulPublishes: failedPublishesIdentifiers,
|
unsuccessfulPublishes: failedPublishesIdentifiers,
|
||||||
};
|
};
|
||||||
return obj;
|
return obj;
|
||||||
|
}
|
||||||
|
if (hasAppFee && checkbox1) {
|
||||||
|
sendCoinFunc(
|
||||||
|
{
|
||||||
|
amount: appFee,
|
||||||
|
receiver: appFeeRecipient,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if(hasAppFee && checkbox1){
|
|
||||||
sendCoinFunc({
|
|
||||||
amount: appFee,
|
|
||||||
receiver: appFeeRecipient
|
|
||||||
}, true)
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1737,6 +1773,53 @@ export const joinGroup = async (data, isFromExtension) => {
|
|||||||
|
|
||||||
export const saveFile = async (data, sender, isFromExtension, snackMethods) => {
|
export const saveFile = async (data, sender, isFromExtension, snackMethods) => {
|
||||||
try {
|
try {
|
||||||
|
if (data?.location) {
|
||||||
|
const requiredFieldsLocation = ['service', 'name', 'filename'];
|
||||||
|
const missingFieldsLocation: string[] = [];
|
||||||
|
requiredFieldsLocation.forEach((field) => {
|
||||||
|
if (!data?.location[field]) {
|
||||||
|
missingFieldsLocation.push(field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (missingFieldsLocation.length > 0) {
|
||||||
|
const missingFieldsString = missingFieldsLocation.join(', ');
|
||||||
|
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
const resPermission = await getUserPermission(
|
||||||
|
{
|
||||||
|
text1: 'Would you like to download:',
|
||||||
|
highlightedText: `${data?.location?.filename}`,
|
||||||
|
},
|
||||||
|
isFromExtension
|
||||||
|
);
|
||||||
|
const { accepted } = resPermission;
|
||||||
|
if (!accepted) throw new Error('User declined to save file');
|
||||||
|
if(isNative){
|
||||||
|
try {
|
||||||
|
saveFileInChunksFromUrl(data.location)
|
||||||
|
} catch (error) {
|
||||||
|
console.log('save chunks url error', error)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
let locationUrl = `/arbitrary/${data.location.service}/${data.location.name}`;
|
||||||
|
if (data.location.identifier) {
|
||||||
|
locationUrl = locationUrl + `/${data.location.identifier}`;
|
||||||
|
}
|
||||||
|
const endpoint = await createEndpoint(
|
||||||
|
locationUrl +
|
||||||
|
`?attachment=true&attachmentFilename=${data?.location?.filename}`
|
||||||
|
);
|
||||||
|
a.href = endpoint;
|
||||||
|
a.download = data.location.filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const requiredFields = ['filename', 'blob']
|
const requiredFields = ['filename', 'blob']
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
@ -4015,7 +4098,7 @@ export const registerNameRequest = async (data, isFromExtension) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const updateNameRequest = async (data, isFromExtension) => {
|
export const updateNameRequest = async (data, isFromExtension) => {
|
||||||
const requiredFields = ["newName", "oldName"];
|
const requiredFields = ['newName', 'oldName'];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (!data[field]) {
|
if (!data[field]) {
|
||||||
@ -4023,30 +4106,30 @@ export const updateNameRequest = async (data, isFromExtension) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (missingFields.length > 0) {
|
if (missingFields.length > 0) {
|
||||||
const missingFieldsString = missingFields.join(", ");
|
const missingFieldsString = missingFields.join(', ');
|
||||||
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
const errorMsg = `Missing fields: ${missingFieldsString}`;
|
||||||
throw new Error(errorMsg);
|
throw new Error(errorMsg);
|
||||||
}
|
}
|
||||||
const oldName = data.oldName
|
const oldName = data.oldName;
|
||||||
const newName = data.newName
|
const newName = data.newName;
|
||||||
const description = data?.description || ""
|
const description = data?.description || '';
|
||||||
const fee = await getFee("UPDATE_NAME");
|
const fee = await getFee('UPDATE_NAME');
|
||||||
const resPermission = await getUserPermission(
|
const resPermission = await getUserPermission(
|
||||||
{
|
{
|
||||||
text1: `Do you give this application permission to register this name?`,
|
text1: `Do you give this application permission to update this name?`,
|
||||||
highlightedText: data.newName,
|
text2: `previous name: ${oldName}`,
|
||||||
text2: data?.description,
|
text3: `new name: ${newName}`,
|
||||||
|
text4: data?.description,
|
||||||
fee: fee.fee,
|
fee: fee.fee,
|
||||||
},
|
},
|
||||||
isFromExtension
|
isFromExtension
|
||||||
);
|
);
|
||||||
const { accepted } = resPermission;
|
const { accepted } = resPermission;
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
const response = await updateName({ oldName, newName, description });
|
const response = await updateName({ oldName, newName, description });
|
||||||
return response
|
return response;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error("User declined request");
|
throw new Error('User declined request');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -4571,7 +4654,7 @@ export const updateGroupRequest = async (data, isFromExtension) => {
|
|||||||
const requiredFields = ["groupId", "newOwner", "type", "approvalThreshold", "minBlock", "maxBlock"];
|
const requiredFields = ["groupId", "newOwner", "type", "approvalThreshold", "minBlock", "maxBlock"];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (data[field] !== undefined && data[field] !== null) {
|
if (data[field] === undefined || data[field] === null) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -4821,7 +4904,7 @@ export const sellNameRequest = async (data, isFromExtension) => {
|
|||||||
const requiredFields = ["salePrice", "nameForSale"];
|
const requiredFields = ["salePrice", "nameForSale"];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (data[field] !== undefined && data[field] !== null) {
|
if (data[field] === undefined || data[field] === null) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -4866,7 +4949,7 @@ export const cancelSellNameRequest = async (data, isFromExtension) => {
|
|||||||
const requiredFields = ["nameForSale"];
|
const requiredFields = ["nameForSale"];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (data[field] !== undefined && data[field] !== null) {
|
if (data[field] === undefined || data[field] === null) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -4907,7 +4990,7 @@ export const buyNameRequest = async (data, isFromExtension) => {
|
|||||||
const requiredFields = ["nameForSale"];
|
const requiredFields = ["nameForSale"];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (data[field] !== undefined && data[field] !== null) {
|
if (data[field] === undefined || data[field] === null) {
|
||||||
missingFields.push(field);
|
missingFields.push(field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -5210,10 +5293,10 @@ const assetBalance = await getAssetBalanceInfo(assetId)
|
|||||||
const resPublish = await retryTransaction(publishData, [
|
const resPublish = await retryTransaction(publishData, [
|
||||||
{
|
{
|
||||||
registeredName: encodeURIComponent(name),
|
registeredName: encodeURIComponent(name),
|
||||||
file: encryptDataResponse,
|
data: encryptDataResponse,
|
||||||
service: transaction.service,
|
service: transaction.service,
|
||||||
identifier: encodeURIComponent(transaction.identifier),
|
identifier: encodeURIComponent(transaction.identifier),
|
||||||
uploadType: "file",
|
uploadType: "base64",
|
||||||
description: transaction?.description,
|
description: transaction?.description,
|
||||||
isBase64: true,
|
isBase64: true,
|
||||||
apiVersion: 2,
|
apiVersion: 2,
|
||||||
@ -5251,10 +5334,10 @@ const assetBalance = await getAssetBalanceInfo(assetId)
|
|||||||
const resPublish = await retryTransaction(publishData, [
|
const resPublish = await retryTransaction(publishData, [
|
||||||
{
|
{
|
||||||
registeredName: encodeURIComponent(name),
|
registeredName: encodeURIComponent(name),
|
||||||
file: encryptDataResponse,
|
data: encryptDataResponse,
|
||||||
service: transaction.service,
|
service: transaction.service,
|
||||||
identifier: encodeURIComponent(transaction.identifier),
|
identifier: encodeURIComponent(transaction.identifier),
|
||||||
uploadType: "file",
|
uploadType: "base64",
|
||||||
description: transaction?.description,
|
description: transaction?.description,
|
||||||
isBase64: true,
|
isBase64: true,
|
||||||
apiVersion: 2,
|
apiVersion: 2,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user