started infrustructure

This commit is contained in:
2024-10-14 18:59:42 +03:00
parent 0087985f2d
commit b8d5481633
6 changed files with 1054 additions and 281 deletions

View File

@@ -304,6 +304,8 @@ function App() {
const holdRefExtState = useRef<extStates>("not-authenticated");
const isFocusedRef = useRef<boolean>(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() {
</CustomButton>
</Box>
)}
{isShowQortalRequest && !isMainWindow && (
<>
<Spacer height="100px" />
<TextP
sx={{
textAlign: "center",
lineHeight: "15px",
}}
>
{messageQortalRequest?.text1}
</TextP>
<Spacer height="10px" />
<TextP
sx={{
textAlign: "center",
lineHeight: "15px",
fontSize: "10px",
}}
>
{messageQortalRequest?.text2}
</TextP>
<Spacer height="15px" />
<TextP
sx={{
textAlign: "center",
lineHeight: 1.2,
fontSize: "16px",
fontWeight: 700,
}}
>
{messageQortalRequest?.text3}
</TextP>
<Spacer height="29px" />
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "14px",
}}
>
<CustomButton
sx={{
minWidth: "102px",
}}
onClick={() => onOkQortalRequest("accepted")}
>
accept
</CustomButton>
<CustomButton
sx={{
minWidth: "102px",
}}
onClick={() => onCancelQortalRequest()}
>
decline
</CustomButton>
</Box>
<ErrorText>{sendPaymentError}</ErrorText>
</>
)}
{extState === "web-app-request-buy-order" && !isMainWindow && (
<>
<Spacer height="100px" />
@@ -1890,6 +1960,7 @@ function App() {
<ErrorText>{sendPaymentError}</ErrorText>
</>
)}
{extState === "web-app-request-payment" && !isMainWindow && (
<>
<Spacer height="100px" />

View File

@@ -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

View File

@@ -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)
}

85
src/qortalRequests.ts Normal file
View File

@@ -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;
});

281
src/qortalRequests/get.ts Normal file
View File

@@ -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);
}
};