diff --git a/package-lock.json b/package-lock.json index 4281ead..b0edd16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@capacitor/android": "^6.1.2", + "@capacitor/browser": "^6.0.3", "@capacitor/cli": "^6.1.2", "@capacitor/core": "^6.1.2", "@chatscope/chat-ui-kit-react": "^2.0.3", @@ -398,6 +399,14 @@ "@capacitor/core": "^6.1.0" } }, + "node_modules/@capacitor/browser": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@capacitor/browser/-/browser-6.0.3.tgz", + "integrity": "sha512-V5/ViE+fy3rW+6w0L2du4747/hivBcI/5OGOKi01U9YdFvOWbiiGL+qetq1QdkANEqvYd4yoCeXZL2GyGySn5g==", + "peerDependencies": { + "@capacitor/core": "^6.0.0" + } + }, "node_modules/@capacitor/cli": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-6.1.2.tgz", diff --git a/package.json b/package.json index a16c1de..e33a359 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@capacitor/android": "^6.1.2", + "@capacitor/browser": "^6.0.3", "@capacitor/cli": "^6.1.2", "@capacitor/core": "^6.1.2", "@chatscope/chat-ui-kit-react": "^2.0.3", diff --git a/src/App.tsx b/src/App.tsx index be4e85c..ea98624 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import { useRef, useState, } from "react"; - +import { Browser } from "@capacitor/browser"; import "./App.css"; import { useDropzone } from "react-dropzone"; import { @@ -102,9 +102,20 @@ import { MainAvatar } from "./components/MainAvatar"; import { useRetrieveDataLocalStorage } from "./useRetrieveDataLocalStorage"; import { useQortalGetSaveSettings } from "./useQortalGetSaveSettings"; import { useRecoilState, useResetRecoilState, useSetRecoilState } from "recoil"; -import { canSaveSettingToQdnAtom, fullScreenAtom, hasSettingsChangedAtom, oldPinnedAppsAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom } from "./atoms/global"; +import { + canSaveSettingToQdnAtom, + fullScreenAtom, + hasSettingsChangedAtom, + oldPinnedAppsAtom, + settingsLocalLastUpdatedAtom, + settingsQDNLastUpdatedAtom, + sortablePinnedAppsAtom, +} from "./atoms/global"; import { useAppFullScreen } from "./useAppFullscreen"; import { NotAuthenticated } from "./ExtStates/NotAuthenticated"; +import { openIndexedDB, showSaveFilePicker } from "./components/Apps/useQortalMessageListener"; +import { fileToBase64 } from "./utils/fileReading"; +import { handleGetFileFromIndexedDB } from "./utils/indexedDB"; type extStates = @@ -205,18 +216,21 @@ export const clearAllQueues = () => { export const pauseAllQueues = () => { controlAllQueues("pause"); - window.sendMessage("pauseAllQueues", {}) - .catch((error) => { - console.error("Failed to pause all queues:", error.message || "An error occurred"); + window.sendMessage("pauseAllQueues", {}).catch((error) => { + console.error( + "Failed to pause all queues:", + error.message || "An error occurred" + ); }); - }; export const resumeAllQueues = () => { controlAllQueues("resume"); window.sendMessage("resumeAllQueues", {}).catch((error) => { - console.error("Failed to resume all queues:", error.message || "An error occurred"); + console.error( + "Failed to resume all queues:", + error.message || "An error occurred" + ); }); - }; export const MyContext = createContext(defaultValues); @@ -255,15 +269,17 @@ export const getBaseApiReactSocket = (customApi?: string) => { } if (globalApiKey) { - return `${getProtocol(globalApiKey?.url) === 'http' ? 'ws://': 'wss://'}${cleanUrl(globalApiKey?.url)}` + return `${ + getProtocol(globalApiKey?.url) === "http" ? "ws://" : "wss://" + }${cleanUrl(globalApiKey?.url)}`; } else { return groupApiSocket; } }; -export const isMainWindow = true +export const isMainWindow = true; function App() { const [extState, setExtstate] = useState("not-authenticated"); - const [desktopViewMode, setDesktopViewMode] = useState('home') + const [desktopViewMode, setDesktopViewMode] = useState("home"); const [backupjson, setBackupjson] = useState(null); const [rawWallet, setRawWallet] = useState(null); @@ -286,9 +302,7 @@ function App() { const [walletToBeDownloaded, setWalletToBeDownloaded] = useState(null); const [walletToBeDownloadedPassword, setWalletToBeDownloadedPassword] = useState(""); - const [isMain, setIsMain] = useState( - true - ); + const [isMain, setIsMain] = useState(true); const isMainRef = useRef(false); const [authenticatePassword, setAuthenticatePassword] = useState(""); const [sendqortState, setSendqortState] = useState(null); @@ -304,11 +318,19 @@ function App() { const [txList, setTxList] = useState([]); const [memberGroups, setMemberGroups] = useState([]); const [isFocused, setIsFocused] = useState(true); - const [hasSettingsChanged, setHasSettingsChanged] = useRecoilState(hasSettingsChangedAtom) + const [hasSettingsChanged, setHasSettingsChanged] = useRecoilState( + hasSettingsChangedAtom + ); const holdRefExtState = useRef("not-authenticated"); const isFocusedRef = useRef(true); const { isShow, onCancel, onOk, show, message } = useModal(); - const { isShow: isShowUnsavedChanges, onCancel: onCancelUnsavedChanges, onOk: onOkUnsavedChanges, show: showUnsavedChanges, message: messageUnsavedChanges } = useModal(); + const { + isShow: isShowUnsavedChanges, + onCancel: onCancelUnsavedChanges, + onOk: onOkUnsavedChanges, + show: showUnsavedChanges, + message: messageUnsavedChanges, + } = useModal(); const { onCancel: onCancelQortalRequest, @@ -324,7 +346,7 @@ function App() { isShow: isShowQortalRequestExtension, message: messageQortalRequestExtension, } = useModal(); - + const [openRegisterName, setOpenRegisterName] = useState(false); const registerNamePopoverRef = useRef(null); const [isLoadingRegisterName, setIsLoadingRegisterName] = useState(false); @@ -339,34 +361,42 @@ function App() { const [rootHeight, setRootHeight] = useState("100%"); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const qortalRequestCheckbox1Ref = useRef(null); - useRetrieveDataLocalStorage() - useQortalGetSaveSettings(userInfo?.name) + useRetrieveDataLocalStorage(); + useQortalGetSaveSettings(userInfo?.name); const [fullScreen, setFullScreen] = useRecoilState(fullScreenAtom); const { toggleFullScreen } = useAppFullScreen(setFullScreen); - - - useEffect(() => { - // Attach a global event listener for double-click - const handleDoubleClick = () => { - toggleFullScreen(); - }; + // Attach a global event listener for double-click + const handleDoubleClick = () => { + toggleFullScreen(); + }; - // Add the event listener to the root HTML document - document.documentElement.addEventListener('dblclick', handleDoubleClick); + // Add the event listener to the root HTML document + document.documentElement.addEventListener("dblclick", handleDoubleClick); - // Clean up the event listener on unmount - return () => { - document.documentElement.removeEventListener('dblclick', handleDoubleClick); - }; + // Clean up the event listener on unmount + return () => { + document.documentElement.removeEventListener( + "dblclick", + handleDoubleClick + ); + }; }, [toggleFullScreen]); //resets for recoil - const resetAtomSortablePinnedAppsAtom = useResetRecoilState(sortablePinnedAppsAtom); - const resetAtomCanSaveSettingToQdnAtom = useResetRecoilState(canSaveSettingToQdnAtom); - const resetAtomSettingsQDNLastUpdatedAtom = useResetRecoilState(settingsQDNLastUpdatedAtom); - const resetAtomSettingsLocalLastUpdatedAtom = useResetRecoilState(settingsLocalLastUpdatedAtom); + const resetAtomSortablePinnedAppsAtom = useResetRecoilState( + sortablePinnedAppsAtom + ); + const resetAtomCanSaveSettingToQdnAtom = useResetRecoilState( + canSaveSettingToQdnAtom + ); + const resetAtomSettingsQDNLastUpdatedAtom = useResetRecoilState( + settingsQDNLastUpdatedAtom + ); + const resetAtomSettingsLocalLastUpdatedAtom = useResetRecoilState( + settingsLocalLastUpdatedAtom + ); const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom); const resetAllRecoil = () => { @@ -401,21 +431,24 @@ function App() { window.visualViewport?.removeEventListener("resize", resetHeight); }; }, []); - const handleSetGlobalApikey = (key)=> { + const handleSetGlobalApikey = (key) => { globalApiKey = key; - } + }; useEffect(() => { - window.sendMessage("getApiKey") - .then((response) => { - if (response) { - handleSetGlobalApikey(response); - setApiKey(response); - } - }) - .catch((error) => { - console.error("Failed to get API key:", error?.message || "An error occurred"); - }); - + window + .sendMessage("getApiKey") + .then((response) => { + if (response) { + handleSetGlobalApikey(response); + setApiKey(response); + } + }) + .catch((error) => { + console.error( + "Failed to get API key:", + error?.message || "An error occurred" + ); + }); }, []); useEffect(() => { if (extState) { @@ -427,8 +460,6 @@ function App() { isFocusedRef.current = isFocused; }, [isFocused]); - - // const checkIfUserHasLocalNode = useCallback(async () => { // try { // const url = `http://127.0.0.1:12391/admin/status`; @@ -528,37 +559,35 @@ function App() { }; }; - - const getBalanceFunc = () => { setQortBalanceLoading(true); - window.sendMessage("balance") - .then((response) => { - if (!response?.error && !isNaN(+response)) { - setBalance(response); - } - setQortBalanceLoading(false); - }) - .catch((error) => { - console.error("Failed to get balance:", error); - setQortBalanceLoading(false); - }); - + window + .sendMessage("balance") + .then((response) => { + if (!response?.error && !isNaN(+response)) { + setBalance(response); + } + setQortBalanceLoading(false); + }) + .catch((error) => { + console.error("Failed to get balance:", error); + setQortBalanceLoading(false); + }); }; const getLtcBalanceFunc = () => { setLtcBalanceLoading(true); - window.sendMessage("ltcBalance") - .then((response) => { - if (!response?.error && !isNaN(+response)) { - setLtcBalance(response); - } - setLtcBalanceLoading(false); - }) - .catch((error) => { - console.error("Failed to get LTC balance:", error); - setLtcBalanceLoading(false); - }); - + window + .sendMessage("ltcBalance") + .then((response) => { + if (!response?.error && !isNaN(+response)) { + setLtcBalance(response); + } + setLtcBalanceLoading(false); + }) + .catch((error) => { + console.error("Failed to get LTC balance:", error); + setLtcBalanceLoading(false); + }); }; const sendCoinFunc = () => { setSendPaymentError(""); @@ -576,11 +605,12 @@ function App() { return; } setIsLoading(true); - window.sendMessage("sendCoin", { - amount: Number(paymentAmount), - receiver: paymentTo.trim(), - password: paymentPassword, - }) + window + .sendMessage("sendCoin", { + amount: Number(paymentAmount), + receiver: paymentTo.trim(), + password: paymentPassword, + }) .then((response) => { if (response?.error) { setSendPaymentError(response.error); @@ -594,7 +624,6 @@ function App() { console.error("Failed to send coin:", error); setIsLoading(false); }); - }; const clearAllStates = () => { @@ -602,60 +631,63 @@ function App() { setRequestAuthentication(null); }; - const qortalRequestPermisson = async (message, sender, sendResponse) => { - if (message.action === "QORTAL_REQUEST_PERMISSION" && !isMainWindow) { + const qortalRequestPermissonFromExtension = async (message, event) => { + if (message.action === "QORTAL_REQUEST_PERMISSION") { try { - - await showQortalRequest(message?.payload); - if (qortalRequestCheckbox1Ref.current) { - - sendResponse({ - accepted: true, - checkbox1: qortalRequestCheckbox1Ref.current, - }); - return; - } - sendResponse({ accepted: true }); - } catch (error) { - sendResponse({ accepted: false }); - } finally { - window.close(); - } - } - }; - const qortalRequestPermissonFromExtension = async (message, sender, sendResponse) => { - if (message.action === "QORTAL_REQUEST_PERMISSION" && isMainWindow) { - try { - + console.log("QORTAL_REQUEST_PERMISSION2", event, message); await showQortalRequestExtension(message?.payload); - + console.log("event100", event); if (qortalRequestCheckbox1Ref.current) { - - sendResponse({ - accepted: true, - checkbox1: qortalRequestCheckbox1Ref.current, - }); + event.source.postMessage( + { + action: "QORTAL_REQUEST_PERMISSION_RESPONSE", + requestId: message?.requestId, + result: { + accepted: true, + checkbox1: qortalRequestCheckbox1Ref.current, + }, + }, + event.origin + ); return; } - sendResponse({ accepted: true }); + event.source.postMessage( + { + action: "QORTAL_REQUEST_PERMISSION_RESPONSE", + requestId: message?.requestId, + result: { + accepted: true, + }, + }, + event.origin + ); } catch (error) { - sendResponse({ accepted: false }); - } + event.source.postMessage( + { + action: "QORTAL_REQUEST_PERMISSION_RESPONSE", + requestId: message?.requestId, + result: { + accepted: false, + }, + }, + event.origin + ); + } } }; useEffect(() => { // Handler function for incoming messages const messageHandler = (event) => { + console.log("messageHandler", event); const message = event.data; - + if (message?.action === "CHECK_FOCUS") { - - event.source.postMessage({ action: "CHECK_FOCUS_RESPONSE", isFocused: isFocusedRef.current }, event.origin); - - } else if ( - message.action === "NOTIFICATION_OPEN_DIRECT" - ) { + event.source.postMessage( + { action: "CHECK_FOCUS_RESPONSE", isFocused: isFocusedRef.current }, + event.origin + ); + } else if (message.action === "NOTIFICATION_OPEN_DIRECT") { executeEvent("openDirectMessage", { from: message.payload.from, }); @@ -663,7 +695,7 @@ function App() { executeEvent("openGroupMessage", { from: message.payload.from, }); - } else if ( + } else if ( message.action === "NOTIFICATION_OPEN_ANNOUNCEMENT_GROUP" && isMainWindow ) { @@ -677,62 +709,67 @@ function App() { executeEvent("openThreadNewPost", { data: message.payload.data, }); + } else if ( + message.action === "QORTAL_REQUEST_PERMISSION" && + message?.isFromExtension + ) { + qortalRequestPermissonFromExtension(message, event); + } else if(message?.action === 'SHOW_SAVE_FILE_PICKER'){ + showSaveFilePicker(message?.payload) + + } else if(message?.action === 'getFileFromIndexedDB'){ + handleGetFileFromIndexedDB(event); } - - }; - + // Attach the event listener window.addEventListener("message", messageHandler); - + // Clean up the event listener on component unmount return () => { window.removeEventListener("message", messageHandler); }; }, []); - - //param = isDecline const confirmPayment = (isDecline: boolean) => { // REMOVED FOR MOBILE APP }; const confirmBuyOrder = (isDecline: boolean) => { - // REMOVED FOR MOBILE APP - + // REMOVED FOR MOBILE APP }; const responseToConnectionRequest = ( isOkay: boolean, hostname: string, interactionId: string ) => { - // REMOVED FOR MOBILE APP + // REMOVED FOR MOBILE APP }; - useEffect(() => { try { setIsLoading(true); - - window.sendMessage("getWalletInfo") - .then((response) => { - console.log('getwalll', response) - if (response && response?.walletInfo) { - setRawWallet(response?.walletInfo); - if ( - holdRefExtState.current === "web-app-request-payment" || - holdRefExtState.current === "web-app-request-connection" || - holdRefExtState.current === "web-app-request-buy-order" - ) - return; - setExtstate("authenticated"); - } - }) - .catch((error) => { - console.error("Failed to get wallet info:", error); - }); + window + .sendMessage("getWalletInfo") + .then((response) => { + console.log("getwalll", response); + if (response && response?.walletInfo) { + setRawWallet(response?.walletInfo); + if ( + holdRefExtState.current === "web-app-request-payment" || + holdRefExtState.current === "web-app-request-connection" || + holdRefExtState.current === "web-app-request-buy-order" + ) + return; + + setExtstate("authenticated"); + } + }) + .catch((error) => { + console.error("Failed to get wallet info:", error); + }); } catch (error) { } finally { setIsLoading(false); @@ -748,16 +785,17 @@ function App() { }, 10000); }); } - window.sendMessage("userInfo") - .then((response) => { - if (response && !response.error) { - setUserInfo(response); - } - }) - .catch((error) => { - console.error("Failed to get user info:", error); - }); - + window + .sendMessage("userInfo") + .then((response) => { + if (response && !response.error) { + setUserInfo(response); + } + }) + .catch((error) => { + console.error("Failed to get user info:", error); + }); + getBalanceFunc(); } catch (error) {} }, []); @@ -844,10 +882,11 @@ function App() { crypto.kdfThreads, () => {} ); - window.sendMessage("decryptWallet", { - password: walletToBeDownloadedPassword, - wallet, - }) + window + .sendMessage("decryptWallet", { + password: walletToBeDownloadedPassword, + wallet, + }) .then((response) => { if (response && !response.error) { setRawWallet(wallet); @@ -855,8 +894,9 @@ function App() { wallet, qortAddress: wallet.address0, }); - - window.sendMessage("userInfo") + + window + .sendMessage("userInfo") .then((response2) => { setIsLoading(false); if (response2 && !response2.error) { @@ -867,7 +907,7 @@ function App() { setIsLoading(false); console.error("Failed to get user info:", error); }); - + getBalanceFunc(); } else if (response?.error) { setIsLoading(false); @@ -878,7 +918,6 @@ function App() { setIsLoading(false); console.error("Failed to decrypt wallet:", error); }); - } catch (error: any) { setWalletToBeDownloadedError(error?.message); setIsLoading(false); @@ -887,20 +926,26 @@ function App() { const logoutFunc = async () => { try { - if(hasSettingsChanged){ - await showUnsavedChanges({message: 'Your settings have changed. If you logout you will lose your changes. Click on the save button in the header to keep your changed settings.'}) + if (hasSettingsChanged) { + await showUnsavedChanges({ + message: + "Your settings have changed. If you logout you will lose your changes. Click on the save button in the header to keep your changed settings.", + }); } - window.sendMessage("logout", {}) - .then((response) => { - if (response) { - resetAllStates(); - executeEvent("logout-event", {}); - } - }) - .catch((error) => { - console.error("Failed to log out:", error.message || "An error occurred"); - }); - + window + .sendMessage("logout", {}) + .then((response) => { + if (response) { + resetAllStates(); + executeEvent("logout-event", {}); + } + }) + .catch((error) => { + console.error( + "Failed to log out:", + error.message || "An error occurred" + ); + }); } catch (error) {} }; @@ -944,7 +989,7 @@ function App() { setHasLocalNode(false); setTxList([]); setMemberGroups([]); - resetAllRecoil() + resetAllRecoil(); }; function roundUpToDecimals(number, decimals = 8) { @@ -961,18 +1006,20 @@ function App() { res(); }, 250); }); - window.sendMessage("decryptWallet", { - password: authenticatePassword, - wallet: rawWallet, - }) + window + .sendMessage("decryptWallet", { + password: authenticatePassword, + wallet: rawWallet, + }) .then((response) => { - console.log('response2', response) + console.log("response2", response); if (response && !response.error) { setAuthenticatePassword(""); setExtstate("authenticated"); setWalletToBeDecryptedError(""); - - window.sendMessage("userInfo") + + window + .sendMessage("userInfo") .then((response) => { setIsLoading(false); if (response && !response.error) { @@ -983,10 +1030,11 @@ function App() { setIsLoading(false); console.error("Failed to get user info:", error); }); - + getBalanceFunc(); - - window.sendMessage("getWalletInfo") + + window + .sendMessage("getWalletInfo") .then((response) => { if (response && response.walletInfo) { setRawWallet(response.walletInfo); @@ -1004,7 +1052,6 @@ function App() { setIsLoading(false); console.error("Failed to decrypt wallet:", error); }); - } catch (error) { setWalletToBeDecryptedError("Unable to authenticate. Wrong password"); } @@ -1054,13 +1101,13 @@ function App() { const handleFocus = () => { setIsFocused(true); if (isMobile) { - window.sendMessage("clearAllNotifications", {}) - .catch((error) => { - console.error("Failed to clear notifications:", error.message || "An error occurred"); + window.sendMessage("clearAllNotifications", {}).catch((error) => { + console.error( + "Failed to clear notifications:", + error.message || "An error occurred" + ); }); - } - }; // Handler for when the window loses focus @@ -1077,11 +1124,12 @@ function App() { if (document.visibilityState === "visible") { setIsFocused(true); if (isMobile) { - window.sendMessage("clearAllNotifications", {}) - .catch((error) => { - console.error("Failed to clear notifications:", error.message || "An error occurred"); + window.sendMessage("clearAllNotifications", {}).catch((error) => { + console.error( + "Failed to clear notifications:", + error.message || "An error occurred" + ); }); - } } else { setIsFocused(false); @@ -1123,16 +1171,18 @@ function App() { }); setIsLoadingRegisterName(true); new Promise((res, rej) => { - window.sendMessage("registerName", { - name: registerNameValue, - }) + window + .sendMessage("registerName", { + name: registerNameValue, + }) .then((response) => { if (!response?.error) { res(response); setIsLoadingRegisterName(false); setInfoSnack({ type: "success", - message: "Successfully registered. It may take a couple of minutes for the changes to propagate", + message: + "Successfully registered. It may take a couple of minutes for the changes to propagate", }); setOpenRegisterName(false); setRegisterNameValue(""); @@ -1164,7 +1214,6 @@ function App() { setOpenSnack(true); rej(error); }); - }); } catch (error) { if (error?.message) { @@ -1355,8 +1404,8 @@ function App() { marginTop: "10px", textDecoration: "underline", }} - onClick={() => { - // TODO + onClick={async () => { + await Browser.open({ url: "https://www.qort.trade" }); }} > Get QORT at qort.trade @@ -1438,15 +1487,22 @@ function App() { - {extState === "not-authenticated" && ( - + )} {/* {extState !== "not-authenticated" && ( @@ -1487,7 +1543,7 @@ function App() { desktopViewMode={desktopViewMode} setDesktopViewMode={setDesktopViewMode} /> - {(!isMobile && desktopViewMode !== 'apps') && renderProfile()} + {!isMobile && desktopViewMode !== "apps" && renderProfile()} )} - + - + - - {'Fee: '}{messageQortalRequest?.fee}{' QORT'} + {"Fee: "} + {messageQortalRequest?.fee} + {" QORT"} - - + )} {messageQortalRequest?.checkbox1 && ( @@ -2490,7 +2547,7 @@ function App() { )} - {isShowUnsavedChanges && ( + {isShowUnsavedChanges && ( { - onCancelQortalRequestExtension() + onCancelQortalRequestExtension(); }} size={50} strokeWidth={5} > {({ remainingTime }) => {remainingTime}} - - - {messageQortalRequestExtension?.text1} - - - {messageQortalRequestExtension?.text2 && ( - <> - - - - {messageQortalRequestExtension?.text2} - - - - - )} - {messageQortalRequestExtension?.text3 && ( - <> - - - {messageQortalRequestExtension?.text3} - - - - - )} - - {messageQortalRequestExtension?.text4 && ( - {messageQortalRequestExtension?.text4} + {messageQortalRequestExtension?.text1} - )} + {messageQortalRequestExtension?.text2 && ( + <> + + + + {messageQortalRequestExtension?.text2} + + + + + )} + {messageQortalRequestExtension?.text3 && ( + <> + + + {messageQortalRequestExtension?.text3} + + + + + )} - {messageQortalRequestExtension?.html && ( -
- )} - - - - {messageQortalRequestExtension?.highlightedText} - - - {messageQortalRequestExtension?.fee && ( - <> - - - - {'Fee: '}{messageQortalRequestExtension?.fee}{' QORT'} - - + + {messageQortalRequestExtension?.text4} + + + )} - - )} - {messageQortalRequestExtension?.checkbox1 && ( + {messageQortalRequestExtension?.html && ( +
+ )} + + + + {messageQortalRequestExtension?.highlightedText} + + + {messageQortalRequestExtension?.fee && ( + <> + + + + {"Fee: "} + {messageQortalRequestExtension?.fee} + {" QORT"} + + + + )} + {messageQortalRequestExtension?.checkbox1 && ( + + { + qortalRequestCheckbox1Ref.current = e.target.checked; + }} + edge="start" + tabIndex={-1} + disableRipple + defaultChecked={ + messageQortalRequestExtension?.checkbox1?.value + } + sx={{ + "&.Mui-checked": { + color: "white", // Customize the color when checked + }, + "& .MuiSvgIcon-root": { + color: "white", + }, + }} + /> + + + {messageQortalRequestExtension?.checkbox1?.label} + + + )} + + - { - qortalRequestCheckbox1Ref.current = e.target.checked; - }} - edge="start" - tabIndex={-1} - disableRipple - defaultChecked={messageQortalRequestExtension?.checkbox1?.value} + - - onOkQortalRequestExtension("accepted")} > - {messageQortalRequestExtension?.checkbox1?.label} - + accept + + onCancelQortalRequestExtension()} + > + decline + - )} - - - - onOkQortalRequestExtension("accepted")} - > - accept - - onCancelQortalRequestExtension()} - > - decline - + {sendPaymentError} - {sendPaymentError} -
)} { diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx index 765848f..0375f35 100644 --- a/src/components/Apps/useQortalMessageListener.tsx +++ b/src/components/Apps/useQortalMessageListener.tsx @@ -3,6 +3,8 @@ import FileSaver from 'file-saver'; import { executeEvent } from '../../utils/events'; import { useSetRecoilState } from 'recoil'; import { navigationControllerAtom } from '../../atoms/global'; + + class Semaphore { constructor(count) { this.count = count @@ -58,7 +60,7 @@ const fileToBase64 = (file) => new Promise(async (resolve, reject) => { } }) -function openIndexedDB() { +export function openIndexedDB() { return new Promise((resolve, reject) => { const request = indexedDB.open("fileStorageDB", 1); @@ -79,58 +81,7 @@ function openIndexedDB() { }); } -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 () { - 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 UIQortalRequests = [ 'GET_USER_ACCOUNT', 'DECRYPT_DATA', 'SEND_COIN', 'GET_LIST_ITEMS', @@ -215,7 +166,7 @@ const UIQortalRequests = [ } } - const showSaveFilePicker = async (data) => { + export const showSaveFilePicker = async (data) => { let blob let fileName try { @@ -368,7 +319,7 @@ isDOMContentLoaded: false if (event?.data?.requestedHandler !== 'UI') return; const sendMessageToRuntime = (message, eventPort) => { - window.sendMessage(message.action, message, 60000, message.isFromExtension) + window.sendMessage(message.action, message.payload, 60000, message.isExtension) .then((response) => { if (response.error) { eventPort.postMessage({ @@ -402,6 +353,7 @@ isDOMContentLoaded: false ) { let data; try { + console.log('storeFilesInIndexedDB', structuredClone(event.data)) data = await storeFilesInIndexedDB(event.data); } catch (error) { console.error('Error storing files in IndexedDB:', error); @@ -457,7 +409,9 @@ isDOMContentLoaded: false name: event?.data?.payload?.name } }, '*' ); - } + } + + }; // Add the listener for messages coming from the frameWindow @@ -471,17 +425,7 @@ isDOMContentLoaded: false }, []); // Empty dependency array to run once when the component mounts - // TODO - // chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) { - // if(message.action === "SHOW_SAVE_FILE_PICKER"){ - // showSaveFilePicker(message?.data) - // } - - // else if (message.action === "getFileFromIndexedDB") { - // handleGetFileFromIndexedDB(message.fileId, sendResponse); - // return true; // Keep the message channel open for async response - // } - // }); + return {path, history, resetHistory, changeCurrentIndex} }; diff --git a/src/components/Chat/MessageDisplay.tsx b/src/components/Chat/MessageDisplay.tsx index 64a0cf6..67b5892 100644 --- a/src/components/Chat/MessageDisplay.tsx +++ b/src/components/Chat/MessageDisplay.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from 'react'; import DOMPurify from 'dompurify'; import './styles.css'; // Ensure this CSS file is imported +import { Browser } from '@capacitor/browser'; export const MessageDisplay = ({ htmlContent , isReply}) => { @@ -28,25 +29,15 @@ export const MessageDisplay = ({ htmlContent , isReply}) => { }); // Function to handle link clicks - const handleClick = (e) => { + const handleClick = async (e) => { e.preventDefault(); // Ensure we are targeting an element const target = e.target.closest('a'); if (target) { const href = target.getAttribute('href'); - // TODO - // if (chrome && chrome.tabs) { - // chrome.tabs.create({ url: href }, (tab) => { - // if (chrome.runtime.lastError) { - // console.error('Error opening tab:', chrome.runtime.lastError); - // } else { - // console.log('Tab opened successfully:', tab); - // } - // }); - // } else { - // console.error('chrome.tabs API is not available.'); - // } + await Browser.open({ url: href }); + } else { console.error('No tag found or href is null.'); } diff --git a/src/components/Mobile/MobileFooter.tsx b/src/components/Mobile/MobileFooter.tsx index 70304de..df91f32 100644 --- a/src/components/Mobile/MobileFooter.tsx +++ b/src/components/Mobile/MobileFooter.tsx @@ -9,6 +9,7 @@ import { Home, Groups, Message, ShowChart } from "@mui/icons-material"; import Box from "@mui/material/Box"; import BottomLogo from "../../assets/svgs/BottomLogo5.svg"; import LogoSelected from "../../assets/svgs/LogoSelected.svg"; +import { Browser } from '@capacitor/browser'; import { CustomSvg } from "../../common/CustomSvg"; import { WalletIcon } from "../../assets/Icons/WalletIcon"; @@ -183,10 +184,10 @@ export const MobileFooter = ({ }} /> { - // TODO - // chrome.tabs.create({ url: "https://www.qort.trade"}); + onClick={async () => { + await Browser.open({ url: 'https://www.qort.trade' }); }} + icon={ diff --git a/src/messaging/messagesToBackground.tsx b/src/messaging/messagesToBackground.tsx index 2f08433..2213d38 100644 --- a/src/messaging/messagesToBackground.tsx +++ b/src/messaging/messagesToBackground.tsx @@ -24,13 +24,13 @@ window.addEventListener("message", (event) => { } }); -export const sendMessageBackground = (action, data = {}, timeout = 60000, isFromExtension) => { +export const sendMessageBackground = (action, data = {}, timeout = 60000, isExtension) => { return new Promise((resolve, reject) => { const requestId = generateRequestId(); // Unique ID for each request callbackMap.set(requestId, { resolve, reject }); // Store both resolve and reject callbacks // Send the message with `backgroundMessage` type - window.postMessage({ type: "backgroundMessage", action, requestId, payload: data, isFromExtension }, "*"); + window.postMessage({ type: "backgroundMessage", action, requestId, payload: data, isExtension }, "*"); // Set up a timeout to automatically reject if no response is received const timeoutId = setTimeout(() => { diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts index c0b12af..fde249d 100644 --- a/src/qortalRequests.ts +++ b/src/qortalRequests.ts @@ -1,5 +1,5 @@ import { addForeignServer, addListItems, createPoll, decryptData, deleteListItems, deployAt, encryptData, getCrossChainServerInfo, getDaySummary, getForeignFee, getListItems, getServerConnectionHistory, getTxActivitySummary, getUserAccount, getUserWallet, getUserWalletInfo, getWalletBalance, joinGroup, publishMultipleQDNResources, publishQDNResource, removeForeignServer, saveFile, sendChatMessage, sendCoin, setCurrentForeignServer, updateForeignFee, voteOnPoll } from "./qortalRequests/get"; -import { storeData } from "./utils/chromeStorage"; +import { getData, storeData } from "./utils/chromeStorage"; @@ -28,7 +28,7 @@ function setLocalStorage(key, data) { qortalRequestPermissions[key] = value; // Save the updated object back to storage - await setLocalStorage({ qortalRequestPermissions }); + await setLocalStorage('qortalRequestPermissions', qortalRequestPermissions ); console.log('Permission set for', key); } catch (error) { @@ -53,7 +53,7 @@ function setLocalStorage(key, data) { // TODO: GET_FRIENDS_LIST // NOT SURE IF TO IMPLEMENT: LINK_TO_QDN_RESOURCE, QDN_RESOURCE_DISPLAYED, SET_TAB_NOTIFICATIONS - function setupMessageListener() { + function setupMessageListenerQortalRequest() { window.addEventListener("message", async (event) => { const request = event.data; @@ -87,6 +87,7 @@ function setLocalStorage(key, data) { case "ENCRYPT_DATA": { try { + console.log('ENCRYPTDATA', request) const res = await encryptData(request.payload, event.source); event.source.postMessage({ requestId: request.requestId, @@ -98,7 +99,7 @@ function setLocalStorage(key, data) { event.source.postMessage({ requestId: request.requestId, action: request.action, - error: error.message, + error: error?.message, type: "backgroundMessageResponse", }, event.origin); } @@ -368,6 +369,7 @@ function setLocalStorage(key, data) { case "GET_WALLET_BALANCE": { try { const res = await getWalletBalance(request.payload, false, isFromExtension); + console.log('ressss', res) event.source.postMessage({ requestId: request.requestId, action: request.action, @@ -612,5 +614,5 @@ function setLocalStorage(key, data) { } // Initialize the message listener - setupMessageListener(); + setupMessageListenerQortalRequest(); diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts index fa00e81..2509583 100644 --- a/src/qortalRequests/get.ts +++ b/src/qortalRequests/get.ts @@ -180,174 +180,101 @@ const _voteOnPoll = async ({pollName, optionIndex, optionName}, isFromExtension) } }; -function getFileFromContentScript(fileId, sender) { +// 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 || "Failed to retrieve file"); + } + } +}; + +window.addEventListener("message", handleFileMessage); + +function getFileFromContentScript(fileId) { + console.log('handleGetFileFromIndexedDB', fileId) return new Promise((resolve, reject) => { - chrome.tabs.sendMessage( - sender.tab.id, - { action: "getFileFromIndexedDB", fileId: fileId }, - (response) => { - if (response && response.result) { - resolve(response.result); - } else { - reject(response?.error || "Failed to retrieve file"); - } - } + const requestId = `getFile_${fileId}_${Date.now()}`; + console.log('handleGetFileFromIndexedDB', requestId) + + fileRequestResolvers.set(requestId, { resolve, reject }); // Store resolvers by requestId + console.log('handleGetFileFromIndexedDB', 'passed') + + // Send the request message + window.postMessage( + { action: "getFileFromIndexedDB", fileId, requestId }, + "*" ); - }); -} -function sendToSaveFilePicker(data, sender) { + console.log('handleGetFileFromIndexedDB', 'after', window.origin) - chrome.tabs.sendMessage(sender.tab.id, { - action: "SHOW_SAVE_FILE_PICKER", - data, - }); -} - -async function responseFromExtension() { - return new Promise((resolve) => { - - // Send message to the content script to check focus - chrome.runtime.sendMessage({ action: "QORTAL_REQUEST_PERMISSION", payloa }, (response) => { - - if (chrome.runtime.lastError) { - resolve(false); // Error occurred, assume not focused - } else { - resolve(response); // Resolve based on the response - } - }); - }); -} - -async function getUserPermission(payload: any, isFromExtension?: boolean) { - function waitForWindowReady(windowId) { - return new Promise((resolve) => { - const checkInterval = setInterval(() => { - chrome.windows.get(windowId, (win) => { - if (chrome.runtime.lastError) { - clearInterval(checkInterval); // Stop polling if there's an error - resolve(false); - } else if (win.state === "normal" || win.state === "maximized") { - clearInterval(checkInterval); // Window is ready - resolve(true); - } - }); - }, 100); // Check every 100ms - }); - } - - if(isFromExtension){ - - - return new Promise((resolve) => { - // Set a timeout for 1 second - const timeout = setTimeout(() => { - resolve(false); - }, 30000); - - // Send message to the content script to check focus - chrome.runtime.sendMessage( - { action: "QORTAL_REQUEST_PERMISSION", payload, isFromExtension }, - (response) => { - if (response === undefined) return; - clearTimeout(timeout); // Clear the timeout if we get a response - - if (chrome.runtime.lastError) { - resolve(false); // Error occurred, assume not focused - } else { - resolve(response); // Resolve based on the response - } - } - ); - }); - } - await new Promise((res) => { - const popupUrl = chrome.runtime.getURL("index.html?secondary=true"); - chrome.windows.getAll( - { populate: true, windowTypes: ["popup"] }, - (windows) => { - // Attempt to find an existing popup window that has a tab with the correct URL - const existingPopup = windows.find( - (w) => - w.tabs && - w.tabs.some((tab) => tab.url && tab.url.startsWith(popupUrl)) - ); - if (existingPopup) { - // If the popup exists but is minimized or not focused, focus it - chrome.windows.update(existingPopup.id, { - focused: true, - state: "normal", - }); - res(null); - } else { - // No existing popup found, create a new one - chrome.system.display.getInfo((displays) => { - // Assuming the primary display is the first one (adjust logic as needed) - const primaryDisplay = displays[0]; - const screenWidth = primaryDisplay.bounds.width; - const windowHeight = 500; // Your window height - const windowWidth = 400; // Your window width - - // Calculate left position for the window to appear on the right of the screen - const leftPosition = screenWidth - windowWidth; - - // Calculate top position for the window, adjust as desired - const topPosition = - (primaryDisplay.bounds.height - windowHeight) / 2; - - chrome.windows.create( - { - url: popupUrl, - type: "popup", - width: windowWidth, - height: windowHeight, - left: leftPosition, - top: 0, - }, - async (newWindow) => { - removeDuplicateWindow(popupUrl); - await waitForWindowReady(newWindow.id); - - res(null); - } - ); - }); - } - } - ); - }); - - await new Promise((res) => { + // Timeout to handle no response scenario setTimeout(() => { - chrome.runtime.sendMessage({ - action: "SET_COUNTDOWN", - payload: 30, - }); - res(true); - }, 1000); - }); - return new Promise((resolve) => { - // Set a timeout for 1 second - const timeout = setTimeout(() => { - resolve(false); - }, 30000); - - // Send message to the content script to check focus - chrome.runtime.sendMessage( - { action: "QORTAL_REQUEST_PERMISSION", payload }, - (response) => { - if (response === undefined) return; - clearTimeout(timeout); // Clear the timeout if we get a response - - if (chrome.runtime.lastError) { - resolve(false); // Error occurred, assume not focused - } else { - resolve(response); // Resolve based on the response - } + if (fileRequestResolvers.has(requestId)) { + fileRequestResolvers.get(requestId).reject("Request timed out"); + fileRequestResolvers.delete(requestId); // Clean up on timeout } - ); + }, 10000); // 10-second timeout }); } + +function sendToSaveFilePicker(data) { + window.postMessage({ + action: "SHOW_SAVE_FILE_PICKER", + payload: data, + }, "*"); +} + + +const responseResolvers = new Map(); + +const handleMessage = (event) => { + const { action, requestId, result } = event.data; + console.log("Received message:", event); + + // 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 + + // Send the request message + window.postMessage( + { action: "QORTAL_REQUEST_PERMISSION", payload, requestId, isFromExtension }, + "*" + ); + + // 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); + } + }, 30000); // 30-second timeout + }); +} + + export const getUserAccount = async () => { try { const wallet = await getSaveWallet(); @@ -366,7 +293,7 @@ export const encryptData = async (data, sender) => { let data64 = data.data64; let publicKeys = data.publicKeys || []; if (data.fileId) { - data64 = await getFileFromContentScript(data.fileId, sender); + data64 = await getFileFromContentScript(data.fileId); } if (!data64) { throw new Error("Please include data to encrypt"); @@ -633,7 +560,7 @@ export const publishQDNResource = async (data: any, sender, isFromExtension) => throw new Error("Only encrypted data can go into private services"); } if (data.fileId) { - data64 = await getFileFromContentScript(data.fileId, sender); + data64 = await getFileFromContentScript(data.fileId); } if (data.encrypt) { try { @@ -669,7 +596,7 @@ export const publishQDNResource = async (data: any, sender, isFromExtension) => const { accepted } = resPermission; if (accepted) { if (data.fileId && !data.encrypt) { - data64 = await getFileFromContentScript(data.fileId, sender); + data64 = await getFileFromContentScript(data.fileId); } try { const resPublish = await publishData({ @@ -853,7 +780,7 @@ export const publishMultipleQDNResources = async (data: any, sender, isFromExten continue; } if (resource.fileId) { - data64 = await getFileFromContentScript(resource.fileId, sender); + data64 = await getFileFromContentScript(resource.fileId); } if (data.encrypt) { try { @@ -1108,7 +1035,8 @@ export const sendChatMessage = async (data, isFromExtension) => { isEncrypted: 1, isText: 1, }); - const path = chrome.runtime.getURL("memory-pow.wasm.full"); + const path = `${import.meta.env.BASE_URL}memory-pow.wasm.full`; + const { nonce, chatBytesArray } = await computePow({ chatBytes: tx.chatBytes, @@ -1154,7 +1082,8 @@ export const sendChatMessage = async (data, isFromExtension) => { // if (!hasEnoughBalance) { // throw new Error("Must have at least 4 QORT to send a chat message"); // } - const path = chrome.runtime.getURL("memory-pow.wasm.full"); + const path = `${import.meta.env.BASE_URL}memory-pow.wasm.full`; + const { nonce, chatBytesArray } = await computePow({ chatBytes: tx.chatBytes, @@ -1277,8 +1206,7 @@ export const saveFile = async (data, sender, isFromExtension) => { blob, fileId, fileHandleOptions, - }, - sender + } ); return true; } else { diff --git a/src/utils/indexedDB.ts b/src/utils/indexedDB.ts new file mode 100644 index 0000000..ffcf38c --- /dev/null +++ b/src/utils/indexedDB.ts @@ -0,0 +1,83 @@ +export async function handleGetFileFromIndexedDB(event) { + try { + const { fileId, requestId } = event.data; + 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 () { + try { + + window.postMessage( + { action: "getFileFromIndexedDBResponse", requestId, result: base64String }, + "*" + ); + } catch (error) { + console.log('error', error) + } + }; + + deleteRequest.onerror = function () { + console.error(`Error deleting file with ID ${fileId} from IndexedDB`); + + }; + } catch (error) { + console.error("Error converting file to Base64:", error); + event.ports[0].postMessage({ + result: null, + error: "Failed to convert file to Base64", + }); + window.postMessage( + { action: "getFileFromIndexedDBResponse", requestId, result: null, + error: "Failed to convert file to Base64" + }, + "*" + ); + } + } else { + console.error(`File with ID ${fileId} not found in IndexedDB`); + + window.postMessage( + { action: "getFileFromIndexedDBResponse", requestId, result: null, + error: 'File not found in IndexedDB' + }, + "*" + ); + } + }; + + getRequest.onerror = function () { + console.error(`Error retrieving file with ID ${fileId} from IndexedDB`); + + event.source.postMessage( + { action: "getFileFromIndexedDBResponse", requestId, result: null, + error: 'Error retrieving file from IndexedDB' + }, + event.origin + ); + }; + } catch (error) { + const { requestId } = event.data; + console.error("Error opening IndexedDB:", error); + event.source.postMessage( + { action: "getFileFromIndexedDBResponse", requestId, result: null, + error: 'Error opening IndexedDB' + }, + event.origin + ); + } + } \ No newline at end of file