feat: Expand blockchain address support to multiple chains and enhancing UI for address validation and chat migration.
This commit is contained in:
parent
d6fd63dace
commit
80fa542bf9
234
index.html
234
index.html
@ -36,7 +36,7 @@
|
||||
<script src="scripts/floDapps.js"></script>
|
||||
<script src="scripts/keccak.js"></script>
|
||||
<script src="scripts/messengerEthereum.js"></script>
|
||||
<script src="scripts/messenger.min.js"></script>
|
||||
<script src="scripts/messenger.js"></script>
|
||||
<script src="scripts/blockchainAddresses.js"></script>
|
||||
<script id="onLoadStartUp">
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
floGlobals.myMaticID = floGlobals.myEthID;
|
||||
// HBAR (Hedera) uses same address format as Ethereum
|
||||
floGlobals.myHbarID = floGlobals.myEthID;
|
||||
|
||||
|
||||
// Initialize private-key-dependent addresses to null (will be derived after messenger init)
|
||||
floGlobals.myXrpID = null;
|
||||
floGlobals.mySuiID = null;
|
||||
@ -115,10 +115,29 @@
|
||||
rmMessenger.renderUI.pipeline = renderPipelineUI;
|
||||
rmMessenger.renderUI.mails = m => renderMailList(m, false);
|
||||
rmMessenger.renderUI.marked = renderMarked;
|
||||
rmMessenger.renderUI.onChatMigrated = (oldID, newID) => {
|
||||
const oldChatCard = getChatCard(oldID);
|
||||
if (oldChatCard) {
|
||||
oldChatCard.remove(); // Remove the old chat card from the DOM to prevent duplicates
|
||||
}
|
||||
|
||||
if (activeChat && activeChat.address === oldID) {
|
||||
activeChat.address = newID;
|
||||
if (!floGlobals.isMobileView) {
|
||||
history.replaceState(null, null, `#/ch?address=${newID}`);
|
||||
}
|
||||
// Refetch the messages to include the newly migrated history
|
||||
setTimeout(() => {
|
||||
if (activeChat.address === newID) {
|
||||
renderMessages(newID);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
//init messenger
|
||||
rmMessenger.init().then(async result => {
|
||||
console.log(result);
|
||||
|
||||
|
||||
// Check if account is password-secured by checking the stored secret length
|
||||
try {
|
||||
const indexArr = localStorage.getItem(`${floGlobals.application}#privKey`);
|
||||
@ -134,7 +153,7 @@
|
||||
} catch (e) {
|
||||
console.warn('Could not check password security status:', e);
|
||||
}
|
||||
|
||||
|
||||
// Derive private-key-dependent blockchain addresses after messenger is initialized
|
||||
// Check if we have a stored login password (for password-secured accounts)
|
||||
if (floGlobals._loginPassword) {
|
||||
@ -187,7 +206,11 @@
|
||||
console.info('Private key is password-secured. Use Unlock button in Profile to derive addresses.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reconnect inbox now that late-derived addresses (ADA, XRP, SOL, etc.) are available
|
||||
// These were null when requestDirectInbox() ran during init()
|
||||
rmMessenger.reconnectInbox();
|
||||
|
||||
//Check for available bg image
|
||||
setBgImage();
|
||||
routeTo(window.location.hash, { firstLoad: true })
|
||||
@ -231,6 +254,20 @@
|
||||
</div>
|
||||
</sm-form>
|
||||
</sm-popup>
|
||||
<sm-popup id="blockchain_select_popup" dismissable="false">
|
||||
<h4 id="blockchain_select_title">Select Blockchain</h4>
|
||||
<p>Your private key format is used by multiple networks. Please select the correct blockchain:</p>
|
||||
<div class="grid gap-0-5 margin-top-1">
|
||||
<button class="button w-100" onclick="resolveBlockchainSelection('ETH')">Ethereum (ETH) / EVM</button>
|
||||
<button class="button w-100" onclick="resolveBlockchainSelection('ADA')">Cardano (ADA)</button>
|
||||
<button class="button w-100" onclick="resolveBlockchainSelection('TRON')">TRON</button>
|
||||
<button class="button w-100" onclick="resolveBlockchainSelection('DOT')">Polkadot (DOT)</button>
|
||||
<button class="button w-100" onclick="resolveBlockchainSelection('SOL')">Solana (SOL)</button>
|
||||
</div>
|
||||
<div class="flex align-center gap-0-5 margin-top-1 margin-left-auto">
|
||||
<button class="button cancel-button" onclick="resolveBlockchainSelection(null)">Cancel</button>
|
||||
</div>
|
||||
</sm-popup>
|
||||
<div id="adblocker_warning"></div>
|
||||
<div id="secondary_pages" class="page hidden">
|
||||
<header class="flex align-center gap-1 space-between">
|
||||
@ -828,8 +865,8 @@
|
||||
<h4>Add contact</h4>
|
||||
</header>
|
||||
<sm-form>
|
||||
<sm-input id="add_contact_floID" data-address placeholder="FLO/BTC/ETH address" error-text="Invalid address"
|
||||
animate autofocus required></sm-input>
|
||||
<sm-input id="add_contact_floID" data-address placeholder="Enter any blockchain address"
|
||||
error-text="Invalid address" animate autofocus required></sm-input>
|
||||
<sm-input id="add_contact_name" placeholder="Name" animate required></sm-input>
|
||||
<button class="button button--primary" id="add_contact_button" type="submit" disabled>Add</button>
|
||||
</sm-form>
|
||||
@ -936,7 +973,7 @@
|
||||
<div id="contacts_container" class="observe-empty-state"></div>
|
||||
<div class="empty-state">
|
||||
<h4 class="margin-bottom-0-5">No saved contacts</h4>
|
||||
<p>Use 'Add contact' to add new FLO/BTC/ETH address as a contact</p>
|
||||
<p>Use 'Add contact' to add any blockchain address as a contact</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1539,7 +1576,7 @@
|
||||
const contacts = []
|
||||
const groupID = getRef('edit_group_button').dataset.groupId;
|
||||
for (const contact in floGlobals.contacts) {
|
||||
if (!rmMessenger.groups[groupID].members.includes(contact) && floDapps.user.get_pubKey(contact)) {
|
||||
if (!rmMessenger.groups[groupID].members.includes(contact) && getContactPubKey(contact)) {
|
||||
contacts.push(render.selectableContact(contact))
|
||||
}
|
||||
}
|
||||
@ -1779,18 +1816,79 @@
|
||||
}
|
||||
|
||||
function getContactName(contactAddress) {
|
||||
if (floDapps.user.get_contact(contactAddress))
|
||||
return floDapps.user.get_contact(contactAddress)
|
||||
else if (rmMessenger.groups[contactAddress])
|
||||
// Check floGlobals.contacts directly first (handles all blockchain addresses)
|
||||
if (floGlobals.contacts && floGlobals.contacts[contactAddress])
|
||||
return floGlobals.contacts[contactAddress];
|
||||
// Try floDapps.user.get_contact for FLO/BTC address equivalence checking
|
||||
try {
|
||||
const contact = floDapps.user.get_contact(contactAddress);
|
||||
if (contact) return contact;
|
||||
} catch (e) {
|
||||
// Address format not supported by floCrypto.decodeAddr
|
||||
}
|
||||
if (rmMessenger.groups[contactAddress])
|
||||
return rmMessenger.groups[contactAddress].name
|
||||
else if (floCrypto.isSameAddr(contactAddress, floDapps.user.id))
|
||||
return 'You'
|
||||
else
|
||||
return contactAddress
|
||||
}
|
||||
|
||||
// Safely get pubKey for any blockchain address
|
||||
function getContactPubKey(address) {
|
||||
// Check floGlobals.pubKeys directly first (handles all blockchain addresses)
|
||||
if (floGlobals.pubKeys && floGlobals.pubKeys[address])
|
||||
return floGlobals.pubKeys[address];
|
||||
// Try floDapps.user.get_pubKey for FLO/BTC address equivalence checking
|
||||
try {
|
||||
return floDapps.user.get_pubKey(address);
|
||||
} catch (e) {
|
||||
// Address format not supported by floCrypto.decodeAddr
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidEthereumAddress(address) {
|
||||
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
||||
}
|
||||
|
||||
// Validate any blockchain address (all 19 supported chains)
|
||||
function isValidBlockchainAddress(address) {
|
||||
if (!address || typeof address !== 'string') return false;
|
||||
|
||||
// FLO/BTC/DOGE/LTC legacy (Base58, 33-34 chars)
|
||||
if ((address.length === 33 || address.length === 34) && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address)) {
|
||||
return floCrypto.validateAddr(address) || true; // Accept if Base58 valid
|
||||
}
|
||||
// Ethereum/EVM addresses (0x prefix, 40 hex chars) - ETH, AVAX, BSC, MATIC
|
||||
if (/^0x[a-fA-F0-9]{40}$/.test(address)) return true;
|
||||
// SUI addresses (0x prefix, 64 hex chars)
|
||||
if (/^0x[a-fA-F0-9]{64}$/.test(address)) return true;
|
||||
// BTC/LTC Bech32 (bc1/ltc1 prefix)
|
||||
if (/^(bc1|ltc1)[a-zA-HJ-NP-Z0-9]{25,62}$/.test(address)) return true;
|
||||
// Solana (Base58, 43-44 chars)
|
||||
if ((address.length === 43 || address.length === 44) && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address)) return true;
|
||||
// XRP (r-prefix, 25-35 chars)
|
||||
if (address.startsWith('r') && address.length >= 25 && address.length <= 35 && /^r[a-zA-Z0-9]+$/.test(address)) return true;
|
||||
// TRON (T-prefix, 34 chars)
|
||||
if (address.startsWith('T') && address.length === 34 && /^T[a-zA-Z0-9]+$/.test(address)) return true;
|
||||
// Cardano (addr1 prefix, Bech32)
|
||||
if (address.startsWith('addr1') && address.length > 50) return true;
|
||||
// Polkadot (SS58, starts with 1, 47-48 chars)
|
||||
if (address.startsWith('1') && (address.length === 47 || address.length === 48) && /^[a-zA-Z0-9]+$/.test(address)) return true;
|
||||
// TON (Base64 URL-safe, 48 chars)
|
||||
if (address.length === 48 && /^[A-Za-z0-9_-]+$/.test(address)) return true;
|
||||
// Algorand (Base32, 58 chars, uppercase + 2-7)
|
||||
if (address.length === 58 && /^[A-Z2-7]+$/.test(address)) return true;
|
||||
// Stellar (G-prefix, Base32, 56 chars)
|
||||
if (address.startsWith('G') && address.length === 56 && /^G[A-Z2-7]+$/.test(address)) return true;
|
||||
// Bitcoin Cash CashAddr (q-prefix)
|
||||
if (address.startsWith('q') && address.length >= 34 && address.length <= 45 && /^q[a-z0-9]+$/.test(address)) return true;
|
||||
// HBAR (0.0.xxxxx format)
|
||||
if (/^0\.0\.\d+$/.test(address)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
window.addEventListener('hashchange', e => routeTo(window.location.hash))
|
||||
window.addEventListener("load", () => {
|
||||
document.body.classList.remove('hidden')
|
||||
@ -1798,7 +1896,7 @@
|
||||
if (!value) return { isValid: false, errorText: 'Please enter an address' }
|
||||
|
||||
return {
|
||||
isValid: isValidEthereumAddress(value) || floCrypto.validateAddr(value),
|
||||
isValid: isValidBlockchainAddress(value),
|
||||
errorText: `Invalid address.`
|
||||
}
|
||||
})
|
||||
@ -2474,7 +2572,7 @@
|
||||
target.type = target.type === 'password' ? 'text' : 'password';
|
||||
target.focusIn()
|
||||
}
|
||||
|
||||
|
||||
// Get private key by decrypting with password
|
||||
async function getPrivKeyWithPassword(password) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
@ -2510,7 +2608,7 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Derive private-key-dependent blockchain addresses (XRP, SUI, TON, TRON, DOGE, DOT, ALGO) with password
|
||||
async function derivePrivKeyAddresses() {
|
||||
try {
|
||||
@ -2592,7 +2690,16 @@
|
||||
notify(typeof e === 'string' ? e : 'Failed to unlock addresses', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let _blockchainResolve = null;
|
||||
function resolveBlockchainSelection(chain) {
|
||||
closePopup('blockchain_select_popup');
|
||||
if (_blockchainResolve) {
|
||||
_blockchainResolve(chain);
|
||||
_blockchainResolve = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getSignedIn(passwordType) {
|
||||
return new Promise((resolve, reject) => {
|
||||
routeTo(window.location.hash)
|
||||
@ -2625,10 +2732,37 @@
|
||||
if (!generalPages.find(page => window.location.hash.includes(page))) {
|
||||
location.hash = floGlobals.isPrivKeySecured ? '#/sign_in' : `#/landing`;
|
||||
}
|
||||
getRef('sign_in_button').onclick = () => {
|
||||
getRef('sign_in_button').onclick = async () => {
|
||||
let privateKey = getRef('private_key_field').value.trim();
|
||||
if (/^[0-9a-fA-F]{64}$/.test(privateKey)) //if hex private key might be ethereum
|
||||
privateKey = coinjs.privkey2wif(privateKey);
|
||||
let activeChain = null;
|
||||
|
||||
if (/^[0-9a-fA-F]{64}$/.test(privateKey)) { //if hex private key might be ethereum, ADA, SOL, TRON, DOT
|
||||
activeChain = await new Promise(resolve => {
|
||||
_blockchainResolve = resolve;
|
||||
openPopup('blockchain_select_popup');
|
||||
});
|
||||
|
||||
if (!activeChain) {
|
||||
return;
|
||||
}
|
||||
let key = new Bitcoin.ECKey(privateKey);
|
||||
key.setCompressed(true);
|
||||
privateKey = key.getBitcoinWalletImportFormat();
|
||||
} else {
|
||||
if (privateKey.startsWith('suiprivkey1')) activeChain = 'SUI';
|
||||
else if (privateKey.startsWith('s')) activeChain = 'XRP';
|
||||
else if (privateKey.startsWith('Q')) activeChain = 'DOGE';
|
||||
else if (privateKey.startsWith('T') && privateKey.length === 51) activeChain = 'LTC';
|
||||
else if (privateKey.startsWith('K') || privateKey.startsWith('L') ) activeChain = 'BTC';
|
||||
else if (privateKey.startsWith('S') && privateKey.length === 56) activeChain = 'XLM';
|
||||
else if (privateKey.startsWith('R')) activeChain = 'FLO';
|
||||
else activeChain = 'UNKNOWN';
|
||||
}
|
||||
|
||||
if (activeChain) {
|
||||
localStorage.setItem(`${floGlobals.application}#activeChain`, activeChain);
|
||||
}
|
||||
|
||||
// Store password temporarily for deriving other blockchain addresses
|
||||
if (floGlobals.isPrivKeySecured) {
|
||||
floGlobals._loginPassword = privateKey;
|
||||
@ -3405,6 +3539,14 @@
|
||||
}
|
||||
|
||||
async function updateMessageUI(messagesData, sentByMe = false) {
|
||||
const isSameAddrSafe = (a, b) => {
|
||||
if (!a || !b) return false;
|
||||
if (a === b) return true;
|
||||
try {
|
||||
return floCrypto.isSameAddr(a, b);
|
||||
} catch (e) { return false; }
|
||||
}
|
||||
|
||||
const animOptions = {
|
||||
duration: 300,
|
||||
easing: 'ease',
|
||||
@ -3414,8 +3556,8 @@
|
||||
const { category, floID, time, message, sender, groupID, admin, name, pipeID, unconfirmed, type } = messagesData[messageId]
|
||||
const chatAddress = floID || groupID || pipeID
|
||||
// code to run if a chat is opened
|
||||
if (activeChat && floCrypto.isSameAddr(activeChat.address, chatAddress) || floCrypto.isSameAddr(floCrypto.getFloID(getEthPubKey(activeChat.address)), chatAddress)) {
|
||||
if (sentByMe || type === 'TRANSACTION' || !sender || !floCrypto.isSameAddr(sender, floDapps.user.id)) {
|
||||
if (activeChat && (isSameAddrSafe(activeChat.address, chatAddress) || isSameAddrSafe(floCrypto.getFloID(getEthPubKey(activeChat.address)), chatAddress))) {
|
||||
if (sentByMe || type === 'TRANSACTION' || !sender || !isSameAddrSafe(sender, floDapps.user.id)) {
|
||||
const messageBody = render.messageBubble(messagesData[messageId]);
|
||||
getRef('messages_container').append(messageBody);
|
||||
}
|
||||
@ -3423,7 +3565,7 @@
|
||||
scrollToBottom()
|
||||
}
|
||||
// remove encryption badge if it exists
|
||||
if (!groupID && (floDapps.user.get_pubKey(activeChat.address) || getEthPubKey(activeChat.address)) && floID !== floDapps.user.id) {
|
||||
if (!groupID && (getContactPubKey(activeChat.address) || getEthPubKey(activeChat.address)) && floID !== floDapps.user.id) {
|
||||
if (getRef('warn_no_encryption')) {
|
||||
getRef('warn_no_encryption').after(
|
||||
html.node`
|
||||
@ -3445,7 +3587,7 @@
|
||||
}
|
||||
// move chat card to top if it is not already there
|
||||
const topChatCard = getRef('chats_list').children[0]
|
||||
if (!floCrypto.isSameAddr(chatAddress, topChatCard.dataset.address) && !floCrypto.isSameAddr(floCrypto.getFloID(getEthPubKey(chatAddress)), topChatCard.dataset.address)) {
|
||||
if (!isSameAddrSafe(chatAddress, topChatCard.dataset.address) && !isSameAddrSafe(floCrypto.getFloID(getEthPubKey(chatAddress)), topChatCard.dataset.address)) {
|
||||
const cloneContact = chatCard.cloneNode(true)
|
||||
chatCard.remove()
|
||||
getRef('chats_list').prepend(cloneContact)
|
||||
@ -3491,7 +3633,7 @@
|
||||
if (chatCard.querySelector('.time'))
|
||||
chatCard.querySelector('.time').textContent = getFormattedTime(time, 'relative')
|
||||
|
||||
if (floCrypto.isSameAddr(activeChat.address, chatAddress) || floCrypto.isSameAddr(floCrypto.getFloID(getEthPubKey(activeChat.address)), chatAddress)) {
|
||||
if (activeChat && (isSameAddrSafe(activeChat.address, chatAddress) || isSameAddrSafe(floCrypto.getFloID(getEthPubKey(activeChat.address)), chatAddress))) {
|
||||
if (chatScrollInfo.isScrolledUp)
|
||||
getRef('scroll_to_bottom').classList.add('new-message')
|
||||
else {
|
||||
@ -3857,7 +3999,7 @@
|
||||
const selctedPubKeys = [...selectedMembers].map(id => {
|
||||
if (id === floDapps.user.id)
|
||||
return floDapps.user.public
|
||||
return floDapps.user.get_pubKey(id)
|
||||
return getContactPubKey(id)
|
||||
});
|
||||
const minRequired = parseInt(getRef('min_sign_required').value.trim());
|
||||
const label = getRef('multisig_label').value.trim();
|
||||
@ -4152,12 +4294,24 @@
|
||||
notify(`Contact already saved`, 'error')
|
||||
return
|
||||
}
|
||||
// check whether an equivalent BTC/FLO address is already saved
|
||||
const addrInFlo = floCrypto.toFloID(addressToSave);
|
||||
const addrInBtc = btcOperator.convert.legacy2bech(addrInFlo);
|
||||
const addrInEth = floGlobals.pubKeys[addressToSave] ? floEthereum.ethAddressFromCompressedPublicKey(floGlobals.pubKeys[addressToSave]) : null;
|
||||
if (floGlobals.contacts.hasOwnProperty(addrInFlo) || floGlobals.contacts.hasOwnProperty(addrInBtc) || floGlobals.contacts.hasOwnProperty(addrInEth)) {
|
||||
notify(`Equivalent Address is already saved as ${getContactName(addrInFlo)}`, 'error');
|
||||
// check whether an equivalent BTC/FLO address is already saved (only for compatible address types)
|
||||
let addrInFlo = null, addrInBtc = null, addrInEth = null;
|
||||
try {
|
||||
// Only attempt conversion for legacy Base58 addresses (FLO/BTC format)
|
||||
if ((addressToSave.length === 33 || addressToSave.length === 34) && /^[1-9A-HJ-NP-Za-km-z]+$/.test(addressToSave)) {
|
||||
addrInFlo = floCrypto.toFloID(addressToSave);
|
||||
if (addrInFlo) {
|
||||
addrInBtc = btcOperator.convert.legacy2bech(addrInFlo);
|
||||
}
|
||||
}
|
||||
addrInEth = floGlobals.pubKeys[addressToSave] ? floEthereum.ethAddressFromCompressedPublicKey(floGlobals.pubKeys[addressToSave]) : null;
|
||||
} catch (e) {
|
||||
// Conversion not applicable for this address type
|
||||
}
|
||||
if ((addrInFlo && floGlobals.contacts.hasOwnProperty(addrInFlo)) ||
|
||||
(addrInBtc && floGlobals.contacts.hasOwnProperty(addrInBtc)) ||
|
||||
(addrInEth && floGlobals.contacts.hasOwnProperty(addrInEth))) {
|
||||
notify(`Equivalent Address is already saved as ${getContactName(addrInFlo || addressToSave)}`, 'error');
|
||||
return;
|
||||
}
|
||||
rmMessenger.storeContact(addressToSave, name).then(result => {
|
||||
@ -4199,7 +4353,7 @@
|
||||
}
|
||||
for (const floID in floGlobals.contacts) {
|
||||
if (getAddressType(floID) !== 'plain') continue;
|
||||
if (floDapps.user.get_pubKey(floID)) {
|
||||
if (getContactPubKey(floID)) {
|
||||
contacts.push(render.selectableContact(floID))
|
||||
} else {
|
||||
const hasSentRequest = skipSendingRequest.has(floID)
|
||||
@ -4331,12 +4485,19 @@
|
||||
let floChatAddress
|
||||
let btcChatAddress
|
||||
const promises = []
|
||||
floChatAddress = validateEthAddress(address) ? floCrypto.getFloID(floGlobals.pubKeys[address] || getEthPubKey(address)) : floCrypto.toFloID(address);
|
||||
// Safely try to derive FLO address (might fail for ADA/SOL etc)
|
||||
try {
|
||||
floChatAddress = validateEthAddress(address) ? floCrypto.getFloID(floGlobals.pubKeys[address] || getEthPubKey(address)) : floCrypto.toFloID(address);
|
||||
} catch (e) { }
|
||||
|
||||
if (floChatAddress) {
|
||||
btcChatAddress = btcOperator.convert.legacy2bech(floChatAddress);
|
||||
promises.push(rmMessenger.getChat(floChatAddress), rmMessenger.getChat(btcChatAddress))
|
||||
}
|
||||
if (validateEthAddress(address))
|
||||
|
||||
// Fetch chat for the address itself if it's not the derived FLO address
|
||||
// This ensures we get messages for ADA, SOL, XRP etc.
|
||||
if (address !== floChatAddress)
|
||||
promises.push(rmMessenger.getChat(address))
|
||||
Promise.all(promises)
|
||||
.then((chats) => {
|
||||
@ -4362,7 +4523,8 @@
|
||||
batchSize: 20,
|
||||
onEnd: () => {
|
||||
if (activeChat.type === 'plain') {
|
||||
if (floDapps.user.get_pubKey(activeChat.address) || getEthPubKey(activeChat.address)) {
|
||||
const hasPubKey = getContactPubKey(activeChat.address) || getEthPubKey(activeChat.address);
|
||||
if (hasPubKey) {
|
||||
getRef('messages_container').prepend(html.node`<strong class="event-card flex align-center">
|
||||
<svg class="icon margin-right-0-5" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g fill="none"><path d="M0 0h24v24H0V0z"/><path d="M0 0h24v24H0V0z" opacity=".87"/></g><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6zm9 14H6V10h12v10zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/></svg>
|
||||
Conversation is encrypted
|
||||
@ -5601,4 +5763,4 @@
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@ -392,7 +392,9 @@
|
||||
if (!address)
|
||||
return;
|
||||
var bytes;
|
||||
if (address.length == 33 || address.length == 34) { //legacy encoding
|
||||
|
||||
// FLO/BTC legacy encoding (33-34 chars)
|
||||
if (address.length == 33 || address.length == 34) {
|
||||
let decode = bitjs.Base58.decode(address);
|
||||
bytes = decode.slice(0, decode.length - 4);
|
||||
let checksum = decode.slice(decode.length - 4),
|
||||
@ -403,7 +405,9 @@
|
||||
});
|
||||
hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3] ?
|
||||
bytes = undefined : bytes.shift();
|
||||
} else if (!address.startsWith("0x") && address.length == 42 || address.length == 62) { //bech encoding
|
||||
}
|
||||
// BTC/LTC Bech32 encoding (42 or 62 chars, not starting with 0x)
|
||||
else if (!address.startsWith("0x") && (address.length == 42 || address.length == 62) && !address.startsWith("addr1")) {
|
||||
if (typeof coinjs !== 'function')
|
||||
throw "library missing (lib_btc.js)";
|
||||
let decode = coinjs.bech32_decode(address);
|
||||
@ -414,14 +418,154 @@
|
||||
if (address.length == 62) //for long bech, aggregate once more to get 160 bit
|
||||
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
|
||||
}
|
||||
} else if (address.length == 66) { //public key hex
|
||||
}
|
||||
// Public key hex (66 chars starting with 02, 03, or 04)
|
||||
else if (address.length == 66 && /^0[234][a-fA-F0-9]{64}$/.test(address)) {
|
||||
bytes = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(address), {
|
||||
asBytes: true
|
||||
}));
|
||||
} else if ((address.length == 42 && address.startsWith("0x")) || (address.length == 40 && !address.startsWith("0x"))) { //Ethereum Address
|
||||
}
|
||||
// Ethereum/EVM Address (40 or 42 chars with 0x prefix)
|
||||
else if ((address.length == 42 && address.startsWith("0x")) || (address.length == 40 && /^[a-fA-F0-9]{40}$/.test(address))) {
|
||||
if (address.startsWith("0x")) { address = address.substring(2); }
|
||||
bytes = Crypto.util.hexToBytes(address);
|
||||
}
|
||||
// SUI Address (66 chars with 0x prefix, 64 hex chars)
|
||||
else if (address.length == 66 && address.startsWith("0x") && /^0x[a-fA-F0-9]{64}$/.test(address)) {
|
||||
// SUI uses 32-byte (256-bit) addresses, hash to get 20 bytes for compatibility
|
||||
let fullBytes = Crypto.util.hexToBytes(address.substring(2));
|
||||
bytes = ripemd160(Crypto.SHA256(fullBytes, { asBytes: true }));
|
||||
}
|
||||
// Solana Address (Base58, 43-44 chars)
|
||||
else if ((address.length == 43 || address.length == 44) && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address)) {
|
||||
try {
|
||||
// Solana address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
// XRP Address (Base58, starts with 'r', 25-35 chars)
|
||||
else if (address.length >= 25 && address.length <= 35 && address.startsWith('r')) {
|
||||
try {
|
||||
// XRP address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
// TRON Address (Base58, starts with 'T', 34 chars)
|
||||
else if (address.length == 34 && address.startsWith('T')) {
|
||||
try {
|
||||
// TRON address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
// Cardano Address (Bech32, starts with 'addr1', typically 98-103 chars)
|
||||
else if (address.startsWith('addr1') && address.length > 50) {
|
||||
try {
|
||||
// Cardano bech32 address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
// Polkadot Address (SS58, starts with '1', 47-48 chars)
|
||||
else if ((address.length == 47 || address.length == 48) && /^1[a-zA-Z0-9]+$/.test(address)) {
|
||||
try {
|
||||
// Polkadot address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
// TON Address (Base64, 48 chars)
|
||||
else if (address.length == 48 && /^[A-Za-z0-9_-]+$/.test(address)) {
|
||||
try {
|
||||
// TON address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
// Algorand Address (Base32, 58 chars)
|
||||
else if (address.length == 58 && /^[A-Z2-7]+$/.test(address)) {
|
||||
try {
|
||||
// Algorand address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
// Stellar Address (Base32, starts with 'G', 56 chars)
|
||||
else if (address.length == 56 && address.startsWith('G')) {
|
||||
try {
|
||||
// Stellar address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
// Bitcoin Cash CashAddr format (without prefix, 42 chars of data)
|
||||
else if (address.length >= 34 && address.length <= 45 && /^q[a-z0-9]+$/.test(address)) {
|
||||
try {
|
||||
// BCH address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
// HBAR Address (account ID format: 0.0.xxxxx)
|
||||
else if (/^0\.0\.\d+$/.test(address)) {
|
||||
try {
|
||||
// HBAR address - hash the raw address for unique proxy ID
|
||||
let addrBytes = [];
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
addrBytes.push(address.charCodeAt(i));
|
||||
}
|
||||
bytes = ripemd160(Crypto.SHA256(addrBytes, { asBytes: true }));
|
||||
} catch (e) {
|
||||
bytes = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bytes)
|
||||
throw "Invalid address: " + address;
|
||||
@ -535,9 +679,17 @@
|
||||
//send any message to supernode cloud storage
|
||||
const sendApplicationData = floCloudAPI.sendApplicationData = function (message, type, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let originalReceiverID = options.receiverID || DEFAULT.adminID;
|
||||
let serverReceiverID = originalReceiverID;
|
||||
try {
|
||||
if (!floCrypto.validateAddr(originalReceiverID)) {
|
||||
serverReceiverID = proxyID(originalReceiverID);
|
||||
}
|
||||
} catch (e) { }
|
||||
console.log("[SEND] original:", originalReceiverID, "server:", serverReceiverID);
|
||||
var data = {
|
||||
senderID: user.id,
|
||||
receiverID: options.receiverID || DEFAULT.adminID,
|
||||
receiverID: serverReceiverID,
|
||||
pubKey: user.public,
|
||||
message: encodeMessage(message),
|
||||
time: Date.now(),
|
||||
@ -548,7 +700,7 @@
|
||||
let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"]
|
||||
.map(d => data[d]).join("|")
|
||||
data.sign = user.sign(hashcontent);
|
||||
singleRequest(data.receiverID, data)
|
||||
singleRequest(originalReceiverID, data)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
@ -557,8 +709,16 @@
|
||||
//request any data from supernode cloud
|
||||
const requestApplicationData = floCloudAPI.requestApplicationData = function (type, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let originalReceiverID = options.receiverID || DEFAULT.adminID;
|
||||
let serverReceiverID = originalReceiverID;
|
||||
try {
|
||||
if (!floCrypto.validateAddr(originalReceiverID)) {
|
||||
serverReceiverID = proxyID(originalReceiverID);
|
||||
}
|
||||
} catch (e) { }
|
||||
console.log("[RECV] original:", originalReceiverID, "server:", serverReceiverID);
|
||||
var request = {
|
||||
receiverID: options.receiverID || DEFAULT.adminID,
|
||||
receiverID: serverReceiverID,
|
||||
senderID: options.senderID || undefined,
|
||||
application: options.application || DEFAULT.application,
|
||||
type: type,
|
||||
@ -571,7 +731,7 @@
|
||||
}
|
||||
|
||||
if (options.callback instanceof Function) {
|
||||
liveRequest(request.receiverID, request, options.callback)
|
||||
liveRequest(originalReceiverID, request, options.callback)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
} else {
|
||||
@ -580,7 +740,7 @@
|
||||
time: Date.now(),
|
||||
request
|
||||
};
|
||||
singleRequest(request.receiverID, request, options.method || "GET")
|
||||
singleRequest(originalReceiverID, request, options.method || "GET")
|
||||
.then(data => resolve(data)).catch(error => reject(error))
|
||||
}
|
||||
})
|
||||
|
||||
@ -431,8 +431,8 @@
|
||||
} else
|
||||
return null;
|
||||
} else if ((address.length == 42 && address.startsWith("0x")) || (address.length == 40 && !address.startsWith("0x"))) { //Ethereum Address
|
||||
if (address.startsWith("0x")) { address = address.substring(2); }
|
||||
let bytes = Crypto.util.hexToBytes(address);
|
||||
if (address.startsWith("0x")) { address = address.substring(2); }
|
||||
let bytes = Crypto.util.hexToBytes(address);
|
||||
return {
|
||||
version: 1,
|
||||
hex: address,
|
||||
|
||||
@ -30,13 +30,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
var user_id, user_public, user_private;
|
||||
var user_id, user_public, user_private, user_loginID;
|
||||
const user = floDapps.user = {
|
||||
get id() {
|
||||
if (!user_id)
|
||||
throw "User not logged in";
|
||||
return user_id;
|
||||
},
|
||||
get loginID() {
|
||||
if (!user_loginID)
|
||||
return user_id; // Default to FLO ID if not set
|
||||
return user_loginID;
|
||||
},
|
||||
get public() {
|
||||
if (!user_public)
|
||||
throw "User not logged in";
|
||||
@ -97,7 +102,7 @@
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
user_id = user_public = user_private = undefined;
|
||||
user_id = user_public = user_private = user_loginID = undefined;
|
||||
user_priv_raw = aes_key = undefined;
|
||||
delete user.contacts;
|
||||
delete user.pubKeys;
|
||||
@ -450,8 +455,80 @@
|
||||
try {
|
||||
user_public = floCrypto.getPubKeyHex(privKey);
|
||||
user_id = floCrypto.getAddress(privKey);
|
||||
if (startUpOptions.cloud)
|
||||
floCloudAPI.user(user_id, privKey); //Set user for floCloudAPI
|
||||
|
||||
// Derive Login ID based on Private Key Type
|
||||
try {
|
||||
if (privKey.length === 64) { // Hex Key -> Ethereum
|
||||
if (typeof floEthereum !== 'undefined' && floEthereum.ethAddressFromPrivateKey) {
|
||||
user_loginID = floEthereum.ethAddressFromPrivateKey(privKey);
|
||||
}
|
||||
} else { // WIF Key
|
||||
let decode = bitjs.Base58.decode(privKey);
|
||||
let version = decode[0];
|
||||
switch (version) {
|
||||
case 128: // BTC (Mainnet WIF)
|
||||
case 0: // BTC (Address Version - fallback)
|
||||
if (typeof btcOperator !== 'undefined')
|
||||
user_loginID = new Bitcoin.ECKey(privKey).getBitcoinAddress();
|
||||
break;
|
||||
case 176: // LTC (Mainnet WIF)
|
||||
case 48: // LTC (Address Version - fallback)
|
||||
if (typeof convertWIFtoLitecoinAddress === 'function')
|
||||
user_loginID = convertWIFtoLitecoinAddress(privKey);
|
||||
break;
|
||||
case 163: // FLO (Mainnet WIF 0xA3)
|
||||
case 35: // FLO (Address Version)
|
||||
user_loginID = user_id;
|
||||
break;
|
||||
case 158: // DOGE (Mainnet WIF 0x9E)
|
||||
case 30: // DOGE (Address Version)
|
||||
if (typeof convertWIFtoDogeAddress === 'function')
|
||||
user_loginID = convertWIFtoDogeAddress(privKey);
|
||||
break;
|
||||
}
|
||||
// Manual overrides/checks for keys that might not match standard WIF versions or rely on library detection
|
||||
if (!user_loginID) {
|
||||
// Try XRP
|
||||
if (typeof convertWIFtoXrpAddress === 'function') {
|
||||
let xrpAddr = convertWIFtoXrpAddress(privKey);
|
||||
if (xrpAddr) user_loginID = xrpAddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Login ID derivation failed:", e);
|
||||
}
|
||||
if (!user_loginID) user_loginID = user_id; // Fallback
|
||||
|
||||
if (startUpOptions.cloud) {
|
||||
// Ensure we pass a format floCloudAPI/floCrypto accepts.
|
||||
// For DOGE/LTC WIFs, Bitcoin.ECKey might reject the version byte if gloabls aren't set.
|
||||
// safer to pass RAW HEX Private Key to floCloudAPI.
|
||||
let cloudPrivKey = privKey;
|
||||
try {
|
||||
if (privKey.length > 50 && typeof bitjs !== 'undefined') { // valid WIF length check
|
||||
let decode = bitjs.Base58.decode(privKey);
|
||||
if (decode && decode.length) {
|
||||
// Remove 4-byte checksum
|
||||
let raw = decode.slice(0, decode.length - 4);
|
||||
// Remove 1-byte Version
|
||||
raw.shift();
|
||||
// Check compression flag (last byte 0x01) if length is 33
|
||||
if (raw.length === 33 && raw[32] === 1) {
|
||||
raw.pop();
|
||||
}
|
||||
// Convert to Hex
|
||||
if (raw.length === 32)
|
||||
cloudPrivKey = Crypto.util.bytesToHex(raw);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("WIF decode for cloud failed, using original:", e);
|
||||
}
|
||||
floCloudAPI.user(user_loginID, cloudPrivKey);
|
||||
}
|
||||
|
||||
|
||||
user_priv_wrap = () => checkIfPinRequired(key);
|
||||
let n = floCrypto.randInt(12, 20);
|
||||
aes_key = floCrypto.randString(n);
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
direct: (d, e) => console.log(d, e),
|
||||
chats: (c) => console.log(c),
|
||||
mails: (m) => console.log(m),
|
||||
marked: (r) => console.log(r)
|
||||
marked: (r) => console.log(r),
|
||||
onChatMigrated: (oldID, newID) => console.log(`Migrated ${oldID} to ${newID}`)
|
||||
};
|
||||
rmMessenger.renderUI = {};
|
||||
Object.defineProperties(rmMessenger.renderUI, {
|
||||
@ -39,6 +40,9 @@
|
||||
},
|
||||
marked: {
|
||||
set: ui_fn => UI.marked = ui_fn
|
||||
},
|
||||
onChatMigrated: {
|
||||
set: ui_fn => UI.onChatMigrated = ui_fn
|
||||
}
|
||||
});
|
||||
|
||||
@ -74,13 +78,58 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Validate any blockchain address (all 19 supported chains)
|
||||
function isValidBlockchainAddress(address) {
|
||||
if (!address || typeof address !== 'string') return false;
|
||||
|
||||
// FLO/BTC/DOGE/LTC legacy (Base58, 33-34 chars)
|
||||
if ((address.length === 33 || address.length === 34) && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address)) {
|
||||
return floCrypto.validateAddr(address) || true;
|
||||
}
|
||||
// Ethereum/EVM addresses (0x prefix, 40 hex chars) - ETH, AVAX, BSC, MATIC
|
||||
if (/^0x[a-fA-F0-9]{40}$/.test(address)) return true;
|
||||
// SUI addresses (0x prefix, 64 hex chars)
|
||||
if (/^0x[a-fA-F0-9]{64}$/.test(address)) return true;
|
||||
// BTC/LTC Bech32 (bc1/ltc1 prefix)
|
||||
if (/^(bc1|ltc1)[a-zA-HJ-NP-Z0-9]{25,62}$/.test(address)) return true;
|
||||
// Solana (Base58, 43-44 chars)
|
||||
if ((address.length === 43 || address.length === 44) && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address)) return true;
|
||||
// XRP (r-prefix, 25-35 chars)
|
||||
if (address.startsWith('r') && address.length >= 25 && address.length <= 35 && /^r[a-zA-Z0-9]+$/.test(address)) return true;
|
||||
// TRON (T-prefix, 34 chars)
|
||||
if (address.startsWith('T') && address.length === 34 && /^T[a-zA-Z0-9]+$/.test(address)) return true;
|
||||
// Cardano (addr1 prefix, Bech32)
|
||||
if (address.startsWith('addr1') && address.length > 50) return true;
|
||||
// Polkadot (SS58, starts with 1, 47-48 chars)
|
||||
if (address.startsWith('1') && (address.length === 47 || address.length === 48) && /^[a-zA-Z0-9]+$/.test(address)) return true;
|
||||
// TON (Base64 URL-safe, 48 chars)
|
||||
if (address.length === 48 && /^[A-Za-z0-9_-]+$/.test(address)) return true;
|
||||
// Algorand (Base32, 58 chars, uppercase + 2-7)
|
||||
if (address.length === 58 && /^[A-Z2-7]+$/.test(address)) return true;
|
||||
// Stellar (G-prefix, Base32, 56 chars)
|
||||
if (address.startsWith('G') && address.length === 56 && /^G[A-Z2-7]+$/.test(address)) return true;
|
||||
// Bitcoin Cash CashAddr (q-prefix)
|
||||
if (address.startsWith('q') && address.length >= 34 && address.length <= 45 && /^q[a-z0-9]+$/.test(address)) return true;
|
||||
// HBAR (0.0.xxxxx format)
|
||||
if (/^0\.0\.\d+$/.test(address)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function sendRaw(message, recipient, type, encrypt = null, comment = undefined) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(recipient))
|
||||
if (!isValidBlockchainAddress(recipient))
|
||||
return reject("Invalid Recipient");
|
||||
|
||||
if ([true, null].includes(encrypt)) {
|
||||
let r_pubKey = floDapps.user.get_pubKey(recipient);
|
||||
// Try to get pubKey safely (may fail for non-FLO addresses)
|
||||
let r_pubKey = null;
|
||||
try {
|
||||
r_pubKey = floDapps.user.get_pubKey(recipient);
|
||||
} catch (e) {
|
||||
// Address format not supported by floCrypto.decodeAddr
|
||||
r_pubKey = floGlobals.pubKeys ? floGlobals.pubKeys[recipient] : null;
|
||||
}
|
||||
if (r_pubKey)
|
||||
message = floCrypto.encryptData(message, r_pubKey);
|
||||
else if (encrypt === true)
|
||||
@ -89,8 +138,44 @@
|
||||
let options = {
|
||||
receiverID: recipient,
|
||||
}
|
||||
if (comment)
|
||||
options.comment = comment
|
||||
if (comment) {
|
||||
options.comment = comment;
|
||||
}
|
||||
|
||||
// If we're logged in with an alt chain, embed it in the comment for migration mapping
|
||||
try {
|
||||
let activeChain = localStorage.getItem(`${floGlobals.application}#activeChain`);
|
||||
if (activeChain && activeChain !== 'FLO') {
|
||||
let proxyID = null;
|
||||
switch (activeChain) {
|
||||
case 'ETH':
|
||||
case 'AVAX':
|
||||
case 'BSC':
|
||||
case 'MATIC':
|
||||
case 'HBAR':
|
||||
proxyID = floEthereum.ethAddressFromCompressedPublicKey(user.public); break;
|
||||
case 'BTC': proxyID = floGlobals.myBtcID; break;
|
||||
case 'BCH': proxyID = floGlobals.myBchID; break;
|
||||
case 'XRP': proxyID = floGlobals.myXrpID; break;
|
||||
case 'SUI': proxyID = floGlobals.mySuiID; break;
|
||||
case 'TON': proxyID = floGlobals.myTonID; break;
|
||||
case 'TRON': proxyID = floGlobals.myTronID; break;
|
||||
case 'DOGE': proxyID = floGlobals.myDogeID; break;
|
||||
case 'LTC': proxyID = floGlobals.myLtcID; break;
|
||||
case 'DOT': proxyID = floGlobals.myDotID; break;
|
||||
case 'ALGO': proxyID = floGlobals.myAlgoID; break;
|
||||
case 'XLM': proxyID = floGlobals.myXlmID; break;
|
||||
case 'SOL': proxyID = floGlobals.mySolID; break;
|
||||
case 'ADA': proxyID = floGlobals.myAdaID; break;
|
||||
}
|
||||
if (proxyID && proxyID !== user.id) {
|
||||
options.comment = (options.comment ? options.comment + "|" : "") + "FROM_ALT:" + proxyID;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Could not append FROM_ALT tag", e);
|
||||
}
|
||||
|
||||
floCloudAPI.sendApplicationData(message, type, options)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
@ -316,7 +401,7 @@
|
||||
|
||||
const processData = {};
|
||||
processData.direct = function () {
|
||||
return (unparsed, newInbox) => {
|
||||
return async (unparsed, newInbox) => {
|
||||
//store the pubKey if not stored already
|
||||
floDapps.storePubKey(unparsed.senderID, unparsed.pubKey);
|
||||
if (_loaded.blocked.has(unparsed.senderID) && unparsed.type !== "REVOKE_KEY")
|
||||
@ -326,6 +411,51 @@
|
||||
let vc = unparsed.vectorClock;
|
||||
switch (unparsed.type) {
|
||||
case "MESSAGE": { //process as message
|
||||
let vc = unparsed.vectorClock;
|
||||
|
||||
// Chat Migration Logic
|
||||
if (unparsed.comment && unparsed.comment.includes("FROM_ALT:")) {
|
||||
let altID = unparsed.comment.split("FROM_ALT:")[1].split("|")[0]; // Extract the alt ID
|
||||
if (altID && altID !== unparsed.senderID) {
|
||||
// Check if an existing chat with this alt ID exists
|
||||
if (_loaded.chats[altID] !== undefined) {
|
||||
console.log(`Migrating chat history from ${altID} to ${unparsed.senderID}`);
|
||||
|
||||
// Run migration asynchronously but wait for it to complete
|
||||
try {
|
||||
// Migrate Messages
|
||||
let _options = { lowerKey: `${altID}|`, upperKey: `${altID}||` };
|
||||
let result = await compactIDB.searchData("messages", _options);
|
||||
for (let i in result) {
|
||||
let messageData = result[i];
|
||||
messageData.floID = unparsed.senderID;
|
||||
let oldVc = i.split("|")[1];
|
||||
await compactIDB.addData("messages", messageData, `${unparsed.senderID}|${oldVc}`).catch(e => console.warn(e));
|
||||
await compactIDB.removeData("messages", i);
|
||||
}
|
||||
|
||||
// Migrate Contacts
|
||||
if (floGlobals.contacts[altID]) {
|
||||
let contactName = floGlobals.contacts[altID];
|
||||
floDapps.storeContact(unparsed.senderID, contactName);
|
||||
await compactIDB.removeData("contacts", altID, floDapps.user.db_name);
|
||||
delete floGlobals.contacts[altID];
|
||||
}
|
||||
|
||||
// Delete old chat reference
|
||||
delete _loaded.chats[altID];
|
||||
await compactIDB.removeData("chats", altID);
|
||||
|
||||
// Trigger UI update if available
|
||||
if (UI.onChatMigrated) UI.onChatMigrated(altID, unparsed.senderID);
|
||||
if (UI.chats) await UI.chats(getChatOrder());
|
||||
} catch (error) {
|
||||
console.error("Migration failed:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dm = {
|
||||
time: unparsed.time,
|
||||
floID: unparsed.senderID,
|
||||
@ -333,7 +463,12 @@
|
||||
message: encrypt(unparsed.message)
|
||||
}
|
||||
console.debug(dm, `${dm.floID}|${vc}`);
|
||||
compactIDB.addData("messages", Object.assign({}, dm), `${dm.floID}|${vc}`)
|
||||
try {
|
||||
await compactIDB.addData("messages", Object.assign({}, dm), `${dm.floID}|${vc}`);
|
||||
} catch (e) {
|
||||
console.warn("Message already exists (skipping UI push):", e);
|
||||
break; // Deduplicate: don't push to UI if we already processed it (e.g. self-messaging)
|
||||
}
|
||||
_loaded.chats[dm.floID] = parseInt(vc)
|
||||
compactIDB.writeData("chats", parseInt(vc), dm.floID)
|
||||
dm.message = unparsed.message;
|
||||
@ -431,13 +566,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function requestDirectInbox() {
|
||||
const requestDirectInbox = rmMessenger.reconnectInbox = function () {
|
||||
if (directConnID.length) { //close existing request connection (if any)
|
||||
directConnID.forEach(id => floCloudAPI.closeRequest(id));
|
||||
directConnID = [];
|
||||
}
|
||||
const parseData = processData.direct();
|
||||
let callbackFn = function (dataSet, error) {
|
||||
let callbackFn = async function (dataSet, error) {
|
||||
if (error)
|
||||
return console.error(error)
|
||||
let newInbox = {
|
||||
@ -449,9 +584,11 @@
|
||||
keyrevoke: [],
|
||||
pipeline: {}
|
||||
}
|
||||
for (let vc in dataSet) {
|
||||
// Await processing in order according to vector clocks
|
||||
let sortedVCs = Object.keys(dataSet).sort((a, b) => parseInt(a) - parseInt(b));
|
||||
for (let vc of sortedVCs) {
|
||||
try {
|
||||
parseData(dataSet[vc], newInbox);
|
||||
await parseData(dataSet[vc], newInbox);
|
||||
} catch (error) {
|
||||
//if (error !== "blocked-user")
|
||||
console.log(error);
|
||||
@ -464,19 +601,80 @@
|
||||
console.debug(newInbox);
|
||||
UI.direct(newInbox)
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const promises = [
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// All blockchain address IDs to listen on
|
||||
let activeChain = localStorage.getItem(`${floGlobals.application}#activeChain`);
|
||||
const blockchainAddressIDs = [user.id]; // Always listen to FLO address (primary)
|
||||
|
||||
if (!activeChain) {
|
||||
try {
|
||||
let privKey = floDapps.user.private;
|
||||
if (privKey instanceof Promise) privKey = await privKey;
|
||||
if (typeof privKey === 'string' && privKey.length > 0) {
|
||||
if (privKey.startsWith('suiprivkey1')) activeChain = 'SUI';
|
||||
else if (privKey.startsWith('s')) activeChain = 'XRP';
|
||||
else if (privKey.startsWith('Q') || privKey.startsWith('6')) activeChain = 'DOGE';
|
||||
else if (privKey.startsWith('T') && privKey.length === 51) activeChain = 'LTC';
|
||||
else if (privKey.startsWith('K') || privKey.startsWith('L') || privKey.startsWith('5')) activeChain = 'BTC';
|
||||
else if (privKey.startsWith('S') && privateKey.length === 56) activeChain = 'XLM';
|
||||
else if (privKey.startsWith('R') || privKey.startsWith('c') || privKey.startsWith('p')) activeChain = 'FLO';
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Could not deduce fallback activeChain", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeChain) {
|
||||
const addIfValid = (id) => { if (id && !blockchainAddressIDs.includes(id)) blockchainAddressIDs.push(id) };
|
||||
|
||||
switch (activeChain) {
|
||||
case 'ETH':
|
||||
case 'AVAX':
|
||||
case 'BSC':
|
||||
case 'MATIC':
|
||||
case 'HBAR':
|
||||
addIfValid(floEthereum.ethAddressFromCompressedPublicKey(user.public));
|
||||
break;
|
||||
case 'BTC': addIfValid(floGlobals.myBtcID); break;
|
||||
case 'BCH': addIfValid(floGlobals.myBchID); break;
|
||||
case 'XRP': addIfValid(floGlobals.myXrpID); break;
|
||||
case 'SUI': addIfValid(floGlobals.mySuiID); break;
|
||||
case 'TON': addIfValid(floGlobals.myTonID); break;
|
||||
case 'TRON': addIfValid(floGlobals.myTronID); break;
|
||||
case 'DOGE': addIfValid(floGlobals.myDogeID); break;
|
||||
case 'LTC': addIfValid(floGlobals.myLtcID); break;
|
||||
case 'DOT': addIfValid(floGlobals.myDotID); break;
|
||||
case 'ALGO': addIfValid(floGlobals.myAlgoID); break;
|
||||
case 'XLM': addIfValid(floGlobals.myXlmID); break;
|
||||
case 'SOL': addIfValid(floGlobals.mySolID); break;
|
||||
case 'ADA': addIfValid(floGlobals.myAdaID); break;
|
||||
case 'FLO': break;
|
||||
}
|
||||
} else {
|
||||
// Fallback: listen to all derived addresses if no active chain is set
|
||||
const allDerived = [
|
||||
floEthereum.ethAddressFromCompressedPublicKey(user.public),
|
||||
floGlobals.myBtcID, floGlobals.myAvaxID, floGlobals.myBscID,
|
||||
floGlobals.myMaticID, floGlobals.myHbarID, floGlobals.myXrpID,
|
||||
floGlobals.mySuiID, floGlobals.myTonID, floGlobals.myTronID,
|
||||
floGlobals.myDogeID, floGlobals.myLtcID, floGlobals.myBchID,
|
||||
floGlobals.myDotID, floGlobals.myAlgoID, floGlobals.myXlmID,
|
||||
floGlobals.mySolID, floGlobals.myAdaID
|
||||
];
|
||||
allDerived.forEach(id => {
|
||||
if (id && !blockchainAddressIDs.includes(id)) {
|
||||
blockchainAddressIDs.push(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const promises = blockchainAddressIDs.map(receiverID =>
|
||||
floCloudAPI.requestApplicationData(null, {
|
||||
receiverID: user.id,
|
||||
lowerVectorClock: _loaded.appendix.lastReceived + 1,
|
||||
callback: callbackFn
|
||||
}),
|
||||
floCloudAPI.requestApplicationData(null, {
|
||||
receiverID: floEthereum.ethAddressFromCompressedPublicKey(user.public),
|
||||
receiverID: receiverID,
|
||||
lowerVectorClock: _loaded.appendix.lastReceived + 1,
|
||||
callback: callbackFn
|
||||
})
|
||||
]
|
||||
);
|
||||
Promise.all(promises).then(connectionIds => {
|
||||
directConnID = [...directConnID, ...connectionIds];
|
||||
resolve("Direct Inbox connected");
|
||||
@ -513,7 +711,17 @@
|
||||
}
|
||||
|
||||
rmMessenger.storeContact = function (floID, name) {
|
||||
return floDapps.storeContact(floID, name)
|
||||
// For FLO/BTC addresses, use the standard validation
|
||||
if (floCrypto.validateAddr(floID)) {
|
||||
return floDapps.storeContact(floID, name);
|
||||
}
|
||||
// For other blockchain addresses (ETH, SOL, ADA, etc.), store directly
|
||||
return new Promise((resolve, reject) => {
|
||||
compactIDB.writeData("contacts", name, floID, floDapps.user.db_name).then(result => {
|
||||
floGlobals.contacts[floID] = name;
|
||||
resolve("Contact stored");
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
const loadDataFromIDB = function (defaultList = true) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user