diff --git a/public/content-script.js b/public/content-script.js index e84cd7d..183b230 100644 --- a/public/content-script.js +++ b/public/content-script.js @@ -1,290 +1,584 @@ - - async function connection(hostname) { +async function connection(hostname) { const isConnected = await chrome.storage.local.get([hostname]); - let connected = false - if(isConnected && Object.keys(isConnected).length > 0 && isConnected[hostname]){ - connected = true + let connected = false; + if ( + isConnected && + Object.keys(isConnected).length > 0 && + isConnected[hostname] + ) { + connected = true; } - return connected + return connected; } // In your content script -document.addEventListener('qortalExtensionRequests', async (event) => { +document.addEventListener("qortalExtensionRequests", async (event) => { const { type, payload, requestId, timeout } = event.detail; // Capture the requestId - if (type === 'REQUEST_USER_INFO') { - const hostname = window.location.hostname - const res = await connection(hostname) + if (type === "REQUEST_USER_INFO") { + const hostname = window.location.hostname; + const res = await connection(hostname); - if(!res){ - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "USER_INFO", data: { - error: "Not authorized" - }, requestId } - })); - return + if (!res) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "USER_INFO", + data: { + error: "Not authorized", + }, + requestId, + }, + }) + ); + return; } chrome?.runtime?.sendMessage({ action: "userInfo" }, (response) => { if (response.error) { - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "USER_INFO", data: { - error: response.error - }, requestId } - })); + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "USER_INFO", + data: { + error: response.error, + }, + requestId, + }, + }) + ); } else { // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "USER_INFO", data: response, requestId } - })); + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "USER_INFO", data: response, requestId }, + }) + ); } }); - } else if (type === 'REQUEST_IS_INSTALLED') { + } else if (type === "REQUEST_IS_INSTALLED") { chrome?.runtime?.sendMessage({ action: "version" }, (response) => { if (response.error) { console.error("Error:", response.error); } else { // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "IS_INSTALLED", data: response, requestId } - })); + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "IS_INSTALLED", data: response, requestId }, + }) + ); } }); - } else if (type === 'REQUEST_CONNECTION') { - const hostname = window.location.hostname - chrome?.runtime?.sendMessage({ action: "connection", payload: { - hostname - }, timeout }, (response) => { - if (response.error) { - console.error("Error:", response.error); - } else { - // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "CONNECTION", data: response, requestId } - })); + } else if (type === "REQUEST_CONNECTION") { + const hostname = window.location.hostname; + chrome?.runtime?.sendMessage( + { + action: "connection", + payload: { + hostname, + }, + timeout, + }, + (response) => { + if (response.error) { + console.error("Error:", response.error); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "CONNECTION", data: response, requestId }, + }) + ); + } } - }); - } else if (type === 'REQUEST_OAUTH') { - const hostname = window.location.hostname - const res = await connection(hostname) - if(!res){ - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "OAUTH", data: { - error: "Not authorized" - }, requestId } - })); - return + ); + } else if (type === "REQUEST_OAUTH") { + const hostname = window.location.hostname; + const res = await connection(hostname); + if (!res) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "OAUTH", + data: { + error: "Not authorized", + }, + requestId, + }, + }) + ); + return; } - chrome?.runtime?.sendMessage({ action: "oauth", payload: { - nodeBaseUrl: payload.nodeBaseUrl, - senderAddress: payload.senderAddress, - senderPublicKey: payload.senderPublicKey, timestamp: payload.timestamp - }}, (response) => { - if (response.error) { - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "OAUTH", data: { - error: response.error - }, requestId } - })); - } else { - // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "OAUTH", data: response, requestId } - })); + chrome?.runtime?.sendMessage( + { + action: "oauth", + payload: { + nodeBaseUrl: payload.nodeBaseUrl, + senderAddress: payload.senderAddress, + senderPublicKey: payload.senderPublicKey, + timestamp: payload.timestamp, + }, + }, + (response) => { + if (response.error) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "OAUTH", + data: { + error: response.error, + }, + requestId, + }, + }) + ); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "OAUTH", data: response, requestId }, + }) + ); + } } - }); - } else if (type === 'REQUEST_BUY_ORDER') { - const hostname = window.location.hostname - const res = await connection(hostname) - if(!res){ - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "BUY_ORDER", data: { - error: "Not authorized" - }, requestId } - })); - return + ); + } else if (type === "REQUEST_BUY_ORDER") { + const hostname = window.location.hostname; + const res = await connection(hostname); + if (!res) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "BUY_ORDER", + data: { + error: "Not authorized", + }, + requestId, + }, + }) + ); + return; } - chrome?.runtime?.sendMessage({ action: "buyOrder", payload: { - qortalAtAddresses: payload.qortalAtAddresses, - hostname, - useLocal: payload?.useLocal - - }, timeout}, (response) => { - if (response.error) { - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "BUY_ORDER", data: { - error: response.error - }, requestId } - })); - } else { - // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "BUY_ORDER", data: response, requestId } - })); + chrome?.runtime?.sendMessage( + { + action: "buyOrder", + payload: { + qortalAtAddresses: payload.qortalAtAddresses, + hostname, + useLocal: payload?.useLocal, + }, + timeout, + }, + (response) => { + if (response.error) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "BUY_ORDER", + data: { + error: response.error, + }, + requestId, + }, + }) + ); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "BUY_ORDER", data: response, requestId }, + }) + ); + } } - }); - } else if(type === 'REQUEST_LTC_BALANCE'){ - - - const hostname = window.location.hostname - const res = await connection(hostname) - if(!res){ - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "USER_INFO", data: { - error: "Not authorized" - }, requestId } - })); - return + ); + } else if (type === "REQUEST_LTC_BALANCE") { + const hostname = window.location.hostname; + const res = await connection(hostname); + if (!res) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "USER_INFO", + data: { + error: "Not authorized", + }, + requestId, + }, + }) + ); + return; } - chrome?.runtime?.sendMessage({ action: "ltcBalance", payload: { - hostname - }, timeout }, (response) => { - - if (response.error) { - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "LTC_BALANCE", data: { - error: response.error - }, requestId } - })); - } else { - // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "LTC_BALANCE", data: response, requestId } - })); + chrome?.runtime?.sendMessage( + { + action: "ltcBalance", + payload: { + hostname, + }, + timeout, + }, + (response) => { + if (response.error) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "LTC_BALANCE", + data: { + error: response.error, + }, + requestId, + }, + }) + ); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "LTC_BALANCE", data: response, requestId }, + }) + ); + } } - }); - } else if(type === 'CHECK_IF_LOCAL'){ - - - const hostname = window.location.hostname - const res = await connection(hostname) - if(!res){ - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "USER_INFO", data: { - error: "Not authorized" - }, requestId } - })); - return + ); + } else if (type === "CHECK_IF_LOCAL") { + const hostname = window.location.hostname; + const res = await connection(hostname); + if (!res) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "USER_INFO", + data: { + error: "Not authorized", + }, + requestId, + }, + }) + ); + return; } - chrome?.runtime?.sendMessage({ action: "checkLocal", payload: { - hostname - }, timeout }, (response) => { - - if (response.error) { - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "CHECK_IF_LOCAL", data: { - error: response.error - }, requestId } - })); - } else { - // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "CHECK_IF_LOCAL", data: response, requestId } - })); + chrome?.runtime?.sendMessage( + { + action: "checkLocal", + payload: { + hostname, + }, + timeout, + }, + (response) => { + if (response.error) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "CHECK_IF_LOCAL", + data: { + error: response.error, + }, + requestId, + }, + }) + ); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "CHECK_IF_LOCAL", data: response, requestId }, + }) + ); + } } - }); - } else if (type === 'REQUEST_AUTHENTICATION') { - const hostname = window.location.hostname - const res = await connection(hostname) - if(!res){ - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "USER_INFO", data: { - error: "Not authorized" - }, requestId } - })); - return + ); + } else if (type === "REQUEST_AUTHENTICATION") { + const hostname = window.location.hostname; + const res = await connection(hostname); + if (!res) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "USER_INFO", + data: { + error: "Not authorized", + }, + requestId, + }, + }) + ); + return; } - chrome?.runtime?.sendMessage({ action: "authentication", payload: { - hostname - }, timeout }, (response) => { - if (response.error) { - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "AUTHENTICATION", data: { - error: response.error - }, requestId } - })); - } else { - // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "AUTHENTICATION", data: response, requestId } - })); + chrome?.runtime?.sendMessage( + { + action: "authentication", + payload: { + hostname, + }, + timeout, + }, + (response) => { + if (response.error) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "AUTHENTICATION", + data: { + error: response.error, + }, + requestId, + }, + }) + ); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "AUTHENTICATION", data: response, requestId }, + }) + ); + } } - }); - } else if (type === 'REQUEST_SEND_QORT') { - const hostname = window.location.hostname - const res = await connection(hostname) - if(!res){ - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "USER_INFO", data: { - error: "Not authorized" - }, requestId } - })); - return + ); + } else if (type === "REQUEST_SEND_QORT") { + const hostname = window.location.hostname; + const res = await connection(hostname); + if (!res) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "USER_INFO", + data: { + error: "Not authorized", + }, + requestId, + }, + }) + ); + return; } - chrome?.runtime?.sendMessage({ action: "sendQort", payload: { - hostname, - amount: payload.amount, - description: payload.description, - address: payload.address - }, timeout }, (response) => { - if (response.error) { - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "SEND_QORT", data: { - error: response.error - }, requestId } - })); - } else { - // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "SEND_QORT", data: response, requestId } - })); + chrome?.runtime?.sendMessage( + { + action: "sendQort", + payload: { + hostname, + amount: payload.amount, + description: payload.description, + address: payload.address, + }, + timeout, + }, + (response) => { + if (response.error) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "SEND_QORT", + data: { + error: response.error, + }, + requestId, + }, + }) + ); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "SEND_QORT", data: response, requestId }, + }) + ); + } } - }); - } else if (type === 'REQUEST_CLOSE_POPUP') { - const hostname = window.location.hostname - const res = await connection(hostname) - if(!res){ - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "USER_INFO", data: { - error: "Not authorized" - }, requestId } - })); - return + ); + } else if (type === "REQUEST_CLOSE_POPUP") { + const hostname = window.location.hostname; + const res = await connection(hostname); + if (!res) { + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "USER_INFO", + data: { + error: "Not authorized", + }, + requestId, + }, + }) + ); + return; } chrome?.runtime?.sendMessage({ action: "closePopup" }, (response) => { if (response.error) { - - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "CLOSE_POPUP", data: { - error: response.error - }, requestId } - })); + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { + type: "CLOSE_POPUP", + data: { + error: response.error, + }, + requestId, + }, + }) + ); } else { // Include the requestId in the detail when dispatching the response - document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { - detail: { type: "CLOSE_POPUP", data: true, requestId } - })); + document.dispatchEvent( + new CustomEvent("qortalExtensionResponses", { + detail: { type: "CLOSE_POPUP", data: true, requestId }, + }) + ); } }); } // Handle other request types as needed... }); - -chrome.runtime?.onMessage.addListener(function(message, sender, sendResponse) { +chrome.runtime?.onMessage.addListener(function (message, sender, sendResponse) { if (message.type === "LOGOUT") { - // Notify the web page - window.postMessage({ - type: "LOGOUT", - from: 'qortal' - }, "*"); + // Notify the web page + window.postMessage( + { + type: "LOGOUT", + from: "qortal", + }, + "*" + ); } else if (message.type === "RESPONSE_FOR_TRADES") { // Notify the web page - window.postMessage({ + window.postMessage( + { type: "RESPONSE_FOR_TRADES", - from: 'qortal', - payload: message.message - }, "*"); -} + from: "qortal", + payload: message.message, + }, + "*" + ); + } }); +const lastActionTimestamps = {}; + +// Function to debounce actions based on message.action +function debounceAction(action, wait = 500) { + const currentTime = Date.now(); + + // Check if this action has been recently triggered + if (lastActionTimestamps[action] && currentTime - lastActionTimestamps[action] < wait) { + // Ignore this action if it occurred within the debounce time window + return false; + } + + // Update the last timestamp for this action + lastActionTimestamps[action] = currentTime; + return true; +} + +if (!window.hasAddedQortalListener) { + console.log("Listener added"); + window.hasAddedQortalListener = true; + //qortalRequests + const listener = (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; + // // Check if this action should be processed (debounced) + // if (!debounceAction(event.data.action)) { + // console.log(`Debounced action: ${event.data.action}`); + // return; + // } + if(event?.data?.requestedHandler !== 'UI') return + if (event.data.action === "GET_USER_ACCOUNT") { + chrome?.runtime?.sendMessage( + { action: "GET_USER_ACCOUNT", type: "qortalRequest" }, + (response) => { + if (response.error) { + event.ports[0].postMessage({ + result: null, + error: response.error, + }); + } else { + event.ports[0].postMessage({ + result: response, + error: null, + }); + } + } + ); + } else if (event.data.action === "SEND_COIN") { + chrome?.runtime?.sendMessage( + { action: "SEND_COIN", type: "qortalRequest", payload: event.data }, + (response) => { + if (response.error) { + event.ports[0].postMessage({ + result: null, + error: response.error, + }); + } else { + event.ports[0].postMessage({ + result: response, + error: null, + }); + } + } + ); + } else if (event.data.action === "ENCRYPT_DATA") { + chrome?.runtime?.sendMessage( + { action: "ENCRYPT_DATA", type: "qortalRequest", payload: event.data }, + (response) => { + if (response.error) { + event.ports[0].postMessage({ + result: null, + error: response.error, + }); + } else { + event.ports[0].postMessage({ + result: response, + error: null, + }); + } + } + ); + } else if (event.data.action === "DECRYPT_DATA") { + chrome?.runtime?.sendMessage( + { action: "DECRYPT_DATA", type: "qortalRequest", payload: event.data }, + (response) => { + if (response.error) { + event.ports[0].postMessage({ + result: null, + error: response.error, + }); + } else { + event.ports[0].postMessage({ + result: response, + error: null, + }); + } + } + ); + } else if (event.data.action === "GET_LIST_ITEMS") { + console.log("message", event); + + chrome?.runtime?.sendMessage( + { + action: "GET_LIST_ITEMS", + 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, + }); + } + } + ); + } + }; + window.addEventListener("message", listener); +} diff --git a/src/App.tsx b/src/App.tsx index ac78985..a119d20 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -304,6 +304,8 @@ function App() { 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 [openRegisterName, setOpenRegisterName] = useState(false); const registerNamePopoverRef = useRef(null); const [isLoadingRegisterName, setIsLoadingRegisterName] = useState(false); @@ -561,55 +563,49 @@ function App() { setRequestAuthentication(null); }; + const qortalRequestPermisson = async (message, sender, sendResponse)=> { + if ( + message.action === "QORTAL_REQUEST_PERMISSION" && + !isMainWindow + ) { + try { + await showQortalRequest(message?.payload) + sendResponse(true) + } catch (error) { + sendResponse(false) + } finally { + window.close(); + } + } + } + useEffect(() => { // Listen for messages from the background script - chrome.runtime?.onMessage.addListener((message, sender, sendResponse) => { - // Check if the message is to update the state - if ( - message.action === "UPDATE_STATE_CONFIRM_SEND_QORT" && - !isMainWindow - ) { - // Update the component state with the received 'sendqort' state + const messageListener = (message, sender, sendResponse) => { + // Handle various actions + if (message.action === "UPDATE_STATE_CONFIRM_SEND_QORT" && !isMainWindow) { setSendqortState(message.payload); setExtstate("web-app-request-payment"); } else if (message.action === "closePopup" && !isMainWindow) { - // Update the component state with the received 'sendqort' state window.close(); - } else if ( - message.action === "UPDATE_STATE_REQUEST_CONNECTION" && - !isMainWindow - ) { - - // Update the component state with the received 'sendqort' state + } 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 - ) { - // Update the component state with the received 'sendqort' state + } 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 - ) { - // Update the component state with the received 'sendqort' state + } else if (message.action === "UPDATE_STATE_REQUEST_AUTHENTICATION" && !isMainWindow) { setRequestAuthentication(message.payload); setExtstate("web-app-request-authentication"); } else if (message.action === "SET_COUNTDOWN" && !isMainWindow) { setCountdown(message.payload); } else if (message.action === "INITIATE_MAIN") { - // Update the component state with the received 'sendqort' state setIsMain(true); isMainRef.current = true; } else if (message.action === "CHECK_FOCUS" && isMainWindow) { - - sendResponse(isFocusedRef.current); - } 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, }); @@ -617,22 +613,34 @@ 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); + 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); + }; }, []); @@ -1800,6 +1808,68 @@ function App() { )} + +{isShowQortalRequest && !isMainWindow && ( + <> + + + + {messageQortalRequest?.text1} + + + + {messageQortalRequest?.text2} + + + + {messageQortalRequest?.text3} + + + + onOkQortalRequest("accepted")} + > + accept + + onCancelQortalRequest()} + > + decline + + + {sendPaymentError} + + )} {extState === "web-app-request-buy-order" && !isMainWindow && ( <> @@ -1890,6 +1960,7 @@ function App() { {sendPaymentError} )} + {extState === "web-app-request-payment" && !isMainWindow && ( <> diff --git a/src/background.ts b/src/background.ts index adccae7..e0058d0 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,5 +1,7 @@ // @ts-nocheck // import { encryptAndPublishSymmetricKeyGroupChat } from "./backgroundFunctions/encryption"; + +import './qortalRequests' import { constant, isArray } from "lodash"; import { decryptGroupEncryption, @@ -144,7 +146,7 @@ export const createEndpointSocket = async (endpoint) => { } }; -export const createEndpoint = async (endpoint, customApi) => { +export const createEndpoint = async (endpoint, customApi?: string) => { if (customApi) { return `${customApi}${endpoint}`; } @@ -949,7 +951,7 @@ async function getAddressInfo(address) { return data; } -async function getKeyPair() { +export async function getKeyPair() { const res = await chrome.storage.local.get(["keyPair"]); if (res?.keyPair) { return res.keyPair; @@ -958,7 +960,7 @@ async function getKeyPair() { } } -async function getSaveWallet() { +export async function getSaveWallet() { const res = await chrome.storage.local.get(["walletInfo"]); if (res?.walletInfo) { return res.walletInfo; @@ -2498,7 +2500,7 @@ async function listenForChatMessageForBuyOrder({ } } -function removeDuplicateWindow(popupUrl) { +export function removeDuplicateWindow(popupUrl) { chrome.windows.getAll( { populate: true, windowTypes: ["popup"] }, (windows) => { @@ -2800,6 +2802,7 @@ async function getChatHeadsDirect() { } chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => { if (request) { + switch (request.action) { case "version": // Example: respond with the version diff --git a/src/qdn/encryption/group-encryption.ts b/src/qdn/encryption/group-encryption.ts index 1905d4d..24d53df 100644 --- a/src/qdn/encryption/group-encryption.ts +++ b/src/qdn/encryption/group-encryption.ts @@ -331,4 +331,43 @@ export function decryptGroupData(data64EncryptedData: string, privateKey: string } } throw new Error("Unable to decrypt data") +} + +export function uint8ArrayStartsWith(uint8Array, string) { + const stringEncoder = new TextEncoder() + const stringUint8Array = stringEncoder.encode(string) + if (uint8Array.length < stringUint8Array.length) { + return false + } + for (let i = 0; i < stringUint8Array.length; i++) { + if (uint8Array[i] !== stringUint8Array[i]) { + return false + } + } + return true +} + +export function decryptDeprecatedSingle(uint8Array, publicKey, privateKey) { + const combinedData = uint8Array + const str = "qortalEncryptedData" + const strEncoder = new TextEncoder() + const strUint8Array = strEncoder.encode(str) + const strData = combinedData.slice(0, strUint8Array.length) + const nonce = combinedData.slice(strUint8Array.length, strUint8Array.length + 24) + const _encryptedData = combinedData.slice(strUint8Array.length + 24) + + const _publicKey = window.parent.Base58.decode(publicKey) + if (!privateKey || !_publicKey) { + throw new Error("Unable to retrieve keys") + } + const convertedPrivateKey = ed2curve.convertSecretKey(privateKey) + const convertedPublicKey = ed2curve.convertPublicKey(_publicKey) + const sharedSecret = new Uint8Array(32) + nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) + const _chatEncryptionSeed = new window.parent.Sha256().process(sharedSecret).finish().result + const _decryptedData = nacl.secretbox.open(_encryptedData, nonce, _chatEncryptionSeed) + if (!_decryptedData) { + throw new Error("Unable to decrypt") + } + return uint8ArrayToBase64(_decryptedData) } \ No newline at end of file diff --git a/src/qortalRequests.ts b/src/qortalRequests.ts new file mode 100644 index 0000000..388ccf9 --- /dev/null +++ b/src/qortalRequests.ts @@ -0,0 +1,85 @@ +import { decryptData, encryptData, getListItems, getUserAccount, sendCoin } from "./qortalRequests/get"; + +chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => { + if (request) { + switch (request.action) { + case "GET_USER_ACCOUNT": { + getUserAccount() + .then((res) => { + sendResponse(res); + }) + .catch((error) => { + sendResponse({ error: "Unable to get user account" }); + }); + + break; + } + case "ENCRYPT_DATA": { + const data = request.payload; + + encryptData(data) + .then((res) => { + sendResponse(res); + }) + .catch((error) => { + sendResponse({ error: error.message }); + }); + + break; + } + case "DECRYPT_DATA": { + const data = request.payload; + + decryptData(data) + .then((res) => { + sendResponse(res); + }) + .catch((error) => { + sendResponse({ error: error.message }); + }); + + break; + } + case "GET_LIST_ITEMS": { + const data = request.payload; + + getListItems(data) + .then((res) => { + sendResponse(res); + }) + .catch((error) => { + sendResponse({ error: error.message }); + }); + + break; + } + case "SEND_COIN": { + const data = request.payload; + const requiredFields = ["coin", "destinationAddress", "amount"]; + const missingFields: string[] = []; + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(", "); + const errorMsg = `Missing fields: ${missingFieldsString}`; + sendResponse({ error: errorMsg }); + break; + } + // Example: respond with the version + sendCoin() + .then((res) => { + sendResponse(res); + }) + .catch((error) => { + sendResponse({ error: "Unable to get user account" }); + }); + + break; + } + } + } + return true; +}); diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts new file mode 100644 index 0000000..2450a64 --- /dev/null +++ b/src/qortalRequests/get.ts @@ -0,0 +1,281 @@ +import { createEndpoint, getKeyPair, getSaveWallet, removeDuplicateWindow } from "../background"; +import Base58 from "../deps/Base58"; +import { + base64ToUint8Array, + decryptDeprecatedSingle, + decryptGroupData, + encryptDataGroup, + uint8ArrayStartsWith, + uint8ArrayToBase64, +} from "../qdn/encryption/group-encryption"; +import { fileToBase64 } from "../utils/fileReading"; + +async function getUserPermission(payload: any) { + + + 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 + }); + } + + await new Promise((res)=> { + const popupUrl = chrome.runtime.getURL( + "index.html?secondary=true" + ); + console.log('popupUrl', popupUrl) + chrome.windows.getAll( + { populate: true, windowTypes: ["popup"] }, + (windows) => { + console.log('windows', 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) => { + await waitForWindowReady(newWindow.id); + removeDuplicateWindow(popupUrl); + res(null) + } + ); + + }); + } + + + + + } + ); + }) + + await new Promise((res)=> { + setTimeout(() => { + chrome.runtime.sendMessage({ + action: "SET_COUNTDOWN", + payload: 15, + }); + res(true) + }, 450); + }) + return new Promise((resolve) => { + // Set a timeout for 1 second + const timeout = setTimeout(() => { + resolve(false); // No response within 10 second, assume not focused + }, 15000); + + // Send message to the content script to check focus + console.log('send msg') + chrome.runtime.sendMessage({ action: "QORTAL_REQUEST_PERMISSION", payload }, (response) => { + console.log('permission response', 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 + } + }); + }); + } + +export const getUserAccount = async () => { + try { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const publicKey = wallet.publicKey; + return { + address, + publicKey, + }; + } catch (error) { + throw new Error("Unable to fetch user account"); + } +}; + +export const encryptData = async (data) => { + let data64 = data.data64; + let publicKeys = data.publicKeys || []; + if (data.file) { + data64 = await fileToBase64(data.file); + } + if (!data64) { + throw new Error("Please include data to encrypt"); + } + + const resKeyPair = await getKeyPair(); + const parsedData = JSON.parse(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("Unable to encrypt"); + } +}; +export const decryptData = async (data) => { + const { encryptedData, publicKey } = data; + + + if (!encryptedData) { + throw new Error(`Missing fields: encryptedData`); + } + const resKeyPair = await getKeyPair(); + const parsedData = JSON.parse(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 = decryptGroupData( + encryptedData, + parsedData.privateKey + ); + const decryptedDataToBase64 = uint8ArrayToBase64(decryptedData); + return decryptedDataToBase64; + } + throw new Error("Unable to decrypt"); +}; + + + +export const getListItems = async (data) => { + 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 = `Missing fields: ${missingFieldsString}` + throw new Error(errorMsg) + } + let skip = false + // if (window.parent.reduxStore.getState().app.qAPPAutoLists) { + // skip = true + // } + let resPermission + if (!skip) { + // res1 = await showModalAndWait( + // actions.GET_LIST_ITEMS, + // { + // list_name: data.list_name + // } + // ) + + resPermission = await getUserPermission({ + text1: 'Do you give this application permission to', + text2: 'Access the list', + text3: data.list_name + }) + + } + console.log('resPermission', resPermission) + if (resPermission || skip) { + try { + + const url = await createEndpoint(`/lists/${data.list_name}`); + console.log('url', url) + const response = await fetch(url); + console.log('response', response) + if (!response.ok) throw new Error("Failed to fetch"); + + const list = await response.json(); + return list + + } catch (error) { + throw new Error("Error in retrieving list") + } + } else { + const data = {} + throw new Error("User declined to share list") + } + }; + +export const sendCoin = async () => { + try { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const publicKey = wallet.publicKey; + return { + address, + publicKey, + }; + } catch (error) { + console.error(error); + } +};