From 56fea6f41341a754447afacb5d2e8a56edfea64b Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 15 Oct 2024 17:48:03 +0300 Subject: [PATCH] added multi-publish --- public/content-script.js | 361 +++++++++- src/App.tsx | 1306 ++++++++++++++++++++----------------- src/qortalRequests.ts | 17 +- src/qortalRequests/get.ts | 268 +++++++- 4 files changed, 1308 insertions(+), 644 deletions(-) diff --git a/public/content-script.js b/public/content-script.js index d60420b..5b21b83 100644 --- a/public/content-script.js +++ b/public/content-script.js @@ -1,3 +1,61 @@ +class Semaphore { + constructor(count) { + this.count = count + this.waiting = [] + } + acquire() { + return new Promise(resolve => { + if (this.count > 0) { + this.count-- + resolve() + } else { + this.waiting.push(resolve) + } + }) + } + release() { + if (this.waiting.length > 0) { + const resolve = this.waiting.shift() + resolve() + } else { + this.count++ + } + } +} +let semaphore = new Semaphore(1) +let reader = new FileReader() + +const fileToBase64 = (file) => new Promise(async (resolve, reject) => { + if (!reader) { + reader = new FileReader() + } + await semaphore.acquire() + reader.readAsDataURL(file) + reader.onload = () => { + const dataUrl = reader.result + if (typeof dataUrl === "string") { + const base64String = dataUrl.split(',')[1] + reader.onload = null + reader.onerror = null + resolve(base64String) + } else { + reader.onload = null + reader.onerror = null + reject(new Error('Invalid data URL')) + } + semaphore.release() + } + reader.onerror = (error) => { + reader.onload = null + reader.onerror = null + reject(error) + semaphore.release() + } +}) + + + + async function connection(hostname) { const isConnected = await chrome.storage.local.get([hostname]); let connected = false; @@ -430,7 +488,70 @@ document.addEventListener("qortalExtensionRequests", async (event) => { // Handle other request types as needed... }); -chrome.runtime?.onMessage.addListener(function (message, sender, sendResponse) { +async function handleGetFileFromIndexedDB(fileId, sendResponse) { + try { + const db = await openIndexedDB(); + const transaction = db.transaction(["files"], "readonly"); + const objectStore = transaction.objectStore("files"); + + const getRequest = objectStore.get(fileId); + + getRequest.onsuccess = async function (event) { + if (getRequest.result) { + const file = getRequest.result.data; + + try { + const base64String = await fileToBase64(file); + + // Create a new transaction to delete the file + const deleteTransaction = db.transaction(["files"], "readwrite"); + const deleteObjectStore = deleteTransaction.objectStore("files"); + const deleteRequest = deleteObjectStore.delete(fileId); + + deleteRequest.onsuccess = function () { + console.log(`File with ID ${fileId} has been removed from IndexedDB`); + try { + sendResponse({ result: base64String }); + + } catch (error) { + console.log('error', error) + } + }; + + deleteRequest.onerror = function () { + console.error(`Error deleting file with ID ${fileId} from IndexedDB`); + sendResponse({ result: null, error: "Failed to delete file from IndexedDB" }); + }; + } catch (error) { + console.error("Error converting file to Base64:", error); + sendResponse({ result: null, error: "Failed to convert file to Base64" }); + } + } else { + console.error(`File with ID ${fileId} not found in IndexedDB`); + sendResponse({ result: null, error: "File not found in IndexedDB" }); + } + }; + + getRequest.onerror = function () { + console.error(`Error retrieving file with ID ${fileId} from IndexedDB`); + sendResponse({ result: null, error: "Error retrieving file from IndexedDB" }); + }; + } catch (error) { + console.error("Error opening IndexedDB:", error); + sendResponse({ result: null, error: "Error opening IndexedDB" }); + } +} + +const testAsync = async (sendResponse)=> { + await new Promise((res)=> { + setTimeout(() => { + res() + }, 2500); + }) + sendResponse({ result: null, error: "Testing" }); +} + +chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) { if (message.type === "LOGOUT") { // Notify the web page window.postMessage( @@ -451,42 +572,232 @@ chrome.runtime?.onMessage.addListener(function (message, sender, sendResponse) { "*" ); } + + else if (message.action === "getFileFromIndexedDB") { + handleGetFileFromIndexedDB(message.fileId, sendResponse); + return true; // Keep the message channel open for async response + } }); -const UIQortalRequests = ['GET_USER_ACCOUNT', 'ENCRYPT_DATA', 'DECRYPT_DATA', 'SEND_COIN', 'GET_LIST_ITEMS', 'ADD_LIST_ITEMS', 'DELETE_LIST_ITEM', 'PUBLISH_QDN_RESOURCE'] +function openIndexedDB() { + return new Promise((resolve, reject) => { + const request = indexedDB.open("fileStorageDB", 1); + + request.onupgradeneeded = function (event) { + const db = event.target.result; + if (!db.objectStoreNames.contains("files")) { + db.createObjectStore("files", { keyPath: "id" }); + } + }; + + request.onsuccess = function (event) { + resolve(event.target.result); + }; + + request.onerror = function () { + reject("Error opening IndexedDB"); + }; + }); +} + + +async function retrieveFileFromIndexedDB(fileId) { + const db = await openIndexedDB(); + const transaction = db.transaction(["files"], "readwrite"); + const objectStore = transaction.objectStore("files"); + + return new Promise((resolve, reject) => { + const getRequest = objectStore.get(fileId); + + getRequest.onsuccess = function (event) { + if (getRequest.result) { + // File found, resolve it and delete from IndexedDB + const file = getRequest.result.data; + objectStore.delete(fileId); + resolve(file); + } else { + reject("File not found in IndexedDB"); + } + }; + + getRequest.onerror = function () { + reject("Error retrieving file from IndexedDB"); + }; + }); +} + +async function deleteQortalFilesFromIndexedDB() { + try { + console.log("Opening IndexedDB for deleting files..."); + const db = await openIndexedDB(); + const transaction = db.transaction(["files"], "readwrite"); + const objectStore = transaction.objectStore("files"); + + // Create a request to get all keys + const getAllKeysRequest = objectStore.getAllKeys(); + + getAllKeysRequest.onsuccess = function (event) { + const keys = event.target.result; + + // Iterate through keys to find and delete those containing '_qortalfile' + for (let key of keys) { + if (key.includes("_qortalfile")) { + const deleteRequest = objectStore.delete(key); + + deleteRequest.onsuccess = function () { + console.log(`File with key '${key}' has been deleted from IndexedDB`); + }; + + deleteRequest.onerror = function () { + console.error(`Failed to delete file with key '${key}' from IndexedDB`); + }; + } + } + }; + + getAllKeysRequest.onerror = function () { + console.error("Failed to retrieve keys from IndexedDB"); + }; + + transaction.oncomplete = function () { + console.log("Transaction complete for deleting files from IndexedDB"); + }; + + transaction.onerror = function () { + console.error("Error occurred during transaction for deleting files"); + }; + } catch (error) { + console.error("Error opening IndexedDB:", error); + } +} + + +async function storeFilesInIndexedDB(obj) { + // First delete any existing files in IndexedDB with '_qortalfile' in their ID + await deleteQortalFilesFromIndexedDB(); + + // Open the IndexedDB + const db = await openIndexedDB(); + const transaction = db.transaction(["files"], "readwrite"); + const objectStore = transaction.objectStore("files"); + + // Handle the obj.file if it exists and is a File instance + if (obj.file instanceof File) { + const fileId = "objFile_qortalfile"; + + // Store the file in IndexedDB + const fileData = { + id: fileId, + data: obj.file, + }; + objectStore.put(fileData); + + // Replace the file object with the file ID in the original object + obj.fileId = fileId; + delete obj.file; + } + + // Iterate through resources to find files and save them to IndexedDB + for (let resource of obj.resources) { + if (resource.file instanceof File) { + const fileId = resource.identifier + "_qortalfile"; + + // Store the file in IndexedDB + const fileData = { + id: fileId, + data: resource.file, + }; + objectStore.put(fileData); + + // Replace the file object with the file ID in the original object + resource.fileId = fileId; + delete resource.file; + } + } + + // Set transaction completion handlers + transaction.oncomplete = function () { + console.log("Files saved successfully to IndexedDB"); + }; + + transaction.onerror = function () { + console.error("Error saving files to IndexedDB"); + }; + + return obj; // Updated object with references to stored files +} + + + +const UIQortalRequests = ['GET_USER_ACCOUNT', 'ENCRYPT_DATA', 'DECRYPT_DATA', 'SEND_COIN', 'GET_LIST_ITEMS', 'ADD_LIST_ITEMS', 'DELETE_LIST_ITEM'] if (!window.hasAddedQortalListener) { console.log("Listener added"); window.hasAddedQortalListener = true; //qortalRequests - const listener = (event) => { - + const listener = async (event) => { event.preventDefault(); // Prevent default behavior event.stopImmediatePropagation(); // Stop other listeners from firing + // Verify that the message is from the web page and contains expected data if (event.source !== window || !event.data || !event.data.action) return; - - if(event?.data?.requestedHandler !== 'UI') return - if (UIQortalRequests.includes(event.data.action)) { - - chrome?.runtime?.sendMessage( - { action: event.data.action, type: "qortalRequest", payload: event.data }, - (response) => { - console.log('response', response) - if (response.error) { - event.ports[0].postMessage({ - result: null, - error: response.error, - }); - } else { - event.ports[0].postMessage({ - result: response, - error: null, - }); - } + + if (event?.data?.requestedHandler !== 'UI') return; + + const sendMessageToRuntime = (message, eventPort) => { + chrome?.runtime?.sendMessage(message, (response) => { + console.log('response', response); + if (response.error) { + eventPort.postMessage({ + result: null, + error: response.error, + }); + } else { + eventPort.postMessage({ + result: response, + error: null, + }); } + }); + }; + + // Check if action is included in the predefined list of UI requests + if (UIQortalRequests.includes(event.data.action)) { + console.log('event?.data', event?.data); + sendMessageToRuntime( + { action: event.data.action, type: 'qortalRequest', payload: event.data }, + event.ports[0] ); - } + } else if (event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' || event?.data?.action === 'PUBLISH_QDN_RESOURCE') { + let data; + try { + data = await storeFilesInIndexedDB(event.data); + } catch (error) { + console.error('Error storing files in IndexedDB:', error); + event.ports[0].postMessage({ + result: null, + error: 'Failed to store files in IndexedDB', + }); + return; + } + + if (data) { + sendMessageToRuntime( + { action: event.data.action, type: 'qortalRequest', payload: data }, + event.ports[0] + ); + } else { + event.ports[0].postMessage({ + result: null, + error: 'Failed to prepare data for publishing', + }); + } + } }; - window.addEventListener("message", listener); + + // Add the listener for messages coming from the window + window.addEventListener('message', listener); + } + + diff --git a/src/App.tsx b/src/App.tsx index 5910f8c..241e13e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,7 +42,7 @@ import Logout from "./assets/svgs/Logout.svg"; import Return from "./assets/svgs/Return.svg"; import Success from "./assets/svgs/Success.svg"; import Info from "./assets/svgs/Info.svg"; -import CloseIcon from '@mui/icons-material/Close'; +import CloseIcon from "@mui/icons-material/Close"; import { createAccount, @@ -70,13 +70,13 @@ import { Spacer } from "./common/Spacer"; import { Loader } from "./components/Loader"; import { PasswordField, ErrorText } from "./components"; import { ChatGroup } from "./components/Chat/ChatGroup"; -import { Group, requestQueueMemberNames } from "./components/Group/Group"; +import { Group, requestQueueMemberNames } from "./components/Group/Group"; import { TaskManger } from "./components/TaskManager/TaskManger"; import { useModal } from "./common/useModal"; import { LoadingButton } from "@mui/lab"; import { Label } from "./components/Group/AddGroup"; import { CustomizedSnackbars } from "./components/Snackbar/Snackbar"; -import SettingsIcon from '@mui/icons-material/Settings'; +import SettingsIcon from "@mui/icons-material/Settings"; import { getFee, groupApi, @@ -84,8 +84,15 @@ import { groupApiSocket, groupApiSocketLocal, } from "./background"; -import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "./utils/events"; -import { requestQueueCommentCount, requestQueuePublishedAccouncements } from "./components/Chat/GroupAnnouncements"; +import { + executeEvent, + subscribeToEvent, + unsubscribeFromEvent, +} from "./utils/events"; +import { + requestQueueCommentCount, + requestQueuePublishedAccouncements, +} from "./components/Chat/GroupAnnouncements"; import { requestQueueGroupJoinRequests } from "./components/Group/GroupJoinRequests"; import { DrawerComponent } from "./components/Drawer/Drawer"; import { AddressQRCode } from "./components/AddressQRCode"; @@ -134,11 +141,11 @@ const defaultValues: MyContextInterface = { message: "", }, }; -export let isMobile = false +export let isMobile = false; const isMobileDevice = () => { const userAgent = navigator.userAgent || navigator.vendor || window.opera; - + if (/android/i.test(userAgent)) { return true; // Android device } @@ -151,7 +158,7 @@ const isMobileDevice = () => { }; if (isMobileDevice()) { - isMobile = true + isMobile = true; console.log("Running on a mobile device"); } else { console.log("Running on a desktop"); @@ -161,14 +168,14 @@ export const allQueues = { requestQueueCommentCount: requestQueueCommentCount, requestQueuePublishedAccouncements: requestQueuePublishedAccouncements, requestQueueMemberNames: requestQueueMemberNames, - requestQueueGroupJoinRequests: requestQueueGroupJoinRequests -} + requestQueueGroupJoinRequests: requestQueueGroupJoinRequests, +}; const controlAllQueues = (action) => { Object.keys(allQueues).forEach((key) => { const val = allQueues[key]; try { - if (typeof val[action] === 'function') { + if (typeof val[action] === "function") { val[action](); } } catch (error) { @@ -186,38 +193,28 @@ export const clearAllQueues = () => { console.error(error); } }); -} +}; export const pauseAllQueues = () => { - controlAllQueues('pause'); - chrome?.runtime?.sendMessage( - { - action: "pauseAllQueues", - payload: { - - }, - } - ); -} + controlAllQueues("pause"); + chrome?.runtime?.sendMessage({ + action: "pauseAllQueues", + payload: {}, + }); +}; export const resumeAllQueues = () => { - controlAllQueues('resume'); - chrome?.runtime?.sendMessage( - { - action: "resumeAllQueues", - payload: { - - }, - } - ); -} - + controlAllQueues("resume"); + chrome?.runtime?.sendMessage({ + action: "resumeAllQueues", + payload: {}, + }); +}; export const MyContext = createContext(defaultValues); export let globalApiKey: string | null = null; export const getBaseApiReact = (customApi?: string) => { - if (customApi) { return customApi; } @@ -229,7 +226,6 @@ export const getBaseApiReact = (customApi?: string) => { } }; // export const getArbitraryEndpointReact = () => { - // if (globalApiKey) { // return `/arbitrary/resources/search`; @@ -238,8 +234,6 @@ export const getBaseApiReact = (customApi?: string) => { // } // }; export const getArbitraryEndpointReact = () => { - - if (globalApiKey) { return `/arbitrary/resources/search`; } else { @@ -247,7 +241,6 @@ export const getArbitraryEndpointReact = () => { } }; export const getBaseApiReactSocket = (customApi?: string) => { - if (customApi) { return customApi; } @@ -300,11 +293,17 @@ function App() { const [txList, setTxList] = useState([]); const [memberGroups, setMemberGroups] = useState([]); const [isFocused, setIsFocused] = useState(true); - + const holdRefExtState = useRef("not-authenticated"); const isFocusedRef = useRef(true); const { isShow, onCancel, onOk, show, message } = useModal(); - const { onCancel: onCancelQortalRequest, onOk: onOkQortalRequest, show: showQortalRequest, isShow: isShowQortalRequest, message: messageQortalRequest } = useModal(); + const { + onCancel: onCancelQortalRequest, + onOk: onOkQortalRequest, + show: showQortalRequest, + isShow: isShowQortalRequest, + message: messageQortalRequest, + } = useModal(); const [openRegisterName, setOpenRegisterName] = useState(false); const registerNamePopoverRef = useRef(null); @@ -318,43 +317,44 @@ function App() { const [confirmUseOfLocal, setConfirmUseOfLocal] = useState(false); const [isOpenDrawerProfile, setIsOpenDrawerProfile] = useState(false); const [apiKey, setApiKey] = useState(""); - const [isOpenSendQort, setIsOpenSendQort] = useState(false) - const [isOpenSendQortSuccess, setIsOpenSendQortSuccess] = useState(false) - const [rootHeight, setRootHeight] = useState('100%') - const [isSettingsOpen, setIsSettingsOpen] = useState(false) + const [isOpenSendQort, setIsOpenSendQort] = useState(false); + const [isOpenSendQortSuccess, setIsOpenSendQortSuccess] = useState(false); + const [rootHeight, setRootHeight] = useState("100%"); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); const qortalRequestCheckbox1Ref = useRef(null); useEffect(() => { - if(!isMobile) return + if (!isMobile) return; // Function to set the height of the app to the viewport height const resetHeight = () => { - const height = window.visualViewport ? window.visualViewport.height : window.innerHeight; + const height = window.visualViewport + ? window.visualViewport.height + : window.innerHeight; // Set the height to the root element (usually #root) - document.getElementById('root').style.height = height + "px"; - setRootHeight(height + "px") + document.getElementById("root").style.height = height + "px"; + setRootHeight(height + "px"); }; // Set the initial height resetHeight(); // Add event listeners for resize and visualViewport changes - window.addEventListener('resize', resetHeight); - window.visualViewport?.addEventListener('resize', resetHeight); + window.addEventListener("resize", resetHeight); + window.visualViewport?.addEventListener("resize", resetHeight); // Clean up the event listeners when the component unmounts return () => { - window.removeEventListener('resize', resetHeight); - window.visualViewport?.removeEventListener('resize', resetHeight); + window.removeEventListener("resize", resetHeight); + window.visualViewport?.removeEventListener("resize", resetHeight); }; }, []); useEffect(() => { chrome?.runtime?.sendMessage({ action: "getApiKey" }, (response) => { if (response) { - globalApiKey = response; setApiKey(response); - setUseLocalNode(true) - setConfirmUseOfLocal(true) - setOpenAdvancedSettings(true) + setUseLocalNode(true); + setConfirmUseOfLocal(true); + setOpenAdvancedSettings(true); } }); }, []); @@ -380,7 +380,7 @@ function App() { reader.readAsText(file); // Read the file as text } }; - + // const checkIfUserHasLocalNode = useCallback(async () => { // try { // const url = `http://127.0.0.1:12391/admin/status`; @@ -549,8 +549,8 @@ function App() { if (response?.error) { setSendPaymentError(response.error); } else { - setIsOpenSendQort(false) - setIsOpenSendQortSuccess(true) + setIsOpenSendQort(false); + setIsOpenSendQortSuccess(true); // setExtstate("transfer-success-regular"); // setSendPaymentSuccess("Payment successfully sent"); } @@ -564,49 +564,62 @@ function App() { setRequestAuthentication(null); }; - const qortalRequestPermisson = async (message, sender, sendResponse)=> { - if ( - message.action === "QORTAL_REQUEST_PERMISSION" && - !isMainWindow - ) { + const qortalRequestPermisson = async (message, sender, sendResponse) => { + if (message.action === "QORTAL_REQUEST_PERMISSION" && !isMainWindow) { try { - console.log('payloadbefore', message.payload) + console.log("payloadbefore", message.payload); - await showQortalRequest(message?.payload) - console.log('payload', message.payload) - if(message?.payload?.checkbox1){ - console.log('qortalRequestCheckbox1Ref.current', qortalRequestCheckbox1Ref.current) - sendResponse({accepted: true, - checkbox1: qortalRequestCheckbox1Ref.current - }) - return + await showQortalRequest(message?.payload); + console.log("payload", message.payload); + if (message?.payload?.checkbox1) { + console.log( + "qortalRequestCheckbox1Ref.current", + qortalRequestCheckbox1Ref.current + ); + sendResponse({ + accepted: true, + checkbox1: qortalRequestCheckbox1Ref.current, + }); + return; } - sendResponse({accepted: true}) + sendResponse({ accepted: true }); } catch (error) { - console.log('error', error) - sendResponse({accepted: false}) + console.log("error", error); + sendResponse({ accepted: false }); } finally { window.close(); } } - } + }; useEffect(() => { // Listen for messages from the background script const messageListener = (message, sender, sendResponse) => { // Handle various actions - if (message.action === "UPDATE_STATE_CONFIRM_SEND_QORT" && !isMainWindow) { + if ( + message.action === "UPDATE_STATE_CONFIRM_SEND_QORT" && + !isMainWindow + ) { setSendqortState(message.payload); setExtstate("web-app-request-payment"); } else if (message.action === "closePopup" && !isMainWindow) { window.close(); - } else if (message.action === "UPDATE_STATE_REQUEST_CONNECTION" && !isMainWindow) { + } else if ( + message.action === "UPDATE_STATE_REQUEST_CONNECTION" && + !isMainWindow + ) { setRequestConnection(message.payload); setExtstate("web-app-request-connection"); - } else if (message.action === "UPDATE_STATE_REQUEST_BUY_ORDER" && !isMainWindow) { + } else if ( + message.action === "UPDATE_STATE_REQUEST_BUY_ORDER" && + !isMainWindow + ) { setRequestBuyOrder(message.payload); setExtstate("web-app-request-buy-order"); - } else if (message.action === "UPDATE_STATE_REQUEST_AUTHENTICATION" && !isMainWindow) { + } else if ( + message.action === "UPDATE_STATE_REQUEST_AUTHENTICATION" && + !isMainWindow + ) { setRequestAuthentication(message.payload); setExtstate("web-app-request-authentication"); } else if (message.action === "SET_COUNTDOWN" && !isMainWindow) { @@ -615,9 +628,12 @@ function App() { setIsMain(true); isMainRef.current = true; } else if (message.action === "CHECK_FOCUS" && isMainWindow) { - sendResponse(isFocusedRef.current); // Synchronous response - return true; // Return true if you plan to send a response asynchronously - } else if (message.action === "NOTIFICATION_OPEN_DIRECT" && isMainWindow) { + sendResponse(isFocusedRef.current); // Synchronous response + return true; // Return true if you plan to send a response asynchronously + } else if ( + message.action === "NOTIFICATION_OPEN_DIRECT" && + isMainWindow + ) { executeEvent("openDirectMessage", { from: message.payload.from, }); @@ -625,37 +641,42 @@ function App() { executeEvent("openGroupMessage", { from: message.payload.from, }); - } else if (message.action === "NOTIFICATION_OPEN_ANNOUNCEMENT_GROUP" && isMainWindow) { + } else if ( + message.action === "NOTIFICATION_OPEN_ANNOUNCEMENT_GROUP" && + isMainWindow + ) { executeEvent("openGroupAnnouncement", { from: message.payload.from, }); - } else if (message.action === "NOTIFICATION_OPEN_THREAD_NEW_POST" && isMainWindow) { + } else if ( + message.action === "NOTIFICATION_OPEN_THREAD_NEW_POST" && + isMainWindow + ) { executeEvent("openThreadNewPost", { data: message.payload.data, }); } - + // Call the permission request handler for "QORTAL_REQUEST_PERMISSION" - qortalRequestPermisson(message, sender, sendResponse); + qortalRequestPermisson(message, sender, sendResponse); if (message.action === "QORTAL_REQUEST_PERMISSION" && !isMainWindow) { - console.log('isMainWindow', isMainWindow, window?.location?.href ) - return true; // Return true to indicate an async response is coming - } - if(message.action === "QORTAL_REQUEST_PERMISSION" && isMainWindow){ + console.log("isMainWindow", isMainWindow, window?.location?.href); + return true; // Return true to indicate an async response is coming + } + if (message.action === "QORTAL_REQUEST_PERMISSION" && isMainWindow) { return; } }; - + // Add message listener chrome.runtime?.onMessage.addListener(messageListener); - + // Clean up the listener on component unmount return () => { chrome.runtime?.onMessage.removeListener(messageListener); }; }, []); - //param = isDecline const confirmPayment = (isDecline: boolean) => { if (isDecline) { @@ -716,7 +737,7 @@ function App() { crosschainAtInfo: requestBuyOrder?.crosschainAtInfo, interactionId: requestBuyOrder?.interactionId, isDecline: true, - useLocal: requestBuyOrder?.useLocal + useLocal: requestBuyOrder?.useLocal, }, }, (response) => { @@ -734,7 +755,7 @@ function App() { crosschainAtInfo: requestBuyOrder?.crosschainAtInfo, interactionId: requestBuyOrder?.interactionId, isDecline: false, - useLocal: requestBuyOrder?.useLocal + useLocal: requestBuyOrder?.useLocal, }, }, (response) => { @@ -787,7 +808,6 @@ function App() { ) return; - setExtstate("authenticated"); } }); @@ -912,12 +932,15 @@ function App() { wallet, qortAddress: wallet.address0, }); - chrome?.runtime?.sendMessage({ action: "userInfo" }, (response2) => { - setIsLoading(false); - if (response2 && !response2.error) { - setUserInfo(response); + chrome?.runtime?.sendMessage( + { action: "userInfo" }, + (response2) => { + setIsLoading(false); + if (response2 && !response2.error) { + setUserInfo(response); + } } - }); + ); getBalanceFunc(); } else if (response?.error) { setIsLoading(false); @@ -952,8 +975,8 @@ function App() { setWalletToBeDownloaded(null); setWalletToBeDownloadedPassword(""); setExtstate("authenticated"); - setIsOpenSendQort(false) - setIsOpenSendQortSuccess(false) + setIsOpenSendQort(false); + setIsOpenSendQortSuccess(false); }; const resetAllStates = () => { @@ -984,9 +1007,9 @@ function App() { setUseLocalNode(false); setHasLocalNode(false); setOpenAdvancedSettings(false); - setConfirmUseOfLocal(false) - setTxList([]) - setMemberGroups([]) + setConfirmUseOfLocal(false); + setTxList([]); + setMemberGroups([]); }; function roundUpToDecimals(number, decimals = 8) { @@ -1068,7 +1091,7 @@ function App() { const handleBeforeUnload = (e) => { e.preventDefault(); e.returnValue = ""; // This is required for Chrome to display the confirmation dialog. - return ""; + return ""; }; // Add the event listener when the component mounts @@ -1085,17 +1108,13 @@ function App() { // Handler for when the window gains focus const handleFocus = () => { setIsFocused(true); - if(isMobile){ - chrome?.runtime?.sendMessage( - { - action: "clearAllNotifications", - payload: { - - }, - } - ); + if (isMobile) { + chrome?.runtime?.sendMessage({ + action: "clearAllNotifications", + payload: {}, + }); } - + console.log("Webview is focused"); }; @@ -1113,15 +1132,11 @@ function App() { const handleVisibilityChange = () => { if (document.visibilityState === "visible") { setIsFocused(true); - if(isMobile){ - chrome?.runtime?.sendMessage( - { - action: "clearAllNotifications", - payload: { - - }, - } - ); + if (isMobile) { + chrome?.runtime?.sendMessage({ + action: "clearAllNotifications", + payload: {}, + }); } console.log("Webview is visible"); } else { @@ -1140,12 +1155,11 @@ function App() { }; }, []); - const openPaymentInternal = (e) => { const directAddress = e.detail?.address; - const name = e.detail?.name - setIsOpenSendQort(true) - setPaymentTo(name || directAddress) + const name = e.detail?.name; + setIsOpenSendQort(true); + setPaymentTo(name || directAddress); }; useEffect(() => { @@ -1174,7 +1188,6 @@ function App() { }, }, (response) => { - if (!response?.error) { res(response); setIsLoadingRegisterName(false); @@ -1219,248 +1232,268 @@ function App() { } }; - const renderProfile = ()=> { + const renderProfile = () => { return ( - - {isMobile && ( - { - setIsOpenDrawerProfile(false) - }} sx={{ - cursor: 'pointer', - color: 'white' - }} /> - )} - - - - - {authenticatedMode === "ltc" ? ( - <> - - - - - {rawWallet?.ltcAddress?.slice(0, 6)}... - {rawWallet?.ltcAddress?.slice(-4)} - - - - {ltcBalanceLoading && ( - - )} - {!isNaN(+ltcBalance) && !ltcBalanceLoading && ( - + {isMobile && ( + + { + setIsOpenDrawerProfile(false); }} - > + sx={{ + cursor: "pointer", + color: "white", + }} + /> + + )} + + + + + {authenticatedMode === "ltc" ? ( + <> + + + + + {rawWallet?.ltcAddress?.slice(0, 6)}... + {rawWallet?.ltcAddress?.slice(-4)} + + + + {ltcBalanceLoading && ( + + )} + {!isNaN(+ltcBalance) && !ltcBalanceLoading && ( + + + {ltcBalance} LTC + + + + )} + + + ) : ( + <> + + - {ltcBalance} LTC + {userInfo?.name} - + + + {rawWallet?.address0?.slice(0, 6)}... + {rawWallet?.address0?.slice(-4)} + + + + {qortBalanceLoading && ( + + )} + {!qortBalanceLoading && balance >= 0 && ( + + + {balance?.toFixed(2)} QORT + + + + )} + + + {userInfo && !userInfo?.name && ( + { + setOpenRegisterName(true); + }} + > + REGISTER NAME + + )} + + { + setIsOpenSendQort(true); + // setExtstate("send-qort"); + setIsOpenDrawerProfile(false); }} - /> - + > + Transfer QORT + + + )} - - - ) : ( - <> - - { + chrome.tabs.create({ url: "https://www.qort.trade" }); }} > - {userInfo?.name} + Get QORT at qort.trade - - - - {rawWallet?.address0?.slice(0, 6)}... - {rawWallet?.address0?.slice(-4)} - - - - {qortBalanceLoading && ( - - )} - {!qortBalanceLoading && balance >= 0 && ( - - + + + { + setExtstate("download-wallet"); + setIsOpenDrawerProfile(false); + }} + src={Download} + style={{ + cursor: "pointer", + }} + /> + {!isMobile && ( + <> + + { + logoutFunc(); + setIsOpenDrawerProfile(false); }} - > - {balance?.toFixed(2)} QORT - - - - )} - - - {userInfo && !userInfo?.name && ( - { - setOpenRegisterName(true); - }} - > - REGISTER NAME - + )} - { - setIsOpenSendQort(true) - // setExtstate("send-qort"); - setIsOpenDrawerProfile(false) + setIsSettingsOpen(true); }} > - Transfer QORT - - - - )} - { - chrome.tabs.create({ url: "https://www.qort.trade" }); - }} - > - Get QORT at qort.trade - - - - - { - setExtstate("download-wallet"); - setIsOpenDrawerProfile(false) - }} - src={Download} - style={{ - cursor: "pointer", - }} - /> - {!isMobile && ( - <> - - { - logoutFunc() - setIsOpenDrawerProfile(false) - }} - style={{ - cursor: "pointer", - }} - /> - - )} - - - { - setIsSettingsOpen(true) - }}> - - - - {authenticatedMode === "qort" && ( - { - setAuthenticatedMode("ltc"); - }} - src={ltcLogo} - style={{ - cursor: "pointer", - width: "20px", - height: "auto", - }} - /> - )} - {authenticatedMode === "ltc" && ( - { - setAuthenticatedMode("qort"); - }} - src={qortLogo} - style={{ - cursor: "pointer", - width: "20px", - height: "auto", - }} - /> - )} - - - - ) - } + + + + {authenticatedMode === "qort" && ( + { + setAuthenticatedMode("ltc"); + }} + src={ltcLogo} + style={{ + cursor: "pointer", + width: "20px", + height: "auto", + }} + /> + )} + {authenticatedMode === "ltc" && ( + { + setAuthenticatedMode("qort"); + }} + src={qortLogo} + style={{ + cursor: "pointer", + width: "20px", + height: "auto", + }} + /> + )} + + + ); + }; return ( - + {/* {extState === 'group' && ( )} */} @@ -1533,41 +1566,40 @@ function App() { }} /> - - <> - - + + + { + setOpenAdvancedSettings(true); }} > - - { - setOpenAdvancedSettings(true); + Advanced settings + + + {openAdvancedSettings && ( + <> + - Advanced settings - - - {openAdvancedSettings && ( - <> - Use local node - - {useLocalNode && ( - <> - - - - {apiKey} - - - + + + {apiKey} + + + - - )} - - )} - - - + } + ); + }} + variant="contained" + sx={{ + color: "white", + }} + > + {!confirmUseOfLocal + ? "Confirm use of local node" + : "Switch back to gateway"} + + + )} + + )} + + )} {/* {extState !== "not-authenticated" && ( )} */} - {extState === "authenticated" && isMainWindow && ( + {extState === "authenticated" && isMainWindow && ( {!isMobile && renderProfile()} - - - - - - - + + + + )} {isOpenSendQort && isMainWindow && ( - + )} -{isShowQortalRequest && !isMainWindow && ( + {isShowQortalRequest && !isMainWindow && ( <> - - - - {messageQortalRequest?.text1} - - - - - - {messageQortalRequest?.text2} - - - - {messageQortalRequest?.text3 && ( - - - {messageQortalRequest?.text3} - - - - )} - {messageQortalRequest?.text4 && ( - - - {messageQortalRequest?.text4} - - - - )} - - - {messageQortalRequest?.highlightedText} - - {messageQortalRequest?.checkbox1 && - ( + + sx={{ + display: "flex", + justifyContent: "center", + width: "100%", + }} + > + + {messageQortalRequest?.text1} + + + {messageQortalRequest?.text2 && ( + <> + + + + {messageQortalRequest?.text2} + + + + )} + {messageQortalRequest?.text3 && ( + <> + + + {messageQortalRequest?.text3} + + + + + )} + + {messageQortalRequest?.text4 && ( + + + {messageQortalRequest?.text4} + + + )} + + {messageQortalRequest?.html && ( +
+ )} + + {messageQortalRequest?.fee && ( + <> + + {'Fee: '}{messageQortalRequest?.fee}{' QORT'} + + + + + )} + + {messageQortalRequest?.highlightedText} + + {messageQortalRequest?.checkbox1 && ( + { - qortalRequestCheckbox1Ref.current = e.target.checked + onChange={(e) => { + qortalRequestCheckbox1Ref.current = e.target.checked; }} edge="start" tabIndex={-1} @@ -1936,39 +2006,43 @@ function App() { }} /> - {messageQortalRequest?.checkbox1?.label} - - )} - - - - + {messageQortalRequest?.checkbox1?.label} + + + )} + + + onOkQortalRequest("accepted")} > - accept - - onCancelQortalRequest()} - > - decline - - - {sendPaymentError} - + onOkQortalRequest("accepted")} + > + accept + + onCancelQortalRequest()} + > + decline + + + {sendPaymentError} + )} {extState === "web-app-request-buy-order" && !isMainWindow && ( <> @@ -1982,7 +2056,12 @@ function App() { > The Application

{" "} {requestBuyOrder?.hostname}

- is requesting {requestBuyOrder?.crosschainAtInfo?.length} {`buy order${requestBuyOrder?.crosschainAtInfo.length === 1 ? '' : 's'}`} + + is requesting {requestBuyOrder?.crosschainAtInfo?.length}{" "} + {`buy order${ + requestBuyOrder?.crosschainAtInfo.length === 1 ? "" : "s" + }`} + - {requestBuyOrder?.crosschainAtInfo?.reduce((latest, cur)=> { - return latest + +cur?.qortAmount - }, 0)} QORT + {requestBuyOrder?.crosschainAtInfo?.reduce((latest, cur) => { + return latest + +cur?.qortAmount; + }, 0)}{" "} + QORT - {roundUpToDecimals(requestBuyOrder?.crosschainAtInfo?.reduce((latest, cur)=> { - return latest + +cur?.expectedForeignAmount - }, 0))} + {roundUpToDecimals( + requestBuyOrder?.crosschainAtInfo?.reduce((latest, cur) => { + return latest + +cur?.expectedForeignAmount; + }, 0) + )} {` ${requestBuyOrder?.crosschainAtInfo?.[0]?.foreignBlockchain}`} {/* @@ -2060,7 +2142,7 @@ function App() { {sendPaymentError} )} - + {extState === "web-app-request-payment" && !isMainWindow && ( <> @@ -2508,16 +2590,18 @@ function App() { )} {isOpenSendQortSuccess && ( - + @@ -2682,8 +2766,7 @@ function App() { {isSettingsOpen && ( - - + )} - {renderProfile()} + + {renderProfile()} + ); } diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts index 1990371..4e316db 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests.ts @@ -1,4 +1,4 @@ -import { addListItems, decryptData, deleteListItems, encryptData, getListItems, getUserAccount, publishQDNResource, sendCoin } from "./qortalRequests/get"; +import { addListItems, decryptData, deleteListItems, encryptData, getListItems, getUserAccount, publishMultipleQDNResources, publishQDNResource, sendCoin } from "./qortalRequests/get"; @@ -143,7 +143,20 @@ chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => { case "PUBLISH_QDN_RESOURCE": { const data = request.payload; - publishQDNResource(data) + publishQDNResource(data, sender) + .then((res) => { + sendResponse(res); + }) + .catch((error) => { + sendResponse({ error: error.message }); + }); + + break; + } + case "PUBLISH_MULTIPLE_QDN_RESOURCES": { + const data = request.payload; + + publishMultipleQDNResources(data, sender) .then((res) => { sendResponse(res); }) diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index 5e141d1..74b37c4 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -19,6 +19,30 @@ import { publishData } from "../qdn/publish/pubish"; import { getPermission, setPermission } from "../qortalRequests"; import { fileToBase64 } from "../utils/fileReading"; +function getFileFromContentScript(fileId, sender) { + console.log('sender', sender) + return new Promise((resolve, reject) => { + + + chrome.tabs.sendMessage( + sender.tab.id, + { action: "getFileFromIndexedDB", fileId: fileId }, + (response) => { + console.log('response2', response) + if (response && response.result) { + + resolve(response.result); + } else { + reject(response?.error || "Failed to retrieve file"); + } + } + ); + + + }); + } + + async function getUserPermission(payload: any) { function waitForWindowReady(windowId) { return new Promise((resolve) => { @@ -369,7 +393,7 @@ export const deleteListItems = async (data) => { } }; -export const publishQDNResource = async (data: any) => { +export const publishQDNResource = async (data: any, sender) => { const requiredFields = ["service"]; const missingFields: string[] = []; requiredFields.forEach((field) => { @@ -382,7 +406,7 @@ export const publishQDNResource = async (data: any) => { const errorMsg = `Missing fields: ${missingFieldsString}`; throw new Error(errorMsg); } - if (!data.file && !data.data64) { + if (!data.fileId && !data.data64) { throw new Error("No data or file was submitted"); } // Use "default" if user hasn't specified an identifer @@ -414,8 +438,8 @@ export const publishQDNResource = async (data: any) => { if (!data.encrypt && data.service.endsWith("_PRIVATE")) { throw new Error("Only encrypted data can go into private services"); } - if (data.file) { - data64 = await fileToBase64(data.file); + if (data.fileId) { + data64 = await getFileFromContentScript(data.fileId, sender); } if (data.encrypt) { try { @@ -433,19 +457,19 @@ export const publishQDNResource = async (data: any) => { } } - const fee = await getFee('ARBITRARY') + const fee = await getFee("ARBITRARY"); const resPermission = await getUserPermission({ text1: "Do you give this application permission to publish to QDN?", text2: `service: ${service}`, text3: `identifier: ${identifier || null}`, highlightedText: `isEncrypted: ${!!data.encrypt}`, - fee: fee.fee + fee: fee.fee, }); const { accepted } = resPermission; if (accepted) { - if (data.file && !data.encrypt) { - data64 = await fileToBase64(data.file); + if (data.fileId && !data.encrypt) { + data64 = await getFileFromContentScript(data.fileId, sender); } try { const resPublish = await publishData({ @@ -476,6 +500,234 @@ export const publishQDNResource = async (data: any) => { } }; +export const publishMultipleQDNResources = async (data: any, sender) => { + const requiredFields = ["resources"]; + const missingFields: string[] = []; + let feeAmount = null; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(", "); + const errorMsg = `Missing fields: ${missingFieldsString}`; + throw new Error(errorMsg); + } + const resources = data.resources; + if (!Array.isArray(resources)) { + throw new Error("Invalid data"); + } + if (resources.length === 0) { + throw new Error("No resources to publish"); + } + 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; + const resPermission = await getUserPermission({ + text1: "Do you give this application permission to publish to QDN?", + html: ` +
+ + + ${data.resources + .map( + (resource) => ` +
+
Service: ${resource.service}
+
Name: ${resource.name}
+
Identifier: ${resource.identifier}
+ ${ + resource.filename + ? `
Filename: ${resource.filename}
` + : "" + } +
` + ) + .join("")} +
+ + `, + highlightedText: `isEncrypted: ${!!data.encrypt}`, + fee: fee.fee * resources.length, + }); + const { accepted } = resPermission; + console.log('accepted', accepted) + if (!accepted) { + throw new Error("User declined request"); + } + let failedPublishesIdentifiers = []; + console.log('resources', resources) + 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 = `Missing fields: ${missingFieldsString}`; + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + }); + continue; + } + if (!resource.fileId && !resource.data64) { + const errorMsg = "No data or file was submitted"; + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + }); + continue; + } + const service = resource.service; + let identifier = resource.identifier; + let data64 = resource.data64; + const filename = resource.filename; + const title = resource.title; + const description = resource.description; + const category = resource.category; + const tag1 = resource.tag1; + const tag2 = resource.tag2; + const tag3 = resource.tag3; + const tag4 = resource.tag4; + const tag5 = resource.tag5; + if (resource.identifier == null) { + identifier = "default"; + } + if (!data.encrypt && service.endsWith("_PRIVATE")) { + const errorMsg = "Only encrypted data can go into private services"; + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + }); + continue; + } + // if (data.file) { + // data64 = await getFileFromContentScript(resource.identifier + "_file"); + // } + if (data.encrypt) { + try { + const encryptDataResponse = encryptDataGroup({ + data64, + publicKeys: data.publicKeys, + }); + if (encryptDataResponse) { + data64 = encryptDataResponse; + } + } catch (error) { + const errorMsg = + error.message || "Upload failed due to failed encryption"; + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + }); + continue; + } + } + if (resource.fileId && !data.encrypt) { + + data64 = await getFileFromContentScript(resource.fileId, sender); + + } + try { + + 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, + }); + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000); + }); + } catch (error) { + const errorMsg = error.message || "Upload failed"; + failedPublishesIdentifiers.push({ + reason: errorMsg, + identifier: resource.identifier, + }); + } + } catch (error) { + console.log('error', error) + failedPublishesIdentifiers.push({ + reason: "Unknown error", + identifier: resource.identifier, + }); + } + } + if (failedPublishesIdentifiers.length > 0) { + const obj = {}; + obj["error"] = { + unsuccessfulPublishes: failedPublishesIdentifiers, + }; + return obj; + } + return true; +}; + export const sendCoin = async () => { try { const wallet = await getSaveWallet();