// @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 { executeEvent } from '../../utils/events'; 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, }); if (!response.ok) { const errorText = await response.text(); throw new Error(errorText); } let data; try { data = await response.clone().json(); } catch (e) { data = await response.text(); } return data; } async function reusablePostStream(endpoint, _body) { const url = await createEndpoint(endpoint); const headers = {}; const response = await fetch(url, { method: 'POST', headers, body: _body, }); return response; // return the actual response so calling code can use response.ok } async function uploadChunkWithRetry(endpoint, formData, index, maxRetries = 3) { let attempt = 0; while (attempt < maxRetries) { try { const response = await reusablePostStream(endpoint, formData); if (!response.ok) { const errorText = await response.text(); throw new Error(errorText); } return; // Success } catch (err) { attempt++; console.warn( `Chunk ${index} failed (attempt ${attempt}): ${err.message}` ); if (attempt >= maxRetries) { throw new Error(`Chunk ${index} failed after ${maxRetries} attempts`); } // Wait 10 seconds before next retry await new Promise((res) => setTimeout(res, 25_000)); } } } async function resuablePostRetry( endpoint, body, maxRetries = 3, appInfo, resourceInfo ) { let attempt = 0; while (attempt < maxRetries) { try { const response = await reusablePost(endpoint, body); return response; } catch (err) { attempt++; if (attempt >= maxRetries) { throw new Error( err instanceof Error ? err?.message || `Failed to make request` : `Failed to make request` ); } if (appInfo?.tabId && resourceInfo) { executeEvent('receiveChunks', { tabId: appInfo.tabId, publishLocation: { name: resourceInfo?.name, identifier: resourceInfo?.identifier, service: resourceInfo?.service, }, retry: true, }); } // Wait 10 seconds before next retry await new Promise((res) => setTimeout(res, 25_000)); } } } async function getKeyPair() { const res = await getData('keyPair').catch(() => null); if (res) { return res; } else { throw new Error('Wallet not authenticated'); } } export const publishData = async ({ registeredName, data, service, identifier, uploadType, filename, withFee, title, description, category, tag1, tag2, tag3, tag4, tag5, feeAmount, appInfo }: any) => { const validateName = async (receiverName: string) => { return await reusableGet(`/names/${receiverName}`); }; const convertBytesForSigning = async (transactionBytesBase58: string) => { return await resuablePostRetry( '/transactions/convert', transactionBytesBase58, 3, appInfo, { identifier, name: registeredName, service } ); }; const getArbitraryFee = async () => { const timestamp = Date.now(); let fee = await reusableGet( `/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}` ); 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 processTransactionVersion2 = async (bytes) => { return await resuablePostRetry( '/transactions/process?apiVersion=2', Base58.encode(bytes), 3, appInfo, { identifier, name: registeredName, service } ); }; const signAndProcessWithFee = async (transactionBytesBase58: string) => { let convertedBytesBase58 = await convertBytesForSigning( transactionBytesBase58 ); if (convertedBytesBase58.error) { throw new Error('Error when signing'); } 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 signedArbitraryBytes = signArbitraryWithFee( transactionBytesBase58, convertedBytesBase58, keyPair ); const response = await processTransactionVersion2(signedArbitraryBytes); let myResponse = { error: '' }; if (response === false) { throw new Error('Error when signing'); } else { myResponse = response; } if (appInfo?.tabId) { executeEvent('receiveChunks', { tabId: appInfo.tabId, publishLocation: { name: registeredName, identifier, service, }, processed: true, }); } return myResponse; }; const validate = async () => { let validNameRes = await validateName(registeredName); if (validNameRes.error) { throw new Error('Name not found'); } let fee = null; 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, data, fee); if (!transactionBytes || transactionBytes.error) { throw new Error(transactionBytes?.message || 'Error when uploading'); } else if (transactionBytes.includes('Error 500 Internal Server Error')) { throw new Error('Error when uploading'); } let signAndProcessRes; if (withFee) { signAndProcessRes = await signAndProcessWithFee(transactionBytes); } if (signAndProcessRes?.error) { throw new Error('Error when signing'); } return signAndProcessRes; }; const uploadData = async (registeredName: string, data: any, fee: number) => { let postBody = ''; let urlSuffix = ''; if (data != null) { if (uploadType === 'base64') { urlSuffix = '/base64'; } if (uploadType === 'base64') { postBody = data; } } else { throw new Error('No data provided'); } let uploadDataUrl = `/arbitrary/${service}/${registeredName}`; let paramQueries = ''; if (identifier?.trim().length > 0) { uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`; } paramQueries = paramQueries + `?fee=${fee}`; if (filename != null && filename != 'undefined') { paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename); } if (title != null && title != 'undefined') { paramQueries = paramQueries + '&title=' + encodeURIComponent(title); } if (description != null && description != 'undefined') { paramQueries = paramQueries + '&description=' + encodeURIComponent(description); } if (category != null && category != 'undefined') { paramQueries = paramQueries + '&category=' + encodeURIComponent(category); } if (tag1 != null && tag1 != 'undefined') { paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1); } if (tag2 != null && tag2 != 'undefined') { paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2); } if (tag3 != null && tag3 != 'undefined') { paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3); } if (tag4 != null && tag4 != 'undefined') { paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4); } if (tag5 != null && tag5 != 'undefined') { paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag5); } if (uploadType === 'zip') { paramQueries = paramQueries + '&isZip=' + true; } if (uploadType === 'base64') { if (urlSuffix) { uploadDataUrl = uploadDataUrl + urlSuffix; } uploadDataUrl = uploadDataUrl + paramQueries; if (appInfo?.tabId) { executeEvent('receiveChunks', { tabId: appInfo.tabId, publishLocation: { name: registeredName, identifier, service, }, chunksSubmitted: 1, totalChunks: 1, processed: false, filename: filename || title || `${service}-${identifier || ''}`, }); } return await resuablePostRetry(uploadDataUrl, postBody, 3, appInfo, { identifier, name: registeredName, service, }); } const file = data; const urlCheck = `/arbitrary/check/tmp?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); if (appInfo?.tabId) { executeEvent('receiveChunks', { tabId: appInfo.tabId, publishLocation: { name: registeredName, identifier, service, }, chunksSubmitted: 0, totalChunks, processed: false, filename: file?.name || filename || title || `${service}-${identifier || ''}`, }); } for (let index = 0; index < totalChunks; index++) { const start = index * chunkSize; const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); const formData = new FormData(); formData.append('chunk', chunk, file.name); // Optional: include filename formData.append('index', index); await uploadChunkWithRetry(chunkUrl, formData, index); if (appInfo?.tabId) { executeEvent('receiveChunks', { tabId: appInfo.tabId, publishLocation: { name: registeredName, identifier, service, }, chunksSubmitted: index + 1, totalChunks, }); } } const finalizeUrl = uploadDataUrl + `/finalize` + paramQueries; const finalizeEndpoint = await createEndpoint(finalizeUrl); 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); } };