chrome-extension/public/content-script.js
2024-10-17 01:12:34 +03:00

871 lines
24 KiB
JavaScript

class Semaphore {
constructor(count) {
this.count = count
this.waiting = []
}
acquire() {
return new Promise(resolve => {
if (this.count > 0) {
this.count--
resolve()
} else {
this.waiting.push(resolve)
}
})
}
release() {
if (this.waiting.length > 0) {
const resolve = this.waiting.shift()
resolve()
} else {
this.count++
}
}
}
let semaphore = new Semaphore(1)
let reader = new FileReader()
const fileToBase64 = (file) => new Promise(async (resolve, reject) => {
if (!reader) {
reader = new FileReader()
}
await semaphore.acquire()
reader.readAsDataURL(file)
reader.onload = () => {
const dataUrl = reader.result
if (typeof dataUrl === "string") {
const base64String = dataUrl.split(',')[1]
reader.onload = null
reader.onerror = null
resolve(base64String)
} else {
reader.onload = null
reader.onerror = null
reject(new Error('Invalid data URL'))
}
semaphore.release()
}
reader.onerror = (error) => {
reader.onload = null
reader.onerror = null
reject(error)
semaphore.release()
}
})
async function connection(hostname) {
const isConnected = await chrome.storage.local.get([hostname]);
let connected = false;
if (
isConnected &&
Object.keys(isConnected).length > 0 &&
isConnected[hostname]
) {
connected = true;
}
return connected;
}
// In your content script
document.addEventListener("qortalExtensionRequests", async (event) => {
const { type, payload, requestId, timeout } = event.detail; // Capture the requestId
if (type === "REQUEST_USER_INFO") {
const hostname = window.location.hostname;
const res = await connection(hostname);
if (!res) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "USER_INFO",
data: {
error: "Not authorized",
},
requestId,
},
})
);
return;
}
chrome?.runtime?.sendMessage({ action: "userInfo" }, (response) => {
if (response.error) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "USER_INFO",
data: {
error: response.error,
},
requestId,
},
})
);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "USER_INFO", data: response, requestId },
})
);
}
});
} else if (type === "REQUEST_IS_INSTALLED") {
chrome?.runtime?.sendMessage({ action: "version" }, (response) => {
if (response.error) {
console.error("Error:", response.error);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "IS_INSTALLED", data: response, requestId },
})
);
}
});
} else if (type === "REQUEST_CONNECTION") {
const hostname = window.location.hostname;
chrome?.runtime?.sendMessage(
{
action: "connection",
payload: {
hostname,
},
timeout,
},
(response) => {
if (response.error) {
console.error("Error:", response.error);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "CONNECTION", data: response, requestId },
})
);
}
}
);
} else if (type === "REQUEST_OAUTH") {
const hostname = window.location.hostname;
const res = await connection(hostname);
if (!res) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "OAUTH",
data: {
error: "Not authorized",
},
requestId,
},
})
);
return;
}
chrome?.runtime?.sendMessage(
{
action: "oauth",
payload: {
nodeBaseUrl: payload.nodeBaseUrl,
senderAddress: payload.senderAddress,
senderPublicKey: payload.senderPublicKey,
timestamp: payload.timestamp,
},
},
(response) => {
if (response.error) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "OAUTH",
data: {
error: response.error,
},
requestId,
},
})
);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "OAUTH", data: response, requestId },
})
);
}
}
);
} else if (type === "REQUEST_BUY_ORDER") {
const hostname = window.location.hostname;
const res = await connection(hostname);
if (!res) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "BUY_ORDER",
data: {
error: "Not authorized",
},
requestId,
},
})
);
return;
}
chrome?.runtime?.sendMessage(
{
action: "buyOrder",
payload: {
qortalAtAddresses: payload.qortalAtAddresses,
hostname,
useLocal: payload?.useLocal,
},
timeout,
},
(response) => {
if (response.error) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "BUY_ORDER",
data: {
error: response.error,
},
requestId,
},
})
);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "BUY_ORDER", data: response, requestId },
})
);
}
}
);
} else if (type === "REQUEST_LTC_BALANCE") {
const hostname = window.location.hostname;
const res = await connection(hostname);
if (!res) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "USER_INFO",
data: {
error: "Not authorized",
},
requestId,
},
})
);
return;
}
chrome?.runtime?.sendMessage(
{
action: "ltcBalance",
payload: {
hostname,
},
timeout,
},
(response) => {
if (response.error) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "LTC_BALANCE",
data: {
error: response.error,
},
requestId,
},
})
);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "LTC_BALANCE", data: response, requestId },
})
);
}
}
);
} else if (type === "CHECK_IF_LOCAL") {
const hostname = window.location.hostname;
const res = await connection(hostname);
if (!res) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "USER_INFO",
data: {
error: "Not authorized",
},
requestId,
},
})
);
return;
}
chrome?.runtime?.sendMessage(
{
action: "checkLocal",
payload: {
hostname,
},
timeout,
},
(response) => {
if (response.error) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "CHECK_IF_LOCAL",
data: {
error: response.error,
},
requestId,
},
})
);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "CHECK_IF_LOCAL", data: response, requestId },
})
);
}
}
);
} else if (type === "REQUEST_AUTHENTICATION") {
const hostname = window.location.hostname;
const res = await connection(hostname);
if (!res) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "USER_INFO",
data: {
error: "Not authorized",
},
requestId,
},
})
);
return;
}
chrome?.runtime?.sendMessage(
{
action: "authentication",
payload: {
hostname,
},
timeout,
},
(response) => {
if (response.error) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "AUTHENTICATION",
data: {
error: response.error,
},
requestId,
},
})
);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "AUTHENTICATION", data: response, requestId },
})
);
}
}
);
} else if (type === "REQUEST_SEND_QORT") {
const hostname = window.location.hostname;
const res = await connection(hostname);
if (!res) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "USER_INFO",
data: {
error: "Not authorized",
},
requestId,
},
})
);
return;
}
chrome?.runtime?.sendMessage(
{
action: "sendQort",
payload: {
hostname,
amount: payload.amount,
description: payload.description,
address: payload.address,
},
timeout,
},
(response) => {
if (response.error) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "SEND_QORT",
data: {
error: response.error,
},
requestId,
},
})
);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "SEND_QORT", data: response, requestId },
})
);
}
}
);
} else if (type === "REQUEST_CLOSE_POPUP") {
const hostname = window.location.hostname;
const res = await connection(hostname);
if (!res) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "USER_INFO",
data: {
error: "Not authorized",
},
requestId,
},
})
);
return;
}
chrome?.runtime?.sendMessage({ action: "closePopup" }, (response) => {
if (response.error) {
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: {
type: "CLOSE_POPUP",
data: {
error: response.error,
},
requestId,
},
})
);
} else {
// Include the requestId in the detail when dispatching the response
document.dispatchEvent(
new CustomEvent("qortalExtensionResponses", {
detail: { type: "CLOSE_POPUP", data: true, requestId },
})
);
}
});
}
// Handle other request types as needed...
});
async function handleGetFileFromIndexedDB(fileId, sendResponse) {
try {
const db = await openIndexedDB();
const transaction = db.transaction(["files"], "readonly");
const objectStore = transaction.objectStore("files");
const getRequest = objectStore.get(fileId);
getRequest.onsuccess = async function (event) {
if (getRequest.result) {
const file = getRequest.result.data;
try {
const base64String = await fileToBase64(file);
// Create a new transaction to delete the file
const deleteTransaction = db.transaction(["files"], "readwrite");
const deleteObjectStore = deleteTransaction.objectStore("files");
const deleteRequest = deleteObjectStore.delete(fileId);
deleteRequest.onsuccess = function () {
console.log(`File with ID ${fileId} has been removed from IndexedDB`);
try {
sendResponse({ result: base64String });
} catch (error) {
console.log('error', error)
}
};
deleteRequest.onerror = function () {
console.error(`Error deleting file with ID ${fileId} from IndexedDB`);
sendResponse({ result: null, error: "Failed to delete file from IndexedDB" });
};
} catch (error) {
console.error("Error converting file to Base64:", error);
sendResponse({ result: null, error: "Failed to convert file to Base64" });
}
} else {
console.error(`File with ID ${fileId} not found in IndexedDB`);
sendResponse({ result: null, error: "File not found in IndexedDB" });
}
};
getRequest.onerror = function () {
console.error(`Error retrieving file with ID ${fileId} from IndexedDB`);
sendResponse({ result: null, error: "Error retrieving file from IndexedDB" });
};
} catch (error) {
console.error("Error opening IndexedDB:", error);
sendResponse({ result: null, error: "Error opening IndexedDB" });
}
}
const testAsync = async (sendResponse)=> {
await new Promise((res)=> {
setTimeout(() => {
res()
}, 2500);
})
sendResponse({ result: null, error: "Testing" });
}
const showSaveFilePicker = async (data) => {
try {
const {filename, mimeType, fileHandleOptions, fileId} = data
const blob = await retrieveFileFromIndexedDB(fileId)
const fileHandle = await window.showSaveFilePicker({
suggestedName: filename,
types: [
{
description: mimeType,
...fileHandleOptions
}
]
})
const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable()
await writable.write(contents)
await writable.close()
}
writeFile(fileHandle, blob).then(() => console.log("FILE SAVED"))
} catch (error) {
FileSaver.saveAs(blob, filename)
}
}
chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) {
console.log('message', message)
if (message.type === "LOGOUT") {
// Notify the web page
window.postMessage(
{
type: "LOGOUT",
from: "qortal",
},
"*"
);
} else if (message.type === "RESPONSE_FOR_TRADES") {
// Notify the web page
window.postMessage(
{
type: "RESPONSE_FOR_TRADES",
from: "qortal",
payload: message.message,
},
"*"
);
} else if(message.action === "SHOW_SAVE_FILE_PICKER"){
showSaveFilePicker(message?.data)
}
else if (message.action === "getFileFromIndexedDB") {
handleGetFileFromIndexedDB(message.fileId, sendResponse);
return true; // Keep the message channel open for async response
}
});
function openIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("fileStorageDB", 1);
request.onupgradeneeded = function (event) {
const db = event.target.result;
if (!db.objectStoreNames.contains("files")) {
db.createObjectStore("files", { keyPath: "id" });
}
};
request.onsuccess = function (event) {
resolve(event.target.result);
};
request.onerror = function () {
reject("Error opening IndexedDB");
};
});
}
async function retrieveFileFromIndexedDB(fileId) {
const db = await openIndexedDB();
const transaction = db.transaction(["files"], "readwrite");
const objectStore = transaction.objectStore("files");
return new Promise((resolve, reject) => {
const getRequest = objectStore.get(fileId);
getRequest.onsuccess = function (event) {
if (getRequest.result) {
// File found, resolve it and delete from IndexedDB
const file = getRequest.result.data;
objectStore.delete(fileId);
resolve(file);
} else {
reject("File not found in IndexedDB");
}
};
getRequest.onerror = function () {
reject("Error retrieving file from IndexedDB");
};
});
}
async function deleteQortalFilesFromIndexedDB() {
try {
console.log("Opening IndexedDB for deleting files...");
const db = await openIndexedDB();
const transaction = db.transaction(["files"], "readwrite");
const objectStore = transaction.objectStore("files");
// Create a request to get all keys
const getAllKeysRequest = objectStore.getAllKeys();
getAllKeysRequest.onsuccess = function (event) {
const keys = event.target.result;
// Iterate through keys to find and delete those containing '_qortalfile'
for (let key of keys) {
if (key.includes("_qortalfile")) {
const deleteRequest = objectStore.delete(key);
deleteRequest.onsuccess = function () {
console.log(`File with key '${key}' has been deleted from IndexedDB`);
};
deleteRequest.onerror = function () {
console.error(`Failed to delete file with key '${key}' from IndexedDB`);
};
}
}
};
getAllKeysRequest.onerror = function () {
console.error("Failed to retrieve keys from IndexedDB");
};
transaction.oncomplete = function () {
console.log("Transaction complete for deleting files from IndexedDB");
};
transaction.onerror = function () {
console.error("Error occurred during transaction for deleting files");
};
} catch (error) {
console.error("Error opening IndexedDB:", error);
}
}
async function storeFilesInIndexedDB(obj) {
// First delete any existing files in IndexedDB with '_qortalfile' in their ID
await deleteQortalFilesFromIndexedDB();
// Open the IndexedDB
const db = await openIndexedDB();
const transaction = db.transaction(["files"], "readwrite");
const objectStore = transaction.objectStore("files");
// Handle the obj.file if it exists and is a File instance
if (obj.file instanceof File) {
const fileId = "objFile_qortalfile";
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
obj.fileId = fileId;
delete obj.file;
}
if (obj.blob instanceof Blob) {
const fileId = "objFile_qortalfile";
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.blob,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
let blobObj = {
type: obj.blob?.type
}
obj.fileId = fileId;
delete obj.blob;
obj.blob = blobObj
}
// Iterate through resources to find files and save them to IndexedDB
for (let resource of (obj?.resources || [])) {
if (resource.file instanceof File) {
const fileId = resource.identifier + "_qortalfile";
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: resource.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
resource.fileId = fileId;
delete resource.file;
}
}
// Set transaction completion handlers
transaction.oncomplete = function () {
console.log("Files saved successfully to IndexedDB");
};
transaction.onerror = function () {
console.error("Error saving files to IndexedDB");
};
return obj; // Updated object with references to stored files
}
const UIQortalRequests = ['GET_USER_ACCOUNT', 'DECRYPT_DATA', 'SEND_COIN', 'GET_LIST_ITEMS', 'ADD_LIST_ITEMS', 'DELETE_LIST_ITEM', 'VOTE_ON_POLL', 'CREATE_POLL', 'SEND_CHAT_MESSAGE', 'JOIN_GROUP', 'DEPLOY_AT', 'GET_USER_WALLET', 'GET_WALLET_BALANCE', 'GET_USER_WALLET_INFO', 'GET_CROSSCHAIN_SERVER_INFO', 'GET_TX_ACTIVITY_SUMMARY', 'GET_FOREIGN_FEE', 'UPDATE_FOREIGN_FEE', 'GET_SERVER_CONNECTION_HISTORY', 'SET_CURRENT_FOREIGN_SERVER', 'ADD_FOREIGN_SERVER', 'REMOVE_FOREIGN_SERVER', 'GET_DAY_SUMMARY']
if (!window.hasAddedQortalListener) {
console.log("Listener added");
window.hasAddedQortalListener = true;
//qortalRequests
const listener = async (event) => {
event.preventDefault(); // Prevent default behavior
event.stopImmediatePropagation(); // Stop other listeners from firing
// Verify that the message is from the web page and contains expected data
if (event.source !== window || !event.data || !event.data.action) return;
if (event?.data?.requestedHandler !== 'UI') return;
await new Promise((res)=> {
chrome?.runtime?.sendMessage(
{
action: "authentication",
timeout: 60,
},
(response) => {
if (response.error) {
eventPort.postMessage({
result: null,
error: 'User not authenticated',
});
res()
return
} else {
res()
}
}
);
})
const sendMessageToRuntime = (message, eventPort) => {
chrome?.runtime?.sendMessage(message, (response) => {
console.log('response', response);
if (response.error) {
eventPort.postMessage({
result: null,
error: response,
});
} else {
eventPort.postMessage({
result: response,
error: null,
});
}
});
};
// Check if action is included in the predefined list of UI requests
if (UIQortalRequests.includes(event.data.action)) {
console.log('event?.data', event?.data);
sendMessageToRuntime(
{ action: event.data.action, type: 'qortalRequest', payload: event.data },
event.ports[0]
);
} else if (event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' || event?.data?.action === 'PUBLISH_QDN_RESOURCE' || event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'SAVE_FILE') {
console.log('event?.data?', event?.data)
let data;
try {
data = await storeFilesInIndexedDB(event.data);
} catch (error) {
console.error('Error storing files in IndexedDB:', error);
event.ports[0].postMessage({
result: null,
error: 'Failed to store files in IndexedDB',
});
return;
}
if (data) {
sendMessageToRuntime(
{ action: event.data.action, type: 'qortalRequest', payload: data },
event.ports[0]
);
} else {
event.ports[0].postMessage({
result: null,
error: 'Failed to prepare data for publishing',
});
}
}
};
// Add the listener for messages coming from the window
window.addEventListener('message', listener);
}