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)=> {
|
const testAsync = async (sendResponse)=> {
|
||||||
await new Promise((res)=> {
|
await new Promise((res)=> {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -573,6 +654,22 @@ const saveFile = (blob, filename) => {
|
|||||||
|
|
||||||
|
|
||||||
const showSaveFilePicker = async (data) => {
|
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 blob
|
||||||
let fileName
|
let fileName
|
||||||
try {
|
try {
|
||||||
@ -599,7 +696,10 @@ const showSaveFilePicker = async (data) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!window.hasAddedChromeMessageListener) {
|
||||||
|
|
||||||
chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) {
|
chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) {
|
||||||
|
window.hasAddedChromeMessageListener = true;
|
||||||
if (message.type === "LOGOUT") {
|
if (message.type === "LOGOUT") {
|
||||||
// Notify the web page
|
// Notify the web page
|
||||||
window.postMessage(
|
window.postMessage(
|
||||||
@ -621,14 +721,18 @@ chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse)
|
|||||||
);
|
);
|
||||||
} else if(message.action === "SHOW_SAVE_FILE_PICKER"){
|
} else if(message.action === "SHOW_SAVE_FILE_PICKER"){
|
||||||
showSaveFilePicker(message?.data)
|
showSaveFilePicker(message?.data)
|
||||||
}
|
} else if (message.action === "getFileFromIndexedDB") {
|
||||||
|
|
||||||
else if (message.action === "getFileFromIndexedDB") {
|
|
||||||
handleGetFileFromIndexedDB(message.fileId, sendResponse);
|
handleGetFileFromIndexedDB(message.fileId, sendResponse);
|
||||||
return true; // Keep the message channel open for async response
|
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() {
|
function openIndexedDB() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const request = indexedDB.open("fileStorageDB", 1);
|
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']
|
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 },
|
{ action: event.data.action, type: 'qortalRequest', payload: event.data },
|
||||||
event.ports[0]
|
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;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = await storeFilesInIndexedDB(event.data);
|
data = await storeFilesInIndexedDB(event.data);
|
||||||
@ -877,6 +1008,52 @@ if (!window.hasAddedQortalListener) {
|
|||||||
error: 'Failed to prepare data for publishing',
|
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 "";
|
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 chrome.storage.local.get(["keyPair"]);
|
// const res = await chrome.storage.local.get(["keyPair"]);
|
||||||
// if (res?.keyPair) {
|
// if (res?.keyPair) {
|
||||||
@ -148,7 +158,7 @@ export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({groupId,
|
|||||||
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,
|
||||||
@ -198,7 +208,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,
|
||||||
@ -219,7 +229,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
|
||||||
|
|
||||||
@ -230,7 +240,7 @@ export const publishGroupEncryptedResource = async ({encryptedData, identifier})
|
|||||||
throw new Error(error.message);
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const publishOnQDN = async ({data, identifier, service, title,
|
export const publishOnQDN = async ({data, name = "", identifier, service, title,
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
tag1,
|
tag1,
|
||||||
@ -238,15 +248,15 @@ export const publishOnQDN = async ({data, identifier, service, title,
|
|||||||
tag3,
|
tag3,
|
||||||
tag4,
|
tag4,
|
||||||
tag5,
|
tag5,
|
||||||
uploadType = 'file'
|
uploadType
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
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: data, service, identifier, uploadType, withFee: true, title,
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
tag1,
|
tag1,
|
||||||
@ -254,7 +264,6 @@ export const publishOnQDN = async ({data, identifier, service, title,
|
|||||||
tag3,
|
tag3,
|
||||||
tag4,
|
tag4,
|
||||||
tag5
|
tag5
|
||||||
|
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -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,8 @@ 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 { publishData } from "../../qdn/publish/pubish";
|
||||||
|
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 +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 [name, setName] = useState("");
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
@ -98,6 +101,8 @@ export const AppPublish = ({ names, categories }) => {
|
|||||||
const [tag5, setTag5] = useState("");
|
const [tag5, setTag5] = useState("");
|
||||||
const [openSnack, setOpenSnack] = useState(false);
|
const [openSnack, setOpenSnack] = useState(false);
|
||||||
const [infoSnack, setInfoSnack] = useState(null);
|
const [infoSnack, setInfoSnack] = useState(null);
|
||||||
|
const mySortedNames = useSortedMyNames(names, myName);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState("");
|
const [isLoading, setIsLoading] = useState("");
|
||||||
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({
|
||||||
@ -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) => {
|
const getQapp = React.useCallback(async (name, appType) => {
|
||||||
try {
|
try {
|
||||||
setIsLoading("Loading app information");
|
setIsLoading("Loading app information");
|
||||||
@ -199,34 +223,16 @@ 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 publishData({
|
||||||
await new Promise((res, rej) => {
|
registeredName: name, data: file, service: appType, identifier: null, uploadType: 'zip', withFee: true, title,
|
||||||
chrome?.runtime?.sendMessage(
|
description,
|
||||||
{
|
category,
|
||||||
action: "publishOnQDN",
|
tag1,
|
||||||
payload: {
|
tag2,
|
||||||
data: fileBase64,
|
tag3,
|
||||||
service: appType,
|
tag4,
|
||||||
title,
|
tag5
|
||||||
description,
|
})
|
||||||
category,
|
|
||||||
tag1,
|
|
||||||
tag2,
|
|
||||||
tag3,
|
|
||||||
tag4,
|
|
||||||
tag5,
|
|
||||||
uploadType: 'zip'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(response) => {
|
|
||||||
if (!response?.error) {
|
|
||||||
res(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rej(response.error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
setInfoSnack({
|
setInfoSnack({
|
||||||
type: "success",
|
type: "success",
|
||||||
message:
|
message:
|
||||||
@ -288,7 +294,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>
|
||||||
|
@ -25,7 +25,7 @@ import { AppsIcon } from "../../assets/Icons/AppsIcon";
|
|||||||
|
|
||||||
const uid = new ShortUniqueId({ length: 8 });
|
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 [availableQapps, setAvailableQapps] = useState([]);
|
||||||
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
|
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
|
||||||
const [selectedCategory, setSelectedCategory] = useState(null)
|
const [selectedCategory, setSelectedCategory] = useState(null)
|
||||||
@ -395,7 +395,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
|||||||
}}>
|
}}>
|
||||||
|
|
||||||
<Spacer height="30px" />
|
<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>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -412,7 +412,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
|||||||
{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} />}
|
||||||
<AppsCategoryDesktop availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} 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) => {
|
{tabs.map((tab) => {
|
||||||
if (!iframeRefs.current[tab.tabId]) {
|
if (!iframeRefs.current[tab.tabId]) {
|
||||||
iframeRefs.current[tab.tabId] = React.createRef();
|
iframeRefs.current[tab.tabId] = React.createRef();
|
||||||
@ -440,7 +440,7 @@ export const AppsDesktop = ({ mode, setMode, show , myName, goToHome, setDesktop
|
|||||||
}}>
|
}}>
|
||||||
|
|
||||||
<Spacer height="30px" />
|
<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>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -23,7 +23,8 @@ export const AppsHomeDesktop = ({
|
|||||||
myApp,
|
myApp,
|
||||||
myWebsite,
|
myWebsite,
|
||||||
availableQapps,
|
availableQapps,
|
||||||
myName
|
myName,
|
||||||
|
myAddress
|
||||||
}) => {
|
}) => {
|
||||||
const [qortalUrl, setQortalUrl] = useState('')
|
const [qortalUrl, setQortalUrl] = useState('')
|
||||||
|
|
||||||
@ -138,7 +139,7 @@ export const AppsHomeDesktop = ({
|
|||||||
<AppCircleLabel>Library</AppCircleLabel>
|
<AppCircleLabel>Library</AppCircleLabel>
|
||||||
</AppCircleContainer>
|
</AppCircleContainer>
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<AppsPrivate myName={myName} />
|
<AppsPrivate myName={myName} myAddress={myAddress} />
|
||||||
<SortablePinnedApps
|
<SortablePinnedApps
|
||||||
isDesktop={true}
|
isDesktop={true}
|
||||||
availableQapps={availableQapps}
|
availableQapps={availableQapps}
|
||||||
|
@ -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,14 +30,17 @@ 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 [names, setNames] = useState([]);
|
||||||
|
const [name, setName] = useState(0);
|
||||||
const { openApp } = useHandlePrivateApps();
|
const { openApp } = useHandlePrivateApps();
|
||||||
const [file, setFile] = useState(null);
|
const [file, setFile] = useState(null);
|
||||||
const [logo, setLogo] = useState(null);
|
const [logo, setLogo] = useState(null);
|
||||||
@ -49,6 +52,9 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
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)
|
||||||
}, [myGroupsWhereIAmAdminFromGlobal, groupsProperties])
|
}, [myGroupsWhereIAmAdminFromGlobal, groupsProperties])
|
||||||
@ -180,6 +186,8 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
data: decryptedData,
|
data: decryptedData,
|
||||||
identifier: newPrivateAppValues?.identifier,
|
identifier: newPrivateAppValues?.identifier,
|
||||||
service: newPrivateAppValues?.service,
|
service: newPrivateAppValues?.service,
|
||||||
|
uploadType: 'base64',
|
||||||
|
name
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(response) => {
|
(response) => {
|
||||||
@ -195,7 +203,7 @@ export const AppsPrivate = ({myName}) => {
|
|||||||
{
|
{
|
||||||
identifier: newPrivateAppValues?.identifier,
|
identifier: newPrivateAppValues?.identifier,
|
||||||
service: newPrivateAppValues?.service,
|
service: newPrivateAppValues?.service,
|
||||||
name: myName,
|
name: name,
|
||||||
groupId: selectedGroup,
|
groupId: selectedGroup,
|
||||||
},
|
},
|
||||||
true
|
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) => {
|
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||||
setValueTabPrivateApp(newValue);
|
setValueTabPrivateApp(newValue);
|
||||||
};
|
};
|
||||||
@ -446,6 +470,33 @@ 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",
|
||||||
|
@ -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 {
|
class Semaphore {
|
||||||
constructor(count) {
|
constructor(count) {
|
||||||
this.count = 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 = [
|
const UIQortalRequests = [
|
||||||
@ -329,6 +436,20 @@ const UIQortalRequests = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
const showSaveFilePicker = async (data) => {
|
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 blob
|
||||||
let fileName
|
let fileName
|
||||||
try {
|
try {
|
||||||
@ -539,9 +660,7 @@ isDOMContentLoaded: false
|
|||||||
event.ports[0]
|
event.ports[0]
|
||||||
);
|
);
|
||||||
} else if (
|
} else if (
|
||||||
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
|
event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'ENCRYPT_DATA_WITH_SHARING_KEY' || event?.data?.action === 'ENCRYPT_QORTAL_GROUP_DATA'
|
||||||
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'
|
|
||||||
|
|
||||||
) {
|
) {
|
||||||
let data;
|
let data;
|
||||||
@ -566,6 +685,54 @@ isDOMContentLoaded: false
|
|||||||
error: 'Failed to prepare data for publishing',
|
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' ||
|
} 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
|
||||||
@ -679,16 +846,29 @@ isDOMContentLoaded: false
|
|||||||
|
|
||||||
}, [appName, appService]); // Empty dependency array to run once when the component mounts
|
}, [appName, appService]); // Empty dependency array to run once when the component mounts
|
||||||
|
|
||||||
chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) {
|
useEffect(() => {
|
||||||
if(message.action === "SHOW_SAVE_FILE_PICKER"){
|
const listener = (message, sender, sendResponse) => {
|
||||||
showSaveFilePicker(message?.data)
|
if (message.action === 'SHOW_SAVE_FILE_PICKER') {
|
||||||
}
|
showSaveFilePicker(message?.data);
|
||||||
|
} else if (message.action === 'getFileFromIndexedDB') {
|
||||||
else if (message.action === "getFileFromIndexedDB") {
|
handleGetFileFromIndexedDB(message.fileId, sendResponse);
|
||||||
handleGetFileFromIndexedDB(message.fileId, sendResponse);
|
return true; // Keep channel open for async
|
||||||
return true; // Keep the message channel open for async response
|
} 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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.runtime?.onMessage.addListener(listener);
|
||||||
|
|
||||||
|
// ✅ Cleanup on unmount
|
||||||
|
return () => {
|
||||||
|
chrome.runtime?.onMessage.removeListener(listener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {path, history, resetHistory, changeCurrentIndex}
|
return {path, history, resetHistory, changeCurrentIndex}
|
||||||
};
|
};
|
||||||
|
@ -638,6 +638,7 @@ const sendMessage = async ()=> {
|
|||||||
data: 'RA==',
|
data: 'RA==',
|
||||||
identifier: onEditMessage?.images[0]?.identifier,
|
identifier: onEditMessage?.images[0]?.identifier,
|
||||||
service: onEditMessage?.images[0]?.service,
|
service: onEditMessage?.images[0]?.service,
|
||||||
|
uploadType: 'base64',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(response) => {
|
(response) => {
|
||||||
|
@ -2844,7 +2844,7 @@ export const Group = ({
|
|||||||
{!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}
|
||||||
isDirects={isOpenSideViewDirects} hasUnreadGroups={groupChatHasUnread ||
|
isDirects={isOpenSideViewDirects} hasUnreadGroups={groupChatHasUnread ||
|
||||||
groupsAnnHasUnread} setDesktopViewMode={setDesktopViewMode} isApps={desktopViewMode === 'apps'} desktopViewMode={desktopViewMode} />
|
groupsAnnHasUnread} setDesktopViewMode={setDesktopViewMode} isApps={desktopViewMode === 'apps'} desktopViewMode={desktopViewMode} myAddress={userInfo?.address} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<WalletsAppWrapper />
|
<WalletsAppWrapper />
|
||||||
|
@ -229,6 +229,7 @@ export const ListOfGroupPromotions = () => {
|
|||||||
data: data,
|
data: data,
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
service: "DOCUMENT",
|
service: "DOCUMENT",
|
||||||
|
uploadType: 'base64',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(response) => {
|
(response) => {
|
||||||
|
@ -71,7 +71,8 @@ const [isLoading, setIsLoading] = useState(false)
|
|||||||
payload: {
|
payload: {
|
||||||
data: avatarBase64,
|
data: avatarBase64,
|
||||||
identifier: "qortal_avatar",
|
identifier: "qortal_avatar",
|
||||||
service: 'THUMBNAIL'
|
service: 'THUMBNAIL',
|
||||||
|
uploadType: 'base64',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(response) => {
|
(response) => {
|
||||||
|
@ -154,7 +154,8 @@ export const Save = ({ isDesktop, disableWidth, myName }) => {
|
|||||||
payload: {
|
payload: {
|
||||||
data: encryptData,
|
data: encryptData,
|
||||||
identifier: "ext_saved_settings",
|
identifier: "ext_saved_settings",
|
||||||
service: 'DOCUMENT_PRIVATE'
|
service: 'DOCUMENT_PRIVATE',
|
||||||
|
uploadType: 'base64',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(response) => {
|
(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
|
// @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, getKeyPair } from "../../background";
|
import { createEndpoint, getBaseApi, getKeyPair } from '../../background';
|
||||||
|
import { sendDataChunksToCore } from '../../qortalRequests/get';
|
||||||
|
|
||||||
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() {
|
|
||||||
// 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 ({
|
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
|
sender
|
||||||
}: any) => {
|
}: any) => {
|
||||||
|
const validateName = async (receiverName: string) => {
|
||||||
const validateName = async (receiverName: string) => {
|
return await reusableGet(`/names/${receiverName}`);
|
||||||
return await reusableGet(`/names/${receiverName}`)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const convertBytesForSigning = async (transactionBytesBase58: string) => {
|
const convertBytesForSigning = async (transactionBytesBase58: string) => {
|
||||||
return await reusablePost('/transactions/convert', transactionBytesBase58)
|
return await reusablePost('/transactions/convert', transactionBytesBase58);
|
||||||
}
|
};
|
||||||
|
|
||||||
const getArbitraryFee = async () => {
|
const getArbitraryFee = async () => {
|
||||||
const timestamp = Date.now()
|
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 {
|
return {
|
||||||
timestamp,
|
timestamp,
|
||||||
fee: Number(fee),
|
fee: Number(fee),
|
||||||
feeToShow: (Number(fee) / 1e8).toFixed(8)
|
feeToShow: (Number(fee) / 1e8).toFixed(8),
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
const signArbitraryWithFee = (arbitraryBytesBase58, arbitraryBytesForSigningBase58, keyPair) => {
|
const signArbitraryWithFee = (
|
||||||
if (!arbitraryBytesBase58) {
|
arbitraryBytesBase58,
|
||||||
throw new Error('ArbitraryBytesBase58 not defined')
|
arbitraryBytesForSigningBase58,
|
||||||
}
|
keyPair
|
||||||
|
) => {
|
||||||
if (!keyPair) {
|
if (!arbitraryBytesBase58) {
|
||||||
throw new Error('keyPair not defined')
|
throw new Error('ArbitraryBytesBase58 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 = JSON.parse(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 = 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) {
|
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 (!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')
|
|
||||||
}
|
|
||||||
|
|
||||||
let signAndProcessRes
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (withFee) {
|
let transactionBytes = await uploadData(registeredName, data, fee);
|
||||||
signAndProcessRes = await signAndProcessWithFee(transactionBytes)
|
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 (signAndProcessRes?.error) {
|
let signAndProcessRes;
|
||||||
throw new Error('Error when signing')
|
|
||||||
}
|
|
||||||
|
|
||||||
return signAndProcessRes
|
if (withFee) {
|
||||||
}
|
signAndProcessRes = await signAndProcessWithFee(transactionBytes);
|
||||||
|
}
|
||||||
|
|
||||||
const uploadData = async (registeredName: string, file:any, fee: number) => {
|
if (signAndProcessRes?.error) {
|
||||||
|
throw new Error('Error when signing');
|
||||||
|
}
|
||||||
|
|
||||||
let postBody = ''
|
return signAndProcessRes;
|
||||||
let urlSuffix = ''
|
};
|
||||||
|
|
||||||
if (file != null) {
|
const uploadData = async (registeredName: string, data: any, fee: number) => {
|
||||||
// If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API
|
let postBody = '';
|
||||||
if (uploadType === 'zip') {
|
let urlSuffix = '';
|
||||||
urlSuffix = '/zip'
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're sending file data, use the /base64 version of the POST /arbitrary/* API
|
if (data != null) {
|
||||||
else if (uploadType === 'file') {
|
if (uploadType === 'base64') {
|
||||||
urlSuffix = '/base64'
|
urlSuffix = '/base64';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base64 encode the file to work around compatibility issues between javascript and java byte arrays
|
if (uploadType === 'base64') {
|
||||||
if (isBase64) {
|
postBody = data;
|
||||||
postBody = file
|
}
|
||||||
}
|
} else {
|
||||||
|
throw new Error('No data provided');
|
||||||
|
}
|
||||||
|
|
||||||
if (!isBase64) {
|
let uploadDataUrl = `/arbitrary/${service}/${registeredName}`;
|
||||||
let fileBuffer = new Uint8Array(await file.arrayBuffer())
|
let paramQueries = '';
|
||||||
postBody = Buffer.from(fileBuffer).toString("base64")
|
if (identifier?.trim().length > 0) {
|
||||||
}
|
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
paramQueries = paramQueries + `?fee=${fee}`;
|
||||||
|
|
||||||
let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}`
|
|
||||||
if (identifier?.trim().length > 0) {
|
|
||||||
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}`
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadDataUrl = uploadDataUrl + `?fee=${fee}`
|
|
||||||
|
|
||||||
|
|
||||||
if (filename != null && filename != 'undefined') {
|
if (filename != null && filename != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&filename=' + encodeURIComponent(filename)
|
paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (title != null && title != 'undefined') {
|
if (title != null && title != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title)
|
paramQueries = paramQueries + '&title=' + encodeURIComponent(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (description != null && description != 'undefined') {
|
if (description != null && description != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&description=' + encodeURIComponent(description)
|
paramQueries =
|
||||||
}
|
paramQueries + '&description=' + encodeURIComponent(description);
|
||||||
|
}
|
||||||
|
|
||||||
if (category != null && category != 'undefined') {
|
if (category != null && category != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&category=' + encodeURIComponent(category)
|
paramQueries = paramQueries + '&category=' + encodeURIComponent(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag1 != null && tag1 != 'undefined') {
|
if (tag1 != null && tag1 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag2 != null && tag2 != 'undefined') {
|
if (tag2 != null && tag2 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag3 != null && tag3 != 'undefined') {
|
if (tag3 != null && tag3 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag4 != null && tag4 != 'undefined') {
|
if (tag4 != null && tag4 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tag5 != null && tag5 != 'undefined') {
|
if (tag5 != null && tag5 != 'undefined') {
|
||||||
uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5)
|
paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag5);
|
||||||
}
|
}
|
||||||
|
if (uploadType === 'zip') {
|
||||||
|
paramQueries = paramQueries + '&isZip=' + true;
|
||||||
|
}
|
||||||
|
|
||||||
return await reusablePost(uploadDataUrl, postBody)
|
if (uploadType === 'base64') {
|
||||||
|
if (urlSuffix) {
|
||||||
}
|
uploadDataUrl = uploadDataUrl + urlSuffix;
|
||||||
|
}
|
||||||
|
uploadDataUrl = uploadDataUrl + paramQueries;
|
||||||
|
return await reusablePost(uploadDataUrl, postBody);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
const file = data;
|
||||||
return await validate()
|
// const urlCheck = `/arbitrary/check-tmp-space?totalSize=${file.size}`;
|
||||||
} catch (error: any) {
|
|
||||||
throw new Error(error?.message)
|
// const checkEndpoint = await createEndpoint(urlCheck);
|
||||||
}
|
// const checkRes = await fetch(checkEndpoint);
|
||||||
}
|
// if (!checkRes.ok) {
|
||||||
|
// throw new Error('Not enough space on your hard drive');
|
||||||
|
// }
|
||||||
|
|
||||||
|
const chunkUrl = uploadDataUrl + `/chunk`;
|
||||||
|
const createdChunkUrl = await createEndpoint(chunkUrl)
|
||||||
|
if(sender){
|
||||||
|
await sendDataChunksToCore(file, createdChunkUrl, sender)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const chunkSize = 5 * 1024 * 1024; // 5MB
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
getNameOrAddress,
|
||||||
getAssetBalanceInfo
|
getAssetBalanceInfo
|
||||||
} from "../background";
|
} from "../background";
|
||||||
import { decryptGroupEncryption, getNameInfo, uint8ArrayToObject } from "../backgroundFunctions/encryption";
|
import { decryptGroupEncryption, getAllUserNames, getNameInfo, uint8ArrayToObject } from "../backgroundFunctions/encryption";
|
||||||
import { QORT_DECIMALS } from "../constants/constants";
|
import { QORT_DECIMALS } from "../constants/constants";
|
||||||
import Base58 from "../deps/Base58";
|
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) {
|
function sendToSaveFilePicker(data, sender) {
|
||||||
|
|
||||||
chrome.tabs.sendMessage(sender.tab.id, {
|
chrome.tabs.sendMessage(sender.tab.id, {
|
||||||
@ -883,7 +916,7 @@ export const publishQDNResource = async (data: any, sender, isFromExtension) =>
|
|||||||
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')
|
||||||
@ -917,15 +950,16 @@ const { tag1, tag2, tag3, tag4, tag5 } = result;
|
|||||||
throw new Error("Encrypting data requires public keys");
|
throw new Error("Encrypting data requires public keys");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.fileId) {
|
|
||||||
data64 = await getFileFromContentScript(data.fileId, sender);
|
|
||||||
}
|
|
||||||
if (data.encrypt) {
|
if (data.encrypt) {
|
||||||
try {
|
try {
|
||||||
const resKeyPair = await getKeyPair()
|
const resKeyPair = await getKeyPair()
|
||||||
const parsedData = JSON.parse(resKeyPair)
|
const parsedData = JSON.parse(resKeyPair)
|
||||||
const privateKey = parsedData.privateKey
|
const privateKey = parsedData.privateKey
|
||||||
const userPublicKey = parsedData.publicKey
|
const userPublicKey = parsedData.publicKey
|
||||||
|
if (data?.fileId) {
|
||||||
|
data64 = await getFileBase64(data?.fileId, sender);
|
||||||
|
}
|
||||||
const encryptDataResponse = encryptDataGroup({
|
const encryptDataResponse = encryptDataGroup({
|
||||||
data64,
|
data64,
|
||||||
publicKeys: data.publicKeys,
|
publicKeys: data.publicKeys,
|
||||||
@ -973,11 +1007,10 @@ const { tag1, tag2, tag3, tag4, tag5 } = result;
|
|||||||
try {
|
try {
|
||||||
const resPublish = await publishData({
|
const resPublish = await publishData({
|
||||||
registeredName: encodeURIComponent(name),
|
registeredName: encodeURIComponent(name),
|
||||||
file: data64,
|
data: data64 ? data64 : data?.fileId,
|
||||||
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,
|
||||||
@ -989,6 +1022,7 @@ const { tag1, tag2, tag3, tag4, tag5 } = result;
|
|||||||
tag5,
|
tag5,
|
||||||
apiVersion: 2,
|
apiVersion: 2,
|
||||||
withFee: true,
|
withFee: true,
|
||||||
|
sender
|
||||||
});
|
});
|
||||||
if(resPublish?.signature && hasAppFee && checkbox1){
|
if(resPublish?.signature && hasAppFee && checkbox1){
|
||||||
sendCoinFunc({
|
sendCoinFunc({
|
||||||
@ -1082,6 +1116,14 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
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 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 appFee = data?.appFee ? +data.appFee : undefined
|
||||||
const appFeeRecipient = data?.appFeeRecipient
|
const appFeeRecipient = data?.appFeeRecipient
|
||||||
let hasAppFee = false
|
let hasAppFee = false
|
||||||
@ -1208,7 +1250,8 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
}
|
}
|
||||||
const service = resource.service;
|
const service = resource.service;
|
||||||
let identifier = resource.identifier;
|
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 filename = resource.filename;
|
||||||
const title = resource.title;
|
const title = resource.title;
|
||||||
const description = resource.description;
|
const description = resource.description;
|
||||||
@ -1232,26 +1275,34 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
failedPublishesIdentifiers.push({
|
failedPublishesIdentifiers.push({
|
||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (resource.fileId) {
|
if (resource.fileId) {
|
||||||
data64 = await getFileFromContentScript(resource.fileId, sender);
|
rawData = resource.fileId;
|
||||||
}
|
}
|
||||||
|
// if (resource.fileId) {
|
||||||
|
// data64 = await sendDataChunksToCore(resource.fileId, sender);
|
||||||
|
// }
|
||||||
if (resourceEncrypt) {
|
if (resourceEncrypt) {
|
||||||
try {
|
try {
|
||||||
|
if (resource?.fileId) {
|
||||||
|
rawData = await getFileBase64(resource.fileId, sender);
|
||||||
|
}
|
||||||
const resKeyPair = await getKeyPair()
|
const resKeyPair = await getKeyPair()
|
||||||
const parsedData = JSON.parse(resKeyPair)
|
const parsedData = JSON.parse(resKeyPair)
|
||||||
const privateKey = parsedData.privateKey
|
const privateKey = parsedData.privateKey
|
||||||
const userPublicKey = parsedData.publicKey
|
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 =
|
||||||
@ -1260,20 +1311,24 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const dataType =
|
||||||
|
(resource?.base64 || resource?.data64 || resourceEncrypt)
|
||||||
|
? 'base64'
|
||||||
|
: 'file';
|
||||||
await retryTransaction(publishData, [
|
await retryTransaction(publishData, [
|
||||||
{
|
{
|
||||||
registeredName: encodeURIComponent(name),
|
registeredName: encodeURIComponent(name),
|
||||||
file: data64,
|
data: rawData,
|
||||||
service: service,
|
service: service,
|
||||||
identifier: encodeURIComponent(identifier),
|
identifier: encodeURIComponent(identifier),
|
||||||
uploadType: "file",
|
uploadType: dataType,
|
||||||
isBase64: true,
|
|
||||||
filename: filename,
|
filename: filename,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@ -1285,6 +1340,7 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
tag5,
|
tag5,
|
||||||
apiVersion: 2,
|
apiVersion: 2,
|
||||||
withFee: true,
|
withFee: true,
|
||||||
|
sender
|
||||||
},
|
},
|
||||||
], true);
|
], true);
|
||||||
await new Promise((res) => {
|
await new Promise((res) => {
|
||||||
@ -1298,6 +1354,7 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
reason: errorMsg,
|
reason: errorMsg,
|
||||||
identifier: resource.identifier,
|
identifier: resource.identifier,
|
||||||
service: resource.service,
|
service: resource.service,
|
||||||
|
name: resource?.name || name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1305,6 +1362,7 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten
|
|||||||
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1683,6 +1741,46 @@ export const joinGroup = async (data, isFromExtension) => {
|
|||||||
|
|
||||||
export const saveFile = async (data, sender, isFromExtension) => {
|
export const saveFile = async (data, sender, isFromExtension) => {
|
||||||
try {
|
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 requiredFields = ["filename", "fileId"];
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
@ -4084,9 +4182,10 @@ export const updateNameRequest = async (data, isFromExtension) => {
|
|||||||
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
|
||||||
@ -4807,7 +4906,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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -4872,7 +4971,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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -4917,7 +5016,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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -4958,7 +5057,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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -5261,12 +5360,11 @@ 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,
|
|
||||||
apiVersion: 2,
|
apiVersion: 2,
|
||||||
withFee: true,
|
withFee: true,
|
||||||
},
|
},
|
||||||
@ -5302,12 +5400,11 @@ 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,
|
|
||||||
apiVersion: 2,
|
apiVersion: 2,
|
||||||
withFee: true,
|
withFee: true,
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user