qortal-mobile/src/background.ts

4609 lines
138 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @ts-nocheck
// import { encryptAndPublishSymmetricKeyGroupChat } from "./backgroundFunctions/encryption";
import './qortalRequests'
import { constant, isArray } from "lodash";
import {
decryptGroupEncryption,
encryptAndPublishSymmetricKeyGroupChat,
publishGroupEncryptedResource,
publishOnQDN,
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";
import { TradeBotRespondMultipleRequest } from "./transactions/TradeBotRespondMultipleRequest";
import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from "./constants/resourceTypes";
export function cleanUrl(url) {
return url?.replace(/^(https?:\/\/)?(www\.)?/, '');
}
export function getProtocol(url) {
if (url?.startsWith('https://')) {
return 'https';
} else if (url?.startsWith('http://')) {
return 'http';
} else {
return 'unknown'; // If neither protocol is present
}
}
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
});
});
};
const getCustomNodesFromStorage = async () => {
return new Promise((resolve, reject) => {
chrome.storage.local.get("customNodes", (result) => {
if (chrome.runtime.lastError) {
return reject(chrome.runtime.lastError);
}
resolve(result.customNodes || null); // Return null if apiKey isn't found
});
});
};
// const getArbitraryEndpoint = ()=> {
// const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
// if (apiKey) {
// return `/arbitrary/resources/search`;
// } else {
// return `/arbitrary/resources/searchsimple`;
// }
// }
const getArbitraryEndpoint = async () => {
const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
if (apiKey) {
return `/arbitrary/resources/searchsimple`;
} else {
return `/arbitrary/resources/searchsimple`;
}
};
export const getBaseApi = async (customApi?: string) => {
if (customApi) {
return customApi;
}
const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
if (apiKey) {
return apiKey?.url;
} else {
return groupApi;
}
};
export const isUsingLocal = async () => {
const apiKey = await getApiKeyFromStorage(); // Retrieve apiKey asynchronously
if (apiKey) {
return true
} else {
return false;
}
};
export const createEndpoint = async (endpoint, customApi?: string) => {
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 `${apiKey?.url}${endpoint}${separator}apiKey=${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");
}
export function isExtMsg(data) {
let isMsgFromExtensionGroup = true;
try {
const decode1 = atob(data);
const decode2 = atob(decode1);
const keyStr = decode2.slice(0, 10);
// Convert the key string back to a number
const highestKey = parseInt(keyStr, 10);
if (isNaN(highestKey)) {
isMsgFromExtensionGroup = false;
}
} catch (error) {
isMsgFromExtensionGroup = false;
}
return isMsgFromExtensionGroup;
}
export function isUpdateMsg(data) {
let isUpdateMessage = true;
try {
const decode1 = atob(data);
const decode2 = atob(decode1);
const keyStr = decode2.slice(10, 13);
// Convert the key string back to a number
const numberKey = parseInt(keyStr, 10);
if (isNaN(numberKey)) {
isUpdateMessage = false;
} else if(numberKey !== RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS){
isUpdateMessage = false;
}
} catch (error) {
isUpdateMessage = false;
}
return isUpdateMessage;
}
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;
let isDisableNotifications = await getUserSettings({key: 'disable-push-notifications'}) || false
const dataDirects = directs.filter((direct) => direct?.sender !== address);
try {
if(isDisableNotifications) return
if (!dataDirects || dataDirects?.length === 0) return;
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 isDisableNotifications = await getUserSettings({key: 'disable-push-notifications'}) || false
let mutedGroups = await getUserSettings({key: 'mutedGroups'}) || []
if(!isArray(mutedGroups)) mutedGroups = []
let isFocused;
const data = groups.filter((group) => group?.sender !== address && !mutedGroups.includes(group.groupId) && !isUpdateMsg(group?.data));
const dataWithUpdates = groups.filter((group) => group?.sender !== address && !mutedGroups.includes(group.groupId));
try {
if(isDisableNotifications) return
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
) {
if (
!newestLatestTimestamp?.data ||
!isExtMsg(newestLatestTimestamp?.data)
)
return;
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(dataWithUpdates);
// 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 endpoint = await getArbitraryEndpoint();
const url = await createEndpoint(
`${endpoint}?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])}`;
let isDisableNotifications = await getUserSettings({key: 'disable-push-notifications'}) || false
if(!isDisableNotifications){
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 mutedGroups = await getUserSettings({key: 'mutedGroups'}) || []
if(!isArray(mutedGroups)) mutedGroups = []
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 endpoint = await getArbitraryEndpoint();
const url = await createEndpoint(
`${endpoint}?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
}
})
);
let isDisableNotifications = await getUserSettings({key: 'disable-push-notifications'}) || false
if (newAnnouncements.length > 0 && !mutedGroups.includes(newAnnouncements[0]?.groupId) && !isDisableNotifications) {
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;
}
export async function getKeyPair() {
const res = await chrome.storage.local.get(["keyPair"]);
if (res?.keyPair) {
return res.keyPair;
} else {
throw new Error("Wallet not authenticated");
}
}
export 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 clearAllNotifications() {
const notifications = await chrome.notifications.getAll();
for (const notificationId of Object.keys(notifications)) {
await chrome.notifications.clear(notificationId);
}
}
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 getTradesInfo(qortalAtAddresses) {
// Use Promise.all to fetch data for all addresses concurrently
const trades = await Promise.all(
qortalAtAddresses.map((address) => getTradeInfo(address))
);
return trades; // Return the array of trade info objects
}
export 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();
}
});
};
export 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;
};
export 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;
};
export const sendQortFee = async (): Promise<number> => {
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");
}
}
export 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");
}
}
const MAX_STORAGE_SIZE = 3 * 1024 * 1024; // 3MB in bytes
async function getDataPublishes(groupId, type) {
const wallet = await getSaveWallet();
const address = wallet.address0;
return new Promise((resolve) => {
chrome.storage.local.get([`${address}-publishData`], (result) => {
if (chrome.runtime.lastError) {
console.error("Error retrieving data:", chrome.runtime.lastError);
resolve(null); // Return null in case of an error
return;
}
let storedData = result[`${address}-publishData`] || {}; // Get the stored data or initialize an empty object
let groupData = storedData[groupId] || {}; // Get data by groupId
let typeData = groupData[type] || {}; // Get data by type
resolve(typeData); // Resolve with the data inside the specific type
});
});
}
async function addDataPublishes(newData, groupId, type) {
const wallet = await getSaveWallet();
const address = wallet.address0;
const nameIdentifier = `${newData.name}-${newData.identifier}`;
// Prevent adding data larger than 50KB
if (newData?.size > 50000) return false;
return new Promise((res) => {
chrome.storage.local.get([`${address}-publishData`], (result) => {
let storedData = result[`${address}-publishData`] || {}; // Get existing data or initialize
let groupData = storedData[groupId] || {}; // Get or initialize group by groupId
let typeData = groupData[type] || {}; // Get or initialize the type within the group
let totalSize = 0;
// Calculate the current size of all stored data
Object.values(storedData).forEach((group) => {
Object.values(group).forEach((type) => {
Object.values(type).forEach((data) => {
totalSize += data.size; // Accumulate the sizes of actual data
});
});
});
// Check if adding the new data exceeds 3MB
if (totalSize + newData.size > MAX_STORAGE_SIZE) {
// Sort and remove older data within the group and type
let dataEntries = Object.entries(typeData);
dataEntries.sort((a, b) => a[1].timestampSaved - b[1].timestampSaved);
// Remove old data until there's enough space
while (
totalSize + newData.size > MAX_STORAGE_SIZE &&
dataEntries.length > 0
) {
const removedEntry = dataEntries.shift();
totalSize -= removedEntry[1].size;
delete typeData[removedEntry[0]]; // Remove from the typeData
}
}
// Add or update the new data within the group and type
if (totalSize + newData.size <= MAX_STORAGE_SIZE) {
typeData[`${nameIdentifier}`] = newData; // Add new data under name-identifier
groupData[type] = typeData; // Update type data within the group
storedData[groupId] = groupData; // Update group data within the stored data
// Save the updated structure back to chrome.storage.local
chrome.storage.local.set(
{ [`${address}-publishData`]: storedData },
() => {
res(true); // Data successfully added
}
);
} else {
console.error("Failed to add data, still exceeds storage limit.");
res(false); // Failure due to storage limit
}
});
});
}
// Fetch user settings based on the key
async function getUserSettings({ key }) {
const wallet = await getSaveWallet();
const address = wallet.address0;
return new Promise((resolve) => {
chrome.storage.local.get([`${address}-userSettings`], (result) => {
if (chrome.runtime.lastError) {
console.error("Error retrieving data:", chrome.runtime.lastError);
resolve(null); // Return null in case of an error
return;
}
const storedData = result[`${address}-userSettings`] || {}; // Get the stored data or initialize an empty object
const value = storedData[key] || null; // Get data by key
resolve(value); // Resolve with the data for the specific key
});
});
}
// Add or update user settings
async function addUserSettings({ keyValue }) {
const wallet = await getSaveWallet();
const address = wallet.address0;
const { key, value } = keyValue;
// No need to check size here, unless value is a large object. For simple settings, size checks aren't necessary.
return new Promise((res) => {
chrome.storage.local.get([`${address}-userSettings`], (result) => {
let storedData = result[`${address}-userSettings`] || {}; // Get existing data or initialize
storedData[key] = value; // Update the key-value pair within the stored data
// Save the updated structure back to chrome.storage.local
chrome.storage.local.set(
{ [`${address}-userSettings`]: storedData },
() => {
res(true); // Data successfully added
}
);
});
});
}
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,
arrrSeed58: wallet2._addresses[0].arrrWallet.seed58,
btcAddress: wallet2._addresses[0].btcWallet.address,
btcPublicKey: wallet2._addresses[0].btcWallet.derivedMasterPublicKey,
btcPrivateKey: wallet2._addresses[0].btcWallet.derivedMasterPrivateKey,
ltcAddress: wallet2._addresses[0].ltcWallet.address,
dogeAddress: wallet2._addresses[0].dogeWallet.address,
dogePublicKey: wallet2._addresses[0].dogeWallet.derivedMasterPublicKey,
dogePrivateKey: wallet2._addresses[0].dogeWallet.derivedMasterPrivateKey,
dgbAddress: wallet2._addresses[0].dgbWallet.address,
dgbPublicKey: wallet2._addresses[0].dgbWallet.derivedMasterPublicKey,
dgbPrivateKey: wallet2._addresses[0].dgbWallet.derivedMasterPrivateKey,
rvnAddress: wallet2._addresses[0].rvnWallet.address,
rvnPublicKey: wallet2._addresses[0].rvnWallet.derivedMasterPublicKey,
rvnPrivateKey: wallet2._addresses[0].rvnWallet.derivedMasterPrivateKey
};
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);
}
}
export 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;
}
export 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 = {
addresses: message.addresses,
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 txBody = {
timestamp: Date.now(),
groupID: Number(groupId),
hasReceipient: 0,
hasChatReference: chatReference ? 1 : 0,
message: messageText,
lastReference: reference,
proofOfWorkNonce: 0,
isEncrypted: 0, // Set default to not encrypted for groups
isText: 1,
}
if(chatReference){
txBody['chatReference'] = chatReference
}
const tx = await createTransaction(181, keyPair, txBody);
// 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({
address,
directTo,
typeMessage,
chatReference,
messageText,
publicKeyOfRecipient,
otherData
}) {
let recipientPublicKey;
let recipientAddress = address;
if (publicKeyOfRecipient) {
recipientPublicKey = publicKeyOfRecipient;
} else {
recipientAddress = await getNameOrAddress(directTo);
recipientPublicKey = await getPublicKey(recipientAddress);
}
if (!recipientAddress) {
recipientAddress = await getNameOrAddress(directTo);
}
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,
...(otherData || {})
};
const messageStringified = JSON.stringify(finalJson);
const txBody = {
timestamp: Date.now(),
recipient: recipientAddress,
recipientPublicKey: recipientPublicKey,
hasChatReference: chatReference ? 1 : 0,
message: messageStringified,
lastReference: reference,
proofOfWorkNonce: 0,
isEncrypted: 1,
isText: 1,
}
if(chatReference){
txBody['chatReference'] = chatReference
}
const tx = await createTransaction(18, keyPair, txBody);
// 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, decryptedData: 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, useLocal }) {
try {
if(useLocal){
const wallet = await getSaveWallet();
const address = wallet.address0;
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const message = {
addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
foreignKey: parsedData.ltcPrivateKey,
receivingAddress: address,
};
let responseVar
const txn = new TradeBotRespondMultipleRequest().createTransaction(message)
const apiKey = await getApiKeyFromStorage();
const responseFetch = await fetch(`${apiKey?.url}/crosschain/tradebot/respondmultiple?apiKey=${apiKey?.apikey}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(txn),
});
const res = await responseFetch.json();
if(res === false){
responseVar = { response: "Unable to execute buy order", success: false };
} else {
responseVar = { response: res, success: true };
}
const { response, success } = responseVar
let responseMessage;
if (success) {
responseMessage = {
callResponse: response,
extra: {
message: 'Transaction processed successfully!',
atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
}
};
} else {
responseMessage = {
callResponse: 'ERROR',
extra: {
message: response,
atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
}
};
}
setTimeout(() => {
chrome.tabs.query({}, function (tabs) {
tabs.forEach((tab) => {
chrome.tabs.sendMessage(tab.id, {
type: "RESPONSE_FOR_TRADES",
message: responseMessage,
});
});
});
}, 5000);
return
}
const wallet = await getSaveWallet();
const address = wallet.address0;
const resKeyPair = await getKeyPair();
const parsedData = JSON.parse(resKeyPair);
const message = {
addresses: crosschainAtInfo.map((order)=> order.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 {
atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
encryptedMessageToBase58: res?.encryptedMessageToBase58,
node: buyTradeNodeBaseUrl,
qortAddress: address,
chatSignature: res?.signature,
senderPublicKey: parsedData.publicKey,
sender: address,
reference: res?.reference,
};
}
return {
atAddresses: crosschainAtInfo.map((order)=> order.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}&timestamp=${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;
}
export 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;
}
export 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);
}
}
export 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 checkLocalFunc(){
const apiKey = await getApiKeyFromStorage()
return !!apiKey
}
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 getGroupData() {
const wallet = await getSaveWallet();
const address = wallet.address0;
const key = `group-data-${address}`;
const res = await chrome.storage.local.get([key]);
if (res?.[key]) {
const parsedData = JSON.parse(res[key]);
return parsedData;
} else {
return {};
}
}
async function getGroupDataSingle(groupId) {
const wallet = await getSaveWallet();
const address = wallet.address0;
const key = `group-data-${address}`;
const res = await chrome.storage.local.get([key]);
if (res?.[key]) {
const parsedData = JSON.parse(res[key]);
return parsedData[groupId] || null;
} else {
return null;
}
}
async function setGroupData({
groupId,
secretKeyData,
secretKeyResource,
admins,
}) {
const wallet = await getSaveWallet();
const address = wallet.address0;
const data = (await getGroupData()) || {};
data[groupId] = {
timestampLastSet: Date.now(),
admins,
secretKeyData,
secretKeyResource,
};
const dataString = JSON.stringify(data);
return await new Promise((resolve, reject) => {
chrome.storage.local.set({ [`group-data-${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: `<p>Member ${nameOrAddress} has requested that you regenerate the group's secret key. Group: ${groupName}</p>`,
});
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) {
console.log('REQUEST MESSAGE', 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 "addDataPublishes":
{
const { data, groupId, type } = request.payload;
addDataPublishes(data, groupId, type)
.then((res) => {
sendResponse(res);
})
.catch((error) => {
sendResponse({ error: error.message });
console.error(error.message);
});
}
break;
case "getDataPublishes":
{
const { groupId, type } = request.payload;
getDataPublishes(groupId, type)
.then((res) => {
sendResponse(res);
})
.catch((error) => {
sendResponse({ error: error.message });
console.error(error.message);
});
}
break;
case "addUserSettings":
{
const { keyValue } = request.payload;
addUserSettings({keyValue})
.then((res) => {
sendResponse(res);
})
.catch((error) => {
sendResponse({ error: error.message });
console.error(error.message);
});
}
break;
case "getUserSettings":
{
const { key } = request.payload;
getUserSettings({key})
.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 "setCustomNodes": {
const { nodes } = request;
// Save the customNodes in chrome.storage.local for persistence
chrome.storage.local.set({ customNodes: nodes }, () => {
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 "getCustomNodesFromStorage": {
getCustomNodesFromStorage()
.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 "clearAllNotifications": {
clearAllNotifications()
.then((res) => {})
.catch((error) => {});
break;
}
case "setGroupData": {
const { groupId, secretKeyData, secretKeyResource, admins } =
request.payload;
setGroupData({
groupId,
secretKeyData,
secretKeyResource,
admins,
})
.then((res) => {
sendResponse(res);
})
.catch((error) => {
sendResponse({ error: error.message });
console.error(error.message);
});
break;
}
case "getGroupDataSingle": {
const { groupId } = request.payload;
getGroupDataSingle(groupId)
.then((res) => {
sendResponse(res);
})
.catch((error) => {
sendResponse({ error: error.message });
console.error(error.message);
});
return true;
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 { qortalAtAddresses, hostname, useLocal } = request.payload;
getTradesInfo(qortalAtAddresses)
.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,
useLocal
},
});
}, 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, useLocal } = 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, useLocal })
.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 "publishOnQDN": {
const { data, identifier, service, title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5, uploadType } = request.payload;
publishOnQDN({
data,
identifier,
service,
title,
description,
category,
tag1,
tag2,
tag3,
tag4,
tag5,
uploadType
})
.then((data) => {
sendResponse(data);
})
.catch((error) => {
console.error(error?.message);
sendResponse({ error: error?.message || 'Unable to publish' });
});
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, typeNumber } = request.payload;
encryptSingle({ data64: data, secretKeyObject: secretKeyObject, typeNumber })
.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(true);
break;
}
case "resumeAllQueues": {
resumeAllQueues();
sendResponse(true);
break;
}
case "checkLocal": {
checkLocalFunc()
.then((res) => {
sendResponse(res);
})
.catch((error) => {
console.error(error.message);
sendResponse({ error: error.message });
});
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,
publicKeyOfRecipient,
address,
otherData
} = request.payload;
sendChatDirect({
directTo,
chatReference,
messageText,
typeMessage,
publicKeyOfRecipient,
address,
otherData
})
.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}`;
const key2 = `group-data-${address}`;
const key3 = `${address}-publishData`;
chrome.storage.local.remove(
[
"keyPair",
"walletInfo",
"active-groups-directs",
key1,
key2,
key3,
],
() => {
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
if (isMobile) {
const correctTab = existingPopup.tabs.find(
(tab) => tab.url && tab.url.startsWith(popupUrl)
);
if (correctTab) {
chrome.tabs.update(correctTab.id, { active: true });
chrome.windows.update(existingPopup.id, {
focused: true,
state: "normal",
});
}
} else {
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 its 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 its 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: 10 });
}
});
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) {}
});