Workflow updating files of messenger
21
messenger/LICENCE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Sai Raj
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
80
messenger/README.md
Normal file
@ -0,0 +1,80 @@
|
||||
# FLO Messenger
|
||||
|
||||
• Messenger is a blockchain-based decentralized messaging app that uses Bitcoin or FLO blockchain addresses as user identities. Instead of a centralized server, messages are encrypted and stored in the users' browsers.
|
||||
• Bitcoin or FLO blockchain addresses can communicate with each other using a messaging interface
|
||||
• Messenger comes with "Multisig" where users can create multi-sig addresses and using a messaging interface make transactions on the multi-sig.
|
||||
• Switching browsers or devices won't bring back old messages. Remember to back up and import to access your messages in the new browser/device. That's the security of Messenger.
|
||||
|
||||
#### Note:
|
||||
Do not lose the private key. Copy and save it securely. Once a private key is lost, it cannot be recovered
|
||||
|
||||
### Live URL for FLO Messenger:
|
||||
*https://ranchimall.github.io/messenger/*
|
||||
|
||||
## Messenger Architecture
|
||||
### Product Pipelines
|
||||
1. The core feature of the product is pipelines. A pipeline is created by invloking inbuilt models
|
||||
2. Right now we have models for Multisig creation for Bitcoin and FLO Multisigs.
|
||||
3. What is pipeline ?
|
||||
• It has an ID
|
||||
• It has model like TYPE_BTC_MULTISIG
|
||||
• It has members like different Bitcoin IDs or FLO IDs
|
||||
• It has an encryption key unique to the pipeline, and known just to members of that pipeline
|
||||
4. A pipeline sends custom messages defined as per a model to an attached group
|
||||
5. Pipeline ID could be a recipient of a message. Then every Bitcoin or FLO Address will get the message with the action needed for that pipeline
|
||||
6. Details of the technical functions are available here- [Functions](docs/functions.md)
|
||||
|
||||
## How to use Messenger
|
||||
### General messaging
|
||||
1. Go to the homepage of Messenger
|
||||
2. Sign in using a Bitcoin or FLO blockchain private key
|
||||
3. In case you don't have the private key, generate using
|
||||
FLO Wallet (for FLO address and private key): https://ranchimall.github.io/flowallet/
|
||||
BTC Wallet (for Bitcoin address and private key): https://ranchimall.github.io/btcwallet/
|
||||
** Note: FLO address or FLO ID and private key can be created from Messenger's homepage as well
|
||||
4. To start a new message or chat, click on the "New chat" button
|
||||
5. Add a FLO ID or a Bitcoin address as a contact
|
||||
6. Select the contact to start messaging
|
||||
** Note: Until the receiver replies, the message is not encrypted.
|
||||
|
||||
### Mail
|
||||
1. Mail is similar to Messaging except the user can send messages to multiple FLO IDs or Bitcoin addresses at the same time
|
||||
2. Go to "Mail" and enter the recipient's FLO or Bitcoin address
|
||||
3. Separate multiple addresses with a comma
|
||||
4. Type a mail and send
|
||||
|
||||
### Multisig messaging
|
||||
1. Go to "Multisig" on the homepage
|
||||
2. To create a Bitcoin multisig, click on "BTC"
|
||||
3. To create a FLO multisig, click on "FLO"
|
||||
4. To add BTC or FLO addresses in the new multisig, select contacts that are to be added
|
||||
5. Contacts have to be saved in advance before creating a multisig address
|
||||
6. After selecting the contacts, click "Next" and give the multisig address a label name
|
||||
7. Select the minimum number of signatures required for the multisig
|
||||
8. Click "Create" and the multisig address will be created
|
||||
|
||||
### Sending a multisig transaction
|
||||
1. The user must have some balance in the multisig address
|
||||
2. Go to "Multisig" and click on "init transaction"
|
||||
3. Enter the receiver's BTC address for a Bitcoin multisig or FLO address for a FLO multisig
|
||||
4. Enter the amount to be transferred
|
||||
5. Multiple addresses can be added as receivers with different amounts for each address
|
||||
6. Click on "Initiate" to initiate the transaction from the multisig address
|
||||
7. Associated multisig owners will be notified of this transaction
|
||||
8. Once the required number of signatures is approved, the transaction will take place from the multisig address
|
||||
|
||||
### Requests
|
||||
1. Multisig owners will get a notification under the "Request" tab for multisig transaction approvals
|
||||
2. They can approve or deny a multisig transaction request
|
||||
|
||||
# Messenger Documentation
|
||||
|
||||
- [Product Overview](docs/product-overview.md)
|
||||
- [Features](docs/features.md)
|
||||
- [Usage](docs/usage.md)
|
||||
- [Functions](docs/functions.md)
|
||||
- [Technical Architecture](docs/technical-architecture.md)
|
||||
- [Additional Resources](docs/additional-resources.md)
|
||||
- [Changelog](docs/changelog.md)
|
||||
|
||||
|
||||
5
messenger/assets/add contact illustration.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="299" height="299" viewBox="0 0 299 299" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="149.5" cy="149.5" r="149" stroke="black"/>
|
||||
<path d="M105 149.31H194.605" stroke="black" stroke-width="3"/>
|
||||
<path d="M149.359 105L149.359 194.605" stroke="black" stroke-width="3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 299 B |
3
messenger/assets/corner.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="29" height="27" viewBox="0 0 29 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.5 26.5V0.5C25.1667 10 11.3 22.9 0.5 26.5" stroke="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 175 B |
2038
messenger/assets/illustrations-2.ai
Normal file
1958
messenger/assets/illustrations.ai
Normal file
24
messenger/assets/lock.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<svg width="184" height="280" 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>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
1
messenger/assets/mascot.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" 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:12px;}.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>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
BIN
messenger/assets/message-background.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
1
messenger/assets/message-background.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
messenger/assets/message-background@2x.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
messenger/assets/message-background@4x.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
messenger/assets/messaging@4x.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
messenger/assets/messenger-favicon_1.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
146
messenger/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 |
8
messenger/assets/new conversation.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg 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" ry="84.7"/>
|
||||
<path d="M423.3,261.78a80.7,80.7,0,1,1-80.71,80.7,80.79,80.79,0,0,1,80.71-80.7m0-8a88.7,88.7,0,1,0,88.7,88.7,88.71,88.71,0,0,0-88.7-88.7Z"/>
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 477 B |
4
messenger/assets/no-mails.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg 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>
|
||||
|
After Width: | Height: | Size: 270 B |
2655
messenger/css/main.css
Normal file
1
messenger/css/main.min.css
vendored
Normal file
2773
messenger/css/main.scss
Normal file
6
messenger/docs/additional-resources.md
Normal file
@ -0,0 +1,6 @@
|
||||
## Additional Resources
|
||||
|
||||
### Please refer the following for explanation of non messenger files
|
||||
- RanchiMall Standard Operations
|
||||
- RanchiMall Standard UI
|
||||
- RanchiMall Single User BTC Wallet
|
||||
2
messenger/docs/changelog.md
Normal file
@ -0,0 +1,2 @@
|
||||
### Important Changes
|
||||
- Added Bitcoin Transaction fee increase update capability for multisig if the transaction is not getting confirmed.
|
||||
400
messenger/docs/compactIDB.md
Normal file
@ -0,0 +1,400 @@
|
||||
## upgradeDB Function
|
||||
|
||||
The `upgradeDB` function is used to upgrade an IndexedDB database to a new version. It allows creating and deleting object stores and indexes during the upgrade process.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **dbName**: The name of the IndexedDB database to be upgraded.
|
||||
- **createList**: An object specifying the object stores to create and their options. Each key represents the object store name, and the corresponding value is an object with optional `options` for the object store and `indexes` for creating indexes.
|
||||
- **deleteList**: An array containing the names of object stores to be deleted during the upgrade.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Getting Current Version:** Retrieves the current version of the IndexedDB database using the `getDBversion` function.
|
||||
|
||||
2. **Opening a New Database:** Opens a new version of the IndexedDB database (version + 1).
|
||||
|
||||
3. **Upgrade Logic:** Handles the upgrade process inside the `onupgradeneeded` event. It creates new object stores and indexes based on the provided `createList` and deletes specified object stores from `deleteList`.
|
||||
|
||||
4. **Success and Error Handling:** Resolves with a success message when the upgrade is completed successfully. Rejects with an error message if there's an error in opening the IndexedDB.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message when the database upgrade is completed successfully.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the upgradeDB function is used to upgrade the 'exampleDB' IndexedDB. It creates new object stores ('users' and 'posts') with specified options and indexes, and it deletes the 'oldStore' object store.
|
||||
|
||||
```javascript
|
||||
const dbName = 'exampleDB';
|
||||
const createList = {
|
||||
'users': {
|
||||
options: { keyPath: 'id', autoIncrement: true },
|
||||
indexes: { 'username': 'username' }
|
||||
},
|
||||
'posts': {
|
||||
options: { keyPath: 'postId', autoIncrement: true },
|
||||
indexes: { 'author': 'authorId' }
|
||||
}
|
||||
};
|
||||
const deleteList = ['oldStore'];
|
||||
|
||||
upgradeDB(dbName, createList, deleteList)
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
// Output: Database upgraded
|
||||
```
|
||||
|
||||
## compactIDB.initDB Function
|
||||
|
||||
The `compactIDB.initDB` function is used to initialize an IndexedDB database with specified object stores. It checks the existing object stores in the database and creates new ones and deletes unwanted ones based on the provided `objectStores` parameter.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **dbName**: The name of the IndexedDB database to be initialized.
|
||||
- **objectStores**: An object specifying the object stores to be created. Each key represents the object store name, and the corresponding value is an object with optional `options` for the object store.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Opening Existing Database:** Opens the existing IndexedDB database specified by `dbName`.
|
||||
|
||||
2. **Object Store Handling:** Compares the existing object stores with the specified `objectStores`. It creates new object stores that are not present in the database and deletes object stores that are not specified in `objectStores`.
|
||||
|
||||
3. **Upgrade Process:** Uses the `upgradeDB` function to handle the upgrade process, creating new object stores and deleting unwanted object stores.
|
||||
|
||||
4. **Success and Error Handling:** Resolves with a success message when the database initialization is completed successfully. Rejects with an error message if there's an error in opening the IndexedDB.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message when the database initialization is completed successfully.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the compactIDB.initDB function is used to initialize the 'exampleDB' IndexedDB. It creates new object stores ('users' and 'posts') with specified options and indexes.
|
||||
|
||||
```javascript
|
||||
const dbName = 'exampleDB';
|
||||
const objectStores = {
|
||||
'users': {
|
||||
options: { keyPath: 'id', autoIncrement: true },
|
||||
indexes: { 'username': 'username' }
|
||||
},
|
||||
'posts': {
|
||||
options: { keyPath: 'postId', autoIncrement: true },
|
||||
indexes: { 'author': 'authorId' }
|
||||
}
|
||||
};
|
||||
|
||||
compactIDB.initDB(dbName, objectStores)
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
// Output: Initiated IndexedDB
|
||||
```
|
||||
|
||||
## compactIDB.openDB Function
|
||||
|
||||
The `compactIDB.openDB` function is used to open an IndexedDB database for performing database operations.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **dbName** (Optional): The name of the IndexedDB database to open. If not provided, the default database specified during initialization is used.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Opening Database:** Attempts to open the specified IndexedDB database (`dbName`).
|
||||
|
||||
2. **Error Handling:** If there's an error in opening the database, the function rejects the promise with an error message indicating the failure.
|
||||
|
||||
3. **Upgradeneeded Event:** If an "upgradeneeded" event is triggered during the database opening process, the function closes the existing database, deletes the database using `deleteDB` function, and rejects the promise with a message indicating that the database was not found.
|
||||
|
||||
4. **Success Event:** If the database is successfully opened, the function resolves with the opened database object for performing further database operations.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the opened IndexedDB database object or rejects with an error message if there's an issue opening the database.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const dbName = 'myDatabase';
|
||||
|
||||
compactIDB.openDB(dbName)
|
||||
.then(database => {
|
||||
// Perform database operations using the 'database' object.
|
||||
console.log(`Successfully opened database: ${dbName}`);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Error opening database: ${error}`);
|
||||
});
|
||||
// Output: Successfully opened database: myDatabase
|
||||
|
||||
// Opens the 'myDatabase' IndexedDB database and performs operations inside the 'then' block.
|
||||
```
|
||||
|
||||
## compactIDB.deleteDB Function
|
||||
|
||||
The `compactIDB.deleteDB` function is used to delete an existing IndexedDB database.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **dbName** (Optional): The name of the IndexedDB database to be deleted. If not provided, the default database specified during initialization is used.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Deleting Database:** Initiates a request to delete the specified IndexedDB database (`dbName`).
|
||||
|
||||
2. **Error Handling:** If there's an error during the deletion process, the function rejects the promise with an error message indicating the failure.
|
||||
|
||||
3. **Success Event:** If the database is successfully deleted, the function resolves with a success message indicating that the database was deleted successfully.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message if the database is deleted successfully or rejects with an error message if there's an issue deleting the database.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const dbName = 'myDatabase';
|
||||
|
||||
compactIDB.deleteDB(dbName)
|
||||
.then(message => {
|
||||
console.log(message); // Output: Database deleted successfully
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Error deleting database: ${error}`);
|
||||
});
|
||||
// Output: Database deleted successfully
|
||||
|
||||
// Deletes the 'myDatabase' IndexedDB database and logs a success message if the deletion is successful.
|
||||
```
|
||||
|
||||
|
||||
## compactIDB.writeData Function
|
||||
|
||||
The `compactIDB.writeData` function is used to write data to a specified object store in the IndexedDB. It allows adding or updating data records in the specified object store.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **obsName**: The name of the object store where the data will be written.
|
||||
- **data**: The data to be written to the object store.
|
||||
- **key** (Optional): The key to identify the data record. If provided, the function updates the existing record with the specified key. If not provided, a new record is added to the object store.
|
||||
- **dbName** (Optional): The name of the IndexedDB database where the object store is located. If not provided, the default database specified during initialization is used.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Opening Database:** Opens the specified IndexedDB database (`dbName`).
|
||||
|
||||
2. **Transaction and Object Store:** Starts a read-write transaction on the specified object store (`obsName`).
|
||||
|
||||
3. **Writing Data:** Writes the provided `data` to the object store. If a `key` is provided, it updates the existing record; otherwise, it adds a new record.
|
||||
|
||||
4. **Success and Error Handling:** Resolves with a success message when the data writing is successful. Rejects with an error message if there's an error during the write operation.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message when the data writing is completed successfully.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const obsName = 'users';
|
||||
const data = { id: 1, name: 'John Doe', email: 'john@example.com' };
|
||||
|
||||
compactIDB.writeData(obsName, data, 1)
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
// Output: Write data Successful
|
||||
|
||||
// Updating an existing record with key 1 in the 'users' object store.
|
||||
```
|
||||
|
||||
## compactIDB.addData Function
|
||||
|
||||
The `compactIDB.addData` function is used to add new data records to a specified object store in the IndexedDB. It allows adding data records with unique keys, ensuring no duplicate records with the same key are added.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **obsName**: The name of the object store where the data will be added.
|
||||
- **data**: The data to be added to the object store.
|
||||
- **key** (Optional): The key to identify the new data record. If provided and a record with the same key already exists, the function fails, ensuring the uniqueness of the key. If not provided, the function generates a unique key for the new record.
|
||||
- **dbName** (Optional): The name of the IndexedDB database where the object store is located. If not provided, the default database specified during initialization is used.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Opening Database:** Opens the specified IndexedDB database (`dbName`).
|
||||
|
||||
2. **Transaction and Object Store:** Starts a read-write transaction on the specified object store (`obsName`).
|
||||
|
||||
3. **Adding Data:** Adds the provided `data` to the object store. If a `key` is provided and a record with the same key already exists, the add operation fails. If no `key` is provided, the function generates a unique key for the new record.
|
||||
|
||||
4. **Success and Error Handling:** Resolves with a success message when the data addition is successful. Rejects with an error message if there's an error during the add operation, such as attempting to add a duplicate record with an existing key.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message when the data addition is completed successfully.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const obsName = 'users';
|
||||
const data = { id: 2, name: 'Alice Smith', email: 'alice@example.com' };
|
||||
|
||||
compactIDB.addData(obsName, data, 2)
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
// Output: Add data successful
|
||||
|
||||
// Adding a new record with key 2 to the 'users' object store.
|
||||
```
|
||||
|
||||
## compactIDB.removeData Function
|
||||
|
||||
The `compactIDB.removeData` function is used to remove a data record from a specified object store in the IndexedDB based on its key.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **obsName**: The name of the object store from which the data record will be removed.
|
||||
- **key**: The key of the data record that needs to be removed from the object store.
|
||||
- **dbName** (Optional): The name of the IndexedDB database where the object store is located. If not provided, the default database specified during initialization is used.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Opening Database:** Opens the specified IndexedDB database (`dbName`).
|
||||
|
||||
2. **Transaction and Object Store:** Starts a read-write transaction on the specified object store (`obsName`).
|
||||
|
||||
3. **Removing Data:** Removes the data record with the provided `key` from the object store.
|
||||
|
||||
4. **Success and Error Handling:** Resolves with a success message, including the removed key, when the removal is successful. Rejects with an error message if there's an error during the delete operation, such as attempting to remove a non-existent record.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message, including the removed key, when the removal is completed successfully.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the compactIDB.removeData function is used to remove the data record with key 2 from the 'users' object store in the IndexedDB.
|
||||
|
||||
```javascript
|
||||
const obsName = 'users';
|
||||
const key = 2;
|
||||
|
||||
compactIDB.removeData(obsName, key)
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
// Output: Removed Data 2
|
||||
|
||||
// Removes the data record with key 2 from the 'users' object store.
|
||||
```
|
||||
|
||||
## compactIDB.clearData Function
|
||||
|
||||
The `compactIDB.clearData` function is used to clear all data records from a specified object store in the IndexedDB.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **obsName**: The name of the object store from which all data records will be cleared.
|
||||
- **dbName** (Optional): The name of the IndexedDB database where the object store is located. If not provided, the default database specified during initialization is used.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Opening Database:** Opens the specified IndexedDB database (`dbName`).
|
||||
|
||||
2. **Transaction and Object Store:** Starts a read-write transaction on the specified object store (`obsName`).
|
||||
|
||||
3. **Clearing Data:** Removes all data records from the object store, effectively clearing it.
|
||||
|
||||
4. **Success and Error Handling:** Resolves with a success message when the clear operation is successful. Rejects with an error message if there's an error during the clear operation.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message when the clear operation is completed successfully.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const obsName = 'users';
|
||||
|
||||
compactIDB.clearData(obsName)
|
||||
.then(result => console.log(result))
|
||||
.catch(error => console.error(error));
|
||||
// Output: Clear data Successful
|
||||
|
||||
// Clears all data records from the 'users' object store.
|
||||
```
|
||||
|
||||
## compactIDB.readData Function
|
||||
|
||||
The `compactIDB.readData` function is used to read a specific data record from an object store in the IndexedDB.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **obsName**: The name of the object store from which the data record will be read.
|
||||
- **key**: The key of the specific data record to be retrieved from the object store.
|
||||
- **dbName** (Optional): The name of the IndexedDB database where the object store is located. If not provided, the default database specified during initialization is used.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Opening Database:** Opens the specified IndexedDB database (`dbName`).
|
||||
|
||||
2. **Transaction and Object Store:** Starts a read-only transaction on the specified object store (`obsName`).
|
||||
|
||||
3. **Reading Data:** Retrieves the data record corresponding to the provided key from the object store.
|
||||
|
||||
4. **Success and Error Handling:** Resolves with the retrieved data record when the read operation is successful. Rejects with an error message if there's an error during the read operation.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the retrieved data record when the read operation is completed successfully.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const obsName = 'users';
|
||||
const userID = 123;
|
||||
|
||||
compactIDB.readData(obsName, userID)
|
||||
.then(data => console.log(data))
|
||||
.catch(error => console.error(error));
|
||||
// Output: { id: 123, name: 'John Doe', ... }
|
||||
|
||||
// Retrieves the data record with the key 123 from the 'users' object store.
|
||||
```
|
||||
|
||||
## compactIDB.readAllData Function
|
||||
|
||||
The `compactIDB.readAllData` function is used to retrieve all data records from an object store in the IndexedDB.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **obsName**: The name of the object store from which all data records will be retrieved.
|
||||
- **dbName** (Optional): The name of the IndexedDB database where the object store is located. If not provided, the default database specified during initialization is used.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Opening Database:** Opens the specified IndexedDB database (`dbName`).
|
||||
|
||||
2. **Transaction and Object Store:** Starts a read-only transaction on the specified object store (`obsName`).
|
||||
|
||||
3. **Reading All Data:** Iterates over the object store using a cursor and collects all data records into a temporary result object.
|
||||
|
||||
4. **Success and Error Handling:** Resolves with the temporary result object containing all data records when the read operation is successful. Rejects with an error message if there's an error during the read operation.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with an object containing all retrieved data records when the read operation is completed successfully.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const obsName = 'users';
|
||||
|
||||
compactIDB.readAllData(obsName)
|
||||
.then(data => console.log(data))
|
||||
.catch(error => console.error(error));
|
||||
// Output: { 123: { id: 123, name: 'John Doe', ... }, 124: { id: 124, name: 'Jane Smith', ... }, ... }
|
||||
|
||||
// Retrieves all data records from the 'users' object store and logs the result.
|
||||
```
|
||||
|
||||
7
messenger/docs/features.md
Normal file
@ -0,0 +1,7 @@
|
||||
## Features
|
||||
- Ability to send messages to Bitcoin addresses
|
||||
- Ability to co-ordinate Bitcoin Multisig Creation
|
||||
- Ability to create messaging group of Bitcoin IDs
|
||||
- Ability to add and remove Bitcoin ID as contact
|
||||
- Ability to send direct messages, and mails
|
||||
- Attachments are not supported yet
|
||||
1204
messenger/docs/floBlockchainAPI.md
Normal file
968
messenger/docs/floCloud.md
Normal file
@ -0,0 +1,968 @@
|
||||
## ws_connect Function
|
||||
|
||||
The `ws_connect` function is used to establish a WebSocket connection to a specified supernode (`snID`). It performs checks to ensure that the supernode is in the list of available supernodes and is active before attempting to establish a connection.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `snID` (string): The ID of the supernode to which the WebSocket connection will be established.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the WebSocket connection object if the connection is successfully established, and rejects with an error message if the specified supernode is not in the list of available supernodes or is inactive.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the ws_connect function establishes a WebSocket connection to the specified supernode (snID). It checks if the supernode is available and active before attempting to establish the connection. Once the connection is established, the function resolves with the WebSocket connection object, allowing communication with the server in real-time.
|
||||
|
||||
```javascript
|
||||
const snID = "exampleSnID";
|
||||
|
||||
ws_connect(snID)
|
||||
.then(wsConnection => {
|
||||
console.log("WebSocket connection established:", wsConnection);
|
||||
// You can now use wsConnection to send and receive messages.
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error occurred during WebSocket connection:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## ws_activeConnect Function
|
||||
|
||||
The `ws_activeConnect` function is used to establish an active WebSocket connection to a supernode, handling failover by selecting the next or previous active supernode from the routing table (`kBucket`).
|
||||
|
||||
### Parameters
|
||||
|
||||
- `snID` (string): The ID of the supernode to which the WebSocket connection will be established. If the provided supernode is inactive, the function selects the next or previous active supernode based on the `reverse` parameter.
|
||||
- `reverse` (boolean, optional): A flag indicating whether to select the previous active supernode (`true`) or the next active supernode (`false`). Default is `false`.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the active WebSocket connection object if the connection is successfully established, and rejects with an error message if all supernodes are inactive or if all attempts to establish an active connection fail.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the ws_activeConnect function establishes an active WebSocket connection to the specified supernode (snID). If the specified supernode is inactive, it selects the next or previous active supernode based on the reverse parameter. The function handles failover by attempting to connect to the active supernodes in the routing table until a successful connection is established, allowing real-time communication with the server.
|
||||
|
||||
```javascript
|
||||
const snID = "exampleSnID";
|
||||
|
||||
ws_activeConnect(snID)
|
||||
.then(wsConnection => {
|
||||
console.log("Active WebSocket connection established:", wsConnection);
|
||||
// You can now use wsConnection to send and receive messages in real-time.
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error occurred during active WebSocket connection:", error);
|
||||
});
|
||||
|
||||
ws_activeConnect(snID, true) // To establish a connection to the previous active supernode
|
||||
.then(wsConnection => {
|
||||
console.log("Active WebSocket connection established:", wsConnection);
|
||||
// You can now use wsConnection to send and receive messages in real-time.
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error occurred during active WebSocket connection:", error);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## fetch_API Function
|
||||
|
||||
The `fetch_API` function is used to send an HTTP request to the specified supernode (`snID`). It allows making GET and POST requests and handles response validation.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `snID` (string): The ID of the supernode to which the request will be sent.
|
||||
- `data` (string or object): The data to be sent with the request. If it is a string, it is appended to the URL as query parameters for GET requests. If it is an object with `method` property set to `"POST"`, it is sent as the body for a POST request.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response object if the request is successful (HTTP status code 200 or 400 or 500), and rejects with an error message if the request fails.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the fetch_API function sends an HTTP request to the specified supernode (snID). It handles both GET and POST requests and provides flexibility in sending data with the request. The function validates the response and resolves or rejects the Promise based on the response status code.
|
||||
|
||||
```javascript
|
||||
const snID = "exampleSnID";
|
||||
const queryParams = "param1=value1¶m2=value2";
|
||||
|
||||
fetch_API(snID, queryParams)
|
||||
.then(response => {
|
||||
console.log("GET request successful. Response:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error occurred during the GET request:", error);
|
||||
});
|
||||
|
||||
const postData = {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ key: "value" })
|
||||
};
|
||||
|
||||
fetch_API(snID, postData)
|
||||
.then(response => {
|
||||
console.log("POST request successful. Response:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error occurred during the POST request:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## fetch_ActiveAPI Function
|
||||
|
||||
The `fetch_ActiveAPI` function is used to send an HTTP request to an active supernode, handling failover by selecting the next or previous active supernode from the routing table (`kBucket`).
|
||||
|
||||
### Parameters
|
||||
|
||||
- `snID` (string): The ID of the supernode to which the request will be sent. If the provided supernode is inactive, the function selects the next or previous active supernode based on the `reverse` parameter.
|
||||
- `data` (string or object): The data to be sent with the request.
|
||||
- `reverse` (boolean, optional): A flag indicating whether to select the previous active supernode (`true`) or the next active supernode (`false`). Default is `false`.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response object if the request is successful, and rejects with an error message if all supernodes are inactive or if all attempts to fetch from active supernodes fail.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the fetch_ActiveAPI function sends an HTTP request to an active supernode (snID). If the specified supernode is inactive, it selects the next or previous active supernode based on the reverse parameter. The function handles failover by attempting to fetch from the active supernodes in the routing table until a successful response is received.
|
||||
|
||||
```javascript
|
||||
const snID = "exampleSnID";
|
||||
const queryParams = "param1=value1¶m2=value2";
|
||||
|
||||
fetch_ActiveAPI(snID, queryParams)
|
||||
.then(response => {
|
||||
console.log("Request successful. Response:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error occurred during the request:", error);
|
||||
});
|
||||
|
||||
const postData = {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ key: "value" })
|
||||
};
|
||||
|
||||
fetch_ActiveAPI(snID, postData, true)
|
||||
.then(response => {
|
||||
console.log("Request successful. Response:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error occurred during the request:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## singleRequest Function
|
||||
|
||||
The `singleRequest` function is used to send a single HTTP request to the specified flo server (`floID`). It allows customization of the request method and data payload.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `floID` (string): The ID of the flo server to which the request will be sent.
|
||||
- `data_obj` (object): The data object to be sent with the request. It will be serialized to JSON for POST requests and URL parameters for other request methods.
|
||||
- `method` (string, optional): The HTTP request method. Default is `"POST"`. Can be `"POST"` or any other valid HTTP method like `"GET"`, `"PUT"`, `"DELETE"`, etc.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the JSON response body if the request is successful (HTTP status code 200), and rejects with an error message if the request fails.
|
||||
|
||||
### Example Usage
|
||||
- In this example, the singleRequest function sends a single HTTP request to the cloud server. The request method and data payload are customizable
|
||||
|
||||
```javascript
|
||||
const floID = "exampleFloID";
|
||||
const dataObj = {
|
||||
key: "value",
|
||||
anotherKey: "anotherValue"
|
||||
};
|
||||
|
||||
singleRequest(floID, dataObj, "POST")
|
||||
.then(response => {
|
||||
console.log("POST request successful. Response:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error occurred during the request:", error);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## liveRequest Function
|
||||
|
||||
The `liveRequest` function is used to make a live request to FLO cloud node using WebSockets. It takes the following parameters:
|
||||
|
||||
### Parameters
|
||||
|
||||
- `floID` (string): The ID of the flo server.
|
||||
- `request` (object): An object containing filter criteria for the live request. It can have the following properties:
|
||||
- `status` (boolean, optional): If true, includes all data in the response. If false, filters the response based on other criteria.
|
||||
- `trackList` (array of strings, optional): An array of keys to include in the response when `status` is false.
|
||||
- `atVectorClock` (string, optional): Filters data with vector clock equal to the specified value.
|
||||
- `lowerVectorClock` (string, optional): Filters data with vector clock greater than or equal to the specified value.
|
||||
- `upperVectorClock` (string, optional): Filters data with vector clock less than or equal to the specified value.
|
||||
- `afterTime` (number, optional): Filters data with log_time greater than the specified value.
|
||||
- `application` (string, optional): Filters data with the specified application.
|
||||
- `receiverID` (string, optional): Filters data with the specified receiver ID or proxy ID.
|
||||
- `comment` (string, optional): Filters data with the specified comment.
|
||||
- `type` (string, optional): Filters data with the specified type.
|
||||
- `senderID` (array of strings, optional): Filters data with sender IDs included in the specified array.
|
||||
|
||||
- `callback` (function): A callback function to handle the response data and errors.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a unique `randID` (string) representing the live request.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the liveRequest function connects to the specified flo server (floID), sends a live request with the provided filter criteria (request), and handles the response using the callback function.
|
||||
|
||||
```javascript
|
||||
const floID = "exampleFloID";
|
||||
const request = {
|
||||
status: true,
|
||||
application: "exampleApp",
|
||||
callback: function(data, error) {
|
||||
if (error) {
|
||||
console.error("Error occurred:", error);
|
||||
} else {
|
||||
console.log("Filtered data received:", data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
liveRequest(floID, request, callback)
|
||||
.then(randID => {
|
||||
console.log("Live request sent with ID:", randID);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error sending live request:", error);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## proxyID Function
|
||||
|
||||
The `proxyID` function is used to convert a given address into a proxy ID. It supports various address formats including legacy encoding, bech32 encoding, and public key hex format.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `address` (string): The input address to be converted into a proxy ID.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a string representing the proxy ID derived from the input address.
|
||||
|
||||
### Address Formats Supported
|
||||
|
||||
- **Legacy Encoding (Base58)**
|
||||
- Addresses with lengths 33 or 34 characters are supported.
|
||||
- **Bech32 Encoding**
|
||||
- Addresses with lengths 42 or 62 characters are supported.
|
||||
- **Public Key Hex**
|
||||
- Addresses with length 66 characters are supported.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the proxyID function converts a legacy Bitcoin address into a proxy ID. The function automatically detects the input address format and converts it into the corresponding proxy ID format.
|
||||
|
||||
```javascript
|
||||
const address = "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2";
|
||||
|
||||
const proxyID = util.proxyID(address);
|
||||
console.log("Proxy ID:", proxyID);
|
||||
```
|
||||
|
||||
|
||||
|
||||
## updateObject Function
|
||||
|
||||
The `updateObject` function is used to update an object in the `appObjects` data store based on the incoming dataset. It performs actions like resetting or updating the object based on the dataset's content.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `objectName` (string): The name of the object to be updated.
|
||||
- `dataSet` (object): The dataset containing the updates for the specified object.
|
||||
|
||||
### Functionality
|
||||
|
||||
- The function processes the dataset for the specified object and performs the following actions:
|
||||
- If the comment in the dataset is "RESET," the function resets the object with the data provided in the `message.reset` property.
|
||||
- If the comment in the dataset is "UPDATE," the function updates the object by merging the changes provided in the `message.diff` property using the `diff.merge` function.
|
||||
|
||||
### Example Usage
|
||||
|
||||
In this example, the updateObject function processes the dataSet and updates the specified object (exampleObject) based on the provided reset and diff actions. The function maintains the last version control (lastVC) and stores the updated object in the appObjects data store.
|
||||
|
||||
|
||||
```javascript
|
||||
const objectName = "exampleObject";
|
||||
const dataSet = {
|
||||
"123": { type: "exampleObject", comment: "RESET", message: { reset: { key: "value" } } },
|
||||
"456": { type: "exampleObject", comment: "UPDATE", message: { diff: { updatedKey: "updatedValue" } } }
|
||||
};
|
||||
|
||||
updateObject(objectName, dataSet);
|
||||
```
|
||||
|
||||
### Note
|
||||
- The diff.merge function and other related functions used in the implementation are assumed to be available in the scope where this function is used.
|
||||
|
||||
|
||||
## storeGeneral Function
|
||||
|
||||
The `storeGeneral` function is used to store general data entries in the `generalData` data store. It updates the data store with the provided dataset for a specific foreign key (`fk`).
|
||||
|
||||
### Parameters
|
||||
|
||||
- `fk` (string): The foreign key indicating the category or type of the general data.
|
||||
- `dataSet` (object): The dataset containing the general data entries to be stored.
|
||||
|
||||
### Functionality
|
||||
|
||||
- The function updates the `generalData` data store for the specified foreign key with the entries from the provided dataset.
|
||||
- It checks the `log_time` property of each entry in the dataset and updates the `lastVC` (last version control) property for the specified foreign key with the maximum log time value among the entries.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the storeGeneral function updates the generalData data store for the specified foreign key (exampleForeignKey) with the provided dataset. It also updates the lastVC property with the maximum log time value from the entries in the dataset.
|
||||
|
||||
```javascript
|
||||
const fk = "exampleForeignKey";
|
||||
const dataSet = {
|
||||
"123": { log_time: 1633363200000, data: "Entry 1" },
|
||||
"124": { log_time: 1633363800000, data: "Entry 2" },
|
||||
// ... more data entries
|
||||
};
|
||||
|
||||
storeGeneral(fk, dataSet);
|
||||
```
|
||||
|
||||
### Note
|
||||
Ensure that the necessary data structures and storage mechanisms, such as generalData and lastVC, are defined and available in the scope where this function is used.
|
||||
|
||||
## objectifier Function
|
||||
|
||||
The `objectifier` function is used to transform data from an array format into an object format. It takes an array of data objects as input and returns an object where the keys are taken from the `vectorClock` property of the input objects and the values are the input objects themselves with an additional property `message` that is decoded using the `decodeMessage` function.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `data` (array or object): The input data to be transformed. If `data` is not an array, it will be converted into a single-element array before processing.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns an object where keys are taken from the `vectorClock` property of the input objects, and the values are objects with the following properties:
|
||||
- `vectorClock` (string): The key used in the input data.
|
||||
- `message` (string): The decoded message obtained by applying the `decodeMessage` function to the `message` property of the input data.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const inputData = [
|
||||
{ vectorClock: "12345", message: "Encoded Message 1" },
|
||||
{ vectorClock: "67890", message: "Encoded Message 2" }
|
||||
];
|
||||
|
||||
const transformedData = objectifier(inputData);
|
||||
console.log(transformedData);
|
||||
|
||||
|
||||
/*
|
||||
Output
|
||||
{
|
||||
"12345": { vectorClock: "12345", message: "Decoded Message 1" },
|
||||
"67890": { vectorClock: "67890", message: "Decoded Message 2" }
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
## setStatus Function
|
||||
|
||||
The `setStatus` function is used to set the online status for a user specified by their `user_id` on the floCloud platform. It takes an options object as input, allowing customization of the request parameters.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `callback` (function, optional): A callback function to handle the response data and errors. If not provided, the default callback function is used.
|
||||
- `application` (string, optional): The application name associated with the status update. If not provided, the default application name is used.
|
||||
- `refID` (string, optional): The reference ID used for the live request. If not provided, the default admin ID is used.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the result of the live request upon success.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the setStatus function sets the online status for the user specified by user.id on the floCloud platform. The function allows customization of the request parameters through the options object and provides a callback function to handle the response data and errors.
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
callback: function(data, error) {
|
||||
if (error) {
|
||||
console.error("Error occurred:", error);
|
||||
} else {
|
||||
console.log("Status update successful:", data);
|
||||
}
|
||||
},
|
||||
application: "MyApp",
|
||||
refID: "admin123"
|
||||
};
|
||||
|
||||
floCloudAPI.setStatus(options)
|
||||
.then(result => {
|
||||
console.log("Status set successfully:", result);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error setting status:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## requestStatus Function
|
||||
|
||||
The `requestStatus` function is used to request the status of one or more `floID`s specified in the `trackList`. It sends a live request to obtain the status of the specified `floID`s and returns the response.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `trackList` (string or array of strings): The `floID`(s) for which the status will be requested. Can be a single `floID` or an array of multiple `floID`s.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `callback` (function, optional): A callback function to handle the response data and errors. If not provided, the default callback function is used.
|
||||
- `application` (string, optional): The application name associated with the status request. If not provided, the default application name is used.
|
||||
- `refID` (string, optional): The reference ID used for the live request. If not provided, the default admin ID is used.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data containing the status of the specified `floID`(s) upon success.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the requestStatus function sends a live request to obtain the status of the floID(s) specified in the trackList. The function allows customization of the request parameters through the options object and provides a callback function to handle the response data and errors.
|
||||
|
||||
```javascript
|
||||
const trackList = ["floID1", "floID2", "floID3"];
|
||||
const options = {
|
||||
callback: function(data, error) {
|
||||
if (error) {
|
||||
console.error("Error occurred:", error);
|
||||
} else {
|
||||
console.log("Status data received:", data);
|
||||
}
|
||||
},
|
||||
application: "MyApp",
|
||||
refID: "admin123"
|
||||
};
|
||||
|
||||
floCloudAPI.requestStatus(trackList, options)
|
||||
.then(statusData => {
|
||||
console.log("Status request successful:", statusData);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error sending status request:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## sendApplicationData Function
|
||||
|
||||
The `sendApplicationData` function is used to send application-specific data messages to a receiver identified by their `receiverID`. It allows customization of the message content, type, receiver ID, application name, and additional comments.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `message` (string): The application-specific message to be sent.
|
||||
- `type` (string): The type of the application data.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `receiverID` (string, optional): The ID of the receiver for the application data message. If not provided, the default admin ID is used.
|
||||
- `application` (string, optional): The application name associated with the data message. If not provided, the default application name is used.
|
||||
- `comment` (string, optional): Additional comments or notes associated with the data message.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful delivery of the application data message.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the sendApplicationData function sends an application-specific data message with the specified content, type, receiver ID, application name, and additional comments. The function allows customization of the message parameters through the options object and resolves with the response data upon successful delivery of the message.
|
||||
|
||||
```javascript
|
||||
const message = "Hello, this is an application data message.";
|
||||
const type = "text";
|
||||
const options = {
|
||||
receiverID: "receiverUserID",
|
||||
application: "MyApp",
|
||||
comment: "Optional comment for the message."
|
||||
};
|
||||
|
||||
floCloudAPI.sendApplicationData(message, type, options)
|
||||
.then(response => {
|
||||
console.log("Application data message sent successfully:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error occurred while sending application data message:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## requestApplicationData Function
|
||||
|
||||
The `requestApplicationData` function is a versatile method used to request data from the supernode cloud. It allows customization of the request parameters including the type of data, receiver and sender IDs, application name, vector clocks, timestamp, and request method.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `type` (string): The type of data to be requested.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `receiverID` (string, optional): The ID of the receiver for the data request. If not provided, the default admin ID is used.
|
||||
- `senderID` (string, optional): The ID of the sender for the data request. If not provided, it's set to `undefined`.
|
||||
- `application` (string, optional): The application name associated with the data request. If not provided, the default application name is used.
|
||||
- `comment` (string, optional): Additional comments or notes for the data request. If not provided, it's set to `undefined`.
|
||||
- `lowerVectorClock` (string, optional): The lower boundary for vector clock filtering. If not provided, it's set to `undefined`.
|
||||
- `upperVectorClock` (string, optional): The upper boundary for vector clock filtering. If not provided, it's set to `undefined`.
|
||||
- `atVectorClock` (string, optional): The specific vector clock at which the data is requested. If not provided, it's set to `undefined`.
|
||||
- `afterTime` (number, optional): Request data after the specified timestamp. If not provided, it's set to `undefined`.
|
||||
- `mostRecent` (boolean, optional): If set to `true`, requests the most recent data. If not provided, it's set to `undefined`.
|
||||
- `method` (string, optional): The HTTP request method. Default is `"GET"`. Can be `"GET"` or `"POST"`.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful retrieval of the requested data.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the requestApplicationData function sends a customizable data request to the supernode cloud. It allows flexibility in specifying request parameters, including vector clocks, timestamps, and request method. The function resolves with the response data upon successful retrieval of the requested data.
|
||||
|
||||
```javascript
|
||||
const type = "exampleData";
|
||||
const options = {
|
||||
receiverID: "receiverUserID",
|
||||
senderID: "senderUserID",
|
||||
application: "MyApp",
|
||||
comment: "Optional comment for the request.",
|
||||
lowerVectorClock: "123",
|
||||
upperVectorClock: "456",
|
||||
atVectorClock: "789",
|
||||
afterTime: 1633363200000,
|
||||
mostRecent: true,
|
||||
method: "POST",
|
||||
callback: function(data, error) {
|
||||
if (error) {
|
||||
console.error("Error occurred:", error);
|
||||
} else {
|
||||
console.log("Data received:", data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
floCloudAPI.requestApplicationData(type, options)
|
||||
.then(response => {
|
||||
console.log("Data request successful:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error sending data request:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## editApplicationData Function
|
||||
|
||||
The `editApplicationData` function is used by the sender to edit the comment of specific data in the supernode cloud identified by its `vectorClock`. It retrieves the data, verifies the sender's identity, edits the comment, and updates the data in the supernode cloud.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `vectorClock` (string): The vector clock of the data to be edited.
|
||||
- `comment_edit` (string): The edited comment to be applied to the data.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `receiverID` (string, optional): The ID of the receiver for the edited data. If not provided, the default admin ID is used.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful editing of the data comment.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the editApplicationData function allows the sender to edit the comment of specific data identified by its vectorClock. The function verifies the sender's identity and ensures that only the sender can edit the comment. Upon successful editing, the function resolves with the response data.
|
||||
|
||||
```javascript
|
||||
const vectorClock = "exampleVectorClock123";
|
||||
const editedComment = "This is the edited comment for the data.";
|
||||
const options = {
|
||||
receiverID: "receiverUserID"
|
||||
};
|
||||
|
||||
floCloudAPI.editApplicationData(vectorClock, editedComment, options)
|
||||
.then(response => {
|
||||
console.log("Data comment edited successfully:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error editing data comment:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## tagApplicationData Function
|
||||
|
||||
The `tagApplicationData` function is used by subAdmins to tag specific data in the supernode cloud identified by its `vectorClock`. It allows subAdmins to add tags to the data for organizational purposes.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `vectorClock` (string): The vector clock of the data to be tagged.
|
||||
- `tag` (string): The tag to be applied to the data.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `receiverID` (string, optional): The ID of the receiver for the tagged data. If not provided, the default admin ID is used.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful tagging of the data.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the tagApplicationData function allows subAdmins to tag specific data identified by its vectorClock. SubAdmins can add tags to the data for organizational purposes. The function ensures that only subAdmins have access to tagging data, and upon successful tagging, it resolves with the response data.
|
||||
|
||||
```javascript
|
||||
const vectorClock = "exampleVectorClock123";
|
||||
const tag = "important";
|
||||
|
||||
floCloudAPI.tagApplicationData(vectorClock, tag)
|
||||
.then(response => {
|
||||
console.log("Data tagged successfully:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error tagging data:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## noteApplicationData Function
|
||||
|
||||
The `noteApplicationData` function allows users (receivers) and subAdmins (if the receiver is the `adminID`) to add notes to specific data in the supernode cloud identified by its `vectorClock`.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `vectorClock` (string): The vector clock of the data to be noted.
|
||||
- `note` (string): The note to be added to the data.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `receiverID` (string, optional): The ID of the receiver for the noted data. If not provided, the default admin ID is used.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful noting of the data.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the noteApplicationData function allows users and subAdmins to add notes to specific data identified by its vectorClock in the supernode cloud. The function ensures that only the receiver and subAdmins (if the receiver is the adminID) can add notes to the data. Upon successful noting, it resolves with the response data.
|
||||
|
||||
```javascript
|
||||
const vectorClock = "exampleVectorClock123";
|
||||
const note = "This is a note for the data.";
|
||||
|
||||
floCloudAPI.noteApplicationData(vectorClock, note)
|
||||
.then(response => {
|
||||
console.log("Data noted successfully:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error adding note to data:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## sendGeneralData Function
|
||||
|
||||
The `sendGeneralData` function is used to send general data messages to the supernode cloud. It allows customization of the data content, type, encryption, and other options before sending.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `message` (object or string): The general data to be sent. It can be an object or a string.
|
||||
- `type` (string): The type of the general data.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `encrypt` (boolean or string, optional): If `true`, the `message` will be encrypted using the default encryption key. If a string is provided, it will be used as the encryption key. If not provided or `false`, no encryption will be applied.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful sending of the general data message.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the sendGeneralData function sends a general data message with the specified content and type to the supernode cloud. The function allows customization of the encryption option through the options object. Upon successful sending, it resolves with the response data.
|
||||
|
||||
```javascript
|
||||
const message = {
|
||||
key: "value"
|
||||
};
|
||||
const type = "exampleType";
|
||||
const options = {
|
||||
encrypt: true // Encrypt the message using the default encryption key
|
||||
};
|
||||
|
||||
floCloudAPI.sendGeneralData(message, type, options)
|
||||
.then(response => {
|
||||
console.log("General data sent successfully:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error sending general data:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## requestGeneralData Function
|
||||
|
||||
The `requestGeneralData` function is used to request general data of a specific type from the supernode cloud. It allows customization of the request parameters including the data type, filtering options, and callback function for data storage and handling.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `type` (string): The type of general data to be requested.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `receiverID` (string, optional): The ID of the receiver for the data request. If not provided, the default admin ID is used.
|
||||
- `senderID` (string, optional): The ID of the sender for the data request. If not provided, it's set to `undefined`.
|
||||
- `application` (string, optional): The application name associated with the data request. If not provided, the default application name is used.
|
||||
- `comment` (string, optional): Additional comments or notes for the data request. If not provided, it's set to `undefined`.
|
||||
- `lowerVectorClock` (string, optional): The lower boundary for vector clock filtering. If not provided, it's set to `undefined`.
|
||||
- `upperVectorClock` (string, optional): The upper boundary for vector clock filtering. If not provided, it's set to `undefined`.
|
||||
- `atVectorClock` (string, optional): The specific vector clock at which the data is requested. If not provided, it's set to `undefined`.
|
||||
- `afterTime` (number, optional): Request data after the specified timestamp. If not provided, it's set to the last stored vector clock for the specified data type.
|
||||
- `mostRecent` (boolean, optional): If set to `true`, requests the most recent data. If not provided, it's set to `undefined`.
|
||||
- `callback` (function, optional): A callback function to handle the response data and errors. If provided, the function stores the data and then calls the callback. If not provided, the data is directly resolved.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful retrieval of the requested general data.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the requestGeneralData function sends a customizable data request to the supernode cloud. It allows flexibility in specifying request parameters, including vector clocks, timestamps, and callback function for data storage and handling. The function resolves with the response data upon successful retrieval of the requested general data.
|
||||
|
||||
```javascript
|
||||
const type = "exampleType";
|
||||
const options = {
|
||||
receiverID: "receiverUserID",
|
||||
senderID: "senderUserID",
|
||||
application: "MyApp",
|
||||
comment: "Optional comment for the request.",
|
||||
lowerVectorClock: "123",
|
||||
upperVectorClock: "456",
|
||||
atVectorClock: "789",
|
||||
afterTime: 1633363200000,
|
||||
mostRecent: true,
|
||||
callback: function(data, error) {
|
||||
if (error) {
|
||||
console.error("Error occurred:", error);
|
||||
} else {
|
||||
console.log("Data received:", data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
floCloudAPI.requestGeneralData(type, options)
|
||||
.then(response => {
|
||||
console.log("General data request successful:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error sending general data request:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## requestObjectData Function
|
||||
|
||||
The `requestObjectData` function is used to request data of a specific object type from the supernode cloud. It allows customization of the request parameters including the object name, filtering options, and callback function for data storage and handling.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `objectName` (string): The name of the object data to be requested.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `lowerVectorClock` (string, optional): The lower boundary for vector clock filtering. If not provided, it's set to the last stored vector clock for the specified object data type plus 1.
|
||||
- `senderID` (string or array, optional): The sender ID(s) for filtering the data. If not provided, it's set to `null`. If provided, only data from the specified sender(s) will be requested.
|
||||
- `mostRecent` (boolean, optional): If set to `true`, requests the most recent data. If not provided, it's set to `true`.
|
||||
- `comment` (string, optional): The comment to be applied to the request. If provided, it's set to `'RESET'`.
|
||||
- `callback` (function, optional): A callback function to handle the response data and errors. If provided, the function stores the data and then calls the callback. If not provided, the data is directly resolved.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful retrieval of the requested object data.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the requestObjectData function sends a customizable data request for a specific object type to the supernode cloud. It allows flexibility in specifying request parameters, including vector clocks, sender IDs, and callback function for data storage and handling. The function resolves with the response data upon successful retrieval of the requested object data.
|
||||
|
||||
```javascript
|
||||
const objectName = "exampleObject";
|
||||
const options = {
|
||||
lowerVectorClock: "123",
|
||||
senderID: "senderUserID",
|
||||
mostRecent: true,
|
||||
callback: function(data, error) {
|
||||
if (error) {
|
||||
console.error("Error occurred:", error);
|
||||
} else {
|
||||
console.log("Data received:", data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
floCloudAPI.requestObjectData(objectName, options)
|
||||
.then(response => {
|
||||
console.log("Object data request successful:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error sending object data request:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## closeRequest Function
|
||||
|
||||
The `closeRequest` function is used to close an active request connection to the supernode cloud identified by its `requestID`.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `requestID` (string): The unique identifier of the request connection to be closed.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message upon successful closure of the request connection.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the closeRequest function closes an active request connection to the supernode cloud based on the provided requestID. Upon successful closure, it resolves with a success message. If the request connection is not found, it rejects with an error message.
|
||||
|
||||
```javascript
|
||||
const requestID = "exampleRequestID";
|
||||
|
||||
floCloudAPI.closeRequest(requestID)
|
||||
.then(response => {
|
||||
console.log("Request connection closed successfully:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error closing request connection:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## resetObjectData Function
|
||||
|
||||
The `resetObjectData` function is used to reset or initialize an object and send it to the supernode cloud. It sends a reset message containing the initial state of the specified object to the cloud for synchronization.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `objectName` (string): The name of the object to be reset and sent to the cloud.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `comment` (string, optional): The comment to be applied to the reset operation. If provided, it's set to `'RESET'`.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful reset and synchronization of the object data.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the resetObjectData function resets the specified object to its initial state and sends the reset message to the supernode cloud for synchronization. The function allows customization through the options object, including adding a comment to the reset operation. Upon successful reset and synchronization, it resolves with the response data.
|
||||
|
||||
```javascript
|
||||
const objectName = "exampleObject";
|
||||
const options = {
|
||||
comment: "Resetting object to initial state."
|
||||
};
|
||||
|
||||
floCloudAPI.resetObjectData(objectName, options)
|
||||
.then(response => {
|
||||
console.log("Object data reset and synchronized successfully:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error resetting object data:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## updateObjectData Function
|
||||
|
||||
The `updateObjectData` function is used to update the differential changes of an object and send them to the supernode cloud. It computes the difference between the last committed state and the current state of the specified object and sends the update message to the cloud for synchronization.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `objectName` (string): The name of the object whose differential changes need to be sent to the cloud.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `comment` (string, optional): The comment to be applied to the update operation. If provided, it's set to `'UPDATE'`.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with the response data upon successful update and synchronization of the object's differential changes.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the updateObjectData function computes the differential changes of the specified object and sends the update message to the supernode cloud for synchronization. The function allows customization through the options object, including adding a comment to the update operation. Upon successful update and synchronization, it resolves with the response data.
|
||||
|
||||
```javascript
|
||||
const objectName = "exampleObject";
|
||||
const options = {
|
||||
comment: "Updating object with differential changes."
|
||||
};
|
||||
|
||||
floCloudAPI.updateObjectData(objectName, options)
|
||||
.then(response => {
|
||||
console.log("Object data updated and synchronized successfully:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error updating object data:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## uploadFile Function
|
||||
|
||||
The `uploadFile` function is used to upload a file to the supernode cloud. It accepts a file blob or instance of File/Blob, processes the file content, and sends it to the cloud for storage.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `fileBlob` (File/Blob): The File or Blob instance representing the file to be uploaded.
|
||||
- `type` (string): The type of data to be associated with the uploaded file.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `encrypt` (boolean or string, optional): If `true`, encrypts the file data using the default encryption key. If a string is provided, it uses the specified encryption key. If not provided, the file data is not encrypted.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with an object containing vectorClock, receiverID, type, and application upon successful upload of the file.
|
||||
|
||||
### Example Usage
|
||||
- In this example, the uploadFile function uploads a file to the supernode cloud. It accepts a file blob or instance of File/Blob, processes the file content, and sends it to the cloud for storage. The function allows customization through the options object, including encryption of the file data. Upon successful upload, it resolves with an object containing vectorClock, receiverID, type, and application.
|
||||
|
||||
```javascript
|
||||
const fileBlob = new Blob(["File content"], { type: "text/plain" });
|
||||
const type = "fileData";
|
||||
const options = {
|
||||
encrypt: true
|
||||
};
|
||||
|
||||
floCloudAPI.uploadFile(fileBlob, type, options)
|
||||
.then(response => {
|
||||
console.log("File uploaded successfully:", response);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error uploading file:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## downloadFile Function
|
||||
|
||||
The `downloadFile` function is used to download a file from the supernode cloud based on its vectorClock. It retrieves the file data, decrypts it if necessary, and reconstructs the file for download.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `vectorClock` (string): The vectorClock of the file to be downloaded.
|
||||
- `options` (object, optional): An object containing the following properties:
|
||||
- `type` (string, optional): The type of the data to be downloaded. If not provided, it uses the default type.
|
||||
- `decrypt` (boolean or string, optional): If `true`, decrypts the file data using the default decryption key. If a string is provided, it uses the specified decryption key. If not provided, and the file data is encrypted, it rejects the download request.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with an object containing the downloaded File instance upon successful download.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, the downloadFile function downloads a file from the supernode cloud based on its vectorClock. It allows customization through the options object, including specifying the data type and providing a decryption key if the file data is encrypted. Upon successful download, it resolves with an object containing the downloaded File instance.
|
||||
|
||||
```javascript
|
||||
const vectorClock = "exampleVectorClock";
|
||||
const options = {
|
||||
type: "fileData",
|
||||
decrypt: true
|
||||
};
|
||||
|
||||
floCloudAPI.downloadFile(vectorClock, options)
|
||||
.then(response => {
|
||||
console.log("File downloaded successfully:", response);
|
||||
// Use response.file to access the downloaded file instance
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error downloading file:", error);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
685
messenger/docs/floCrypto.md
Normal file
@ -0,0 +1,685 @@
|
||||
# encryptData Function Documentation
|
||||
|
||||
The `encryptData` function takes two parameters: `data` (string) and `receiverPublicKeyHex` (string). It encrypts the given data using AES encryption algorithm with a shared key derived from the sender's private key and receiver's public key. The function returns an object with two properties: `secret` (the encrypted data) and `senderPublicKeyString` (the sender's public key string).
|
||||
|
||||
## Parameters
|
||||
- **data** (String): The data to be encrypted.
|
||||
- **receiverPublicKeyHex** (String): The hexadecimal representation of the receiver's public key.
|
||||
|
||||
## Return Value
|
||||
An object containing the following properties:
|
||||
- **secret** (String): The encrypted data.
|
||||
- **senderPublicKeyString** (String): The sender's public key string.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const data = "Hello, World!";
|
||||
const receiverPublicKeyHex = "receiver_public_key_hex_here";
|
||||
|
||||
const encryptedData = floCrypto.encryptData(data, receiverPublicKeyHex);
|
||||
console.log("Encrypted Data:", encryptedData.secret);
|
||||
console.log("Sender's Public Key:", encryptedData.senderPublicKeyString);
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
`getSenderPublicKeyString`: A function to retrieve the sender's public key string.
|
||||
`deriveSharedKeySender`: A function to derive a shared key using sender's private key and receiver's public key.
|
||||
`Crypto.AES.encrypt`: AES encryption function used to encrypt the data.
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependencies (getSenderPublicKeyString, deriveSharedKeySender, Crypto.AES.encrypt) are available and properly implemented before using this function.
|
||||
- Note: Replace receiver_public_key_hex_here with the actual hexadecimal representation of the receiver's public key when using the function.
|
||||
|
||||
# decryptData Function Documentation
|
||||
|
||||
The `decryptData` function is used to decrypt encrypted data using the receiver's private key.
|
||||
|
||||
## Parameters
|
||||
- **data** (Object): An object containing the encrypted data and sender's public key string.
|
||||
- **secret** (String): The encrypted data.
|
||||
- **senderPublicKeyString** (String): The sender's public key string.
|
||||
- **privateKeyHex** (String): The hexadecimal representation of the receiver's private key.
|
||||
|
||||
## Return Value
|
||||
The decrypted message as a string.
|
||||
|
||||
## Throws
|
||||
- **Error**: If `privateKeyHex` is not a valid string or if the private key cannot be determined from the provided hexadecimal representation.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const encryptedData = {
|
||||
secret: "encrypted_data_here",
|
||||
senderPublicKeyString: "sender_public_key_string_here"
|
||||
};
|
||||
const privateKeyHex = "receiver_private_key_hex_here";
|
||||
|
||||
const decryptedMessage = floCrypto.decryptData(encryptedData, privateKeyHex);
|
||||
console.log("Decrypted Message:", decryptedMessage);
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **wifToDecimal**: A function to convert the receiver's private key from WIF format to decimal format.
|
||||
- **deriveSharedKeyReceiver**: A function to derive a shared key using sender's public key string and receiver's private key.
|
||||
- **Crypto.AES.decrypt**: AES decryption function used to decrypt the data.
|
||||
|
||||
## Notes
|
||||
|
||||
- Ensure that the necessary dependencies (`wifToDecimal`, `deriveSharedKeyReceiver`, `Crypto.AES.decrypt`) are available and properly implemented before using this function.
|
||||
- **Note**: Replace `"encrypted_data_here"` with the actual encrypted data, `"sender_public_key_string_here"` with the sender's public key string, and `"receiver_private_key_hex_here"` with the receiver's private key in hexadecimal format when using the function.
|
||||
|
||||
# signData Function Documentation
|
||||
|
||||
The `signData` function is used to sign data using the sender's private key.
|
||||
|
||||
## Parameters
|
||||
- **data** (String): The data to be signed.
|
||||
- **privateKeyHex** (String): The hexadecimal representation of the sender's private key.
|
||||
|
||||
## Return Value
|
||||
A hexadecimal string representing the signature of the input data.
|
||||
|
||||
## Dependencies
|
||||
- **Bitcoin.ECKey**: A class for handling elliptic curve cryptography operations.
|
||||
- **Crypto.SHA256**: SHA-256 hash function used to create the message hash.
|
||||
- **Bitcoin.ECDSA.sign**: Function for generating the ECDSA signature.
|
||||
- **Crypto.util.bytesToHex**: Utility function to convert bytes to hexadecimal string.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const data = "Hello, World!";
|
||||
const privateKeyHex = "sender_private_key_hex_here";
|
||||
|
||||
const signature = floCrypto.signData(data, privateKeyHex);
|
||||
console.log("Signature:", signature);
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Ensure that the necessary dependencies (`Bitcoin.ECKey`, `Crypto.SHA256`, `Bitcoin.ECDSA.sign`, `Crypto.util.bytesToHex`) are available and properly implemented before using this function.
|
||||
- **Note**: Replace `"sender_private_key_hex_here"` with the actual sender's private key in hexadecimal format when using the function.
|
||||
|
||||
# verifySign Function Documentation
|
||||
|
||||
The `verifySign` function is used to verify the signature of data using the sender's public key.
|
||||
|
||||
## Parameters
|
||||
- **data** (String): The original data that was signed.
|
||||
- **signatureHex** (String): The hexadecimal representation of the signature to be verified.
|
||||
- **publicKeyHex** (String): The hexadecimal representation of the sender's public key.
|
||||
|
||||
## Return Value
|
||||
A boolean value indicating whether the signature is valid (`true`) or not (`false`).
|
||||
|
||||
## Dependencies
|
||||
- **Crypto.SHA256**: SHA-256 hash function used to create the message hash.
|
||||
- **Crypto.util.hexToBytes**: Utility function to convert hexadecimal string to bytes.
|
||||
- **ecparams.getCurve().decodePointHex**: Function to decode the sender's public key from hexadecimal format.
|
||||
- **Bitcoin.ECDSA.verify**: Function for verifying the ECDSA signature.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const data = "Hello, World!";
|
||||
const signatureHex = "signature_hex_here";
|
||||
const publicKeyHex = "sender_public_key_hex_here";
|
||||
|
||||
const isSignatureValid = floCrypto.verifySign(data, signatureHex, publicKeyHex);
|
||||
console.log("Is Signature Valid?", isSignatureValid);
|
||||
```
|
||||
## Notes
|
||||
|
||||
- Ensure that the necessary dependencies (`Crypto.SHA256`, `Crypto.util.hexToBytes`, `ecparams.getCurve().decodePointHex`, `Bitcoin.ECDSA.verify`) are available and properly implemented before using this function.
|
||||
- **Note**: Replace `"signature_hex_here"` with the actual signature in hexadecimal format, and `"sender_public_key_hex_here"` with the sender's public key in hexadecimal format when using the function
|
||||
|
||||
# generateNewID Function Documentation
|
||||
|
||||
The `generateNewID` function is used to generate a new flo ID, along with its corresponding private key and public key.
|
||||
|
||||
## Return Value
|
||||
An object containing the following properties:
|
||||
- **floID** (String): The newly generated flo ID.
|
||||
- **pubKey** (String): The hexadecimal representation of the corresponding public key.
|
||||
- **privKey** (String): The Wallet Import Format (WIF) representation of the corresponding private key.
|
||||
|
||||
## Dependencies
|
||||
- **Bitcoin.ECKey**: A class for generating elliptic curve key pairs.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const newIDInfo = floCrypto.generateNewID();
|
||||
console.log("Flo ID:", newIDInfo.floID);
|
||||
console.log("Public Key:", newIDInfo.pubKey);
|
||||
console.log("Private Key (WIF):", newIDInfo.privKey);
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Ensure that the necessary dependency (`Bitcoin.ECKey`) is available and properly implemented before using this function.
|
||||
|
||||
# getPubKeyHex Function Documentation
|
||||
|
||||
The `getPubKeyHex` function is used to obtain the public key from a given private key.
|
||||
|
||||
## Parameters
|
||||
- **privateKeyHex** (String): The hexadecimal representation of the private key.
|
||||
|
||||
## Return Value
|
||||
- (String): The hexadecimal representation of the corresponding public key.
|
||||
|
||||
## Dependencies
|
||||
- **Bitcoin.ECKey**: A class for handling elliptic curve cryptography operations.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const privateKeyHex = "private_key_hex_here";
|
||||
const publicKeyHex = floCrypto.getPubKeyHex(privateKeyHex);
|
||||
console.log("Public Key:", publicKeyHex);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependency (`Bitcoin.ECKey`) is available and properly implemented before using this function.
|
||||
- If `privateKeyHex` is not provided or invalid, the function will return `null`.
|
||||
|
||||
# getFloID Function Documentation
|
||||
|
||||
The `getFloID` function is used to obtain the flo-ID from a given public key or private key.
|
||||
|
||||
## Parameters
|
||||
- **keyHex** (String): The hexadecimal representation of the public key or private key.
|
||||
|
||||
## Return Value
|
||||
- (String): The corresponding flo-ID.
|
||||
|
||||
## Dependencies
|
||||
- **Bitcoin.ECKey**: A class for handling elliptic curve cryptography operations.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const keyHex = "public_key_or_private_key_hex_here";
|
||||
const floID = floCrypto.getFloID(keyHex);
|
||||
console.log("Flo-ID:", floID);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependency (`Bitcoin.ECKey`) is available and properly implemented before using this function.
|
||||
- If `keyHex` is not provided or invalid, the function will return `null`.
|
||||
|
||||
# getAddress Function Documentation
|
||||
|
||||
The `getAddress` function is used to obtain the cryptocurrency address (BTC or FLO) corresponding to a given private key.
|
||||
|
||||
## Parameters
|
||||
- **privateKeyHex** (String): The hexadecimal representation of the private key.
|
||||
- **strict** (Boolean, optional): A flag to enforce strict address generation. Default is `false`.
|
||||
|
||||
## Return Value
|
||||
- (String): The cryptocurrency address corresponding to the provided private key.
|
||||
|
||||
## Dependencies
|
||||
- **Bitcoin.ECKey**: A class for handling elliptic curve cryptography operations.
|
||||
- **bitjs.Base58.decode**: Function to decode Base58 encoded strings.
|
||||
- **coinjs.bech32Address**: Function to generate BTC bech32 address.
|
||||
- **bitjs.pubkey2address**: Function to generate FLO address from public key.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const privateKeyHex = "private_key_hex_here";
|
||||
const address = floCrypto.getAddress(privateKeyHex, true);
|
||||
console.log("Cryptocurrency Address:", address);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependencies (`Bitcoin.ECKey`, `bitjs.Base58.decode`, `coinjs.bech32Address`, `bitjs.pubkey2address`) are available and properly implemented before using this function.
|
||||
- If `privateKeyHex` is not provided or invalid, the function will return `null`.
|
||||
- The `strict` flag, if set to `true`, enforces strict address generation based on the provided private key version. If set to `false`, the function will default to generating a FLO address.
|
||||
|
||||
# verifyPrivKey Function Documentation
|
||||
|
||||
The `verifyPrivKey` function is used to verify whether a given private key corresponds to a provided public key or flo-ID.
|
||||
|
||||
## Parameters
|
||||
- **privateKeyHex** (String): The hexadecimal representation of the private key.
|
||||
- **pubKey_floID** (String): The public key or flo-ID to be verified.
|
||||
- **isfloID** (Boolean, optional): A flag indicating whether the provided key is a flo-ID. Default is `true`.
|
||||
|
||||
## Return Value
|
||||
- (Boolean): `true` if the private key corresponds to the provided public key or flo-ID, `false` otherwise. Returns `null` if an error occurs during verification.
|
||||
|
||||
## Dependencies
|
||||
- **Bitcoin.ECKey**: A class for handling elliptic curve cryptography operations.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const privateKeyHex = "private_key_hex_here";
|
||||
const pubKey_floID = "public_key_or_floID_here";
|
||||
const isfloID = true;
|
||||
|
||||
const isPrivateKeyValid = floCrypto.verifyPrivKey(privateKeyHex, pubKey_floID, isfloID);
|
||||
console.log("Is Private Key Valid?", isPrivateKeyValid);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependency (`Bitcoin.ECKey`) is available and properly implemented before using this function.
|
||||
- If `privateKeyHex` or `pubKey_floID` is not provided or invalid, the function will return `false`.
|
||||
- If `isfloID` is set to `true`, the function will verify against a flo-ID. If set to `false`, it will verify against a public key.
|
||||
|
||||
# getMultisigAddress Function Documentation
|
||||
|
||||
The `getMultisigAddress` function is used to generate a multisignature address based on a list of public keys and the number of required signatures.
|
||||
|
||||
## Parameters
|
||||
- **publicKeyList** (Array of Strings): An array containing hexadecimal representations of public keys.
|
||||
- **requiredSignatures** (Integer): The number of required signatures for the multisignature address.
|
||||
|
||||
## Return Value
|
||||
- (String): The generated multisignature address, or `null` if an error occurs during the generation.
|
||||
|
||||
## Dependencies
|
||||
- **bitjs.pubkeys2multisig**: Function to generate a multisignature address from an array of public keys.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const publicKeyList = ["pubKey1_hex", "pubKey2_hex", "pubKey3_hex"];
|
||||
const requiredSignatures = 2;
|
||||
|
||||
const multisigAddress = floCrypto.getMultisigAddress(publicKeyList, requiredSignatures);
|
||||
console.log("Multisig Address:", multisigAddress);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependency (`bitjs.pubkeys2multisig`) is available and properly implemented before using this function.
|
||||
- `publicKeyList` should be an array of hexadecimal strings representing the public keys.
|
||||
- `requiredSignatures` should be an integer indicating the number of required signatures for the multisignature address.
|
||||
- If `publicKeyList` is empty, not an array, or contains invalid keys, or if `requiredSignatures` is not a valid integer, the function will return `null`.
|
||||
|
||||
# decodeRedeemScript Function Documentation
|
||||
|
||||
The `decodeRedeemScript` function is used to decode a multisignature redeem script.
|
||||
|
||||
## Parameters
|
||||
- **redeemScript** (String): The hexadecimal representation of the redeem script.
|
||||
|
||||
## Return Value
|
||||
- (Object): An object containing the decoded information of the multisignature redeem script, or `null` if an error occurs during decoding.
|
||||
|
||||
## Dependencies
|
||||
- **bitjs.transaction().decodeRedeemScript**: Function to decode a multisignature redeem script.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const redeemScript = "redeem_script_hex_here";
|
||||
|
||||
const decodedRedeemScript = floCrypto.decodeRedeemScript(redeemScript);
|
||||
console.log("Decoded Redeem Script:", decodedRedeemScript);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependency (`bitjs.transaction().decodeRedeemScript`) is available and properly implemented before using this function.
|
||||
- If the `redeemScript` is not a valid hexadecimal string or if an error occurs during decoding, the function will return `null`.
|
||||
|
||||
# validateFloID Function Documentation
|
||||
|
||||
The `validateFloID` function is used to check whether a given flo-ID is valid or not.
|
||||
|
||||
## Parameters
|
||||
- **floID** (String): The flo-ID to be validated.
|
||||
- **regularOnly** (Boolean, optional): A flag indicating whether to allow only regular flo-IDs. Default is `false`.
|
||||
|
||||
## Return Value
|
||||
- (Boolean): `true` if the flo-ID is valid, `false` otherwise.
|
||||
|
||||
## Dependencies
|
||||
- **Bitcoin.Address**: A class for handling Bitcoin addresses.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const floID = "flo_ID_here";
|
||||
const isValidFloID = floCrypto.validateFloID(floID, true);
|
||||
console.log("Is Flo-ID Valid?", isValidFloID);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependency (`Bitcoin.Address`) is available and properly implemented before using this function.
|
||||
- If `floID` is not a valid flo-ID (not a valid Bitcoin address), the function will return `false`.
|
||||
- If `regularOnly` is set to `true`, the function will only consider standard flo-IDs as valid; otherwise, it will consider all valid flo-IDs as valid.
|
||||
|
||||
# validateAddr Function Documentation
|
||||
|
||||
The `validateAddr` function is used to check whether a given address (from any blockchain) is valid or not.
|
||||
|
||||
## Parameters
|
||||
- **address** (String): The address to be validated.
|
||||
- **std** (Boolean or Integer or Array, optional): A flag or array indicating whether to allow specific address versions (standard addresses). Default is `true`.
|
||||
- **bech** (Boolean or Integer or Array, optional): A flag or array indicating whether to allow specific bech32 address versions. Default is `true`.
|
||||
|
||||
## Return Value
|
||||
- (Boolean): `true` if the address is valid, `false` otherwise.
|
||||
|
||||
## Dependencies
|
||||
- **decodeAddress**: A function to decode the provided address.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const address = "address_here";
|
||||
const isValidAddress = floCrypto.validateAddr(address, true, true);
|
||||
console.log("Is Address Valid?", isValidAddress);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependency (`decodeAddress`) is available and properly implemented before using this function.
|
||||
- If `address` is not a valid address, the function will return `false`.
|
||||
- The `std` parameter allows specifying the allowed standard address versions. If set to `true`, all standard versions are allowed. If set to an integer or an array of integers, only the specified version(s) are allowed.
|
||||
- The `bech` parameter allows specifying the allowed bech32 address versions. If set to `true`, all bech32 versions are allowed. If set to an integer or an array of integers, only the specified version(s) are allowed.
|
||||
|
||||
# verifyPubKey Function Documentation
|
||||
|
||||
The `verifyPubKey` function is used to verify whether a given public key or redeem script corresponds to the provided address (from any blockchain).
|
||||
|
||||
## Parameters
|
||||
- **pubKeyHex** (String): The hexadecimal representation of the public key or redeem script.
|
||||
- **address** (String): The address to be verified.
|
||||
|
||||
## Return Value
|
||||
- (Boolean): `true` if the public key or redeem script corresponds to the provided address, `false` otherwise.
|
||||
|
||||
## Dependencies
|
||||
- **decodeAddress**: A function to decode the provided address.
|
||||
- **Crypto.util.bytesToHex**: Utility function to convert bytes to hexadecimal string.
|
||||
- **Crypto.SHA256**: SHA-256 hash function.
|
||||
- **ripemd160**: RIPEMD-160 hash function.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const pubKeyHex = "public_key_or_redeem_script_hex_here";
|
||||
const address = "address_here";
|
||||
|
||||
const isPubKeyValid = floCrypto.verifyPubKey(pubKeyHex, address);
|
||||
console.log("Is Public Key Valid?", isPubKeyValid);
|
||||
```
|
||||
## Notes
|
||||
- Ensure that the necessary dependencies (`decodeAddress`, `Crypto.util.bytesToHex`, `Crypto.SHA256`, `Crypto.util.hexToBytes`, `ripemd160`) are available and properly implemented before using this function.
|
||||
- If the public key or redeem script does not correspond to the provided address, the function will return `false`.
|
||||
|
||||
# toFloID Function Documentation
|
||||
|
||||
The `toFloID` function is used to convert a given address (from any blockchain) to its equivalent floID.
|
||||
|
||||
## Parameters
|
||||
- **address** (String): The address to be converted.
|
||||
- **options** (Object, optional): An object containing options for version checks. Default is `null`.
|
||||
- **std** (Array of Integers, optional): An array of allowed standard address versions.
|
||||
- **bech** (Array of Integers, optional): An array of allowed bech32 address versions.
|
||||
|
||||
## Return Value
|
||||
- (String): The equivalent floID corresponding to the provided address. Returns `undefined` if the address is not valid or does not match the specified options.
|
||||
|
||||
## Dependencies
|
||||
- **decodeAddress**: A function to decode the provided address.
|
||||
- **Crypto.SHA256**: SHA-256 hash function.
|
||||
- **bitjs.pub**: The version byte for FLO addresses.
|
||||
- **bitjs.Base58.encode**: Base58 encoding function.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const address = "address_here";
|
||||
const options = {
|
||||
std: [version1, version2],
|
||||
bech: [version3, version4]
|
||||
};
|
||||
|
||||
const floID = floCrypto.toFloID(address, options);
|
||||
console.log("Equivalent FloID:", floID);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependencies (`decodeAddress`, `Crypto.SHA256`, `bitjs.pub`, `bitjs.Base58.encode`) are available and properly implemented before using this function.
|
||||
- If the `address` is not valid or does not match the specified options, the function will return `undefined`.
|
||||
|
||||
# toMultisigFloID Function Documentation
|
||||
|
||||
The `toMultisigFloID` function is used to convert a given multisig address (from any blockchain) to its equivalent multisig floID.
|
||||
|
||||
## Parameters
|
||||
- **address** (String): The multisig address to be converted.
|
||||
- **options** (Object, optional): An object containing options for version checks. Default is `null`.
|
||||
- **std** (Array of Integers, optional): An array of allowed standard address versions.
|
||||
- **bech** (Array of Integers, optional): An array of allowed bech32 address versions.
|
||||
|
||||
## Return Value
|
||||
- (String): The equivalent multisig floID corresponding to the provided multisig address. Returns `undefined` if the address is not valid or does not match the specified options.
|
||||
|
||||
## Dependencies
|
||||
- **decodeAddress**: A function to decode the provided multisig address.
|
||||
- **Crypto.SHA256**: SHA-256 hash function.
|
||||
- **ripemd160**: A cryptographic hash function.
|
||||
- **bitjs.multisig**: The version byte for multisig FLO addresses.
|
||||
- **bitjs.Base58.encode**: Base58 encoding function.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const multisigAddress = "multisig_address_here";
|
||||
const options = {
|
||||
std: [version1, version2],
|
||||
bech: [version3, version4]
|
||||
};
|
||||
|
||||
const multisigFloID = floCrypto.toMultisigFloID(multisigAddress, options);
|
||||
console.log("Equivalent Multisig FloID:", multisigFloID);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependencies (`decodeAddress`, `Crypto.SHA256`, `ripemd160`, `bitjs.multisig`, `bitjs.Base58.encode`) are available and properly implemented before using this function.
|
||||
- If the `address` is not valid or does not match the specified options, the function will return `undefined`.
|
||||
|
||||
# isSameAddr Function Documentation
|
||||
|
||||
The `isSameAddr` function is used to check whether two given addresses (from any blockchain) correspond to the same keys.
|
||||
|
||||
## Parameters
|
||||
- **addr1** (String): The first address to be compared.
|
||||
- **addr2** (String): The second address to be compared.
|
||||
|
||||
## Return Value
|
||||
- (Boolean): `true` if the addresses correspond to the same keys, `false` otherwise. Returns `undefined` if either `addr1` or `addr2` is not provided or invalid.
|
||||
|
||||
## Dependencies
|
||||
- **decodeAddress**: A function to decode the provided addresses.
|
||||
- **Crypto.util.bytesToHex**: Utility function to convert bytes to hexadecimal string.
|
||||
- **ripemd160**: A cryptographic hash function.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const addr1 = "address1_here";
|
||||
const addr2 = "address2_here";
|
||||
|
||||
const sameKeys = floCrypto.isSameAddr(addr1, addr2);
|
||||
console.log("Do Addresses Correspond to Same Keys?", sameKeys);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependencies (`decodeAddress`, `Crypto.util.bytesToHex`, `ripemd160`) are available and properly implemented before using this function.
|
||||
- If either `addr1` or `addr2` is not provided or invalid, the function will return `undefined`.
|
||||
|
||||
# decodeAddr Function Documentation
|
||||
|
||||
The `decodeAddr` function is used to decode the provided address and determine its version, hexadecimal representation, and raw bytes.
|
||||
|
||||
## Parameters
|
||||
- **address** (String): The address to be decoded.
|
||||
|
||||
## Return Value
|
||||
- (Object or null): An object containing decoded information, including version, hexadecimal representation, and raw bytes. Returns `null` if the provided address is not valid.
|
||||
|
||||
## Dependencies
|
||||
- **bitjs.Base58.decode**: Base58 decoding function for legacy addresses.
|
||||
- **Crypto.SHA256**: SHA-256 hash function.
|
||||
- **Crypto.util.bytesToHex**: Utility function to convert bytes to hexadecimal string.
|
||||
- **coinjs.bech32_decode**: Bech32 decoding function for bech32 addresses.
|
||||
- **coinjs.bech32_convert**: Bech32 conversion function.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const address = "address_here";
|
||||
const decodedInfo = floCrypto.decodeAddr(address);
|
||||
console.log("Decoded Address Information:", decodedInfo);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependencies (`bitjs.Base58.decode`, `Crypto.SHA256`, `Crypto.util.bytesToHex`, `coinjs.bech32_decode`, `coinjs.bech32_convert`) are available and properly implemented before using this function.
|
||||
- The function will return `null` if the provided address is not valid.
|
||||
|
||||
# createShamirsSecretShares Function Documentation
|
||||
|
||||
The `createShamirsSecretShares` function uses Shamir's Secret Sharing algorithm to split a given string into shares based on the specified total shares and threshold limit.
|
||||
|
||||
## Parameters
|
||||
- **str** (String): The input string to be split.
|
||||
- **total_shares** (Integer): The total number of shares to be generated.
|
||||
- **threshold_limit** (Integer): The minimum number of shares required to reconstruct the original string.
|
||||
|
||||
## Return Value
|
||||
- (Array or Boolean): An array containing the shares generated using Shamir's Secret Sharing algorithm. Returns `false` if the input string is empty or if there is an error during the splitting process.
|
||||
|
||||
## Dependencies
|
||||
- **shamirSecretShare.str2hex**: Function to convert a string to hexadecimal representation.
|
||||
- **shamirSecretShare.share**: Function to generate shares using Shamir's Secret Sharing algorithm.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const inputString = "input_string_here";
|
||||
const totalShares = 5;
|
||||
const thresholdLimit = 3;
|
||||
|
||||
const shares = floCrypto.createShamirsSecretShares(inputString, totalShares, thresholdLimit);
|
||||
console.log("Generated Shares:", shares);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ensure that the necessary dependencies (`shamirSecretShare.str2hex`, `shamirSecretShare.share`) are available and properly implemented before using this function.
|
||||
- The function will return `false` if the input string is empty or if there is an error during the splitting process.
|
||||
|
||||
# retrieveShamirSecret Function Documentation
|
||||
|
||||
The `retrieveShamirSecret` function is used to retrieve the original secret by combining the provided Shamir's Secret Sharing shares.
|
||||
|
||||
## Parameters
|
||||
- **sharesArray** (Array): An array containing Shamir's Secret Sharing shares to be combined.
|
||||
|
||||
## Return Value
|
||||
- (String or Boolean): The retrieved original secret. Returns `false` if the input shares array is empty or if there is an error during the retrieval process.
|
||||
|
||||
## Dependencies
|
||||
- **shamirSecretShare.combine**: Function to combine shares using Shamir's Secret Sharing algorithm.
|
||||
- **shamirSecretShare.hex2str**: Function to convert hexadecimal representation to a string.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const sharesArray = ["share1", "share2", "share3"];
|
||||
const retrievedSecret = floCrypto.retrieveShamirSecret(sharesArray);
|
||||
console.log("Retrieved Secret:", retrievedSecret);
|
||||
```
|
||||
## Notes
|
||||
- Ensure that the necessary dependencies (`shamirSecretShare.combine`, `shamirSecretShare.hex2str`) are available and properly implemented before using this function.
|
||||
- The function will return `false` if the input shares array is empty or if there is an error during the retrieval process.
|
||||
|
||||
# verifyShamirsSecret Function Documentation
|
||||
|
||||
The `verifyShamirsSecret` function is used to verify the provided Shamir's Secret Sharing shares against a given string.
|
||||
|
||||
## Parameters
|
||||
- **sharesArray** (Array): An array containing Shamir's Secret Sharing shares to be verified.
|
||||
- **str** (String): The original string to be verified against the retrieved secret.
|
||||
|
||||
## Return Value
|
||||
- (Boolean or null): `true` if the shares verify the provided string, `false` otherwise. Returns `null` if the input string is not provided.
|
||||
|
||||
## Dependencies
|
||||
- **retrieveShamirSecret**: Function to retrieve the original secret by combining shares using Shamir's Secret Sharing algorithm.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const sharesArray = ["share1", "share2", "share3"];
|
||||
const originalString = "original_string_here";
|
||||
|
||||
const verificationResult = floCrypto.verifyShamirsSecret(sharesArray, originalString);
|
||||
console.log("Shares Verification Result:", verificationResult);
|
||||
```
|
||||
## Notes
|
||||
- Ensure that the necessary dependency (`retrieveShamirSecret`) is available and properly implemented before using this function.
|
||||
- The function will return `null` if the input string is not provided.
|
||||
|
||||
# validateASCII Function Documentation
|
||||
|
||||
The `validateASCII` function is used to validate a string to ensure it contains only ASCII characters within the printable range (32 to 127) by default.
|
||||
|
||||
## Parameters
|
||||
- **string** (String): The input string to be validated.
|
||||
- **bool** (Boolean, optional): If `true`, the function returns `true` if the entire string contains only printable ASCII characters; if `false`, it returns an object containing information about invalid characters and their positions. Default is `true`.
|
||||
|
||||
## Return Value
|
||||
- (Boolean or Object or null): Returns `true` if the input string contains only printable ASCII characters. Returns an object containing information about invalid characters and their positions if `bool` is set to `false`. Returns `null` if the input is not a string.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const inputString = "ASCII_string_here";
|
||||
|
||||
// Validate ASCII characters
|
||||
const isValid = floCrypto.validateASCII(inputString);
|
||||
console.log("Is Valid ASCII?", isValid);
|
||||
|
||||
// Validate ASCII characters and get invalid characters with their positions
|
||||
const invalidChars = floCrypto.validateASCII(inputString, false);
|
||||
console.log("Invalid Characters:", invalidChars);
|
||||
```
|
||||
|
||||
## Notes
|
||||
- If `bool` is set to `true`, the function will return `true` if the input string contains only printable ASCII characters; otherwise, it will return `false`.
|
||||
- If `bool` is set to `false`, the function will return an object containing information about invalid characters and their positions. If there are no invalid characters, it will return an empty object.
|
||||
- The function will return `null` if the input is not a string.
|
||||
|
||||
# convertToASCII Function Documentation
|
||||
|
||||
The `convertToASCII` function is used to convert a string to ASCII characters using specified conversion modes.
|
||||
|
||||
## Parameters
|
||||
- **string** (String): The input string to be converted.
|
||||
- **mode** (String, optional): The conversion mode. Available options are 'hard-unicode', 'soft-unicode', 'hard-remove', and 'soft-remove'. Default is 'soft-remove'.
|
||||
|
||||
## Return Value
|
||||
- (String or null): The converted string. Returns `null` if the input is not a string.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const inputString = "input_string_here";
|
||||
|
||||
// Convert to ASCII characters using soft-remove mode
|
||||
const convertedString = floCrypto.convertToASCII(inputString);
|
||||
console.log("Converted String:", convertedString);
|
||||
```
|
||||
## Notes
|
||||
- Available conversion modes:
|
||||
- 'hard-unicode': Converts characters to Unicode escape sequences (e.g., `\uXXXX`).
|
||||
- 'soft-unicode': Converts characters to Unicode escape sequences if available in `ascii_alternatives` list; otherwise, uses regular Unicode escape sequences.
|
||||
- 'hard-remove': Removes non-ASCII characters from the string.
|
||||
- 'soft-remove': Replaces non-ASCII characters with their alternatives from `ascii_alternatives` list if available; otherwise, removes them.
|
||||
- Ensure that the necessary dependencies (`validateASCII`) and the `ascii_alternatives` list are available and properly implemented before using this function.
|
||||
- The function will return `null` if the input is not a string.
|
||||
|
||||
# revertUnicode Function Documentation
|
||||
|
||||
The `revertUnicode` function is used to revert Unicode escape sequences (e.g., `\uXXXX`) back to their corresponding characters in a given string.
|
||||
|
||||
## Parameters
|
||||
- **string** (String): The input string containing Unicode escape sequences to be reverted.
|
||||
|
||||
## Return Value
|
||||
- (String): The string with reverted Unicode escape sequences.
|
||||
|
||||
## Example Usage
|
||||
```javascript
|
||||
const unicodeString = "\\u0048\\u0065\\u006c\\u006c\\u006f";
|
||||
const revertedString = floCrypto.revertUnicode(unicodeString);
|
||||
console.log("Reverted String:", revertedString);
|
||||
```
|
||||
344
messenger/docs/floDapps.md
Normal file
@ -0,0 +1,344 @@
|
||||
## readSupernodeListFromAPI Function
|
||||
|
||||
The `readSupernodeListFromAPI` function is a startup function used to fetch and update the list of supernodes from the Flo blockchain API. It reads supernode data from the blockchain, processes the data, and updates the list of supernodes stored in the local database.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. Checks if cloud functionality is enabled for the application. If not, it resolves with "No cloud for this app".
|
||||
2. Reads the last transaction count or transaction ID from the local database for the cloud storage.
|
||||
3. Fetches supernode data from the Flo blockchain using the specified query options.
|
||||
4. Compares the fetched data with the local list of supernodes, updating or removing nodes as necessary.
|
||||
5. Writes the updated supernode list and the last transaction information to the local database.
|
||||
6. Initializes the `floCloudAPI` with the updated supernode list.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message upon successful loading and initialization of the supernode list.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- This function is typically called during the startup process of the application to ensure that the local list of supernodes is up-to-date.
|
||||
|
||||
- In this example, the readSupernodeListFromAPI function updates the local list of supernodes based on the data fetched from the Flo blockchain. It resolves with a success message after successfully loading and initializing the supernode list.
|
||||
|
||||
```javascript
|
||||
startUpFunctions.push(function readSupernodeListFromAPI() {
|
||||
// Function logic as described above
|
||||
// ...
|
||||
return new Promise((resolve, reject) => {
|
||||
// Resolving or rejecting based on success or error
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## readAppConfigFromAPI Function
|
||||
|
||||
The `readAppConfigFromAPI` function is a startup function used to fetch and update the application configuration from the Flo blockchain API. It reads application configuration data from the blockchain, processes the data, and updates the local database with sub-admins, trusted IDs, and settings.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. Checks if application configuration functionality is enabled for the application. If not, it resolves with "No configs for this app".
|
||||
2. Reads the last transaction count or transaction ID for the specified application and admin ID from the local database.
|
||||
3. Fetches application configuration data from the Flo blockchain using the specified query options.
|
||||
4. Processes the fetched data, updating sub-admins, trusted IDs, and settings in the local database.
|
||||
5. Resolves with a success message after successfully reading the app configuration from the blockchain.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message upon successful reading and updating of the application configuration.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- This function is typically called during the startup process of the application to ensure that the local application configuration is synchronized with the data on the blockchain.
|
||||
|
||||
- In this example, the readAppConfigFromAPI function updates the local application configuration based on the data fetched from the Flo blockchain. It resolves with a success message after successfully reading and updating the application configuration.
|
||||
|
||||
```javascript
|
||||
startUpFunctions.push(function readAppConfigFromAPI() {
|
||||
// Function logic as described above
|
||||
// ...
|
||||
return new Promise((resolve, reject) => {
|
||||
// Resolving or rejecting based on success or error
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## loadDataFromAppIDB Function
|
||||
|
||||
The `loadDataFromAppIDB` function is a startup function used to load data from the IndexedDB (IDB) storage of the application. It reads specific data sets (`appObjects`, `generalData`, and `lastVC`) from the local IndexedDB and stores them in the respective global variables of the `floGlobals` object.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. Checks if cloud functionality is enabled for the application. If not, it resolves with "No cloud for this app".
|
||||
2. Defines an array `loadData` containing the names of the data sets to be loaded from IndexedDB (`appObjects`, `generalData`, `lastVC`).
|
||||
3. Iterates through the `loadData` array and creates an array of promises to read all data from each data set in IndexedDB.
|
||||
4. Uses `Promise.all` to wait for all data reading operations to complete.
|
||||
5. Assigns the retrieved data to the respective properties of the `floGlobals` object.
|
||||
6. Resolves with a success message after successfully loading data from IndexedDB.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message upon successful loading of data from IndexedDB.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- This function is typically called during the startup process of the application to load essential data sets from the local IndexedDB storage.
|
||||
- In this example, the loadDataFromAppIDB function loads specific data sets from the IndexedDB storage and assigns them to global variables within the floGlobals object. It resolves with a success message after successfully loading the data.
|
||||
|
||||
```javascript
|
||||
startUpFunctions.push(function loadDataFromAppIDB() {
|
||||
// Function logic as described above
|
||||
// ...
|
||||
return new Promise((resolve, reject) => {
|
||||
// Resolving or rejecting based on success or error
|
||||
// ...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## callStartUpFunction Function
|
||||
|
||||
The `callStartUpFunction` function is a utility function used for executing startup functions sequentially. It takes an index `i` as an argument, which represents the index of the startup function to be executed. This function returns a Promise that resolves when the specified startup function is successfully executed or rejects if there is an error during execution.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **i**: Index of the startup function to be executed.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. Takes an index `i` as an argument representing the index of the startup function to be executed.
|
||||
2. Executes the startup function at the specified index.
|
||||
3. If the startup function execution is successful, it increments the `callStartUpFunction.completed` counter and logs the result as a successful startup function execution.
|
||||
4. If the startup function encounters an error, it increments the `callStartUpFunction.failed` counter and logs the error message as a failed startup function execution.
|
||||
5. Returns a Promise that resolves with `true` when the startup function is successfully executed and rejects with `false` if there is an error during execution.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with `true` if the startup function is executed successfully and rejects with `false` if there is an error during execution.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- The `callStartUpFunction` function is typically used in a loop to sequentially execute multiple startup functions.
|
||||
- In this example, the callStartUpFunction function is used to execute startup functions sequentially and handle success and error cases for each function execution.
|
||||
|
||||
```javascript
|
||||
const callStartUpFunction = i => new Promise((resolve, reject) => {
|
||||
startUpFunctions[i]().then(result => {
|
||||
callStartUpFunction.completed += 1;
|
||||
startUpLog(true, `${result}\nCompleted ${callStartUpFunction.completed}/${callStartUpFunction.total} Startup functions`)
|
||||
resolve(true)
|
||||
}).catch(error => {
|
||||
callStartUpFunction.failed += 1;
|
||||
startUpLog(false, `${error}\nFailed ${callStartUpFunction.failed}/${callStartUpFunction.total} Startup functions`)
|
||||
reject(false)
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
## midStartUp Function
|
||||
|
||||
The `midStartUp` function is a utility function used for executing a middle-stage startup function. It checks if the `_midFunction` variable contains a function reference. If `_midFunction` is a function, it executes `_midFunction()`, and the function resolves with a success message. If `_midFunction` is not a function, the function resolves with a message indicating that there is no middle-stage startup function.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. Checks if `_midFunction` is a function.
|
||||
2. If `_midFunction` is a function, it executes `_midFunction()` and resolves with a success message.
|
||||
3. If `_midFunction` is not a function, it resolves with a message indicating that there is no middle-stage startup function.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message if the middle-stage startup function is executed successfully.
|
||||
- Returns a Promise that resolves with a message indicating that there is no middle-stage startup function if `_midFunction` is not a function.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- The `midStartUp` function is typically used to execute a specific function during the middle stage of the application startup process.
|
||||
|
||||
```javascript
|
||||
const midStartUp = () => new Promise((res, rej) => {
|
||||
if (_midFunction instanceof Function) {
|
||||
_midFunction()
|
||||
.then(r => res("Mid startup function completed"))
|
||||
.catch(e => rej("Mid startup function failed"))
|
||||
} else
|
||||
res("No mid startup function")
|
||||
});
|
||||
```
|
||||
|
||||
## floDapps.launchStartUp Function
|
||||
|
||||
The `floDapps.launchStartUp` function is a central startup function responsible for initializing and launching various components of the application. It ensures the setup of the IndexedDB, executes a series of startup functions, and handles the initialization of user-related databases and credentials.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **IndexedDB Initialization:** The function initializes the IndexedDB for the application.
|
||||
|
||||
2. **Startup Functions Execution:** Executes a list of startup functions (`startUpFunctions`) using the `callStartUpFunction` function. It logs the progress of the startup functions, indicating completion or failure.
|
||||
|
||||
3. **Middle-Stage Startup:** Executes the `midStartUp` function, if available. This function typically handles middle-stage startup tasks.
|
||||
|
||||
4. **User Database and Credentials:** Calls `getCredentials` to retrieve user credentials, initializes the user database using `initUserDB`, and loads user data using `loadUserDB`.
|
||||
|
||||
5. **Promise Handling:** The function uses promises to manage the asynchronous tasks and resolves with a success message if all tasks are completed successfully. If any task fails, it rejects with an error message.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with a success message when the entire startup process is finished successfully.
|
||||
- Returns a Promise that rejects with an error message if any part of the startup process fails.
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
floDapps.launchStartUp()
|
||||
.then(result => {
|
||||
console.log(result); // Output: 'App Startup finished successful'
|
||||
// Further application logic after successful startup
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error); // Output: 'App Startup failed'
|
||||
// Handle error and provide user feedback
|
||||
});
|
||||
```
|
||||
|
||||
## floDapps.manageAppConfig Function
|
||||
|
||||
The `floDapps.manageAppConfig` function allows the administrator to manage the application configuration. It enables adding or removing sub-administrators and updating application-specific settings.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **adminPrivKey**: Private key of the administrator for authentication.
|
||||
- **addList**: An array of FloIDs to be added as sub-administrators. (Optional)
|
||||
- **rmList**: An array of FloIDs to be removed from sub-administrators. (Optional)
|
||||
- **settings**: An object containing application-specific settings. (Optional)
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Parameter Validation:** Validates the input parameters. If no changes are provided (addList, rmList, or settings), the function rejects with a message indicating no configuration change.
|
||||
|
||||
2. **FloData Preparation:** Prepares the `floData` object containing the application configuration changes, including added sub-administrators, removed sub-administrators, and updated settings.
|
||||
|
||||
3. **Admin Privilege Check:** Checks if the provided `adminPrivKey` matches the default admin ID (`DEFAULT.adminID`). If not, it rejects with an "Access Denied" error.
|
||||
|
||||
4. **Blockchain Data Writing:** Uses `floBlockchainAPI.writeData` to write the updated `floData` to the Flo blockchain. Resolves with a success message and the transaction result if successful. Rejects with an error message if the write operation fails.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with an array containing the success message ("Updated App Configuration") and the transaction result from the blockchain write operation.
|
||||
- Returns a Promise that rejects with an error message if any of the following conditions are met:
|
||||
- No configuration change is provided.
|
||||
- The provided `adminPrivKey` does not match the default admin ID (`DEFAULT.adminID`).
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const adminPrivKey = 'your_admin_private_key';
|
||||
const addList = ['sub_admin_floID_1', 'sub_admin_floID_2'];
|
||||
const rmList = ['sub_admin_floID_3'];
|
||||
const settings = {
|
||||
key: 'value',
|
||||
// additional application-specific settings
|
||||
};
|
||||
|
||||
floDapps.manageAppConfig(adminPrivKey, addList, rmList, settings)
|
||||
.then(result => {
|
||||
console.log(result[0]); // Output: 'Updated App Configuration'
|
||||
console.log(result[1]); // Output: Transaction result from blockchain write operation
|
||||
// Further application logic after successful configuration update
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error); // Output: Error message if configuration update fails
|
||||
// Handle error and provide user feedback
|
||||
});
|
||||
```
|
||||
|
||||
## floDapps.manageAppTrustedIDs Function
|
||||
|
||||
The `floDapps.manageAppTrustedIDs` function allows the administrator to manage trusted IDs for the application. It enables adding or removing trusted IDs.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **adminPrivKey**: Private key of the administrator for authentication.
|
||||
- **addList**: An array of FloIDs to be added as trusted IDs. (Optional)
|
||||
- **rmList**: An array of FloIDs to be removed from trusted IDs. (Optional)
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Parameter Validation:** Validates the input parameters. If no changes are provided (addList or rmList), the function rejects with a message indicating no change in the list.
|
||||
|
||||
2. **FloData Preparation:** Prepares the `floData` object containing the trusted ID changes, including added trusted IDs and removed trusted IDs.
|
||||
|
||||
3. **Admin Privilege Check:** Checks if the provided `adminPrivKey` matches the default admin ID (`DEFAULT.adminID`). If not, it rejects with an "Access Denied" error.
|
||||
|
||||
4. **Blockchain Data Writing:** Uses `floBlockchainAPI.writeData` to write the updated `floData` to the Flo blockchain. Resolves with a success message and the transaction result if successful. Rejects with an error message if the write operation fails.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns a Promise that resolves with an array containing the success message ("Updated App Configuration") and the transaction result from the blockchain write operation.
|
||||
- Returns a Promise that rejects with an error message if no change is provided in the list or if the provided `adminPrivKey` does not match the default admin ID (`DEFAULT.adminID`).
|
||||
|
||||
### Example Usage
|
||||
|
||||
```javascript
|
||||
const adminPrivKey = 'your_admin_private_key';
|
||||
const addList = ['trusted_floID_1', 'trusted_floID_2'];
|
||||
const rmList = ['trusted_floID_3'];
|
||||
|
||||
floDapps.manageAppTrustedIDs(adminPrivKey, addList, rmList)
|
||||
.then(result => {
|
||||
console.log(result[0]); // Output: 'Updated App Configuration'
|
||||
console.log(result[1]); // Output: Transaction result from blockchain write operation
|
||||
// Further application logic after successful trusted IDs update
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error); // Output: Error message if trusted IDs update fails
|
||||
// Handle error and provide user feedback
|
||||
});
|
||||
```
|
||||
|
||||
## floDapps.getNextGeneralData Function
|
||||
|
||||
The `floDapps.getNextGeneralData` function allows retrieving the next set of general data based on the specified type and vector clock. It filters the data based on the provided type, vector clock, and optional comment.
|
||||
|
||||
### Function Parameters
|
||||
|
||||
- **type**: The type of general data to retrieve.
|
||||
- **vectorClock**: The vector clock to start filtering the data from. If not provided, it defaults to the stored vector clock or '0'.
|
||||
- **options**: An optional object containing additional filtering options.
|
||||
- **comment**: Optional. If provided, filters the data based on the specified comment.
|
||||
- **decrypt**: Optional. Decryption key for decrypting encrypted data. If `true`, it uses the user's private key for decryption.
|
||||
|
||||
### Function Logic
|
||||
|
||||
1. **Filtering by Type and Vector Clock:** Retrieves general data of the specified type and greater vector clocks than the provided vector clock.
|
||||
|
||||
2. **Filtering by Comment (Optional):** If the `comment` option is provided, further filters the data based on the specified comment.
|
||||
|
||||
3. **Data Decryption (Optional):** If the `decrypt` option is provided, decrypts the data using the specified decryption key. If `true`, it uses the user's private key for decryption.
|
||||
|
||||
4. **Updating Stored Vector Clock:** Updates the stored vector clock for the specified type with the latest vector clock from the retrieved data.
|
||||
|
||||
5. **Return Filtered Data:** Returns the filtered general data as an object.
|
||||
|
||||
### Return Value
|
||||
|
||||
- Returns an object containing the filtered general data based on the specified type, vector clock, comment, and decryption key.
|
||||
|
||||
### Example Usage
|
||||
|
||||
- In this example, floDapps.getNextGeneralData is used to retrieve and filter the next set of general data for the specified type, vector clock, comment, and decryption key.
|
||||
|
||||
```javascript
|
||||
const type = 'exampleType';
|
||||
const vectorClock = '12345';
|
||||
const options = {
|
||||
comment: 'exampleComment',
|
||||
decrypt: true
|
||||
};
|
||||
|
||||
const filteredData = floDapps.getNextGeneralData(type, vectorClock, options);
|
||||
console.log(filteredData);
|
||||
// Output: Filtered general data based on the specified type, vector clock, comment, and decryption key
|
||||
```
|
||||
|
||||
307
messenger/docs/floTokenAPI.md
Normal file
@ -0,0 +1,307 @@
|
||||
## `fetch_api(apicall)`
|
||||
|
||||
This utility function performs a fetch request to the specified API endpoint using the provided `apicall`. It returns a promise that resolves with the parsed JSON response if the request is successful, and rejects with the response object or an error if the request fails.
|
||||
|
||||
### Parameters:
|
||||
|
||||
- **`apicall`** (string): The API endpoint to be called.
|
||||
|
||||
### Return Value:
|
||||
|
||||
A Promise that resolves with the parsed JSON response if the request is successful, and rejects with the response object or an error if the request fails.
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```javascript
|
||||
const apiEndpoint = "exampleEndpoint";
|
||||
fetch_api(apiEndpoint)
|
||||
.then(data => {
|
||||
console.log("API Response:", data);
|
||||
// Handle the API response data here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("API Error:", error);
|
||||
// Handle API errors here
|
||||
});
|
||||
```
|
||||
|
||||
## `getBalance(floID, token = DEFAULT.currency)`
|
||||
|
||||
This function retrieves the balance of the specified FLO address (`floID`) for the given `token`. It returns a promise that resolves with the balance value if the request is successful, and rejects with an error if the request fails.
|
||||
|
||||
### Parameters:
|
||||
|
||||
- **`floID`** (string): The FLO address for which the balance needs to be retrieved.
|
||||
- **`token`** (string, optional): The token for which the balance is retrieved. Defaults to `DEFAULT.currency`.
|
||||
|
||||
### Return Value:
|
||||
|
||||
A Promise that resolves with the balance value if the request is successful, and rejects with an error if the request fails.
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```javascript
|
||||
const floID = "exampleFLOAddress";
|
||||
const token = "exampleToken";
|
||||
|
||||
getBalance(floID, token)
|
||||
.then(balance => {
|
||||
console.log("Balance:", balance);
|
||||
// Handle the balance data here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Balance Retrieval Error:", error);
|
||||
// Handle balance retrieval errors here
|
||||
});
|
||||
```
|
||||
|
||||
## `getTx(txID)`
|
||||
|
||||
This function retrieves transaction details for the specified transaction ID (`txID`). It returns a promise that resolves with the transaction details if the request is successful and rejects with an error if the request fails.
|
||||
|
||||
### Parameters:
|
||||
|
||||
- **`txID`** (string): The unique identifier of the transaction for which details are retrieved.
|
||||
|
||||
### Return Value:
|
||||
|
||||
A Promise that resolves with the transaction details if the request is successful, and rejects with an error if the request fails.
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```javascript
|
||||
const txID = "exampleTxID";
|
||||
|
||||
getTx(txID)
|
||||
.then(transactionDetails => {
|
||||
console.log("Transaction Details:", transactionDetails);
|
||||
// Handle the transaction details here
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Transaction Retrieval Error:", error);
|
||||
// Handle transaction retrieval errors here
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## `tokenAPI.sendToken(privKey, amount, receiverID, message = "", token = DEFAULT.currency, options = {})`
|
||||
|
||||
This function allows sending a specified amount of tokens from the sender to a receiver. It takes the following parameters:
|
||||
|
||||
- **`privKey`** (string): Private key of the sender.
|
||||
- **`amount`** (number): Amount of tokens to be sent.
|
||||
- **`receiverID`** (string): FLO ID of the receiver.
|
||||
- **`message`** (string, optional): Additional message to attach to the transaction (default is an empty string).
|
||||
- **`token`** (string, optional): Token type to be sent (default is `DEFAULT.currency`).
|
||||
- **`options`** (object, optional): Additional options for the transaction (default is an empty object).
|
||||
|
||||
### Parameters:
|
||||
|
||||
- **`privKey`** (string): Private key of the sender.
|
||||
- **`amount`** (number): Amount of tokens to be sent.
|
||||
- **`receiverID`** (string): FLO ID of the receiver.
|
||||
- **`message`** (string, optional): Additional message to attach to the transaction (default is an empty string).
|
||||
- **`token`** (string, optional): Token type to be sent (default is `DEFAULT.currency`).
|
||||
- **`options`** (object, optional): Additional options for the transaction (default is an empty object).
|
||||
|
||||
### Return Value:
|
||||
|
||||
A Promise that resolves to the transaction ID (txid) if the transaction is successful. If there are any errors, the Promise is rejected with an error message.
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```javascript
|
||||
const privKey = "senderPrivateKey";
|
||||
const amount = 100;
|
||||
const receiverID = "receiverFLOID";
|
||||
const message = "Payment for services";
|
||||
const token = "exampleToken";
|
||||
const options = { fee: 0.1, someOption: "value" };
|
||||
|
||||
tokenAPI.sendToken(privKey, amount, receiverID, message, token, options)
|
||||
.then(txid => {
|
||||
console.log("Transaction ID:", txid);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## `sendTokens_raw(privKey, receiverID, token, amount, utxo, vout, scriptPubKey)`
|
||||
|
||||
This function is a low-level utility used to send tokens from a specific Unspent Transaction Output (UTXO) to a receiver. It takes the following parameters:
|
||||
|
||||
- **`privKey`** (string): Private key of the sender.
|
||||
- **`receiverID`** (string): FLO ID of the receiver.
|
||||
- **`token`** (string): Token type to be sent.
|
||||
- **`amount`** (number): Amount of tokens to be sent.
|
||||
- **`utxo`** (string): UTXO (Unspent Transaction Output) hash.
|
||||
- **`vout`** (number): Vout (index) of the UTXO in the transaction.
|
||||
- **`scriptPubKey`** (string): Script Public Key associated with the UTXO.
|
||||
|
||||
### Parameters:
|
||||
|
||||
- **`privKey`** (string): Private key of the sender.
|
||||
- **`receiverID`** (string): FLO ID of the receiver.
|
||||
- **`token`** (string): Token type to be sent.
|
||||
- **`amount`** (number): Amount of tokens to be sent.
|
||||
- **`utxo`** (string): UTXO (Unspent Transaction Output) hash.
|
||||
- **`vout`** (number): Vout (index) of the UTXO in the transaction.
|
||||
- **`scriptPubKey`** (string): Script Public Key associated with the UTXO.
|
||||
|
||||
### Return Value:
|
||||
|
||||
A Promise that resolves to an array containing the receiver's FLO ID and the transaction ID (txid) if the transaction is successful. If there are any errors, the Promise is rejected with an array containing the receiver's FLO ID and the error message.
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```javascript
|
||||
const privKey = "senderPrivateKey";
|
||||
const receiverID = "receiverFLOID";
|
||||
const token = "exampleToken";
|
||||
const amount = 100;
|
||||
const utxo = "utxoHash";
|
||||
const vout = 0;
|
||||
const scriptPubKey = "scriptPublicKey";
|
||||
|
||||
sendTokens_raw(privKey, receiverID, token, amount, utxo, vout, scriptPubKey)
|
||||
.then(([receiver, txid]) => {
|
||||
console.log(`Tokens sent to ${receiver}. Transaction ID: ${txid}`);
|
||||
})
|
||||
.catch(([receiver, error]) => {
|
||||
console.error(`Failed to send tokens to ${receiver}. Error: ${error}`);
|
||||
});
|
||||
```
|
||||
|
||||
## `tokenAPI.bulkTransferTokens(sender, privKey, token, receivers)`
|
||||
|
||||
This function facilitates bulk token transfers from a sender to multiple receivers. It takes the following parameters:
|
||||
|
||||
- **`sender`** (string): FLO ID of the sender.
|
||||
- **`privKey`** (string): Private key corresponding to the sender's FLO ID.
|
||||
- **`token`** (string): Token type to be transferred.
|
||||
- **`receivers`** (object): An object containing receiver addresses as keys and the corresponding token amounts as values.
|
||||
|
||||
### Parameters:
|
||||
|
||||
- **`sender`** (string): FLO ID of the sender.
|
||||
- **`privKey`** (string): Private key corresponding to the sender's FLO ID.
|
||||
- **`token`** (string): Token type to be transferred.
|
||||
- **`receivers`** (object): An object representing receivers and their corresponding token amounts. The format of the object should be `{ receiver1: amount1, receiver2: amount2, ... }`.
|
||||
|
||||
### Return Value:
|
||||
|
||||
A Promise that resolves to an object containing successful transactions and any failed transactions. The resolved object has the following format:
|
||||
|
||||
```javascript
|
||||
{
|
||||
success: {
|
||||
receiver1: txid1,
|
||||
receiver2: txid2,
|
||||
// ... other successful transactions
|
||||
},
|
||||
failed: {
|
||||
receiverX: "error message",
|
||||
// ... other failed transactions
|
||||
}
|
||||
}
|
||||
// If the function encounters any invalid inputs or errors during the process, it rejects the Promise with an error message.
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
```javascript
|
||||
const sender = "senderFLOID";
|
||||
const privKey = "senderPrivateKey";
|
||||
const token = "exampleToken";
|
||||
const receivers = {
|
||||
"receiver1FLOID": 100,
|
||||
"receiver2FLOID": 200,
|
||||
// ... other receivers and amounts
|
||||
};
|
||||
|
||||
tokenAPI.bulkTransferTokens(sender, privKey, token, receivers)
|
||||
.then(result => {
|
||||
console.log("Successful Transactions:", result.success);
|
||||
console.log("Failed Transactions:", result.failed);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## `tokenAPI.getAllTxs(floID, token = DEFAULT.currency)`
|
||||
|
||||
This function retrieves all transactions associated with a specific FLO ID and token. It takes the following parameters:
|
||||
|
||||
- **`floID`** (string): FLO ID for which transactions need to be retrieved.
|
||||
- **`token`** (string, optional): Token type for which transactions need to be retrieved. Defaults to the DEFAULT currency.
|
||||
|
||||
### Parameters:
|
||||
|
||||
- **`floID`** (string): FLO ID for which transactions need to be retrieved.
|
||||
- **`token`** (string, optional): Token type for which transactions need to be retrieved. Defaults to the DEFAULT currency.
|
||||
|
||||
### Return Value:
|
||||
|
||||
A Promise that resolves to the result of the API call, containing the transactions associated with the specified FLO ID and token.
|
||||
|
||||
### Example Usage:
|
||||
|
||||
```javascript
|
||||
const floID = "exampleFLOID";
|
||||
const token = "exampleToken";
|
||||
|
||||
tokenAPI.getAllTxs(floID, token)
|
||||
.then(transactions => {
|
||||
console.log("All transactions for FLO ID:", floID);
|
||||
console.log(transactions);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching transactions:", error);
|
||||
});
|
||||
```
|
||||
|
||||
## `util.parseTxData(txData)`
|
||||
|
||||
This utility function parses transaction data and extracts relevant information such as sender, receiver, parsed FLO data, and timestamp.
|
||||
|
||||
### Parameters:
|
||||
|
||||
- **`txData`** (object): Transaction data object to be parsed.
|
||||
|
||||
### Return Value:
|
||||
|
||||
An object containing the parsed transaction data with the following properties:
|
||||
|
||||
- **`sender`** (string): Address of the sender.
|
||||
- **`receiver`** (string): Address of the receiver.
|
||||
- **`parsedFloData`** (object): Parsed FLO data extracted from the transaction.
|
||||
- **`time`** (number): Timestamp of the transaction in Unix epoch format.
|
||||
|
||||
### Example Usage:
|
||||
|
||||
- In this example, the util.parseTxData function is used to parse the provided txData object. It extracts sender, receiver, parsed FLO data, and the transaction timestamp. The function returns an object containing the parsed data for further processing or display.
|
||||
|
||||
```javascript
|
||||
const txData = {
|
||||
parsedFloData: {
|
||||
key1: "value1",
|
||||
key2: "value2"
|
||||
// ...other parsed FLO data fields
|
||||
},
|
||||
transactionDetails: {
|
||||
vin: [{ addr: "senderAddress" }],
|
||||
vout: [{ scriptPubKey: { addresses: ["receiverAddress"] } }],
|
||||
time: 1633442400 // Unix timestamp
|
||||
// ...other transaction details
|
||||
}
|
||||
};
|
||||
|
||||
const parsedData = util.parseTxData(txData);
|
||||
console.log(parsedData);
|
||||
```
|
||||
|
||||
|
||||
1261
messenger/docs/functions.md
Normal file
9
messenger/docs/product-overview.md
Normal file
@ -0,0 +1,9 @@
|
||||
# FLO Messenger
|
||||
|
||||
• Messenger is a blockchain-based decentralized messaging app that uses Bitcoin or FLO blockchain addresses as user identities. Instead of a centralized server, messages are encrypted and stored in the users' browsers.
|
||||
• Bitcoin or FLO blockchain addresses can communicate with each other using a messaging interface
|
||||
• Messenger comes with "Multisig" where users can create multi-sig addresses and using a messaging interface make transactions on the multi-sig.
|
||||
• Switching browsers or devices won't bring back old messages. Remember to back up and import to access your messages in the new browser/device. That's the security of Messenger.
|
||||
|
||||
#### Note:
|
||||
Do not lose the private key. Copy and save it securely. Once a private key is lost, it cannot be recovered
|
||||
41
messenger/docs/render.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!-- A simple example illustrating render logic we have extensively used in this application -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dynamic List with uhtml</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
||||
<script>
|
||||
const { html, render : renderElem } = uhtml;
|
||||
|
||||
// Data to be displayed
|
||||
const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
|
||||
|
||||
// Function to render items using uhtml
|
||||
function renderItems(items) {
|
||||
return html`
|
||||
<ul>
|
||||
${items.map(item => html`<li>${item}</li>`)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
// Render items into the 'app' element
|
||||
const appElement = document.getElementById('app');
|
||||
|
||||
|
||||
// render is defined and exported from uhtml. html is also define and exported from uhtml
|
||||
// Create html first, and then render it
|
||||
// Creates actual HTML when render is applied to something that creates html
|
||||
renderElem(appElement, renderItems(items));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
595
messenger/docs/technical-architecture.md
Normal file
@ -0,0 +1,595 @@
|
||||
# Messenger Architecture
|
||||
|
||||
### Standard Operations Configuration
|
||||
|
||||
|
||||
### `floGlobals Object`
|
||||
- floGlobals is a standard object used in Standard Operations based App
|
||||
- floGlobals.adminID is the blockchain based address whose commands all browser clients will follow. Currently it is `floGlobals.adminID: "FMRsefPydWznGWneLqi4ABeQAJeFvtS3aQ"`
|
||||
- floGlobals.name is "messenger" indicating name of application
|
||||
- floGlobals.idInterval is the time interval used to switch display between your Bitcoin Address used to log-in, and FLO address
|
||||
- floGlobals.subadmins are used to do regular access control in the mesenger app. Currently there are none set
|
||||
|
||||
### `processData` object
|
||||
- Understanding `processData` object is key to understand messenger product
|
||||
- Overview of processDAta: `processData` is the processing part of messenger. There are kinds of `processData` activities:
|
||||
1. `processData.direct` - This is used in processing individual mails and messages
|
||||
2. `processData.group(groupID)` - This is used in processing group messages
|
||||
3. `processData.pipeline[model](pipeID);` - This is used in processing any pipeline events foor
|
||||
|
||||
|
||||
### Product Pipelines
|
||||
1. The core feature of the product is pipelines. A pipeline is created by invloking inbuilt models
|
||||
2. Right now we have models for Multisig creation for Bitcoin and FLO Multisigs.
|
||||
3. What is pipeline ?
|
||||
• It has an ID
|
||||
• It has model like TYPE_BTC_MULTISIG
|
||||
• It has members like different Bitcoin IDs or FLO IDs
|
||||
• It has an encryption key unique to the pipeline, and known just to members of that pipeline
|
||||
4. A pipeline sends custom messages defined as per a model to an attached group
|
||||
5. Pipeline ID could be a recipient of a message. Then every Bitcoin or FLO Address will get the message with the action needed for that pipeline
|
||||
|
||||
|
||||
|
||||
# Let us specify all DATA DEFINITIONS used in Messenger Application first
|
||||
## The master object encapsulates all the functionalities
|
||||
|
||||
```
|
||||
var obj = {
|
||||
messages: {},
|
||||
mails: {},
|
||||
marked: {},
|
||||
chats: {},
|
||||
groups: {},
|
||||
gkeys: {},
|
||||
blocked: {},
|
||||
pipeline: {},
|
||||
request_sent: {},
|
||||
request_received: {},
|
||||
response_sent: {},
|
||||
response_received: {},
|
||||
flodata: {},
|
||||
appendix: {},
|
||||
userSettings: {},
|
||||
multisigLabels: {}
|
||||
}
|
||||
```
|
||||
|
||||
## Internal message `types` defined in application for usage in sendApplicationData
|
||||
|
||||
### The key to understand messenger application is to start with message types first
|
||||
|
||||
For sendraw function, `function sendRaw(message, recipient, type, encrypt = null, comment = undefined)` following types are defined
|
||||
- "MESSAGE"
|
||||
- "MAIL"
|
||||
- "REQUEST"
|
||||
- "RESPONSE"
|
||||
- "UP_NAME"
|
||||
- "UP_DESCRIPTION"
|
||||
- "CREATE GROUP"
|
||||
- "ADD_MEMBERS"
|
||||
- "RM_MEMBERS"
|
||||
- "REVOKE_KEY"
|
||||
- "GROUP_MSG"
|
||||
- "TRANSACTION"
|
||||
- "BROADCAST"
|
||||
- "CREATE_PIPELINE"
|
||||
|
||||
#### function `sendRaw(message, recipient, type, encrypt = null, comment = undefined)` packages the input parameters into `floCloudAPI.sendApplicationData(message, type, options)`
|
||||
|
||||
- options contain options.receiverID, and options.comment
|
||||
|
||||
```options = {
|
||||
receiverID: recipient,
|
||||
}
|
||||
if (comment)
|
||||
options.comment = comment
|
||||
```
|
||||
### Message Payload: When a message is sent it has a payload of vectorclock and data.
|
||||
|
||||
```
|
||||
sendRaw(message, receiver, "MESSAGE").then(result => {
|
||||
let vc = result.vectorClock;
|
||||
let data = {
|
||||
floID: receiver,
|
||||
time: result.time,
|
||||
category: 'sent',
|
||||
message: encrypt(message)
|
||||
}
|
||||
```
|
||||
|
||||
### Description of Mail Object
|
||||
|
||||
```
|
||||
let mail = {
|
||||
subject: subject,
|
||||
content: content,
|
||||
ref: Date.now() + floCrypto.randString(8, true),
|
||||
prev: prev
|
||||
}
|
||||
|
||||
let promises = recipients.map(r => sendRaw(JSON.stringify(mail), r, "MAIL"))
|
||||
|
||||
```
|
||||
|
||||
### SendRequest types
|
||||
- "PUBLIC_KEY"
|
||||
- This is used in sendResponse as well
|
||||
|
||||
```
|
||||
sendRequest(receiver, "PUBLIC_KEY", message, false);
|
||||
|
||||
```
|
||||
|
||||
### Understanding GroupInfo
|
||||
- Contains groupInfo.eKey, groupInfo.admin, groupInfo.members, groupInfo.disabled, groupInfo.name
|
||||
|
||||
### Understanding DataList
|
||||
- dataList = ["messages", "mails", "marked", "chats", "groups", "gkeys", "pipeline", "blocked", "appendix"]
|
||||
- defaultList : dataList = ["mails", "marked", "groups", "pipeline", "chats", "blocked", "appendix"]
|
||||
|
||||
### Understanding Mapping between functionalities and sendRaw messages
|
||||
This section illustrates how functionaility is getting translated into sendRaw feature
|
||||
|
||||
- messenger.changeGroupName = function (groupID, name)
|
||||
- sendRaw(message, groupID, "UP_NAME", false)
|
||||
|
||||
- messenger.changeGroupDescription = function (groupID, description)
|
||||
- sendRaw(message, groupID, "UP_DESCRIPTION", false)
|
||||
|
||||
- messenger.addGroupMembers = function (groupID, newMem, note = undefined)
|
||||
- sendRaw(message, groupID, "ADD_MEMBERS", false, note)
|
||||
|
||||
- messenger.rmGroupMembers = function (groupID, rmMem, note = undefined)
|
||||
- p1 = sendRaw(message, groupID, "RM_MEMBERS", false, note)
|
||||
|
||||
- messenger.revokeKey = function (groupID)
|
||||
- groupInfo.members.map(m => sendRaw(JSON.stringify({
|
||||
newKey,
|
||||
groupID
|
||||
}), m, "REVOKE_KEY", true))
|
||||
|
||||
- messenger.sendGroupMessage = function (message, groupID)
|
||||
- sendRaw(message, groupID, "GROUP_MSG", false)
|
||||
|
||||
- message = encrypt(tx_hex, pipeline.eKey);
|
||||
- sendRaw(message, pipeline.id, "TRANSACTION", false)
|
||||
|
||||
- btcOperator.broadcastTx(tx_hex_signed)
|
||||
- sendRaw(encrypt(txid, pipeline.eKey), pipeline.id, "BROADCAST", false)
|
||||
|
||||
- messenger.createPipeline = function (model, members, ekeySize = 16, pubkeys = null)
|
||||
- sendRaw(pipelineInfo, m, "CREATE_PIPELINE", true));
|
||||
|
||||
- messenger.sendPipelineMessage = function (message, pipeID)
|
||||
- sendRaw(message, pipeID, "MESSAGE", false)
|
||||
|
||||
### Understanding appendix
|
||||
It has two usages in the application
|
||||
- appendix.lastReceived to compare against what vectorClock data we have
|
||||
- appendix.AESKey to check what encryption keys to be used
|
||||
|
||||
### compactIDB.addData and compactIDB.writeData
|
||||
|
||||
- The addData function is used to add new records to an object store. It will throw an error if the key already exists
|
||||
- The writeData function is used to update or insert data into an object store. It will update if the key already exists
|
||||
|
||||
### Why do we use uhtml
|
||||
|
||||
uhtml is a small, efficient, and ultra-fast JavaScript library for creating web components and managing the DOM (Document Object Model). It allows developers to write templates directly in JavaScript without needing a separate build step. uhtml focuses on minimalism and performance, aiming to provide a lightweight solution for rendering dynamic content in web applications.
|
||||
|
||||
## Here's a brief overview of what uhtml does:
|
||||
|
||||
- Template Rendering: uhtml provides a way to create HTML templates directly in JavaScript code. These templates can include dynamic content and data-binding expressions.
|
||||
|
||||
- Virtual DOM: uhtml utilizes a virtual DOM approach, where changes to the UI are first made in memory (virtual DOM), and then efficiently applied to the actual DOM, reducing unnecessary reflows and repaints.
|
||||
|
||||
- Efficiency: uhtml is designed to be incredibly fast and efficient. It achieves this by minimizing the overhead associated with creating and updating DOM elements.
|
||||
|
||||
- Reactivity: uhtml templates can be reactive, meaning they can automatically update when the underlying data changes. This reactivity is often used in modern web frameworks to create responsive and dynamic user interfaces.
|
||||
|
||||
## Explain the UI process
|
||||
|
||||
### `getRef(elementId)`
|
||||
getRef is the cornerstone of UI in Messenger App. getRef(elementId), is a custom utility function designed to improve the efficiency of accessing DOM elements by their IDs. It provides a way to cache references to DOM elements and reuse them, reducing the number of times the actual DOM is queried. Instead of using `document.getElementById(elementId)` directly in the code, we use `getRef(elementId)` to access the DOM elements. The function takes care of efficient element retrieval and caching.
|
||||
|
||||
getRef(elementId) Function:
|
||||
|
||||
1. getRef(elementId) is a custom JavaScript function used to obtain references to DOM elements using their unique IDs. It optimizes the process of accessing DOM elements by caching the references, allowing for efficient reuse without the need to repeatedly query the DOM. When you call getRef('mail_contact_list'), it returns the DOM element with the ID 'mail_contact_list'.
|
||||
|
||||
2. We use getRef(elementId) instead of document.getElementById
|
||||
|
||||
### uhtml Library Usage:
|
||||
|
||||
1. uhtml is a JavaScript library for efficient and lightweight HTML templating and rendering. It allows you to create HTML templates directly in your JavaScript code.
|
||||
|
||||
2. The line const { html, render: renderElem } = uhtml; imports the html function and an alias for the render function from the uhtml library. The html function is used to create HTML template literals, and renderElem is used to render those templates into DOM elements efficiently.
|
||||
|
||||
- Rendering HTML with renderElem():
|
||||
|
||||
1. The code renderElem(getRef('mail_contact_list'), html${mailingContacts}) combines the previously mentioned concepts. Here's what's happening:
|
||||
|
||||
2. html${mailingContacts} creates an HTML template literal using the uhtml library. It represents the HTML structure defined by the mailingContacts variable.
|
||||
|
||||
3. getRef('mail_contact_list') retrieves the DOM element with the ID 'mail_contact_list' using the custom getRef() function.
|
||||
|
||||
4. renderElem(targetElement, htmlTemplate) is a call to the renderElem function from the uhtml library. It takes two parameters: the target DOM element where the rendered HTML will be appended (getRef('mail_contact_list') in this case) and the HTML template created with the html function.
|
||||
|
||||
### A direct simple example explaining the render logic
|
||||
|
||||
```
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dynamic List with uhtml</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
||||
<script>
|
||||
const { html, render } = uhtml;
|
||||
|
||||
// Data to be displayed
|
||||
const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
|
||||
|
||||
// Function to render items using uhtml
|
||||
function renderItems(items) {
|
||||
return html`
|
||||
<ul>
|
||||
${items.map(item => html`<li>${item}</li>`)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
// Render items into the 'app' element
|
||||
const appElement = document.getElementById('app');
|
||||
render(appElement, renderItems(items));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
### Functions created for Rendering
|
||||
|
||||
|
||||
```
|
||||
messenger.renderUI.chats = renderChatList;
|
||||
messenger.renderUI.directChat = renderDirectUI;
|
||||
messenger.renderUI.groupChat = renderGroupUI;
|
||||
messenger.renderUI.pipeline = renderPipelineUI;
|
||||
messenger.renderUI.mails = m => renderMailList(m, false);
|
||||
messenger.renderUI.marked = renderMarked;
|
||||
```
|
||||
|
||||
### There are two meanings of render in the UI of messenger application
|
||||
|
||||
1. In class lazyloader, this.render() is a method internal to lazyloader based objects
|
||||
2. In uhtml invocations, render creates actual html when `html` parameter is fed to it
|
||||
3. Sometimes in apps like BTCWallet, render is an object with collection of functions like addressDetails, and txDetails. However, its not used in messenger.
|
||||
|
||||
### Example of uhtml html creation
|
||||
|
||||
- METHOD 1: render.something () In the code below function takes floID as input parameter, and return uhtml compatible html. This can then be fed to `render.selectableContact(contact)` for actual HTML creation
|
||||
|
||||
|
||||
```
|
||||
//HTML creation stage
|
||||
selectableContact(floID) {
|
||||
const name = getContactName(floID)
|
||||
const initial = name.charAt(0)
|
||||
return html`
|
||||
<label class="flex align-center selectable-contact interactive" data-flo-address=${floID} style=${`--contact-color: var(${contactColor(floID)})`}>
|
||||
<div class="initial flex align-center"> ${initial} </div>
|
||||
<div class="grid gap-0-3">
|
||||
<h4 class="name">${name}</h4>
|
||||
<p class="contact__flo-address wrap-around">${floID}</p>
|
||||
</div>
|
||||
<input type="checkbox" autocomplete="off" value=${floID}/>
|
||||
</label>
|
||||
`
|
||||
}
|
||||
|
||||
//At render stage
|
||||
render.selectableContact(contact);
|
||||
```
|
||||
|
||||
- METHOD 2: renderElem(getRef,html) method. Another way we have used to to achieve html from render is the code snippet below using renderElem function
|
||||
|
||||
```
|
||||
const { html, render: renderElem } = uhtml;
|
||||
|
||||
function renderContactList(contactList = floGlobals.contacts) {
|
||||
const contacts = Object.keys(contactList)
|
||||
.sort((a, b) => getContactName(a).localeCompare(getContactName(b)))
|
||||
.map(floID => {
|
||||
const isSelected = selectedMembers.has(floID)
|
||||
return render.contactCard(floID, { type: 'contact', isSelected, ref: getRef('contacts_container') })
|
||||
})
|
||||
renderElem(getRef('contacts_container'), html`${contacts}`)
|
||||
}
|
||||
```
|
||||
## How do you create new models
|
||||
|
||||
- Lets look at usage of createPipeline function which uses `TYPE_BTC_MULTISIG` model here.
|
||||
|
||||
```
|
||||
createPipeline(TYPE_BTC_MULTISIG, co_owners, 32, decode.pubkeys).then(pipeline => {
|
||||
let message = encrypt(tx_hex, pipeline.eKey);
|
||||
sendRaw(message, pipeline.id, "TRANSACTION", false)
|
||||
.then(result => resolve(pipeline.id))
|
||||
.catch(error => reject(error)) //SENDRAW
|
||||
}).catch(error => reject(error)) //CREATE PIPELINE
|
||||
```
|
||||
|
||||
- To design a new model, you need to define the states the pipeline in the model will take, and also define the message types that will move the state forward.
|
||||
- A model tells what are the types of messages in that system, and how should they be handled. There is no explicit definition of model. Rather we straight define the processing logic as part of processData.pipeLine function, and it initiates the model.
|
||||
- You need to define what are the type of messages in your model. For instance the message types for `TYPE_BTC_MULTISIG` model are "TRANSACTION", "BROADCAST" and "MESSAGE".
|
||||
- If the model message type is "TRANSACTION", then it needs to be signed by anyone of the person who sees this first, and then needs to sign the BTC Multisig transaction.
|
||||
- If the model message type is "BROADCAST", then all signatures are received, and multisig messaeg has to be broadcasted to the blockchain.
|
||||
- If the model message type is "MESSAGE", then simply show the message to all members in the pipeline
|
||||
- Similarly as part of model design, you need to identify the types of messages for the models first.
|
||||
- Then you need to create the processing logic for each type of message in processData.pipeline[MODEL_NAME]. It will have a common processing section and switch case for each of the message type
|
||||
- A pipelines moves its state forward as the message type changes due to action of members.
|
||||
- The model processing logic specified how the state of pipeline changes.
|
||||
- In this example the state of pipeline changes as soon as one member performs an action which leads him client to proclaim the state of pipeline has moved to the next message
|
||||
- States in TYPE_BTC_MULTISIG model are Create -> Transact -> Transact -> Broadcast -> close if it is 2 of 3 BTC Multisig
|
||||
- After the processing, the message has to be given to inboxes of correct recipients like using `newInbox.messages[vc] = data;`
|
||||
- You should also store a copy of that message locally like using `compactIDB.addData("messages", Object.assign({}, data),${pipeID}|${vc});`
|
||||
- Every pipeline is identified by a system generated pipeID which is made available to creator and members when a new pipleline is created
|
||||
|
||||
```
|
||||
//pipeline model for btc multisig
|
||||
processData.pipeline[TYPE_BTC_MULTISIG] = function (pipeID) {
|
||||
return (unparsed, newInbox) => {
|
||||
if (!_loaded.pipeline[pipeID].members.includes(floCrypto.toFloID(unparsed.senderID)))
|
||||
return;
|
||||
let data = {
|
||||
time: unparsed.time,
|
||||
sender: unparsed.senderID,
|
||||
pipeID: unparsed.receiverID
|
||||
}
|
||||
let vc = unparsed.vectorClock,
|
||||
k = _loaded.pipeline[pipeID].eKey;
|
||||
unparsed.message = decrypt(unparsed.message, k)
|
||||
//store the pubKey if not stored already
|
||||
floDapps.storePubKey(unparsed.senderID, unparsed.pubKey);
|
||||
data.type = unparsed.type;
|
||||
switch (unparsed.type) {
|
||||
case "TRANSACTION": {
|
||||
data.tx_hex = unparsed.message;
|
||||
break;
|
||||
}
|
||||
case "BROADCAST": {
|
||||
data.txid = unparsed.message;
|
||||
//the following check is done on parallel (in background) instead of sync
|
||||
btcOperator.getTx.hex(data.txid).then(tx_hex_final => {
|
||||
getChat(pipeID).then(result => {
|
||||
let tx_hex_inital = Object.keys(result).sort().map(i => result[i].tx_hex).filter(x => x).shift();
|
||||
if (btcOperator.checkIfSameTx(tx_hex_inital, tx_hex_final))
|
||||
disablePipeline(pipeID);
|
||||
}).catch(error => console.error(error))
|
||||
}).catch(error => console.error(error))
|
||||
break;
|
||||
}
|
||||
case "MESSAGE": {
|
||||
data.message = encrypt(unparsed.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
compactIDB.addData("messages", Object.assign({}, data), `${pipeID}|${vc}`);
|
||||
if (data.message)
|
||||
data.message = decrypt(data.message);
|
||||
newInbox.messages[vc] = data;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ok. The model is created. How do I use it
|
||||
|
||||
- After a model has been created in processData.pipeline[YOUR_MODEL_NAME] with pipeID as the input parameter, the first action is creation of pipeline using that model.
|
||||
- Pipeline creation is a standard function, and as new model creator, the system does it all for you. All you have to do is to use messenger.createPipeline with model parameter as YOUR_MODEL_NAME.
|
||||
- Pipeline creation does not need a special message type. It will be created by the initiator, and sent as messages to everyone who is member of that pipeline.
|
||||
- However once the message has been given to all users, a valid action from any of them can move the state of pipeline forward, and your model processing logic has to handle what do do next.
|
||||
- Such sequence of actions has to be defined till the state reaches closure.
|
||||
- You can also define role based messages in model processing logic, and assign those roles to specific Bitcoin or FLO IDs. Each of them will need their own message type. Switch case of message type has has handle the sequencing of hange of state.
|
||||
- Role based implementation was not needed in Multisig cases. But it could be created if use case emerges by expanding the processing states.
|
||||
- In theory role definition can be done at model stage. But role allocation will need the creator to act, and that would mean that createPipeline function will need an expansion.
|
||||
|
||||
```
|
||||
const createPipeline = messenger.createPipeline = function (model, members, ekeySize = 16, pubkeys = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//optional pubkey parameter
|
||||
if (pubkeys !== null) {
|
||||
if (!Array.isArray(pubkeys))
|
||||
return reject('pubkeys must be an array (if passed)');
|
||||
else if (pubkeys.length !== members.length)
|
||||
return reject('pubkey length doesnot match members length');
|
||||
}
|
||||
|
||||
//validate members
|
||||
let imem1 = [],
|
||||
imem2 = []
|
||||
members.forEach((m, i) => {
|
||||
if (!floCrypto.validateAddr(m))
|
||||
imem1.push(m);
|
||||
else if (!(m in floGlobals.pubKeys) && !floCrypto.isSameAddr(user.id, m)) {
|
||||
if (pubkeys !== null && floCrypto.verifyPubKey(pubkeys[i], m))
|
||||
floGlobals.pubKeys[m] = pubkeys[i];
|
||||
else
|
||||
imem2.push(m);
|
||||
}
|
||||
});
|
||||
if (imem1.length)
|
||||
return reject(`Invalid Members(floIDs): ${imem1}`);
|
||||
else if (imem2.length)
|
||||
return reject(`Invalid Members (pubKey not available): ${imem2}`);
|
||||
//create pipeline info
|
||||
const id = floCrypto.tmpID;
|
||||
let pipeline = {
|
||||
id,
|
||||
model,
|
||||
members
|
||||
}
|
||||
if (ekeySize)
|
||||
pipeline.eKey = floCrypto.randString(ekeySize);
|
||||
//send pipeline info to members
|
||||
let pipelineInfo = JSON.stringify(pipeline);
|
||||
let promises = members.filter(m => !floCrypto.isSameAddr(m, user.id)).map(m => sendRaw(pipelineInfo, m, "CREATE_PIPELINE", true));
|
||||
Promise.allSettled(promises).then(results => {
|
||||
console.debug(results.filter(r => r.status === "rejected").map(r => r.reason));
|
||||
_loaded.pipeline[pipeline.id] = Object.assign({}, pipeline);
|
||||
if (pipeline.eKey)
|
||||
pipeline.eKey = encrypt(pipeline.eKey);
|
||||
compactIDB.addData("pipeline", pipeline, pipeline.id).then(result => {
|
||||
requestPipelineInbox(pipeline.id, pipeline.model);
|
||||
resolve(_loaded.pipeline[pipeline.id])
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Explain me the full cycle of pipeline creation process
|
||||
|
||||
- TYPE_BTC_MULTISIG pipeline is created in MultiSig.createTx_BTC function. And as part of creation process, the creator performs the first signature at `tx_hex = btcOperator.signTx(tx_hex, privateKey)`, and the proceeds to create the pipeline. So the pipleine starts at "TRANSACT" stage itself as seen in `sendRaw(message, pipeline.id, "TRANSACTION", false)` message.
|
||||
|
||||
```
|
||||
MultiSig.createTx_BTC = function (address, redeemScript, receivers, amounts, fee = null, options = {}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let addr_type = btcOperator.validateAddress(address);
|
||||
if (addr_type != "multisig" && addr_type != "multisigBech32")
|
||||
return reject("Sender address is not a multisig");
|
||||
let decode = (addr_type == "multisig" ?
|
||||
coinjs.script().decodeRedeemScript : coinjs.script().decodeRedeemScriptBech32)(redeemScript);
|
||||
if (!decode || decode.address !== address || decode.type !== "multisig__")
|
||||
return reject("Invalid redeem-script");
|
||||
else if (!decode.pubkeys.includes(user.public.toLowerCase()) && !decode.pubkeys.includes(user.public.toUpperCase()))
|
||||
return reject("User is not a part of this multisig");
|
||||
else if (decode.pubkeys.length < decode.signaturesRequired)
|
||||
return reject("Invalid multisig (required is greater than users)");
|
||||
let co_owners = decode.pubkeys.map(p => floCrypto.getFloID(p));
|
||||
let privateKey = await floDapps.user.private;
|
||||
btcOperator.createMultiSigTx(address, redeemScript, receivers, amounts, fee, options).then(({ tx_hex }) => {
|
||||
tx_hex = btcOperator.signTx(tx_hex, privateKey);
|
||||
createPipeline(TYPE_BTC_MULTISIG, co_owners, 32, decode.pubkeys).then(pipeline => {
|
||||
let message = encrypt(tx_hex, pipeline.eKey);
|
||||
sendRaw(message, pipeline.id, "TRANSACTION", false)
|
||||
.then(result => resolve(pipeline.id))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- Similarly all role based definitions can be created at initial `MultiSig.createTx_BTC` equivalent function at pipeline creation stage. The initial start is needed here. Then the processing logic of model will dictate how will the state move forward.
|
||||
- Right now all members are flat. But a system can be created where the message goes to a specific role.
|
||||
|
||||
### Callback functions used
|
||||
|
||||
- callbackFn are used to push data in localIDB and invoke new UI after main fetching of data has been done from Supernodes
|
||||
|
||||
- In requestDirectInbox()
|
||||
|
||||
```
|
||||
let callbackFn = function (dataSet, error) {
|
||||
if (error)
|
||||
return console.error(error)
|
||||
let newInbox = {
|
||||
messages: {},
|
||||
requests: {},
|
||||
responses: {},
|
||||
mails: {},
|
||||
newgroups: [],
|
||||
keyrevoke: [],
|
||||
pipeline: {}
|
||||
}
|
||||
for (let vc in dataSet) {
|
||||
try {
|
||||
parseData(dataSet[vc], newInbox);
|
||||
} catch (error) {
|
||||
//if (error !== "blocked-user")
|
||||
console.log(error);
|
||||
} finally {
|
||||
if (_loaded.appendix.lastReceived < vc)
|
||||
_loaded.appendix.lastReceived = vc;
|
||||
}
|
||||
}
|
||||
compactIDB.writeData("appendix", _loaded.appendix.lastReceived, "lastReceived");
|
||||
console.debug(newInbox);
|
||||
UI.direct(newInbox)
|
||||
}
|
||||
```
|
||||
|
||||
- In `requestGroupInbox(groupID, _async = true)`
|
||||
|
||||
```
|
||||
let callbackFn = function (dataSet, error) {
|
||||
if (error)
|
||||
return console.error(error)
|
||||
console.info(dataSet)
|
||||
let newInbox = {
|
||||
messages: {}
|
||||
}
|
||||
let infoChange = false;
|
||||
for (let vc in dataSet) {
|
||||
if (groupID !== dataSet[vc].receiverID)
|
||||
continue;
|
||||
try {
|
||||
infoChange = parseData(dataSet[vc], newInbox) || infoChange;
|
||||
if (!_loaded.appendix[`lastReceived_${groupID}`] ||
|
||||
_loaded.appendix[`lastReceived_${groupID}`] < vc)
|
||||
_loaded.appendix[`lastReceived_${groupID}`] = vc;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
compactIDB.writeData("appendix", _loaded.appendix[`lastReceived_${groupID}`], `lastReceived_${groupID}`);
|
||||
if (infoChange) {
|
||||
let newInfo = Object.assign({}, _loaded.groups[groupID]);
|
||||
newInfo.eKey = encrypt(newInfo.eKey)
|
||||
compactIDB.writeData("groups", newInfo, groupID)
|
||||
}
|
||||
console.debug(newInbox);
|
||||
UI.group(newInbox);
|
||||
}
|
||||
```
|
||||
|
||||
- In `requestPipelineInbox(pipeID, model, _async = true)`
|
||||
|
||||
```
|
||||
let callbackFn = function (dataSet, error) {
|
||||
if (error)
|
||||
return console.error(error);
|
||||
console.info(dataSet)
|
||||
let newInbox = {
|
||||
messages: {}
|
||||
}
|
||||
for (let vc in dataSet) {
|
||||
if (pipeID !== dataSet[vc].receiverID)
|
||||
continue;
|
||||
try {
|
||||
parseData(dataSet[vc], newInbox);
|
||||
if (!floCrypto.isSameAddr(dataSet[vc].senderID, user.id))
|
||||
addMark(pipeID, "unread")
|
||||
if (!_loaded.appendix[`lastReceived_${pipeID}`] ||
|
||||
_loaded.appendix[`lastReceived_${pipeID}`] < vc)
|
||||
_loaded.appendix[`lastReceived_${pipeID}`] = vc;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
compactIDB.writeData("appendix", _loaded.appendix[`lastReceived_${pipeID}`], `lastReceived_${pipeID}`);
|
||||
console.debug(newInbox);
|
||||
UI.pipeline(model, newInbox);
|
||||
}
|
||||
```
|
||||
38
messenger/docs/usage.md
Normal file
@ -0,0 +1,38 @@
|
||||
## How to use Messenger
|
||||
### General messaging
|
||||
1. Go to the homepage of Messenger
|
||||
2. Sign in using a Bitcoin or FLO blockchain private key
|
||||
3. In case you don't have the private key, generate using
|
||||
FLO Wallet (for FLO address and private key): https://ranchimall.github.io/flowallet/
|
||||
BTC Wallet (for Bitcoin address and private key): https://ranchimall.github.io/btcwallet/
|
||||
** Note: FLO address or FLO ID and private key can be created from Messenger's homepage as well
|
||||
4. To start a new message or chat, click on the "New chat" button
|
||||
5. Add a FLO ID or a Bitcoin address as a contact
|
||||
6. Select the contact to start messaging
|
||||
** Note: Until the receiver replies, the message is not encrypted.
|
||||
|
||||
### Mail
|
||||
1. Mail is similar to Messaging except the user can send messages to multiple FLO IDs or Bitcoin addresses at the same time
|
||||
2. Go to "Mail" and enter the recipient's FLO or Bitcoin address
|
||||
3. Separate multiple addresses with a comma
|
||||
4. Type a mail and send
|
||||
|
||||
### Multisig messaging
|
||||
1. Go to "Multisig" on the homepage
|
||||
2. To create a Bitcoin multisig, click on "BTC"
|
||||
3. To create a FLO multisig, click on "FLO"
|
||||
4. To add BTC or FLO addresses in the new multisig, select contacts that are to be added
|
||||
5. Contacts have to be saved in advance before creating a multisig address
|
||||
6. After selecting the contacts, click "Next" and give the multisig address a label name
|
||||
7. Select the minimum number of signatures required for the multisig
|
||||
8. Click "Create" and the multisig address will be created
|
||||
|
||||
### Sending a multisig transaction
|
||||
1. The user must have some balance in the multisig address
|
||||
2. Go to "Multisig" and click on "init transaction"
|
||||
3. Enter the receiver's BTC address for a Bitcoin multisig or FLO address for a FLO multisig
|
||||
4. Enter the amount to be transferred
|
||||
5. Multiple addresses can be added as receivers with different amounts for each address
|
||||
6. Click on "Initiate" to initiate the transaction from the multisig address
|
||||
7. Associated multisig owners will be notified of this transaction
|
||||
8. Once the required number of signatures is approved, the transaction will take place from the multisig address
|
||||
5237
messenger/index.html
Normal file
1135
messenger/scripts/btcOperator.js
Normal file
257
messenger/scripts/compactIDB.js
Normal file
@ -0,0 +1,257 @@
|
||||
(function (EXPORTS) { //compactIDB v2.1.2
|
||||
/* Compact IndexedDB operations */
|
||||
'use strict';
|
||||
const compactIDB = EXPORTS;
|
||||
|
||||
var defaultDB;
|
||||
|
||||
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
const IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
|
||||
const IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
|
||||
|
||||
if (!indexedDB) {
|
||||
console.error("Your browser doesn't support a stable version of IndexedDB.");
|
||||
return;
|
||||
}
|
||||
|
||||
compactIDB.setDefaultDB = dbName => defaultDB = dbName;
|
||||
|
||||
Object.defineProperty(compactIDB, 'default', {
|
||||
get: () => defaultDB,
|
||||
set: dbName => defaultDB = dbName
|
||||
});
|
||||
|
||||
function getDBversion(dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
resolve(db.version)
|
||||
db.close()
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function upgradeDB(dbName, createList = null, deleteList = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getDBversion(dbName).then(version => {
|
||||
var idb = indexedDB.open(dbName, version + 1);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onupgradeneeded = (event) => {
|
||||
let db = event.target.result;
|
||||
if (createList instanceof Object) {
|
||||
if (Array.isArray(createList)) {
|
||||
let tmp = {}
|
||||
createList.forEach(o => tmp[o] = {})
|
||||
createList = tmp
|
||||
}
|
||||
for (let o in createList) {
|
||||
let obs = db.createObjectStore(o, createList[o].options || {});
|
||||
if (createList[o].indexes instanceof Object)
|
||||
for (let i in createList[o].indexes)
|
||||
obs.createIndex(i, i, createList[o].indexes || {});
|
||||
}
|
||||
}
|
||||
if (Array.isArray(deleteList))
|
||||
deleteList.forEach(o => db.deleteObjectStore(o));
|
||||
resolve('Database upgraded')
|
||||
}
|
||||
idb.onsuccess = (event) => event.target.result.close();
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
compactIDB.initDB = function (dbName, objectStores = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!(objectStores instanceof Object))
|
||||
return reject('ObjectStores must be an object or array')
|
||||
defaultDB = defaultDB || dbName;
|
||||
var idb = indexedDB.open(dbName);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onsuccess = (event) => {
|
||||
var db = event.target.result;
|
||||
let cList = Object.values(db.objectStoreNames);
|
||||
var obs = {},
|
||||
a_obs = {},
|
||||
d_obs = [];
|
||||
if (!Array.isArray(objectStores))
|
||||
var obs = objectStores
|
||||
else
|
||||
objectStores.forEach(o => obs[o] = {})
|
||||
let nList = Object.keys(obs)
|
||||
for (let o of nList)
|
||||
if (!cList.includes(o))
|
||||
a_obs[o] = obs[o]
|
||||
for (let o of cList)
|
||||
if (!nList.includes(o))
|
||||
d_obs.push(o)
|
||||
if (!Object.keys(a_obs).length && !d_obs.length)
|
||||
resolve("Initiated IndexedDB");
|
||||
else
|
||||
upgradeDB(dbName, a_obs, d_obs)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const openDB = compactIDB.openDB = function (dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var idb = indexedDB.open(dbName);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onupgradeneeded = (event) => {
|
||||
event.target.result.close();
|
||||
deleteDB(dbName).then(_ => null).catch(_ => null).finally(_ => reject("Datebase not found"))
|
||||
}
|
||||
idb.onsuccess = (event) => resolve(event.target.result);
|
||||
});
|
||||
}
|
||||
|
||||
const deleteDB = compactIDB.deleteDB = function (dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var deleteReq = indexedDB.deleteDatabase(dbName);;
|
||||
deleteReq.onerror = (event) => reject("Error deleting database!");
|
||||
deleteReq.onsuccess = (event) => resolve("Database deleted successfully");
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.writeData = function (obsName, data, key = false, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let writeReq = (key ? obs.put(data, key) : obs.put(data));
|
||||
writeReq.onsuccess = (evt) => resolve(`Write data Successful`);
|
||||
writeReq.onerror = (evt) => reject(
|
||||
`Write data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.addData = function (obsName, data, key = false, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let addReq = (key ? obs.add(data, key) : obs.add(data));
|
||||
addReq.onsuccess = (evt) => resolve(`Add data successful`);
|
||||
addReq.onerror = (evt) => reject(
|
||||
`Add data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.removeData = function (obsName, key, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let delReq = obs.delete(key);
|
||||
delReq.onsuccess = (evt) => resolve(`Removed Data ${key}`);
|
||||
delReq.onerror = (evt) => reject(
|
||||
`Remove data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.clearData = function (obsName, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let clearReq = obs.clear();
|
||||
clearReq.onsuccess = (evt) => resolve(`Clear data Successful`);
|
||||
clearReq.onerror = (evt) => reject(`Clear data Unsuccessful`);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.readData = function (obsName, key, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
let getReq = obs.get(key);
|
||||
getReq.onsuccess = (evt) => resolve(evt.target.result);
|
||||
getReq.onerror = (evt) => reject(
|
||||
`Read data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.readAllData = function (obsName, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var tmpResult = {}
|
||||
let curReq = obs.openCursor();
|
||||
curReq.onsuccess = (evt) => {
|
||||
var cursor = evt.target.result;
|
||||
if (cursor) {
|
||||
tmpResult[cursor.primaryKey] = cursor.value;
|
||||
cursor.continue();
|
||||
} else
|
||||
resolve(tmpResult);
|
||||
}
|
||||
curReq.onerror = (evt) => reject(
|
||||
`Read-All data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
/* compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var filteredResult = {}
|
||||
let keyRange;
|
||||
if(options.lowerKey!==null && options.upperKey!==null)
|
||||
keyRange = IDBKeyRange.bound(options.lowerKey, options.upperKey);
|
||||
else if(options.lowerKey!==null)
|
||||
keyRange = IDBKeyRange.lowerBound(options.lowerKey);
|
||||
else if (options.upperKey!==null)
|
||||
keyRange = IDBKeyRange.upperBound(options.upperBound);
|
||||
else if (options.atKey)
|
||||
let curReq = obs.openCursor(keyRange, )
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}*/
|
||||
|
||||
compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
|
||||
options.lowerKey = options.atKey || options.lowerKey || 0
|
||||
options.upperKey = options.atKey || options.upperKey || false
|
||||
options.patternEval = options.patternEval || ((k, v) => true);
|
||||
options.limit = options.limit || false;
|
||||
options.reverse = options.reverse || false;
|
||||
options.lastOnly = options.lastOnly || false
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var filteredResult = {}
|
||||
let curReq = obs.openCursor(
|
||||
options.upperKey ? IDBKeyRange.bound(options.lowerKey, options.upperKey) : IDBKeyRange.lowerBound(options.lowerKey),
|
||||
options.lastOnly || options.reverse ? "prev" : "next");
|
||||
curReq.onsuccess = (evt) => {
|
||||
var cursor = evt.target.result;
|
||||
if (!cursor || (options.limit && options.limit <= Object.keys(filteredResult).length))
|
||||
return resolve(filteredResult); //reached end of key list or limit reached
|
||||
else if (options.patternEval(cursor.primaryKey, cursor.value)) {
|
||||
filteredResult[cursor.primaryKey] = cursor.value;
|
||||
options.lastOnly ? resolve(filteredResult) : cursor.continue();
|
||||
} else
|
||||
cursor.continue();
|
||||
}
|
||||
curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
})(window.compactIDB = {});
|
||||
388
messenger/scripts/components.js
Normal file
1
messenger/scripts/components.min.js
vendored
Normal file
1044
messenger/scripts/floBlockchainAPI.js
Normal file
1110
messenger/scripts/floCloudAPI.js
Normal file
1
messenger/scripts/floCloudAPI.min.js
vendored
Normal file
541
messenger/scripts/floCrypto.js
Normal file
@ -0,0 +1,541 @@
|
||||
(function (EXPORTS) { //floCrypto v2.3.6a
|
||||
/* FLO Crypto Operators */
|
||||
'use strict';
|
||||
const floCrypto = EXPORTS;
|
||||
|
||||
const p = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
|
||||
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();
|
||||
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
|
||||
return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
|
||||
}
|
||||
|
||||
function getUncompressedPublicKey(compressedPublicKey) {
|
||||
// Fetch x from compressedPublicKey
|
||||
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
|
||||
const prefix = pubKeyBytes.shift() // remove prefix
|
||||
let prefix_modulus = prefix % 2;
|
||||
pubKeyBytes.unshift(0) // add prefix 0
|
||||
let x = new BigInteger(pubKeyBytes)
|
||||
let xDecimalValue = x.toString()
|
||||
// Fetch y
|
||||
let y = calculateY(x);
|
||||
let yDecimalValue = y.toString();
|
||||
// verify y value
|
||||
let resultBigInt = y.mod(BigInteger("2"));
|
||||
let check = resultBigInt.toString() % 2;
|
||||
if (prefix_modulus !== check)
|
||||
yDecimalValue = y.negate().mod(p).toString();
|
||||
return {
|
||||
x: xDecimalValue,
|
||||
y: yDecimalValue
|
||||
};
|
||||
}
|
||||
|
||||
function getSenderPublicKeyString() {
|
||||
let privateKey = ellipticCurveEncryption.senderRandom();
|
||||
var senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
|
||||
return {
|
||||
privateKey: privateKey,
|
||||
senderPublicKeyString: senderPublicKeyString
|
||||
}
|
||||
}
|
||||
|
||||
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
|
||||
let receiverPublicKeyString = getUncompressedPublicKey(receiverPublicKeyHex);
|
||||
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
|
||||
receiverPublicKeyString.x, receiverPublicKeyString.y, senderPrivateKey);
|
||||
return senderDerivedKey;
|
||||
}
|
||||
|
||||
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
|
||||
return ellipticCurveEncryption.receiverSharedKeyDerivation(
|
||||
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
|
||||
}
|
||||
|
||||
function getReceiverPublicKeyString(privateKey) {
|
||||
return ellipticCurveEncryption.receiverPublicString(privateKey);
|
||||
}
|
||||
|
||||
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
|
||||
let pk = Bitcoin.Base58.decode(pk_wif)
|
||||
pk.shift()
|
||||
pk.splice(-4, 4)
|
||||
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
|
||||
if (isPubKeyCompressed == true) pk.pop()
|
||||
pk.unshift(0)
|
||||
let privateKeyDecimal = BigInteger(pk).toString()
|
||||
let privateKeyHex = Crypto.util.bytesToHex(pk)
|
||||
return {
|
||||
privateKeyDecimal: privateKeyDecimal,
|
||||
privateKeyHex: privateKeyHex
|
||||
}
|
||||
}
|
||||
|
||||
//generate a random Interger within range
|
||||
floCrypto.randInt = function (min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
//generate a random String within length (options : alphaNumeric chars only)
|
||||
floCrypto.randString = function (length, alphaNumeric = true) {
|
||||
var result = '';
|
||||
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
||||
for (var i = 0; i < length; i++)
|
||||
result += characters.charAt(Math.floor(securedMathRandom() * characters.length));
|
||||
return result;
|
||||
}
|
||||
|
||||
//Encrypt Data using public-key
|
||||
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
|
||||
var senderECKeyData = getSenderPublicKeyString();
|
||||
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
|
||||
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
||||
let secret = Crypto.AES.encrypt(data, senderKey);
|
||||
return {
|
||||
secret: secret,
|
||||
senderPublicKeyString: senderECKeyData.senderPublicKeyString
|
||||
};
|
||||
}
|
||||
|
||||
//Decrypt Data using private-key
|
||||
floCrypto.decryptData = function (data, privateKeyHex) {
|
||||
var receiverECKeyData = {};
|
||||
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
|
||||
let privateKey = wifToDecimal(privateKeyHex, true);
|
||||
if (typeof privateKey.privateKeyDecimal !== "string") throw new Error("Failed to detremine your private key.");
|
||||
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
|
||||
var receiverDerivedKey = deriveSharedKeyReceiver(data.senderPublicKeyString, receiverECKeyData.privateKey);
|
||||
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
|
||||
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
|
||||
return decryptMsg;
|
||||
}
|
||||
|
||||
//Sign data using private-key
|
||||
floCrypto.signData = function (data, privateKeyHex) {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
var messageHash = Crypto.SHA256(data);
|
||||
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
|
||||
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||
return sighex;
|
||||
}
|
||||
|
||||
//Verify signatue of the data using public-key
|
||||
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
|
||||
var msgHash = Crypto.SHA256(data);
|
||||
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
||||
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
|
||||
return verify;
|
||||
}
|
||||
|
||||
//Generates a new flo ID and returns private-key, public-key and floID
|
||||
const generateNewID = floCrypto.generateNewID = function () {
|
||||
var key = new Bitcoin.ECKey(false);
|
||||
key.setCompressed(true);
|
||||
return {
|
||||
floID: key.getBitcoinAddress(),
|
||||
pubKey: key.getPubKeyHex(),
|
||||
privKey: key.getBitcoinWalletImportFormat()
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(floCrypto, {
|
||||
newID: {
|
||||
get: () => generateNewID()
|
||||
},
|
||||
hashID: {
|
||||
value: (str) => {
|
||||
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), { asBytes: true });
|
||||
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));
|
||||
}
|
||||
},
|
||||
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
|
||||
floCrypto.getPubKeyHex = function (privateKeyHex) {
|
||||
if (!privateKeyHex)
|
||||
return null;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
return key.getPubKeyHex();
|
||||
}
|
||||
|
||||
//Returns flo-ID from public-key or private-key
|
||||
floCrypto.getFloID = function (keyHex) {
|
||||
if (!keyHex)
|
||||
return null;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(keyHex);
|
||||
if (key.priv == null)
|
||||
key.setPub(keyHex);
|
||||
return key.getBitcoinAddress();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
return false;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return false;
|
||||
key.setCompressed(true);
|
||||
if (isfloID && pubKey_floID == key.getBitcoinAddress())
|
||||
return true;
|
||||
else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
|
||||
if (!Array.isArray(publicKeyList) || !publicKeyList.length)
|
||||
return null;
|
||||
if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length)
|
||||
return null;
|
||||
try {
|
||||
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
|
||||
return multisig;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.decodeRedeemScript = function (redeemScript) {
|
||||
try {
|
||||
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
|
||||
return decoded;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given flo-id is valid or not
|
||||
floCrypto.validateFloID = function (floID, regularOnly = false) {
|
||||
if (!floID)
|
||||
return false;
|
||||
try {
|
||||
let addr = new Bitcoin.Address(floID);
|
||||
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
|
||||
return false;
|
||||
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 if (raw.type === 'ethereum') {
|
||||
return true
|
||||
} else //unknown
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check the public-key (or redeem-script) for the address (any blockchain)
|
||||
floCrypto.verifyPubKey = function (pubKeyHex, address) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })));
|
||||
if (typeof raw.bech_version !== 'undefined' && raw.bytes.length == 32) //bech32-multisig
|
||||
raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true }));
|
||||
return pub_hash === raw.hex;
|
||||
}
|
||||
|
||||
//Convert the given address (any blockchain) to equivalent floID
|
||||
floCrypto.toFloID = function (address, options = null) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
else if (options) { //if (optional) version check is passed
|
||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||
return;
|
||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||
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)));
|
||||
}
|
||||
|
||||
//Convert raw address bytes to floID
|
||||
floCrypto.rawToFloID = function (raw_bytes) {
|
||||
if (typeof raw_bytes === 'string')
|
||||
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
|
||||
if (raw_bytes.length != 20)
|
||||
return null;
|
||||
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)));
|
||||
}
|
||||
|
||||
//Convert the given multisig address (any blockchain) to equivalent multisig floID
|
||||
floCrypto.toMultisigFloID = function (address, options = null) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
else if (options) { //if (optional) version check is passed
|
||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||
return;
|
||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||
return;
|
||||
}
|
||||
if (typeof raw.bech_version !== 'undefined') {
|
||||
if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes
|
||||
//multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech
|
||||
raw.bytes = ripemd160(raw.bytes, {
|
||||
asBytes: true
|
||||
});
|
||||
}
|
||||
raw.bytes.unshift(bitjs.multisig);
|
||||
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 {
|
||||
if (typeof raw1.bech_version !== 'undefined' && raw1.bytes.length == 32) //bech32-multisig
|
||||
raw1.hex = Crypto.util.bytesToHex(ripemd160(raw1.bytes, { asBytes: true }));
|
||||
if (typeof raw2.bech_version !== 'undefined' && raw2.bytes.length == 32) //bech32-multisig
|
||||
raw2.hex = Crypto.util.bytesToHex(ripemd160(raw2.bytes, { asBytes: true }));
|
||||
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.startsWith("0x") && address.length == 42 || address.length == 62) { //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;
|
||||
} else if ((address.length == 42 && address.startsWith("0x")) || (address.length == 40 && !address.startsWith("0x"))) { //Ethereum Address
|
||||
if (address.startsWith("0x")) { address = address.substring(2); }
|
||||
let bytes = Crypto.util.hexToBytes(address);
|
||||
return {
|
||||
version: 1,
|
||||
hex: address,
|
||||
type: 'ethereum',
|
||||
bytes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Split the str using shamir's Secret and Returns the shares
|
||||
floCrypto.createShamirsSecretShares = function (str, total_shares, threshold_limit) {
|
||||
try {
|
||||
if (str.length > 0) {
|
||||
var strHex = shamirSecretShare.str2hex(str);
|
||||
var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit);
|
||||
return shares;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//Returns the retrived secret by combining the shamirs shares
|
||||
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
|
||||
try {
|
||||
if (sharesArray.length > 0) {
|
||||
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
|
||||
comb = shamirSecretShare.hex2str(comb);
|
||||
return comb;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Verifies the shares and str
|
||||
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
|
||||
if (!str)
|
||||
return null;
|
||||
else if (retrieveShamirSecret(sharesArray) === str)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
|
||||
if (typeof string !== "string")
|
||||
return null;
|
||||
if (bool) {
|
||||
let x;
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
let x, invalids = {};
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
if (x in invalids)
|
||||
invalids[string[i]].push(i)
|
||||
else
|
||||
invalids[string[i]] = [i];
|
||||
}
|
||||
if (Object.keys(invalids).length)
|
||||
return invalids;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.convertToASCII = function (string, mode = 'soft-remove') {
|
||||
let chars = validateASCII(string, false);
|
||||
if (chars === true)
|
||||
return string;
|
||||
else if (chars === null)
|
||||
return null;
|
||||
let convertor, result = string,
|
||||
refAlt = {};
|
||||
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
|
||||
mode = mode.toLowerCase();
|
||||
if (mode === "hard-unicode")
|
||||
convertor = (c) => `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "soft-unicode")
|
||||
convertor = (c) => refAlt[c] || `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "hard-remove")
|
||||
convertor = c => "";
|
||||
else if (mode === "soft-remove")
|
||||
convertor = c => refAlt[c] || "";
|
||||
else
|
||||
return null;
|
||||
for (let c in chars)
|
||||
result = result.replaceAll(c, convertor(c));
|
||||
return result;
|
||||
}
|
||||
|
||||
floCrypto.revertUnicode = function (string) {
|
||||
return string.replace(/\\u[\dA-F]{4}/gi,
|
||||
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
|
||||
}
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floCrypto = {});
|
||||
1
messenger/scripts/floCrypto.min.js
vendored
Normal file
843
messenger/scripts/floDapps.js
Normal file
@ -0,0 +1,843 @@
|
||||
(function (EXPORTS) { //floDapps v2.4.1
|
||||
/* General functions for FLO Dapps*/
|
||||
'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, trustedIDs, settings;
|
||||
Object.defineProperties(floGlobals, {
|
||||
subAdmins: {
|
||||
get: () => subAdmins
|
||||
},
|
||||
trustedIDs: {
|
||||
get: () => trustedIDs
|
||||
},
|
||||
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 = {
|
||||
//general
|
||||
lastTx: {},
|
||||
//supernode (cloud list)
|
||||
supernodes: {}
|
||||
}
|
||||
var obs_a = {
|
||||
//login credentials
|
||||
credentials: {},
|
||||
//for Dapps
|
||||
subAdmins: {},
|
||||
trustedIDs: {},
|
||||
settings: {},
|
||||
appObjects: {},
|
||||
generalData: {},
|
||||
lastVC: {}
|
||||
}
|
||||
//add other given objectStores
|
||||
initIndexedDB.appObs = initIndexedDB.appObs || {}
|
||||
for (let o in initIndexedDB.appObs)
|
||||
if (!(o in obs_a))
|
||||
obs_a[o] = initIndexedDB.appObs[o]
|
||||
Promise.all([
|
||||
compactIDB.initDB(DEFAULT.application, obs_a),
|
||||
compactIDB.initDB(DEFAULT.root, obs_g)
|
||||
]).then(result => {
|
||||
compactIDB.setDefaultDB(DEFAULT.application)
|
||||
resolve("IndexedDB App Storage Initated Successfully")
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
function initUserDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var obs = {
|
||||
contacts: {},
|
||||
pubKeys: {},
|
||||
messages: {}
|
||||
}
|
||||
compactIDB.initDB(user.db_name, obs).then(result => {
|
||||
resolve("UserDB Initated Successfully")
|
||||
}).catch(error => reject('Init userDB failed'));
|
||||
})
|
||||
}
|
||||
|
||||
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], user.db_name)
|
||||
Promise.all(promises).then(results => {
|
||||
for (var i = 0; i < loadData.length; i++)
|
||||
user[loadData[i]] = results[i]
|
||||
resolve("Loaded Data from userDB")
|
||||
}).catch(error => reject('Load userDB failed'))
|
||||
})
|
||||
}
|
||||
|
||||
const startUpOptions = {
|
||||
cloud: true,
|
||||
app_config: true,
|
||||
}
|
||||
|
||||
floDapps.startUpOptions = {
|
||||
set app_config(val) {
|
||||
if (val === true || val === false)
|
||||
startUpOptions.app_config = val;
|
||||
},
|
||||
get app_config() { return startUpOptions.app_config },
|
||||
|
||||
set cloud(val) {
|
||||
if (val === true || val === false)
|
||||
startUpOptions.cloud = val;
|
||||
},
|
||||
get cloud() { return startUpOptions.cloud },
|
||||
}
|
||||
|
||||
const startUpFunctions = [];
|
||||
|
||||
startUpFunctions.push(function readSupernodeListFromAPI() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.cloud)
|
||||
return resolve("No cloud for this app");
|
||||
const CLOUD_KEY = "floCloudAPI#" + floCloudAPI.SNStorageID;
|
||||
compactIDB.readData("lastTx", CLOUD_KEY, DEFAULT.root).then(lastTx => {
|
||||
var query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName };
|
||||
if (typeof lastTx == 'number') //lastTx is tx count (*backward support)
|
||||
query_options.ignoreOld = lastTx;
|
||||
else if (typeof lastTx == 'string') //lastTx is txid of last tx
|
||||
query_options.after = lastTx;
|
||||
//fetch data from flosight
|
||||
floBlockchainAPI.readData(floCloudAPI.SNStorageID, query_options).then(result => {
|
||||
compactIDB.readData("supernodes", CLOUD_KEY, DEFAULT.root).then(nodes => {
|
||||
nodes = nodes || {};
|
||||
for (var i = result.data.length - 1; i >= 0; i--) {
|
||||
var content = JSON.parse(result.data[i])[floCloudAPI.SNStorageName];
|
||||
for (let sn in content.removeNodes)
|
||||
delete nodes[sn];
|
||||
for (let sn in content.newNodes)
|
||||
nodes[sn] = content.newNodes[sn];
|
||||
for (let sn in content.updateNodes)
|
||||
if (sn in nodes) //check if node is listed
|
||||
nodes[sn].uri = content.updateNodes[sn];
|
||||
}
|
||||
Promise.all([
|
||||
compactIDB.writeData("lastTx", result.lastItem, CLOUD_KEY, DEFAULT.root),
|
||||
compactIDB.writeData("supernodes", nodes, CLOUD_KEY, DEFAULT.root)
|
||||
]).then(_ => {
|
||||
floCloudAPI.init(nodes)
|
||||
.then(result => resolve("Loaded Supernode list\n" + result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
});
|
||||
|
||||
startUpFunctions.push(function readAppConfigFromAPI() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.app_config)
|
||||
return resolve("No configs for this app");
|
||||
compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => {
|
||||
var query_options = { sentOnly: true, pattern: DEFAULT.application };
|
||||
if (typeof lastTx == 'number') //lastTx is tx count (*backward support)
|
||||
query_options.ignoreOld = lastTx;
|
||||
else if (typeof lastTx == 'string') //lastTx is txid of last tx
|
||||
query_options.after = lastTx;
|
||||
//fetch data from flosight
|
||||
floBlockchainAPI.readData(DEFAULT.adminID, query_options).then(result => {
|
||||
for (var i = result.data.length - 1; i >= 0; i--) {
|
||||
var content = JSON.parse(result.data[i])[DEFAULT.application];
|
||||
if (!content || typeof content !== "object")
|
||||
continue;
|
||||
if (Array.isArray(content.removeSubAdmin))
|
||||
for (var j = 0; j < content.removeSubAdmin.length; j++)
|
||||
compactIDB.removeData("subAdmins", content.removeSubAdmin[j]);
|
||||
if (Array.isArray(content.addSubAdmin))
|
||||
for (var k = 0; k < content.addSubAdmin.length; k++)
|
||||
compactIDB.writeData("subAdmins", true, content.addSubAdmin[k]);
|
||||
if (Array.isArray(content.removeTrustedID))
|
||||
for (var j = 0; j < content.removeTrustedID.length; j++)
|
||||
compactIDB.removeData("trustedIDs", content.removeTrustedID[j]);
|
||||
if (Array.isArray(content.addTrustedID))
|
||||
for (var k = 0; k < content.addTrustedID.length; k++)
|
||||
compactIDB.writeData("trustedIDs", true, content.addTrustedID[k]);
|
||||
if (content.settings)
|
||||
for (let l in content.settings)
|
||||
compactIDB.writeData("settings", content.settings[l], l)
|
||||
}
|
||||
compactIDB.writeData("lastTx", result.lastItem, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root);
|
||||
compactIDB.readAllData("subAdmins").then(result => {
|
||||
subAdmins = Object.keys(result);
|
||||
compactIDB.readAllData("trustedIDs").then(result => {
|
||||
trustedIDs = Object.keys(result);
|
||||
compactIDB.readAllData("settings").then(result => {
|
||||
settings = result;
|
||||
resolve("Read app configuration from blockchain");
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
});
|
||||
|
||||
startUpFunctions.push(function loadDataFromAppIDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.cloud)
|
||||
return resolve("No cloud for this app");
|
||||
var loadData = ["appObjects", "generalData", "lastVC"]
|
||||
var promises = []
|
||||
for (var i = 0; i < loadData.length; i++)
|
||||
promises[i] = compactIDB.readAllData(loadData[i])
|
||||
Promise.all(promises).then(results => {
|
||||
for (var i = 0; i < loadData.length; i++)
|
||||
floGlobals[loadData[i]] = results[i]
|
||||
resolve("Loaded Data from app IDB")
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
});
|
||||
|
||||
var keyInput = type => new Promise((resolve, reject) => {
|
||||
let inputVal = prompt(`Enter ${type}: `)
|
||||
if (inputVal === null)
|
||||
reject(null)
|
||||
else
|
||||
resolve(inputVal)
|
||||
});
|
||||
|
||||
function getCredentials() {
|
||||
|
||||
const readSharesFromIDB = indexArr => new Promise((resolve, reject) => {
|
||||
var promises = []
|
||||
for (var i = 0; i < indexArr.length; i++)
|
||||
promises.push(compactIDB.readData('credentials', indexArr[i]))
|
||||
Promise.all(promises).then(shares => {
|
||||
var secret = floCrypto.retrieveShamirSecret(shares)
|
||||
if (secret)
|
||||
resolve(secret)
|
||||
else
|
||||
reject("Shares are insufficient or incorrect")
|
||||
}).catch(error => {
|
||||
clearCredentials();
|
||||
location.reload();
|
||||
})
|
||||
});
|
||||
|
||||
const writeSharesToIDB = (shares, i = 0, resultIndexes = []) => new Promise(resolve => {
|
||||
if (i >= shares.length)
|
||||
return resolve(resultIndexes)
|
||||
var n = floCrypto.randInt(0, 100000)
|
||||
compactIDB.addData("credentials", shares[i], n).then(res => {
|
||||
resultIndexes.push(n)
|
||||
writeSharesToIDB(shares, i + 1, resultIndexes)
|
||||
.then(result => resolve(result))
|
||||
}).catch(error => {
|
||||
writeSharesToIDB(shares, i, resultIndexes)
|
||||
.then(result => resolve(result))
|
||||
})
|
||||
});
|
||||
|
||||
const getPrivateKeyCredentials = () => new Promise((resolve, reject) => {
|
||||
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||
if (indexArr) {
|
||||
readSharesFromIDB(JSON.parse(indexArr))
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
} else {
|
||||
var privKey;
|
||||
keyInput("PRIVATE_KEY").then(result => {
|
||||
if (!result)
|
||||
return reject("Empty Private Key")
|
||||
var floID = floCrypto.getFloID(result)
|
||||
if (!floID || !floCrypto.validateFloID(floID))
|
||||
return reject("Invalid Private Key")
|
||||
privKey = result;
|
||||
}).catch(error => {
|
||||
console.log(error, "Generating Random Keys")
|
||||
privKey = floCrypto.generateNewID().privKey
|
||||
}).finally(_ => {
|
||||
if (!privKey)
|
||||
return;
|
||||
var threshold = floCrypto.randInt(10, 20)
|
||||
var shares = floCrypto.createShamirsSecretShares(privKey, threshold, threshold)
|
||||
writeSharesToIDB(shares).then(resultIndexes => {
|
||||
//store index keys in localStorage
|
||||
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)
|
||||
var randomShares = floCrypto.createShamirsSecretShares(randomPrivKey, randomThreshold, randomThreshold)
|
||||
writeSharesToIDB(randomShares)
|
||||
//resolve private Key
|
||||
resolve(privKey)
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const checkIfPinRequired = key => new Promise((resolve, reject) => {
|
||||
if (key.length == 52)
|
||||
resolve(key)
|
||||
else {
|
||||
keyInput("PIN/Password").then(pwd => {
|
||||
try {
|
||||
let privKey = Crypto.AES.decrypt(key, pwd);
|
||||
resolve(privKey)
|
||||
} catch (error) {
|
||||
reject("Access Denied: Incorrect PIN/Password")
|
||||
}
|
||||
}).catch(error => reject("Access Denied: PIN/Password required"))
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getPrivateKeyCredentials().then(key => {
|
||||
checkIfPinRequired(key).then(privKey => {
|
||||
try {
|
||||
user_public = floCrypto.getPubKeyHex(privKey);
|
||||
user_id = floCrypto.getAddress(privKey);
|
||||
if (startUpOptions.cloud)
|
||||
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)
|
||||
reject("Corrupted Private Key")
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
var startUpLog = (status, log) => status ? console.log(log) : console.error(log);
|
||||
|
||||
const callStartUpFunction = i => new Promise((resolve, reject) => {
|
||||
startUpFunctions[i]().then(result => {
|
||||
callStartUpFunction.completed += 1;
|
||||
startUpLog(true, `${result}\nCompleted ${callStartUpFunction.completed}/${callStartUpFunction.total} Startup functions`)
|
||||
resolve(true)
|
||||
}).catch(error => {
|
||||
callStartUpFunction.failed += 1;
|
||||
startUpLog(false, `${error}\nFailed ${callStartUpFunction.failed}/${callStartUpFunction.total} Startup functions`)
|
||||
reject(false)
|
||||
})
|
||||
});
|
||||
|
||||
var _midFunction;
|
||||
const midStartUp = () => new Promise((res, rej) => {
|
||||
if (_midFunction instanceof Function) {
|
||||
_midFunction()
|
||||
.then(r => res("Mid startup function completed"))
|
||||
.catch(e => rej("Mid startup function failed"))
|
||||
} else
|
||||
res("No mid startup function")
|
||||
});
|
||||
|
||||
const callAndLog = p => new Promise((res, rej) => {
|
||||
p.then(r => {
|
||||
startUpLog(true, r)
|
||||
res(r)
|
||||
}).catch(e => {
|
||||
startUpLog(false, e)
|
||||
rej(e)
|
||||
})
|
||||
});
|
||||
|
||||
floDapps.launchStartUp = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
initIndexedDB().then(log => {
|
||||
console.log(log)
|
||||
callStartUpFunction.total = startUpFunctions.length;
|
||||
callStartUpFunction.completed = 0;
|
||||
callStartUpFunction.failed = 0;
|
||||
let p1 = new Promise((res, rej) => {
|
||||
Promise.all(startUpFunctions.map((f, i) => callStartUpFunction(i))).then(r => {
|
||||
callAndLog(midStartUp())
|
||||
.then(r => res(true))
|
||||
.catch(e => rej(false))
|
||||
})
|
||||
});
|
||||
let p2 = new Promise((res, rej) => {
|
||||
callAndLog(getCredentials()).then(r => {
|
||||
callAndLog(initUserDB()).then(r => {
|
||||
callAndLog(loadUserDB())
|
||||
.then(r => res(true))
|
||||
.catch(e => rej(false))
|
||||
}).catch(e => rej(false))
|
||||
}).catch(e => rej(false))
|
||||
})
|
||||
Promise.all([p1, p2])
|
||||
.then(r => resolve('App Startup finished successful'))
|
||||
.catch(e => reject('App Startup failed'))
|
||||
}).catch(error => {
|
||||
startUpLog(false, error);
|
||||
reject("App database initiation failed")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.addStartUpFunction = fn => fn instanceof Function && !startUpFunctions.includes(fn) ? startUpFunctions.push(fn) : false;
|
||||
|
||||
floDapps.setMidStartup = fn => fn instanceof Function ? _midFunction = fn : false;
|
||||
|
||||
floDapps.setCustomStartupLogger = fn => fn instanceof Function ? startUpLog = fn : false;
|
||||
|
||||
floDapps.setCustomPrivKeyInput = fn => fn instanceof Function ? keyInput = fn : false;
|
||||
|
||||
floDapps.setAppObjectStores = appObs => initIndexedDB.appObs = appObs;
|
||||
|
||||
floDapps.storeContact = function (floID, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject("Invalid floID!")
|
||||
compactIDB.writeData("contacts", name, floID, user.db_name).then(result => {
|
||||
user.contacts[floID] = name;
|
||||
resolve("Contact stored")
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
floDapps.storePubKey = function (floID, pubKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (floID in user.pubKeys)
|
||||
return resolve("pubKey already stored")
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject("Invalid floID!")
|
||||
if (!floCrypto.verifyPubKey(pubKey, floID))
|
||||
return reject("Incorrect pubKey")
|
||||
compactIDB.writeData("pubKeys", pubKey, floID, user.db_name).then(result => {
|
||||
user.pubKeys[floID] = pubKey;
|
||||
resolve("pubKey stored")
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
floDapps.sendMessage = function (floID, message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let options = {
|
||||
receiverID: floID,
|
||||
application: DEFAULT.root,
|
||||
comment: DEFAULT.application
|
||||
}
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.requestInbox = function (callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let lastVC = Object.keys(user.messages).sort().pop()
|
||||
let options = {
|
||||
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, privKey)
|
||||
} catch (error) { }
|
||||
compactIDB.writeData("messages", d[v], v, user.db_name)
|
||||
user.messages[v] = d[v]
|
||||
}
|
||||
if (callback instanceof Function)
|
||||
callback(d, e)
|
||||
}
|
||||
floCloudAPI.requestApplicationData("Message", options)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.manageAppConfig = function (adminPrivKey, addList, rmList, settings) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.app_config)
|
||||
return reject("No configs for this app");
|
||||
if (!Array.isArray(addList) || !addList.length) addList = undefined;
|
||||
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
|
||||
if (!settings || typeof settings !== "object" || !Object.keys(settings).length) settings = undefined;
|
||||
if (!addList && !rmList && !settings)
|
||||
return reject("No configuration change")
|
||||
var floData = {
|
||||
[DEFAULT.application]: {
|
||||
addSubAdmin: addList,
|
||||
removeSubAdmin: rmList,
|
||||
settings: settings
|
||||
}
|
||||
}
|
||||
var floID = floCrypto.getFloID(adminPrivKey)
|
||||
if (floID != DEFAULT.adminID)
|
||||
reject('Access Denied for Admin privilege')
|
||||
else
|
||||
floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey)
|
||||
.then(result => resolve(['Updated App Configuration', result]))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.manageAppTrustedIDs = function (adminPrivKey, addList, rmList) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.app_config)
|
||||
return reject("No configs for this app");
|
||||
if (!Array.isArray(addList) || !addList.length) addList = undefined;
|
||||
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
|
||||
if (!addList && !rmList)
|
||||
return reject("No change in list")
|
||||
var floData = {
|
||||
[DEFAULT.application]: {
|
||||
addTrustedID: addList,
|
||||
removeTrustedID: rmList
|
||||
}
|
||||
}
|
||||
var floID = floCrypto.getFloID(adminPrivKey)
|
||||
if (floID != DEFAULT.adminID)
|
||||
reject('Access Denied for Admin privilege')
|
||||
else
|
||||
floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey)
|
||||
.then(result => resolve(['Updated App Configuration', result]))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const clearCredentials = floDapps.clearCredentials = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
compactIDB.clearData('credentials', DEFAULT.application).then(result => {
|
||||
localStorage.removeItem(`${DEFAULT.application}#privKey`);
|
||||
user.clear();
|
||||
resolve("privKey credentials deleted!")
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.deleteUserData = function (credentials = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let p = []
|
||||
p.push(compactIDB.deleteDB(user.db_name))
|
||||
if (credentials)
|
||||
p.push(clearCredentials())
|
||||
Promise.all(p)
|
||||
.then(result => resolve('User database(local) deleted'))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.deleteAppData = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.securePrivKey = function (pwd) {
|
||||
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(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, DEFAULT.application);
|
||||
for (var i = 0; i < threshold; i++)
|
||||
promises.push(overwriteFn(shares[i], indexArr[i]));
|
||||
Promise.all(promises)
|
||||
.then(results => resolve("Private Key Secured"))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.verifyPin = function (pin = null) {
|
||||
const readSharesFromIDB = function (indexArr) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var promises = []
|
||||
for (var i = 0; i < indexArr.length; i++)
|
||||
promises.push(compactIDB.readData('credentials', indexArr[i]))
|
||||
Promise.all(promises).then(shares => {
|
||||
var secret = floCrypto.retrieveShamirSecret(shares)
|
||||
console.info(shares, secret)
|
||||
if (secret)
|
||||
resolve(secret)
|
||||
else
|
||||
reject("Shares are insufficient or incorrect")
|
||||
}).catch(error => {
|
||||
clearCredentials();
|
||||
location.reload();
|
||||
})
|
||||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||
console.info(indexArr)
|
||||
if (!indexArr)
|
||||
reject('No login credentials found')
|
||||
readSharesFromIDB(JSON.parse(indexArr)).then(key => {
|
||||
if (key.length == 52) {
|
||||
if (pin === null)
|
||||
resolve("Private key not secured")
|
||||
else
|
||||
reject("Private key not secured")
|
||||
} else {
|
||||
if (pin === null)
|
||||
return reject("PIN/Password required")
|
||||
try {
|
||||
let privKey = Crypto.AES.decrypt(key, pin);
|
||||
resolve("PIN/Password verified")
|
||||
} catch (error) {
|
||||
reject("Incorrect PIN/Password")
|
||||
}
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const getNextGeneralData = floDapps.getNextGeneralData = function (type, vectorClock = null, options = {}) {
|
||||
var fk = floCloudAPI.util.filterKey(type, options)
|
||||
vectorClock = vectorClock || getNextGeneralData[fk] || '0';
|
||||
var filteredResult = {}
|
||||
if (floGlobals.generalData[fk]) {
|
||||
for (let d in floGlobals.generalData[fk])
|
||||
if (d > vectorClock)
|
||||
filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d]))
|
||||
} else if (options.comment) {
|
||||
let comment = options.comment;
|
||||
delete options.comment;
|
||||
let fk = floCloudAPI.util.filterKey(type, options);
|
||||
for (let d in floGlobals.generalData[fk])
|
||||
if (d > vectorClock && floGlobals.generalData[fk][d].comment == comment)
|
||||
filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d]))
|
||||
}
|
||||
if (options.decrypt) {
|
||||
let decryptionKey = (options.decrypt === true) ? raw_user.private : options.decrypt;
|
||||
if (!Array.isArray(decryptionKey))
|
||||
decryptionKey = [decryptionKey];
|
||||
for (let f in filteredResult) {
|
||||
let data = filteredResult[f]
|
||||
try {
|
||||
if (data.message instanceof Object && "secret" in data.message) {
|
||||
for (let key of decryptionKey) {
|
||||
try {
|
||||
let tmp = floCrypto.decryptData(data.message, key)
|
||||
data.message = JSON.parse(tmp)
|
||||
break;
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
getNextGeneralData[fk] = Object.keys(filteredResult).sort().pop();
|
||||
return filteredResult;
|
||||
}
|
||||
|
||||
const syncData = floDapps.syncData = {};
|
||||
|
||||
syncData.oldDevice = () => new Promise((resolve, reject) => {
|
||||
let sync = {
|
||||
contacts: user.contacts,
|
||||
pubKeys: user.pubKeys,
|
||||
messages: user.messages
|
||||
}
|
||||
let message = Crypto.AES.encrypt(JSON.stringify(sync), raw_user.private)
|
||||
let options = {
|
||||
receiverID: user.id,
|
||||
application: DEFAULT.root
|
||||
}
|
||||
floCloudAPI.sendApplicationData(message, "syncData", options)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
syncData.newDevice = () => new Promise((resolve, reject) => {
|
||||
var options = {
|
||||
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, raw_user.private))
|
||||
let promises = []
|
||||
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)
|
||||
user[c][i] = sync[c][i]
|
||||
}
|
||||
})
|
||||
Promise.all(promises)
|
||||
.then(results => resolve("Sync data successful"))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
})('object' === typeof module ? module.exports : window.floDapps = {});
|
||||
166
messenger/scripts/floTokenAPI.js
Normal file
@ -0,0 +1,166 @@
|
||||
(function (EXPORTS) { //floTokenAPI v1.0.4a
|
||||
/* Token Operator to send/receive tokens via blockchain using API calls*/
|
||||
'use strict';
|
||||
const tokenAPI = EXPORTS;
|
||||
|
||||
const DEFAULT = {
|
||||
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
|
||||
currency: floGlobals.currency || "rupee"
|
||||
}
|
||||
|
||||
Object.defineProperties(tokenAPI, {
|
||||
URL: {
|
||||
get: () => DEFAULT.apiURL
|
||||
},
|
||||
currency: {
|
||||
get: () => DEFAULT.currency,
|
||||
set: currency => DEFAULT.currency = currency
|
||||
}
|
||||
});
|
||||
|
||||
if (floGlobals.currency) tokenAPI.currency = floGlobals.currency;
|
||||
|
||||
Object.defineProperties(floGlobals, {
|
||||
currency: {
|
||||
get: () => DEFAULT.currency,
|
||||
set: currency => DEFAULT.currency = currency
|
||||
}
|
||||
});
|
||||
|
||||
const fetch_api = tokenAPI.fetch = function (apicall) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.debug(DEFAULT.apiURL + apicall);
|
||||
fetch(DEFAULT.apiURL + apicall).then(response => {
|
||||
if (response.ok)
|
||||
response.json().then(data => resolve(data));
|
||||
else
|
||||
reject(response)
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const getBalance = tokenAPI.getBalance = function (floID, token = DEFAULT.currency) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
|
||||
.then(result => resolve(result.balance || 0))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
tokenAPI.getTx = function (txID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => {
|
||||
if (res.result === "error")
|
||||
reject(res.description);
|
||||
else if (!res.parsedFloData)
|
||||
reject("Data piece (parsedFloData) missing");
|
||||
else if (!res.transactionDetails)
|
||||
reject("Data piece (transactionDetails) missing");
|
||||
else
|
||||
resolve(res);
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
tokenAPI.sendToken = function (privKey, amount, receiverID, message = "", token = DEFAULT.currency, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let senderID = floCrypto.getFloID(privKey);
|
||||
if (typeof amount !== "number" || isNaN(amount) || amount <= 0)
|
||||
return reject("Invalid amount");
|
||||
getBalance(senderID, token).then(bal => {
|
||||
if (amount > bal)
|
||||
return reject(`Insufficient ${token}# balance`);
|
||||
floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID, options)
|
||||
.then(txid => resolve(txid))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
function sendTokens_raw(privKey, receiverID, token, amount, utxo, vout, scriptPubKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var trx = bitjs.transaction();
|
||||
trx.addinput(utxo, vout, scriptPubKey)
|
||||
trx.addoutput(receiverID, floBlockchainAPI.sendAmt);
|
||||
trx.addflodata(`send ${amount} ${token}#`);
|
||||
var signedTxHash = trx.sign(privKey, 1);
|
||||
floBlockchainAPI.broadcastTx(signedTxHash)
|
||||
.then(txid => resolve([receiverID, txid]))
|
||||
.catch(error => reject([receiverID, error]))
|
||||
})
|
||||
}
|
||||
|
||||
//bulk transfer tokens
|
||||
tokenAPI.bulkTransferTokens = function (sender, privKey, token, receivers) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof receivers !== 'object')
|
||||
return reject("receivers must be object in format {receiver1: amount1, receiver2:amount2...}")
|
||||
|
||||
let receiver_list = Object.keys(receivers), amount_list = Object.values(receivers);
|
||||
let invalidReceivers = receiver_list.filter(id => !floCrypto.validateFloID(id));
|
||||
let invalidAmount = amount_list.filter(val => typeof val !== 'number' || val <= 0);
|
||||
if (invalidReceivers.length)
|
||||
return reject(`Invalid receivers: ${invalidReceivers}`);
|
||||
else if (invalidAmount.length)
|
||||
return reject(`Invalid amounts: ${invalidAmount}`);
|
||||
|
||||
if (receiver_list.length == 0)
|
||||
return reject("Receivers cannot be empty");
|
||||
|
||||
if (receiver_list.length == 1) {
|
||||
let receiver = receiver_list[0], amount = amount_list[0];
|
||||
floTokenAPI.sendToken(privKey, amount, receiver, "", token)
|
||||
.then(txid => resolve({ success: { [receiver]: txid } }))
|
||||
.catch(error => reject(error))
|
||||
} else {
|
||||
//check for token balance
|
||||
floTokenAPI.getBalance(sender, token).then(token_balance => {
|
||||
let total_token_amout = amount_list.reduce((a, e) => a + e, 0);
|
||||
if (total_token_amout > token_balance)
|
||||
return reject(`Insufficient ${token}# balance`);
|
||||
|
||||
//split utxos
|
||||
floBlockchainAPI.splitUTXOs(sender, privKey, receiver_list.length).then(split_txid => {
|
||||
//wait for the split utxo to get confirmation
|
||||
floBlockchainAPI.waitForConfirmation(split_txid).then(split_tx => {
|
||||
//send tokens using the split-utxo
|
||||
var scriptPubKey = split_tx.vout[0].scriptPubKey.hex;
|
||||
let promises = [];
|
||||
for (let i in receiver_list)
|
||||
promises.push(sendTokens_raw(privKey, receiver_list[i], token, amount_list[i], split_txid, i, scriptPubKey));
|
||||
Promise.allSettled(promises).then(results => {
|
||||
let success = Object.fromEntries(results.filter(r => r.status == 'fulfilled').map(r => r.value));
|
||||
let failed = Object.fromEntries(results.filter(r => r.status == 'rejected').map(r => r.reason));
|
||||
resolve({ success, failed });
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const util = tokenAPI.util = {};
|
||||
|
||||
util.parseTxData = function (txData) {
|
||||
let parsedData = {};
|
||||
for (let p in txData.parsedFloData)
|
||||
parsedData[p] = txData.parsedFloData[p];
|
||||
parsedData.sender = txData.transactionDetails.vin[0].addr;
|
||||
for (let vout of txData.transactionDetails.vout)
|
||||
if (vout.scriptPubKey.addresses[0] !== parsedData.sender)
|
||||
parsedData.receiver = vout.scriptPubKey.addresses[0];
|
||||
parsedData.time = txData.transactionDetails.time;
|
||||
return parsedData;
|
||||
}
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floTokenAPI = {});
|
||||
673
messenger/scripts/keccak.js
Normal file
@ -0,0 +1,673 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var INPUT_ERROR = 'input is invalid type';
|
||||
var FINALIZE_ERROR = 'finalize already called';
|
||||
var WINDOW = typeof window === 'object';
|
||||
var root = WINDOW ? (window.keccak = window.keccak || {}) : {};
|
||||
if (root.JS_SHA3_NO_WINDOW) {
|
||||
WINDOW = false;
|
||||
}
|
||||
var WEB_WORKER = !WINDOW && typeof self === 'object';
|
||||
var NODE_JS = !root.JS_SHA3_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
|
||||
if (NODE_JS) {
|
||||
root = global;
|
||||
} else if (WEB_WORKER) {
|
||||
root = self;
|
||||
}
|
||||
var COMMON_JS = !root.JS_SHA3_NO_COMMON_JS && typeof module === 'object' && module.exports;
|
||||
var AMD = typeof define === 'function' && define.amd;
|
||||
var ARRAY_BUFFER = !root.JS_SHA3_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';
|
||||
var HEX_CHARS = '0123456789abcdef'.split('');
|
||||
var SHAKE_PADDING = [31, 7936, 2031616, 520093696];
|
||||
var CSHAKE_PADDING = [4, 1024, 262144, 67108864];
|
||||
var KECCAK_PADDING = [1, 256, 65536, 16777216];
|
||||
var PADDING = [6, 1536, 393216, 100663296];
|
||||
var SHIFT = [0, 8, 16, 24];
|
||||
var RC = [1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 32907, 0, 2147483649,
|
||||
0, 2147516545, 2147483648, 32777, 2147483648, 138, 0, 136, 0, 2147516425, 0,
|
||||
2147483658, 0, 2147516555, 0, 139, 2147483648, 32905, 2147483648, 32771,
|
||||
2147483648, 32770, 2147483648, 128, 2147483648, 32778, 0, 2147483658, 2147483648,
|
||||
2147516545, 2147483648, 32896, 2147483648, 2147483649, 0, 2147516424, 2147483648];
|
||||
var BITS = [224, 256, 384, 512];
|
||||
var SHAKE_BITS = [128, 256];
|
||||
var OUTPUT_TYPES = ['hex', 'buffer', 'arrayBuffer', 'array', 'digest'];
|
||||
var CSHAKE_BYTEPAD = {
|
||||
'128': 168,
|
||||
'256': 136
|
||||
};
|
||||
|
||||
|
||||
var isArray = root.JS_SHA3_NO_NODE_JS || !Array.isArray
|
||||
? function (obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Array]';
|
||||
}
|
||||
: Array.isArray;
|
||||
|
||||
var isView = (ARRAY_BUFFER && (root.JS_SHA3_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView))
|
||||
? function (obj) {
|
||||
return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;
|
||||
}
|
||||
: ArrayBuffer.isView;
|
||||
|
||||
// [message: string, isString: bool]
|
||||
var formatMessage = function (message) {
|
||||
var type = typeof message;
|
||||
if (type === 'string') {
|
||||
return [message, true];
|
||||
}
|
||||
if (type !== 'object' || message === null) {
|
||||
throw new Error(INPUT_ERROR);
|
||||
}
|
||||
if (ARRAY_BUFFER && message.constructor === ArrayBuffer) {
|
||||
return [new Uint8Array(message), false];
|
||||
}
|
||||
if (!isArray(message) && !isView(message)) {
|
||||
throw new Error(INPUT_ERROR);
|
||||
}
|
||||
return [message, false];
|
||||
}
|
||||
|
||||
var empty = function (message) {
|
||||
return formatMessage(message)[0].length === 0;
|
||||
};
|
||||
|
||||
var createOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message) {
|
||||
return new Keccak(bits, padding, bits).update(message)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createShakeOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message, outputBits) {
|
||||
return new Keccak(bits, padding, outputBits).update(message)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createCshakeOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message, outputBits, n, s) {
|
||||
return methods['cshake' + bits].update(message, outputBits, n, s)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createKmacOutputMethod = function (bits, padding, outputType) {
|
||||
return function (key, message, outputBits, s) {
|
||||
return methods['kmac' + bits].update(key, message, outputBits, s)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createOutputMethods = function (method, createMethod, bits, padding) {
|
||||
for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
|
||||
var type = OUTPUT_TYPES[i];
|
||||
method[type] = createMethod(bits, padding, type);
|
||||
}
|
||||
return method;
|
||||
};
|
||||
|
||||
var createMethod = function (bits, padding) {
|
||||
var method = createOutputMethod(bits, padding, 'hex');
|
||||
method.create = function () {
|
||||
return new Keccak(bits, padding, bits);
|
||||
};
|
||||
method.update = function (message) {
|
||||
return method.create().update(message);
|
||||
};
|
||||
return createOutputMethods(method, createOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createShakeMethod = function (bits, padding) {
|
||||
var method = createShakeOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (outputBits) {
|
||||
return new Keccak(bits, padding, outputBits);
|
||||
};
|
||||
method.update = function (message, outputBits) {
|
||||
return method.create(outputBits).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createShakeOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createCshakeMethod = function (bits, padding) {
|
||||
var w = CSHAKE_BYTEPAD[bits];
|
||||
var method = createCshakeOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (outputBits, n, s) {
|
||||
if (empty(n) && empty(s)) {
|
||||
return methods['shake' + bits].create(outputBits);
|
||||
} else {
|
||||
return new Keccak(bits, padding, outputBits).bytepad([n, s], w);
|
||||
}
|
||||
};
|
||||
method.update = function (message, outputBits, n, s) {
|
||||
return method.create(outputBits, n, s).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createCshakeOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createKmacMethod = function (bits, padding) {
|
||||
var w = CSHAKE_BYTEPAD[bits];
|
||||
var method = createKmacOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (key, outputBits, s) {
|
||||
return new Kmac(bits, padding, outputBits).bytepad(['KMAC', s], w).bytepad([key], w);
|
||||
};
|
||||
method.update = function (key, message, outputBits, s) {
|
||||
return method.create(key, outputBits, s).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createKmacOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var algorithms = [
|
||||
{ name: 'keccak', padding: KECCAK_PADDING, bits: BITS, createMethod: createMethod },
|
||||
{ name: 'sha3', padding: PADDING, bits: BITS, createMethod: createMethod },
|
||||
{ name: 'shake', padding: SHAKE_PADDING, bits: SHAKE_BITS, createMethod: createShakeMethod },
|
||||
{ name: 'cshake', padding: CSHAKE_PADDING, bits: SHAKE_BITS, createMethod: createCshakeMethod },
|
||||
{ name: 'kmac', padding: CSHAKE_PADDING, bits: SHAKE_BITS, createMethod: createKmacMethod }
|
||||
];
|
||||
|
||||
var methods = {}, methodNames = [];
|
||||
|
||||
for (var i = 0; i < algorithms.length; ++i) {
|
||||
var algorithm = algorithms[i];
|
||||
var bits = algorithm.bits;
|
||||
for (var j = 0; j < bits.length; ++j) {
|
||||
var methodName = algorithm.name + '_' + bits[j];
|
||||
methodNames.push(methodName);
|
||||
methods[methodName] = algorithm.createMethod(bits[j], algorithm.padding);
|
||||
if (algorithm.name !== 'sha3') {
|
||||
var newMethodName = algorithm.name + bits[j];
|
||||
methodNames.push(newMethodName);
|
||||
methods[newMethodName] = methods[methodName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
methodNames.push("extractLast20Bytes");
|
||||
methods["extractLast20Bytes"] = extractLast20Bytes;
|
||||
|
||||
|
||||
function Keccak(bits, padding, outputBits) {
|
||||
this.blocks = [];
|
||||
this.s = [];
|
||||
this.padding = padding;
|
||||
this.outputBits = outputBits;
|
||||
this.reset = true;
|
||||
this.finalized = false;
|
||||
this.block = 0;
|
||||
this.start = 0;
|
||||
this.blockCount = (1600 - (bits << 1)) >> 5;
|
||||
this.byteCount = this.blockCount << 2;
|
||||
this.outputBlocks = outputBits >> 5;
|
||||
this.extraBytes = (outputBits & 31) >> 3;
|
||||
|
||||
for (var i = 0; i < 50; ++i) {
|
||||
this.s[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Keccak.prototype.update = function (message) {
|
||||
if (this.finalized) {
|
||||
throw new Error(FINALIZE_ERROR);
|
||||
}
|
||||
var result = formatMessage(message);
|
||||
message = result[0];
|
||||
var isString = result[1];
|
||||
var blocks = this.blocks, byteCount = this.byteCount, length = message.length,
|
||||
blockCount = this.blockCount, index = 0, s = this.s, i, code;
|
||||
|
||||
while (index < length) {
|
||||
if (this.reset) {
|
||||
this.reset = false;
|
||||
blocks[0] = this.block;
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
if (isString) {
|
||||
for (i = this.start; index < length && i < byteCount; ++index) {
|
||||
code = message.charCodeAt(index);
|
||||
if (code < 0x80) {
|
||||
blocks[i >> 2] |= code << SHIFT[i++ & 3];
|
||||
} else if (code < 0x800) {
|
||||
blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
|
||||
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = this.start; index < length && i < byteCount; ++index) {
|
||||
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
this.lastByteIndex = i;
|
||||
if (i >= byteCount) {
|
||||
this.start = i - byteCount;
|
||||
this.block = blocks[blockCount];
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
this.reset = true;
|
||||
} else {
|
||||
this.start = i;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Keccak.prototype.encode = function (x, right) {
|
||||
var o = x & 255, n = 1;
|
||||
var bytes = [o];
|
||||
x = x >> 8;
|
||||
o = x & 255;
|
||||
while (o > 0) {
|
||||
bytes.unshift(o);
|
||||
x = x >> 8;
|
||||
o = x & 255;
|
||||
++n;
|
||||
}
|
||||
if (right) {
|
||||
bytes.push(n);
|
||||
} else {
|
||||
bytes.unshift(n);
|
||||
}
|
||||
this.update(bytes);
|
||||
return bytes.length;
|
||||
};
|
||||
|
||||
Keccak.prototype.encodeString = function (str) {
|
||||
var result = formatMessage(str);
|
||||
str = result[0];
|
||||
var isString = result[1];
|
||||
var bytes = 0, length = str.length;
|
||||
if (isString) {
|
||||
for (var i = 0; i < str.length; ++i) {
|
||||
var code = str.charCodeAt(i);
|
||||
if (code < 0x80) {
|
||||
bytes += 1;
|
||||
} else if (code < 0x800) {
|
||||
bytes += 2;
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
bytes += 3;
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (str.charCodeAt(++i) & 0x3ff));
|
||||
bytes += 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bytes = length;
|
||||
}
|
||||
bytes += this.encode(bytes * 8);
|
||||
this.update(str);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
Keccak.prototype.bytepad = function (strs, w) {
|
||||
var bytes = this.encode(w);
|
||||
for (var i = 0; i < strs.length; ++i) {
|
||||
bytes += this.encodeString(strs[i]);
|
||||
}
|
||||
var paddingBytes = (w - bytes % w) % w;
|
||||
var zeros = [];
|
||||
zeros.length = paddingBytes;
|
||||
this.update(zeros);
|
||||
return this;
|
||||
};
|
||||
|
||||
Keccak.prototype.finalize = function () {
|
||||
if (this.finalized) {
|
||||
return;
|
||||
}
|
||||
this.finalized = true;
|
||||
var blocks = this.blocks, i = this.lastByteIndex, blockCount = this.blockCount, s = this.s;
|
||||
blocks[i >> 2] |= this.padding[i & 3];
|
||||
if (this.lastByteIndex === this.byteCount) {
|
||||
blocks[0] = blocks[blockCount];
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
blocks[blockCount - 1] |= 0x80000000;
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
};
|
||||
|
||||
Keccak.prototype.toString = Keccak.prototype.hex = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var hex = '', block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F] +
|
||||
HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F] +
|
||||
HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F] +
|
||||
HEX_CHARS[(block >> 28) & 0x0F] + HEX_CHARS[(block >> 24) & 0x0F];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F];
|
||||
if (extraBytes > 1) {
|
||||
hex += HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F];
|
||||
}
|
||||
if (extraBytes > 2) {
|
||||
hex += HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F];
|
||||
}
|
||||
}
|
||||
return hex;
|
||||
};
|
||||
|
||||
Keccak.prototype.arrayBuffer = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var bytes = this.outputBits >> 3;
|
||||
var buffer;
|
||||
if (extraBytes) {
|
||||
buffer = new ArrayBuffer((outputBlocks + 1) << 2);
|
||||
} else {
|
||||
buffer = new ArrayBuffer(bytes);
|
||||
}
|
||||
var array = new Uint32Array(buffer);
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
array[j] = s[i];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
array[i] = s[i];
|
||||
buffer = buffer.slice(0, bytes);
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
Keccak.prototype.buffer = Keccak.prototype.arrayBuffer;
|
||||
|
||||
Keccak.prototype.digest = Keccak.prototype.array = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var array = [], offset, block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
array[offset] = block & 0xFF;
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
array[offset + 3] = (block >> 24) & 0xFF;
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
array[offset] = block & 0xFF;
|
||||
if (extraBytes > 1) {
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
}
|
||||
if (extraBytes > 2) {
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
function Kmac(bits, padding, outputBits) {
|
||||
Keccak.call(this, bits, padding, outputBits);
|
||||
}
|
||||
|
||||
Kmac.prototype = new Keccak();
|
||||
|
||||
Kmac.prototype.finalize = function () {
|
||||
this.encode(this.outputBits, true);
|
||||
return Keccak.prototype.finalize.call(this);
|
||||
};
|
||||
|
||||
var f = function (s) {
|
||||
var h, l, n, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9,
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17,
|
||||
b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31, b32, b33,
|
||||
b34, b35, b36, b37, b38, b39, b40, b41, b42, b43, b44, b45, b46, b47, b48, b49;
|
||||
for (n = 0; n < 48; n += 2) {
|
||||
c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
|
||||
c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
|
||||
c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
|
||||
c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
|
||||
c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
|
||||
c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
|
||||
c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
|
||||
c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
|
||||
c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
|
||||
c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
|
||||
|
||||
h = c8 ^ ((c2 << 1) | (c3 >>> 31));
|
||||
l = c9 ^ ((c3 << 1) | (c2 >>> 31));
|
||||
s[0] ^= h;
|
||||
s[1] ^= l;
|
||||
s[10] ^= h;
|
||||
s[11] ^= l;
|
||||
s[20] ^= h;
|
||||
s[21] ^= l;
|
||||
s[30] ^= h;
|
||||
s[31] ^= l;
|
||||
s[40] ^= h;
|
||||
s[41] ^= l;
|
||||
h = c0 ^ ((c4 << 1) | (c5 >>> 31));
|
||||
l = c1 ^ ((c5 << 1) | (c4 >>> 31));
|
||||
s[2] ^= h;
|
||||
s[3] ^= l;
|
||||
s[12] ^= h;
|
||||
s[13] ^= l;
|
||||
s[22] ^= h;
|
||||
s[23] ^= l;
|
||||
s[32] ^= h;
|
||||
s[33] ^= l;
|
||||
s[42] ^= h;
|
||||
s[43] ^= l;
|
||||
h = c2 ^ ((c6 << 1) | (c7 >>> 31));
|
||||
l = c3 ^ ((c7 << 1) | (c6 >>> 31));
|
||||
s[4] ^= h;
|
||||
s[5] ^= l;
|
||||
s[14] ^= h;
|
||||
s[15] ^= l;
|
||||
s[24] ^= h;
|
||||
s[25] ^= l;
|
||||
s[34] ^= h;
|
||||
s[35] ^= l;
|
||||
s[44] ^= h;
|
||||
s[45] ^= l;
|
||||
h = c4 ^ ((c8 << 1) | (c9 >>> 31));
|
||||
l = c5 ^ ((c9 << 1) | (c8 >>> 31));
|
||||
s[6] ^= h;
|
||||
s[7] ^= l;
|
||||
s[16] ^= h;
|
||||
s[17] ^= l;
|
||||
s[26] ^= h;
|
||||
s[27] ^= l;
|
||||
s[36] ^= h;
|
||||
s[37] ^= l;
|
||||
s[46] ^= h;
|
||||
s[47] ^= l;
|
||||
h = c6 ^ ((c0 << 1) | (c1 >>> 31));
|
||||
l = c7 ^ ((c1 << 1) | (c0 >>> 31));
|
||||
s[8] ^= h;
|
||||
s[9] ^= l;
|
||||
s[18] ^= h;
|
||||
s[19] ^= l;
|
||||
s[28] ^= h;
|
||||
s[29] ^= l;
|
||||
s[38] ^= h;
|
||||
s[39] ^= l;
|
||||
s[48] ^= h;
|
||||
s[49] ^= l;
|
||||
|
||||
b0 = s[0];
|
||||
b1 = s[1];
|
||||
b32 = (s[11] << 4) | (s[10] >>> 28);
|
||||
b33 = (s[10] << 4) | (s[11] >>> 28);
|
||||
b14 = (s[20] << 3) | (s[21] >>> 29);
|
||||
b15 = (s[21] << 3) | (s[20] >>> 29);
|
||||
b46 = (s[31] << 9) | (s[30] >>> 23);
|
||||
b47 = (s[30] << 9) | (s[31] >>> 23);
|
||||
b28 = (s[40] << 18) | (s[41] >>> 14);
|
||||
b29 = (s[41] << 18) | (s[40] >>> 14);
|
||||
b20 = (s[2] << 1) | (s[3] >>> 31);
|
||||
b21 = (s[3] << 1) | (s[2] >>> 31);
|
||||
b2 = (s[13] << 12) | (s[12] >>> 20);
|
||||
b3 = (s[12] << 12) | (s[13] >>> 20);
|
||||
b34 = (s[22] << 10) | (s[23] >>> 22);
|
||||
b35 = (s[23] << 10) | (s[22] >>> 22);
|
||||
b16 = (s[33] << 13) | (s[32] >>> 19);
|
||||
b17 = (s[32] << 13) | (s[33] >>> 19);
|
||||
b48 = (s[42] << 2) | (s[43] >>> 30);
|
||||
b49 = (s[43] << 2) | (s[42] >>> 30);
|
||||
b40 = (s[5] << 30) | (s[4] >>> 2);
|
||||
b41 = (s[4] << 30) | (s[5] >>> 2);
|
||||
b22 = (s[14] << 6) | (s[15] >>> 26);
|
||||
b23 = (s[15] << 6) | (s[14] >>> 26);
|
||||
b4 = (s[25] << 11) | (s[24] >>> 21);
|
||||
b5 = (s[24] << 11) | (s[25] >>> 21);
|
||||
b36 = (s[34] << 15) | (s[35] >>> 17);
|
||||
b37 = (s[35] << 15) | (s[34] >>> 17);
|
||||
b18 = (s[45] << 29) | (s[44] >>> 3);
|
||||
b19 = (s[44] << 29) | (s[45] >>> 3);
|
||||
b10 = (s[6] << 28) | (s[7] >>> 4);
|
||||
b11 = (s[7] << 28) | (s[6] >>> 4);
|
||||
b42 = (s[17] << 23) | (s[16] >>> 9);
|
||||
b43 = (s[16] << 23) | (s[17] >>> 9);
|
||||
b24 = (s[26] << 25) | (s[27] >>> 7);
|
||||
b25 = (s[27] << 25) | (s[26] >>> 7);
|
||||
b6 = (s[36] << 21) | (s[37] >>> 11);
|
||||
b7 = (s[37] << 21) | (s[36] >>> 11);
|
||||
b38 = (s[47] << 24) | (s[46] >>> 8);
|
||||
b39 = (s[46] << 24) | (s[47] >>> 8);
|
||||
b30 = (s[8] << 27) | (s[9] >>> 5);
|
||||
b31 = (s[9] << 27) | (s[8] >>> 5);
|
||||
b12 = (s[18] << 20) | (s[19] >>> 12);
|
||||
b13 = (s[19] << 20) | (s[18] >>> 12);
|
||||
b44 = (s[29] << 7) | (s[28] >>> 25);
|
||||
b45 = (s[28] << 7) | (s[29] >>> 25);
|
||||
b26 = (s[38] << 8) | (s[39] >>> 24);
|
||||
b27 = (s[39] << 8) | (s[38] >>> 24);
|
||||
b8 = (s[48] << 14) | (s[49] >>> 18);
|
||||
b9 = (s[49] << 14) | (s[48] >>> 18);
|
||||
|
||||
s[0] = b0 ^ (~b2 & b4);
|
||||
s[1] = b1 ^ (~b3 & b5);
|
||||
s[10] = b10 ^ (~b12 & b14);
|
||||
s[11] = b11 ^ (~b13 & b15);
|
||||
s[20] = b20 ^ (~b22 & b24);
|
||||
s[21] = b21 ^ (~b23 & b25);
|
||||
s[30] = b30 ^ (~b32 & b34);
|
||||
s[31] = b31 ^ (~b33 & b35);
|
||||
s[40] = b40 ^ (~b42 & b44);
|
||||
s[41] = b41 ^ (~b43 & b45);
|
||||
s[2] = b2 ^ (~b4 & b6);
|
||||
s[3] = b3 ^ (~b5 & b7);
|
||||
s[12] = b12 ^ (~b14 & b16);
|
||||
s[13] = b13 ^ (~b15 & b17);
|
||||
s[22] = b22 ^ (~b24 & b26);
|
||||
s[23] = b23 ^ (~b25 & b27);
|
||||
s[32] = b32 ^ (~b34 & b36);
|
||||
s[33] = b33 ^ (~b35 & b37);
|
||||
s[42] = b42 ^ (~b44 & b46);
|
||||
s[43] = b43 ^ (~b45 & b47);
|
||||
s[4] = b4 ^ (~b6 & b8);
|
||||
s[5] = b5 ^ (~b7 & b9);
|
||||
s[14] = b14 ^ (~b16 & b18);
|
||||
s[15] = b15 ^ (~b17 & b19);
|
||||
s[24] = b24 ^ (~b26 & b28);
|
||||
s[25] = b25 ^ (~b27 & b29);
|
||||
s[34] = b34 ^ (~b36 & b38);
|
||||
s[35] = b35 ^ (~b37 & b39);
|
||||
s[44] = b44 ^ (~b46 & b48);
|
||||
s[45] = b45 ^ (~b47 & b49);
|
||||
s[6] = b6 ^ (~b8 & b0);
|
||||
s[7] = b7 ^ (~b9 & b1);
|
||||
s[16] = b16 ^ (~b18 & b10);
|
||||
s[17] = b17 ^ (~b19 & b11);
|
||||
s[26] = b26 ^ (~b28 & b20);
|
||||
s[27] = b27 ^ (~b29 & b21);
|
||||
s[36] = b36 ^ (~b38 & b30);
|
||||
s[37] = b37 ^ (~b39 & b31);
|
||||
s[46] = b46 ^ (~b48 & b40);
|
||||
s[47] = b47 ^ (~b49 & b41);
|
||||
s[8] = b8 ^ (~b0 & b2);
|
||||
s[9] = b9 ^ (~b1 & b3);
|
||||
s[18] = b18 ^ (~b10 & b12);
|
||||
s[19] = b19 ^ (~b11 & b13);
|
||||
s[28] = b28 ^ (~b20 & b22);
|
||||
s[29] = b29 ^ (~b21 & b23);
|
||||
s[38] = b38 ^ (~b30 & b32);
|
||||
s[39] = b39 ^ (~b31 & b33);
|
||||
s[48] = b48 ^ (~b40 & b42);
|
||||
s[49] = b49 ^ (~b41 & b43);
|
||||
|
||||
s[0] ^= RC[n];
|
||||
s[1] ^= RC[n + 1];
|
||||
}
|
||||
};
|
||||
|
||||
function extractLast20Bytes(hexString, addPrefix) {
|
||||
// Ensure the input hexString has '0x' prefix
|
||||
if (!hexString.startsWith('0x')) {
|
||||
hexString = '0x' + hexString;
|
||||
}
|
||||
|
||||
// Remove '0x' prefix and parse the hex string to a BigInt
|
||||
var bigIntValue = BigInt(hexString);
|
||||
|
||||
// Extract the last 20 bytes (160 bits) from the BigInt
|
||||
var last20Bytes = bigIntValue & BigInt('0x' + 'f'.repeat(40)); // 0xf is 4 bits in hexadecimal, repeated 40 times for 160 bits
|
||||
|
||||
// Convert the result back to a hexadecimal string
|
||||
var result = last20Bytes.toString(16).padStart(40, '0'); // 40 characters for 160 bits
|
||||
|
||||
// Add '0x' prefix if addPrefix is truthy
|
||||
if (addPrefix) {
|
||||
result = '0x' + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (typeof root.keccak === 'object') {
|
||||
Object.assign(root.keccak, methods);
|
||||
}
|
||||
|
||||
if (COMMON_JS) {
|
||||
module.exports = methods;
|
||||
} else {
|
||||
for (i = 0; i < methodNames.length; ++i) {
|
||||
root[methodNames[i]] = methods[methodNames[i]];
|
||||
}
|
||||
if (AMD) {
|
||||
define(function () {
|
||||
return methods;
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
9975
messenger/scripts/lib.js
Normal file
1654
messenger/scripts/messenger.js
Normal file
1
messenger/scripts/messenger.min.js
vendored
Normal file
43
messenger/scripts/messengerEthereum.js
Normal file
@ -0,0 +1,43 @@
|
||||
(function (EXPORTS) { //floEthereum v1.0.1a
|
||||
/* FLO Ethereum Operators */
|
||||
/* Make sure you added Taproot, Keccak, FLO and BTC Libraries before */
|
||||
'use strict';
|
||||
const floEthereum = EXPORTS;
|
||||
|
||||
// onlyEvenY is usually false. It is needed to be true only when taproot private keys are input
|
||||
const ethAddressFromPrivateKey = floEthereum.ethAddressFromPrivateKey = function(privateKey, onlyEvenY = false){
|
||||
var t1,t1_x,t1_y,t1_y_BigInt,t2,t3,t4;
|
||||
var groupOrder = BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");
|
||||
|
||||
t1 = bitjs.newPubkey(privateKey);
|
||||
t1_x = t1.slice(2, 66); t1_y = t1.slice(-64);
|
||||
if (onlyEvenY) {
|
||||
t1_y_BigInt = BigInt("0x"+t1_y);
|
||||
if (t1_y_BigInt % 2n !== 0n) { t1_y_BigInt = (groupOrder-t1_y_BigInt)%groupOrder; t1_y=t1_y_BigInt.toString(16)}
|
||||
};
|
||||
|
||||
t2 = t1_x.toString(16) + t1_y.toString(16);
|
||||
t3 = keccak.keccak_256(Crypto.util.hexToBytes(t2));
|
||||
t4 = keccak.extractLast20Bytes(t3);
|
||||
return "0x" + t4;
|
||||
}
|
||||
|
||||
const ethAddressFromCompressedPublicKey = floEthereum.ethAddressFromCompressedPublicKey = function(compressedPublicKey){
|
||||
var t1,t2,t3,t4;
|
||||
t1 = coinjs.compressedToUncompressed(compressedPublicKey);
|
||||
t2 = t1.slice(2);
|
||||
t3 = keccak.keccak_256(Crypto.util.hexToBytes(t2));
|
||||
t4 = keccak.extractLast20Bytes(t3);
|
||||
return "0x" + t4;
|
||||
}
|
||||
|
||||
const ethAddressFromUncompressedPublicKey = floEthereum.ethAddressFromUncompressedPublicKey = function(unCompressedPublicKey){
|
||||
var t1,t2,t3,t4;
|
||||
t1 = unCompressedPublicKey;
|
||||
t2 = t1.slice(2);
|
||||
t3 = keccak.keccak_256(Crypto.util.hexToBytes(t2));
|
||||
t4 = keccak.extractLast20Bytes(t3);
|
||||
return "0x" + t4;
|
||||
}
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floEthereum = {});
|
||||
57
messenger/test.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<style>
|
||||
|
||||
|
||||
|
||||
.loginComponent{
|
||||
position:absolute;
|
||||
top:40%;
|
||||
left:50%;
|
||||
transform:translate(-50%,-40%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body >
|
||||
<script src="scripts/components.js"></script>
|
||||
|
||||
|
||||
<div class="loginComponent">
|
||||
<settings-menu>
|
||||
<div slot="menu-options">Playback</div>
|
||||
<div slot="menu-options">quality</div>
|
||||
<div slot="menu-options">About</div>
|
||||
|
||||
<div slot="menu1">0.25</div>
|
||||
<div slot="menu1">0.5</div>
|
||||
<div slot="menu1">0.75</div>
|
||||
<div slot="menu1">Normal</div>
|
||||
<div slot="menu1">1.25</div>
|
||||
<div slot="menu1">1.5</div>
|
||||
<div slot="menu1">1.75</div>
|
||||
|
||||
<div slot="menu2">1044p</div>
|
||||
<div slot="menu2">720p</div>
|
||||
<div slot="menu2">480p</div>
|
||||
<div slot="menu2">360p</div>
|
||||
<div slot="menu2">240p</div>
|
||||
<div slot="menu2">144p</div>
|
||||
<div slot="menu2">auto</div>
|
||||
-
|
||||
<div slot="menu3">Profile</div>
|
||||
<div slot="menu3">Hobbies</div>
|
||||
<div slot="menu3">Education</div>
|
||||
<div slot="menu3">Family</div>
|
||||
<div slot="menu3">Career</div>
|
||||
</settings-menu>
|
||||
</div>
|
||||
<script src="scripts/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||