// @ts-nocheck // import { encryptAndPublishSymmetricKeyGroupChat } from "./backgroundFunctions/encryption"; import { decryptGroupEncryption, encryptAndPublishSymmetricKeyGroupChat, publishGroupEncryptedResource, uint8ArrayToObject, } from "./backgroundFunctions/encryption"; import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from "./constants/codes"; import { QORT_DECIMALS } from "./constants/constants"; import Base58 from "./deps/Base58"; import { base64ToUint8Array, decryptSingle, encryptSingle, objectToBase64, } from "./qdn/encryption/group-encryption"; import { reusableGet } from "./qdn/publish/pubish"; import { signChat } from "./transactions/signChat"; import { createTransaction } from "./transactions/transactions"; import { decryptChatMessage } from "./utils/decryptChatMessage"; import { decryptStoredWallet } from "./utils/decryptWallet"; import PhraseWallet from "./utils/generateWallet/phrase-wallet"; import { RequestQueueWithPromise } from "./utils/queue/queue"; import { validateAddress } from "./utils/validateAddress"; import { Sha256 } from "asmcrypto.js"; let lastGroupNotification export const groupApi = "https://ext-node.qortal.link" export const groupApiSocket = "wss://ext-node.qortal.link" export const groupApiLocal = "http://127.0.0.1:12391" export const groupApiSocketLocal = "ws://127.0.0.1:12391" const timeDifferenceForNotificationChatsBackground = 600000 const requestQueueAnnouncements = new RequestQueueWithPromise(1) let isMobile = false const isMobileDevice = () => { const userAgent = navigator.userAgent || navigator.vendor || window.opera; if (/android/i.test(userAgent)) { return true; // Android device } if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { return true; // iOS device } return false; }; if (isMobileDevice()) { isMobile = true console.log("Running on a mobile device"); } else { console.log("Running on a desktop"); } const allQueues = { requestQueueAnnouncements: requestQueueAnnouncements, } const controlAllQueues = (action) => { Object.keys(allQueues).forEach((key) => { const val = allQueues[key]; try { if (typeof val[action] === 'function') { val[action](); } } catch (error) { console.error(error); } }); }; export const clearAllQueues = () => { Object.keys(allQueues).forEach((key) => { const val = allQueues[key]; try { val.clear(); } catch (error) { console.error(error); } }); } const pauseAllQueues = () => controlAllQueues('pause'); const resumeAllQueues = () => controlAllQueues('resume'); const checkDifference = (createdTimestamp)=> { return (Date.now() - createdTimestamp) < timeDifferenceForNotificationChatsBackground } const getApiKeyFromStorage = async () => { return new Promise((resolve, reject) => { chrome.storage.local.get('apiKey', (result) => { if (chrome.runtime.lastError) { return reject(chrome.runtime.lastError); } resolve(result.apiKey || null); // Return null if apiKey isn't found }); }); }; export const getBaseApi = async (customApi?: string) => { if (customApi) { return customApi; } const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously if (apiKey) { return groupApiLocal; } else { return groupApi; } }; export const createEndpointSocket = async (endpoint) => { const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously if (apiKey) { return `${groupApiSocketLocal}${endpoint}`; } else { return `${groupApiSocket}${endpoint}`; } }; export const createEndpoint = async (endpoint, customApi) => { if (customApi) { return `${customApi}${endpoint}`; } const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously if (apiKey) { // Check if the endpoint already contains a query string const separator = endpoint.includes('?') ? '&' : '?'; return `${groupApiLocal}${endpoint}${separator}apiKey=${apiKey}`; } else { return `${groupApi}${endpoint}`; } }; export const walletVersion = 2; // List of your API endpoints const apiEndpoints = [ "https://api.qortal.org", "https://api2.qortal.org", "https://appnode.qortal.org", "https://apinode.qortalnodes.live", "https://apinode1.qortalnodes.live", "https://apinode2.qortalnodes.live", "https://apinode3.qortalnodes.live", "https://apinode4.qortalnodes.live", ]; const buyTradeNodeBaseUrl = "https://appnode.qortal.org"; const proxyAccountAddress = "QXPejUe5Za1KD3zCMViWCX35AreMQ9H7ku"; const proxyAccountPublicKey = "5hP6stDWybojoDw5t8z9D51nV945oMPX7qBd29rhX1G7"; const pendingResponses = new Map(); let groups = null let socket let timeoutId; let groupSocketTimeout; let socketTimeout: any; let interval let intervalThreads // Function to check each API endpoint export async function findUsableApi() { for (const endpoint of apiEndpoints) { try { const response = await fetch(`${endpoint}/admin/status`); if (!response.ok) throw new Error("Failed to fetch"); const data = await response.json(); if (data.isSynchronizing === false && data.syncPercent === 100) { console.log(`Usable API found: ${endpoint}`); return endpoint; } else { console.log(`API not ready: ${endpoint}`); } } catch (error) { console.error(`Error checking API ${endpoint}:`, error); } } throw new Error("No usable API found"); } async function checkWebviewFocus() { return new Promise((resolve) => { // Set a timeout for 1 second const timeout = setTimeout(() => { resolve(false); // No response within 1 second, assume not focused }, 1000); // Send message to the content script to check focus chrome.runtime.sendMessage({ action: 'CHECK_FOCUS' }, (response) => { 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 } }); }); } function playNotificationSound() { chrome.runtime.sendMessage({ action: 'PLAY_NOTIFICATION_SOUND' }); } const handleNotificationDirect = async (directs)=> { let isFocused const wallet = await getSaveWallet(); const address = wallet.address0; const dataDirects = directs.filter((direct)=> direct?.sender !== address) try { isFocused = await checkWebviewFocus() if(isFocused){ throw new Error('isFocused') } const newActiveChats= dataDirects const oldActiveChats = await getChatHeadsDirect() if(newActiveChats?.length === 0) return let newestLatestTimestamp let oldestLatestTimestamp // Find the latest timestamp from newActiveChats newActiveChats?.forEach(newChat => { if (!newestLatestTimestamp || newChat?.timestamp > newestLatestTimestamp?.timestamp) { newestLatestTimestamp = newChat; } }); // Find the latest timestamp from oldActiveChats oldActiveChats?.forEach(oldChat => { if (!oldestLatestTimestamp || oldChat?.timestamp > oldestLatestTimestamp?.timestamp) { oldestLatestTimestamp = oldChat; } }); if(checkDifference(newestLatestTimestamp.timestamp) && !oldestLatestTimestamp || (newestLatestTimestamp && newestLatestTimestamp?.timestamp > oldestLatestTimestamp?.timestamp)){ const notificationId = 'chat_notification_' + Date.now() + '_type=direct' + `_from=${newestLatestTimestamp.address}`; chrome.notifications.create(notificationId, { type: 'basic', iconUrl: 'qort.png', // Add an appropriate icon for chat notifications title: `New Direct message! ${newestLatestTimestamp?.name && `from ${newestLatestTimestamp.name}`}`, message: 'You have received a new direct message', priority: 2, // Use the maximum priority to ensure it's noticeable // buttons: [ // { title: 'Go to group' } // ] }); if(!isMobile){ setTimeout(() => { chrome.notifications.clear(notificationId); }, 7000); } // chrome.runtime.sendMessage( // { // action: "notification", // payload: { // }, // } // ) // audio.play(); playNotificationSound() } } catch (error) { if(!isFocused){ chrome.runtime.sendMessage( { action: "notification", payload: { }, }, (response) => { if (!response?.error) { } } ); const notificationId = 'chat_notification_' + Date.now() chrome.notifications.create(notificationId, { type: 'basic', iconUrl: 'qort.png', // Add an appropriate icon for chat notifications title: `New Direct message!`, message: 'You have received a new direct message', priority: 2, // Use the maximum priority to ensure it's noticeable // buttons: [ // { title: 'Go to group' } // ] }); if(!isMobile){ setTimeout(() => { chrome.notifications.clear(notificationId); }, 7000); } playNotificationSound() // audio.play(); // } } } finally { setChatHeadsDirect(dataDirects) // chrome.runtime.sendMessage( // { // action: "setChatHeads", // payload: { // data, // }, // } // ); } } async function getThreadActivity(){ const wallet = await getSaveWallet(); const address = wallet.address0; const key = `threadactivity-${address}` const res = await chrome.storage.local.get([key]); if (res?.[key]) { const parsedData = JSON.parse(res[key]) return parsedData; } else { return null } } async function updateThreadActivity({threadId, qortalName, groupId, thread}) { const wallet = await getSaveWallet(); const address = wallet.address0; const ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000; // One week in milliseconds let lastResetTime = 0 // Retrieve the last reset timestamp from storage const key = `threadactivity-${address}` chrome.storage.local.get([key], (data) => { let threads if (!data[key] || Object.keys(data?.[key]?.length === 0)) { threads = { createdThreads: [], mostVisitedThreads: [], recentThreads: [] }; } else { threads = JSON.parse(data[key]) } if(threads?.lastResetTime){ lastResetTime = threads.lastResetTime } const currentTime = Date.now(); // Check if a week has passed since the last reset if (!lastResetTime || currentTime - lastResetTime > ONE_WEEK_IN_MS) { // Reset the visit counts for all most visited threads threads.mostVisitedThreads.forEach(thread => thread.visitCount = 0); lastResetTime = currentTime; // Update the last reset time threads.lastResetTime = lastResetTime } // Update the recent threads list threads.recentThreads = threads.recentThreads.filter(t => t.threadId !== threadId); threads.recentThreads.unshift({ threadId, qortalName, groupId, thread, visitCount: 1, lastVisited: Date.now() }); // Sort the recent threads by lastVisited time (descending) threads.recentThreads.sort((a, b) => b.lastVisited - a.lastVisited); // Limit the recent threads list to 2 items threads.recentThreads = threads.recentThreads.slice(0, 2); // Update the most visited threads list const existingThread = threads.mostVisitedThreads.find(t => t.threadId === threadId); if (existingThread) { existingThread.visitCount += 1; existingThread.lastVisited = Date.now(); // Update the last visited time as well } else { threads.mostVisitedThreads.push({ threadId, qortalName, groupId, thread, visitCount: 1, lastVisited: Date.now() }); } // Sort the most visited threads by visitCount (descending) threads.mostVisitedThreads.sort((a, b) => b.visitCount - a.visitCount); // Limit the most visited threads list to 2 items threads.mostVisitedThreads = threads.mostVisitedThreads.slice(0, 2); // Store the updated thread information and last reset time // chrome.storage.local.set({ threads, lastResetTime }); const dataString = JSON.stringify(threads); chrome.storage.local.set({ [`threadactivity-${address}`]: dataString }) }); } const handleNotification = async (groups)=> { const wallet = await getSaveWallet(); const address = wallet.address0; let isFocused const data = groups.filter((group)=> group?.sender !== address) try { if(!data || data?.length === 0) return isFocused = await checkWebviewFocus() if(isFocused){ throw new Error('isFocused') } const newActiveChats= data const oldActiveChats = await getChatHeads() let results = [] let newestLatestTimestamp let oldestLatestTimestamp // Find the latest timestamp from newActiveChats newActiveChats?.forEach(newChat => { if (!newestLatestTimestamp || newChat?.timestamp > newestLatestTimestamp?.timestamp) { newestLatestTimestamp = newChat; } }); // Find the latest timestamp from oldActiveChats oldActiveChats?.forEach(oldChat => { if (!oldestLatestTimestamp || oldChat?.timestamp > oldestLatestTimestamp?.timestamp) { oldestLatestTimestamp = oldChat; } }); if(checkDifference(newestLatestTimestamp.timestamp) && !oldestLatestTimestamp || (newestLatestTimestamp && newestLatestTimestamp?.timestamp > oldestLatestTimestamp?.timestamp)){ if (!lastGroupNotification || ((Date.now() - lastGroupNotification) >= 120000)) { const notificationId = 'chat_notification_' + Date.now() + '_type=group' + `_from=${newestLatestTimestamp.groupId}`; chrome.notifications.create(notificationId, { type: 'basic', iconUrl: 'qort.png', // Add an appropriate icon for chat notifications title: 'New Group Message!', message: `You have received a new message from ${newestLatestTimestamp?.groupName}`, priority: 2, // Use the maximum priority to ensure it's noticeable // buttons: [ // { title: 'Go to group' } // ] }); if(!isMobile){ setTimeout(() => { chrome.notifications.clear(notificationId); }, 7000); } // chrome.runtime.sendMessage( // { // action: "notification", // payload: { // }, // } // ) // audio.play(); playNotificationSound() lastGroupNotification = Date.now() } } } catch (error) { if(!isFocused){ chrome.runtime.sendMessage( { action: "notification", payload: { }, }, (response) => { if (!response?.error) { } } ); const notificationId = 'chat_notification_' + Date.now(); chrome.notifications.create(notificationId, { type: 'basic', iconUrl: 'qort.png', // Add an appropriate icon for chat notifications title: 'New Group Message!', message: 'You have received a new message from one of your groups', priority: 2, // Use the maximum priority to ensure it's noticeable // buttons: [ // { title: 'Go to group' } // ] }); if(!isMobile){ setTimeout(() => { chrome.notifications.clear(notificationId); }, 7000); } playNotificationSound() // audio.play(); lastGroupNotification = Date.now() // } } } finally { if(!data || data?.length === 0) return setChatHeads(data) // chrome.runtime.sendMessage( // { // action: "setChatHeads", // payload: { // data, // }, // } // ); } } const checkThreads = async (bringBack) => { try { let myName = "" const userData = await getUserInfo() if(userData?.name){ myName = userData.name } let newAnnouncements = [] let dataToBringBack = [] const threadActivity = await getThreadActivity() if(!threadActivity) return null const selectedThreads = [ ...threadActivity.createdThreads.slice(0, 2), ...threadActivity.mostVisitedThreads.slice(0, 2), ...threadActivity.recentThreads.slice(0, 2), ] if(selectedThreads?.length === 0) return null const tempData = { } for (const thread of selectedThreads){ try { const identifier = `thmsg-${thread?.threadId}` const name = thread?.qortalName const url = await createEndpoint(`/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true`); const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' } }) const responseData = await response.json() const latestMessage = responseData.filter((pub)=> pub?.name !== myName)[0] // const latestMessage = responseData[0] if (!latestMessage) { continue } if(checkDifference(latestMessage.created) && latestMessage.created > thread?.lastVisited && (!thread?.lastNotified || thread?.lastNotified < thread?.created)){ tempData[thread.threadId] = latestMessage.created newAnnouncements.push(thread) } if(latestMessage.created > thread?.lastVisited){ dataToBringBack.push(thread) } } catch (error) { conosle.log({error}) } } if(bringBack){ return dataToBringBack } const updateThreadWithLastNotified = { ...threadActivity, createdThreads: (threadActivity?.createdThreads || [])?.map((item)=> { if(tempData[item.threadId]){ return { ...item, lastNotified: tempData[item.threadId] } } else { return item } }), mostVisitedThreads: (threadActivity?.mostVisitedThreads || [])?.map((item)=> { if(tempData[item.threadId]){ return { ...item, lastNotified: tempData[item.threadId] } } else { return item } }), recentThreads: (threadActivity?.recentThreads || [])?.map((item)=> { if(tempData[item.threadId]){ return { ...item, lastNotified: tempData[item.threadId] } } else { return item } }), } const wallet = await getSaveWallet(); const address = wallet.address0; const dataString = JSON.stringify(updateThreadWithLastNotified); chrome.storage.local.set({ [`threadactivity-${address}`]: dataString }) if(newAnnouncements.length > 0){ const notificationId = 'chat_notification_' + Date.now() + '_type=thread-post' + `_data=${JSON.stringify(newAnnouncements[0])}`; chrome.notifications.create(notificationId, { type: 'basic', iconUrl: 'qort.png', // Add an appropriate icon for chat notifications title: `New thread post!`, message: `New post in ${newAnnouncements[0]?.thread?.threadData?.title}`, priority: 2, // Use the maximum priority to ensure it's noticeable // buttons: [ // { title: 'Go to group' } // ] }); if(!isMobile){ setTimeout(() => { chrome.notifications.clear(notificationId); }, 7000); } playNotificationSound() } const savedtimestampAfter = await getTimestampGroupAnnouncement() chrome.runtime.sendMessage({ action: "SET_GROUP_ANNOUNCEMENTS", payload: savedtimestampAfter, }); } catch (error) { } finally { } } const checkNewMessages = async () => { try { let myName = "" const userData = await getUserInfo() if(userData?.name){ myName = userData.name } let newAnnouncements = [] const activeData = await getStoredData('active-groups-directs') || { groups: [], directs: [] }; const groups = activeData?.groups if(!groups || groups?.length === 0) return const savedtimestamp = await getTimestampGroupAnnouncement() await Promise.all(groups.map(async (group) => { try { const identifier = `grp-${group.groupId}-anc-`; const url = await createEndpoint(`/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=1&includemetadata=false&offset=0&reverse=true&prefix=true`); const response = await requestQueueAnnouncements.enqueue(()=> { return fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); }) const responseData = await response.json(); const latestMessage = responseData.filter((pub) => pub?.name !== myName)[0]; if (!latestMessage) { return; // continue to the next group } if (checkDifference(latestMessage.created) && (!savedtimestamp[group.groupId] || latestMessage.created > savedtimestamp?.[group.groupId]?.notification)) { newAnnouncements.push(group); await addTimestampGroupAnnouncement({ groupId: group.groupId, timestamp: Date.now() }); // save new timestamp } } catch (error) { console.error(error); // Handle error if needed } })); if(newAnnouncements.length > 0){ const notificationId = 'chat_notification_' + Date.now() + '_type=group-announcement' + `_from=${newAnnouncements[0]?.groupId}`; chrome.notifications.create(notificationId, { type: 'basic', iconUrl: 'qort.png', // Add an appropriate icon for chat notifications title: `New group announcement!`, message: `You have received a new announcement from ${newAnnouncements[0]?.groupName}`, priority: 2, // Use the maximum priority to ensure it's noticeable // buttons: [ // { title: 'Go to group' } // ] }); if(!isMobile){ setTimeout(() => { chrome.notifications.clear(notificationId); }, 7000); } playNotificationSound() } const savedtimestampAfter = await getTimestampGroupAnnouncement() chrome.runtime.sendMessage({ action: "SET_GROUP_ANNOUNCEMENTS", payload: savedtimestampAfter, }); } catch (error) { } finally { } } const listenForNewGroupAnnouncements = async ()=> { try { setTimeout(() => { checkNewMessages() }, 500); if(interval){ clearInterval(interval) } let isCalling = false interval = setInterval(async () => { if (isCalling) return isCalling = true const res = await checkNewMessages() isCalling = false }, 180000) } catch (error) { } } const listenForThreadUpdates = async ()=> { try { setTimeout(() => { checkThreads() }, 500); if(intervalThreads){ clearInterval(intervalThreads) } let isCalling = false intervalThreads = setInterval(async () => { if (isCalling) return isCalling = true const res = await checkThreads() isCalling = false }, 60000) } catch (error) { } } const forceCloseWebSocket = () => { if (socket) { clearTimeout(timeoutId); clearTimeout(groupSocketTimeout); clearTimeout(socketTimeout); timeoutId = null groupSocketTimeout = null socket.close(1000, 'forced') socket = null } }; async function getNameInfo() { const wallet = await getSaveWallet(); const address = wallet.address0; const validApi = await getBaseApi() const response = await fetch(validApi + "/names/address/" + address); const nameData = await response.json(); if (nameData?.length > 0) { return nameData[0].name; } else { return ""; } } async function getAddressInfo(address) { const validApi = await getBaseApi() const response = await fetch(validApi + "/addresses/" + address); const data = await response.json(); if (!response?.ok && data?.error !== 124) throw new Error("Cannot fetch address info"); if (data?.error === 124) { return { address, }; } return data; } async function getKeyPair() { const res = await chrome.storage.local.get(["keyPair"]); if (res?.keyPair) { return res.keyPair; } else { throw new Error("Wallet not authenticated"); } } async function getSaveWallet() { const res = await chrome.storage.local.get(["walletInfo"]); if (res?.walletInfo) { return res.walletInfo; } else { throw new Error("No wallet saved"); } } async function getUserInfo() { const wallet = await getSaveWallet(); const address = wallet.address0; const addressInfo = await getAddressInfo(address); const name = await getNameInfo(); return { name, publicKey: wallet.publicKey, ...addressInfo, }; } async function connection(hostname) { const isConnected = chrome.storage.local.get([hostname]); return isConnected; } async function getTradeInfo(qortalAtAddress) { const response = await fetch( buyTradeNodeBaseUrl + "/crosschain/trade/" + qortalAtAddress ); if (!response?.ok) throw new Error("Cannot crosschain trade information"); const data = await response.json(); return data; } async function getBalanceInfo() { const wallet = await getSaveWallet(); const address = wallet.address0; const validApi = await getBaseApi() const response = await fetch(validApi + "/addresses/balance/" + address); if (!response?.ok) throw new Error("Cannot fetch balance"); const data = await response.json(); return data; } async function getLTCBalance() { const wallet = await getSaveWallet(); let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`; const keyPair = await getKeyPair(); const parsedKeyPair = JSON.parse(keyPair); let _body = parsedKeyPair.ltcPublicKey; const response = await fetch(_url, { method: "POST", headers: { "Content-Type": "application/json", }, body: _body, }); if (response?.ok) { const data = await response.text(); const dataLTCBalance = (Number(data) / 1e8).toFixed(8); return +dataLTCBalance; } else throw new Error("Onable to get LTC balance"); } const processTransactionVersion2Chat = async (body: any, customApi) => { // const validApi = await findUsableApi(); const url = await createEndpoint("/transactions/process?apiVersion=2", customApi); return fetch(url, { method: "POST", headers: {}, body: Base58.encode(body), }).then(async (response) => { try { const json = await response.clone().json(); return json; } catch (e) { return await response.text(); } }); }; const processTransactionVersion2 = async (body: any) => { const url = await createEndpoint(`/transactions/process?apiVersion=2`); try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", // Ensure the body is correctly parsed }, body, // Convert body to JSON string }); // if (!response.ok) { // // If the response is not successful (status code is not 2xx) // throw new Error(`HTTP error! Status: ${response.status}`); // } try { const json = await response.clone().json(); return json; } catch (jsonError) { try { const text = await response.text(); return text } catch (textError) { throw new Error(`Failed to parse response as both JSON and text.`); } } } catch (error) { console.error("Error processing transaction:", error); throw error; // Re-throw the error after logging it } }; const transaction = async ( { type, params, apiVersion, keyPair }: any, validApi ) => { const tx = createTransaction(type, keyPair, params); let res; if (apiVersion && apiVersion === 2) { const signedBytes = Base58.encode(tx.signedBytes); res = await processTransactionVersion2(signedBytes, validApi); } let success = true if(res?.error){ success = false } return { success, data: res, }; }; const makeTransactionRequest = async ( receiver, lastRef, amount, fee, keyPair, validApi ) => { const myTxnrequest = await transaction( { nonce: 0, type: 2, params: { recipient: receiver, // recipientName: recipientName, amount: amount, lastReference: lastRef, fee: fee, }, apiVersion: 2, keyPair, }, validApi ); return myTxnrequest; }; const getLastRef = async () => { const wallet = await getSaveWallet(); const address = wallet.address0; const validApi = await getBaseApi() const response = await fetch( validApi + "/addresses/lastreference/" + address ); if (!response?.ok) throw new Error("Cannot fetch balance"); const data = await response.text(); return data; }; const sendQortFee = async () => { const validApi = await getBaseApi() const response = await fetch( validApi + "/transactions/unitfee?txType=PAYMENT" ); if (!response.ok) { throw new Error("Error when fetching join fee"); } const data = await response.json(); const qortFee = (Number(data) / 1e8).toFixed(8); return qortFee; }; async function getNameOrAddress(receiver) { try { const isAddress = validateAddress(receiver); if (isAddress) { return receiver; } const validApi = await getBaseApi() const response = await fetch(validApi + "/names/" + receiver); const data = await response.json(); if (data?.owner) return data.owner; if (data?.error) { throw new Error("Name does not exist"); } if (!response?.ok) throw new Error("Cannot fetch name"); return { error: "cannot validate address or name" }; } catch (error) { throw new Error(error?.message || "cannot validate address or name"); } } async function getPublicKey(receiver) { try { const validApi = await getBaseApi() const response = await fetch(validApi + "/addresses/publickey/" + receiver); if (!response?.ok) throw new Error("Cannot fetch recipient's public key"); const data = await response.text(); if (!data?.error && data !== 'false') return data; if (data?.error) { throw new Error("Cannot fetch recipient's public key"); } throw new Error("Cannot fetch recipient's public key"); } catch (error) { throw new Error(error?.message || "cannot validate address or name"); } } async function decryptWallet({ password, wallet, walletVersion }) { try { const response = await decryptStoredWallet(password, wallet); const wallet2 = new PhraseWallet(response, walletVersion); const keyPair = wallet2._addresses[0].keyPair; const ltcPrivateKey = wallet2._addresses[0].ltcWallet.derivedMasterPrivateKey; const ltcPublicKey = wallet2._addresses[0].ltcWallet.derivedMasterPublicKey; const ltcAddress = wallet2._addresses[0].ltcWallet.address; const toSave = { privateKey: Base58.encode(keyPair.privateKey), publicKey: Base58.encode(keyPair.publicKey), ltcPrivateKey: ltcPrivateKey, ltcPublicKey: ltcPublicKey, }; const dataString = JSON.stringify(toSave); await new Promise((resolve, reject) => { chrome.storage.local.set({ keyPair: dataString }, () => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(true); } }); }); const newWallet = { ...wallet, publicKey: Base58.encode(keyPair.publicKey), ltcAddress: ltcAddress, }; await new Promise((resolve, reject) => { chrome.storage.local.set({ walletInfo: newWallet }, () => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(true); } }); }); return true; } catch (error) { throw new Error(error.message); } } async function signChatFunc(chatBytesArray, chatNonce, customApi, keyPair) { let response; try { const signedChatBytes = signChat(chatBytesArray, chatNonce, keyPair); const res = await processTransactionVersion2Chat(signedChatBytes, customApi); response = res; } catch (e) { console.error(e); console.error(e.message); response = false; } return response; } function sbrk(size, heap) { let brk = 512 * 1024; // stack top let old = brk; brk += size; if (brk > heap.length) throw new Error("heap exhausted"); return old; } const computePow = async ({ chatBytes, path, difficulty }) => { let response = null; await new Promise((resolve, reject) => { const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }); const chatBytesArray = new Uint8Array(_chatBytesArray); const chatBytesHash = new Sha256().process(chatBytesArray).finish().result; const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }); const heap = new Uint8Array(memory.buffer); const hashPtr = sbrk(32, heap); const hashAry = new Uint8Array(memory.buffer, hashPtr, 32); hashAry.set(chatBytesHash); const workBufferLength = 8 * 1024 * 1024; const workBufferPtr = sbrk(workBufferLength, heap); const importObject = { env: { memory: memory, }, }; function loadWebAssembly(filename, imports) { // Fetch the file and compile it return fetch(filename) .then((response) => response.arrayBuffer()) .then((buffer) => WebAssembly.compile(buffer)) .then((module) => { // Create the instance. return new WebAssembly.Instance(module, importObject); }); } loadWebAssembly(path).then((wasmModule) => { response = { nonce: wasmModule.exports.compute2( hashPtr, workBufferPtr, workBufferLength, difficulty ), chatBytesArray, }; resolve(); }); }); return response; }; const getStoredData = async (key) => { return new Promise((resolve, reject) => { chrome.storage.local.get(key, (result) => { if (chrome.runtime.lastError) { return reject(chrome.runtime.lastError); } resolve(result[key]); }); }); }; async function handleActiveGroupDataFromSocket({groups, directs}){ try { chrome.runtime.sendMessage({ action: "SET_GROUPS", payload: groups, }); chrome.runtime.sendMessage({ action: "SET_DIRECTS", payload: directs, }); groups = groups directs = directs const activeData = { groups: groups || [], // Your groups data here directs: directs || [] // Your directs data here }; // Save the active data to localStorage chrome.storage.local.set({ 'active-groups-directs': activeData }); try { handleNotification(groups) handleNotificationDirect(directs) } catch (error) { } } catch (error) { } } async function sendChat({ qortAddress, recipientPublicKey, message }) { let _reference = new Uint8Array(64); self.crypto.getRandomValues(_reference); let sendTimestamp = Date.now(); let reference = Base58.encode(_reference); const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const balance = await getBalanceInfo(); const hasEnoughBalance = +balance < 4 ? false : true; const difficulty = 8; const jsonData = { atAddress: message.atAddress, foreignKey: message.foreignKey, receivingAddress: message.receivingAddress, }; const finalJson = { callRequest: jsonData, extra: "whatever additional data goes here", }; const messageStringified = JSON.stringify(finalJson); const tx = await createTransaction(18, keyPair, { timestamp: sendTimestamp, recipient: qortAddress, recipientPublicKey: recipientPublicKey, hasChatReference: 0, message: messageStringified, lastReference: reference, proofOfWorkNonce: 0, isEncrypted: 1, isText: 1, }); if (!hasEnoughBalance) { const _encryptedMessage = tx._encryptedMessage; const encryptedMessageToBase58 = Base58.encode(_encryptedMessage); return { encryptedMessageToBase58, signature: "id-" + Date.now() + "-" + Math.floor(Math.random() * 1000), reference, }; } const path = chrome.runtime.getURL("memory-pow.wasm.full"); const { nonce, chatBytesArray } = await computePow({ chatBytes: tx.chatBytes, path, difficulty, }); let _response = await signChatFunc( chatBytesArray, nonce, "https://appnode.qortal.org", keyPair ); if (_response?.error) { throw new Error(_response?.message); } return _response; } async function sendChatGroup({ groupId, typeMessage, chatReference, messageText, }) { let _reference = new Uint8Array(64); self.crypto.getRandomValues(_reference); let reference = Base58.encode(_reference); const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const balance = await getBalanceInfo(); const hasEnoughBalance = +balance < 4 ? false : true; const difficulty = 8; const tx = await createTransaction(181, keyPair, { timestamp: Date.now(), groupID: Number(groupId), hasReceipient: 0, hasChatReference: typeMessage === "edit" ? 1 : 0, // chatReference: chatReference, message: messageText, lastReference: reference, proofOfWorkNonce: 0, isEncrypted: 0, // Set default to not encrypted for groups isText: 1, }); 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 { nonce, chatBytesArray } = await computePow({ chatBytes: tx.chatBytes, path, difficulty, }); let _response = await signChatFunc( chatBytesArray, nonce, null, keyPair ); if (_response?.error) { throw new Error(_response?.message); } return _response; } async function sendChatDirect({ directTo, typeMessage, chatReference, messageText, }) { const recipientAddress = await getNameOrAddress(directTo) const recipientPublicKey = await getPublicKey(recipientAddress) if(!recipientPublicKey) throw new Error('Cannot retrieve publickey') let _reference = new Uint8Array(64); self.crypto.getRandomValues(_reference); let reference = Base58.encode(_reference); const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const balance = await getBalanceInfo(); const hasEnoughBalance = +balance < 4 ? false : true; const difficulty = 8; const finalJson = { message: messageText, version: 2, }; const messageStringified = JSON.stringify(finalJson); const tx = await createTransaction(18, keyPair, { timestamp: Date.now(), recipient: recipientAddress, recipientPublicKey: recipientPublicKey, hasChatReference: 0, message: messageStringified, lastReference: reference, proofOfWorkNonce: 0, isEncrypted: 1, isText: 1, }); 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 { nonce, chatBytesArray } = await computePow({ chatBytes: tx.chatBytes, path, difficulty, }); let _response = await signChatFunc( chatBytesArray, nonce, null, keyPair ); if (_response?.error) { throw new Error(_response?.message); } return _response; } async function decryptSingleFunc({ messages, secretKeyObject, skipDecodeBase64 }) { let holdMessages = []; for (const message of messages) { try { const res = await decryptSingle({ data64: message.data, secretKeyObject, skipDecodeBase64 }); const decryptToUnit8Array = base64ToUint8Array(res); const responseData = uint8ArrayToObject(decryptToUnit8Array); holdMessages.push({ ...message, text: responseData }); } catch (error) { } } return holdMessages; } async function decryptSingleForPublishes({ messages, secretKeyObject, skipDecodeBase64 }) { let holdMessages = []; for (const message of messages) { try { const res = await decryptSingle({ data64: message.data, secretKeyObject, skipDecodeBase64 }); const decryptToUnit8Array = base64ToUint8Array(res); const responseData = uint8ArrayToObject(decryptToUnit8Array); holdMessages.push({ ...message, decryptedData: responseData }); } catch (error) { } } return holdMessages; } async function decryptDirectFunc({ messages, involvingAddress }) { const senderPublicKey = await getPublicKey(involvingAddress) let holdMessages = []; const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; for (const message of messages) { try { const decodedMessage = decryptChatMessage( message.data, keyPair.privateKey, senderPublicKey, message.reference ); const parsedMessage = JSON.parse(decodedMessage); holdMessages.push({ ...message, ...parsedMessage }); } catch (error) { } } return holdMessages; } async function createBuyOrderTx({ crosschainAtInfo }) { try { const wallet = await getSaveWallet(); const address = wallet.address0; const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const message = { atAddress: crosschainAtInfo.qortalAtAddress, foreignKey: parsedData.ltcPrivateKey, receivingAddress: address, }; const res = await sendChat({ qortAddress: proxyAccountAddress, recipientPublicKey: proxyAccountPublicKey, message, }); if (res?.signature) { listenForChatMessageForBuyOrder({ nodeBaseUrl: buyTradeNodeBaseUrl, senderAddress: proxyAccountAddress, senderPublicKey: proxyAccountPublicKey, signature: res?.signature, }); if (res?.encryptedMessageToBase58) { return { atAddress: crosschainAtInfo.qortalAtAddress, encryptedMessageToBase58: res?.encryptedMessageToBase58, node: buyTradeNodeBaseUrl, qortAddress: address, chatSignature: res?.signature, senderPublicKey: parsedData.publicKey, sender: address, reference: res?.reference, }; } return { atAddress: crosschainAtInfo.qortalAtAddress, chatSignature: res?.signature, node: buyTradeNodeBaseUrl, qortAddress: address, }; } else { throw new Error("Unable to send buy order message"); } } catch (error) { throw new Error(error.message); } } async function sendChatNotification(res, groupId, secretKeyObject, numberOfMembers){ try { const data = await objectToBase64({ type: "notification", subType: "new-group-encryption", data: { timestamp: res.timestamp, name: res.name, message: `${res.name} has updated the encryption key`, numberOfMembers }, }) encryptSingle({ data64: data, secretKeyObject: secretKeyObject, }) .then((res2) => { pauseAllQueues() sendChatGroup({ groupId, typeMessage: undefined, chatReference: undefined, messageText: res2, }) .then(() => {}) .catch((error) => { console.error('1',error.message); }).finally(()=> { resumeAllQueues() }) }) .catch((error) => { console.error('2',error.message); }); } catch (error) { } } export const getFee = async(txType)=> { const timestamp = Date.now() const data = await reusableGet(`/transactions/unitfee?txType=${txType}×tamp=${timestamp}`) const arbitraryFee = (Number(data) / 1e8).toFixed(8) return { timestamp, fee: arbitraryFee } } async function leaveGroup({groupId}){ const wallet = await getSaveWallet(); const address = wallet.address0; const lastReference = await getLastRef() const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const feeres = await getFee('LEAVE_GROUP') const tx = await createTransaction(32, keyPair, { fee: feeres.fee, registrantAddress: address, rGroupId: groupId, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function joinGroup({groupId}){ const wallet = await getSaveWallet(); const address = wallet.address0; const lastReference = await getLastRef() const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const feeres = await getFee('JOIN_GROUP') const tx = await createTransaction(31, keyPair, { fee: feeres.fee, registrantAddress: address, rGroupId: groupId, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error(res?.message || 'Transaction was not able to be processed') return res } async function cancelInvitationToGroup({groupId, qortalAddress}){ const lastReference = await getLastRef() const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const feeres = await getFee('CANCEL_GROUP_INVITE') const tx = await createTransaction(30, keyPair, { fee: feeres.fee, recipient: qortalAddress, rGroupId: groupId, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function cancelBan({groupId, qortalAddress}){ const lastReference = await getLastRef() const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const feeres = await getFee('CANCEL_GROUP_BAN') const tx = await createTransaction(27, keyPair, { fee: feeres.fee, recipient: qortalAddress, rGroupId: groupId, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function registerName({name}){ const lastReference = await getLastRef() const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const feeres = await getFee('REGISTER_NAME') const tx = await createTransaction(3, keyPair, { fee: feeres.fee, name, value: "", lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function makeAdmin({groupId, qortalAddress}){ const lastReference = await getLastRef() const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const feeres = await getFee('ADD_GROUP_ADMIN') const tx = await createTransaction(24, keyPair, { fee: feeres.fee, recipient: qortalAddress, rGroupId: groupId, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function removeAdmin({groupId, qortalAddress}){ const lastReference = await getLastRef() const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const feeres = await getFee('REMOVE_GROUP_ADMIN') const tx = await createTransaction(25, keyPair, { fee: feeres.fee, recipient: qortalAddress, rGroupId: groupId, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function banFromGroup({groupId, qortalAddress, rBanReason = "", rBanTime}){ const lastReference = await getLastRef() const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const feeres = await getFee('GROUP_BAN') const tx = await createTransaction(26, keyPair, { fee: feeres.fee, recipient: qortalAddress, rGroupId: groupId, rBanReason: rBanReason, rBanTime, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function kickFromGroup({groupId, qortalAddress, rBanReason = ""}){ const lastReference = await getLastRef() const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const feeres = await getFee('GROUP_KICK') const tx = await createTransaction(28, keyPair, { fee: feeres.fee, recipient: qortalAddress, rGroupId: groupId, rBanReason: rBanReason, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function createGroup({ groupName, groupDescription, groupType, groupApprovalThreshold, minBlock, maxBlock}){ const wallet = await getSaveWallet(); const address = wallet.address0; if(!address) throw new Error('Cannot find user') const lastReference = await getLastRef() const feeres = await getFee('CREATE_GROUP') const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const tx = await createTransaction(22, keyPair, { fee: feeres.fee, registrantAddress: address, rGroupName: groupName, rGroupDesc: groupDescription, rGroupType: groupType, rGroupApprovalThreshold: groupApprovalThreshold, rGroupMinimumBlockDelay: minBlock, rGroupMaximumBlockDelay: maxBlock, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function inviteToGroup({groupId, qortalAddress, inviteTime}){ const address = await getNameOrAddress(qortalAddress) if(!address) throw new Error('Cannot find user') const lastReference = await getLastRef() const feeres = await getFee('GROUP_INVITE') const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const tx = await createTransaction(29, keyPair, { fee: feeres.fee, recipient: address, rGroupId: groupId, rInviteTime: inviteTime, lastReference: lastReference, }); const signedBytes = Base58.encode(tx.signedBytes); const res = await processTransactionVersion2(signedBytes) if(!res?.signature) throw new Error('Transaction was not able to be processed') return res } async function sendCoin({ password, amount, receiver }, skipConfirmPassword) { try { const confirmReceiver = await getNameOrAddress(receiver); if (confirmReceiver.error) throw new Error("Invalid receiver address or name"); const wallet = await getSaveWallet(); let keyPair = ""; if (skipConfirmPassword) { const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; } else { const response = await decryptStoredWallet(password, wallet); const wallet2 = new PhraseWallet(response, walletVersion); keyPair = wallet2._addresses[0].keyPair; } const lastRef = await getLastRef(); const fee = await sendQortFee(); const validApi = await findUsableApi() const res = await makeTransactionRequest( confirmReceiver, lastRef, amount, fee, keyPair, validApi ); return { res, validApi }; } catch (error) { throw new Error(error.message); } } function fetchMessages(apiCall) { let retryDelay = 2000; // Start with a 2-second delay const maxDuration = 360000 * 2; // Maximum duration set to 12 minutes const startTime = Date.now(); // Record the start time // Promise to handle polling logic return new Promise((resolve, reject) => { const attemptFetch = async () => { if (Date.now() - startTime > maxDuration) { return reject(new Error("Maximum polling time exceeded")); } try { const response = await fetch(apiCall); const data = await response.json(); if (data && data.length > 0) { resolve(data[0]); // Resolve the promise when data is found } else { setTimeout(attemptFetch, retryDelay); retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes } } catch (error) { reject(error); // Reject the promise on error } }; attemptFetch(); // Initial call to start the polling }); } async function fetchMessagesForBuyOrders(apiCall, signature, senderPublicKey) { let retryDelay = 2000; // Start with a 2-second delay const maxDuration = 360000 * 2; // Maximum duration set to 12 minutes const startTime = Date.now(); // Record the start time let triedChatMessage = []; // Promise to handle polling logic await new Promise((res) => { setTimeout(() => { res(); }, 40000); }); return new Promise((resolve, reject) => { const attemptFetch = async () => { if (Date.now() - startTime > maxDuration) { return reject(new Error("Maximum polling time exceeded")); } try { const response = await fetch(apiCall); let data = await response.json(); data = data.filter( (item) => !triedChatMessage.includes(item.signature) ); if (data && data.length > 0) { const encodedMessageObj = data[0]; const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const decodedMessage = decryptChatMessage( encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference ); const parsedMessage = JSON.parse(decodedMessage); if (parsedMessage?.extra?.chatRequestSignature === signature) { resolve(parsedMessage); } else { triedChatMessage.push(encodedMessageObj.signature); setTimeout(attemptFetch, retryDelay); retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes } // Resolve the promise when data is found } else { setTimeout(attemptFetch, retryDelay); retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes } } catch (error) { reject(error); // Reject the promise on error } }; attemptFetch(); // Initial call to start the polling }); } async function listenForChatMessage({ nodeBaseUrl, senderAddress, senderPublicKey, timestamp, }) { try { let validApi = ""; const checkIfNodeBaseUrlIsAcceptable = apiEndpoints.find( (item) => item === nodeBaseUrl ); if (checkIfNodeBaseUrlIsAcceptable) { validApi = checkIfNodeBaseUrlIsAcceptable; } else { validApi = await findUsableApi(); } const wallet = await getSaveWallet(); const address = wallet.address0; const before = timestamp + 5000; const after = timestamp - 5000; const apiCall = `${validApi}/chat/messages?involving=${senderAddress}&involving=${address}&reverse=true&limit=1&before=${before}&after=${after}&encoding=BASE64`; const encodedMessageObj = await fetchMessages(apiCall); const resKeyPair = await getKeyPair(); const parsedData = JSON.parse(resKeyPair); const uint8PrivateKey = Base58.decode(parsedData.privateKey); const uint8PublicKey = Base58.decode(parsedData.publicKey); const keyPair = { privateKey: uint8PrivateKey, publicKey: uint8PublicKey, }; const decodedMessage = decryptChatMessage( encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference ); return { secretCode: decodedMessage }; } catch (error) { console.error(error); throw new Error(error.message); } } async function listenForChatMessageForBuyOrder({ nodeBaseUrl, senderAddress, senderPublicKey, signature, }) { try { let validApi = ""; const checkIfNodeBaseUrlIsAcceptable = apiEndpoints.find( (item) => item === nodeBaseUrl ); if (checkIfNodeBaseUrlIsAcceptable) { validApi = checkIfNodeBaseUrlIsAcceptable; } else { validApi = await findUsableApi(); } const wallet = await getSaveWallet(); const address = wallet.address0; const before = Date.now() + 1200000; const after = Date.now(); const apiCall = `${validApi}/chat/messages?involving=${senderAddress}&involving=${address}&reverse=true&limit=1&before=${before}&after=${after}&encoding=BASE64`; const parsedMessageObj = await fetchMessagesForBuyOrders( apiCall, signature, senderPublicKey ); // const resKeyPair = await getKeyPair() // const parsedData = JSON.parse(resKeyPair) // const uint8PrivateKey = Base58.decode(parsedData.privateKey); // const uint8PublicKey = Base58.decode(parsedData.publicKey); // const keyPair = { // privateKey: uint8PrivateKey, // publicKey: uint8PublicKey // }; // const decodedMessage = decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) // const parsedMessage = JSON.parse(decodedMessage) chrome.tabs.query({}, function (tabs) { tabs.forEach((tab) => { chrome.tabs.sendMessage(tab.id, { type: "RESPONSE_FOR_TRADES", message: parsedMessageObj, }); }); }); } catch (error) { console.error(error); throw new Error(error.message); } } function removeDuplicateWindow(popupUrl){ chrome.windows.getAll( { populate: true, windowTypes: ["popup"] }, (windows) => { // Filter to find popups matching the specific URL const existingPopupsPending = windows.filter( (w) => w.tabs && w.tabs.some( (tab) => tab.pendingUrl && tab.pendingUrl.startsWith(popupUrl) ) ); const existingPopups = windows.filter( (w) => w.tabs && w.tabs.some( (tab) => tab.url && tab.url.startsWith(popupUrl) ) ); if(existingPopupsPending.length > 1){ chrome.windows.remove(existingPopupsPending?.[0]?.tabs?.[0]?.windowId, () => { }); } else if(existingPopupsPending.length > 0 && existingPopups.length > 0){ chrome.windows.remove(existingPopupsPending?.[0]?.tabs?.[0]?.windowId, () => { }); } } ); } async function setChatHeads(data){ const wallet = await getSaveWallet(); const address = wallet.address0; const dataString = JSON.stringify(data); return await new Promise((resolve, reject) => { chrome.storage.local.set({ [`chatheads-${address}`]: dataString }, () => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(true); } }); }); } async function getTempPublish(){ const wallet = await getSaveWallet(); const address = wallet.address0; const key = `tempPublish-${address}` const res = await chrome.storage.local.get([key]); const SIX_MINUTES = 6 * 60 * 1000; // 6 minutes in milliseconds if (res?.[key]) { const parsedData = JSON.parse(res[key]); const currentTime = Date.now(); // Filter through each top-level key (e.g., "announcement") and then through its nested entries const filteredData = Object.fromEntries( Object.entries(parsedData).map(([category, entries]) => { // Filter out entries inside each category that are older than 6 minutes const filteredEntries = Object.fromEntries( Object.entries(entries).filter(([entryKey, entryValue]) => { return currentTime - entryValue.timestampSaved < SIX_MINUTES; }) ); return [category, filteredEntries]; }) ); if (JSON.stringify(filteredData) !== JSON.stringify(parsedData)) { const dataString = JSON.stringify(filteredData); await chrome.storage.local.set({ [key]: dataString }); } return filteredData; } else { return {}; } } async function saveTempPublish({data, key}){ const existingTemp = await getTempPublish() const wallet = await getSaveWallet(); const address = wallet.address0; const newTemp = { ...existingTemp, [key]: { ...(existingTemp[key] || {}), [data.identifier] : { data, timestampSaved: Date.now() } } } const dataString = JSON.stringify(newTemp); return await new Promise((resolve, reject) => { chrome.storage.local.set({ [`tempPublish-${address}`]: dataString }, () => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(newTemp[key]); } }); }); } async function setChatHeadsDirect(data){ const wallet = await getSaveWallet(); const address = wallet.address0; const dataString = JSON.stringify(data); return await new Promise((resolve, reject) => { chrome.storage.local.set({ [`chatheads-direct-${address}`]: dataString }, () => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(true); } }); }); } async function getTimestampEnterChat(){ const wallet = await getSaveWallet(); const address = wallet.address0; const key = `enter-chat-timestamp-${address}` const res = await chrome.storage.local.get([key]); if (res?.[key]) { const parsedData = JSON.parse(res[key]) return parsedData; } else { return {} } } async function getTimestampGroupAnnouncement(){ const wallet = await getSaveWallet(); const address = wallet.address0; const key = `group-announcement-${address}` const res = await chrome.storage.local.get([key]); if (res?.[key]) { const parsedData = JSON.parse(res[key]) return parsedData; } else { return {} } } async function addTimestampGroupAnnouncement({groupId, timestamp, seenTimestamp}){ const wallet = await getSaveWallet(); const address = wallet.address0; const data = await getTimestampGroupAnnouncement() || {} data[groupId] = { notification: timestamp, seentimestamp: seenTimestamp ? true : false } const dataString = JSON.stringify(data); return await new Promise((resolve, reject) => { chrome.storage.local.set({ [`group-announcement-${address}`]: dataString }, () => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(true); } }); }); } async function addTimestampEnterChat({groupId, timestamp}){ const wallet = await getSaveWallet(); const address = wallet.address0; const data = await getTimestampEnterChat() data[groupId] = timestamp const dataString = JSON.stringify(data); return await new Promise((resolve, reject) => { chrome.storage.local.set({ [`enter-chat-timestamp-${address}`]: dataString }, () => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); } else { resolve(true); } }); }); } async function notifyAdminRegenerateSecretKey({groupName, adminAddress}){ const wallet = await getSaveWallet(); const address = wallet.address0; const name = await getNameInfo(address) const nameOrAddress = name || address await sendChatDirect({ directTo: adminAddress, typeMessage: undefined, chatReference: undefined, messageText: `

