Workflow updating files of cc
This commit is contained in:
parent
ace27d4e14
commit
100207a133
10316
cc/Content Collaboration.html
Normal file
10316
cc/Content Collaboration.html
Normal file
File diff suppressed because it is too large
Load Diff
674
cc/LICENSE
Normal file
674
cc/LICENSE
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
26
cc/README.md
Normal file
26
cc/README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# p2p-content-collaboration
|
||||||
|
|
||||||
|
This peer-to-peer FLO Blockchain-based content collaboration software runs on a single HTML file.
|
||||||
|
|
||||||
|
### Live URL for CC
|
||||||
|
*https://ranchimall.github.io/cc/*
|
||||||
|
|
||||||
|
|
||||||
|
## How to run this software
|
||||||
|
|
||||||
|
1. Download and run the HTML file on any browser (Google Chrome is preferred)
|
||||||
|
2. Or access the LIVE URL
|
||||||
|
3. It will ask for a FLO private key. If you do not have one please generate it.
|
||||||
|
4. Once you provide a correct private key you are logged in
|
||||||
|
5. Inside the app, you can find the contents that are active
|
||||||
|
6. To add your writeup please write inside the boxes under respective sections or subtopics
|
||||||
|
7. You can also choose to edit any content by adding/deleting contents
|
||||||
|
8. To submit your writeup click on the 'Submit' button. Your entries will be recorded
|
||||||
|
9. The respective admins or sub-admins can score the content to choose the best ones
|
||||||
|
10. To score the content, please click on the star icon and give a score. The score will be recorded. (Only authorized sub-admins can score content)
|
||||||
|
11. Also you can download any content by selecting it and exporting it as a file.
|
||||||
|
|
||||||
|
Any user running the file gets to edit shared content. Hence, this application can be used for multiple purposes
|
||||||
|
like creating shared content, writing books by multiple authors, or reaching a consensus through collaborative discussion.
|
||||||
|
|
||||||
|
Please fork the project and contribute.
|
||||||
9479
cc/blockchainCloud_client.html
Normal file
9479
cc/blockchainCloud_client.html
Normal file
File diff suppressed because it is too large
Load Diff
1306
cc/css/main.css
Normal file
1306
cc/css/main.css
Normal file
File diff suppressed because it is too large
Load Diff
1
cc/css/main.min.css
vendored
Normal file
1
cc/css/main.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1247
cc/css/main.scss
Normal file
1247
cc/css/main.scss
Normal file
File diff suppressed because it is too large
Load Diff
5426
cc/index.html
Normal file
5426
cc/index.html
Normal file
File diff suppressed because one or more lines are too long
13
cc/package-lock.json
generated
Normal file
13
cc/package-lock.json
generated
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "p2p-content-collaboration",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tiny-editor": {
|
||||||
|
"version": "0.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-editor/-/tiny-editor-0.2.5.tgz",
|
||||||
|
"integrity": "sha512-K4luWQbam/TrgjLst+Ztb7uPltXHwtFm+Oi1hs3xM6biI7N1X8HfyDtmROOEbyJM8+mMP5/FjsMnc96zJZoZCg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
cc/package.json
Normal file
22
cc/package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "p2p-content-collaboration",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "components.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ranchimall/p2p-content-collaboration.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ranchimall/p2p-content-collaboration/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ranchimall/p2p-content-collaboration#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"tiny-editor": "^0.2.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
10665
cc/reports.html
Normal file
10665
cc/reports.html
Normal file
File diff suppressed because it is too large
Load Diff
996
cc/scripts/btcOperator.js
Normal file
996
cc/scripts/btcOperator.js
Normal file
@ -0,0 +1,996 @@
|
|||||||
|
(function (EXPORTS) { //btcOperator v1.1.3b
|
||||||
|
/* BTC Crypto and API Operator */
|
||||||
|
const btcOperator = EXPORTS;
|
||||||
|
|
||||||
|
//This library uses API provided by chain.so (https://chain.so/)
|
||||||
|
const URL = "https://blockchain.info/";
|
||||||
|
|
||||||
|
const DUST_AMT = 546,
|
||||||
|
MIN_FEE_UPDATE = 219;
|
||||||
|
|
||||||
|
const fetch_api = btcOperator.fetch = function (api, json_res = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.debug(URL + api);
|
||||||
|
fetch(URL + api).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
(json_res ? response.json() : response.text())
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
} else {
|
||||||
|
response.json()
|
||||||
|
.then(result => reject(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const SATOSHI_IN_BTC = 1e8;
|
||||||
|
|
||||||
|
const util = btcOperator.util = {};
|
||||||
|
|
||||||
|
util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
|
||||||
|
util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
|
||||||
|
|
||||||
|
function get_fee_rate() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch('https://api.blockchain.info/mempool/fees').then(response => {
|
||||||
|
if (response.ok)
|
||||||
|
response.json()
|
||||||
|
.then(result => resolve(util.Sat_to_BTC(result.regular)))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
else
|
||||||
|
reject(response);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => {
|
||||||
|
let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction';
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: "rawtx=" + rawTxHex
|
||||||
|
}).then(response => {
|
||||||
|
response.text().then(resultText => {
|
||||||
|
let r = resultText.match(/<result>.*<\/result>/);
|
||||||
|
if (!r)
|
||||||
|
reject(resultText);
|
||||||
|
else {
|
||||||
|
r = r.pop().replace('<result>', '').replace('</result>', '');
|
||||||
|
if (r == '1') {
|
||||||
|
let txid = resultText.match(/<txid>.*<\/txid>/).pop().replace('<txid>', '').replace('</txid>', '');
|
||||||
|
resolve(txid);
|
||||||
|
} else if (r == '0') {
|
||||||
|
let error = resultText.match(/<response>.*<\/response>/).pop().replace('<response>', '').replace('</response>', '');
|
||||||
|
reject(decodeURIComponent(error.replace(/\+/g, " ")));
|
||||||
|
} else reject(resultText);
|
||||||
|
}
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperties(btcOperator, {
|
||||||
|
newKeys: {
|
||||||
|
get: () => {
|
||||||
|
let r = coinjs.newKeys();
|
||||||
|
r.segwitAddress = coinjs.segwitAddress(r.pubkey).address;
|
||||||
|
r.bech32Address = coinjs.bech32Address(r.pubkey).address;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pubkey: {
|
||||||
|
value: key => key.length >= 66 ? key : (key.length == 64 ? coinjs.newPubkey(key) : coinjs.wif2pubkey(key).pubkey)
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
value: (key, prefix = undefined) => coinjs.pubkey2address(btcOperator.pubkey(key), prefix)
|
||||||
|
},
|
||||||
|
segwitAddress: {
|
||||||
|
value: key => coinjs.segwitAddress(btcOperator.pubkey(key)).address
|
||||||
|
},
|
||||||
|
bech32Address: {
|
||||||
|
value: key => coinjs.bech32Address(btcOperator.pubkey(key)).address
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
coinjs.compressed = true;
|
||||||
|
|
||||||
|
const verifyKey = btcOperator.verifyKey = function (addr, key) {
|
||||||
|
if (!addr || !key)
|
||||||
|
return undefined;
|
||||||
|
switch (coinjs.addressDecode(addr).type) {
|
||||||
|
case "standard":
|
||||||
|
return btcOperator.address(key) === addr;
|
||||||
|
case "multisig":
|
||||||
|
return btcOperator.segwitAddress(key) === addr;
|
||||||
|
case "bech32":
|
||||||
|
return btcOperator.bech32Address(key) === addr;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateAddress = btcOperator.validateAddress = function (addr) {
|
||||||
|
if (!addr)
|
||||||
|
return undefined;
|
||||||
|
let type = coinjs.addressDecode(addr).type;
|
||||||
|
if (["standard", "multisig", "bech32", "multisigBech32"].includes(type))
|
||||||
|
return type;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.multiSigAddress = function (pubKeys, minRequired, bech32 = true) {
|
||||||
|
if (!Array.isArray(pubKeys))
|
||||||
|
throw "pubKeys must be an array of public keys";
|
||||||
|
else if (pubKeys.length < minRequired)
|
||||||
|
throw "minimum required should be less than the number of pubKeys";
|
||||||
|
if (bech32)
|
||||||
|
return coinjs.pubkeys2MultisigAddressBech32(pubKeys, minRequired);
|
||||||
|
else
|
||||||
|
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.decodeRedeemScript = function (redeemScript, bech32 = true) {
|
||||||
|
let script = coinjs.script();
|
||||||
|
let decoded = (bech32) ?
|
||||||
|
script.decodeRedeemScriptBech32(redeemScript) :
|
||||||
|
script.decodeRedeemScript(redeemScript);
|
||||||
|
if (!decoded)
|
||||||
|
return null;
|
||||||
|
return {
|
||||||
|
address: decoded.address,
|
||||||
|
pubKeys: decoded.pubkeys,
|
||||||
|
redeemScript: decoded.redeemscript,
|
||||||
|
required: decoded.signaturesRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//convert from one blockchain to another blockchain (target version)
|
||||||
|
btcOperator.convert = {};
|
||||||
|
|
||||||
|
btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) {
|
||||||
|
let keyHex = util.decodeLegacy(source_wif).hex;
|
||||||
|
if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex))
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return util.encodeLegacy(keyHex, target_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) {
|
||||||
|
let rawHex = util.decodeLegacy(source_addr).hex;
|
||||||
|
if (!rawHex)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return util.encodeLegacy(rawHex, target_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
||||||
|
let rawHex = util.decodeLegacy(source_addr).hex;
|
||||||
|
if (!rawHex)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return util.encodeBech32(rawHex, target_version, target_hrp);
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
|
||||||
|
let rawHex = util.decodeBech32(source_addr).hex;
|
||||||
|
if (!rawHex)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return util.encodeBech32(rawHex, target_version, target_hrp);
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) {
|
||||||
|
let rawHex = util.decodeBech32(source_addr).hex;
|
||||||
|
if (!rawHex)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return util.encodeLegacy(rawHex, target_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.convert.multisig2multisig = function (source_addr, target_version = coinjs.multisig) {
|
||||||
|
let rawHex = util.decodeLegacy(source_addr).hex;
|
||||||
|
if (!rawHex)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return util.encodeLegacy(rawHex, target_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.convert.bech2multisig = function (source_addr, target_version = coinjs.multisig) {
|
||||||
|
let rawHex = util.decodeBech32(source_addr).hex;
|
||||||
|
if (!rawHex)
|
||||||
|
return null;
|
||||||
|
else {
|
||||||
|
rawHex = Crypto.util.bytesToHex(ripemd160(Crypto.util.hexToBytes(rawHex), { asBytes: true }));
|
||||||
|
return util.encodeLegacy(rawHex, target_version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.decodeLegacy = function (source) {
|
||||||
|
var decode = coinjs.base58decode(source);
|
||||||
|
var raw = decode.slice(0, decode.length - 4),
|
||||||
|
checksum = decode.slice(decode.length - 4);
|
||||||
|
var hash = Crypto.SHA256(Crypto.SHA256(raw, {
|
||||||
|
asBytes: true
|
||||||
|
}), {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3])
|
||||||
|
return false;
|
||||||
|
let version = raw.shift();
|
||||||
|
return {
|
||||||
|
version: version,
|
||||||
|
hex: Crypto.util.bytesToHex(raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.encodeLegacy = function (hex, version) {
|
||||||
|
var bytes = Crypto.util.hexToBytes(hex);
|
||||||
|
bytes.unshift(version);
|
||||||
|
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||||
|
asBytes: true
|
||||||
|
}), {
|
||||||
|
asBytes: true
|
||||||
|
});
|
||||||
|
var checksum = hash.slice(0, 4);
|
||||||
|
return coinjs.base58encode(bytes.concat(checksum));
|
||||||
|
}
|
||||||
|
|
||||||
|
util.decodeBech32 = function (source) {
|
||||||
|
let decode = coinjs.bech32_decode(source);
|
||||||
|
if (!decode)
|
||||||
|
return false;
|
||||||
|
var raw = decode.data;
|
||||||
|
let version = raw.shift();
|
||||||
|
raw = coinjs.bech32_convert(raw, 5, 8, false);
|
||||||
|
return {
|
||||||
|
hrp: decode.hrp,
|
||||||
|
version: version,
|
||||||
|
hex: Crypto.util.bytesToHex(raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.encodeBech32 = function (hex, version, hrp) {
|
||||||
|
var bytes = Crypto.util.hexToBytes(hex);
|
||||||
|
bytes = coinjs.bech32_convert(bytes, 8, 5, true);
|
||||||
|
bytes.unshift(version)
|
||||||
|
return coinjs.bech32_encode(hrp, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
//BTC blockchain APIs
|
||||||
|
|
||||||
|
btcOperator.getBalance = addr => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`q/addressbalance/${addr}`)
|
||||||
|
.then(result => resolve(util.Sat_to_BTC(result)))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
});
|
||||||
|
|
||||||
|
const BASE_TX_SIZE = 12,
|
||||||
|
BASE_INPUT_SIZE = 41,
|
||||||
|
LEGACY_INPUT_SIZE = 107,
|
||||||
|
BECH32_INPUT_SIZE = 27,
|
||||||
|
BECH32_MULTISIG_INPUT_SIZE = 35,
|
||||||
|
SEGWIT_INPUT_SIZE = 59,
|
||||||
|
MULTISIG_INPUT_SIZE_ES = 351,
|
||||||
|
BASE_OUTPUT_SIZE = 9,
|
||||||
|
LEGACY_OUTPUT_SIZE = 25,
|
||||||
|
BECH32_OUTPUT_SIZE = 23,
|
||||||
|
BECH32_MULTISIG_OUTPUT_SIZE = 34,
|
||||||
|
SEGWIT_OUTPUT_SIZE = 23;
|
||||||
|
|
||||||
|
function _redeemScript(addr, key) {
|
||||||
|
let decode = coinjs.addressDecode(addr);
|
||||||
|
switch (decode.type) {
|
||||||
|
case "standard":
|
||||||
|
return false;
|
||||||
|
case "multisig":
|
||||||
|
return key ? coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript : null;
|
||||||
|
case "bech32":
|
||||||
|
return decode.redeemscript;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sizePerInput(addr, rs) {
|
||||||
|
switch (coinjs.addressDecode(addr).type) {
|
||||||
|
case "standard":
|
||||||
|
return BASE_INPUT_SIZE + LEGACY_INPUT_SIZE;
|
||||||
|
case "bech32":
|
||||||
|
return BASE_INPUT_SIZE + BECH32_INPUT_SIZE;
|
||||||
|
case "multisigBech32":
|
||||||
|
return BASE_INPUT_SIZE + BECH32_MULTISIG_INPUT_SIZE;
|
||||||
|
case "multisig":
|
||||||
|
switch (coinjs.script().decodeRedeemScript(rs).type) {
|
||||||
|
case "segwit__":
|
||||||
|
return BASE_INPUT_SIZE + SEGWIT_INPUT_SIZE;
|
||||||
|
case "multisig__":
|
||||||
|
return BASE_INPUT_SIZE + MULTISIG_INPUT_SIZE_ES;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sizePerOutput(addr) {
|
||||||
|
switch (coinjs.addressDecode(addr).type) {
|
||||||
|
case "standard":
|
||||||
|
return BASE_OUTPUT_SIZE + LEGACY_OUTPUT_SIZE;
|
||||||
|
case "bech32":
|
||||||
|
return BASE_OUTPUT_SIZE + BECH32_OUTPUT_SIZE;
|
||||||
|
case "multisigBech32":
|
||||||
|
return BASE_OUTPUT_SIZE + BECH32_MULTISIG_OUTPUT_SIZE;
|
||||||
|
case "multisig":
|
||||||
|
return BASE_OUTPUT_SIZE + SEGWIT_OUTPUT_SIZE;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateTxParameters(parameters) {
|
||||||
|
let invalids = [];
|
||||||
|
//sender-ids
|
||||||
|
if (parameters.senders) {
|
||||||
|
if (!Array.isArray(parameters.senders))
|
||||||
|
parameters.senders = [parameters.senders];
|
||||||
|
parameters.senders.forEach(id => !validateAddress(id) ? invalids.push(id) : null);
|
||||||
|
if (invalids.length)
|
||||||
|
throw "Invalid senders:" + invalids;
|
||||||
|
}
|
||||||
|
if (parameters.privkeys) {
|
||||||
|
if (!Array.isArray(parameters.privkeys))
|
||||||
|
parameters.privkeys = [parameters.privkeys];
|
||||||
|
if (parameters.senders.length != parameters.privkeys.length)
|
||||||
|
throw "Array length for senders and privkeys should be equal";
|
||||||
|
parameters.senders.forEach((id, i) => {
|
||||||
|
let key = parameters.privkeys[i];
|
||||||
|
if (!verifyKey(id, key)) //verify private-key
|
||||||
|
invalids.push(id);
|
||||||
|
if (key.length === 64) //convert Hex to WIF if needed
|
||||||
|
parameters.privkeys[i] = coinjs.privkey2wif(key);
|
||||||
|
});
|
||||||
|
if (invalids.length)
|
||||||
|
throw "Invalid private key for address:" + invalids;
|
||||||
|
}
|
||||||
|
//receiver-ids (and change-id)
|
||||||
|
if (!Array.isArray(parameters.receivers))
|
||||||
|
parameters.receivers = [parameters.receivers];
|
||||||
|
parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null);
|
||||||
|
if (invalids.length)
|
||||||
|
throw "Invalid receivers:" + invalids;
|
||||||
|
if (parameters.change_address && !validateAddress(parameters.change_address))
|
||||||
|
throw "Invalid change_address:" + parameters.change_address;
|
||||||
|
//fee and amounts
|
||||||
|
if ((typeof parameters.fee !== "number" || parameters.fee <= 0) && parameters.fee !== null) //fee = null (auto calc)
|
||||||
|
throw "Invalid fee:" + parameters.fee;
|
||||||
|
if (!Array.isArray(parameters.amounts))
|
||||||
|
parameters.amounts = [parameters.amounts];
|
||||||
|
if (parameters.receivers.length != parameters.amounts.length)
|
||||||
|
throw "Array length for receivers and amounts should be equal";
|
||||||
|
parameters.amounts.forEach(a => typeof a !== "number" || a <= 0 ? invalids.push(a) : null);
|
||||||
|
if (invalids.length)
|
||||||
|
throw "Invalid amounts:" + invalids;
|
||||||
|
//return
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
|
||||||
|
const tx = coinjs.transaction();
|
||||||
|
let output_size = addOutputs(tx, receivers, amounts, change_address);
|
||||||
|
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
|
||||||
|
if (result.change_amount > 0 && result.change_amount > result.fee) //add change amount if any (ignore dust change)
|
||||||
|
tx.outs[tx.outs.length - 1].value = util.BTC_to_Sat(result.change_amount); //values are in satoshi
|
||||||
|
if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
|
||||||
|
let fee_remaining = util.BTC_to_Sat(result.fee);
|
||||||
|
for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) {
|
||||||
|
if (fee_remaining < tx.outs[i].value) {
|
||||||
|
tx.outs[i].value -= fee_remaining;
|
||||||
|
fee_remaining = 0;
|
||||||
|
} else {
|
||||||
|
fee_remaining -= tx.outs[i].value;
|
||||||
|
tx.outs[i].value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fee_remaining > 0)
|
||||||
|
return reject("Send amount is less than fee");
|
||||||
|
|
||||||
|
}
|
||||||
|
//remove all output with value less than DUST amount
|
||||||
|
let filtered_outputs = [], dust_value = 0;
|
||||||
|
tx.outs.forEach(o => o.value >= DUST_AMT ? filtered_outputs.push(o) : dust_value += o.value);
|
||||||
|
tx.outs = filtered_outputs;
|
||||||
|
//update result values
|
||||||
|
result.fee += util.Sat_to_BTC(dust_value);
|
||||||
|
result.output_size = output_size;
|
||||||
|
result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0);
|
||||||
|
result.total_size = BASE_TX_SIZE + output_size + result.input_size;
|
||||||
|
result.transaction = tx;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (fee !== null) {
|
||||||
|
addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => {
|
||||||
|
result.fee = fee;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
} else {
|
||||||
|
get_fee_rate().then(fee_rate => {
|
||||||
|
let net_fee = BASE_TX_SIZE * fee_rate;
|
||||||
|
net_fee += (output_size * fee_rate);
|
||||||
|
(fee_from_receiver ?
|
||||||
|
addUTXOs(tx, senders, redeemScripts, total_amount, false) :
|
||||||
|
addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate)
|
||||||
|
).then(result => {
|
||||||
|
result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8));
|
||||||
|
result.fee_rate = fee_rate;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
required_amount = parseFloat(required_amount.toFixed(8));
|
||||||
|
if (typeof rec_args.n === "undefined") {
|
||||||
|
rec_args.n = 0;
|
||||||
|
rec_args.input_size = 0;
|
||||||
|
rec_args.input_amount = 0;
|
||||||
|
}
|
||||||
|
if (required_amount <= 0)
|
||||||
|
return resolve({
|
||||||
|
input_size: rec_args.input_size,
|
||||||
|
input_amount: rec_args.input_amount,
|
||||||
|
change_amount: required_amount * -1 //required_amount will be -ve of change_amount
|
||||||
|
});
|
||||||
|
else if (rec_args.n >= senders.length)
|
||||||
|
return reject("Insufficient Balance");
|
||||||
|
let addr = senders[rec_args.n],
|
||||||
|
rs = redeemScripts[rec_args.n];
|
||||||
|
let addr_type = coinjs.addressDecode(addr).type;
|
||||||
|
let size_per_input = _sizePerInput(addr, rs);
|
||||||
|
fetch_api(`unspent?active=${addr}`).then(result => {
|
||||||
|
let utxos = result.unspent_outputs;
|
||||||
|
//console.debug("add-utxo", addr, rs, required_amount, utxos);
|
||||||
|
for (let i = 0; i < utxos.length && required_amount > 0; i++) {
|
||||||
|
if (!utxos[i].confirmations) //ignore unconfirmed utxo
|
||||||
|
continue;
|
||||||
|
var script;
|
||||||
|
if (!rs || !rs.length) //legacy script
|
||||||
|
script = utxos[i].script;
|
||||||
|
else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi)) || addr_type === 'multisigBech32') {
|
||||||
|
//redeemScript for segwit/bech32 and multisig (bech32)
|
||||||
|
let s = coinjs.script();
|
||||||
|
s.writeBytes(Crypto.util.hexToBytes(rs));
|
||||||
|
s.writeOp(0);
|
||||||
|
s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0), 8));
|
||||||
|
script = Crypto.util.bytesToHex(s.buffer);
|
||||||
|
} else //redeemScript for multisig (segwit)
|
||||||
|
script = rs;
|
||||||
|
tx.addinput(utxos[i].tx_hash_big_endian, utxos[i].tx_output_n, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
|
||||||
|
//update track values
|
||||||
|
rec_args.input_size += size_per_input;
|
||||||
|
rec_args.input_amount += util.Sat_to_BTC(utxos[i].value);
|
||||||
|
required_amount -= util.Sat_to_BTC(utxos[i].value);
|
||||||
|
if (fee_rate) //automatic fee calculation (dynamic)
|
||||||
|
required_amount += size_per_input * fee_rate;
|
||||||
|
}
|
||||||
|
rec_args.n += 1;
|
||||||
|
addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addOutputs(tx, receivers, amounts, change_address) {
|
||||||
|
let size = 0;
|
||||||
|
for (let i in receivers) {
|
||||||
|
tx.addoutput(receivers[i], amounts[i]);
|
||||||
|
size += _sizePerOutput(receivers[i]);
|
||||||
|
}
|
||||||
|
tx.addoutput(change_address, 0);
|
||||||
|
size += _sizePerOutput(change_address);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
function autoFeeCalc(tx) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
get_fee_rate().then(fee_rate => {
|
||||||
|
let tx_size = tx.size();
|
||||||
|
for (var i = 0; i < this.ins.length; i++)
|
||||||
|
switch (tx.extractScriptKey(i).type) {
|
||||||
|
case 'scriptpubkey':
|
||||||
|
tx_size += SIGN_SIZE;
|
||||||
|
break;
|
||||||
|
case 'segwit':
|
||||||
|
case 'multisig':
|
||||||
|
tx_size += SIGN_SIZE * 0.25;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn('Unknown script-type');
|
||||||
|
tx_size += SIGN_SIZE;
|
||||||
|
}
|
||||||
|
resolve(tx_size * fee_rate);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function editFee(tx, current_fee, target_fee, index = -1) {
|
||||||
|
//values are in satoshi
|
||||||
|
index = parseInt(index >= 0 ? index : tx.outs.length - index);
|
||||||
|
if (index < 0 || index >= tx.outs.length)
|
||||||
|
throw "Invalid index";
|
||||||
|
let edit_value = parseInt(current_fee - target_fee), //rip of any decimal places
|
||||||
|
current_value = tx.outs[index].value; //could be BigInterger
|
||||||
|
if (edit_value < 0 && edit_value > current_value)
|
||||||
|
throw "Insufficient value at vout";
|
||||||
|
tx.outs[index].value = current_value instanceof BigInteger ?
|
||||||
|
current_value.add(new BigInteger('' + edit_value)) : parseInt(current_value + edit_value);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function tx_fetch_for_editing(tx) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (typeof tx == 'string' && /^[0-9a-f]{64}$/i.test(tx)) { //tx is txid
|
||||||
|
getTx.hex(tx)
|
||||||
|
.then(txhex => resolve(deserializeTx(txhex)))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
} else resolve(deserializeTx(tx));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
btcOperator.editFee = function (tx_hex, new_fee, private_keys, change_only = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!Array.isArray(private_keys))
|
||||||
|
private_keys = [private_keys];
|
||||||
|
tx_fetch_for_editing(tx_hex).then(tx => {
|
||||||
|
parseTransaction(tx).then(tx_parsed => {
|
||||||
|
if (tx_parsed.fee >= new_fee)
|
||||||
|
return reject("Fees can only be increased");
|
||||||
|
|
||||||
|
//editable addresses in output values (for fee increase)
|
||||||
|
var edit_output_address = new Set();
|
||||||
|
if (change_only === true) //allow only change values (ie, sender address) to be edited to inc fee
|
||||||
|
tx_parsed.inputs.forEach(inp => edit_output_address.add(inp.address));
|
||||||
|
else if (change_only === false) //allow all output values to be edited
|
||||||
|
tx_parsed.outputs.forEach(out => edit_output_address.add(out.address));
|
||||||
|
else if (typeof change_only == 'string') // allow only given receiver id output to be edited
|
||||||
|
edit_output_address.add(change_only);
|
||||||
|
else if (Array.isArray(change_only)) //allow only given set of receiver id outputs to be edited
|
||||||
|
change_only.forEach(id => edit_output_address.add(id));
|
||||||
|
|
||||||
|
//edit output values to increase fee
|
||||||
|
let inc_fee = util.BTC_to_Sat(new_fee - tx_parsed.fee);
|
||||||
|
if (inc_fee < MIN_FEE_UPDATE)
|
||||||
|
return reject(`Insufficient additional fee. Minimum increment: ${MIN_FEE_UPDATE}`);
|
||||||
|
for (let i = tx.outs.length - 1; i >= 0 && inc_fee > 0; i--) //reduce in reverse order
|
||||||
|
if (edit_output_address.has(tx_parsed.outputs[i].address)) {
|
||||||
|
let current_value = tx.outs[i].value;
|
||||||
|
if (current_value instanceof BigInteger) //convert BigInteger class to inv value
|
||||||
|
current_value = current_value.intValue();
|
||||||
|
//edit the value as required
|
||||||
|
if (current_value > inc_fee) {
|
||||||
|
tx.outs[i].value = current_value - inc_fee;
|
||||||
|
inc_fee = 0;
|
||||||
|
} else {
|
||||||
|
inc_fee -= current_value;
|
||||||
|
tx.outs[i].value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inc_fee > 0) {
|
||||||
|
let max_possible_fee = util.BTC_to_Sat(new_fee) - inc_fee; //in satoshi
|
||||||
|
return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`);
|
||||||
|
}
|
||||||
|
tx.outs = tx.outs.filter(o => o.value >= DUST_AMT); //remove all output with value less than DUST amount
|
||||||
|
|
||||||
|
//remove existing signatures and reset the scripts
|
||||||
|
let wif_keys = [];
|
||||||
|
for (let i in tx.ins) {
|
||||||
|
var addr = tx_parsed.inputs[i].address,
|
||||||
|
value = util.BTC_to_Sat(tx_parsed.inputs[i].value);
|
||||||
|
let addr_decode = coinjs.addressDecode(addr);
|
||||||
|
//find the correct key for addr
|
||||||
|
var privKey = private_keys.find(pk => verifyKey(addr, pk));
|
||||||
|
if (!privKey)
|
||||||
|
return reject(`Private key missing for ${addr}`);
|
||||||
|
//find redeemScript (if any)
|
||||||
|
const rs = _redeemScript(addr, privKey);
|
||||||
|
rs === false ? wif_keys.unshift(privKey) : wif_keys.push(privKey); //sorting private-keys (wif)
|
||||||
|
//reset the script for re-signing
|
||||||
|
var script;
|
||||||
|
if (!rs || !rs.length) {
|
||||||
|
//legacy script (derive from address)
|
||||||
|
let s = coinjs.script();
|
||||||
|
s.writeOp(118); //OP_DUP
|
||||||
|
s.writeOp(169); //OP_HASH160
|
||||||
|
s.writeBytes(addr_decode.bytes);
|
||||||
|
s.writeOp(136); //OP_EQUALVERIFY
|
||||||
|
s.writeOp(172); //OP_CHECKSIG
|
||||||
|
script = Crypto.util.bytesToHex(s.buffer);
|
||||||
|
} else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi)) || addr_decode.type === 'multisigBech32') {
|
||||||
|
//redeemScript for segwit/bech32 and multisig (bech32)
|
||||||
|
let s = coinjs.script();
|
||||||
|
s.writeBytes(Crypto.util.hexToBytes(rs));
|
||||||
|
s.writeOp(0);
|
||||||
|
s.writeBytes(coinjs.numToBytes(value.toFixed(0), 8));
|
||||||
|
script = Crypto.util.bytesToHex(s.buffer);
|
||||||
|
} else //redeemScript for multisig (segwit)
|
||||||
|
script = rs;
|
||||||
|
tx.ins[i].script = coinjs.script(script);
|
||||||
|
}
|
||||||
|
tx.witness = false; //remove all witness signatures
|
||||||
|
console.debug("Unsigned:", tx.serialize());
|
||||||
|
//re-sign the transaction
|
||||||
|
new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/)); //Sign the tx using private key WIF
|
||||||
|
resolve(tx.serialize());
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => {
|
||||||
|
debugger;
|
||||||
|
broadcastTx(result.transaction.serialize())
|
||||||
|
.then(txid => resolve(txid))
|
||||||
|
.catch(error => reject(error));
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
({
|
||||||
|
senders,
|
||||||
|
privkeys,
|
||||||
|
receivers,
|
||||||
|
amounts
|
||||||
|
} = validateTxParameters({
|
||||||
|
senders,
|
||||||
|
privkeys,
|
||||||
|
receivers,
|
||||||
|
amounts,
|
||||||
|
fee,
|
||||||
|
change_address: options.change_address
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
return reject(e)
|
||||||
|
}
|
||||||
|
let redeemScripts = [],
|
||||||
|
wif_keys = [];
|
||||||
|
for (let i in senders) {
|
||||||
|
let rs = _redeemScript(senders[i], privkeys[i]); //get redeem-script (segwit/bech32)
|
||||||
|
redeemScripts.push(rs);
|
||||||
|
rs === false ? wif_keys.unshift(privkeys[i]) : wif_keys.push(privkeys[i]); //sorting private-keys (wif)
|
||||||
|
}
|
||||||
|
if (redeemScripts.includes(null)) //TODO: segwit
|
||||||
|
return reject("Unable to get redeem-script");
|
||||||
|
//create transaction
|
||||||
|
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
|
||||||
|
let tx = result.transaction;
|
||||||
|
console.debug("Unsigned:", tx.serialize());
|
||||||
|
new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/)); //Sign the tx using private key WIF
|
||||||
|
console.debug("Signed:", tx.serialize());
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
({
|
||||||
|
senders,
|
||||||
|
receivers,
|
||||||
|
amounts
|
||||||
|
} = validateTxParameters({
|
||||||
|
senders,
|
||||||
|
receivers,
|
||||||
|
amounts,
|
||||||
|
fee,
|
||||||
|
change_address: options.change_address
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
return reject(e)
|
||||||
|
}
|
||||||
|
let redeemScripts = senders.map(id => _redeemScript(id));
|
||||||
|
if (redeemScripts.includes(null)) //TODO: segwit
|
||||||
|
return reject("Unable to get redeem-script");
|
||||||
|
//create transaction
|
||||||
|
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
|
||||||
|
result.tx_hex = result.transaction.serialize();
|
||||||
|
delete result.transaction;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
//validate tx parameters
|
||||||
|
let addr_type = validateAddress(sender);
|
||||||
|
if (!(["multisig", "multisigBech32"].includes(addr_type)))
|
||||||
|
return reject("Invalid sender (multisig):" + sender);
|
||||||
|
else {
|
||||||
|
let script = coinjs.script();
|
||||||
|
let decode = (addr_type == "multisig") ?
|
||||||
|
script.decodeRedeemScript(redeemScript) :
|
||||||
|
script.decodeRedeemScriptBech32(redeemScript);
|
||||||
|
if (!decode || decode.address !== sender)
|
||||||
|
return reject("Invalid redeem-script");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
({
|
||||||
|
receivers,
|
||||||
|
amounts
|
||||||
|
} = validateTxParameters({
|
||||||
|
receivers,
|
||||||
|
amounts,
|
||||||
|
fee,
|
||||||
|
change_address: options.change_address
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
return reject(e)
|
||||||
|
}
|
||||||
|
//create transaction
|
||||||
|
createTransaction([sender], [redeemScript], receivers, amounts, fee, options.change_address || sender, options.fee_from_receiver).then(result => {
|
||||||
|
result.tx_hex = result.transaction.serialize();
|
||||||
|
delete result.transaction;
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserializeTx(tx) {
|
||||||
|
if (typeof tx === 'string' || Array.isArray(tx)) {
|
||||||
|
try {
|
||||||
|
tx = coinjs.transaction().deserialize(tx);
|
||||||
|
} catch {
|
||||||
|
throw "Invalid transaction hex";
|
||||||
|
}
|
||||||
|
} else if (typeof tx !== 'object' || typeof tx.sign !== 'function')
|
||||||
|
throw "Invalid transaction object";
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.signTx = function (tx, privkeys, sighashtype = 1) {
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
if (!Array.isArray(privkeys))
|
||||||
|
privkeys = [privkeys];
|
||||||
|
for (let i in privkeys)
|
||||||
|
if (privkeys[i].length === 64)
|
||||||
|
privkeys[i] = coinjs.privkey2wif(privkeys[i]);
|
||||||
|
new Set(privkeys).forEach(key => tx.sign(key, sighashtype)); //Sign the tx using private key WIF
|
||||||
|
return tx.serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkSigned = btcOperator.checkSigned = function (tx, bool = true) {
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
let n = [];
|
||||||
|
for (let i in tx.ins) {
|
||||||
|
var s = tx.extractScriptKey(i);
|
||||||
|
if (s['type'] !== 'multisig' && s['type'] !== 'multisig_bech32')
|
||||||
|
n.push(s.signed == 'true' || (tx.witness[i] && tx.witness[i].length == 2))
|
||||||
|
else {
|
||||||
|
var rs = coinjs.script().decodeRedeemScript(s.script); //will work for bech32 too, as only address is diff
|
||||||
|
let x = {
|
||||||
|
s: s['signatures'],
|
||||||
|
r: rs['signaturesRequired'],
|
||||||
|
t: rs['pubkeys'].length
|
||||||
|
};
|
||||||
|
if (x.r > x.t)
|
||||||
|
throw "signaturesRequired is more than publicKeys";
|
||||||
|
else if (x.s < x.r)
|
||||||
|
n.push(x);
|
||||||
|
else
|
||||||
|
n.push(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bool ? !(n.filter(x => x !== true).length) : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.checkIfSameTx = function (tx1, tx2) {
|
||||||
|
tx1 = deserializeTx(tx1);
|
||||||
|
tx2 = deserializeTx(tx2);
|
||||||
|
//compare input and output length
|
||||||
|
if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length)
|
||||||
|
return false;
|
||||||
|
//compare inputs
|
||||||
|
for (let i = 0; i < tx1.ins.length; i++)
|
||||||
|
if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index)
|
||||||
|
return false;
|
||||||
|
//compare outputs
|
||||||
|
for (let i = 0; i < tx1.outs.length; i++)
|
||||||
|
if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`rawtx/${txid}`)
|
||||||
|
.then(result => resolve(result.out[i]))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
});
|
||||||
|
|
||||||
|
const parseTransaction = btcOperator.parseTransaction = function (tx) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
let result = {};
|
||||||
|
let promises = [];
|
||||||
|
//Parse Inputs
|
||||||
|
for (let i = 0; i < tx.ins.length; i++)
|
||||||
|
promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index));
|
||||||
|
Promise.all(promises).then(inputs => {
|
||||||
|
result.inputs = inputs.map(inp => Object({
|
||||||
|
address: inp.addr,
|
||||||
|
value: util.Sat_to_BTC(inp.value)
|
||||||
|
}));
|
||||||
|
let signed = checkSigned(tx, false);
|
||||||
|
result.inputs.forEach((inp, i) => inp.signed = signed[i]);
|
||||||
|
//Parse Outputs
|
||||||
|
result.outputs = tx.outs.map(out => {
|
||||||
|
var address;
|
||||||
|
switch (out.script.chunks[0]) {
|
||||||
|
case 0: //bech32, multisig-bech32
|
||||||
|
address = util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp);
|
||||||
|
break;
|
||||||
|
case 169: //segwit, multisig-segwit
|
||||||
|
address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig);
|
||||||
|
break;
|
||||||
|
case 118: //legacy
|
||||||
|
address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
address,
|
||||||
|
value: util.Sat_to_BTC(out.value)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//Parse Totals
|
||||||
|
result.total_input = parseFloat(result.inputs.reduce((a, inp) => a += inp.value, 0).toFixed(8));
|
||||||
|
result.total_output = parseFloat(result.outputs.reduce((a, out) => a += out.value, 0).toFixed(8));
|
||||||
|
result.fee = parseFloat((result.total_input - result.total_output).toFixed(8));
|
||||||
|
resolve(result);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
btcOperator.transactionID = function (tx) {
|
||||||
|
tx = deserializeTx(tx);
|
||||||
|
let clone = coinjs.clone(tx);
|
||||||
|
clone.witness = null;
|
||||||
|
let raw_bytes = Crypto.util.hexToBytes(clone.serialize());
|
||||||
|
let txid = Crypto.SHA256(Crypto.SHA256(raw_bytes, { asBytes: true }), { asBytes: true }).reverse();
|
||||||
|
return Crypto.util.bytesToHex(txid);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLatestBlock = btcOperator.getLatestBlock = () => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`q/getblockcount`)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
|
||||||
|
const getTx = btcOperator.getTx = txid => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`rawtx/${txid}`).then(result => {
|
||||||
|
getLatestBlock().then(latest_block => resolve({
|
||||||
|
block: result.block_height,
|
||||||
|
txid: result.hash,
|
||||||
|
time: result.time * 1000,
|
||||||
|
confirmations: result.block_height === null ? 0 : latest_block - result.block_height, //calculate confirmations using latest block number as api doesnt relay it
|
||||||
|
size: result.size,
|
||||||
|
fee: util.Sat_to_BTC(result.fee),
|
||||||
|
inputs: result.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
|
||||||
|
total_input_value: util.Sat_to_BTC(result.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
|
||||||
|
outputs: result.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
|
||||||
|
total_output_value: util.Sat_to_BTC(result.out.reduce((a, o) => a += o.value, 0)),
|
||||||
|
}))
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
|
||||||
|
getTx.hex = txid => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`rawtx/${txid}?format=hex`, false)
|
||||||
|
.then(result => resolve(result))
|
||||||
|
.catch(error => reject(error))
|
||||||
|
})
|
||||||
|
|
||||||
|
btcOperator.getAddressData = address => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`rawaddr/${address}`).then(data => {
|
||||||
|
let details = {};
|
||||||
|
details.balance = util.Sat_to_BTC(data.final_balance);
|
||||||
|
details.address = data.address;
|
||||||
|
details.txs = data.txs.map(tx => {
|
||||||
|
let d = {
|
||||||
|
txid: tx.hash,
|
||||||
|
time: tx.time * 1000, //s to ms
|
||||||
|
block: tx.block_height,
|
||||||
|
}
|
||||||
|
//sender list
|
||||||
|
d.tx_senders = {};
|
||||||
|
tx.inputs.forEach(i => {
|
||||||
|
if (i.prev_out.addr in d.tx_senders)
|
||||||
|
d.tx_senders[i.prev_out.addr] += i.prev_out.value;
|
||||||
|
else d.tx_senders[i.prev_out.addr] = i.prev_out.value;
|
||||||
|
});
|
||||||
|
d.tx_input_value = 0;
|
||||||
|
for (let s in d.tx_senders) {
|
||||||
|
let val = d.tx_senders[s];
|
||||||
|
d.tx_senders[s] = util.Sat_to_BTC(val);
|
||||||
|
d.tx_input_value += val;
|
||||||
|
}
|
||||||
|
d.tx_input_value = util.Sat_to_BTC(d.tx_input_value);
|
||||||
|
//receiver list
|
||||||
|
d.tx_receivers = {};
|
||||||
|
tx.out.forEach(o => {
|
||||||
|
if (o.addr in d.tx_receivers)
|
||||||
|
d.tx_receivers[o.addr] += o.value;
|
||||||
|
else d.tx_receivers[o.addr] = o.value;
|
||||||
|
});
|
||||||
|
d.tx_output_value = 0;
|
||||||
|
for (let r in d.tx_receivers) {
|
||||||
|
let val = d.tx_receivers[r];
|
||||||
|
d.tx_receivers[r] = util.Sat_to_BTC(val);
|
||||||
|
d.tx_output_value += val;
|
||||||
|
}
|
||||||
|
d.tx_output_value = util.Sat_to_BTC(d.tx_output_value);
|
||||||
|
d.tx_fee = util.Sat_to_BTC(tx.fee);
|
||||||
|
//tx type
|
||||||
|
if (tx.result > 0) { //net > 0, balance inc => type=in
|
||||||
|
d.type = "in";
|
||||||
|
d.amount = util.Sat_to_BTC(tx.result);
|
||||||
|
d.sender = Object.keys(d.tx_senders).filter(s => s !== address);
|
||||||
|
} else if (Object.keys(d.tx_receivers).some(r => r !== address)) { //net < 0, balance dec & receiver present => type=out
|
||||||
|
d.type = "out";
|
||||||
|
d.amount = util.Sat_to_BTC(tx.result * -1);
|
||||||
|
d.receiver = Object.keys(d.tx_receivers).filter(r => r !== address);
|
||||||
|
d.fee = d.tx_fee;
|
||||||
|
} else { //net < 0 (fee) & no other id in receiver list => type=self
|
||||||
|
d.type = "self";
|
||||||
|
d.amount = d.tx_receivers[address];
|
||||||
|
d.address = address
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
})
|
||||||
|
resolve(details);
|
||||||
|
}).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
|
||||||
|
btcOperator.getBlock = block => new Promise((resolve, reject) => {
|
||||||
|
fetch_api(`rawblock/${block}`).then(result => resolve({
|
||||||
|
height: result.height,
|
||||||
|
hash: result.hash,
|
||||||
|
merkle_root: result.mrkl_root,
|
||||||
|
prev_block: result.prev_block,
|
||||||
|
next_block: result.next_block[0],
|
||||||
|
size: result.size,
|
||||||
|
time: result.time * 1000, //s to ms
|
||||||
|
txs: result.tx.map(t => Object({
|
||||||
|
fee: t.fee,
|
||||||
|
size: t.size,
|
||||||
|
inputs: t.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
|
||||||
|
total_input_value: util.Sat_to_BTC(t.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
|
||||||
|
outputs: t.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
|
||||||
|
total_output_value: util.Sat_to_BTC(t.out.reduce((a, o) => a += o.value, 0)),
|
||||||
|
}))
|
||||||
|
|
||||||
|
})).catch(error => reject(error))
|
||||||
|
});
|
||||||
|
|
||||||
|
})('object' === typeof module ? module.exports : window.btcOperator = {});
|
||||||
257
cc/scripts/compactIDB.js
Normal file
257
cc/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 = {});
|
||||||
16
cc/scripts/components.js
Normal file
16
cc/scripts/components.js
Normal file
File diff suppressed because one or more lines are too long
1044
cc/scripts/floBlockchainAPI.js
Normal file
1044
cc/scripts/floBlockchainAPI.js
Normal file
File diff suppressed because it is too large
Load Diff
1106
cc/scripts/floCloudAPI.js
Normal file
1106
cc/scripts/floCloudAPI.js
Normal file
File diff suppressed because it is too large
Load Diff
530
cc/scripts/floCrypto.js
Normal file
530
cc/scripts/floCrypto.js
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
(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 //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.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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 = {});
|
||||||
843
cc/scripts/floDapps.js
Normal file
843
cc/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
cc/scripts/floTokenAPI.js
Normal file
166
cc/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 = {});
|
||||||
9975
cc/scripts/lib.js
Normal file
9975
cc/scripts/lib.js
Normal file
File diff suppressed because it is too large
Load Diff
3
cc/scripts/purify.min.js
vendored
Normal file
3
cc/scripts/purify.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
443
cc/template/css/main.css
Normal file
443
cc/template/css/main.css
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
* {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-size: clamp(1rem, 1.2vmax, 1.2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: rgba(var(--text-color), 1);
|
||||||
|
background: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
body * {
|
||||||
|
--accent-color: rgb(0, 156, 78);
|
||||||
|
--text-color: 36, 36, 36;
|
||||||
|
--background-color: 248, 248, 248;
|
||||||
|
--foreground-color: rgb(255, 255, 255);
|
||||||
|
--danger-color: rgb(255, 75, 75);
|
||||||
|
--like-color: #e91e63;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=dark],
|
||||||
|
body[data-theme=dark] * {
|
||||||
|
--accent-color: rgb(14, 230, 122);
|
||||||
|
--text-color: 230, 230, 230;
|
||||||
|
--text-color-light: 170, 170, 170;
|
||||||
|
--background-color: 10, 10, 10;
|
||||||
|
--foreground-color: rgb(24, 24, 24);
|
||||||
|
--danger-color: rgb(255, 106, 106);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=dark] sm-popup::part(popup) {
|
||||||
|
background-color: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
max-width: 70ch;
|
||||||
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
p * {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direction-column {
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-column {
|
||||||
|
grid-auto-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-0-5 {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-1 {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-1-5 {
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-2 {
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-3 {
|
||||||
|
gap: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-self-center {
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-self-start {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-self-end {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
fill: rgba(var(--text-color), 0.8);
|
||||||
|
-ms-flex-negative: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
.button {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
display: -webkit-inline-box;
|
||||||
|
display: -ms-inline-flexbox;
|
||||||
|
display: inline-flex;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
overflow: hidden;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-transition: -webkit-transform 0.3s;
|
||||||
|
transition: -webkit-transform 0.3s;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
transition: transform 0.3s, -webkit-transform 0.3s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
-webkit-box-pack: center;
|
||||||
|
-ms-flex-pack: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.button--primary {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
color: rgba(var(--background-color), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active,
|
||||||
|
.button:active,
|
||||||
|
sm-button:not([disabled]):active,
|
||||||
|
.interact:active {
|
||||||
|
-webkit-transform: scale(0.9);
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup__header {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 1.5rem 0 0.5rem;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup__header__close {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sign_in,
|
||||||
|
#sign_up {
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#sign_in sm-form,
|
||||||
|
#sign_up sm-form {
|
||||||
|
margin: 2rem 0;
|
||||||
|
--gap: 1rem;
|
||||||
|
}
|
||||||
|
#sign_in header,
|
||||||
|
#sign_up header {
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sign_up sm-copy {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
#sign_up h5 {
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: rgba(var(--text-color), 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
background-color: khaki;
|
||||||
|
color: rgba(0, 0, 0, 0.7);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main_header {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: 1fr auto auto;
|
||||||
|
background-color: var(--foreground-color);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: grid;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: 0 0.5rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo h4 {
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-logo {
|
||||||
|
height: 1.4rem;
|
||||||
|
width: 1.4rem;
|
||||||
|
fill: rgba(var(--text-color), 1);
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 3rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
article::after {
|
||||||
|
justify-self: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
content: "";
|
||||||
|
width: 4rem;
|
||||||
|
height: 0.3rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: rgba(var(--text-color), 0.5);
|
||||||
|
}
|
||||||
|
article p {
|
||||||
|
font-family: "noto serif", serif;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1rem minmax(0, 1fr) 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-layout > * {
|
||||||
|
grid-column: 2/3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
-o-object-fit: cover;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
height: 40vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3:not(:first-of-type) {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-bleed {
|
||||||
|
grid-column: 1/-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-template {
|
||||||
|
position: relative;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: solid thin rgba(var(--text-color), 0.3);
|
||||||
|
-webkit-box-shadow: 0.3rem 0.5rem 0 0.1rem rgba(var(--text-color), 0.8);
|
||||||
|
box-shadow: 0.3rem 0.5rem 0 0.1rem rgba(var(--text-color), 0.8);
|
||||||
|
overflow: hidden;
|
||||||
|
justify-self: center;
|
||||||
|
padding-left: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-template figcaption {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upvote {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
position: relative;
|
||||||
|
padding: 0.8rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
background-color: var(--foreground-color);
|
||||||
|
-webkit-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
||||||
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
||||||
|
border: solid rgba(var(--text-color), 0.2) thin;
|
||||||
|
}
|
||||||
|
.upvote > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.upvote:active {
|
||||||
|
-webkit-transform: none;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
.upvote:active .icon {
|
||||||
|
-webkit-transform: scale(0.7);
|
||||||
|
transform: scale(0.7);
|
||||||
|
}
|
||||||
|
.upvote.liked {
|
||||||
|
background-color: var(--like-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.upvote.liked .icon {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanding-heart,
|
||||||
|
.ring {
|
||||||
|
grid-area: 1/1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ring {
|
||||||
|
border: 0.1rem solid var(--like-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 0.5rem;
|
||||||
|
width: 0.5rem;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upvote .icon {
|
||||||
|
grid-area: 1/1;
|
||||||
|
fill: var(--like-color);
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
-webkit-transition: -webkit-transform 0.2s;
|
||||||
|
transition: -webkit-transform 0.2s;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
transition: transform 0.2s, -webkit-transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-count,
|
||||||
|
#like_count {
|
||||||
|
grid-area: 1/2;
|
||||||
|
}
|
||||||
|
.temp-count:not(:empty),
|
||||||
|
#like_count:not(:empty) {
|
||||||
|
margin-left: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 40rem) {
|
||||||
|
sm-popup {
|
||||||
|
--width: 24rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup__header {
|
||||||
|
padding: 1rem 1.5rem 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-layout {
|
||||||
|
grid-template-columns: 1fr 60ch 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (any-hover: hover) {
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(var(--text-color), 0.3);
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(var(--text-color), 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.hide-completely {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
1
cc/template/css/main.min.css
vendored
Normal file
1
cc/template/css/main.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
396
cc/template/css/main.scss
Normal file
396
cc/template/css/main.scss
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
* {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-size: clamp(1rem, 1.2vmax, 1.2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: rgba(var(--text-color), 1);
|
||||||
|
background: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
body * {
|
||||||
|
--accent-color: rgb(0, 156, 78);
|
||||||
|
--text-color: 36, 36, 36;
|
||||||
|
--background-color: 248, 248, 248;
|
||||||
|
--foreground-color: rgb(255, 255, 255);
|
||||||
|
--danger-color: rgb(255, 75, 75);
|
||||||
|
--like-color: #e91e63;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme="dark"],
|
||||||
|
body[data-theme="dark"] * {
|
||||||
|
--accent-color: rgb(14, 230, 122);
|
||||||
|
--text-color: 230, 230, 230;
|
||||||
|
--text-color-light: 170, 170, 170;
|
||||||
|
--background-color: 10, 10, 10;
|
||||||
|
--foreground-color: rgb(24, 24, 24);
|
||||||
|
--danger-color: rgb(255, 106, 106);
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme="dark"] sm-popup::part(popup) {
|
||||||
|
background-color: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
max-width: 70ch;
|
||||||
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
p * {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.direction-column {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-column {
|
||||||
|
grid-auto-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-0-5 {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-1 {
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-1-5 {
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-2 {
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-3 {
|
||||||
|
gap: 3rem;
|
||||||
|
}
|
||||||
|
.justify-self-center {
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-self-start {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-self-end {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
fill: rgba(var(--text-color), 0.8);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
.button {
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
overflow: hidden;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
background-color: rgba(var(--text-color), 0.06);
|
||||||
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
justify-content: center;
|
||||||
|
&--primary {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
color: rgba(var(--background-color), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active,
|
||||||
|
.button:active,
|
||||||
|
sm-button:not([disabled]):active,
|
||||||
|
.interact:active {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
.popup__header {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 1.5rem 0 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
}
|
||||||
|
.popup__header__close {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sign_in,
|
||||||
|
#sign_up {
|
||||||
|
align-items: center;
|
||||||
|
sm-form {
|
||||||
|
margin: 2rem 0;
|
||||||
|
--gap: 1rem;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#sign_up {
|
||||||
|
sm-copy {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: rgba(var(--text-color), 0.04);
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
background-color: khaki;
|
||||||
|
color: rgba(0, 0, 0, 0.7);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main_header {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: 1fr auto auto;
|
||||||
|
background-color: var(--foreground-color);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: 0 0.5rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo h4 {
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-logo {
|
||||||
|
height: 1.4rem;
|
||||||
|
width: 1.4rem;
|
||||||
|
fill: rgba(var(--text-color), 1);
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 3rem;
|
||||||
|
gap: 1rem;
|
||||||
|
&::after {
|
||||||
|
justify-self: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
content: "";
|
||||||
|
width: 4rem;
|
||||||
|
height: 0.3rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: rgba(var(--text-color), 0.5);
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-family: "noto serif", serif;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1rem minmax(0, 1fr) 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-layout > * {
|
||||||
|
grid-column: 2/3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
height: 40vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3:not(:first-of-type) {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-bleed {
|
||||||
|
grid-column: 1/-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-template {
|
||||||
|
position: relative;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
border: solid thin rgba(var(--text-color), 0.3);
|
||||||
|
box-shadow: 0.3rem 0.5rem 0 0.1rem rgba(var(--text-color), 0.8);
|
||||||
|
overflow: hidden;
|
||||||
|
justify-self: center;
|
||||||
|
padding-left: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-template figcaption {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upvote {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
position: relative;
|
||||||
|
padding: 0.8rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
background-color: var(--foreground-color);
|
||||||
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
||||||
|
border: solid rgba(var(--text-color), 0.2) thin;
|
||||||
|
& > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: none;
|
||||||
|
.icon {
|
||||||
|
transform: scale(0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.liked {
|
||||||
|
background-color: var(--like-color);
|
||||||
|
color: white;
|
||||||
|
.icon {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.expanding-heart,
|
||||||
|
.ring {
|
||||||
|
grid-area: 1/1;
|
||||||
|
}
|
||||||
|
.ring {
|
||||||
|
border: 0.1rem solid var(--like-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 0.5rem;
|
||||||
|
width: 0.5rem;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
.upvote .icon {
|
||||||
|
grid-area: 1/1;
|
||||||
|
fill: var(--like-color);
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
.temp-count,
|
||||||
|
#like_count {
|
||||||
|
grid-area: 1/2;
|
||||||
|
&:not(:empty) {
|
||||||
|
margin-left: 0.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 40rem) {
|
||||||
|
sm-popup {
|
||||||
|
--width: 24rem;
|
||||||
|
}
|
||||||
|
.popup__header {
|
||||||
|
padding: 1rem 1.5rem 0 1rem;
|
||||||
|
}
|
||||||
|
.page-layout {
|
||||||
|
grid-template-columns: 1fr 60ch 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (any-hover: hover) {
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(var(--text-color), 0.3);
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(var(--text-color), 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// highest priority styles
|
||||||
|
.hide-completely {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
11445
cc/template/index.html
Normal file
11445
cc/template/index.html
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user