1927 lines
96 KiB
HTML
1927 lines
96 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>FLO Messenger</title>
|
|
<link rel="stylesheet" href="css/dist/main.css">
|
|
<script id="floGlobals">
|
|
/* Constants for FLO blockchain operations !!Make sure to add this at begining!! */
|
|
const floGlobals = {
|
|
|
|
//Required for all
|
|
blockchain: "FLO",
|
|
|
|
//Required for blockchain API operators
|
|
apiURL: {
|
|
FLO: ['https://explorer.mediciland.com/', 'https://livenet.flocha.in/',
|
|
'https://flosight.duckdns.org/', 'http://livenet-explorer.floexperiments.com/'
|
|
],
|
|
FLO_TEST: ['https://testnet-flosight.duckdns.org/', 'https://testnet.flocha.in/']
|
|
},
|
|
|
|
//Required for Supernode operations
|
|
SNStorageID: "FEzk75EGMPEQMrCuPosGiwuK162hcEu49E",
|
|
supernodes: {}, //each supnernode must be stored as floID : {uri:<uri>,pubKey:<publicKey>}
|
|
|
|
//for app
|
|
application: "messenger",
|
|
pubKeys: {},
|
|
contacts: {},
|
|
appendix: {}
|
|
}
|
|
</script>
|
|
<script id="onLoadStartUp">
|
|
function onLoadStartUp() {
|
|
privKeyNotSecured = true;
|
|
|
|
//display loading screen
|
|
loadingPage.classList.remove("hide-completely")
|
|
//clear Rendered Elements
|
|
let elementsToReset = ["startup-load-msg", 'inbox_mail_container', 'sent_mail_container', 'contacts_container', 'chat_container',
|
|
'receiver_floID', 'receiver_name'
|
|
]
|
|
//, "backup_info"
|
|
elementsToReset.forEach(e => clearElement(document.getElementById(e)))
|
|
|
|
floDapps.setCustomPrivKeyInput((type) => {
|
|
return new Promise((resolve, reject) => {
|
|
mainPage.classList.add('hide-completely')
|
|
loadingPage.classList.add('hide-completely')
|
|
signInLanding.classList.remove('hide-completely')
|
|
signInButton.addEventListener('clicked', () => {
|
|
let key = privateKeyInputField.value;
|
|
signInPopup.hide()
|
|
console.log(key)
|
|
signInLanding.classList.add('hide-completely')
|
|
loadingPage.classList.remove('hide-completely')
|
|
resolve(key)
|
|
})
|
|
guestLoginButton.onclick = () => {
|
|
signInLanding.classList.add('hide-completely')
|
|
loadingPage.classList.remove('hide-completely')
|
|
reject(null)
|
|
}
|
|
if (type === "PRIVATE_KEY") {
|
|
privateKeyInputField.setAttribute("placeholder", "Private Key")
|
|
document.getElementById("type_of_key").textContent = 'FLO private key'
|
|
document.getElementById("remove_account").classList.add("hide-completely");
|
|
} else if (type === "PIN/Password") {
|
|
privateKeyInputField.setAttribute("placeholder", "PIN/Password")
|
|
signInPopup.show()
|
|
privateKeyInputField.focusIn()
|
|
document.getElementById("remove_account").classList.remove("hide-completely");
|
|
document.getElementById("type_of_key").textContent = 'PIN/Password'
|
|
privKeyNotSecured = false;
|
|
}
|
|
})
|
|
})
|
|
|
|
//invoke the startup functions
|
|
floDapps.launchStartUp().then(result => {
|
|
console.log(result)
|
|
document.getElementById("greet_tag").textContent = myFloID
|
|
//load messages from IDB and render them
|
|
reactor.dispatchEvent("startUpSuccessLog", `Loading Data! Please Wait...`)
|
|
messenger.loadDataFromIDB().then(data => {
|
|
floGlobals.pubKeys = data.pubKeys;
|
|
floGlobals.contacts = data.contacts;
|
|
floGlobals.settings = data.settings;
|
|
floGlobals.appendix = data.appendix;
|
|
renderContactList(floGlobals.contacts)
|
|
renderMailContactList(floGlobals.contacts)
|
|
renderMessages(data.messages, false)
|
|
renderMailList(data.mails, false)
|
|
renderMarked(data.marked)
|
|
reactor.dispatchEvent("startUpSuccessLog", `Load Successful!`)
|
|
//hide loading screen
|
|
loadingPage.classList.add("hide-completely")
|
|
mainPage.classList.remove('hide-completely')
|
|
chatSection.classList.add('hide-completely')
|
|
mailSection.classList.add('hide-completely')
|
|
refreshAgain()
|
|
document.getElementById('chat_page_button').click()
|
|
if(privKeyNotSecured){
|
|
notify("Private key is not secured with password. Remember to secure the private key in settings", "warn", '')
|
|
document.getElementById("secure_key").textContent = 'Set password'
|
|
}
|
|
else{
|
|
document.getElementById("secure_key").textContent = 'Change password'
|
|
}
|
|
}).catch(error => {
|
|
reactor.dispatchEvent("startUpErrorLog", `Failed to load data`)
|
|
notify(error, "error")
|
|
})
|
|
}).catch(error => notify(error, "error"))
|
|
}
|
|
</script>
|
|
</head>
|
|
|
|
<body data-theme="light" onload="onLoadStartUp()">
|
|
<audio id="notification_sound">
|
|
<source src="https://rmservices.duckdns.org/files/notification-sound.mp3" type="audio/mpeg">
|
|
<source src="https://rmservices.duckdns.org/files/notification-sound.ogg" type="audio/ogg">
|
|
</audio>
|
|
<div id="startup-load-msg" class="hide-completely"></div>
|
|
<sm-notifications id="notifications"></sm-notifications>
|
|
|
|
<div id="sign_in_page" class="page hide-completely">
|
|
<header class="logo-section">
|
|
<svg class="main-logo" viewBox="0 0 27.25 32">
|
|
<title>RanchiMall</title>
|
|
<path
|
|
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z" />
|
|
</svg>
|
|
<h4 class="light-text">
|
|
RanchiMall
|
|
</h4>
|
|
</header>
|
|
<div id="sign_in">
|
|
<div class="left">
|
|
<h4>
|
|
Messenger
|
|
<div class="line"></div>
|
|
</h4>
|
|
<h1 class="title-font">
|
|
Messages or Emails?<br>
|
|
Why Not Both!
|
|
</h1>
|
|
<div class="flex align-center">
|
|
<sm-button variant="primary" id="open_sign_in_button">
|
|
login
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<title>chevron_right</title>
|
|
<polyline points="15.99 0.35 47.65 32 15.99 63.65" />
|
|
</svg>
|
|
</sm-button>
|
|
<sm-button id="guest_login_button" variant="outlined">Guest login</sm-button>
|
|
</div>
|
|
<p class="light-text">New here? login as guest and get your FLO private key.</p>
|
|
</div>
|
|
<div id="sign_in_illustration" class="right">
|
|
<svg viewBox="0 0 512 512">
|
|
<defs>
|
|
<style>
|
|
.a {
|
|
fill: #fff;
|
|
}
|
|
|
|
.b {
|
|
fill: #ccc;
|
|
}
|
|
|
|
.c {
|
|
fill: none;
|
|
}
|
|
|
|
.c,
|
|
.d,
|
|
.e {
|
|
stroke: #000;
|
|
stroke-miterlimit: 10;
|
|
stroke-width: 20;
|
|
}
|
|
|
|
.d {
|
|
fill: #ed1c24;
|
|
}
|
|
|
|
.e {
|
|
fill: #d30d41;
|
|
}
|
|
</style>
|
|
</defs>
|
|
<title>mascot</title>
|
|
<path class="a"
|
|
d="M506,367.94v51.19a52,52,0,0,1-52,52H130.4a52,52,0,0,1-52-52V183.69L9.58,40.88,454,43.24a52,52,0,0,1,52,52.05V225.38" />
|
|
<path class="b"
|
|
d="M506,98.69V431.58c.57,56.06-149.95,44-149.95,44a52,52,0,0,0,52-52V44.86L454,46.65A52,52,0,0,1,506,98.69Z" />
|
|
<line class="c" x1="506" y1="273.75" x2="506" y2="327.21" />
|
|
<path class="c"
|
|
d="M506,367.94v51.19a52,52,0,0,1-52,52H130.4a52,52,0,0,1-52-52V183.69L9.58,40.88,454,43.24a52,52,0,0,1,52,52.05V225.38" />
|
|
<path class="d" d="M361.83,340a69.63,69.63,0,1,1-139.26,0C222.57,301.5,361.83,301.5,361.83,340Z" />
|
|
<path class="c" d="M152,242.43a34.32,34.32,0,0,1,64.68.2" />
|
|
<path class="c" d="M367.79,242.43a34.32,34.32,0,0,1,64.68.2" />
|
|
<path class="e" d="M325,314.13c21,4.22,36.85,12.82,36.85,25.8a69.7,69.7,0,0,1-41.09,63.58" />
|
|
</svg>
|
|
<div class="circle"></div>
|
|
<div class="circle"></div>
|
|
</div>
|
|
<sm-popup id="sign_in_popup">
|
|
<svg id="lock" viewBox="0 0 184 280" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<g filter="url(#filter0_d)">
|
|
<path d="M61.7756 195.257L70.5122 180.085C73.1971 175.397 78.1833 172.541 83.5958 172.541H101.112C106.524 172.541 111.51 175.439 114.195 180.085L122.932 195.257C125.617 199.945 125.617 205.698 122.932 210.386L114.195 225.558C111.51 230.246 106.524 233.101 101.112 233.101H83.5958C78.1833 233.101 73.1971 230.203 70.5122 225.558L61.7756 210.386C59.0907 205.698 59.0907 199.945 61.7756 195.257ZM81.9337 221.424L83.2548 223.683C84.235 225.345 86.025 226.367 87.9428 226.367H96.8072C98.725 226.367 100.515 225.345 101.495 223.683L102.816 221.424C104.905 217.801 102.305 213.326 98.1284 213.326H86.6216C82.4451 213.284 79.8454 217.801 81.9337 221.424Z" fill="#FBB03B"/>
|
|
<path d="M61.7756 210.386L70.5122 225.558C73.1971 230.246 78.1833 233.101 83.5958 233.101H92.375V226.41H87.9428C86.025 226.41 84.235 225.387 83.2548 223.725L81.9337 221.466C79.8454 217.844 82.4451 213.369 86.6216 213.369H92.375V172.627H83.5958C78.1833 172.627 73.1971 175.525 70.5122 180.17L61.7756 195.342C59.0907 199.945 59.0907 205.698 61.7756 210.386Z" fill="#D8701A"/>
|
|
<path d="M92.8438 29.1333C84.0646 29.1333 76.0951 32.7132 70.2991 38.4665C64.5457 44.2199 60.9659 52.1894 60.9659 61.0112V102.947L74.3478 97.5771V61.0112C74.3478 50.8256 82.6156 42.5578 92.8012 42.5578C97.9153 42.5578 102.518 44.6035 105.842 47.9703C109.166 51.2944 111.255 55.9398 111.255 61.0112V71.964C111.851 72.0066 112.405 72.0918 113.002 72.177L118.329 72.8589L124.679 73.6687V60.9686C124.679 43.4102 110.445 29.1333 92.8438 29.1333Z" fill="#4D4D4D"/>
|
|
<path d="M92.8438 14.899C80.6978 14.899 69.7024 19.8427 61.733 27.7696C53.7635 35.739 48.8625 46.7344 48.8625 58.8804V102.904L67.3585 84.4083V58.8804C67.3585 44.774 78.78 33.3525 92.8864 33.3525C99.9183 33.3525 106.311 36.2078 110.914 40.8105C115.516 45.4132 118.372 51.8059 118.372 58.8378V72.8589L124.722 73.6687L132.904 74.7341L136.868 75.2455V58.8804C136.868 34.5884 117.136 14.899 92.8438 14.899Z" fill="#808080"/>
|
|
<path d="M136.868 58.923V75.2881L132.904 74.7767L124.722 73.7113L118.372 72.9016V58.9656C118.372 51.9337 115.516 45.5411 110.914 40.9384C106.311 36.3357 99.9183 33.4803 92.8864 33.4803V14.899C117.136 14.899 136.868 34.5884 136.868 58.923Z" fill="#CCCCCC"/>
|
|
<path d="M154 91.0567V178.934C154 181.15 152.381 182.983 150.164 183.281L132.862 185.54V84.4509L150.164 86.667C152.381 86.9654 154 88.8405 154 91.0567Z" fill="#D8701A"/>
|
|
<path d="M113.045 81.8939C106.183 80.9989 99.2791 80.5727 92.375 80.5727C85.471 80.5727 78.5669 80.9989 71.7055 81.8939L52.1866 84.4083V185.54L71.7055 188.054C78.5669 188.949 85.471 189.375 92.375 189.375C99.2791 189.375 106.183 188.949 113.045 188.054L132.904 185.497V84.4509L113.045 81.8939Z" fill="#F7931E"/>
|
|
<path d="M52.2293 84.4083V185.54L34.5856 183.238C32.4121 182.94 30.75 181.108 30.75 178.891V91.0566C30.75 88.8405 32.3695 87.008 34.5856 86.7096L52.2293 84.4083Z" fill="#FBB03B"/>
|
|
<path d="M99.3217 211.92H85.4283C80.3995 211.92 77.2458 217.375 79.7602 221.722L81.337 224.492C82.4877 226.495 84.6612 227.774 87.0052 227.774H97.7448C100.089 227.774 102.22 226.538 103.413 224.492L104.99 221.722C107.504 217.375 104.351 211.92 99.3217 211.92ZM101.836 220.017L100.643 222.063C99.7478 223.597 98.1284 224.492 96.381 224.492H88.3263C86.579 224.492 84.9595 223.555 84.0646 222.063L82.8713 220.017C80.9961 216.736 83.3401 212.687 87.133 212.687H97.5743C101.367 212.687 103.711 216.778 101.836 220.017Z" fill="#A03C1D"/>
|
|
</g>
|
|
<defs>
|
|
<filter id="filter0_d" x="0.75" y="0.899048" width="183.25" height="278.202" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
|
<feOffset dy="16"/>
|
|
<feGaussianBlur stdDeviation="15"/>
|
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
|
</filter>
|
|
</defs>
|
|
</svg>
|
|
<h4 class="accent-color">Sign In</h4>
|
|
<h2>Welcome Back!</h2>
|
|
<p class="light-text">
|
|
Please enter your <span id="type_of_key">FLO private key</span> to continue.
|
|
</p>
|
|
<sm-input id="private_key_input_field" type="password" placeholder="private key"></sm-input>
|
|
<sm-button id="sign_in_button" width="cover" variant="primary" disabled>continue</sm-button>
|
|
<sm-button id="remove_account" variant="outlined">Remove Account</sm-button>
|
|
</sm-popup>
|
|
</div>
|
|
</div>
|
|
<div id="loading_page" class="page hide-completely">
|
|
<svg viewBox="0 0 512 512">
|
|
<defs>
|
|
<style>
|
|
.a {
|
|
fill: #fff;
|
|
}
|
|
|
|
.b {
|
|
fill: #ccc;
|
|
}
|
|
|
|
.c {
|
|
fill: none;
|
|
}
|
|
|
|
.c,
|
|
.d,
|
|
.e {
|
|
stroke: #000;
|
|
stroke-miterlimit: 10;
|
|
stroke-width: 20;
|
|
}
|
|
|
|
.d {
|
|
fill: #ed1c24;
|
|
}
|
|
|
|
.e {
|
|
fill: #d30d41;
|
|
}
|
|
</style>
|
|
</defs>
|
|
<title>mascot</title>
|
|
<path class="a"
|
|
d="M506,367.94v51.19a52,52,0,0,1-52,52H130.4a52,52,0,0,1-52-52V183.69L9.58,40.88,454,43.24a52,52,0,0,1,52,52.05V225.38" />
|
|
<path class="b"
|
|
d="M506,98.69V431.58c.57,56.06-149.95,44-149.95,44a52,52,0,0,0,52-52V44.86L454,46.65A52,52,0,0,1,506,98.69Z" />
|
|
<line class="c" x1="506" y1="273.75" x2="506" y2="327.21" />
|
|
<path class="c"
|
|
d="M506,367.94v51.19a52,52,0,0,1-52,52H130.4a52,52,0,0,1-52-52V183.69L9.58,40.88,454,43.24a52,52,0,0,1,52,52.05V225.38" />
|
|
<path class="d" d="M361.83,340a69.63,69.63,0,1,1-139.26,0C222.57,301.5,361.83,301.5,361.83,340Z" />
|
|
<path class="c" d="M152,242.43a34.32,34.32,0,0,1,64.68.2" />
|
|
<path class="c" d="M367.79,242.43a34.32,34.32,0,0,1,64.68.2" />
|
|
<path class="e" d="M325,314.13c21,4.22,36.85,12.82,36.85,25.8a69.7,69.7,0,0,1-41.09,63.58" />
|
|
</svg>
|
|
<div class="shadow"></div>
|
|
<h4>Getting everything ready, Hang on.</h4>
|
|
</div>
|
|
<sm-popup id="confirmation">
|
|
<h4 class="title"></h4>
|
|
<p class="message"></p>
|
|
<div class="flex">
|
|
<sm-button class="justify-right">ok</sm-button>
|
|
<sm-button>cancel</sm-button>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="prompt">
|
|
<h4 class="title"></h4>
|
|
<sm-input></sm-input>
|
|
<div class="flex align-center">
|
|
<sm-button class="justify-right">Ok</sm-button>
|
|
<sm-button>Cancel</sm-button>
|
|
</div>
|
|
</sm-popup>
|
|
<sm-popup id="add_contact_popup">
|
|
<header class="flex align-center" slot="header">
|
|
<svg class="icon" viewBox="0 0 64 64" onclick="this.closest('sm-popup').hide()">
|
|
<title>Close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>Add contact</h4>
|
|
<sm-button id="add_contact_button" variant="primary" disabled>Add</sm-button>
|
|
</header>
|
|
<sm-input id="add_contact_floID" placeholder="FLO address" animate required></sm-input>
|
|
<sm-input id="add_contact_name" placeholder="Name" animate required></sm-input>
|
|
</sm-popup>
|
|
<sm-popup id="compose_mail_popup">
|
|
<header class="flex align-center" slot="header">
|
|
<svg class="icon" viewBox="0 0 64 64" onclick="this.closest('sm-popup').hide()">
|
|
<title>Close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>Compose Mail</h4>
|
|
<sm-button id="send_mail_button" variant="primary" disabled>Send</sm-button>
|
|
</header>
|
|
<div id="auto_complete_contact" class="flex direction-column">
|
|
<sm-input id="send_mail_to" placeholder="To" animate required></sm-input>
|
|
<div id="mail_contact_list" class="hide-completely contact-list"></div>
|
|
</div>
|
|
<sm-input id="subject_of_mail" placeholder="Subject" animate></sm-input>
|
|
<textarea id="mail_content" placeholder="Type a mail" name="" id="" rows="10" required></textarea>
|
|
</sm-popup>
|
|
<sm-popup id="reply_mail_popup">
|
|
<header class="flex align-center" slot="header">
|
|
<svg class="icon" viewBox="0 0 64 64" onclick="this.closest('sm-popup').hide()">
|
|
<title>Close</title>
|
|
<line x1="64" y1="0" x2="0" y2="64" />
|
|
<line x1="64" y1="64" x2="0" y2="0" />
|
|
</svg>
|
|
<h4>Reply</h4>
|
|
<sm-button id="reply_mail_button" variant="primary" disabled>Send</sm-button>
|
|
</header>
|
|
<sm-input id="subject_of_reply_mail" placeholder="Subject" animate></sm-input>
|
|
<textarea id="reply_mail_content" placeholder="Type a mail" id="" rows="10" required></textarea>
|
|
</sm-popup>
|
|
|
|
<!-- Templates -->
|
|
|
|
<template id="mail_card_template">
|
|
<div class="mail-card">
|
|
<div class="flex align-center">
|
|
<h5 class="sender"></h5>
|
|
<h5 class="date"></h5>
|
|
</div>
|
|
<h4 class="subject text-overflow"></h4>
|
|
<p class="description"></p>
|
|
</div>
|
|
</template>
|
|
|
|
<template id="mail_template">
|
|
<div class="mail">
|
|
<header class="flex align-center">
|
|
<svg class="icon hide-on-desktop back-button" onclick="goto('mails')" viewBox="0 0 64 64">
|
|
<title>Go to main page</title>
|
|
<polyline points="48.01 0.35 16.35 32 48.01 63.65"/>
|
|
</svg>
|
|
<div class="rest">
|
|
<h4 class="sender-name"></h4>
|
|
<h5 class="flo-id text-overflow"></h5>
|
|
</div>
|
|
<h5 class="date justify-right"></h5>
|
|
</header>
|
|
<h4 class="mail-subject"></h4>
|
|
<p class="mail-content"></p>
|
|
</div>
|
|
</template>
|
|
|
|
<template id="contact_template">
|
|
<div class="contact" tabindex="0">
|
|
<div class="initial flex align-center"></div>
|
|
<h4 class="name"></h4>
|
|
<h5 class="address copy"></h5>
|
|
<sm-menu align-options="right">
|
|
<sm-menu-option class="send-mail-option">Send mail</sm-menu-option>
|
|
<sm-menu-option onclick="copyToClipboard('.copy', 'FLO ID copied', this.closest('.contact'))">Copy FLO ID</sm-menu-option>
|
|
</sm-menu>
|
|
</div>
|
|
</template>
|
|
|
|
<template id="message_template">
|
|
<div class="message">
|
|
<p class="message-body"></p>
|
|
<div class="time"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<main id="main" class="grid hide-completely">
|
|
<nav id="main_navbar" class="flex">
|
|
<div class="logo-section hide-on-mobile">
|
|
<svg class="main-logo" viewBox="0 0 27.25 32">
|
|
<title>RanchiMall</title>
|
|
<path
|
|
d="M27.14,30.86c-.74-2.48-3-4.36-8.25-6.94a20,20,0,0,1-4.2-2.49,6,6,0,0,1-1.25-1.67,4,4,0,0,1,0-2.26c.37-1.08.79-1.57,3.89-4.55a11.66,11.66,0,0,0,3.34-4.67,6.54,6.54,0,0,0,.05-2.82C20,3.6,18.58,2,16.16.49c-.89-.56-1.29-.64-1.3-.24a3,3,0,0,1-.3.72l-.3.55L13.42.94C13,.62,12.4.26,12.19.15c-.4-.2-.73-.18-.72.05a9.39,9.39,0,0,1-.61,1.33s-.14,0-.27-.13C8.76.09,8-.27,8,.23A11.73,11.73,0,0,1,6.76,2.6C4.81,5.87,2.83,7.49.77,7.49c-.89,0-.88,0-.61,1,.22.85.33.92,1.09.69A5.29,5.29,0,0,0,3,8.33c.23-.17.45-.29.49-.26a2,2,0,0,1,.22.63A1.31,1.31,0,0,0,4,9.34a5.62,5.62,0,0,0,2.27-.87L7,8l.13.55c.19.74.32.82,1,.65a7.06,7.06,0,0,0,3.46-2.47l.6-.71-.06.64c-.17,1.63-1.3,3.42-3.39,5.42L6.73,14c-3.21,3.06-3,5.59.6,8a46.77,46.77,0,0,0,4.6,2.41c.28.13,1,.52,1.59.87,3.31,2,4.95,3.92,4.95,5.93a2.49,2.49,0,0,0,.07.77h0c.09.09,0,.1.9-.14a2.61,2.61,0,0,0,.83-.32,3.69,3.69,0,0,0-.55-1.83A11.14,11.14,0,0,0,17,26.81a35.7,35.7,0,0,0-5.1-2.91C9.37,22.64,8.38,22,7.52,21.17a3.53,3.53,0,0,1-1.18-2.48c0-1.38.71-2.58,2.5-4.23,2.84-2.6,3.92-3.91,4.67-5.65a3.64,3.64,0,0,0,.42-2A3.37,3.37,0,0,0,13.61,5l-.32-.74.29-.48c.17-.27.37-.63.46-.8l.15-.3.44.64a5.92,5.92,0,0,1,1,2.81,5.86,5.86,0,0,1-.42,1.94c0,.12-.12.3-.15.4a9.49,9.49,0,0,1-.67,1.1,28,28,0,0,1-4,4.29C8.62,15.49,8.05,16.44,8,17.78a3.28,3.28,0,0,0,1.11,2.76c.95,1,2.07,1.74,5.25,3.32,3.64,1.82,5.22,2.9,6.41,4.38A4.78,4.78,0,0,1,21.94,31a3.21,3.21,0,0,0,.14.92,1.06,1.06,0,0,0,.43-.05l.83-.22.46-.12-.06-.46c-.21-1.53-1.62-3.25-3.94-4.8a37.57,37.57,0,0,0-5.22-2.82A13.36,13.36,0,0,1,11,21.19a3.36,3.36,0,0,1-.8-4.19c.41-.85.83-1.31,3.77-4.15,2.39-2.31,3.43-4.13,3.43-6a5.85,5.85,0,0,0-2.08-4.29c-.23-.21-.44-.43-.65-.65A2.5,2.5,0,0,1,15.27.69a10.6,10.6,0,0,1,2.91,2.78A4.16,4.16,0,0,1,19,6.16a4.91,4.91,0,0,1-.87,3c-.71,1.22-1.26,1.82-4.27,4.67a9.47,9.47,0,0,0-2.07,2.6,2.76,2.76,0,0,0-.33,1.54,2.76,2.76,0,0,0,.29,1.47c.57,1.21,2.23,2.55,4.65,3.73a32.41,32.41,0,0,1,5.82,3.24c2.16,1.6,3.2,3.16,3.2,4.8a1.94,1.94,0,0,0,.09.76,4.54,4.54,0,0,0,1.66-.4C27.29,31.42,27.29,31.37,27.14,30.86ZM6.1,7h0a3.77,3.77,0,0,1-1.46.45L4,7.51l.68-.83a25.09,25.09,0,0,0,3-4.82A12,12,0,0,1,8.28.76c.11-.12.77.32,1.53,1l.63.58-.57.84A10.34,10.34,0,0,1,6.1,7Zm5.71-1.78A9.77,9.77,0,0,1,9.24,7.18h0a5.25,5.25,0,0,1-1.17.28l-.58,0,.65-.78a21.29,21.29,0,0,0,2.1-3.12c.22-.41.42-.76.44-.79s.5.43.9,1.24L12,5ZM13.41,3a2.84,2.84,0,0,1-.45.64,11,11,0,0,1-.9-.91l-.84-.9.19-.45c.34-.79.39-.8,1-.31A9.4,9.4,0,0,1,13.8,2.33q-.18.34-.39.69Z" />
|
|
</svg>
|
|
<h5 class="label">RanchiMall</h5>
|
|
<h4 class="label">Messenger</h4>
|
|
</div>
|
|
<div class="navbar-item flex badge align-center active" data-notifications="0" id="chat_page_button" data-target="chat_page">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<path
|
|
d="M47.28,16.8,29.6,34.64a3.3,3.3,0,0,1-3.59.71L2.5,25.42a3.28,3.28,0,0,1,.26-6.13L59.21.87A3.28,3.28,0,0,1,63.32,5l-18.93,56a3.26,3.26,0,0,1-6.12.18l-6.4-15.68" />
|
|
</svg>
|
|
<h5 class="label">Chat</h5>
|
|
</div>
|
|
<div class="navbar-item flex badge align-center" id="mail_page_button" data-notifications="0" data-target="mail_page">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<title>mail</title>
|
|
<path d="M63.5,34.58V49.67A5.36,5.36,0,0,1,58.14,55H5.86A5.36,5.36,0,0,1,.5,49.67V34.58"/>
|
|
<path d="M63.5,14.33V17.2l-32,20.38L.5,17.19V14.33A5.36,5.36,0,0,1,5.86,9H58.14A5.36,5.36,0,0,1,63.5,14.33Z"/>
|
|
</svg>
|
|
<h5 class="label">Mail</h5>
|
|
</div>
|
|
<div class="navbar-item flex align-center" data-target="settings_page">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<path
|
|
d="M41,62.92a1.7,1.7,0,0,1-1.45-.83L37,57.7a2.63,2.63,0,0,0-2.27-1.34h-.26a23.91,23.91,0,0,1-5,0h-.26A2.63,2.63,0,0,0,27,57.7l-2.54,4.39a1.67,1.67,0,0,1-1.44.83,1.72,1.72,0,0,1-.83-.22L10.33,55.86a1.67,1.67,0,0,1-.61-2.27l2.54-4.41a2.61,2.61,0,0,0-.12-2.85A23.77,23.77,0,0,1,9.65,42,2.65,2.65,0,0,0,7.24,40.5H2.17A1.67,1.67,0,0,1,.5,38.83V25.17A1.67,1.67,0,0,1,2.17,23.5H7.24A2.64,2.64,0,0,0,9.65,22a25,25,0,0,1,2.49-4.31,2.63,2.63,0,0,0,.12-2.85l-2.54-4.4a1.67,1.67,0,0,1,.61-2.27L22.17,1.3A1.72,1.72,0,0,1,23,1.08a1.67,1.67,0,0,1,1.44.83L27,6.3a2.63,2.63,0,0,0,2.27,1.34h.26a23.91,23.91,0,0,1,5,0h.26A2.63,2.63,0,0,0,37,6.3l2.53-4.39A1.7,1.7,0,0,1,41,1.08a1.72,1.72,0,0,1,.83.22L53.67,8.14a1.67,1.67,0,0,1,.61,2.27l-2.54,4.41a2.61,2.61,0,0,0,.12,2.85A24.46,24.46,0,0,1,54.35,22a2.65,2.65,0,0,0,2.41,1.52h5.07a1.67,1.67,0,0,1,1.67,1.67V38.83a1.67,1.67,0,0,1-1.67,1.67H56.76A2.63,2.63,0,0,0,54.35,42a24.63,24.63,0,0,1-2.49,4.31,2.63,2.63,0,0,0-.12,2.85l2.54,4.4a1.68,1.68,0,0,1-.61,2.27L41.83,62.7A1.72,1.72,0,0,1,41,62.92Z" />
|
|
<path
|
|
d="M23,1.58h0a1.16,1.16,0,0,1,1,.58l2.54,4.39a3.14,3.14,0,0,0,2.7,1.59l.31,0C30.37,8,31.19,8,32,8s1.63,0,2.44.12l.31,0a3.14,3.14,0,0,0,2.7-1.59L40,2.16a1.16,1.16,0,0,1,1-.58,1.1,1.1,0,0,1,.58.16L53.42,8.57a1.16,1.16,0,0,1,.42,1.59L51.3,14.57a3.15,3.15,0,0,0,.15,3.4,23.69,23.69,0,0,1,2.45,4.21A3.12,3.12,0,0,0,56.76,24h5.07A1.17,1.17,0,0,1,63,25.17V38.83A1.17,1.17,0,0,1,61.83,40H56.76a3.11,3.11,0,0,0-2.86,1.82,24.33,24.33,0,0,1-2.44,4.23,3.11,3.11,0,0,0-.15,3.39l2.53,4.4a1.13,1.13,0,0,1,.12.88,1.17,1.17,0,0,1-.54.71L41.58,62.26a1.08,1.08,0,0,1-.58.16,1.16,1.16,0,0,1-1-.58l-2.54-4.39a3.14,3.14,0,0,0-2.7-1.59l-.31,0C33.63,56,32.81,56,32,56s-1.63,0-2.44-.12l-.31,0a3.14,3.14,0,0,0-2.7,1.59L24,61.84a1.16,1.16,0,0,1-1,.58,1.1,1.1,0,0,1-.58-.16L10.58,55.43a1.16,1.16,0,0,1-.42-1.59l2.54-4.41a3.15,3.15,0,0,0-.15-3.4,23.69,23.69,0,0,1-2.45-4.21A3.12,3.12,0,0,0,7.24,40H2.17A1.17,1.17,0,0,1,1,38.83V25.17A1.17,1.17,0,0,1,2.17,24H7.24a3.11,3.11,0,0,0,2.86-1.82A24.33,24.33,0,0,1,12.54,18a3.11,3.11,0,0,0,.15-3.39l-2.53-4.4a1.16,1.16,0,0,1,.42-1.59L22.42,1.74A1.08,1.08,0,0,1,23,1.58m0-1a2.11,2.11,0,0,0-1.08.29L10.08,7.7a2.17,2.17,0,0,0-.79,3l2.54,4.4a2.15,2.15,0,0,1-.1,2.31,24.92,24.92,0,0,0-2.54,4.4A2.13,2.13,0,0,1,7.24,23H2.17A2.17,2.17,0,0,0,0,25.17V38.83A2.17,2.17,0,0,0,2.17,41H7.24a2.13,2.13,0,0,1,1.95,1.23,25.25,25.25,0,0,0,2.55,4.39,2.13,2.13,0,0,1,.09,2.31L9.29,53.34a2.17,2.17,0,0,0,.79,3l11.84,6.83a2.11,2.11,0,0,0,1.08.29,2.17,2.17,0,0,0,1.88-1.08L27.41,58a2.14,2.14,0,0,1,1.84-1.09h.21a24.88,24.88,0,0,0,5.08,0h.21A2.14,2.14,0,0,1,36.59,58l2.53,4.39A2.17,2.17,0,0,0,41,63.42a2.11,2.11,0,0,0,1.08-.29L53.92,56.3a2.17,2.17,0,0,0,.79-3l-2.54-4.4a2.15,2.15,0,0,1,.1-2.31,24.92,24.92,0,0,0,2.54-4.4A2.13,2.13,0,0,1,56.76,41h5.07A2.17,2.17,0,0,0,64,38.83V25.17A2.17,2.17,0,0,0,61.83,23H56.76a2.13,2.13,0,0,1-1.95-1.23,25.25,25.25,0,0,0-2.55-4.39,2.13,2.13,0,0,1-.09-2.31l2.54-4.41a2.17,2.17,0,0,0-.79-3L42.08.87A2.11,2.11,0,0,0,41,.58a2.17,2.17,0,0,0-1.88,1.08L36.59,6.05a2.14,2.14,0,0,1-1.84,1.09h-.21a24.88,24.88,0,0,0-5.08,0h-.21a2.14,2.14,0,0,1-1.84-1.09L24.88,1.66A2.17,2.17,0,0,0,23,.58Z" />
|
|
<circle cx="32" cy="32" r="11.5" />
|
|
<path d="M32,21A11,11,0,1,1,21,32,11,11,0,0,1,32,21m0-1A12,12,0,1,0,44,32,12,12,0,0,0,32,20Z" />
|
|
</svg>
|
|
<h5 class="label">Settings</h5>
|
|
</div>
|
|
</nav>
|
|
<section id="chat_page" class="grid page">
|
|
<div id="contacts" class="grid">
|
|
<header class="flex align-center">
|
|
<h4>Contacts</h4>
|
|
<sm-button class="round" id="show_contact_popup_button">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<line x1="32" x2="32" y2="64" />
|
|
<line x1="64" y1="32" y2="32" />
|
|
</svg>
|
|
Add Contact
|
|
</sm-button>
|
|
</header>
|
|
<div id="contacts_container" class="flex"></div>
|
|
<div id="new_conversation" class="flex direction-column empty-state">
|
|
<svg class="icon new-conversation align-center" viewBox="0 0 512 512">
|
|
<title>new conversation</title>
|
|
<path d="M304.11,403.5H101.42a51,51,0,0,1-51-51v-191L6.87,84.82H424.36a51,51,0,0,1,51,51v86.72"/>
|
|
<ellipse cx="423.3" cy="342.48" rx="84.7"/>
|
|
<line x1="423.3" y1="306.34" x2="423.3" y2="379.64"/>
|
|
<line x1="459.95" y1="342.99" x2="386.65" y2="342.99"/>
|
|
</svg>
|
|
<h4>Add a contact for getting started</h4>
|
|
<p class="light-text">Tap/click on added contact to start a conversation</p>
|
|
</div>
|
|
</div>
|
|
<div id="chat" class="grid hide-on-mobile hide-completely">
|
|
<header class="flex align-center">
|
|
<svg class="icon hide-on-desktop back-button" onclick="goto('chats')" viewBox="0 0 64 64">
|
|
<title>Go to main page</title>
|
|
<polyline points="48.01 0.35 16.35 32 48.01 63.65"/>
|
|
</svg>
|
|
<div id="receiver_initial" onclick="goto('chats')" class="initial flex align-center"></div>
|
|
<div class="flex rest direction-column">
|
|
<h4 id="receiver_name"></h4>
|
|
<h5 id="receiver_floID" class="text-overflow"></h5>
|
|
</div>
|
|
</header>
|
|
<h5 id="warn_no_encryption">Messages are not encrypted until receiver replies</h5>
|
|
<section id="chat_container" class="flex">
|
|
</section>
|
|
<footer class="flex">
|
|
<sm-textarea id="type_message" placeholder="Type a message" class="rest"></sm-textarea>
|
|
<sm-button variant="primary" class="round" id="send_message_button" disabled>
|
|
<svg class="icon" viewBox="-4 0 64 64">
|
|
<path d="M35.32,32.09,19.74,32a1.66,1.66,0,0,1-1.36-.82L.91,4.19c-1.15-1.78.31-4.36,2-3.53L31.63,14.78,62.37,29.9a2.51,2.51,0,0,1,0,4.2L31.63,49.22,2.92,63.34c-1.7.83-3.16-1.75-2-3.53L9.07,47.2"/>
|
|
</svg>
|
|
</sm-button>
|
|
</footer>
|
|
</div>
|
|
</section>
|
|
<section class="page grid hide-completely" id="mail_page">
|
|
<div id="mails" class="grid">
|
|
<header class="flex align-center">
|
|
<sm-tab-header variant="tab" target="all_mail_container">
|
|
<sm-tab>Inbox</sm-tab>
|
|
<sm-tab>Sent</sm-tab>
|
|
</sm-tab-header>
|
|
<sm-button class="round" onclick="refresh()">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<polyline points="42.99 0.16 50.25 21.23 28.78 27.7"/>
|
|
<path d="M56.61,44.63A24.65,24.65,0,0,1,50,56.24a25.1,25.1,0,0,1-35.31,0,24.67,24.67,0,0,1,0-35,25.1,25.1,0,0,1,35.31,0"/>
|
|
</svg>
|
|
refresh
|
|
</sm-button>
|
|
</header>
|
|
<sm-button variant="primary" id="new_mail_button" class="fab">
|
|
<svg class="icon" viewBox="0 0 64 64">
|
|
<path
|
|
d="M46.73,14.81l7,7,7.65-7.6A7.15,7.15,0,0,0,61.39,4L60.11,2.77a7.23,7.23,0,0,0-10.19,0L3.87,48.57a5,5,0,0,0-1.39,2.6L.53,61.27a1.74,1.74,0,0,0,2,2l10.15-1.94A5.06,5.06,0,0,0,15.34,60L49.6,25.9" />
|
|
</svg>
|
|
New Mail
|
|
</sm-button>
|
|
<div class="empty-state flex direction-column align-center" id="no_mails">
|
|
<svg class="icon new-conversation" viewBox="0 0 512 512">
|
|
<path d="M227,26.16,20.5,177.56a49,49,0,0,0-20,39.53V446.35a49,49,0,0,0,49,49h413a49,49,0,0,0,49-49h0V217.09a49,49,0,0,0-20-39.53L285,26.16A49,49,0,0,0,227,26.16Z"/>
|
|
<path d="M71,250.29l166.31,86.88a39,39,0,0,0,36.22,0L441,249.66"/>
|
|
</svg>
|
|
<h4>All your mails will appear here.</h4>
|
|
<p class="light-text">Tap/click on 'New Mail' button below to compose new mail.</p>
|
|
</div>
|
|
<sm-tab-panels id="all_mail_container">
|
|
<sm-panel id="inbox_mail_container" class="flex">
|
|
</sm-panel>
|
|
<sm-panel id="sent_mail_container" class="flex"></sm-panel>
|
|
</sm-tab-panels>
|
|
</div>
|
|
<div id="mail" class="flex hide-on-mobile hide-completely">
|
|
<div id="mail_container">
|
|
</div>
|
|
<div class="flex">
|
|
<sm-button id="prev_mail" variant="outlined">View Previous Mail</sm-button>
|
|
<sm-button id="show_reply_popup" variant="outlined">reply</sm-button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section id="settings_page" class="page hide-completely">
|
|
<header>
|
|
<h3>User</h3>
|
|
<p>Manage settings and personal information.</p>
|
|
</header>
|
|
<section>
|
|
<div class="card">
|
|
<h4>My FLO ID</h4>
|
|
<div class="flex align-center">
|
|
<p id="greet_tag" class="text-overflow"></p>
|
|
<sm-button class="small justify-right" onclick="copyToClipboard('#greet_tag', 'FLO ID Copied')">copy</sm-button>
|
|
</div>
|
|
|
|
<h4>My private key</h4>
|
|
<sm-input id="view_private_key" title="Click to Copy" placeholder="View Private Key. Click to copy" readonly/></sm-input>
|
|
|
|
<h4>Set Password</h4>
|
|
<p>
|
|
This will require entering password everytime you refresh the window or directly close this tab.<br>
|
|
<strong>This password will only be stored on this device locally, so this password won't work on other devices.</strong>
|
|
</p>
|
|
<sm-button id="secure_key">Set Password</sm-button>
|
|
|
|
<h4>Clear data</h4>
|
|
<p><strong></strong>This can't be undone.</strong> Make sure you have stored the PRIVATE KEY and backed up the data.</p>
|
|
<sm-button id="clear_data">Clear Data</sm-button>
|
|
|
|
<h4>Sign out</h4>
|
|
<p>*Remember to store your <strong>PRIVATE KEY </strong> before signing out.</p>
|
|
<sm-button id="sign_out">Sign Out</sm-button>
|
|
</div>
|
|
<div class="card">
|
|
<h4>Backup data</h4>
|
|
<p>Create a backup of contacts, conversations and mails. Which can later be used to restore these in case of data is cleared. </p>
|
|
<sm-button id="backup_data" >Backup Data</sm-button>
|
|
<span id="backup_info"></span>
|
|
|
|
<h4>Restore backup</h4>
|
|
<p>Select backup file with extension '.json'. Which was downloaded when backup was performed.</p>
|
|
<label class="select-file">
|
|
<sm-button >Select File</sm-button>
|
|
<input type="file" id="restore_data" accept=".json" />
|
|
</label>
|
|
|
|
<h4>Theme</h4>
|
|
<div class="flex">
|
|
<p>Toggle dark theme</p>
|
|
<sm-switch id="theme_switcher" class="justify-right"></sm-switch>
|
|
</div>
|
|
|
|
<h4>Enter key is send</h4>
|
|
<div class="flex">
|
|
<p>If this toggle is ON then pressing 'Enter' key will send messages</p>
|
|
<sm-switch id="enter_is_send" class="justify-right"></sm-switch>
|
|
</div>
|
|
|
|
<h4>Version</h4>
|
|
<p>0.0.33</p>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
</main>
|
|
<script src="components.js"></script>
|
|
<script>
|
|
//Checks for internet connection status
|
|
if (!navigator.onLine)
|
|
notify('There seems to be a problem connecting to the internet.', 'error', true)
|
|
window.addEventListener('offline', () => {
|
|
notify('There seems to be a problem connecting to the internet.', 'error', true)
|
|
})
|
|
window.addEventListener('online', () => {
|
|
notify('We are back online.')
|
|
})
|
|
|
|
</script>
|
|
<script>
|
|
let isSignInOpen = false,
|
|
activePage = { page: document.getElementById('chat_page'),
|
|
button: document.getElementById('chat_page_button')},
|
|
activeChatPage = document.getElementById('contacts'),
|
|
activeMailPage = document.getElementById('mails'),
|
|
activeChat,
|
|
activeMail,
|
|
refreshCount = 0,
|
|
refreshTimeout = 20000;
|
|
|
|
|
|
const openSignInButton = document.getElementById('open_sign_in_button'),
|
|
closeSignInButton = document.getElementById('close_sign_in_button'),
|
|
signInButton = document.getElementById('sign_in_button'),
|
|
guestLoginButton = document.getElementById('guest_login_button'),
|
|
signInLanding = document.getElementById('sign_in_page'),
|
|
privateKeyInputField = document.getElementById('private_key_input_field'),
|
|
loadingPage = document.getElementById('loading_page')
|
|
|
|
const allmailsSection = document.getElementById('mails'),
|
|
allContactsSection = document.getElementById('contacts'),
|
|
mailSection = document.getElementById('mail'),
|
|
chatSection = document.getElementById('chat'),
|
|
chatPageButton = document.getElementById('chat_page_button'),
|
|
mailPageButton = document.getElementById('mail_page_button'),
|
|
mainNavbar = document.getElementById('main_navbar')
|
|
|
|
const mainPage = document.querySelector('main'),
|
|
contactsContainer = document.getElementById("contacts_container"),
|
|
inboxMailContainer = document.getElementById("inbox_mail_container"),
|
|
sentMailContainer = document.getElementById("sent_mail_container"),
|
|
addContactButton = document.getElementById('add_contact_button'),
|
|
addContactName = document.getElementById('add_contact_name'),
|
|
chatContainer = document.getElementById('chat_container'),
|
|
typeMessage = document.getElementById('type_message'),
|
|
addContactFloID = document.getElementById('add_contact_floID'),
|
|
signInPopup = document.getElementById('sign_in_popup'),
|
|
composeMailPopup = document.getElementById('compose_mail_popup'),
|
|
sendMailTo = document.getElementById('send_mail_to'),
|
|
mailContactList = document.getElementById('mail_contact_list'),
|
|
addContactPopup = document.getElementById('add_contact_popup'),
|
|
allInputs = document.querySelectorAll('sm-popup sm-input, sm-popup textarea')
|
|
|
|
const replyMailPopup = document.getElementById('reply_mail_popup'),
|
|
subjectOfReplyMail = document.getElementById('subject_of_reply_mail'),
|
|
replyMailContent = document.getElementById('reply_mail_content'),
|
|
replyMailButton = document.getElementById('reply_mail_button')
|
|
|
|
// render elements
|
|
const mailCardTemplate = document.getElementById('mail_card_template'),
|
|
contactTemplate = document.getElementById('contact_template'),
|
|
messageTemplate = document.getElementById('message_template'),
|
|
mailTemplate = document.getElementById('mail_template')
|
|
const render = {
|
|
mailCard(mailRef, subject, timestamp, category, floID, content, markUnread){
|
|
let card = mailCardTemplate.content.cloneNode(true),
|
|
cardContainer = card.querySelector('.mail-card'),
|
|
time = new Date(timestamp).toString(),
|
|
minutes = String(new Date(timestamp).getMinutes()),
|
|
hours = new Date(timestamp).getHours(),
|
|
dateTime
|
|
minutes = minutes.length === 1 ? `0${minutes}` : minutes
|
|
let finalHours = hours - 12 > 0 ? `${hours - 12}:${minutes} pm` : `${hours}:${minutes} am`
|
|
|
|
if(new Date().getDate() === new Date(timestamp).getDate())
|
|
dateTime = finalHours
|
|
else
|
|
dateTime = time.slice(4, 10)
|
|
let mailSummery = content.split(' ')
|
|
mailSummery.splice(16)
|
|
mailSummery = mailSummery.join(' ')
|
|
cardContainer.setAttribute("name", mailRef);
|
|
if (Array.isArray(floID))
|
|
floID = floID.join(",");
|
|
if(markUnread)
|
|
cardContainer.classList.add('unread')
|
|
card.querySelector('.sender').textContent = floGlobals.contacts[floID] || floID
|
|
card.querySelector('.subject').textContent = subject
|
|
card.querySelector('.date').textContent = dateTime
|
|
card.querySelector('.description').textContent = mailSummery
|
|
return card
|
|
},
|
|
mail(senderName, floID, timestamp, category, subject, content){
|
|
let card = mailTemplate.content.cloneNode(true),
|
|
cardContainer = card.querySelector('.mail'),
|
|
time = new Date(timestamp).toString(),
|
|
minutes = String(new Date(timestamp).getMinutes()),
|
|
hours = new Date(timestamp).getHours(),
|
|
dateTime
|
|
minutes = minutes.length === 1 ? `0${minutes}` : minutes
|
|
let finalHours = hours - 12 > 0 ? `${hours - 12}:${minutes} pm` : `${hours}:${minutes} am`
|
|
|
|
if(new Date().getDate() === new Date(timestamp).getDate())
|
|
dateTime = finalHours
|
|
else
|
|
dateTime = time.slice(4, 10)
|
|
if (Array.isArray(floID))
|
|
floID = floID.join(",");
|
|
if(category === 'receivedFrom')
|
|
card.querySelector('.sender-name').textContent = `From : ${senderName}`
|
|
if(category === 'sentTo')
|
|
card.querySelector('.sender-name').textContent = `To : ${senderName}`
|
|
card.querySelector('.flo-id').textContent = floID
|
|
card.querySelector('.mail-subject').textContent = subject
|
|
card.querySelector('.date').textContent = dateTime;
|
|
card.querySelector('.mail-content').textContent = content
|
|
return card
|
|
},
|
|
contactCard(floID, name){
|
|
let card = contactTemplate.content.cloneNode(true),
|
|
cardContainer = card.querySelector('.contact')
|
|
cardContainer.setAttribute("name", name || 'Unknown')
|
|
cardContainer.setAttribute("flo-id", floID)
|
|
card.querySelector('.name').textContent = name || 'Unknown'
|
|
card.querySelector('.address').textContent = floID
|
|
let initial = card.querySelector('.initial')
|
|
initial.textContent = name ? name.charAt(0) : 'U'
|
|
let randomColor = randomHsl(90, 40)
|
|
cardContainer.setAttribute("text-color", randomColor)
|
|
initial.setAttribute(`style`, `color: ${randomColor}`)
|
|
let duplicateCard = card.cloneNode(true);
|
|
contactsContainer.append(card);
|
|
mailContactList.append(duplicateCard);
|
|
},
|
|
messageBubble(floID, message, timestamp, category){
|
|
let card = messageTemplate.content.cloneNode(true),
|
|
cardContainer = card.querySelector('.message'),
|
|
time = new Date(timestamp).toString(),
|
|
minutes = String(new Date(timestamp).getMinutes()),
|
|
hours = new Date(timestamp).getHours()
|
|
minutes = minutes.length === 1 ? `0${minutes}` : minutes
|
|
let finalHours = hours - 12 > 0 ? `${hours - 12}:${minutes} pm` : `${hours}:${minutes} am`
|
|
|
|
cardContainer.classList.add(category)
|
|
card.querySelector('.message-body').textContent = message
|
|
card.querySelector('.time').textContent = finalHours
|
|
if(currentFloID !== floID){
|
|
currentDate = null
|
|
currentFloID = floID
|
|
}
|
|
if(time.slice(4, 10) !== currentDate){
|
|
let dateCard = document.createElement('h5')
|
|
dateCard.classList.add('date-card')
|
|
dateCard.textContent = time.slice(4, 10)
|
|
getConversationElement(floID).appendChild(dateCard)
|
|
currentDate = time.slice(4, 10)
|
|
}
|
|
getConversationElement(floID).appendChild(card)
|
|
},
|
|
}
|
|
|
|
let currentDate, currentFloID
|
|
|
|
function randomHsl(saturation = 90, lightness = 50) {
|
|
return `hsla( ${(Math.random() * 360)}, ${saturation}%, ${lightness}%, 1)`;
|
|
}
|
|
|
|
openSignInButton.addEventListener('clicked', () => {
|
|
isSignInOpen = true;
|
|
signInPopup.show()
|
|
privateKeyInputField.focusIn()
|
|
})
|
|
|
|
function checkInput(element){
|
|
let parent = element.closest('sm-popup'),
|
|
inputs = parent.querySelectorAll('sm-input, textarea')
|
|
if(!parent.querySelector('sm-button[variant="primary"]')) return
|
|
if([...inputs].every(input => {
|
|
if(input.tagName === 'SM-INPUT' && input.isValid){
|
|
return true
|
|
}
|
|
else if(input.tagName !== 'SM-INPUT' && input.checkValidity()){
|
|
return true
|
|
}
|
|
})){
|
|
parent.querySelector('sm-button[variant="primary"]').disabled = false
|
|
}
|
|
else
|
|
parent.querySelector('sm-button[variant="primary"]').disabled = true
|
|
}
|
|
|
|
document.addEventListener('input', e => {
|
|
if(e.target.closest('sm-popup')){
|
|
checkInput(e.target)
|
|
}
|
|
})
|
|
|
|
document.addEventListener('keyup', e => {
|
|
if(e.target.closest('sm-input') && e.target.closest('sm-popup')){
|
|
if(e.code === 'Enter'){
|
|
e.target.closest('sm-popup').querySelector('sm-button[variant="primary"]').click()
|
|
}
|
|
}
|
|
if(e.target.closest('#send_mail_to')){
|
|
if(e.code === 'ArrowDown'){
|
|
for(child of mailContactList.children){
|
|
if(!child.classList.contains('hide-completely')){
|
|
child.focus()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if(e.code === 'Enter' && mailContactList.firstElementChild){
|
|
for(child of mailContactList.children){
|
|
if(!child.classList.contains('hide-completely')){
|
|
child.click()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(e.target.closest('.contact')){
|
|
if(e.code === 'ArrowDown' || e.code === 'ArrowRight'){
|
|
if(document.activeElement.nextElementSibling)
|
|
document.activeElement.nextElementSibling.focus()
|
|
else
|
|
mailContactList.firstElementChild.focus()
|
|
}
|
|
if(e.code === 'ArrowUp' || e.code === 'ArrowLeft' ){
|
|
if(document.activeElement.previousElementSibling)
|
|
document.activeElement.previousElementSibling.focus()
|
|
else
|
|
mailContactList.lastElementChild.focus()
|
|
}
|
|
}
|
|
})
|
|
|
|
sendMailTo.addEventListener('input', function() {
|
|
mailContactList.classList.remove('hide-completely')
|
|
if(this.value.trim !== ''){
|
|
for(child of mailContactList.children) {
|
|
if(child.getAttribute('name').toUpperCase().indexOf(this.value.toUpperCase()) > -1){
|
|
child.classList.remove('hide-completely')
|
|
}
|
|
else{
|
|
child.classList.add('hide-completely')
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
mailContactList.addEventListener('click', e => {
|
|
if(e.target.closest('.contact')){
|
|
sendMailTo.value = e.target.closest('.contact').getAttribute('flo-id')
|
|
mailContactList.classList.add('hide-completely')
|
|
sendMailTo.focusIn()
|
|
}
|
|
})
|
|
|
|
mailContactList.addEventListener('keyup', e => {
|
|
if(e.target.closest('.contact') && e.code === 'Enter' || e.code === 'Space'){
|
|
sendMailTo.value = document.activeElement.getAttribute('flo-id')
|
|
mailContactList.classList.add('hide-completely')
|
|
sendMailTo.focusIn()
|
|
}
|
|
})
|
|
|
|
document.addEventListener('click', e => {
|
|
if(e.target.closest('.navbar-item') && activePage.button !== e.target.closest('.navbar-item')){
|
|
refresh()
|
|
let targetButton = e.target.closest('.navbar-item'),
|
|
targetPage = document.getElementById(targetButton.dataset.target)
|
|
targetButton.classList.add('active')
|
|
targetPage.classList.remove('hide-completely')
|
|
if(activePage.page){
|
|
activePage.page.classList.add('hide-completely')
|
|
activePage.button.classList.remove('active')
|
|
}
|
|
activePage.button = targetButton
|
|
activePage.page = targetPage;
|
|
}
|
|
if(e.target.closest('#send_mail_to') || e.target.closest('#mail_contact_list')){
|
|
mailContactList.classList.remove('hide-completely')
|
|
}
|
|
else{
|
|
mailContactList.classList.add('hide-completely')
|
|
}
|
|
if(e.target.closest('#new_mail_button')){
|
|
composeMailPopup.show()
|
|
sendMailTo.focusIn()
|
|
}
|
|
})
|
|
|
|
function copyToClipboard(target, message, parent){
|
|
let copyText, copyTarget = target;
|
|
if(typeof target === 'string'){
|
|
if(parent)
|
|
copyTarget = parent.querySelector(target)
|
|
else{
|
|
copyTarget = document.querySelector(target)
|
|
}
|
|
}
|
|
if(copyTarget.tagName === 'SM-INPUT' || copyTarget.tagName === 'INPUT' || copyTarget.tagName === 'TEXTAREA')
|
|
copyText = copyTarget.value
|
|
else
|
|
copyText = copyTarget.textContent
|
|
navigator.clipboard.writeText(copyText).then(() => {
|
|
notify(`${message}`, 'success')
|
|
})
|
|
.catch(error => {
|
|
notify(error, 'error')
|
|
})
|
|
}
|
|
|
|
let themeSwitcher = document.getElementById('theme_switcher'),
|
|
enterIsSend = document.getElementById('enter_is_send'),
|
|
isEnterSend = true
|
|
|
|
if (localStorage.theme === "dark") {
|
|
nightlight()
|
|
themeSwitcher.checked = true;
|
|
} else {
|
|
daylight()
|
|
themeSwitcher.checked = false;
|
|
}
|
|
|
|
function daylight() {
|
|
document.body.setAttribute("data-theme", "light");
|
|
}
|
|
|
|
function nightlight() {
|
|
document.body.setAttribute("data-theme", "dark");
|
|
}
|
|
themeSwitcher.addEventListener('change', function(e){
|
|
if(this.checked){
|
|
nightlight();
|
|
localStorage.setItem("theme", "dark");
|
|
}
|
|
else{
|
|
daylight();
|
|
localStorage.setItem("theme", "light");
|
|
}
|
|
})
|
|
|
|
if(localStorage.isEnterSend === null)
|
|
localStorage.setItem('isEnterSend', 'true')
|
|
|
|
if (localStorage.isEnterSend === 'true') {
|
|
isEnterSend = true
|
|
enterIsSend.checked = true;
|
|
} else {
|
|
isEnterSend = false
|
|
enterIsSend.checked = false;
|
|
}
|
|
|
|
enterIsSend.addEventListener('change', function(){
|
|
if(this.checked){
|
|
isEnterSend = true
|
|
localStorage.setItem("isEnterSend", 'true');
|
|
}
|
|
else{
|
|
isEnterSend = false
|
|
localStorage.setItem("isEnterSend", 'false');
|
|
}
|
|
})
|
|
|
|
function updateHeight(){
|
|
if(window.innerWidth < 640){
|
|
chatSection.style.height = window.innerHeight + 'px'
|
|
signInLanding.style.height = window.innerHeight + 'px'
|
|
}
|
|
else{
|
|
chatSection.style.height = '100vh'
|
|
signInLanding.style.height = '100vh'
|
|
}
|
|
}
|
|
window.addEventListener('resize', e => {
|
|
requestAnimationFrame(updateHeight)
|
|
})
|
|
|
|
function goto(page){
|
|
if(page === 'chats'){
|
|
chatSection.classList.add('hide-on-mobile')
|
|
allContactsSection.classList.remove('hide-on-mobile')
|
|
activeChatPage = allContactsSection
|
|
}
|
|
if(page === 'mails'){
|
|
mailSection.classList.add('hide-on-mobile')
|
|
allmailsSection.classList.remove('hide-on-mobile')
|
|
activeMailPage = allmailsSection
|
|
}
|
|
mainNavbar.classList.remove('hide-on-mobile')
|
|
}
|
|
|
|
document.getElementById("mails").addEventListener('click', function (e) {
|
|
if (e.target.closest(".mail-card") && e.target.closest(".mail-card") !== activeMail){
|
|
refresh()
|
|
mailPageButton.setAttribute('data-notifications', '0')
|
|
mailSection.classList.remove('hide-completely')
|
|
e.target.closest(".mail-card").classList.remove('unread')
|
|
viewMail(e.target.closest(".mail-card").getAttribute("name"));
|
|
if(activeMail)
|
|
activeMail.classList.remove('active')
|
|
e.target.closest(".mail-card").classList.add('active')
|
|
activeMail = e.target.closest(".mail-card")
|
|
if(activeMailPage.id === 'mails'){
|
|
mailSection.classList.remove('hide-on-mobile')
|
|
allmailsSection.classList.add('hide-on-mobile')
|
|
activeMailPage = mailSection
|
|
mainNavbar.classList.add('hide-on-mobile')
|
|
}
|
|
}
|
|
})
|
|
|
|
document.getElementById("prev_mail").addEventListener('click', function (e) {
|
|
viewMail(this.dataset["value"], false);
|
|
})
|
|
|
|
document.getElementById('send_message_button').addEventListener('clicked', sendMessage)
|
|
|
|
typeMessage.addEventListener('input', e => {
|
|
if(typeMessage.value.trim() !== '')
|
|
document.getElementById('send_message_button').disabled = false
|
|
else
|
|
document.getElementById('send_message_button').disabled = true
|
|
})
|
|
typeMessage.addEventListener('keydown', e => {
|
|
if(e.code === "Enter" && typeMessage.value.trim() !== '' && isEnterSend){
|
|
e.preventDefault()
|
|
sendMessage()
|
|
}
|
|
})
|
|
|
|
document.getElementById("contacts_container").addEventListener('click', function (e) {
|
|
if (!e.target.closest("sm-menu") && e.target.closest(".contact")){
|
|
refresh()
|
|
chatPageButton.setAttribute('data-notifications', '0')
|
|
chatSection.classList.remove('hide-completely')
|
|
e.target.closest(".contact").classList.remove('unread')
|
|
viewConversation(e.target.closest(".contact"))
|
|
if(activeChat)
|
|
activeChat.classList.remove('active')
|
|
e.target.closest(".contact").classList.add('active')
|
|
activeChat = e.target.closest(".contact")
|
|
if(activeChatPage.id === 'contacts'){
|
|
chatSection.classList.remove('hide-on-mobile')
|
|
allContactsSection.classList.add('hide-on-mobile')
|
|
activeChatPage = chatSection
|
|
mainNavbar.classList.add('hide-on-mobile')
|
|
}
|
|
}
|
|
let floID;
|
|
if (e.target.closest(".send-mail-option")){
|
|
composeMailPopup.show()
|
|
floID = e.target.closest(".contact").getAttribute("flo-id");
|
|
let tmp = sendMailTo.value.trim();
|
|
if (!tmp.includes(floID)) {
|
|
if (tmp == '')
|
|
tmp = floID;
|
|
else
|
|
tmp += `, ${floID}`;
|
|
}
|
|
sendMailTo.value = tmp;
|
|
return false;
|
|
}
|
|
});
|
|
|
|
document.getElementById("sign_out").addEventListener('click', function (e) {
|
|
getConfirmation('Sign Out?',
|
|
'Are you sure you want to Sign out?', "Sign Out",
|
|
"Stay Signed In").then(
|
|
result => {
|
|
floDapps.logout().then(result => {
|
|
notify("Successfully Signed out", 'success')
|
|
setTimeout(onLoadStartUp, 2000)
|
|
}).catch(error => notify("Signout Unsuccessful", "error", error))
|
|
}).catch(error => {})
|
|
});
|
|
|
|
document.getElementById("remove_account").addEventListener('click', function (e) {
|
|
getConfirmation('Remove Account?',
|
|
'**Remember to store your PRIVATE-KEY**\n*Private-Key will be needed to signIn again*\nAre you sure you want to remove account?',
|
|
"Remove").then(result => {
|
|
floDapps.logout().then(result => {
|
|
notify("Removed Account")
|
|
setTimeout(onLoadStartUp, 2000)
|
|
}).catch(error => notify("Remove Unsuccessful", "error", error))
|
|
}).catch(error => {})
|
|
});
|
|
|
|
document.getElementById("secure_key").addEventListener('click', async function (e) {
|
|
try{
|
|
if(privKeyNotSecured){
|
|
let value = await getPromptInput("Set password", 'Enter Password', false);
|
|
floDapps.securePrivKey(value).then(result => {
|
|
privKeyNotSecured = false;
|
|
notify("Private Key secured", 'success');
|
|
this.textContent = 'Change Password'
|
|
})
|
|
}
|
|
else{
|
|
let value = await getPromptInput("Change password", 'New Password', false);
|
|
floDapps.securePrivKey(value).then(result => {
|
|
notify("Password changed", 'success');
|
|
})
|
|
}
|
|
}
|
|
catch(error){
|
|
notify("Securing Failed", "error", error)
|
|
}
|
|
});
|
|
|
|
document.getElementById("clear_data").addEventListener('click', function (e) {
|
|
getConfirmation('Clear Data?',
|
|
`Are you sure you want to clear stored data?`, "Clear").then(
|
|
result => {
|
|
floDapps.clearUserData().then(result => {
|
|
notify("Successfully Cleared local data", 'success')
|
|
setTimeout(onLoadStartUp, 2000)
|
|
}).catch(error => notify(error, "error"))
|
|
}).catch(error => {})
|
|
});
|
|
|
|
document.getElementById('show_contact_popup_button').addEventListener("click", function (e) {
|
|
addContactPopup.show()
|
|
addContactFloID.focusIn()
|
|
})
|
|
|
|
document.getElementById('add_contact_button').addEventListener("clicked", addContact)
|
|
|
|
document.getElementById('show_reply_popup').addEventListener("click", () => {
|
|
replyMailPopup.show()
|
|
})
|
|
|
|
replyMailButton.addEventListener("clicked", replyMail)
|
|
|
|
let refreshing = false
|
|
|
|
function refresh(){
|
|
if(refreshing) return;
|
|
refreshing = true
|
|
messenger.refreshInbox().then(data => {
|
|
renderMessages(data.messages)
|
|
renderMailList(data.mails)
|
|
if(Object.keys(data.messages).length && activePage.button !== chatPageButton){
|
|
chatPageButton.setAttribute('data-notifications', Object.keys(data.messages).length)
|
|
notify(`${Object.keys(data.messages).length} new message(s)`)
|
|
}
|
|
if(Object.keys(data.mails).length && activePage.button !== mailPageButton){
|
|
mailPageButton.setAttribute('data-notifications', Object.keys(data.mails).length)
|
|
notify(`${Object.keys(data.mails).length} new mail(s)`)
|
|
}
|
|
setTimeout(() => {
|
|
refreshing = false
|
|
}, 5000);
|
|
}).catch(error => notify(error, "error"))
|
|
}
|
|
|
|
document.getElementById("view_private_key").addEventListener("mouseover", function (e) {
|
|
this.value = myPrivKey;
|
|
})
|
|
|
|
document.getElementById("view_private_key").addEventListener("mouseout", function (e) {
|
|
this.value = "";
|
|
});
|
|
|
|
document.getElementById("view_private_key").addEventListener("click", function (e) {
|
|
copyToClipboard(this, "Private Key copied to clipboard")
|
|
});
|
|
|
|
document.getElementById("backup_data").addEventListener("click", function (e) {
|
|
let backupInfoText = document.getElementById("backup_info")
|
|
backupInfoText.classList.remove("error")
|
|
backupInfoText.textContent = `Generating the backup file! Please wait...`;
|
|
messenger.backupData().then(blob => {
|
|
let anchor = document.createElement('a')
|
|
anchor.setAttribute("download", `BackupFor_${myFloID}_${Date.now()}.json`)
|
|
anchor.setAttribute("href", URL.createObjectURL(blob))
|
|
backupInfoText.textContent =
|
|
`Backup file generated! If the download didn't start automatically, click `;
|
|
anchor.textContent = `here.`;
|
|
backupInfoText.append(anchor);
|
|
anchor.click();
|
|
}).catch(error => {
|
|
backupInfoText.classList.add("error")
|
|
backupInfoText.textContent = `Unable to generate backup file! Try again later...`;
|
|
notify("Backup data Unsuccessful!", "error", error);
|
|
})
|
|
})
|
|
|
|
document.getElementById("restore_data").addEventListener("change", function (e) {
|
|
let file = this.files[0]
|
|
notify(`Retrieving backup data! Please wait.`);
|
|
if (!file) {
|
|
notify(`No files selected!`);
|
|
return;
|
|
}
|
|
messenger.parseBackup(file).then(data => {
|
|
getConfirmation('Restore Data?',
|
|
`Found: ${Object.keys(data.contacts).length} Contacts,\n ${Object.keys(data.messages).length} Messages, ${Object.keys(data.mails).length} Mails.`,
|
|
"Restore"
|
|
).then(result => {
|
|
notify(`Restoring data! Please wait.`);
|
|
messenger.restoreData(data).then(result => {
|
|
notify("Data restore completed successful! Initiating reload, Please wait")
|
|
setTimeout(onLoadStartUp, 2000)
|
|
}).catch(error => {
|
|
notify("Failed to restore data! Try again later", "error", error);
|
|
});
|
|
}).catch(error => {
|
|
notify("Restore data Cancelled!", "warn");
|
|
});
|
|
}).catch(error => {
|
|
notify("Retrive data Unsuccessful!", "error", error);
|
|
})
|
|
})
|
|
|
|
let refreshAgain = function() {
|
|
if(refreshCount > 6)
|
|
refreshTimeout *= 2;
|
|
refreshCount++;
|
|
refresh()
|
|
setTimeout(refreshAgain, refreshTimeout);
|
|
}
|
|
|
|
function sendMessage() {
|
|
let receiver = document.getElementById("receiver_floID").textContent;
|
|
let container;
|
|
let message = typeMessage.value;
|
|
typeMessage.value = ''
|
|
if(message.trim() === '') return
|
|
messenger.sendMessage(message, receiver).then(data => {
|
|
render.messageBubble(data.floID, data.message, data.time, data.category)
|
|
chatContainer.scrollTo({left: 0, top: chatContainer.scrollHeight, behavior: 'smooth'})
|
|
refreshCount = 0;
|
|
refreshTimeout = 10000;
|
|
setTimeout(refreshAgain, refreshTimeout)
|
|
}).catch(error => notify(error, "error"));
|
|
}
|
|
|
|
function removeElement(element) {
|
|
element.parentNode.removeChild(element);
|
|
}
|
|
|
|
function clearElement(element) {
|
|
element.innerHTML = '';
|
|
return element;
|
|
}
|
|
|
|
function getConfirmation(title, message, trueBtn = "Ok", falseBtn = "Cancel") {
|
|
let confirmation = document.getElementById("confirmation");
|
|
confirmation.querySelector('.title').textContent = title;
|
|
confirmation.querySelector('.message').textContent = message;
|
|
confirmation.show()
|
|
let options = confirmation.getElementsByTagName("sm-button");
|
|
options[0].textContent = trueBtn;
|
|
options[1].textContent = falseBtn;
|
|
return new Promise((resolve, reject) => {
|
|
options[0].onclick = () => {
|
|
confirmation.hide()
|
|
resolve(true)
|
|
}
|
|
options[1].onclick = () => {
|
|
confirmation.hide()
|
|
reject(true)
|
|
}
|
|
})
|
|
}
|
|
|
|
async function getPromptInput(title, message, showText = true, trueBtn = "Ok", falseBtn = "Cancel") {
|
|
let prompt = document.getElementById("prompt");
|
|
prompt.show()
|
|
prompt.querySelector('.title').textContent = title;
|
|
let input = prompt.querySelector("sm-input");
|
|
input.setAttribute("placeholder", message)
|
|
let buttons = prompt.querySelectorAll("sm-button");
|
|
if (showText)
|
|
input.setAttribute("type", "text")
|
|
else
|
|
input.setAttribute("type", "password")
|
|
input.focus()
|
|
buttons[0].textContent = trueBtn;
|
|
buttons[1].textContent = falseBtn;
|
|
return new Promise((resolve, reject) => {
|
|
buttons[0].onclick = () => {
|
|
let value = input.value;
|
|
prompt.hide()
|
|
resolve(value)
|
|
}
|
|
buttons[1].onclick = () => {
|
|
prompt.hide()
|
|
reject(true)
|
|
}
|
|
})
|
|
}
|
|
|
|
const notifications = document.getElementById('notifications')
|
|
|
|
function notify(message, mode = "normal", log = "", pinned) {
|
|
if (mode === "error")
|
|
console.error(message, log)
|
|
else if (mode === "warn")
|
|
console.warn(message, log)
|
|
else
|
|
console.log(mode.toUpperCase(), message, log)
|
|
notifications.push(message, mode, pinned)
|
|
}
|
|
|
|
function addContact() {
|
|
let floID = addContactFloID.value.trim();
|
|
let name = addContactName.value.trim();
|
|
messenger.storeContact(floID, name).then(result => {
|
|
getConversationElement(floID, name)
|
|
addContactPopup.hide()
|
|
notify(`Added Contact: ${floID}`)
|
|
}).catch(error => notify(error, "error"));
|
|
}
|
|
|
|
function renderContactList(contactList = {}) {
|
|
for (floID in contactList)
|
|
getConversationElement(floID, contactList[floID])
|
|
}
|
|
|
|
function renderMailContactList(contactList = {}) {
|
|
for (floID in contactList)
|
|
getConversationElement(floID, contactList[floID])
|
|
}
|
|
|
|
function renderMarked(data) {
|
|
for (let d in data) {
|
|
let element = document.getElementsByName(d)[0]
|
|
if(element)
|
|
data[d].forEach(mark => element.classList.add(mark))
|
|
}
|
|
}
|
|
|
|
function getConversationElement(floID, name = "Unknown") {
|
|
let element = chatContainer.querySelector(`#${floID}`)
|
|
if (!element) {
|
|
element = document.createElement("div");
|
|
element.setAttribute("id", floID)
|
|
element.classList.add('flex', 'direction-column', 'hide-completely')
|
|
chatContainer.append(element)
|
|
render.contactCard(floID, name)
|
|
}
|
|
return element;
|
|
}
|
|
|
|
let frag = document.createDocumentFragment()
|
|
|
|
function renderMessages(data, markUnread = true) {
|
|
let floID
|
|
console.log(data)
|
|
for (m in data) {
|
|
floID = data[m].floID
|
|
render.messageBubble(data[m].floID, data[m].message, data[m].time, data[m].category)
|
|
if (markUnread)
|
|
allContactsSection.querySelector(`[flo-id='${floID}']`).classList.add("unread");
|
|
}
|
|
chatContainer.scrollTo(0, chatContainer.scrollHeight)
|
|
}
|
|
|
|
const receiverInitial = document.getElementById("receiver_initial"),
|
|
receiverName = document.getElementById("receiver_name"),
|
|
receiverFloID = document.getElementById("receiver_floID"),
|
|
noEncryption = document.getElementById("warn_no_encryption")
|
|
function viewConversation(contact) {
|
|
let floID = contact.getAttribute("flo-id"),
|
|
name = contact.getAttribute('name'),
|
|
textColor = contact.getAttribute('text-color')
|
|
let currentConversation = document.getElementById("receiver_floID").textContent;
|
|
console.log(floID, floGlobals.contacts[floID], currentConversation)
|
|
if (currentConversation != "")
|
|
document.getElementById(currentConversation).classList.add('hide-completely')
|
|
let currentChat = document.getElementById(floID)
|
|
currentChat.classList.remove('hide-completely');
|
|
receiverInitial.textContent = floGlobals.contacts[floID] ? floGlobals.contacts[floID].charAt(0) : name.charAt(0) || ' ';
|
|
receiverInitial.setAttribute('style', `color: ${textColor}`)
|
|
receiverName.textContent = floGlobals.contacts[floID] || name || ' ';
|
|
receiverFloID.textContent = floID;
|
|
if (floGlobals.pubKeys[floID])
|
|
noEncryption.classList.add("hide-completely");
|
|
else
|
|
noEncryption.classList.remove("hide-completely");
|
|
chatContainer.scrollTo(0, chatContainer.scrollHeight)
|
|
messenger.removeMark(floID, "unread");
|
|
}
|
|
|
|
let sentMailFrag = document.createDocumentFragment()
|
|
function renderMailList(mails, markUnread = true) {
|
|
for (m in mails) {
|
|
let { ref, subject, time, category, floID, content} = mails[m]
|
|
if(category === 'receivedFrom' || category === 'replyFrom')
|
|
frag.prepend(render.mailCard(ref, subject, time, category, floID, content, markUnread));
|
|
if(category === 'sentTo' || category === 'replyTo')
|
|
sentMailFrag.prepend(render.mailCard(ref, subject, time, category, floID, content, markUnread));
|
|
}
|
|
inboxMailContainer.prepend(frag)
|
|
sentMailContainer.prepend(sentMailFrag)
|
|
if(inboxMailContainer.children.length){
|
|
document.getElementById('no_mails').classList.add('hide-completely')
|
|
document.getElementById('all_mail_container').classList.remove('hide-completely')
|
|
}
|
|
else{
|
|
document.getElementById('all_mail_container').classList.add('hide-completely')
|
|
document.getElementById('no_mails').classList.remove('hide-completely')
|
|
}
|
|
}
|
|
|
|
function viewMail(mailRef, newView = true) {
|
|
//clear the container and display popup (if needed)
|
|
if (newView)
|
|
clearElement(document.getElementById("mail_container"))
|
|
|
|
messenger.getMail(mailRef).then(result => {
|
|
let {floID, time, category, subject, content, prevRef} = result
|
|
//add name (if available)
|
|
let senderName = floGlobals.contacts[floID] || ' ';
|
|
//append the contents to mail container
|
|
document.getElementById("mail_container").append(render.mail(senderName, floID, time, category, subject, content));
|
|
//add prop for previous mail (if available)
|
|
let prevMail = document.getElementById("prev_mail");
|
|
prevMail.dataset["value"] = prevRef;
|
|
prevMail.style.display = prevRef ? 'block' : 'none';
|
|
//set values for reply mail form if new view
|
|
if (newView) {
|
|
if (floID.includes(','))
|
|
document.getElementById("show_reply_popup").classList.add("hide-completely");
|
|
else {
|
|
replyMailPopup.dataset["to"] = floID;
|
|
replyMailPopup.dataset["prevRef"] = mailRef;
|
|
subjectOfReplyMail.value = subject.startsWith("Re: ") ? result
|
|
.subject : `Re: ${subject}`;
|
|
document.getElementById("show_reply_popup").classList.remove("hide-completely");
|
|
}
|
|
}
|
|
messenger.removeMark(mailRef, "unread");
|
|
}).catch(error => notify("Unable to read mail", "error", error))
|
|
}
|
|
|
|
document.getElementById('send_mail_button').addEventListener('clicked', sendMail)
|
|
|
|
function sendMail() {
|
|
let to = sendMailTo.value.split(",");
|
|
let subject = document.getElementById('subject_of_mail').value;
|
|
let content = document.getElementById('mail_content').value
|
|
let recipients = [];
|
|
try {
|
|
to.forEach(id => {
|
|
let tmp = id.trim();
|
|
if (!floCrypto.validateAddr(tmp))
|
|
throw "Invalid Address: " + tmp
|
|
if (!recipients.includes(tmp))
|
|
recipients.push(tmp);
|
|
})
|
|
messenger.sendMail(subject, content, recipients).then(result => {
|
|
console.log(result);
|
|
let sucessCount = Object.keys(result.success).length;
|
|
let errorCount = Object.keys(result.error).length
|
|
if (!sucessCount)
|
|
notify("Failed to send mail!", "error", result)
|
|
else {
|
|
if (errorCount)
|
|
notify(
|
|
`${Object.keys(result.success).length} Mail(s) sent! (${Object.keys(result.error).length} failed)`,
|
|
"warn", {
|
|
sent: result.success,
|
|
failed: result.error
|
|
});
|
|
else
|
|
notify(`${Object.keys(result.success).length} Mail(s) sent!`)
|
|
let {ref, subject, time, category, floID, content} = result.data
|
|
sentMailContainer.prepend(render.mailCard(ref, subject, time, category, floID, content));
|
|
composeMailPopup.hide()
|
|
}
|
|
}).catch(error => notify("Failed to send mail!", "error", error))
|
|
} catch (error) {
|
|
notify(error, "error")
|
|
}
|
|
}
|
|
|
|
function replyMail() {
|
|
let recipient = replyMailPopup.dataset.to;
|
|
let subject = subjectOfReplyMail.value;
|
|
let content = replyMailContent.value;
|
|
let prevRef = replyMailPopup.dataset.prevRef;
|
|
messenger.replyMail(prevRef, subject, content, recipient).then(data => {
|
|
notify(`Mail replied!`);
|
|
replyMailPopup.hide()
|
|
let {ref, subject, time, category, floID, content} = data
|
|
sentMailContainer.prepend(render.mailCard(ref, subject, time, category, floID, content));
|
|
}).catch(error => notify("Failed to send mail!", "error", error))
|
|
}
|
|
</script>
|
|
|
|
<script src="default.js"></script>
|
|
|
|
<script id="messenger">
|
|
const messenger = {
|
|
|
|
util: {
|
|
|
|
sendRaw(message, receiver, type) {
|
|
return new Promise((resolve, reject) => {
|
|
if (!floCrypto.validateAddr(receiver))
|
|
return reject("Invalid Receiver floID");
|
|
var data = {
|
|
senderID: myFloID,
|
|
receiverID: receiver,
|
|
pubKey: myPubKey,
|
|
message: message,
|
|
sign: floCrypto.signData(JSON.stringify(message), myPrivKey),
|
|
application: floGlobals.application,
|
|
type: type,
|
|
comment: ""
|
|
}
|
|
floSupernode.sendData(JSON.stringify(data), data.receiverID)
|
|
.then(result => resolve(result))
|
|
.catch(error => reject(error))
|
|
})
|
|
},
|
|
|
|
sendEncoded(message, receiver) {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
let unencrytedData = btoa(unescape(encodeURIComponent(JSON.stringify(message))))
|
|
this.sendRaw(unencrytedData, receiver, "ENCODED")
|
|
.then(result => resolve(result))
|
|
.catch(error => reject(error))
|
|
} catch (error) {
|
|
reject(error.message)
|
|
}
|
|
})
|
|
},
|
|
|
|
sendEncrypted(message, receiver) {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
if (!(receiver in floGlobals.pubKeys))
|
|
throw Error("pubKey of receiver not found")
|
|
let encryptedData = floCrypto.encryptData(JSON.stringify(message), floGlobals
|
|
.pubKeys[receiver])
|
|
encryptedData = btoa(unescape(encodeURIComponent(JSON.stringify(
|
|
encryptedData))))
|
|
this.sendRaw(encryptedData, receiver, "ENCRYPTED")
|
|
.then(result => resolve(result))
|
|
.catch(error => reject(error))
|
|
} catch (error) {
|
|
reject(error.message)
|
|
}
|
|
})
|
|
},
|
|
|
|
},
|
|
|
|
sendMessage(message, receiver) {
|
|
return new Promise(async (resolve, reject) => {
|
|
try {
|
|
if (receiver in floGlobals.pubKeys)
|
|
await this.util.sendEncrypted(message, receiver);
|
|
else
|
|
await this.util.sendEncoded(message, receiver);
|
|
let data = {
|
|
time: Date.now(),
|
|
floID: receiver,
|
|
category: "sent",
|
|
message: Crypto.AES.encrypt(message, floGlobals.appendix.AESKey)
|
|
}
|
|
compactIDB.addData("messages", JSON.parse(JSON.stringify(data)), `${data.time}`)
|
|
data.message = message;
|
|
resolve(data)
|
|
} catch (error) {
|
|
reject(error)
|
|
}
|
|
})
|
|
},
|
|
|
|
sendMail(subject, content, recipients) {
|
|
return new Promise(async (resolve, reject) => {
|
|
let result = {
|
|
success: {},
|
|
error: {}
|
|
}
|
|
let mail = {
|
|
subject: subject,
|
|
content: content,
|
|
ref: floCrypto.randString(32, true)
|
|
}
|
|
for (let i = 0; i < recipients.length; i++) {
|
|
try {
|
|
if (recipients[i] in floGlobals.pubKeys)
|
|
result.success[recipients[i]] = await this.util.sendEncrypted(mail,
|
|
recipients[i]);
|
|
else
|
|
result.success[recipients[i]] = await this.util.sendEncoded(mail,
|
|
recipients[i]);
|
|
} catch (error) {
|
|
result.error[recipients[i]] = error;
|
|
}
|
|
}
|
|
let data = {
|
|
time: Date.now(),
|
|
floID: Object.keys(result.success),
|
|
category: "sentTo",
|
|
subject: subject,
|
|
content: Crypto.AES.encrypt(content, floGlobals.appendix.AESKey),
|
|
ref: mail.ref
|
|
}
|
|
compactIDB.addData("mails", data, `${data.time}`)
|
|
compactIDB.addData("mailRef", `${data.time}`, mail.ref)
|
|
result.data = data
|
|
resolve(result)
|
|
})
|
|
},
|
|
|
|
replyMail(prevRef, subject, content, recipient) {
|
|
return new Promise(async (resolve, reject) => {
|
|
let mail = {
|
|
prevRef: prevRef,
|
|
subject: subject,
|
|
content: content,
|
|
ref: floCrypto.randString(32, true)
|
|
}
|
|
try {
|
|
if (recipient in floGlobals.pubKeys)
|
|
await this.util.sendEncrypted(mail, recipient);
|
|
else
|
|
await this.util.sendEncoded(mail, recipient);
|
|
let data = {
|
|
time: Date.now(),
|
|
floID: recipient,
|
|
category: "replyTo",
|
|
prevRef: prevRef,
|
|
subject: subject,
|
|
content: Crypto.AES.encrypt(content,floGlobals.appendix.AESKey),
|
|
ref: mail.ref
|
|
}
|
|
compactIDB.addData("mails", data, `${data.time}`)
|
|
compactIDB.addData("mailRef", `${data.time}`, mail.ref)
|
|
resolve(data)
|
|
} catch (error) {
|
|
reject(error)
|
|
}
|
|
})
|
|
},
|
|
|
|
refreshInbox() {
|
|
return new Promise((resolve, reject) => {
|
|
var request = {
|
|
receiverID: myFloID,
|
|
application: floGlobals.application,
|
|
lowerVectorClock: floGlobals.appendix.lastReceived + 1,
|
|
}
|
|
floSupernode.requestData(JSON.stringify(request), request.receiverID).then(response => {
|
|
let dataSet = JSON.parse(response);
|
|
console.log(dataSet)
|
|
let newInbox = {
|
|
messages: {},
|
|
mails: {}
|
|
}
|
|
for (vc in dataSet) {
|
|
try {
|
|
//check for validity of message
|
|
if (floCrypto.getFloIDfromPubkeyHex(dataSet[vc].pubKey) != dataSet[
|
|
vc]
|
|
.senderID || !floCrypto.verifySign(JSON.stringify(dataSet[vc]
|
|
.message),
|
|
dataSet[vc].sign, dataSet[vc].pubKey))
|
|
continue;
|
|
//store the pubKey if not stored already
|
|
if (!(dataSet[vc].senderID in floGlobals.pubKeys)) {
|
|
floGlobals.pubKeys[dataSet[vc].senderID] = dataSet[vc].pubKey
|
|
compactIDB.writeData("pubKeys", dataSet[vc].pubKey, dataSet[vc]
|
|
.senderID)
|
|
}
|
|
let data = {
|
|
time: parseInt(vc.split("_")[0]),
|
|
floID: dataSet[vc].senderID,
|
|
}
|
|
let tmp;
|
|
switch (dataSet[vc].type) {
|
|
case "ENCODED":
|
|
tmp = JSON.parse(decodeURIComponent(escape(atob(dataSet[vc]
|
|
.message))))
|
|
break;
|
|
case "ENCRYPTED":
|
|
tmp = JSON.parse(floCrypto.decryptData(JSON.parse(
|
|
decodeURIComponent(escape(atob(dataSet[vc]
|
|
.message)))), myPrivKey))
|
|
break;
|
|
default:
|
|
tmp = dataSet[vc].message
|
|
}
|
|
if (typeof tmp === "string") {
|
|
//process as message
|
|
data.category = "received"
|
|
data.message = Crypto.AES.encrypt(tmp, floGlobals.appendix.AESKey);
|
|
newInbox.messages[vc] = data;
|
|
compactIDB.addData("messages", JSON.parse(JSON.stringify(data)), vc)
|
|
data.message = tmp;
|
|
this.addMark(data.floID, "unread")
|
|
} else if (typeof tmp === "object") {
|
|
//process as mail
|
|
data.subject = tmp.subject;
|
|
data.ref = tmp.ref;
|
|
data.content = Crypto.AES.encrypt(tmp.content, floGlobals.appendix.AESKey);
|
|
if (tmp.prevRef) {
|
|
data.prevRef = tmp.prevRef;
|
|
data.category = "replyFrom";
|
|
} else
|
|
data.category = "receivedFrom"
|
|
newInbox.mails[vc] = data;
|
|
compactIDB.addData("mails", data, vc);
|
|
compactIDB.addData("mailRef", vc, data.ref)
|
|
this.addMark(data.ref, "unread")
|
|
}
|
|
} catch (error) {
|
|
console.error(error)
|
|
} finally {
|
|
floGlobals.appendix.lastReceived = vc;
|
|
}
|
|
}
|
|
compactIDB.writeData("appendix", floGlobals.appendix.lastReceived,
|
|
"lastReceived");
|
|
resolve(newInbox)
|
|
}).catch(error => reject(error))
|
|
})
|
|
},
|
|
|
|
getMail(mailRef) {
|
|
return new Promise((resolve, reject) => {
|
|
compactIDB.readData("mailRef", mailRef).then(mailIndex => {
|
|
compactIDB.readData("mails", mailIndex).then(result => {
|
|
result.content = Crypto.AES.decrypt(result.content, floGlobals.appendix.AESKey)
|
|
resolve(result)
|
|
}).catch(error => reject(error))
|
|
}).catch(error => reject(error))
|
|
});
|
|
},
|
|
|
|
addMark(key, mark) {
|
|
return new Promise((resolve, reject) => {
|
|
compactIDB.readData("marked", key).then(result => {
|
|
if (!result)
|
|
result = [mark];
|
|
else if (!result.includes(mark))
|
|
result.push(mark);
|
|
else
|
|
return resolve("Mark already exist");
|
|
compactIDB.writeData("marked", result, key)
|
|
.then(result => resolve(result))
|
|
.catch(error => reject(error))
|
|
}).catch(error => reject(error))
|
|
})
|
|
},
|
|
|
|
removeMark(key, mark) {
|
|
return new Promise((resolve, reject) => {
|
|
compactIDB.readData("marked", key).then(result => {
|
|
if (!result || !result.includes(mark))
|
|
return resolve("Mark doesnot exist")
|
|
else {
|
|
result.splice(result.indexOf(mark), 1); //remove the mark from the list of marks
|
|
compactIDB.writeData("marked", result, key)
|
|
.then(result => resolve("Mark removed"))
|
|
.catch(error => reject(error))
|
|
}
|
|
}).catch(error => reject(error))
|
|
})
|
|
},
|
|
|
|
storeContact(floID, name) {
|
|
return new Promise((resolve, reject) => {
|
|
if (!floCrypto.validateAddr(floID))
|
|
reject("Invalid floID!")
|
|
else
|
|
compactIDB.writeData("contacts", name, floID)
|
|
.then(result => resolve(result))
|
|
.catch(error => reject(error))
|
|
});
|
|
},
|
|
|
|
loadDataFromIDB() {
|
|
return new Promise((resolve, reject) => {
|
|
let loadData = ["contacts", "pubKeys", "messages", "mails", "marked", "appendix"]
|
|
let promises = []
|
|
for (var i = 0; i < loadData.length; i++)
|
|
promises[i] = compactIDB.readAllData(loadData[i])
|
|
Promise.all(promises).then(results => {
|
|
let data = {}
|
|
for (var i = 0; i < loadData.length; i++)
|
|
data[loadData[i]] = results[i]
|
|
data.appendix.lastReceived = data.appendix.lastReceived || '0'
|
|
if (data.appendix.AESKey) {
|
|
try {
|
|
let AESKey = floCrypto.decryptData(data.appendix.AESKey, myPrivKey);
|
|
data.appendix.AESKey = AESKey;
|
|
for (let m in data.messages) {
|
|
let decrypted = Crypto.AES.decrypt(data.messages[m].message,
|
|
AESKey)
|
|
|
|
data.messages[m].message = decrypted;
|
|
}
|
|
for (let m in data.mails) {
|
|
let decrypted = Crypto.AES.decrypt(data.mails[m].content,
|
|
AESKey)
|
|
data.mails[m].content = decrypted;
|
|
}
|
|
|
|
resolve(data)
|
|
} catch (error) {
|
|
reject("Corrupted AES Key");
|
|
}
|
|
} else {
|
|
if (Object.keys(data.messages).length)
|
|
return reject("AES Key not Found")
|
|
let AESKey = floCrypto.randString(32);
|
|
let encryptedKey = floCrypto.encryptData(AESKey, myPubKey);
|
|
compactIDB.addData("appendix", encryptedKey, "AESKey").then(result => {
|
|
data.appendix.AESKey = AESKey;
|
|
resolve(data);
|
|
}).catch(error => reject("Unable to Generate AES Key"))
|
|
}
|
|
}).catch(error => reject(error))
|
|
})
|
|
},
|
|
|
|
backupData() {
|
|
return new Promise((resolve, reject) => {
|
|
let obs = ["contacts", "pubKeys", "messages", "mails", "mailRef", "marked", "appendix"]
|
|
let promises = [];
|
|
obs.forEach(o => promises.push(compactIDB.readAllData(o)))
|
|
Promise.all(promises).then(results => {
|
|
let data = {}
|
|
for (let i = 0; i < obs.length; i++)
|
|
data[obs[i]] = results[i]
|
|
results = undefined;
|
|
data = btoa(unescape(encodeURIComponent(JSON.stringify(data))))
|
|
data = {
|
|
floID: myFloID,
|
|
pubKey: myPubKey,
|
|
data: floCrypto.encryptData(data, myPubKey),
|
|
}
|
|
data.sign = floCrypto.signData(JSON.stringify(data.data), myPrivKey);
|
|
resolve(new Blob([JSON.stringify(data)], {
|
|
type: 'application/json'
|
|
}));
|
|
}).catch(error => reject(error))
|
|
})
|
|
},
|
|
|
|
parseBackup(blob) {
|
|
return new Promise((resolve, reject) => {
|
|
if (blob instanceof Blob || blob instanceof File) {
|
|
let reader = new FileReader();
|
|
reader.onload = evt => {
|
|
var data = JSON.parse(evt.target.result);
|
|
if (!floCrypto.verifySign(JSON.stringify(data.data), data.sign, data.pubKey))
|
|
reject("Corrupted Backup file: Signature verification failed");
|
|
else if (myFloID !== data.floID || myPubKey !== data.pubKey)
|
|
reject("Invalid Backup file: Incorrect floID");
|
|
else {
|
|
try {
|
|
data = floCrypto.decryptData(data.data, myPrivKey);
|
|
try {
|
|
data = JSON.parse(decodeURIComponent(escape(atob(data))));
|
|
resolve(data)
|
|
} catch (e) {
|
|
reject("Corrupted Backup file: Parse failed");
|
|
}
|
|
} catch (e) {
|
|
reject("Corrupted Backup file: Decryption failed");
|
|
}
|
|
}
|
|
}
|
|
reader.readAsText(blob);
|
|
} else
|
|
reject("Backup is not a valid File (or) Blob")
|
|
})
|
|
},
|
|
|
|
restoreData(arg) {
|
|
return new Promise((resolve, reject) => {
|
|
let parseData;
|
|
if (arg instanceof Blob || arg instanceof File)
|
|
parseData = this.parseBackup
|
|
else
|
|
parseData = data => new Promise((res, rej) => res(data))
|
|
parseData(arg).then(data => {
|
|
if (data.appendix.lastReceived < floGlobals.appendix.lastReceived)
|
|
data.appendix.lastReceived = floGlobals.appendix.lastReceived;
|
|
let AESKey = floCrypto.decryptData(data.appendix.AESKey, myPrivKey);
|
|
if (AESKey !== floGlobals.appendix.AESKey) {
|
|
for (let m in data.messages) {
|
|
let decrypted = Crypto.AES.decrypt(data.messages[m].message, AESKey)
|
|
let encrypted = Crypto.AES.encrypt(decrypted, floGlobals.appendix
|
|
.AESKey)
|
|
data.messages[m].message = encrypted;
|
|
}
|
|
for (let m in data.mails) {
|
|
let decrypted = Crypto.AES.decrypt(data.mails[m].content, AESKey)
|
|
let encrypted = Crypto.AES.encrypt(decrypted, floGlobals.appendix
|
|
.AESKey)
|
|
data.mails[m].content = encrypted;
|
|
}
|
|
}
|
|
delete data.appendix.AESKey;
|
|
let promises = [];
|
|
for (obs in data)
|
|
for (value in data[obs])
|
|
promises.push(compactIDB.writeData(obs, data[obs][value], value));
|
|
Promise.all(promises)
|
|
.then(results => resolve("Restore Successful"))
|
|
.catch(error => reject("Restore Failed: Unable to write to IDB"))
|
|
}).catch(error => reject(error))
|
|
})
|
|
},
|
|
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |