From e2bb817c6f9c9409ab2131f17eb1b678b4503287 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 8 May 2025 22:14:34 +0300 Subject: [PATCH 01/31] fix qrs, added publish apps with multi-name, fixed publish qdn data for multi names --- src/background-cases.ts | 1738 ++++++++++++----------- src/backgroundFunctions/encryption.ts | 12 +- src/components/Apps/AppPublish.tsx | 25 +- src/components/Apps/AppsDesktop.tsx | 9 +- src/components/Apps/AppsHomeDesktop.tsx | 3 +- src/components/Apps/AppsPrivate.tsx | 63 +- src/components/Group/Group.tsx | 3 +- src/qortalRequests/get.ts | 41 +- 8 files changed, 1029 insertions(+), 865 deletions(-) diff --git a/src/background-cases.ts b/src/background-cases.ts index d4ab82a..eff3a0f 100644 --- a/src/background-cases.ts +++ b/src/background-cases.ts @@ -56,22 +56,28 @@ import { setGroupData, updateThreadActivity, walletVersion, -} from "./background"; -import { decryptGroupEncryption, encryptAndPublishSymmetricKeyGroupChat, encryptAndPublishSymmetricKeyGroupChatForAdmins, publishGroupEncryptedResource, publishOnQDN } from "./backgroundFunctions/encryption"; -import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from "./constants/codes"; -import Base58 from "./deps/Base58"; -import { encryptSingle } from "./qdn/encryption/group-encryption"; -import { _createPoll, _voteOnPoll } from "./qortalRequests/get"; -import { createTransaction } from "./transactions/transactions"; -import { getData, storeData } from "./utils/chromeStorage"; +} from './background'; +import { + decryptGroupEncryption, + encryptAndPublishSymmetricKeyGroupChat, + encryptAndPublishSymmetricKeyGroupChatForAdmins, + publishGroupEncryptedResource, + publishOnQDN, +} from './backgroundFunctions/encryption'; +import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from './constants/codes'; +import Base58 from './deps/Base58'; +import { encryptSingle } from './qdn/encryption/group-encryption'; +import { _createPoll, _voteOnPoll } from './qortalRequests/get'; +import { createTransaction } from './transactions/transactions'; +import { getData, storeData } from './utils/chromeStorage'; export function versionCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "version", - payload: { version: "1.0" }, - type: "backgroundMessageResponse", + action: 'version', + payload: { version: '1.0' }, + type: 'backgroundMessageResponse', }, event.origin ); @@ -82,14 +88,14 @@ export async function getWalletInfoCase(request, event) { const response = await getKeyPair(); try { - const walletInfo = await getData('walletInfo').catch((error)=> null) - if(walletInfo){ + const walletInfo = await getData('walletInfo').catch((error) => null); + if (walletInfo) { event.source.postMessage( { requestId: request.requestId, - action: "getWalletInfo", + action: 'getWalletInfo', payload: { walletInfo, hasKeyPair: true }, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -97,36 +103,34 @@ export async function getWalletInfoCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getWalletInfo", - error: "No wallet info found", - type: "backgroundMessageResponse", + action: 'getWalletInfo', + error: 'No wallet info found', + type: 'backgroundMessageResponse', }, event.origin ); } - } catch (error) { event.source.postMessage( { requestId: request.requestId, - action: "getWalletInfo", - error: "No wallet info found", - type: "backgroundMessageResponse", + action: 'getWalletInfo', + error: 'No wallet info found', + type: 'backgroundMessageResponse', }, event.origin ); } - } catch (error) { try { - const walletInfo = await getData('walletInfo').catch((error)=> null) - if(walletInfo){ + const walletInfo = await getData('walletInfo').catch((error) => null); + if (walletInfo) { event.source.postMessage( { requestId: request.requestId, - action: "getWalletInfo", + action: 'getWalletInfo', payload: { walletInfo, hasKeyPair: false }, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -134,9 +138,9 @@ export async function getWalletInfoCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getWalletInfo", - error: "Wallet not authenticated", - type: "backgroundMessageResponse", + action: 'getWalletInfo', + error: 'Wallet not authenticated', + type: 'backgroundMessageResponse', }, event.origin ); @@ -145,14 +149,13 @@ export async function getWalletInfoCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getWalletInfo", - error: "Wallet not authenticated", - type: "backgroundMessageResponse", + action: 'getWalletInfo', + error: 'Wallet not authenticated', + type: 'backgroundMessageResponse', }, event.origin ); } - } } @@ -163,9 +166,9 @@ export async function validApiCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "validApi", + action: 'validApi', payload: usableApi, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -173,9 +176,9 @@ export async function validApiCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "validApi", + action: 'validApi', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -189,9 +192,9 @@ export async function nameCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "name", + action: 'name', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -199,9 +202,9 @@ export async function nameCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "name", + action: 'name', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -215,9 +218,9 @@ export async function userInfoCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "userInfo", + action: 'userInfo', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -225,9 +228,9 @@ export async function userInfoCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "userInfo", - error: "User not authenticated", - type: "backgroundMessageResponse", + action: 'userInfo', + error: 'User not authenticated', + type: 'backgroundMessageResponse', }, event.origin ); @@ -235,15 +238,19 @@ export async function userInfoCase(request, event) { } export async function decryptWalletCase(request, event) { - try { + try { const { password, wallet } = request.payload; - const response = await decryptWallet({password, wallet, walletVersion: wallet?.version || walletVersion}); + const response = await decryptWallet({ + password, + wallet, + walletVersion: wallet?.version || walletVersion, + }); event.source.postMessage( { requestId: request.requestId, - action: "decryptWallet", + action: 'decryptWallet', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -251,9 +258,9 @@ export async function decryptWalletCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "decryptWallet", + action: 'decryptWallet', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -267,9 +274,9 @@ export async function balanceCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "balance", + action: 'balance', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -277,9 +284,9 @@ export async function balanceCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "balance", + action: 'balance', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -292,9 +299,9 @@ export async function ltcBalanceCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "ltcBalance", + action: 'ltcBalance', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -302,9 +309,9 @@ export async function ltcBalanceCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "ltcBalance", + action: 'ltcBalance', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -319,9 +326,9 @@ export async function sendCoinCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "sendCoin", + action: 'sendCoin', error: res?.data?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -330,9 +337,9 @@ export async function sendCoinCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "sendCoin", + action: 'sendCoin', payload: true, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -340,9 +347,9 @@ export async function sendCoinCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "sendCoin", + action: 'sendCoin', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -361,9 +368,9 @@ export async function inviteToGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "inviteToGroup", + action: 'inviteToGroup', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -371,9 +378,9 @@ export async function inviteToGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "inviteToGroup", + action: 'inviteToGroup', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -388,9 +395,9 @@ export async function saveTempPublishCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "saveTempPublish", + action: 'saveTempPublish', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -398,9 +405,9 @@ export async function saveTempPublishCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "saveTempPublish", + action: 'saveTempPublish', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -414,9 +421,9 @@ export async function getTempPublishCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getTempPublish", + action: 'getTempPublish', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -424,9 +431,9 @@ export async function getTempPublishCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getTempPublish", + action: 'getTempPublish', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -455,9 +462,9 @@ export async function createGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "createGroup", + action: 'createGroup', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -465,9 +472,9 @@ export async function createGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "createGroup", + action: 'createGroup', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -482,9 +489,9 @@ export async function cancelInvitationToGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "cancelInvitationToGroup", + action: 'cancelInvitationToGroup', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -492,9 +499,9 @@ export async function cancelInvitationToGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "cancelInvitationToGroup", + action: 'cancelInvitationToGroup', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -509,9 +516,9 @@ export async function leaveGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "leaveGroup", + action: 'leaveGroup', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -519,9 +526,9 @@ export async function leaveGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "leaveGroup", + action: 'leaveGroup', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -536,9 +543,9 @@ export async function joinGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "joinGroup", + action: 'joinGroup', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -546,9 +553,9 @@ export async function joinGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "joinGroup", + action: 'joinGroup', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -567,9 +574,9 @@ export async function kickFromGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "kickFromGroup", + action: 'kickFromGroup', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -577,9 +584,9 @@ export async function kickFromGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "kickFromGroup", + action: 'kickFromGroup', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -599,9 +606,9 @@ export async function banFromGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "banFromGroup", + action: 'banFromGroup', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -609,9 +616,9 @@ export async function banFromGroupCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "banFromGroup", + action: 'banFromGroup', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -621,14 +628,14 @@ export async function banFromGroupCase(request, event) { export async function addDataPublishesCase(request, event) { try { const { data, groupId, type } = request.payload; - const response = await addDataPublishes( data, groupId, type ); + const response = await addDataPublishes(data, groupId, type); event.source.postMessage( { requestId: request.requestId, - action: "addDataPublishes", + action: 'addDataPublishes', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -636,9 +643,9 @@ export async function addDataPublishesCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addDataPublishes", + action: 'addDataPublishes', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -648,14 +655,14 @@ export async function addDataPublishesCase(request, event) { export async function getDataPublishesCase(request, event) { try { const { groupId, type } = request.payload; - const response = await getDataPublishes(groupId, type ); + const response = await getDataPublishes(groupId, type); event.source.postMessage( { requestId: request.requestId, - action: "getDataPublishes", + action: 'getDataPublishes', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -663,9 +670,9 @@ export async function getDataPublishesCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getDataPublishes", + action: 'getDataPublishes', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -679,9 +686,9 @@ export async function addUserSettingsCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addUserSettings", + action: 'addUserSettings', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -689,9 +696,9 @@ export async function addUserSettingsCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addUserSettings", + action: 'addUserSettings', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -706,9 +713,9 @@ export async function getUserSettingsCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getUserSettings", + action: 'getUserSettings', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -716,9 +723,9 @@ export async function getUserSettingsCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getUserSettings", + action: 'getUserSettings', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -733,9 +740,9 @@ export async function cancelBanCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "cancelBan", + action: 'cancelBan', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -743,9 +750,9 @@ export async function cancelBanCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "cancelBan", + action: 'cancelBan', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -760,9 +767,9 @@ export async function registerNameCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "registerName", + action: 'registerName', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -770,9 +777,9 @@ export async function registerNameCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "registerName", + action: 'registerName', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -788,15 +795,15 @@ export async function createPollCase(request, event) { options: pollOptions, }, true, - true // skip permission + true // skip permission ); event.source.postMessage( { requestId: request.requestId, - action: "registerName", + action: 'registerName', payload: resCreatePoll, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -804,9 +811,9 @@ export async function createPollCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "registerName", + action: 'registerName', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -816,13 +823,12 @@ export async function voteOnPollCase(request, event) { try { const res = await _voteOnPoll(request.payload, true, true); - event.source.postMessage( { requestId: request.requestId, - action: "registerName", + action: 'registerName', payload: res, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -830,9 +836,9 @@ export async function voteOnPollCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "registerName", + action: 'registerName', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -847,9 +853,9 @@ export async function makeAdminCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "makeAdmin", + action: 'makeAdmin', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -857,9 +863,9 @@ export async function makeAdminCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "makeAdmin", + action: 'makeAdmin', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -874,9 +880,9 @@ export async function removeAdminCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "removeAdmin", + action: 'removeAdmin', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -884,9 +890,9 @@ export async function removeAdminCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "removeAdmin", + action: 'removeAdmin', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -895,14 +901,14 @@ export async function removeAdminCase(request, event) { export async function notificationCase(request, event) { try { - const notificationId = "chat_notification_" + Date.now(); // Create a unique ID + const notificationId = 'chat_notification_' + Date.now(); // Create a unique ID // chrome.notifications.create(notificationId, { // type: "basic", // iconUrl: "qort.png", // Add an appropriate icon for chat notifications // title: "New Group Message!", // message: "You have received a new message from one of your groups", - // priority: 2, // Use the maximum priority to ensure it's + // priority: 2, // Use the maximum priority to ensure it's // }); // Set a timeout to clear the notification after 'timeout' milliseconds // setTimeout(() => { @@ -922,9 +928,9 @@ export async function notificationCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "notification", - error: "Error displaying notifaction", - type: "backgroundMessageResponse", + action: 'notification', + error: 'Error displaying notifaction', + type: 'backgroundMessageResponse', }, event.origin ); @@ -939,9 +945,9 @@ export async function addTimestampEnterChatCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addTimestampEnterChat", + action: 'addTimestampEnterChat', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -949,9 +955,9 @@ export async function addTimestampEnterChatCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addTimestampEnterChat", + action: 'addTimestampEnterChat', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -961,13 +967,13 @@ export async function addTimestampEnterChatCase(request, event) { export async function setApiKeyCase(request, event) { try { const payload = request.payload; - storeData('apiKey', payload) + storeData('apiKey', payload); event.source.postMessage( { requestId: request.requestId, - action: "setApiKey", + action: 'setApiKey', payload: true, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -975,9 +981,9 @@ export async function setApiKeyCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "setApiKey", + action: 'setApiKey', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -986,14 +992,14 @@ export async function setApiKeyCase(request, event) { export async function setCustomNodesCase(request, event) { try { const nodes = request.payload; - storeData('customNodes', nodes) + storeData('customNodes', nodes); event.source.postMessage( { requestId: request.requestId, - action: "setCustomNodes", + action: 'setCustomNodes', payload: true, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1001,9 +1007,9 @@ export async function setCustomNodesCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "setCustomNodes", + action: 'setCustomNodes', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1017,9 +1023,9 @@ export async function getApiKeyCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getApiKey", + action: 'getApiKey', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1027,9 +1033,9 @@ export async function getApiKeyCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getApiKey", + action: 'getApiKey', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1043,9 +1049,9 @@ export async function getCustomNodesFromStorageCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getCustomNodesFromStorage", + action: 'getCustomNodesFromStorage', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1053,9 +1059,9 @@ export async function getCustomNodesFromStorageCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getCustomNodesFromStorage", + action: 'getCustomNodesFromStorage', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1073,9 +1079,9 @@ export async function notifyAdminRegenerateSecretKeyCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "notifyAdminRegenerateSecretKey", + action: 'notifyAdminRegenerateSecretKey', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1083,9 +1089,9 @@ export async function notifyAdminRegenerateSecretKeyCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "notifyAdminRegenerateSecretKey", + action: 'notifyAdminRegenerateSecretKey', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1098,15 +1104,15 @@ export async function addGroupNotificationTimestampCase(request, event) { const response = await addTimestampGroupAnnouncement({ groupId, timestamp, - seenTimestamp: true + seenTimestamp: true, }); event.source.postMessage( { requestId: request.requestId, - action: "addGroupNotificationTimestamp", + action: 'addGroupNotificationTimestamp', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1114,9 +1120,9 @@ export async function addGroupNotificationTimestampCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addGroupNotificationTimestamp", + action: 'addGroupNotificationTimestamp', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1129,9 +1135,9 @@ export async function addEnteredQmailTimestampCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addEnteredQmailTimestamp", + action: 'addEnteredQmailTimestamp', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1139,9 +1145,9 @@ export async function addEnteredQmailTimestampCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addEnteredQmailTimestamp", + action: 'addEnteredQmailTimestamp', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1154,9 +1160,9 @@ export async function getEnteredQmailTimestampCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getEnteredQmailTimestamp", - payload: {timestamp: response}, - type: "backgroundMessageResponse", + action: 'getEnteredQmailTimestamp', + payload: { timestamp: response }, + type: 'backgroundMessageResponse', }, event.origin ); @@ -1164,9 +1170,9 @@ export async function getEnteredQmailTimestampCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getEnteredQmailTimestamp", + action: 'getEnteredQmailTimestamp', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1180,9 +1186,9 @@ export async function clearAllNotificationsCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "clearAllNotifications", + action: 'clearAllNotifications', payload: true, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1190,9 +1196,9 @@ export async function clearAllNotificationsCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "clearAllNotifications", + action: 'clearAllNotifications', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1213,9 +1219,9 @@ export async function setGroupDataCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "setGroupData", + action: 'setGroupData', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1223,9 +1229,9 @@ export async function setGroupDataCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "setGroupData", + action: 'setGroupData', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1240,9 +1246,9 @@ export async function getGroupDataSingleCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getGroupDataSingle", + action: 'getGroupDataSingle', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1250,9 +1256,9 @@ export async function getGroupDataSingleCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getGroupDataSingle", + action: 'getGroupDataSingle', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1266,9 +1272,9 @@ export async function getTimestampEnterChatCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getTimestampEnterChat", + action: 'getTimestampEnterChat', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1276,9 +1282,9 @@ export async function getTimestampEnterChatCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getTimestampEnterChat", + action: 'getTimestampEnterChat', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1288,66 +1294,66 @@ export async function getTimestampEnterChatCase(request, event) { export async function listActionsCase(request, event) { try { const { type, listName = '', items = [] } = request.payload; - let responseData + let responseData; - if(type === 'get'){ + if (type === 'get') { const url = await createEndpoint(`/lists/${listName}`); - const response = await fetch(url); - if (!response.ok) throw new Error("Failed to fetch"); - - responseData = await response.json(); - } else if(type === 'remove'){ - const url = await createEndpoint(`/lists/${listName}`); - const body = { - items: items , - }; - const bodyToString = JSON.stringify(body); - const response = await fetch(url, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - body: bodyToString, - }); + const response = await fetch(url); + if (!response.ok) throw new Error('Failed to fetch'); - if (!response.ok) throw new Error("Failed to remove from list"); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - responseData = res; - } else if(type === 'add'){ - const url = await createEndpoint(`/lists/${listName}`); - const body = { - items: items , - }; - const bodyToString = JSON.stringify(body); - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: bodyToString, - }); - - if (!response.ok) throw new Error("Failed to add to list"); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - responseData = res; - } + responseData = await response.json(); + } else if (type === 'remove') { + const url = await createEndpoint(`/lists/${listName}`); + const body = { + items: items, + }; + const bodyToString = JSON.stringify(body); + const response = await fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + + if (!response.ok) throw new Error('Failed to remove from list'); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + responseData = res; + } else if (type === 'add') { + const url = await createEndpoint(`/lists/${listName}`); + const body = { + items: items, + }; + const bodyToString = JSON.stringify(body); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + + if (!response.ok) throw new Error('Failed to add to list'); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + responseData = res; + } event.source.postMessage( { requestId: request.requestId, - action: "listActions", + action: 'listActions', payload: responseData, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1355,9 +1361,9 @@ export async function listActionsCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "listActions", + action: 'listActions', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1371,9 +1377,9 @@ export async function getTimestampMentionCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getTimestampMention", + action: 'getTimestampMention', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1381,9 +1387,9 @@ export async function getTimestampMentionCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getTimestampMention", + action: 'getTimestampMention', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1398,9 +1404,9 @@ export async function addTimestampMentionCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addTimestampMention", + action: 'addTimestampMention', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1408,9 +1414,9 @@ export async function addTimestampMentionCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "addTimestampMention", + action: 'addTimestampMention', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1424,9 +1430,9 @@ export async function getGroupNotificationTimestampCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getGroupNotificationTimestamp", + action: 'getGroupNotificationTimestamp', payload: response, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1434,9 +1440,9 @@ export async function getGroupNotificationTimestampCase(request, event) { event.source.postMessage( { requestId: request.requestId, - action: "getGroupNotificationTimestamp", + action: 'getGroupNotificationTimestamp', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1459,24 +1465,24 @@ export async function encryptAndPublishSymmetricKeyGroupChatCase( event.source.postMessage( { requestId: request.requestId, - action: "encryptAndPublishSymmetricKeyGroupChat", + action: 'encryptAndPublishSymmetricKeyGroupChat', payload: data, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); if (!previousData) { - try { - sendChatGroup({ - groupId, - typeMessage: undefined, - chatReference: undefined, - messageText: PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY, - }); - } catch (error) { - // error in sending chat message + try { + sendChatGroup({ + groupId, + typeMessage: undefined, + chatReference: undefined, + messageText: PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY, + }); + } catch (error) { + // error in sending chat message + } } - } try { sendChatNotification(data, groupId, previousData, numberOfMembers); } catch (error) { @@ -1486,9 +1492,9 @@ export async function encryptAndPublishSymmetricKeyGroupChatCase( event.source.postMessage( { requestId: request.requestId, - action: "encryptAndPublishSymmetricKeyGroupChat", + action: 'encryptAndPublishSymmetricKeyGroupChat', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1505,15 +1511,15 @@ export async function encryptAndPublishSymmetricKeyGroupChatForAdminsCase( await encryptAndPublishSymmetricKeyGroupChatForAdmins({ groupId, previousData, - admins + admins, }); event.source.postMessage( { requestId: request.requestId, - action: "encryptAndPublishSymmetricKeyGroupChatForAdmins", + action: 'encryptAndPublishSymmetricKeyGroupChatForAdmins', payload: data, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1521,9 +1527,9 @@ export async function encryptAndPublishSymmetricKeyGroupChatForAdminsCase( event.source.postMessage( { requestId: request.requestId, - action: "encryptAndPublishSymmetricKeyGroupChat", + action: 'encryptAndPublishSymmetricKeyGroupChat', error: error?.message, - type: "backgroundMessageResponse", + type: 'backgroundMessageResponse', }, event.origin ); @@ -1531,588 +1537,634 @@ export async function encryptAndPublishSymmetricKeyGroupChatForAdminsCase( } export async function publishGroupEncryptedResourceCase(request, event) { - try { - const {encryptedData, identifier} = request.payload; - const response = await publishGroupEncryptedResource({encryptedData, identifier}); - - event.source.postMessage( - { - requestId: request.requestId, - action: "publishGroupEncryptedResource", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "publishGroupEncryptedResource", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } - } + try { + const { encryptedData, identifier } = request.payload; + const response = await publishGroupEncryptedResource({ + encryptedData, + identifier, + }); - export async function publishOnQDNCase(request, event) { - try { - const {data, identifier, service, title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5, uploadType} = request.payload; - const response = await publishOnQDN({data, identifier, service, title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5, uploadType}); - - event.source.postMessage( - { - requestId: request.requestId, - action: "publishOnQDN", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "publishOnQDN", - error: error?.message || 'Unable to publish', - type: "backgroundMessageResponse", - }, - event.origin - ); - } + event.source.postMessage( + { + requestId: request.requestId, + action: 'publishGroupEncryptedResource', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'publishGroupEncryptedResource', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function handleActiveGroupDataFromSocketCase(request, event) { - try { - const {groups, directs} = request.payload; - const response = await handleActiveGroupDataFromSocket({groups, directs}); - - event.source.postMessage( - { - requestId: request.requestId, - action: "handleActiveGroupDataFromSocket", - payload: true, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "handleActiveGroupDataFromSocket", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function publishOnQDNCase(request, event) { + try { + const { + data, + name = '', + identifier, + service, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + uploadType, + } = request.payload; + const response = await publishOnQDN({ + data, + name, + identifier, + service, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + uploadType, + }); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'publishOnQDN', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'publishOnQDN', + error: error?.message || 'Unable to publish', + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function getThreadActivityCase(request, event) { - try { - const response = await checkThreads(true) - - event.source.postMessage( - { - requestId: request.requestId, - action: "getThreadActivity", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "getThreadActivity", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function handleActiveGroupDataFromSocketCase(request, event) { + try { + const { groups, directs } = request.payload; + const response = await handleActiveGroupDataFromSocket({ groups, directs }); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'handleActiveGroupDataFromSocket', + payload: true, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'handleActiveGroupDataFromSocket', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function updateThreadActivityCase(request, event) { - try { - const { threadId, qortalName, groupId, thread} = request.payload; - const response = await updateThreadActivity({ threadId, qortalName, groupId, thread }); - - event.source.postMessage( - { - requestId: request.requestId, - action: "updateThreadActivity", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "updateThreadActivity", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function getThreadActivityCase(request, event) { + try { + const response = await checkThreads(true); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'getThreadActivity', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'getThreadActivity', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function decryptGroupEncryptionCase(request, event) { - try { - const { data} = request.payload; - const response = await decryptGroupEncryption({ data }); - event.source.postMessage( - { - requestId: request.requestId, - action: "decryptGroupEncryption", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "decryptGroupEncryption", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function updateThreadActivityCase(request, event) { + try { + const { threadId, qortalName, groupId, thread } = request.payload; + const response = await updateThreadActivity({ + threadId, + qortalName, + groupId, + thread, + }); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'updateThreadActivity', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'updateThreadActivity', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function encryptSingleCase(request, event) { - try { - const { data, secretKeyObject, typeNumber} = request.payload; - const response = await encryptSingle({ data64: data, secretKeyObject, typeNumber }); - - event.source.postMessage( - { - requestId: request.requestId, - action: "encryptSingle", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "encryptSingle", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function decryptGroupEncryptionCase(request, event) { + try { + const { data } = request.payload; + const response = await decryptGroupEncryption({ data }); + event.source.postMessage( + { + requestId: request.requestId, + action: 'decryptGroupEncryption', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'decryptGroupEncryption', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function decryptSingleCase(request, event) { - try { - const { data, secretKeyObject, skipDecodeBase64} = request.payload; - const response = await decryptSingleFunc({ messages: data, secretKeyObject, skipDecodeBase64 }); - event.source.postMessage( - { - requestId: request.requestId, - action: "decryptSingle", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "decryptSingle", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function encryptSingleCase(request, event) { + try { + const { data, secretKeyObject, typeNumber } = request.payload; + const response = await encryptSingle({ + data64: data, + secretKeyObject, + typeNumber, + }); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'encryptSingle', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'encryptSingle', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function pauseAllQueuesCase(request, event) { - try { - await pauseAllQueues(); - - event.source.postMessage( - { - requestId: request.requestId, - action: "pauseAllQueues", - payload: true, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "pauseAllQueues", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function decryptSingleCase(request, event) { + try { + const { data, secretKeyObject, skipDecodeBase64 } = request.payload; + const response = await decryptSingleFunc({ + messages: data, + secretKeyObject, + skipDecodeBase64, + }); + event.source.postMessage( + { + requestId: request.requestId, + action: 'decryptSingle', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'decryptSingle', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function resumeAllQueuesCase(request, event) { - try { - await resumeAllQueues(); - - event.source.postMessage( - { - requestId: request.requestId, - action: "resumeAllQueues", - payload: true, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "resumeAllQueues", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function pauseAllQueuesCase(request, event) { + try { + await pauseAllQueues(); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'pauseAllQueues', + payload: true, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'pauseAllQueues', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } - export async function checkLocalCase(request, event) { - try { - const response = await checkLocalFunc() - event.source.postMessage( - { - requestId: request.requestId, - action: "pauseAllQueues", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "checkLocal", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +} + +export async function resumeAllQueuesCase(request, event) { + try { + await resumeAllQueues(); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'resumeAllQueues', + payload: true, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'resumeAllQueues', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } - - export async function decryptSingleForPublishesCase(request, event) { - try { - const { data, secretKeyObject, skipDecodeBase64} = request.payload; - const response = await decryptSingleForPublishes({ messages: data, secretKeyObject, skipDecodeBase64 }); - - event.source.postMessage( - { - requestId: request.requestId, - action: "decryptSingleForPublishes", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "decryptSingle", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +} +export async function checkLocalCase(request, event) { + try { + const response = await checkLocalFunc(); + event.source.postMessage( + { + requestId: request.requestId, + action: 'pauseAllQueues', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'checkLocal', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function decryptDirectCase(request, event) { - try { - const { data, involvingAddress} = request.payload; - const response = await decryptDirectFunc({ messages: data, involvingAddress }); - - event.source.postMessage( - { - requestId: request.requestId, - action: "decryptDirect", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "decryptDirect", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function decryptSingleForPublishesCase(request, event) { + try { + const { data, secretKeyObject, skipDecodeBase64 } = request.payload; + const response = await decryptSingleForPublishes({ + messages: data, + secretKeyObject, + skipDecodeBase64, + }); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'decryptSingleForPublishes', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'decryptSingle', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } - export async function sendChatGroupCase(request, event) { - try { - const { groupId, - typeMessage = undefined, - chatReference = undefined, - messageText} = request.payload; - const response = await sendChatGroup({ groupId, typeMessage, chatReference, messageText }); - - event.source.postMessage( - { - requestId: request.requestId, - action: "sendChatGroup", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "sendChatGroup", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +} + +export async function decryptDirectCase(request, event) { + try { + const { data, involvingAddress } = request.payload; + const response = await decryptDirectFunc({ + messages: data, + involvingAddress, + }); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'decryptDirect', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'decryptDirect', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } - export async function sendChatDirectCase(request, event) { - try { - const { directTo, - typeMessage = undefined, - chatReference = undefined, - messageText, - publicKeyOfRecipient, - address, - otherData} = request.payload; - const response = await sendChatDirect({ directTo, - chatReference, - messageText, - typeMessage, - publicKeyOfRecipient, - address, - otherData }); - - event.source.postMessage( - { - requestId: request.requestId, - action: "sendChatDirect", - payload: response, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "sendChatDirect", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +} +export async function sendChatGroupCase(request, event) { + try { + const { + groupId, + typeMessage = undefined, + chatReference = undefined, + messageText, + } = request.payload; + const response = await sendChatGroup({ + groupId, + typeMessage, + chatReference, + messageText, + }); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'sendChatGroup', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'sendChatGroup', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} +export async function sendChatDirectCase(request, event) { + try { + const { + directTo, + typeMessage = undefined, + chatReference = undefined, + messageText, + publicKeyOfRecipient, + address, + otherData, + } = request.payload; + const response = await sendChatDirect({ + directTo, + chatReference, + messageText, + typeMessage, + publicKeyOfRecipient, + address, + otherData, + }); - export async function setupGroupWebsocketCase(request, event) { - try { - - checkNewMessages(); - checkThreads(); - event.source.postMessage( - { - requestId: request.requestId, - action: "sendChatDirect", - payload: true, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "sendChatDirect", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } + event.source.postMessage( + { + requestId: request.requestId, + action: 'sendChatDirect', + payload: response, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'sendChatDirect', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function createRewardShareCase(request, event) { - try { - const {recipientPublicKey} = request.payload; - 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 lastRef = await getLastRef(); - - const tx = await createTransaction(38, keyPair, { - recipientPublicKey, - percentageShare: 0, - lastReference: lastRef, - }); - - const signedBytes = Base58.encode(tx.signedBytes); - - const res = await processTransactionVersion2(signedBytes); - if (!res?.signature) - throw new Error("Transaction was not able to be processed"); - event.source.postMessage( - { - requestId: request.requestId, - action: "createRewardShare", - payload: res, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "createRewardShare", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } +export async function setupGroupWebsocketCase(request, event) { + try { + checkNewMessages(); + checkThreads(); + event.source.postMessage( + { + requestId: request.requestId, + action: 'sendChatDirect', + payload: true, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'sendChatDirect', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function removeRewardShareCase(request, event) { - try { - const {rewardShareKeyPairPublicKey, recipient, percentageShare} = request.payload; - 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 lastRef = await getLastRef(); - - const tx = await createTransaction(381, keyPair, { - rewardShareKeyPairPublicKey, - recipient, - percentageShare, - lastReference: lastRef, - }); +export async function createRewardShareCase(request, event) { + try { + const { recipientPublicKey } = request.payload; + 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 lastRef = await getLastRef(); - const signedBytes = Base58.encode(tx.signedBytes); - - const res = await processTransactionVersion2(signedBytes); - if (!res?.signature) - throw new Error("Transaction was not able to be processed"); - event.source.postMessage( - { - requestId: request.requestId, - action: "removeRewardShare", - payload: res, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "removeRewardShare", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } + const tx = await createTransaction(38, keyPair, { + recipientPublicKey, + percentageShare: 0, + lastReference: lastRef, + }); + + const signedBytes = Base58.encode(tx.signedBytes); + + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error('Transaction was not able to be processed'); + event.source.postMessage( + { + requestId: request.requestId, + action: 'createRewardShare', + payload: res, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'createRewardShare', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - export async function getRewardSharePrivateKeyCase(request, event) { - try { - const {recipientPublicKey} = request.payload; - 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 lastRef = await getLastRef(); - - const tx = await createTransaction(38, keyPair, { - recipientPublicKey, - percentageShare: 0, - lastReference: lastRef, - }); +export async function removeRewardShareCase(request, event) { + try { + const { rewardShareKeyPairPublicKey, recipient, percentageShare } = + request.payload; + 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 lastRef = await getLastRef(); - event.source.postMessage( - { - requestId: request.requestId, - action: "getRewardSharePrivateKey", - payload: tx?._base58RewardShareSeed, - type: "backgroundMessageResponse", - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: "getRewardSharePrivateKey", - error: error?.message, - type: "backgroundMessageResponse", - }, - event.origin - ); - } + const tx = await createTransaction(381, keyPair, { + rewardShareKeyPairPublicKey, + recipient, + percentageShare, + lastReference: lastRef, + }); + + const signedBytes = Base58.encode(tx.signedBytes); + + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error('Transaction was not able to be processed'); + event.source.postMessage( + { + requestId: request.requestId, + action: 'removeRewardShare', + payload: res, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'removeRewardShare', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); } +} - \ No newline at end of file +export async function getRewardSharePrivateKeyCase(request, event) { + try { + const { recipientPublicKey } = request.payload; + 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 lastRef = await getLastRef(); + + const tx = await createTransaction(38, keyPair, { + recipientPublicKey, + percentageShare: 0, + lastReference: lastRef, + }); + + event.source.postMessage( + { + requestId: request.requestId, + action: 'getRewardSharePrivateKey', + payload: tx?._base58RewardShareSeed, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: 'getRewardSharePrivateKey', + error: error?.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } +} diff --git a/src/backgroundFunctions/encryption.ts b/src/backgroundFunctions/encryption.ts index 6cc0150..205fd34 100644 --- a/src/backgroundFunctions/encryption.ts +++ b/src/backgroundFunctions/encryption.ts @@ -66,6 +66,15 @@ export async function getNameInfo() { } } +export async function getAllUserNames() { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const validApi = await getBaseApi(); + const response = await fetch(validApi + '/names/address/' + address); + const nameData = await response.json(); + return nameData.map((item) => item.name); +} + async function getKeyPair() { const res = await getData('keyPair').catch(() => null); if (res) { @@ -288,9 +297,10 @@ export const publishOnQDN = async ({ tag4, tag5, uploadType = 'file', + name, }) => { if (data && service) { - const registeredName = await getNameInfo(); + const registeredName = name || (await getNameInfo()); if (!registeredName) throw new Error('You need a name to publish'); const res = await publishData({ diff --git a/src/components/Apps/AppPublish.tsx b/src/components/Apps/AppPublish.tsx index 486fd8a..5ceed1e 100644 --- a/src/components/Apps/AppPublish.tsx +++ b/src/components/Apps/AppPublish.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; import { AppCircle, AppCircleContainer, @@ -72,7 +72,8 @@ const CustomMenuItem = styled(MenuItem)({ // }, }); -export const AppPublish = ({ names, categories }) => { +export const AppPublish = ({ categories, myAddress, myName }) => { + const [names, setNames] = useState([]); const [name, setName] = useState(''); const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); @@ -152,6 +153,25 @@ export const AppPublish = ({ names, categories }) => { getQapp(name, appType); }, [name, appType]); + const getNames = useCallback(async () => { + if (!myAddress) return; + try { + setIsLoading('Loading names'); + const res = await fetch( + `${getBaseApiReact()}/names/address/${myAddress}` + ); + const data = await res.json(); + setNames(data?.map((item) => item.name)); + } catch (error) { + console.error(error); + } finally { + setIsLoading(''); + } + }, [myAddress]); + useEffect(() => { + getNames(); + }, [getNames]); + const publishApp = async () => { try { const data = { @@ -196,6 +216,7 @@ export const AppPublish = ({ names, categories }) => { data: fileBase64, service: appType, title, + name, description, category, tag1, diff --git a/src/components/Apps/AppsDesktop.tsx b/src/components/Apps/AppsDesktop.tsx index 80f57b9..73cdb20 100644 --- a/src/components/Apps/AppsDesktop.tsx +++ b/src/components/Apps/AppsDesktop.tsx @@ -38,6 +38,7 @@ export const AppsDesktop = ({ hasUnreadGroups, setDesktopViewMode, desktopViewMode, + myAddress, }) => { const [availableQapps, setAvailableQapps] = useState([]); const [selectedAppInfo, setSelectedAppInfo] = useState(null); @@ -458,6 +459,7 @@ export const AppsDesktop = ({ setMode={setMode} myApp={myApp} myWebsite={myWebsite} + myAddress={myAddress} /> )} @@ -485,7 +487,11 @@ export const AppsDesktop = ({ myName={myName} /> {mode === 'publish' && !selectedTab && ( - + )} {tabs.map((tab) => { if (!iframeRefs.current[tab.tabId]) { @@ -521,6 +527,7 @@ export const AppsDesktop = ({ setMode={setMode} myApp={myApp} myWebsite={myWebsite} + myAddress={myAddress} /> diff --git a/src/components/Apps/AppsHomeDesktop.tsx b/src/components/Apps/AppsHomeDesktop.tsx index 8a96015..e01a715 100644 --- a/src/components/Apps/AppsHomeDesktop.tsx +++ b/src/components/Apps/AppsHomeDesktop.tsx @@ -23,6 +23,7 @@ export const AppsHomeDesktop = ({ myWebsite, availableQapps, myName, + myAddress, }) => { const [qortalUrl, setQortalUrl] = useState(''); const theme = useTheme(); @@ -147,7 +148,7 @@ export const AppsHomeDesktop = ({ - + { +export const AppsPrivate = ({ myName, myAddress }) => { + const [names, setNames] = useState([]); + const [name, setName] = useState(0); + const { openApp } = useHandlePrivateApps(); const [file, setFile] = useState(null); const [logo, setLogo] = useState(null); @@ -140,7 +149,7 @@ export const AppsPrivate = ({ myName }) => { try { if (selectedGroup === 0) return; if (!logo) throw new Error('Please select an image for a logo'); - if (!myName) throw new Error('You need a Qortal name to publish'); + if (!name) throw new Error('Please select a Qortal name'); if (!newPrivateAppValues?.name) throw new Error('Your app needs a name'); const base64Logo = await fileToBase64(logo); const base64App = await fileToBase64(file); @@ -177,6 +186,7 @@ export const AppsPrivate = ({ myName }) => { data: decryptedData, identifier: newPrivateAppValues?.identifier, service: newPrivateAppValues?.service, + name, }) .then((response) => { if (!response?.error) { @@ -194,7 +204,7 @@ export const AppsPrivate = ({ myName }) => { { identifier: newPrivateAppValues?.identifier, service: newPrivateAppValues?.service, - name: myName, + name, groupId: selectedGroup, }, true @@ -220,6 +230,22 @@ export const AppsPrivate = ({ myName }) => { }; } + const getNames = useCallback(async () => { + if (!myAddress) return; + try { + const res = await fetch( + `${getBaseApiReact()}/names/address/${myAddress}` + ); + const data = await res.json(); + setNames(data?.map((item) => item.name)); + } catch (error) { + console.error(error); + } + }, [myAddress]); + useEffect(() => { + getNames(); + }, [getNames]); + return ( <> { {file ? 'Change' : 'Choose'} File + + + + + + 0 && appFeeRecipient) { hasAppFee = true; } - const registeredName = await getNameInfo(); + + const registeredName = data?.name || (await getNameInfo()); const name = registeredName; if (!name) { throw new Error('User has no Qortal name'); @@ -1144,6 +1146,7 @@ export const publishQDNResource = async ( text1: 'Do you give this application permission to publish to QDN?', text2: `service: ${service}`, text3: `identifier: ${identifier || null}`, + text4: `name: ${registeredName}`, fee: fee.fee, ...handleDynamicValues, }, @@ -1272,10 +1275,19 @@ export const publishMultipleQDNResources = async ( // } const fee = await getFee('ARBITRARY'); const registeredName = await getNameInfo(); + const name = registeredName; if (!name) { throw new Error('You need a Qortal name to publish.'); } + const userNames = await getAllUserNames(); + data.resources?.forEach((item) => { + if (item?.name && !userNames?.includes(item.name)) + throw new Error( + `The name ${item.name}, does not belong to the publisher.` + ); + }); + const appFee = data?.appFee ? +data.appFee : undefined; const appFeeRecipient = data?.appFeeRecipient; let hasAppFee = false; @@ -1343,7 +1355,7 @@ export const publishMultipleQDNResources = async (
Service: ${ resource.service }
-
Name: ${name}
+
Name: ${resource?.name || name}
Identifier: ${ resource.identifier }
@@ -1384,6 +1396,7 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); continue; } @@ -1393,6 +1406,7 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); continue; } @@ -1423,6 +1437,7 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); continue; } @@ -1451,6 +1466,7 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); continue; } @@ -1461,7 +1477,7 @@ export const publishMultipleQDNResources = async ( publishData, [ { - registeredName: encodeURIComponent(name), + registeredName: encodeURIComponent(resource?.name || name), file: data64, service: service, identifier: encodeURIComponent(identifier), @@ -1493,6 +1509,7 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); } } catch (error) { @@ -1500,6 +1517,7 @@ export const publishMultipleQDNResources = async ( reason: error?.message || 'Unknown error', identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); } } @@ -4305,9 +4323,10 @@ export const updateNameRequest = async (data, isFromExtension) => { const fee = await getFee('UPDATE_NAME'); const resPermission = await getUserPermission( { - text1: `Do you give this application permission to register this name?`, - highlightedText: data.newName, - text2: data?.description, + text1: `Do you give this application permission to update this name?`, + text2: `previous name: ${oldName}`, + text3: `new name: ${newName}`, + text4: data?.description, fee: fee.fee, }, isFromExtension @@ -4741,7 +4760,7 @@ export const createGroupRequest = async (data, isFromExtension) => { ]; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); @@ -4793,7 +4812,7 @@ export const updateGroupRequest = async (data, isFromExtension) => { ]; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); @@ -4928,7 +4947,7 @@ export const sellNameRequest = async (data, isFromExtension) => { const requiredFields = ['salePrice', 'nameForSale']; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); @@ -4972,7 +4991,7 @@ export const cancelSellNameRequest = async (data, isFromExtension) => { const requiredFields = ['nameForSale']; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); @@ -5012,7 +5031,7 @@ export const buyNameRequest = async (data, isFromExtension) => { const requiredFields = ['nameForSale']; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); From 13a77762b6f91a1014df487f7ecc26da875432a3 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 17 May 2025 05:35:32 +0300 Subject: [PATCH 02/31] 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, }, From 69c9959a8752624753c037594fdf03a42bde8c70 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 22 May 2025 01:32:34 +0300 Subject: [PATCH 03/31] save_file update --- src/qortalRequests/get.ts | 5 ++++- src/utils/time.ts | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 6e2c09d..0020a09 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -1949,7 +1949,10 @@ export const saveFile = async (data, sender, isFromExtension, snackMethods) => { if (data.location.identifier) { locationUrl = locationUrl + `/${data.location.identifier}`; } - const endpoint = await createEndpoint(locationUrl); + const endpoint = await createEndpoint( + locationUrl + + `?attachment=true&attachmentFilename=${data?.location?.filename}` + ); a.href = endpoint; a.download = data.location.filename; document.body.appendChild(a); diff --git a/src/utils/time.ts b/src/utils/time.ts index e434cfb..d8d1ef4 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -44,14 +44,14 @@ export function sortArrayByTimestampAndGroupName(array) { // Both have timestamp, sort by timestamp descending return b.timestamp - a.timestamp; } else if (a.timestamp) { - // Only `a` has timestamp, it comes first return -1; } else if (b.timestamp) { - // Only `b` has timestamp, it comes first return 1; } else { - // Neither has timestamp, sort alphabetically by groupName - return a.groupName.localeCompare(b.groupName); + // Neither has timestamp, sort alphabetically by groupName (with fallback) + const nameA = a.groupName || ''; + const nameB = b.groupName || ''; + return nameA.localeCompare(nameB); } }); } From 8c2b635afb8422782bb3892263ca9c944cbe364c Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 22 May 2025 14:56:32 +0300 Subject: [PATCH 04/31] sort name- put primary on top --- src/components/Apps/AppPublish.tsx | 7 +++++-- src/components/Apps/AppsDesktop.tsx | 2 +- src/components/Apps/AppsPrivate.tsx | 7 +++++-- src/hooks/useSortedMyNames.tsx | 11 +++++++++++ src/qortalRequests/get.ts | 1 - 5 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 src/hooks/useSortedMyNames.tsx diff --git a/src/components/Apps/AppPublish.tsx b/src/components/Apps/AppPublish.tsx index b1565b9..6ac7e76 100644 --- a/src/components/Apps/AppPublish.tsx +++ b/src/components/Apps/AppPublish.tsx @@ -25,6 +25,7 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { getFee } from '../../background'; import { fileToBase64 } from '../../utils/fileReading'; import { useTranslation } from 'react-i18next'; +import { useSortedMyNames } from '../../hooks/useSortedMyNames'; const CustomSelect = styled(Select)({ border: '0.5px solid var(--50-white, #FFFFFF80)', @@ -148,7 +149,7 @@ export const AppPublish = ({ categories, myAddress, myName }) => { try { setIsLoading('Loading names'); const res = await fetch( - `${getBaseApiReact()}/names/address/${myAddress}` + `${getBaseApiReact()}/names/address/${myAddress}?limit=0` ); const data = await res.json(); setNames(data?.map((item) => item.name)); @@ -162,6 +163,8 @@ export const AppPublish = ({ categories, myAddress, myName }) => { getNames(); }, [getNames]); + const mySortedNames = useSortedMyNames(names, myName); + const publishApp = async () => { try { const data = { @@ -329,7 +332,7 @@ export const AppPublish = ({ categories, myAddress, myName }) => { {/* This is the placeholder item */} - {names.map((name) => { + {mySortedNames.map((name) => { return {name}; })} diff --git a/src/components/Apps/AppsDesktop.tsx b/src/components/Apps/AppsDesktop.tsx index bca16cb..1cc6d0d 100644 --- a/src/components/Apps/AppsDesktop.tsx +++ b/src/components/Apps/AppsDesktop.tsx @@ -508,9 +508,9 @@ export const AppsDesktop = ({ {mode === 'publish' && !selectedTab && ( )} diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index 54f785c..a9f8d45 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -43,6 +43,7 @@ import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import { getFee } from '../../background'; import { useAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; +import { useSortedMyNames } from '../../hooks/useSortedMyNames'; const maxFileSize = 50 * 1024 * 1024; // 50MB @@ -93,6 +94,8 @@ export const AppsPrivate = ({ myName, myAddress }) => { name: '', }); + const mySortedNames = useSortedMyNames(names, myName); + const { getRootProps, getInputProps } = useDropzone({ accept: { 'application/zip': ['.zip'], // Only accept zip files @@ -271,7 +274,7 @@ export const AppsPrivate = ({ myName, myAddress }) => { if (!myAddress) return; try { const res = await fetch( - `${getBaseApiReact()}/names/address/${myAddress}` + `${getBaseApiReact()}/names/address/${myAddress}?limit=0` ); const data = await res.json(); setNames(data?.map((item) => item.name)); @@ -584,7 +587,7 @@ export const AppsPrivate = ({ myName, myAddress }) => { onChange={(e) => setName(e.target.value)} > No name selected - {names.map((name) => { + {mySortedNames.map((name) => { return ( {name} diff --git a/src/hooks/useSortedMyNames.tsx b/src/hooks/useSortedMyNames.tsx new file mode 100644 index 0000000..6fb8917 --- /dev/null +++ b/src/hooks/useSortedMyNames.tsx @@ -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]); +} diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 0020a09..034180a 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -1442,7 +1442,6 @@ export const publishMultipleQDNResources = async ( if (resource?.file) { rawData = await fileToBase64(resource.file); } - console.log('encrypteddata', rawData); const resKeyPair = await getKeyPair(); const parsedData = resKeyPair; const privateKey = parsedData.privateKey; From dd1c4c19997f00160e9266f7f7e4ca11d2a210a2 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 22 May 2025 21:07:29 +0300 Subject: [PATCH 05/31] fix filename --- src/qortalRequests/get.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 034180a..9895f2a 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -1921,8 +1921,9 @@ export const joinGroup = async (data, isFromExtension) => { export const saveFile = async (data, sender, isFromExtension, snackMethods) => { try { + if (!data?.filename) throw new Error('Missing filename'); if (data?.location) { - const requiredFieldsLocation = ['service', 'name', 'filename']; + const requiredFieldsLocation = ['service', 'name']; const missingFieldsLocation: string[] = []; requiredFieldsLocation.forEach((field) => { if (!data?.location[field]) { @@ -1937,7 +1938,7 @@ export const saveFile = async (data, sender, isFromExtension, snackMethods) => { const resPermission = await getUserPermission( { text1: 'Would you like to download:', - highlightedText: `${data?.location?.filename}`, + highlightedText: `${data?.filename}`, }, isFromExtension ); @@ -1949,11 +1950,10 @@ export const saveFile = async (data, sender, isFromExtension, snackMethods) => { locationUrl = locationUrl + `/${data.location.identifier}`; } const endpoint = await createEndpoint( - locationUrl + - `?attachment=true&attachmentFilename=${data?.location?.filename}` + locationUrl + `?attachment=true&attachmentFilename=${data?.filename}` ); a.href = endpoint; - a.download = data.location.filename; + a.download = data.filename; document.body.appendChild(a); a.click(); a.remove(); From 3d7a342f1b80980d7726e161acaf0ab943ba3363 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 23 May 2025 21:55:33 +0300 Subject: [PATCH 06/31] removed commented --- src/qortalRequests/get.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 9895f2a..b541b88 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -1483,7 +1483,6 @@ export const publishMultipleQDNResources = async ( service: service, identifier: encodeURIComponent(identifier), uploadType: dataType, - // isBase64: true, filename: filename, title, description, From 9d36c1e7339b38c3588242ea1cb6d704eacae9c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 18:06:16 +0000 Subject: [PATCH 07/31] Bump axios from 1.7.7 to 1.8.2 Bumps [axios](https://github.com/axios/axios) from 1.7.7 to 1.8.2. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.7.7...v1.8.2) --- updated-dependencies: - dependency-name: axios dependency-version: 1.8.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index fbba254..11070dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "@uiw/react-color": "^2.5.1", "adm-zip": "^0.5.16", "asmcrypto.js": "2.3.2", - "axios": "^1.7.7", + "axios": "^1.8.2", "bcryptjs": "2.4.3", "buffer": "6.0.3", "chokidar": "^3.6.0", @@ -6963,9 +6963,10 @@ } }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", diff --git a/package.json b/package.json index 2869cb9..2f922de 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@uiw/react-color": "^2.5.1", "adm-zip": "^0.5.16", "asmcrypto.js": "2.3.2", - "axios": "^1.7.7", + "axios": "^1.8.2", "bcryptjs": "2.4.3", "buffer": "6.0.3", "chokidar": "^3.6.0", From 9edb2cf4d4bbbfc54d3e1ef8f183055e9478d0ed Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 11:27:14 +0200 Subject: [PATCH 08/31] Rename context name --- src/App.tsx | 7 ++++--- src/Wallets.tsx | 4 ++-- src/components/Apps/AppPublish.tsx | 4 ++-- src/components/Apps/AppRating.tsx | 4 ++-- src/components/Apps/AppsDesktop.tsx | 4 ++-- src/components/Apps/AppsDevModeHome.tsx | 4 ++-- src/components/Apps/AppsPrivate.tsx | 4 ++-- src/components/Chat/AdminSpaceInner.tsx | 4 ++-- src/components/Chat/ChatGroup.tsx | 4 ++-- src/components/Chat/CreateCommonSecret.tsx | 4 ++-- src/components/Chat/GroupAnnouncements.tsx | 4 ++-- src/components/Chat/GroupAvatar.tsx | 4 ++-- src/components/Chat/MessageItem.tsx | 4 ++-- src/components/Embeds/AttachmentEmbed.tsx | 4 ++-- src/components/Embeds/PollEmbed.tsx | 4 ++-- src/components/Embeds/VideoPlayer.tsx | 4 ++-- src/components/GlobalActions/JoinGroup.tsx | 4 ++-- src/components/Group/AddGroup.tsx | 4 ++-- src/components/Group/AddGroupList.tsx | 4 ++-- src/components/Group/BlockedUsersModal.tsx | 4 ++-- src/components/Group/Forum/NewThread.tsx | 8 ++++++-- src/components/Group/ListOfGroupPromotions.tsx | 4 ++-- src/components/Group/ManageMembers.tsx | 4 ++-- src/components/Group/Settings.tsx | 5 +++-- src/components/Group/UserListOfInvites.tsx | 4 ++-- src/components/MainAvatar.tsx | 8 ++++++-- src/components/NotAuthenticated.tsx | 5 +++-- src/components/Save/Save.tsx | 4 ++-- src/components/Tutorials/Tutorials.tsx | 5 +++-- src/components/WrapperUserAction.tsx | 4 ++-- src/hooks/useHandlePrivateApps.tsx | 4 ++-- src/hooks/useQortalMessageListener.tsx | 4 ++-- 32 files changed, 77 insertions(+), 65 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d756f44..777454d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -236,7 +236,8 @@ const defaultValuesGlobal = { setOpenTutorialModal: () => {}, }; -export const MyContext = createContext(defaultValues); +export const QORTAL_APP_CONTEXT = + createContext(defaultValues); export let globalApiKey: string | null = null; @@ -2016,7 +2017,7 @@ function App() { > - + {extState === 'not-authenticated' && ( - + {extState === 'create-wallet' && walletToBeDownloaded && ( { @@ -44,7 +44,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => { const [seedValue, setSeedValue] = useState(''); const [seedName, setSeedName] = useState(''); const [seedError, setSeedError] = useState(''); - const { hasSeenGettingStarted } = useContext(MyContext); + const { hasSeenGettingStarted } = useContext(QORTAL_APP_CONTEXT); const [password, setPassword] = useState(''); const [isOpenSeedModal, setIsOpenSeedModal] = useState(false); const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false); diff --git a/src/components/Apps/AppPublish.tsx b/src/components/Apps/AppPublish.tsx index 697805f..1e102ee 100644 --- a/src/components/Apps/AppPublish.tsx +++ b/src/components/Apps/AppPublish.tsx @@ -16,7 +16,7 @@ import { useTheme, } from '@mui/material'; import { styled } from '@mui/system'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { Spacer } from '../../common/Spacer'; import { executeEvent } from '../../utils/events'; import { useDropzone } from 'react-dropzone'; @@ -67,7 +67,7 @@ export const AppPublish = ({ categories, myAddress, myName }) => { const [category, setCategory] = useState(''); const [appType, setAppType] = useState('APP'); const [file, setFile] = useState(null); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); const [tag1, setTag1] = useState(''); diff --git a/src/components/Apps/AppRating.tsx b/src/components/Apps/AppRating.tsx index 17d2cf0..9d63213 100644 --- a/src/components/Apps/AppRating.tsx +++ b/src/components/Apps/AppRating.tsx @@ -1,7 +1,7 @@ import { Box, Rating } from '@mui/material'; import { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { getFee } from '../../background'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { StarFilledIcon } from '../../assets/Icons/StarFilled'; import { StarEmptyIcon } from '../../assets/Icons/StarEmpty'; @@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'; export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => { const [value, setValue] = useState(0); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [hasPublishedRating, setHasPublishedRating] = useState( null ); diff --git a/src/components/Apps/AppsDesktop.tsx b/src/components/Apps/AppsDesktop.tsx index 1cc6d0d..71472db 100644 --- a/src/components/Apps/AppsDesktop.tsx +++ b/src/components/Apps/AppsDesktop.tsx @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { AppsHomeDesktop } from './AppsHomeDesktop'; import { Spacer } from '../../common/Spacer'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { AppInfo } from './AppInfo'; import { executeEvent, @@ -49,7 +49,7 @@ export const AppsDesktop = ({ const [categories, setCategories] = useState([]); const iframeRefs = useRef({}); const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); - const { showTutorial } = useContext(MyContext); + const { showTutorial } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index 74bce21..d21618b 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -19,7 +19,7 @@ import { Input, } from '@mui/material'; import { Add } from '@mui/icons-material'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { executeEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; import { useModal } from '../../common/useModal'; @@ -48,7 +48,7 @@ export const AppsDevModeHome = ({ setOpenSnackGlobal, infoSnackCustom, setInfoSnackCustom, - } = useContext(MyContext); + } = useContext(QORTAL_APP_CONTEXT); const handleSelectFile = async (existingFilePath) => { const filePath = existingFilePath || (await window.electron.selectFile()); diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index da74b64..298fc6a 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -37,7 +37,7 @@ import { } from './Apps-styles'; import AddIcon from '@mui/icons-material/Add'; import ImageUploader from '../../common/ImageUploader'; -import { getBaseApiReact, MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { fileToBase64 } from '../../utils/fileReading'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import { getFee } from '../../background'; @@ -69,7 +69,7 @@ export const AppsPrivate = ({ myName, myAddress }) => { const [isOpenPrivateModal, setIsOpenPrivateModal] = useState(false); const { show, setInfoSnackCustom, setOpenSnackGlobal } = - useContext(MyContext); + useContext(QORTAL_APP_CONTEXT); const [memberGroups] = useAtom(memberGroupsAtom); const theme = useTheme(); diff --git a/src/components/Chat/AdminSpaceInner.tsx b/src/components/Chat/AdminSpaceInner.tsx index 0312e5d..0a68048 100644 --- a/src/components/Chat/AdminSpaceInner.tsx +++ b/src/components/Chat/AdminSpaceInner.tsx @@ -1,6 +1,6 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, } from '../../App'; @@ -73,7 +73,7 @@ export const AdminSpaceInner = ({ useState(null); const [isLoadingPublishKey, setIsLoadingPublishKey] = useState(false); const { show, setInfoSnackCustom, setOpenSnackGlobal } = - useContext(MyContext); + useContext(QORTAL_APP_CONTEXT); const { t } = useTranslation(['auth', 'core', 'group']); const getAdminGroupSecretKey = useCallback(async () => { diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index f83005c..0b2ede3 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -19,7 +19,7 @@ import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { getBaseApiReact, getBaseApiReactSocket, - MyContext, + QORTAL_APP_CONTEXT, pauseAllQueues, resumeAllQueues, } from '../../App'; @@ -71,7 +71,7 @@ export const ChatGroup = ({ hideView, isPrivate, }) => { - const { isUserBlocked, show } = useContext(MyContext); + const { isUserBlocked, show } = useContext(QORTAL_APP_CONTEXT); const [messages, setMessages] = useState([]); const [chatReferences, setChatReferences] = useState({}); const [isSending, setIsSending] = useState(false); diff --git a/src/components/Chat/CreateCommonSecret.tsx b/src/components/Chat/CreateCommonSecret.tsx index bb5834e..e7472f6 100644 --- a/src/components/Chat/CreateCommonSecret.tsx +++ b/src/components/Chat/CreateCommonSecret.tsx @@ -3,7 +3,7 @@ import { Box, Button, Typography, useTheme } from '@mui/material'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { LoadingButton } from '@mui/lab'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, pauseAllQueues, @@ -32,7 +32,7 @@ export const CreateCommonSecret = ({ setIsForceShowCreationKeyPopup, isForceShowCreationKeyPopup, }) => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const [openSnack, setOpenSnack] = useState(false); diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx index 0ece390..a9d1e0a 100644 --- a/src/components/Chat/GroupAnnouncements.tsx +++ b/src/components/Chat/GroupAnnouncements.tsx @@ -24,7 +24,7 @@ import { AnnouncementList } from './AnnouncementList'; import CampaignIcon from '@mui/icons-material/Campaign'; import { AnnouncementDiscussion } from './AnnouncementDiscussion'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, pauseAllQueues, @@ -142,7 +142,7 @@ export const GroupAnnouncements = ({ const [selectedAnnouncement, setSelectedAnnouncement] = useState(null); const [isFocusedParent, setIsFocusedParent] = useState(false); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); const hasInitialized = useRef(false); diff --git a/src/components/Chat/GroupAvatar.tsx b/src/components/Chat/GroupAvatar.tsx index 903092f..17bf99b 100644 --- a/src/components/Chat/GroupAvatar.tsx +++ b/src/components/Chat/GroupAvatar.tsx @@ -1,7 +1,7 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import Logo2 from '../../assets/svgs/Logo2.svg'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, } from '../../App'; @@ -32,7 +32,7 @@ export const GroupAvatar = ({ const [hasAvatar, setHasAvatar] = useState(false); const [avatarFile, setAvatarFile] = useState(null); const [tempAvatar, setTempAvatar] = useState(null); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const { t } = useTranslation(['auth', 'core', 'group']); const [anchorEl, setAnchorEl] = useState(null); const [isLoading, setIsLoading] = useState(false); diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index a2e7fd5..a429846 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -22,7 +22,7 @@ import { useTheme, } from '@mui/material'; import { formatTimestamp } from '../../utils/time'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { generateHTML } from '@tiptap/react'; import Highlight from '@tiptap/extension-highlight'; import Mention from '@tiptap/extension-mention'; @@ -113,7 +113,7 @@ export const MessageItem = memo( onEdit, isPrivate, }) => { - const { getIndividualUserInfo } = useContext(MyContext); + const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); const [anchorEl, setAnchorEl] = useState(null); const [selectedReaction, setSelectedReaction] = useState(null); const [userInfo, setUserInfo] = useState(null); diff --git a/src/components/Embeds/AttachmentEmbed.tsx b/src/components/Embeds/AttachmentEmbed.tsx index 5a30d2d..d7458a7 100644 --- a/src/components/Embeds/AttachmentEmbed.tsx +++ b/src/components/Embeds/AttachmentEmbed.tsx @@ -1,5 +1,5 @@ import { useContext, useState } from 'react'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { Card, CardContent, @@ -36,7 +36,7 @@ export const AttachmentCard = ({ selectedGroupId, }) => { const [isOpen, setIsOpen] = useState(true); - const { downloadResource } = useContext(MyContext); + const { downloadResource } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Embeds/PollEmbed.tsx b/src/components/Embeds/PollEmbed.tsx index cffe724..bc246ba 100644 --- a/src/components/Embeds/PollEmbed.tsx +++ b/src/components/Embeds/PollEmbed.tsx @@ -1,5 +1,5 @@ import { useContext, useEffect, useState } from 'react'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { Card, CardContent, @@ -37,7 +37,7 @@ export const PollCard = ({ const [ownerName, setOwnerName] = useState(''); const [showResults, setShowResults] = useState(false); const [isOpen, setIsOpen] = useState(false); - const { show, userInfo } = useContext(MyContext); + const { show, userInfo } = useContext(QORTAL_APP_CONTEXT); const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Embeds/VideoPlayer.tsx b/src/components/Embeds/VideoPlayer.tsx index 958b2ad..ab9d3a6 100644 --- a/src/components/Embeds/VideoPlayer.tsx +++ b/src/components/Embeds/VideoPlayer.tsx @@ -21,7 +21,7 @@ import { } from '@mui/icons-material'; import { styled } from '@mui/system'; import { Refresh } from '@mui/icons-material'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { resourceKeySelector } from '../../atoms/global'; import { useAtomValue } from 'jotai'; @@ -88,7 +88,7 @@ export const VideoPlayer: FC = ({ }, [service, name, identifier]); const download = useAtomValue(resourceKeySelector(keyIdentifier)); - const { downloadResource } = useContext(MyContext); + const { downloadResource } = useContext(QORTAL_APP_CONTEXT); const videoRef = useRef(null); const [playing, setPlaying] = useState(false); const [volume, setVolume] = useState(1); diff --git a/src/components/GlobalActions/JoinGroup.tsx b/src/components/GlobalActions/JoinGroup.tsx index de3fd22..48ea442 100644 --- a/src/components/GlobalActions/JoinGroup.tsx +++ b/src/components/GlobalActions/JoinGroup.tsx @@ -11,7 +11,7 @@ import { useTheme, } from '@mui/material'; import { CustomButtonAccept } from '../../styles/App-styles'; -import { getBaseApiReact, MyContext } from '../../App'; +import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../../App'; import { getFee } from '../../background'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { FidgetSpinner } from 'react-loader-spinner'; @@ -20,7 +20,7 @@ import { memberGroupsAtom, txListAtom } from '../../atoms/global'; import { useTranslation } from 'react-i18next'; export const JoinGroup = () => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const [memberGroups] = useAtom(memberGroupsAtom); const [openSnack, setOpenSnack] = useState(false); diff --git a/src/components/Group/AddGroup.tsx b/src/components/Group/AddGroup.tsx index 1916aaa..2e0ca92 100644 --- a/src/components/Group/AddGroup.tsx +++ b/src/components/Group/AddGroup.tsx @@ -35,7 +35,7 @@ import { AddGroupList } from './AddGroupList'; import { UserListOfInvites } from './UserListOfInvites'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { getFee } from '../../background'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { useTranslation } from 'react-i18next'; import { useSetAtom } from 'jotai'; @@ -59,7 +59,7 @@ const Transition = forwardRef(function Transition( }); export const AddGroup = ({ address, open, setOpen }) => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const [openAdvance, setOpenAdvance] = useState(false); diff --git a/src/components/Group/AddGroupList.tsx b/src/components/Group/AddGroupList.tsx index eaa7f4d..e9316d8 100644 --- a/src/components/Group/AddGroupList.tsx +++ b/src/components/Group/AddGroupList.tsx @@ -23,7 +23,7 @@ import { List, } from 'react-virtualized'; import _ from 'lodash'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { LoadingButton } from '@mui/lab'; import { getFee } from '../../background'; import LockIcon from '@mui/icons-material/Lock'; @@ -39,7 +39,7 @@ const cache = new CellMeasurerCache({ }); export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [memberGroups] = useAtom(memberGroupsAtom); const setTxList = useSetAtom(txListAtom); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index 99c9191..c8145b3 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -11,7 +11,7 @@ import { useTheme, } from '@mui/material'; import { useContext, useEffect, useState } from 'react'; -import { getBaseApiReact, MyContext } from '../../App'; +import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../../App'; import { Spacer } from '../../common/Spacer'; import { executeEvent, @@ -42,7 +42,7 @@ export const BlockedUsersModal = () => { addToBlockList, setOpenSnackGlobal, setInfoSnackCustom, - } = useContext(MyContext); + } = useContext(QORTAL_APP_CONTEXT); const [blockedUsers, setBlockedUsers] = useState({ addresses: {}, diff --git a/src/components/Group/Forum/NewThread.tsx b/src/components/Group/Forum/NewThread.tsx index 763ecb6..b9d8048 100644 --- a/src/components/Group/Forum/NewThread.tsx +++ b/src/components/Group/Forum/NewThread.tsx @@ -17,7 +17,11 @@ import { ReusableModal } from './ReusableModal'; import { Spacer } from '../../../common/Spacer'; import { CreateThreadIcon } from '../../../assets/Icons/CreateThreadIcon'; import { SendNewMessage } from '../../../assets/Icons/SendNewMessage'; -import { MyContext, pauseAllQueues, resumeAllQueues } from '../../../App'; +import { + QORTAL_APP_CONTEXT, + pauseAllQueues, + resumeAllQueues, +} from '../../../App'; import { getFee } from '../../../background'; import TipTap from '../../Chat/TipTap'; import { MessageDisplay } from '../../Chat/MessageDisplay'; @@ -141,7 +145,7 @@ export const NewThread = ({ isPrivate, }: NewMessageProps) => { const { t } = useTranslation(['auth', 'core', 'group']); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [isOpen, setIsOpen] = useState(false); const [value, setValue] = useState(''); const [isSending, setIsSending] = useState(false); diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index ad3dd2d..75fe480 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -21,7 +21,7 @@ import { LoadingButton } from '@mui/lab'; import LockIcon from '@mui/icons-material/Lock'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import { - MyContext, + QORTAL_APP_CONTEXT, getArbitraryEndpointReact, getBaseApiReact, } from '../../App'; @@ -88,7 +88,7 @@ export const ListOfGroupPromotions = () => { const [fee, setFee] = useState(null); const [isLoadingJoinGroup, setIsLoadingJoinGroup] = useState(false); const [isLoadingPublish, setIsLoadingPublish] = useState(false); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index e64eeab..9fd3150 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -25,7 +25,7 @@ import { ListOfBans } from './ListOfBans'; import { ListOfJoinRequests } from './ListOfJoinRequests'; import { Box, ButtonBase, Card, Tab, Tabs, useTheme } from '@mui/material'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { getGroupMembers, getNames } from './Group'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { getFee } from '../../background'; @@ -72,7 +72,7 @@ export const ManageMembers = ({ }; const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const handleClose = () => { diff --git a/src/components/Group/Settings.tsx b/src/components/Group/Settings.tsx index c285fa0..058adde 100644 --- a/src/components/Group/Settings.tsx +++ b/src/components/Group/Settings.tsx @@ -38,7 +38,7 @@ import { Spacer } from '../../common/Spacer'; import PhraseWallet from '../../utils/generateWallet/phrase-wallet'; import { walletVersion } from '../../background'; import Base58 from '../../deps/Base58'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { useTranslation } from 'react-i18next'; const LocalNodeSwitch = styled(Switch)(({ theme }) => ({ @@ -233,7 +233,8 @@ const ExportPrivateKey = ({ rawWallet }) => { const [password, setPassword] = useState(''); const [privateKey, setPrivateKey] = useState(''); const [isOpen, setIsOpen] = useState(false); - const { setOpenSnackGlobal, setInfoSnackCustom } = useContext(MyContext); + const { setOpenSnackGlobal, setInfoSnackCustom } = + useContext(QORTAL_APP_CONTEXT); const { t } = useTranslation(['auth', 'core', 'group']); const exportPrivateKeyFunc = async () => { diff --git a/src/components/Group/UserListOfInvites.tsx b/src/components/Group/UserListOfInvites.tsx index 3b32f54..064296a 100644 --- a/src/components/Group/UserListOfInvites.tsx +++ b/src/components/Group/UserListOfInvites.tsx @@ -14,7 +14,7 @@ import { CellMeasurerCache, List, } from 'react-virtualized'; -import { MyContext, getBaseApiReact } from '../../App'; +import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { LoadingButton } from '@mui/lab'; import { getFee } from '../../background'; import LockIcon from '@mui/icons-material/Lock'; @@ -55,7 +55,7 @@ export const UserListOfInvites = ({ setInfoSnack, setOpenSnack, }) => { - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const [invites, setInvites] = useState([]); diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index 24f5744..47c985c 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -1,6 +1,10 @@ import { useContext, useEffect, useState } from 'react'; import Logo2 from '../assets/svgs/Logo2.svg'; -import { MyContext, getArbitraryEndpointReact, getBaseApiReact } from '../App'; +import { + QORTAL_APP_CONTEXT, + getArbitraryEndpointReact, + getBaseApiReact, +} from '../App'; import { Avatar, Box, @@ -22,7 +26,7 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { const [hasAvatar, setHasAvatar] = useState(false); const [avatarFile, setAvatarFile] = useState(null); const [tempAvatar, setTempAvatar] = useState(null); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const [anchorEl, setAnchorEl] = useState(null); const [isLoading, setIsLoading] = useState(false); diff --git a/src/components/NotAuthenticated.tsx b/src/components/NotAuthenticated.tsx index a87fdce..c9be445 100644 --- a/src/components/NotAuthenticated.tsx +++ b/src/components/NotAuthenticated.tsx @@ -32,7 +32,7 @@ import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; import ThemeSelector from './Theme/ThemeSelector'; import { useTranslation } from 'react-i18next'; import LanguageSelector from './Language/LanguageSelector'; -import { MyContext } from '../App'; +import { QORTAL_APP_CONTEXT } from '../App'; export const manifestData = { version: '0.5.4', @@ -81,7 +81,8 @@ export const NotAuthenticated = ({ const [showSelectApiKey, setShowSelectApiKey] = useState(false); const [enteredApiKey, setEnteredApiKey] = useState(''); const [customNodeToSaveIndex, setCustomNodeToSaveIndex] = useState(null); - const { showTutorial, hasSeenGettingStarted } = useContext(MyContext); + const { showTutorial, hasSeenGettingStarted } = + useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core']); diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index 11d2f9b..67950e6 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -18,7 +18,7 @@ import { useTheme, } from '@mui/material'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { getFee } from '../../background'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { SaveIcon } from '../../assets/Icons/SaveIcon'; @@ -82,7 +82,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { const [oldPinnedApps, setOldPinnedApps] = useAtom(oldPinnedAppsAtom); const [anchorEl, setAnchorEl] = useState(null); - const { show } = useContext(MyContext); + const { show } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/components/Tutorials/Tutorials.tsx b/src/components/Tutorials/Tutorials.tsx index d480b1c..068c3ad 100644 --- a/src/components/Tutorials/Tutorials.tsx +++ b/src/components/Tutorials/Tutorials.tsx @@ -1,5 +1,5 @@ import { useContext, useState } from 'react'; -import { MyContext } from '../../App'; +import { QORTAL_APP_CONTEXT } from '../../App'; import { Button, Dialog, @@ -16,7 +16,8 @@ import { VideoPlayer } from '../Embeds/VideoPlayer'; import { useTranslation } from 'react-i18next'; export const Tutorials = () => { - const { openTutorialModal, setOpenTutorialModal } = useContext(MyContext); + const { openTutorialModal, setOpenTutorialModal } = + useContext(QORTAL_APP_CONTEXT); const [multiNumber, setMultiNumber] = useState(0); const theme = useTheme(); const { t } = useTranslation(['core', 'tutorial']); diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index 591c200..490333d 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -7,7 +7,7 @@ import { useTheme, } from '@mui/material'; import { executeEvent } from '../utils/events'; -import { MyContext } from '../App'; +import { QORTAL_APP_CONTEXT } from '../App'; import { useAtom } from 'jotai'; import { isRunningPublicNodeAtom } from '../atoms/global'; import { useTranslation } from 'react-i18next'; @@ -175,7 +175,7 @@ const BlockUser = ({ address, name, handleClose }) => { const [isAlreadyBlocked, setIsAlreadyBlocked] = useState(null); const [isLoading, setIsLoading] = useState(false); const { isUserBlocked, addToBlockList, removeBlockFromList } = - useContext(MyContext); + useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/hooks/useHandlePrivateApps.tsx b/src/hooks/useHandlePrivateApps.tsx index fdeee79..e1d8288 100644 --- a/src/hooks/useHandlePrivateApps.tsx +++ b/src/hooks/useHandlePrivateApps.tsx @@ -1,6 +1,6 @@ import { useContext, useState } from 'react'; import { executeEvent } from '../utils/events'; -import { getBaseApiReact, MyContext } from '../App'; +import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../App'; import { createEndpoint } from '../background'; import { settingsLocalLastUpdatedAtom, @@ -19,7 +19,7 @@ export const useHandlePrivateApps = () => { setOpenSnackGlobal, infoSnackCustom, setInfoSnackCustom, - } = useContext(MyContext); + } = useContext(QORTAL_APP_CONTEXT); const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom); const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom); const { t } = useTranslation(['auth', 'core', 'group']); diff --git a/src/hooks/useQortalMessageListener.tsx b/src/hooks/useQortalMessageListener.tsx index 81f697a..c6c24da 100644 --- a/src/hooks/useQortalMessageListener.tsx +++ b/src/hooks/useQortalMessageListener.tsx @@ -4,7 +4,7 @@ import { navigationControllerAtom } from '../atoms/global'; import { Filesystem, Directory } from '@capacitor/filesystem'; import { saveFile } from '../qortalRequests/get'; import { mimeToExtensionMap } from '../utils/memeTypes'; -import { MyContext } from '../App'; +import { QORTAL_APP_CONTEXT } from '../App'; import FileSaver from 'file-saver'; import { useSetAtom } from 'jotai'; @@ -526,7 +526,7 @@ export const useQortalMessageListener = ( setOpenSnackGlobal, infoSnackCustom, setInfoSnackCustom, - } = useContext(MyContext); + } = useContext(QORTAL_APP_CONTEXT); useEffect(() => { if (tabId && !isNaN(history?.currentIndex)) { From cec429c691634bec9db781437198bdbf78817106 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 11:53:08 +0200 Subject: [PATCH 09/31] Create encryption folder and move files --- src/App.tsx | 4 +- src/Wallets.tsx | 6 +- src/{ => background}/background-cases.ts | 16 +- src/{ => background}/background.ts | 41 +- src/components/Apps/AppPublish.tsx | 2 +- src/components/Apps/AppRating.tsx | 2 +- src/components/Apps/AppsDevModeHome.tsx | 2 +- src/components/Apps/AppsPrivate.tsx | 2 +- src/components/Chat/AdminSpaceInner.tsx | 4 +- .../Chat/AnnouncementDiscussion.tsx | 2 +- src/components/Chat/ChatDirect.tsx | 2 +- src/components/Chat/ChatGroup.tsx | 2 +- src/components/Chat/CreateCommonSecret.tsx | 4 +- src/components/Chat/GroupAnnouncements.tsx | 4 +- src/components/Chat/GroupAvatar.tsx | 2 +- src/components/Embeds/PollEmbed.tsx | 2 +- src/components/GlobalActions/JoinGroup.tsx | 2 +- src/components/Group/AddGroup.tsx | 2 +- src/components/Group/AddGroupList.tsx | 2 +- src/components/Group/Forum/NewThread.tsx | 2 +- src/components/Group/Group.tsx | 2 +- src/components/Group/InviteMember.tsx | 2 +- src/components/Group/ListOfBans.tsx | 2 +- .../Group/ListOfGroupPromotions.tsx | 2 +- src/components/Group/ListOfInvites.tsx | 2 +- src/components/Group/ListOfJoinRequests.tsx | 2 +- src/components/Group/ListOfMembers.tsx | 2 +- src/components/Group/ManageMembers.tsx | 2 +- src/components/Group/Settings.tsx | 4 +- src/components/Group/UserListOfInvites.tsx | 2 +- src/components/MainAvatar.tsx | 2 +- src/components/Minting/Minting.tsx | 2 +- src/components/NotAuthenticated.tsx | 2 +- src/components/QortPayment.tsx | 2 +- src/components/RegisterName.tsx | 2 +- src/components/Save/Save.tsx | 4 +- src/components/UserLookup.tsx/UserLookup.tsx | 5 +- src/deps/AltcoinHDWallet.ts | 864 ----------------- src/encryption/AltcoinHDWallet.ts | 881 ++++++++++++++++++ src/{deps => encryption}/Base58.ts | 0 .../bcryptworker.worker.js | 0 .../bcryptworkerwasm.worker.js | 0 src/{deps => encryption}/broken-ripemd160.ts | 0 src/{deps => encryption}/ecbn.ts | 0 src/{deps => encryption}/ed2curve.ts | 0 .../encryption.ts | 12 +- src/{deps => encryption}/kdf.ts | 0 src/{deps => encryption}/nacl-fast.ts | 0 src/{deps => encryption}/ripemd160.ts | 0 src/hooks/useHandlePaymentNotification.tsx | 5 +- src/hooks/useHandlePrivateApps.tsx | 4 +- src/hooks/useQortalGetSaveSettings.tsx | 2 +- src/qdn/encryption/group-encryption.ts | 6 +- src/qdn/publish/pubish.ts | 90 +- src/qortalRequests/get.ts | 13 +- src/{ => qortalRequests}/qortalRequests.ts | 10 +- src/transactions/ChatBase.ts | 4 +- src/transactions/ChatTransaction.ts | 4 +- .../RemoveRewardShareTransaction.ts | 2 +- src/transactions/RewardShareTransaction.ts | 4 +- src/transactions/TransactionBase.ts | 4 +- src/transactions/signChat.ts | 2 +- src/transactions/signTradeBotTransaction.ts | 4 +- src/utils/decryptChatMessage.ts | 6 +- src/utils/decryptWallet.ts | 4 +- src/utils/generateWallet/generateWallet.ts | 2 +- src/utils/generateWallet/phrase-wallet.ts | 346 ++++--- .../generateWallet/publicKeyToAddress.ts | 6 +- src/utils/generateWallet/storeWallet.ts | 4 +- src/utils/validateAddress.ts | 2 +- 70 files changed, 1272 insertions(+), 1155 deletions(-) rename src/{ => background}/background-cases.ts (99%) rename src/{ => background}/background.ts (98%) delete mode 100644 src/deps/AltcoinHDWallet.ts create mode 100644 src/encryption/AltcoinHDWallet.ts rename src/{deps => encryption}/Base58.ts (100%) rename src/{deps => encryption}/bcryptworker.worker.js (100%) rename src/{deps => encryption}/bcryptworkerwasm.worker.js (100%) rename src/{deps => encryption}/broken-ripemd160.ts (100%) rename src/{deps => encryption}/ecbn.ts (100%) rename src/{deps => encryption}/ed2curve.ts (100%) rename src/{backgroundFunctions => encryption}/encryption.ts (96%) rename src/{deps => encryption}/kdf.ts (100%) rename src/{deps => encryption}/nacl-fast.ts (100%) rename src/{deps => encryption}/ripemd160.ts (100%) rename src/{ => qortalRequests}/qortalRequests.ts (99%) diff --git a/src/App.tsx b/src/App.tsx index 777454d..fe721a7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -76,7 +76,7 @@ import { groupApi, groupApiSocket, storeWallets, -} from './background'; +} from './background/background.ts'; import { executeEvent, subscribeToEvent, @@ -124,7 +124,7 @@ import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './hooks/useHandleTutorials.tsx'; import { useHandleUserInfo } from './hooks/useHandleUserInfo.tsx'; import { Minting } from './components/Minting/Minting'; -import { isRunningGateway } from './qortalRequests'; +import { isRunningGateway } from './qortalRequests/qortalRequests.ts'; import { QMailStatus } from './components/QMailStatus'; import { GlobalActions } from './components/GlobalActions/GlobalActions'; import { useBlockedAddresses } from './hooks/useBlockUsers.tsx'; diff --git a/src/Wallets.tsx b/src/Wallets.tsx index ef259fc..e6e4906 100644 --- a/src/Wallets.tsx +++ b/src/Wallets.tsx @@ -23,7 +23,11 @@ import { useDropzone } from 'react-dropzone'; import EditIcon from '@mui/icons-material/Edit'; import { Label } from './components/Group/AddGroup'; import { Spacer } from './common/Spacer'; -import { getWallets, storeWallets, walletVersion } from './background'; +import { + getWallets, + storeWallets, + walletVersion, +} from './background/background.ts'; import { useModal } from './common/useModal'; import PhraseWallet from './utils/generateWallet/phrase-wallet'; import { decryptStoredWalletFromSeedPhrase } from './utils/decryptWallet'; diff --git a/src/background-cases.ts b/src/background/background-cases.ts similarity index 99% rename from src/background-cases.ts rename to src/background/background-cases.ts index 29dab32..f6c0158 100644 --- a/src/background-cases.ts +++ b/src/background/background-cases.ts @@ -55,20 +55,20 @@ import { setGroupData, updateThreadActivity, walletVersion, -} from './background'; +} from '../background/background.ts'; import { decryptGroupEncryption, encryptAndPublishSymmetricKeyGroupChat, encryptAndPublishSymmetricKeyGroupChatForAdmins, publishGroupEncryptedResource, publishOnQDN, -} from './backgroundFunctions/encryption'; -import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from './constants/constants'; -import Base58 from './deps/Base58'; -import { encryptSingle } from './qdn/encryption/group-encryption'; -import { _createPoll, _voteOnPoll } from './qortalRequests/get'; -import { createTransaction } from './transactions/transactions'; -import { getData, storeData } from './utils/chromeStorage'; +} from '../encryption/encryption.ts'; +import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../constants/constants'; +import Base58 from '../encryption/Base58.ts'; +import { encryptSingle } from '../qdn/encryption/group-encryption'; +import { _createPoll, _voteOnPoll } from '../qortalRequests/get'; +import { createTransaction } from '../transactions/transactions'; +import { getData, storeData } from '../utils/chromeStorage'; export function versionCase(request, event) { event.source.postMessage( diff --git a/src/background.ts b/src/background/background.ts similarity index 98% rename from src/background.ts rename to src/background/background.ts index cbc96b3..ec55028 100644 --- a/src/background.ts +++ b/src/background/background.ts @@ -1,27 +1,27 @@ // @ts-nocheck -import './qortalRequests'; +import '../qortalRequests/qortalRequests'; import { isArray } from 'lodash'; -import { uint8ArrayToObject } from './backgroundFunctions/encryption'; -import Base58 from './deps/Base58'; +import { uint8ArrayToObject } from '../encryption/encryption.ts'; +import Base58 from '../encryption/Base58'; import axios from 'axios'; import { base64ToUint8Array, decryptSingle, encryptSingle, objectToBase64, -} from './qdn/encryption/group-encryption'; -import ChatComputePowWorker from './chatComputePow.worker.js?worker'; -import { reusableGet } from './qdn/publish/pubish'; -import { signChat } from './transactions/signChat'; -import { createTransaction } from './transactions/transactions'; -import { decryptChatMessage } from './utils/decryptChatMessage'; -import { decryptStoredWallet } from './utils/decryptWallet'; -import PhraseWallet from './utils/generateWallet/phrase-wallet'; -import { RequestQueueWithPromise } from './utils/queue/queue'; -import { validateAddress } from './utils/validateAddress'; +} from '../qdn/encryption/group-encryption'; +import ChatComputePowWorker from '../chatComputePow.worker.js?worker'; +import { reusableGet } from '../qdn/publish/pubish'; +import { signChat } from '../transactions/signChat'; +import { createTransaction } from '../transactions/transactions'; +import { decryptChatMessage } from '../utils/decryptChatMessage'; +import { decryptStoredWallet } from '../utils/decryptWallet'; +import PhraseWallet from '../utils/generateWallet/phrase-wallet'; +import { RequestQueueWithPromise } from '../utils/queue/queue'; +import { validateAddress } from '../utils/validateAddress'; import { Sha256 } from 'asmcrypto.js'; -import { TradeBotRespondMultipleRequest } from './transactions/TradeBotRespondMultipleRequest'; -import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from './constants/constants'; +import { TradeBotRespondMultipleRequest } from '../transactions/TradeBotRespondMultipleRequest'; +import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../constants/constants'; import { addDataPublishesCase, addEnteredQmailTimestampCase, @@ -68,7 +68,6 @@ import { ltcBalanceCase, makeAdminCase, nameCase, - notificationCase, notifyAdminRegenerateSecretKeyCase, pauseAllQueuesCase, publishGroupEncryptedResourceCase, @@ -90,9 +89,13 @@ import { validApiCase, versionCase, voteOnPollCase, -} from './background-cases'; -import { getData, removeKeysAndLogout, storeData } from './utils/chromeStorage'; -import TradeBotRespondRequest from './transactions/TradeBotRespondRequest'; +} from '../background/background-cases'; +import { + getData, + removeKeysAndLogout, + storeData, +} from '../utils/chromeStorage'; +import TradeBotRespondRequest from '../transactions/TradeBotRespondRequest'; export let groupSecretkeys = {}; diff --git a/src/components/Apps/AppPublish.tsx b/src/components/Apps/AppPublish.tsx index 1e102ee..da58cc9 100644 --- a/src/components/Apps/AppPublish.tsx +++ b/src/components/Apps/AppPublish.tsx @@ -22,7 +22,7 @@ import { executeEvent } from '../../utils/events'; import { useDropzone } from 'react-dropzone'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { fileToBase64 } from '../../utils/fileReading'; import { useTranslation } from 'react-i18next'; import { useSortedMyNames } from '../../hooks/useSortedMyNames'; diff --git a/src/components/Apps/AppRating.tsx b/src/components/Apps/AppRating.tsx index 9d63213..edd628a 100644 --- a/src/components/Apps/AppRating.tsx +++ b/src/components/Apps/AppRating.tsx @@ -1,6 +1,6 @@ import { Box, Rating } from '@mui/material'; import { useCallback, useContext, useEffect, useRef, useState } from 'react'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { StarFilledIcon } from '../../assets/Icons/StarFilled'; diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index d21618b..f9ade5d 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -23,7 +23,7 @@ import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { executeEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; import { useModal } from '../../common/useModal'; -import { createEndpoint, isUsingLocal } from '../../background'; +import { createEndpoint, isUsingLocal } from '../../background/background.ts'; import { Label } from '../Group/AddGroup'; import ShortUniqueId from 'short-unique-id'; import swaggerSVG from '../../assets/svgs/swagger.svg'; diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index 298fc6a..f24d15a 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -40,7 +40,7 @@ import ImageUploader from '../../common/ImageUploader'; import { QORTAL_APP_CONTEXT } from '../../App'; import { fileToBase64 } from '../../utils/fileReading'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { useAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; import { useSortedMyNames } from '../../hooks/useSortedMyNames'; diff --git a/src/components/Chat/AdminSpaceInner.tsx b/src/components/Chat/AdminSpaceInner.tsx index 0a68048..97e3fd1 100644 --- a/src/components/Chat/AdminSpaceInner.tsx +++ b/src/components/Chat/AdminSpaceInner.tsx @@ -10,9 +10,9 @@ import { getPublishesFromAdmins, validateSecretKey, } from '../Group/Group'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { base64ToUint8Array } from '../../qdn/encryption/group-encryption'; -import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../../encryption/encryption.ts'; import { formatTimestampForum } from '../../utils/time'; import { Spacer } from '../../common/Spacer'; import { GroupAvatar } from './GroupAvatar'; diff --git a/src/components/Chat/AnnouncementDiscussion.tsx b/src/components/Chat/AnnouncementDiscussion.tsx index 8959a9f..e0da144 100644 --- a/src/components/Chat/AnnouncementDiscussion.tsx +++ b/src/components/Chat/AnnouncementDiscussion.tsx @@ -8,7 +8,7 @@ import { Box, CircularProgress, useTheme } from '@mui/material'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import ShortUniqueId from 'short-unique-id'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { decryptPublishes, getTempPublish, diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx index 35370ad..36681e9 100644 --- a/src/components/Chat/ChatDirect.tsx +++ b/src/components/Chat/ChatDirect.tsx @@ -14,7 +14,7 @@ import { pauseAllQueues, resumeAllQueues, } from '../../App'; -import { getPublicKey } from '../../background'; +import { getPublicKey } from '../../background/background.ts'; import { useMessageQueue } from '../../MessageQueueContext'; import { executeEvent, diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index 0b2ede3..e6b75d0 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -44,7 +44,7 @@ import ShortUniqueId from 'short-unique-id'; import { ReplyPreview } from './MessageItem'; import { ExitIcon } from '../../assets/Icons/ExitIcon'; import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../../constants/constants'; -import { getFee, isExtMsg } from '../../background'; +import { getFee, isExtMsg } from '../../background/background.ts'; import AppViewerContainer from '../Apps/AppViewerContainer'; import CloseIcon from '@mui/icons-material/Close'; import { throttle } from 'lodash'; diff --git a/src/components/Chat/CreateCommonSecret.tsx b/src/components/Chat/CreateCommonSecret.tsx index e7472f6..46f76d6 100644 --- a/src/components/Chat/CreateCommonSecret.tsx +++ b/src/components/Chat/CreateCommonSecret.tsx @@ -8,14 +8,14 @@ import { getBaseApiReact, pauseAllQueues, } from '../../App'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { decryptResource, getGroupAdmins, validateSecretKey, } from '../Group/Group'; import { base64ToUint8Array } from '../../qdn/encryption/group-encryption'; -import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../../encryption/encryption.ts'; import { useSetAtom } from 'jotai'; import { txListAtom } from '../../atoms/global'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx index a9d1e0a..18170cd 100644 --- a/src/components/Chat/GroupAnnouncements.tsx +++ b/src/components/Chat/GroupAnnouncements.tsx @@ -7,7 +7,7 @@ import { useRef, useState, } from 'react'; -import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../../encryption/encryption.ts'; import { base64ToUint8Array, objectToBase64, @@ -15,7 +15,7 @@ import { import Tiptap from './TipTap'; import { CustomButton } from '../../styles/App-styles'; import CircularProgress from '@mui/material/CircularProgress'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; import { Box, Typography, useTheme } from '@mui/material'; import { Spacer } from '../../common/Spacer'; diff --git a/src/components/Chat/GroupAvatar.tsx b/src/components/Chat/GroupAvatar.tsx index 17bf99b..ecc7c86 100644 --- a/src/components/Chat/GroupAvatar.tsx +++ b/src/components/Chat/GroupAvatar.tsx @@ -16,7 +16,7 @@ import { } from '@mui/material'; import { Spacer } from '../../common/Spacer'; import ImageUploader from '../../common/ImageUploader'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { fileToBase64 } from '../../utils/fileReading'; import { LoadingButton } from '@mui/lab'; import ErrorIcon from '@mui/icons-material/Error'; diff --git a/src/components/Embeds/PollEmbed.tsx b/src/components/Embeds/PollEmbed.tsx index bc246ba..6d7a510 100644 --- a/src/components/Embeds/PollEmbed.tsx +++ b/src/components/Embeds/PollEmbed.tsx @@ -16,7 +16,7 @@ import { } from '@mui/material'; import { getNameInfo } from '../Group/Group'; import PollIcon from '@mui/icons-material/Poll'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import RefreshIcon from '@mui/icons-material/Refresh'; import { Spacer } from '../../common/Spacer'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; diff --git a/src/components/GlobalActions/JoinGroup.tsx b/src/components/GlobalActions/JoinGroup.tsx index 48ea442..95e95e6 100644 --- a/src/components/GlobalActions/JoinGroup.tsx +++ b/src/components/GlobalActions/JoinGroup.tsx @@ -12,7 +12,7 @@ import { } from '@mui/material'; import { CustomButtonAccept } from '../../styles/App-styles'; import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../../App'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { FidgetSpinner } from 'react-loader-spinner'; import { useAtom, useSetAtom } from 'jotai'; diff --git a/src/components/Group/AddGroup.tsx b/src/components/Group/AddGroup.tsx index 2e0ca92..ec01443 100644 --- a/src/components/Group/AddGroup.tsx +++ b/src/components/Group/AddGroup.tsx @@ -34,7 +34,7 @@ import { import { AddGroupList } from './AddGroupList'; import { UserListOfInvites } from './UserListOfInvites'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { QORTAL_APP_CONTEXT } from '../../App'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/AddGroupList.tsx b/src/components/Group/AddGroupList.tsx index e9316d8..8cad787 100644 --- a/src/components/Group/AddGroupList.tsx +++ b/src/components/Group/AddGroupList.tsx @@ -25,7 +25,7 @@ import { import _ from 'lodash'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { LoadingButton } from '@mui/lab'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import LockIcon from '@mui/icons-material/Lock'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import { Spacer } from '../../common/Spacer'; diff --git a/src/components/Group/Forum/NewThread.tsx b/src/components/Group/Forum/NewThread.tsx index b9d8048..9749480 100644 --- a/src/components/Group/Forum/NewThread.tsx +++ b/src/components/Group/Forum/NewThread.tsx @@ -22,7 +22,7 @@ import { pauseAllQueues, resumeAllQueues, } from '../../../App'; -import { getFee } from '../../../background'; +import { getFee } from '../../../background/background'; import TipTap from '../../Chat/TipTap'; import { MessageDisplay } from '../../Chat/MessageDisplay'; import { CustomizedSnackbars } from '../../Snackbar/Snackbar'; diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 13a807b..368dd15 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -13,7 +13,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ChatGroup } from '../Chat/ChatGroup'; import { CreateCommonSecret } from '../Chat/CreateCommonSecret'; import { base64ToUint8Array } from '../../qdn/encryption/group-encryption'; -import { uint8ArrayToObject } from '../../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../../encryption/encryption'; import { AddGroup } from './AddGroup'; import CreateIcon from '@mui/icons-material/Create'; import { diff --git a/src/components/Group/InviteMember.tsx b/src/components/Group/InviteMember.tsx index 07ee3a7..8a562d0 100644 --- a/src/components/Group/InviteMember.tsx +++ b/src/components/Group/InviteMember.tsx @@ -3,7 +3,7 @@ import { Box, Input, MenuItem, Select, SelectChangeEvent } from '@mui/material'; import { useState } from 'react'; import { Spacer } from '../../common/Spacer'; import { Label } from './AddGroup'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { useTranslation } from 'react-i18next'; export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => { diff --git a/src/components/Group/ListOfBans.tsx b/src/components/Group/ListOfBans.tsx index 9399b7d..122ce27 100644 --- a/src/components/Group/ListOfBans.tsx +++ b/src/components/Group/ListOfBans.tsx @@ -15,7 +15,7 @@ import { List, } from 'react-virtualized'; import { getNameInfo } from './Group'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingButton } from '@mui/lab'; import { getBaseApiReact } from '../../App'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index 75fe480..01fbed3 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -42,7 +42,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import ErrorBoundary from '../../common/ErrorBoundary'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { useAtom, useSetAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/ListOfInvites.tsx b/src/components/Group/ListOfInvites.tsx index 8c4fe9b..037dddc 100644 --- a/src/components/Group/ListOfInvites.tsx +++ b/src/components/Group/ListOfInvites.tsx @@ -15,7 +15,7 @@ import { List, } from 'react-virtualized'; import { getNameInfo } from './Group'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingButton } from '@mui/lab'; import { getBaseApiReact } from '../../App'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/ListOfJoinRequests.tsx b/src/components/Group/ListOfJoinRequests.tsx index 1805733..5fbfe9e 100644 --- a/src/components/Group/ListOfJoinRequests.tsx +++ b/src/components/Group/ListOfJoinRequests.tsx @@ -15,7 +15,7 @@ import { List, } from 'react-virtualized'; import { getNameInfo } from './Group'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingButton } from '@mui/lab'; import { getBaseApiReact } from '../../App'; import { txListAtom } from '../../atoms/global'; diff --git a/src/components/Group/ListOfMembers.tsx b/src/components/Group/ListOfMembers.tsx index 7a0048f..c57e501 100644 --- a/src/components/Group/ListOfMembers.tsx +++ b/src/components/Group/ListOfMembers.tsx @@ -17,7 +17,7 @@ import { List, } from 'react-virtualized'; import { LoadingButton } from '@mui/lab'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { getBaseApiReact } from '../../App'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index 9fd3150..e87b002 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -28,7 +28,7 @@ import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { getGroupMembers, getNames } from './Group'; import { LoadingSnackbar } from '../Snackbar/LoadingSnackbar'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { LoadingButton } from '@mui/lab'; import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; diff --git a/src/components/Group/Settings.tsx b/src/components/Group/Settings.tsx index 058adde..78db9bc 100644 --- a/src/components/Group/Settings.tsx +++ b/src/components/Group/Settings.tsx @@ -36,8 +36,8 @@ import { useAtom } from 'jotai'; import { decryptStoredWallet } from '../../utils/decryptWallet'; import { Spacer } from '../../common/Spacer'; import PhraseWallet from '../../utils/generateWallet/phrase-wallet'; -import { walletVersion } from '../../background'; -import Base58 from '../../deps/Base58'; +import { walletVersion } from '../../background/background.ts'; +import Base58 from '../../encryption/Base58.ts'; import { QORTAL_APP_CONTEXT } from '../../App'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Group/UserListOfInvites.tsx b/src/components/Group/UserListOfInvites.tsx index 064296a..2717af9 100644 --- a/src/components/Group/UserListOfInvites.tsx +++ b/src/components/Group/UserListOfInvites.tsx @@ -16,7 +16,7 @@ import { } from 'react-virtualized'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { LoadingButton } from '@mui/lab'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import LockIcon from '@mui/icons-material/Lock'; import NoEncryptionGmailerrorredIcon from '@mui/icons-material/NoEncryptionGmailerrorred'; import { Spacer } from '../../common/Spacer'; diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index 47c985c..b458145 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -16,7 +16,7 @@ import { } from '@mui/material'; import { Spacer } from '../common/Spacer'; import ImageUploader from '../common/ImageUploader'; -import { getFee } from '../background'; +import { getFee } from '../background/background.ts'; import { fileToBase64 } from '../utils/fileReading'; import { LoadingButton } from '@mui/lab'; import ErrorIcon from '@mui/icons-material/Error'; diff --git a/src/components/Minting/Minting.tsx b/src/components/Minting/Minting.tsx index 114d521..2c2d877 100644 --- a/src/components/Minting/Minting.tsx +++ b/src/components/Minting/Minting.tsx @@ -21,7 +21,7 @@ import { subscribeToEvent, unsubscribeFromEvent, } from '../../utils/events'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { Spacer } from '../../common/Spacer'; import { FidgetSpinner } from 'react-loader-spinner'; import { useModal } from '../../common/useModal'; diff --git a/src/components/NotAuthenticated.tsx b/src/components/NotAuthenticated.tsx index c9be445..0d2949c 100644 --- a/src/components/NotAuthenticated.tsx +++ b/src/components/NotAuthenticated.tsx @@ -27,7 +27,7 @@ import { import Logo1Dark from '../assets/svgs/Logo1Dark.svg'; import HelpIcon from '@mui/icons-material/Help'; import { CustomizedSnackbars } from './Snackbar/Snackbar'; -import { cleanUrl, gateways } from '../background'; +import { cleanUrl, gateways } from '../background/background.ts'; import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; import ThemeSelector from './Theme/ThemeSelector'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/QortPayment.tsx b/src/components/QortPayment.tsx index c031914..1907686 100644 --- a/src/components/QortPayment.tsx +++ b/src/components/QortPayment.tsx @@ -2,7 +2,7 @@ import { Box, useTheme } from '@mui/material'; import { useState } from 'react'; import { TextP } from '../styles/App-styles'; import { Spacer } from '../common/Spacer'; -import { getFee } from '../background'; +import { getFee } from '../background/background.ts'; import { useTranslation } from 'react-i18next'; export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { diff --git a/src/components/RegisterName.tsx b/src/components/RegisterName.tsx index 6e2bfe3..2b5b224 100644 --- a/src/components/RegisterName.tsx +++ b/src/components/RegisterName.tsx @@ -17,7 +17,7 @@ import { import { Label } from './Group/AddGroup'; import { Spacer } from '../common/Spacer'; import { getBaseApiReact } from '../App'; -import { getFee } from '../background'; +import { getFee } from '../background/background.ts'; import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked'; import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events'; import { BarSpinner } from '../common/Spinners/BarSpinner/BarSpinner'; diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index 67950e6..e213174 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -19,7 +19,7 @@ import { } from '@mui/material'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import { QORTAL_APP_CONTEXT } from '../../App'; -import { getFee } from '../../background'; +import { getFee } from '../../background/background.ts'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { SaveIcon } from '../../assets/Icons/SaveIcon'; import { IconWrapper } from '../Desktop/DesktopFooter'; @@ -31,7 +31,7 @@ import { saveFileToDiskGeneric } from '../../utils/generateWallet/generateWallet import { base64ToUint8Array, uint8ArrayToObject, -} from '../../backgroundFunctions/encryption'; +} from '../../encryption/encryption.ts'; import { useTranslation } from 'react-i18next'; import { useAtom, useSetAtom } from 'jotai'; diff --git a/src/components/UserLookup.tsx/UserLookup.tsx b/src/components/UserLookup.tsx/UserLookup.tsx index 675d661..3be4ef6 100644 --- a/src/components/UserLookup.tsx/UserLookup.tsx +++ b/src/components/UserLookup.tsx/UserLookup.tsx @@ -19,7 +19,10 @@ import { useTheme, Autocomplete, } from '@mui/material'; -import { getAddressInfo, getNameOrAddress } from '../../background'; +import { + getAddressInfo, + getNameOrAddress, +} from '../../background/background.ts'; import { getBaseApiReact } from '../../App'; import { getNameInfo } from '../Group/Group'; import AccountCircleIcon from '@mui/icons-material/AccountCircle'; diff --git a/src/deps/AltcoinHDWallet.ts b/src/deps/AltcoinHDWallet.ts deleted file mode 100644 index cd4a10b..0000000 --- a/src/deps/AltcoinHDWallet.ts +++ /dev/null @@ -1,864 +0,0 @@ -// @ts-nocheck -; -import Base58 from '../deps/Base58.js' -import {Sha256, Sha512} from 'asmcrypto.js' -import jsSHA from 'jssha' -import RIPEMD160 from '../deps/ripemd160.js' -import utils from '../utils/utils' -import {BigInteger, EllipticCurve} from './ecbn' -import {Buffer} from 'buffer' - -export default class AltcoinHDWallet { - - constructor(addressParams) { - - /** - * Seed - 32 bytes - */ - - this.seed = new Uint8Array(32) - - /** - * Version Bytes - 4 byte - */ - - this.versionBytes = addressParams - - /** - * Depth - 1 byte - */ - - this.depth = 0 - - /** - * Parent Fingerprint - 4 bytes - */ - - this.parentFingerprint = '0x00000000' // master key - - /** - * Child Index - 4 bytes - */ - - this.childIndex = '0x00000000' // master key - - /** - * Chain Code - 32 bytes - */ - - this.chainCode = new Uint8Array(32) - - /** - * Key Data - 33 bytes - */ - - this.keyData = new Uint8Array(33) - - /** - * Seed Hash - 64 bytes - */ - - this.seedHash = new Uint8Array(64) - - /** - * Private Key - 32 bytes - */ - - this.privateKey = new Uint8Array(32) - - /** - * Public Key - 33 bytes (compressed) - */ - - this.publicKey = new Uint8Array(33) - - /** - * Public Key Hash160 (used to derive the parent fingerprint for derived) - */ - - this.publicKeyHash = new Uint8Array(20) - - /** - * Master Private Key (Base58 encoded) - */ - - this.masterPrivateKey = '' - - /** - * Master Public Key (Base58 encoded) - */ - - this.masterPublicKey = '' - - /** - * Testnet Master Private Key (Base58 encoded) - THIS IS TESTNET - */ - - this._tMasterPrivateKey = '' - - /** - * Testnet Master Public Key (Base58 encoded) - THIS IS TESTNET - */ - - this._tmasterPublicKey = '' - - /** - * Child Keys Derivation from the Parent Keys - */ - - /** - * Child Private Key - 32 bytes - */ - - this.childPrivateKey = new Uint8Array(32) - - /** - * Child Chain Code - 32 bytes - */ - - this.childChainCode = new Uint8Array(32) - - /** - * Child Public Key - 33 bytes (compressed) - */ - - this.childPublicKey = new Uint8Array(33) - - /** - * Child Public Key Hash160 (used to derive the parent fingerprint for derived) - */ - - this.childPublicKeyHash = new Uint8Array(20) - - /** - * Extended Private Child Key - Base58 encoded - */ - - this.xPrivateChildKey = '' - - /** - * Extended Public Child Key - Base58 encoded - */ - - this.xPublicChildKey = '' - - /** - * Grand Child Keys Derivation from the Child Keys - */ - - /** - * Grand Child Private Key - 32 bytes - */ - - this.grandChildPrivateKey = new Uint8Array(32) - - /** - * Grand Child Chain Code - 32 bytes - */ - - this.grandChildChainCode = new Uint8Array(32) - - /** - * Grand Child Public Key - 33 bytes (compressed) - */ - - this.grandChildPublicKey = new Uint8Array(33) - - /** - * Grand Public Key Hash160 (used to derive the parent fingerprint for derived) - */ - - this.grandChildPublicKeyHash = new Uint8Array(20) - - /** - * Extended Private Grand Child Key - Base58 encoded - */ - - this.xPrivateGrandChildKey = '' - - /** - * Extended Public Grand Child Key - Base58 encoded - */ - - this.xPublicGrandChildKey = '' - - /** - * Litecoin Legacy Address - Derived from the Grand Child Public Key Hash - */ - - this.litecoinLegacyAddress = '' - - /** - * TESTNET Litecoin Legacy Address (Derived from the Grand Child Public Key Hash) - THIS IS TESTNET - */ - - this._tlitecoinLegacyAddress = '' - - /** - * Wallet - Wallet Object (keys...) - */ - - this.wallet = {} - } - - setSeed(seed) { - this.seed = seed - } - - createWallet(seed, isBIP44, indicator = null) { - - // Set Seeed - this.setSeed(seed) - - // Generate Seed Hash - this.generateSeedHash(this.seed, isBIP44, indicator) - - // Generate Private Key - this.generatePrivateKey(this.seedHash) - - // Generate Chain Code - this.generateChainCode(this.seedHash) - - // Generate Public Key from Private Key - this.generatePublicKey(this.privateKey) - - // Generate Mainnet Master Private Key - this.generateMainnetMasterPrivateKey() - - // Generate Mainnet Master Public Key - this.generateMainnetMasterPublicKey() - - // Generate Testnet Master Private Key - this.generateTestnetMasterPrivateKey() - - // Generate Testnet Master Public Key - this.generateTestnetMasterPublicKey() - - // Generate Child and Grand Child Keys - this.generateDerivedChildKeys() - - // Return Wallet Object Specification - return this.returnWallet() - } - - - generateSeedHash(seed, isBIP44, indicator = null) { - let buffer - - if (isBIP44) { - buffer = utils.appendBuffer(seed.reverse(), utils.int32ToBytes(indicator)) - } else { - if(indicator !== null) { - const indicatorString = utils.stringtoUTF8Array(indicator) - buffer = utils.appendBuffer(seed.reverse(), indicatorString) - } - else - { - buffer = seed.reverse() - } - } - - const _reverseSeedHash = new Sha256().process(buffer).finish().result - this.seedHash = new Sha512().process(utils.appendBuffer(seed, _reverseSeedHash)).finish().result - } - - generatePrivateKey(seedHash) { - const SECP256K1_CURVE_ORDER = new BigInteger("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141") - - const privateKeyHash = seedHash.slice(0, 32) - - this.seed58 = Base58.encode(privateKeyHash) - - const _privateKeyHash = [...privateKeyHash] - let privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKeyHash) - - const privateKey = (privateKeyBigInt.mod(SECP256K1_CURVE_ORDER.subtract(BigInteger.ONE))).add(BigInteger.ONE) - this.privateKey = privateKey.toByteArrayUnsigned() - } - - generateChainCode(seedHash) { - this.chainCode = new Sha256().process(seedHash.slice(32, 64)).finish().result - } - - generatePublicKey(privateKey) { - const _privateKey = [...privateKey] - const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey) - - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - const curvePoints = epCurve.getG().multiply(privateKeyBigInt) - - - const x = curvePoints.getX().toBigInteger() - const y = curvePoints.getY().toBigInteger() - - /** - * Deriving Uncompressed Public Key (65 bytes) - * - * const publicKeyBytes = EllipticCurve.integerToBytes(x, 32) - * this.publicKey = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32)) - * this.publicKey.unshift(0x04) // append point indicator - */ - - // Compressed Public Key (33 bytes) - this.publicKey = EllipticCurve.integerToBytes(x, 32) - - - if (y.isEven()) { - this.publicKey.unshift(0x02) // append point indicator - } else { - this.publicKey.unshift(0x03) // append point indicator - } - - // PublicKey Hash - const publicKeySHA256 = new Sha256().process(new Uint8Array(this.publicKey)).finish().result - const _publicKeyHash = new RIPEMD160().update(Buffer.from(publicKeySHA256)).digest('hex') - this.publicKeyHash = _publicKeyHash - } - - generateMainnetMasterPrivateKey() { - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.private))) - - // Append Depth - s.push(this.depth) - - // Append Parent Fingerprint - s.push(...(utils.int32ToBytes(this.parentFingerprint))) - - // Append Child Number - s.push(...(utils.int32ToBytes(this.childIndex))) - - // Append Chain Code - s.push(...this.chainCode) - - // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) - s.push(0) - - //if the private key length is less than 32 let's add leading zeros - if(this.privateKey.length<32){ - for(let i=this.privateKey.length;i<32;i++){ - s.push(0) - } - } - - // Append Private Key - s.push(...this.privateKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this.masterPrivateKey = Base58.encode(s) - } - - generateMainnetMasterPublicKey() { - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.public))) - - // Append Depth - s.push(this.depth) - - // Append Parent Fingerprint - s.push(...(utils.int32ToBytes(this.parentFingerprint))) - - // Append Child Number - s.push(...(utils.int32ToBytes(this.childIndex))) - - // Append Chain Code - s.push(...this.chainCode) - - // Append Public Key - s.push(...this.publicKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Public Key as Base58 encoded - this.masterPublicKey = Base58.encode(s) - } - - generateTestnetMasterPrivateKey() { - - // To be Used ONLY in Testnet... - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.testnet.private))) - - // Append Depth - s.push(this.depth) - - // Append Parent Fingerprint - s.push(...(utils.int32ToBytes(this.parentFingerprint))) - - // Append Child Number - s.push(...(utils.int32ToBytes(this.childIndex))) - - // Append Chain Code - s.push(...this.chainCode) - - // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) - s.push(0) - - // Append Private Key - s.push(...this.privateKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this._tMasterPrivateKey = Base58.encode(s) - } - - generateTestnetMasterPublicKey() { - - // To be Used ONLY in Testnet... - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.testnet.public))) - - // Append Depth - s.push(this.depth) - - // Append Parent Fingerprint - s.push(...(utils.int32ToBytes(this.parentFingerprint))) - - // Append Child Number - s.push(...(utils.int32ToBytes(this.childIndex))) - - // Append Chain Code - s.push(...this.chainCode) - - // Append Private Key - s.push(...this.publicKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this._tmasterPublicKey = Base58.encode(s) - } - - generateDerivedChildKeys() { - - // SPEC INFO: https://en.bitcoin.it/wiki/BIP_0032#Child_key_derivation_.28CKD.29_functions - // NOTE: will not be using some of derivations func as the value is known. (So I'd rather shove in the values and rewrite out the derivations later ?) - - // NOTE: I "re-wrote" and "reduplicate" the code for child and grandChild keys derivations inorder to get the child and grandchild from the child - // TODO: Make this more better in the future - - const path = 'm/0/0' - // let p = path.split('/') - - // Get Public kEY - const derivePublicChildKey = () => { - - const _privateKey = [...this.childPrivateKey] - const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey) - - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - const curvePoints = epCurve.getG().multiply(privateKeyBigInt) - - const x = curvePoints.getX().toBigInteger() - const y = curvePoints.getY().toBigInteger() - - // Compressed Public Key (33 bytes) - this.childPublicKey = EllipticCurve.integerToBytes(x, 32) - - - if (y.isEven()) { - - this.childPublicKey.unshift(0x02) // append point indicator - } else { - - this.childPublicKey.unshift(0x03) // append point indicator - } - - // PublicKey Hash - const childPublicKeySHA256 = new Sha256().process(new Uint8Array(this.childPublicKey)).finish().result - const _childPublicKeyHash = new RIPEMD160().update(Buffer.from(childPublicKeySHA256)).digest('hex') - this.childPublicKeyHash = _childPublicKeyHash - - - // Call deriveExtendedPublicChildKey // WIll be hardcoding the values... - deriveExtendedPublicChildKey(1, 0) - } - - const derivePrivateChildKey = (cI) => { - - let ib = [] - ib.push((cI >> 24) & 0xff) - ib.push((cI >> 16) & 0xff) - ib.push((cI >> 8) & 0xff) - ib.push(cI & 0xff) - - const s = [...this.publicKey].concat(ib) - - const _hmacSha512 = new jsSHA("SHA-512", "UINT8ARRAY", { numRounds: 1, hmacKey: { value: this.chainCode, format: "UINT8ARRAY" } }) - _hmacSha512.update(new Uint8Array(s)) - - - const IL = BigInteger.fromByteArrayUnsigned([..._hmacSha512.getHMAC('UINT8ARRAY').slice(0, 32)]) - this.childChainCode = _hmacSha512.getHMAC('UINT8ARRAY').slice(32, 64) // IR according to the SPEC - - - // SECP256k1 init - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - - - const ki = IL.add(BigInteger.fromByteArrayUnsigned(this.privateKey)).mod(epCurve.getN()) // parse256(IL) + kpar (mod n) ==> ki - this.childPrivateKey = ki.toByteArrayUnsigned() - - // Call deriveExtendedPrivateChildKey - deriveExtendedPrivateChildKey(1, 0) - } - - - const deriveExtendedPrivateChildKey = (i, childIndex) => { - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.private))) - - // Append Depth (using the index as depth) - i = parseInt(i) - s.push(i) - - // Append Parent Fingerprint - s.push(...(this.publicKeyHash.slice(0, 4))) - - // Append Child Index - s.push(childIndex >>> 24) - s.push((childIndex >>> 16) & 0xff) - s.push((childIndex >>> 8) & 0xff) - s.push(childIndex & 0xff) - - // Append Chain Code - s.push(...this.childChainCode) - - // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) - s.push(0) - - // Append Private Key - s.push(...this.childPrivateKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this.xPrivateChildKey = Base58.encode(s) - } - - const deriveExtendedPublicChildKey = (i, childIndex) => { - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.public))) - - // Append Depth - i = parseInt(i) - s.push(i) - - // Append Parent Fingerprint - s.push(...(this.publicKeyHash.slice(0, 4))) - - // Append Child Index - s.push(childIndex >>> 24) - s.push((childIndex >>> 16) & 0xff) - s.push((childIndex >>> 8) & 0xff) - s.push(childIndex & 0xff) - - // Append Chain Code - s.push(...this.childChainCode) - - // Append Public Key - s.push(...this.childPublicKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - - // Save to Public Key as Base58 encoded - this.xPublicChildKey = Base58.encode(s) - } - - - /** - * GRAND CHILD KEYS - * - * NOTE: I know this is not the best way to generate this (even though it works the way it ought) - * Things to rewrite will be and not limited to deriving this through a for loop, removing hard code values, etc... - */ - - const derivePublicGrandChildKey = () => { - - const _privateKey = [...this.grandChildPrivateKey] - const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey) - - - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - const curvePoints = epCurve.getG().multiply(privateKeyBigInt) - - const x = curvePoints.getX().toBigInteger() - const y = curvePoints.getY().toBigInteger() - - // Compressed Public Key (33 bytes) - this.grandChildPublicKey = EllipticCurve.integerToBytes(x, 32) - - - if (y.isEven()) { - this.grandChildPublicKey.unshift(0x02) // append point indicator - } else { - this.grandChildPublicKey.unshift(0x03) // append point indicator - } - - - // PublicKey Hash - const grandChildPublicKeySHA256 = new Sha256().process(new Uint8Array(this.grandChildPublicKey)).finish().result - const _grandChildPublicKeyHash = new RIPEMD160().update(Buffer.from(grandChildPublicKeySHA256)).digest('hex') - this.grandChildPublicKeyHash = _grandChildPublicKeyHash - - - // Call deriveExtendedPublicChildKey // WIll be hardcoding the values... - deriveExtendedPublicGrandChildKey(2, 0) - - /** - * Derive Litecoin Legacy Address - */ - - // Append Address Prefix - let prefix = [this.versionBytes.mainnet.prefix] - if (2 == this.versionBytes.mainnet.prefix.length) { - prefix = [this.versionBytes.mainnet.prefix[0]] - prefix.push([this.versionBytes.mainnet.prefix[1]]) - } - - const k = prefix.concat(...this.grandChildPublicKeyHash) - - // Derive Checksum - const _addressCheckSum = new Sha256().process(new Sha256().process(new Uint8Array(k)).finish().result).finish().result - const addressCheckSum = _addressCheckSum.slice(0, 4) - - // Append CheckSum - const _litecoinLegacyAddress = k.concat(...addressCheckSum) - - // Convert to Base58 - this.litecoinLegacyAddress = Base58.encode(_litecoinLegacyAddress) - - - /** - * Derive TESTNET Litecoin Legacy Address - */ - - // Append Version Byte - const tK = [this.versionBytes.testnet.prefix].concat(...this.grandChildPublicKeyHash) - - // Derive Checksum - const _tAddressCheckSum = new Sha256().process(new Sha256().process(new Uint8Array(tK)).finish().result).finish().result - const tAddressCheckSum = _tAddressCheckSum.slice(0, 4) - - // Append CheckSum - const _tlitecoinLegacyAddress = tK.concat(...tAddressCheckSum) - - // Convert to Base58 - this._tlitecoinLegacyAddress = Base58.encode(_tlitecoinLegacyAddress) - } - - const derivePrivateGrandChildKey = (cI, i) => { - - let ib = [] - ib.push((cI >> 24) & 0xff) - ib.push((cI >> 16) & 0xff) - ib.push((cI >> 8) & 0xff) - ib.push(cI & 0xff) - - const s = [...this.childPublicKey].concat(ib) - - - const _hmacSha512 = new jsSHA("SHA-512", "UINT8ARRAY", { numRounds: 1, hmacKey: { value: this.childChainCode, format: "UINT8ARRAY" } }) - _hmacSha512.update(new Uint8Array(s)) - - - const IL = BigInteger.fromByteArrayUnsigned([..._hmacSha512.getHMAC('UINT8ARRAY').slice(0, 32)]) - this.grandChildChainCode = _hmacSha512.getHMAC('UINT8ARRAY').slice(32, 64) // IR according to the SPEC - - // SECP256k1 init - const epCurve = EllipticCurve.getSECCurveByName("secp256k1") - - - const ki = IL.add(BigInteger.fromByteArrayUnsigned(this.childPrivateKey)).mod(epCurve.getN()) // parse256(IL) + kpar (mod n) ==> ki - this.grandChildPrivateKey = ki.toByteArrayUnsigned() - - - // Call deriveExtendedPrivateChildKey - deriveExtendedPrivateGrandChildKey(2, 0) - } - - - const deriveExtendedPrivateGrandChildKey = (i, childIndex) => { - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.private))) - - // Append Depth (using the index as depth) - i = parseInt(i) - s.push(i) - - // Append Parent Fingerprint - s.push(...(this.childPublicKeyHash.slice(0, 4))) - - // Append Child Index - s.push(childIndex >>> 24) - s.push((childIndex >>> 16) & 0xff) - s.push((childIndex >>> 8) & 0xff) - s.push(childIndex & 0xff) - - // Append Chain Code - s.push(...this.grandChildChainCode) - - // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) - s.push(0) - - // Append Private Key - s.push(...this.grandChildPrivateKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Private Key as Base58 encoded - this.xPrivateGrandChildKey = Base58.encode(s) - } - - const deriveExtendedPublicGrandChildKey = (i, childIndex) => { - - // Serialization Variable - const s = [] - - // Append Version Byte - s.push(...(utils.int32ToBytes(this.versionBytes.mainnet.public))) - - // Append Depth - i = parseInt(i) - s.push(i) - - // Append Parent Fingerprint - s.push(...(this.childPublicKeyHash.slice(0, 4))) - - // Append Child Index - s.push(childIndex >>> 24) - s.push((childIndex >>> 16) & 0xff) - s.push((childIndex >>> 8) & 0xff) - s.push(childIndex & 0xff) - - // Append Chain Code - s.push(...this.grandChildChainCode) - - // Append Public Key - s.push(...this.grandChildPublicKey) - - // Generate CheckSum - const _s = new Uint8Array(s) - const _checkSum = new Sha256().process(new Sha256().process(_s).finish().result).finish().result - const checkSum = _checkSum.slice(0, 4) - - // Append CheckSum - s.push(...checkSum) // And this brings us to the end of the serialization... - - // Save to Public Key as Base58 encoded - this.xPublicGrandChildKey = Base58.encode(s) - } - - - - // Hard Code value.. - let childIndex = 0 - - // Call derivePrivateChildKey //Hard code value - derivePrivateChildKey(childIndex) - - // Call derivePublicChildKey - derivePublicChildKey() - - - // Call derivePrivateGrandChildKey // Hard Code value... - derivePrivateGrandChildKey(0, 2) - - // Call derivePublicGrandChildKey - derivePublicGrandChildKey() - } - - returnWallet() { - - // Will be limiting the exported Wallet Object to just the Master keys and Legacy Addresses - - const wallet = { - derivedMasterPrivateKey: this.masterPrivateKey, - derivedMasterPublicKey: this.masterPublicKey, - _tDerivedMasterPrivateKey: this._tMasterPrivateKey, - _tDerivedmasterPublicKey: this._tmasterPublicKey, - seed58: this.seed58, - // derivedPrivateChildKey: this.xPrivateChildKey, - // derivedPublicChildKey: this.xPublicChildKey, - // derivedPrivateGrandChildKey: this.xPrivateGrandChildKey, - // derivedPublicGrandChildKey: this.xPublicGrandChildKey, - address: this.litecoinLegacyAddress, - _taddress: this._tlitecoinLegacyAddress - } - - this.wallet = wallet - return wallet - } -} diff --git a/src/encryption/AltcoinHDWallet.ts b/src/encryption/AltcoinHDWallet.ts new file mode 100644 index 0000000..0af6875 --- /dev/null +++ b/src/encryption/AltcoinHDWallet.ts @@ -0,0 +1,881 @@ +// @ts-nocheck +import Base58 from './Base58.js'; +import { Sha256, Sha512 } from 'asmcrypto.js'; +import jsSHA from 'jssha'; +import RIPEMD160 from './ripemd160.js'; +import utils from '../utils/utils.js'; +import { BigInteger, EllipticCurve } from './ecbn'; +import { Buffer } from 'buffer'; + +export default class AltcoinHDWallet { + constructor(addressParams) { + /** + * Seed - 32 bytes + */ + + this.seed = new Uint8Array(32); + + /** + * Version Bytes - 4 byte + */ + + this.versionBytes = addressParams; + + /** + * Depth - 1 byte + */ + + this.depth = 0; + + /** + * Parent Fingerprint - 4 bytes + */ + + this.parentFingerprint = '0x00000000'; // master key + + /** + * Child Index - 4 bytes + */ + + this.childIndex = '0x00000000'; // master key + + /** + * Chain Code - 32 bytes + */ + + this.chainCode = new Uint8Array(32); + + /** + * Key Data - 33 bytes + */ + + this.keyData = new Uint8Array(33); + + /** + * Seed Hash - 64 bytes + */ + + this.seedHash = new Uint8Array(64); + + /** + * Private Key - 32 bytes + */ + + this.privateKey = new Uint8Array(32); + + /** + * Public Key - 33 bytes (compressed) + */ + + this.publicKey = new Uint8Array(33); + + /** + * Public Key Hash160 (used to derive the parent fingerprint for derived) + */ + + this.publicKeyHash = new Uint8Array(20); + + /** + * Master Private Key (Base58 encoded) + */ + + this.masterPrivateKey = ''; + + /** + * Master Public Key (Base58 encoded) + */ + + this.masterPublicKey = ''; + + /** + * Testnet Master Private Key (Base58 encoded) - THIS IS TESTNET + */ + + this._tMasterPrivateKey = ''; + + /** + * Testnet Master Public Key (Base58 encoded) - THIS IS TESTNET + */ + + this._tmasterPublicKey = ''; + + /** + * Child Keys Derivation from the Parent Keys + */ + + /** + * Child Private Key - 32 bytes + */ + + this.childPrivateKey = new Uint8Array(32); + + /** + * Child Chain Code - 32 bytes + */ + + this.childChainCode = new Uint8Array(32); + + /** + * Child Public Key - 33 bytes (compressed) + */ + + this.childPublicKey = new Uint8Array(33); + + /** + * Child Public Key Hash160 (used to derive the parent fingerprint for derived) + */ + + this.childPublicKeyHash = new Uint8Array(20); + + /** + * Extended Private Child Key - Base58 encoded + */ + + this.xPrivateChildKey = ''; + + /** + * Extended Public Child Key - Base58 encoded + */ + + this.xPublicChildKey = ''; + + /** + * Grand Child Keys Derivation from the Child Keys + */ + + /** + * Grand Child Private Key - 32 bytes + */ + + this.grandChildPrivateKey = new Uint8Array(32); + + /** + * Grand Child Chain Code - 32 bytes + */ + + this.grandChildChainCode = new Uint8Array(32); + + /** + * Grand Child Public Key - 33 bytes (compressed) + */ + + this.grandChildPublicKey = new Uint8Array(33); + + /** + * Grand Public Key Hash160 (used to derive the parent fingerprint for derived) + */ + + this.grandChildPublicKeyHash = new Uint8Array(20); + + /** + * Extended Private Grand Child Key - Base58 encoded + */ + + this.xPrivateGrandChildKey = ''; + + /** + * Extended Public Grand Child Key - Base58 encoded + */ + + this.xPublicGrandChildKey = ''; + + /** + * Litecoin Legacy Address - Derived from the Grand Child Public Key Hash + */ + + this.litecoinLegacyAddress = ''; + + /** + * TESTNET Litecoin Legacy Address (Derived from the Grand Child Public Key Hash) - THIS IS TESTNET + */ + + this._tlitecoinLegacyAddress = ''; + + /** + * Wallet - Wallet Object (keys...) + */ + + this.wallet = {}; + } + + setSeed(seed) { + this.seed = seed; + } + + createWallet(seed, isBIP44, indicator = null) { + // Set Seeed + this.setSeed(seed); + + // Generate Seed Hash + this.generateSeedHash(this.seed, isBIP44, indicator); + + // Generate Private Key + this.generatePrivateKey(this.seedHash); + + // Generate Chain Code + this.generateChainCode(this.seedHash); + + // Generate Public Key from Private Key + this.generatePublicKey(this.privateKey); + + // Generate Mainnet Master Private Key + this.generateMainnetMasterPrivateKey(); + + // Generate Mainnet Master Public Key + this.generateMainnetMasterPublicKey(); + + // Generate Testnet Master Private Key + this.generateTestnetMasterPrivateKey(); + + // Generate Testnet Master Public Key + this.generateTestnetMasterPublicKey(); + + // Generate Child and Grand Child Keys + this.generateDerivedChildKeys(); + + // Return Wallet Object Specification + return this.returnWallet(); + } + + generateSeedHash(seed, isBIP44, indicator = null) { + let buffer; + + if (isBIP44) { + buffer = utils.appendBuffer( + seed.reverse(), + utils.int32ToBytes(indicator) + ); + } else { + if (indicator !== null) { + const indicatorString = utils.stringtoUTF8Array(indicator); + buffer = utils.appendBuffer(seed.reverse(), indicatorString); + } else { + buffer = seed.reverse(); + } + } + + const _reverseSeedHash = new Sha256().process(buffer).finish().result; + this.seedHash = new Sha512() + .process(utils.appendBuffer(seed, _reverseSeedHash)) + .finish().result; + } + + generatePrivateKey(seedHash) { + const SECP256K1_CURVE_ORDER = new BigInteger( + '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141' + ); + + const privateKeyHash = seedHash.slice(0, 32); + + this.seed58 = Base58.encode(privateKeyHash); + + const _privateKeyHash = [...privateKeyHash]; + let privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKeyHash); + + const privateKey = privateKeyBigInt + .mod(SECP256K1_CURVE_ORDER.subtract(BigInteger.ONE)) + .add(BigInteger.ONE); + this.privateKey = privateKey.toByteArrayUnsigned(); + } + + generateChainCode(seedHash) { + this.chainCode = new Sha256() + .process(seedHash.slice(32, 64)) + .finish().result; + } + + generatePublicKey(privateKey) { + const _privateKey = [...privateKey]; + const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey); + + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + const curvePoints = epCurve.getG().multiply(privateKeyBigInt); + + const x = curvePoints.getX().toBigInteger(); + const y = curvePoints.getY().toBigInteger(); + + /** + * Deriving Uncompressed Public Key (65 bytes) + * + * const publicKeyBytes = EllipticCurve.integerToBytes(x, 32) + * this.publicKey = publicKeyBytes.concat(EllipticCurve.integerToBytes(y, 32)) + * this.publicKey.unshift(0x04) // append point indicator + */ + + // Compressed Public Key (33 bytes) + this.publicKey = EllipticCurve.integerToBytes(x, 32); + + if (y.isEven()) { + this.publicKey.unshift(0x02); // append point indicator + } else { + this.publicKey.unshift(0x03); // append point indicator + } + + // PublicKey Hash + const publicKeySHA256 = new Sha256() + .process(new Uint8Array(this.publicKey)) + .finish().result; + const _publicKeyHash = new RIPEMD160() + .update(Buffer.from(publicKeySHA256)) + .digest('hex'); + this.publicKeyHash = _publicKeyHash; + } + + generateMainnetMasterPrivateKey() { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.private)); + + // Append Depth + s.push(this.depth); + + // Append Parent Fingerprint + s.push(...utils.int32ToBytes(this.parentFingerprint)); + + // Append Child Number + s.push(...utils.int32ToBytes(this.childIndex)); + + // Append Chain Code + s.push(...this.chainCode); + + // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) + s.push(0); + + //if the private key length is less than 32 let's add leading zeros + if (this.privateKey.length < 32) { + for (let i = this.privateKey.length; i < 32; i++) { + s.push(0); + } + } + + // Append Private Key + s.push(...this.privateKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this.masterPrivateKey = Base58.encode(s); + } + + generateMainnetMasterPublicKey() { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.public)); + + // Append Depth + s.push(this.depth); + + // Append Parent Fingerprint + s.push(...utils.int32ToBytes(this.parentFingerprint)); + + // Append Child Number + s.push(...utils.int32ToBytes(this.childIndex)); + + // Append Chain Code + s.push(...this.chainCode); + + // Append Public Key + s.push(...this.publicKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Public Key as Base58 encoded + this.masterPublicKey = Base58.encode(s); + } + + generateTestnetMasterPrivateKey() { + // To be Used ONLY in Testnet... + + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.testnet.private)); + + // Append Depth + s.push(this.depth); + + // Append Parent Fingerprint + s.push(...utils.int32ToBytes(this.parentFingerprint)); + + // Append Child Number + s.push(...utils.int32ToBytes(this.childIndex)); + + // Append Chain Code + s.push(...this.chainCode); + + // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) + s.push(0); + + // Append Private Key + s.push(...this.privateKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this._tMasterPrivateKey = Base58.encode(s); + } + + generateTestnetMasterPublicKey() { + // To be Used ONLY in Testnet... + + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.testnet.public)); + + // Append Depth + s.push(this.depth); + + // Append Parent Fingerprint + s.push(...utils.int32ToBytes(this.parentFingerprint)); + + // Append Child Number + s.push(...utils.int32ToBytes(this.childIndex)); + + // Append Chain Code + s.push(...this.chainCode); + + // Append Private Key + s.push(...this.publicKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this._tmasterPublicKey = Base58.encode(s); + } + + generateDerivedChildKeys() { + // SPEC INFO: https://en.bitcoin.it/wiki/BIP_0032#Child_key_derivation_.28CKD.29_functions + // NOTE: will not be using some of derivations func as the value is known. (So I'd rather shove in the values and rewrite out the derivations later ?) + + // NOTE: I "re-wrote" and "reduplicate" the code for child and grandChild keys derivations inorder to get the child and grandchild from the child + // TODO: Make this more better in the future + + const path = 'm/0/0'; + // let p = path.split('/') + + // Get Public kEY + const derivePublicChildKey = () => { + const _privateKey = [...this.childPrivateKey]; + const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey); + + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + const curvePoints = epCurve.getG().multiply(privateKeyBigInt); + + const x = curvePoints.getX().toBigInteger(); + const y = curvePoints.getY().toBigInteger(); + + // Compressed Public Key (33 bytes) + this.childPublicKey = EllipticCurve.integerToBytes(x, 32); + + if (y.isEven()) { + this.childPublicKey.unshift(0x02); // append point indicator + } else { + this.childPublicKey.unshift(0x03); // append point indicator + } + + // PublicKey Hash + const childPublicKeySHA256 = new Sha256() + .process(new Uint8Array(this.childPublicKey)) + .finish().result; + const _childPublicKeyHash = new RIPEMD160() + .update(Buffer.from(childPublicKeySHA256)) + .digest('hex'); + this.childPublicKeyHash = _childPublicKeyHash; + + // Call deriveExtendedPublicChildKey // WIll be hardcoding the values... + deriveExtendedPublicChildKey(1, 0); + }; + + const derivePrivateChildKey = (cI) => { + let ib = []; + ib.push((cI >> 24) & 0xff); + ib.push((cI >> 16) & 0xff); + ib.push((cI >> 8) & 0xff); + ib.push(cI & 0xff); + + const s = [...this.publicKey].concat(ib); + + const _hmacSha512 = new jsSHA('SHA-512', 'UINT8ARRAY', { + numRounds: 1, + hmacKey: { value: this.chainCode, format: 'UINT8ARRAY' }, + }); + _hmacSha512.update(new Uint8Array(s)); + + const IL = BigInteger.fromByteArrayUnsigned([ + ..._hmacSha512.getHMAC('UINT8ARRAY').slice(0, 32), + ]); + this.childChainCode = _hmacSha512.getHMAC('UINT8ARRAY').slice(32, 64); // IR according to the SPEC + + // SECP256k1 init + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + + const ki = IL.add(BigInteger.fromByteArrayUnsigned(this.privateKey)).mod( + epCurve.getN() + ); // parse256(IL) + kpar (mod n) ==> ki + this.childPrivateKey = ki.toByteArrayUnsigned(); + + // Call deriveExtendedPrivateChildKey + deriveExtendedPrivateChildKey(1, 0); + }; + + const deriveExtendedPrivateChildKey = (i, childIndex) => { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.private)); + + // Append Depth (using the index as depth) + i = parseInt(i); + s.push(i); + + // Append Parent Fingerprint + s.push(...this.publicKeyHash.slice(0, 4)); + + // Append Child Index + s.push(childIndex >>> 24); + s.push((childIndex >>> 16) & 0xff); + s.push((childIndex >>> 8) & 0xff); + s.push(childIndex & 0xff); + + // Append Chain Code + s.push(...this.childChainCode); + + // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) + s.push(0); + + // Append Private Key + s.push(...this.childPrivateKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this.xPrivateChildKey = Base58.encode(s); + }; + + const deriveExtendedPublicChildKey = (i, childIndex) => { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.public)); + + // Append Depth + i = parseInt(i); + s.push(i); + + // Append Parent Fingerprint + s.push(...this.publicKeyHash.slice(0, 4)); + + // Append Child Index + s.push(childIndex >>> 24); + s.push((childIndex >>> 16) & 0xff); + s.push((childIndex >>> 8) & 0xff); + s.push(childIndex & 0xff); + + // Append Chain Code + s.push(...this.childChainCode); + + // Append Public Key + s.push(...this.childPublicKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Public Key as Base58 encoded + this.xPublicChildKey = Base58.encode(s); + }; + + /** + * GRAND CHILD KEYS + * + * NOTE: I know this is not the best way to generate this (even though it works the way it ought) + * Things to rewrite will be and not limited to deriving this through a for loop, removing hard code values, etc... + */ + + const derivePublicGrandChildKey = () => { + const _privateKey = [...this.grandChildPrivateKey]; + const privateKeyBigInt = BigInteger.fromByteArrayUnsigned(_privateKey); + + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + const curvePoints = epCurve.getG().multiply(privateKeyBigInt); + + const x = curvePoints.getX().toBigInteger(); + const y = curvePoints.getY().toBigInteger(); + + // Compressed Public Key (33 bytes) + this.grandChildPublicKey = EllipticCurve.integerToBytes(x, 32); + + if (y.isEven()) { + this.grandChildPublicKey.unshift(0x02); // append point indicator + } else { + this.grandChildPublicKey.unshift(0x03); // append point indicator + } + + // PublicKey Hash + const grandChildPublicKeySHA256 = new Sha256() + .process(new Uint8Array(this.grandChildPublicKey)) + .finish().result; + const _grandChildPublicKeyHash = new RIPEMD160() + .update(Buffer.from(grandChildPublicKeySHA256)) + .digest('hex'); + this.grandChildPublicKeyHash = _grandChildPublicKeyHash; + + // Call deriveExtendedPublicChildKey // WIll be hardcoding the values... + deriveExtendedPublicGrandChildKey(2, 0); + + /** + * Derive Litecoin Legacy Address + */ + + // Append Address Prefix + let prefix = [this.versionBytes.mainnet.prefix]; + if (2 == this.versionBytes.mainnet.prefix.length) { + prefix = [this.versionBytes.mainnet.prefix[0]]; + prefix.push([this.versionBytes.mainnet.prefix[1]]); + } + + const k = prefix.concat(...this.grandChildPublicKeyHash); + + // Derive Checksum + const _addressCheckSum = new Sha256() + .process(new Sha256().process(new Uint8Array(k)).finish().result) + .finish().result; + const addressCheckSum = _addressCheckSum.slice(0, 4); + + // Append CheckSum + const _litecoinLegacyAddress = k.concat(...addressCheckSum); + + // Convert to Base58 + this.litecoinLegacyAddress = Base58.encode(_litecoinLegacyAddress); + + /** + * Derive TESTNET Litecoin Legacy Address + */ + + // Append Version Byte + const tK = [this.versionBytes.testnet.prefix].concat( + ...this.grandChildPublicKeyHash + ); + + // Derive Checksum + const _tAddressCheckSum = new Sha256() + .process(new Sha256().process(new Uint8Array(tK)).finish().result) + .finish().result; + const tAddressCheckSum = _tAddressCheckSum.slice(0, 4); + + // Append CheckSum + const _tlitecoinLegacyAddress = tK.concat(...tAddressCheckSum); + + // Convert to Base58 + this._tlitecoinLegacyAddress = Base58.encode(_tlitecoinLegacyAddress); + }; + + const derivePrivateGrandChildKey = (cI, i) => { + let ib = []; + ib.push((cI >> 24) & 0xff); + ib.push((cI >> 16) & 0xff); + ib.push((cI >> 8) & 0xff); + ib.push(cI & 0xff); + + const s = [...this.childPublicKey].concat(ib); + + const _hmacSha512 = new jsSHA('SHA-512', 'UINT8ARRAY', { + numRounds: 1, + hmacKey: { value: this.childChainCode, format: 'UINT8ARRAY' }, + }); + _hmacSha512.update(new Uint8Array(s)); + + const IL = BigInteger.fromByteArrayUnsigned([ + ..._hmacSha512.getHMAC('UINT8ARRAY').slice(0, 32), + ]); + this.grandChildChainCode = _hmacSha512 + .getHMAC('UINT8ARRAY') + .slice(32, 64); // IR according to the SPEC + + // SECP256k1 init + const epCurve = EllipticCurve.getSECCurveByName('secp256k1'); + + const ki = IL.add( + BigInteger.fromByteArrayUnsigned(this.childPrivateKey) + ).mod(epCurve.getN()); // parse256(IL) + kpar (mod n) ==> ki + this.grandChildPrivateKey = ki.toByteArrayUnsigned(); + + // Call deriveExtendedPrivateChildKey + deriveExtendedPrivateGrandChildKey(2, 0); + }; + + const deriveExtendedPrivateGrandChildKey = (i, childIndex) => { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.private)); + + // Append Depth (using the index as depth) + i = parseInt(i); + s.push(i); + + // Append Parent Fingerprint + s.push(...this.childPublicKeyHash.slice(0, 4)); + + // Append Child Index + s.push(childIndex >>> 24); + s.push((childIndex >>> 16) & 0xff); + s.push((childIndex >>> 8) & 0xff); + s.push(childIndex & 0xff); + + // Append Chain Code + s.push(...this.grandChildChainCode); + + // Append 1 byte '0x00' (to make the key data 33 bytes, DO THIS ONLY FOR PRIVATE KEYS ) + s.push(0); + + // Append Private Key + s.push(...this.grandChildPrivateKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Private Key as Base58 encoded + this.xPrivateGrandChildKey = Base58.encode(s); + }; + + const deriveExtendedPublicGrandChildKey = (i, childIndex) => { + // Serialization Variable + const s = []; + + // Append Version Byte + s.push(...utils.int32ToBytes(this.versionBytes.mainnet.public)); + + // Append Depth + i = parseInt(i); + s.push(i); + + // Append Parent Fingerprint + s.push(...this.childPublicKeyHash.slice(0, 4)); + + // Append Child Index + s.push(childIndex >>> 24); + s.push((childIndex >>> 16) & 0xff); + s.push((childIndex >>> 8) & 0xff); + s.push(childIndex & 0xff); + + // Append Chain Code + s.push(...this.grandChildChainCode); + + // Append Public Key + s.push(...this.grandChildPublicKey); + + // Generate CheckSum + const _s = new Uint8Array(s); + const _checkSum = new Sha256() + .process(new Sha256().process(_s).finish().result) + .finish().result; + const checkSum = _checkSum.slice(0, 4); + + // Append CheckSum + s.push(...checkSum); // And this brings us to the end of the serialization... + + // Save to Public Key as Base58 encoded + this.xPublicGrandChildKey = Base58.encode(s); + }; + + // Hard Code value.. + let childIndex = 0; + + // Call derivePrivateChildKey //Hard code value + derivePrivateChildKey(childIndex); + + // Call derivePublicChildKey + derivePublicChildKey(); + + // Call derivePrivateGrandChildKey // Hard Code value... + derivePrivateGrandChildKey(0, 2); + + // Call derivePublicGrandChildKey + derivePublicGrandChildKey(); + } + + returnWallet() { + // Will be limiting the exported Wallet Object to just the Master keys and Legacy Addresses + + const wallet = { + derivedMasterPrivateKey: this.masterPrivateKey, + derivedMasterPublicKey: this.masterPublicKey, + _tDerivedMasterPrivateKey: this._tMasterPrivateKey, + _tDerivedmasterPublicKey: this._tmasterPublicKey, + seed58: this.seed58, + // derivedPrivateChildKey: this.xPrivateChildKey, + // derivedPublicChildKey: this.xPublicChildKey, + // derivedPrivateGrandChildKey: this.xPrivateGrandChildKey, + // derivedPublicGrandChildKey: this.xPublicGrandChildKey, + address: this.litecoinLegacyAddress, + _taddress: this._tlitecoinLegacyAddress, + }; + + this.wallet = wallet; + return wallet; + } +} diff --git a/src/deps/Base58.ts b/src/encryption/Base58.ts similarity index 100% rename from src/deps/Base58.ts rename to src/encryption/Base58.ts diff --git a/src/deps/bcryptworker.worker.js b/src/encryption/bcryptworker.worker.js similarity index 100% rename from src/deps/bcryptworker.worker.js rename to src/encryption/bcryptworker.worker.js diff --git a/src/deps/bcryptworkerwasm.worker.js b/src/encryption/bcryptworkerwasm.worker.js similarity index 100% rename from src/deps/bcryptworkerwasm.worker.js rename to src/encryption/bcryptworkerwasm.worker.js diff --git a/src/deps/broken-ripemd160.ts b/src/encryption/broken-ripemd160.ts similarity index 100% rename from src/deps/broken-ripemd160.ts rename to src/encryption/broken-ripemd160.ts diff --git a/src/deps/ecbn.ts b/src/encryption/ecbn.ts similarity index 100% rename from src/deps/ecbn.ts rename to src/encryption/ecbn.ts diff --git a/src/deps/ed2curve.ts b/src/encryption/ed2curve.ts similarity index 100% rename from src/deps/ed2curve.ts rename to src/encryption/ed2curve.ts diff --git a/src/backgroundFunctions/encryption.ts b/src/encryption/encryption.ts similarity index 96% rename from src/backgroundFunctions/encryption.ts rename to src/encryption/encryption.ts index def79c0..97f6d75 100644 --- a/src/backgroundFunctions/encryption.ts +++ b/src/encryption/encryption.ts @@ -1,14 +1,14 @@ -import { getBaseApi } from '../background'; -import i18n from '../i18n/i18n'; +import { getBaseApi } from '../background/background.ts'; +import i18n from '../i18n/i18n.ts'; import { createSymmetricKeyAndNonce, decryptGroupData, encryptDataGroup, objectToBase64, -} from '../qdn/encryption/group-encryption'; -import { publishData } from '../qdn/publish/pubish'; -import { getData } from '../utils/chromeStorage'; -import { RequestQueueWithPromise } from '../utils/queue/queue'; +} from '../qdn/encryption/group-encryption.ts'; +import { publishData } from '../qdn/publish/pubish.ts'; +import { getData } from '../utils/chromeStorage.ts'; +import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; export const requestQueueGetPublicKeys = new RequestQueueWithPromise(10); diff --git a/src/deps/kdf.ts b/src/encryption/kdf.ts similarity index 100% rename from src/deps/kdf.ts rename to src/encryption/kdf.ts diff --git a/src/deps/nacl-fast.ts b/src/encryption/nacl-fast.ts similarity index 100% rename from src/deps/nacl-fast.ts rename to src/encryption/nacl-fast.ts diff --git a/src/deps/ripemd160.ts b/src/encryption/ripemd160.ts similarity index 100% rename from src/deps/ripemd160.ts rename to src/encryption/ripemd160.ts diff --git a/src/hooks/useHandlePaymentNotification.tsx b/src/hooks/useHandlePaymentNotification.tsx index 7a13072..e25fae2 100644 --- a/src/hooks/useHandlePaymentNotification.tsx +++ b/src/hooks/useHandlePaymentNotification.tsx @@ -1,7 +1,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { getBaseApiReact } from '../App'; import { getData, storeData } from '../utils/chromeStorage'; -import { checkDifference, getNameInfoForOthers } from '../background'; +import { + checkDifference, + getNameInfoForOthers, +} from '../background/background.ts'; import { lastPaymentSeenTimestampAtom } from '../atoms/global'; import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events'; import { useAtom } from 'jotai'; diff --git a/src/hooks/useHandlePrivateApps.tsx b/src/hooks/useHandlePrivateApps.tsx index e1d8288..3876130 100644 --- a/src/hooks/useHandlePrivateApps.tsx +++ b/src/hooks/useHandlePrivateApps.tsx @@ -1,14 +1,14 @@ import { useContext, useState } from 'react'; import { executeEvent } from '../utils/events'; import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../App'; -import { createEndpoint } from '../background'; +import { createEndpoint } from '../background/background.ts'; import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom, } from '../atoms/global'; import { saveToLocalStorage } from '../components/Apps/AppsNavBarDesktop'; import { base64ToUint8Array } from '../qdn/encryption/group-encryption'; -import { uint8ArrayToObject } from '../backgroundFunctions/encryption'; +import { uint8ArrayToObject } from '../encryption/encryption.ts'; import { useSetAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; diff --git a/src/hooks/useQortalGetSaveSettings.tsx b/src/hooks/useQortalGetSaveSettings.tsx index 214455b..5fb62fc 100644 --- a/src/hooks/useQortalGetSaveSettings.tsx +++ b/src/hooks/useQortalGetSaveSettings.tsx @@ -12,7 +12,7 @@ import { decryptResource } from '../components/Group/Group'; import { base64ToUint8Array, uint8ArrayToObject, -} from '../backgroundFunctions/encryption'; +} from '../encryption/encryption'; import { useAtom, useSetAtom } from 'jotai'; function fetchFromLocalStorage(key) { diff --git a/src/qdn/encryption/group-encryption.ts b/src/qdn/encryption/group-encryption.ts index 24998e9..6f018ba 100644 --- a/src/qdn/encryption/group-encryption.ts +++ b/src/qdn/encryption/group-encryption.ts @@ -1,8 +1,8 @@ // @ts-nocheck -import Base58 from '../../deps/Base58'; -import ed2curve from '../../deps/ed2curve'; -import nacl from '../../deps/nacl-fast'; +import Base58 from '../../encryption/Base58'; +import ed2curve from '../../encryption/ed2curve'; +import nacl from '../../encryption/nacl-fast'; import i18n from '../../i18n/i18n'; export function base64ToUint8Array(base64: string) { diff --git a/src/qdn/publish/pubish.ts b/src/qdn/publish/pubish.ts index 5f00ffb..d49791b 100644 --- a/src/qdn/publish/pubish.ts +++ b/src/qdn/publish/pubish.ts @@ -1,10 +1,10 @@ // @ts-nocheck import { Buffer } from 'buffer'; -import Base58 from '../../deps/Base58'; -import nacl from '../../deps/nacl-fast'; +import Base58 from '../../encryption/Base58'; +import nacl from '../../encryption/nacl-fast'; import utils from '../../utils/utils'; -import { createEndpoint, getBaseApi } from '../../background'; +import { createEndpoint, getBaseApi } from '../../background/background'; import { getData } from '../../utils/chromeStorage'; export async function reusableGet(endpoint) { @@ -30,6 +30,7 @@ async function reusablePost(endpoint, _body) { data = await response.clone().json(); } catch (e) { data = await response.text(); +<<<<<<< HEAD } return data; } @@ -70,6 +71,7 @@ async function uploadChunkWithRetry(endpoint, formData, index, maxRetries = 3) { await new Promise((res) => setTimeout(res, 10_000)); } } + return data; } async function getKeyPair() { @@ -87,6 +89,11 @@ export const publishData = async ({ service, identifier, uploadType, + file, + service, + identifier, + uploadType, + isBase64, filename, withFee, title, @@ -245,6 +252,7 @@ export const publishData = async ({ return signAndProcessRes; }; +<<<<<<< HEAD const uploadData = async (registeredName: string, data: any, fee: number) => { console.log('data', uploadType, data); let postBody = ''; @@ -359,6 +367,82 @@ export const publishData = async ({ const result = await response.text(); // Base58-encoded unsigned transaction return result; +} + + const uploadData = async (registeredName: string, file: any, fee: number) => { + let postBody = ''; + let urlSuffix = ''; + + if (file != null) { + // If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API + if (uploadType === 'zip') { + urlSuffix = '/zip'; + } + + // If we're sending file data, use the /base64 version of the POST /arbitrary/* API + else if (uploadType === 'file') { + urlSuffix = '/base64'; + } + + // Base64 encode the file to work around compatibility issues between javascript and java byte arrays + if (isBase64) { + postBody = file; + } + + if (!isBase64) { + let fileBuffer = new Uint8Array(await file.arrayBuffer()); + postBody = Buffer.from(fileBuffer).toString('base64'); + } + } + + 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') { + uploadDataUrl = + uploadDataUrl + '&filename=' + encodeURIComponent(filename); + } + + if (title != null && title != 'undefined') { + uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title); + } + + if (description != null && description != 'undefined') { + uploadDataUrl = + uploadDataUrl + '&description=' + encodeURIComponent(description); + } + + if (category != null && category != 'undefined') { + uploadDataUrl = + uploadDataUrl + '&category=' + encodeURIComponent(category); + } + + if (tag1 != null && tag1 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1); + } + + if (tag2 != null && tag2 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2); + } + + if (tag3 != null && tag3 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3); + } + + if (tag4 != null && tag4 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4); + } + + if (tag5 != null && tag5 != 'undefined') { + uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag5); + } + + return await reusablePost(uploadDataUrl, postBody); +>>>>>>> 2d01b3e (Create encryption folder and move files) }; try { diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index a698fcc..15576aa 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -37,12 +37,17 @@ import { getAssetInfo, getPublicKey, transferAsset, +<<<<<<< HEAD } from '../background'; import { getAllUserNames, getNameInfo, uint8ArrayToObject, } from '../backgroundFunctions/encryption'; +======= +} from '../background/background.ts'; +import { getNameInfo, uint8ArrayToObject } from '../encryption/encryption.ts'; +>>>>>>> 2d01b3e (Create encryption folder and move files) import { showSaveFilePicker } from '../hooks/useQortalMessageListener'; import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner'; import { extractComponents } from '../components/Chat/MessageDisplay'; @@ -53,9 +58,9 @@ import { validateSecretKey, } from '../components/Group/Group'; import { QORT_DECIMALS } from '../constants/constants'; -import Base58 from '../deps/Base58'; -import ed2curve from '../deps/ed2curve'; -import nacl from '../deps/nacl-fast'; +import Base58 from '../encryption/Base58.ts'; +import ed2curve from '../encryption/ed2curve.ts'; +import nacl from '../encryption/nacl-fast.ts'; import { base64ToUint8Array, createSymmetricKeyAndNonce, @@ -74,7 +79,7 @@ import { getPermission, isRunningGateway, setPermission, -} from '../qortalRequests'; +} from './qortalRequests.ts'; import TradeBotCreateRequest from '../transactions/TradeBotCreateRequest'; import DeleteTradeOffer from '../transactions/TradeBotDeleteRequest'; import signTradeBotTransaction from '../transactions/signTradeBotTransaction'; diff --git a/src/qortalRequests.ts b/src/qortalRequests/qortalRequests.ts similarity index 99% rename from src/qortalRequests.ts rename to src/qortalRequests/qortalRequests.ts index 08a7c4f..9681790 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests/qortalRequests.ts @@ -1,5 +1,5 @@ -import { gateways, getApiKeyFromStorage } from './background'; -import { listOfAllQortalRequests } from './hooks/useQortalMessageListener'; +import { gateways, getApiKeyFromStorage } from '../background/background.ts'; +import { listOfAllQortalRequests } from '../hooks/useQortalMessageListener.tsx'; import { addForeignServer, addGroupAdminRequest, @@ -63,9 +63,9 @@ import { signForeignFees, multiPaymentWithPrivateData, transferAssetRequest, -} from './qortalRequests/get'; -import { getData, storeData } from './utils/chromeStorage'; -import { executeEvent } from './utils/events'; +} from './get.ts'; +import { getData, storeData } from '../utils/chromeStorage.ts'; +import { executeEvent } from '../utils/events.ts'; function getLocalStorage(key) { return getData(key).catch((error) => { diff --git a/src/transactions/ChatBase.ts b/src/transactions/ChatBase.ts index c88d634..17cb509 100644 --- a/src/transactions/ChatBase.ts +++ b/src/transactions/ChatBase.ts @@ -1,8 +1,8 @@ // @ts-nocheck import { QORT_DECIMALS, TX_TYPES } from '../constants/constants'; -import nacl from '../deps/nacl-fast'; -import Base58 from '../deps/Base58'; +import nacl from '../encryption/nacl-fast'; +import Base58 from '../encryption/Base58'; import utils from '../utils/utils'; export default class ChatBase { static get utils() { diff --git a/src/transactions/ChatTransaction.ts b/src/transactions/ChatTransaction.ts index fc67531..7abf040 100644 --- a/src/transactions/ChatTransaction.ts +++ b/src/transactions/ChatTransaction.ts @@ -1,8 +1,8 @@ // @ts-nocheck import ChatBase from './ChatBase'; -import nacl from '../deps/nacl-fast'; -import ed2curve from '../deps/ed2curve'; +import nacl from '../encryption/nacl-fast'; +import ed2curve from '../encryption/ed2curve'; import { Sha256 } from 'asmcrypto.js'; import { CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP } from '../constants/constants'; export default class ChatTransaction extends ChatBase { diff --git a/src/transactions/RemoveRewardShareTransaction.ts b/src/transactions/RemoveRewardShareTransaction.ts index c91d389..078c8ed 100644 --- a/src/transactions/RemoveRewardShareTransaction.ts +++ b/src/transactions/RemoveRewardShareTransaction.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { DYNAMIC_FEE_TIMESTAMP } from '../constants/constants'; -import Base58 from '../deps/Base58'; +import Base58 from '../encryption/Base58'; import publicKeyToAddress from '../utils/generateWallet/publicKeyToAddress'; import TransactionBase from './TransactionBase'; export default class RemoveRewardShareTransaction extends TransactionBase { diff --git a/src/transactions/RewardShareTransaction.ts b/src/transactions/RewardShareTransaction.ts index 67131b3..661f7f8 100644 --- a/src/transactions/RewardShareTransaction.ts +++ b/src/transactions/RewardShareTransaction.ts @@ -2,8 +2,8 @@ import TransactionBase from './TransactionBase'; import { Sha256 } from 'asmcrypto.js'; -import nacl from '../deps/nacl-fast'; -import ed2curve from '../deps/ed2curve'; +import nacl from '../encryption/nacl-fast'; +import ed2curve from '../encryption/ed2curve'; import { DYNAMIC_FEE_TIMESTAMP } from '../constants/constants'; import publicKeyToAddress from '../utils/generateWallet/publicKeyToAddress'; export default class RewardShareTransaction extends TransactionBase { diff --git a/src/transactions/TransactionBase.ts b/src/transactions/TransactionBase.ts index 055a08b..f4abf0f 100644 --- a/src/transactions/TransactionBase.ts +++ b/src/transactions/TransactionBase.ts @@ -1,7 +1,7 @@ // @ts-nocheck -import nacl from '../deps/nacl-fast'; -import Base58 from '../deps/Base58'; +import nacl from '../encryption/nacl-fast.js'; +import Base58 from '../encryption/Base58.js'; import utils from '../utils/utils'; import { QORT_DECIMALS, TX_TYPES } from '../constants/constants.js'; export default class TransactionBase { diff --git a/src/transactions/signChat.ts b/src/transactions/signChat.ts index 5995128..bdc3cb9 100644 --- a/src/transactions/signChat.ts +++ b/src/transactions/signChat.ts @@ -1,6 +1,6 @@ // @ts-nocheck -import nacl from '../deps/nacl-fast'; +import nacl from '../encryption/nacl-fast'; import utils from '../utils/utils'; export const signChat = (chatBytes, nonce, keyPair) => { diff --git a/src/transactions/signTradeBotTransaction.ts b/src/transactions/signTradeBotTransaction.ts index 31b7430..fc0e0e5 100644 --- a/src/transactions/signTradeBotTransaction.ts +++ b/src/transactions/signTradeBotTransaction.ts @@ -1,7 +1,7 @@ // @ts-nocheck -import nacl from '../deps/nacl-fast'; -import Base58 from '../deps/Base58'; +import nacl from '../encryption/nacl-fast'; +import Base58 from '../encryption/Base58'; import utils from '../utils/utils'; const signTradeBotTransaction = async (unsignedTxn, keyPair) => { diff --git a/src/utils/decryptChatMessage.ts b/src/utils/decryptChatMessage.ts index 39085e8..d58009f 100644 --- a/src/utils/decryptChatMessage.ts +++ b/src/utils/decryptChatMessage.ts @@ -1,8 +1,8 @@ // @ts-nocheck -import Base58 from '../deps/Base58'; -import ed2curve from '../deps/ed2curve'; -import nacl from '../deps/nacl-fast'; +import Base58 from '../encryption/Base58'; +import ed2curve from '../encryption/ed2curve'; +import nacl from '../encryption/nacl-fast'; import { Sha256 } from 'asmcrypto.js'; export const decryptChatMessage = ( diff --git a/src/utils/decryptWallet.ts b/src/utils/decryptWallet.ts index bf27e8a..3fa002a 100644 --- a/src/utils/decryptWallet.ts +++ b/src/utils/decryptWallet.ts @@ -1,9 +1,9 @@ // @ts-nocheck import { crypto } from '../constants/decryptWallet'; -import Base58 from '../deps/Base58'; +import Base58 from '../encryption/Base58'; import { AES_CBC, HmacSha512 } from 'asmcrypto.js'; -import { doInitWorkers, kdf } from '../deps/kdf'; +import { doInitWorkers, kdf } from '../encryption/kdf'; import i18n from 'i18next'; export const decryptStoredWallet = async (password, wallet) => { diff --git a/src/utils/generateWallet/generateWallet.ts b/src/utils/generateWallet/generateWallet.ts index ee3a683..977f903 100644 --- a/src/utils/generateWallet/generateWallet.ts +++ b/src/utils/generateWallet/generateWallet.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { crypto, walletVersion } from '../../constants/decryptWallet'; -import { doInitWorkers, kdf } from '../../deps/kdf'; +import { doInitWorkers, kdf } from '../../encryption/kdf'; import PhraseWallet from './phrase-wallet'; import * as WORDLISTS from './wordlists'; import FileSaver from 'file-saver'; diff --git a/src/utils/generateWallet/phrase-wallet.ts b/src/utils/generateWallet/phrase-wallet.ts index 0f21cba..3cd9ed1 100644 --- a/src/utils/generateWallet/phrase-wallet.ts +++ b/src/utils/generateWallet/phrase-wallet.ts @@ -3,208 +3,206 @@ Copyright 2017-2018 @ irontiga and vbcs (original developer) */ -import Base58 from '../../deps/Base58' -import {Sha256, Sha512} from 'asmcrypto.js' -import nacl from '../../deps/nacl-fast' -import utils from '../../utils/utils' +import Base58 from '../../encryption/Base58.js'; +import { Sha256, Sha512 } from 'asmcrypto.js'; +import nacl from '../../encryption/nacl-fast.js'; +import utils from '../../utils/utils'; -import {generateSaveWalletData} from './storeWallet.js' +import { generateSaveWalletData } from './storeWallet.js'; -import publicKeyToAddress from './publicKeyToAddress' -import AltcoinHDWallet from "../../deps/AltcoinHDWallet" +import publicKeyToAddress from './publicKeyToAddress'; +import AltcoinHDWallet from '../../encryption/AltcoinHDWallet.js'; export default class PhraseWallet { - constructor(seed, walletVersion) { + constructor(seed, walletVersion) { + this._walletVersion = walletVersion || 2; + this.seed = seed; - this._walletVersion = walletVersion || 2 - this.seed = seed + this.savedSeedData = {}; + this.hasBeenSaved = false; + } - this.savedSeedData = {} - this.hasBeenSaved = false - } + set seed(seed) { + this._byteSeed = seed; + this._base58Seed = Base58.encode(seed); - set seed(seed) { - this._byteSeed = seed - this._base58Seed = Base58.encode(seed) + this._addresses = []; - this._addresses = [] + this.genAddress(0); + } - this.genAddress(0) - } + getAddress(nonce) { + return this._addresses[nonce]; + } - getAddress(nonce) { - return this._addresses[nonce] - } + get addresses() { + return this._addresses; + } - get addresses() { - return this._addresses - } + get addressIDs() { + return this._addresses.map((addr) => { + return addr.address; + }); + } - get addressIDs() { - return this._addresses.map(addr => { - return addr.address - }) - } + get seed() { + return this._byteSeed; + } - get seed() { - return this._byteSeed - } + addressExists(nonce) { + return this._addresses[nonce] != undefined; + } - addressExists(nonce) { - return this._addresses[nonce] != undefined - } + _genAddressSeed(seed) { + let newSeed = new Sha512().process(seed).finish().result; + newSeed = new Sha512() + .process(utils.appendBuffer(newSeed, seed)) + .finish().result; + return newSeed; + } - _genAddressSeed(seed) { - let newSeed = new Sha512().process(seed).finish().result - newSeed = new Sha512().process(utils.appendBuffer(newSeed, seed)).finish().result - return newSeed - } + genAddress(nonce) { + if (nonce >= this._addresses.length) { + this._addresses.length = nonce + 1; + } - genAddress(nonce) { - if (nonce >= this._addresses.length) { - this._addresses.length = nonce + 1 - } + if (this.addressExists(nonce)) { + return this.addresses[nonce]; + } - if (this.addressExists(nonce)) { - return this.addresses[nonce] - } + const nonceBytes = utils.int32ToBytes(nonce); - const nonceBytes = utils.int32ToBytes(nonce) + let addrSeed = new Uint8Array(); + addrSeed = utils.appendBuffer(addrSeed, nonceBytes); + addrSeed = utils.appendBuffer(addrSeed, this._byteSeed); + addrSeed = utils.appendBuffer(addrSeed, nonceBytes); - let addrSeed = new Uint8Array() - addrSeed = utils.appendBuffer(addrSeed, nonceBytes) - addrSeed = utils.appendBuffer(addrSeed, this._byteSeed) - addrSeed = utils.appendBuffer(addrSeed, nonceBytes) + if (this._walletVersion == 1) { + addrSeed = new Sha256() + .process(new Sha256().process(addrSeed).finish().result) + .finish().result; - if (this._walletVersion == 1) { - addrSeed = new Sha256().process( - new Sha256() - .process(addrSeed) - .finish() - .result - ).finish().result + addrSeed = this._byteSeed; + } else { + addrSeed = this._genAddressSeed(addrSeed).slice(0, 32); + } - addrSeed = this._byteSeed - } else { - addrSeed = this._genAddressSeed(addrSeed).slice(0, 32) - } + const addrKeyPair = nacl.sign.keyPair.fromSeed(new Uint8Array(addrSeed)); - const addrKeyPair = nacl.sign.keyPair.fromSeed(new Uint8Array(addrSeed)); + const address = publicKeyToAddress(addrKeyPair.publicKey); + const qoraAddress = publicKeyToAddress(addrKeyPair.publicKey, true); - const address = publicKeyToAddress(addrKeyPair.publicKey); - const qoraAddress = publicKeyToAddress(addrKeyPair.publicKey, true); + // Create Bitcoin HD Wallet + const btcSeed = [...addrSeed]; + const btcWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: 0, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x6f, + }, + }).createWallet(new Uint8Array(btcSeed), false); - // Create Bitcoin HD Wallet - const btcSeed = [...addrSeed]; - const btcWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: 0 - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x6F - } - }).createWallet(new Uint8Array(btcSeed), false); + // Create Litecoin HD Wallet + const ltcSeed = [...addrSeed]; + const ltcWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: 0x30, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x6f, + }, + }).createWallet(new Uint8Array(ltcSeed), false, 'LTC'); - // Create Litecoin HD Wallet - const ltcSeed = [...addrSeed]; - const ltcWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: 0x30 - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x6F - } - }).createWallet(new Uint8Array(ltcSeed), false, 'LTC'); + // Create Dogecoin HD Wallet + const dogeSeed = [...addrSeed]; + const dogeWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x02fac398, + public: 0x02facafd, + prefix: 0x1e, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x71, + }, + }).createWallet(new Uint8Array(dogeSeed), false, 'DOGE'); - // Create Dogecoin HD Wallet - const dogeSeed = [...addrSeed]; - const dogeWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x02FAC398, - public: 0x02FACAFD, - prefix: 0x1E - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x71 - } - }).createWallet(new Uint8Array(dogeSeed), false, 'DOGE'); + // Create Digibyte HD Wallet + const dgbSeed = [...addrSeed]; + const dgbWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: 0x1e, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x7e, + }, + }).createWallet(new Uint8Array(dgbSeed), false, 'DGB'); - // Create Digibyte HD Wallet - const dgbSeed = [...addrSeed]; - const dgbWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: 0x1E - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x7E - } - }).createWallet(new Uint8Array(dgbSeed), false, 'DGB'); + // Create Ravencoin HD Wallet + const rvnSeed = [...addrSeed]; + const rvnWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: 0x3c, + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: 0x6f, + }, + }).createWallet(new Uint8Array(rvnSeed), false, 'RVN'); - // Create Ravencoin HD Wallet - const rvnSeed = [...addrSeed]; - const rvnWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: 0x3C - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: 0x6F - } - }).createWallet(new Uint8Array(rvnSeed), false, 'RVN'); + // Create Pirate Chain HD Wallet + const arrrSeed = [...addrSeed]; + const arrrWallet = new AltcoinHDWallet({ + mainnet: { + private: 0x0488ade4, + public: 0x0488b21e, + prefix: [0x16, 0x9a], + }, + testnet: { + private: 0x04358394, + public: 0x043587cf, + prefix: [0x14, 0x51], + }, + }).createWallet(new Uint8Array(arrrSeed), false, 'ARRR'); - // Create Pirate Chain HD Wallet - const arrrSeed = [...addrSeed]; - const arrrWallet = new AltcoinHDWallet({ - mainnet: { - private: 0x0488ADE4, - public: 0x0488B21E, - prefix: [0x16, 0x9A] - }, - testnet: { - private: 0x04358394, - public: 0x043587CF, - prefix: [0x14, 0x51] - } - }).createWallet(new Uint8Array(arrrSeed), false, 'ARRR'); + this._addresses[nonce] = { + address, + btcWallet, + ltcWallet, + dogeWallet, + dgbWallet, + rvnWallet, + arrrWallet, + qoraAddress, + keyPair: { + publicKey: addrKeyPair.publicKey, + privateKey: addrKeyPair.secretKey, + }, + base58PublicKey: Base58.encode(addrKeyPair.publicKey), + seed: addrSeed, + nonce: nonce, + }; + return this._addresses[nonce]; + } - this._addresses[nonce] = { - address, - btcWallet, - ltcWallet, - dogeWallet, - dgbWallet, - rvnWallet, - arrrWallet, - qoraAddress, - keyPair: { - publicKey: addrKeyPair.publicKey, - privateKey: addrKeyPair.secretKey - }, - base58PublicKey: Base58.encode(addrKeyPair.publicKey), - seed: addrSeed, - nonce: nonce - } - return this._addresses[nonce] - } - - generateSaveWalletData(...args) { - return generateSaveWalletData(this, ...args) - } + generateSaveWalletData(...args) { + return generateSaveWalletData(this, ...args); + } } diff --git a/src/utils/generateWallet/publicKeyToAddress.ts b/src/utils/generateWallet/publicKeyToAddress.ts index ef91420..dba25a1 100644 --- a/src/utils/generateWallet/publicKeyToAddress.ts +++ b/src/utils/generateWallet/publicKeyToAddress.ts @@ -1,7 +1,7 @@ // @ts-nocheck -import Base58 from '../../deps/Base58'; -import BROKEN_RIPEMD160 from '../../deps/broken-ripemd160'; -import RIPEMD160 from '../../deps/ripemd160'; +import Base58 from '../../encryption/Base58.js'; +import BROKEN_RIPEMD160 from '../../encryption/broken-ripemd160.js'; +import RIPEMD160 from '../../encryption/ripemd160.js'; import utils from '../../utils/utils'; import { Buffer } from 'buffer'; import { Sha256 } from 'asmcrypto.js'; diff --git a/src/utils/generateWallet/storeWallet.ts b/src/utils/generateWallet/storeWallet.ts index 458543f..c90b549 100644 --- a/src/utils/generateWallet/storeWallet.ts +++ b/src/utils/generateWallet/storeWallet.ts @@ -1,8 +1,8 @@ // @ts-nocheck import { AES_CBC, HmacSha512 } from 'asmcrypto.js'; -import Base58 from '../../deps/Base58'; -import { doInitWorkers, kdf } from '../../deps/kdf.js'; +import Base58 from '../../encryption/Base58.js'; +import { doInitWorkers, kdf } from '../../encryption/kdf.js'; import { crypto as cryptoVals } from '../../constants/decryptWallet.js'; const getRandomValues = crypto diff --git a/src/utils/validateAddress.ts b/src/utils/validateAddress.ts index 4238311..aa01af1 100644 --- a/src/utils/validateAddress.ts +++ b/src/utils/validateAddress.ts @@ -1,6 +1,6 @@ // @ts-nocheck -import Base58 from '../deps/Base58'; +import Base58 from '../encryption/Base58'; export const validateAddress = (address) => { let isAddress = false; From f6a89d6d1faf7043c0e1181d65a9e6a2416d231d Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 11:55:40 +0200 Subject: [PATCH 10/31] Rename file (typo) --- src/background/background.ts | 2 +- src/encryption/encryption.ts | 2 +- src/qdn/publish/{pubish.ts => publish.ts} | 2 +- src/qortalRequests/get.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/qdn/publish/{pubish.ts => publish.ts} (99%) diff --git a/src/background/background.ts b/src/background/background.ts index ec55028..075abea 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -11,7 +11,7 @@ import { objectToBase64, } from '../qdn/encryption/group-encryption'; import ChatComputePowWorker from '../chatComputePow.worker.js?worker'; -import { reusableGet } from '../qdn/publish/pubish'; +import { reusableGet } from '../qdn/publish/publish.ts'; import { signChat } from '../transactions/signChat'; import { createTransaction } from '../transactions/transactions'; import { decryptChatMessage } from '../utils/decryptChatMessage'; diff --git a/src/encryption/encryption.ts b/src/encryption/encryption.ts index 97f6d75..d8f9d25 100644 --- a/src/encryption/encryption.ts +++ b/src/encryption/encryption.ts @@ -6,7 +6,7 @@ import { encryptDataGroup, objectToBase64, } from '../qdn/encryption/group-encryption.ts'; -import { publishData } from '../qdn/publish/pubish.ts'; +import { publishData } from '../qdn/publish/publish.ts'; import { getData } from '../utils/chromeStorage.ts'; import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; diff --git a/src/qdn/publish/pubish.ts b/src/qdn/publish/publish.ts similarity index 99% rename from src/qdn/publish/pubish.ts rename to src/qdn/publish/publish.ts index d49791b..f1f75ad 100644 --- a/src/qdn/publish/pubish.ts +++ b/src/qdn/publish/publish.ts @@ -135,7 +135,7 @@ export const publishData = async ({ keyPair ) => { if (!arbitraryBytesBase58) { - throw new Error('ArbitraryBytesBase58 not defined'); + throw new Error('ArbitraryBytesBase58 not defined'); // TODO translate } if (!keyPair) { diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 15576aa..7957dc4 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -74,7 +74,7 @@ import { uint8ArrayStartsWith, uint8ArrayToBase64, } from '../qdn/encryption/group-encryption'; -import { publishData } from '../qdn/publish/pubish'; +import { publishData } from '../qdn/publish/publish.ts'; import { getPermission, isRunningGateway, From c151a4d2249b73ac1abe6e32a349d10f89a76ef8 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 11:59:25 +0200 Subject: [PATCH 11/31] Delete audio and image files (unused) --- public/appsBg.svg | 9 --------- public/msg-not1.wav | Bin 27712 -> 0 bytes public/vite.svg | 1 - 3 files changed, 10 deletions(-) delete mode 100644 public/appsBg.svg delete mode 100644 public/msg-not1.wav delete mode 100644 public/vite.svg diff --git a/public/appsBg.svg b/public/appsBg.svg deleted file mode 100644 index 9775d89..0000000 --- a/public/appsBg.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/public/msg-not1.wav b/public/msg-not1.wav deleted file mode 100644 index 475c2100cf3ab731bb890a9f83d583cbf33d3a02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27712 zcmZU*2V7Iv`#*lO2O(ka4GJotAc_d?RTS%1+={rW)=@_t-Hy6vZQZNIz4x9dLu4Gh$D2ifH zY-;#5in6-Rrnr<96+eIN{HkRXZ>XU^Xy;h9JX1&3?_vw$p7tD7GlNG<9kERQsv+c;)fRgwljk$rs5N zP9eAOT;7?yklfz6_jB&%bj$6YtISj7wH7K0dzXZje6B34e9-Wq;j!|uvRYrGm-Cu= z@2uWiN!=uFTl|0YZw_q=EgVodz~(EPuLe&TG@(4UGFCWOFt`7L{tH^-o8!;MUx=sY zY3Jp~7RSA*H#EO|KY#mf_Bq^4?xMD(Z9}RCR-Mi{pX2b}=6&&_@<-#Y zkG{V5!r=@06YVE%AANB2!{NlkGY(Hbyzq}De)!Okiips=9$$VHJuzZ&$_l1a-Zy`A-XmMmTtZ@1KO z>6c{%%Yv5oT<-Rb`!}fzKP(KK8!~sz)Rj|P#wfi1AS8qlp^i8;T`tj-M$EF+`c68*?U;p^&57D0% ze>NR&IxagWJ=gDY#N{D(2jA^@(eXn1QT8$7bHr!Adf)mLniZN{L7u?I(aP~wm%Ck- z^!pTb~t7gh)uAaYc{`>g1@l6X`7Usw2#Gjaddj6PM(X)6{_){9j zG>!=!(rbuQALl;x{&oJP&gIUdEut;f8P*xzHotA&Ub?;1BFiFcf8xQ!aZg4+`Q_G* zTbnMeyENi_^m+HwZl}FYx}O|=a@5IPr+1z(5gLI4Zl zl~I+(R(-38C1lxIIa+0SqclB`4s!fp~${yciqmq1szK}mUH8|{x&^rHhF&M`K3pBkAdNl;r)h0 z4S5m$D0nO=h(zC z@5fC3YSLFDh7BL~Eb2wn=dgmXydH%;D!fa)dpm|Z+DmLC52+{Avi7Czvm53#l$Dm1 zdgXcLO;4Mi_BioT;)<86ULJjN=*h^3qaU8Sf9(E|`+wdKec1EiH&0eR>Gsn1<+Q|^ ziOC<|f1H{do10UTUBas8)ZbEFR~@F0&_9ZP7Im}hY8UG~`MeLvKCq}9kbqu!1xf;MgB)RCixMGt#8@czIL{gV0}3po}tt=qJ2pb%QEUS>mR7@s&b5lMuR{rXtr*$Ugo;O zHQH~a-`K$Mf#Z9R@7*J!Tg0OQPX^Qut{!}5=-Ht~(9RAyJEVSK-N2&$CH;Tuv#F0h zmdq3*kwO>HL36T>b0|#^;aCN}70hv*m zQD^&~=^qpx623QdM`%`HL10kVu3hhXJoY&2aMHnE>LeY;8^&8?h&P;7ol^xj1~>Mp z>|I$=R9h66J1aLeJtKWg>Q|{PALJiW5>pau-qpSvmpC?2^}gf%=j4LqnzZUPDw~__ zQRq}imvYO_)E=+BD}N~Oq3Nc1L%pV^ieg2#t#4TW?R?Q$10s;*5zO_6PV+I}@`GgNV_xB-@3EdA`e z+h1}!@3znPknfdlm%5b)mj?@bi+Vfub?mDQSBCEg|85Oeh1>L%_N7C)z0-qJg71w zq{-6gWNos0l6TVX4?7X+Nr5RrDO1uWr~RGzPbQsb%o|ZWvUq&exGHs{w$Y|d*0$L2 zjUkhp&b70&x0KjR?0PA*q%F52XY3&a`hOm?E~weE|uTsfw3RAb+&epM;O z$;G|}eg&(tS7tv*f0*u;=8|?c^+M{j)JdsfANzkC`YH0$tjxH~-MKq*PkuiAd0W}m zGW%MG+TJZ8EtlJ`wZ|H#8ihOo?}+42Nv>^{?QxeAE-$=ZdHHws?Ru&E_3i_MBZ43I zdfuyhXxGr?p{qhqg!~n9x#!iM*8{Hw-VL}HkmsA@+sh-wqsFn$(b?MGS}tl69c3S6 z_ti(}J5(JiK{Ky;cg?<$t)*|RfeWU@1O8Sm2HrXS2Wkg+yv zUDo8>$+_JN0}8W>^NNR84z7%@A5)(uPm}-MalYfS@tW~I_b&H@#R-ePHhpd4oEA7S z?nd_tpK2eDKhw2e_sH&>12+Y}33?r*4blho3F;lRsK=5XR|BpD@Vaoi?Dsn2wcK@) zYl8hX`wg=1WDUYbVHmp)dy9UP{!3eN+wUzqTTa#;uj^YGQTbcRPbE7(@BX|he?@*& z?x0*=4l5@yJ0*Kh&W@a@yuo>m1q}sDzbyXppzKN6gX%lggBpi5j%Xdyx<`FLEv5zZ zHtr5?mAG1Lw9;AKaJcPI@NN;^;=0f6ezg0s?j_xdy7dYO z3%JihgVR&+W(Lr{(Sqjt#ce zF4cF+u9q3VFkhTMJAalGhzlC>n)7bvUC)clkISbD*@c^nHWa-pNiIpPcwe!hc1!K` zrprw+%CX9DO+U?7^hkO(cP=+p94nStSz5*0FR_nu33plJvEGC7F?c`od*gSj>*cNw z{Ga<*`j_~3^Y8AzyUV^VKECe0b)HS0TsMx}Hpi`wKiK?i<8J9}xl*u3aF%t3RiH1> zdv<79B(|ei-yrQbA%DLXLKBGCKxm;PHjMPME?in8%`*FfK=Y?m4pDeR1Z`<6kad-4|6uXLD zXL!VVXub4aHGlyNKMTLb&~&~!-z1-8pB%3&ubv*E9?~6h{^#xJ@r zxy1#=t4hBueN_Iq{BqUhs?fU7y8cc5o31OaDb}>FZC|Hbqub5wXRdQEa}z|DMQf#N zq_sA+HmwdShhJQNa#`uV%01CD(Q}^nJnv|qXrFIxYXV>Qa^qQ!NKB>r3edsZ^*vEiX1QT;*9C>iDUmaQ$T8rC#?t|_cZugs`i zf-$x9TIto2>m`dz;!6rkzLdzyq-BcomhvB~eyjSY_Dt>g#>tH<;>#Tf-pgmMX^PcRg~2LyZ&|`98(=rToPS^+(X?J9!(xPPow8_uUIdwr^d6v zqtTY48{bXfw%cW=%W|icPMP+f>~GuLwTY00%jR0dSKoMyU?exQ4* zyU}r}BU|}d87rSC4{Qo)+F8G~eroNE+LzVOs_#`jtctE0RkfvRbJd9IQPo#!F4z23 z_g9@oqoh&Z+|nH08s3`TmeF=elb|_o_{VUAx=P*SKIC2ioKBHUm!!&)Wo0&HHhb;& z+P`vq?YPu=vGX>U-(4(Rg|2EBjmt=vQ7$59fpag%P{(q+GCQe_%;t-tx9qL5Y2i4b#7)AHy9?hE@wloZ?8&G$!=5Wor>hG#ss#>Z9 z)uQUB)sL&WwY=K$y7Ib#jRP7TTO3;^w@z!VZL4T|sClM&Xt-l|OMRfyxT)Ny!so(` zk`0m;S&NKsE4005f7`y=vEFgF^A6{8E*D&!T%BDR7lTWb%U~CkQ;U-Xez#n;yJYv# zI^BAQbemKqZWFr*oCOdDI3h;O&^lTtQ%lv8RFhR@@=|$HQ%aM6gI|NXR$n`}W=74p z>PgiVRi#xH)za$h>Wu2(+R$3BdYAfbjk_Dyw5({k)q1yeV*7-453Q%x%jjYBWp!nB z=XK@rMIzB7$peX#m5Y^^t-I|5`^WYfj%kkboa3G6yUcP)c1d$N=5o{}!#T@&z0-Q9 zLkv{fR_9lqK=q_1X2FP2NoKL_{`wiDk=qfyKan9ni^oumfI>|cPZlv8rhp7$~jwOzzPL)ov&QqPE zoFkq0I~{VG={U{tr2TpOm9|T4ldRIL(kxRge-!^DrujzxM9w76Ipz#gtFPBj*Tia? z+M3$ZTQgee7JbW6_>p;`;dFyfeL#I!U2xs&+Qi!G+REB}b^Gg{*T1M=(iq>kt$A0o zR3TE-C>xbl9hMz)wez$^h9bi(Y6g|VDd6-M^c5@=FA>kOoMEYvX=Q_L2H9BI+1PEg zUuQqrVYD^-S?nalGYNOLkLM(=UzN8m~4aG>mMBZdl&1tbyOiYjkh& zYP#H<(0p8eQm$x~w;pUe(DuE0v-*hcpze(^(I{r|Sy9|U+y#O-!36OHvDnhWa=Gjq z*(U2P*3Pz$wqxwZ+Oh2^`$6`D?c41->>?p<(KfUV+B;e{N>(f>m2?*eh$9410vM#Y zEmSM@-1yjdME9reu==3-O{*_!?O1N!OoOu8ql2dj;v;Uw_S^Sg-rMc-Mh zwb*F6!7@zNM|Q~Spw&U^!`8tzJ#1#%%(n@$39#8|z0dlf)gG&!vM||p%U>;bTI{kI zCz>E?;WzMS!d&46>nY2Dabh+aHX1bA4((C(arH|04f=~}hpM8rr1gm6sN#(Ll$kbFUba1No7FM9BSaIT>80zb8*BL5@IAeOzCqoj zirHV-Dz1vF;J5N$Kn$HJo+-X>@z5e$k}f%6dD_xl>L!hrj+XjCyK8yJQZ8wcR9e(n z9EVjrYmvQZo?w>XXWlQo-4Ne@Vg1DVj#!jR{z>7fnO&e6@%UDw~zTN^EnKhVF> zDn`M?vEo_D?DuRBt~YlIZ!&KUtZl{#;sv3?Uc!&UG+~6Ozi72+m1qOBXlS*TL;X{Iks;q8 zp=I<)W(2d9`jxuNdc>+@SFn{FHAl^D=VtS=czgJJ`CbAa!A`+eL89QJpg@o*cp`Wy zSO;@rj({&%!C%V1#=Ffch@^&YkO$@waprZCSQ}QY1A}p{I!1C?b=;hYn`R;mhP@@Bg_xJ27E>t z2O1;k2zmfBkQqh|qoP@(ScBQa*&&=jj(}^){m4z^uHdcZwe#e#oH(6-l>Y~4?gsxX z{~Tz0HQy218Qxi*h$rMtokt7x53YjiTY8pj&O z7>?+V>NUD{-FnciGjLp>E!4iyzSO?gzSSzUN^O)bQg=yrS=UP+qR-do>h~EA7-k!1 z7(?hjv@>JRXc?NRq)Mo#te32V?0xL#+sEO^d551x!C z;Z?#K#Yyfd?hqKuk~qnnL7ah{^X$LbdcfyM)>o`U)B!4o$z?pDEu)vwAB^vfJ&Zxd zzkzn3p_d^?pP~O)@V#M*VTNHM;BdNOwqY}TzHYc_Xfr4cql}}Cw~g0XCP0d!bKe5vQCHvW5L2G5n zS%+AEumV}#Sg)wJFd|Q+YMFXwGxI$l%b9sZ->2i~g|riGM;E}4%uB|L#;wp+8dn*= zF)lH#H-2y2ZQNvrXSFcX$wZg#4`(-XV4re2kKjDC9ICUqGT*9 z))dxc)_&FjRwDRC9W()3z_x|v1kDnfk)>zl0ZQ(%Zm>49egiKY!s4<7tarfU7HTuq zmx`d|OcQg3xy~$SmN0=#FUCM?=_ERZzCvH3chEcOjr8~QxAY2nDZQLtPk%@MN^hf2 z(T`7EEY{wQ*~4}l}_ED?oxkJhd|cvsBzS2sv8wZ@u5{Q70f&419O>4 zVD>Txm`$MHMbKt2GnsMF#xgO?C?=Yj!b}8<}krghM^GTWI`VCNgK$&d>7e`ZRV zHl~%~L7%8of1nUfg;4{jA=FpYa4H6JyN*C(!>`JI76;+0-%OxJ5W`?6ZKhVde$&iObyrr zQB%QG!r3~oNflht1h0*7HIDblDQd?6yrI@azz@G#|>(WPS3k z3*`%u!-MjKr`^9z1hVm=7qlbV6Q)Z*3wf#s)D?r(D*+QZfPx~xL$2w4E?}bo-hDBl zqQt~^18}YY4eEeDJ=jP9y^5jPKy!fR4$a5JPZufxcL+6~HB8v>3R_hgJyh3*m^UEd%~a;i_u5 zt_JRC1gx8_OaqUI9>k5*WGP#)pp8k3-k?c0;KLhu_5uz1ncn-rdoR!&dXgKU*%hw0 z2ff>xd`bj;pp6lgh+jQ$+X~d$L8Hy4CI^kyns}%IJ{p0ea(JR7X5P>j%B0$esz0Z%Q!FCw)ObXf&YMHB{bAA9*L$A2h|{x z!Xz`1zyQ5zfqS8;cOlS~!PCyflLzP$wcG`s-hhJuz=$v4qpL~pT}_BUKKEV^Qfl;mquBm`4t3j?- zxK{-n(!eJV#sUdw6aCv2=(&TvF@gpFT6_7o(TINK3)F+an^1#2fg^n14ScExyn4g;7!6#ZAr3Hl5qonfjHen? zG$K6Km?+kOcF?Y6;8)m+;ECQ;1h18F1wN54YzmMO5!yKxU}VRr{(c#A8< z9vlZyudqyS@*o?NBw~m|79bZFFoM|~^uK7wh^z$J(eII3vk8SQCPX&Cr&f5^4Ciqy zgC$fW9yqp}!zs=r0&CiN8qxe-FTO$b}^|0dRn$B#tL+h&?Q@ zB%+ug8KuQhLtLmW+orbfm!SRyugCC9`KdOt=O)Vl^KAyxAc{Q<`?@)={X4jNh+qcTQ396xbH zLr*6tK-#Dyv55=hMP%X&Kcf~fGN2YQx>3LZ13h8G7=fOR4QV26vsRSwSq+*XXFGr& z998iSBNIj@Vl~7D>IARACt8FH?o)} z7_=I4g!duVOrn8?5gBNCq^vNZqzR%7;ud2+Vird+GMB<{(8m$ys7J&v;us$!CUZ>E z3xFDl+1B7WID%tr#~BySKy1LHTuol$1IgBwF zMesdJL1wae2ImSG3-ErF0I4C5C=1T6P-pnWaaCaAjPQ+|<8?S|!l)<)o(bAf0`w$| z4TNLt87bl{UJn#0ig1iGX|w=xNw`CwMhlQnXeE>n^-Uxuza3D^*eCXcoZ&c)`bNED zHX&*hqa8*`)FpC``b4ebuK^OZh>XZBY5?_&52T6uz^G!@G0`-6HNbhIbCih4f%-*F z;z)zq!`Uv5TsR6MuY@+4TXt#``5|`3Bhp7n%y*(hs5N4dPWz)4kuuunzql};Cz`=4 zFqRoiN0f)49%&%As5O)k`@uWSb|Uk{8IpC!GJu^%a+oiQd5 zPVo+Wc5-c&<9}sEpCL9ee{ZIPxJ9>Z)?nOjl%p%AqJs=toGl)h+GFlz+ zgZk{`8YM(d2nU2FB8kL%@)^AfF^N5rxe-c*GkqNOD3ex@GU1kBkI*2xG2_WBC*p%3 zfZ(5Kq!Yc^6XFB=Lv0h@5#dBfa0)m^DT!8y){!omwV~7~0iqCfVx~*hn6BooQZmL_z` zInqD*hRB8S2_rm8jyl1f5px)qFftSE;k9UC;?vj*u{!DnbxNd18rTc5xfvzIf<)Iu z8syjP;Yf#gy4m{3FH%Cy61|XUgcik~Q9Gzba}WQE8^R%>Nv^{?%+ezlC?$S_ybymT z82aybW*eD%`>zbBJ?s%*39rZta*BFD-U)_CJSC%RC))5n{6zGJdL-6BJQ1mp8|()? zo_Icy)l8GB;uK+H6zdLu^4Y~bi&wa zMicfzDF3f)|7#V3bQ04#5kPzcsi806Jch)2qH82t(h~_ zxmhP>jFMjeuP>m633dr@p!9!r*r{_O5BbK-8=*=tOMIQ!i*S$kB8O(bCy|~+IkVRA zh!3-e{&zgXagBH)evguncx1Md`G|DP9CaehJi?KPNP5E2iTL~f%13;Y;Ge`ceMoM-^Azz4Acp-uWH7Bt5zl+bK5yxweObr8B+-K4lEh=8M|0f3yNS*IZ)`@M zu{RR8$W@(k<7a|nvIdGCPi7uwwk!5Q|4JY)YT4bk_1uQ8*+Y#H+v$Rim6P&%_^h+iSTF#ee5 zCDy+P5p}2s65|QB@OtxHj9{Di zVy8w)WH8&2Xv-|S*&C4Zf30u+WrQ?{?8JwOhm+BQ;0GhWc^oreO(-HITn|9Bp{`Ij z_=?y>gb;d&cKmILyJWbU)&{E$xTb~c54bLYYbW??p8TG}6+I5DB9JvjM_B71>&Li) zjO(4aQ-JFpWZe~i(W7>7-2nT-wG3QG#WiePZNZfxTp_~!AzT5$U$m$ZT(iSp%(!-e z`)5)U74l91s{&{*{2hU-V7Qjl2z}tnJy|Ek)f=(~fnJNN3HGMd87Ejf#=Q<-(~7Yl ztYZhjTCxXxc8Ap^+^a)P;a&!=|7I}R%p2w<^MHBGT!o$3OUy;s-@MA)g5ApJ%nRlt zv?^%$I~-Slf?)-EFziQ8qQ+D6pq6JL6;CaJHk+DDO@*_O)Iihz53aePP77h}8~02f zGWVEk(Cba;{Tg!z`hLtLGVkI0B9opFbGUZv1#8Q=dk_kgaBUvf=)1sry*Hf2l`&j- zvH~e^tsGaOaUHxD6#_g(!M^DPY7XS~%?8fq0Y4Lg$0+y)_s>x4A3)MwkiUStv=Z1i z&Y?5ubM)V^$Go1NP0ypJ&=csn^a9vp{+WIX4RbkH!M<`G*l#_xhGMZetRt+$EPu8? z8}k>$93kgx&N$9W&MMA#@Epz=#Zj_b*=yLV*&kUMEHPZ~5Bv{iBA94;9OShuHlBfu z3XQ%)zYcO$d?3$4uQTc#^>+H%kdaZNuh1VeoHYDu{0Z`bexmpYxycSMa~(=R;;l8@Gd-&B@|yVsBtKP;zPl>`32$ zi~~1=i{YZ~qHex6PCHvOTk~A~Or56wq&}wkQ&XpH(msPs3IQ#lA5-_JA348phC=Sx z4dFH6F7aORDaej`YVq76N&H@XQ+Qi=n0Ju3p7lNJCge2yrv61eL^(`3qj_4hC)D-v zAWw8`)%dCT(^D608Lgv}biPf>y0zFHA+xnq(nSYW0>&V2& z;W0yE%3>>GmGfHX*~Ht#2gU`(Et$D!rpI`%@wJ0%2k#8t5nS$E>CECX91-L?`WAL6 z9Q!`{{qGO%N;U9;oJz{%Q4X7M2Y3$^&t+6e!9`k(W9g5o@=P}Q7URi8; zY|pWQWB(a&X@Izgs7H)LjKd=E){PCD8~(~Yn)~rh+8gy9?VXl{`h>_cL(YskIpk#U z>E5Sn|E~O7bW3u}=cVV%nVEAk>#J+3U+7=!bF4G1?fsqnSN31tUp-nk`t!8HY3t{$ zn>RatPW<8pix(W8eQ0*SN&P1!5C1S6Qeu0*@p$856w<;E%J<5!;y%SQlBXw!J_>oX z^U9tpHs@sLB2Gp@4d&33-OhD8cjL;9D>EO>ezYrjNAkqtsl^$}RAq^asI^3vo_7*#Bt-ULi=vcnmK!??3=Rwt94&xg=dAY^IPvXPc~QfhxSix z->TkKgEEF>B))k2V*jm!x0WU>O?Ywk`Pt$#C1-LjWL=nlE&iI@W0%KMlBXnxe+mDR zu1Hsi`4;>lmm-&t;9kLyP&?>e%)OW;Qv_$XU`!YcO?X9k#rlrj9g7$I z7ZGVuX_^vs$wcKukF^fT&%wMAmL#` z$7S{9?{2TTo%Jm1*)OR-r6zs(_$6JQD#!fCuN_A_rgzKgHm-k6|LRcJA7d@`N z(RgForNx&>4&9UDD0dTQ#p)Nyl1%p0*Yd`GyttETI2yWMtOSY24}n%_0g zE1FmIIpuQ-=Q;cNuXncIDZ5&Hb?xO1m#1Hyc6H0`A8&7c`peU{Bz2Nkp+{kGW6#Dg z$fFx&J=}VZZ=CPt-U+=UhYT3<?lYsy zj4s8t#kM~|-f5`3mprRDw|Ls8sh^g;UiSL!!*>tg+_G{$|5dqnrB?y4|(SZP#M)cqm1 zhXf4^9yWByupw4amQj_x>v{)w3+T4aZL`}d=`!gX7-a{w4s6|9xu_|*E?U2cs1%(?>D{P^!d>513#UYJ`f7pCs$9azNWmS{EIqH&6mbW7r4%MZRx7$ z`hBnUy>^E03QvwqiWChH59l8m5&2W!U;0XVih7RiGQP`m=cmp}iAu5yM%ez!2<3(9 z3)PE1fAiTUQsvokhwpz>2vevt~Fh14l9o+Z^F#r zwdA!V*g42~qTgh{i$VVc-3_}N<`Ur^ai{-{{!=4nM0D@dwa>KR8Nma)M0JUA8S7#r zwU%~+yzV}2z1xawOKYEhdGcj`_JZulkC7j5e7OFhHnA?z;)D1@drC*jj?5jIb3f1h zY*j6*wrRC%O@jRTBjUfrxei$ljowY(6MIbVu`hII=&-R1!F>G7# zufd$Ig0BC#o_B4PDP`YtHgd{3iaVY(yll8rdcAaL-iW-0Pj#Q@WGdy&hu0s*B#lYh zo4Pl3S>`vHI|_Fe?yB5bS>ICM;;MJoZx#G3_|0~Q?H-TcJ$M1)fZaW}_q-Z*BP=4k zU${lMIJ`$#P}q~;N5MjWk^dt1cy}DEHuKl=@;xrw{cZQFXuGJ*s4#9-{;o`~O{<+) zGO=WP-p;(VjHHYOX-m?6{rJ;I&re>TAmnCkFW6S_zAUM1d&Aa-nD(*lhpEHVCyO+T zMGlJ{Dm^Pb-}t}wzZ`TWC_3b;kWHbRL%W3pge(kN9JI`TiT^s!4W3$it^Fd4c#Gjw zH09Us-oC%#XoGFJWw|$uzrNW%*-O$Fr#pUf{xmi{CS8@K%KBVTRPeIwSy@m+uZ9

g&=ve&U*g@HwZ`Caq6e)cHz7-K)q z{)+e?@%PLYW_;V&wg>eO>j#w$DVtp|qu{5kU$g2zRewrP%S!8?9+B>w?Uy~eaCG5n zsLpb2bZsnZ|J?2azB^AcQ=*5|n;*P3dBp}y3kV7B6Z|6NNk~#?YUr4d(IFFp#s%5I zI9Uwz9PM4~2Z^J^MG!-OSN*1XSC?EDS{hUulpm5Gnb|LMU)rIxB_HEHrlsYi-OId@ zS)bpW?^Nbk)>vO(p9b|*F_3%s!6M1xj>BDtonG6$UIn}jFb3;_8$;!x)50c&_3Rzm z``4a7_uSI$hi>0^FZI6dc+W9ik|G%Zd6o(7f46T1yR0tzuBi(qb)2_FJZU(Ii zT@(5+>|xkSXh%c-2$>!@J+RqN?)TjFiEFTRsC5P}mDg1ls7q@}ZW#r2V(vv=Menj- zW^ezr@6-F#7pa?I97;<|P1~KdJ8M(nmcnrrF%`cz?P_|iey;wSGnR8-dS4pnyukUR zPpZ$>?pwQe=@rmx1^g#ONLb&nWuc2hdj^LD-}S%iKh`tGbH06?{deN;#6qZFx~96K zQr5NC%`cr@x+H&j{`$;ynV-@=rXBhC=f{S$+O$iVS2M5XU&=pIdcM@J-n;&>>Y1vX zE}^AjYw-=cYjz_%MtMx`Ig;%pzony1( zC(9&D4$Sp?X@WGbo8L5#t{Pnx@umNl*4&od=b5iF^V4(Ehh+@U5M&Fp)%orD=_Q#Z zFs-c3Q{*a~^)C9oygj@GS%R#^N$#ZdQhJT?AMJmq$K4)^AZ+rW8$E9InB+gf-_G04 zd!+MM&e2vQtqws}`Z2>Fh7zc6F09Y5*OY6^&lR05a?N+mKaq1PXK>EI9CjWjub?o$ za6{>a(o0a2$CY#Bo*FOB9>^m8DE=s3ZM)i505x_)y@zyp@Ialpa=UN?TXlm4gt zzxEsB*UvM`^P1CTC$*K@YMXF}@Hz8@aZo#}TQukE;ME;9gE#AXChk9DMTDk7E-)Aq931pdq9Km?Vam~{dXmVS#TaPpzZCqRPUCrMW|5Pk2 zTTu42^m*yCvZrO^E5}!ks~ul^plN^8bL9)=9_?=JIjCtkEjS_Ym-dj#Z5wRQI{od$ zcjLNg+%@i#Jf?UAxcj+(=d!^C=hzX}gRJ{m^tQkp`2u5}F$C%_q^*{%kD$6~Q|;Q? zU#oUh2`Zrwp^{&Dr0Q_hueHC{zJ(enajUr1QSGQcVLWM!fV}oo;#1;PR;!>kINm*Qc&2ZmDjuZd2VBxh!#UaI|yWXLHcT%F@!Zm|w=91lhZfw9mAgR2x)T&DqWV z_1)^vRiCWhQ2AqJW<^THKb4m&b=BJHi}eZhKehbaGOF#Xw(Ghpx_H(SmZeZ6oGP6z zt+B1L{oZM#Q@U%q>qGYk?(QC59vZ0YoaQpaWtzhjhal^o*6tQ=7Hha`xI3X1;d*p)&evnI3*arB!{Z64vwUm$ZD-^0Mt|+e!U-eJVSV zog?}zT5h$-D$-$~gWN^#66-O|Bg$)#*8#6RUXh*yJ(svGa&vHUayoBw&L&V2B=O;S z^Dsw$TgQ%$d-6N-i}jc5Z&lr@5|<0gD@tlgW|d4USzo%PG^oPAVlvb_zHNNb*jw3G zIY~Q7n?|Kj1BF9{lVuZRSM0Ca8(oYpj0fYf&3n7|a;O8o>V4IOl_{>%T|d6FNc8_V?P&+Fr^Y%3m6HG!|9oRWB=FQT`*;rw0{xFWz0eulR83!P3ym(8`{5 zA$4H&;dILVoO_E`lh+BK6Fz5r&ibglTD@+% z-*(qIX`HNVZEOcfq9l3TTyBIh()gnNX?sRXPRrA}hjkw+Qz{3RMU@SL8twYx`r`hj z5vAT0eifT)Hq?llESh#JcPnpbZ)h>Ue4%imaD;4_Y`Xm{`}r<&U2HsT;pgT!FBLR{ zr_pn>`)2oj&ikB~*e$jjFC8b{CpaM3NPSN&(=OLOS3XrfZFjX{tM_FH|K|-Ke@+#iGJ>lUkuxjDWhj4!uU7!AfIo5^NFNm)w(# zupVJu3-ysdIc|2WaH@5(auz!qKnZW{U)ld+^Q+B#>3r!)(Mb{F)Rl2%8ng{s+jiUb z5=DvPTd3Wm8;lJn>(A8Bt)Epts9{J$NK>ySce%U#qw=HDPu)eGuK%RJ3N^)xcnf(c zqBPMZ%fBsOTP0d)Y}#$Y?ZWNi?H1YXwcTU;*!r-OmOb?okNRk=f*>paDlmLFPnHvbN7ck@oD8=R|%Q`o6&RC7D#bp%0;+W@E?ie=AY zkK@PiOGRa(21%o2AjGzNR*$SESx>MwS{ba~$ll5BTHdm(6E}$ugYEL5o)mLsDFfFq zMKfL_ZMSbiGXODk9{tVCWC zZ!*+RE|M&gWJoimKUi(H8VbK>0<8V5y{&w#hDwJ@&sdzcm@b?lJPonz1a*SiZP;x% zr#Y{Yv|F_Elsx54`E7ZxmcW+%&4-)i&CSgr@(?*Jd9;pci)kxW7pqJ3WqN;D4_wJz z!QCU=Cmb#rDp?>~C`+_{XFc3@xNW-aC);VZGi;l!>#g_8j>uL?zLA^~o)^yJ&f=Cp z?&<{nI6b9isZm$S@)Y^d=3&hj8~jo_R01;?6=zwv>Rw=ur^rdz<9U``bYIo>z_3|X-I7P(DX$9SiTu* z0}Is!>Z69g47*u7S@HbEe1ljk7Rp4jc~H|?YFBJG8Di!ehj$Kk4pt7+>}J{}SYNQV zmfA?ah>Ar%JTG1ltT~42L-lJr)^-Fa15E!f@BnIHtLw|^o$5X6*VHeo&uGYR7}`9b z`FF)G#qqY|ZQZrqwAN5pW6hCp$_2H8>5_?(V5>e>gKeX1ajf{!;U|Y3(0;W4!G5Xj za@*Bb%dIX-u1Y2d#|!`99O2xjAJWy@3hnatZ`)V4u4;{F32(`6%xzo=Rl>3cTlkgn zv|&Zl>L!_7B44BYPPwJyhYp3VMfWFjj2Xloz)cWd5*~y9E0N0-vUN~b>tyF__tNee zc*Hk$?zT?0rB+o|)s~f(L&QVGJ@`HN1+0A5cgD5G0onoDCGFp|dnjF$zsk4EUpGH* z{?b(1RM%A7RNh?Hd`W&)K1?}SxuShlyQ|hwD=>>@ph?mv>A4J-8NeRE{+buVlL>8v z3&iuqJz)HawVZ013IBg_9a@?sO)}qNzD2w!UIe8@f*9^t?rPRb)*gC4{YjsqAFcgb z+e6(=y|sO7`@*)xZB?o=l}2SyU2aQgt7)%mKdwHeexiM%buc&@&e3OR{5^S`bDWdS z%i!G*+!1sWbrHFU-Nn1byG;K(mm*3Q{VhxodJ5bG{dkc)PmVjMnyR5L(h2k)!#=~` zy7M|4t*y35U8MF^d#ejNiaH$CwlLFuqy7Swi!r+KIjP^ItX5ZZ>$qNg z4?fLj_}k$B70wIJ38o0939j%j@z3(k@y3DiIwfbLlzv$w$ z3$>?U-ME*gFZ^>#nC1k~U7}s0-3Il89k8x_(0Ir=otegXv;0`qP#e01`vbR}SHaWr zJNWknj|DbDsgNra2=@y12p;pF@b~d{^BlRZ+|}$A>~m22oI_{OBB)jVR=-@&*70;r zng&f*O@Jl^{_`VCovR+K8KAL-|E7x6^)uDZPBBh0!t{YrQEk)<_A_=I{0~|I)JNj~ z2c!uy1cRV{cbIULuu)JafS>UKOTHEV3O9i(;mA1APd&nwOe4nm16*X`#2$pD-LXMAI>}6!vT2uW(qKIh#3Ju9h3l z59j*{`~;6c#uh;%RI|Sl^b+(G#PG-R9eEDC%bY75E2xQ^1A8Vn=xemhC^a6{AJfm) z&Co5-#%n)lK4`XRHiKr5X?W0KIpj)Ktrw`W;Hw-guHvVY*1T}a8u#@v6tdBisKW3+MaybvUx45yq znY_o)GI*bOr+MdjT_A3*;cnpm&iNJS{m71H4P#-3a3x(%CmWNENrqGdt=H*i>gT|! zJfo}D)#x~So_?u*q28!x8D1Nn8Lq+}#WngO{S@}0-a~caYp5tZ$3DZJ$(hB0zhmQI z-RJ`DLO|tME}zTgZi9Nnp3N@B$N&`DgvD9tmKI6hTFnj2IP*>?hzl1u_-;CRh zOJR=(|5N6q@szRNSYsSPe+AX3Pw9coU?z!4X7G1GI+a5WXANSVXC=VD8P&3!*v=q{ zAKM*{%`6q`KIf8s+k8fCGh+8FZvJoM}xWaV0r)@M)##h zfPXKim%xhTKXfZCr$;cunG3Lwhr6qDsm0V|>Nf0M@mT{{k*vk6g{+NW)8AM>u$Hq{ zv9LDU2`VCUr~;^!+y!-pgJ9pTnK{FpgSy7)P?77!G}BFVI-La-ug~dM^jkU`d{jZJ zXb;96YGEfs)%GD%Z6WTHPJ&&)z0^*qx_wDiQRSd510{rgFbe3a;r}NJs4q~B`HD)KWH_Se0xQkc-JBPRr zj1_SssZronbEtJNf8GtW_k$#dU@dA7eEJRQ)K|ma-E8=NIMi1Lz!>ic`-e){Rjq>R zmt-adDqEjJ1?pAP&SwJiH|&94hSlr4@N4)X*enewWy9>J26kW-uy;;@=UbWTU9bwL zH|#$4g4g~~aT*R4t-Ya+s5k6hV%?4>>{??dMvVEMoGI7P6I zg%xIjdcuI2Y~yaPS88n3*p|V7wqr)n|3;V;G7$L z;{tMGO&FVi4AlWx!-Z8yPVkD=pjeNF`_NXfLu(D1!b(!y$wt{3*yF{_ZmiYBY=5k^ z!5Rvb4{Pdh&m7-lRV3DQ;J!CjPGH_JW+dQlKFK@Bd~U4l!JTt_#VP=d$e8gj`IjPQ z`eOzFR-<4J2WB*21_5RbU|ud(a$)WS))!&EKh}0&-Zoa7U`3`9K9Rk5Qk#R>1DM4@ zst_o?okAd}qXm_k9vx9H3{>c)wfI06Z z=NM~tuyzJ(XfQhgD^qKsnQO%mcUZfK`3qP}i?t6V&jV{_`9KT%#`;Pp6aGlWpBLcW z9Z-Nz7x>;Du9KQLM9X3A1Xk)`%@@)`q?JJ{H&q^*u~%k79#*Yjz5^l;bB8b+gVgC_ zHJ&rjM1*^pD&btf|GSvZVwI;IP_zZQ79cC?nE_j3Ef-c{wm|(0smVpZ!I>yWxuQrUuf#Ohm8 z3yhkr_?MSjlRgmXn0bZ%g{VP96KkR!(aY_CwhJ81xesVSImk*k0sZUoHV3} zn8$vwA@At9=%1MVk5vWe*(5Vj4|LEHSdWHvf9Uy`LnMS(j3tODk|9QVLrF*_48{P= zkHXL9*kI1dLJB0O2z?lR9yLPhkIdC|<`{sQ!mE*Q%xl7|Z<1L=YKbteVKp6zgjn5z zRp2NOMjpHxbNDe@k*I}opmxyaXeUxbM=(HWV?7vp8Rl}aK~A&`Y5?gVHM9)o5VZkS zw3eAR!2(7$L=oPLx5+;lq>33)IC_y9 zHI$8LnV<-5MWQ09%EwwRq>D0RJSVuo(F-%ONCOtZ+N5D#5~&fzx-7&Wp@ni{gg2Nz zqfPM>Ms1R}X2v3^y+Ycgo($iSI8EyR(2A&89KX!jd1TZ;{!wDI6h=4f8)={>5j_~I z5y$2%UhDx;L^9;?8|)FWglNQ1_&v@q2s+6p@|k4Cp%nNTDI&7)E*uHaGtdgC74!j= zkz{0&99SG>aO}b-)`uXkSfPSgBl&K`1OA&0h|(keP~)fpq8%LHuu2Wlfg><_9eNhl z9pl|Z3eqPlBKiHPf@NTLhs7qyP`kWYdZCR)fV+RuzPl!Z{hBle9{No+>{06U?Kn zi0$wlxeI-vlY25QA|=!iQbI4l-bwr+N5ss3{mG06v%RrT64x>QkZ%!vh<3ySK8R#Q z`^YPaapsth=toOntVO*bAE+gBrYutLvtZo7k$+YpRg*}Y zco*?Af_5S~>H@WhI3#*ReG<&$8T0`3aDqXz2Jm{6lUNjWhwl;9s9B7ZVAu6E)OqncTp1eIuIj7TK& znHyr7ixMD@=InLiX`PxvZy>rh$8&-d^a;Wv=^OL4(c2J%h%b}?dHmne0lND4 zfm~q^cnx|EN`goHHG^EDG-fNCeV^EXSiy`}^gc2ZLk`Tj@MbKNYf&b&6Y)nfP9c4> zKa)`csh}PRRT3+R9uNsQN|QP^vxe|~d=UCX$9O-9P@O)E{h3Fj&ggv>##$Dd2rX7Z|J28bq^5;lK2V&k;)hS7tm*_#hfUjv@QSCEkgv5WXXzadc>?=jy?qBN1fj99a5cqFXM`asSQnZ$STdZdY#!>b9$om$3gQO9T- zNhkB?&rhbFX7HxIz#~G(80C#DF~fI6iAQqb&W+AspUQL(gB+!)JX)$?o^)BOWL_4) zK<+@NH@$@0xa2Ka5}Ps{wMIiYv0}15oCnM!pbrN3iN>${GvuZ>%$evtK4ee*6DBztMfb&Y$^MD Ytu@+R+{@*3otInL+T-(HUu}DqFHwX?ZvX%Q diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 9626caca71c99704b6411f3e7feca089a9563360 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:05:23 +0200 Subject: [PATCH 12/31] Remove empty function clearAllNotifications --- src/background/background-cases.ts | 27 --------------------- src/background/background.ts | 21 +++-------------- src/encryption/encryption.ts | 38 +----------------------------- 3 files changed, 4 insertions(+), 82 deletions(-) diff --git a/src/background/background-cases.ts b/src/background/background-cases.ts index f6c0158..c1aa7df 100644 --- a/src/background/background-cases.ts +++ b/src/background/background-cases.ts @@ -11,7 +11,6 @@ import { checkLocalFunc, checkNewMessages, checkThreads, - clearAllNotifications, createEndpoint, createGroup, decryptDirectFunc, @@ -1142,32 +1141,6 @@ export async function getEnteredQmailTimestampCase(request, event) { } } -export async function clearAllNotificationsCase(request, event) { - try { - await clearAllNotifications(); - - event.source.postMessage( - { - requestId: request.requestId, - action: 'clearAllNotifications', - payload: true, - type: 'backgroundMessageResponse', - }, - event.origin - ); - } catch (error) { - event.source.postMessage( - { - requestId: request.requestId, - action: 'clearAllNotifications', - error: error?.message, - type: 'backgroundMessageResponse', - }, - event.origin - ); - } -} - export async function setGroupDataCase(request, event) { try { const { groupId, secretKeyData, secretKeyResource, admins } = diff --git a/src/background/background.ts b/src/background/background.ts index 075abea..90b2f2e 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -34,7 +34,6 @@ import { cancelBanCase, cancelInvitationToGroupCase, checkLocalCase, - clearAllNotificationsCase, createGroupCase, createPollCase, createRewardShareCase, @@ -303,6 +302,7 @@ export const createEndpoint = async (endpoint, customApi?: string) => { }; export const walletVersion = 2; + // List of your API endpoints const apiEndpoints = [ 'https://api.qortal.org', @@ -436,10 +436,6 @@ export async function performPowTask(chatBytes, difficulty) { }); } -function playNotificationSound() { - // chrome.runtime.sendMessage({ action: "PLAY_NOTIFICATION_SOUND" }); -} - const handleNotificationDirect = async (directs) => { let isFocused; const wallet = await getSaveWallet(); @@ -603,7 +599,7 @@ export function updateThreadActivity({ threads = JSON.parse(storedData); } - let lastResetTime = threads.lastResetTime || 0; + const lastResetTime = threads.lastResetTime || 0; // Check if a week has passed since the last reset if (currentTime - lastResetTime > ONE_WEEK_IN_MS) { @@ -653,7 +649,7 @@ export function updateThreadActivity({ const handleNotification = async (groups) => { const wallet = await getSaveWallet(); const address = wallet.address0; - let isDisableNotifications = + const isDisableNotifications = (await getUserSettings({ key: 'disable-push-notifications' })) || false; let mutedGroups = (await getUserSettings({ key: 'mutedGroups' })) || []; @@ -678,7 +674,6 @@ const handleNotification = async (groups) => { const newActiveChats = data; const oldActiveChats = await getChatHeads(); - let results = []; let newestLatestTimestamp = null; let oldestLatestTimestamp = null; // Find the latest timestamp from newActiveChats @@ -878,13 +873,6 @@ export async function storeWallets(wallets) { }); } -export async function clearAllNotifications() { - // const notifications = await chrome.notifications.getAll(); - // for (const notificationId of Object.keys(notifications)) { - // await chrome.notifications.clear(notificationId); - // } -} - export async function getUserInfo() { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -3233,9 +3221,6 @@ function setupMessageListener() { case 'addGroupNotificationTimestamp': addGroupNotificationTimestampCase(request, event); break; - case 'clearAllNotifications': - clearAllNotificationsCase(request, event); - break; case 'setGroupData': setGroupDataCase(request, event); break; diff --git a/src/encryption/encryption.ts b/src/encryption/encryption.ts index d8f9d25..dae1709 100644 --- a/src/encryption/encryption.ts +++ b/src/encryption/encryption.ts @@ -12,49 +12,13 @@ import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; export const requestQueueGetPublicKeys = new RequestQueueWithPromise(10); -const apiEndpoints = [ - 'https://api.qortal.org', - 'https://api2.qortal.org', - 'https://appnode.qortal.org', - 'https://apinode.qortalnodes.live', - 'https://apinode1.qortalnodes.live', - 'https://apinode2.qortalnodes.live', - 'https://apinode3.qortalnodes.live', - 'https://apinode4.qortalnodes.live', -]; - -async function findUsableApi() { - for (const endpoint of apiEndpoints) { - try { - const response = await fetch(`${endpoint}/admin/status`); - if (!response.ok) throw new Error('Failed to fetch'); - - const data = await response.json(); - if (data.isSynchronizing === false && data.syncPercent === 100) { - console.log(`Usable API found: ${endpoint}`); - return endpoint; - } else { - console.log(`API not ready: ${endpoint}`); - } - } catch (error) { - console.error(`Error checking API ${endpoint}:`, error); - } - } - - throw new Error( - i18n.t('question:message.error.no_api_found', { - postProcess: 'capitalizeFirstChar', - }) - ); -} - async function getSaveWallet() { const res = await getData('walletInfo').catch(() => null); if (res) { return res; } else { - throw new Error('No wallet saved'); + throw new Error('No wallet saved'); // TODO translate } } From e8a09bd805b17e3073adf4b4497ca8fd4ab3032c Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:09:50 +0200 Subject: [PATCH 13/31] Rename folder and file --- src/App.tsx | 2 +- src/background/background-cases.ts | 2 +- src/background/background.ts | 2 +- src/components/Save/Save.tsx | 2 +- src/hooks/useQortalMessageListener.tsx | 2 +- src/qortal/get.ts | 7005 +++++++++++++++++ .../qortal-requests.ts} | 0 src/qortalRequests/get.ts | 41 +- 8 files changed, 7026 insertions(+), 30 deletions(-) create mode 100644 src/qortal/get.ts rename src/{qortalRequests/qortalRequests.ts => qortal/qortal-requests.ts} (100%) diff --git a/src/App.tsx b/src/App.tsx index fe721a7..4fd9057 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -124,7 +124,7 @@ import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './hooks/useHandleTutorials.tsx'; import { useHandleUserInfo } from './hooks/useHandleUserInfo.tsx'; import { Minting } from './components/Minting/Minting'; -import { isRunningGateway } from './qortalRequests/qortalRequests.ts'; +import { isRunningGateway } from './qortal/qortal-requests.ts'; import { QMailStatus } from './components/QMailStatus'; import { GlobalActions } from './components/GlobalActions/GlobalActions'; import { useBlockedAddresses } from './hooks/useBlockUsers.tsx'; diff --git a/src/background/background-cases.ts b/src/background/background-cases.ts index c1aa7df..0392893 100644 --- a/src/background/background-cases.ts +++ b/src/background/background-cases.ts @@ -65,7 +65,7 @@ import { import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../constants/constants'; import Base58 from '../encryption/Base58.ts'; import { encryptSingle } from '../qdn/encryption/group-encryption'; -import { _createPoll, _voteOnPoll } from '../qortalRequests/get'; +import { _createPoll, _voteOnPoll } from '../qortal/get.ts'; import { createTransaction } from '../transactions/transactions'; import { getData, storeData } from '../utils/chromeStorage'; diff --git a/src/background/background.ts b/src/background/background.ts index 90b2f2e..2a7b158 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import '../qortalRequests/qortalRequests'; +import '../qortal/qortal-requests.ts'; import { isArray } from 'lodash'; import { uint8ArrayToObject } from '../encryption/encryption.ts'; import Base58 from '../encryption/Base58'; diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index e213174..cebccf8 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -26,7 +26,7 @@ import { IconWrapper } from '../Desktop/DesktopFooter'; import { Spacer } from '../../common/Spacer'; import { LoadingButton } from '@mui/lab'; import { saveToLocalStorage } from '../Apps/AppsNavBarDesktop'; -import { decryptData, encryptData } from '../../qortalRequests/get'; +import { decryptData, encryptData } from '../../qortal/get.ts'; import { saveFileToDiskGeneric } from '../../utils/generateWallet/generateWallet'; import { base64ToUint8Array, diff --git a/src/hooks/useQortalMessageListener.tsx b/src/hooks/useQortalMessageListener.tsx index c6c24da..29549dc 100644 --- a/src/hooks/useQortalMessageListener.tsx +++ b/src/hooks/useQortalMessageListener.tsx @@ -2,7 +2,7 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import { executeEvent } from '../utils/events'; import { navigationControllerAtom } from '../atoms/global'; import { Filesystem, Directory } from '@capacitor/filesystem'; -import { saveFile } from '../qortalRequests/get'; +import { saveFile } from '../qortal/get'; import { mimeToExtensionMap } from '../utils/memeTypes'; import { QORTAL_APP_CONTEXT } from '../App'; import FileSaver from 'file-saver'; diff --git a/src/qortal/get.ts b/src/qortal/get.ts new file mode 100644 index 0000000..102a9bf --- /dev/null +++ b/src/qortal/get.ts @@ -0,0 +1,7005 @@ +import { Sha256 } from 'asmcrypto.js'; +import { + createEndpoint, + getBalanceInfo, + getFee, + getKeyPair, + getLastRef, + getSaveWallet, + processTransactionVersion2, + signChatFunc, + joinGroup as joinGroupFunc, + sendQortFee, + sendCoin as sendCoinFunc, + createBuyOrderTx, + performPowTask, + parseErrorResponse, + groupSecretkeys, + registerName, + updateName, + leaveGroup, + inviteToGroup, + getNameInfoForOthers, + kickFromGroup, + banFromGroup, + cancelBan, + makeAdmin, + removeAdmin, + cancelInvitationToGroup, + createGroup, + updateGroup, + sellName, + cancelSellName, + buyName, + getBaseApi, + getAssetBalanceInfo, + getNameOrAddress, + getAssetInfo, + getPublicKey, + transferAsset, +} from '../background/background.ts'; +import { getNameInfo, uint8ArrayToObject } from '../encryption/encryption.ts'; +import { showSaveFilePicker } from '../hooks/useQortalMessageListener.tsx'; +import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner.tsx'; +import { extractComponents } from '../components/Chat/MessageDisplay.tsx'; +import { + decryptResource, + getGroupAdmins, + getPublishesFromAdmins, + validateSecretKey, +} from '../components/Group/Group.tsx'; +import { QORT_DECIMALS } from '../constants/constants.ts'; +import Base58 from '../encryption/Base58.ts'; +import ed2curve from '../encryption/ed2curve.ts'; +import nacl from '../encryption/nacl-fast.ts'; +import { + base64ToUint8Array, + createSymmetricKeyAndNonce, + decryptDeprecatedSingle, + decryptGroupDataQortalRequest, + decryptGroupEncryptionWithSharingKey, + decryptSingle, + encryptDataGroup, + encryptSingle, + objectToBase64, + uint8ArrayStartsWith, + uint8ArrayToBase64, +} from '../qdn/encryption/group-encryption.ts'; +import { publishData } from '../qdn/publish/publish.ts'; +import { + getPermission, + isRunningGateway, + setPermission, +} from './qortal-requests.ts'; +import TradeBotCreateRequest from '../transactions/TradeBotCreateRequest.ts'; +import DeleteTradeOffer from '../transactions/TradeBotDeleteRequest.ts'; +import signTradeBotTransaction from '../transactions/signTradeBotTransaction.ts'; +import { createTransaction } from '../transactions/transactions.ts'; +import { executeEvent } from '../utils/events.ts'; +import { fileToBase64 } from '../utils/fileReading/index.ts'; +import { mimeToExtensionMap } from '../utils/memeTypes.ts'; +import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; +import utils from '../utils/utils.ts'; +import ShortUniqueId from 'short-unique-id'; +import { isValidBase64WithDecode } from '../utils/decode.ts'; +import i18n from 'i18next'; + +const uid = new ShortUniqueId({ length: 6 }); + +export const requestQueueGetAtAddresses = new RequestQueueWithPromise(10); + +const sellerForeignFee = { + LITECOIN: { + value: '~0.00005', + ticker: 'LTC', + }, + DOGECOIN: { + value: '~0.005', + ticker: 'DOGE', + }, + BITCOIN: { + value: '~0.0001', + ticker: 'BTC', + }, + DIGIBYTE: { + value: '~0.0005', + ticker: 'DGB', + }, + RAVENCOIN: { + value: '~0.006', + ticker: 'RVN', + }, + PIRATECHAIN: { + value: '~0.0002', + ticker: 'ARRR', + }, +}; + +const btcFeePerByte = 0.000001; +const ltcFeePerByte = 0.0000003; +const dogeFeePerByte = 0.00001; +const dgbFeePerByte = 0.0000001; +const rvnFeePerByte = 0.00001125; + +const MAX_RETRIES = 3; // Set max number of retries + +export async function retryTransaction( + fn, + args, + throwError, + retries = MAX_RETRIES +) { + let attempt = 0; + while (attempt < retries) { + try { + return await fn(...args); + } catch (error) { + console.error(`Attempt ${attempt + 1} failed: ${error.message}`); + attempt++; + if (attempt === retries) { + console.error( + i18n.t('question:message.generic.max_retry_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + if (throwError) { + throw new Error( + error?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + } else { + throw new Error( + error?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + await new Promise((res) => setTimeout(res, 10000)); + } + } +} + +function roundUpToDecimals(number, decimals = 8) { + const factor = Math.pow(10, decimals); // Create a factor based on the number of decimals + return Math.ceil(+number * factor) / factor; +} + +export const _createPoll = async ( + { pollName, pollDescription, options }, + isFromExtension, + skipPermission +) => { + const fee = await getFee('CREATE_POLL'); + let resPermission = {}; + if (!skipPermission) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:request_create_poll', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:poll', { + name: pollName, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:description', { + description: pollDescription, + postProcess: 'capitalizeFirstChar', + }), + text4: i18n.t('question:options', { + optionList: options?.join(', '), + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + } + + const { accepted = false } = resPermission; + + if (accepted || skipPermission) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + 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 lastRef = await getLastRef(); + + const tx = await createTransaction(8, keyPair, { + fee: fee.fee, + ownerAddress: address, + rPollName: pollName, + rPollDesc: pollDescription, + rOptions: options, + lastReference: lastRef, + }); + const signedBytes = Base58.encode(tx.signedBytes); + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error( + res?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +const _deployAt = async ( + { name, description, tags, creationBytes, amount, assetId, atType }, + isFromExtension +) => { + const fee = await getFee('DEPLOY_AT'); + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:deploy_at', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:name', { + name: name, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:description', { + description: description, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + + const { accepted } = resPermission; + + if (accepted) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const lastReference = await getLastRef(); + 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, + }; + + const tx = await createTransaction(16, keyPair, { + fee: fee.fee, + rName: name, + rDescription: description, + rTags: tags, + rAmount: amount, + rAssetId: assetId, + rCreationBytes: creationBytes, + atType: atType, + lastReference: lastReference, + }); + + const signedBytes = Base58.encode(tx.signedBytes); + + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error( + res?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const _voteOnPoll = async ( + { pollName, optionIndex, optionName }, + isFromExtension, + skipPermission +) => { + const fee = await getFee('VOTE_ON_POLL'); + let resPermission = {}; + + if (!skipPermission) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:request_vote_poll', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:poll', { + name: pollName, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:option', { + option: optionName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + } + + const { accepted = false } = resPermission; + + if (accepted || skipPermission) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + 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 lastRef = await getLastRef(); + + const tx = await createTransaction(9, keyPair, { + fee: fee.fee, + voterAddress: address, + rPollName: pollName, + rOptionIndex: optionIndex, + lastReference: lastRef, + }); + const signedBytes = Base58.encode(tx.signedBytes); + const res = await processTransactionVersion2(signedBytes); + if (!res?.signature) + throw new Error( + res?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +// Map to store resolvers and rejectors by requestId +const fileRequestResolvers = new Map(); + +const handleFileMessage = (event) => { + const { action, requestId, result, error } = event.data; + + if ( + action === 'getFileFromIndexedDBResponse' && + fileRequestResolvers.has(requestId) + ) { + const { resolve, reject } = fileRequestResolvers.get(requestId); + fileRequestResolvers.delete(requestId); // Clean up after resolving + + if (result) { + resolve(result); + } else { + reject( + error || + i18n.t('question:message.error.retrieve_file', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } +}; + +window.addEventListener('message', handleFileMessage); + +function getFileFromContentScript(fileId) { + return new Promise((resolve, reject) => { + const requestId = `getFile_${fileId}_${Date.now()}`; + + fileRequestResolvers.set(requestId, { resolve, reject }); // Store resolvers by requestId + const targetOrigin = window.location.origin; + + // Send the request message + window.postMessage( + { action: 'getFileFromIndexedDB', fileId, requestId }, + targetOrigin + ); + + // Timeout to handle no response scenario + setTimeout(() => { + if (fileRequestResolvers.has(requestId)) { + fileRequestResolvers.get(requestId).reject( + i18n.t('question:message.error.timeout_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + fileRequestResolvers.delete(requestId); // Clean up on timeout + } + }, 10000); // 10-second timeout + }); +} + +const responseResolvers = new Map(); + +const handleMessage = (event) => { + const { action, requestId, result } = event.data; + + // Check if this is the expected response action and if we have a stored resolver + if ( + action === 'QORTAL_REQUEST_PERMISSION_RESPONSE' && + responseResolvers.has(requestId) + ) { + // Resolve the stored promise with the result + responseResolvers.get(requestId)(result || false); + responseResolvers.delete(requestId); // Clean up after resolving + } +}; + +window.addEventListener('message', handleMessage); + +async function getUserPermission(payload, isFromExtension) { + return new Promise((resolve) => { + const requestId = `qortalRequest_${Date.now()}`; + responseResolvers.set(requestId, resolve); // Store resolver by requestId + const targetOrigin = window.location.origin; + + // Send the request message + window.postMessage( + { + action: 'QORTAL_REQUEST_PERMISSION', + payload, + requestId, + isFromExtension, + }, + targetOrigin + ); + + // Optional timeout to handle no response scenario + setTimeout(() => { + if (responseResolvers.has(requestId)) { + responseResolvers.get(requestId)(false); // Resolve with `false` if no response + responseResolvers.delete(requestId); + } + }, 60000); // 30-second timeout + }); +} + +export const getUserAccount = async ({ + isFromExtension, + appInfo, + skipAuth, +}) => { + try { + const value = + (await getPermission(`qAPPAutoAuth-${appInfo?.name}`)) || false; + let skip = false; + if (value) { + skip = true; + } + if (skipAuth) { + skip = true; + } + let resPermission; + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.authenticate', { + postProcess: 'capitalizeFirstChar', + }), + checkbox1: { + value: false, + label: i18n.t('question:always_authenticate', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + + const { accepted = false, checkbox1 = false } = resPermission || {}; + if (resPermission) { + setPermission(`qAPPAutoAuth-${appInfo?.name}`, checkbox1); + } + if (accepted || skip) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const publicKey = wallet.publicKey; + return { + address, + publicKey, + }; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + i18n.t('auth:message.error.fetch_user_account', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const encryptData = async (data, sender) => { + let data64 = data.data64 || data.base64; + let publicKeys = data.publicKeys || []; + if (data?.file || data?.blob) { + data64 = await fileToBase64(data?.file || data?.blob); + } + if (!data64) { + throw new Error( + i18n.t('question:message.generic.include_data_encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + + const encryptDataResponse = encryptDataGroup({ + data64, + publicKeys: publicKeys, + privateKey, + userPublicKey, + }); + if (encryptDataResponse) { + return encryptDataResponse; + } else { + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const encryptQortalGroupData = async (data, sender) => { + let data64 = data?.data64 || data?.base64; + let groupId = data?.groupId; + let isAdmins = data?.isAdmins; + if (!groupId) { + throw new Error( + i18n.t('question:message.generic.provide_group_id', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (data?.file || data?.blob) { + data64 = await fileToBase64(data?.file || data?.blob); + } + if (!data64) { + throw new Error( + i18n.t('question:message.generic.include_data_encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + let secretKeyObject; + if (!isAdmins) { + if ( + groupSecretkeys[groupId] && + groupSecretkeys[groupId].secretKeyObject && + groupSecretkeys[groupId]?.timestamp && + Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 + ) { + secretKeyObject = groupSecretkeys[groupId].secretKeyObject; + } + + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId); + + const publish = await getPublishesFromAdmins(names, groupId); + if (publish === false) + throw new Error( + i18n.t('question:message.error.no_group_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + const url = await createEndpoint( + `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ + publish.identifier + }?encoding=base64&rebuild=true` + ); + + const res = await fetch(url); + const resData = await res.text(); + + const decryptedKey: any = await decryptResource(resData, true); + + const dataint8Array = base64ToUint8Array(decryptedKey.data); + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); + + if (!validateSecretKey(decryptedKeyToObject)) + throw new Error( + i18n.t('auth:message.error.invalid_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + secretKeyObject = decryptedKeyToObject; + groupSecretkeys[groupId] = { + secretKeyObject, + timestamp: Date.now(), + }; + } + } else { + if ( + groupSecretkeys[`admins-${groupId}`] && + groupSecretkeys[`admins-${groupId}`].secretKeyObject && + groupSecretkeys[`admins-${groupId}`]?.timestamp && + Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 + ) { + secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; + } + + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId); + + const publish = await getPublishesFromAdminsAdminSpace(names, groupId); + if (publish === false) + throw new Error( + i18n.t('question:message.error.no_group_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + const url = await createEndpoint( + `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ + publish.identifier + }?encoding=base64&rebuild=true` + ); + + const res = await fetch(url); + const resData = await res.text(); + const decryptedKey: any = await decryptResource(resData, true); + const dataint8Array = base64ToUint8Array(decryptedKey.data); + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); + + if (!validateSecretKey(decryptedKeyToObject)) + throw new Error( + i18n.t('auth:message.error.invalid_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + secretKeyObject = decryptedKeyToObject; + groupSecretkeys[`admins-${groupId}`] = { + secretKeyObject, + timestamp: Date.now(), + }; + } + } + + const resGroupEncryptedResource = encryptSingle({ + data64, + secretKeyObject: secretKeyObject, + }); + + if (resGroupEncryptedResource) { + return resGroupEncryptedResource; + } else { + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const decryptQortalGroupData = async (data, sender) => { + let data64 = data?.data64 || data?.base64; + let groupId = data?.groupId; + let isAdmins = data?.isAdmins; + if (!groupId) { + throw new Error( + i18n.t('question:message.generic.provide_group_id', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + if (!data64) { + throw new Error( + i18n.t('question:message.generic.include_data_encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + let secretKeyObject; + if (!isAdmins) { + if ( + groupSecretkeys[groupId] && + groupSecretkeys[groupId].secretKeyObject && + groupSecretkeys[groupId]?.timestamp && + Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 + ) { + secretKeyObject = groupSecretkeys[groupId].secretKeyObject; + } + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId); + + const publish = await getPublishesFromAdmins(names, groupId); + if (publish === false) + throw new Error( + i18n.t('question:message.error.no_group_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + const url = await createEndpoint( + `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ + publish.identifier + }?encoding=base64&rebuild=true` + ); + + const res = await fetch(url); + const resData = await res.text(); + const decryptedKey: any = await decryptResource(resData, true); + + const dataint8Array = base64ToUint8Array(decryptedKey.data); + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); + if (!validateSecretKey(decryptedKeyToObject)) + throw new Error( + i18n.t('auth:message.error.invalid_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + secretKeyObject = decryptedKeyToObject; + groupSecretkeys[groupId] = { + secretKeyObject, + timestamp: Date.now(), + }; + } + } else { + if ( + groupSecretkeys[`admins-${groupId}`] && + groupSecretkeys[`admins-${groupId}`].secretKeyObject && + groupSecretkeys[`admins-${groupId}`]?.timestamp && + Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 + ) { + secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; + } + if (!secretKeyObject) { + const { names } = await getGroupAdmins(groupId); + + const publish = await getPublishesFromAdminsAdminSpace(names, groupId); + if (publish === false) + throw new Error( + i18n.t('question:message.error.no_group_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + const url = await createEndpoint( + `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ + publish.identifier + }?encoding=base64&rebuild=true` + ); + + const res = await fetch(url); + const resData = await res.text(); + const decryptedKey: any = await decryptResource(resData, true); + + const dataint8Array = base64ToUint8Array(decryptedKey.data); + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); + if (!validateSecretKey(decryptedKeyToObject)) + throw new Error( + i18n.t('auth:message.error.invalid_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + secretKeyObject = decryptedKeyToObject; + groupSecretkeys[`admins-${groupId}`] = { + secretKeyObject, + timestamp: Date.now(), + }; + } + } + + const resGroupDecryptResource = decryptSingle({ + data64, + secretKeyObject: secretKeyObject, + skipDecodeBase64: true, + }); + if (resGroupDecryptResource) { + return resGroupDecryptResource; + } else { + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const encryptDataWithSharingKey = async (data, sender) => { + let data64 = data?.data64 || data?.base64; + let publicKeys = data.publicKeys || []; + if (data?.file || data?.blob) { + data64 = await fileToBase64(data?.file || data?.blob); + } + if (!data64) { + throw new Error( + i18n.t('question:message.generic.include_data_encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const symmetricKey = createSymmetricKeyAndNonce(); + const dataObject = { + data: data64, + key: symmetricKey.messageKey, + }; + const dataObjectBase64 = await objectToBase64(dataObject); + + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + + const encryptDataResponse = encryptDataGroup({ + data64: dataObjectBase64, + publicKeys: publicKeys, + privateKey, + userPublicKey, + customSymmetricKey: symmetricKey.messageKey, + }); + if (encryptDataResponse) { + return encryptDataResponse; + } else { + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const decryptDataWithSharingKey = async (data, sender) => { + const { encryptedData, key } = data; + + if (!encryptedData) { + throw new Error( + i18n.t('question:message.generic.include_data_decrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const decryptedData = await decryptGroupEncryptionWithSharingKey({ + data64EncryptedData: encryptedData, + key, + }); + const base64ToObject = JSON.parse(atob(decryptedData)); + + if (!base64ToObject.data) + throw new Error( + i18n.t('question:message.error.no_data_encrypted_resource', { + postProcess: 'capitalizeFirstChar', + }) + ); + return base64ToObject.data; +}; + +export const getHostedData = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const resPermission = await getUserPermission( + { + text1: i18n.t('question:message.error.submit_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const limit = data?.limit ? data?.limit : 20; + const query = data?.query ? data?.query : ''; + const offset = data?.offset ? data?.offset : 0; + + let urlPath = `/arbitrary/hosted/resources/?limit=${limit}&offset=${offset}`; + if (query) { + urlPath = urlPath + `&query=${query}`; + } + + const url = await createEndpoint(urlPath); + const response = await fetch(url); + const dataResponse = await response.json(); + return dataResponse; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const deleteHostedData = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['hostedData']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.delete_hosts_resources', { + size: data?.hostedData?.length, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const { hostedData } = data; + + for (const hostedDataItem of hostedData) { + try { + const url = await createEndpoint( + `/arbitrary/resource/${hostedDataItem.service}/${hostedDataItem.name}/${hostedDataItem.identifier}` + ); + await fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + console.log(error); + } + } + + return true; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_delete_hosted_resources', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; +export const decryptData = async (data) => { + const { encryptedData, publicKey } = data; + + if (!encryptedData) { + throw new Error(`Missing fields: encryptedData`); + } + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8Array = base64ToUint8Array(encryptedData); + const startsWithQortalEncryptedData = uint8ArrayStartsWith( + uint8Array, + 'qortalEncryptedData' + ); + if (startsWithQortalEncryptedData) { + if (!publicKey) { + throw new Error(`Missing fields: publicKey`); + } + + const decryptedDataToBase64 = decryptDeprecatedSingle( + uint8Array, + publicKey, + uint8PrivateKey + ); + return decryptedDataToBase64; + } + const startsWithQortalGroupEncryptedData = uint8ArrayStartsWith( + uint8Array, + 'qortalGroupEncryptedData' + ); + if (startsWithQortalGroupEncryptedData) { + const decryptedData = decryptGroupDataQortalRequest( + encryptedData, + parsedData.privateKey + ); + const decryptedDataToBase64 = uint8ArrayToBase64(decryptedData); + return decryptedDataToBase64; + } + throw new Error( + i18n.t('question:message.error.encrypt', { + postProcess: 'capitalizeFirstChar', + }) + ); +}; + +export const getListItems = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['list_name']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const value = (await getPermission('qAPPAutoLists')) || false; + + let skip = false; + if (value) { + skip = true; + } + let resPermission; + let acceptedVar; + let checkbox1Var; + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.access_list', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: data.list_name, + checkbox1: { + value: value, + label: i18n.t('question:always_retrieve_list', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + const { accepted, checkbox1 } = resPermission; + acceptedVar = accepted; + checkbox1Var = checkbox1; + setPermission('qAPPAutoLists', checkbox1); + } + + if (acceptedVar || skip) { + const url = await createEndpoint(`/lists/${data.list_name}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + + const list = await response.json(); + return list; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_share_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const addListItems = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['list_name', 'items']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const items = data.items; + const list_name = data.list_name; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.all_item_list', { + name: list_name, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: items.join(', '), + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const url = await createEndpoint(`/lists/${list_name}`); + const body = { + items: items, + }; + const bodyToString = JSON.stringify(body); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.add_to_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_add_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const deleteListItems = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['list_name']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (!data?.item && !data?.items) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: 'items', + postProcess: 'capitalizeFirstChar', + }) + ); + } + const item = data?.item; + const items = data?.items; + const list_name = data.list_name; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.remove_from_list', { + name: list_name, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: items ? JSON.stringify(items) : item, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const url = await createEndpoint(`/lists/${list_name}`); + const body = { + items: items || [item], + }; + const bodyToString = JSON.stringify(body); + const response = await fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.add_to_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_delete_from_list', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const publishQDNResource = async ( + data: any, + sender, + isFromExtension +) => { + const requiredFields = ['service']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (!data.file && !data.data64 && !data.base64) { + throw new Error( + i18n.t('question:message.error.no_data_file_submitted', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + // Use "default" if user hasn't specified an identifier + const service = data.service; + const appFee = data?.appFee ? +data.appFee : undefined; + const appFeeRecipient = data?.appFeeRecipient; + let hasAppFee = false; + if (appFee && appFee > 0 && appFeeRecipient) { + hasAppFee = true; + } + const registeredName = await getNameInfo(); + const name = registeredName; + if (!name) { + throw new Error( + i18n.t('question:message.error.user_qortal_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + let identifier = data.identifier; + let data64 = data.data64 || data.base64; + const filename = data.filename; + const title = data.title; + const description = data.description; + const category = data.category; + + const tags = data?.tags || []; + const result = {}; + + // Fill tags dynamically while maintaining backward compatibility + for (let i = 0; i < 5; i++) { + result[`tag${i + 1}`] = tags[i] || data[`tag${i + 1}`] || undefined; + } + + // Access tag1 to tag5 from result + const { tag1, tag2, tag3, tag4, tag5 } = result; + + if (data.identifier == null) { + identifier = 'default'; + } + if (data?.file || data?.blob) { + data64 = await fileToBase64(data?.file || data?.blob); + } + if ( + data.encrypt && + (!data.publicKeys || + (Array.isArray(data.publicKeys) && data.publicKeys.length === 0)) + ) { + throw new Error( + i18n.t('question:message.error.encryption_requires_public_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + if (data.encrypt) { + try { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + const encryptDataResponse = encryptDataGroup({ + data64, + publicKeys: data.publicKeys, + privateKey, + userPublicKey, + }); + if (encryptDataResponse) { + data64 = encryptDataResponse; + } + } catch (error) { + throw new Error( + error.message || + i18n.t('question:message.error.upload_encryption', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + + const fee = await getFee('ARBITRARY'); + + const handleDynamicValues = {}; + if (hasAppFee) { + const feePayment = await getFee('PAYMENT'); + + (handleDynamicValues['appFee'] = +appFee + +feePayment.fee), + (handleDynamicValues['checkbox1'] = { + value: true, + label: i18n.t('question:accept_app_fee', { + postProcess: 'capitalizeFirstChar', + }), + }); + } + if (!!data?.encrypt) { + handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`; + } + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.publish_qdn', { + postProcess: 'capitalizeFirstChar', + }), + text2: `service: ${service}`, + text3: `identifier: ${identifier || null}`, + fee: fee.fee, + ...handleDynamicValues, + }, + isFromExtension + ); + const { accepted, checkbox1 = false } = resPermission; + if (accepted) { + try { + const resPublish = await publishData({ + registeredName: encodeURIComponent(name), + file: data64, + service: service, + identifier: encodeURIComponent(identifier), + uploadType: 'file', + isBase64: true, + filename: filename, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + apiVersion: 2, + withFee: true, + }); + if (resPublish?.signature && hasAppFee && checkbox1) { + sendCoinFunc( + { + amount: appFee, + receiver: appFeeRecipient, + }, + true + ); + } + return resPublish; + } catch (error) { + throw new Error(error?.message || 'Upload failed'); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const checkArrrSyncStatus = async (seed) => { + const _url = await createEndpoint(`/crosschain/arrr/syncstatus`); + let tries = 0; // Track the number of attempts + + while (tries < 36) { + const response = await fetch(_url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: seed, + }); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res.indexOf('<') > -1 || res !== 'Synchronized') { + // Wait 2 seconds before trying again + await new Promise((resolve) => setTimeout(resolve, 2000)); + tries += 1; + } else { + // If the response doesn't meet the two conditions, exit the function + return; + } + } + + // If we exceed N tries, throw an error + throw new Error( + i18n.t('question:message.error.synchronization_attempts', { + quantity: 36, + postProcess: 'capitalizeFirstChar', + }) + ); +}; + +export const publishMultipleQDNResources = async ( + data: any, + sender, + isFromExtension +) => { + const requiredFields = ['resources']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resources = data.resources; + if (!Array.isArray(resources)) { + throw new Error( + i18n.t('group:message.generic.invalid_data', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + if (resources.length === 0) { + throw new Error( + i18n.t('question:message.error.no_resources_publish', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const encrypt = data?.encrypt; + + for (const resource of resources) { + const resourceEncrypt = encrypt && resource?.disableEncrypt !== true; + + if (!resourceEncrypt && resource?.service.endsWith('_PRIVATE')) { + const errorMsg = i18n.t('question:message.error.only_encrypted_data', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } else if (resourceEncrypt && !resource?.service.endsWith('_PRIVATE')) { + const errorMsg = i18n.t('question:message.error.use_private_service', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + } + + const fee = await getFee('ARBITRARY'); + const registeredName = await getNameInfo(); + const name = registeredName; + + if (!name) { + throw new Error( + i18n.t('question:message.error.registered_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const appFee = data?.appFee ? +data.appFee : undefined; + const appFeeRecipient = data?.appFeeRecipient; + let hasAppFee = false; + + if (appFee && appFee > 0 && appFeeRecipient) { + hasAppFee = true; + } + + const handleDynamicValues = {}; + if (hasAppFee) { + const feePayment = await getFee('PAYMENT'); + + (handleDynamicValues['appFee'] = +appFee + +feePayment.fee), + (handleDynamicValues['checkbox1'] = { + value: true, + label: i18n.t('question:accept_app_fee', { + postProcess: 'capitalizeFirstChar', + }), + }); + } + if (data?.encrypt) { + handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`; + } + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.publish_qdn', { + postProcess: 'capitalizeFirstChar', + }), + html: ` +

+ + + ${data.resources + .map( + (resource) => ` +
+
Service: ${ + resource.service + }
+
Name: ${name}
+
Identifier: ${ + resource.identifier + }
+ ${ + resource.filename + ? `
Filename: ${resource.filename}
` + : '' + } +
` + ) + .join('')} +
+ + `, + fee: +fee.fee * resources.length, + ...handleDynamicValues, + }, + isFromExtension + ); + + const { accepted, checkbox1 = false } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + type FailedPublish = { + reason: string; + identifier: any; + service: any; + }; + + const failedPublishesIdentifiers: FailedPublish[] = []; + + for (const resource of resources) { + try { + const requiredFields = ['service']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!resource[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + continue; + } + if (!resource.file && !resource.data64 && !resource?.base64) { + const errorMsg = i18n.t( + 'question:message.error.no_data_file_submitted', + { + postProcess: 'capitalizeFirstChar', + } + ); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + continue; + } + const service = resource.service; + let identifier = resource.identifier; + let data64 = 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; + } + + // Access tag1 to tag5 from result + const { tag1, tag2, tag3, tag4, tag5 } = result; + const resourceEncrypt = encrypt && resource?.disableEncrypt !== true; + if (resource.identifier == null) { + identifier = 'default'; + } + if (!resourceEncrypt && service.endsWith('_PRIVATE')) { + const errorMsg = i18n.t('question:message.error.only_encrypted_data', { + postProcess: 'capitalizeFirstChar', + }); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + continue; + } + if (resource.file) { + data64 = await fileToBase64(resource.file); + } + if (resourceEncrypt) { + try { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + const encryptDataResponse = encryptDataGroup({ + data64, + publicKeys: data.publicKeys, + privateKey, + userPublicKey, + }); + if (encryptDataResponse) { + data64 = encryptDataResponse; + } + } catch (error) { + const errorMsg = + error?.message || + i18n.t('question:message.error.upload_encryption', { + postProcess: 'capitalizeFirstChar', + }); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + continue; + } + } + + try { + await retryTransaction( + publishData, + [ + { + registeredName: encodeURIComponent(name), + file: data64, + service: service, + identifier: encodeURIComponent(identifier), + uploadType: 'file', + isBase64: true, + filename: filename, + title, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, + apiVersion: 2, + withFee: true, + }, + ], + true + ); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000); + }); + } catch (error) { + const errorMsg = + error.message || + i18n.t('question:message.error.upload', { + postProcess: 'capitalizeFirstChar', + }); + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + service: resource.service, + }); + } + } catch (error) { + failedPublishesIdentifiers.push({ + reason: + error?.message || + i18n.t('question:message.error.unknown_error', { + postProcess: 'capitalizeFirstChar', + }), + identifier: resource.identifier, + service: resource.service, + }); + } + } + if (failedPublishesIdentifiers.length > 0) { + const obj = { + message: i18n.t('question:message.error.resources_publish', { + postProcess: 'capitalizeFirstChar', + }), + }; + obj['error'] = { + unsuccessfulPublishes: failedPublishesIdentifiers, + }; + return obj; + } + if (hasAppFee && checkbox1) { + sendCoinFunc( + { + amount: appFee, + receiver: appFeeRecipient, + }, + true + ); + } + return true; +}; + +export const voteOnPoll = async (data, isFromExtension) => { + const requiredFields = ['pollName', 'optionIndex']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field] && data[field] !== 0) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const pollName = data.pollName; + const optionIndex = data.optionIndex; + let pollInfo = null; + try { + const url = await createEndpoint(`/polls/${encodeURIComponent(pollName)}`); + const response = await fetch(url); + if (!response.ok) { + const errorMessage = await parseErrorResponse( + response, + i18n.t('question:message.error.fetch_poll', { + postProcess: 'capitalizeFirstChar', + }) + ); + throw new Error(errorMessage); + } + + pollInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_poll', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (!pollInfo || pollInfo.error) { + const errorMsg = + (pollInfo && pollInfo.message) || + i18n.t('question:message.error.no_poll', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + try { + const optionName = pollInfo.pollOptions[optionIndex].optionName; + const resVoteOnPoll = await _voteOnPoll( + { pollName, optionIndex, optionName }, + isFromExtension + ); + return resVoteOnPoll; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.poll_vote', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const createPoll = async (data, isFromExtension) => { + const requiredFields = [ + 'pollName', + 'pollDescription', + 'pollOptions', + 'pollOwnerAddress', + ]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const pollName = data.pollName; + const pollDescription = data.pollDescription; + const pollOptions = data.pollOptions; + try { + const resCreatePoll = await _createPoll( + { + pollName, + pollDescription, + options: pollOptions, + }, + isFromExtension + ); + return resCreatePoll; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.poll_create', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +function isBase64(str) { + const base64Regex = + /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; + return base64Regex.test(str) && str.length % 4 === 0; +} + +function checkValue(value) { + if (typeof value === 'string') { + if (isBase64(value)) { + return 'string'; + } else { + return 'string'; + } + } else if (typeof value === 'object' && value !== null) { + return 'object'; + } else { + throw new Error( + i18n.t('question:message.error.invalid_fullcontent', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +} + +export const sendChatMessage = async (data, isFromExtension, appInfo) => { + const message = data?.message; + const fullMessageObject = data?.fullMessageObject || data?.fullContent; + const recipient = data?.destinationAddress || data.recipient; + const groupId = data.groupId; + const isRecipient = groupId === undefined; + const chatReference = data?.chatReference; + if (groupId === undefined && recipient === undefined) { + throw new Error( + i18n.t('question:provide_recipient_group_id', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + let fullMessageObjectType; + if (fullMessageObject) { + fullMessageObjectType = checkValue(fullMessageObject); + } + const value = + (await getPermission(`qAPPSendChatMessage-${appInfo?.name}`)) || false; + let skip = false; + if (value) { + skip = true; + } + let resPermission; + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_chat_message', { + postProcess: 'capitalizeFirstChar', + }), + text2: isRecipient + ? i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }) + : i18n.t('question:to_group', { + group_id: groupId, + postProcess: 'capitalizeFirstChar', + }), + text3: fullMessageObject + ? fullMessageObjectType === 'string' + ? `${fullMessageObject?.slice(0, 25)}${fullMessageObject?.length > 25 ? '...' : ''}` + : `${JSON.stringify(fullMessageObject)?.slice(0, 25)}${JSON.stringify(fullMessageObject)?.length > 25 ? '...' : ''}` + : `${message?.slice(0, 25)}${message?.length > 25 ? '...' : ''}`, + checkbox1: { + value: false, + label: i18n.t('question:always_chat_messages', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + if (resPermission && accepted) { + setPermission(`qAPPSendChatMessage-${appInfo?.name}`, checkbox1); + } + if (accepted || skip) { + const tiptapJson = { + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: message, + }, + ], + }, + ], + }; + const messageObject = fullMessageObject + ? fullMessageObject + : { + messageText: tiptapJson, + images: [], + repliedTo: '', + version: 3, + }; + + let stringifyMessageObject = JSON.stringify(messageObject); + if (fullMessageObjectType === 'string') { + stringifyMessageObject = messageObject; + } + + const balance = await getBalanceInfo(); + const hasEnoughBalance = +balance < 4 ? false : true; + if (!hasEnoughBalance) { + throw new Error( + i18n.t('group:message.error.qortals_required', { + quantity: 4, + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (isRecipient && recipient) { + const url = await createEndpoint(`/addresses/publickey/${recipient}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_recipient_public_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let key; + let hasPublicKey; + let res; + const contentType = response.headers.get('content-type'); + + // If the response is JSON, parse it as JSON + if (contentType && contentType.includes('application/json')) { + res = await response.json(); + } else { + // Otherwise, treat it as plain text + res = await response.text(); + } + if (res?.error === 102) { + key = ''; + hasPublicKey = false; + } else if (res !== false) { + key = res; + hasPublicKey = true; + } else { + key = ''; + hasPublicKey = false; + } + + if (!hasPublicKey && isRecipient) { + throw new Error( + 'Cannot send an encrypted message to this user since they do not have their publickey on chain.' + ); + } + let _reference = new Uint8Array(64); + self.crypto.getRandomValues(_reference); + + let sendTimestamp = Date.now(); + + let reference = Base58.encode(_reference); + 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 handleDynamicValues = {}; + if (chatReference) { + handleDynamicValues['chatReference'] = chatReference; + } + + const tx = await createTransaction(18, keyPair, { + timestamp: sendTimestamp, + recipient: recipient, + recipientPublicKey: key, + hasChatReference: chatReference ? 1 : 0, + message: stringifyMessageObject, + lastReference: reference, + proofOfWorkNonce: 0, + isEncrypted: 1, + isText: 1, + ...handleDynamicValues, + }); + + const chatBytes = tx.chatBytes; + const difficulty = 8; + const { nonce, chatBytesArray } = await performPowTask( + chatBytes, + difficulty + ); + + let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); + if (_response?.error) { + throw new Error(_response?.message); + } + return _response; + } else if (!isRecipient && groupId) { + let _reference = new Uint8Array(64); + self.crypto.getRandomValues(_reference); + + let reference = Base58.encode(_reference); + 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 handleDynamicValues = {}; + if (chatReference) { + handleDynamicValues['chatReference'] = chatReference; + } + + const txBody = { + timestamp: Date.now(), + groupID: Number(groupId), + hasReceipient: 0, + hasChatReference: chatReference ? 1 : 0, + message: stringifyMessageObject, + lastReference: reference, + proofOfWorkNonce: 0, + isEncrypted: 0, // Set default to not encrypted for groups + isText: 1, + ...handleDynamicValues, + }; + + const tx = await createTransaction(181, keyPair, txBody); + + // if (!hasEnoughBalance) { + // throw new Error("Must have at least 4 QORT to send a chat message"); + // } + + const chatBytes = tx.chatBytes; + const difficulty = 8; + const { nonce, chatBytesArray } = await performPowTask( + chatBytes, + difficulty + ); + + let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); + if (_response?.error) { + throw new Error(_response?.message); + } + return _response; + } else { + throw new Error( + i18n.t('question:provide_recipient_group_id', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_send_message', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const joinGroup = async (data, isFromExtension) => { + const requiredFields = ['groupId']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${data.groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const fee = await getFee('JOIN_GROUP'); + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:message.generic.confirm_join_group', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${groupInfo.groupName}`, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const groupId = data.groupId; + + if (!groupInfo || groupInfo.error) { + const errorMsg = + (groupInfo && groupInfo.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + try { + const resJoinGroup = await joinGroupFunc({ groupId }); + return resJoinGroup; + } catch (error) { + throw new Error( + error?.message || + i18n.t('group:message.error.group_join', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_join', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const saveFile = async (data, sender, isFromExtension, snackMethods) => { + try { + const requiredFields = ['filename', 'blob']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const filename = data.filename; + const blob = data.blob; + const resPermission = await getUserPermission( + { + text1: i18n.t('question:download_file', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${filename}`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + 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( + i18n.t('question:message.error.mime_type', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (!fileExtension) { + throw new Error( + i18n.t('question:message.error.file_extension', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (fileExtension && mimeType) { + fileHandleOptions = { + accept: { + [mimeType]: [fileExtension], + }, + }; + } + + showSaveFilePicker( + { + filename, + mimeType, + blob, + }, + snackMethods + ); + return true; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_save_file', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('core:message.error.initiate_download', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const deployAt = async (data, isFromExtension) => { + const requiredFields = [ + 'name', + 'description', + 'tags', + 'creationBytes', + 'amount', + 'assetId', + 'type', + ]; + + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field] && data[field] !== 0) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + try { + const resDeployAt = await _deployAt( + { + name: data.name, + description: data.description, + tags: data.tags, + creationBytes: data.creationBytes, + amount: data.amount, + assetId: data.assetId, + atType: data.type, + }, + isFromExtension + ); + return resDeployAt; + } catch (error) { + throw new Error( + error?.message || + i18n.t('group:message.error.group_join', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getUserWallet = async (data, isFromExtension, appInfo) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const isGateway = await isRunningGateway(); + + if (data?.coin === 'ARRR' && isGateway) + throw new Error( + i18n.t('question:message.error.gateway_wallet_local_node', { + token: 'ARRR', + postProcess: 'capitalizeFirstChar', + }) + ); + + const value = + (await getPermission( + `qAPPAutoGetUserWallet-${appInfo?.name}-${data.coin}` + )) || false; + let skip = false; + if (value) { + skip = true; + } + + let resPermission; + + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.get_wallet_info', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `coin: ${data.coin}`, + checkbox1: { + value: true, + label: i18n.t('question:always_retrieve_wallet', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + + if (resPermission) { + setPermission( + `qAPPAutoGetUserWallet-${appInfo?.name}-${data.coin}`, + checkbox1 + ); + } + + if (accepted || skip) { + let coin = data.coin; + let userWallet = {}; + let arrrAddress = ''; + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const arrrSeed58 = parsedData.arrrSeed58; + if (coin === 'ARRR') { + const bodyToString = arrrSeed58; + const url = await createEndpoint(`/crosschain/arrr/walletaddress`); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + arrrAddress = res; + } + switch (coin) { + case 'QORT': + userWallet['address'] = address; + userWallet['publickey'] = parsedData.publicKey; + break; + case 'BTC': + userWallet['address'] = parsedData.btcAddress; + userWallet['publickey'] = parsedData.btcPublicKey; + break; + case 'LTC': + userWallet['address'] = parsedData.ltcAddress; + userWallet['publickey'] = parsedData.ltcPublicKey; + break; + case 'DOGE': + userWallet['address'] = parsedData.dogeAddress; + userWallet['publickey'] = parsedData.dogePublicKey; + break; + case 'DGB': + userWallet['address'] = parsedData.dgbAddress; + userWallet['publickey'] = parsedData.dgbPublicKey; + break; + case 'RVN': + userWallet['address'] = parsedData.rvnAddress; + userWallet['publickey'] = parsedData.rvnPublicKey; + break; + case 'ARRR': + await checkArrrSyncStatus(parsedData.arrrSeed58); + userWallet['address'] = arrrAddress; + break; + default: + break; + } + return userWallet; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getWalletBalance = async ( + data, + bypassPermission?: boolean, + isFromExtension?: boolean, + appInfo?: any +) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const isGateway = await isRunningGateway(); + + if (data?.coin === 'ARRR' && isGateway) + throw new Error( + i18n.t('question:message.error.gateway_balance_local_node', { + token: 'ARRR', + postProcess: 'capitalizeFirstChar', + }) + ); + + const value = + (await getPermission( + `qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}` + )) || false; + let skip = false; + if (value) { + skip = true; + } + let resPermission; + + if (!bypassPermission && !skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.fetch_balance', { + coin: data.coin, // TODO highlight coin in the modal + postProcess: 'capitalizeFirstChar', + }), + checkbox1: { + value: true, + label: i18n.t('question:always_retrieve_balance', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + if (resPermission) { + setPermission( + `qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}`, + checkbox1 + ); + } + if (accepted || bypassPermission || skip) { + let coin = data.coin; + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + if (coin === 'QORT') { + let qortAddress = address; + try { + const url = await createEndpoint(`/addresses/balance/${qortAddress}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_wallet', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + let _url = ``; + let _body = null; + switch (coin) { + case 'BTC': + _url = await createEndpoint(`/crosschain/btc/walletbalance`); + + _body = parsedData.btcPublicKey; + break; + case 'LTC': + _url = await createEndpoint(`/crosschain/ltc/walletbalance`); + _body = parsedData.ltcPublicKey; + break; + case 'DOGE': + _url = await createEndpoint(`/crosschain/doge/walletbalance`); + _body = parsedData.dogePublicKey; + break; + case 'DGB': + _url = await createEndpoint(`/crosschain/dgb/walletbalance`); + _body = parsedData.dgbPublicKey; + break; + case 'RVN': + _url = await createEndpoint(`/crosschain/rvn/walletbalance`); + _body = parsedData.rvnPublicKey; + break; + case 'ARRR': + await checkArrrSyncStatus(parsedData.arrrSeed58); + _url = await createEndpoint(`/crosschain/arrr/walletbalance`); + _body = parsedData.arrrSeed58; + break; + default: + break; + } + try { + const response = await fetch(_url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: _body, + }); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + if (isNaN(Number(res))) { + throw new Error( + i18n.t('question:message.error.fetch_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + } else { + return (Number(res) / 1e8).toFixed(8); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +const getPirateWallet = async (arrrSeed58) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.error.gateway_retrieve_balance', { + token: 'PIRATECHAIN', + postProcess: 'capitalizeFirstChar', + }) + ); + } + const bodyToString = arrrSeed58; + await checkArrrSyncStatus(bodyToString); + const url = await createEndpoint(`/crosschain/arrr/walletaddress`); + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res; +}; + +export const getUserWalletFunc = async (coin) => { + let userWallet = {}; + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + switch (coin) { + case 'QORT': + userWallet['address'] = address; + userWallet['publickey'] = parsedData.publicKey; + break; + case 'BTC': + case 'BITCOIN': + userWallet['address'] = parsedData.btcAddress; + userWallet['publickey'] = parsedData.btcPublicKey; + break; + case 'LTC': + case 'LITECOIN': + userWallet['address'] = parsedData.ltcAddress; + userWallet['publickey'] = parsedData.ltcPublicKey; + break; + case 'DOGE': + case 'DOGECOIN': + userWallet['address'] = parsedData.dogeAddress; + userWallet['publickey'] = parsedData.dogePublicKey; + break; + case 'DGB': + case 'DIGIBYTE': + userWallet['address'] = parsedData.dgbAddress; + userWallet['publickey'] = parsedData.dgbPublicKey; + break; + case 'RVN': + case 'RAVENCOIN': + userWallet['address'] = parsedData.rvnAddress; + userWallet['publickey'] = parsedData.rvnPublicKey; + break; + case 'ARRR': + case 'PIRATECHAIN': + const arrrAddress = await getPirateWallet(parsedData.arrrSeed58); + userWallet['address'] = arrrAddress; + break; + default: + break; + } + return userWallet; +}; + +export const getUserWalletInfo = async (data, isFromExtension, appInfo) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (data?.coin === 'ARRR') { + throw new Error( + i18n.t('question:message.error.token_not_supported', { + token: 'ARRR', + postProcess: 'capitalizeFirstChar', + }) + ); + } + const value = + (await getPermission(`getUserWalletInfo-${appInfo?.name}-${data.coin}`)) || + false; + let skip = false; + if (value) { + skip = true; + } + let resPermission; + + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.get_wallet_info', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `coin: ${data.coin}`, + checkbox1: { + value: true, + label: i18n.t('question:always_retrieve_wallet', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + + if (resPermission) { + setPermission(`getUserWalletInfo-${appInfo?.name}-${data.coin}`, checkbox1); + } + + if (accepted || skip) { + let coin = data.coin; + let walletKeys = await getUserWalletFunc(coin); + + const _url = await createEndpoint( + `/crosschain/` + data.coin.toLowerCase() + `/addressinfos` + ); + let _body = { xpub58: walletKeys['publickey'] }; + try { + const response = await fetch(_url, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(_body), + }); + if (!response?.ok) + throw new Error( + i18n.t('question:message.error.fetch_wallet_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_wallet', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getUserWalletTransactions = async ( + data, + isFromExtension, + appInfo +) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const value = + (await getPermission( + `getUserWalletTransactions-${appInfo?.name}-${data.coin}` + )) || false; + let skip = false; + if (value) { + skip = true; + } + let resPermission; + + if (!skip) { + resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.get_wallet_transactions', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `coin: ${data.coin}`, + checkbox1: { + value: true, + label: i18n.t('question:always_retrieve_wallet_transactions', { + postProcess: 'capitalizeFirstChar', + }), + }, + }, + isFromExtension + ); + } + const { accepted = false, checkbox1 = false } = resPermission || {}; + + if (resPermission) { + setPermission( + `getUserWalletTransactions-${appInfo?.name}-${data.coin}`, + checkbox1 + ); + } + + if (accepted || skip) { + const coin = data.coin; + const walletKeys = await getUserWalletFunc(coin); + let publicKey; + if (data?.coin === 'ARRR') { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + publicKey = parsedData.arrrSeed58; + } else { + publicKey = walletKeys['publickey']; + } + + const _url = await createEndpoint( + `/crosschain/` + data.coin.toLowerCase() + `/wallettransactions` + ); + const _body = publicKey; + try { + const response = await fetch(_url, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: _body, + }); + if (!response?.ok) + throw new Error( + i18n.t('question:message.error.fetch_wallet_transactions', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_wallet_transactions', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getCrossChainServerInfo = async (data) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const _url = `/crosschain/` + data.coin.toLowerCase() + `/serverinfos`; + try { + const url = await createEndpoint(_url); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_generic', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res.servers; + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.server_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getTxActivitySummary = async (data) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const coin = data.coin; + const url = `/crosschain/txactivity?foreignBlockchain=${coin}`; // No apiKey here + + try { + const endpoint = await createEndpoint(url); + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_generic', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res; // Return full response here + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.transaction_activity_summary', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getForeignFee = async (data) => { + const requiredFields = ['coin', 'type']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, type } = data; + const url = `/crosschain/${coin.toLowerCase()}/${type}`; + + try { + const endpoint = await createEndpoint(url); + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_generic', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res; // Return full response here + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.get_foreign_fee', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +function calculateRateFromFee(totalFee, sizeInBytes) { + const fee = (totalFee / sizeInBytes) * 1000; + return fee.toFixed(0); +} + +export const updateForeignFee = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['coin', 'type', 'value']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, type, value } = data; + + const text3 = + type === 'feerequired' + ? i18n.t('question:sats', { + amount: value, + postProcess: 'capitalizeFirstChar', + }) + : i18n.t('question:sats_per_kb', { + amount: value, + postProcess: 'capitalizeFirstChar', + }); + const text4 = + type === 'feerequired' + ? i18n.t('question:message.generic.calculate_fee', { + amount: value, + rate: calculateRateFromFee(value, 300), + postProcess: 'capitalizeFirstChar', + }) + : ''; + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.update_foreign_fee', { + postProcess: 'capitalizeFirstChar', + }), + text2: `type: ${type === 'feerequired' ? 'unlocking' : 'locking'}`, + text3: i18n.t('question:value', { + value: text3, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:coin', { + coin: coin, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const url = `/crosschain/${coin.toLowerCase()}/update${type}`; + const valueStringified = JSON.stringify(+value); + + const endpoint = await createEndpoint(url); + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: valueStringified, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.update_foreign_fee', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + if (res?.error && res?.message) { + throw new Error(res.message); + } + return res; // Return full response here +}; + +export const getServerConnectionHistory = async (data) => { + const requiredFields = ['coin']; + const missingFields: string[] = []; + + // Validate required fields + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const coin = data.coin.toLowerCase(); + const url = `/crosschain/${coin.toLowerCase()}/serverconnectionhistory`; + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_connection_history', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return full response here + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.fetch_connection_history', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const setCurrentForeignServer = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['coin']; + const missingFields: string[] = []; + + // Validate required fields + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, host, port, type } = data; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.set_current_server', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:server_type', { + type: type, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:server_host', { + host: host, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:coin', { + coin: coin, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const body = { + hostName: host, + port: port, + connectionType: type, + }; + + const url = `/crosschain/${coin.toLowerCase()}/setcurrentserver`; + + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.server_current_set', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response +}; + +export const addForeignServer = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['coin']; + const missingFields: string[] = []; + + // Validate required fields + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, host, port, type } = data; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.server_add', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:server_type', { + type: type, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:server_host', { + host: host, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:coin', { + coin: coin, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const body = { + hostName: host, + port: port, + connectionType: type, + }; + + const url = `/crosschain/${coin.toLowerCase()}/addserver`; + + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.server_current_add', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response +}; + +export const removeForeignServer = async (data, isFromExtension) => { + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const requiredFields = ['coin']; + const missingFields: string[] = []; + + // Validate required fields + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const { coin, host, port, type } = data; + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.server_remove', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:server_type', { + type: type, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:server_host', { + host: host, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:coin', { + coin: coin, + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const body = { + hostName: host, + port: port, + connectionType: type, + }; + + const url = `/crosschain/${coin.toLowerCase()}/removeserver`; + + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.server_remove', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response +}; + +export const getDaySummary = async () => { + const url = `/admin/summary`; // Simplified endpoint URL + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.retrieve_summary', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.retrieve_summary', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getNodeInfo = async () => { + const url = `/admin/info`; // Simplified endpoint URL + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.node_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.node_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getNodeStatus = async () => { + const url = `/admin/status`; // Simplified endpoint URL + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL + const response = await fetch(endpoint, { + method: 'GET', + headers: { + Accept: '*/*', + }, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.node_status', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + if (res?.error && res?.message) { + throw new Error(res.message); + } + + return res; // Return the full response + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.node_status', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const getArrrSyncStatus = async () => { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const arrrSeed = parsedData.arrrSeed58; + const url = `/crosschain/arrr/syncstatus`; // Simplified endpoint URL + + try { + const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Accept: '*/*', + }, + body: arrrSeed, + }); + + let res; + + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + + return res; // Return the full response + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.retrieve_sync_status', { + token: 'ARRR', + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const sendCoin = async (data, isFromExtension) => { + const requiredFields = ['coin', 'amount']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (!data?.destinationAddress && !data?.recipient) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: 'recipient', + postProcess: 'capitalizeFirstChar', + }) + ); + } + let checkCoin = data.coin; + const wallet = await getSaveWallet(); + const address = wallet.address0; + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const isGateway = await isRunningGateway(); + + if (checkCoin !== 'QORT' && isGateway) + throw new Error( + i18n.t('question:message.error.gateway_non_qort_local_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + if (checkCoin === 'QORT') { + // Params: data.coin, data.recipient, data.amount, data.fee + // TODO: prompt user to send. If they confirm, call `POST /crosschain/:coin/send`, or for QORT, broadcast a PAYMENT transaction + // then set the response string from the core to the `response` variable (defined above) + // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}` + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + + const url = await createEndpoint(`/addresses/balance/${address}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + let walletBalance; + try { + walletBalance = await response.clone().json(); + } catch (e) { + walletBalance = await response.text(); + } + if (isNaN(Number(walletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'QORT', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const transformDecimals = (Number(walletBalance) * QORT_DECIMALS).toFixed( + 0 + ); + const walletBalanceDecimals = Number(transformDecimals); + const amountDecimals = Number(amount) * QORT_DECIMALS; + const fee: number = await sendQortFee(); + if (amountDecimals + fee * QORT_DECIMALS > walletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (amount <= 0) { + const errorMsg = i18n.t('core:message.error.invalid_amount', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + if (recipient.length === 0) { + const errorMsg = i18n.t('question:message.error.empty_receiver', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${amount} ${checkCoin}`, + fee: fee, + confirmCheckbox: true, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const makePayment = await sendCoinFunc( + { amount, password: null, receiver: recipient }, + true + ); + return makePayment.res?.data; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'BTC') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.btcPrivateKey; + const feePerByte = data.fee ? data.fee : btcFeePerByte; + + const btcWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + + if (isNaN(Number(btcWalletBalance))) { + throw new Error( + i18n.t('question:message.error.fetch_balance_token', { + token: 'BTC', + postProcess: 'capitalizeFirstChar', + }) + ); + } + const btcWalletBalanceDecimals = Number(btcWalletBalance); + const btcAmountDecimals = Number(amount); + const fee = feePerByte * 500; // default 0.00050000 + if (btcAmountDecimals + fee > btcWalletBalanceDecimals) { + throw new Error( + i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} BTC`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + bitcoinAmount: amount, + feePerByte: feePerByte, + }; + const url = await createEndpoint(`/crosschain/btc/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'LTC') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.ltcPrivateKey; + const feePerByte = data.fee ? data.fee : ltcFeePerByte; + const ltcWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + + if (isNaN(Number(ltcWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'LTC', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const ltcWalletBalanceDecimals = Number(ltcWalletBalance); + const ltcAmountDecimals = Number(amount); + const fee = feePerByte * 1000; // default 0.00030000 + if (ltcAmountDecimals + fee > ltcWalletBalanceDecimals) { + throw new Error( + i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} LTC`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const url = await createEndpoint(`/crosschain/ltc/send`); + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + litecoinAmount: amount, + feePerByte: feePerByte, + }; + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'DOGE') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.dogePrivateKey; + const feePerByte = data.fee ? data.fee : dogeFeePerByte; + const dogeWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + if (isNaN(Number(dogeWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'DOGE', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const dogeWalletBalanceDecimals = Number(dogeWalletBalance); + const dogeAmountDecimals = Number(amount); + const fee = feePerByte * 5000; // default 0.05000000 + if (dogeAmountDecimals + fee > dogeWalletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:to_recipient', { + recipient: recipient, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} DOGE`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + dogecoinAmount: amount, + feePerByte: feePerByte, + }; + const url = await createEndpoint(`/crosschain/doge/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'DGB') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.dbgPrivateKey; + const feePerByte = data.fee ? data.fee : dgbFeePerByte; + const dgbWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + if (isNaN(Number(dgbWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'DGB', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const dgbWalletBalanceDecimals = Number(dgbWalletBalance); + const dgbAmountDecimals = Number(amount); + const fee = feePerByte * 500; // default 0.00005000 + if (dgbAmountDecimals + fee > dgbWalletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: `To: ${recipient}`, + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} DGB`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + digibyteAmount: amount, + feePerByte: feePerByte, + }; + const url = await createEndpoint(`/crosschain/dgb/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'RVN') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const xprv58 = parsedData.rvnPrivateKey; + const feePerByte = data.fee ? data.fee : rvnFeePerByte; + const rvnWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + if (isNaN(Number(rvnWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'RVN', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const rvnWalletBalanceDecimals = Number(rvnWalletBalance); + const rvnAmountDecimals = Number(amount); + const fee = feePerByte * 500; // default 0.00562500 + if (rvnAmountDecimals + fee > rvnWalletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: `To: ${recipient}`, + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} RVN`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + xprv58: xprv58, + receivingAddress: recipient, + ravencoinAmount: amount, + feePerByte: feePerByte, + }; + const url = await createEndpoint(`/crosschain/rvn/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } else if (checkCoin === 'ARRR') { + const amount = Number(data.amount); + const recipient = data?.recipient || data.destinationAddress; + const memo = data?.memo; + const arrrWalletBalance = await getWalletBalance({ coin: checkCoin }, true); + + if (isNaN(Number(arrrWalletBalance))) { + const errorMsg = i18n.t('question:message.error.fetch_balance_token', { + token: 'ARR', + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const arrrWalletBalanceDecimals = Number(arrrWalletBalance); + const arrrAmountDecimals = Number(amount); + const fee = 0.0001; + if (arrrAmountDecimals + fee > arrrWalletBalanceDecimals) { + const errorMsg = i18n.t('question:message.error.insufficient_funds', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.send_coins', { + postProcess: 'capitalizeFirstChar', + }), + text2: `To: ${recipient}`, + highlightedText: `${amount} ${checkCoin}`, + foreignFee: `${fee} ARRR`, + }, + isFromExtension + ); + const { accepted } = resPermission; + + if (accepted) { + const opts = { + entropy58: parsedData.arrrSeed58, + receivingAddress: recipient, + arrrAmount: amount, + memo: memo, + }; + const url = await createEndpoint(`/crosschain/arrr/send`); + + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(opts), + }); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.send', { + postProcess: 'capitalizeFirstChar', + }) + ); + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } +}; + +function calculateFeeFromRate(feePerKb, sizeInBytes) { + return (feePerKb / 1000) * sizeInBytes; +} + +const getBuyingFees = async (foreignBlockchain) => { + const ticker = sellerForeignFee[foreignBlockchain].ticker; + if (!ticker) throw new Error('invalid foreign blockchain'); + const unlockFee = await getForeignFee({ + coin: ticker, + type: 'feerequired', + }); + const lockFee = await getForeignFee({ + coin: ticker, + type: 'feekb', + }); + return { + ticker: ticker, + lock: { + sats: lockFee, + fee: lockFee / QORT_DECIMALS, + }, + unlock: { + sats: unlockFee, + fee: unlockFee / QORT_DECIMALS, + feePerKb: +calculateRateFromFee(+unlockFee, 300) / QORT_DECIMALS, + }, + }; +}; + +export const createBuyOrder = async (data, isFromExtension) => { + const requiredFields = ['crosschainAtInfo', 'foreignBlockchain']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const isGateway = await isRunningGateway(); + const foreignBlockchain = data.foreignBlockchain; + const atAddresses = data.crosschainAtInfo?.map( + (order) => order.qortalAtAddress + ); + + const atPromises = atAddresses.map((atAddress) => + requestQueueGetAtAddresses.enqueue(async () => { + const url = await createEndpoint(`/crosschain/trade/${atAddress}`); + const resAddress = await fetch(url); + const resData = await resAddress.json(); + if (foreignBlockchain !== resData?.foreignBlockchain) { + throw new Error( + i18n.t('core:message.error.same_foreign_blockchain', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + return resData; + }) + ); + + const crosschainAtInfo = await Promise.all(atPromises); + + try { + const buyingFees = await getBuyingFees(foreignBlockchain); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.buy_order', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:permission.buy_order_quantity', { + quantity: atAddresses?.length, + postProcess: 'capitalizeFirstChar', + }), + text3: i18n.t('question:permission.buy_order_ticker', { + qort_amount: crosschainAtInfo?.reduce((latest, cur) => { + return latest + +cur?.qortAmount; + }, 0), + foreign_amount: roundUpToDecimals( + crosschainAtInfo?.reduce((latest, cur) => { + return latest + +cur?.expectedForeignAmount; + }, 0) + ), + ticker: buyingFees.ticker, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('auth:node.using_public_gateway', { + gateway: isGateway, + postProcess: 'capitalizeFirstChar', + }), + fee: '', + html: ` +
+ + +
+
${i18n.t('question:total_unlocking_fee', { + postProcess: 'capitalizeFirstChar', + })}
+
${(+buyingFees?.unlock?.fee * atAddresses?.length)?.toFixed(8)} ${buyingFees.ticker}
+
+ ${i18n.t('question:permission.buy_order_fee_estimation', { + quantity: atAddresses?.length, + fee: buyingFees?.unlock?.feePerKb?.toFixed(8), + ticker: buyingFees.ticker, + postProcess: 'capitalizeFirstChar', + })} +
+
${i18n.t('question:total_locking_fee', { + postProcess: 'capitalizeFirstChar', + })}
+
${i18n.t('question:permission.buy_order_per_kb', { + fee: +buyingFees?.lock.fee.toFixed(8), + ticker: buyingFees.ticker, + postProcess: 'capitalizeFirstChar', + })} +
+
+
+`, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const resBuyOrder = await createBuyOrderTx({ + crosschainAtInfo, + isGateway, + foreignBlockchain, + }); + return resBuyOrder; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.buy_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +const cancelTradeOfferTradeBot = async (body, keyPair) => { + const txn = new DeleteTradeOffer().createTransaction(body); + const url = await createEndpoint(`/crosschain/tradeoffer`); + const bodyToString = JSON.stringify(txn); + + const deleteTradeBotResponse = await fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + + if (!deleteTradeBotResponse.ok) { + throw new Error( + i18n.t('question:message.error.update_tradebot', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + const unsignedTxn = await deleteTradeBotResponse.text(); + const signedTxnBytes = await signTradeBotTransaction(unsignedTxn, keyPair); + const signedBytes = Base58.encode(signedTxnBytes); + + let res; + try { + res = await processTransactionVersion2(signedBytes); + } catch (error) { + return { + error: i18n.t('question:message.error.cancel_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + failedTradeBot: { + atAddress: body.atAddress, + creatorAddress: body.creatorAddress, + }, + }; + } + if (res?.error) { + return { + error: i18n.t('question:message.error.cancel_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + failedTradeBot: { + atAddress: body.atAddress, + creatorAddress: body.creatorAddress, + }, + }; + } + if (res?.signature) { + return res; + } else { + throw new Error( + i18n.t('question:message.error.cancel_sell_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; +const findFailedTradebot = async (createBotCreationTimestamp, body) => { + //wait 5 secs + const wallet = await getSaveWallet(); + const address = wallet.address0; + await new Promise((res) => { + setTimeout(() => { + res(null); + }, 5000); + }); + const url = await createEndpoint( + `/crosschain/tradebot?foreignBlockchain=LITECOIN` + ); + + const tradeBotsReponse = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await tradeBotsReponse.json(); + const latestItem2 = data + .filter((item) => item.creatorAddress === address) + .sort((a, b) => b.timestamp - a.timestamp)[0]; + const latestItem = data + .filter( + (item) => + item.creatorAddress === address && + +item.foreignAmount === +body.foreignAmount + ) + .sort((a, b) => b.timestamp - a.timestamp)[0]; + if ( + latestItem && + createBotCreationTimestamp - latestItem.timestamp <= 5000 && + createBotCreationTimestamp > latestItem.timestamp // Ensure latestItem's timestamp is before createBotCreationTimestamp + ) { + return latestItem; + } else { + return null; + } +}; +const tradeBotCreateRequest = async (body, keyPair) => { + const txn = new TradeBotCreateRequest().createTransaction(body); + const url = await createEndpoint(`/crosschain/tradebot/create`); + const bodyToString = JSON.stringify(txn); + + const unsignedTxnResponse = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: bodyToString, + }); + if (!unsignedTxnResponse.ok) + throw new Error( + i18n.t('question:message.error.create_tradebot', { + postProcess: 'capitalizeFirstChar', + }) + ); + const createBotCreationTimestamp = Date.now(); + const unsignedTxn = await unsignedTxnResponse.text(); + const signedTxnBytes = await signTradeBotTransaction(unsignedTxn, keyPair); + const signedBytes = Base58.encode(signedTxnBytes); + + let res; + try { + res = await processTransactionVersion2(signedBytes); + } catch (error) { + const findFailedTradeBot = await findFailedTradebot( + createBotCreationTimestamp, + body + ); + return { + error: i18n.t('question:message.error.create_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + failedTradeBot: findFailedTradeBot, + }; + } + + if (res?.signature) { + return res; + } else { + throw new Error( + i18n.t('question:message.error.create_sell_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const createSellOrder = async (data, isFromExtension) => { + const requiredFields = ['qortAmount', 'foreignBlockchain', 'foreignAmount']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const parsedForeignAmount = Number(data.foreignAmount)?.toFixed(8); + + const receivingAddress = await getUserWalletFunc(data.foreignBlockchain); + try { + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.sell_order', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:permission.order_detail', { + qort_amount: data.qortAmount, + foreign_amount: parsedForeignAmount, + ticker: data.foreignBlockchain, + postProcess: 'capitalizeFirstChar', + }), + fee: '0.02', + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const userPublicKey = parsedData.publicKey; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + const response = await tradeBotCreateRequest( + { + creatorPublicKey: userPublicKey, + qortAmount: parseFloat(data.qortAmount), + fundingQortAmount: parseFloat(data.qortAmount) + 0.01, + foreignBlockchain: data.foreignBlockchain, + foreignAmount: parseFloat(parsedForeignAmount), + tradeTimeout: 120, + receivingAddress: receivingAddress.address, + }, + keyPair + ); + + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.submit_sell_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const cancelSellOrder = async (data, isFromExtension) => { + const requiredFields = ['atAddress']; + const missingFields: string[] = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const url = await createEndpoint(`/crosschain/trade/${data.atAddress}`); + const resAddress = await fetch(url); + const resData = await resAddress.json(); + + if (!resData?.qortalAtAddress) + throw new Error( + i18n.t('question:message.error.at_info', { + postProcess: 'capitalizeFirstChar', + }) + ); + + try { + const fee = await getFee('MESSAGE'); + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.cancel_sell_order', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:permission.order_detail', { + qort_amount: resData.qortAmount, + foreign_amount: resData.expectedForeignAmount, + ticker: resData.foreignBlockchain, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const userPublicKey = parsedData.publicKey; + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + }; + const response = await cancelTradeOfferTradeBot( + { + creatorPublicKey: userPublicKey, + atAddress: data.atAddress, + }, + keyPair + ); + + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + } catch (error) { + throw new Error( + error?.message || + i18n.t('question:message.error.submit_sell_order', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const openNewTab = async (data, isFromExtension) => { + const requiredFields = ['qortalLink']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const res = extractComponents(data.qortalLink); + if (res) { + const { service, name, identifier, path } = res; + if (!service && !name) + throw new Error( + i18n.t('auth:message.error.invalid_qortal_link', { + postProcess: 'capitalizeFirstChar', + }) + ); + executeEvent('addTab', { data: { service, name, identifier, path } }); + executeEvent('open-apps-mode', {}); + return true; + } else { + throw new Error( + i18n.t('auth:message.error.invalid_qortal_link', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const adminAction = async (data, isFromExtension) => { + const requiredFields = ['type']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + // For actions that require a value, check for 'value' field + const actionsRequiringValue = [ + 'addpeer', + 'removepeer', + 'forcesync', + 'addmintingaccount', + 'removemintingaccount', + ]; + if (actionsRequiringValue.includes(data.type.toLowerCase()) && !data.value) { + missingFields.push('value'); + } + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const isGateway = await isRunningGateway(); + if (isGateway) { + throw new Error( + i18n.t('question:message.generic.no_action_public_node', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + let apiEndpoint = ''; + let method = 'GET'; // Default method + let includeValueInBody = false; + switch (data.type.toLowerCase()) { + case 'stop': + apiEndpoint = await createEndpoint('/admin/stop'); + break; + case 'restart': + apiEndpoint = await createEndpoint('/admin/restart'); + break; + case 'bootstrap': + apiEndpoint = await createEndpoint('/admin/bootstrap'); + break; + case 'addmintingaccount': + apiEndpoint = await createEndpoint('/admin/mintingaccounts'); + method = 'POST'; + includeValueInBody = true; + break; + case 'removemintingaccount': + apiEndpoint = await createEndpoint('/admin/mintingaccounts'); + method = 'DELETE'; + includeValueInBody = true; + break; + case 'forcesync': + apiEndpoint = await createEndpoint('/admin/forcesync'); + method = 'POST'; + includeValueInBody = true; + break; + case 'addpeer': + apiEndpoint = await createEndpoint('/peers'); + method = 'POST'; + includeValueInBody = true; + break; + case 'removepeer': + apiEndpoint = await createEndpoint('/peers'); + method = 'DELETE'; + includeValueInBody = true; + break; + default: + throw new Error( + i18n.t('question:message.error.unknown_admin_action_type', { + type: data.type, + postProcess: 'capitalizeFirstChar', + }) + ); + } + // Prepare the permission prompt text + let permissionText = i18n.t('question:permission.perform_admin_action', { + type: data.type, + postProcess: 'capitalizeFirstChar', + }); + + if (data.value) { + permissionText += + ' ' + + i18n.t('question:permission.perform_admin_action_with_value', { + value: data.value, + postProcess: 'capitalizeFirstChar', + }); + } + + const resPermission = await getUserPermission( + { + text1: permissionText, + }, + isFromExtension + ); + + const { accepted } = resPermission; + + if (accepted) { + // Set up options for the API call + const options: RequestInit = { + method: method, + headers: {}, + }; + if (includeValueInBody) { + options.headers['Content-Type'] = 'text/plain'; + options.body = data.value; + } + const response = await fetch(apiEndpoint, options); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.perform_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + + let res; + try { + res = await response.clone().json(); + } catch (e) { + res = await response.text(); + } + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const signTransaction = async (data, isFromExtension) => { + const requiredFields = ['unsignedBytes']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const shouldProcess = data?.process || false; + const _url = await createEndpoint( + '/transactions/decode?ignoreValidityChecks=false' + ); + + const _body = data.unsignedBytes; + const response = await fetch(_url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: _body, + }); + + if (!response.ok) + throw new Error( + i18n.t('question:message.error.decode_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + const decodedData = await response.json(); + const resPermission = await getUserPermission( + { + text1: shouldProcess + ? i18n.t('question:permission.sign_process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + : i18n.t('question:permission.sign_transaction', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t( + 'question:message.generic.read_transaction_carefully', + { postProcess: 'capitalizeFirstChar' } + ), + text2: `Tx type: ${decodedData.type}`, + json: decodedData, + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (accepted) { + let urlConverted = await createEndpoint('/transactions/convert'); + + const responseConverted = await fetch(urlConverted, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: data.unsignedBytes, + }); + 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, + }; + const convertedBytes = await responseConverted.text(); + const txBytes = Base58.decode(data.unsignedBytes); + const _arbitraryBytesBuffer = Object.keys(txBytes).map(function (key) { + return txBytes[key]; + }); + const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer); + const txByteSigned = Base58.decode(convertedBytes); + const _bytesForSigningBuffer = Object.keys(txByteSigned).map( + function (key) { + return txByteSigned[key]; + } + ); + const bytesForSigningBuffer = new Uint8Array(_bytesForSigningBuffer); + const signature = nacl.sign.detached( + bytesForSigningBuffer, + keyPair.privateKey + ); + const signedBytes = utils.appendBuffer(arbitraryBytesBuffer, signature); + const signedBytesToBase58 = Base58.encode(signedBytes); + if (!shouldProcess) { + return signedBytesToBase58; + } + const res = await processTransactionVersion2(signedBytesToBase58); + if (!res?.signature) + throw new Error( + res?.message || + i18n.t('question:message.error.process_transaction', { + postProcess: 'capitalizeFirstChar', + }) + ); + return res; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +const missingFieldsFunc = (data, requiredFields) => { + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } +}; + +const encode = (value) => encodeURIComponent(value.trim()); // Helper to encode values +const buildQueryParams = (data) => { + const allowedParams = [ + 'name', + 'service', + 'identifier', + 'mimeType', + 'fileName', + 'encryptionType', + 'key', + ]; + return Object.entries(data) + .map(([key, value]) => { + if ( + value === undefined || + value === null || + value === false || + !allowedParams.includes(key) + ) + return null; // Skip null, undefined, or false + if (typeof value === 'boolean') return `${key}=${value}`; // Handle boolean values + return `${key}=${encode(value)}`; // Encode other values + }) + .filter(Boolean) // Remove null values + .join('&'); // Join with `&` +}; +export const createAndCopyEmbedLink = async (data, isFromExtension) => { + const requiredFields = ['type']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + switch (data.type) { + case 'POLL': { + missingFieldsFunc(data, ['type', 'name']); + + const queryParams = [ + `name=${encode(data.name)}`, + data.ref ? `ref=${encode(data.ref)}` : null, // Add only if ref exists + ] + .filter(Boolean) // Remove null values + .join('&'); // Join with `&` + const link = `qortal://use-embed/POLL?${queryParams}`; + try { + await navigator.clipboard.writeText(link); + } catch (error) { + throw new Error( + i18n.t('question:message.error.copy_clipboard', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + return link; + } + case 'IMAGE': + case 'ATTACHMENT': { + missingFieldsFunc(data, ['type', 'name', 'service', 'identifier']); + if (data?.encryptionType === 'private' && !data?.key) { + throw new Error( + i18n.t('question:message.generic.provide_key_shared_link', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const queryParams = buildQueryParams(data); + + const link = `qortal://use-embed/${data.type}?${queryParams}`; + + try { + await navigator.clipboard.writeText(link); + } catch (error) { + throw new Error( + i18n.t('question:message.error.copy_clipboard', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + return link; + } + + default: + throw new Error( + i18n.t('question:message.error.invalid_type', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const registerNameRequest = async (data, isFromExtension) => { + const requiredFields = ['name']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const fee = await getFee('REGISTER_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.register_name', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: data.name, + text2: data?.description, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const name = data.name; + const description = data?.description || ''; + const response = await registerName({ name, description }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const updateNameRequest = async (data, isFromExtension) => { + const requiredFields = ['newName', 'oldName']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const oldName = data.oldName; + const newName = data.newName; + const description = data?.description || ''; + const fee = await getFee('UPDATE_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.register_name', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: data.newName, + text2: data?.description, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await updateName({ oldName, newName, description }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const leaveGroupRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const fee = await getFee('LEAVE_GROUP'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.leave_group', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: `${groupInfo.groupName}`, + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await leaveGroup({ groupId }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const inviteToGroupRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'inviteTime', 'inviteeAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.inviteeAddress; + const inviteTime = data?.inviteTime; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('GROUP_INVITE'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.invite', { + invitee: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await inviteToGroup({ + groupId, + qortalAddress, + inviteTime, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const kickFromGroupRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + const reason = data?.reason; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('GROUP_KICK'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.kick', { + partecipant: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await kickFromGroup({ + groupId, + qortalAddress, + rBanReason: reason, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const banFromGroupRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + const rBanTime = data?.banTime; + const reason = data?.reason; + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('GROUP_BAN'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.ban', { + partecipant: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await banFromGroup({ + groupId, + qortalAddress, + rBanTime, + rBanReason: reason, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const cancelGroupBanRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('CANCEL_GROUP_BAN'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.cancel_ban', { + partecipant: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await cancelBan({ + groupId, + qortalAddress, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const addGroupAdminRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('ADD_GROUP_ADMIN'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.add_admin', { + invitee: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await makeAdmin({ + groupId, + qortalAddress, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const removeGroupAdminRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('REMOVE_GROUP_ADMIN'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.remove_admin', { + partecipant: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await removeAdmin({ + groupId, + qortalAddress, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const cancelGroupInviteRequest = async (data, isFromExtension) => { + const requiredFields = ['groupId', 'qortalAddress']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = data.groupId; + const qortalAddress = data?.qortalAddress; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(qortalAddress); + + const fee = await getFee('CANCEL_GROUP_INVITE'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.cancel_group_invite', { + invitee: displayInvitee || qortalAddress, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + + const { accepted } = resPermission; + + if (accepted) { + const response = await cancelInvitationToGroup({ + groupId, + qortalAddress, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const createGroupRequest = async (data, isFromExtension) => { + const requiredFields = [ + 'approvalThreshold', + 'groupId', + 'groupName', + 'maxBlock', + 'minBlock', + 'qortalAddress', + 'type', + ]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupName = data.groupName; + const description = data?.description || ''; + const type = +data.type; + const approvalThreshold = +data?.approvalThreshold; + const minBlock = +data?.minBlock; + const maxBlock = +data.maxBlock; + + const fee = await getFee('CREATE_GROUP'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.create_group', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await createGroup({ + groupName, + groupDescription: description, + groupType: type, + groupApprovalThreshold: approvalThreshold, + minBlock, + maxBlock, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const updateGroupRequest = async (data, isFromExtension) => { + const requiredFields = [ + 'groupId', + 'newOwner', + 'type', + 'approvalThreshold', + 'minBlock', + 'maxBlock', + ]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const groupId = +data.groupId; + const newOwner = data.newOwner; + const description = data?.description || ''; + const type = +data.type; + const approvalThreshold = +data?.approvalThreshold; + const minBlock = +data?.minBlock; + const maxBlock = +data.maxBlock; + + let groupInfo = null; + try { + const url = await createEndpoint(`/groups/${groupId}`); + const response = await fetch(url); + if (!response.ok) + throw new Error( + i18n.t('question:message.error.fetch_group', { + postProcess: 'capitalizeFirstChar', + }) + ); + + groupInfo = await response.json(); + } catch (error) { + const errorMsg = + (error && error.message) || + i18n.t('question:message.error.no_group_found', { + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const displayInvitee = await getNameInfoForOthers(newOwner); + + const fee = await getFee('CREATE_GROUP'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.update_group', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:permission.update_group_detail', { + owner: displayInvitee || newOwner, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('group:group.group_name', { + name: groupInfo?.groupName, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await updateGroup({ + groupId, + newOwner, + newIsOpen: type, + newDescription: description, + newApprovalThreshold: approvalThreshold, + newMinimumBlockDelay: minBlock, + newMaximumBlockDelay: maxBlock, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const decryptAESGCMRequest = async (data, isFromExtension) => { + const requiredFields = ['encryptedData', 'iv', 'senderPublicKey']; + requiredFields.forEach((field) => { + if (!data[field]) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + }); + + const encryptedData = data.encryptedData; + const iv = data.iv; + const senderPublicKeyBase58 = data.senderPublicKey; + + // Decode keys and IV + const senderPublicKey = Base58.decode(senderPublicKeyBase58); + const resKeyPair = await getKeyPair(); // Assume this retrieves the current user's keypair + const uint8PrivateKey = Base58.decode(resKeyPair.privateKey); + + // Convert ed25519 keys to Curve25519 + const convertedPrivateKey = ed2curve.convertSecretKey(uint8PrivateKey); + const convertedPublicKey = ed2curve.convertPublicKey(senderPublicKey); + + // Generate shared secret + const sharedSecret = new Uint8Array(32); + nacl.lowlevel.crypto_scalarmult( + sharedSecret, + convertedPrivateKey, + convertedPublicKey + ); + + // Derive encryption key + const encryptionKey: Uint8Array = new Sha256() + .process(sharedSecret) + .finish().result; + + // Convert IV and ciphertext from Base64 + const base64ToUint8Array = (base64) => + Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); + const ivUint8Array = base64ToUint8Array(iv); + const ciphertext = base64ToUint8Array(encryptedData); + // Validate IV and key lengths + if (ivUint8Array.length !== 12) { + throw new Error( + i18n.t('question:message.error.invalid_encryption_iv', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + if (encryptionKey.length !== 32) { + throw new Error( + i18n.t('question:message.error.invalid_encryption_key', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + try { + // Decrypt data + const algorithm = { name: 'AES-GCM', iv: ivUint8Array }; + const cryptoKey = await crypto.subtle.importKey( + 'raw', + encryptionKey, + algorithm, + false, + ['decrypt'] + ); + const decryptedArrayBuffer = await crypto.subtle.decrypt( + algorithm, + cryptoKey, + ciphertext + ); + + // Return decrypted data as Base64 + return uint8ArrayToBase64(new Uint8Array(decryptedArrayBuffer)); + } catch (error) { + console.error('Decryption failed:', error); + throw new Error( + i18n.t('question:message.error.decrypt_message', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const sellNameRequest = async (data, isFromExtension) => { + const requiredFields = ['salePrice', 'nameForSale']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const name = data.nameForSale; + const sellPrice = +data.salePrice; + + const validApi = await getBaseApi(); + + const response = await fetch(validApi + '/names/' + name); + const nameData = await response.json(); + if (!nameData) + throw new Error( + i18n.t('auth:message.error.name_not_existing', { + postProcess: 'capitalizeFirstChar', + }) + ); + + if (nameData?.isForSale) + throw new Error( + i18n.t('question:message.error.name_already_for_sale', { + postProcess: 'capitalizeFirstChar', + }) + ); + const fee = await getFee('SELL_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.sell_name_transaction', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t( + 'question:permission.sell_name_transaction_detail', + { + name: name, + price: sellPrice, + postProcess: 'capitalizeFirstChar', + } + ), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await sellName({ + name, + sellPrice, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const cancelSellNameRequest = async (data, isFromExtension) => { + const requiredFields = ['nameForSale']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + const name = data.nameForSale; + const validApi = await getBaseApi(); + + const response = await fetch(validApi + '/names/' + name); + const nameData = await response.json(); + if (!nameData?.isForSale) + throw new Error( + i18n.t('question:message.error.name_not_for_sale', { + postProcess: 'capitalizeFirstChar', + }) + ); + + const fee = await getFee('CANCEL_SELL_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.sell_name_cancel', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:name', { + name: name, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await cancelSellName({ + name, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const buyNameRequest = async (data, isFromExtension) => { + const requiredFields = ['nameForSale']; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (data[field] !== undefined && data[field] !== null) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = i18n.t('question:message.error.missing_fields', { + fields: missingFieldsString, + postProcess: 'capitalizeFirstChar', + }); + throw new Error(errorMsg); + } + + const name = data.nameForSale; + const validApi = await getBaseApi(); + const response = await fetch(validApi + '/names/' + name); + const nameData = await response.json(); + + if (!nameData?.isForSale) + throw new Error( + i18n.t('question:message.error.name_not_for_sale', { + postProcess: 'capitalizeFirstChar', + }) + ); + + const sellerAddress = nameData.owner; + const sellPrice = +nameData.salePrice; + + const fee = await getFee('BUY_NAME'); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.buy_name', { + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:permission.buy_name_detail', { + name: name, + price: sellPrice, + postProcess: 'capitalizeFirstChar', + }), + fee: fee.fee, + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const response = await buyName({ + name, + sellerAddress, + sellPrice, + }); + return response; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; + +export const signForeignFees = async (data, isFromExtension) => { + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.sign_fee', { + postProcess: 'capitalizeFirstChar', + }), + }, + isFromExtension + ); + const { accepted } = resPermission; + if (accepted) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + 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, + }; + + const unsignedFeesUrl = await createEndpoint( + `/crosschain/unsignedfees/${address}` + ); + + const unsignedFeesResponse = await fetch(unsignedFeesUrl); + + const unsignedFees = await unsignedFeesResponse.json(); + + const signedFees = []; + + unsignedFees.forEach((unsignedFee) => { + const unsignedDataDecoded = Base58.decode(unsignedFee.data); + + const signature = nacl.sign.detached( + unsignedDataDecoded, + keyPair.privateKey + ); + + const signedFee = { + timestamp: unsignedFee.timestamp, + data: `${Base58.encode(signature)}`, + atAddress: unsignedFee.atAddress, + fee: unsignedFee.fee, + }; + + signedFees.push(signedFee); + }); + + const signedFeesUrl = await createEndpoint(`/crosschain/signedfees`); + + await fetch(signedFeesUrl, { + method: 'POST', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: `${JSON.stringify(signedFees)}`, + }); + + return true; + } else { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } +}; +export const multiPaymentWithPrivateData = async (data, isFromExtension) => { + const requiredFields = ['payments', 'assetId']; + requiredFields.forEach((field) => { + if (data[field] === undefined || data[field] === null) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + }); + const resKeyPair = await getKeyPair(); + const parsedData = resKeyPair; + const privateKey = parsedData.privateKey; + const userPublicKey = parsedData.publicKey; + const { fee: paymentFee } = await getFee('TRANSFER_ASSET'); + const { fee: arbitraryFee } = await getFee('ARBITRARY'); + + let name = null; + const payments = data.payments; + const assetId = data.assetId; + const pendingTransactions = []; + const pendingAdditionalArbitraryTxs = []; + const additionalArbitraryTxsWithoutPayment = + data?.additionalArbitraryTxsWithoutPayment || []; + let totalAmount = 0; + let fee = 0; + for (const payment of payments) { + const paymentRefId = uid.rnd(); + const requiredFieldsPayment = ['recipient', 'amount']; + + for (const field of requiredFieldsPayment) { + if (!payment[field]) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + + const confirmReceiver = await getNameOrAddress(payment.recipient); + if (confirmReceiver.error) { + throw new Error( + i18n.t('question:message.error.invalid_receiver', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const receiverPublicKey = await getPublicKey(confirmReceiver); + + const amount = +payment.amount.toFixed(8); + + pendingTransactions.push({ + type: 'PAYMENT', + recipientAddress: confirmReceiver, + amount: amount, + paymentRefId, + }); + + fee = fee + +paymentFee; + totalAmount = totalAmount + amount; + + if (payment.arbitraryTxs && payment.arbitraryTxs.length > 0) { + for (const arbitraryTx of payment.arbitraryTxs) { + const requiredFieldsArbitraryTx = ['service', 'identifier', 'base64']; + + for (const field of requiredFieldsArbitraryTx) { + if (!arbitraryTx[field]) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + + if (!name) { + const getName = await getNameInfo(); + if (!getName) + throw new Error( + i18n.t('question:message.error.registered_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + name = getName; + } + + const isValid = isValidBase64WithDecode(arbitraryTx.base64); + if (!isValid) + throw new Error( + i18n.t('core:message.error.invalid_base64', { + postProcess: 'capitalizeFirstChar', + }) + ); + if (!arbitraryTx?.service?.includes('_PRIVATE')) + throw new Error( + i18n.t('question:message.generic.private_service', { + postProcess: 'capitalizeFirstChar', + }) + ); + const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || []; + pendingTransactions.push({ + type: 'ARBITRARY', + identifier: arbitraryTx.identifier, + service: arbitraryTx.service, + base64: arbitraryTx.base64, + description: arbitraryTx?.description || '', + paymentRefId, + publicKeys: [receiverPublicKey, ...additionalPublicKeys], + }); + + fee = fee + +arbitraryFee; + } + } + } + + if ( + additionalArbitraryTxsWithoutPayment && + additionalArbitraryTxsWithoutPayment.length > 0 + ) { + for (const arbitraryTx of additionalArbitraryTxsWithoutPayment) { + const requiredFieldsArbitraryTx = ['service', 'identifier', 'base64']; + + for (const field of requiredFieldsArbitraryTx) { + if (!arbitraryTx[field]) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + + if (!name) { + const getName = await getNameInfo(); + if (!getName) + throw new Error( + i18n.t('question:message.error.registered_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + name = getName; + } + + const isValid = isValidBase64WithDecode(arbitraryTx.base64); + if (!isValid) + throw new Error( + i18n.t('core:message.error.invalid_base64', { + postProcess: 'capitalizeFirstChar', + }) + ); + if (!arbitraryTx?.service?.includes('_PRIVATE')) + throw new Error( + i18n.t('question:message.generic.private_service', { + postProcess: 'capitalizeFirstChar', + }) + ); + const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || []; + pendingAdditionalArbitraryTxs.push({ + type: 'ARBITRARY', + identifier: arbitraryTx.identifier, + service: arbitraryTx.service, + base64: arbitraryTx.base64, + description: arbitraryTx?.description || '', + publicKeys: additionalPublicKeys, + }); + + fee = fee + +arbitraryFee; + } + } + + if (!name) + throw new Error( + i18n.t('question:message.error.registered_name', { + postProcess: 'capitalizeFirstChar', + }) + ); + const balance = await getBalanceInfo(); + + if (+balance < fee) + throw new Error( + i18n.t('question:message.error.insufficient_balance_qort', { + postProcess: 'capitalizeFirstChar', + }) + ); + const assetBalance = await getAssetBalanceInfo(assetId); + const assetInfo = await getAssetInfo(assetId); + if (assetBalance < totalAmount) + throw new Error( + i18n.t('question:message.error.insufficient_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.pay_publish', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:assets_used_pay', { + asset: assetInfo.name, + postProcess: 'capitalizeFirstChar', + }), + html: ` +
+ + + ${pendingTransactions + .filter((item) => item.type === 'PAYMENT') + .map( + (payment) => ` +
+
Recipient: ${ + payment.recipientAddress + }
+
Amount: ${payment.amount}
+
` + ) + .join('')} + ${[...pendingTransactions, ...pendingAdditionalArbitraryTxs] + .filter((item) => item.type === 'ARBITRARY') + .map( + (arbitraryTx) => ` +
+
Service: ${ + arbitraryTx.service + }
+
Name: ${name}
+
Identifier: ${ + arbitraryTx.identifier + }
+
` + ) + .join('')} +
+ + `, + highlightedText: `Total Amount: ${totalAmount}`, + fee: fee, + }, + isFromExtension + ); + + const { accepted, checkbox1 = false } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + // const failedTxs = [] + const paymentsDone = {}; + + const transactionsDone = []; + + for (const transaction of pendingTransactions) { + const type = transaction.type; + + if (type === 'PAYMENT') { + const makePayment = await retryTransaction( + transferAsset, + [ + { + amount: transaction.amount, + assetId, + recipient: transaction.recipientAddress, + }, + ], + true + ); + if (makePayment) { + transactionsDone.push(makePayment?.signature); + if (transaction.paymentRefId) { + paymentsDone[transaction.paymentRefId] = makePayment; + } + } + } else if (type === 'ARBITRARY' && paymentsDone[transaction.paymentRefId]) { + const objectToEncrypt = { + data: transaction.base64, + payment: paymentsDone[transaction.paymentRefId], + }; + + const toBase64 = await retryTransaction( + objectToBase64, + [objectToEncrypt], + true + ); + + if (!toBase64) continue; // Skip if encryption fails + + const encryptDataResponse = await retryTransaction( + encryptDataGroup, + [ + { + data64: toBase64, + publicKeys: transaction.publicKeys, + privateKey, + userPublicKey, + }, + ], + true + ); + + if (!encryptDataResponse) continue; // Skip if encryption fails + + const resPublish = await retryTransaction( + publishData, + [ + { + registeredName: encodeURIComponent(name), + file: encryptDataResponse, + service: transaction.service, + identifier: encodeURIComponent(transaction.identifier), + uploadType: 'file', + description: transaction?.description, + isBase64: true, + apiVersion: 2, + withFee: true, + }, + ], + true + ); + + if (resPublish?.signature) { + transactionsDone.push(resPublish?.signature); + } + } + } + + for (const transaction of pendingAdditionalArbitraryTxs) { + const objectToEncrypt = { + data: transaction.base64, + }; + + const toBase64 = await retryTransaction( + objectToBase64, + [objectToEncrypt], + true + ); + + if (!toBase64) continue; // Skip if encryption fails + + const encryptDataResponse = await retryTransaction( + encryptDataGroup, + [ + { + data64: toBase64, + publicKeys: transaction.publicKeys, + privateKey, + userPublicKey, + }, + ], + true + ); + + if (!encryptDataResponse) continue; // Skip if encryption fails + + const resPublish = await retryTransaction( + publishData, + [ + { + registeredName: encodeURIComponent(name), + file: encryptDataResponse, + service: transaction.service, + identifier: encodeURIComponent(transaction.identifier), + uploadType: 'file', + description: transaction?.description, + isBase64: true, + apiVersion: 2, + withFee: true, + }, + ], + true + ); + + if (resPublish?.signature) { + transactionsDone.push(resPublish?.signature); + } + } + + return transactionsDone; +}; + +export const transferAssetRequest = async (data, isFromExtension) => { + const requiredFields = ['amount', 'assetId', 'recipient']; + requiredFields.forEach((field) => { + if (data[field] === undefined || data[field] === null) { + throw new Error( + i18n.t('question:message.error.missing_fields', { + fields: field, + postProcess: 'capitalizeFirstChar', + }) + ); + } + }); + const amount = data.amount; + const assetId = data.assetId; + const recipient = data.recipient; + + const { fee } = await getFee('TRANSFER_ASSET'); + const balance = await getBalanceInfo(); + + if (+balance < +fee) + throw new Error( + i18n.t('question:message.error.insufficient_balance_qort', { + postProcess: 'capitalizeFirstChar', + }) + ); + const assetBalance = await getAssetBalanceInfo(assetId); + if (assetBalance < amount) + throw new Error( + i18n.t('question:message.error.insufficient_balance', { + postProcess: 'capitalizeFirstChar', + }) + ); + const confirmReceiver = await getNameOrAddress(recipient); + if (confirmReceiver.error) { + throw new Error( + i18n.t('question:message.error.invalid_receiver', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const assetInfo = await getAssetInfo(assetId); + const resPermission = await getUserPermission( + { + text1: i18n.t('question:permission.transfer_asset', { + postProcess: 'capitalizeFirstChar', + }), + text2: i18n.t('question:asset_name', { + asset: assetInfo?.name, + postProcess: 'capitalizeFirstChar', + }), + highlightedText: i18n.t('question:amount_qty', { + quantity: amount, + postProcess: 'capitalizeFirstChar', + }), + fee: fee, + }, + isFromExtension + ); + + const { accepted } = resPermission; + if (!accepted) { + throw new Error( + i18n.t('question:message.generic.user_declined_request', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + const res = await transferAsset({ + amount, + recipient: confirmReceiver, + assetId, + }); + return res; +}; diff --git a/src/qortalRequests/qortalRequests.ts b/src/qortal/qortal-requests.ts similarity index 100% rename from src/qortalRequests/qortalRequests.ts rename to src/qortal/qortal-requests.ts diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 7957dc4..6819842 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -37,27 +37,18 @@ import { getAssetInfo, getPublicKey, transferAsset, -<<<<<<< HEAD -} from '../background'; -import { - getAllUserNames, - getNameInfo, - uint8ArrayToObject, -} from '../backgroundFunctions/encryption'; -======= } from '../background/background.ts'; import { getNameInfo, uint8ArrayToObject } from '../encryption/encryption.ts'; ->>>>>>> 2d01b3e (Create encryption folder and move files) -import { showSaveFilePicker } from '../hooks/useQortalMessageListener'; -import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner'; -import { extractComponents } from '../components/Chat/MessageDisplay'; +import { showSaveFilePicker } from '../hooks/useQortalMessageListener.tsx'; +import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner.tsx'; +import { extractComponents } from '../components/Chat/MessageDisplay.tsx'; import { decryptResource, getGroupAdmins, getPublishesFromAdmins, validateSecretKey, -} from '../components/Group/Group'; -import { QORT_DECIMALS } from '../constants/constants'; +} from '../components/Group/Group.tsx'; +import { QORT_DECIMALS } from '../constants/constants.ts'; import Base58 from '../encryption/Base58.ts'; import ed2curve from '../encryption/ed2curve.ts'; import nacl from '../encryption/nacl-fast.ts'; @@ -73,24 +64,24 @@ import { objectToBase64, uint8ArrayStartsWith, uint8ArrayToBase64, -} from '../qdn/encryption/group-encryption'; +} from '../qdn/encryption/group-encryption.ts'; import { publishData } from '../qdn/publish/publish.ts'; import { getPermission, isRunningGateway, setPermission, } from './qortalRequests.ts'; -import TradeBotCreateRequest from '../transactions/TradeBotCreateRequest'; -import DeleteTradeOffer from '../transactions/TradeBotDeleteRequest'; -import signTradeBotTransaction from '../transactions/signTradeBotTransaction'; -import { createTransaction } from '../transactions/transactions'; -import { executeEvent } from '../utils/events'; -import { fileToBase64 } from '../utils/fileReading'; -import { mimeToExtensionMap } from '../utils/memeTypes'; -import { RequestQueueWithPromise } from '../utils/queue/queue'; -import utils from '../utils/utils'; +import TradeBotCreateRequest from '../transactions/TradeBotCreateRequest.ts'; +import DeleteTradeOffer from '../transactions/TradeBotDeleteRequest.ts'; +import signTradeBotTransaction from '../transactions/signTradeBotTransaction.ts'; +import { createTransaction } from '../transactions/transactions.ts'; +import { executeEvent } from '../utils/events.ts'; +import { fileToBase64 } from '../utils/fileReading/index.ts'; +import { mimeToExtensionMap } from '../utils/memeTypes.ts'; +import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; +import utils from '../utils/utils.ts'; import ShortUniqueId from 'short-unique-id'; -import { isValidBase64WithDecode } from '../utils/decode'; +import { isValidBase64WithDecode } from '../utils/decode.ts'; import i18n from 'i18next'; const uid = new ShortUniqueId({ length: 6 }); From a996015b2055672f92f1a3522369e6ef387ac2ae Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:12:25 +0200 Subject: [PATCH 14/31] Remove CustomSvg (unused) --- src/common/CustomSvg.tsx | 14 -------------- src/components/Embeds/AttachmentEmbed.tsx | 3 +-- src/components/Group/Forum/Thread.tsx | 1 + 3 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 src/common/CustomSvg.tsx diff --git a/src/common/CustomSvg.tsx b/src/common/CustomSvg.tsx deleted file mode 100644 index fc805a9..0000000 --- a/src/common/CustomSvg.tsx +++ /dev/null @@ -1,14 +0,0 @@ -export const CustomSvg = ({ src, color = 'black', size = 24 }) => { - return ( - - {src} - - ); -}; diff --git a/src/components/Embeds/AttachmentEmbed.tsx b/src/components/Embeds/AttachmentEmbed.tsx index d7458a7..adbcd82 100644 --- a/src/components/Embeds/AttachmentEmbed.tsx +++ b/src/components/Embeds/AttachmentEmbed.tsx @@ -226,8 +226,7 @@ export const AttachmentCard = ({ width: '100%', }} > - {' '} - {' '} +
)} {errorMsg && ( diff --git a/src/components/Group/Forum/Thread.tsx b/src/components/Group/Forum/Thread.tsx index bf8682f..a293d5f 100644 --- a/src/components/Group/Forum/Thread.tsx +++ b/src/components/Group/Forum/Thread.tsx @@ -931,6 +931,7 @@ export const Thread = ({ }} > + Date: Sat, 24 May 2025 12:14:06 +0200 Subject: [PATCH 15/31] List all namespaces in useTranslation --- src/App.tsx | 8 +++++++- src/Wallets.tsx | 16 ++++++++++++++-- src/components/Apps/AppInfo.tsx | 8 +++++++- src/components/Apps/AppInfoSnippet.tsx | 8 +++++++- src/components/Apps/AppPublish.tsx | 8 +++++++- src/components/Apps/AppRating.tsx | 8 +++++++- src/components/Apps/AppsCategoryDesktop.tsx | 8 +++++++- src/components/Apps/AppsDesktop.tsx | 8 +++++++- src/components/Apps/AppsDevMode.tsx | 8 +++++++- src/components/Apps/AppsDevModeHome.tsx | 8 +++++++- src/components/Apps/AppsHomeDesktop.tsx | 8 +++++++- src/components/Apps/AppsLibraryDesktop.tsx | 8 +++++++- src/components/Apps/AppsNavBarDesktop.tsx | 8 +++++++- src/components/Apps/AppsPrivate.tsx | 8 +++++++- src/components/BuyQortInformation.tsx | 8 +++++++- src/components/Chat/AdminSpace.tsx | 8 +++++++- src/components/Chat/AdminSpaceInner.tsx | 8 +++++++- src/components/Chat/AnnouncementDiscussion.tsx | 8 +++++++- src/components/Chat/AnnouncementItem.tsx | 8 +++++++- src/components/Chat/AnnouncementList.tsx | 8 +++++++- src/components/Chat/ChatDirect.tsx | 8 +++++++- src/components/Chat/ChatGroup.tsx | 8 +++++++- src/components/Chat/ChatList.tsx | 8 +++++++- src/components/Chat/ChatOptions.tsx | 8 +++++++- src/components/Chat/CreateCommonSecret.tsx | 8 +++++++- src/components/Chat/GroupAnnouncements.tsx | 8 +++++++- src/components/Chat/GroupAvatar.tsx | 16 ++++++++++++++-- src/components/Chat/MentionList.tsx | 8 +++++++- src/components/Chat/MessageItem.tsx | 16 ++++++++++++++-- src/components/Chat/TipTap.tsx | 8 +++++++- src/components/CoreSyncStatus.tsx | 8 +++++++- src/components/Desktop/DesktopFooter.tsx | 8 +++++++- src/components/Desktop/DesktopHeader.tsx | 8 +++++++- src/components/Desktop/DesktopSideBar.tsx | 8 +++++++- src/components/Embeds/AttachmentEmbed.tsx | 8 +++++++- src/components/Embeds/Embed.tsx | 8 +++++++- src/components/Embeds/ImageEmbed.tsx | 8 +++++++- src/components/Embeds/PollEmbed.tsx | 16 ++++++++++++++-- src/components/GeneralNotifications.tsx | 8 +++++++- src/components/GlobalActions/JoinGroup.tsx | 8 +++++++- src/components/Group/AddGroup.tsx | 8 +++++++- src/components/Group/AddGroupList.tsx | 8 +++++++- src/components/Group/BlockedUsersModal.tsx | 8 +++++++- src/components/Group/Forum/GroupMail.tsx | 8 +++++++- src/components/Group/Forum/NewThread.tsx | 8 +++++++- src/components/Group/Forum/Thread.tsx | 8 +++++++- src/components/Group/Group.tsx | 8 +++++++- src/components/Group/GroupInvites.tsx | 8 +++++++- src/components/Group/GroupJoinRequests.tsx | 8 +++++++- src/components/Group/GroupList.tsx | 8 +++++++- src/components/Group/HomeDesktop.tsx | 8 +++++++- src/components/Group/InviteMember.tsx | 8 +++++++- src/components/Group/ListOfBans.tsx | 8 +++++++- src/components/Group/ListOfGroupPromotions.tsx | 8 +++++++- src/components/Group/ListOfInvites.tsx | 8 +++++++- src/components/Group/ListOfJoinRequests.tsx | 8 +++++++- src/components/Group/ListOfMembers.tsx | 8 +++++++- .../Group/ListOfThreadPostsWatched.tsx | 8 +++++++- src/components/Group/ManageMembers.tsx | 8 +++++++- src/components/Group/QMailMessages.tsx | 8 +++++++- src/components/Group/Settings.tsx | 16 ++++++++++++++-- src/components/Group/UserListOfInvites.tsx | 8 +++++++- src/components/Group/WalletsAppWrapper.tsx | 8 +++++++- src/components/MainAvatar.tsx | 16 ++++++++++++++-- src/components/Minting/Minting.tsx | 8 +++++++- src/components/NewUsersCTA.tsx | 8 +++++++- src/components/QMailStatus.tsx | 8 +++++++- src/components/QortPayment.tsx | 8 +++++++- src/components/RegisterName.tsx | 8 +++++++- src/components/Save/Save.tsx | 8 +++++++- src/components/Theme/ThemeManager.tsx | 8 +++++++- src/components/Theme/ThemeSelector.tsx | 8 +++++++- src/components/UserLookup.tsx/UserLookup.tsx | 8 +++++++- src/components/WrapperUserAction.tsx | 16 ++++++++++++++-- src/hooks/useHandlePrivateApps.tsx | 8 +++++++- 75 files changed, 574 insertions(+), 82 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 4fd9057..f58c7c2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -308,7 +308,13 @@ function App() { const [isLoading, setIsLoading] = useState(false); const [isLoadingSendCoin, setIsLoadingSendCoin] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const [ diff --git a/src/Wallets.tsx b/src/Wallets.tsx index e6e4906..c0aa098 100644 --- a/src/Wallets.tsx +++ b/src/Wallets.tsx @@ -53,7 +53,13 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => { const [isOpenSeedModal, setIsOpenSeedModal] = useState(false); const [isLoadingEncryptSeed, setIsLoadingEncryptSeed] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { isShow, onCancel, onOk, show } = useModal(); const { getRootProps, getInputProps } = useDropzone({ @@ -461,7 +467,13 @@ const WalletItem = ({ wallet, updateWalletItem, idx, setSelectedWallet }) => { const [note, setNote] = useState(''); const [isEdit, setIsEdit] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { if (wallet?.name) { diff --git a/src/components/Apps/AppInfo.tsx b/src/components/Apps/AppInfo.tsx index 1c77d25..180dac4 100644 --- a/src/components/Apps/AppInfo.tsx +++ b/src/components/Apps/AppInfo.tsx @@ -37,7 +37,13 @@ export const AppInfo = ({ app, myName }) => { ); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const isSelectedAppPinned = !!sortablePinnedApps?.find( (item) => item?.name === app?.name && item?.service === app?.service diff --git a/src/components/Apps/AppInfoSnippet.tsx b/src/components/Apps/AppInfoSnippet.tsx index a0b8f72..41391dc 100644 --- a/src/components/Apps/AppInfoSnippet.tsx +++ b/src/components/Apps/AppInfoSnippet.tsx @@ -42,7 +42,13 @@ export const AppInfoSnippet = ({ ); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { const [file, setFile] = useState(null); const { show } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [tag1, setTag1] = useState(''); const [tag2, setTag2] = useState(''); const [tag3, setTag3] = useState(''); diff --git a/src/components/Apps/AppRating.tsx b/src/components/Apps/AppRating.tsx index edd628a..b0f8258 100644 --- a/src/components/Apps/AppRating.tsx +++ b/src/components/Apps/AppRating.tsx @@ -20,7 +20,13 @@ export const AppRating = ({ app, myName, ratingCountPosition = 'right' }) => { const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); const hasCalledRef = useRef(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getRating = useCallback(async (name, service) => { try { diff --git a/src/components/Apps/AppsCategoryDesktop.tsx b/src/components/Apps/AppsCategoryDesktop.tsx index 4c4a541..3752353 100644 --- a/src/components/Apps/AppsCategoryDesktop.tsx +++ b/src/components/Apps/AppsCategoryDesktop.tsx @@ -45,7 +45,13 @@ export const AppsCategoryDesktop = ({ const [searchValue, setSearchValue] = useState(''); const virtuosoRef = useRef(null); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const categoryList = useMemo(() => { if (category?.id === 'all') return availableQapps; diff --git a/src/components/Apps/AppsDesktop.tsx b/src/components/Apps/AppsDesktop.tsx index 71472db..369adc9 100644 --- a/src/components/Apps/AppsDesktop.tsx +++ b/src/components/Apps/AppsDesktop.tsx @@ -51,7 +51,13 @@ export const AppsDesktop = ({ const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); const { showTutorial } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const myApp = useMemo(() => { return availableQapps.find( diff --git a/src/components/Apps/AppsDevMode.tsx b/src/components/Apps/AppsDevMode.tsx index 89358f5..7d90e0b 100644 --- a/src/components/Apps/AppsDevMode.tsx +++ b/src/components/Apps/AppsDevMode.tsx @@ -47,7 +47,13 @@ export const AppsDevMode = ({ const [categories, setCategories] = useState([]); const iframeRefs = useRef({}); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { setTimeout(() => { diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index f9ade5d..88218ca 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -41,7 +41,13 @@ export const AppsDevModeHome = ({ const [domain, setDomain] = useState('127.0.0.1'); const [port, setPort] = useState(''); const [selectedPreviewFile, setSelectedPreviewFile] = useState(null); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { isShow, onCancel, onOk, show, message } = useModal(); const { openSnackGlobal, diff --git a/src/components/Apps/AppsHomeDesktop.tsx b/src/components/Apps/AppsHomeDesktop.tsx index 9c7d0d3..137e6b3 100644 --- a/src/components/Apps/AppsHomeDesktop.tsx +++ b/src/components/Apps/AppsHomeDesktop.tsx @@ -28,7 +28,13 @@ export const AppsHomeDesktop = ({ }) => { const [qortalUrl, setQortalUrl] = useState(''); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const openQortalUrl = () => { try { diff --git a/src/components/Apps/AppsLibraryDesktop.tsx b/src/components/Apps/AppsLibraryDesktop.tsx index d953bff..d844dbf 100644 --- a/src/components/Apps/AppsLibraryDesktop.tsx +++ b/src/components/Apps/AppsLibraryDesktop.tsx @@ -105,7 +105,13 @@ export const AppsLibraryDesktop = ({ const [searchValue, setSearchValue] = useState(''); const virtuosoRef = useRef(null); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const officialApps = useMemo(() => { return availableQapps.filter( diff --git a/src/components/Apps/AppsNavBarDesktop.tsx b/src/components/Apps/AppsNavBarDesktop.tsx index e6d2ea2..616c09f 100644 --- a/src/components/Apps/AppsNavBarDesktop.tsx +++ b/src/components/Apps/AppsNavBarDesktop.tsx @@ -76,7 +76,13 @@ export const AppsNavBarDesktop = ({ disableBack }) => { ); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isNewTabWindow, setIsNewTabWindow] = useState(false); const tabsRef = useRef(null); const [anchorEl, setAnchorEl] = useState(null); diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index f24d15a..56b33d9 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -73,7 +73,13 @@ export const AppsPrivate = ({ myName, myAddress }) => { const [memberGroups] = useAtom(memberGroupsAtom); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const myGroupsPrivate = useMemo(() => { return memberGroups?.filter( diff --git a/src/components/BuyQortInformation.tsx b/src/components/BuyQortInformation.tsx index e10f535..c3519fa 100644 --- a/src/components/BuyQortInformation.tsx +++ b/src/components/BuyQortInformation.tsx @@ -27,7 +27,13 @@ import { useTranslation } from 'react-i18next'; export const BuyQortInformation = ({ balance }) => { const [isOpen, setIsOpen] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const openBuyQortInfoFunc = useCallback( (e) => { diff --git a/src/components/Chat/AdminSpace.tsx b/src/components/Chat/AdminSpace.tsx index 276369a..62a8832 100644 --- a/src/components/Chat/AdminSpace.tsx +++ b/src/components/Chat/AdminSpace.tsx @@ -19,7 +19,13 @@ export const AdminSpace = ({ isOwner, }) => { const [isMoved, setIsMoved] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { if (hide) { diff --git a/src/components/Chat/AdminSpaceInner.tsx b/src/components/Chat/AdminSpaceInner.tsx index 97e3fd1..7dbb705 100644 --- a/src/components/Chat/AdminSpaceInner.tsx +++ b/src/components/Chat/AdminSpaceInner.tsx @@ -74,7 +74,13 @@ export const AdminSpaceInner = ({ const [isLoadingPublishKey, setIsLoadingPublishKey] = useState(false); const { show, setInfoSnackCustom, setOpenSnackGlobal } = useContext(QORTAL_APP_CONTEXT); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getAdminGroupSecretKey = useCallback(async () => { try { diff --git a/src/components/Chat/AnnouncementDiscussion.tsx b/src/components/Chat/AnnouncementDiscussion.tsx index e0da144..b248735 100644 --- a/src/components/Chat/AnnouncementDiscussion.tsx +++ b/src/components/Chat/AnnouncementDiscussion.tsx @@ -40,7 +40,13 @@ export const AnnouncementDiscussion = ({ isPrivate, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isSending, setIsSending] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isFocusedParent, setIsFocusedParent] = useState(false); diff --git a/src/components/Chat/AnnouncementItem.tsx b/src/components/Chat/AnnouncementItem.tsx index 98ebe11..a20f58d 100644 --- a/src/components/Chat/AnnouncementItem.tsx +++ b/src/components/Chat/AnnouncementItem.tsx @@ -18,7 +18,13 @@ export const AnnouncementItem = ({ myName, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [commentLength, setCommentLength] = useState(0); const getNumberOfComments = useCallback(async () => { diff --git a/src/components/Chat/AnnouncementList.tsx b/src/components/Chat/AnnouncementList.tsx index ba2892e..eea6c83 100644 --- a/src/components/Chat/AnnouncementList.tsx +++ b/src/components/Chat/AnnouncementList.tsx @@ -20,7 +20,13 @@ export const AnnouncementList = ({ myName, }) => { const [messages, setMessages] = useState(initialMessages); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { cache.clearAll(); diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx index 36681e9..0af54a6 100644 --- a/src/components/Chat/ChatDirect.tsx +++ b/src/components/Chat/ChatDirect.tsx @@ -42,7 +42,13 @@ export const ChatDirect = ({ setMobileViewModeKeepOpen, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue(); const [isFocusedParent, setIsFocusedParent] = useState(false); const [onEditMessage, setOnEditMessage] = useState(null); diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index e6b75d0..6fada40 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -96,7 +96,13 @@ export const ChatGroup = ({ const [, forceUpdate] = useReducer((x) => x + 1, 0); const lastReadTimestamp = useRef(null); const handleUpdateRef = useRef(null); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getTimestampEnterChat = async (selectedGroup) => { try { diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 96abf7a..1a47778 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -181,7 +181,13 @@ export const ChatList = ({ }, []); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { const queryString = admins.map((name) => `name=${name}`).join('&'); diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx index 18170cd..7e1e5f9 100644 --- a/src/components/Chat/GroupAnnouncements.tsx +++ b/src/components/Chat/GroupAnnouncements.tsx @@ -153,7 +153,13 @@ export const GroupAnnouncements = ({ editorRef.current = editorInstance; }; const [, forceUpdate] = useReducer((x) => x + 1, 0); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const triggerRerender = () => { forceUpdate(); // Trigger re-render by updating the state diff --git a/src/components/Chat/GroupAvatar.tsx b/src/components/Chat/GroupAvatar.tsx index ecc7c86..f7322bd 100644 --- a/src/components/Chat/GroupAvatar.tsx +++ b/src/components/Chat/GroupAvatar.tsx @@ -33,7 +33,13 @@ export const GroupAvatar = ({ const [avatarFile, setAvatarFile] = useState(null); const [tempAvatar, setTempAvatar] = useState(null); const { show } = useContext(QORTAL_APP_CONTEXT); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [anchorEl, setAnchorEl] = useState(null); const [isLoading, setIsLoading] = useState(false); // Handle child element click to open Popover @@ -263,7 +269,13 @@ const PopoverComp = ({ myName, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [selectedIndex, setSelectedIndex] = useState(0); diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index a429846..f570ba2 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -170,7 +170,13 @@ export const MessageItem = memo( }, [message?.id]); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( <> @@ -612,7 +618,13 @@ export const MessageItem = memo( export const ReplyPreview = ({ message, isEdit = false }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { if (editor && setEditorRef) { diff --git a/src/components/CoreSyncStatus.tsx b/src/components/CoreSyncStatus.tsx index 09a50ed..345678f 100644 --- a/src/components/CoreSyncStatus.tsx +++ b/src/components/CoreSyncStatus.tsx @@ -13,7 +13,13 @@ export const CoreSyncStatus = () => { const [coreInfos, setCoreInfos] = useState({}); const [isUsingGateway, setIsUsingGateway] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); useEffect(() => { diff --git a/src/components/Desktop/DesktopFooter.tsx b/src/components/Desktop/DesktopFooter.tsx index ac567ec..3441cf6 100644 --- a/src/components/Desktop/DesktopFooter.tsx +++ b/src/components/Desktop/DesktopFooter.tsx @@ -67,7 +67,13 @@ export const DesktopFooter = ({ }) => { const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); if (hide) return; return ( diff --git a/src/components/Desktop/DesktopHeader.tsx b/src/components/Desktop/DesktopHeader.tsx index c82eae0..0428462 100644 --- a/src/components/Desktop/DesktopHeader.tsx +++ b/src/components/Desktop/DesktopHeader.tsx @@ -84,7 +84,13 @@ export const DesktopHeader = ({ }) => { const [value, setValue] = useState(0); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { const { name, service, identifier } = resourceData; diff --git a/src/components/Embeds/Embed.tsx b/src/components/Embeds/Embed.tsx index 6be25f7..0038830 100644 --- a/src/components/Embeds/Embed.tsx +++ b/src/components/Embeds/Embed.tsx @@ -63,7 +63,13 @@ export const Embed = ({ embedLink }) => { const [parsedData, setParsedData] = useState(null); const setBlobs = useSetAtom(blobControllerAtom); const [selectedGroupId] = useAtom(selectedGroupIdAtom); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const resourceData = useMemo(() => { const parsedDataOnTheFly = parseQortalLink(embedLink); if ( diff --git a/src/components/Embeds/ImageEmbed.tsx b/src/components/Embeds/ImageEmbed.tsx index a7b6534..ab0548d 100644 --- a/src/components/Embeds/ImageEmbed.tsx +++ b/src/components/Embeds/ImageEmbed.tsx @@ -30,7 +30,13 @@ export const ImageCard = ({ encryptionType, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isOpen, setIsOpen] = useState(true); const [height, setHeight] = useState('400px'); diff --git a/src/components/Embeds/PollEmbed.tsx b/src/components/Embeds/PollEmbed.tsx index 6d7a510..eb3e304 100644 --- a/src/components/Embeds/PollEmbed.tsx +++ b/src/components/Embeds/PollEmbed.tsx @@ -40,7 +40,13 @@ export const PollCard = ({ const { show, userInfo } = useContext(QORTAL_APP_CONTEXT); const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const handleVote = async () => { const fee = await getFee('VOTE_ON_POLL'); @@ -379,7 +385,13 @@ const PollResults = ({ votes }) => { ...votes?.voteCounts?.map((option) => option.voteCount) ); const options = votes?.voteCounts; - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( diff --git a/src/components/GeneralNotifications.tsx b/src/components/GeneralNotifications.tsx index 3e23637..53e85a8 100644 --- a/src/components/GeneralNotifications.tsx +++ b/src/components/GeneralNotifications.tsx @@ -32,7 +32,13 @@ export const GeneralNotifications = ({ address }) => { setAnchorEl(event.currentTarget); }; - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); return ( diff --git a/src/components/GlobalActions/JoinGroup.tsx b/src/components/GlobalActions/JoinGroup.tsx index 95e95e6..aca7e1f 100644 --- a/src/components/GlobalActions/JoinGroup.tsx +++ b/src/components/GlobalActions/JoinGroup.tsx @@ -29,7 +29,13 @@ export const JoinGroup = () => { const [isLoadingInfo, setIsLoadingInfo] = useState(false); const [isOpen, setIsOpen] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isLoadingJoinGroup, setIsLoadingJoinGroup] = useState(false); const handleJoinGroup = async (e) => { diff --git a/src/components/Group/AddGroup.tsx b/src/components/Group/AddGroup.tsx index ec01443..3815d0d 100644 --- a/src/components/Group/AddGroup.tsx +++ b/src/components/Group/AddGroup.tsx @@ -97,7 +97,13 @@ export const AddGroup = ({ address, open, setOpen }) => { setMaxBlock(event.target.value as string); }; - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const handleCreateGroup = async () => { diff --git a/src/components/Group/AddGroupList.tsx b/src/components/Group/AddGroupList.tsx index 8cad787..db4460c 100644 --- a/src/components/Group/AddGroupList.tsx +++ b/src/components/Group/AddGroupList.tsx @@ -42,7 +42,13 @@ export const AddGroupList = ({ setInfoSnack, setOpenSnack }) => { const { show } = useContext(QORTAL_APP_CONTEXT); const [memberGroups] = useAtom(memberGroupsAtom); const setTxList = useSetAtom(txListAtom); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [groups, setGroups] = useState([]); const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index c8145b3..8cdd358 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -28,7 +28,13 @@ import { useTranslation } from 'react-i18next'; export const BlockedUsersModal = () => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isOpenBlockedModal, setIsOpenBlockedModal] = useAtom( isOpenBlockedModalAtom ); diff --git a/src/components/Group/Forum/GroupMail.tsx b/src/components/Group/Forum/GroupMail.tsx index afa6e91..ba4b620 100644 --- a/src/components/Group/Forum/GroupMail.tsx +++ b/src/components/Group/Forum/GroupMail.tsx @@ -73,7 +73,13 @@ export const GroupMail = ({ const anchorElInstanceFilter = useRef(null); const [tempPublishedList, setTempPublishedList] = useState([]); const dataPublishes = useRef({}); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const [isLoading, setIsLoading] = useState(false); const groupIdRef = useRef(null); diff --git a/src/components/Group/Forum/NewThread.tsx b/src/components/Group/Forum/NewThread.tsx index 9749480..f89e2aa 100644 --- a/src/components/Group/Forum/NewThread.tsx +++ b/src/components/Group/Forum/NewThread.tsx @@ -144,7 +144,13 @@ export const NewThread = ({ setPostReply, isPrivate, }: NewMessageProps) => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { show } = useContext(QORTAL_APP_CONTEXT); const [isOpen, setIsOpen] = useState(false); const [value, setValue] = useState(''); diff --git a/src/components/Group/Forum/Thread.tsx b/src/components/Group/Forum/Thread.tsx index a293d5f..762db5d 100644 --- a/src/components/Group/Forum/Thread.tsx +++ b/src/components/Group/Forum/Thread.tsx @@ -115,7 +115,13 @@ export const Thread = ({ const [isLoading, setIsLoading] = useState(true); const [postReply, setPostReply] = useState(null); const [hasLastPage, setHasLastPage] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); // Update: Use a new ref for the scrollable container const threadContainerRef = useRef(null); diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 368dd15..8f312ec 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -444,7 +444,13 @@ export const Group = ({ const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false); const groupsOwnerNamesRef = useRef({}); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [groupsProperties, setGroupsProperties] = useAtom(groupsPropertiesAtom); const setGroupsOwnerNames = useSetAtom(groupsOwnerNamesAtom); diff --git a/src/components/Group/GroupInvites.tsx b/src/components/Group/GroupInvites.tsx index 96f375c..8bad077 100644 --- a/src/components/Group/GroupInvites.tsx +++ b/src/components/Group/GroupInvites.tsx @@ -18,7 +18,13 @@ export const GroupInvites = ({ myAddress, setOpenAddGroup }) => { const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]); const [isExpanded, setIsExpanded] = useState(false); const [loading, setLoading] = useState(true); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const getJoinRequests = async () => { diff --git a/src/components/Group/GroupJoinRequests.tsx b/src/components/Group/GroupJoinRequests.tsx index 8f617a1..1310174 100644 --- a/src/components/Group/GroupJoinRequests.tsx +++ b/src/components/Group/GroupJoinRequests.tsx @@ -28,7 +28,13 @@ export const GroupJoinRequests = ({ setDesktopViewMode, }) => { const [isExpanded, setIsExpanded] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [groupsWithJoinRequests, setGroupsWithJoinRequests] = useState([]); const [loading, setLoading] = useState(true); const [txList] = useAtom(txListAtom); diff --git a/src/components/Group/GroupList.tsx b/src/components/Group/GroupList.tsx index 156d744..e928331 100644 --- a/src/components/Group/GroupList.tsx +++ b/src/components/Group/GroupList.tsx @@ -49,7 +49,13 @@ export const GroupList = ({ myAddress, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isRunningPublicNode] = useAtom(isRunningPublicNodeAtom); return ( diff --git a/src/components/Group/HomeDesktop.tsx b/src/components/Group/HomeDesktop.tsx index a5cf85e..f148be1 100644 --- a/src/components/Group/HomeDesktop.tsx +++ b/src/components/Group/HomeDesktop.tsx @@ -31,7 +31,13 @@ export const HomeDesktop = ({ const [checked1, setChecked1] = useState(false); const [checked2, setChecked2] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); useEffect(() => { diff --git a/src/components/Group/InviteMember.tsx b/src/components/Group/InviteMember.tsx index 8a562d0..8ac8d93 100644 --- a/src/components/Group/InviteMember.tsx +++ b/src/components/Group/InviteMember.tsx @@ -10,7 +10,13 @@ export const InviteMember = ({ groupId, setInfoSnack, setOpenSnack, show }) => { const [value, setValue] = useState(''); const [expiryTime, setExpiryTime] = useState('259200'); const [isLoadingInvite, setIsLoadingInvite] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const inviteMember = async () => { try { diff --git a/src/components/Group/ListOfBans.tsx b/src/components/Group/ListOfBans.tsx index 122ce27..fc84cc9 100644 --- a/src/components/Group/ListOfBans.tsx +++ b/src/components/Group/ListOfBans.tsx @@ -56,7 +56,13 @@ export const ListOfBans = ({ groupId, setInfoSnack, setOpenSnack, show }) => { const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const listRef = useRef(null); const [isLoadingUnban, setIsLoadingUnban] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getInvites = async (groupId) => { try { diff --git a/src/components/Group/ListOfGroupPromotions.tsx b/src/components/Group/ListOfGroupPromotions.tsx index 01fbed3..c077475 100644 --- a/src/components/Group/ListOfGroupPromotions.tsx +++ b/src/components/Group/ListOfGroupPromotions.tsx @@ -91,7 +91,13 @@ export const ListOfGroupPromotions = () => { const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const listRef = useRef(null); const rowVirtualizer = useVirtualizer({ count: promotions.length, diff --git a/src/components/Group/ListOfInvites.tsx b/src/components/Group/ListOfInvites.tsx index 037dddc..3758863 100644 --- a/src/components/Group/ListOfInvites.tsx +++ b/src/components/Group/ListOfInvites.tsx @@ -60,7 +60,13 @@ export const ListOfInvites = ({ const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const [isLoadingCancelInvite, setIsLoadingCancelInvite] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const listRef = useRef(null); const getInvites = async (groupId) => { diff --git a/src/components/Group/ListOfJoinRequests.tsx b/src/components/Group/ListOfJoinRequests.tsx index 5fbfe9e..2a65cfe 100644 --- a/src/components/Group/ListOfJoinRequests.tsx +++ b/src/components/Group/ListOfJoinRequests.tsx @@ -64,7 +64,13 @@ export const ListOfJoinRequests = ({ const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const listRef = useRef(null); const [isLoadingAccept, setIsLoadingAccept] = useState(false); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getInvites = async (groupId) => { try { diff --git a/src/components/Group/ListOfMembers.tsx b/src/components/Group/ListOfMembers.tsx index c57e501..1d23b44 100644 --- a/src/components/Group/ListOfMembers.tsx +++ b/src/components/Group/ListOfMembers.tsx @@ -42,7 +42,13 @@ const ListOfMembers = ({ const [isLoadingMakeAdmin, setIsLoadingMakeAdmin] = useState(false); const [isLoadingRemoveAdmin, setIsLoadingRemoveAdmin] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const listRef = useRef(null); const handlePopoverOpen = (event, index) => { diff --git a/src/components/Group/ListOfThreadPostsWatched.tsx b/src/components/Group/ListOfThreadPostsWatched.tsx index 0868a53..8a3c3d2 100644 --- a/src/components/Group/ListOfThreadPostsWatched.tsx +++ b/src/components/Group/ListOfThreadPostsWatched.tsx @@ -14,7 +14,13 @@ import { useTranslation } from 'react-i18next'; export const ListOfThreadPostsWatched = () => { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getPosts = async () => { try { diff --git a/src/components/Group/ManageMembers.tsx b/src/components/Group/ManageMembers.tsx index e87b002..b8c7052 100644 --- a/src/components/Group/ManageMembers.tsx +++ b/src/components/Group/ManageMembers.tsx @@ -71,7 +71,13 @@ export const ManageMembers = ({ setValue(newValue); }; const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { show } = useContext(QORTAL_APP_CONTEXT); const setTxList = useSetAtom(txListAtom); diff --git a/src/components/Group/QMailMessages.tsx b/src/components/Group/QMailMessages.tsx index f6490e0..93d96cb 100644 --- a/src/components/Group/QMailMessages.tsx +++ b/src/components/Group/QMailMessages.tsx @@ -54,7 +54,13 @@ export const QMailMessages = ({ userName, userAddress }) => { const [loading, setLoading] = useState(true); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const getMails = useCallback(async () => { try { diff --git a/src/components/Group/Settings.tsx b/src/components/Group/Settings.tsx index 78db9bc..ef6d0cb 100644 --- a/src/components/Group/Settings.tsx +++ b/src/components/Group/Settings.tsx @@ -87,7 +87,13 @@ export const Settings = ({ open, setOpen, rawWallet }) => { const [checked, setChecked] = useState(false); const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const handleChange = (event: ChangeEvent) => { setChecked(event.target.checked); @@ -235,7 +241,13 @@ const ExportPrivateKey = ({ rawWallet }) => { const [isOpen, setIsOpen] = useState(false); const { setOpenSnackGlobal, setInfoSnackCustom } = useContext(QORTAL_APP_CONTEXT); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const exportPrivateKeyFunc = async () => { try { diff --git a/src/components/Group/UserListOfInvites.tsx b/src/components/Group/UserListOfInvites.tsx index 2717af9..50cbb5b 100644 --- a/src/components/Group/UserListOfInvites.tsx +++ b/src/components/Group/UserListOfInvites.tsx @@ -61,7 +61,13 @@ export const UserListOfInvites = ({ const [invites, setInvites] = useState([]); const [isLoading, setIsLoading] = useState(false); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [popoverAnchor, setPopoverAnchor] = useState(null); // Track which list item the popover is anchored to const [openPopoverIndex, setOpenPopoverIndex] = useState(null); // Track which list item has the popover open const listRef = useRef(null); diff --git a/src/components/Group/WalletsAppWrapper.tsx b/src/components/Group/WalletsAppWrapper.tsx index ee9885a..990544d 100644 --- a/src/components/Group/WalletsAppWrapper.tsx +++ b/src/components/Group/WalletsAppWrapper.tsx @@ -15,7 +15,13 @@ import { useAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; export const WalletsAppWrapper = () => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const iframeRef = useRef(null); const [isOpen, setIsOpen] = useState(false); const [navigationController, setNavigationController] = useAtom( diff --git a/src/components/MainAvatar.tsx b/src/components/MainAvatar.tsx index b458145..9f6945e 100644 --- a/src/components/MainAvatar.tsx +++ b/src/components/MainAvatar.tsx @@ -45,7 +45,13 @@ export const MainAvatar = ({ myName, balance, setOpenSnack, setInfoSnack }) => { const open = Boolean(anchorEl); const id = open ? 'avatar-img' : undefined; - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const checkIfAvatarExists = async () => { try { @@ -258,7 +264,13 @@ const PopoverComp = ({ myName, }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); return ( { const { show: showKey, message } = useModal(); const { isShow: isShowNext, onOk, show: showNext } = useModal(); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [info, setInfo] = useState(null); const [names, setNames] = useState({}); const [accountInfos, setAccountInfos] = useState({}); diff --git a/src/components/NewUsersCTA.tsx b/src/components/NewUsersCTA.tsx index 94606d1..d444717 100644 --- a/src/components/NewUsersCTA.tsx +++ b/src/components/NewUsersCTA.tsx @@ -3,7 +3,13 @@ import { Spacer } from '../common/Spacer'; import { useTranslation } from 'react-i18next'; export const NewUsersCTA = ({ balance }) => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); if (balance === undefined || +balance > 0) return null; diff --git a/src/components/QMailStatus.tsx b/src/components/QMailStatus.tsx index 45e51c8..f0b51f3 100644 --- a/src/components/QMailStatus.tsx +++ b/src/components/QMailStatus.tsx @@ -8,7 +8,13 @@ import { useTranslation } from 'react-i18next'; import { useAtom } from 'jotai'; export const QMailStatus = () => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const theme = useTheme(); const [lastEnteredTimestamp, setLastEnteredTimestamp] = useAtom( diff --git a/src/components/QortPayment.tsx b/src/components/QortPayment.tsx index 1907686..f31c7c0 100644 --- a/src/components/QortPayment.tsx +++ b/src/components/QortPayment.tsx @@ -7,7 +7,13 @@ import { useTranslation } from 'react-i18next'; export const QortPayment = ({ balance, show, onSuccess, defaultPaymentTo }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [paymentTo, setPaymentTo] = useState(defaultPaymentTo); const [paymentAmount, setPaymentAmount] = useState(0); const [paymentPassword, setPaymentPassword] = useState(''); diff --git a/src/components/RegisterName.tsx b/src/components/RegisterName.tsx index 2b5b224..eb988ff 100644 --- a/src/components/RegisterName.tsx +++ b/src/components/RegisterName.tsx @@ -51,7 +51,13 @@ export const RegisterName = ({ ); const [nameFee, setNameFee] = useState(null); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const checkIfNameExisits = async (name) => { if (!name?.trim()) { setIsNameAvailable(Availability.NULL); diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index cebccf8..3c6a89b 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -84,7 +84,13 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { const [anchorEl, setAnchorEl] = useState(null); const { show } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const hasChanged = useMemo(() => { const newChanges = { diff --git a/src/components/Theme/ThemeManager.tsx b/src/components/Theme/ThemeManager.tsx index 69c63c9..f6b1551 100644 --- a/src/components/Theme/ThemeManager.tsx +++ b/src/components/Theme/ThemeManager.tsx @@ -82,7 +82,13 @@ export default function ThemeManager() { }); const [currentTab, setCurrentTab] = useState('light'); const nameInputRef = useRef(null); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { if (openEditor && nameInputRef.current) { diff --git a/src/components/Theme/ThemeSelector.tsx b/src/components/Theme/ThemeSelector.tsx index dd0dbba..6c25e3c 100644 --- a/src/components/Theme/ThemeSelector.tsx +++ b/src/components/Theme/ThemeSelector.tsx @@ -5,7 +5,13 @@ import DarkModeIcon from '@mui/icons-material/DarkMode'; import { useTranslation } from 'react-i18next'; const ThemeSelector = () => { - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const { themeMode, toggleTheme } = useThemeContext(); return ( diff --git a/src/components/UserLookup.tsx/UserLookup.tsx b/src/components/UserLookup.tsx/UserLookup.tsx index 3be4ef6..bd52b2a 100644 --- a/src/components/UserLookup.tsx/UserLookup.tsx +++ b/src/components/UserLookup.tsx/UserLookup.tsx @@ -49,7 +49,13 @@ function formatAddress(str) { export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [nameOrAddress, setNameOrAddress] = useState(''); const [inputValue, setInputValue] = useState(''); const { results, isLoading } = useNameSearch(inputValue); diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index 490333d..caaee97 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -14,7 +14,13 @@ import { useTranslation } from 'react-i18next'; export const WrapperUserAction = ({ children, address, name, disabled }) => { const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const [isRunningPublicNode] = useAtom(isRunningPublicNodeAtom); const [anchorEl, setAnchorEl] = useState(null); @@ -177,7 +183,13 @@ const BlockUser = ({ address, name, handleClose }) => { const { isUserBlocked, addToBlockList, removeBlockFromList } = useContext(QORTAL_APP_CONTEXT); const theme = useTheme(); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); useEffect(() => { if (!address) return; diff --git a/src/hooks/useHandlePrivateApps.tsx b/src/hooks/useHandlePrivateApps.tsx index 3876130..1065502 100644 --- a/src/hooks/useHandlePrivateApps.tsx +++ b/src/hooks/useHandlePrivateApps.tsx @@ -22,7 +22,13 @@ export const useHandlePrivateApps = () => { } = useContext(QORTAL_APP_CONTEXT); const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom); const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom); - const { t } = useTranslation(['auth', 'core', 'group']); + const { t } = useTranslation([ + 'auth', + 'core', + 'group', + 'question', + 'tutorial', + ]); const openApp = async ( privateAppProperties, From 702e18a33476c9304c5111818a943b653f5762a6 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:19:11 +0200 Subject: [PATCH 16/31] Rename files and folders (uniform the case) --- src/App.tsx | 2 +- src/Wallets.tsx | 2 +- src/components/Chat/ChatDirect.tsx | 2 +- src/components/Chat/ChatGroup.tsx | 2 +- src/components/Group/Group.tsx | 2 +- src/main.tsx | 4 +- src/{ => messaging}/MessageQueueContext.tsx | 0 ...ackground.tsx => MessagesToBackground.tsx} | 44 ++++++++++++++----- ...enerator.ts => randomSentenceGenerator.ts} | 0 src/utils/{Size => size}/index.ts | 0 10 files changed, 40 insertions(+), 18 deletions(-) rename src/{ => messaging}/MessageQueueContext.tsx (100%) rename src/messaging/{messagesToBackground.tsx => MessagesToBackground.tsx} (71%) rename src/utils/seedPhrase/{RandomSentenceGenerator.ts => randomSentenceGenerator.ts} (100%) rename src/utils/{Size => size}/index.ts (100%) diff --git a/src/App.tsx b/src/App.tsx index f58c7c2..9e1332b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,7 +35,7 @@ import PersonSearchIcon from '@mui/icons-material/PersonSearch'; import qortLogo from './assets/qort.png'; import { Return } from './assets/Icons/Return.tsx'; import WarningIcon from '@mui/icons-material/Warning'; -import './utils/seedPhrase/RandomSentenceGenerator'; +import './utils/seedPhrase/randomSentenceGenerator.ts'; import EngineeringIcon from '@mui/icons-material/Engineering'; import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; import PriorityHighIcon from '@mui/icons-material/PriorityHigh'; diff --git a/src/Wallets.tsx b/src/Wallets.tsx index c0aa098..1fe7583 100644 --- a/src/Wallets.tsx +++ b/src/Wallets.tsx @@ -75,7 +75,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => { const fileContents = await new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onabort = () => reject('File reading was aborted'); + reader.onabort = () => reject('File reading was aborted'); // TODO translate reader.onerror = () => reject('File reading has failed'); reader.onload = () => { // Resolve the promise with the reader result when reading completes diff --git a/src/components/Chat/ChatDirect.tsx b/src/components/Chat/ChatDirect.tsx index 0af54a6..6f95eee 100644 --- a/src/components/Chat/ChatDirect.tsx +++ b/src/components/Chat/ChatDirect.tsx @@ -15,7 +15,7 @@ import { resumeAllQueues, } from '../../App'; import { getPublicKey } from '../../background/background.ts'; -import { useMessageQueue } from '../../MessageQueueContext'; +import { useMessageQueue } from '../../messaging/MessageQueueContext.tsx'; import { executeEvent, subscribeToEvent, diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index 6fada40..9fc5e9a 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -25,7 +25,7 @@ import { } from '../../App'; import { CustomizedSnackbars } from '../Snackbar/Snackbar'; import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from '../../constants/constants'; -import { useMessageQueue } from '../../MessageQueueContext'; +import { useMessageQueue } from '../../messaging/MessageQueueContext.tsx'; import { executeEvent, subscribeToEvent, diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 8f312ec..fa534f9 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -43,7 +43,7 @@ import { } from '../../utils/events'; import { RequestQueueWithPromise } from '../../utils/queue/queue'; import { WebSocketActive } from './WebsocketActive'; -import { useMessageQueue } from '../../MessageQueueContext'; +import { useMessageQueue } from '../../messaging/MessageQueueContext'; import { HomeDesktop } from './HomeDesktop'; import { IconWrapper } from '../Desktop/DesktopFooter'; import { DesktopHeader } from '../Desktop/DesktopHeader'; diff --git a/src/main.tsx b/src/main.tsx index d6da0d2..2b224c8 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,8 +1,8 @@ import { createRoot } from 'react-dom/client'; import App from './App.tsx'; import '../src/styles/index.css'; -import './messaging/messagesToBackground'; -import { MessageQueueProvider } from './MessageQueueContext.tsx'; +import './messaging/MessagesToBackground.tsx'; +import { MessageQueueProvider } from './messaging/MessageQueueContext.tsx'; import { ThemeProvider } from './components/Theme/ThemeContext.tsx'; import { CssBaseline } from '@mui/material'; import './i18n/i18n.js'; diff --git a/src/MessageQueueContext.tsx b/src/messaging/MessageQueueContext.tsx similarity index 100% rename from src/MessageQueueContext.tsx rename to src/messaging/MessageQueueContext.tsx diff --git a/src/messaging/messagesToBackground.tsx b/src/messaging/MessagesToBackground.tsx similarity index 71% rename from src/messaging/messagesToBackground.tsx rename to src/messaging/MessagesToBackground.tsx index 4eebc5e..a325352 100644 --- a/src/messaging/messagesToBackground.tsx +++ b/src/messaging/MessagesToBackground.tsx @@ -1,5 +1,3 @@ - - // Utility to generate unique request IDs function generateRequestId() { return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; @@ -9,34 +7,55 @@ function generateRequestId() { const callbackMap = new Map(); // Global listener for handling message responses -window.addEventListener("message", (event) => { +window.addEventListener('message', (event) => { const { type, requestId, payload, error, message } = event.data; // Only process messages of type `backgroundMessageResponse` - if (type !== "backgroundMessageResponse") return; + if (type !== 'backgroundMessageResponse') return; // Check if there’s a callback stored for this requestId if (callbackMap.has(requestId)) { const { resolve, reject } = callbackMap.get(requestId); callbackMap.delete(requestId); // Remove callback after use - resolve(event.data) + resolve(event.data); } }); -export const sendMessageBackground = (action, data = {}, timeout = 240000, isExtension, appInfo, skipAuth) => { +export const sendMessageBackground = ( + action, + data = {}, + timeout = 240000, + isExtension, + appInfo, + skipAuth +) => { return new Promise((resolve, reject) => { const requestId = generateRequestId(); // Unique ID for each request callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks - const targetOrigin = window.location.origin + const targetOrigin = window.location.origin; // Send the message with `backgroundMessage` type - window.postMessage({ type: "backgroundMessage", action, requestId, payload: data, isExtension, appInfo, skipAuth }, targetOrigin); + window.postMessage( + { + type: 'backgroundMessage', + action, + requestId, + payload: data, + isExtension, + appInfo, + skipAuth, + }, + targetOrigin + ); // Set up a timeout to automatically reject if no response is received const timeoutId = setTimeout(() => { // Remove the callback to prevent memory leaks callbackMap.delete(requestId); - reject({ error: "timeout", message: `Request timed out after ${timeout} ms` }); + reject({ + error: 'timeout', + message: `Request timed out after ${timeout} ms`, + }); }, timeout); // Adjust resolve/reject to clear the timeout when a response arrives @@ -48,14 +67,17 @@ export const sendMessageBackground = (action, data = {}, timeout = 240000, isExt reject: (error) => { clearTimeout(timeoutId); // Clear the timeout if an error occurs reject(error); - } + }, }); }).then((response) => { // Return payload or error based on response content if (response?.payload !== null && response?.payload !== undefined) { return response.payload; } else if (response?.error) { - return { error: response.error, message: response?.message || "An error occurred" }; + return { + error: response.error, + message: response?.message || 'An error occurred', + }; } }); }; diff --git a/src/utils/seedPhrase/RandomSentenceGenerator.ts b/src/utils/seedPhrase/randomSentenceGenerator.ts similarity index 100% rename from src/utils/seedPhrase/RandomSentenceGenerator.ts rename to src/utils/seedPhrase/randomSentenceGenerator.ts diff --git a/src/utils/Size/index.ts b/src/utils/size/index.ts similarity index 100% rename from src/utils/Size/index.ts rename to src/utils/size/index.ts From e017d5d50d9743946d9e39c3c81158c658de377c Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:21:37 +0200 Subject: [PATCH 17/31] Move file into components --- src/App.tsx | 2 +- src/{ => components}/Wallets.tsx | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) rename src/{ => components}/Wallets.tsx (96%) diff --git a/src/App.tsx b/src/App.tsx index 9e1332b..ed70e64 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -118,7 +118,7 @@ import { } from './atoms/global'; import { NotAuthenticated } from './components/NotAuthenticated.tsx'; import { handleGetFileFromIndexedDB } from './utils/indexedDB'; -import { Wallets } from './Wallets'; +import { Wallets } from './components/Wallets.tsx'; import { useFetchResources } from './common/useFetchResources'; import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './hooks/useHandleTutorials.tsx'; diff --git a/src/Wallets.tsx b/src/components/Wallets.tsx similarity index 96% rename from src/Wallets.tsx rename to src/components/Wallets.tsx index 1fe7583..24aa4705 100644 --- a/src/Wallets.tsx +++ b/src/components/Wallets.tsx @@ -18,24 +18,24 @@ import { Input, useTheme, } from '@mui/material'; -import { CustomButton } from './styles/App-styles'; +import { CustomButton } from '../styles/App-styles.ts'; import { useDropzone } from 'react-dropzone'; import EditIcon from '@mui/icons-material/Edit'; -import { Label } from './components/Group/AddGroup'; -import { Spacer } from './common/Spacer'; +import { Label } from './Group/AddGroup.tsx'; +import { Spacer } from '../common/Spacer.tsx'; import { getWallets, storeWallets, walletVersion, -} from './background/background.ts'; -import { useModal } from './common/useModal'; -import PhraseWallet from './utils/generateWallet/phrase-wallet'; -import { decryptStoredWalletFromSeedPhrase } from './utils/decryptWallet'; -import { crypto } from './constants/decryptWallet'; +} from '../background/background.ts'; +import { useModal } from '../common/useModal.tsx'; +import PhraseWallet from '../utils/generateWallet/phrase-wallet.ts'; +import { decryptStoredWalletFromSeedPhrase } from '../utils/decryptWallet.ts'; +import { crypto } from '../constants/decryptWallet.ts'; import { LoadingButton } from '@mui/lab'; -import { PasswordField } from './components'; -import { HtmlTooltip } from './components/NotAuthenticated'; -import { QORTAL_APP_CONTEXT } from './App'; +import { PasswordField } from './index.ts'; +import { HtmlTooltip } from './NotAuthenticated.tsx'; +import { QORTAL_APP_CONTEXT } from '../App.tsx'; import { useTranslation } from 'react-i18next'; const parsefilenameQortal = (filename) => { From 593252cce334f332009a63f5d3d5e954b2da3bed Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 12:26:21 +0200 Subject: [PATCH 18/31] Move hooks into proper folder --- src/App.tsx | 4 ++-- src/components/Apps/AppsDevModeHome.tsx | 2 +- src/components/Group/BlockedUsersModal.tsx | 2 +- src/components/Minting/Minting.tsx | 2 +- src/components/Wallets.tsx | 2 +- src/{common => hooks}/useFetchResources.tsx | 0 src/{common => hooks}/useModal.tsx | 0 7 files changed, 6 insertions(+), 6 deletions(-) rename src/{common => hooks}/useFetchResources.tsx (100%) rename src/{common => hooks}/useModal.tsx (100%) diff --git a/src/App.tsx b/src/App.tsx index ed70e64..4aad404 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -64,7 +64,7 @@ import { Loader } from './components/Loader'; import { PasswordField, ErrorText } from './components'; import { Group, requestQueueMemberNames } from './components/Group/Group'; import { TaskManager } from './components/TaskManager/TaskManager.tsx'; -import { useModal } from './common/useModal'; +import { useModal } from './hooks/useModal.tsx'; import { CustomizedSnackbars } from './components/Snackbar/Snackbar'; import SettingsIcon from '@mui/icons-material/Settings'; import LogoutIcon from '@mui/icons-material/Logout'; @@ -119,7 +119,7 @@ import { import { NotAuthenticated } from './components/NotAuthenticated.tsx'; import { handleGetFileFromIndexedDB } from './utils/indexedDB'; import { Wallets } from './components/Wallets.tsx'; -import { useFetchResources } from './common/useFetchResources'; +import { useFetchResources } from './hooks/useFetchResources.tsx'; import { Tutorials } from './components/Tutorials/Tutorials'; import { useHandleTutorials } from './hooks/useHandleTutorials.tsx'; import { useHandleUserInfo } from './hooks/useHandleUserInfo.tsx'; diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx index 88218ca..db32248 100644 --- a/src/components/Apps/AppsDevModeHome.tsx +++ b/src/components/Apps/AppsDevModeHome.tsx @@ -22,7 +22,7 @@ import { Add } from '@mui/icons-material'; import { QORTAL_APP_CONTEXT, getBaseApiReact } from '../../App'; import { executeEvent } from '../../utils/events'; import { Spacer } from '../../common/Spacer'; -import { useModal } from '../../common/useModal'; +import { useModal } from '../../hooks/useModal.tsx'; import { createEndpoint, isUsingLocal } from '../../background/background.ts'; import { Label } from '../Group/AddGroup'; import ShortUniqueId from 'short-unique-id'; diff --git a/src/components/Group/BlockedUsersModal.tsx b/src/components/Group/BlockedUsersModal.tsx index 8cdd358..487711e 100644 --- a/src/components/Group/BlockedUsersModal.tsx +++ b/src/components/Group/BlockedUsersModal.tsx @@ -20,7 +20,7 @@ import { } from '../../utils/events'; import { validateAddress } from '../../utils/validateAddress'; import { getNameInfo, requestQueueMemberNames } from './Group'; -import { useModal } from '../../common/useModal'; +import { useModal } from '../../hooks/useModal'; import { isOpenBlockedModalAtom } from '../../atoms/global'; import InfoIcon from '@mui/icons-material/Info'; import { useAtom } from 'jotai'; diff --git a/src/components/Minting/Minting.tsx b/src/components/Minting/Minting.tsx index ce3846b..cddd67c 100644 --- a/src/components/Minting/Minting.tsx +++ b/src/components/Minting/Minting.tsx @@ -24,7 +24,7 @@ import { import { getFee } from '../../background/background.ts'; import { Spacer } from '../../common/Spacer'; import { FidgetSpinner } from 'react-loader-spinner'; -import { useModal } from '../../common/useModal'; +import { useModal } from '../../hooks/useModal.tsx'; import { useAtom, useSetAtom } from 'jotai'; import { memberGroupsAtom, txListAtom } from '../../atoms/global'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/Wallets.tsx b/src/components/Wallets.tsx index 24aa4705..ae749d3 100644 --- a/src/components/Wallets.tsx +++ b/src/components/Wallets.tsx @@ -28,7 +28,7 @@ import { storeWallets, walletVersion, } from '../background/background.ts'; -import { useModal } from '../common/useModal.tsx'; +import { useModal } from '../hooks/useModal.tsx'; import PhraseWallet from '../utils/generateWallet/phrase-wallet.ts'; import { decryptStoredWalletFromSeedPhrase } from '../utils/decryptWallet.ts'; import { crypto } from '../constants/decryptWallet.ts'; diff --git a/src/common/useFetchResources.tsx b/src/hooks/useFetchResources.tsx similarity index 100% rename from src/common/useFetchResources.tsx rename to src/hooks/useFetchResources.tsx diff --git a/src/common/useModal.tsx b/src/hooks/useModal.tsx similarity index 100% rename from src/common/useModal.tsx rename to src/hooks/useModal.tsx From a957c6fe04b73cc9690a9040823618dd3c4a8163 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 14:07:22 +0200 Subject: [PATCH 19/31] Remove duplicated uploadData function --- src/qdn/publish/publish.ts | 145 +++---------------------------------- 1 file changed, 11 insertions(+), 134 deletions(-) diff --git a/src/qdn/publish/publish.ts b/src/qdn/publish/publish.ts index f1f75ad..c73efd2 100644 --- a/src/qdn/publish/publish.ts +++ b/src/qdn/publish/publish.ts @@ -30,7 +30,6 @@ async function reusablePost(endpoint, _body) { data = await response.clone().json(); } catch (e) { data = await response.text(); -<<<<<<< HEAD } return data; } @@ -84,27 +83,23 @@ async function getKeyPair() { } export const publishData = async ({ - registeredName, - data, - service, - identifier, - uploadType, - file, - service, - identifier, - uploadType, - isBase64, - filename, - withFee, - title, - description, category, + data, + description, + feeAmount, + filename, + identifier, + isBase64, + registeredName, + service, tag1, tag2, tag3, tag4, tag5, - feeAmount, + title, + uploadType, + withFee, }: any) => { console.log('data', data); const validateName = async (receiverName: string) => { @@ -252,123 +247,6 @@ export const publishData = async ({ return signAndProcessRes; }; -<<<<<<< HEAD - const uploadData = async (registeredName: string, data: any, fee: number) => { - console.log('data', uploadType, data); - 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; - return await reusablePost(uploadDataUrl, postBody); - } - - 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; -} - const uploadData = async (registeredName: string, file: any, fee: number) => { let postBody = ''; let urlSuffix = ''; @@ -442,7 +320,6 @@ export const publishData = async ({ } return await reusablePost(uploadDataUrl, postBody); ->>>>>>> 2d01b3e (Create encryption folder and move files) }; try { From c9a678368333b81190bce403c064d2acf777c34b Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 14:16:31 +0200 Subject: [PATCH 20/31] Fix conflicts --- src/encryption/encryption.ts | 3 --- src/qdn/publish/publish.ts | 16 ++++++---------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/encryption/encryption.ts b/src/encryption/encryption.ts index 09af412..cf96e89 100644 --- a/src/encryption/encryption.ts +++ b/src/encryption/encryption.ts @@ -294,9 +294,6 @@ export const publishOnQDN = async ({ }) => { if (data && service) { const registeredName = name || (await getNameInfo()); -}) => { - if (data && service) { - const registeredName = await getNameInfo(); if (!registeredName) throw new Error( i18n.t('core:message.generic.name_publish', { diff --git a/src/qdn/publish/publish.ts b/src/qdn/publish/publish.ts index b105a1a..43ca630 100644 --- a/src/qdn/publish/publish.ts +++ b/src/qdn/publish/publish.ts @@ -83,29 +83,25 @@ async function getKeyPair() { } export const publishData = async ({ + category, data, + description, feeAmount, filename, identifier, isBase64, registeredName, - file, service, - identifier, - uploadType, - withFee, - title, - description, - category, tag1, tag2, tag3, tag4, tag5, + title, + uploadType, + withFee, }: any) => { console.log('data', data); - feeAmount, -}: any) => { const validateName = async (receiverName: string) => { return await reusableGet(`/names/${receiverName}`); }; @@ -232,7 +228,7 @@ export const publishData = async ({ 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')) { From 7deb2501e2664b6ea96789013fe480dd69f746f0 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 14:18:40 +0200 Subject: [PATCH 21/31] Remove duplicated --- src/encryption/encryption.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/encryption/encryption.ts b/src/encryption/encryption.ts index cf96e89..e0d068e 100644 --- a/src/encryption/encryption.ts +++ b/src/encryption/encryption.ts @@ -143,16 +143,13 @@ export const encryptAndPublishSymmetricKeyGroupChat = async ({ if (encryptedData) { const registeredName = await getNameInfo(); const data = await publishData({ - registeredName, data: encryptedData, - service: 'DOCUMENT_PRIVATE', - identifier: `symmetric-qchat-group-${groupId}`, - uploadType: 'base64', file: encryptedData, - service: 'DOCUMENT_PRIVATE', identifier: `symmetric-qchat-group-${groupId}`, - uploadType: 'file', isBase64: true, + registeredName, + service: 'DOCUMENT_PRIVATE', + uploadType: 'base64', withFee: true, }); return { From a778aa7a0f05f123e8e9a6f65d453c3c79958ec8 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 14:32:51 +0200 Subject: [PATCH 22/31] Set correct type (base64), remove isBase64 --- src/encryption/encryption.ts | 33 +++++------ src/qdn/publish/publish.ts | 108 ++++++++++++++++++++++++----------- 2 files changed, 88 insertions(+), 53 deletions(-) diff --git a/src/encryption/encryption.ts b/src/encryption/encryption.ts index e0d068e..ab0a36d 100644 --- a/src/encryption/encryption.ts +++ b/src/encryption/encryption.ts @@ -146,7 +146,6 @@ export const encryptAndPublishSymmetricKeyGroupChat = async ({ data: encryptedData, file: encryptedData, identifier: `symmetric-qchat-group-${groupId}`, - isBase64: true, registeredName, service: 'DOCUMENT_PRIVATE', uploadType: 'base64', @@ -212,14 +211,12 @@ export const encryptAndPublishSymmetricKeyGroupChatForAdmins = async ({ if (encryptedData) { const registeredName = await getNameInfo(); const data = await publishData({ - registeredName, data: encryptedData, - service: 'DOCUMENT_PRIVATE', - identifier: `admins-symmetric-qchat-group-${groupId}`, - uploadType: 'base64', file: encryptedData, - uploadType: 'file', - isBase64: true, + identifier: `admins-symmetric-qchat-group-${groupId}`, + registeredName, + service: 'DOCUMENT_PRIVATE', + uploadType: 'base64', withFee: true, }); return { @@ -252,13 +249,12 @@ export const publishGroupEncryptedResource = async ({ }) ); const data = await publishData({ - registeredName, data: encryptedData, - uploadType: 'base64', file: encryptedData, - service: 'DOCUMENT', identifier, - isBase64: true, + registeredName, + service: 'DOCUMENT', + uploadType: 'base64', withFee: true, }); return data; @@ -275,19 +271,19 @@ export const publishGroupEncryptedResource = async ({ }; export const publishOnQDN = async ({ - data, - identifier, - service, - title, - description, category, + data, + description, + identifier, + name, + service, tag1, tag2, tag3, tag4, tag5, - uploadType = 'file', - name, + title, + uploadType = 'base64', }) => { if (data && service) { const registeredName = name || (await getNameInfo()); @@ -305,7 +301,6 @@ export const publishOnQDN = async ({ service, identifier, uploadType, - isBase64: true, withFee: true, title, description, diff --git a/src/qdn/publish/publish.ts b/src/qdn/publish/publish.ts index 43ca630..dfe682f 100644 --- a/src/qdn/publish/publish.ts +++ b/src/qdn/publish/publish.ts @@ -70,7 +70,6 @@ async function uploadChunkWithRetry(endpoint, formData, index, maxRetries = 3) { await new Promise((res) => setTimeout(res, 10_000)); } } - return data; } async function getKeyPair() { @@ -248,79 +247,120 @@ export const publishData = async ({ return signAndProcessRes; }; - const uploadData = async (registeredName: string, file: any, fee: number) => { + const uploadData = async (registeredName: string, data: any, fee: number) => { + console.log('data', uploadType, data); let postBody = ''; let urlSuffix = ''; - if (file != null) { - // If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API - if (uploadType === 'zip') { - urlSuffix = '/zip'; - } - - // If we're sending file data, use the /base64 version of the POST /arbitrary/* API - else if (uploadType === 'file') { + 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 (!isBase64) { - let fileBuffer = new Uint8Array(await file.arrayBuffer()); - postBody = Buffer.from(fileBuffer).toString('base64'); + if (uploadType === 'base64') { + postBody = data; } + } else { + throw new Error('No data provided'); } - let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}`; + let uploadDataUrl = `/arbitrary/${service}/${registeredName}`; + let paramQueries = ''; if (identifier?.trim().length > 0) { - uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}`; + uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`; } - uploadDataUrl = uploadDataUrl + `?fee=${fee}`; + paramQueries = paramQueries + `?fee=${fee}`; if (filename != null && filename != 'undefined') { - uploadDataUrl = - uploadDataUrl + '&filename=' + encodeURIComponent(filename); + paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename); } if (title != null && title != 'undefined') { - uploadDataUrl = uploadDataUrl + '&title=' + encodeURIComponent(title); + paramQueries = paramQueries + '&title=' + encodeURIComponent(title); } if (description != null && description != 'undefined') { - uploadDataUrl = - uploadDataUrl + '&description=' + encodeURIComponent(description); + paramQueries = + paramQueries + '&description=' + encodeURIComponent(description); } if (category != null && category != 'undefined') { - uploadDataUrl = - uploadDataUrl + '&category=' + encodeURIComponent(category); + paramQueries = paramQueries + '&category=' + encodeURIComponent(category); } if (tag1 != null && tag1 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag1); + paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1); } if (tag2 != null && tag2 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag2); + paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2); } if (tag3 != null && tag3 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag3); + paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3); } if (tag4 != null && tag4 != 'undefined') { - uploadDataUrl = uploadDataUrl + '&tags=' + encodeURIComponent(tag4); + paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4); } 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); + } + + 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 { From 506d65dc0879fbfb4e6b3e8f6ee11a19c017c295 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Sat, 24 May 2025 14:53:46 +0200 Subject: [PATCH 23/31] Manage conflicts --- src/qortal/get.ts | 151 +- src/qortalRequests/get.ts | 7077 ------------------------------------- 2 files changed, 114 insertions(+), 7114 deletions(-) delete mode 100644 src/qortalRequests/get.ts diff --git a/src/qortal/get.ts b/src/qortal/get.ts index 102a9bf..dad01f5 100644 --- a/src/qortal/get.ts +++ b/src/qortal/get.ts @@ -38,7 +38,11 @@ import { getPublicKey, transferAsset, } from '../background/background.ts'; -import { getNameInfo, uint8ArrayToObject } from '../encryption/encryption.ts'; +import { + getAllUserNames, + getNameInfo, + uint8ArrayToObject, +} from '../encryption/encryption.ts'; import { showSaveFilePicker } from '../hooks/useQortalMessageListener.tsx'; import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner.tsx'; import { extractComponents } from '../components/Chat/MessageDisplay.tsx'; @@ -1315,7 +1319,7 @@ export const publishQDNResource = async ( if (appFee && appFee > 0 && appFeeRecipient) { hasAppFee = true; } - const registeredName = await getNameInfo(); + const registeredName = data?.name || (await getNameInfo()); const name = registeredName; if (!name) { throw new Error( @@ -1331,7 +1335,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 = {}; @@ -1346,9 +1350,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 || @@ -1367,6 +1369,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, @@ -1410,6 +1415,7 @@ export const publishQDNResource = async ( }), text2: `service: ${service}`, text3: `identifier: ${identifier || null}`, + text4: `name: ${registeredName}`, fee: fee.fee, ...handleDynamicValues, }, @@ -1420,11 +1426,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, @@ -1558,6 +1563,7 @@ export const publishMultipleQDNResources = async ( const fee = await getFee('ARBITRARY'); const registeredName = await getNameInfo(); + const name = registeredName; if (!name) { @@ -1568,6 +1574,14 @@ export const publishMultipleQDNResources = async ( ); } + const userNames = await getAllUserNames(); + data.resources?.forEach((item) => { + if (item?.name && !userNames?.includes(item.name)) + throw new Error( + `The name ${item.name}, does not belong to the publisher.` + ); + }); + const appFee = data?.appFee ? +data.appFee : undefined; const appFeeRecipient = data?.appFeeRecipient; let hasAppFee = false; @@ -1638,7 +1652,7 @@ export const publishMultipleQDNResources = async (
Service: ${ resource.service }
-
Name: ${name}
+
Name: ${resource?.name || name}
Identifier: ${ resource.identifier }
@@ -1695,6 +1709,7 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); continue; } @@ -1709,12 +1724,13 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); continue; } 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; @@ -1741,26 +1757,31 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); continue; } if (resource.file) { - data64 = await fileToBase64(resource.file); + rawData = resource.file; } + if (resourceEncrypt) { try { + if (resource?.file) { + rawData = await fileToBase64(resource.file); + } 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 = @@ -1772,32 +1793,37 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); continue; } } try { + const dataType = + resource?.base64 || resource?.data64 || resourceEncrypt + ? 'base64' + : 'file'; + console.log('dataType', dataType); await retryTransaction( publishData, [ { - registeredName: encodeURIComponent(name), - file: data64, - service: service, - identifier: encodeURIComponent(identifier), - uploadType: 'file', - isBase64: true, - filename: filename, - title, - description, + apiVersion: 2, category, + data: rawData, + description, + filename: filename, + identifier: encodeURIComponent(identifier), + registeredName: encodeURIComponent(resource?.name || name), + service: service, tag1, tag2, tag3, tag4, tag5, - apiVersion: 2, + title, + uploadType: dataType, withFee: true, }, ], @@ -1818,6 +1844,7 @@ export const publishMultipleQDNResources = async ( reason: errorMsg, identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); } } catch (error) { @@ -1829,6 +1856,7 @@ export const publishMultipleQDNResources = async ( }), identifier: resource.identifier, service: resource.service, + name: resource?.name || name, }); } } @@ -2324,6 +2352,44 @@ export const joinGroup = async (data, isFromExtension) => { export const saveFile = async (data, sender, isFromExtension, snackMethods) => { 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'); + const a = document.createElement('a'); + let locationUrl = `/arbitrary/${data.location.service}/${data.location.name}`; + if (data.location.identifier) { + locationUrl = locationUrl + `/${data.location.identifier}`; + } + const endpoint = await createEndpoint( + locationUrl + `?attachment=true&attachmentFilename=${data?.filename}` + ); + a.href = endpoint; + a.download = data.filename; + document.body.appendChild(a); + a.click(); + a.remove(); + return true; + } const requiredFields = ['filename', 'blob']; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -2341,6 +2407,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: i18n.t('question:download_file', { @@ -2351,6 +2419,17 @@ export const saveFile = async (data, sender, isFromExtension, snackMethods) => { isFromExtension ); const { accepted } = resPermission; + if (!accepted) throw new Error('User declined to save file'); // TODO translate + showSaveFilePicker( + { + filename, + mimeType, + blob, + }, + snackMethods + ); + + return true; if (accepted) { const mimeType = blob.type || data.mimeType; @@ -5396,11 +5475,10 @@ export const updateNameRequest = async (data, isFromExtension) => { const fee = await getFee('UPDATE_NAME'); const resPermission = await getUserPermission( { - text1: i18n.t('question:permission.register_name', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: data.newName, - text2: data?.description, + text1: `Do you give this application permission to update this name?`, // TODO translate + text2: `previous name: ${oldName}`, + text3: `new name: ${newName}`, + text4: data?.description, fee: fee.fee, }, isFromExtension @@ -6012,7 +6090,7 @@ export const createGroupRequest = async (data, isFromExtension) => { ]; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); @@ -6076,7 +6154,7 @@ export const updateGroupRequest = async (data, isFromExtension) => { ]; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); @@ -6250,7 +6328,7 @@ export const sellNameRequest = async (data, isFromExtension) => { const requiredFields = ['salePrice', 'nameForSale']; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); @@ -6320,7 +6398,7 @@ export const cancelSellNameRequest = async (data, isFromExtension) => { const requiredFields = ['nameForSale']; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); @@ -6377,7 +6455,7 @@ export const buyNameRequest = async (data, isFromExtension) => { const requiredFields = ['nameForSale']; const missingFields: string[] = []; requiredFields.forEach((field) => { - if (data[field] !== undefined && data[field] !== null) { + if (data[field] === undefined || data[field] === null) { missingFields.push(field); } }); @@ -6908,12 +6986,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, }, diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts deleted file mode 100644 index 6819842..0000000 --- a/src/qortalRequests/get.ts +++ /dev/null @@ -1,7077 +0,0 @@ -import { Sha256 } from 'asmcrypto.js'; -import { - createEndpoint, - getBalanceInfo, - getFee, - getKeyPair, - getLastRef, - getSaveWallet, - processTransactionVersion2, - signChatFunc, - joinGroup as joinGroupFunc, - sendQortFee, - sendCoin as sendCoinFunc, - createBuyOrderTx, - performPowTask, - parseErrorResponse, - groupSecretkeys, - registerName, - updateName, - leaveGroup, - inviteToGroup, - getNameInfoForOthers, - kickFromGroup, - banFromGroup, - cancelBan, - makeAdmin, - removeAdmin, - cancelInvitationToGroup, - createGroup, - updateGroup, - sellName, - cancelSellName, - buyName, - getBaseApi, - getAssetBalanceInfo, - getNameOrAddress, - getAssetInfo, - getPublicKey, - transferAsset, -} from '../background/background.ts'; -import { getNameInfo, uint8ArrayToObject } from '../encryption/encryption.ts'; -import { showSaveFilePicker } from '../hooks/useQortalMessageListener.tsx'; -import { getPublishesFromAdminsAdminSpace } from '../components/Chat/AdminSpaceInner.tsx'; -import { extractComponents } from '../components/Chat/MessageDisplay.tsx'; -import { - decryptResource, - getGroupAdmins, - getPublishesFromAdmins, - validateSecretKey, -} from '../components/Group/Group.tsx'; -import { QORT_DECIMALS } from '../constants/constants.ts'; -import Base58 from '../encryption/Base58.ts'; -import ed2curve from '../encryption/ed2curve.ts'; -import nacl from '../encryption/nacl-fast.ts'; -import { - base64ToUint8Array, - createSymmetricKeyAndNonce, - decryptDeprecatedSingle, - decryptGroupDataQortalRequest, - decryptGroupEncryptionWithSharingKey, - decryptSingle, - encryptDataGroup, - encryptSingle, - objectToBase64, - uint8ArrayStartsWith, - uint8ArrayToBase64, -} from '../qdn/encryption/group-encryption.ts'; -import { publishData } from '../qdn/publish/publish.ts'; -import { - getPermission, - isRunningGateway, - setPermission, -} from './qortalRequests.ts'; -import TradeBotCreateRequest from '../transactions/TradeBotCreateRequest.ts'; -import DeleteTradeOffer from '../transactions/TradeBotDeleteRequest.ts'; -import signTradeBotTransaction from '../transactions/signTradeBotTransaction.ts'; -import { createTransaction } from '../transactions/transactions.ts'; -import { executeEvent } from '../utils/events.ts'; -import { fileToBase64 } from '../utils/fileReading/index.ts'; -import { mimeToExtensionMap } from '../utils/memeTypes.ts'; -import { RequestQueueWithPromise } from '../utils/queue/queue.ts'; -import utils from '../utils/utils.ts'; -import ShortUniqueId from 'short-unique-id'; -import { isValidBase64WithDecode } from '../utils/decode.ts'; -import i18n from 'i18next'; - -const uid = new ShortUniqueId({ length: 6 }); - -export const requestQueueGetAtAddresses = new RequestQueueWithPromise(10); - -const sellerForeignFee = { - LITECOIN: { - value: '~0.00005', - ticker: 'LTC', - }, - DOGECOIN: { - value: '~0.005', - ticker: 'DOGE', - }, - BITCOIN: { - value: '~0.0001', - ticker: 'BTC', - }, - DIGIBYTE: { - value: '~0.0005', - ticker: 'DGB', - }, - RAVENCOIN: { - value: '~0.006', - ticker: 'RVN', - }, - PIRATECHAIN: { - value: '~0.0002', - ticker: 'ARRR', - }, -}; - -const btcFeePerByte = 0.000001; -const ltcFeePerByte = 0.0000003; -const dogeFeePerByte = 0.00001; -const dgbFeePerByte = 0.0000001; -const rvnFeePerByte = 0.00001125; - -const MAX_RETRIES = 3; // Set max number of retries - -export async function retryTransaction( - fn, - args, - throwError, - retries = MAX_RETRIES -) { - let attempt = 0; - while (attempt < retries) { - try { - return await fn(...args); - } catch (error) { - console.error(`Attempt ${attempt + 1} failed: ${error.message}`); - attempt++; - if (attempt === retries) { - console.error( - i18n.t('question:message.generic.max_retry_transaction', { - postProcess: 'capitalizeFirstChar', - }) - ); - if (throwError) { - throw new Error( - error?.message || - i18n.t('question:message.error.process_transaction', { - postProcess: 'capitalizeFirstChar', - }) - ); - } else { - throw new Error( - error?.message || - i18n.t('question:message.error.process_transaction', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } - await new Promise((res) => setTimeout(res, 10000)); - } - } -} - -function roundUpToDecimals(number, decimals = 8) { - const factor = Math.pow(10, decimals); // Create a factor based on the number of decimals - return Math.ceil(+number * factor) / factor; -} - -export const _createPoll = async ( - { pollName, pollDescription, options }, - isFromExtension, - skipPermission -) => { - const fee = await getFee('CREATE_POLL'); - let resPermission = {}; - if (!skipPermission) { - resPermission = await getUserPermission( - { - text1: i18n.t('question:request_create_poll', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:poll', { - name: pollName, - postProcess: 'capitalizeFirstChar', - }), - text3: i18n.t('question:description', { - description: pollDescription, - postProcess: 'capitalizeFirstChar', - }), - text4: i18n.t('question:options', { - optionList: options?.join(', '), - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - } - - const { accepted = false } = resPermission; - - if (accepted || skipPermission) { - const wallet = await getSaveWallet(); - const address = wallet.address0; - 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 lastRef = await getLastRef(); - - const tx = await createTransaction(8, keyPair, { - fee: fee.fee, - ownerAddress: address, - rPollName: pollName, - rPollDesc: pollDescription, - rOptions: options, - lastReference: lastRef, - }); - const signedBytes = Base58.encode(tx.signedBytes); - const res = await processTransactionVersion2(signedBytes); - if (!res?.signature) - throw new Error( - res?.message || - i18n.t('question:message.error.process_transaction', { - postProcess: 'capitalizeFirstChar', - }) - ); - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -const _deployAt = async ( - { name, description, tags, creationBytes, amount, assetId, atType }, - isFromExtension -) => { - const fee = await getFee('DEPLOY_AT'); - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:deploy_at', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:name', { - name: name, - postProcess: 'capitalizeFirstChar', - }), - text3: i18n.t('question:description', { - description: description, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - - const { accepted } = resPermission; - - if (accepted) { - const wallet = await getSaveWallet(); - const address = wallet.address0; - const lastReference = await getLastRef(); - 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, - }; - - const tx = await createTransaction(16, keyPair, { - fee: fee.fee, - rName: name, - rDescription: description, - rTags: tags, - rAmount: amount, - rAssetId: assetId, - rCreationBytes: creationBytes, - atType: atType, - lastReference: lastReference, - }); - - const signedBytes = Base58.encode(tx.signedBytes); - - const res = await processTransactionVersion2(signedBytes); - if (!res?.signature) - throw new Error( - res?.message || - i18n.t('question:message.error.process_transaction', { - postProcess: 'capitalizeFirstChar', - }) - ); - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const _voteOnPoll = async ( - { pollName, optionIndex, optionName }, - isFromExtension, - skipPermission -) => { - const fee = await getFee('VOTE_ON_POLL'); - let resPermission = {}; - - if (!skipPermission) { - resPermission = await getUserPermission( - { - text1: i18n.t('question:request_vote_poll', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:poll', { - name: pollName, - postProcess: 'capitalizeFirstChar', - }), - text3: i18n.t('question:option', { - option: optionName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - } - - const { accepted = false } = resPermission; - - if (accepted || skipPermission) { - const wallet = await getSaveWallet(); - const address = wallet.address0; - 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 lastRef = await getLastRef(); - - const tx = await createTransaction(9, keyPair, { - fee: fee.fee, - voterAddress: address, - rPollName: pollName, - rOptionIndex: optionIndex, - lastReference: lastRef, - }); - const signedBytes = Base58.encode(tx.signedBytes); - const res = await processTransactionVersion2(signedBytes); - if (!res?.signature) - throw new Error( - res?.message || - i18n.t('question:message.error.process_transaction', { - postProcess: 'capitalizeFirstChar', - }) - ); - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -// Map to store resolvers and rejectors by requestId -const fileRequestResolvers = new Map(); - -const handleFileMessage = (event) => { - const { action, requestId, result, error } = event.data; - - if ( - action === 'getFileFromIndexedDBResponse' && - fileRequestResolvers.has(requestId) - ) { - const { resolve, reject } = fileRequestResolvers.get(requestId); - fileRequestResolvers.delete(requestId); // Clean up after resolving - - if (result) { - resolve(result); - } else { - reject( - error || - i18n.t('question:message.error.retrieve_file', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } -}; - -window.addEventListener('message', handleFileMessage); - -function getFileFromContentScript(fileId) { - return new Promise((resolve, reject) => { - const requestId = `getFile_${fileId}_${Date.now()}`; - - fileRequestResolvers.set(requestId, { resolve, reject }); // Store resolvers by requestId - const targetOrigin = window.location.origin; - - // Send the request message - window.postMessage( - { action: 'getFileFromIndexedDB', fileId, requestId }, - targetOrigin - ); - - // Timeout to handle no response scenario - setTimeout(() => { - if (fileRequestResolvers.has(requestId)) { - fileRequestResolvers.get(requestId).reject( - i18n.t('question:message.error.timeout_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - fileRequestResolvers.delete(requestId); // Clean up on timeout - } - }, 10000); // 10-second timeout - }); -} - -const responseResolvers = new Map(); - -const handleMessage = (event) => { - const { action, requestId, result } = event.data; - - // Check if this is the expected response action and if we have a stored resolver - if ( - action === 'QORTAL_REQUEST_PERMISSION_RESPONSE' && - responseResolvers.has(requestId) - ) { - // Resolve the stored promise with the result - responseResolvers.get(requestId)(result || false); - responseResolvers.delete(requestId); // Clean up after resolving - } -}; - -window.addEventListener('message', handleMessage); - -async function getUserPermission(payload, isFromExtension) { - return new Promise((resolve) => { - const requestId = `qortalRequest_${Date.now()}`; - responseResolvers.set(requestId, resolve); // Store resolver by requestId - const targetOrigin = window.location.origin; - - // Send the request message - window.postMessage( - { - action: 'QORTAL_REQUEST_PERMISSION', - payload, - requestId, - isFromExtension, - }, - targetOrigin - ); - - // Optional timeout to handle no response scenario - setTimeout(() => { - if (responseResolvers.has(requestId)) { - responseResolvers.get(requestId)(false); // Resolve with `false` if no response - responseResolvers.delete(requestId); - } - }, 60000); // 30-second timeout - }); -} - -export const getUserAccount = async ({ - isFromExtension, - appInfo, - skipAuth, -}) => { - try { - const value = - (await getPermission(`qAPPAutoAuth-${appInfo?.name}`)) || false; - let skip = false; - if (value) { - skip = true; - } - if (skipAuth) { - skip = true; - } - let resPermission; - if (!skip) { - resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.authenticate', { - postProcess: 'capitalizeFirstChar', - }), - checkbox1: { - value: false, - label: i18n.t('question:always_authenticate', { - postProcess: 'capitalizeFirstChar', - }), - }, - }, - isFromExtension - ); - } - - const { accepted = false, checkbox1 = false } = resPermission || {}; - if (resPermission) { - setPermission(`qAPPAutoAuth-${appInfo?.name}`, checkbox1); - } - if (accepted || skip) { - const wallet = await getSaveWallet(); - const address = wallet.address0; - const publicKey = wallet.publicKey; - return { - address, - publicKey, - }; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } catch (error) { - throw new Error( - i18n.t('auth:message.error.fetch_user_account', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const encryptData = async (data, sender) => { - let data64 = data.data64 || data.base64; - let publicKeys = data.publicKeys || []; - if (data?.file || data?.blob) { - data64 = await fileToBase64(data?.file || data?.blob); - } - if (!data64) { - throw new Error( - i18n.t('question:message.generic.include_data_encrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const privateKey = parsedData.privateKey; - const userPublicKey = parsedData.publicKey; - - const encryptDataResponse = encryptDataGroup({ - data64, - publicKeys: publicKeys, - privateKey, - userPublicKey, - }); - if (encryptDataResponse) { - return encryptDataResponse; - } else { - throw new Error( - i18n.t('question:message.error.encrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const encryptQortalGroupData = async (data, sender) => { - let data64 = data?.data64 || data?.base64; - let groupId = data?.groupId; - let isAdmins = data?.isAdmins; - if (!groupId) { - throw new Error( - i18n.t('question:message.generic.provide_group_id', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - if (data?.file || data?.blob) { - data64 = await fileToBase64(data?.file || data?.blob); - } - if (!data64) { - throw new Error( - i18n.t('question:message.generic.include_data_encrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - let secretKeyObject; - if (!isAdmins) { - if ( - groupSecretkeys[groupId] && - groupSecretkeys[groupId].secretKeyObject && - groupSecretkeys[groupId]?.timestamp && - Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 - ) { - secretKeyObject = groupSecretkeys[groupId].secretKeyObject; - } - - if (!secretKeyObject) { - const { names } = await getGroupAdmins(groupId); - - const publish = await getPublishesFromAdmins(names, groupId); - if (publish === false) - throw new Error( - i18n.t('question:message.error.no_group_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - const url = await createEndpoint( - `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ - publish.identifier - }?encoding=base64&rebuild=true` - ); - - const res = await fetch(url); - const resData = await res.text(); - - const decryptedKey: any = await decryptResource(resData, true); - - const dataint8Array = base64ToUint8Array(decryptedKey.data); - const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); - - if (!validateSecretKey(decryptedKeyToObject)) - throw new Error( - i18n.t('auth:message.error.invalid_secret_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - secretKeyObject = decryptedKeyToObject; - groupSecretkeys[groupId] = { - secretKeyObject, - timestamp: Date.now(), - }; - } - } else { - if ( - groupSecretkeys[`admins-${groupId}`] && - groupSecretkeys[`admins-${groupId}`].secretKeyObject && - groupSecretkeys[`admins-${groupId}`]?.timestamp && - Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 - ) { - secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; - } - - if (!secretKeyObject) { - const { names } = await getGroupAdmins(groupId); - - const publish = await getPublishesFromAdminsAdminSpace(names, groupId); - if (publish === false) - throw new Error( - i18n.t('question:message.error.no_group_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - const url = await createEndpoint( - `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ - publish.identifier - }?encoding=base64&rebuild=true` - ); - - const res = await fetch(url); - const resData = await res.text(); - const decryptedKey: any = await decryptResource(resData, true); - const dataint8Array = base64ToUint8Array(decryptedKey.data); - const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); - - if (!validateSecretKey(decryptedKeyToObject)) - throw new Error( - i18n.t('auth:message.error.invalid_secret_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - secretKeyObject = decryptedKeyToObject; - groupSecretkeys[`admins-${groupId}`] = { - secretKeyObject, - timestamp: Date.now(), - }; - } - } - - const resGroupEncryptedResource = encryptSingle({ - data64, - secretKeyObject: secretKeyObject, - }); - - if (resGroupEncryptedResource) { - return resGroupEncryptedResource; - } else { - throw new Error( - i18n.t('question:message.error.encrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const decryptQortalGroupData = async (data, sender) => { - let data64 = data?.data64 || data?.base64; - let groupId = data?.groupId; - let isAdmins = data?.isAdmins; - if (!groupId) { - throw new Error( - i18n.t('question:message.generic.provide_group_id', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - if (!data64) { - throw new Error( - i18n.t('question:message.generic.include_data_encrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - let secretKeyObject; - if (!isAdmins) { - if ( - groupSecretkeys[groupId] && - groupSecretkeys[groupId].secretKeyObject && - groupSecretkeys[groupId]?.timestamp && - Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 - ) { - secretKeyObject = groupSecretkeys[groupId].secretKeyObject; - } - if (!secretKeyObject) { - const { names } = await getGroupAdmins(groupId); - - const publish = await getPublishesFromAdmins(names, groupId); - if (publish === false) - throw new Error( - i18n.t('question:message.error.no_group_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - const url = await createEndpoint( - `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ - publish.identifier - }?encoding=base64&rebuild=true` - ); - - const res = await fetch(url); - const resData = await res.text(); - const decryptedKey: any = await decryptResource(resData, true); - - const dataint8Array = base64ToUint8Array(decryptedKey.data); - const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); - if (!validateSecretKey(decryptedKeyToObject)) - throw new Error( - i18n.t('auth:message.error.invalid_secret_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - secretKeyObject = decryptedKeyToObject; - groupSecretkeys[groupId] = { - secretKeyObject, - timestamp: Date.now(), - }; - } - } else { - if ( - groupSecretkeys[`admins-${groupId}`] && - groupSecretkeys[`admins-${groupId}`].secretKeyObject && - groupSecretkeys[`admins-${groupId}`]?.timestamp && - Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 - ) { - secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; - } - if (!secretKeyObject) { - const { names } = await getGroupAdmins(groupId); - - const publish = await getPublishesFromAdminsAdminSpace(names, groupId); - if (publish === false) - throw new Error( - i18n.t('question:message.error.no_group_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - const url = await createEndpoint( - `/arbitrary/DOCUMENT_PRIVATE/${publish.name}/${ - publish.identifier - }?encoding=base64&rebuild=true` - ); - - const res = await fetch(url); - const resData = await res.text(); - const decryptedKey: any = await decryptResource(resData, true); - - const dataint8Array = base64ToUint8Array(decryptedKey.data); - const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); - if (!validateSecretKey(decryptedKeyToObject)) - throw new Error( - i18n.t('auth:message.error.invalid_secret_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - secretKeyObject = decryptedKeyToObject; - groupSecretkeys[`admins-${groupId}`] = { - secretKeyObject, - timestamp: Date.now(), - }; - } - } - - const resGroupDecryptResource = decryptSingle({ - data64, - secretKeyObject: secretKeyObject, - skipDecodeBase64: true, - }); - if (resGroupDecryptResource) { - return resGroupDecryptResource; - } else { - throw new Error( - i18n.t('question:message.error.encrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const encryptDataWithSharingKey = async (data, sender) => { - let data64 = data?.data64 || data?.base64; - let publicKeys = data.publicKeys || []; - if (data?.file || data?.blob) { - data64 = await fileToBase64(data?.file || data?.blob); - } - if (!data64) { - throw new Error( - i18n.t('question:message.generic.include_data_encrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const symmetricKey = createSymmetricKeyAndNonce(); - const dataObject = { - data: data64, - key: symmetricKey.messageKey, - }; - const dataObjectBase64 = await objectToBase64(dataObject); - - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const privateKey = parsedData.privateKey; - const userPublicKey = parsedData.publicKey; - - const encryptDataResponse = encryptDataGroup({ - data64: dataObjectBase64, - publicKeys: publicKeys, - privateKey, - userPublicKey, - customSymmetricKey: symmetricKey.messageKey, - }); - if (encryptDataResponse) { - return encryptDataResponse; - } else { - throw new Error( - i18n.t('question:message.error.encrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const decryptDataWithSharingKey = async (data, sender) => { - const { encryptedData, key } = data; - - if (!encryptedData) { - throw new Error( - i18n.t('question:message.generic.include_data_decrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - const decryptedData = await decryptGroupEncryptionWithSharingKey({ - data64EncryptedData: encryptedData, - key, - }); - const base64ToObject = JSON.parse(atob(decryptedData)); - - if (!base64ToObject.data) - throw new Error( - i18n.t('question:message.error.no_data_encrypted_resource', { - postProcess: 'capitalizeFirstChar', - }) - ); - return base64ToObject.data; -}; - -export const getHostedData = async (data, isFromExtension) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const resPermission = await getUserPermission( - { - text1: i18n.t('question:message.error.submit_sell_order', { - postProcess: 'capitalizeFirstChar', - }), - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const limit = data?.limit ? data?.limit : 20; - const query = data?.query ? data?.query : ''; - const offset = data?.offset ? data?.offset : 0; - - let urlPath = `/arbitrary/hosted/resources/?limit=${limit}&offset=${offset}`; - if (query) { - urlPath = urlPath + `&query=${query}`; - } - - const url = await createEndpoint(urlPath); - const response = await fetch(url); - const dataResponse = await response.json(); - return dataResponse; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_list', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const deleteHostedData = async (data, isFromExtension) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const requiredFields = ['hostedData']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.delete_hosts_resources', { - size: data?.hostedData?.length, - postProcess: 'capitalizeFirstChar', - }), - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const { hostedData } = data; - - for (const hostedDataItem of hostedData) { - try { - const url = await createEndpoint( - `/arbitrary/resource/${hostedDataItem.service}/${hostedDataItem.name}/${hostedDataItem.identifier}` - ); - await fetch(url, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - }); - } catch (error) { - console.log(error); - } - } - - return true; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_delete_hosted_resources', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; -export const decryptData = async (data) => { - const { encryptedData, publicKey } = data; - - if (!encryptedData) { - throw new Error(`Missing fields: encryptedData`); - } - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const uint8PrivateKey = Base58.decode(parsedData.privateKey); - const uint8Array = base64ToUint8Array(encryptedData); - const startsWithQortalEncryptedData = uint8ArrayStartsWith( - uint8Array, - 'qortalEncryptedData' - ); - if (startsWithQortalEncryptedData) { - if (!publicKey) { - throw new Error(`Missing fields: publicKey`); - } - - const decryptedDataToBase64 = decryptDeprecatedSingle( - uint8Array, - publicKey, - uint8PrivateKey - ); - return decryptedDataToBase64; - } - const startsWithQortalGroupEncryptedData = uint8ArrayStartsWith( - uint8Array, - 'qortalGroupEncryptedData' - ); - if (startsWithQortalGroupEncryptedData) { - const decryptedData = decryptGroupDataQortalRequest( - encryptedData, - parsedData.privateKey - ); - const decryptedDataToBase64 = uint8ArrayToBase64(decryptedData); - return decryptedDataToBase64; - } - throw new Error( - i18n.t('question:message.error.encrypt', { - postProcess: 'capitalizeFirstChar', - }) - ); -}; - -export const getListItems = async (data, isFromExtension) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const requiredFields = ['list_name']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const value = (await getPermission('qAPPAutoLists')) || false; - - let skip = false; - if (value) { - skip = true; - } - let resPermission; - let acceptedVar; - let checkbox1Var; - if (!skip) { - resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.access_list', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: data.list_name, - checkbox1: { - value: value, - label: i18n.t('question:always_retrieve_list', { - postProcess: 'capitalizeFirstChar', - }), - }, - }, - isFromExtension - ); - const { accepted, checkbox1 } = resPermission; - acceptedVar = accepted; - checkbox1Var = checkbox1; - setPermission('qAPPAutoLists', checkbox1); - } - - if (acceptedVar || skip) { - const url = await createEndpoint(`/lists/${data.list_name}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_list', { - postProcess: 'capitalizeFirstChar', - }) - ); - - const list = await response.json(); - return list; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_share_list', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const addListItems = async (data, isFromExtension) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const requiredFields = ['list_name', 'items']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const items = data.items; - const list_name = data.list_name; - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.all_item_list', { - name: list_name, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: items.join(', '), - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const url = await createEndpoint(`/lists/${list_name}`); - const body = { - items: items, - }; - const bodyToString = JSON.stringify(body); - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: bodyToString, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.add_to_list', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_add_list', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const deleteListItems = async (data, isFromExtension) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const requiredFields = ['list_name']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - if (!data?.item && !data?.items) { - throw new Error( - i18n.t('question:message.error.missing_fields', { - fields: 'items', - postProcess: 'capitalizeFirstChar', - }) - ); - } - const item = data?.item; - const items = data?.items; - const list_name = data.list_name; - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.remove_from_list', { - name: list_name, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: items ? JSON.stringify(items) : item, - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const url = await createEndpoint(`/lists/${list_name}`); - const body = { - items: items || [item], - }; - const bodyToString = JSON.stringify(body); - const response = await fetch(url, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - body: bodyToString, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.add_to_list', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_delete_from_list', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const publishQDNResource = async ( - data: any, - sender, - isFromExtension -) => { - const requiredFields = ['service']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - if (!data.file && !data.data64 && !data.base64) { - throw new Error( - i18n.t('question:message.error.no_data_file_submitted', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - // Use "default" if user hasn't specified an identifier - const service = data.service; - const appFee = data?.appFee ? +data.appFee : undefined; - const appFeeRecipient = data?.appFeeRecipient; - let hasAppFee = false; - if (appFee && appFee > 0 && appFeeRecipient) { - hasAppFee = true; - } - - const registeredName = data?.name || (await getNameInfo()); - const name = registeredName; - if (!name) { - throw new Error( - i18n.t('question:message.error.user_qortal_name', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - let identifier = data.identifier; - let data64 = data.data64 || data.base64; - const filename = data.filename; - const title = data.title; - const description = data.description; - const category = data.category; - const file = data?.file || data?.blob; - const tags = data?.tags || []; - const result = {}; - - // Fill tags dynamically while maintaining backward compatibility - for (let i = 0; i < 5; i++) { - result[`tag${i + 1}`] = tags[i] || data[`tag${i + 1}`] || undefined; - } - - // Access tag1 to tag5 from result - const { tag1, tag2, tag3, tag4, tag5 } = result; - - if (data.identifier == null) { - identifier = 'default'; - } - - if ( - data.encrypt && - (!data.publicKeys || - (Array.isArray(data.publicKeys) && data.publicKeys.length === 0)) - ) { - throw new Error( - i18n.t('question:message.error.encryption_requires_public_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - if (data.encrypt) { - try { - const resKeyPair = await getKeyPair(); - 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, - privateKey, - userPublicKey, - }); - if (encryptDataResponse) { - data64 = encryptDataResponse; - } - } catch (error) { - throw new Error( - error.message || - i18n.t('question:message.error.upload_encryption', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } - - const fee = await getFee('ARBITRARY'); - - const handleDynamicValues = {}; - if (hasAppFee) { - const feePayment = await getFee('PAYMENT'); - - (handleDynamicValues['appFee'] = +appFee + +feePayment.fee), - (handleDynamicValues['checkbox1'] = { - value: true, - label: i18n.t('question:accept_app_fee', { - postProcess: 'capitalizeFirstChar', - }), - }); - } - if (!!data?.encrypt) { - handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`; - } - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.publish_qdn', { - postProcess: 'capitalizeFirstChar', - }), - text2: `service: ${service}`, - text3: `identifier: ${identifier || null}`, - text4: `name: ${registeredName}`, - fee: fee.fee, - ...handleDynamicValues, - }, - isFromExtension - ); - const { accepted, checkbox1 = false } = resPermission; - if (accepted) { - try { - const resPublish = await publishData({ - registeredName: encodeURIComponent(name), - data: data64 ? data64 : file, - service: service, - identifier: encodeURIComponent(identifier), - uploadType: data64 ? 'base64' : 'file', - filename: filename, - title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5, - apiVersion: 2, - withFee: true, - }); - if (resPublish?.signature && hasAppFee && checkbox1) { - sendCoinFunc( - { - amount: appFee, - receiver: appFeeRecipient, - }, - true - ); - } - return resPublish; - } catch (error) { - throw new Error(error?.message || 'Upload failed'); - } - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const checkArrrSyncStatus = async (seed) => { - const _url = await createEndpoint(`/crosschain/arrr/syncstatus`); - let tries = 0; // Track the number of attempts - - while (tries < 36) { - const response = await fetch(_url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: seed, - }); - - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - - if (res.indexOf('<') > -1 || res !== 'Synchronized') { - // Wait 2 seconds before trying again - await new Promise((resolve) => setTimeout(resolve, 2000)); - tries += 1; - } else { - // If the response doesn't meet the two conditions, exit the function - return; - } - } - - // If we exceed N tries, throw an error - throw new Error( - i18n.t('question:message.error.synchronization_attempts', { - quantity: 36, - postProcess: 'capitalizeFirstChar', - }) - ); -}; - -export const publishMultipleQDNResources = async ( - data: any, - sender, - isFromExtension -) => { - const requiredFields = ['resources']; - const missingFields: string[] = []; - - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const resources = data.resources; - if (!Array.isArray(resources)) { - throw new Error( - i18n.t('group:message.generic.invalid_data', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - if (resources.length === 0) { - throw new Error( - i18n.t('question:message.error.no_resources_publish', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - const encrypt = data?.encrypt; - - for (const resource of resources) { - const resourceEncrypt = encrypt && resource?.disableEncrypt !== true; - - if (!resourceEncrypt && resource?.service.endsWith('_PRIVATE')) { - const errorMsg = i18n.t('question:message.error.only_encrypted_data', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } else if (resourceEncrypt && !resource?.service.endsWith('_PRIVATE')) { - const errorMsg = i18n.t('question:message.error.use_private_service', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - } - - const fee = await getFee('ARBITRARY'); - const registeredName = await getNameInfo(); - - const name = registeredName; - - if (!name) { - throw new Error( - i18n.t('question:message.error.registered_name', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - const userNames = await getAllUserNames(); - data.resources?.forEach((item) => { - if (item?.name && !userNames?.includes(item.name)) - throw new Error( - `The name ${item.name}, does not belong to the publisher.` - ); - }); - - const appFee = data?.appFee ? +data.appFee : undefined; - const appFeeRecipient = data?.appFeeRecipient; - let hasAppFee = false; - - if (appFee && appFee > 0 && appFeeRecipient) { - hasAppFee = true; - } - - const handleDynamicValues = {}; - if (hasAppFee) { - const feePayment = await getFee('PAYMENT'); - - (handleDynamicValues['appFee'] = +appFee + +feePayment.fee), - (handleDynamicValues['checkbox1'] = { - value: true, - label: i18n.t('question:accept_app_fee', { - postProcess: 'capitalizeFirstChar', - }), - }); - } - if (data?.encrypt) { - handleDynamicValues['highlightedText'] = `isEncrypted: ${!!data.encrypt}`; - } - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.publish_qdn', { - postProcess: 'capitalizeFirstChar', - }), - html: ` -
- - - ${data.resources - .map( - (resource) => ` -
-
Service: ${ - resource.service - }
-
Name: ${resource?.name || name}
-
Identifier: ${ - resource.identifier - }
- ${ - resource.filename - ? `
Filename: ${resource.filename}
` - : '' - } -
` - ) - .join('')} -
- - `, - fee: +fee.fee * resources.length, - ...handleDynamicValues, - }, - isFromExtension - ); - - const { accepted, checkbox1 = false } = resPermission; - if (!accepted) { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - type FailedPublish = { - reason: string; - identifier: any; - service: any; - }; - - const failedPublishesIdentifiers: FailedPublish[] = []; - - for (const resource of resources) { - try { - const requiredFields = ['service']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!resource[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - failedPublishesIdentifiers.push({ - reason: errorMsg, - identifier: resource.identifier, - service: resource.service, - name: resource?.name || name, - }); - continue; - } - if (!resource.file && !resource.data64 && !resource?.base64) { - const errorMsg = i18n.t( - 'question:message.error.no_data_file_submitted', - { - postProcess: 'capitalizeFirstChar', - } - ); - failedPublishesIdentifiers.push({ - reason: errorMsg, - identifier: resource.identifier, - service: resource.service, - name: resource?.name || name, - }); - continue; - } - const service = resource.service; - let identifier = resource.identifier; - 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; - } - - // Access tag1 to tag5 from result - const { tag1, tag2, tag3, tag4, tag5 } = result; - const resourceEncrypt = encrypt && resource?.disableEncrypt !== true; - if (resource.identifier == null) { - identifier = 'default'; - } - if (!resourceEncrypt && service.endsWith('_PRIVATE')) { - const errorMsg = i18n.t('question:message.error.only_encrypted_data', { - postProcess: 'capitalizeFirstChar', - }); - failedPublishesIdentifiers.push({ - reason: errorMsg, - identifier: resource.identifier, - service: resource.service, - name: resource?.name || name, - }); - continue; - } - if (resource.file) { - rawData = resource.file; - } - - if (resourceEncrypt) { - try { - if (resource?.file) { - rawData = await fileToBase64(resource.file); - } - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const privateKey = parsedData.privateKey; - const userPublicKey = parsedData.publicKey; - const encryptDataResponse = encryptDataGroup({ - data64: rawData, - publicKeys: data.publicKeys, - privateKey, - userPublicKey, - }); - if (encryptDataResponse) { - rawData = encryptDataResponse; - } - } catch (error) { - const errorMsg = - error?.message || - i18n.t('question:message.error.upload_encryption', { - postProcess: 'capitalizeFirstChar', - }); - failedPublishesIdentifiers.push({ - reason: errorMsg, - identifier: resource.identifier, - service: resource.service, - name: resource?.name || name, - }); - continue; - } - } - - try { - const dataType = - resource?.base64 || resource?.data64 || resourceEncrypt - ? 'base64' - : 'file'; - console.log('dataType', dataType); - await retryTransaction( - publishData, - [ - { - data: rawData, - registeredName: encodeURIComponent(resource?.name || name), - service: service, - identifier: encodeURIComponent(identifier), - uploadType: dataType, - filename: filename, - title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5, - apiVersion: 2, - withFee: true, - }, - ], - true - ); - await new Promise((res) => { - setTimeout(() => { - res(); - }, 1000); - }); - } catch (error) { - const errorMsg = - error.message || - i18n.t('question:message.error.upload', { - postProcess: 'capitalizeFirstChar', - }); - failedPublishesIdentifiers.push({ - reason: errorMsg, - identifier: resource.identifier, - service: resource.service, - name: resource?.name || name, - }); - } - } catch (error) { - failedPublishesIdentifiers.push({ - reason: - error?.message || - i18n.t('question:message.error.unknown_error', { - postProcess: 'capitalizeFirstChar', - }), - identifier: resource.identifier, - service: resource.service, - name: resource?.name || name, - }); - } - } - if (failedPublishesIdentifiers.length > 0) { - const obj = { - message: i18n.t('question:message.error.resources_publish', { - postProcess: 'capitalizeFirstChar', - }), - }; - obj['error'] = { - unsuccessfulPublishes: failedPublishesIdentifiers, - }; - return obj; - } - if (hasAppFee && checkbox1) { - sendCoinFunc( - { - amount: appFee, - receiver: appFeeRecipient, - }, - true - ); - } - return true; -}; - -export const voteOnPoll = async (data, isFromExtension) => { - const requiredFields = ['pollName', 'optionIndex']; - const missingFields: string[] = []; - - requiredFields.forEach((field) => { - if (!data[field] && data[field] !== 0) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const pollName = data.pollName; - const optionIndex = data.optionIndex; - let pollInfo = null; - try { - const url = await createEndpoint(`/polls/${encodeURIComponent(pollName)}`); - const response = await fetch(url); - if (!response.ok) { - const errorMessage = await parseErrorResponse( - response, - i18n.t('question:message.error.fetch_poll', { - postProcess: 'capitalizeFirstChar', - }) - ); - throw new Error(errorMessage); - } - - pollInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_poll', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - if (!pollInfo || pollInfo.error) { - const errorMsg = - (pollInfo && pollInfo.message) || - i18n.t('question:message.error.no_poll', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - try { - const optionName = pollInfo.pollOptions[optionIndex].optionName; - const resVoteOnPoll = await _voteOnPoll( - { pollName, optionIndex, optionName }, - isFromExtension - ); - return resVoteOnPoll; - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.poll_vote', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const createPoll = async (data, isFromExtension) => { - const requiredFields = [ - 'pollName', - 'pollDescription', - 'pollOptions', - 'pollOwnerAddress', - ]; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const pollName = data.pollName; - const pollDescription = data.pollDescription; - const pollOptions = data.pollOptions; - try { - const resCreatePoll = await _createPoll( - { - pollName, - pollDescription, - options: pollOptions, - }, - isFromExtension - ); - return resCreatePoll; - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.poll_create', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -function isBase64(str) { - const base64Regex = - /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; - return base64Regex.test(str) && str.length % 4 === 0; -} - -function checkValue(value) { - if (typeof value === 'string') { - if (isBase64(value)) { - return 'string'; - } else { - return 'string'; - } - } else if (typeof value === 'object' && value !== null) { - return 'object'; - } else { - throw new Error( - i18n.t('question:message.error.invalid_fullcontent', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -} - -export const sendChatMessage = async (data, isFromExtension, appInfo) => { - const message = data?.message; - const fullMessageObject = data?.fullMessageObject || data?.fullContent; - const recipient = data?.destinationAddress || data.recipient; - const groupId = data.groupId; - const isRecipient = groupId === undefined; - const chatReference = data?.chatReference; - if (groupId === undefined && recipient === undefined) { - throw new Error( - i18n.t('question:provide_recipient_group_id', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - let fullMessageObjectType; - if (fullMessageObject) { - fullMessageObjectType = checkValue(fullMessageObject); - } - const value = - (await getPermission(`qAPPSendChatMessage-${appInfo?.name}`)) || false; - let skip = false; - if (value) { - skip = true; - } - let resPermission; - if (!skip) { - resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.send_chat_message', { - postProcess: 'capitalizeFirstChar', - }), - text2: isRecipient - ? i18n.t('question:to_recipient', { - recipient: recipient, - postProcess: 'capitalizeFirstChar', - }) - : i18n.t('question:to_group', { - group_id: groupId, - postProcess: 'capitalizeFirstChar', - }), - text3: fullMessageObject - ? fullMessageObjectType === 'string' - ? `${fullMessageObject?.slice(0, 25)}${fullMessageObject?.length > 25 ? '...' : ''}` - : `${JSON.stringify(fullMessageObject)?.slice(0, 25)}${JSON.stringify(fullMessageObject)?.length > 25 ? '...' : ''}` - : `${message?.slice(0, 25)}${message?.length > 25 ? '...' : ''}`, - checkbox1: { - value: false, - label: i18n.t('question:always_chat_messages', { - postProcess: 'capitalizeFirstChar', - }), - }, - }, - isFromExtension - ); - } - const { accepted = false, checkbox1 = false } = resPermission || {}; - if (resPermission && accepted) { - setPermission(`qAPPSendChatMessage-${appInfo?.name}`, checkbox1); - } - if (accepted || skip) { - const tiptapJson = { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: message, - }, - ], - }, - ], - }; - const messageObject = fullMessageObject - ? fullMessageObject - : { - messageText: tiptapJson, - images: [], - repliedTo: '', - version: 3, - }; - - let stringifyMessageObject = JSON.stringify(messageObject); - if (fullMessageObjectType === 'string') { - stringifyMessageObject = messageObject; - } - - const balance = await getBalanceInfo(); - const hasEnoughBalance = +balance < 4 ? false : true; - if (!hasEnoughBalance) { - throw new Error( - i18n.t('group:message.error.qortals_required', { - quantity: 4, - postProcess: 'capitalizeFirstChar', - }) - ); - } - if (isRecipient && recipient) { - const url = await createEndpoint(`/addresses/publickey/${recipient}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_recipient_public_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - - let key; - let hasPublicKey; - let res; - const contentType = response.headers.get('content-type'); - - // If the response is JSON, parse it as JSON - if (contentType && contentType.includes('application/json')) { - res = await response.json(); - } else { - // Otherwise, treat it as plain text - res = await response.text(); - } - if (res?.error === 102) { - key = ''; - hasPublicKey = false; - } else if (res !== false) { - key = res; - hasPublicKey = true; - } else { - key = ''; - hasPublicKey = false; - } - - if (!hasPublicKey && isRecipient) { - throw new Error( - 'Cannot send an encrypted message to this user since they do not have their publickey on chain.' - ); - } - let _reference = new Uint8Array(64); - self.crypto.getRandomValues(_reference); - - let sendTimestamp = Date.now(); - - let reference = Base58.encode(_reference); - 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 handleDynamicValues = {}; - if (chatReference) { - handleDynamicValues['chatReference'] = chatReference; - } - - const tx = await createTransaction(18, keyPair, { - timestamp: sendTimestamp, - recipient: recipient, - recipientPublicKey: key, - hasChatReference: chatReference ? 1 : 0, - message: stringifyMessageObject, - lastReference: reference, - proofOfWorkNonce: 0, - isEncrypted: 1, - isText: 1, - ...handleDynamicValues, - }); - - const chatBytes = tx.chatBytes; - const difficulty = 8; - const { nonce, chatBytesArray } = await performPowTask( - chatBytes, - difficulty - ); - - let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); - if (_response?.error) { - throw new Error(_response?.message); - } - return _response; - } else if (!isRecipient && groupId) { - let _reference = new Uint8Array(64); - self.crypto.getRandomValues(_reference); - - let reference = Base58.encode(_reference); - 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 handleDynamicValues = {}; - if (chatReference) { - handleDynamicValues['chatReference'] = chatReference; - } - - const txBody = { - timestamp: Date.now(), - groupID: Number(groupId), - hasReceipient: 0, - hasChatReference: chatReference ? 1 : 0, - message: stringifyMessageObject, - lastReference: reference, - proofOfWorkNonce: 0, - isEncrypted: 0, // Set default to not encrypted for groups - isText: 1, - ...handleDynamicValues, - }; - - const tx = await createTransaction(181, keyPair, txBody); - - // if (!hasEnoughBalance) { - // throw new Error("Must have at least 4 QORT to send a chat message"); - // } - - const chatBytes = tx.chatBytes; - const difficulty = 8; - const { nonce, chatBytesArray } = await performPowTask( - chatBytes, - difficulty - ); - - let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); - if (_response?.error) { - throw new Error(_response?.message); - } - return _response; - } else { - throw new Error( - i18n.t('question:provide_recipient_group_id', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_send_message', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const joinGroup = async (data, isFromExtension) => { - const requiredFields = ['groupId']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${data.groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const fee = await getFee('JOIN_GROUP'); - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:message.generic.confirm_join_group', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `${groupInfo.groupName}`, - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const groupId = data.groupId; - - if (!groupInfo || groupInfo.error) { - const errorMsg = - (groupInfo && groupInfo.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - try { - const resJoinGroup = await joinGroupFunc({ groupId }); - return resJoinGroup; - } catch (error) { - throw new Error( - error?.message || - i18n.t('group:message.error.group_join', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_join', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const saveFile = async (data, sender, isFromExtension, snackMethods) => { - 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'); - const a = document.createElement('a'); - let locationUrl = `/arbitrary/${data.location.service}/${data.location.name}`; - if (data.location.identifier) { - locationUrl = locationUrl + `/${data.location.identifier}`; - } - const endpoint = await createEndpoint( - locationUrl + `?attachment=true&attachmentFilename=${data?.filename}` - ); - a.href = endpoint; - a.download = data.filename; - document.body.appendChild(a); - a.click(); - a.remove(); - return true; - } - const requiredFields = ['filename', 'blob']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const filename = data.filename; - const blob = data.blob; - - const mimeType = blob.type || data.mimeType; - const resPermission = await getUserPermission( - { - text1: i18n.t('question:download_file', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `${filename}`, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (!accepted) throw new Error('User declined to save file'); - showSaveFilePicker( - { - filename, - mimeType, - blob, - }, - snackMethods - ); - - return true; - - 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( - i18n.t('question:message.error.mime_type', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - if (!fileExtension) { - throw new Error( - i18n.t('question:message.error.file_extension', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - if (fileExtension && mimeType) { - fileHandleOptions = { - accept: { - [mimeType]: [fileExtension], - }, - }; - } - - showSaveFilePicker( - { - filename, - mimeType, - blob, - }, - snackMethods - ); - return true; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_save_file', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } catch (error) { - throw new Error( - error?.message || - i18n.t('core:message.error.initiate_download', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const deployAt = async (data, isFromExtension) => { - const requiredFields = [ - 'name', - 'description', - 'tags', - 'creationBytes', - 'amount', - 'assetId', - 'type', - ]; - - const missingFields: string[] = []; - - requiredFields.forEach((field) => { - if (!data[field] && data[field] !== 0) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - try { - const resDeployAt = await _deployAt( - { - name: data.name, - description: data.description, - tags: data.tags, - creationBytes: data.creationBytes, - amount: data.amount, - assetId: data.assetId, - atType: data.type, - }, - isFromExtension - ); - return resDeployAt; - } catch (error) { - throw new Error( - error?.message || - i18n.t('group:message.error.group_join', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const getUserWallet = async (data, isFromExtension, appInfo) => { - const requiredFields = ['coin']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const isGateway = await isRunningGateway(); - - if (data?.coin === 'ARRR' && isGateway) - throw new Error( - i18n.t('question:message.error.gateway_wallet_local_node', { - token: 'ARRR', - postProcess: 'capitalizeFirstChar', - }) - ); - - const value = - (await getPermission( - `qAPPAutoGetUserWallet-${appInfo?.name}-${data.coin}` - )) || false; - let skip = false; - if (value) { - skip = true; - } - - let resPermission; - - if (!skip) { - resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.get_wallet_info', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `coin: ${data.coin}`, - checkbox1: { - value: true, - label: i18n.t('question:always_retrieve_wallet', { - postProcess: 'capitalizeFirstChar', - }), - }, - }, - isFromExtension - ); - } - const { accepted = false, checkbox1 = false } = resPermission || {}; - - if (resPermission) { - setPermission( - `qAPPAutoGetUserWallet-${appInfo?.name}-${data.coin}`, - checkbox1 - ); - } - - if (accepted || skip) { - let coin = data.coin; - let userWallet = {}; - let arrrAddress = ''; - const wallet = await getSaveWallet(); - const address = wallet.address0; - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const arrrSeed58 = parsedData.arrrSeed58; - if (coin === 'ARRR') { - const bodyToString = arrrSeed58; - const url = await createEndpoint(`/crosschain/arrr/walletaddress`); - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: bodyToString, - }); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - if (res?.error && res?.message) { - throw new Error(res.message); - } - arrrAddress = res; - } - switch (coin) { - case 'QORT': - userWallet['address'] = address; - userWallet['publickey'] = parsedData.publicKey; - break; - case 'BTC': - userWallet['address'] = parsedData.btcAddress; - userWallet['publickey'] = parsedData.btcPublicKey; - break; - case 'LTC': - userWallet['address'] = parsedData.ltcAddress; - userWallet['publickey'] = parsedData.ltcPublicKey; - break; - case 'DOGE': - userWallet['address'] = parsedData.dogeAddress; - userWallet['publickey'] = parsedData.dogePublicKey; - break; - case 'DGB': - userWallet['address'] = parsedData.dgbAddress; - userWallet['publickey'] = parsedData.dgbPublicKey; - break; - case 'RVN': - userWallet['address'] = parsedData.rvnAddress; - userWallet['publickey'] = parsedData.rvnPublicKey; - break; - case 'ARRR': - await checkArrrSyncStatus(parsedData.arrrSeed58); - userWallet['address'] = arrrAddress; - break; - default: - break; - } - return userWallet; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const getWalletBalance = async ( - data, - bypassPermission?: boolean, - isFromExtension?: boolean, - appInfo?: any -) => { - const requiredFields = ['coin']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const isGateway = await isRunningGateway(); - - if (data?.coin === 'ARRR' && isGateway) - throw new Error( - i18n.t('question:message.error.gateway_balance_local_node', { - token: 'ARRR', - postProcess: 'capitalizeFirstChar', - }) - ); - - const value = - (await getPermission( - `qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}` - )) || false; - let skip = false; - if (value) { - skip = true; - } - let resPermission; - - if (!bypassPermission && !skip) { - resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.fetch_balance', { - coin: data.coin, // TODO highlight coin in the modal - postProcess: 'capitalizeFirstChar', - }), - checkbox1: { - value: true, - label: i18n.t('question:always_retrieve_balance', { - postProcess: 'capitalizeFirstChar', - }), - }, - }, - isFromExtension - ); - } - const { accepted = false, checkbox1 = false } = resPermission || {}; - if (resPermission) { - setPermission( - `qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}`, - checkbox1 - ); - } - if (accepted || bypassPermission || skip) { - let coin = data.coin; - const wallet = await getSaveWallet(); - const address = wallet.address0; - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - if (coin === 'QORT') { - let qortAddress = address; - try { - const url = await createEndpoint(`/addresses/balance/${qortAddress}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_balance', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.fetch_wallet', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else { - let _url = ``; - let _body = null; - switch (coin) { - case 'BTC': - _url = await createEndpoint(`/crosschain/btc/walletbalance`); - - _body = parsedData.btcPublicKey; - break; - case 'LTC': - _url = await createEndpoint(`/crosschain/ltc/walletbalance`); - _body = parsedData.ltcPublicKey; - break; - case 'DOGE': - _url = await createEndpoint(`/crosschain/doge/walletbalance`); - _body = parsedData.dogePublicKey; - break; - case 'DGB': - _url = await createEndpoint(`/crosschain/dgb/walletbalance`); - _body = parsedData.dgbPublicKey; - break; - case 'RVN': - _url = await createEndpoint(`/crosschain/rvn/walletbalance`); - _body = parsedData.rvnPublicKey; - break; - case 'ARRR': - await checkArrrSyncStatus(parsedData.arrrSeed58); - _url = await createEndpoint(`/crosschain/arrr/walletbalance`); - _body = parsedData.arrrSeed58; - break; - default: - break; - } - try { - const response = await fetch(_url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: _body, - }); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - if (res?.error && res?.message) { - throw new Error(res.message); - } - if (isNaN(Number(res))) { - throw new Error( - i18n.t('question:message.error.fetch_balance', { - postProcess: 'capitalizeFirstChar', - }) - ); - } else { - return (Number(res) / 1e8).toFixed(8); - } - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.fetch_balance', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -const getPirateWallet = async (arrrSeed58) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.error.gateway_retrieve_balance', { - token: 'PIRATECHAIN', - postProcess: 'capitalizeFirstChar', - }) - ); - } - const bodyToString = arrrSeed58; - await checkArrrSyncStatus(bodyToString); - const url = await createEndpoint(`/crosschain/arrr/walletaddress`); - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: bodyToString, - }); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - if (res?.error && res?.message) { - throw new Error(res.message); - } - return res; -}; - -export const getUserWalletFunc = async (coin) => { - let userWallet = {}; - const wallet = await getSaveWallet(); - const address = wallet.address0; - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - switch (coin) { - case 'QORT': - userWallet['address'] = address; - userWallet['publickey'] = parsedData.publicKey; - break; - case 'BTC': - case 'BITCOIN': - userWallet['address'] = parsedData.btcAddress; - userWallet['publickey'] = parsedData.btcPublicKey; - break; - case 'LTC': - case 'LITECOIN': - userWallet['address'] = parsedData.ltcAddress; - userWallet['publickey'] = parsedData.ltcPublicKey; - break; - case 'DOGE': - case 'DOGECOIN': - userWallet['address'] = parsedData.dogeAddress; - userWallet['publickey'] = parsedData.dogePublicKey; - break; - case 'DGB': - case 'DIGIBYTE': - userWallet['address'] = parsedData.dgbAddress; - userWallet['publickey'] = parsedData.dgbPublicKey; - break; - case 'RVN': - case 'RAVENCOIN': - userWallet['address'] = parsedData.rvnAddress; - userWallet['publickey'] = parsedData.rvnPublicKey; - break; - case 'ARRR': - case 'PIRATECHAIN': - const arrrAddress = await getPirateWallet(parsedData.arrrSeed58); - userWallet['address'] = arrrAddress; - break; - default: - break; - } - return userWallet; -}; - -export const getUserWalletInfo = async (data, isFromExtension, appInfo) => { - const requiredFields = ['coin']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - if (data?.coin === 'ARRR') { - throw new Error( - i18n.t('question:message.error.token_not_supported', { - token: 'ARRR', - postProcess: 'capitalizeFirstChar', - }) - ); - } - const value = - (await getPermission(`getUserWalletInfo-${appInfo?.name}-${data.coin}`)) || - false; - let skip = false; - if (value) { - skip = true; - } - let resPermission; - - if (!skip) { - resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.get_wallet_info', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `coin: ${data.coin}`, - checkbox1: { - value: true, - label: i18n.t('question:always_retrieve_wallet', { - postProcess: 'capitalizeFirstChar', - }), - }, - }, - isFromExtension - ); - } - const { accepted = false, checkbox1 = false } = resPermission || {}; - - if (resPermission) { - setPermission(`getUserWalletInfo-${appInfo?.name}-${data.coin}`, checkbox1); - } - - if (accepted || skip) { - let coin = data.coin; - let walletKeys = await getUserWalletFunc(coin); - - const _url = await createEndpoint( - `/crosschain/` + data.coin.toLowerCase() + `/addressinfos` - ); - let _body = { xpub58: walletKeys['publickey'] }; - try { - const response = await fetch(_url, { - method: 'POST', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(_body), - }); - if (!response?.ok) - throw new Error( - i18n.t('question:message.error.fetch_wallet_info', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - if (res?.error && res?.message) { - throw new Error(res.message); - } - - return res; - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.fetch_wallet', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const getUserWalletTransactions = async ( - data, - isFromExtension, - appInfo -) => { - const requiredFields = ['coin']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const value = - (await getPermission( - `getUserWalletTransactions-${appInfo?.name}-${data.coin}` - )) || false; - let skip = false; - if (value) { - skip = true; - } - let resPermission; - - if (!skip) { - resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.get_wallet_transactions', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `coin: ${data.coin}`, - checkbox1: { - value: true, - label: i18n.t('question:always_retrieve_wallet_transactions', { - postProcess: 'capitalizeFirstChar', - }), - }, - }, - isFromExtension - ); - } - const { accepted = false, checkbox1 = false } = resPermission || {}; - - if (resPermission) { - setPermission( - `getUserWalletTransactions-${appInfo?.name}-${data.coin}`, - checkbox1 - ); - } - - if (accepted || skip) { - const coin = data.coin; - const walletKeys = await getUserWalletFunc(coin); - let publicKey; - if (data?.coin === 'ARRR') { - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - publicKey = parsedData.arrrSeed58; - } else { - publicKey = walletKeys['publickey']; - } - - const _url = await createEndpoint( - `/crosschain/` + data.coin.toLowerCase() + `/wallettransactions` - ); - const _body = publicKey; - try { - const response = await fetch(_url, { - method: 'POST', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - body: _body, - }); - if (!response?.ok) - throw new Error( - i18n.t('question:message.error.fetch_wallet_transactions', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - if (res?.error && res?.message) { - throw new Error(res.message); - } - - return res; - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.fetch_wallet_transactions', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const getCrossChainServerInfo = async (data) => { - const requiredFields = ['coin']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const _url = `/crosschain/` + data.coin.toLowerCase() + `/serverinfos`; - try { - const url = await createEndpoint(_url); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_generic', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - if (res?.error && res?.message) { - throw new Error(res.message); - } - return res.servers; - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.server_info', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const getTxActivitySummary = async (data) => { - const requiredFields = ['coin']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const coin = data.coin; - const url = `/crosschain/txactivity?foreignBlockchain=${coin}`; // No apiKey here - - try { - const endpoint = await createEndpoint(url); - const response = await fetch(endpoint, { - method: 'POST', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_generic', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - if (res?.error && res?.message) { - throw new Error(res.message); - } - return res; // Return full response here - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.transaction_activity_summary', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const getForeignFee = async (data) => { - const requiredFields = ['coin', 'type']; - const missingFields: string[] = []; - - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const { coin, type } = data; - const url = `/crosschain/${coin.toLowerCase()}/${type}`; - - try { - const endpoint = await createEndpoint(url); - const response = await fetch(endpoint, { - method: 'GET', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_generic', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - if (res?.error && res?.message) { - throw new Error(res.message); - } - return res; // Return full response here - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.get_foreign_fee', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -function calculateRateFromFee(totalFee, sizeInBytes) { - const fee = (totalFee / sizeInBytes) * 1000; - return fee.toFixed(0); -} - -export const updateForeignFee = async (data, isFromExtension) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const requiredFields = ['coin', 'type', 'value']; - const missingFields: string[] = []; - - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const { coin, type, value } = data; - - const text3 = - type === 'feerequired' - ? i18n.t('question:sats', { - amount: value, - postProcess: 'capitalizeFirstChar', - }) - : i18n.t('question:sats_per_kb', { - amount: value, - postProcess: 'capitalizeFirstChar', - }); - const text4 = - type === 'feerequired' - ? i18n.t('question:message.generic.calculate_fee', { - amount: value, - rate: calculateRateFromFee(value, 300), - postProcess: 'capitalizeFirstChar', - }) - : ''; - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.update_foreign_fee', { - postProcess: 'capitalizeFirstChar', - }), - text2: `type: ${type === 'feerequired' ? 'unlocking' : 'locking'}`, - text3: i18n.t('question:value', { - value: text3, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('question:coin', { - coin: coin, - postProcess: 'capitalizeFirstChar', - }), - }, - isFromExtension - ); - - const { accepted } = resPermission; - if (!accepted) { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const url = `/crosschain/${coin.toLowerCase()}/update${type}`; - const valueStringified = JSON.stringify(+value); - - const endpoint = await createEndpoint(url); - const response = await fetch(endpoint, { - method: 'POST', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - body: valueStringified, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.update_foreign_fee', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - if (res?.error && res?.message) { - throw new Error(res.message); - } - return res; // Return full response here -}; - -export const getServerConnectionHistory = async (data) => { - const requiredFields = ['coin']; - const missingFields: string[] = []; - - // Validate required fields - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const coin = data.coin.toLowerCase(); - const url = `/crosschain/${coin.toLowerCase()}/serverconnectionhistory`; - - try { - const endpoint = await createEndpoint(url); // Assuming createEndpoint is available - const response = await fetch(endpoint, { - method: 'GET', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_connection_history', { - postProcess: 'capitalizeFirstChar', - }) - ); - - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - - if (res?.error && res?.message) { - throw new Error(res.message); - } - - return res; // Return full response here - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.fetch_connection_history', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const setCurrentForeignServer = async (data, isFromExtension) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const requiredFields = ['coin']; - const missingFields: string[] = []; - - // Validate required fields - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const { coin, host, port, type } = data; - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.set_current_server', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:server_type', { - type: type, - postProcess: 'capitalizeFirstChar', - }), - text3: i18n.t('question:server_host', { - host: host, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('question:coin', { - coin: coin, - postProcess: 'capitalizeFirstChar', - }), - }, - isFromExtension - ); - - const { accepted } = resPermission; - if (!accepted) { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const body = { - hostName: host, - port: port, - connectionType: type, - }; - - const url = `/crosschain/${coin.toLowerCase()}/setcurrentserver`; - - const endpoint = await createEndpoint(url); // Assuming createEndpoint is available - const response = await fetch(endpoint, { - method: 'POST', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.server_current_set', { - postProcess: 'capitalizeFirstChar', - }) - ); - - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - - if (res?.error && res?.message) { - throw new Error(res.message); - } - - return res; // Return the full response -}; - -export const addForeignServer = async (data, isFromExtension) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const requiredFields = ['coin']; - const missingFields: string[] = []; - - // Validate required fields - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const { coin, host, port, type } = data; - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.server_add', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:server_type', { - type: type, - postProcess: 'capitalizeFirstChar', - }), - text3: i18n.t('question:server_host', { - host: host, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('question:coin', { - coin: coin, - postProcess: 'capitalizeFirstChar', - }), - }, - isFromExtension - ); - - const { accepted } = resPermission; - if (!accepted) { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const body = { - hostName: host, - port: port, - connectionType: type, - }; - - const url = `/crosschain/${coin.toLowerCase()}/addserver`; - - const endpoint = await createEndpoint(url); // Assuming createEndpoint is available - const response = await fetch(endpoint, { - method: 'POST', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.server_current_add', { - postProcess: 'capitalizeFirstChar', - }) - ); - - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - - if (res?.error && res?.message) { - throw new Error(res.message); - } - - return res; // Return the full response -}; - -export const removeForeignServer = async (data, isFromExtension) => { - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const requiredFields = ['coin']; - const missingFields: string[] = []; - - // Validate required fields - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const { coin, host, port, type } = data; - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.server_remove', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:server_type', { - type: type, - postProcess: 'capitalizeFirstChar', - }), - text3: i18n.t('question:server_host', { - host: host, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('question:coin', { - coin: coin, - postProcess: 'capitalizeFirstChar', - }), - }, - isFromExtension - ); - - const { accepted } = resPermission; - if (!accepted) { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const body = { - hostName: host, - port: port, - connectionType: type, - }; - - const url = `/crosschain/${coin.toLowerCase()}/removeserver`; - - const endpoint = await createEndpoint(url); // Assuming createEndpoint is available - const response = await fetch(endpoint, { - method: 'POST', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.server_remove', { - postProcess: 'capitalizeFirstChar', - }) - ); - - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - - if (res?.error && res?.message) { - throw new Error(res.message); - } - - return res; // Return the full response -}; - -export const getDaySummary = async () => { - const url = `/admin/summary`; // Simplified endpoint URL - - try { - const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL - const response = await fetch(endpoint, { - method: 'GET', - headers: { - Accept: '*/*', - }, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.retrieve_summary', { - postProcess: 'capitalizeFirstChar', - }) - ); - - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - - if (res?.error && res?.message) { - throw new Error(res.message); - } - - return res; // Return the full response - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.retrieve_summary', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const getNodeInfo = async () => { - const url = `/admin/info`; // Simplified endpoint URL - - try { - const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL - const response = await fetch(endpoint, { - method: 'GET', - headers: { - Accept: '*/*', - }, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.node_info', { - postProcess: 'capitalizeFirstChar', - }) - ); - - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - - if (res?.error && res?.message) { - throw new Error(res.message); - } - - return res; // Return the full response - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.node_info', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const getNodeStatus = async () => { - const url = `/admin/status`; // Simplified endpoint URL - - try { - const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL - const response = await fetch(endpoint, { - method: 'GET', - headers: { - Accept: '*/*', - }, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.node_status', { - postProcess: 'capitalizeFirstChar', - }) - ); - - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - - if (res?.error && res?.message) { - throw new Error(res.message); - } - - return res; // Return the full response - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.node_status', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const getArrrSyncStatus = async () => { - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const arrrSeed = parsedData.arrrSeed58; - const url = `/crosschain/arrr/syncstatus`; // Simplified endpoint URL - - try { - const endpoint = await createEndpoint(url); // Assuming createEndpoint is available for constructing the full URL - const response = await fetch(endpoint, { - method: 'POST', - headers: { - Accept: '*/*', - }, - body: arrrSeed, - }); - - let res; - - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - - return res; // Return the full response - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.retrieve_sync_status', { - token: 'ARRR', - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const sendCoin = async (data, isFromExtension) => { - const requiredFields = ['coin', 'amount']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - if (!data?.destinationAddress && !data?.recipient) { - throw new Error( - i18n.t('question:message.error.missing_fields', { - fields: 'recipient', - postProcess: 'capitalizeFirstChar', - }) - ); - } - let checkCoin = data.coin; - const wallet = await getSaveWallet(); - const address = wallet.address0; - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const isGateway = await isRunningGateway(); - - if (checkCoin !== 'QORT' && isGateway) - throw new Error( - i18n.t('question:message.error.gateway_non_qort_local_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - if (checkCoin === 'QORT') { - // Params: data.coin, data.recipient, data.amount, data.fee - // TODO: prompt user to send. If they confirm, call `POST /crosschain/:coin/send`, or for QORT, broadcast a PAYMENT transaction - // then set the response string from the core to the `response` variable (defined above) - // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}` - const amount = Number(data.amount); - const recipient = data?.recipient || data.destinationAddress; - - const url = await createEndpoint(`/addresses/balance/${address}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_balance', { - postProcess: 'capitalizeFirstChar', - }) - ); - let walletBalance; - try { - walletBalance = await response.clone().json(); - } catch (e) { - walletBalance = await response.text(); - } - if (isNaN(Number(walletBalance))) { - const errorMsg = i18n.t('question:message.error.fetch_balance_token', { - token: 'QORT', - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const transformDecimals = (Number(walletBalance) * QORT_DECIMALS).toFixed( - 0 - ); - const walletBalanceDecimals = Number(transformDecimals); - const amountDecimals = Number(amount) * QORT_DECIMALS; - const fee: number = await sendQortFee(); - if (amountDecimals + fee * QORT_DECIMALS > walletBalanceDecimals) { - const errorMsg = i18n.t('question:message.error.insufficient_funds', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - if (amount <= 0) { - const errorMsg = i18n.t('core:message.error.invalid_amount', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - if (recipient.length === 0) { - const errorMsg = i18n.t('question:message.error.empty_receiver', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.send_coins', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:to_recipient', { - recipient: recipient, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `${amount} ${checkCoin}`, - fee: fee, - confirmCheckbox: true, - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const makePayment = await sendCoinFunc( - { amount, password: null, receiver: recipient }, - true - ); - return makePayment.res?.data; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else if (checkCoin === 'BTC') { - const amount = Number(data.amount); - const recipient = data?.recipient || data.destinationAddress; - const xprv58 = parsedData.btcPrivateKey; - const feePerByte = data.fee ? data.fee : btcFeePerByte; - - const btcWalletBalance = await getWalletBalance({ coin: checkCoin }, true); - - if (isNaN(Number(btcWalletBalance))) { - throw new Error( - i18n.t('question:message.error.fetch_balance_token', { - token: 'BTC', - postProcess: 'capitalizeFirstChar', - }) - ); - } - const btcWalletBalanceDecimals = Number(btcWalletBalance); - const btcAmountDecimals = Number(amount); - const fee = feePerByte * 500; // default 0.00050000 - if (btcAmountDecimals + fee > btcWalletBalanceDecimals) { - throw new Error( - i18n.t('question:message.error.insufficient_funds', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.send_coins', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:to_recipient', { - recipient: recipient, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `${amount} ${checkCoin}`, - foreignFee: `${fee} BTC`, - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const opts = { - xprv58: xprv58, - receivingAddress: recipient, - bitcoinAmount: amount, - feePerByte: feePerByte, - }; - const url = await createEndpoint(`/crosschain/btc/send`); - - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(opts), - }); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.send', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else if (checkCoin === 'LTC') { - const amount = Number(data.amount); - const recipient = data?.recipient || data.destinationAddress; - const xprv58 = parsedData.ltcPrivateKey; - const feePerByte = data.fee ? data.fee : ltcFeePerByte; - const ltcWalletBalance = await getWalletBalance({ coin: checkCoin }, true); - - if (isNaN(Number(ltcWalletBalance))) { - const errorMsg = i18n.t('question:message.error.fetch_balance_token', { - token: 'LTC', - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const ltcWalletBalanceDecimals = Number(ltcWalletBalance); - const ltcAmountDecimals = Number(amount); - const fee = feePerByte * 1000; // default 0.00030000 - if (ltcAmountDecimals + fee > ltcWalletBalanceDecimals) { - throw new Error( - i18n.t('question:message.error.insufficient_funds', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.send_coins', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:to_recipient', { - recipient: recipient, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `${amount} ${checkCoin}`, - foreignFee: `${fee} LTC`, - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const url = await createEndpoint(`/crosschain/ltc/send`); - const opts = { - xprv58: xprv58, - receivingAddress: recipient, - litecoinAmount: amount, - feePerByte: feePerByte, - }; - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(opts), - }); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.send', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else if (checkCoin === 'DOGE') { - const amount = Number(data.amount); - const recipient = data?.recipient || data.destinationAddress; - const xprv58 = parsedData.dogePrivateKey; - const feePerByte = data.fee ? data.fee : dogeFeePerByte; - const dogeWalletBalance = await getWalletBalance({ coin: checkCoin }, true); - if (isNaN(Number(dogeWalletBalance))) { - const errorMsg = i18n.t('question:message.error.fetch_balance_token', { - token: 'DOGE', - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const dogeWalletBalanceDecimals = Number(dogeWalletBalance); - const dogeAmountDecimals = Number(amount); - const fee = feePerByte * 5000; // default 0.05000000 - if (dogeAmountDecimals + fee > dogeWalletBalanceDecimals) { - const errorMsg = i18n.t('question:message.error.insufficient_funds', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.send_coins', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:to_recipient', { - recipient: recipient, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `${amount} ${checkCoin}`, - foreignFee: `${fee} DOGE`, - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const opts = { - xprv58: xprv58, - receivingAddress: recipient, - dogecoinAmount: amount, - feePerByte: feePerByte, - }; - const url = await createEndpoint(`/crosschain/doge/send`); - - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(opts), - }); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.send', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else if (checkCoin === 'DGB') { - const amount = Number(data.amount); - const recipient = data?.recipient || data.destinationAddress; - const xprv58 = parsedData.dbgPrivateKey; - const feePerByte = data.fee ? data.fee : dgbFeePerByte; - const dgbWalletBalance = await getWalletBalance({ coin: checkCoin }, true); - if (isNaN(Number(dgbWalletBalance))) { - const errorMsg = i18n.t('question:message.error.fetch_balance_token', { - token: 'DGB', - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const dgbWalletBalanceDecimals = Number(dgbWalletBalance); - const dgbAmountDecimals = Number(amount); - const fee = feePerByte * 500; // default 0.00005000 - if (dgbAmountDecimals + fee > dgbWalletBalanceDecimals) { - const errorMsg = i18n.t('question:message.error.insufficient_funds', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.send_coins', { - postProcess: 'capitalizeFirstChar', - }), - text2: `To: ${recipient}`, - highlightedText: `${amount} ${checkCoin}`, - foreignFee: `${fee} DGB`, - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const opts = { - xprv58: xprv58, - receivingAddress: recipient, - digibyteAmount: amount, - feePerByte: feePerByte, - }; - const url = await createEndpoint(`/crosschain/dgb/send`); - - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(opts), - }); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.send', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else if (checkCoin === 'RVN') { - const amount = Number(data.amount); - const recipient = data?.recipient || data.destinationAddress; - const xprv58 = parsedData.rvnPrivateKey; - const feePerByte = data.fee ? data.fee : rvnFeePerByte; - const rvnWalletBalance = await getWalletBalance({ coin: checkCoin }, true); - if (isNaN(Number(rvnWalletBalance))) { - const errorMsg = i18n.t('question:message.error.fetch_balance_token', { - token: 'RVN', - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const rvnWalletBalanceDecimals = Number(rvnWalletBalance); - const rvnAmountDecimals = Number(amount); - const fee = feePerByte * 500; // default 0.00562500 - if (rvnAmountDecimals + fee > rvnWalletBalanceDecimals) { - const errorMsg = i18n.t('question:message.error.insufficient_funds', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.send_coins', { - postProcess: 'capitalizeFirstChar', - }), - text2: `To: ${recipient}`, - highlightedText: `${amount} ${checkCoin}`, - foreignFee: `${fee} RVN`, - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const opts = { - xprv58: xprv58, - receivingAddress: recipient, - ravencoinAmount: amount, - feePerByte: feePerByte, - }; - const url = await createEndpoint(`/crosschain/rvn/send`); - - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(opts), - }); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.send', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } else if (checkCoin === 'ARRR') { - const amount = Number(data.amount); - const recipient = data?.recipient || data.destinationAddress; - const memo = data?.memo; - const arrrWalletBalance = await getWalletBalance({ coin: checkCoin }, true); - - if (isNaN(Number(arrrWalletBalance))) { - const errorMsg = i18n.t('question:message.error.fetch_balance_token', { - token: 'ARR', - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const arrrWalletBalanceDecimals = Number(arrrWalletBalance); - const arrrAmountDecimals = Number(amount); - const fee = 0.0001; - if (arrrAmountDecimals + fee > arrrWalletBalanceDecimals) { - const errorMsg = i18n.t('question:message.error.insufficient_funds', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.send_coins', { - postProcess: 'capitalizeFirstChar', - }), - text2: `To: ${recipient}`, - highlightedText: `${amount} ${checkCoin}`, - foreignFee: `${fee} ARRR`, - }, - isFromExtension - ); - const { accepted } = resPermission; - - if (accepted) { - const opts = { - entropy58: parsedData.arrrSeed58, - receivingAddress: recipient, - arrrAmount: amount, - memo: memo, - }; - const url = await createEndpoint(`/crosschain/arrr/send`); - - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(opts), - }); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.send', { - postProcess: 'capitalizeFirstChar', - }) - ); - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } -}; - -function calculateFeeFromRate(feePerKb, sizeInBytes) { - return (feePerKb / 1000) * sizeInBytes; -} - -const getBuyingFees = async (foreignBlockchain) => { - const ticker = sellerForeignFee[foreignBlockchain].ticker; - if (!ticker) throw new Error('invalid foreign blockchain'); - const unlockFee = await getForeignFee({ - coin: ticker, - type: 'feerequired', - }); - const lockFee = await getForeignFee({ - coin: ticker, - type: 'feekb', - }); - return { - ticker: ticker, - lock: { - sats: lockFee, - fee: lockFee / QORT_DECIMALS, - }, - unlock: { - sats: unlockFee, - fee: unlockFee / QORT_DECIMALS, - feePerKb: +calculateRateFromFee(+unlockFee, 300) / QORT_DECIMALS, - }, - }; -}; - -export const createBuyOrder = async (data, isFromExtension) => { - const requiredFields = ['crosschainAtInfo', 'foreignBlockchain']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const isGateway = await isRunningGateway(); - const foreignBlockchain = data.foreignBlockchain; - const atAddresses = data.crosschainAtInfo?.map( - (order) => order.qortalAtAddress - ); - - const atPromises = atAddresses.map((atAddress) => - requestQueueGetAtAddresses.enqueue(async () => { - const url = await createEndpoint(`/crosschain/trade/${atAddress}`); - const resAddress = await fetch(url); - const resData = await resAddress.json(); - if (foreignBlockchain !== resData?.foreignBlockchain) { - throw new Error( - i18n.t('core:message.error.same_foreign_blockchain', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - return resData; - }) - ); - - const crosschainAtInfo = await Promise.all(atPromises); - - try { - const buyingFees = await getBuyingFees(foreignBlockchain); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.buy_order', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:permission.buy_order_quantity', { - quantity: atAddresses?.length, - postProcess: 'capitalizeFirstChar', - }), - text3: i18n.t('question:permission.buy_order_ticker', { - qort_amount: crosschainAtInfo?.reduce((latest, cur) => { - return latest + +cur?.qortAmount; - }, 0), - foreign_amount: roundUpToDecimals( - crosschainAtInfo?.reduce((latest, cur) => { - return latest + +cur?.expectedForeignAmount; - }, 0) - ), - ticker: buyingFees.ticker, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('auth:node.using_public_gateway', { - gateway: isGateway, - postProcess: 'capitalizeFirstChar', - }), - fee: '', - html: ` -
- - -
-
${i18n.t('question:total_unlocking_fee', { - postProcess: 'capitalizeFirstChar', - })}
-
${(+buyingFees?.unlock?.fee * atAddresses?.length)?.toFixed(8)} ${buyingFees.ticker}
-
- ${i18n.t('question:permission.buy_order_fee_estimation', { - quantity: atAddresses?.length, - fee: buyingFees?.unlock?.feePerKb?.toFixed(8), - ticker: buyingFees.ticker, - postProcess: 'capitalizeFirstChar', - })} -
-
${i18n.t('question:total_locking_fee', { - postProcess: 'capitalizeFirstChar', - })}
-
${i18n.t('question:permission.buy_order_per_kb', { - fee: +buyingFees?.lock.fee.toFixed(8), - ticker: buyingFees.ticker, - postProcess: 'capitalizeFirstChar', - })} -
-
-
-`, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const resBuyOrder = await createBuyOrderTx({ - crosschainAtInfo, - isGateway, - foreignBlockchain, - }); - return resBuyOrder; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.buy_order', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -const cancelTradeOfferTradeBot = async (body, keyPair) => { - const txn = new DeleteTradeOffer().createTransaction(body); - const url = await createEndpoint(`/crosschain/tradeoffer`); - const bodyToString = JSON.stringify(txn); - - const deleteTradeBotResponse = await fetch(url, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, - body: bodyToString, - }); - - if (!deleteTradeBotResponse.ok) { - throw new Error( - i18n.t('question:message.error.update_tradebot', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - const unsignedTxn = await deleteTradeBotResponse.text(); - const signedTxnBytes = await signTradeBotTransaction(unsignedTxn, keyPair); - const signedBytes = Base58.encode(signedTxnBytes); - - let res; - try { - res = await processTransactionVersion2(signedBytes); - } catch (error) { - return { - error: i18n.t('question:message.error.cancel_sell_order', { - postProcess: 'capitalizeFirstChar', - }), - failedTradeBot: { - atAddress: body.atAddress, - creatorAddress: body.creatorAddress, - }, - }; - } - if (res?.error) { - return { - error: i18n.t('question:message.error.cancel_sell_order', { - postProcess: 'capitalizeFirstChar', - }), - failedTradeBot: { - atAddress: body.atAddress, - creatorAddress: body.creatorAddress, - }, - }; - } - if (res?.signature) { - return res; - } else { - throw new Error( - i18n.t('question:message.error.cancel_sell_order', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; -const findFailedTradebot = async (createBotCreationTimestamp, body) => { - //wait 5 secs - const wallet = await getSaveWallet(); - const address = wallet.address0; - await new Promise((res) => { - setTimeout(() => { - res(null); - }, 5000); - }); - const url = await createEndpoint( - `/crosschain/tradebot?foreignBlockchain=LITECOIN` - ); - - const tradeBotsReponse = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - const data = await tradeBotsReponse.json(); - const latestItem2 = data - .filter((item) => item.creatorAddress === address) - .sort((a, b) => b.timestamp - a.timestamp)[0]; - const latestItem = data - .filter( - (item) => - item.creatorAddress === address && - +item.foreignAmount === +body.foreignAmount - ) - .sort((a, b) => b.timestamp - a.timestamp)[0]; - if ( - latestItem && - createBotCreationTimestamp - latestItem.timestamp <= 5000 && - createBotCreationTimestamp > latestItem.timestamp // Ensure latestItem's timestamp is before createBotCreationTimestamp - ) { - return latestItem; - } else { - return null; - } -}; -const tradeBotCreateRequest = async (body, keyPair) => { - const txn = new TradeBotCreateRequest().createTransaction(body); - const url = await createEndpoint(`/crosschain/tradebot/create`); - const bodyToString = JSON.stringify(txn); - - const unsignedTxnResponse = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: bodyToString, - }); - if (!unsignedTxnResponse.ok) - throw new Error( - i18n.t('question:message.error.create_tradebot', { - postProcess: 'capitalizeFirstChar', - }) - ); - const createBotCreationTimestamp = Date.now(); - const unsignedTxn = await unsignedTxnResponse.text(); - const signedTxnBytes = await signTradeBotTransaction(unsignedTxn, keyPair); - const signedBytes = Base58.encode(signedTxnBytes); - - let res; - try { - res = await processTransactionVersion2(signedBytes); - } catch (error) { - const findFailedTradeBot = await findFailedTradebot( - createBotCreationTimestamp, - body - ); - return { - error: i18n.t('question:message.error.create_sell_order', { - postProcess: 'capitalizeFirstChar', - }), - failedTradeBot: findFailedTradeBot, - }; - } - - if (res?.signature) { - return res; - } else { - throw new Error( - i18n.t('question:message.error.create_sell_order', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const createSellOrder = async (data, isFromExtension) => { - const requiredFields = ['qortAmount', 'foreignBlockchain', 'foreignAmount']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const parsedForeignAmount = Number(data.foreignAmount)?.toFixed(8); - - const receivingAddress = await getUserWalletFunc(data.foreignBlockchain); - try { - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.sell_order', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:permission.order_detail', { - qort_amount: data.qortAmount, - foreign_amount: parsedForeignAmount, - ticker: data.foreignBlockchain, - postProcess: 'capitalizeFirstChar', - }), - fee: '0.02', - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const userPublicKey = parsedData.publicKey; - const uint8PrivateKey = Base58.decode(parsedData.privateKey); - const uint8PublicKey = Base58.decode(parsedData.publicKey); - const keyPair = { - privateKey: uint8PrivateKey, - publicKey: uint8PublicKey, - }; - const response = await tradeBotCreateRequest( - { - creatorPublicKey: userPublicKey, - qortAmount: parseFloat(data.qortAmount), - fundingQortAmount: parseFloat(data.qortAmount) + 0.01, - foreignBlockchain: data.foreignBlockchain, - foreignAmount: parseFloat(parsedForeignAmount), - tradeTimeout: 120, - receivingAddress: receivingAddress.address, - }, - keyPair - ); - - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.submit_sell_order', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const cancelSellOrder = async (data, isFromExtension) => { - const requiredFields = ['atAddress']; - const missingFields: string[] = []; - - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const url = await createEndpoint(`/crosschain/trade/${data.atAddress}`); - const resAddress = await fetch(url); - const resData = await resAddress.json(); - - if (!resData?.qortalAtAddress) - throw new Error( - i18n.t('question:message.error.at_info', { - postProcess: 'capitalizeFirstChar', - }) - ); - - try { - const fee = await getFee('MESSAGE'); - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.cancel_sell_order', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:permission.order_detail', { - qort_amount: resData.qortAmount, - foreign_amount: resData.expectedForeignAmount, - ticker: resData.foreignBlockchain, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const userPublicKey = parsedData.publicKey; - const uint8PrivateKey = Base58.decode(parsedData.privateKey); - const uint8PublicKey = Base58.decode(parsedData.publicKey); - const keyPair = { - privateKey: uint8PrivateKey, - publicKey: uint8PublicKey, - }; - const response = await cancelTradeOfferTradeBot( - { - creatorPublicKey: userPublicKey, - atAddress: data.atAddress, - }, - keyPair - ); - - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - } catch (error) { - throw new Error( - error?.message || - i18n.t('question:message.error.submit_sell_order', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const openNewTab = async (data, isFromExtension) => { - const requiredFields = ['qortalLink']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const res = extractComponents(data.qortalLink); - if (res) { - const { service, name, identifier, path } = res; - if (!service && !name) - throw new Error( - i18n.t('auth:message.error.invalid_qortal_link', { - postProcess: 'capitalizeFirstChar', - }) - ); - executeEvent('addTab', { data: { service, name, identifier, path } }); - executeEvent('open-apps-mode', {}); - return true; - } else { - throw new Error( - i18n.t('auth:message.error.invalid_qortal_link', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const adminAction = async (data, isFromExtension) => { - const requiredFields = ['type']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - // For actions that require a value, check for 'value' field - const actionsRequiringValue = [ - 'addpeer', - 'removepeer', - 'forcesync', - 'addmintingaccount', - 'removemintingaccount', - ]; - if (actionsRequiringValue.includes(data.type.toLowerCase()) && !data.value) { - missingFields.push('value'); - } - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const isGateway = await isRunningGateway(); - if (isGateway) { - throw new Error( - i18n.t('question:message.generic.no_action_public_node', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - let apiEndpoint = ''; - let method = 'GET'; // Default method - let includeValueInBody = false; - switch (data.type.toLowerCase()) { - case 'stop': - apiEndpoint = await createEndpoint('/admin/stop'); - break; - case 'restart': - apiEndpoint = await createEndpoint('/admin/restart'); - break; - case 'bootstrap': - apiEndpoint = await createEndpoint('/admin/bootstrap'); - break; - case 'addmintingaccount': - apiEndpoint = await createEndpoint('/admin/mintingaccounts'); - method = 'POST'; - includeValueInBody = true; - break; - case 'removemintingaccount': - apiEndpoint = await createEndpoint('/admin/mintingaccounts'); - method = 'DELETE'; - includeValueInBody = true; - break; - case 'forcesync': - apiEndpoint = await createEndpoint('/admin/forcesync'); - method = 'POST'; - includeValueInBody = true; - break; - case 'addpeer': - apiEndpoint = await createEndpoint('/peers'); - method = 'POST'; - includeValueInBody = true; - break; - case 'removepeer': - apiEndpoint = await createEndpoint('/peers'); - method = 'DELETE'; - includeValueInBody = true; - break; - default: - throw new Error( - i18n.t('question:message.error.unknown_admin_action_type', { - type: data.type, - postProcess: 'capitalizeFirstChar', - }) - ); - } - // Prepare the permission prompt text - let permissionText = i18n.t('question:permission.perform_admin_action', { - type: data.type, - postProcess: 'capitalizeFirstChar', - }); - - if (data.value) { - permissionText += - ' ' + - i18n.t('question:permission.perform_admin_action_with_value', { - value: data.value, - postProcess: 'capitalizeFirstChar', - }); - } - - const resPermission = await getUserPermission( - { - text1: permissionText, - }, - isFromExtension - ); - - const { accepted } = resPermission; - - if (accepted) { - // Set up options for the API call - const options: RequestInit = { - method: method, - headers: {}, - }; - if (includeValueInBody) { - options.headers['Content-Type'] = 'text/plain'; - options.body = data.value; - } - const response = await fetch(apiEndpoint, options); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.perform_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - - let res; - try { - res = await response.clone().json(); - } catch (e) { - res = await response.text(); - } - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const signTransaction = async (data, isFromExtension) => { - const requiredFields = ['unsignedBytes']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const shouldProcess = data?.process || false; - const _url = await createEndpoint( - '/transactions/decode?ignoreValidityChecks=false' - ); - - const _body = data.unsignedBytes; - const response = await fetch(_url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: _body, - }); - - if (!response.ok) - throw new Error( - i18n.t('question:message.error.decode_transaction', { - postProcess: 'capitalizeFirstChar', - }) - ); - const decodedData = await response.json(); - const resPermission = await getUserPermission( - { - text1: shouldProcess - ? i18n.t('question:permission.sign_process_transaction', { - postProcess: 'capitalizeFirstChar', - }) - : i18n.t('question:permission.sign_transaction', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t( - 'question:message.generic.read_transaction_carefully', - { postProcess: 'capitalizeFirstChar' } - ), - text2: `Tx type: ${decodedData.type}`, - json: decodedData, - }, - isFromExtension - ); - - const { accepted } = resPermission; - if (accepted) { - let urlConverted = await createEndpoint('/transactions/convert'); - - const responseConverted = await fetch(urlConverted, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: data.unsignedBytes, - }); - 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, - }; - const convertedBytes = await responseConverted.text(); - const txBytes = Base58.decode(data.unsignedBytes); - const _arbitraryBytesBuffer = Object.keys(txBytes).map(function (key) { - return txBytes[key]; - }); - const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer); - const txByteSigned = Base58.decode(convertedBytes); - const _bytesForSigningBuffer = Object.keys(txByteSigned).map( - function (key) { - return txByteSigned[key]; - } - ); - const bytesForSigningBuffer = new Uint8Array(_bytesForSigningBuffer); - const signature = nacl.sign.detached( - bytesForSigningBuffer, - keyPair.privateKey - ); - const signedBytes = utils.appendBuffer(arbitraryBytesBuffer, signature); - const signedBytesToBase58 = Base58.encode(signedBytes); - if (!shouldProcess) { - return signedBytesToBase58; - } - const res = await processTransactionVersion2(signedBytesToBase58); - if (!res?.signature) - throw new Error( - res?.message || - i18n.t('question:message.error.process_transaction', { - postProcess: 'capitalizeFirstChar', - }) - ); - return res; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -const missingFieldsFunc = (data, requiredFields) => { - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } -}; - -const encode = (value) => encodeURIComponent(value.trim()); // Helper to encode values -const buildQueryParams = (data) => { - const allowedParams = [ - 'name', - 'service', - 'identifier', - 'mimeType', - 'fileName', - 'encryptionType', - 'key', - ]; - return Object.entries(data) - .map(([key, value]) => { - if ( - value === undefined || - value === null || - value === false || - !allowedParams.includes(key) - ) - return null; // Skip null, undefined, or false - if (typeof value === 'boolean') return `${key}=${value}`; // Handle boolean values - return `${key}=${encode(value)}`; // Encode other values - }) - .filter(Boolean) // Remove null values - .join('&'); // Join with `&` -}; -export const createAndCopyEmbedLink = async (data, isFromExtension) => { - const requiredFields = ['type']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - switch (data.type) { - case 'POLL': { - missingFieldsFunc(data, ['type', 'name']); - - const queryParams = [ - `name=${encode(data.name)}`, - data.ref ? `ref=${encode(data.ref)}` : null, // Add only if ref exists - ] - .filter(Boolean) // Remove null values - .join('&'); // Join with `&` - const link = `qortal://use-embed/POLL?${queryParams}`; - try { - await navigator.clipboard.writeText(link); - } catch (error) { - throw new Error( - i18n.t('question:message.error.copy_clipboard', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - return link; - } - case 'IMAGE': - case 'ATTACHMENT': { - missingFieldsFunc(data, ['type', 'name', 'service', 'identifier']); - if (data?.encryptionType === 'private' && !data?.key) { - throw new Error( - i18n.t('question:message.generic.provide_key_shared_link', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const queryParams = buildQueryParams(data); - - const link = `qortal://use-embed/${data.type}?${queryParams}`; - - try { - await navigator.clipboard.writeText(link); - } catch (error) { - throw new Error( - i18n.t('question:message.error.copy_clipboard', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - return link; - } - - default: - throw new Error( - i18n.t('question:message.error.invalid_type', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const registerNameRequest = async (data, isFromExtension) => { - const requiredFields = ['name']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const fee = await getFee('REGISTER_NAME'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.register_name', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: data.name, - text2: data?.description, - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const name = data.name; - const description = data?.description || ''; - const response = await registerName({ name, description }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const updateNameRequest = async (data, isFromExtension) => { - const requiredFields = ['newName', 'oldName']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const oldName = data.oldName; - const newName = data.newName; - const description = data?.description || ''; - const fee = await getFee('UPDATE_NAME'); - const resPermission = await getUserPermission( - { - text1: `Do you give this application permission to update this name?`, - text2: `previous name: ${oldName}`, - text3: `new name: ${newName}`, - text4: data?.description, - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await updateName({ oldName, newName, description }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const leaveGroupRequest = async (data, isFromExtension) => { - const requiredFields = ['groupId']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupId = data.groupId; - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const fee = await getFee('LEAVE_GROUP'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.leave_group', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: `${groupInfo.groupName}`, - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await leaveGroup({ groupId }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const inviteToGroupRequest = async (data, isFromExtension) => { - const requiredFields = ['groupId', 'inviteTime', 'inviteeAddress']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupId = data.groupId; - const qortalAddress = data?.inviteeAddress; - const inviteTime = data?.inviteTime; - - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const displayInvitee = await getNameInfoForOthers(qortalAddress); - - const fee = await getFee('GROUP_INVITE'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.invite', { - invitee: displayInvitee || qortalAddress, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('group:group.group_name', { - name: groupInfo?.groupName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await inviteToGroup({ - groupId, - qortalAddress, - inviteTime, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const kickFromGroupRequest = async (data, isFromExtension) => { - const requiredFields = ['groupId', 'qortalAddress']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupId = data.groupId; - const qortalAddress = data?.qortalAddress; - const reason = data?.reason; - - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const displayInvitee = await getNameInfoForOthers(qortalAddress); - - const fee = await getFee('GROUP_KICK'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.kick', { - partecipant: displayInvitee || qortalAddress, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('group:group.group_name', { - name: groupInfo?.groupName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await kickFromGroup({ - groupId, - qortalAddress, - rBanReason: reason, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const banFromGroupRequest = async (data, isFromExtension) => { - const requiredFields = ['groupId', 'qortalAddress']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupId = data.groupId; - const qortalAddress = data?.qortalAddress; - const rBanTime = data?.banTime; - const reason = data?.reason; - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const displayInvitee = await getNameInfoForOthers(qortalAddress); - - const fee = await getFee('GROUP_BAN'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.ban', { - partecipant: displayInvitee || qortalAddress, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('group:group.group_name', { - name: groupInfo?.groupName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await banFromGroup({ - groupId, - qortalAddress, - rBanTime, - rBanReason: reason, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const cancelGroupBanRequest = async (data, isFromExtension) => { - const requiredFields = ['groupId', 'qortalAddress']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupId = data.groupId; - const qortalAddress = data?.qortalAddress; - - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const displayInvitee = await getNameInfoForOthers(qortalAddress); - - const fee = await getFee('CANCEL_GROUP_BAN'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.cancel_ban', { - partecipant: displayInvitee || qortalAddress, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('group:group.group_name', { - name: groupInfo?.groupName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await cancelBan({ - groupId, - qortalAddress, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const addGroupAdminRequest = async (data, isFromExtension) => { - const requiredFields = ['groupId', 'qortalAddress']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupId = data.groupId; - const qortalAddress = data?.qortalAddress; - - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const displayInvitee = await getNameInfoForOthers(qortalAddress); - - const fee = await getFee('ADD_GROUP_ADMIN'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.add_admin', { - invitee: displayInvitee || qortalAddress, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('group:group.group_name', { - name: groupInfo?.groupName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await makeAdmin({ - groupId, - qortalAddress, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const removeGroupAdminRequest = async (data, isFromExtension) => { - const requiredFields = ['groupId', 'qortalAddress']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupId = data.groupId; - const qortalAddress = data?.qortalAddress; - - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const displayInvitee = await getNameInfoForOthers(qortalAddress); - - const fee = await getFee('REMOVE_GROUP_ADMIN'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.remove_admin', { - partecipant: displayInvitee || qortalAddress, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('group:group.group_name', { - name: groupInfo?.groupName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await removeAdmin({ - groupId, - qortalAddress, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const cancelGroupInviteRequest = async (data, isFromExtension) => { - const requiredFields = ['groupId', 'qortalAddress']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (!data[field]) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupId = data.groupId; - const qortalAddress = data?.qortalAddress; - - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const displayInvitee = await getNameInfoForOthers(qortalAddress); - - const fee = await getFee('CANCEL_GROUP_INVITE'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.cancel_group_invite', { - invitee: displayInvitee || qortalAddress, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('group:group.group_name', { - name: groupInfo?.groupName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - - const { accepted } = resPermission; - - if (accepted) { - const response = await cancelInvitationToGroup({ - groupId, - qortalAddress, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const createGroupRequest = async (data, isFromExtension) => { - const requiredFields = [ - 'approvalThreshold', - 'groupId', - 'groupName', - 'maxBlock', - 'minBlock', - 'qortalAddress', - 'type', - ]; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (data[field] === undefined || data[field] === null) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupName = data.groupName; - const description = data?.description || ''; - const type = +data.type; - const approvalThreshold = +data?.approvalThreshold; - const minBlock = +data?.minBlock; - const maxBlock = +data.maxBlock; - - const fee = await getFee('CREATE_GROUP'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.create_group', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('group:group.group_name', { - name: groupName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await createGroup({ - groupName, - groupDescription: description, - groupType: type, - groupApprovalThreshold: approvalThreshold, - minBlock, - maxBlock, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const updateGroupRequest = async (data, isFromExtension) => { - const requiredFields = [ - 'groupId', - 'newOwner', - 'type', - 'approvalThreshold', - 'minBlock', - 'maxBlock', - ]; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (data[field] === undefined || data[field] === null) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const groupId = +data.groupId; - const newOwner = data.newOwner; - const description = data?.description || ''; - const type = +data.type; - const approvalThreshold = +data?.approvalThreshold; - const minBlock = +data?.minBlock; - const maxBlock = +data.maxBlock; - - let groupInfo = null; - try { - const url = await createEndpoint(`/groups/${groupId}`); - const response = await fetch(url); - if (!response.ok) - throw new Error( - i18n.t('question:message.error.fetch_group', { - postProcess: 'capitalizeFirstChar', - }) - ); - - groupInfo = await response.json(); - } catch (error) { - const errorMsg = - (error && error.message) || - i18n.t('question:message.error.no_group_found', { - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const displayInvitee = await getNameInfoForOthers(newOwner); - - const fee = await getFee('CREATE_GROUP'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.update_group', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:permission.update_group_detail', { - owner: displayInvitee || newOwner, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('group:group.group_name', { - name: groupInfo?.groupName, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await updateGroup({ - groupId, - newOwner, - newIsOpen: type, - newDescription: description, - newApprovalThreshold: approvalThreshold, - newMinimumBlockDelay: minBlock, - newMaximumBlockDelay: maxBlock, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const decryptAESGCMRequest = async (data, isFromExtension) => { - const requiredFields = ['encryptedData', 'iv', 'senderPublicKey']; - requiredFields.forEach((field) => { - if (!data[field]) { - throw new Error( - i18n.t('question:message.error.missing_fields', { - fields: field, - postProcess: 'capitalizeFirstChar', - }) - ); - } - }); - - const encryptedData = data.encryptedData; - const iv = data.iv; - const senderPublicKeyBase58 = data.senderPublicKey; - - // Decode keys and IV - const senderPublicKey = Base58.decode(senderPublicKeyBase58); - const resKeyPair = await getKeyPair(); // Assume this retrieves the current user's keypair - const uint8PrivateKey = Base58.decode(resKeyPair.privateKey); - - // Convert ed25519 keys to Curve25519 - const convertedPrivateKey = ed2curve.convertSecretKey(uint8PrivateKey); - const convertedPublicKey = ed2curve.convertPublicKey(senderPublicKey); - - // Generate shared secret - const sharedSecret = new Uint8Array(32); - nacl.lowlevel.crypto_scalarmult( - sharedSecret, - convertedPrivateKey, - convertedPublicKey - ); - - // Derive encryption key - const encryptionKey: Uint8Array = new Sha256() - .process(sharedSecret) - .finish().result; - - // Convert IV and ciphertext from Base64 - const base64ToUint8Array = (base64) => - Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); - const ivUint8Array = base64ToUint8Array(iv); - const ciphertext = base64ToUint8Array(encryptedData); - // Validate IV and key lengths - if (ivUint8Array.length !== 12) { - throw new Error( - i18n.t('question:message.error.invalid_encryption_iv', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - if (encryptionKey.length !== 32) { - throw new Error( - i18n.t('question:message.error.invalid_encryption_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - try { - // Decrypt data - const algorithm = { name: 'AES-GCM', iv: ivUint8Array }; - const cryptoKey = await crypto.subtle.importKey( - 'raw', - encryptionKey, - algorithm, - false, - ['decrypt'] - ); - const decryptedArrayBuffer = await crypto.subtle.decrypt( - algorithm, - cryptoKey, - ciphertext - ); - - // Return decrypted data as Base64 - return uint8ArrayToBase64(new Uint8Array(decryptedArrayBuffer)); - } catch (error) { - console.error('Decryption failed:', error); - throw new Error( - i18n.t('question:message.error.decrypt_message', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const sellNameRequest = async (data, isFromExtension) => { - const requiredFields = ['salePrice', 'nameForSale']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (data[field] === undefined || data[field] === null) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const name = data.nameForSale; - const sellPrice = +data.salePrice; - - const validApi = await getBaseApi(); - - const response = await fetch(validApi + '/names/' + name); - const nameData = await response.json(); - if (!nameData) - throw new Error( - i18n.t('auth:message.error.name_not_existing', { - postProcess: 'capitalizeFirstChar', - }) - ); - - if (nameData?.isForSale) - throw new Error( - i18n.t('question:message.error.name_already_for_sale', { - postProcess: 'capitalizeFirstChar', - }) - ); - const fee = await getFee('SELL_NAME'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.sell_name_transaction', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t( - 'question:permission.sell_name_transaction_detail', - { - name: name, - price: sellPrice, - postProcess: 'capitalizeFirstChar', - } - ), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await sellName({ - name, - sellPrice, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const cancelSellNameRequest = async (data, isFromExtension) => { - const requiredFields = ['nameForSale']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (data[field] === undefined || data[field] === null) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - const name = data.nameForSale; - const validApi = await getBaseApi(); - - const response = await fetch(validApi + '/names/' + name); - const nameData = await response.json(); - if (!nameData?.isForSale) - throw new Error( - i18n.t('question:message.error.name_not_for_sale', { - postProcess: 'capitalizeFirstChar', - }) - ); - - const fee = await getFee('CANCEL_SELL_NAME'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.sell_name_cancel', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('question:name', { - name: name, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await cancelSellName({ - name, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const buyNameRequest = async (data, isFromExtension) => { - const requiredFields = ['nameForSale']; - const missingFields: string[] = []; - requiredFields.forEach((field) => { - if (data[field] === undefined || data[field] === null) { - missingFields.push(field); - } - }); - if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', '); - const errorMsg = i18n.t('question:message.error.missing_fields', { - fields: missingFieldsString, - postProcess: 'capitalizeFirstChar', - }); - throw new Error(errorMsg); - } - - const name = data.nameForSale; - const validApi = await getBaseApi(); - const response = await fetch(validApi + '/names/' + name); - const nameData = await response.json(); - - if (!nameData?.isForSale) - throw new Error( - i18n.t('question:message.error.name_not_for_sale', { - postProcess: 'capitalizeFirstChar', - }) - ); - - const sellerAddress = nameData.owner; - const sellPrice = +nameData.salePrice; - - const fee = await getFee('BUY_NAME'); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.buy_name', { - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('question:permission.buy_name_detail', { - name: name, - price: sellPrice, - postProcess: 'capitalizeFirstChar', - }), - fee: fee.fee, - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const response = await buyName({ - name, - sellerAddress, - sellPrice, - }); - return response; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; - -export const signForeignFees = async (data, isFromExtension) => { - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.sign_fee', { - postProcess: 'capitalizeFirstChar', - }), - }, - isFromExtension - ); - const { accepted } = resPermission; - if (accepted) { - const wallet = await getSaveWallet(); - const address = wallet.address0; - 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, - }; - - const unsignedFeesUrl = await createEndpoint( - `/crosschain/unsignedfees/${address}` - ); - - const unsignedFeesResponse = await fetch(unsignedFeesUrl); - - const unsignedFees = await unsignedFeesResponse.json(); - - const signedFees = []; - - unsignedFees.forEach((unsignedFee) => { - const unsignedDataDecoded = Base58.decode(unsignedFee.data); - - const signature = nacl.sign.detached( - unsignedDataDecoded, - keyPair.privateKey - ); - - const signedFee = { - timestamp: unsignedFee.timestamp, - data: `${Base58.encode(signature)}`, - atAddress: unsignedFee.atAddress, - fee: unsignedFee.fee, - }; - - signedFees.push(signedFee); - }); - - const signedFeesUrl = await createEndpoint(`/crosschain/signedfees`); - - await fetch(signedFeesUrl, { - method: 'POST', - headers: { - Accept: '*/*', - 'Content-Type': 'application/json', - }, - body: `${JSON.stringify(signedFees)}`, - }); - - return true; - } else { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } -}; -export const multiPaymentWithPrivateData = async (data, isFromExtension) => { - const requiredFields = ['payments', 'assetId']; - requiredFields.forEach((field) => { - if (data[field] === undefined || data[field] === null) { - throw new Error( - i18n.t('question:message.error.missing_fields', { - fields: field, - postProcess: 'capitalizeFirstChar', - }) - ); - } - }); - const resKeyPair = await getKeyPair(); - const parsedData = resKeyPair; - const privateKey = parsedData.privateKey; - const userPublicKey = parsedData.publicKey; - const { fee: paymentFee } = await getFee('TRANSFER_ASSET'); - const { fee: arbitraryFee } = await getFee('ARBITRARY'); - - let name = null; - const payments = data.payments; - const assetId = data.assetId; - const pendingTransactions = []; - const pendingAdditionalArbitraryTxs = []; - const additionalArbitraryTxsWithoutPayment = - data?.additionalArbitraryTxsWithoutPayment || []; - let totalAmount = 0; - let fee = 0; - for (const payment of payments) { - const paymentRefId = uid.rnd(); - const requiredFieldsPayment = ['recipient', 'amount']; - - for (const field of requiredFieldsPayment) { - if (!payment[field]) { - throw new Error( - i18n.t('question:message.error.missing_fields', { - fields: field, - postProcess: 'capitalizeFirstChar', - }) - ); - } - } - - const confirmReceiver = await getNameOrAddress(payment.recipient); - if (confirmReceiver.error) { - throw new Error( - i18n.t('question:message.error.invalid_receiver', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const receiverPublicKey = await getPublicKey(confirmReceiver); - - const amount = +payment.amount.toFixed(8); - - pendingTransactions.push({ - type: 'PAYMENT', - recipientAddress: confirmReceiver, - amount: amount, - paymentRefId, - }); - - fee = fee + +paymentFee; - totalAmount = totalAmount + amount; - - if (payment.arbitraryTxs && payment.arbitraryTxs.length > 0) { - for (const arbitraryTx of payment.arbitraryTxs) { - const requiredFieldsArbitraryTx = ['service', 'identifier', 'base64']; - - for (const field of requiredFieldsArbitraryTx) { - if (!arbitraryTx[field]) { - throw new Error( - i18n.t('question:message.error.missing_fields', { - fields: field, - postProcess: 'capitalizeFirstChar', - }) - ); - } - } - - if (!name) { - const getName = await getNameInfo(); - if (!getName) - throw new Error( - i18n.t('question:message.error.registered_name', { - postProcess: 'capitalizeFirstChar', - }) - ); - name = getName; - } - - const isValid = isValidBase64WithDecode(arbitraryTx.base64); - if (!isValid) - throw new Error( - i18n.t('core:message.error.invalid_base64', { - postProcess: 'capitalizeFirstChar', - }) - ); - if (!arbitraryTx?.service?.includes('_PRIVATE')) - throw new Error( - i18n.t('question:message.generic.private_service', { - postProcess: 'capitalizeFirstChar', - }) - ); - const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || []; - pendingTransactions.push({ - type: 'ARBITRARY', - identifier: arbitraryTx.identifier, - service: arbitraryTx.service, - base64: arbitraryTx.base64, - description: arbitraryTx?.description || '', - paymentRefId, - publicKeys: [receiverPublicKey, ...additionalPublicKeys], - }); - - fee = fee + +arbitraryFee; - } - } - } - - if ( - additionalArbitraryTxsWithoutPayment && - additionalArbitraryTxsWithoutPayment.length > 0 - ) { - for (const arbitraryTx of additionalArbitraryTxsWithoutPayment) { - const requiredFieldsArbitraryTx = ['service', 'identifier', 'base64']; - - for (const field of requiredFieldsArbitraryTx) { - if (!arbitraryTx[field]) { - throw new Error( - i18n.t('question:message.error.missing_fields', { - fields: field, - postProcess: 'capitalizeFirstChar', - }) - ); - } - } - - if (!name) { - const getName = await getNameInfo(); - if (!getName) - throw new Error( - i18n.t('question:message.error.registered_name', { - postProcess: 'capitalizeFirstChar', - }) - ); - name = getName; - } - - const isValid = isValidBase64WithDecode(arbitraryTx.base64); - if (!isValid) - throw new Error( - i18n.t('core:message.error.invalid_base64', { - postProcess: 'capitalizeFirstChar', - }) - ); - if (!arbitraryTx?.service?.includes('_PRIVATE')) - throw new Error( - i18n.t('question:message.generic.private_service', { - postProcess: 'capitalizeFirstChar', - }) - ); - const additionalPublicKeys = arbitraryTx?.additionalPublicKeys || []; - pendingAdditionalArbitraryTxs.push({ - type: 'ARBITRARY', - identifier: arbitraryTx.identifier, - service: arbitraryTx.service, - base64: arbitraryTx.base64, - description: arbitraryTx?.description || '', - publicKeys: additionalPublicKeys, - }); - - fee = fee + +arbitraryFee; - } - } - - if (!name) - throw new Error( - i18n.t('question:message.error.registered_name', { - postProcess: 'capitalizeFirstChar', - }) - ); - const balance = await getBalanceInfo(); - - if (+balance < fee) - throw new Error( - i18n.t('question:message.error.insufficient_balance_qort', { - postProcess: 'capitalizeFirstChar', - }) - ); - const assetBalance = await getAssetBalanceInfo(assetId); - const assetInfo = await getAssetInfo(assetId); - if (assetBalance < totalAmount) - throw new Error( - i18n.t('question:message.error.insufficient_balance', { - postProcess: 'capitalizeFirstChar', - }) - ); - - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.pay_publish', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:assets_used_pay', { - asset: assetInfo.name, - postProcess: 'capitalizeFirstChar', - }), - html: ` -
- - - ${pendingTransactions - .filter((item) => item.type === 'PAYMENT') - .map( - (payment) => ` -
-
Recipient: ${ - payment.recipientAddress - }
-
Amount: ${payment.amount}
-
` - ) - .join('')} - ${[...pendingTransactions, ...pendingAdditionalArbitraryTxs] - .filter((item) => item.type === 'ARBITRARY') - .map( - (arbitraryTx) => ` -
-
Service: ${ - arbitraryTx.service - }
-
Name: ${name}
-
Identifier: ${ - arbitraryTx.identifier - }
-
` - ) - .join('')} -
- - `, - highlightedText: `Total Amount: ${totalAmount}`, - fee: fee, - }, - isFromExtension - ); - - const { accepted, checkbox1 = false } = resPermission; - if (!accepted) { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - // const failedTxs = [] - const paymentsDone = {}; - - const transactionsDone = []; - - for (const transaction of pendingTransactions) { - const type = transaction.type; - - if (type === 'PAYMENT') { - const makePayment = await retryTransaction( - transferAsset, - [ - { - amount: transaction.amount, - assetId, - recipient: transaction.recipientAddress, - }, - ], - true - ); - if (makePayment) { - transactionsDone.push(makePayment?.signature); - if (transaction.paymentRefId) { - paymentsDone[transaction.paymentRefId] = makePayment; - } - } - } else if (type === 'ARBITRARY' && paymentsDone[transaction.paymentRefId]) { - const objectToEncrypt = { - data: transaction.base64, - payment: paymentsDone[transaction.paymentRefId], - }; - - const toBase64 = await retryTransaction( - objectToBase64, - [objectToEncrypt], - true - ); - - if (!toBase64) continue; // Skip if encryption fails - - const encryptDataResponse = await retryTransaction( - encryptDataGroup, - [ - { - data64: toBase64, - publicKeys: transaction.publicKeys, - privateKey, - userPublicKey, - }, - ], - true - ); - - if (!encryptDataResponse) continue; // Skip if encryption fails - - const resPublish = await retryTransaction( - publishData, - [ - { - registeredName: encodeURIComponent(name), - data: encryptDataResponse, - service: transaction.service, - identifier: encodeURIComponent(transaction.identifier), - uploadType: 'base64', - description: transaction?.description, - apiVersion: 2, - withFee: true, - }, - ], - true - ); - - if (resPublish?.signature) { - transactionsDone.push(resPublish?.signature); - } - } - } - - for (const transaction of pendingAdditionalArbitraryTxs) { - const objectToEncrypt = { - data: transaction.base64, - }; - - const toBase64 = await retryTransaction( - objectToBase64, - [objectToEncrypt], - true - ); - - if (!toBase64) continue; // Skip if encryption fails - - const encryptDataResponse = await retryTransaction( - encryptDataGroup, - [ - { - data64: toBase64, - publicKeys: transaction.publicKeys, - privateKey, - userPublicKey, - }, - ], - true - ); - - if (!encryptDataResponse) continue; // Skip if encryption fails - - const resPublish = await retryTransaction( - publishData, - [ - { - registeredName: encodeURIComponent(name), - data: encryptDataResponse, - service: transaction.service, - identifier: encodeURIComponent(transaction.identifier), - uploadType: 'base64', - description: transaction?.description, - apiVersion: 2, - withFee: true, - }, - ], - true - ); - - if (resPublish?.signature) { - transactionsDone.push(resPublish?.signature); - } - } - - return transactionsDone; -}; - -export const transferAssetRequest = async (data, isFromExtension) => { - const requiredFields = ['amount', 'assetId', 'recipient']; - requiredFields.forEach((field) => { - if (data[field] === undefined || data[field] === null) { - throw new Error( - i18n.t('question:message.error.missing_fields', { - fields: field, - postProcess: 'capitalizeFirstChar', - }) - ); - } - }); - const amount = data.amount; - const assetId = data.assetId; - const recipient = data.recipient; - - const { fee } = await getFee('TRANSFER_ASSET'); - const balance = await getBalanceInfo(); - - if (+balance < +fee) - throw new Error( - i18n.t('question:message.error.insufficient_balance_qort', { - postProcess: 'capitalizeFirstChar', - }) - ); - const assetBalance = await getAssetBalanceInfo(assetId); - if (assetBalance < amount) - throw new Error( - i18n.t('question:message.error.insufficient_balance', { - postProcess: 'capitalizeFirstChar', - }) - ); - const confirmReceiver = await getNameOrAddress(recipient); - if (confirmReceiver.error) { - throw new Error( - i18n.t('question:message.error.invalid_receiver', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const assetInfo = await getAssetInfo(assetId); - const resPermission = await getUserPermission( - { - text1: i18n.t('question:permission.transfer_asset', { - postProcess: 'capitalizeFirstChar', - }), - text2: i18n.t('question:asset_name', { - asset: assetInfo?.name, - postProcess: 'capitalizeFirstChar', - }), - highlightedText: i18n.t('question:amount_qty', { - quantity: amount, - postProcess: 'capitalizeFirstChar', - }), - fee: fee, - }, - isFromExtension - ); - - const { accepted } = resPermission; - if (!accepted) { - throw new Error( - i18n.t('question:message.generic.user_declined_request', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - const res = await transferAsset({ - amount, - recipient: confirmReceiver, - assetId, - }); - return res; -}; From d06a66c59147420c90749999c5ddd09856d489d3 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 24 May 2025 16:01:44 +0300 Subject: [PATCH 24/31] fix missing imports --- src/components/Apps/AppsDevModeNavBar.tsx | 3 ++- src/components/Apps/AppsPrivate.tsx | 2 +- src/qdn/publish/publish.ts | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Apps/AppsDevModeNavBar.tsx b/src/components/Apps/AppsDevModeNavBar.tsx index 798929b..edce516 100644 --- a/src/components/Apps/AppsDevModeNavBar.tsx +++ b/src/components/Apps/AppsDevModeNavBar.tsx @@ -16,6 +16,7 @@ import RefreshIcon from '@mui/icons-material/Refresh'; import { navigationControllerAtom } from '../../atoms/global'; import { AppsDevModeTabComponent } from './AppsDevModeTabComponent'; import { useAtom } from 'jotai'; +import { useTranslation } from 'react-i18next'; export const AppsDevModeNavBar = () => { const [tabs, setTabs] = useState([]); @@ -28,7 +29,7 @@ export const AppsDevModeNavBar = () => { const tabsRef = useRef(null); const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); - + const { t } = useTranslation(); const handleClick = (event) => { setAnchorEl(event.currentTarget); }; diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index 56b33d9..143ad0e 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -37,7 +37,7 @@ import { } from './Apps-styles'; import AddIcon from '@mui/icons-material/Add'; import ImageUploader from '../../common/ImageUploader'; -import { QORTAL_APP_CONTEXT } from '../../App'; +import { getBaseApiReact, QORTAL_APP_CONTEXT } from '../../App'; import { fileToBase64 } from '../../utils/fileReading'; import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import { getFee } from '../../background/background.ts'; diff --git a/src/qdn/publish/publish.ts b/src/qdn/publish/publish.ts index dfe682f..848ddc9 100644 --- a/src/qdn/publish/publish.ts +++ b/src/qdn/publish/publish.ts @@ -88,7 +88,6 @@ export const publishData = async ({ feeAmount, filename, identifier, - isBase64, registeredName, service, tag1, From 388e44e4e773ad6769727a4c107eb62172c374dc Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 24 May 2025 16:03:42 +0300 Subject: [PATCH 25/31] remove logs --- src/qdn/publish/publish.ts | 3 --- src/qortal/get.ts | 1 - 2 files changed, 4 deletions(-) diff --git a/src/qdn/publish/publish.ts b/src/qdn/publish/publish.ts index 848ddc9..3bb3307 100644 --- a/src/qdn/publish/publish.ts +++ b/src/qdn/publish/publish.ts @@ -99,7 +99,6 @@ export const publishData = async ({ uploadType, withFee, }: any) => { - console.log('data', data); const validateName = async (receiverName: string) => { return await reusableGet(`/names/${receiverName}`); }; @@ -225,7 +224,6 @@ export const publishData = async ({ } 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'); @@ -247,7 +245,6 @@ export const publishData = async ({ }; const uploadData = async (registeredName: string, data: any, fee: number) => { - console.log('data', uploadType, data); let postBody = ''; let urlSuffix = ''; diff --git a/src/qortal/get.ts b/src/qortal/get.ts index dad01f5..7bd9bee 100644 --- a/src/qortal/get.ts +++ b/src/qortal/get.ts @@ -1804,7 +1804,6 @@ export const publishMultipleQDNResources = async ( resource?.base64 || resource?.data64 || resourceEncrypt ? 'base64' : 'file'; - console.log('dataType', dataType); await retryTransaction( publishData, [ From 1a549be89b8d16f225e5ceb90eaecd448d46f8fa Mon Sep 17 00:00:00 2001 From: "nico.benaz" <52411515+nbenaglia@users.noreply.github.com> Date: Sat, 24 May 2025 15:05:43 +0200 Subject: [PATCH 26/31] Set namespaces --- src/components/Apps/AppsDevModeNavBar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Apps/AppsDevModeNavBar.tsx b/src/components/Apps/AppsDevModeNavBar.tsx index edce516..b5f0593 100644 --- a/src/components/Apps/AppsDevModeNavBar.tsx +++ b/src/components/Apps/AppsDevModeNavBar.tsx @@ -25,11 +25,12 @@ export const AppsDevModeNavBar = () => { navigationControllerAtom ); const theme = useTheme(); + const { t } = useTranslation(['auth', 'core', 'group', 'question', 'tutorial']); const [isNewTabWindow, setIsNewTabWindow] = useState(false); const tabsRef = useRef(null); const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); - const { t } = useTranslation(); + const handleClick = (event) => { setAnchorEl(event.currentTarget); }; From 88d624c003d2e14a3e665e820db0abb76d5ed754 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 24 May 2025 22:24:14 +0300 Subject: [PATCH 27/31] fetch primary name --- src/background/background.ts | 11 +++++---- src/components/Group/Group.tsx | 6 ++--- src/components/Minting/Minting.tsx | 6 ++--- src/encryption/encryption.ts | 6 ++--- src/hooks/useQortalMessageListener.tsx | 2 ++ src/qdn/publish/publish.ts | 2 +- src/qortal/qortal-requests.ts | 33 +++++++++++++++++++++++++- 7 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/background/background.ts b/src/background/background.ts index 2a7b158..c95c7c7 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -805,9 +805,9 @@ export async function getNameInfo() { const wallet = await getSaveWallet(); const address = wallet.address0; const validApi = await getBaseApi(); - const response = await fetch(validApi + '/names/address/' + address); + const response = await fetch(validApi + '/names/primary/' + address); const nameData = await response.json(); - if (nameData?.length > 0) { + if (nameData?.name) { return nameData[0].name; } else { return ''; @@ -815,11 +815,12 @@ export async function getNameInfo() { } export async function getNameInfoForOthers(address) { + if (!address) return ''; const validApi = await getBaseApi(); - const response = await fetch(validApi + '/names/address/' + address); + const response = await fetch(validApi + '/names/primary/' + address); const nameData = await response.json(); - if (nameData?.length > 0) { - return nameData[0].name; + if (nameData?.name) { + return nameData?.name; } else { return ''; } diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index fa534f9..694d35d 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -265,11 +265,11 @@ export const getDataPublishesFunc = async (groupId, type) => { }; export async function getNameInfo(address: string) { - const response = await fetch(`${getBaseApiReact()}/names/address/` + address); + const response = await fetch(`${getBaseApiReact()}/names/primary/` + address); const nameData = await response.json(); - if (nameData?.length > 0) { - return nameData[0]?.name; + if (nameData?.name) { + return nameData?.name; } else { return ''; } diff --git a/src/components/Minting/Minting.tsx b/src/components/Minting/Minting.tsx index cddd67c..b812a66 100644 --- a/src/components/Minting/Minting.tsx +++ b/src/components/Minting/Minting.tsx @@ -85,14 +85,14 @@ export const Minting = ({ setIsOpenMinting, myAddress, show }) => { const getName = async (address) => { try { const response = await fetch( - `${getBaseApiReact()}/names/address/${address}` + `${getBaseApiReact()}/names/primary/${address}` ); const nameData = await response.json(); - if (nameData?.length > 0) { + if (nameData?.name) { setNames((prev) => { return { ...prev, - [address]: nameData[0].name, + [address]: nameData?.name, }; }); } else { diff --git a/src/encryption/encryption.ts b/src/encryption/encryption.ts index ab0a36d..2bf82f6 100644 --- a/src/encryption/encryption.ts +++ b/src/encryption/encryption.ts @@ -26,10 +26,10 @@ export async function getNameInfo() { const wallet = await getSaveWallet(); const address = wallet.address0; const validApi = await getBaseApi(); - const response = await fetch(validApi + '/names/address/' + address); + const response = await fetch(validApi + '/names/primary/' + address); const nameData = await response.json(); - if (nameData?.length > 0) { - return nameData[0].name; + if (nameData?.name) { + return nameData?.name; } else { return ''; } diff --git a/src/hooks/useQortalMessageListener.tsx b/src/hooks/useQortalMessageListener.tsx index 29549dc..d2a54c4 100644 --- a/src/hooks/useQortalMessageListener.tsx +++ b/src/hooks/useQortalMessageListener.tsx @@ -256,6 +256,7 @@ export const listOfAllQortalRequests = [ 'UPDATE_GROUP', 'UPDATE_NAME', 'VOTE_ON_POLL', + 'GET_PRIMARY_NAME', ]; export const UIQortalRequests = [ @@ -319,6 +320,7 @@ export const UIQortalRequests = [ 'UPDATE_GROUP', 'UPDATE_NAME', 'VOTE_ON_POLL', + 'GET_PRIMARY_NAME', ]; async function retrieveFileFromIndexedDB(fileId) { diff --git a/src/qdn/publish/publish.ts b/src/qdn/publish/publish.ts index 3bb3307..782ecdd 100644 --- a/src/qdn/publish/publish.ts +++ b/src/qdn/publish/publish.ts @@ -317,7 +317,7 @@ export const publishData = async ({ } const file = data; - const urlCheck = `/arbitrary/check-tmp-space?totalSize=${file.size}`; + const urlCheck = `/arbitrary/check/tmp?totalSize=${file.size}`; const checkEndpoint = await createEndpoint(urlCheck); const checkRes = await fetch(checkEndpoint); diff --git a/src/qortal/qortal-requests.ts b/src/qortal/qortal-requests.ts index 9681790..32a4186 100644 --- a/src/qortal/qortal-requests.ts +++ b/src/qortal/qortal-requests.ts @@ -1,4 +1,8 @@ -import { gateways, getApiKeyFromStorage } from '../background/background.ts'; +import { + gateways, + getApiKeyFromStorage, + getNameInfoForOthers, +} from '../background/background.ts'; import { listOfAllQortalRequests } from '../hooks/useQortalMessageListener.tsx'; import { addForeignServer, @@ -1932,6 +1936,33 @@ function setupMessageListenerQortalRequest() { break; } + case 'GET_PRIMARY_NAME': { + try { + const res = await getNameInfoForOthers(request.payload?.address); + const resData = res ? res : null; + event.source.postMessage( + { + requestId: request.requestId, + action: request.action, + payload: resData, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: request.action, + error: error.message, + type: 'backgroundMessageResponse', + }, + event.origin + ); + } + break; + } + default: break; } From 6774f6233949ec18c30eca2dfa4fdd93b52ba7f3 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 25 May 2025 18:50:08 +0300 Subject: [PATCH 28/31] change endpoint --- src/qdn/publish/publish.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qdn/publish/publish.ts b/src/qdn/publish/publish.ts index 3bb3307..782ecdd 100644 --- a/src/qdn/publish/publish.ts +++ b/src/qdn/publish/publish.ts @@ -317,7 +317,7 @@ export const publishData = async ({ } const file = data; - const urlCheck = `/arbitrary/check-tmp-space?totalSize=${file.size}`; + const urlCheck = `/arbitrary/check/tmp?totalSize=${file.size}`; const checkEndpoint = await createEndpoint(urlCheck); const checkRes = await fetch(checkEndpoint); From 25695c87b01e127ee45b148cf93383259b250687 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 28 May 2025 18:54:52 +0300 Subject: [PATCH 29/31] fixes --- src/App.tsx | 20 +++++++++++++++++++- src/background/background.ts | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 5138080..fb60be6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -766,6 +766,24 @@ function App() { balanceSetInterval(); }); }; + + const refetchUserInfo = () => { + window + .sendMessage('userInfo') + .then((response) => { + if (response && !response.error) { + setUserInfo(response); + } + }) + .catch((error) => { + console.error('Failed to get user info:', error); + }); + }; + + const getBalanceAndUserInfoFunc = () => { + getBalanceFunc(); + refetchUserInfo(); + }; const getLtcBalanceFunc = () => { setLtcBalanceLoading(true); window @@ -1502,7 +1520,7 @@ function App() { Date: Thu, 29 May 2025 21:20:12 +0300 Subject: [PATCH 30/31] add ability to enter html for public group chat --- src/components/Chat/ChatOptions.tsx | 16 +++++++++------ src/components/Chat/MessageItem.tsx | 32 ++++++++++++++++++++--------- src/utils/chat.ts | 4 ++++ 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/components/Chat/ChatOptions.tsx b/src/components/Chat/ChatOptions.tsx index 59124a0..2d2c352 100644 --- a/src/components/Chat/ChatOptions.tsx +++ b/src/components/Chat/ChatOptions.tsx @@ -35,6 +35,7 @@ import { convert } from 'html-to-text'; import { generateHTML } from '@tiptap/react'; import ErrorBoundary from '../../common/ErrorBoundary'; import { useTranslation } from 'react-i18next'; +import { isHtmlString } from '../../utils/chat'; const extractTextFromHTML = (htmlString = '') => { return convert(htmlString, { @@ -76,13 +77,16 @@ export const ChatOptions = ({ return untransformedMessages?.map((item) => { if (item?.messageText) { let transformedMessage = item?.messageText; + const isHtml = isHtmlString(item?.messageText); try { - transformedMessage = generateHTML(item?.messageText, [ - StarterKit, - Underline, - Highlight, - Mention, - ]); + transformedMessage = isHtml + ? item?.messageText + : generateHTML(item?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + ]); return { ...item, messageText: transformedMessage, diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index 3151fc5..29a6a88 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -47,7 +47,11 @@ import level8Img from '../../assets/badges/level-8.png'; import level9Img from '../../assets/badges/level-9.png'; import level10Img from '../../assets/badges/level-10.png'; import { Embed } from '../Embeds/Embed'; -import { buildImageEmbedLink, messageHasImage } from '../../utils/chat'; +import { + buildImageEmbedLink, + isHtmlString, + messageHasImage, +} from '../../utils/chat'; import { useTranslation } from 'react-i18next'; const getBadgeImg = (level) => { @@ -135,6 +139,8 @@ export const MessageItem = memo( const htmlText = useMemo(() => { if (message?.messageText) { + const isHtml = isHtmlString(message?.messageText); + if (isHtml) return message?.messageText; return generateHTML(message?.messageText, [ StarterKit, Underline, @@ -147,6 +153,8 @@ export const MessageItem = memo( const htmlReply = useMemo(() => { if (reply?.messageText) { + const isHtml = isHtmlString(reply?.messageText); + if (isHtml) return reply?.messageText; return generateHTML(reply?.messageText, [ StarterKit, Underline, @@ -616,6 +624,18 @@ export const ReplyPreview = ({ message, isEdit = false }) => { 'tutorial', ]); + const replyMessageText = useMemo(() => { + const isHtml = isHtmlString(message?.messageText); + if (isHtml) return message?.messageText; + return generateHTML(message?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + TextStyle, + ]); + }, [message?.messageText]); + return ( { )} {message?.messageText && ( - + )} {message?.decryptedData?.type === 'notification' ? ( diff --git a/src/utils/chat.ts b/src/utils/chat.ts index c7099c0..fed287d 100644 --- a/src/utils/chat.ts +++ b/src/utils/chat.ts @@ -20,3 +20,7 @@ export const messageHasImage = (message) => { message.images[0]?.service ); }; + +export function isHtmlString(value) { + return typeof value === 'string' && /<[^>]+>/.test(value.trim()); +} From 0d6b0f8f2d9ca4d8ea0af9181b7f32a5a4811064 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 30 May 2025 14:03:19 +0300 Subject: [PATCH 31/31] fixes --- src/components/Apps/AppViewer.tsx | 4 ++-- src/components/Apps/AppsDevModeNavBar.tsx | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/components/Apps/AppViewer.tsx b/src/components/Apps/AppViewer.tsx index 38cfe71..33ade79 100644 --- a/src/components/Apps/AppViewer.tsx +++ b/src/components/Apps/AppViewer.tsx @@ -86,7 +86,7 @@ export const AppViewer = forwardRef( useEffect(() => { const iframe = iframeRef?.current; - if (!iframe) return; + if (!iframe || !iframe?.src) return; try { const targetOrigin = new URL(iframe.src).origin; @@ -101,7 +101,7 @@ export const AppViewer = forwardRef( useEffect(() => { const iframe = iframeRef?.current; - if (!iframe) return; + if (!iframe || !iframe?.src) return; try { const targetOrigin = new URL(iframe.src).origin; diff --git a/src/components/Apps/AppsDevModeNavBar.tsx b/src/components/Apps/AppsDevModeNavBar.tsx index 1ce7d76..548f1d2 100644 --- a/src/components/Apps/AppsDevModeNavBar.tsx +++ b/src/components/Apps/AppsDevModeNavBar.tsx @@ -32,13 +32,7 @@ export const AppsDevModeNavBar = () => { 'tutorial', ]); const theme = useTheme(); - const { t } = useTranslation([ - 'auth', - 'core', - 'group', - 'question', - 'tutorial', - ]); + const [isNewTabWindow, setIsNewTabWindow] = useState(false); const tabsRef = useRef(null);