From 13a77762b6f91a1014df487f7ecc26da875432a3 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 17 May 2025 05:35:32 +0300 Subject: [PATCH] initial --- src/backgroundFunctions/encryption.ts | 18 +- src/components/Apps/AppPublish.tsx | 3 +- src/components/Apps/AppsPrivate.tsx | 1 + .../Apps/useQortalMessageListener.tsx | 13 +- src/components/Chat/ChatGroup.tsx | 1 + .../Group/ListOfGroupPromotions.tsx | 1 + src/components/GroupAvatar.tsx | 1 + src/components/MainAvatar.tsx | 1 + src/components/Save/Save.tsx | 1 + src/qdn/publish/pubish.ts | 523 +++++++++++------- src/qortalRequests/get.ts | 146 ++--- 11 files changed, 413 insertions(+), 296 deletions(-) diff --git a/src/backgroundFunctions/encryption.ts b/src/backgroundFunctions/encryption.ts index 6cc0150..0a1fdd4 100644 --- a/src/backgroundFunctions/encryption.ts +++ b/src/backgroundFunctions/encryption.ts @@ -166,11 +166,10 @@ export const encryptAndPublishSymmetricKeyGroupChat = async ({ const registeredName = await getNameInfo(); const data = await publishData({ registeredName, - file: encryptedData, + data: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, - uploadType: 'file', - isBase64: true, + uploadType: 'base64', withFee: true, }); return { @@ -230,11 +229,10 @@ export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({ const registeredName = await getNameInfo(); const data = await publishData({ registeredName, - file: encryptedData, + data: encryptedData, service: 'DOCUMENT_PRIVATE', identifier: `admins-symmetric-qchat-group-${groupId}`, - uploadType: 'file', - isBase64: true, + uploadType: 'base64', withFee: true, }); return { @@ -259,11 +257,10 @@ export const publishGroupEncryptedResource = async ({ if (!registeredName) throw new Error('You need a name to publish'); const data = await publishData({ registeredName, - file: encryptedData, + data: encryptedData, service: 'DOCUMENT', identifier, - uploadType: 'file', - isBase64: true, + uploadType: 'base64', withFee: true, }); return data; @@ -295,11 +292,10 @@ export const publishOnQDN = async ({ const res = await publishData({ registeredName, - file: data, + data, service, identifier, uploadType, - isBase64: true, withFee: true, title, description, diff --git a/src/components/Apps/AppPublish.tsx b/src/components/Apps/AppPublish.tsx index 486fd8a..91c2961 100644 --- a/src/components/Apps/AppPublish.tsx +++ b/src/components/Apps/AppPublish.tsx @@ -189,11 +189,10 @@ export const AppPublish = ({ names, categories }) => { publishFee: fee.fee + ' QORT', }); setIsLoading('Publishing... Please wait.'); - const fileBase64 = await fileToBase64(file); await new Promise((res, rej) => { window .sendMessage('publishOnQDN', { - data: fileBase64, + data: file, service: appType, title, description, diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index 86ec6d3..1c85a9d 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -177,6 +177,7 @@ export const AppsPrivate = ({ myName }) => { data: decryptedData, identifier: newPrivateAppValues?.identifier, service: newPrivateAppValues?.service, + uploadType: 'base64', }) .then((response) => { if (!response?.error) { diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx index b1736a2..d7f7266 100644 --- a/src/components/Apps/useQortalMessageListener.tsx +++ b/src/components/Apps/useQortalMessageListener.tsx @@ -615,13 +615,22 @@ export const useQortalMessageListener = ( ); } else if (event?.data?.action === 'SAVE_FILE') { try { - const res = await saveFile(event.data, null, true, { + await saveFile(event.data, null, true, { openSnackGlobal, setOpenSnackGlobal, infoSnackCustom, setInfoSnackCustom, }); - } catch (error) {} + event.ports[0].postMessage({ + result: true, + error: null, + }); + } catch (error) { + event.ports[0].postMessage({ + result: null, + error: error?.message || 'Failed to save file', + }); + } } else if ( event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' || event?.data?.action === 'PUBLISH_QDN_RESOURCE' || diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index a4f6e5a..00dad95 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -803,6 +803,7 @@ export const ChatGroup = ({ data: 'RA==', identifier: onEditMessage?.images[0]?.identifier, service: onEditMessage?.images[0]?.service, + uploadType: 'base64', }); } if (chatImagesToSave?.length > 0) { diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index 1074ccc..b95329e 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -233,6 +233,7 @@ export const ListOfGroupPromotions = () => { data: data, identifier: identifier, service: 'DOCUMENT', + uploadType: 'base64', }) .then((response) => { if (!response?.error) { diff --git a/src/components/GroupAvatar.tsx b/src/components/GroupAvatar.tsx index 3244d5c..33a84b5 100644 --- a/src/components/GroupAvatar.tsx +++ b/src/components/GroupAvatar.tsx @@ -87,6 +87,7 @@ export const GroupAvatar = ({ data: avatarBase64, identifier: `qortal_group_avatar_${groupId}`, service: 'THUMBNAIL', + uploadType: 'base64', }) .then((response) => { if (!response?.error) { diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index 10370de..bb5e7d2 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -80,6 +80,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { data: avatarBase64, identifier: 'qortal_avatar', service: 'THUMBNAIL', + uploadType: 'base64', }) .then((response) => { if (!response?.error) { diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index d67780a..69c1785 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -164,6 +164,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { data: encryptData, identifier: 'ext_saved_settings', service: 'DOCUMENT_PRIVATE', + uploadType: 'base64', }) .then((response) => { if (!response?.error) { diff --git a/src/qdn/publish/pubish.ts b/src/qdn/publish/pubish.ts index 08c4d15..5f00ffb 100644 --- a/src/qdn/publish/pubish.ts +++ b/src/qdn/publish/pubish.ts @@ -1,266 +1,369 @@ // @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 } from "../../background"; -import { getData } from "../../utils/chromeStorage"; +import { Buffer } from 'buffer'; +import Base58 from '../../deps/Base58'; +import nacl from '../../deps/nacl-fast'; +import utils from '../../utils/utils'; +import { createEndpoint, getBaseApi } from '../../background'; +import { getData } from '../../utils/chromeStorage'; -export async function reusableGet(endpoint){ - const validApi = await getBaseApi(); - - 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 +export async function reusableGet(endpoint) { + const validApi = await getBaseApi(); + + 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, }); - let data + let data; try { - data = await response.clone().json() + data = await response.clone().json(); } 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 getData("keyPair").catch(() => null); - if (res) { - return res - } else { - throw new Error("Wallet not authenticated"); - } + const res = await getData('keyPair').catch(() => null); + if (res) { + return res; + } 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, }: any) => { - - const validateName = async (receiverName: string) => { - return await reusableGet(`/names/${receiverName}`) - } + console.log('data', data); + 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 = 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 = 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 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 fee = null; - 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) { - signAndProcessRes = await signAndProcessWithFee(transactionBytes) - } + let transactionBytes = await uploadData(registeredName, data, fee); + console.log('transactionBytes length', transactionBytes?.length); + 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) { - throw new Error('Error when signing') - } + let signAndProcessRes; - 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 = '' - let urlSuffix = '' + return signAndProcessRes; + }; - 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' - } + const uploadData = async (registeredName: string, data: any, fee: number) => { + console.log('data', uploadType, data); + let postBody = ''; + let urlSuffix = ''; - // If we're sending file data, use the /base64 version of the POST /arbitrary/* API - else if (uploadType === 'file') { - urlSuffix = '/base64' - } + if (data != null) { + if (uploadType === 'base64') { + urlSuffix = '/base64'; + } - // Base64 encode the file to work around compatibility issues between javascript and java byte arrays - if (isBase64) { - postBody = file - } + if (uploadType === 'base64') { + postBody = data; + } + } else { + throw new Error('No data provided'); + } - if (!isBase64) { - let fileBuffer = new Uint8Array(await file.arrayBuffer()) - postBody = Buffer.from(fileBuffer).toString("base64") - } + let uploadDataUrl = `/arbitrary/${service}/${registeredName}`; + let paramQueries = ''; + if (identifier?.trim().length > 0) { + uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`; + } - } - - let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}` - if (identifier?.trim().length > 0) { - uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}` - } - - uploadDataUrl = uploadDataUrl + `?fee=${fee}` - + paramQueries = paramQueries + `?fee=${fee}`; - if (filename != null && filename != 'undefined') { - uploadDataUrl = uploadDataUrl + '&filename=' + encodeURIComponent(filename) - } + if (filename != null && filename != 'undefined') { + paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename); + } - if (title != null && title != 'undefined') { - uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title) - } + if (title != null && title != 'undefined') { + paramQueries = paramQueries + '&title=' + encodeURIComponent(title); + } - if (description != null && description != 'undefined') { - uploadDataUrl = uploadDataUrl + '&description=' + encodeURIComponent(description) - } + if (description != null && description != 'undefined') { + paramQueries = + paramQueries + '&description=' + encodeURIComponent(description); + } - if (category != null && category != 'undefined') { - uploadDataUrl = uploadDataUrl + '&category=' + encodeURIComponent(category) - } + if (category != null && category != 'undefined') { + paramQueries = paramQueries + '&category=' + encodeURIComponent(category); + } - if (tag1 != null && tag1 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1) - } + if (tag1 != null && tag1 != 'undefined') { + paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1); + } - if (tag2 != null && tag2 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2) - } + if (tag2 != null && tag2 != 'undefined') { + paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2); + } - if (tag3 != null && tag3 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3) - } + if (tag3 != null && tag3 != 'undefined') { + paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3); + } - if (tag4 != null && tag4 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4) - } + if (tag4 != null && tag4 != 'undefined') { + paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4); + } - if (tag5 != null && tag5 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5) - } + if (tag5 != null && tag5 != 'undefined') { + 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 { - return await validate() - } catch (error: any) { - throw new Error(error?.message) - } -} \ No newline at end of file + const file = data; + const urlCheck = `/arbitrary/check-tmp-space?totalSize=${file.size}`; + + 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 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); + } +}; diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index a14676a..183cc9c 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -1076,7 +1076,7 @@ export const publishQDNResource = async ( const title = data.title; const description = data.description; const category = data.category; - + const file = data?.file || data?.blob; const tags = data?.tags || []; const result = {}; @@ -1091,9 +1091,7 @@ export const publishQDNResource = async ( if (data.identifier == null) { identifier = 'default'; } - if (data?.file || data?.blob) { - data64 = await fileToBase64(data?.file || data?.blob); - } + if ( data.encrypt && (!data.publicKeys || @@ -1108,6 +1106,9 @@ export const publishQDNResource = async ( const parsedData = resKeyPair; const privateKey = parsedData.privateKey; const userPublicKey = parsedData.publicKey; + if (data?.file || data?.blob) { + data64 = await fileToBase64(data?.file || data?.blob); + } const encryptDataResponse = encryptDataGroup({ data64, publicKeys: data.publicKeys, @@ -1154,11 +1155,10 @@ export const publishQDNResource = async ( try { const resPublish = await publishData({ registeredName: encodeURIComponent(name), - file: data64, + data: data64 ? data64 : file, service: service, identifier: encodeURIComponent(identifier), - uploadType: 'file', - isBase64: true, + uploadType: data64 ? 'base64' : 'file', filename: filename, title, description, @@ -1263,13 +1263,6 @@ export const publishMultipleQDNResources = async ( } } - // if ( - // data.encrypt && - // (!data.publicKeys || - // (Array.isArray(data.publicKeys) && data.publicKeys.length === 0)) - // ) { - // throw new Error("Encrypting data requires public keys"); - // } const fee = await getFee('ARBITRARY'); const registeredName = await getNameInfo(); const name = registeredName; @@ -1398,14 +1391,13 @@ export const publishMultipleQDNResources = async ( } const service = resource.service; let identifier = resource.identifier; - let data64 = resource?.data64 || resource?.base64; + let rawData = resource?.data64 || resource?.base64; const filename = resource.filename; const title = resource.title; const description = resource.description; const category = resource.category; const tags = resource?.tags || []; const result = {}; - // Fill tags dynamically while maintaining backward compatibility for (let i = 0; i < 5; i++) { result[`tag${i + 1}`] = tags[i] || resource[`tag${i + 1}`] || undefined; @@ -1427,22 +1419,27 @@ export const publishMultipleQDNResources = async ( continue; } if (resource.file) { - data64 = await fileToBase64(resource.file); + rawData = resource.file; } + if (resourceEncrypt) { try { + if (resource?.file) { + rawData = await fileToBase64(resource.file); + } + console.log('encrypteddata', rawData); const resKeyPair = await getKeyPair(); const parsedData = 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 = @@ -1457,16 +1454,21 @@ export const publishMultipleQDNResources = async ( } try { + const dataType = + resource?.base64 || resource?.data64 || resourceEncrypt + ? 'base64' + : 'file'; + console.log('dataType', dataType); await retryTransaction( publishData, [ { registeredName: encodeURIComponent(name), - file: data64, + data: rawData, service: service, identifier: encodeURIComponent(identifier), - uploadType: 'file', - isBase64: true, + uploadType: dataType, + // isBase64: true, filename: filename, title, description, @@ -1902,6 +1904,41 @@ export const joinGroup = async (data, isFromExtension) => { export const saveFile = async (data, sender, isFromExtension, snackMethods) => { try { + if (data?.location) { + const requiredFieldsLocation = ['service', 'name', 'filename']; + const missingFieldsLocation: string[] = []; + requiredFieldsLocation.forEach((field) => { + if (!data?.location[field]) { + missingFieldsLocation.push(field); + } + }); + if (missingFieldsLocation.length > 0) { + const missingFieldsString = missingFieldsLocation.join(', '); + const errorMsg = `Missing fields: ${missingFieldsString}`; + throw new Error(errorMsg); + } + const resPermission = await getUserPermission( + { + text1: 'Would you like to download:', + highlightedText: `${data?.location?.filename}`, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (!accepted) throw new Error('User declined to save file'); + const a = document.createElement('a'); + let locationUrl = `/arbitrary/${data.location.service}/${data.location.name}`; + if (data.location.identifier) { + locationUrl = locationUrl + `/${data.location.identifier}`; + } + const endpoint = await createEndpoint(locationUrl); + a.href = endpoint; + a.download = data.location.filename; + document.body.appendChild(a); + a.click(); + a.remove(); + return true; + } const requiredFields = ['filename', 'blob']; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -1916,6 +1953,8 @@ export const saveFile = async (data, sender, isFromExtension, snackMethods) => { } const filename = data.filename; const blob = data.blob; + + const mimeType = blob.type || data.mimeType; const resPermission = await getUserPermission( { text1: 'Would you like to download:', @@ -1924,50 +1963,17 @@ export const saveFile = async (data, sender, isFromExtension, snackMethods) => { isFromExtension ); const { accepted } = resPermission; + if (!accepted) throw new Error('User declined to save file'); + showSaveFilePicker( + { + filename, + mimeType, + blob, + }, + snackMethods + ); - if (accepted) { - const mimeType = blob.type || data.mimeType; - let backupExention = filename.split('.').pop(); - if (backupExention) { - backupExention = '.' + backupExention; - } - const fileExtension = mimeToExtensionMap[mimeType] || backupExention; - let fileHandleOptions = {}; - if (!mimeType) { - throw new Error('A mimeType could not be derived'); - } - if (!fileExtension) { - const obj = {}; - throw new Error('A file extension could not be derived'); - } - if (fileExtension && mimeType) { - fileHandleOptions = { - accept: { - [mimeType]: [fileExtension], - }, - }; - } - - showSaveFilePicker( - { - filename, - mimeType, - blob, - }, - snackMethods - ); - // sendToSaveFilePicker( - // { - // filename, - // mimeType, - // blob, - // fileId - // } - // ); - return true; - } else { - throw new Error('User declined to save file'); - } + return true; } catch (error) { throw new Error(error?.message || 'Failed to initiate download'); } @@ -5391,12 +5397,11 @@ export const multiPaymentWithPrivateData = async (data, isFromExtension) => { [ { 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, }, @@ -5443,12 +5448,11 @@ export const multiPaymentWithPrivateData = async (data, isFromExtension) => { [ { 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, },