mirror of
https://github.com/Qortal/chrome-extension.git
synced 2025-05-25 19:16:58 +00:00
handle large files and multi-name
This commit is contained in:
parent
28c85cc271
commit
be60211365
@ -53,6 +53,7 @@ const fileToBase64 = (file) => new Promise(async (resolve, reject) => {
|
||||
}
|
||||
})
|
||||
|
||||
const fileReferences = {}
|
||||
|
||||
|
||||
|
||||
@ -542,6 +543,86 @@ async function handleGetFileFromIndexedDB(fileId, sendResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
async function reusablePostStream(endpoint, _body) {
|
||||
|
||||
const headers = {};
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: _body,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
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 handleSendDataChunksToCore(fileId, chunkUrl, sendResponse){
|
||||
try {
|
||||
if(!fileReferences[fileId]) throw new Error('No file reference found')
|
||||
const chunkSize = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
const file = fileReferences[fileId]
|
||||
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);
|
||||
|
||||
await uploadChunkWithRetry(chunkUrl, formData, index);
|
||||
}
|
||||
sendResponse({ result: true });
|
||||
} catch (error) {
|
||||
sendResponse({ result: null, error: error?.message || "Could not save chunks to the core" });
|
||||
} finally {
|
||||
if(fileReferences[fileId]){
|
||||
delete fileReferences[fileId]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGetFileBase64(fileId, sendResponse){
|
||||
try {
|
||||
if(!fileReferences[fileId]) throw new Error('No file reference found')
|
||||
const base64 = await fileToBase64(fileReferences[fileId]);
|
||||
sendResponse({ result: base64 });
|
||||
} catch (error) {
|
||||
sendResponse({ result: null, error: error?.message || "Could not save chunks to the core" });
|
||||
} finally {
|
||||
if(fileReferences[fileId]){
|
||||
delete fileReferences[fileId]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const testAsync = async (sendResponse)=> {
|
||||
await new Promise((res)=> {
|
||||
setTimeout(() => {
|
||||
@ -573,6 +654,22 @@ const saveFile = (blob, filename) => {
|
||||
|
||||
|
||||
const showSaveFilePicker = async (data) => {
|
||||
|
||||
if(data?.locationEndpoint){
|
||||
try {
|
||||
const a = document.createElement('a');
|
||||
|
||||
a.href = data?.locationEndpoint;
|
||||
a.download = data.filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
let blob
|
||||
let fileName
|
||||
try {
|
||||
@ -599,7 +696,10 @@ const showSaveFilePicker = async (data) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.hasAddedChromeMessageListener) {
|
||||
|
||||
chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) {
|
||||
window.hasAddedChromeMessageListener = true;
|
||||
if (message.type === "LOGOUT") {
|
||||
// Notify the web page
|
||||
window.postMessage(
|
||||
@ -621,14 +721,18 @@ chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse)
|
||||
);
|
||||
} else if(message.action === "SHOW_SAVE_FILE_PICKER"){
|
||||
showSaveFilePicker(message?.data)
|
||||
}
|
||||
|
||||
else if (message.action === "getFileFromIndexedDB") {
|
||||
} else if (message.action === "getFileFromIndexedDB") {
|
||||
handleGetFileFromIndexedDB(message.fileId, sendResponse);
|
||||
return true; // Keep the message channel open for async response
|
||||
} else if (message.action === "sendDataChunksToCore") {
|
||||
handleSendDataChunksToCore(message.fileId, message.chunkUrl, sendResponse);
|
||||
return true; // Keep the message channel open for async response
|
||||
} else if(message.action === "getFileBase64"){
|
||||
handleGetFileBase64(message.fileId, sendResponse);
|
||||
return true
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
function openIndexedDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open("fileStorageDB", 1);
|
||||
@ -795,6 +899,33 @@ async function storeFilesInIndexedDB(obj) {
|
||||
}
|
||||
|
||||
|
||||
function saveFileReferences(obj) {
|
||||
|
||||
if (obj.file instanceof File) {
|
||||
const fileId = "objFile_qortalfile_" + Date.now();
|
||||
|
||||
fileReferences[fileId] = obj.file
|
||||
obj.fileId = fileId;
|
||||
}
|
||||
if (obj.blob instanceof Blob) {
|
||||
const fileId = "objFile_qortalfile_" + Date.now();
|
||||
|
||||
fileReferences[fileId] = obj.blob
|
||||
obj.fileId = fileId
|
||||
}
|
||||
|
||||
// Iterate through resources to find files and save them to IndexedDB
|
||||
for (let resource of (obj?.resources || [])) {
|
||||
if (resource.file instanceof File) {
|
||||
const fileId = resource.identifier + "_qortalfile_" + Date.now();
|
||||
|
||||
fileReferences[fileId] = resource.file
|
||||
resource.fileId = fileId
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
|
||||
const UIQortalRequests = ['GET_USER_ACCOUNT', 'DECRYPT_DATA', 'SEND_COIN', 'GET_LIST_ITEMS', 'ADD_LIST_ITEMS', 'DELETE_LIST_ITEM', 'VOTE_ON_POLL', 'CREATE_POLL', 'SEND_CHAT_MESSAGE', 'JOIN_GROUP', 'DEPLOY_AT', 'GET_USER_WALLET', 'GET_WALLET_BALANCE', 'GET_USER_WALLET_INFO', 'GET_CROSSCHAIN_SERVER_INFO', 'GET_TX_ACTIVITY_SUMMARY', 'GET_FOREIGN_FEE', 'UPDATE_FOREIGN_FEE', 'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER', 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY']
|
||||
|
||||
@ -853,7 +984,7 @@ if (!window.hasAddedQortalListener) {
|
||||
{ action: event.data.action, type: 'qortalRequest', payload: event.data },
|
||||
event.ports[0]
|
||||
);
|
||||
} else if (event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' || event?.data?.action === 'PUBLISH_QDN_RESOURCE' || event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'SAVE_FILE') {
|
||||
} else if (event?.data?.action === 'ENCRYPT_DATA') {
|
||||
let data;
|
||||
try {
|
||||
data = await storeFilesInIndexedDB(event.data);
|
||||
@ -877,6 +1008,52 @@ if (!window.hasAddedQortalListener) {
|
||||
error: 'Failed to prepare data for publishing',
|
||||
});
|
||||
}
|
||||
} else if (event?.data?.action === 'SAVE_FILE') {
|
||||
let data;
|
||||
try {
|
||||
console.log('event', event?.data)
|
||||
if(!event?.data?.location){
|
||||
data = await storeFilesInIndexedDB(event.data);
|
||||
} else {
|
||||
data = event.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error storing files in IndexedDB:', error);
|
||||
event.ports[0].postMessage({
|
||||
result: null,
|
||||
error: 'Failed to store files in IndexedDB',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
sendMessageToRuntime(
|
||||
{ action: event.data.action, type: 'qortalRequest', payload: data },
|
||||
event.ports[0]
|
||||
);
|
||||
} else {
|
||||
event.ports[0].postMessage({
|
||||
result: null,
|
||||
error: 'Failed to prepare data for publishing',
|
||||
});
|
||||
}
|
||||
} else if (event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' || event?.data?.action === 'PUBLISH_QDN_RESOURCE' ) {
|
||||
let data;
|
||||
try {
|
||||
data = saveFileReferences(event.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to store file references::', error);
|
||||
event.ports[0].postMessage({
|
||||
result: null,
|
||||
error: 'Failed to store file references',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessageToRuntime(
|
||||
{ action: event.data.action, type: 'qortalRequest', payload: data },
|
||||
event.ports[0]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -907,3 +1084,5 @@ window.addEventListener("message", (event) => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
@ -58,6 +58,16 @@ export async function getNameInfo() {
|
||||
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() {
|
||||
// const res = await chrome.storage.local.get(["keyPair"]);
|
||||
// if (res?.keyPair) {
|
||||
@ -148,7 +158,7 @@ export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({groupId,
|
||||
if(encryptedData){
|
||||
const registeredName = await getNameInfo()
|
||||
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 {
|
||||
data,
|
||||
@ -198,7 +208,7 @@ export const encryptAndPublishSymmetricKeyGroupChat = async ({groupId, previousD
|
||||
if(encryptedData){
|
||||
const registeredName = await getNameInfo()
|
||||
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 {
|
||||
data,
|
||||
@ -219,7 +229,7 @@ export const publishGroupEncryptedResource = async ({encryptedData, identifier})
|
||||
const registeredName = await getNameInfo()
|
||||
if(!registeredName) throw new Error('You need a name to publish')
|
||||
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
|
||||
|
||||
@ -230,7 +240,7 @@ export const publishGroupEncryptedResource = async ({encryptedData, identifier})
|
||||
throw new Error(error.message);
|
||||
}
|
||||
}
|
||||
export const publishOnQDN = async ({data, identifier, service, title,
|
||||
export const publishOnQDN = async ({data, name = "", identifier, service, title,
|
||||
description,
|
||||
category,
|
||||
tag1,
|
||||
@ -238,15 +248,15 @@ export const publishOnQDN = async ({data, identifier, service, title,
|
||||
tag3,
|
||||
tag4,
|
||||
tag5,
|
||||
uploadType = 'file'
|
||||
uploadType
|
||||
}) => {
|
||||
|
||||
if(data && service){
|
||||
const registeredName = await getNameInfo()
|
||||
const registeredName = name || await getNameInfo()
|
||||
if(!registeredName) throw new Error('You need a name to publish')
|
||||
|
||||
const res = await publishData({
|
||||
registeredName, file: data, service, identifier, uploadType, isBase64: true, withFee: true, title,
|
||||
registeredName, data: data, service, identifier, uploadType, withFee: true, title,
|
||||
description,
|
||||
category,
|
||||
tag1,
|
||||
@ -254,7 +264,6 @@ export const publishOnQDN = async ({data, identifier, service, title,
|
||||
tag3,
|
||||
tag4,
|
||||
tag5
|
||||
|
||||
})
|
||||
return res
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext, useEffect, useMemo, useState } from "react";
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
@ -49,6 +49,8 @@ import { LoadingSnackbar } from "../Snackbar/LoadingSnackbar";
|
||||
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
|
||||
import { getFee } from "../../background";
|
||||
import { fileToBase64 } from "../../utils/fileReading";
|
||||
import { publishData } from "../../qdn/publish/pubish";
|
||||
import { useSortedMyNames } from "../../hooks/useSortedMyNames";
|
||||
|
||||
const CustomSelect = styled(Select)({
|
||||
border: "0.5px solid var(--50-white, #FFFFFF80)",
|
||||
@ -82,7 +84,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 [title, setTitle] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
@ -98,6 +101,8 @@ export const AppPublish = ({ names, categories }) => {
|
||||
const [tag5, setTag5] = useState("");
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const mySortedNames = useSortedMyNames(names, myName);
|
||||
|
||||
const [isLoading, setIsLoading] = useState("");
|
||||
const maxFileSize = appType === "APP" ? 50 * 1024 * 1024 : 400 * 1024 * 1024; // 50MB or 400MB
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
@ -126,6 +131,25 @@ export const AppPublish = ({ names, categories }) => {
|
||||
},
|
||||
});
|
||||
|
||||
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 getQapp = React.useCallback(async (name, appType) => {
|
||||
try {
|
||||
setIsLoading("Loading app information");
|
||||
@ -199,34 +223,16 @@ export const AppPublish = ({ names, categories }) => {
|
||||
publishFee: fee.fee + " QORT",
|
||||
});
|
||||
setIsLoading("Publishing... Please wait.");
|
||||
const fileBase64 = await fileToBase64(file);
|
||||
await new Promise((res, rej) => {
|
||||
chrome?.runtime?.sendMessage(
|
||||
{
|
||||
action: "publishOnQDN",
|
||||
payload: {
|
||||
data: fileBase64,
|
||||
service: appType,
|
||||
title,
|
||||
description,
|
||||
category,
|
||||
tag1,
|
||||
tag2,
|
||||
tag3,
|
||||
tag4,
|
||||
tag5,
|
||||
uploadType: 'zip'
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
if (!response?.error) {
|
||||
res(response);
|
||||
return;
|
||||
}
|
||||
rej(response.error);
|
||||
}
|
||||
);
|
||||
});
|
||||
await publishData({
|
||||
registeredName: name, data: file, service: appType, identifier: null, uploadType: 'zip', withFee: true, title,
|
||||
description,
|
||||
category,
|
||||
tag1,
|
||||
tag2,
|
||||
tag3,
|
||||
tag4,
|
||||
tag5
|
||||
})
|
||||
setInfoSnack({
|
||||
type: "success",
|
||||
message:
|
||||
@ -288,7 +294,7 @@ export const AppPublish = ({ names, categories }) => {
|
||||
</em>{" "}
|
||||
{/* This is the placeholder item */}
|
||||
</CustomMenuItem>
|
||||
{names.map((name) => {
|
||||
{mySortedNames.map((name) => {
|
||||
return <CustomMenuItem value={name}>{name}</CustomMenuItem>;
|
||||
})}
|
||||
</CustomSelect>
|
||||
|
@ -25,7 +25,7 @@ import { AppsIcon } from "../../assets/Icons/AppsIcon";
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktopSideView, hasUnreadDirects, isDirects, isGroups, hasUnreadGroups, toggleSideViewGroups, toggleSideViewDirects, setDesktopViewMode, isApps, desktopViewMode}) => {
|
||||
export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktopSideView, hasUnreadDirects, isDirects, isGroups, hasUnreadGroups, toggleSideViewGroups, toggleSideViewDirects, setDesktopViewMode, isApps, desktopViewMode, myAddress}) => {
|
||||
const [availableQapps, setAvailableQapps] = useState([]);
|
||||
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
|
||||
const [selectedCategory, setSelectedCategory] = useState(null)
|
||||
@ -395,7 +395,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
||||
}}>
|
||||
|
||||
<Spacer height="30px" />
|
||||
<AppsHomeDesktop myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||
<AppsHomeDesktop myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myAddress={myAddress} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@ -412,7 +412,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
||||
{mode === "appInfo" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
|
||||
{mode === "appInfo-from-category" && !selectedTab && <AppInfo app={selectedAppInfo} myName={myName} />}
|
||||
<AppsCategoryDesktop 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) => {
|
||||
if (!iframeRefs.current[tab.tabId]) {
|
||||
iframeRefs.current[tab.tabId] = React.createRef();
|
||||
@ -440,7 +440,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
||||
}}>
|
||||
|
||||
<Spacer height="30px" />
|
||||
<AppsHomeDesktop myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||
<AppsHomeDesktop myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} myAddress={myAddress} />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
@ -23,7 +23,8 @@ export const AppsHomeDesktop = ({
|
||||
myApp,
|
||||
myWebsite,
|
||||
availableQapps,
|
||||
myName
|
||||
myName,
|
||||
myAddress
|
||||
}) => {
|
||||
const [qortalUrl, setQortalUrl] = useState('')
|
||||
|
||||
@ -138,7 +139,7 @@ export const AppsHomeDesktop = ({
|
||||
<AppCircleLabel>Library</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
<AppsPrivate myName={myName} />
|
||||
<AppsPrivate myName={myName} myAddress={myAddress} />
|
||||
<SortablePinnedApps
|
||||
isDesktop={true}
|
||||
availableQapps={availableQapps}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
@ -30,14 +30,17 @@ import {
|
||||
PublishQAppInfo,
|
||||
} from "./Apps-styles";
|
||||
import ImageUploader from "../../common/ImageUploader";
|
||||
import { isMobile, MyContext } from "../../App";
|
||||
import { getBaseApiReact, isMobile, MyContext } from "../../App";
|
||||
import { fileToBase64 } from "../../utils/fileReading";
|
||||
import { objectToBase64 } from "../../qdn/encryption/group-encryption";
|
||||
import { getFee } from "../../background";
|
||||
import { useSortedMyNames } from "../../hooks/useSortedMyNames";
|
||||
|
||||
const maxFileSize = 50 * 1024 * 1024; // 50MB
|
||||
|
||||
export const AppsPrivate = ({myName}) => {
|
||||
export const AppsPrivate = ({myName, myAddress}) => {
|
||||
const [names, setNames] = useState([]);
|
||||
const [name, setName] = useState(0);
|
||||
const { openApp } = useHandlePrivateApps();
|
||||
const [file, setFile] = useState(null);
|
||||
const [logo, setLogo] = useState(null);
|
||||
@ -49,6 +52,9 @@ export const AppsPrivate = ({myName}) => {
|
||||
myGroupsWhereIAmAdminAtom
|
||||
);
|
||||
|
||||
const mySortedNames = useSortedMyNames(names, myName);
|
||||
|
||||
|
||||
const myGroupsWhereIAmAdmin = useMemo(()=> {
|
||||
return myGroupsWhereIAmAdminFromGlobal?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false)
|
||||
}, [myGroupsWhereIAmAdminFromGlobal, groupsProperties])
|
||||
@ -180,6 +186,8 @@ export const AppsPrivate = ({myName}) => {
|
||||
data: decryptedData,
|
||||
identifier: newPrivateAppValues?.identifier,
|
||||
service: newPrivateAppValues?.service,
|
||||
uploadType: 'base64',
|
||||
name
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
@ -195,7 +203,7 @@ export const AppsPrivate = ({myName}) => {
|
||||
{
|
||||
identifier: newPrivateAppValues?.identifier,
|
||||
service: newPrivateAppValues?.service,
|
||||
name: myName,
|
||||
name: name,
|
||||
groupId: selectedGroup,
|
||||
},
|
||||
true
|
||||
@ -210,6 +218,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) => {
|
||||
setValueTabPrivateApp(newValue);
|
||||
};
|
||||
@ -446,6 +470,33 @@ export const AppsPrivate = ({myName}) => {
|
||||
{file ? "Change" : "Choose"} File
|
||||
</PublishQAppChoseFile>
|
||||
<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
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
@ -105,6 +105,35 @@ export const createAndCopyEmbedLink = async (data) => {
|
||||
|
||||
};
|
||||
|
||||
const fileReferences = {}
|
||||
|
||||
|
||||
function saveFileReferences(obj) {
|
||||
if (obj.file) {
|
||||
const fileId = "objFile_qortalfile_" + Date.now();
|
||||
|
||||
fileReferences[fileId] = obj.file
|
||||
obj.fileId = fileId;
|
||||
}
|
||||
if (obj.blob) {
|
||||
const fileId = "objFile_qortalfile_" + Date.now();
|
||||
|
||||
fileReferences[fileId] = obj.blob
|
||||
obj.fileId = fileId
|
||||
}
|
||||
|
||||
// Iterate through resources to find files and save them to IndexedDB
|
||||
for (let resource of (obj?.resources || [])) {
|
||||
if (resource.file) {
|
||||
const fileId = resource.identifier + "_qortalfile_" + Date.now();
|
||||
|
||||
fileReferences[fileId] = resource.file
|
||||
resource.fileId = fileId
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
class Semaphore {
|
||||
constructor(count) {
|
||||
this.count = count
|
||||
@ -235,7 +264,85 @@ async function handleGetFileFromIndexedDB(fileId, sendResponse) {
|
||||
}
|
||||
|
||||
|
||||
async function reusablePostStream(endpoint, _body) {
|
||||
|
||||
const headers = {};
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: _body,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
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 handleSendDataChunksToCore(fileId, chunkUrl, sendResponse){
|
||||
try {
|
||||
if(!fileReferences[fileId]) throw new Error('No file reference found')
|
||||
const chunkSize = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
const file = fileReferences[fileId]
|
||||
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);
|
||||
|
||||
await uploadChunkWithRetry(chunkUrl, formData, index);
|
||||
}
|
||||
sendResponse({ result: true });
|
||||
} catch (error) {
|
||||
sendResponse({ result: null, error: error?.message || "Could not save chunks to the core" });
|
||||
} finally {
|
||||
if(fileReferences[fileId]){
|
||||
delete fileReferences[fileId]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGetFileBase64(fileId, sendResponse){
|
||||
try {
|
||||
if(!fileReferences[fileId]) throw new Error('No file reference found')
|
||||
const base64 = await fileToBase64(fileReferences[fileId]);
|
||||
sendResponse({ result: base64 });
|
||||
} catch (error) {
|
||||
sendResponse({ result: null, error: error?.message || "Could not save chunks to the core" });
|
||||
} finally {
|
||||
if(fileReferences[fileId]){
|
||||
delete fileReferences[fileId]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const UIQortalRequests = [
|
||||
@ -329,6 +436,20 @@ const UIQortalRequests = [
|
||||
}
|
||||
|
||||
const showSaveFilePicker = async (data) => {
|
||||
if(data?.locationEndpoint){
|
||||
try {
|
||||
const a = document.createElement('a');
|
||||
|
||||
a.href = data?.locationEndpoint;
|
||||
a.download = data.filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
let blob
|
||||
let fileName
|
||||
try {
|
||||
@ -539,9 +660,7 @@ isDOMContentLoaded: false
|
||||
event.ports[0]
|
||||
);
|
||||
} else if (
|
||||
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
|
||||
event?.data?.action === 'PUBLISH_QDN_RESOURCE' ||
|
||||
event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'SAVE_FILE' || 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'
|
||||
|
||||
) {
|
||||
let data;
|
||||
@ -566,6 +685,54 @@ isDOMContentLoaded: false
|
||||
error: 'Failed to prepare data for publishing',
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
event?.data?.action === 'SAVE_FILE'
|
||||
|
||||
) {
|
||||
let data;
|
||||
try {
|
||||
if(!event?.data?.location){
|
||||
data = await storeFilesInIndexedDB(event.data);
|
||||
} else {
|
||||
data = event?.data
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error storing files in IndexedDB:', error);
|
||||
event.ports[0].postMessage({
|
||||
result: null,
|
||||
error: 'Failed to store files in IndexedDB',
|
||||
});
|
||||
return;
|
||||
}
|
||||
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 === 'PUBLISH_MULTIPLE_QDN_RESOURCES' || event?.data?.action === 'PUBLISH_QDN_RESOURCE' ) {
|
||||
let data;
|
||||
try {
|
||||
data = saveFileReferences(event.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to store file references:', error);
|
||||
event.ports[0].postMessage({
|
||||
result: null,
|
||||
error: 'Failed to store file references',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessageToRuntime(
|
||||
{ action: event.data.action, type: 'qortalRequest', payload: data, isExtension: true },
|
||||
event.ports[0]
|
||||
);
|
||||
} else if(event?.data?.action === 'LINK_TO_QDN_RESOURCE' ||
|
||||
event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){
|
||||
const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null
|
||||
@ -679,16 +846,29 @@ isDOMContentLoaded: false
|
||||
|
||||
}, [appName, appService]); // Empty dependency array to run once when the component mounts
|
||||
|
||||
chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) {
|
||||
if(message.action === "SHOW_SAVE_FILE_PICKER"){
|
||||
showSaveFilePicker(message?.data)
|
||||
}
|
||||
useEffect(() => {
|
||||
const listener = (message, sender, sendResponse) => {
|
||||
if (message.action === 'SHOW_SAVE_FILE_PICKER') {
|
||||
showSaveFilePicker(message?.data);
|
||||
} else if (message.action === 'getFileFromIndexedDB') {
|
||||
handleGetFileFromIndexedDB(message.fileId, sendResponse);
|
||||
return true; // Keep channel open for async
|
||||
} else if (message.action === 'sendDataChunksToCore') {
|
||||
handleSendDataChunksToCore(message.fileId, message.chunkUrl, sendResponse);
|
||||
return true; // Keep channel open for async
|
||||
} else if (message.action === 'getFileBase64') {
|
||||
handleGetFileBase64(message.fileId, sendResponse);
|
||||
return true; // Keep channel open for async
|
||||
}
|
||||
};
|
||||
|
||||
else if (message.action === "getFileFromIndexedDB") {
|
||||
handleGetFileFromIndexedDB(message.fileId, sendResponse);
|
||||
return true; // Keep the message channel open for async response
|
||||
}
|
||||
});
|
||||
chrome.runtime?.onMessage.addListener(listener);
|
||||
|
||||
// ✅ Cleanup on unmount
|
||||
return () => {
|
||||
chrome.runtime?.onMessage.removeListener(listener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {path, history, resetHistory, changeCurrentIndex}
|
||||
};
|
||||
|
@ -638,6 +638,7 @@ const sendMessage = async ()=> {
|
||||
data: 'RA==',
|
||||
identifier: onEditMessage?.images[0]?.identifier,
|
||||
service: onEditMessage?.images[0]?.service,
|
||||
uploadType: 'base64',
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
|
@ -2844,7 +2844,7 @@ export const Group = ({
|
||||
{!isMobile && (
|
||||
<AppsDesktop toggleSideViewGroups={toggleSideViewGroups} toggleSideViewDirects={toggleSideViewDirects} goToHome={goToHome} mode={appsMode} setMode={setAppsMode} setDesktopSideView={setDesktopSideView} hasUnreadDirects={directChatHasUnread} show={desktopViewMode === "apps"} myName={userInfo?.name} isGroups={isOpenSideViewGroups}
|
||||
isDirects={isOpenSideViewDirects} hasUnreadGroups={groupChatHasUnread ||
|
||||
groupsAnnHasUnread} setDesktopViewMode={setDesktopViewMode} isApps={desktopViewMode === 'apps'} desktopViewMode={desktopViewMode} />
|
||||
groupsAnnHasUnread} setDesktopViewMode={setDesktopViewMode} isApps={desktopViewMode === 'apps'} desktopViewMode={desktopViewMode} myAddress={userInfo?.address} />
|
||||
)}
|
||||
|
||||
<WalletsAppWrapper />
|
||||
|
@ -229,6 +229,7 @@ export const ListOfGroupPromotions = () => {
|
||||
data: data,
|
||||
identifier: identifier,
|
||||
service: "DOCUMENT",
|
||||
uploadType: 'base64',
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
|
@ -71,7 +71,8 @@ const [isLoading, setIsLoading] = useState(false)
|
||||
payload: {
|
||||
data: avatarBase64,
|
||||
identifier: "qortal_avatar",
|
||||
service: 'THUMBNAIL'
|
||||
service: 'THUMBNAIL',
|
||||
uploadType: 'base64',
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
|
@ -154,7 +154,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
||||
payload: {
|
||||
data: encryptData,
|
||||
identifier: "ext_saved_settings",
|
||||
service: 'DOCUMENT_PRIVATE'
|
||||
service: 'DOCUMENT_PRIVATE',
|
||||
uploadType: 'base64',
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
|
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,264 +1,366 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { Buffer } from "buffer"
|
||||
import Base58 from "../../deps/Base58"
|
||||
import nacl from "../../deps/nacl-fast"
|
||||
import utils from "../../utils/utils"
|
||||
import { createEndpoint, getBaseApi, getKeyPair } from "../../background";
|
||||
import { Buffer } from 'buffer';
|
||||
import Base58 from '../../deps/Base58';
|
||||
import nacl from '../../deps/nacl-fast';
|
||||
import utils from '../../utils/utils';
|
||||
import { createEndpoint, getBaseApi, getKeyPair } from '../../background';
|
||||
import { sendDataChunksToCore } from '../../qortalRequests/get';
|
||||
|
||||
export async function reusableGet(endpoint){
|
||||
const validApi = await getBaseApi();
|
||||
export async function reusableGet(endpoint) {
|
||||
const validApi = await getBaseApi();
|
||||
|
||||
const response = await fetch(validApi + endpoint);
|
||||
const data = await response.json();
|
||||
return data
|
||||
}
|
||||
const response = await fetch(validApi + endpoint);
|
||||
const data = await response.json();
|
||||
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
|
||||
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
|
||||
let data;
|
||||
try {
|
||||
data = await response.clone().json()
|
||||
data = await response.clone().json();
|
||||
} catch (e) {
|
||||
data = await response.text()
|
||||
}
|
||||
return data
|
||||
data = await response.text();
|
||||
}
|
||||
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() {
|
||||
// const res = await chrome.storage.local.get(["keyPair"]);
|
||||
// if (res?.keyPair) {
|
||||
// return res.keyPair;
|
||||
// } else {
|
||||
// throw new Error("Wallet not authenticated");
|
||||
// }
|
||||
// }
|
||||
|
||||
export const publishData = async ({
|
||||
registeredName,
|
||||
file,
|
||||
service,
|
||||
identifier,
|
||||
uploadType,
|
||||
isBase64,
|
||||
filename,
|
||||
withFee,
|
||||
title,
|
||||
description,
|
||||
category,
|
||||
tag1,
|
||||
tag2,
|
||||
tag3,
|
||||
tag4,
|
||||
tag5,
|
||||
feeAmount
|
||||
registeredName,
|
||||
data,
|
||||
service,
|
||||
identifier,
|
||||
uploadType,
|
||||
filename,
|
||||
withFee,
|
||||
title,
|
||||
description,
|
||||
category,
|
||||
tag1,
|
||||
tag2,
|
||||
tag3,
|
||||
tag4,
|
||||
tag5,
|
||||
feeAmount,
|
||||
sender
|
||||
}: any) => {
|
||||
const validateName = async (receiverName: string) => {
|
||||
return await reusableGet(`/names/${receiverName}`);
|
||||
};
|
||||
|
||||
const validateName = async (receiverName: string) => {
|
||||
return await reusableGet(`/names/${receiverName}`)
|
||||
}
|
||||
const convertBytesForSigning = async (transactionBytesBase58: string) => {
|
||||
return await reusablePost('/transactions/convert', transactionBytesBase58);
|
||||
};
|
||||
|
||||
const convertBytesForSigning = async (transactionBytesBase58: string) => {
|
||||
return await reusablePost('/transactions/convert', transactionBytesBase58)
|
||||
}
|
||||
const getArbitraryFee = async () => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
const getArbitraryFee = async () => {
|
||||
const timestamp = Date.now()
|
||||
let fee = await reusableGet(
|
||||
`/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 {
|
||||
timestamp,
|
||||
fee: Number(fee),
|
||||
feeToShow: (Number(fee) / 1e8).toFixed(8)
|
||||
}
|
||||
}
|
||||
|
||||
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 signArbitraryWithFee = (
|
||||
arbitraryBytesBase58,
|
||||
arbitraryBytesForSigningBase58,
|
||||
keyPair
|
||||
) => {
|
||||
if (!arbitraryBytesBase58) {
|
||||
throw new Error('ArbitraryBytesBase58 not defined');
|
||||
}
|
||||
|
||||
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) => {
|
||||
let convertedBytesBase58 = await convertBytesForSigning(
|
||||
transactionBytesBase58
|
||||
)
|
||||
return utils.appendBuffer(arbitraryBytesBuffer, signature);
|
||||
};
|
||||
|
||||
if (convertedBytesBase58.error) {
|
||||
throw new Error('Error when signing')
|
||||
}
|
||||
const processTransactionVersion2 = async (bytes) => {
|
||||
return await reusablePost(
|
||||
'/transactions/process?apiVersion=2',
|
||||
Base58.encode(bytes)
|
||||
);
|
||||
};
|
||||
|
||||
const signAndProcessWithFee = async (transactionBytesBase58: string) => {
|
||||
let convertedBytesBase58 = await convertBytesForSigning(
|
||||
transactionBytesBase58
|
||||
);
|
||||
|
||||
const resKeyPair = await getKeyPair()
|
||||
const parsedData = JSON.parse(resKeyPair)
|
||||
const uint8PrivateKey = Base58.decode(parsedData.privateKey);
|
||||
const uint8PublicKey = Base58.decode(parsedData.publicKey);
|
||||
const keyPair = {
|
||||
privateKey: uint8PrivateKey,
|
||||
publicKey: uint8PublicKey
|
||||
};
|
||||
if (convertedBytesBase58.error) {
|
||||
throw new Error('Error when signing');
|
||||
}
|
||||
|
||||
let signedArbitraryBytes = signArbitraryWithFee(transactionBytesBase58, convertedBytesBase58, keyPair)
|
||||
const response = await processTransactionVersion2(signedArbitraryBytes)
|
||||
const resKeyPair = await getKeyPair()
|
||||
const parsedData = JSON.parse(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) {
|
||||
throw new Error('Error when signing')
|
||||
} else {
|
||||
myResponse = response
|
||||
}
|
||||
let myResponse = { error: '' };
|
||||
|
||||
return myResponse
|
||||
}
|
||||
if (response === false) {
|
||||
throw new Error('Error when signing');
|
||||
} else {
|
||||
myResponse = response;
|
||||
}
|
||||
|
||||
const validate = async () => {
|
||||
let validNameRes = await validateName(registeredName)
|
||||
return myResponse;
|
||||
};
|
||||
|
||||
if (validNameRes.error) {
|
||||
throw new Error('Name not found')
|
||||
}
|
||||
const validate = async () => {
|
||||
let validNameRes = await validateName(registeredName);
|
||||
|
||||
let fee = null
|
||||
if (validNameRes.error) {
|
||||
throw new Error('Name not found');
|
||||
}
|
||||
|
||||
if (withFee && feeAmount) {
|
||||
fee = feeAmount
|
||||
} else if (withFee) {
|
||||
const res = await getArbitraryFee()
|
||||
if (res.fee) {
|
||||
fee = res.fee
|
||||
} else {
|
||||
throw new Error('unable to get fee')
|
||||
}
|
||||
}
|
||||
let fee = null;
|
||||
|
||||
let transactionBytes = await uploadData(registeredName, file, 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 && feeAmount) {
|
||||
fee = feeAmount;
|
||||
} else if (withFee) {
|
||||
const res = await getArbitraryFee();
|
||||
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) {
|
||||
signAndProcessRes = await signAndProcessWithFee(transactionBytes)
|
||||
}
|
||||
let signAndProcessRes;
|
||||
|
||||
if (signAndProcessRes?.error) {
|
||||
throw new Error('Error when signing')
|
||||
}
|
||||
if (withFee) {
|
||||
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 = ''
|
||||
let urlSuffix = ''
|
||||
const uploadData = async (registeredName: string, data: any, fee: number) => {
|
||||
let postBody = '';
|
||||
let urlSuffix = '';
|
||||
|
||||
if (file != null) {
|
||||
// If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API
|
||||
if (uploadType === 'zip') {
|
||||
urlSuffix = '/zip'
|
||||
}
|
||||
if (data != null) {
|
||||
if (uploadType === 'base64') {
|
||||
urlSuffix = '/base64';
|
||||
}
|
||||
|
||||
// If we're sending file data, use the /base64 version of the POST /arbitrary/* API
|
||||
else if (uploadType === 'file') {
|
||||
urlSuffix = '/base64'
|
||||
}
|
||||
if (uploadType === 'base64') {
|
||||
postBody = data;
|
||||
}
|
||||
} else {
|
||||
throw new Error('No data provided');
|
||||
}
|
||||
|
||||
// Base64 encode the file to work around compatibility issues between javascript and java byte arrays
|
||||
if (isBase64) {
|
||||
postBody = file
|
||||
}
|
||||
let uploadDataUrl = `/arbitrary/${service}/${registeredName}`;
|
||||
let paramQueries = '';
|
||||
if (identifier?.trim().length > 0) {
|
||||
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`;
|
||||
}
|
||||
|
||||
if (!isBase64) {
|
||||
let fileBuffer = new Uint8Array(await file.arrayBuffer())
|
||||
postBody = Buffer.from(fileBuffer).toString("base64")
|
||||
}
|
||||
paramQueries = paramQueries + `?fee=${fee}`;
|
||||
|
||||
}
|
||||
if (filename != null && filename != 'undefined') {
|
||||
paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename);
|
||||
}
|
||||
|
||||
let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}`
|
||||
if (identifier?.trim().length > 0) {
|
||||
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}`
|
||||
}
|
||||
if (title != null && title != 'undefined') {
|
||||
paramQueries = paramQueries + '&title=' + encodeURIComponent(title);
|
||||
}
|
||||
|
||||
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') {
|
||||
uploadDataUrl = uploadDataUrl + '&filename=' + encodeURIComponent(filename)
|
||||
}
|
||||
if (tag1 != null && tag1 != 'undefined') {
|
||||
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1);
|
||||
}
|
||||
|
||||
if (title != null && title != 'undefined') {
|
||||
uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title)
|
||||
}
|
||||
if (tag2 != null && tag2 != 'undefined') {
|
||||
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2);
|
||||
}
|
||||
|
||||
if (description != null && description != 'undefined') {
|
||||
uploadDataUrl = uploadDataUrl + '&description=' + encodeURIComponent(description)
|
||||
}
|
||||
if (tag3 != null && tag3 != 'undefined') {
|
||||
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3);
|
||||
}
|
||||
|
||||
if (category != null && category != 'undefined') {
|
||||
uploadDataUrl = uploadDataUrl + '&category=' + encodeURIComponent(category)
|
||||
}
|
||||
if (tag4 != null && tag4 != 'undefined') {
|
||||
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4);
|
||||
}
|
||||
|
||||
if (tag1 != null && tag1 != 'undefined') {
|
||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1)
|
||||
}
|
||||
if (tag5 != null && tag5 != 'undefined') {
|
||||
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag5);
|
||||
}
|
||||
if (uploadType === 'zip') {
|
||||
paramQueries = paramQueries + '&isZip=' + true;
|
||||
}
|
||||
|
||||
if (tag2 != null && tag2 != 'undefined') {
|
||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2)
|
||||
}
|
||||
if (uploadType === 'base64') {
|
||||
if (urlSuffix) {
|
||||
uploadDataUrl = uploadDataUrl + urlSuffix;
|
||||
}
|
||||
uploadDataUrl = uploadDataUrl + paramQueries;
|
||||
return await reusablePost(uploadDataUrl, postBody);
|
||||
}
|
||||
|
||||
if (tag3 != null && tag3 != 'undefined') {
|
||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3)
|
||||
}
|
||||
const file = data;
|
||||
// const urlCheck = `/arbitrary/check-tmp-space?totalSize=${file.size}`;
|
||||
|
||||
if (tag4 != null && tag4 != 'undefined') {
|
||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4)
|
||||
}
|
||||
// const checkEndpoint = await createEndpoint(urlCheck);
|
||||
// const checkRes = await fetch(checkEndpoint);
|
||||
// if (!checkRes.ok) {
|
||||
// throw new Error('Not enough space on your hard drive');
|
||||
// }
|
||||
|
||||
if (tag5 != null && tag5 != 'undefined') {
|
||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5)
|
||||
}
|
||||
const chunkUrl = uploadDataUrl + `/chunk`;
|
||||
const createdChunkUrl = await createEndpoint(chunkUrl)
|
||||
if(sender){
|
||||
await sendDataChunksToCore(file, createdChunkUrl, sender)
|
||||
|
||||
return await reusablePost(uploadDataUrl, postBody)
|
||||
} else {
|
||||
const chunkSize = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
}
|
||||
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||
|
||||
try {
|
||||
return await validate()
|
||||
} catch (error: any) {
|
||||
throw new Error(error?.message)
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
const finalizeUrl = uploadDataUrl + `/finalize` + paramQueries;
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
@ -38,7 +38,7 @@ import {
|
||||
getNameOrAddress,
|
||||
getAssetBalanceInfo
|
||||
} from "../background";
|
||||
import { decryptGroupEncryption, getNameInfo, uint8ArrayToObject } from "../backgroundFunctions/encryption";
|
||||
import { decryptGroupEncryption, getAllUserNames, getNameInfo, uint8ArrayToObject } from "../backgroundFunctions/encryption";
|
||||
import { QORT_DECIMALS } from "../constants/constants";
|
||||
import Base58 from "../deps/Base58";
|
||||
|
||||
@ -424,6 +424,39 @@ function getFileFromContentScript(fileId, sender) {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function sendDataChunksToCore(fileId, chunkUrl, sender) {
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.tabs.sendMessage(
|
||||
sender.tab.id,
|
||||
{ action: "sendDataChunksToCore", fileId: fileId, chunkUrl },
|
||||
(response) => {
|
||||
if (response && response.result) {
|
||||
resolve(response.result);
|
||||
} else {
|
||||
reject(response?.error || "Failed to retrieve file");
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function getFileBase64(fileId, sender) {
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.tabs.sendMessage(
|
||||
sender.tab.id,
|
||||
{ action: "getFileBase64", fileId: fileId },
|
||||
(response) => {
|
||||
if (response && response.result) {
|
||||
resolve(response.result);
|
||||
} else {
|
||||
reject(response?.error || "Failed to retrieve file");
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function sendToSaveFilePicker(data, sender) {
|
||||
|
||||
chrome.tabs.sendMessage(sender.tab.id, {
|
||||
@ -883,7 +916,7 @@ export const publishQDNResource = async (data: any, sender, isFromExtension) =>
|
||||
if(appFee && appFee > 0 && appFeeRecipient){
|
||||
hasAppFee = true
|
||||
}
|
||||
const registeredName = await getNameInfo();
|
||||
const registeredName = data?.name || await getNameInfo();
|
||||
const name = registeredName;
|
||||
if(!name){
|
||||
throw new Error('User has no Qortal name')
|
||||
@ -917,15 +950,16 @@ const { tag1, tag2, tag3, tag4, tag5 } = result;
|
||||
throw new Error("Encrypting data requires public keys");
|
||||
}
|
||||
|
||||
if (data.fileId) {
|
||||
data64 = await getFileFromContentScript(data.fileId, sender);
|
||||
}
|
||||
|
||||
if (data.encrypt) {
|
||||
try {
|
||||
const resKeyPair = await getKeyPair()
|
||||
const parsedData = JSON.parse(resKeyPair)
|
||||
const privateKey = parsedData.privateKey
|
||||
const userPublicKey = parsedData.publicKey
|
||||
if (data?.fileId) {
|
||||
data64 = await getFileBase64(data?.fileId, sender);
|
||||
}
|
||||
const encryptDataResponse = encryptDataGroup({
|
||||
data64,
|
||||
publicKeys: data.publicKeys,
|
||||
@ -973,11 +1007,10 @@ const { tag1, tag2, tag3, tag4, tag5 } = result;
|
||||
try {
|
||||
const resPublish = await publishData({
|
||||
registeredName: encodeURIComponent(name),
|
||||
file: data64,
|
||||
data: data64 ? data64 : data?.fileId,
|
||||
service: service,
|
||||
identifier: encodeURIComponent(identifier),
|
||||
uploadType: "file",
|
||||
isBase64: true,
|
||||
uploadType: data64 ? "base64" : "file",
|
||||
filename: filename,
|
||||
title,
|
||||
description,
|
||||
@ -989,6 +1022,7 @@ const { tag1, tag2, tag3, tag4, tag5 } = result;
|
||||
tag5,
|
||||
apiVersion: 2,
|
||||
withFee: true,
|
||||
sender
|
||||
});
|
||||
if(resPublish?.signature && hasAppFee && checkbox1){
|
||||
sendCoinFunc({
|
||||
@ -1082,6 +1116,14 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
||||
if(!name){
|
||||
throw new Error('You need a Qortal name to publish.')
|
||||
}
|
||||
|
||||
const userNames = await getAllUserNames();
|
||||
data.resources?.forEach((item) => {
|
||||
if (item?.name && !userNames?.includes(item.name))
|
||||
throw new Error(
|
||||
`The name ${item.name}, does not belong to the publisher.`
|
||||
);
|
||||
});
|
||||
const appFee = data?.appFee ? +data.appFee : undefined
|
||||
const appFeeRecipient = data?.appFeeRecipient
|
||||
let hasAppFee = false
|
||||
@ -1208,7 +1250,8 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
||||
}
|
||||
const service = resource.service;
|
||||
let identifier = resource.identifier;
|
||||
let data64 = resource?.data64 || resource?.base64;
|
||||
// let data64 = resource?.data64 || resource?.base64;
|
||||
let rawData = resource?.data64 || resource?.base64;
|
||||
const filename = resource.filename;
|
||||
const title = resource.title;
|
||||
const description = resource.description;
|
||||
@ -1232,26 +1275,34 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
||||
failedPublishesIdentifiers.push({
|
||||
reason: errorMsg,
|
||||
identifier: resource.identifier,
|
||||
service: resource.service,
|
||||
name: resource?.name || name,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (resource.fileId) {
|
||||
data64 = await getFileFromContentScript(resource.fileId, sender);
|
||||
rawData = resource.fileId;
|
||||
}
|
||||
// if (resource.fileId) {
|
||||
// data64 = await sendDataChunksToCore(resource.fileId, sender);
|
||||
// }
|
||||
if (resourceEncrypt) {
|
||||
try {
|
||||
if (resource?.fileId) {
|
||||
rawData = await getFileBase64(resource.fileId, sender);
|
||||
}
|
||||
const resKeyPair = await getKeyPair()
|
||||
const parsedData = JSON.parse(resKeyPair)
|
||||
const privateKey = parsedData.privateKey
|
||||
const userPublicKey = parsedData.publicKey
|
||||
const encryptDataResponse = encryptDataGroup({
|
||||
data64,
|
||||
data64: rawData,
|
||||
publicKeys: data.publicKeys,
|
||||
privateKey,
|
||||
userPublicKey
|
||||
});
|
||||
if (encryptDataResponse) {
|
||||
data64 = encryptDataResponse;
|
||||
rawData = encryptDataResponse;
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg =
|
||||
@ -1260,20 +1311,24 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
||||
reason: errorMsg,
|
||||
identifier: resource.identifier,
|
||||
service: resource.service,
|
||||
name: resource?.name || name,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const dataType =
|
||||
(resource?.base64 || resource?.data64 || resourceEncrypt)
|
||||
? 'base64'
|
||||
: 'file';
|
||||
await retryTransaction(publishData, [
|
||||
{
|
||||
registeredName: encodeURIComponent(name),
|
||||
file: data64,
|
||||
data: rawData,
|
||||
service: service,
|
||||
identifier: encodeURIComponent(identifier),
|
||||
uploadType: "file",
|
||||
isBase64: true,
|
||||
uploadType: dataType,
|
||||
filename: filename,
|
||||
title,
|
||||
description,
|
||||
@ -1285,6 +1340,7 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
||||
tag5,
|
||||
apiVersion: 2,
|
||||
withFee: true,
|
||||
sender
|
||||
},
|
||||
], true);
|
||||
await new Promise((res) => {
|
||||
@ -1298,6 +1354,7 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
||||
reason: errorMsg,
|
||||
identifier: resource.identifier,
|
||||
service: resource.service,
|
||||
name: resource?.name || name,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@ -1305,6 +1362,7 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
||||
reason: error?.message || "Unknown error",
|
||||
identifier: resource.identifier,
|
||||
service: resource.service,
|
||||
name: resource?.name || name,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1683,6 +1741,46 @@ export const joinGroup = async (data, isFromExtension) => {
|
||||
|
||||
export const saveFile = async (data, sender, isFromExtension) => {
|
||||
try {
|
||||
if (!data?.filename) throw new Error('Missing filename');
|
||||
if (data?.location) {
|
||||
const requiredFieldsLocation = ['service', 'name'];
|
||||
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?.filename}`,
|
||||
},
|
||||
isFromExtension
|
||||
);
|
||||
const { accepted } = resPermission;
|
||||
if (!accepted) throw new Error('User declined to save file');
|
||||
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?.filename}`
|
||||
);
|
||||
|
||||
sendToSaveFilePicker(
|
||||
{
|
||||
locationEndpoint: endpoint,
|
||||
filename: data.filename
|
||||
},
|
||||
sender
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const requiredFields = ["filename", "fileId"];
|
||||
const missingFields: string[] = [];
|
||||
requiredFields.forEach((field) => {
|
||||
@ -4084,9 +4182,10 @@ export const updateNameRequest = async (data, isFromExtension) => {
|
||||
const fee = await getFee("UPDATE_NAME");
|
||||
const resPermission = await getUserPermission(
|
||||
{
|
||||
text1: `Do you give this application permission to register this name?`,
|
||||
highlightedText: data.newName,
|
||||
text2: data?.description,
|
||||
text1: `Do you give this application permission to update this name?`,
|
||||
text2: `previous name: ${oldName}`,
|
||||
text3: `new name: ${newName}`,
|
||||
text4: data?.description,
|
||||
fee: fee.fee,
|
||||
},
|
||||
isFromExtension
|
||||
@ -4807,7 +4906,7 @@ export const updateGroupRequest = async (data, isFromExtension) => {
|
||||
const requiredFields = ["groupId", "newOwner", "type", "approvalThreshold", "minBlock", "maxBlock"];
|
||||
const missingFields: string[] = [];
|
||||
requiredFields.forEach((field) => {
|
||||
if (data[field] !== undefined && data[field] !== null) {
|
||||
if (data[field] === undefined || data[field] === null) {
|
||||
missingFields.push(field);
|
||||
}
|
||||
});
|
||||
@ -4872,7 +4971,7 @@ export const sellNameRequest = async (data, isFromExtension) => {
|
||||
const requiredFields = ["salePrice", "nameForSale"];
|
||||
const missingFields: string[] = [];
|
||||
requiredFields.forEach((field) => {
|
||||
if (data[field] !== undefined && data[field] !== null) {
|
||||
if (data[field] === undefined || data[field] === null) {
|
||||
missingFields.push(field);
|
||||
}
|
||||
});
|
||||
@ -4917,7 +5016,7 @@ export const cancelSellNameRequest = async (data, isFromExtension) => {
|
||||
const requiredFields = ["nameForSale"];
|
||||
const missingFields: string[] = [];
|
||||
requiredFields.forEach((field) => {
|
||||
if (data[field] !== undefined && data[field] !== null) {
|
||||
if (data[field] === undefined || data[field] === null) {
|
||||
missingFields.push(field);
|
||||
}
|
||||
});
|
||||
@ -4958,7 +5057,7 @@ export const buyNameRequest = async (data, isFromExtension) => {
|
||||
const requiredFields = ["nameForSale"];
|
||||
const missingFields: string[] = [];
|
||||
requiredFields.forEach((field) => {
|
||||
if (data[field] !== undefined && data[field] !== null) {
|
||||
if (data[field] === undefined || data[field] === null) {
|
||||
missingFields.push(field);
|
||||
}
|
||||
});
|
||||
@ -5261,12 +5360,11 @@ const assetBalance = await getAssetBalanceInfo(assetId)
|
||||
const resPublish = await retryTransaction(publishData, [
|
||||
{
|
||||
registeredName: encodeURIComponent(name),
|
||||
file: encryptDataResponse,
|
||||
data: encryptDataResponse,
|
||||
service: transaction.service,
|
||||
identifier: encodeURIComponent(transaction.identifier),
|
||||
uploadType: "file",
|
||||
uploadType: "base64",
|
||||
description: transaction?.description,
|
||||
isBase64: true,
|
||||
apiVersion: 2,
|
||||
withFee: true,
|
||||
},
|
||||
@ -5302,12 +5400,11 @@ const assetBalance = await getAssetBalanceInfo(assetId)
|
||||
const resPublish = await retryTransaction(publishData, [
|
||||
{
|
||||
registeredName: encodeURIComponent(name),
|
||||
file: encryptDataResponse,
|
||||
data: encryptDataResponse,
|
||||
service: transaction.service,
|
||||
identifier: encodeURIComponent(transaction.identifier),
|
||||
uploadType: "file",
|
||||
uploadType: "base64",
|
||||
description: transaction?.description,
|
||||
isBase64: true,
|
||||
apiVersion: 2,
|
||||
withFee: true,
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user