mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-21 10:27:52 +00:00
3287 lines
95 KiB
TypeScript
3287 lines
95 KiB
TypeScript
// @ts-nocheck
|
||
|
||
import "./qortalRequests";
|
||
import { isArray } from "lodash";
|
||
import {
|
||
decryptGroupEncryption,
|
||
encryptAndPublishSymmetricKeyGroupChat,
|
||
publishGroupEncryptedResource,
|
||
publishOnQDN,
|
||
uint8ArrayToObject,
|
||
} from "./backgroundFunctions/encryption";
|
||
import { PUBLIC_NOTIFICATION_CODE_FIRST_SECRET_KEY } from "./constants/codes";
|
||
import Base58 from "./deps/Base58";
|
||
import axios from 'axios'
|
||
import {
|
||
base64ToUint8Array,
|
||
decryptSingle,
|
||
encryptDataGroup,
|
||
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";
|
||
import {
|
||
addDataPublishesCase,
|
||
addGroupNotificationTimestampCase,
|
||
addTimestampEnterChatCase,
|
||
addUserSettingsCase,
|
||
balanceCase,
|
||
banFromGroupCase,
|
||
cancelBanCase,
|
||
cancelInvitationToGroupCase,
|
||
checkLocalCase,
|
||
clearAllNotificationsCase,
|
||
createGroupCase,
|
||
decryptDirectCase,
|
||
decryptGroupEncryptionCase,
|
||
decryptSingleCase,
|
||
decryptSingleForPublishesCase,
|
||
decryptWalletCase,
|
||
encryptAndPublishSymmetricKeyGroupChatCase,
|
||
encryptSingleCase,
|
||
getApiKeyCase,
|
||
getCustomNodesFromStorageCase,
|
||
getDataPublishesCase,
|
||
getGroupDataSingleCase,
|
||
getGroupNotificationTimestampCase,
|
||
getTempPublishCase,
|
||
getThreadActivityCase,
|
||
getTimestampEnterChatCase,
|
||
getUserSettingsCase,
|
||
getWalletInfoCase,
|
||
handleActiveGroupDataFromSocketCase,
|
||
inviteToGroupCase,
|
||
joinGroupCase,
|
||
kickFromGroupCase,
|
||
leaveGroupCase,
|
||
ltcBalanceCase,
|
||
makeAdminCase,
|
||
nameCase,
|
||
notificationCase,
|
||
notifyAdminRegenerateSecretKeyCase,
|
||
pauseAllQueuesCase,
|
||
publishGroupEncryptedResourceCase,
|
||
publishOnQDNCase,
|
||
registerNameCase,
|
||
removeAdminCase,
|
||
resumeAllQueuesCase,
|
||
saveTempPublishCase,
|
||
sendChatDirectCase,
|
||
sendChatGroupCase,
|
||
sendCoinCase,
|
||
setApiKeyCase,
|
||
setChatHeadsCase,
|
||
setCustomNodesCase,
|
||
setGroupDataCase,
|
||
setupGroupWebsocketCase,
|
||
updateThreadActivityCase,
|
||
userInfoCase,
|
||
validApiCase,
|
||
versionCase,
|
||
} from "./background-cases";
|
||
import { getData, removeKeysAndLogout, storeData } from "./utils/chromeStorage";
|
||
// import {BackgroundFetch} from '@transistorsoft/capacitor-background-fetch';
|
||
|
||
|
||
|
||
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 = 86400000;
|
||
const requestQueueAnnouncements = new RequestQueueWithPromise(1);
|
||
let isMobile = true;
|
||
|
||
function handleNotificationClick(notificationId) {
|
||
// Decode the notificationId if it was encoded
|
||
const decodedNotificationId = decodeURIComponent(notificationId);
|
||
|
||
// Determine the type of notification by parsing decodedNotificationId
|
||
const isDirect = decodedNotificationId.includes("_type=direct_");
|
||
const isGroup = decodedNotificationId.includes("_type=group_");
|
||
const isGroupAnnouncement = decodedNotificationId.includes("_type=group-announcement_");
|
||
const isNewThreadPost = decodedNotificationId.includes("_type=thread-post_");
|
||
|
||
// Helper function to extract parameter values safely
|
||
function getParameterValue(id, key) {
|
||
const match = id.match(new RegExp(`${key}=([^_]+)`));
|
||
return match ? match[1] : null;
|
||
}
|
||
const targetOrigin = window.location.origin;
|
||
// Handle specific notification types and post the message accordingly
|
||
if (isDirect) {
|
||
const fromValue = getParameterValue(decodedNotificationId, "_from");
|
||
window.postMessage(
|
||
{ action: "NOTIFICATION_OPEN_DIRECT", payload: { from: fromValue } },
|
||
targetOrigin
|
||
);
|
||
} else if (isGroup) {
|
||
const fromValue = getParameterValue(decodedNotificationId, "_from");
|
||
window.postMessage(
|
||
{ action: "NOTIFICATION_OPEN_GROUP", payload: { from: fromValue } },
|
||
targetOrigin
|
||
);
|
||
} else if (isGroupAnnouncement) {
|
||
const fromValue = getParameterValue(decodedNotificationId, "_from");
|
||
window.postMessage(
|
||
{
|
||
action: "NOTIFICATION_OPEN_ANNOUNCEMENT_GROUP",
|
||
payload: { from: fromValue },
|
||
},
|
||
targetOrigin
|
||
);
|
||
} else if (isNewThreadPost) {
|
||
const dataValue = getParameterValue(decodedNotificationId, "_data");
|
||
try {
|
||
const targetOrigin = window.location.origin;
|
||
const dataParsed = JSON.parse(dataValue);
|
||
window.postMessage(
|
||
{
|
||
action: "NOTIFICATION_OPEN_THREAD_NEW_POST",
|
||
payload: { data: dataParsed },
|
||
},
|
||
targetOrigin
|
||
);
|
||
} catch (error) {
|
||
console.error("Error parsing JSON data for thread post notification:", error);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
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);
|
||
}
|
||
});
|
||
};
|
||
|
||
export const pauseAllQueues = () => controlAllQueues("pause");
|
||
export const resumeAllQueues = () => controlAllQueues("resume");
|
||
const checkDifference = (createdTimestamp) => {
|
||
return (
|
||
Date.now() - createdTimestamp < timeDifferenceForNotificationChatsBackground
|
||
);
|
||
};
|
||
export const getApiKeyFromStorage = async (): Promise<string | null> => {
|
||
return getData<string>("apiKey").catch(() => null);
|
||
};
|
||
|
||
export const getCustomNodesFromStorage = async (): Promise<any | null> => {
|
||
return getData<any>("customNodes").catch(() => null);
|
||
};
|
||
|
||
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) => {
|
||
const timeout = setTimeout(() => {
|
||
resolve(false); // No response within 1 second, assume not focused
|
||
}, 1000);
|
||
const targetOrigin = window.location.origin;
|
||
// Send a message to check focus
|
||
window.postMessage({ action: "CHECK_FOCUS" }, targetOrigin);
|
||
|
||
// Listen for the response
|
||
const handleMessage = (event) => {
|
||
if (event.data?.action === "CHECK_FOCUS_RESPONSE") {
|
||
clearTimeout(timeout);
|
||
window.removeEventListener("message", handleMessage); // Clean up listener
|
||
resolve(event.data.isFocused); // Resolve with the response
|
||
}
|
||
};
|
||
|
||
window.addEventListener("message", handleMessage);
|
||
});
|
||
}
|
||
|
||
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 = null;
|
||
let oldestLatestTimestamp = null;
|
||
// 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)
|
||
) {
|
||
// Create the notification and assign the onclick handler
|
||
const title = `New Direct message! ${
|
||
newestLatestTimestamp?.name ? `from ${newestLatestTimestamp.name}` : ""
|
||
}`;
|
||
const body = "You have received a new direct message";
|
||
const notificationId =
|
||
encodeURIComponent("chat_notification_" +
|
||
Date.now() +
|
||
"_type=direct" +
|
||
`_from=${newestLatestTimestamp.address}`);
|
||
const notification = new window.Notification(title, {
|
||
body,
|
||
data: { id: notificationId },
|
||
});
|
||
|
||
// Set up the onclick event to call the handler function
|
||
notification.onclick = () => {
|
||
handleNotificationClick(notificationId);
|
||
notification.close();
|
||
};
|
||
|
||
setTimeout(() => {
|
||
notification.close();
|
||
}, 10000);
|
||
}
|
||
} catch (error) {
|
||
if (!isFocused) {
|
||
window
|
||
.sendMessage("notification", {})
|
||
.then((response) => {
|
||
if (!response?.error) {
|
||
// Handle success if needed
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.error(
|
||
"Failed to send notification:",
|
||
error.message || "An error occurred"
|
||
);
|
||
});
|
||
|
||
// Create a unique notification ID with type and sender information
|
||
const notificationId =
|
||
encodeURIComponent("chat_notification_" +
|
||
Date.now() +
|
||
"_type=direct" +
|
||
`_from=""`);
|
||
|
||
const title = "New Direct message!";
|
||
const body = "You have received a new direct message";
|
||
|
||
const notification = new window.Notification(title, {
|
||
body,
|
||
data: { id: notificationId },
|
||
});
|
||
|
||
// Handle notification click with specific actions based on `notificationId`
|
||
notification.onclick = () => {
|
||
handleNotificationClick(notificationId);
|
||
notification.close(); // Clean up the notification on click
|
||
};
|
||
|
||
// Automatically close the notification after 5 seconds if not clicked
|
||
setTimeout(() => {
|
||
notification.close();
|
||
}, 10000); // Close after 5 seconds
|
||
}
|
||
} finally {
|
||
setChatHeadsDirect(dataDirects);
|
||
}
|
||
};
|
||
async function getThreadActivity(): Promise<any | null> {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
const key = `threadactivity-${address}`;
|
||
|
||
return getData<any>(key).catch(() => null);
|
||
}
|
||
|
||
export function updateThreadActivity({
|
||
threadId,
|
||
qortalName,
|
||
groupId,
|
||
thread,
|
||
}: {
|
||
threadId: string;
|
||
qortalName: string;
|
||
groupId: string;
|
||
thread: any;
|
||
}) {
|
||
getSaveWallet().then((wallet) => {
|
||
const address = wallet.address0;
|
||
const ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000; // One week in milliseconds
|
||
const key = `threadactivity-${address}`;
|
||
const currentTime = Date.now();
|
||
|
||
// Retrieve the existing thread activity data
|
||
const storedData = localStorage.getItem(key);
|
||
|
||
let threads;
|
||
|
||
if (!storedData) {
|
||
// Initialize structure if no data found
|
||
threads = {
|
||
createdThreads: [],
|
||
mostVisitedThreads: [],
|
||
recentThreads: [],
|
||
lastResetTime: 0,
|
||
};
|
||
} else {
|
||
threads = JSON.parse(storedData);
|
||
}
|
||
|
||
let lastResetTime = threads.lastResetTime || 0;
|
||
|
||
// Check if a week has passed since the last reset
|
||
if (currentTime - lastResetTime > ONE_WEEK_IN_MS) {
|
||
// Reset visit counts and update the last reset time
|
||
threads.mostVisitedThreads.forEach((thread) => (thread.visitCount = 0));
|
||
threads.lastResetTime = currentTime;
|
||
}
|
||
|
||
// Update recent threads
|
||
threads.recentThreads = threads.recentThreads.filter(
|
||
(t) => t.threadId !== threadId
|
||
);
|
||
threads.recentThreads.unshift({
|
||
threadId,
|
||
qortalName,
|
||
groupId,
|
||
thread,
|
||
visitCount: 1,
|
||
lastVisited: currentTime,
|
||
});
|
||
threads.recentThreads = threads.recentThreads.slice(0, 2);
|
||
|
||
// Update most visited threads
|
||
const existingThread = threads.mostVisitedThreads.find(
|
||
(t) => t.threadId === threadId
|
||
);
|
||
if (existingThread) {
|
||
existingThread.visitCount += 1;
|
||
existingThread.lastVisited = currentTime;
|
||
} else {
|
||
threads.mostVisitedThreads.push({
|
||
threadId,
|
||
qortalName,
|
||
groupId,
|
||
thread,
|
||
visitCount: 1,
|
||
lastVisited: currentTime,
|
||
});
|
||
}
|
||
threads.mostVisitedThreads = threads.mostVisitedThreads.slice(0, 2);
|
||
|
||
// Save the updated data to localStorage without blocking
|
||
localStorage.setItem(key, JSON.stringify(threads));
|
||
});
|
||
}
|
||
|
||
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 = null;
|
||
let oldestLatestTimestamp = null;
|
||
// 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;
|
||
|
||
// Create a unique notification ID with type and group information
|
||
const notificationId =
|
||
encodeURIComponent("chat_notification_" +
|
||
Date.now() +
|
||
"_type=group" +
|
||
`_from=${newestLatestTimestamp.groupId}`);
|
||
|
||
const title = "New Group Message!";
|
||
const body = `You have received a new message from ${newestLatestTimestamp?.groupName}`;
|
||
|
||
const notification = new window.Notification(title, {
|
||
body,
|
||
data: { id: notificationId },
|
||
});
|
||
|
||
// Handle notification click with specific actions based on `notificationId`
|
||
notification.onclick = () => {
|
||
handleNotificationClick(notificationId);
|
||
notification.close(); // Clean up the notification on click
|
||
};
|
||
|
||
// Automatically close the notification after 5 seconds if not clicked
|
||
setTimeout(() => {
|
||
notification.close();
|
||
}, 10000); // Close after 5 seconds
|
||
|
||
lastGroupNotification = Date.now();
|
||
}
|
||
}
|
||
} catch (error) {
|
||
if (!isFocused) {
|
||
window
|
||
.sendMessage("notification", {})
|
||
.then((response) => {
|
||
if (!response?.error) {
|
||
// Handle success if needed
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.error(
|
||
"Failed to send notification:",
|
||
error.message || "An error occurred"
|
||
);
|
||
});
|
||
|
||
// Generate a unique notification ID
|
||
const notificationId = encodeURIComponent("chat_notification_" + Date.now());
|
||
|
||
const title = "New Group Message!";
|
||
const body = "You have received a new message from one of your groups";
|
||
|
||
// Create and show the notification immediately
|
||
const notification = new window.Notification(title, {
|
||
body,
|
||
data: { id: notificationId },
|
||
});
|
||
|
||
// Handle notification click, allowing specific actions based on `notificationId`
|
||
notification.onclick = () => {
|
||
handleNotificationClick(notificationId);
|
||
notification.close(); // Clean up the notification on click
|
||
};
|
||
|
||
// Automatically close the notification after 5 seconds if it’s not clicked
|
||
setTimeout(() => {
|
||
notification.close();
|
||
}, 10000); // Close after 5 seconds
|
||
|
||
lastGroupNotification = Date.now();
|
||
}
|
||
} finally {
|
||
if (!data || data?.length === 0) return;
|
||
setChatHeads(dataWithUpdates);
|
||
}
|
||
};
|
||
|
||
const forceCloseWebSocket = () => {
|
||
if (socket) {
|
||
clearTimeout(timeoutId);
|
||
clearTimeout(groupSocketTimeout);
|
||
clearTimeout(socketTimeout);
|
||
timeoutId = null;
|
||
groupSocketTimeout = null;
|
||
socket.close(1000, "forced");
|
||
socket = null;
|
||
}
|
||
};
|
||
|
||
export 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 getData<any>("keyPair").catch(() => null);
|
||
if (res) {
|
||
return res;
|
||
} else {
|
||
throw new Error("Wallet not authenticated");
|
||
}
|
||
}
|
||
|
||
export async function getSaveWallet() {
|
||
const res = await getData<any>("walletInfo").catch(() => null);
|
||
if (res) {
|
||
return res;
|
||
} else {
|
||
throw new Error("No wallet saved");
|
||
}
|
||
}
|
||
|
||
export async function clearAllNotifications() {
|
||
// const notifications = await chrome.notifications.getAll();
|
||
// for (const notificationId of Object.keys(notifications)) {
|
||
// await chrome.notifications.clear(notificationId);
|
||
// }
|
||
}
|
||
|
||
export 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: string) {
|
||
const isConnected = await getData<any>(hostname).catch(() => null);
|
||
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;
|
||
}
|
||
export async function getLTCBalance() {
|
||
const wallet = await getSaveWallet();
|
||
let _url = `${buyTradeNodeBaseUrl}/crosschain/ltc/walletbalance`;
|
||
const keyPair = await getKeyPair();
|
||
const parsedKeyPair = 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
|
||
|
||
export async function getDataPublishes(groupId, type) {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
|
||
return new Promise((resolve) => {
|
||
getData<any>(`${address}-publishData`)
|
||
.then((storedData) => {
|
||
storedData = storedData || {}; // Initialize an empty object if no data
|
||
const groupData = storedData[groupId] || {}; // Get data by groupId
|
||
const typeData = groupData[type] || {}; // Get data by type
|
||
|
||
resolve(typeData); // Resolve with the data inside the specific type
|
||
})
|
||
.catch((error) => {
|
||
console.error("Error retrieving data:", error);
|
||
resolve(null); // Return null in case of an error
|
||
});
|
||
});
|
||
}
|
||
|
||
export 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) => {
|
||
getData<any>(`${address}-publishData`)
|
||
.then((storedData) => {
|
||
storedData = storedData || {}; // Initialize if no data found
|
||
let groupData = storedData[groupId] || {}; // Initialize group data if not found
|
||
let typeData = groupData[type] || {}; // Initialize type data if not found
|
||
|
||
let totalSize = 0;
|
||
|
||
// Calculate total size of all stored data
|
||
Object.values(storedData).forEach((group) => {
|
||
Object.values(group).forEach((type) => {
|
||
Object.values(type).forEach((data) => {
|
||
totalSize += data.size; // Accumulate data sizes
|
||
});
|
||
});
|
||
});
|
||
|
||
// Check if adding new data exceeds 3MB limit
|
||
if (totalSize + newData.size > MAX_STORAGE_SIZE) {
|
||
let dataEntries = Object.entries(typeData);
|
||
dataEntries.sort((a, b) => a[1].timestampSaved - b[1].timestampSaved);
|
||
|
||
// Remove oldest entries 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 typeData
|
||
}
|
||
}
|
||
|
||
// Add or update the new data if there's space
|
||
if (totalSize + newData.size <= MAX_STORAGE_SIZE) {
|
||
typeData[`${nameIdentifier}`] = newData;
|
||
groupData[type] = typeData;
|
||
storedData[groupId] = groupData;
|
||
|
||
// Save updated structure back to localStorage
|
||
storeData(`${address}-publishData`, storedData)
|
||
.then(() => res(true)) // Successfully added
|
||
.catch((error) => {
|
||
console.error("Error saving data:", error);
|
||
res(false); // Save failed
|
||
});
|
||
} else {
|
||
console.error("Failed to add data, still exceeds storage limit.");
|
||
res(false); // Failure due to storage limit
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
console.error("Error retrieving data:", error);
|
||
res(false); // Failure due to retrieval error
|
||
});
|
||
});
|
||
}
|
||
|
||
// Fetch user settings based on the key
|
||
export async function getUserSettings({ key }) {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
|
||
return new Promise((resolve) => {
|
||
getData<any>(`${address}-userSettings`)
|
||
.then((storedData) => {
|
||
storedData = storedData || {}; // Initialize empty object if no data
|
||
const value = storedData[key] || null; // Get data by key
|
||
|
||
resolve(value); // Resolve with the data for the specific key
|
||
})
|
||
.catch((error) => {
|
||
resolve(null); // Return null in case of an error
|
||
});
|
||
});
|
||
}
|
||
|
||
// Add or update user settings
|
||
export 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) => {
|
||
getData<any>(`${address}-userSettings`)
|
||
.then((storedData) => {
|
||
storedData = storedData || {}; // Initialize if no data found
|
||
|
||
storedData[key] = value; // Update the key-value pair within stored data
|
||
|
||
// Save updated structure back to localStorage
|
||
storeData(`${address}-userSettings`, storedData)
|
||
.then(() => res(true)) // Data successfully added
|
||
.catch((error) => {
|
||
console.error("Error saving data:", error);
|
||
res(false); // Save failed
|
||
});
|
||
})
|
||
.catch((error) => {
|
||
console.error("Error retrieving data:", error);
|
||
res(false); // Failure due to retrieval error
|
||
});
|
||
});
|
||
}
|
||
|
||
export 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,
|
||
};
|
||
await new Promise((resolve, reject) => {
|
||
storeData("keyPair", toSave)
|
||
.then(() => resolve(true))
|
||
.catch((error) => {
|
||
reject(new Error(error.message || "Error saving data"));
|
||
});
|
||
});
|
||
const newWallet = {
|
||
...wallet,
|
||
publicKey: Base58.encode(keyPair.publicKey),
|
||
ltcAddress: ltcAddress,
|
||
};
|
||
await new Promise((resolve, reject) => {
|
||
storeData("walletInfo", newWallet)
|
||
.then(() => resolve(true))
|
||
.catch((error) => {
|
||
reject(new Error(error.message || "Error saving data"));
|
||
});
|
||
});
|
||
|
||
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) => {
|
||
getData<any>(key)
|
||
.then((data) => resolve(data))
|
||
.catch((error) => reject(error));
|
||
});
|
||
};
|
||
|
||
export async function handleActiveGroupDataFromSocket({ groups, directs }) {
|
||
try {
|
||
const targetOrigin = window.location.origin;
|
||
window.postMessage(
|
||
{
|
||
action: "SET_GROUPS",
|
||
payload: groups,
|
||
},
|
||
targetOrigin
|
||
);
|
||
window.postMessage(
|
||
{
|
||
action: "SET_DIRECTS",
|
||
payload: directs,
|
||
},
|
||
targetOrigin
|
||
);
|
||
|
||
groups = groups;
|
||
directs = directs;
|
||
const activeData = {
|
||
groups: groups || [], // Your groups data here
|
||
directs: directs || [], // Your directs data here
|
||
};
|
||
// Save the active data to localStorage
|
||
storeData("active-groups-directs", activeData).catch((error) => {
|
||
console.error("Error saving data:", error);
|
||
});
|
||
|
||
try {
|
||
handleNotification(groups);
|
||
handleNotificationDirect(directs);
|
||
} catch (error) {}
|
||
} catch (error) {}
|
||
}
|
||
|
||
async function sendChatForBuyOrder({ qortAddress, recipientPublicKey, message, atAddresses }) {
|
||
let _reference = new Uint8Array(64);
|
||
self.crypto.getRandomValues(_reference);
|
||
|
||
let sendTimestamp = Date.now();
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
let reference = Base58.encode(_reference);
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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,
|
||
});
|
||
//TODO
|
||
// if (!hasEnoughBalance) {
|
||
if(!hasEnoughBalance){
|
||
const _encryptedMessage = tx._encryptedMessage;
|
||
const encryptedMessageToBase58 = Base58.encode(_encryptedMessage);
|
||
const signature = "id-" + Date.now() + "-" + Math.floor(Math.random() * 1000)
|
||
const res = await axios.post(
|
||
`https://www.qort.trade/api/transaction/updatetxgateway`,
|
||
{
|
||
qortalAtAddresses: atAddresses, qortAddress: address, node: buyTradeNodeBaseUrl, status: "message-sent", encryptedMessageToBase58, signature ,
|
||
reference, senderPublicKey: parsedData.publicKey,
|
||
sender: address,
|
||
},
|
||
{
|
||
headers: {
|
||
"Content-Type": "application/json"
|
||
},
|
||
}
|
||
);
|
||
|
||
return {
|
||
encryptedMessageToBase58,
|
||
status: "message-sent",
|
||
signature
|
||
}
|
||
}
|
||
const path = `${import.meta.env.BASE_URL}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;
|
||
}
|
||
|
||
export 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 = 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 = `${import.meta.env.BASE_URL}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;
|
||
}
|
||
|
||
export 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 = 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 = `${import.meta.env.BASE_URL}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;
|
||
}
|
||
|
||
export 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;
|
||
}
|
||
export 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;
|
||
}
|
||
|
||
export async function decryptDirectFunc({ messages, involvingAddress }) {
|
||
const senderPublicKey = await getPublicKey(involvingAddress);
|
||
let holdMessages = [];
|
||
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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;
|
||
}
|
||
|
||
export async function createBuyOrderTx({ crosschainAtInfo, useLocal }) {
|
||
try {
|
||
if (useLocal) {
|
||
const wallet = await getSaveWallet();
|
||
|
||
const address = wallet.address0;
|
||
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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(
|
||
`http://127.0.0.1:12391/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),
|
||
senderAddress: address,
|
||
node: 'http://127.0.0.1:12391'
|
||
},
|
||
};
|
||
} else {
|
||
responseMessage = {
|
||
callResponse: "ERROR",
|
||
extra: {
|
||
message: response,
|
||
atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
|
||
senderAddress: address,
|
||
node: 'http://127.0.0.1:12391'
|
||
},
|
||
};
|
||
}
|
||
|
||
return responseMessage
|
||
}
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = resKeyPair;
|
||
const message = {
|
||
addresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
|
||
foreignKey: parsedData.ltcPrivateKey,
|
||
receivingAddress: address,
|
||
};
|
||
const res = await sendChatForBuyOrder({
|
||
qortAddress: proxyAccountAddress,
|
||
recipientPublicKey: proxyAccountPublicKey,
|
||
message,
|
||
atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
|
||
});
|
||
|
||
|
||
if (res?.signature) {
|
||
let responseMessage;
|
||
|
||
if(res?.encryptedMessageToBase58){
|
||
const message = await listenForChatMessageForBuyOrder({
|
||
nodeBaseUrl: buyTradeNodeBaseUrl,
|
||
senderAddress: proxyAccountAddress,
|
||
senderPublicKey: proxyAccountPublicKey,
|
||
signature: res?.signature,
|
||
});
|
||
responseMessage = {
|
||
callResponse: message.callResponse,
|
||
extra: {
|
||
message: message?.extra?.message,
|
||
senderAddress: address,
|
||
node: buyTradeNodeBaseUrl,
|
||
atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
|
||
}
|
||
}
|
||
} else {
|
||
const message = await listenForChatMessageForBuyOrder({
|
||
nodeBaseUrl: buyTradeNodeBaseUrl,
|
||
senderAddress: proxyAccountAddress,
|
||
senderPublicKey: proxyAccountPublicKey,
|
||
signature: res?.signature,
|
||
});
|
||
|
||
responseMessage = {
|
||
callResponse: message.callResponse,
|
||
extra: {
|
||
message: message?.extra?.message,
|
||
senderAddress: address,
|
||
node: buyTradeNodeBaseUrl,
|
||
atAddresses: crosschainAtInfo.map((order)=> order.qortalAtAddress),
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// const status = response.callResponse === true ? 'trade-ongoing' : 'trade-failed'
|
||
// 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,
|
||
// response.callResponse
|
||
// };
|
||
// }
|
||
// return {
|
||
// atAddresses: crosschainAtInfo.map((order) => order.qortalAtAddress),
|
||
// chatSignature: res?.signature,
|
||
// node: buyTradeNodeBaseUrl,
|
||
// qortAddress: address,
|
||
// };
|
||
|
||
|
||
return responseMessage
|
||
} else {
|
||
throw new Error("Unable to send buy order message");
|
||
}
|
||
} catch (error) {
|
||
throw new Error(error.message);
|
||
}
|
||
}
|
||
|
||
export async function sendChatNotification(
|
||
res,
|
||
groupId,
|
||
secretKeyObject,
|
||
numberOfMembers
|
||
) {
|
||
try {
|
||
const data = await objectToBase64({
|
||
type: "notification",
|
||
subType: "new-group-encryption",
|
||
data: {
|
||
timestamp: res.timestamp,
|
||
name: res.name,
|
||
message: `${res.name} has updated the encryption key`,
|
||
numberOfMembers,
|
||
},
|
||
});
|
||
|
||
encryptSingle({
|
||
data64: data,
|
||
secretKeyObject: secretKeyObject,
|
||
})
|
||
.then((res2) => {
|
||
pauseAllQueues();
|
||
sendChatGroup({
|
||
groupId,
|
||
typeMessage: undefined,
|
||
chatReference: undefined,
|
||
messageText: res2,
|
||
})
|
||
.then(() => {})
|
||
.catch((error) => {
|
||
console.error("1", error.message);
|
||
})
|
||
.finally(() => {
|
||
resumeAllQueues();
|
||
});
|
||
})
|
||
.catch((error) => {
|
||
console.error("2", error.message);
|
||
});
|
||
} catch (error) {}
|
||
}
|
||
|
||
export const getFee = async (txType) => {
|
||
const timestamp = Date.now();
|
||
const data = await reusableGet(
|
||
`/transactions/unitfee?txType=${txType}×tamp=${timestamp}`
|
||
);
|
||
const arbitraryFee = (Number(data) / 1e8).toFixed(8);
|
||
|
||
return {
|
||
timestamp,
|
||
fee: arbitraryFee,
|
||
};
|
||
};
|
||
|
||
export async function leaveGroup({ groupId }) {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
const lastReference = await getLastRef();
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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 = 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;
|
||
}
|
||
|
||
export async function cancelInvitationToGroup({ groupId, qortalAddress }) {
|
||
const lastReference = await getLastRef();
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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;
|
||
}
|
||
|
||
export async function cancelBan({ groupId, qortalAddress }) {
|
||
const lastReference = await getLastRef();
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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;
|
||
}
|
||
export async function registerName({ name }) {
|
||
const lastReference = await getLastRef();
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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;
|
||
}
|
||
export async function makeAdmin({ groupId, qortalAddress }) {
|
||
const lastReference = await getLastRef();
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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;
|
||
}
|
||
|
||
export async function removeAdmin({ groupId, qortalAddress }) {
|
||
const lastReference = await getLastRef();
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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;
|
||
}
|
||
|
||
export async function banFromGroup({
|
||
groupId,
|
||
qortalAddress,
|
||
rBanReason = "",
|
||
rBanTime,
|
||
}) {
|
||
const lastReference = await getLastRef();
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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;
|
||
}
|
||
|
||
export async function kickFromGroup({
|
||
groupId,
|
||
qortalAddress,
|
||
rBanReason = "",
|
||
}) {
|
||
const lastReference = await getLastRef();
|
||
const resKeyPair = await getKeyPair();
|
||
const parsedData = 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;
|
||
}
|
||
|
||
export 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 = 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;
|
||
}
|
||
export 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 = 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 = 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 = 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 = 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
|
||
);
|
||
|
||
return parsedMessageObj
|
||
|
||
// 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,
|
||
// () => {}
|
||
// );
|
||
// }
|
||
// }
|
||
// );
|
||
}
|
||
|
||
export async function setChatHeads(data) {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
return await new Promise((resolve, reject) => {
|
||
storeData(`chatheads-${address}`, data)
|
||
.then(() => resolve(true))
|
||
.catch((error) => {
|
||
reject(new Error(error.message || "Error saving data"));
|
||
});
|
||
});
|
||
}
|
||
|
||
export async function checkLocalFunc() {
|
||
const apiKey = await getApiKeyFromStorage();
|
||
return !!apiKey;
|
||
}
|
||
|
||
export async function getTempPublish() {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
const key = `tempPublish-${address}`;
|
||
const res = await getData<any>(key).catch(() => null);
|
||
|
||
const SIX_MINUTES = 6 * 60 * 1000; // 6 minutes in milliseconds
|
||
|
||
if (res) {
|
||
const parsedData = res;
|
||
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)) {
|
||
await storeData(key, filteredData);
|
||
}
|
||
return filteredData;
|
||
} else {
|
||
return {};
|
||
}
|
||
}
|
||
|
||
export 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(),
|
||
},
|
||
},
|
||
};
|
||
|
||
return await new Promise((resolve, reject) => {
|
||
storeData(`tempPublish-${address}`, newTemp)
|
||
.then(() => resolve(newTemp[key]))
|
||
.catch((error) => {
|
||
reject(new Error(error.message || "Error saving data"));
|
||
});
|
||
});
|
||
}
|
||
|
||
async function setChatHeadsDirect(data) {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
return await new Promise((resolve, reject) => {
|
||
storeData(`chatheads-direct-${address}`, data)
|
||
.then(() => resolve(true))
|
||
.catch((error) => {
|
||
reject(new Error(error.message || "Error saving data"));
|
||
});
|
||
});
|
||
}
|
||
|
||
export async function getTimestampEnterChat() {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
const key = `enter-chat-timestamp-${address}`;
|
||
const res = await getData<any>(key).catch(() => null);
|
||
if (res) {
|
||
const parsedData = res;
|
||
return parsedData;
|
||
} else {
|
||
return {};
|
||
}
|
||
}
|
||
export async function getTimestampGroupAnnouncement() {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
const key = `group-announcement-${address}`;
|
||
const res = await getData<any>(key).catch(() => null);
|
||
if (res) {
|
||
const parsedData = res;
|
||
return parsedData;
|
||
} else {
|
||
return {};
|
||
}
|
||
}
|
||
|
||
export 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,
|
||
};
|
||
return await new Promise((resolve, reject) => {
|
||
storeData(`group-announcement-${address}`, data)
|
||
.then(() => resolve(true))
|
||
.catch((error) => {
|
||
reject(new Error(error.message || "Error saving data"));
|
||
});
|
||
});
|
||
}
|
||
|
||
async function getGroupData() {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
const key = `group-data-${address}`;
|
||
const res = await getData<any>(key).catch(() => null);
|
||
if (res) {
|
||
const parsedData = res;
|
||
return parsedData;
|
||
} else {
|
||
return {};
|
||
}
|
||
}
|
||
export async function getGroupDataSingle(groupId) {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
const key = `group-data-${address}`;
|
||
const res = await getData<any>(key).catch(() => null);
|
||
if (res) {
|
||
const parsedData = res;
|
||
return parsedData[groupId] || null;
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export 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,
|
||
};
|
||
return await new Promise((resolve, reject) => {
|
||
storeData(`group-data-${address}`, data)
|
||
.then(() => resolve(true))
|
||
.catch((error) => {
|
||
reject(new Error(error.message || "Error saving data"));
|
||
});
|
||
});
|
||
}
|
||
|
||
export async function addTimestampEnterChat({ groupId, timestamp }) {
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet.address0;
|
||
const data = await getTimestampEnterChat();
|
||
data[groupId] = timestamp;
|
||
return await new Promise((resolve, reject) => {
|
||
storeData(`enter-chat-timestamp-${address}`, data)
|
||
.then(() => resolve(true))
|
||
.catch((error) => {
|
||
reject(new Error(error.message || "Error saving data"));
|
||
});
|
||
});
|
||
}
|
||
|
||
export 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 getData<any>(key).catch(() => null);
|
||
if (res) {
|
||
const parsedData = res;
|
||
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 getData<any>(key).catch(() => null);
|
||
if (res) {
|
||
const parsedData = res;
|
||
return parsedData;
|
||
} else {
|
||
throw new Error("No Chatheads saved");
|
||
}
|
||
}
|
||
|
||
function setupMessageListener() {
|
||
window.addEventListener("message", async (event) => {
|
||
if (event.origin !== window.location.origin) {
|
||
return;
|
||
}
|
||
const request = event.data;
|
||
|
||
// Check if the message is intended for this listener
|
||
if (request?.type !== "backgroundMessage") return; // Only process messages of type 'backgroundMessage'
|
||
|
||
switch (request.action) {
|
||
case "version":
|
||
versionCase(request, event);
|
||
break;
|
||
|
||
// case "storeWalletInfo":
|
||
// storeWalletInfoCase(request, event);
|
||
// break;
|
||
|
||
case "getWalletInfo":
|
||
getWalletInfoCase(request, event);
|
||
break;
|
||
|
||
case "validApi":
|
||
validApiCase(request, event);
|
||
break;
|
||
|
||
case "name":
|
||
nameCase(request, event);
|
||
break;
|
||
case "userInfo":
|
||
userInfoCase(request, event);
|
||
break;
|
||
case "decryptWallet":
|
||
decryptWalletCase(request, event);
|
||
break;
|
||
case "balance":
|
||
balanceCase(request, event);
|
||
break;
|
||
case "ltcBalance":
|
||
ltcBalanceCase(request, event);
|
||
break;
|
||
case "sendCoin":
|
||
sendCoinCase(request, event);
|
||
break;
|
||
case "inviteToGroup":
|
||
inviteToGroupCase(request, event);
|
||
break;
|
||
case "saveTempPublish":
|
||
saveTempPublishCase(request, event);
|
||
break;
|
||
case "getTempPublish":
|
||
getTempPublishCase(request, event);
|
||
break;
|
||
case "createGroup":
|
||
createGroupCase(request, event);
|
||
break;
|
||
case "cancelInvitationToGroup":
|
||
cancelInvitationToGroupCase(request, event);
|
||
break;
|
||
case "leaveGroup":
|
||
leaveGroupCase(request, event);
|
||
break;
|
||
case "joinGroup":
|
||
joinGroupCase(request, event);
|
||
break;
|
||
case "kickFromGroup":
|
||
kickFromGroupCase(request, event);
|
||
break;
|
||
case "banFromGroup":
|
||
banFromGroupCase(request, event);
|
||
break;
|
||
|
||
case "addDataPublishes":
|
||
addDataPublishesCase(request, event);
|
||
break;
|
||
|
||
case "getDataPublishes":
|
||
getDataPublishesCase(request, event);
|
||
break;
|
||
case "addUserSettings":
|
||
addUserSettingsCase(request, event);
|
||
break;
|
||
case "cancelBan":
|
||
cancelBanCase(request, event);
|
||
break;
|
||
case "registerName":
|
||
registerNameCase(request, event);
|
||
break;
|
||
|
||
case "makeAdmin":
|
||
makeAdminCase(request, event);
|
||
break;
|
||
case "removeAdmin":
|
||
removeAdminCase(request, event);
|
||
break;
|
||
case "notification":
|
||
notificationCase(request, event);
|
||
break;
|
||
|
||
case "addTimestampEnterChat":
|
||
addTimestampEnterChatCase(request, event);
|
||
break;
|
||
case "setApiKey":
|
||
setApiKeyCase(request, event);
|
||
break;
|
||
case "setCustomNodes":
|
||
setCustomNodesCase(request, event);
|
||
case "getApiKey":
|
||
getApiKeyCase(request, event);
|
||
break;
|
||
case "getCustomNodesFromStorage":
|
||
getCustomNodesFromStorageCase(request, event);
|
||
break;
|
||
case "notifyAdminRegenerateSecretKey":
|
||
notifyAdminRegenerateSecretKeyCase(request, event);
|
||
break;
|
||
case "addGroupNotificationTimestamp":
|
||
addGroupNotificationTimestampCase(request, event);
|
||
break;
|
||
case "clearAllNotifications":
|
||
clearAllNotificationsCase(request, event);
|
||
break;
|
||
case "setGroupData":
|
||
setGroupDataCase(request, event);
|
||
break;
|
||
case "getGroupDataSingle":
|
||
getGroupDataSingleCase(request, event);
|
||
break;
|
||
case "getTimestampEnterChat":
|
||
getTimestampEnterChatCase(request, event);
|
||
break;
|
||
case "getGroupNotificationTimestamp":
|
||
getGroupNotificationTimestampCase(request, event);
|
||
break;
|
||
case "encryptAndPublishSymmetricKeyGroupChat":
|
||
encryptAndPublishSymmetricKeyGroupChatCase(request, event);
|
||
break;
|
||
case "publishGroupEncryptedResource":
|
||
publishGroupEncryptedResourceCase(request, event);
|
||
break;
|
||
case "publishOnQDN":
|
||
publishOnQDNCase(request, event);
|
||
break;
|
||
case "handleActiveGroupDataFromSocket":
|
||
handleActiveGroupDataFromSocketCase(request, event);
|
||
break;
|
||
case "getThreadActivity":
|
||
getThreadActivityCase(request, event);
|
||
break;
|
||
case "updateThreadActivity":
|
||
updateThreadActivityCase(request, event);
|
||
case "decryptGroupEncryption":
|
||
decryptGroupEncryptionCase(request, event);
|
||
break;
|
||
case "encryptSingle":
|
||
encryptSingleCase(request, event);
|
||
break;
|
||
case "decryptSingle":
|
||
decryptSingleCase(request, event);
|
||
break;
|
||
case "pauseAllQueues":
|
||
pauseAllQueuesCase(request, event);
|
||
break;
|
||
case "resumeAllQueues":
|
||
resumeAllQueuesCase(request, event);
|
||
break;
|
||
case "checkLocal":
|
||
checkLocalCase(request, event);
|
||
break;
|
||
case "decryptSingleForPublishes":
|
||
decryptSingleForPublishesCase(request, event);
|
||
break;
|
||
case "decryptDirect":
|
||
decryptDirectCase(request, event);
|
||
break;
|
||
case "sendChatGroup":
|
||
sendChatGroupCase(request, event);
|
||
break;
|
||
case "sendChatDirect":
|
||
sendChatDirectCase(request, event);
|
||
break;
|
||
case "setupGroupWebsocket":
|
||
setupGroupWebsocketCase(request, event);
|
||
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`;
|
||
const keysToRemove = [
|
||
"keyPair",
|
||
"walletInfo",
|
||
"active-groups-directs",
|
||
key1,
|
||
key2,
|
||
key3,
|
||
];
|
||
|
||
removeKeysAndLogout(keysToRemove, event, request);
|
||
};
|
||
logoutFunc();
|
||
} catch (error) {}
|
||
}
|
||
|
||
break;
|
||
default:
|
||
console.error("Unknown action:", request.action);
|
||
break;
|
||
}
|
||
});
|
||
}
|
||
|
||
setupMessageListener();
|
||
|
||
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 {
|
||
}
|
||
};
|
||
|
||
export 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
|
||
) {
|
||
// Create a unique notification ID with type and group announcement details
|
||
const notificationId =
|
||
encodeURIComponent("chat_notification_" +
|
||
Date.now() +
|
||
"_type=group-announcement" +
|
||
`_from=${newAnnouncements[0]?.groupId}`);
|
||
|
||
const title = "New group announcement!";
|
||
const body = `You have received a new announcement from ${newAnnouncements[0]?.groupName}`;
|
||
|
||
// Create and show the notification
|
||
const notification = new window.Notification(title, {
|
||
body,
|
||
data: { id: notificationId },
|
||
});
|
||
|
||
// Handle notification click with specific actions based on `notificationId`
|
||
notification.onclick = () => {
|
||
handleNotificationClick(notificationId);
|
||
notification.close(); // Clean up the notification on click
|
||
};
|
||
|
||
// Automatically close the notification after 5 seconds if it’s not clicked
|
||
setTimeout(() => {
|
||
notification.close();
|
||
}, 10000); // Close after 5 seconds
|
||
}
|
||
const savedtimestampAfter = await getTimestampGroupAnnouncement();
|
||
const targetOrigin = window.location.origin;
|
||
|
||
window.postMessage(
|
||
{
|
||
action: "SET_GROUP_ANNOUNCEMENTS",
|
||
payload: savedtimestampAfter,
|
||
},
|
||
targetOrigin
|
||
);
|
||
} catch (error) {
|
||
} finally {
|
||
}
|
||
};
|
||
|
||
const checkActiveChatsForNotifications = async () => {
|
||
try {
|
||
checkGroupList();
|
||
} catch (error) {}
|
||
};
|
||
|
||
export 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 =
|
||
encodeURIComponent("chat_notification_" +
|
||
Date.now() +
|
||
"_type=thread-post" +
|
||
`_data=${JSON.stringify(newAnnouncements[0])}`);
|
||
let isDisableNotifications =
|
||
(await getUserSettings({ key: "disable-push-notifications" })) || false;
|
||
if (!isDisableNotifications) {
|
||
|
||
|
||
// Check user settings to see if notifications are disabled
|
||
const isDisableNotifications =
|
||
(await getUserSettings({ key: "disable-push-notifications" })) ||
|
||
false;
|
||
|
||
if (!isDisableNotifications) {
|
||
const title = "New thread post!";
|
||
const body = `New post in ${newAnnouncements[0]?.thread?.threadData?.title}`;
|
||
|
||
// Create and show the notification
|
||
const notification = new window.Notification(title, {
|
||
body,
|
||
data: { id: notificationId },
|
||
});
|
||
|
||
// Handle notification click with specific actions based on `notificationId`
|
||
notification.onclick = () => {
|
||
handleNotificationClick(notificationId);
|
||
notification.close(); // Clean up the notification on click
|
||
};
|
||
|
||
// Automatically close the notification after 5 seconds if it’s not clicked
|
||
setTimeout(() => {
|
||
notification.close();
|
||
}, 10000); // Close after 5 seconds
|
||
}
|
||
}
|
||
}
|
||
const savedtimestampAfter = await getTimestampGroupAnnouncement();
|
||
const targetOrigin = window.location.origin;
|
||
|
||
window.postMessage(
|
||
{
|
||
action: "SET_GROUP_ANNOUNCEMENTS",
|
||
payload: savedtimestampAfter,
|
||
},
|
||
targetOrigin
|
||
);
|
||
} catch (error) {
|
||
} finally {
|
||
}
|
||
};
|
||
|
||
// Configure Background Fetch
|
||
// BackgroundFetch.configure({
|
||
// minimumFetchInterval: 15, // Minimum 15-minute interval
|
||
// enableHeadless: true, // Enable headless mode for Android
|
||
// }, async (taskId) => {
|
||
// // This is where your background task logic goes
|
||
// const wallet = await getSaveWallet();
|
||
// const address = wallet.address0;
|
||
// if (!address) return;
|
||
// checkActiveChatsForNotifications();
|
||
// checkNewMessages();
|
||
// checkThreads();
|
||
|
||
// await new Promise((res)=> {
|
||
// setTimeout(() => {
|
||
// res()
|
||
// }, 55000);
|
||
// })
|
||
// // Always finish the task when complete
|
||
// BackgroundFetch.finish(taskId);
|
||
// }, (taskId) => {
|
||
// // Optional timeout callback
|
||
// BackgroundFetch.finish(taskId);
|
||
// });
|
||
|
||
let notificationCheckInterval
|
||
|
||
const createNotificationCheck = () => {
|
||
// Check if an interval already exists before creating it
|
||
if (!notificationCheckInterval) {
|
||
notificationCheckInterval = setInterval(async () => {
|
||
try {
|
||
// This would replace the Chrome alarm callback
|
||
const wallet = await getSaveWallet();
|
||
const address = wallet?.address0;
|
||
if (!address) return;
|
||
|
||
checkActiveChatsForNotifications();
|
||
checkNewMessages();
|
||
checkThreads();
|
||
} catch (error) {
|
||
console.error('Error checking notifications:', error);
|
||
}
|
||
}, 10 * 60 * 1000); // 10 minutes
|
||
}
|
||
};
|
||
|
||
// Call this function when initializing your app
|
||
createNotificationCheck(); |