feat: implement IndexedDB for saving and managing searched addresses
This commit is contained in:
parent
b9d4a4ea32
commit
70331e19f1
@ -808,6 +808,17 @@ body {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.toggle-key-type {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.5rem;
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-toggle-address:hover:not(.active) {
|
||||
background: var(--background-color);
|
||||
color: var(--text-primary);
|
||||
@ -2684,7 +2695,7 @@ sm-popup::part(popup) {
|
||||
.balance-amount {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
color: var(--primary-color);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@ -4280,7 +4291,7 @@ sm-popup::part(popup) {
|
||||
/* ===== SHARE BUTTON STYLES ===== */
|
||||
.share-btn {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
color: var(--primary-color);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
@ -5028,6 +5039,6 @@ sm-popup::part(popup) {
|
||||
|
||||
.detail-link {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -620,6 +620,7 @@
|
||||
<script src="./scripts/sendTRX.js"></script>
|
||||
<script src="./scripts/recoverAddress.js"></script>
|
||||
<script src="./scripts/balance.js"></script>
|
||||
<script src="./scripts/saveSearchedAddress.js"></script>
|
||||
<script>
|
||||
// Theme toggle
|
||||
function initializeTheme() {
|
||||
|
||||
@ -168,11 +168,35 @@ async function runBalanceCheck() {
|
||||
`;
|
||||
if (typeof notify === "function") notify("Balance loaded", "success");
|
||||
loadHistoryFor(tronAddress);
|
||||
|
||||
// Save searched address to IndexedDB
|
||||
if (typeof searchedAddressDB !== 'undefined') {
|
||||
try {
|
||||
await searchedAddressDB.saveSearchedAddress(tronAddress, balance.toLocaleString());
|
||||
await updateSearchedAddressesList();
|
||||
} catch (dbError) {
|
||||
console.warn("Failed to save address to IndexedDB:", dbError);
|
||||
}
|
||||
}
|
||||
|
||||
updateURLForPage("balance", tronAddress);
|
||||
} else {
|
||||
// Treat as private key (WIF or HEX)
|
||||
const { tronAddress, balance } = await getBalanceByPrivKey(inputVal);
|
||||
|
||||
|
||||
let sourceInfo = null;
|
||||
if (/^[5KLc9RQ][1-9A-HJ-NP-Za-km-z]{50,}$/.test(inputVal)) {
|
||||
// This is a BTC/FLO WIF key
|
||||
sourceInfo = {
|
||||
type: "Private Key",
|
||||
originalKey: inputVal,
|
||||
originalAddress: inputVal, // Store the original private key for toggling
|
||||
blockchain: /^[KL]/.test(inputVal) ? "BTC" : "FLO"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
output.innerHTML = `
|
||||
<div class="card balance-info">
|
||||
<div class="balance-header">
|
||||
@ -201,6 +225,16 @@ async function runBalanceCheck() {
|
||||
`;
|
||||
if (typeof notify === "function") notify("Balance loaded", "success");
|
||||
loadHistoryFor(tronAddress);
|
||||
|
||||
// Save searched address to IndexedDB
|
||||
if (typeof searchedAddressDB !== 'undefined') {
|
||||
try {
|
||||
await searchedAddressDB.saveSearchedAddress(tronAddress, balance.toLocaleString(), Date.now(), sourceInfo);
|
||||
await updateSearchedAddressesList();
|
||||
} catch (dbError) {
|
||||
console.warn("Failed to save address to IndexedDB:", dbError);
|
||||
}
|
||||
}
|
||||
|
||||
updateURLForPage("balance", tronAddress);
|
||||
}
|
||||
|
||||
394
scripts/saveSearchedAddress.js
Normal file
394
scripts/saveSearchedAddress.js
Normal file
@ -0,0 +1,394 @@
|
||||
// IndexedDB for storing searched addresses
|
||||
class SearchedAddressDB {
|
||||
constructor() {
|
||||
this.dbName = "TronWalletDB";
|
||||
this.version = 1;
|
||||
this.storeName = "searchedAddresses";
|
||||
this.db = null;
|
||||
}
|
||||
|
||||
async init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(this.dbName, this.version);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => {
|
||||
this.db = request.result;
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||
const store = db.createObjectStore(this.storeName, {
|
||||
keyPath: "address",
|
||||
});
|
||||
store.createIndex("timestamp", "timestamp", { unique: false });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async saveSearchedAddress(
|
||||
address,
|
||||
balance,
|
||||
timestamp = Date.now(),
|
||||
sourceInfo = null
|
||||
) {
|
||||
if (!this.db) await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db.transaction([this.storeName], "readwrite");
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
|
||||
// check if this address already exists
|
||||
const getRequest = store.get(address);
|
||||
|
||||
getRequest.onsuccess = () => {
|
||||
const existingRecord = getRequest.result;
|
||||
let finalSourceInfo = sourceInfo;
|
||||
|
||||
// If record exists and has sourceInfo, preserve it unless we're providing new sourceInfo
|
||||
if (existingRecord && existingRecord.sourceInfo && !sourceInfo) {
|
||||
finalSourceInfo = existingRecord.sourceInfo;
|
||||
}
|
||||
// If existing record has sourceInfo and new one doesn't, keep the existing one
|
||||
else if (
|
||||
existingRecord &&
|
||||
existingRecord.sourceInfo &&
|
||||
sourceInfo === null
|
||||
) {
|
||||
finalSourceInfo = existingRecord.sourceInfo;
|
||||
}
|
||||
|
||||
const data = {
|
||||
address, // Tron address
|
||||
balance,
|
||||
timestamp,
|
||||
formattedBalance: `${balance} TRX`,
|
||||
sourceInfo: finalSourceInfo, //original blockchain info if converted from private key
|
||||
};
|
||||
|
||||
const putRequest = store.put(data);
|
||||
putRequest.onsuccess = () => resolve();
|
||||
putRequest.onerror = () => reject(putRequest.error);
|
||||
};
|
||||
|
||||
getRequest.onerror = () => reject(getRequest.error);
|
||||
});
|
||||
}
|
||||
|
||||
async getSearchedAddresses() {
|
||||
if (!this.db) await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db.transaction([this.storeName], "readonly");
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const index = store.index("timestamp");
|
||||
|
||||
// Get all records sorted by timestamp (newest first)
|
||||
const request = index.getAll();
|
||||
request.onsuccess = () => {
|
||||
const results = request.result.sort(
|
||||
(a, b) => b.timestamp - a.timestamp
|
||||
);
|
||||
resolve(results);
|
||||
};
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
async deleteSearchedAddress(address) {
|
||||
if (!this.db) await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db.transaction([this.storeName], "readwrite");
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
|
||||
const request = store.delete(address);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
async clearAllSearchedAddresses() {
|
||||
if (!this.db) await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db.transaction([this.storeName], "readwrite");
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
|
||||
const request = store.clear();
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const searchedAddressDB = new SearchedAddressDB();
|
||||
|
||||
|
||||
async function displaySearchedAddresses(addresses) {
|
||||
|
||||
let container = document.getElementById("searchedAddressesContainer");
|
||||
const transactionSection = document.getElementById("transactionSection");
|
||||
|
||||
if (!container && addresses.length > 0) {
|
||||
container = document.createElement("div");
|
||||
container.id = "searchedAddressesContainer";
|
||||
container.className = "card searched-addresses-card";
|
||||
|
||||
if (transactionSection && transactionSection.parentNode) {
|
||||
const nextSibling = transactionSection.nextSibling;
|
||||
transactionSection.parentNode.insertBefore(container, nextSibling);
|
||||
} else {
|
||||
const transactionsPage = document.getElementById("transactionsPage");
|
||||
if (transactionsPage) {
|
||||
transactionsPage.appendChild(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!container) return;
|
||||
|
||||
if (addresses.length === 0) {
|
||||
container.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
container.style.display = "block";
|
||||
container.innerHTML = `
|
||||
<div class="searched-addresses-header">
|
||||
<h3><i class="fas fa-history"></i> Searched Addresses</h3>
|
||||
<button onclick="clearAllSearchedAddresses()" class="btn-clear-all" title="Clear all">
|
||||
<i class="fas fa-trash"></i> Clear All
|
||||
</button>
|
||||
</div>
|
||||
<div class="searched-addresses-list">
|
||||
${addresses
|
||||
.map((addr, index) => {
|
||||
// Check if this was converted from a private key from another blockchain (BTC/FLO)
|
||||
const hasSourceInfo =
|
||||
addr.sourceInfo && addr.sourceInfo.originalKey;
|
||||
|
||||
return `
|
||||
<div class="searched-address-item ${
|
||||
hasSourceInfo ? "has-source-info" : ""
|
||||
}" data-index="${index}" data-current-type="${
|
||||
hasSourceInfo ? addr.sourceInfo.blockchain.toLowerCase() : "tron"
|
||||
}">
|
||||
${
|
||||
hasSourceInfo
|
||||
? `
|
||||
<div class="address-toggle-section">
|
||||
<div class="address-toggle-group">
|
||||
<button onclick="toggleAddressType(${index}, '${addr.sourceInfo.blockchain.toLowerCase()}')"
|
||||
class="btn-toggle-address active"
|
||||
data-type="${addr.sourceInfo.blockchain.toLowerCase()}"
|
||||
title="Show ${addr.sourceInfo.blockchain} Private Key">
|
||||
${addr.sourceInfo.blockchain}
|
||||
</button>
|
||||
<button onclick="toggleAddressType(${index}, 'tron')"
|
||||
class="btn-toggle-address"
|
||||
data-type="tron"
|
||||
title="Show Tron Address">
|
||||
TRON
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="address-content-wrapper">
|
||||
<div class="address-info">
|
||||
|
||||
<div class="address-display">
|
||||
<div class="address-text" id="address-display-${index}" title="${
|
||||
addr.sourceInfo.originalKey
|
||||
}">
|
||||
${addr.sourceInfo.originalKey}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="address-actions">
|
||||
<button onclick="copyCurrentAddress(${index})" class="btn-copy-current" title="Copy Selected Value">
|
||||
<i class="fas fa-copy"></i> COPY
|
||||
</button>
|
||||
<button onclick="deleteSearchedAddress('${
|
||||
addr.address
|
||||
}')" class="btn-delete" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<button onclick="checkBalanceForAddress('${
|
||||
addr.address
|
||||
}')" class="btn-check" title="Check balance">
|
||||
Check Balance
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: `
|
||||
<div class="address-info">
|
||||
<div class="address-display">
|
||||
<div class="address-text" id="address-display-${index}" title="${addr.address}">
|
||||
${addr.address}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="address-actions">
|
||||
<button onclick="copyAddressToClipboard('${addr.address}')" class="btn-copy" title="Copy Tron Address">
|
||||
<i class="fas fa-copy"></i> COPY
|
||||
</button>
|
||||
<button onclick="deleteSearchedAddress('${addr.address}')" class="btn-delete" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<button onclick="checkBalanceForAddress('${addr.address}')" class="btn-check" title="Check balance">
|
||||
Check Balance
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Toggle between address types in searched addresses
|
||||
async function toggleAddressType(addressIndex, type) {
|
||||
try {
|
||||
// Get the searched addresses list
|
||||
const addresses = await searchedAddressDB.getSearchedAddresses();
|
||||
if (!addresses[addressIndex]) return;
|
||||
|
||||
const addressItem = addresses[addressIndex];
|
||||
const container = document.querySelector(`[data-index="${addressIndex}"]`);
|
||||
if (!container) return;
|
||||
|
||||
// Update toggle button states
|
||||
const toggleButtons = container.querySelectorAll(".btn-toggle-address");
|
||||
toggleButtons.forEach((btn) => btn.classList.remove("active"));
|
||||
|
||||
const activeButton = container.querySelector(`[data-type="${type}"]`);
|
||||
if (activeButton) {
|
||||
activeButton.classList.add("active");
|
||||
}
|
||||
|
||||
// Store the current selection in the container data
|
||||
container.setAttribute("data-current-type", type);
|
||||
|
||||
// Update the displayed address text based on selection
|
||||
const addressDisplay = container.querySelector(
|
||||
`#address-display-${addressIndex}`
|
||||
);
|
||||
if (addressDisplay) {
|
||||
if (type === "tron") {
|
||||
// Tron address
|
||||
addressDisplay.textContent = addressItem.address;
|
||||
addressDisplay.title = addressItem.address;
|
||||
} else {
|
||||
// Show original blockchain private key (FLO/BTC)
|
||||
const originalKey =
|
||||
addressItem.sourceInfo?.originalKey || addressItem.address;
|
||||
addressDisplay.textContent = originalKey;
|
||||
addressDisplay.title = originalKey;
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error toggling address type:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the currently selected address
|
||||
async function copyCurrentAddress(addressIndex) {
|
||||
try {
|
||||
// Get the searched addresses list
|
||||
const addresses = await searchedAddressDB.getSearchedAddresses();
|
||||
if (!addresses[addressIndex]) return;
|
||||
|
||||
const addressItem = addresses[addressIndex];
|
||||
const container = document.querySelector(`[data-index="${addressIndex}"]`);
|
||||
if (!container) return;
|
||||
|
||||
// Get the current selection type
|
||||
const currentType = container.getAttribute("data-current-type") || "tron";
|
||||
|
||||
let valueToCopy;
|
||||
let valueLabel;
|
||||
|
||||
if (currentType === "tron") {
|
||||
valueToCopy = addressItem.address;
|
||||
valueLabel = "Tron address";
|
||||
} else {
|
||||
// Copy the private key for non-Tron selection
|
||||
valueToCopy = addressItem.sourceInfo?.originalKey || addressItem.address;
|
||||
valueLabel = `${addressItem.sourceInfo?.blockchain || "Original"} private key`;
|
||||
}
|
||||
|
||||
await copyAddressToClipboard(valueToCopy, valueLabel);
|
||||
} catch (error) {
|
||||
console.error("Error copying current value:", error);
|
||||
notify("Failed to copy value", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSearchedAddress(address) {
|
||||
try {
|
||||
await searchedAddressDB.deleteSearchedAddress(address);
|
||||
await updateSearchedAddressesList();
|
||||
notify("Address removed from history", "success");
|
||||
} catch (error) {
|
||||
console.error("Error deleting searched address:", error);
|
||||
notify("Failed to remove address", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function clearAllSearchedAddresses() {
|
||||
|
||||
try {
|
||||
await searchedAddressDB.clearAllSearchedAddresses();
|
||||
await updateSearchedAddressesList();
|
||||
notify("All searched addresses cleared", "success");
|
||||
} catch (error) {
|
||||
console.error("Error clearing searched addresses:", error);
|
||||
notify("Failed to clear addresses", "error");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function copyAddressToClipboard(address, label = "Address") {
|
||||
try {
|
||||
await navigator.clipboard.writeText(address);
|
||||
notify(`${label} copied to clipboard`, "success");
|
||||
} catch (error) {
|
||||
console.error("Error copying to clipboard:", error);
|
||||
notify("Failed to copy address", "error");
|
||||
}
|
||||
}
|
||||
|
||||
function checkBalanceForAddress(address) {
|
||||
document.getElementById("balanceAddr").value = address;
|
||||
setSearchType("balance");
|
||||
runBalanceCheck();
|
||||
}
|
||||
|
||||
async function updateSearchedAddressesList() {
|
||||
try {
|
||||
const searchedAddresses = await searchedAddressDB.getSearchedAddresses();
|
||||
displaySearchedAddresses(searchedAddresses);
|
||||
} catch (error) {
|
||||
console.error("Error loading searched addresses:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the searched addresses list when the script loads
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
try {
|
||||
await updateSearchedAddressesList();
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize searched addresses:", error);
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user