commit
59b0744b3f
146
assets/messenger-illustration.svg
Normal file
146
assets/messenger-illustration.svg
Normal file
@ -0,0 +1,146 @@
|
||||
<svg width="484" height="484" viewBox="0 0 484 484" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.circle{
|
||||
fill: var(--accent-color)
|
||||
}
|
||||
</style>
|
||||
<g clip-path="url(#clip0_102_2)">
|
||||
<circle class="circle" cx="243" cy="242" r="242" fill="#DBE5FF" fill-opacity="0.1"/>
|
||||
<circle class="circle" cx="242.5" cy="241.5" r="162.5" fill="#A6BEFC" fill-opacity="0.1"/>
|
||||
<circle class="circle" cx="242.5" cy="241.5" r="109.5" fill="#7295EE" fill-opacity="0.1"/>
|
||||
<g filter="url(#filter0_d_102_2)">
|
||||
<circle cx="127.5" cy="383.5" r="42.5" fill="white"/>
|
||||
</g>
|
||||
<path d="M140.757 372.193H117.947C116.017 372.193 114.438 373.758 114.438 375.672V391.328C114.438 393.259 116.017 394.807 117.947 394.807H140.757C142.705 394.807 144.266 393.259 144.266 391.328V375.672C144.266 373.758 142.705 372.193 140.757 372.193ZM140.757 378.577L129.352 384.37L117.947 378.577V375.672L129.352 381.43L140.757 375.672V378.577ZM110.929 391.328C110.929 391.624 110.982 391.902 111.017 392.198H103.911C102.942 392.198 102.156 391.415 102.156 390.458C102.156 389.502 102.942 388.719 103.911 388.719H110.929V391.328ZM107.42 374.802H111.017C110.982 375.098 110.929 375.376 110.929 375.672V378.281H107.42C106.455 378.281 105.665 377.498 105.665 376.542C105.665 375.585 106.455 374.802 107.42 374.802ZM103.911 383.5C103.911 382.543 104.7 381.76 105.665 381.76H110.929V385.24H105.665C104.7 385.24 103.911 384.457 103.911 383.5Z" fill="#72EEC1"/>
|
||||
<g filter="url(#filter1_d_102_2)">
|
||||
<circle cx="410.5" cy="378.5" r="12.5" fill="#7286EE"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_d_102_2)">
|
||||
<circle cx="78.5" cy="286.5" r="6.5" fill="#EE7297"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_d_102_2)">
|
||||
<circle cx="361" cy="131" r="6" fill="#72B2EE"/>
|
||||
</g>
|
||||
<g filter="url(#filter4_d_102_2)">
|
||||
<circle cx="332.5" cy="368.5" r="10.5" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter5_d_102_2)">
|
||||
<circle cx="46.5" cy="185.5" r="10.5" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter6_d_102_2)">
|
||||
<circle cx="356.5" cy="84.5" r="10.5" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter7_d_102_2)">
|
||||
<circle cx="158.5" cy="124.5" r="50.5" fill="white"/>
|
||||
</g>
|
||||
<path d="M165.907 119.56C164.951 123.383 159 121.441 157.15 120.917L158.846 114.195C160.758 114.75 166.893 115.552 165.907 119.56ZM156.318 124.37L154.468 131.801C156.749 132.387 163.81 134.638 164.858 130.444C165.968 126.066 158.599 124.925 156.318 124.37ZM188.908 131.462C184.777 147.988 168.065 158.04 151.538 153.908C135.012 149.777 124.969 133.065 129.092 116.538C133.223 100.012 149.935 89.9723 166.462 94.0917C182.958 98.2233 193.009 114.935 188.908 131.462ZM165.814 111.821L167.202 106.271L163.81 105.5L162.453 110.834C161.559 110.618 160.665 110.403 159.74 110.218L161.097 104.76L157.736 103.958L156.348 109.478C155.608 109.293 154.868 109.138 154.19 108.953L149.534 107.782L148.609 111.389C148.609 111.389 151.138 111.975 151.076 112.006C152.463 112.345 152.71 113.208 152.648 113.979L148.856 129.18C148.702 129.612 148.208 130.167 147.314 130.013C147.345 130.043 144.848 129.396 144.848 129.396L143.183 133.25L147.561 134.36C148.393 134.576 149.195 134.792 149.997 134.977L148.578 140.588L151.97 141.452L153.358 135.871C154.283 136.118 155.177 136.333 156.04 136.58L154.653 142.099L158.044 142.963L159.463 137.351C165.167 138.43 169.545 137.998 171.333 132.788C172.875 128.625 171.333 126.158 168.25 124.586C170.47 124 172.135 122.613 172.598 119.591C173.214 115.49 170.069 113.301 165.814 111.821Z" fill="#FFBF5E"/>
|
||||
<path d="M288.653 268.267V279.265C288.653 282.228 287.476 285.069 285.381 287.164C283.285 289.259 280.444 290.437 277.481 290.437H207.957C204.994 290.437 202.153 289.259 200.058 287.164C197.963 285.069 196.786 282.228 196.786 279.265V228.682L182 198L277.481 198.507C278.949 198.507 280.403 198.796 281.759 199.358C283.115 199.921 284.347 200.744 285.384 201.783C286.422 202.822 287.245 204.054 287.805 205.411C288.366 206.768 288.654 208.222 288.653 209.69V237.639" fill="white"/>
|
||||
<path d="M288.653 210.42V281.939C288.775 293.984 256.437 291.393 256.437 291.393C259.4 291.393 262.242 290.216 264.337 288.12C266.432 286.025 267.609 283.184 267.609 280.221V198.855L277.481 199.24C278.949 199.24 280.402 199.529 281.758 200.091C283.114 200.653 284.346 201.477 285.384 202.515C286.421 203.553 287.244 204.786 287.805 206.142C288.366 207.499 288.654 208.952 288.653 210.42V210.42Z" fill="#CCCCCC"/>
|
||||
<path d="M288.653 248.031V259.516" stroke="black" stroke-width="6" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M288.653 268.267V279.265C288.653 282.228 287.476 285.069 285.381 287.164C283.285 289.259 280.444 290.437 277.481 290.437H207.957C204.994 290.437 202.153 289.259 200.058 287.164C197.963 285.069 196.786 282.228 196.786 279.265V228.682L182 198L277.481 198.507C278.949 198.507 280.403 198.796 281.759 199.358C283.115 199.921 284.347 200.744 285.384 201.783C286.422 202.822 287.245 204.054 287.805 205.411C288.366 206.768 288.654 208.222 288.653 209.69V237.639" stroke="black" stroke-width="6" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M257.679 257.264C257.679 261.232 256.103 265.037 253.297 267.842C250.492 270.648 246.687 272.224 242.719 272.224C238.752 272.224 234.947 270.648 232.141 267.842C229.336 265.037 227.76 261.232 227.76 257.264C227.76 248.993 257.679 248.993 257.679 257.264Z" fill="#ED1C24" stroke="black" stroke-width="6" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M212.598 236.302C213.112 234.868 214.058 233.629 215.305 232.755C216.551 231.88 218.038 231.413 219.561 231.418C221.084 231.423 222.568 231.899 223.81 232.781C225.051 233.663 225.989 234.908 226.494 236.345" stroke="black" stroke-width="6" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M258.959 236.302C259.473 234.868 260.419 233.629 261.666 232.755C262.913 231.88 264.4 231.413 265.922 231.418C267.445 231.423 268.929 231.899 270.171 232.781C271.412 233.663 272.35 234.908 272.855 236.345" stroke="black" stroke-width="6" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M249.766 251.706C254.278 252.613 257.683 254.46 257.683 257.249C257.684 260.134 256.851 262.959 255.285 265.382C253.719 267.805 251.486 269.724 248.855 270.909" fill="#D30D41"/>
|
||||
<path d="M249.766 251.706C254.278 252.613 257.683 254.46 257.683 257.249C257.684 260.134 256.851 262.959 255.285 265.382C253.719 267.805 251.486 269.724 248.855 270.909" stroke="black" stroke-width="6" stroke-miterlimit="10"/>
|
||||
<g filter="url(#filter8_d_102_2)">
|
||||
<circle cx="378.5" cy="242.5" r="44.5" fill="white"/>
|
||||
</g>
|
||||
<path d="M379 229C388.9 229 397 234.967 397 242.333C397 249.7 388.9 255.667 379 255.667C376.768 255.667 374.626 255.367 372.646 254.833C367.39 259 361 259 361 259C365.194 255.117 365.86 252.5 365.95 251.5C362.89 249.117 361 245.883 361 242.333C361 234.967 369.1 229 379 229Z" fill="#E9707F"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_102_2" x="53" y="325" width="149" height="149" 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" result="hardAlpha"/>
|
||||
<feOffset dy="16"/>
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_102_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_102_2" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d_102_2" x="366" y="350" width="89" height="89" 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" result="hardAlpha"/>
|
||||
<feOffset dy="16"/>
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_102_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_102_2" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter2_d_102_2" x="40" y="264" width="77" height="77" 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" result="hardAlpha"/>
|
||||
<feOffset dy="16"/>
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_102_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_102_2" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter3_d_102_2" x="323" y="109" width="76" height="76" 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" result="hardAlpha"/>
|
||||
<feOffset dy="16"/>
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_102_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_102_2" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter4_d_102_2" x="290" y="342" width="85" height="85" 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" result="hardAlpha"/>
|
||||
<feOffset dy="16"/>
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_102_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_102_2" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter5_d_102_2" x="4" y="159" width="85" height="85" 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" result="hardAlpha"/>
|
||||
<feOffset dy="16"/>
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_102_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_102_2" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter6_d_102_2" x="314" y="58" width="85" height="85" 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" result="hardAlpha"/>
|
||||
<feOffset dy="16"/>
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_102_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_102_2" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter7_d_102_2" x="76" y="58" width="165" height="165" 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" result="hardAlpha"/>
|
||||
<feOffset dy="16"/>
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_102_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_102_2" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter8_d_102_2" x="302" y="182" width="153" height="153" 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" result="hardAlpha"/>
|
||||
<feOffset dy="16"/>
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_102_2"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_102_2" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_102_2">
|
||||
<rect width="484" height="484" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
654
css/main.css
654
css/main.css
File diff suppressed because it is too large
Load Diff
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
897
css/main.scss
897
css/main.scss
File diff suppressed because it is too large
Load Diff
8849
default.js
8849
default.js
File diff suppressed because it is too large
Load Diff
1750
index.html
1750
index.html
File diff suppressed because one or more lines are too long
554
scripts/btcOperator.js
Normal file
554
scripts/btcOperator.js
Normal file
@ -0,0 +1,554 @@
|
||||
(function (EXPORTS) { //btcOperator v1.0.8
|
||||
/* BTC Crypto and API Operator */
|
||||
const btcOperator = EXPORTS;
|
||||
|
||||
//This library uses API provided by chain.so (https://chain.so/)
|
||||
const URL = "https://chain.so/api/v2/";
|
||||
|
||||
const fetch_api = btcOperator.fetch = function (api) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.debug(URL + api);
|
||||
fetch(URL + api).then(response => {
|
||||
response.json()
|
||||
.then(result => result.status === "success" ? resolve(result) : reject(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
};
|
||||
|
||||
const SIGN_SIZE = 73;
|
||||
|
||||
function get_fee_rate() {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch('https://api.blockchain.info/mempool/fees').then(response => {
|
||||
if (response.ok)
|
||||
response.json()
|
||||
.then(result => resolve(result.regular))
|
||||
.catch(error => reject(error));
|
||||
else
|
||||
reject(response);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const broadcast = btcOperator.broadcast = rawtx => new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: URL + "send_tx/BTC/",
|
||||
data: {
|
||||
"tx_hex": rawtx
|
||||
},
|
||||
dataType: "json",
|
||||
error: e => reject(e.responseJSON),
|
||||
success: r => r.status === "success" ? resolve(r.data) : reject(r)
|
||||
})
|
||||
});
|
||||
|
||||
Object.defineProperties(btcOperator, {
|
||||
newKeys: {
|
||||
get: () => {
|
||||
let r = coinjs.newKeys();
|
||||
r.segwitAddress = coinjs.segwitAddress(r.pubkey).address;
|
||||
r.bech32Address = coinjs.bech32Address(r.pubkey).address;
|
||||
return r;
|
||||
}
|
||||
},
|
||||
pubkey: {
|
||||
value: key => key.length >= 66 ? key : (key.length == 64 ? coinjs.newPubkey(key) : coinjs.wif2pubkey(key).pubkey)
|
||||
},
|
||||
address: {
|
||||
value: (key, prefix = undefined) => coinjs.pubkey2address(btcOperator.pubkey(key), prefix)
|
||||
},
|
||||
segwitAddress: {
|
||||
value: key => coinjs.segwitAddress(btcOperator.pubkey(key)).address
|
||||
},
|
||||
bech32Address: {
|
||||
value: key => coinjs.bech32Address(btcOperator.pubkey(key)).address
|
||||
}
|
||||
});
|
||||
|
||||
coinjs.compressed = true;
|
||||
|
||||
const verifyKey = btcOperator.verifyKey = function (addr, key) {
|
||||
if (!addr || !key)
|
||||
return undefined;
|
||||
switch (coinjs.addressDecode(addr).type) {
|
||||
case "standard":
|
||||
return btcOperator.address(key) === addr;
|
||||
case "multisig":
|
||||
return btcOperator.segwitAddress(key) === addr;
|
||||
case "bech32":
|
||||
return btcOperator.bech32Address(key) === addr;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const validateAddress = btcOperator.validateAddress = function (addr) {
|
||||
if (!addr)
|
||||
return undefined;
|
||||
let type = coinjs.addressDecode(addr).type;
|
||||
if (["standard", "multisig", "bech32"].includes(type))
|
||||
return type;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
btcOperator.multiSigAddress = function (pubKeys, minRequired) {
|
||||
if (!Array.isArray(pubKeys))
|
||||
throw "pubKeys must be an array of public keys";
|
||||
else if (pubKeys.length < minRequired)
|
||||
throw "minimum required should be less than the number of pubKeys";
|
||||
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired);
|
||||
}
|
||||
|
||||
//convert from one blockchain to another blockchain (target version)
|
||||
btcOperator.convert = {};
|
||||
|
||||
btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) {
|
||||
let keyHex = decodeLegacy(source_wif).hex;
|
||||
if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex))
|
||||
return null;
|
||||
else
|
||||
return encodeLegacy(keyHex, target_version);
|
||||
}
|
||||
|
||||
btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) {
|
||||
let rawHex = decodeLegacy(source_addr).hex;
|
||||
if (!rawHex)
|
||||
return null;
|
||||
else
|
||||
return encodeLegacy(rawHex, target_version);
|
||||
}
|
||||
|
||||
btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
||||
let rawHex = decodeLegacy(source_addr).hex;
|
||||
if (!rawHex)
|
||||
return null;
|
||||
else
|
||||
return encodeBech32(rawHex, target_version, target_hrp);
|
||||
}
|
||||
|
||||
btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
||||
let rawHex = decodeBech32(source_addr).hex;
|
||||
if (!rawHex)
|
||||
return null;
|
||||
else
|
||||
return encodeBech32(rawHex, target_version, target_hrp);
|
||||
}
|
||||
|
||||
btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) {
|
||||
let rawHex = decodeBech32(source_addr).hex;
|
||||
if (!rawHex)
|
||||
return null;
|
||||
else
|
||||
return encodeLegacy(rawHex, target_version);
|
||||
}
|
||||
|
||||
function decodeLegacy(source) {
|
||||
var decode = coinjs.base58decode(source);
|
||||
var raw = decode.slice(0, decode.length - 4),
|
||||
checksum = decode.slice(decode.length - 4);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(raw, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3])
|
||||
return null;
|
||||
let version = raw.shift();
|
||||
return {
|
||||
version: version,
|
||||
hex: Crypto.util.bytesToHex(raw)
|
||||
}
|
||||
}
|
||||
|
||||
function encodeLegacy(hex, version) {
|
||||
var bytes = Crypto.util.hexToBytes(hex);
|
||||
bytes.unshift(version);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return coinjs.base58encode(bytes.concat(checksum));
|
||||
}
|
||||
|
||||
function decodeBech32(source) {
|
||||
let decode = coinjs.bech32_decode(source);
|
||||
if (!decode)
|
||||
return null;
|
||||
var raw = decode.data;
|
||||
let version = raw.shift();
|
||||
raw = coinjs.bech32_convert(raw, 5, 8, false);
|
||||
return {
|
||||
hrp: decode.hrp,
|
||||
version: version,
|
||||
hex: Crypto.util.bytesToHex(raw)
|
||||
}
|
||||
}
|
||||
|
||||
function encodeBech32(hex, version, hrp) {
|
||||
var bytes = Crypto.util.hexToBytes(hex);
|
||||
bytes = coinjs.bech32_convert(bytes, 8, 5, true);
|
||||
bytes.unshift(version)
|
||||
return coinjs.bech32_encode(hrp, bytes);
|
||||
}
|
||||
|
||||
//BTC blockchain APIs
|
||||
|
||||
btcOperator.getBalance = addr => new Promise((resolve, reject) => {
|
||||
fetch_api(`get_address_balance/BTC/${addr}`)
|
||||
.then(result => resolve(parseFloat(result.data.confirmed_balance)))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
function _redeemScript(addr, key) {
|
||||
let decode = coinjs.addressDecode(addr);
|
||||
switch (decode.type) {
|
||||
case "standard":
|
||||
return false;
|
||||
case "multisig":
|
||||
return key ? coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript : null;
|
||||
case "bech32":
|
||||
return decode.redeemscript;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function validateTxParameters(parameters) {
|
||||
let invalids = [];
|
||||
//sender-ids
|
||||
if (parameters.senders) {
|
||||
if (!Array.isArray(parameters.senders))
|
||||
parameters.senders = [parameters.senders];
|
||||
parameters.senders.forEach(id => !validateAddress(id) ? invalids.push(id) : null);
|
||||
if (invalids.length)
|
||||
throw "Invalid senders:" + invalids;
|
||||
}
|
||||
if (parameters.privkeys) {
|
||||
if (!Array.isArray(parameters.privkeys))
|
||||
parameters.privkeys = [parameters.privkeys];
|
||||
if (parameters.senders.length != parameters.privkeys.length)
|
||||
throw "Array length for senders and privkeys should be equal";
|
||||
parameters.senders.forEach((id, i) => {
|
||||
let key = parameters.privkeys[i];
|
||||
if (!verifyKey(id, key)) //verify private-key
|
||||
invalids.push(id);
|
||||
if (key.length === 64) //convert Hex to WIF if needed
|
||||
parameters.privkeys[i] = coinjs.privkey2wif(key);
|
||||
});
|
||||
if (invalids.length)
|
||||
throw "Invalid keys:" + invalids;
|
||||
}
|
||||
//receiver-ids (and change-id)
|
||||
if (!Array.isArray(parameters.receivers))
|
||||
parameters.receivers = [parameters.receivers];
|
||||
parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null);
|
||||
if (invalids.length)
|
||||
throw "Invalid receivers:" + invalids;
|
||||
if (parameters.change_addr && !validateAddress(parameters.change_addr))
|
||||
throw "Invalid change_address:" + parameters.change_addr;
|
||||
//fee and amounts
|
||||
if ((typeof parameters.fee !== "number" || parameters.fee <= 0) && parameters.fee !== null) //fee = null (auto calc)
|
||||
throw "Invalid fee:" + parameters.fee;
|
||||
if (!Array.isArray(parameters.amounts))
|
||||
parameters.amounts = [parameters.amounts];
|
||||
if (parameters.receivers.length != parameters.amounts.length)
|
||||
throw "Array length for receivers and amounts should be equal";
|
||||
parameters.amounts.forEach(a => typeof a !== "number" || a <= 0 ? invalids.push(a) : null);
|
||||
if (invalids.length)
|
||||
throw "Invalid amounts:" + invalids;
|
||||
//return
|
||||
return parameters;
|
||||
}
|
||||
|
||||
const TMP_FEE = 0.00001;
|
||||
|
||||
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let auto_fee = false,
|
||||
total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
|
||||
if (fee === null) {
|
||||
auto_fee = true;
|
||||
fee = TMP_FEE;
|
||||
}
|
||||
const tx = coinjs.transaction();
|
||||
addUTXOs(tx, senders, redeemScripts, total_amount + fee).then(result => {
|
||||
if (result > 0)
|
||||
return reject("Insufficient Balance");
|
||||
let change = addOutputs(tx, receivers, amounts, Math.abs(result), change_addr);
|
||||
if (!auto_fee)
|
||||
return resolve(tx);
|
||||
autoFeeCalc(tx).then(fee_calc => {
|
||||
fee = Math.round((fee * 1) * 1e8); //satoshi convertion
|
||||
if (!change)
|
||||
tx.addoutput(change_addr, 0);
|
||||
editFee(tx, fee, fee_calc);
|
||||
resolve(tx);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function addUTXOs(tx, senders, redeemScripts, required_amount, n = 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
required_amount = parseFloat(required_amount.toFixed(8));
|
||||
if (required_amount <= 0 || n >= senders.length)
|
||||
return resolve(required_amount);
|
||||
let addr = senders[n],
|
||||
rs = redeemScripts[n];
|
||||
fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => {
|
||||
let utxos = result.data.txs;
|
||||
console.debug("add-utxo", addr, rs, required_amount, utxos);
|
||||
for (let i = 0; i < utxos.length && required_amount > 0; i++) {
|
||||
if (!utxos[i].confirmations) //ignore unconfirmed utxo
|
||||
continue;
|
||||
required_amount -= parseFloat(utxos[i].value);
|
||||
var script;
|
||||
if (!rs || !rs.length) //legacy script
|
||||
script = utxos[i].script_hex;
|
||||
else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi))) {
|
||||
//redeemScript for segwit/bech32
|
||||
let s = coinjs.script();
|
||||
s.writeBytes(Crypto.util.hexToBytes(rs));
|
||||
s.writeOp(0);
|
||||
s.writeBytes(coinjs.numToBytes((utxos[i].value * 100000000).toFixed(0), 8));
|
||||
script = Crypto.util.bytesToHex(s.buffer);
|
||||
} else //redeemScript for multisig
|
||||
script = rs;
|
||||
tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
|
||||
}
|
||||
addUTXOs(tx, senders, redeemScripts, required_amount, n + 1)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function addOutputs(tx, receivers, amounts, change, change_addr) {
|
||||
for (let i in receivers)
|
||||
tx.addoutput(receivers[i], amounts[i]);
|
||||
if (parseFloat(change.toFixed(8)) > 0) {
|
||||
tx.addoutput(change_addr, change);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
function autoFeeCalc(tx) {
|
||||
return new Promise((resolve, reject) => {
|
||||
get_fee_rate().then(fee_rate => {
|
||||
let tx_size = tx.size();
|
||||
for (var i = 0; i < this.ins.length; i++)
|
||||
switch (tx.extractScriptKey(i).type) {
|
||||
case 'scriptpubkey':
|
||||
tx_size += SIGN_SIZE;
|
||||
break;
|
||||
case 'segwit':
|
||||
case 'multisig':
|
||||
tx_size += SIGN_SIZE * 0.25;
|
||||
break;
|
||||
default:
|
||||
console.warn('Unknown script-type');
|
||||
tx_size += SIGN_SIZE;
|
||||
}
|
||||
resolve(tx_size * fee_rate);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function editFee(tx, current_fee, target_fee, index = -1) {
|
||||
//values are in satoshi
|
||||
index = parseInt(index >= 0 ? index : tx.out.length - index);
|
||||
if (index < 0 || index >= tx.out.length)
|
||||
throw "Invalid index";
|
||||
let edit_value = parseInt(current_fee - target_fee), //rip of any decimal places
|
||||
current_value = tx.out[index].value; //could be BigInterger
|
||||
if (edit_value < 0 && edit_value > current_value)
|
||||
throw "Insufficient value at vout";
|
||||
tx.out[index].value = current_value instanceof BigInteger ?
|
||||
current_value.add(new BigInteger('' + edit_value)) : parseInt(current_value + edit_value);
|
||||
}
|
||||
|
||||
btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee, change_addr = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
({
|
||||
senders,
|
||||
privkeys,
|
||||
receivers,
|
||||
amounts
|
||||
} = validateTxParameters({
|
||||
senders,
|
||||
privkeys,
|
||||
receivers,
|
||||
amounts,
|
||||
fee,
|
||||
change_addr
|
||||
}));
|
||||
} catch (e) {
|
||||
return reject(e)
|
||||
}
|
||||
let redeemScripts = [],
|
||||
wif_keys = [];
|
||||
for (let i in senders) {
|
||||
let rs = _redeemScript(senders[i], privkeys[i]); //get redeem-script (segwit/bech32)
|
||||
redeemScripts.push(rs);
|
||||
rs === false ? wif_keys.unshift(privkeys[i]) : wif_keys.push(privkeys[i]); //sorting private-keys (wif)
|
||||
}
|
||||
if (redeemScripts.includes(null)) //TODO: segwit
|
||||
return reject("Unable to get redeem-script");
|
||||
//create transaction
|
||||
createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0]).then(tx => {
|
||||
console.debug("Unsigned:", tx.serialize());
|
||||
new Set(wif_keys).forEach(key => console.debug("Signing key:", key, tx.sign(key, 1 /*sighashtype*/))); //Sign the tx using private key WIF
|
||||
console.debug("Signed:", tx.serialize());
|
||||
debugger;
|
||||
broadcast(tx.serialize())
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
btcOperator.createTx = function (senders, receivers, amounts, fee = null, change_addr = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
({
|
||||
senders,
|
||||
receivers,
|
||||
amounts
|
||||
} = validateTxParameters({
|
||||
senders,
|
||||
receivers,
|
||||
amounts,
|
||||
fee,
|
||||
change_addr
|
||||
}));
|
||||
} catch (e) {
|
||||
return reject(e)
|
||||
}
|
||||
let redeemScripts = senders.map(id => _redeemScript(id));
|
||||
if (redeemScripts.includes(null)) //TODO: segwit
|
||||
return reject("Unable to get redeem-script");
|
||||
//create transaction
|
||||
createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0])
|
||||
.then(tx => resolve(tx.serialize()))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//validate tx parameters
|
||||
if (validateAddress(sender) !== "multisig")
|
||||
return reject("Invalid sender (multisig):" + sender);
|
||||
else {
|
||||
let script = coinjs.script();
|
||||
let decode = script.decodeRedeemScript(redeemScript);
|
||||
if (!decode || decode.address !== sender)
|
||||
return reject("Invalid redeem-script");
|
||||
}
|
||||
try {
|
||||
({
|
||||
receivers,
|
||||
amounts
|
||||
} = validateTxParameters({
|
||||
receivers,
|
||||
amounts,
|
||||
fee
|
||||
}));
|
||||
} catch (e) {
|
||||
return reject(e)
|
||||
}
|
||||
//create transaction
|
||||
createTransaction([sender], [redeemScript], receivers, amounts, fee, sender)
|
||||
.then(tx => resolve(tx.serialize()))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function deserializeTx(tx) {
|
||||
if (typeof tx === 'string' || Array.isArray(tx)) {
|
||||
try {
|
||||
tx = coinjs.transaction().deserialize(tx);
|
||||
} catch {
|
||||
throw "Invalid transaction hex";
|
||||
}
|
||||
} else if (typeof tx !== 'object' || typeof tx.sign !== 'function')
|
||||
throw "Invalid transaction object";
|
||||
return tx;
|
||||
}
|
||||
|
||||
btcOperator.signTx = function (tx, privkeys, sighashtype = 1) {
|
||||
tx = deserializeTx(tx);
|
||||
if (!Array.isArray(privkeys))
|
||||
privkeys = [privkeys];
|
||||
for (let i in privkeys)
|
||||
if (privkeys[i].length === 64)
|
||||
privkeys[i] = coinjs.privkey2wif(privkeys[i]);
|
||||
new Set(privkeys).forEach(key => tx.sign(key, sighashtype)); //Sign the tx using private key WIF
|
||||
return tx.serialize();
|
||||
}
|
||||
|
||||
btcOperator.checkSigned = function (tx, bool = true) {
|
||||
tx = deserializeTx(tx);
|
||||
let n = [];
|
||||
for (let i in tx.ins) {
|
||||
var s = tx.extractScriptKey(i);
|
||||
if (s['type'] !== 'multisig')
|
||||
n.push(s.signed == 'true' || (tx.witness[i] && tx.witness[i].length == 2))
|
||||
else {
|
||||
var rs = coinjs.script().decodeRedeemScript(s.script);
|
||||
let x = {
|
||||
s: s['signatures'],
|
||||
r: rs['signaturesRequired'],
|
||||
t: rs['pubkeys'].length
|
||||
};
|
||||
if (x.r > x.t)
|
||||
throw "signaturesRequired is more than publicKeys";
|
||||
else if (x.s < x.r)
|
||||
n.push(x);
|
||||
else
|
||||
n.push(true);
|
||||
}
|
||||
}
|
||||
return bool ? !(n.filter(x => x !== true).length) : n;
|
||||
}
|
||||
|
||||
btcOperator.checkIfSameTx = function (tx1, tx2) {
|
||||
tx1 = deserializeTx(tx1);
|
||||
tx2 = deserializeTx(tx2);
|
||||
if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length)
|
||||
return false;
|
||||
for (let i = 0; i < tx1.ins.length; i++)
|
||||
if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index)
|
||||
return false;
|
||||
for (let i = 0; i < tx2.ins.length; i++)
|
||||
if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
btcOperator.getTx = txid => new Promise((resolve, reject) => {
|
||||
fetch_api(`get_tx/BTC/${txid}`)
|
||||
.then(result => resolve(result.data))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
btcOperator.getAddressData = addr => new Promise((resolve, reject) => {
|
||||
fetch_api(`address/BTC/${addr}`)
|
||||
.then(result => resolve(result.data))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
btcOperator.getBlock = block => new Promise((resolve, reject) => {
|
||||
fetch_api(`get_block/BTC/${block}`)
|
||||
.then(result => resolve(result.data))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
})('object' === typeof module ? module.exports : window.btcOperator = {});
|
||||
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
||||
(function(EXPORTS) { //floBlockchainAPI v2.3.3
|
||||
(function(EXPORTS) { //floBlockchainAPI v2.3.3b
|
||||
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
|
||||
'use strict';
|
||||
const floBlockchainAPI = EXPORTS;
|
||||
@ -6,7 +6,7 @@
|
||||
const DEFAULT = {
|
||||
blockchain: floGlobals.blockchain,
|
||||
apiURL: {
|
||||
FLO: ['https://livenet.flocha.in/', 'https://flosight.duckdns.org/'],
|
||||
FLO: ['https://flosight.duckdns.org/'],
|
||||
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/']
|
||||
},
|
||||
sendAmt: 0.001,
|
||||
@ -49,7 +49,7 @@
|
||||
const allServerList = new Set(floGlobals.apiURL && floGlobals.apiURL[DEFAULT.blockchain] ? floGlobals.apiURL[DEFAULT.blockchain] : DEFAULT.apiURL[DEFAULT.blockchain]);
|
||||
|
||||
var serverList = Array.from(allServerList);
|
||||
var curPos = floCrypto.randInt(0, serverList - 1);
|
||||
var curPos = floCrypto.randInt(0, serverList.length - 1);
|
||||
|
||||
function fetch_retry(apicall, rm_flosight) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -125,9 +125,9 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateASCII(floData))
|
||||
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
|
||||
else if (!floCrypto.validateAddr(senderAddr))
|
||||
else if (!floCrypto.validateFloID(senderAddr))
|
||||
return reject(`Invalid address : ${senderAddr}`);
|
||||
else if (!floCrypto.validateAddr(receiverAddr))
|
||||
else if (!floCrypto.validateFloID(receiverAddr))
|
||||
return reject(`Invalid address : ${receiverAddr}`);
|
||||
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
|
||||
return reject("Invalid Private key!");
|
||||
@ -202,7 +202,7 @@
|
||||
//merge all UTXOs of a given floID into a single UTXO
|
||||
floBlockchainAPI.mergeUTXOs = function(floID, privKey, floData = '') {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
if (!floCrypto.validateFloID(floID))
|
||||
return reject(`Invalid floID`);
|
||||
if (!floCrypto.verifyPrivKey(privKey, floID))
|
||||
return reject("Invalid Private Key");
|
||||
@ -326,7 +326,7 @@
|
||||
}
|
||||
//Validate the receiver IDs and receive amount
|
||||
for (let floID in receivers) {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
if (!floCrypto.validateFloID(floID))
|
||||
invalids.InvalidReceiverIDs.push(floID);
|
||||
if (typeof receivers[floID] !== 'number' || receivers[floID] <= 0)
|
||||
invalids.InvalidReceiveAmountFor.push(floID);
|
||||
@ -371,18 +371,18 @@
|
||||
})
|
||||
//Calculate totalSentAmount and check if totalBalance is sufficient
|
||||
let totalSendAmt = totalFee;
|
||||
for (floID in receivers)
|
||||
for (let floID in receivers)
|
||||
totalSendAmt += receivers[floID];
|
||||
if (totalBalance < totalSendAmt)
|
||||
return reject("Insufficient total Balance");
|
||||
//Get the UTXOs of the senders
|
||||
let promises = [];
|
||||
for (floID in senders)
|
||||
for (let floID in senders)
|
||||
promises.push(promisedAPI(`api/addr/${floID}/utxo`));
|
||||
Promise.all(promises).then(results => {
|
||||
let wifSeq = [];
|
||||
var trx = bitjs.transaction();
|
||||
for (floID in senders) {
|
||||
for (let floID in senders) {
|
||||
let utxos = results.shift();
|
||||
let sendAmt;
|
||||
if (preserveRatio) {
|
||||
@ -406,7 +406,7 @@
|
||||
if (change > 0)
|
||||
trx.addoutput(floID, change);
|
||||
}
|
||||
for (floID in receivers)
|
||||
for (let floID in receivers)
|
||||
trx.addoutput(floID, receivers[floID]);
|
||||
trx.addflodata(floData.replace(/\n/g, ' '));
|
||||
for (let i = 0; i < wifSeq.length; i++)
|
||||
|
||||
@ -1,14 +1,59 @@
|
||||
(function(EXPORTS) { //floCloudAPI v2.3.0
|
||||
(function(EXPORTS) { //floCloudAPI v2.4.2d
|
||||
/* FLO Cloud operations to send/request application data*/
|
||||
'use strict';
|
||||
const floCloudAPI = EXPORTS;
|
||||
|
||||
const DEFAULT = {
|
||||
blockchainPrefix: 0x23, //Prefix version for FLO blockchain
|
||||
SNStorageID: floGlobals.SNStorageID || "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk",
|
||||
adminID: floGlobals.adminID,
|
||||
application: floGlobals.application
|
||||
application: floGlobals.application,
|
||||
callback: (d, e) => console.debug(d, e)
|
||||
};
|
||||
|
||||
var user_id, user_public, user_private, aes_key;
|
||||
|
||||
function user(id, priv) {
|
||||
if (!priv || !id)
|
||||
return user.clear();
|
||||
let pub = floCrypto.getPubKeyHex(priv);
|
||||
if (!pub || !floCrypto.verifyPubKey(pub, id))
|
||||
return user.clear();
|
||||
let n = floCrypto.randInt(12, 20);
|
||||
aes_key = floCrypto.randString(n);
|
||||
user_private = Crypto.AES.encrypt(priv, aes_key);
|
||||
user_public = pub;
|
||||
user_id = id;
|
||||
return user_id;
|
||||
}
|
||||
|
||||
Object.defineProperties(user, {
|
||||
id: {
|
||||
get: () => {
|
||||
if (!user_id)
|
||||
throw "User not set";
|
||||
return user_id;
|
||||
}
|
||||
},
|
||||
public: {
|
||||
get: () => {
|
||||
if (!user_public)
|
||||
throw "User not set";
|
||||
return user_public;
|
||||
}
|
||||
},
|
||||
sign: {
|
||||
value: msg => {
|
||||
if (!user_private)
|
||||
throw "User not set";
|
||||
return floCrypto.signData(msg, Crypto.AES.decrypt(user_private, aes_key));
|
||||
}
|
||||
},
|
||||
clear: {
|
||||
value: () => user_id = user_public = user_private = aes_key = undefined
|
||||
}
|
||||
})
|
||||
|
||||
Object.defineProperties(floCloudAPI, {
|
||||
SNStorageID: {
|
||||
get: () => DEFAULT.SNStorageID
|
||||
@ -18,6 +63,9 @@
|
||||
},
|
||||
application: {
|
||||
get: () => DEFAULT.application
|
||||
},
|
||||
user: {
|
||||
get: () => user
|
||||
}
|
||||
});
|
||||
|
||||
@ -175,7 +223,7 @@
|
||||
if (_inactive.size === kBucket.list.length)
|
||||
return reject('Cloud offline');
|
||||
if (!(snID in supernodes))
|
||||
snID = kBucket.closestNode(snID);
|
||||
snID = kBucket.closestNode(proxyID(snID));
|
||||
ws_connect(snID)
|
||||
.then(node => resolve(node))
|
||||
.catch(error => {
|
||||
@ -213,7 +261,7 @@
|
||||
if (_inactive.size === kBucket.list.length)
|
||||
return reject('Cloud offline');
|
||||
if (!(snID in supernodes))
|
||||
snID = kBucket.closestNode(snID);
|
||||
snID = kBucket.closestNode(proxyID(snID));
|
||||
fetch_API(snID, data)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => {
|
||||
@ -269,6 +317,7 @@
|
||||
data => {
|
||||
data = objectifier(data);
|
||||
let filtered = {},
|
||||
proxy = proxyID(request.receiverID),
|
||||
r = request;
|
||||
for (let v in data) {
|
||||
let d = data[v];
|
||||
@ -277,7 +326,7 @@
|
||||
(r.atVectorClock || !r.upperVectorClock || r.upperVectorClock >= v) &&
|
||||
(!r.afterTime || r.afterTime < d.log_time) &&
|
||||
r.application == d.application &&
|
||||
r.receiverID == d.receiverID &&
|
||||
(proxy == d.receiverID || proxy == d.proxyID) &&
|
||||
(!r.comment || r.comment == d.comment) &&
|
||||
(!r.type || r.type == d.type) &&
|
||||
(!r.senderID || r.senderID.includes(d.senderID)))
|
||||
@ -332,6 +381,50 @@
|
||||
'|' + (options.application || DEFAULT.application);
|
||||
}
|
||||
|
||||
const proxyID = util.proxyID = function(address) {
|
||||
if (!address)
|
||||
return;
|
||||
var bytes;
|
||||
if (address.length == 33 || address.length == 34) { //legacy encoding
|
||||
let decode = bitjs.Base58.decode(address);
|
||||
bytes = decode.slice(0, decode.length - 4);
|
||||
let checksum = decode.slice(decode.length - 4),
|
||||
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3] ?
|
||||
bytes = undefined : bytes.shift();
|
||||
} else if (address.length == 42 || address.length == 62) { //bech encoding
|
||||
if (typeof coinjs !== 'function')
|
||||
throw "library missing (lib_btc.js)";
|
||||
let decode = coinjs.bech32_decode(address);
|
||||
if (decode) {
|
||||
bytes = decode.data;
|
||||
bytes.shift();
|
||||
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
|
||||
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
|
||||
bytes = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(address), {
|
||||
asBytes: true
|
||||
}));
|
||||
}
|
||||
if (!bytes)
|
||||
throw "Invalid address: " + address;
|
||||
else {
|
||||
bytes.unshift(DEFAULT.blockchainPrefix);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
}
|
||||
|
||||
const lastCommit = {};
|
||||
Object.defineProperty(lastCommit, 'get', {
|
||||
value: objName => JSON.parse(lastCommit[objName])
|
||||
@ -392,19 +485,19 @@
|
||||
}));
|
||||
}
|
||||
|
||||
//set status as online for myFloID
|
||||
//set status as online for user_id
|
||||
floCloudAPI.setStatus = function(options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let callback = options.callback instanceof Function ? options.callback : (d, e) => console.debug(d, e);
|
||||
let callback = options.callback instanceof Function ? options.callback : DEFAULT.callback;
|
||||
var request = {
|
||||
floID: myFloID,
|
||||
floID: user.id,
|
||||
application: options.application || DEFAULT.application,
|
||||
time: Date.now(),
|
||||
status: true,
|
||||
pubKey: myPubKey
|
||||
pubKey: user.public
|
||||
}
|
||||
let hashcontent = ["time", "application", "floID"].map(d => request[d]).join("|");
|
||||
request.sign = floCrypto.signData(hashcontent, myPrivKey);
|
||||
request.sign = user.sign(hashcontent);
|
||||
liveRequest(options.refID || DEFAULT.adminID, request, callback)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
@ -416,7 +509,7 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!Array.isArray(trackList))
|
||||
trackList = [trackList];
|
||||
let callback = options.callback instanceof Function ? options.callback : (d, e) => console.debug(d, e);
|
||||
let callback = options.callback instanceof Function ? options.callback : DEFAULT.callback;
|
||||
let request = {
|
||||
status: false,
|
||||
application: options.application || DEFAULT.application,
|
||||
@ -432,9 +525,9 @@
|
||||
const sendApplicationData = floCloudAPI.sendApplicationData = function(message, type, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var data = {
|
||||
senderID: myFloID,
|
||||
senderID: user.id,
|
||||
receiverID: options.receiverID || DEFAULT.adminID,
|
||||
pubKey: myPubKey,
|
||||
pubKey: user.public,
|
||||
message: encodeMessage(message),
|
||||
time: Date.now(),
|
||||
application: options.application || DEFAULT.application,
|
||||
@ -443,7 +536,7 @@
|
||||
}
|
||||
let hashcontent = ["receiverID", "time", "application", "type", "message", "comment"]
|
||||
.map(d => data[d]).join("|")
|
||||
data.sign = floCrypto.signData(hashcontent, myPrivKey);
|
||||
data.sign = user.sign(hashcontent);
|
||||
singleRequest(data.receiverID, data)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
@ -482,19 +575,20 @@
|
||||
})
|
||||
}
|
||||
|
||||
//(NEEDS UPDATE) delete data from supernode cloud (received only)
|
||||
/*(NEEDS UPDATE)
|
||||
//delete data from supernode cloud (received only)
|
||||
floCloudAPI.deleteApplicationData = function(vectorClocks, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var delreq = {
|
||||
requestorID: myFloID,
|
||||
pubKey: myPubKey,
|
||||
requestorID: user.id,
|
||||
pubKey: user.public,
|
||||
time: Date.now(),
|
||||
delete: (Array.isArray(vectorClocks) ? vectorClocks : [vectorClocks]),
|
||||
application: options.application || DEFAULT.application
|
||||
}
|
||||
let hashcontent = ["time", "application", "delete"]
|
||||
.map(d => delreq[d]).join("|")
|
||||
delreq.sign = floCrypto.signData(hashcontent, myPrivKey)
|
||||
delreq.sign = user.sign(hashcontent)
|
||||
singleRequest(delreq.requestorID, delreq).then(result => {
|
||||
let success = [],
|
||||
failed = [];
|
||||
@ -507,8 +601,9 @@
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
//(NEEDS UPDATE) edit comment of data in supernode cloud (mutable comments only)
|
||||
*/
|
||||
/*(NEEDS UPDATE)
|
||||
//edit comment of data in supernode cloud (mutable comments only)
|
||||
floCloudAPI.editApplicationData = function(vectorClock, newComment, oldData, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let p0
|
||||
@ -523,12 +618,12 @@
|
||||
}
|
||||
})
|
||||
p0.then(d => {
|
||||
if (d.senderID != myFloID)
|
||||
if (d.senderID != user.id)
|
||||
return reject("Invalid requestorID")
|
||||
else if (!d.comment.startsWith("EDIT:"))
|
||||
return reject("Data immutable")
|
||||
let data = {
|
||||
requestorID: myFloID,
|
||||
requestorID: user.id,
|
||||
receiverID: d.receiverID,
|
||||
time: Date.now(),
|
||||
application: d.application,
|
||||
@ -542,29 +637,30 @@
|
||||
"comment"
|
||||
]
|
||||
.map(x => d[x]).join("|")
|
||||
data.edit.sign = floCrypto.signData(hashcontent, myPrivKey)
|
||||
data.edit.sign = user.sign(hashcontent)
|
||||
singleRequest(data.receiverID, data)
|
||||
.then(result => resolve("Data comment updated"))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
//tag data in supernode cloud (subAdmin access only)
|
||||
floCloudAPI.tagApplicationData = function(vectorClock, tag, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floGlobals.subAdmins.includes(myFloID))
|
||||
if (!floGlobals.subAdmins.includes(user.id))
|
||||
return reject("Only subAdmins can tag data")
|
||||
var request = {
|
||||
receiverID: options.receiverID || DEFAULT.adminID,
|
||||
requestorID: myFloID,
|
||||
pubKey: myPubKey,
|
||||
requestorID: user.id,
|
||||
pubKey: user.public,
|
||||
time: Date.now(),
|
||||
vectorClock: vectorClock,
|
||||
tag: tag,
|
||||
}
|
||||
let hashcontent = ["time", "vectorClock", 'tag'].map(d => request[d]).join("|");
|
||||
request.sign = floCrypto.signData(hashcontent, myPrivKey);
|
||||
request.sign = user.sign(hashcontent);
|
||||
singleRequest(request.receiverID, request)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
@ -576,14 +672,14 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
var request = {
|
||||
receiverID: options.receiverID || DEFAULT.adminID,
|
||||
requestorID: myFloID,
|
||||
pubKey: myPubKey,
|
||||
requestorID: user.id,
|
||||
pubKey: user.public,
|
||||
time: Date.now(),
|
||||
vectorClock: vectorClock,
|
||||
note: note,
|
||||
}
|
||||
let hashcontent = ["time", "vectorClock", 'note'].map(d => request[d]).join("|");
|
||||
request.sign = floCrypto.signData(hashcontent, myPrivKey);
|
||||
request.sign = user.sign(hashcontent);
|
||||
singleRequest(request.receiverID, request)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
(function(EXPORTS) { //floCrypto v2.3.0a
|
||||
(function(EXPORTS) { //floCrypto v2.3.3d
|
||||
/* FLO Crypto Operators */
|
||||
'use strict';
|
||||
const floCrypto = EXPORTS;
|
||||
@ -7,6 +7,7 @@
|
||||
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
||||
const ascii_alternatives = `‘ '\n’ '\n“ "\n” "\n– --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
|
||||
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
|
||||
coinjs.compressed = true; //defaulting coinjs compressed to true;
|
||||
|
||||
function calculateY(x) {
|
||||
let exp = exponent1();
|
||||
@ -80,7 +81,7 @@
|
||||
floCrypto.randInt = function(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
//generate a random String within length (options : alphaNumeric chars only)
|
||||
@ -89,7 +90,7 @@
|
||||
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
||||
for (var i = 0; i < length; i++)
|
||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
result += characters.charAt(Math.floor(securedMathRandom() * characters.length));
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -121,12 +122,8 @@
|
||||
//Sign data using private-key
|
||||
floCrypto.signData = function(data, privateKeyHex) {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
key.setCompressed(true);
|
||||
var privateKeyArr = key.getBitcoinPrivateKeyByteArray();
|
||||
var privateKey = BigInteger.fromByteArrayUnsigned(privateKeyArr);
|
||||
var messageHash = Crypto.SHA256(data);
|
||||
var messageHashBigInteger = new BigInteger(messageHash);
|
||||
var messageSign = Bitcoin.ECDSA.sign(messageHashBigInteger, key.priv);
|
||||
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
|
||||
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||
return sighex;
|
||||
}
|
||||
@ -134,11 +131,9 @@
|
||||
//Verify signatue of the data using public-key
|
||||
floCrypto.verifySign = function(data, signatureHex, publicKeyHex) {
|
||||
var msgHash = Crypto.SHA256(data);
|
||||
var messageHashBigInteger = new BigInteger(msgHash);
|
||||
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||
var signature = Bitcoin.ECDSA.parseSig(sigBytes);
|
||||
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
||||
var verify = Bitcoin.ECDSA.verifyRaw(messageHashBigInteger, signature.r, signature.s, publicKeyPoint);
|
||||
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
|
||||
return verify;
|
||||
}
|
||||
|
||||
@ -153,8 +148,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(floCrypto, 'newID', {
|
||||
get: () => generateNewID()
|
||||
Object.defineProperties(floCrypto, {
|
||||
newID: {
|
||||
get: () => generateNewID()
|
||||
},
|
||||
tmpID: {
|
||||
get: () => {
|
||||
let bytes = Crypto.util.randomBytes(20);
|
||||
bytes.unshift(bitjs.pub);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Returns public-key from private-key
|
||||
@ -182,6 +192,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.getAddress = function(privateKeyHex, strict = false) {
|
||||
if (!privateKeyHex)
|
||||
return;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
let pubKey = key.getPubKeyHex(),
|
||||
version = bitjs.Base58.decode(privateKeyHex)[0];
|
||||
switch (version) {
|
||||
case coinjs.priv: //BTC
|
||||
return coinjs.bech32Address(pubKey).address;
|
||||
case bitjs.priv: //FLO
|
||||
return bitjs.pubkey2address(pubKey);
|
||||
default:
|
||||
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
|
||||
}
|
||||
}
|
||||
|
||||
//Verify the private-key for the given public-key or flo-ID
|
||||
floCrypto.verifyPrivKey = function(privateKeyHex, pubKey_floID, isfloID = true) {
|
||||
if (!privateKeyHex || !pubKey_floID)
|
||||
@ -202,18 +231,112 @@
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given Address is valid or not
|
||||
floCrypto.validateFloID = floCrypto.validateAddr = function(inpAddr) {
|
||||
if (!inpAddr)
|
||||
//Check if the given flo-id is valid or not
|
||||
floCrypto.validateFloID = function(floID) {
|
||||
if (!floID)
|
||||
return false;
|
||||
try {
|
||||
let addr = new Bitcoin.Address(inpAddr);
|
||||
let addr = new Bitcoin.Address(floID);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given address (any blockchain) is valid or not
|
||||
floCrypto.validateAddr = function(address, std = true, bech = true) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return false;
|
||||
if (typeof raw.version !== 'undefined') { //legacy or segwit
|
||||
if (std == false)
|
||||
return false;
|
||||
else if (std === true || (!Array.isArray(std) && std === raw.version) || (Array.isArray(std) && std.includes(raw.version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else if (typeof raw.bech_version !== 'undefined') { //bech32
|
||||
if (bech === false)
|
||||
return false;
|
||||
else if (bech === true || (!Array.isArray(bech) && bech === raw.bech_version) || (Array.isArray(bech) && bech.includes(raw.bech_version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else //unknown
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check the public-key for the address (any blockchain)
|
||||
floCrypto.verifyPubKey = function(pubKeyHex, address) {
|
||||
let raw = decodeAddress(address),
|
||||
pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), {
|
||||
asBytes: true
|
||||
})));
|
||||
return raw ? pub_hash === raw.hex : false;
|
||||
}
|
||||
|
||||
//Convert the given address (any blockchain) to equivalent floID
|
||||
floCrypto.toFloID = function(address) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
raw.bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
|
||||
floCrypto.isSameAddr = function(addr1, addr2) {
|
||||
if (!addr1 || !addr2)
|
||||
return;
|
||||
let raw1 = decodeAddress(addr1),
|
||||
raw2 = decodeAddress(addr2);
|
||||
if (!raw1 || !raw2)
|
||||
return false;
|
||||
else
|
||||
return raw1.hex === raw2.hex;
|
||||
}
|
||||
|
||||
const decodeAddress = floCrypto.decodeAddr = function(address) {
|
||||
if (!address)
|
||||
return;
|
||||
else if (address.length == 33 || address.length == 34) { //legacy encoding
|
||||
let decode = bitjs.Base58.decode(address);
|
||||
let bytes = decode.slice(0, decode.length - 4);
|
||||
let checksum = decode.slice(decode.length - 4),
|
||||
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) ? null : {
|
||||
version: bytes.shift(),
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else if (address.length == 42) { //bech encoding
|
||||
let decode = coinjs.bech32_decode(address);
|
||||
if (decode) {
|
||||
let bytes = decode.data;
|
||||
let bech_version = bytes.shift();
|
||||
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
|
||||
return {
|
||||
bech_version,
|
||||
hrp: decode.hrp,
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Split the str using shamir's Secret and Returns the shares
|
||||
floCrypto.createShamirsSecretShares = function(str, total_shares, threshold_limit) {
|
||||
try {
|
||||
|
||||
@ -1,8 +1,168 @@
|
||||
(function(EXPORTS) { //floDapps v2.2.1
|
||||
(function(EXPORTS) { //floDapps v2.3.2d
|
||||
/* General functions for FLO Dapps*/
|
||||
//'use strict';
|
||||
'use strict';
|
||||
const floDapps = EXPORTS;
|
||||
|
||||
const DEFAULT = {
|
||||
root: "floDapps",
|
||||
application: floGlobals.application,
|
||||
adminID: floGlobals.adminID
|
||||
};
|
||||
|
||||
Object.defineProperties(floDapps, {
|
||||
application: {
|
||||
get: () => DEFAULT.application
|
||||
},
|
||||
adminID: {
|
||||
get: () => DEFAULT.adminID
|
||||
},
|
||||
root: {
|
||||
get: () => DEFAULT.root
|
||||
}
|
||||
});
|
||||
|
||||
var user_priv_raw, aes_key, user_priv_wrap; //private variable inside capsule
|
||||
const raw_user = {
|
||||
get private() {
|
||||
if (!user_priv_raw)
|
||||
throw "User not logged in";
|
||||
return Crypto.AES.decrypt(user_priv_raw, aes_key);
|
||||
}
|
||||
}
|
||||
|
||||
var user_id, user_public, user_private;
|
||||
const user = floDapps.user = {
|
||||
get id() {
|
||||
if (!user_id)
|
||||
throw "User not logged in";
|
||||
return user_id;
|
||||
},
|
||||
get public() {
|
||||
if (!user_public)
|
||||
throw "User not logged in";
|
||||
return user_public;
|
||||
},
|
||||
get private() {
|
||||
if (!user_private)
|
||||
throw "User not logged in";
|
||||
else if (user_private instanceof Function)
|
||||
return user_private();
|
||||
else
|
||||
return Crypto.AES.decrypt(user_private, aes_key);
|
||||
},
|
||||
sign(message) {
|
||||
return floCrypto.signData(message, raw_user.private);
|
||||
},
|
||||
decrypt(data) {
|
||||
return floCrypto.decryptData(data, raw_user.private);
|
||||
},
|
||||
encipher(message) {
|
||||
return Crypto.AES.encrypt(message, raw_user.private);
|
||||
},
|
||||
decipher(data) {
|
||||
return Crypto.AES.decrypt(data, raw_user.private);
|
||||
},
|
||||
get db_name() {
|
||||
return "floDapps#" + floCrypto.toFloID(user.id);
|
||||
},
|
||||
lock() {
|
||||
user_private = user_priv_wrap;
|
||||
},
|
||||
async unlock() {
|
||||
if (await user.private === raw_user.private)
|
||||
user_private = user_priv_raw;
|
||||
},
|
||||
get_contact(id) {
|
||||
if (!user.contacts)
|
||||
throw "Contacts not available";
|
||||
else if (user.contacts[id])
|
||||
return user.contacts[id];
|
||||
else {
|
||||
let id_raw = floCrypto.decodeAddr(id).hex;
|
||||
for (let i in user.contacts)
|
||||
if (floCrypto.decodeAddr(i).hex == id_raw)
|
||||
return user.contacts[i];
|
||||
}
|
||||
},
|
||||
get_pubKey(id) {
|
||||
if (!user.pubKeys)
|
||||
throw "Contacts not available";
|
||||
else if (user.pubKeys[id])
|
||||
return user.pubKeys[id];
|
||||
else {
|
||||
let id_raw = floCrypto.decodeAddr(id).hex;
|
||||
for (let i in user.pubKeys)
|
||||
if (floCrypto.decodeAddr(i).hex == id_raw)
|
||||
return user.pubKeys[i];
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
user_id = user_public = user_private = undefined;
|
||||
user_priv_raw = aes_key = undefined;
|
||||
delete user.contacts;
|
||||
delete user.pubKeys;
|
||||
delete user.messages;
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperties(window, {
|
||||
myFloID: {
|
||||
get: () => {
|
||||
try {
|
||||
return user.id;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
myUserID: {
|
||||
get: () => {
|
||||
try {
|
||||
return user.id;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
myPubKey: {
|
||||
get: () => {
|
||||
try {
|
||||
return user.public;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
myPrivKey: {
|
||||
get: () => {
|
||||
try {
|
||||
return user.private;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var subAdmins, settings
|
||||
Object.defineProperties(floGlobals, {
|
||||
subAdmins: {
|
||||
get: () => subAdmins
|
||||
},
|
||||
settings: {
|
||||
get: () => settings
|
||||
},
|
||||
contacts: {
|
||||
get: () => user.contacts
|
||||
},
|
||||
pubKeys: {
|
||||
get: () => user.pubKeys
|
||||
},
|
||||
messages: {
|
||||
get: () => user.messages
|
||||
}
|
||||
})
|
||||
|
||||
function initIndexedDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var obs_g = {
|
||||
@ -28,41 +188,41 @@
|
||||
}
|
||||
//add other given objectStores
|
||||
initIndexedDB.appObs = initIndexedDB.appObs || {}
|
||||
for (o in initIndexedDB.appObs)
|
||||
for (let o in initIndexedDB.appObs)
|
||||
if (!(o in obs_a))
|
||||
obs_a[o] = initIndexedDB.appObs[o]
|
||||
Promise.all([
|
||||
compactIDB.initDB(floGlobals.application, obs_a),
|
||||
compactIDB.initDB("floDapps", obs_g)
|
||||
compactIDB.initDB(DEFAULT.application, obs_a),
|
||||
compactIDB.initDB(DEFAULT.root, obs_g)
|
||||
]).then(result => {
|
||||
compactIDB.setDefaultDB(floGlobals.application)
|
||||
compactIDB.setDefaultDB(DEFAULT.application)
|
||||
resolve("IndexedDB App Storage Initated Successfully")
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
function initUserDB(floID) {
|
||||
function initUserDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var obs = {
|
||||
contacts: {},
|
||||
pubKeys: {},
|
||||
messages: {}
|
||||
}
|
||||
compactIDB.initDB(`floDapps#${floID}`, obs).then(result => {
|
||||
compactIDB.initDB(user.db_name, obs).then(result => {
|
||||
resolve("UserDB Initated Successfully")
|
||||
}).catch(error => reject('Init userDB failed'));
|
||||
})
|
||||
}
|
||||
|
||||
function loadUserDB(floID) {
|
||||
function loadUserDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var loadData = ["contacts", "pubKeys", "messages"]
|
||||
var promises = []
|
||||
for (var i = 0; i < loadData.length; i++)
|
||||
promises[i] = compactIDB.readAllData(loadData[i], `floDapps#${floID}`)
|
||||
promises[i] = compactIDB.readAllData(loadData[i], user.db_name)
|
||||
Promise.all(promises).then(results => {
|
||||
for (var i = 0; i < loadData.length; i++)
|
||||
floGlobals[loadData[i]] = results[i]
|
||||
user[loadData[i]] = results[i]
|
||||
resolve("Loaded Data from userDB")
|
||||
}).catch(error => reject('Load userDB failed'))
|
||||
})
|
||||
@ -72,7 +232,7 @@
|
||||
|
||||
startUpFunctions.push(function readSupernodeListFromAPI() {
|
||||
return new Promise((resolve, reject) => {
|
||||
compactIDB.readData("lastTx", floCloudAPI.SNStorageID, "floDapps").then(lastTx => {
|
||||
compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => {
|
||||
floBlockchainAPI.readData(floCloudAPI.SNStorageID, {
|
||||
ignoreOld: lastTx,
|
||||
sentOnly: true,
|
||||
@ -80,14 +240,14 @@
|
||||
}).then(result => {
|
||||
for (var i = result.data.length - 1; i >= 0; i--) {
|
||||
var content = JSON.parse(result.data[i]).SuperNodeStorage;
|
||||
for (sn in content.removeNodes)
|
||||
compactIDB.removeData("supernodes", sn, "floDapps");
|
||||
for (sn in content.newNodes)
|
||||
compactIDB.writeData("supernodes", content.newNodes[sn], sn, "floDapps");
|
||||
for (let sn in content.removeNodes)
|
||||
compactIDB.removeData("supernodes", sn, DEFAULT.root);
|
||||
for (let sn in content.newNodes)
|
||||
compactIDB.writeData("supernodes", content.newNodes[sn], sn, DEFAULT.root);
|
||||
}
|
||||
compactIDB.writeData("lastTx", result.totalTxs, floCloudAPI.SNStorageID, "floDapps");
|
||||
compactIDB.readAllData("supernodes", "floDapps").then(result => {
|
||||
floCloudAPI.init(result)
|
||||
compactIDB.writeData("lastTx", result.totalTxs, floCloudAPI.SNStorageID, DEFAULT.root);
|
||||
compactIDB.readAllData("supernodes", DEFAULT.root).then(nodes => {
|
||||
floCloudAPI.init(nodes)
|
||||
.then(result => resolve("Loaded Supernode list\n" + result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
@ -98,14 +258,14 @@
|
||||
|
||||
startUpFunctions.push(function readAppConfigFromAPI() {
|
||||
return new Promise((resolve, reject) => {
|
||||
compactIDB.readData("lastTx", `${floGlobals.application}|${floGlobals.adminID}`, "floDapps").then(lastTx => {
|
||||
floBlockchainAPI.readData(floGlobals.adminID, {
|
||||
compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => {
|
||||
floBlockchainAPI.readData(DEFAULT.adminID, {
|
||||
ignoreOld: lastTx,
|
||||
sentOnly: true,
|
||||
pattern: floGlobals.application
|
||||
pattern: DEFAULT.application
|
||||
}).then(result => {
|
||||
for (var i = result.data.length - 1; i >= 0; i--) {
|
||||
var content = JSON.parse(result.data[i])[floGlobals.application];
|
||||
var content = JSON.parse(result.data[i])[DEFAULT.application];
|
||||
if (!content || typeof content !== "object")
|
||||
continue;
|
||||
if (Array.isArray(content.removeSubAdmin))
|
||||
@ -118,11 +278,11 @@
|
||||
for (let l in content.settings)
|
||||
compactIDB.writeData("settings", content.settings[l], l)
|
||||
}
|
||||
compactIDB.writeData("lastTx", result.totalTxs, `${floGlobals.application}|${floGlobals.adminID}`, "floDapps");
|
||||
compactIDB.writeData("lastTx", result.totalTxs, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root);
|
||||
compactIDB.readAllData("subAdmins").then(result => {
|
||||
floGlobals.subAdmins = Object.keys(result);
|
||||
subAdmins = Object.keys(result);
|
||||
compactIDB.readAllData("settings").then(result => {
|
||||
floGlobals.settings = result;
|
||||
settings = result;
|
||||
resolve("Read app configuration from blockchain");
|
||||
})
|
||||
})
|
||||
@ -186,7 +346,7 @@
|
||||
});
|
||||
|
||||
const getPrivateKeyCredentials = () => new Promise((resolve, reject) => {
|
||||
var indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
|
||||
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||
if (indexArr) {
|
||||
readSharesFromIDB(JSON.parse(indexArr))
|
||||
.then(result => resolve(result))
|
||||
@ -197,7 +357,7 @@
|
||||
if (!result)
|
||||
return reject("Empty Private Key")
|
||||
var floID = floCrypto.getFloID(result)
|
||||
if (!floID || !floCrypto.validateAddr(floID))
|
||||
if (!floID || !floCrypto.validateFloID(floID))
|
||||
return reject("Invalid Private Key")
|
||||
privKey = result;
|
||||
}).catch(error => {
|
||||
@ -210,7 +370,7 @@
|
||||
var shares = floCrypto.createShamirsSecretShares(privKey, threshold, threshold)
|
||||
writeSharesToIDB(shares).then(resultIndexes => {
|
||||
//store index keys in localStorage
|
||||
localStorage.setItem(`${floGlobals.application}#privKey`, JSON.stringify(resultIndexes))
|
||||
localStorage.setItem(`${DEFAULT.application}#privKey`, JSON.stringify(resultIndexes))
|
||||
//also add a dummy privatekey to the IDB
|
||||
var randomPrivKey = floCrypto.generateNewID().privKey
|
||||
var randomThreshold = floCrypto.randInt(10, 20)
|
||||
@ -242,9 +402,14 @@
|
||||
getPrivateKeyCredentials().then(key => {
|
||||
checkIfPinRequired(key).then(privKey => {
|
||||
try {
|
||||
myPrivKey = privKey
|
||||
myPubKey = floCrypto.getPubKeyHex(myPrivKey)
|
||||
myFloID = floCrypto.getFloID(myPubKey)
|
||||
user_public = floCrypto.getPubKeyHex(privKey);
|
||||
user_id = floCrypto.getAddress(privKey);
|
||||
floCloudAPI.user(user_id, privKey); //Set user for floCloudAPI
|
||||
user_priv_wrap = () => checkIfPinRequired(key);
|
||||
let n = floCrypto.randInt(12, 20);
|
||||
aes_key = floCrypto.randString(n);
|
||||
user_priv_raw = Crypto.AES.encrypt(privKey, aes_key);
|
||||
user_private = user_priv_wrap;
|
||||
resolve('Login Credentials loaded successful')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
@ -305,8 +470,8 @@
|
||||
});
|
||||
let p2 = new Promise((res, rej) => {
|
||||
callAndLog(getCredentials()).then(r => {
|
||||
callAndLog(initUserDB(myFloID)).then(r => {
|
||||
callAndLog(loadUserDB(myFloID))
|
||||
callAndLog(initUserDB()).then(r => {
|
||||
callAndLog(loadUserDB())
|
||||
.then(r => res(true))
|
||||
.catch(e => rej(false))
|
||||
}).catch(e => rej(false))
|
||||
@ -315,7 +480,10 @@
|
||||
Promise.all([p1, p2])
|
||||
.then(r => resolve('App Startup finished successful'))
|
||||
.catch(e => reject('App Startup failed'))
|
||||
}).catch(error => reject("App database initiation failed"))
|
||||
}).catch(error => {
|
||||
startUpLog(false, error);
|
||||
reject("App database initiation failed")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -333,8 +501,8 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject("Invalid floID!")
|
||||
compactIDB.writeData("contacts", name, floID, `floDapps#${myFloID}`).then(result => {
|
||||
floGlobals.contacts[floID] = name;
|
||||
compactIDB.writeData("contacts", name, floID, user.db_name).then(result => {
|
||||
user.contacts[floID] = name;
|
||||
resolve("Contact stored")
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
@ -342,14 +510,14 @@
|
||||
|
||||
floDapps.storePubKey = function(floID, pubKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (floID in floGlobals.pubKeys)
|
||||
if (floID in user.pubKeys)
|
||||
return resolve("pubKey already stored")
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject("Invalid floID!")
|
||||
if (floCrypto.getFloID(pubKey) != floID)
|
||||
if (!floCrypto.verifyPubKey(pubKey, floID))
|
||||
return reject("Incorrect pubKey")
|
||||
compactIDB.writeData("pubKeys", pubKey, floID, `floDapps#${myFloID}`).then(result => {
|
||||
floGlobals.pubKeys[floID] = pubKey;
|
||||
compactIDB.writeData("pubKeys", pubKey, floID, user.db_name).then(result => {
|
||||
user.pubKeys[floID] = pubKey;
|
||||
resolve("pubKey stored")
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
@ -359,11 +527,11 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
let options = {
|
||||
receiverID: floID,
|
||||
application: "floDapps",
|
||||
comment: floGlobals.application
|
||||
application: DEFAULT.root,
|
||||
comment: DEFAULT.application
|
||||
}
|
||||
if (floID in floGlobals.pubKeys)
|
||||
message = floCrypto.encryptData(JSON.stringify(message), floGlobals.pubKeys[floID])
|
||||
if (floID in user.pubKeys)
|
||||
message = floCrypto.encryptData(JSON.stringify(message), user.pubKeys[floID])
|
||||
floCloudAPI.sendApplicationData(message, "Message", options)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
@ -372,20 +540,21 @@
|
||||
|
||||
floDapps.requestInbox = function(callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let lastVC = Object.keys(floGlobals.messages).sort().pop()
|
||||
let lastVC = Object.keys(user.messages).sort().pop()
|
||||
let options = {
|
||||
receiverID: myFloID,
|
||||
application: "floDapps",
|
||||
receiverID: user.id,
|
||||
application: DEFAULT.root,
|
||||
lowerVectorClock: lastVC + 1
|
||||
}
|
||||
let privKey = raw_user.private;
|
||||
options.callback = (d, e) => {
|
||||
for (let v in d) {
|
||||
try {
|
||||
if (d[v].message instanceof Object && "secret" in d[v].message)
|
||||
d[v].message = floCrypto.decryptData(d[v].message, myPrivKey)
|
||||
d[v].message = floCrypto.decryptData(d[v].message, privKey)
|
||||
} catch (error) {}
|
||||
compactIDB.writeData("messages", d[v], v, `floDapps#${myFloID}`)
|
||||
floGlobals.messages[v] = d[v]
|
||||
compactIDB.writeData("messages", d[v], v, user.db_name)
|
||||
user.messages[v] = d[v]
|
||||
}
|
||||
if (callback instanceof Function)
|
||||
callback(d, e)
|
||||
@ -404,14 +573,14 @@
|
||||
if (!addList && !rmList && !settings)
|
||||
return reject("No configuration change")
|
||||
var floData = {
|
||||
[floGlobals.application]: {
|
||||
[DEFAULT.application]: {
|
||||
addSubAdmin: addList,
|
||||
removeSubAdmin: rmList,
|
||||
settings: settings
|
||||
}
|
||||
}
|
||||
var floID = floCrypto.getFloID(adminPrivKey)
|
||||
if (floID != floGlobals.adminID)
|
||||
if (floID != DEFAULT.adminID)
|
||||
reject('Access Denied for Admin privilege')
|
||||
else
|
||||
floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey)
|
||||
@ -422,9 +591,9 @@
|
||||
|
||||
const clearCredentials = floDapps.clearCredentials = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
compactIDB.clearData('credentials', floGlobals.application).then(result => {
|
||||
localStorage.removeItem(`${floGlobals.application}#privKey`)
|
||||
myPrivKey = myPubKey = myFloID = undefined;
|
||||
compactIDB.clearData('credentials', DEFAULT.application).then(result => {
|
||||
localStorage.removeItem(`${DEFAULT.application}#privKey`);
|
||||
user.clear();
|
||||
resolve("privKey credentials deleted!")
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
@ -433,7 +602,7 @@
|
||||
floDapps.deleteUserData = function(credentials = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let p = []
|
||||
p.push(compactIDB.deleteDB(`floDapps#${myFloID}`))
|
||||
p.push(compactIDB.deleteDB(user.db_name))
|
||||
if (credentials)
|
||||
p.push(clearCredentials())
|
||||
Promise.all(p)
|
||||
@ -444,10 +613,10 @@
|
||||
|
||||
floDapps.deleteAppData = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
compactIDB.deleteDB(floGlobals.application).then(result => {
|
||||
localStorage.removeItem(`${floGlobals.application}#privKey`)
|
||||
myPrivKey = myPubKey = myFloID = undefined;
|
||||
compactIDB.removeData('lastTx', `${floGlobals.application}|${floGlobals.adminID}`, 'floDapps')
|
||||
compactIDB.deleteDB(DEFAULT.application).then(result => {
|
||||
localStorage.removeItem(`${DEFAULT.application}#privKey`)
|
||||
user.clear();
|
||||
compactIDB.removeData('lastTx', `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root)
|
||||
.then(result => resolve("App database(local) deleted"))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
@ -455,17 +624,17 @@
|
||||
}
|
||||
|
||||
floDapps.securePrivKey = function(pwd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||
if (!indexArr)
|
||||
return reject("PrivKey not found");
|
||||
indexArr = JSON.parse(indexArr)
|
||||
let encryptedKey = Crypto.AES.encrypt(myPrivKey, pwd);
|
||||
let encryptedKey = Crypto.AES.encrypt(await user.private, pwd);
|
||||
let threshold = indexArr.length;
|
||||
let shares = floCrypto.createShamirsSecretShares(encryptedKey, threshold, threshold)
|
||||
let promises = [];
|
||||
let overwriteFn = (share, index) =>
|
||||
compactIDB.writeData("credentials", share, index, floGlobals.application);
|
||||
compactIDB.writeData("credentials", share, index, DEFAULT.application);
|
||||
for (var i = 0; i < threshold; i++)
|
||||
promises.push(overwriteFn(shares[i], indexArr[i]));
|
||||
Promise.all(promises)
|
||||
@ -494,7 +663,7 @@
|
||||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
var indexArr = localStorage.getItem(`${floGlobals.application}#privKey`)
|
||||
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||
console.info(indexArr)
|
||||
if (!indexArr)
|
||||
reject('No login credentials found')
|
||||
@ -535,7 +704,7 @@
|
||||
filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d]))
|
||||
}
|
||||
if (options.decrypt) {
|
||||
let decryptionKey = (options.decrypt === true) ? myPrivKey : options.decrypt;
|
||||
let decryptionKey = (options.decrypt === true) ? raw_user.private : options.decrypt;
|
||||
if (!Array.isArray(decryptionKey))
|
||||
decryptionKey = [decryptionKey];
|
||||
for (let f in filteredResult) {
|
||||
@ -561,14 +730,14 @@
|
||||
|
||||
syncData.oldDevice = () => new Promise((resolve, reject) => {
|
||||
let sync = {
|
||||
contacts: floGlobals.contacts,
|
||||
pubKeys: floGlobals.pubKeys,
|
||||
messages: floGlobals.messages
|
||||
contacts: user.contacts,
|
||||
pubKeys: user.pubKeys,
|
||||
messages: user.messages
|
||||
}
|
||||
let message = Crypto.AES.encrypt(JSON.stringify(sync), myPrivKey)
|
||||
let message = Crypto.AES.encrypt(JSON.stringify(sync), raw_user.private)
|
||||
let options = {
|
||||
receiverID: myFloID,
|
||||
application: "floDapps"
|
||||
receiverID: user.id,
|
||||
application: DEFAULT.root
|
||||
}
|
||||
floCloudAPI.sendApplicationData(message, "syncData", options)
|
||||
.then(result => resolve(result))
|
||||
@ -577,20 +746,20 @@
|
||||
|
||||
syncData.newDevice = () => new Promise((resolve, reject) => {
|
||||
var options = {
|
||||
receiverID: myFloID,
|
||||
senderID: myFloID,
|
||||
application: "floDapps",
|
||||
receiverID: user.id,
|
||||
senderID: user.id,
|
||||
application: DEFAULT.root,
|
||||
mostRecent: true,
|
||||
}
|
||||
floCloudAPI.requestApplicationData("syncData", options).then(response => {
|
||||
let vc = Object.keys(response).sort().pop()
|
||||
let sync = JSON.parse(Crypto.AES.decrypt(response[vc].message, myPrivKey))
|
||||
let sync = JSON.parse(Crypto.AES.decrypt(response[vc].message, raw_user.private))
|
||||
let promises = []
|
||||
let store = (key, val, obs) => promises.push(compactIDB.writeData(obs, val, key, `floDapps#${floID}`));
|
||||
let store = (key, val, obs) => promises.push(compactIDB.writeData(obs, val, key, user.db_name));
|
||||
["contacts", "pubKeys", "messages"].forEach(c => {
|
||||
for (let i in sync[c]) {
|
||||
store(i, sync[c][i], c)
|
||||
floGlobals[c][i] = sync[c][i]
|
||||
user[c][i] = sync[c][i]
|
||||
}
|
||||
})
|
||||
Promise.all(promises)
|
||||
|
||||
2657
scripts/lib.js
2657
scripts/lib.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,194 +0,0 @@
|
||||
/* settings component */
|
||||
const settingsMenu = document.createElement('template');
|
||||
settingsMenu.innerHTML = `
|
||||
<style>
|
||||
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.settingsContainer{
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: 15rem 5rem;
|
||||
grid-row-gap: 5rem;
|
||||
font-family: Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
|
||||
}
|
||||
.menuContainer{
|
||||
margin-left: -6rem;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.settings{
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.svgClicked{
|
||||
transform: rotate(-25deg);
|
||||
|
||||
}
|
||||
|
||||
.menu {
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: #fff;
|
||||
margin-bottom: 2rem;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.submenu{
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
overflow-y: scroll;
|
||||
max-height: 18rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
::slotted(div) , .submenu div {
|
||||
padding: 1.15rem;
|
||||
width: 14rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.submenu div{
|
||||
width: 16rem;
|
||||
|
||||
}
|
||||
|
||||
::slotted(div:hover) , .submenu div:hover{
|
||||
background: rgba(0,0,0,0.9);
|
||||
}
|
||||
|
||||
</style>
|
||||
<div class="settingsContainer">
|
||||
<div class="menuContainer">
|
||||
<div class="menu">
|
||||
<slot name="menu-options" class="menuSlot"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="40" height="40" viewBox="0 0 512 512" class="settingsIcon">
|
||||
<path d="M491.755 232.653c0-5.95-4.935-10.844-10.865-10.844h-19.876c-6 0-12.318-4.649-13.978-10.383l-27.065-63.191c-2.877-5.161-1.935-12.871 2.284-17.111l13.456-13.435c4.219-4.219 4.219-11.141 0-15.35l-32.379-32.389c-4.199-4.249-11.1-4.249-15.462 0l-14.541 14.633c-4.26 4.24-12.063 5.406-17.316 2.632l-54.978-21.954c-5.745-1.628-10.455-7.885-10.455-13.834v-20.378c0-5.97-4.885-10.823-10.803-10.823h-45.895c-5.939 0-10.823 4.854-10.823 10.823v20.378c0 5.95-4.71 12.247-10.363 14.019l-63.764 27.279c-5.11 3.010-12.728 2.007-16.988-2.212l-14.295-14.295c-4.198-4.188-11.090-4.188-15.329 0l-32.409 32.43c-4.281 4.229-4.281 11.151 0 15.36l15.544 15.626c4.29 4.198 5.458 11.919 2.704 17.244l-21.657 54.477c-1.597 5.796-7.844 10.455-13.824 10.455h-21.648c-5.919 0-10.793 4.894-10.793 10.844v45.855c0 5.98 4.874 10.885 10.793 10.885h21.637c5.98 0 12.237 4.618 13.998 10.363l26.583 62.7c3.062 5.161 2.017 12.82-2.109 17.060l-14.919 14.817c-4.229 4.24-4.229 11.079 0 15.36l32.43 32.399c4.249 4.199 11.162 4.199 15.299 0l15.933-15.862c4.25-4.219 11.879-5.376 17.203-2.519l55.624 22.18c5.653 1.639 10.363 7.885 10.363 13.824v21.218c0 5.959 4.885 10.844 10.823 10.844h45.895c5.918 0 10.803-4.885 10.803-10.844v-21.217c0-5.939 4.659-12.175 10.455-13.937l62.178-26.378c5.161-2.898 12.738-1.884 17.039 2.304l14.019 14.080c4.219 4.199 11.1 4.199 15.36 0l32.42-32.44c4.24-4.199 4.24-11.12 0-15.339l-14.981-14.899c-4.116-4.24-5.284-11.961-2.498-17.244l22.538-56.064c1.577-5.776 7.895-10.404 13.896-10.445h19.876c5.918 0 10.865-4.905 10.865-10.844v-45.855zM256.031 333.097c-42.649 0-77.097-34.539-77.097-77.107 0-42.578 34.447-77.056 77.097-77.056 42.526 0 77.087 34.478 77.087 77.056 0 42.567-34.56 77.107-77.087 77.107z" fill="#000000" />
|
||||
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
|
||||
customElements.define('settings-menu', class extends HTMLElement {
|
||||
constructor() {
|
||||
super()
|
||||
this.attachShadow({
|
||||
mode: 'open'
|
||||
}).append(settingsMenu.content.cloneNode(true))
|
||||
|
||||
this.settingsContainer = this.shadowRoot.querySelector('.settingsContainer');
|
||||
this.settingsIcon = this.shadowRoot.querySelector('.settingsIcon'); //svg icon
|
||||
this.menu = this.shadowRoot.querySelector('.menu'); // for the main menu
|
||||
this.menuContainer = this.shadowRoot.querySelector('.menuContainer');
|
||||
this.menuSlot = this.shadowRoot.querySelector('.menuSlot');
|
||||
this.closeMenu; // for close icons
|
||||
this.count = 0; // a counter
|
||||
this.menuIndex;
|
||||
}
|
||||
|
||||
|
||||
|
||||
connectedCallback() {
|
||||
|
||||
// to rotate the svg icon and display the main menu
|
||||
this.settingsIcon.addEventListener('click', e => {
|
||||
|
||||
if(this.menuIndex){
|
||||
this.shadowRoot.querySelector('.submenu'+(this.menuIndex)).style.visibility = "hidden";
|
||||
}
|
||||
|
||||
|
||||
if(this.count%2==0){
|
||||
|
||||
this.settingsIcon.classList.add('svgClicked');
|
||||
this.menu.style.visibility = "visible";
|
||||
|
||||
}else{
|
||||
|
||||
this.settingsIcon.classList.remove('svgClicked');
|
||||
this.menu.style.visibility = "hidden";
|
||||
/* I know instead of this I should have added and removed a class but it was not working */
|
||||
|
||||
}
|
||||
|
||||
this.count++;
|
||||
|
||||
});
|
||||
|
||||
// creating slots for sub menu
|
||||
const frag = document.createDocumentFragment();
|
||||
|
||||
this.menuSlot.assignedElements().forEach( (menuOption , index) =>{
|
||||
|
||||
let submenu = document.createElement('div');
|
||||
let submenuSlot = document.createElement('slot');
|
||||
let submenuTitle = document.createElement('div');
|
||||
|
||||
// adding a heading and a close icon to a sub menu
|
||||
submenuTitle.innerHTML = "<span class='closeMenu' index=" + (index+1) + ">❮</span> " + menuOption.innerHTML;
|
||||
|
||||
menuOption.setAttribute("index", (index+1));
|
||||
submenuSlot.setAttribute('name', 'menu' + (index+1));
|
||||
submenu.classList.add('submenu');
|
||||
submenu.classList.add('submenu' + (index+1));
|
||||
|
||||
submenu.append(submenuTitle);
|
||||
submenu.append(submenuSlot);
|
||||
frag.append(submenu);
|
||||
|
||||
});
|
||||
|
||||
|
||||
this.menuContainer.append(frag);
|
||||
this.closeMenu = this.shadowRoot.querySelectorAll('.closeMenu'); // get a list of closeIcons
|
||||
|
||||
// adding click event to main menu to open respective sub menu
|
||||
|
||||
this.menu.addEventListener( 'click', e=> {
|
||||
|
||||
let menuOption = e.target.closest('div');
|
||||
|
||||
this.menuIndex = menuOption.getAttribute('index');
|
||||
|
||||
let submenu = this.shadowRoot.querySelector('.submenu'+(this.menuIndex));
|
||||
|
||||
submenu.style.visibility="visible";
|
||||
this.menu.style.visibility = "hidden";
|
||||
|
||||
});
|
||||
|
||||
// adding click event to close Icons and adding code to hide the respective sub menu
|
||||
|
||||
this.closeMenu.forEach((closeButton) =>{
|
||||
closeButton.addEventListener('click', e=>{
|
||||
let submenuIndex = closeButton.getAttribute('index');
|
||||
|
||||
this.shadowRoot.querySelector('.submenu'+(submenuIndex)).style.visibility = "hidden";
|
||||
|
||||
this.menu.style.visibility = "visible";
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user