Member ${nameOrAddress} has requested that you regenerate the group's secret key. Group: ${groupName}

` }) return true } async function getChatHeads(){ const wallet = await getSaveWallet(); const address = wallet.address0; const key = `chatheads-${address}` const res = await chrome.storage.local.get([key]); if (res?.[key]) { const parsedData = JSON.parse(res[key]) return parsedData; } else { throw new Error("No Chatheads saved"); } } async function getChatHeadsDirect(){ const wallet = await getSaveWallet(); const address = wallet.address0; const key = `chatheads-direct-${address}` const res = await chrome.storage.local.get([key]); if (res?.[key]) { const parsedData = JSON.parse(res[key]) return parsedData; } else { throw new Error("No Chatheads saved"); } } chrome?.runtime?.onMessage.addListener((request, sender, sendResponse) => { if (request) { switch (request.action) { case "version": // Example: respond with the version sendResponse({ version: "1.0" }); break; case "storeWalletInfo": chrome.storage.local.set({ walletInfo: request.wallet }, () => { if (chrome.runtime.lastError) { sendResponse({ error: chrome.runtime.lastError.message }); } else { sendResponse({ result: "Data saved successfully" }); } }); break; case "getWalletInfo": getKeyPair() .then(() => { chrome.storage.local.get(["walletInfo"], (result) => { if (chrome.runtime.lastError) { sendResponse({ error: chrome.runtime.lastError.message }); } else if (result.walletInfo) { sendResponse({ walletInfo: result.walletInfo }); } else { sendResponse({ error: "No wallet info found" }); } }); }) .catch((error) => { sendResponse({ error: error.message }); }); break; case "validApi": findUsableApi() .then((usableApi) => { console.log("Usable API:", usableApi); }) .catch((error) => { console.error(error.message); }); case "name": getNameInfo() .then((name) => { sendResponse(name); }) .catch((error) => { console.error(error.message); }); break; case "userInfo": getUserInfo() .then((name) => { sendResponse(name); }) .catch((error) => { sendResponse({ error: "User not authenticated" }); console.error(error.message); }); break; case "decryptWallet": { const { password, wallet } = request.payload; decryptWallet({ password, wallet, walletVersion, }) .then((hasDecrypted) => { sendResponse(hasDecrypted); }) .catch((error) => { sendResponse({ error: error?.message }); console.error(error.message); }); } break; case "balance": getBalanceInfo() .then((balance) => { sendResponse(balance); }) .catch((error) => { console.error(error.message); }); break; case "ltcBalance": { getLTCBalance() .then((balance) => { sendResponse(balance); }) .catch((error) => { console.error(error.message); }); } break; case "sendCoin": { const { receiver, password, amount } = request.payload; sendCoin({ receiver, password, amount }) .then(({res}) => { if(!res?.success){ sendResponse({ error: res?.data?.message }); return } sendResponse(true); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "inviteToGroup": { const { groupId, qortalAddress, inviteTime } = request.payload; inviteToGroup({ groupId, qortalAddress, inviteTime }) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "saveTempPublish": { const { data, key } = request.payload; saveTempPublish({ data, key }) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); return true } break; case "getTempPublish": { getTempPublish() .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "createGroup": { const { groupName, groupDescription, groupType, groupApprovalThreshold, minBlock, maxBlock } = request.payload; createGroup({ groupName, groupDescription, groupType, groupApprovalThreshold, minBlock, maxBlock }) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "cancelInvitationToGroup": { const { groupId, qortalAddress } = request.payload; cancelInvitationToGroup({ groupId, qortalAddress }) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "leaveGroup": { const { groupId } = request.payload; leaveGroup({ groupId }) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "joinGroup": { const { groupId } = request.payload; joinGroup({ groupId }) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "kickFromGroup": { const { groupId, qortalAddress, rBanReason } = request.payload; kickFromGroup({ groupId, qortalAddress, rBanReason }) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "banFromGroup": { const { groupId, qortalAddress, rBanReason, rBanTime } = request.payload; banFromGroup({ groupId, qortalAddress, rBanReason, rBanTime }) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "cancelBan": { const { groupId, qortalAddress } = request.payload; cancelBan({ groupId, qortalAddress}) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "registerName": { const { name } = request.payload; registerName({ name}) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "makeAdmin": { const { groupId, qortalAddress } = request.payload; makeAdmin({ groupId, qortalAddress}) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "removeAdmin": { const { groupId, qortalAddress } = request.payload; removeAdmin({ groupId, qortalAddress}) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); } break; case "oauth": { const { nodeBaseUrl, senderAddress, senderPublicKey, timestamp } = request.payload; listenForChatMessage({ nodeBaseUrl, senderAddress, senderPublicKey, timestamp, }) .then(({ secretCode }) => { sendResponse(secretCode); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); break; } case "setChatHeads": { const { data} = request.payload; setChatHeads({ data }) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); break; } case "getChatHeads": { getChatHeads() .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); break; } case "notification": { const notificationId = 'chat_notification_' + Date.now(); // Create a unique ID const { } = request.payload; chrome.notifications.create(notificationId, { type: 'basic', iconUrl: 'qort.png', // Add an appropriate icon for chat notifications title: 'New Group Message!', message: 'You have received a new message from one of your groups', priority: 2, // Use the maximum priority to ensure it's noticeable // buttons: [ // { title: 'Go to group' } // ] }); // Set a timeout to clear the notification after 'timeout' milliseconds setTimeout(() => { chrome.notifications.clear(notificationId); }, 3000); sendResponse(true) break; } case "addTimestampEnterChat": { const { groupId, timestamp } = request.payload; addTimestampEnterChat({groupId, timestamp}) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); break; } case "setApiKey": { const { payload } = request; // Save the apiKey in chrome.storage.local for persistence chrome.storage.local.set({ apiKey: payload }, () => { sendResponse(true) }); return true break; } case "getApiKey": { getApiKeyFromStorage() .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); return true break; } case "notifyAdminRegenerateSecretKey": { const { groupName, adminAddress } = request.payload; notifyAdminRegenerateSecretKey({groupName, adminAddress}) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); break; } case "addGroupNotificationTimestamp": { const { groupId, timestamp } = request.payload; addTimestampGroupAnnouncement({groupId, timestamp, seenTimestamp: true}) .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); break; } case "getTimestampEnterChat": { getTimestampEnterChat() .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); break; } case "getGroupNotificationTimestamp": { getTimestampGroupAnnouncement() .then((res) => { sendResponse(res); }) .catch((error) => { sendResponse({ error: error.message }); console.error(error.message); }); break; } case "authentication": { getSaveWallet() .then(() => { sendResponse(true); }) .catch((error) => { 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", }); } 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: chrome.runtime.getURL("index.html?secondary=true"), type: "popup", width: windowWidth, height: windowHeight, left: leftPosition, top: 0, } , () => { removeDuplicateWindow(popupUrl) }); }); } const interactionId = Date.now().toString(); // Simple example; consider a better unique ID setTimeout(() => { chrome.runtime.sendMessage({ action: "SET_COUNTDOWN", payload: request.timeout ? 0.75 * request.timeout : 60, }); chrome.runtime.sendMessage({ action: "UPDATE_STATE_REQUEST_AUTHENTICATION", payload: { hostname, interactionId, }, }); }, 500); // Store sendResponse callback with the interaction ID pendingResponses.set(interactionId, sendResponse); let intervalId = null; const startTime = Date.now(); const checkInterval = 3000; // Check every 3 seconds const timeout = request.timeout ? 0.75 * (request.timeout * 1000) : 60000; // Stop after 15 seconds const checkFunction = () => { getSaveWallet() .then(() => { clearInterval(intervalId); // Stop checking sendResponse(true); // Perform the success action chrome.runtime.sendMessage({ action: "closePopup", }); }) .catch((error) => { // Handle error if needed }); if (Date.now() - startTime > timeout) { sendResponse({ error: "User has not authenticated, try again.", }); clearInterval(intervalId); // Stop checking due to timeout // Handle timeout situation if needed } }; intervalId = setInterval(checkFunction, checkInterval); } ); }); } break; case "buyOrder": { const { qortalAtAddress, hostname } = request.payload; getTradeInfo(qortalAtAddress) .then((crosschainAtInfo) => { 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", }); } 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: chrome.runtime.getURL("index.html?secondary=true"), type: "popup", width: windowWidth, height: windowHeight, left: leftPosition, top: 0, }, () => { removeDuplicateWindow(popupUrl) }); }); } const interactionId = Date.now().toString(); // Simple example; consider a better unique ID setTimeout(() => { chrome.runtime.sendMessage({ action: "SET_COUNTDOWN", payload: request.timeout ? 0.9 * request.timeout : 20, }); chrome.runtime.sendMessage({ action: "UPDATE_STATE_REQUEST_BUY_ORDER", payload: { hostname, crosschainAtInfo, interactionId, }, }); }, 500); // Store sendResponse callback with the interaction ID pendingResponses.set(interactionId, sendResponse); } ); }) .catch((error) => { console.error(error.message); }); } break; case "connection": { const { hostname } = request.payload; connection(hostname) .then((isConnected) => { if ( Object.keys(isConnected)?.length > 0 && isConnected[hostname] ) { sendResponse(true); } else { 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", }); } 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, }, () => { removeDuplicateWindow(popupUrl) }); }); } const interactionId = Date.now().toString(); // Simple example; consider a better unique ID setTimeout(() => { chrome.runtime.sendMessage({ action: "SET_COUNTDOWN", payload: request.timeout ? 0.9 * request.timeout : 20, }); chrome.runtime.sendMessage({ action: "UPDATE_STATE_REQUEST_CONNECTION", payload: { hostname, interactionId, }, }); }, 500); // Store sendResponse callback with the interaction ID pendingResponses.set(interactionId, sendResponse); } ); } }) .catch((error) => { console.error(error.message); }); } break; case "sendQort": { const { amount, hostname, address, description } = request.payload; 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", }); } 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: chrome.runtime.getURL("index.html?secondary=true"), type: "popup", width: windowWidth, height: windowHeight, left: leftPosition, top: 0, }, () => { removeDuplicateWindow(popupUrl) }); }); } const interactionId = Date.now().toString(); // Simple example; consider a better unique ID setTimeout(() => { chrome.runtime.sendMessage({ action: "SET_COUNTDOWN", payload: (request.timeout ? request.timeout : 60) - 6, }); chrome.runtime.sendMessage({ action: "UPDATE_STATE_CONFIRM_SEND_QORT", payload: { amount, address, hostname, description, interactionId, }, }); }, 500); // Store sendResponse callback with the interaction ID pendingResponses.set(interactionId, sendResponse); } ); } break; case "responseToConnectionRequest": { const { hostname, isOkay } = request.payload; const interactionId3 = request.payload.interactionId; if (!isOkay) { const originalSendResponse = pendingResponses.get(interactionId3); if (originalSendResponse) { originalSendResponse(false); sendResponse(false); } } else { const originalSendResponse = pendingResponses.get(interactionId3); if (originalSendResponse) { // Example of setting domain permission chrome.storage.local.set({ [hostname]: true }); originalSendResponse(true); sendResponse(true); } } pendingResponses.delete(interactionId3); } break; case "sendQortConfirmation": const { password, amount, receiver, isDecline } = request.payload; const interactionId2 = request.payload.interactionId; // Retrieve the stored sendResponse callback const originalSendResponse = pendingResponses.get(interactionId2); if (originalSendResponse) { if (isDecline) { originalSendResponse({ error: "User has declined" }); sendResponse(false); pendingResponses.delete(interactionId2); return; } sendCoin({ password, amount, receiver }, true) .then((res) => { sendResponse(true); // Use the sendResponse callback to respond to the original message originalSendResponse(res); // Remove the callback from the Map as it's no longer needed pendingResponses.delete(interactionId2); // chrome.runtime.sendMessage({ // action: "closePopup", // }); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); originalSendResponse({ error: error.message }); }); } break; case "buyOrderConfirmation": { const { crosschainAtInfo, isDecline } = request.payload; const interactionId2 = request.payload.interactionId; // Retrieve the stored sendResponse callback const originalSendResponse = pendingResponses.get(interactionId2); if (originalSendResponse) { if (isDecline) { originalSendResponse({ error: "User has declined" }); sendResponse(false); pendingResponses.delete(interactionId2); return; } createBuyOrderTx({ crosschainAtInfo }) .then((res) => { sendResponse(true); originalSendResponse(res); pendingResponses.delete(interactionId2); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); // originalSendResponse({ error: error.message }); }); } } break; case "encryptAndPublishSymmetricKeyGroupChat": { const { groupId, previousData, previousNumber } = request.payload; encryptAndPublishSymmetricKeyGroupChat({ groupId, previousData, previousNumber, }) .then(({data, numberOfMembers}) => { sendResponse(data); if(!previousData){ // first secret key of the group sendChatGroup({ groupId, typeMessage: undefined, chatReference: undefined, messageText: PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY, }) .then(() => {}) .catch((error) => { console.error('1',error.message); }); return } sendChatNotification(data, groupId, previousData, numberOfMembers) }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "publishGroupEncryptedResource": { const { encryptedData, identifier } = request.payload; publishGroupEncryptedResource({ encryptedData, identifier }) .then((data) => { sendResponse(data); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); return true break; } case "handleActiveGroupDataFromSocket": { const { groups, directs } = request.payload; handleActiveGroupDataFromSocket({ groups, directs }) .then((data) => { sendResponse(true); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "getThreadActivity": { checkThreads(true) .then((data) => { sendResponse(data); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "updateThreadActivity": { const { threadId, qortalName, groupId, thread } = request.payload; updateThreadActivity({threadId, qortalName, groupId, thread}) .then(() => { sendResponse(true); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "decryptGroupEncryption": { const { data } = request.payload; decryptGroupEncryption({ data }) .then((res) => { sendResponse(res); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "encryptSingle": { const { data, secretKeyObject } = request.payload; encryptSingle({ data64: data, secretKeyObject: secretKeyObject }) .then((res) => { sendResponse(res); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "decryptSingle": { const { data, secretKeyObject, skipDecodeBase64 } = request.payload; decryptSingleFunc({ messages: data, secretKeyObject, skipDecodeBase64 }) .then((res) => { sendResponse(res); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "pauseAllQueues": { pauseAllQueues() sendResponse(res); break; break; } case "resumeAllQueues": { resumeAllQueues() sendResponse(res); break; } case "decryptSingleForPublishes": { const { data, secretKeyObject, skipDecodeBase64 } = request.payload; decryptSingleForPublishes({ messages: data, secretKeyObject, skipDecodeBase64 }) .then((res) => { sendResponse(res); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "decryptDirect": { const { data, involvingAddress } = request.payload; decryptDirectFunc({ messages: data, involvingAddress }) .then((res) => { sendResponse(res); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "sendChatGroup": { const { groupId, typeMessage = undefined, chatReference = undefined, messageText, } = request.payload; sendChatGroup({ groupId, typeMessage, chatReference, messageText }) .then((res) => { sendResponse(res); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "sendChatDirect": { const { directTo, typeMessage = undefined, chatReference = undefined, messageText, } = request.payload; sendChatDirect({ directTo, chatReference, messageText, typeMessage }) .then((res) => { sendResponse(res); }) .catch((error) => { console.error(error.message); sendResponse({ error: error.message }); }); break; } case "setupGroupWebsocket": { checkNewMessages() checkThreads() // if(socket){ // if(groups){ // console.log('hasgroups1') // chrome.runtime.sendMessage({ // action: "SET_GROUPS", // payload: groups, // }); // } // if(directs){ // console.log('hasgroups1') // chrome.runtime.sendMessage({ // action: "SET_DIRECTS", // payload: directs, // }); // } // sendResponse(true) // return // } // setTimeout(() => { // // initWebsocketMessageGroup() // listenForNewGroupAnnouncements() // listenForThreadUpdates() // }, 200); sendResponse(true) break; } case "logout": { try { const logoutFunc = async()=> { forceCloseWebSocket() clearAllQueues() if(interval){ // for announcement notification clearInterval(interval) } const wallet = await getSaveWallet(); const address = wallet.address0; const key1 = `tempPublish-${address}` chrome.storage.local.remove(["keyPair", "walletInfo", "apiKey", "active-groups-directs", key1], () => { if (chrome.runtime.lastError) { // Handle error console.error(chrome.runtime.lastError.message); } else { chrome.tabs.query({}, function (tabs) { tabs.forEach((tab) => { chrome.tabs.sendMessage(tab.id, { type: "LOGOUT" }); }); }); // Data removed successfully sendResponse(true); } }); } logoutFunc() } catch (error) { } } break; } } return true; }); // Function to save window position and size const saveWindowBounds = (windowId) => { chrome.windows.get(windowId, (window) => { const { top, left, width, height } = window; chrome.storage.local.set({ windowBounds: { top, left, width, height } }, () => { console.log('Window bounds saved:', { top, left, width, height }); }); }); }; // Function to restore window position and size const restoreWindowBounds = (callback) => { chrome.storage.local.get('windowBounds', (data) => { if (data.windowBounds) { callback(data.windowBounds); } else { callback(null); // No saved bounds, use default size/position } }); }; chrome.action?.onClicked?.addListener((tab) => { const popupUrl = chrome.runtime.getURL("index.html?main=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) => { return 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", }); } else { // No existing popup found, restore the saved bounds or create a new one restoreWindowBounds((savedBounds) => { 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 screenHeight = primaryDisplay.bounds.height; // Create a new window that uses the saved bounds if available chrome.windows.create({ url: chrome.runtime.getURL("index.html?main=true"), type: "popup", width: savedBounds ? savedBounds.width : screenWidth, height: savedBounds ? savedBounds.height : screenHeight, left: savedBounds ? savedBounds.left : 0, top: savedBounds ? savedBounds.top : 0, }, (newWindow) => { // Listen for changes in the window's size or position and save them chrome.windows.onBoundsChanged.addListener((window) => { if (window.id === newWindow.id) { saveWindowBounds(newWindow.id); } }); // Save the final window bounds when the window is closed chrome.windows.onRemoved.addListener((windowId) => { if (windowId === newWindow.id) { saveWindowBounds(windowId); // Save the position/size before it’s closed } }); }); }); }); } const interactionId = Date.now().toString(); // Simple example; consider a better unique ID setTimeout(() => { chrome.runtime.sendMessage({ action: "INITIATE_MAIN", payload: {}, }); }, 500); // Store sendResponse callback with the interaction ID pendingResponses.set(interactionId, sendResponse); } ); }); const checkGroupList = async() => { try { const wallet = await getSaveWallet(); const address = wallet.address0; const url = await createEndpoint(`/chat/active/${address}`); const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", }, }); const data = await response.json(); const filteredGroups = data.groups?.filter(item => item?.groupId !== 0) || []; const sortedGroups = filteredGroups.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); const sortedDirects = (data?.direct || []).filter(item => item?.name !== 'extension-proxy' && item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' ).sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); handleActiveGroupDataFromSocket({ groups: sortedGroups, directs: sortedDirects }) } catch (error) { console.error(error) } finally { } } const checkActiveChatsForNotifications = async ()=> { try { const popupUrl = chrome.runtime.getURL("index.html?main=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) => { return w.tabs && w.tabs.some((tab) => tab.url && tab.url.startsWith(popupUrl)); } ); if (existingPopup) { } else { checkGroupList() } } ); } catch (error) { } } chrome.notifications?.onClicked?.addListener( (notificationId) => { const popupUrl = chrome.runtime.getURL("index.html?main=true"); const isDirect = notificationId.includes('_type=direct_'); const isGroup = notificationId.includes('_type=group_'); const isGroupAnnouncement = notificationId.includes('_type=group-announcement_'); const isNewThreadPost = notificationId.includes('_type=thread-post_'); let isExisting = false chrome.windows.getAll( { populate: true, windowTypes: ["popup"] }, async (windows) => { // Attempt to find an existing popup window that has a tab with the correct URL const existingPopup = windows.find( (w) => { return 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", }); isExisting = true } else { // No existing popup found, restore saved bounds or create a new one restoreWindowBounds((savedBounds) => { 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 screenHeight = primaryDisplay.bounds.height; // Create a new window that takes up the full screen or uses saved bounds chrome.windows.create({ url: chrome.runtime.getURL("index.html?main=true"), type: "popup", width: savedBounds ? savedBounds.width : screenWidth, height: savedBounds ? savedBounds.height : screenHeight, left: savedBounds ? savedBounds.left : 0, top: savedBounds ? savedBounds.top : 0, }, (newWindow) => { // Listen for changes in the window's size or position and save them chrome.windows.onBoundsChanged.addListener((window) => { if (window.id === newWindow.id) { saveWindowBounds(newWindow.id); } }); // Save the final window bounds when the window is closed chrome.windows.onRemoved.addListener((windowId) => { if (windowId === newWindow.id) { saveWindowBounds(windowId); // Save the position/size before it’s closed } }); }); }); }); } const activeData = await getStoredData('active-groups-directs') || { groups: [], directs: [] }; setTimeout(() => { chrome.runtime.sendMessage({ action: "SET_GROUPS", payload: activeData?.groups || [], }); chrome.runtime.sendMessage({ action: "SET_DIRECTS", payload: activeData?.directs || [], }); }, isExisting ? 100 : 1000); const interactionId = Date.now().toString(); // Simple example; consider a better unique ID setTimeout(() => { chrome.runtime.sendMessage({ action: "INITIATE_MAIN", payload: {}, }); // Handle different types of notifications if (isDirect) { const fromValue = notificationId.split('_from=')[1]; chrome.runtime.sendMessage({ action: "NOTIFICATION_OPEN_DIRECT", payload: { from: fromValue }, }); } else if (isGroup) { const fromValue = notificationId.split('_from=')[1]; chrome.runtime.sendMessage({ action: "NOTIFICATION_OPEN_GROUP", payload: { from: fromValue }, }); } else if (isGroupAnnouncement) { const fromValue = notificationId.split('_from=')[1]; chrome.runtime.sendMessage({ action: "NOTIFICATION_OPEN_ANNOUNCEMENT_GROUP", payload: { from: fromValue }, }); } else if (isNewThreadPost) { const dataValue = notificationId.split('_data=')[1]; const dataParsed = JSON.parse(dataValue); chrome.runtime.sendMessage({ action: "NOTIFICATION_OPEN_THREAD_NEW_POST", payload: { data: dataParsed }, }); } }, isExisting ? 400 : 3000); // Store sendResponse callback with the interaction ID pendingResponses.set(interactionId, sendResponse); } ); }); // Reconnect when service worker wakes up chrome.runtime?.onStartup.addListener(() => { console.log("Service worker started up, reconnecting WebSocket..."); // initWebsocketMessageGroup(); // listenForNewGroupAnnouncements() // listenForThreadUpdates() }); chrome.runtime?.onInstalled.addListener((details) => { if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) { console.log('Extension Installed'); // Perform tasks that should only happen on extension installation // Example: Initialize WebSocket, set default settings, etc. } else if (details.reason === chrome.runtime.OnInstalledReason.UPDATE) { console.log('Extension Updated'); // Handle the update logic here (e.g., migrate settings) } else if (details.reason === chrome.runtime.OnInstalledReason.CHROME_UPDATE) { console.log('Chrome updated'); // Optional: Handle Chrome-specific updates if necessary } // Initialize WebSocket and other required listeners // initWebsocketMessageGroup(); // listenForNewGroupAnnouncements(); // listenForThreadUpdates(); }); // Check if the alarm already exists before creating it chrome.alarms?.get("checkForNotifications", (existingAlarm) => { if (!existingAlarm) { // If the alarm does not exist, create it chrome.alarms.create("checkForNotifications", { periodInMinutes: 4 }); } }); chrome.alarms?.onAlarm.addListener(async (alarm) => { try { if (alarm.name === "checkForNotifications") { // initWebsocketMessageGroup(address); const wallet = await getSaveWallet(); const address = wallet.address0; if(!address) return checkActiveChatsForNotifications() checkNewMessages() checkThreads() } } catch (error) { } });