mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer.git
synced 2026-09-21 16:52:03 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cfaceb99e | ||
|
|
b12cc194f4 | ||
|
|
7cffd9f377 | ||
|
|
0a406cb9a4 | ||
|
|
f172a51016 | ||
|
|
00d53659b1 | ||
|
|
b0797d92f5 | ||
|
|
48484ed7af | ||
|
|
6a4aadde8a | ||
|
|
79e3aba26e | ||
|
|
bf61359bc7 | ||
|
|
1911991511 | ||
|
|
ef0179fb3f | ||
|
|
38bdfe306b | ||
|
|
5c674f4dc5 | ||
|
|
80155393d9 | ||
|
|
232417b8b1 | ||
|
|
36cb9c5734 | ||
|
|
a79d152993 | ||
|
|
ce7c3a0e2f | ||
|
|
e6b0580209 | ||
|
|
1f3c639100 | ||
|
|
e6efc4f49b | ||
|
|
dbbd321029 | ||
|
|
439dbc54ae | ||
|
|
547e59f52f | ||
|
|
a0d385de73 | ||
|
|
bec136009f | ||
|
|
98b7fa7734 | ||
|
|
d7bb476858 | ||
|
|
18c71c383f | ||
|
|
de85a933c4 | ||
|
|
16247372f1 | ||
|
|
c6c8b6418c | ||
|
|
10c4e7380f | ||
|
|
4933dce472 | ||
|
|
80c2822716 | ||
|
|
f48ff9a67e | ||
|
|
4bd2385359 | ||
|
|
be71492773 | ||
|
|
bcb70edf2b | ||
|
|
07071032bc | ||
|
|
f476e119a2 | ||
|
|
0fed232428 | ||
|
|
0273510485 | ||
|
|
a62dee37f4 | ||
|
|
8d6af3e1b7 | ||
|
|
ee0f2da641 | ||
|
|
44499d9652 | ||
|
|
365a5dcd4b | ||
|
|
7f5b456018 | ||
|
|
c7217486bd | ||
|
|
050141d31e | ||
|
|
b697f37199 | ||
|
|
ceb58caba1 | ||
|
|
2724b500ee | ||
|
|
1b84e7beb8 | ||
|
|
383900d8b5 | ||
|
|
ba806d86b5 | ||
|
|
4d7c7350a0 | ||
|
|
adc6500aed | ||
|
|
eb9fce73eb | ||
|
|
ec3ed165e4 | ||
|
|
d41d20cc81 | ||
|
|
5f965e7146 | ||
|
|
f382bb4578 | ||
|
|
ba37f87f80 | ||
|
|
eaa9c23f55 | ||
|
|
ac3730dbd9 | ||
|
|
581b8eb626 | ||
|
|
cd4ec2711e | ||
|
|
e9e3dbad0d | ||
|
|
295b361431 | ||
|
|
ad60df9d51 | ||
|
|
6ae635c21d | ||
|
|
61807a2f3d | ||
|
|
16ba5a8980 | ||
|
|
99b817549e | ||
|
|
60a8c335c8 | ||
|
|
1783768c32 | ||
|
|
b3630eaa06 | ||
|
|
d691016911 | ||
|
|
6506aead8c | ||
|
|
ddfc83d026 | ||
|
|
fe42c1e0e3 | ||
|
|
928a211a63 | ||
|
|
8bff69a4ca | ||
|
|
5fced7d501 | ||
|
|
7cdf622ad6 | ||
|
|
afd8ceac7b | ||
|
|
5d63db3662 | ||
|
|
9f6cd7dbf1 | ||
|
|
0725aa3ada | ||
|
|
3eb88d4076 | ||
|
|
1c61f150b1 | ||
|
|
4c74b287d8 | ||
|
|
264d762f74 | ||
|
|
3c02461c6b | ||
|
|
a30f2be51f | ||
|
|
0690a9fb65 | ||
|
|
4fa103765e | ||
|
|
b0d6ce40a9 | ||
|
|
c57a3d935b | ||
|
|
82b5943601 | ||
|
|
41b76967f5 | ||
|
|
e4c2ee44c1 | ||
|
|
c05f022995 | ||
|
|
7723c3a201 | ||
|
|
8e338818ca | ||
|
|
ea51aa420c | ||
|
|
bc410678b4 | ||
|
|
a42c15c969 | ||
|
|
a3102a0278 | ||
|
|
f4f142dc48 | ||
|
|
e03a70c9a3 | ||
|
|
d47b8797d4 | ||
|
|
56ba0aad02 | ||
|
|
825bc6d2de | ||
|
|
bf206107f8 | ||
|
|
b1100c21bd | ||
|
|
10d50a6776 | ||
|
|
18e7ff90aa | ||
|
|
dfe390f9c3 | ||
|
|
268035f13b | ||
|
|
18fe63a5ad | ||
|
|
d6e0e7b903 | ||
|
|
8759e1af5e | ||
|
|
b78f3fb08b | ||
|
|
5fa0b2e1c8 | ||
|
|
6a88d0559a | ||
|
|
314fdc2bb0 | ||
|
|
077c60bd34 | ||
|
|
6485537207 | ||
|
|
286f079679 | ||
|
|
075818d551 | ||
|
|
d59415999f | ||
|
|
80d6a879d2 | ||
|
|
1b3681eb41 | ||
|
|
9de2caba94 | ||
|
|
a45fb3ad08 | ||
|
|
666f954529 | ||
|
|
7d2e02fe64 | ||
|
|
9912ed7af7 | ||
|
|
441d898ae7 | ||
|
|
6d1d0c5823 | ||
|
|
9530a17835 | ||
|
|
d0de2569e9 | ||
|
|
3fc384909e | ||
|
|
9a8d0a6ec1 | ||
|
|
9e14e24eb4 | ||
|
|
553a7ff86a | ||
|
|
349d070273 | ||
|
|
1eb68c9ea5 | ||
|
|
c26ad11290 | ||
|
|
b6dcb9c177 | ||
|
|
8aff3a6568 | ||
|
|
9e885b8bc3 | ||
|
|
d6b924a5f4 | ||
|
|
97691a0dd9 | ||
|
|
3781af6305 | ||
|
|
cb5d5a603d | ||
|
|
00a7b78fde | ||
|
|
9d80341357 | ||
|
|
00b76226ec | ||
|
|
f0552bce32 | ||
|
|
4aa7de253d | ||
|
|
abd01e647a | ||
|
|
8237fec8b7 | ||
|
|
0d06b2eaaa | ||
|
|
82ea90c45c | ||
|
|
fbe7bf9742 | ||
|
|
4447c2224e | ||
|
|
81713bfebe | ||
|
|
0f8a6b9299 | ||
|
|
4f2c87bbd4 | ||
|
|
dd7db3814a | ||
|
|
fdb3b9c3ac | ||
|
|
db72e0c95e | ||
|
|
9c9c3e7770 | ||
|
|
2ef0130559 | ||
|
|
610ca4ec8b | ||
|
|
1a32b941df | ||
|
|
433931111c | ||
|
|
3edc945487 | ||
|
|
c66194ceeb | ||
|
|
e35e130816 | ||
|
|
bba4bd5931 | ||
|
|
48d13ed6bc | ||
|
|
c13a5d1593 | ||
|
|
6d7b3db370 | ||
|
|
33d06e99e8 | ||
|
|
3203e56a4d | ||
|
|
ff47f1b356 | ||
|
|
d9377ea78b | ||
|
|
d266bcc54b | ||
|
|
a033fb1042 | ||
|
|
569dcbd576 | ||
|
|
b0a2d73540 | ||
|
|
0d4c8cd56e | ||
|
|
b37dae60d1 | ||
|
|
448b028bb4 | ||
|
|
da637df44a | ||
|
|
9315e6f166 | ||
|
|
7e965e8415 | ||
|
|
a5916c95f9 | ||
|
|
708f97d107 | ||
|
|
637738bfb9 | ||
|
|
d8e82c5ad2 | ||
|
|
d6f9b1b754 | ||
|
|
25edd5fe71 | ||
|
|
cc84636e54 | ||
|
|
d73211191c | ||
|
|
b23acd3cdc | ||
|
|
c71fda38ff | ||
|
|
f99f49d840 | ||
|
|
2edd924c57 | ||
|
|
689c8fd8d9 | ||
|
|
ffd6c452c9 | ||
|
|
fc8e160874 | ||
|
|
d2f83307e6 | ||
|
|
1a09fcd26f | ||
|
|
e96129d7b0 | ||
|
|
4d852bb2ee | ||
|
|
cbefd5e8af | ||
|
|
bb5ea4a93d | ||
|
|
6e7396ba07 | ||
|
|
e2c5821dbe | ||
|
|
9967d5145b | ||
|
|
9d16291855 | ||
|
|
1518d1a12b | ||
|
|
b68a143071 | ||
|
|
5c25ebeeca | ||
|
|
3b17ba8e8b | ||
|
|
434a4d7bd6 | ||
|
|
dd36ebc7a0 | ||
|
|
f117fe15df | ||
|
|
3a45c96467 | ||
|
|
9ca2c1b9db | ||
|
|
7b9bde456e | ||
|
|
ed0d468c2d | ||
|
|
fe1b428dac | ||
|
|
0333f77f5a | ||
|
|
3c908d1a4d | ||
|
|
eb46a4c130 | ||
|
|
d0efdcfa07 | ||
|
|
62a7fcb16d | ||
|
|
d8addd5b27 | ||
|
|
bf6f1cf62d | ||
|
|
b7df7c47a5 | ||
|
|
ae260db4af | ||
|
|
778098ca12 | ||
|
|
0c4bb9c4a1 | ||
|
|
9cb975656d | ||
|
|
4f6564423e | ||
|
|
54e7e74ae5 | ||
|
|
324c372119 | ||
|
|
da62f1bbe3 | ||
|
|
129047fe88 | ||
|
|
3f652688b0 | ||
|
|
3ccbfdb614 | ||
|
|
5b4d00b949 | ||
|
|
a666dbcafd | ||
|
|
3d6d544c21 | ||
|
|
2ca54865bc | ||
|
|
df778d6d8e | ||
|
|
3c64f44dfd | ||
|
|
45ea47d4d0 | ||
|
|
150e3d6799 | ||
|
|
b3011bcb69 | ||
|
|
ccbe9a6707 | ||
|
|
23ebdaa7f3 | ||
|
|
fb182ced58 | ||
|
|
bec7c11e46 | ||
|
|
fb10e10226 | ||
|
|
5b37e47747 | ||
|
|
a3cea782ba | ||
|
|
14ffdae194 | ||
|
|
5792871e0a | ||
|
|
c2ec08f03d | ||
|
|
e4489cfcac | ||
|
|
e8c08beeb3 | ||
|
|
4a64423885 | ||
|
|
7e3ce1564d | ||
|
|
8154dd96f9 | ||
|
|
e37223cc88 | ||
|
|
5002ef2b90 | ||
|
|
5d95109ad4 | ||
|
|
13b3150f62 | ||
|
|
b3a4a7c1ab | ||
|
|
d6b64fce59 | ||
|
|
8c12222d9c | ||
|
|
26a79d1880 | ||
|
|
3f96b608b5 | ||
|
|
a669fc6440 | ||
|
|
42ee03a6d9 | ||
|
|
733e8fd405 | ||
|
|
baee72c1c6 | ||
|
|
5c2ab8a8cf | ||
|
|
11618a796f | ||
|
|
025ae20704 | ||
|
|
5f6b98a1e5 | ||
|
|
e1f5e3fa9c | ||
|
|
fdf73610eb | ||
|
|
b09e77c079 | ||
|
|
d583acd6f3 | ||
|
|
079bd1bebc | ||
|
|
68ad2561b1 | ||
|
|
791d11e597 | ||
|
|
5489e76f55 | ||
|
|
a205f7967f | ||
|
|
346ba79929 | ||
|
|
14a9917670 | ||
|
|
e4fe851871 | ||
|
|
496a1dd07c | ||
|
|
a7b8ca9ad5 | ||
|
|
6eb16eb11a | ||
|
|
589b723629 | ||
|
|
5ec45ee9cb | ||
|
|
915d3563c2 | ||
|
|
9446efe1ae | ||
|
|
c573b71a23 | ||
|
|
1844f30dc5 | ||
|
|
b6ab2f02f4 | ||
|
|
b5c76aa5fe | ||
|
|
26c0611c51 | ||
|
|
3345160447 | ||
|
|
8d4984c4ac | ||
|
|
b2cc85df86 | ||
|
|
51977adad8 | ||
|
|
5c7e722808 | ||
|
|
03572e9c3d | ||
|
|
817b40c489 | ||
|
|
744cf60f90 | ||
|
|
f979cabd50 | ||
|
|
30942a4d6b | ||
|
|
4101615a4a | ||
|
|
34a4072384 | ||
|
|
5fc72be10c | ||
|
|
9a9f1427fd | ||
|
|
5c9539373c | ||
|
|
79484f706d | ||
|
|
7b112a13d4 | ||
|
|
6f6b5874d9 | ||
|
|
d0ef1a8669 | ||
|
|
fe5c573291 | ||
|
|
463d7df582 | ||
|
|
5a1ec640a4 | ||
|
|
8bb8dbcd23 | ||
|
|
ba212b0997 | ||
|
|
9cff05311b | ||
|
|
986d843360 | ||
|
|
4670bd22af | ||
|
|
b3e7c63e61 | ||
|
|
c352c3d6d1 | ||
|
|
5bd425a3d3 | ||
|
|
1fcc92e10d | ||
|
|
f332aece98 | ||
|
|
89f24e9940 | ||
|
|
25a75dc369 | ||
|
|
f36ce14087 | ||
|
|
df45792f47 | ||
|
|
9e71e33ccd | ||
|
|
c7b0b0c1d5 | ||
|
|
e475e5f4c3 | ||
|
|
18964d3ee4 | ||
|
|
197ece4d0d | ||
|
|
a52798b1b7 | ||
|
|
0748106637 | ||
|
|
27dcc219b9 | ||
|
|
b231686a64 | ||
|
|
60668430f5 | ||
|
|
37ab66ef23 | ||
|
|
3382e03e69 | ||
|
|
b9680508db | ||
|
|
acebfdef09 | ||
|
|
3979694109 | ||
|
|
ae888d094b | ||
|
|
e8a3234dcd | ||
|
|
fc717e0dcd | ||
|
|
17f3afbf1f | ||
|
|
18cec75f41 | ||
|
|
7621842952 | ||
|
|
1e27b477a0 | ||
|
|
08ec38e3a5 | ||
|
|
0e9a57a1da | ||
|
|
e374522725 | ||
|
|
e402f33132 | ||
|
|
e85655c0ec | ||
|
|
227ffbc4bc | ||
|
|
b41f819783 | ||
|
|
d6c3635c6a | ||
|
|
37e98e4248 | ||
|
|
6b23d00ffe | ||
|
|
53cd4650bd | ||
|
|
d734a754d6 | ||
|
|
d4863e7d7d | ||
|
|
e6941f7f7f | ||
|
|
4fdf35a55f |
@@ -67,5 +67,7 @@ orbitdb
|
||||
ipfsdata
|
||||
.ipfsdata
|
||||
run-dev.sh
|
||||
data/
|
||||
run-dev.sh
|
||||
|
||||
!README.md
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"srcDir": "",
|
||||
"destDir": "",
|
||||
"files": "**/*.js",
|
||||
"command": "npm run lint"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,93 @@
|
||||
# Permissionless Software Foundation Community Contributing Guide 1.0
|
||||
|
||||
This document describes a very simple process suitable for most projects under
|
||||
the PSF umbrella. It is based on [this Medium article](https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951) and the [Node.js Community Contribution Guide](https://github.com/nodejs/TSC/blob/master/BasePolicies/CONTRIBUTING.md).
|
||||
Projects are encouraged to adopt this whether they
|
||||
are hosted under the PSF or not.
|
||||
|
||||
The goal of this document is to create a contribution process that:
|
||||
|
||||
* Encourages new contributions.
|
||||
* Encourages contributors to remain involved.
|
||||
* Avoids unnecessary processes and bureaucracy whenever possible.
|
||||
* Creates a transparent decision making process which makes it clear how
|
||||
contributors can be involved in decision making.
|
||||
|
||||
This document is based on much prior art in the Node.js community, io.js,
|
||||
and the Node.js project.
|
||||
|
||||
Additional guidance can be found at the [Permissionless Software Foundation Telegram Channel](https://t.me/permissionless_software).
|
||||
|
||||
## Vocabulary
|
||||
|
||||
* A **Contributor** is any individual creating or commenting on an issue or pull request.
|
||||
* A **Committer** is a subset of contributors who have been given write access to the repository.
|
||||
* A **TC (Technical Committee)** is a group of committers representing the required technical
|
||||
expertise to resolve rare disputes.
|
||||
|
||||
# Logging Issues
|
||||
|
||||
Log an issue for any question or problem you might have. When in doubt, log an issue,
|
||||
any additional policies about what to include will be provided in the responses. The only
|
||||
exception is security disclosures which should be sent privately.
|
||||
|
||||
Committers may direct you to another repository, ask for additional clarifications, and
|
||||
add appropriate metadata before the issue is addressed.
|
||||
|
||||
Please be courteous, respectful, and every participant is expected to follow the
|
||||
project's Code of Conduct.
|
||||
|
||||
# Contributions
|
||||
|
||||
Any change to resources in this repository must be through pull requests. This applies to all changes
|
||||
to documentation, code, binary files, etc. Even long term committers and TC members must use
|
||||
pull requests.
|
||||
|
||||
No pull request can be merged without being reviewed.
|
||||
|
||||
For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that
|
||||
contributors in other timezones have time to review. Consideration should also be given to
|
||||
weekends and other holiday periods to ensure active committers all have reasonable time to
|
||||
become involved in the discussion and review process if they wish.
|
||||
|
||||
The default for each contribution is that it is accepted once no committer has an objection.
|
||||
During review committers may also request that a specific contributor who is most versed in a
|
||||
particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off"
|
||||
process for contributions to land. Once all issues brought by committers are addressed it can
|
||||
be landed by any committer.
|
||||
|
||||
In the case of an objection being raised in a pull request by another committer, all involved
|
||||
committers should seek to arrive at a consensus by way of addressing concerns being expressed
|
||||
by discussion, compromise on the proposed change, or withdrawal of the proposed change.
|
||||
|
||||
If a contribution is controversial and committers cannot agree about how to get it to land
|
||||
or if it should land then it should be escalated to the TC. TC members should regularly
|
||||
discuss pending contributions in order to find a resolution. It is expected that only a
|
||||
small minority of issues be brought to the TC for resolution and that discussion and
|
||||
compromise among committers be the default resolution mechanism.
|
||||
|
||||
# Becoming a Committer
|
||||
|
||||
All contributors who land a non-trivial contribution should be on-boarded in a timely manner,
|
||||
and added as a committer, and be given write access to the repository.
|
||||
|
||||
Committers are expected to follow this policy and continue to send pull requests, go through
|
||||
proper review, and have other committers merge their pull requests.
|
||||
|
||||
# TC Process
|
||||
|
||||
The TC uses a "consensus seeking" process for issues that are escalated to the TC.
|
||||
The group tries to find a resolution that has no open objections among TC members.
|
||||
If a consensus cannot be reached that has no objections then a majority wins vote
|
||||
is called. It is also expected that the majority of decisions made by the TC are via
|
||||
a consensus seeking process and that voting is only used as a last-resort.
|
||||
|
||||
Resolution may involve returning the issue to committers with suggestions on how to
|
||||
move forward towards a consensus. It is not expected that a meeting of the TC
|
||||
will resolve all issues on its agenda during that meeting and may prefer to continue
|
||||
the discussion happening among the committers.
|
||||
|
||||
Members can be added to the TC at any time. Any committer can nominate another committer
|
||||
to the TC and the TC uses its standard consensus seeking process to evaluate whether or
|
||||
not to add this new member. Members who do not participate consistently at the level of
|
||||
a majority of the other members are expected to resign.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2021 Permissionless Software Foundation
|
||||
Copyright (c) 2021-2023 Permissionless Software Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Pedigree
|
||||
|
||||
This repository was forked from [koa-api-boilerplate](https://github.com/christroutner/koa-api-boilerplate). Code changes from that upstream repository is frequently pulled in and merged to this repository.
|
||||
@@ -6,75 +6,82 @@ This is a REST API server based on [Koa](https://koajs.com/). It's essentially a
|
||||
|
||||
When started, this web server starts an [IPFS](https://ipfs.io) node and connects to an ipfs-bch-wallet-service server over the IPFS network. It then pipes that connection over its own localized REST API.
|
||||
|
||||
More details are available at [CashStack.info](https://CashStack.info).
|
||||
|
||||
- [Video: Using ipfs-bch-wallet-consumer](https://youtu.be/7ntMPuqAX64)
|
||||
|
||||
## Requirements
|
||||
|
||||
- node **^14.17.0**
|
||||
- npm **^7.13.0**
|
||||
- node **^16.20.2**
|
||||
- npm **^8.19.4**
|
||||
- Docker **^20.10.8**
|
||||
- Docker Compose **^1.27.4**
|
||||
|
||||
## Installation
|
||||
|
||||
### Development Environment
|
||||
|
||||
A development environment will allow you modify the code on-the-fly and contribute to the code base of this repository. [PM2](https://www.npmjs.com/package/pm2) is recommended for running this code base as an IPFS Circuit Relay.
|
||||
To run this software, you must have an instance of go-ipfs running in daemon mode, with the `--enable-pubsub-experiment` flag set. [Here are instruction for installing go-ipfs](https://gist.github.com/christroutner/a39f656850dc022b60f25c9663dd1cdd#install-ipfs).
|
||||
|
||||
- [Video: Installing ipfs-service-provider](https://youtu.be/Z0NsboIVN44)
|
||||
- [Step-by-step installation instructions](https://gist.github.com/christroutner/3304a71d4c12a3a3e1664a438f64d9d0)
|
||||
Clone this repository and install npm dependencies. You'll also need to install MongoDB if you don't already have it installed:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
|
||||
cd ipfs-service-provider
|
||||
git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer
|
||||
cd ipfs-bch-wallet-consumer
|
||||
./install-mongo-sh
|
||||
sudo npm install -g node-pre-gyp
|
||||
npm install
|
||||
./ipfs-service-provider.sh
|
||||
cd shell-script
|
||||
./local-external-ipfs-node.sh
|
||||
```
|
||||
|
||||
### Production Environment
|
||||
|
||||
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment. However, there is currently [a bug](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider/issues/38) preventing the Docker container from being used as a Circuit Relay.
|
||||
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment.
|
||||
|
||||
```
|
||||
docker-compose build --no-cache
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Operation Notes
|
||||
- You can bring the containers down with `docker-compose down`
|
||||
- You can bring the containers back up with `docker-compose up -d`.
|
||||
|
||||
- There is a memory leak in the version of js-ipfs. The app is currently configured to shut down every 8 hours to flush memory. It relies on a process manager like pm2, Docker, or systemd to restart the app after it shuts down, in order to ensure continuous operation.
|
||||
## Configuration
|
||||
|
||||
## Structure
|
||||
This program is intended to started with a Bash shell script. In that script, you can set the following environment variables:
|
||||
|
||||
The file layout of this repository differs from the koa-api-boilerplate. Instead, it follows the file layout of [Clean Architecture](troutsblog.com/blog/clean-architecture).
|
||||
```bash
|
||||
# The IPFS ID of the service running ipfs-bch-wallet-service
|
||||
export PREFERRED_P2WDB_PROVIDER=12D3KooWCNpwHbHmkNSJsqu3CQVVe3iW8g6e1gn3kWTPahj2igiy
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
This app is intended to be started via a bash shell script. See the environment variables used to configure this app in the [config/env/common.js file](./config/env/common.js).
|
||||
|
||||
## File Structure
|
||||
|
||||
The file layout of this repository differs from the koa-api-boilerplate. Instead, it follows the file layout of [Clean Architecture](https://christroutner.github.io/trouts-blog/blog/clean-architecture).
|
||||
|
||||
## Usage
|
||||
|
||||
- `npm start` Start server on live mode
|
||||
- `npm run docs` Generate API documentation
|
||||
- `npm test` Run mocha tests
|
||||
- `docker-compose build` Build a 'production' Docker container
|
||||
- `docker-compose up` Run the docker container
|
||||
|
||||
## Documentation
|
||||
|
||||
API documentation is written inline and generated by [apidoc](http://apidocjs.com/).
|
||||
API documentation is written inline and generated by [apidoc](http://apidocjs.com/). Docs can be generated with this command:
|
||||
- `npm run docs`
|
||||
|
||||
Visit `http://localhost:5000/docs/` to view docs
|
||||
Visit `http://localhost:5020/` to view docs
|
||||
|
||||
There is additional developer documentation in the [dev-docs directory](./dev-docs).
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [koa2](https://github.com/koajs/koa/tree/v2.x)
|
||||
- [koa-router](https://github.com/alexmingoia/koa-router)
|
||||
- [koa-bodyparser](https://github.com/koajs/bodyparser)
|
||||
- [koa-generic-session](https://github.com/koajs/generic-session)
|
||||
- [koa-logger](https://github.com/koajs/logger)
|
||||
- [MongoDB](http://mongodb.org/)
|
||||
- [Mongoose](http://mongoosejs.com/)
|
||||
- [Passport](http://passportjs.org/)
|
||||
- [Nodemon](http://nodemon.io/)
|
||||
- [Mocha](https://mochajs.org/)
|
||||
- [apidoc](http://apidocjs.com/)
|
||||
- [ESLint](http://eslint.org/)
|
||||
- [ipfs-coord](https://www.npmjs.com/package/ipfs-coord)
|
||||
This repository is forked from [ipfs-service-provider](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider).
|
||||
|
||||
## IPFS
|
||||
|
||||
@@ -83,5 +90,3 @@ Snapshots pinned to IPFS will be listed here.
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE.md)
|
||||
|
||||
test
|
||||
|
||||
+69
-42
@@ -8,47 +8,56 @@
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
const Koa = require('koa')
|
||||
const bodyParser = require('koa-bodyparser')
|
||||
const convert = require('koa-convert')
|
||||
const logger = require('koa-logger')
|
||||
const mongoose = require('mongoose')
|
||||
const session = require('koa-generic-session')
|
||||
const passport = require('koa-passport')
|
||||
const mount = require('koa-mount')
|
||||
const serve = require('koa-static')
|
||||
const cors = require('kcors')
|
||||
import Koa from 'koa'
|
||||
|
||||
import bodyParser from 'koa-bodyparser'
|
||||
import convert from 'koa-convert'
|
||||
import logger from 'koa-logger'
|
||||
import mongoose from 'mongoose'
|
||||
import session from 'koa-generic-session'
|
||||
import passport from 'koa-passport'
|
||||
import mount from 'koa-mount'
|
||||
import serve from 'koa-static'
|
||||
import cors from 'kcors'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../config') // this first.
|
||||
const AdminLib = require('../src/adapters/admin')
|
||||
const errorMiddleware = require('../src/controllers/rest-api/middleware/error')
|
||||
const { wlogger } = require('../src/adapters/wlogger')
|
||||
import config from '../config/index.js' // this first.
|
||||
|
||||
import AdminLib from '../src/adapters/admin.js'
|
||||
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
|
||||
import wlogger from '../src/adapters/wlogger.js'
|
||||
import Controllers from '../src/controllers/index.js'
|
||||
import { applyPassportMods } from '../config/passport.js'
|
||||
|
||||
class Server {
|
||||
constructor () {
|
||||
// Encapsulate dependencies
|
||||
this.adminLib = new AdminLib()
|
||||
this.controllers = new Controllers()
|
||||
this.mongoose = mongoose
|
||||
this.config = config
|
||||
this.process = process
|
||||
}
|
||||
|
||||
async startServer () {
|
||||
try {
|
||||
// Create a Koa instance.
|
||||
const app = new Koa()
|
||||
app.keys = [config.session]
|
||||
app.keys = [this.config.session]
|
||||
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
this.mongoose.Promise = global.Promise
|
||||
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
console.log(
|
||||
`Connecting to MongoDB with this connection string: ${config.database}`
|
||||
`Connecting to MongoDB with this connection string: ${this.config.database}`
|
||||
)
|
||||
await mongoose.connect(config.database, {
|
||||
await this.mongoose.connect(this.config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
|
||||
console.log(`Starting environment: ${config.env}`)
|
||||
console.log(`Debug level: ${config.debugLevel}`)
|
||||
console.log(`Starting environment: ${this.config.env}`)
|
||||
console.log(`Debug level: ${this.config.debugLevel}`)
|
||||
|
||||
// MIDDLEWARE START
|
||||
|
||||
@@ -64,48 +73,66 @@ class Server {
|
||||
app.use(mount('/logs', serve(`${process.cwd()}/config/logs`)))
|
||||
|
||||
// User Authentication
|
||||
require('../config/passport')
|
||||
// require('../config/passport')
|
||||
applyPassportMods(passport)
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
const Controllers = require('../src/controllers')
|
||||
const controllers = new Controllers()
|
||||
await controllers.attachRESTControllers(app)
|
||||
|
||||
app.controllers = controllers
|
||||
|
||||
// Enable CORS for testing
|
||||
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
|
||||
// Dev Note: This line must come BEFORE controllers.attachRESTControllers()
|
||||
app.use(cors({ origin: '*' }))
|
||||
|
||||
// Wait for any adapters to initialize.
|
||||
await this.controllers.initAdapters()
|
||||
|
||||
// Wait for any use-libraries to initialize.
|
||||
await this.controllers.initUseCases()
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
await this.controllers.attachRESTControllers(app)
|
||||
|
||||
app.controllers = this.controllers
|
||||
|
||||
// MIDDLEWARE END
|
||||
|
||||
console.log(`Running server in environment: ${config.env}`)
|
||||
wlogger.info(`Running server in environment: ${config.env}`)
|
||||
console.log(`Running server in environment: ${this.config.env}`)
|
||||
wlogger.info(`Running server in environment: ${this.config.env}`)
|
||||
|
||||
await app.listen(config.port)
|
||||
console.log(`Server started on ${config.port}`)
|
||||
this.server = await app.listen(this.config.port)
|
||||
console.log(`Server started on ${this.config.port}`)
|
||||
|
||||
// Create the system admin user.
|
||||
const success = await this.adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
|
||||
// Attach the other IPFS controllers
|
||||
await controllers.attachControllers(app)
|
||||
// Attach the other IPFS controllers.
|
||||
// Skip if this is a test environment.
|
||||
if (this.config.env !== 'test') {
|
||||
await this.controllers.attachControllers(app)
|
||||
}
|
||||
|
||||
// ipfs-coord has a memory leak. This app shuts down after 4 hours. It
|
||||
// expects to be run by Docker or pm2, which can automatically restart
|
||||
// the app.
|
||||
setTimeout(function () {
|
||||
process.exit(0)
|
||||
}, 60000 * 60 * 2) // 2 hours
|
||||
// Display configuration settings
|
||||
console.log('\nConfiguration:')
|
||||
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
|
||||
console.log(`IPFS TCP port: ${this.config.ipfsTcpPort}`)
|
||||
console.log(`IPFS WS port: ${this.config.ipfsWsPort}\n`)
|
||||
|
||||
return app
|
||||
} catch (err) {
|
||||
console.error('Could not start server. Error: ', err)
|
||||
|
||||
console.log(
|
||||
'Exiting after 5 seconds. Depending on process manager to restart.'
|
||||
)
|
||||
await this.sleep(5000)
|
||||
this.process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
sleep (ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Server
|
||||
export default Server
|
||||
|
||||
Vendored
+81
-12
@@ -1,21 +1,33 @@
|
||||
/*
|
||||
This file is used to store unsecure, application-specific data common to all
|
||||
environments.
|
||||
|
||||
Additional Environent Variables:
|
||||
- CONNECT_PREF: should have a value of 'cr' (default), or 'direct'. This is
|
||||
used by helia-coord to select a connection preference between peers. Servers
|
||||
with an ip4 or ip6 address should use 'direct'.
|
||||
*/
|
||||
|
||||
/* eslint no-unneeded-ternary:0 */
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
|
||||
// Get the version from the package.json file.
|
||||
const pkgInfo = require('../../package.json')
|
||||
import { readFileSync } from 'fs'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
const pkgInfo = JSON.parse(readFileSync(`${__dirname.toString()}/../../package.json`))
|
||||
|
||||
const version = pkgInfo.version
|
||||
|
||||
const ipfsCoordName = process.env.COORD_NAME
|
||||
? process.env.COORD_NAME
|
||||
: 'ipfs-bch-wallet-service'
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
// Configure TCP port.
|
||||
port: process.env.PORT || 5001,
|
||||
port: process.env.PORT || 5005,
|
||||
|
||||
// Password for HTML UI that displays logs.
|
||||
logPass: 'test',
|
||||
@@ -31,14 +43,43 @@ module.exports = {
|
||||
? process.env.EMAILPASS
|
||||
: 'emailpassword',
|
||||
|
||||
// BEGIN WALLET CONFIGURATION
|
||||
|
||||
// BCH Mnemonic for generating encryption keys and payment address
|
||||
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
|
||||
|
||||
// (Optional) use a wallet file generated by psf-bch-wallet.
|
||||
walletFile: process.env.WALLET_FILE
|
||||
? process.env.WALLET_FILE
|
||||
: `${__dirname.toString()}/../../wallet.json`,
|
||||
|
||||
// Wallet Configuration
|
||||
// Default value: web3 - connect to bch-api through web 3 JSON RPC over IPFS.
|
||||
// Alternate value: web2 - connect to bch-api through a web 2 REST API over HTTP.
|
||||
walletInterface: process.env.WALLET_INTERFACE ? process.env.WALLET_INTERFACE : 'web3',
|
||||
|
||||
// Wallet API Server
|
||||
// Default web2 server: https://api.fullstack.cash/v5/
|
||||
// Default web3 server: https://free-bch.fullstack.cash/
|
||||
apiServer: process.env.WALLET_INTERFACE === 'web2'
|
||||
? (
|
||||
process.env.APISERVER
|
||||
? process.env.APISERVER
|
||||
: 'https://api.fullstack.cash/v5/'
|
||||
)
|
||||
: 'https://free-bch.fullstack.cash/',
|
||||
|
||||
// Basic Authentication Password for connecting to bch-api through a web 2
|
||||
// REST API over HTTP.
|
||||
authPass: process.env.WALLET_AUTH_PASS
|
||||
? process.env.WALLET_AUTH_PASS
|
||||
: '',
|
||||
|
||||
// FullStack.cash account information, used for automatic JWT handling.
|
||||
getJwtAtStartup: process.env.GET_JWT_AT_STARTUP ? true : false,
|
||||
authServer: process.env.AUTHSERVER
|
||||
? process.env.AUTHSERVER
|
||||
: 'https://auth.fullstack.cash',
|
||||
apiServer: process.env.APISERVER
|
||||
? process.env.APISERVER
|
||||
: 'https://api.fullstack.cash/v5/',
|
||||
fullstackLogin: process.env.FULLSTACKLOGIN
|
||||
? process.env.FULLSTACKLOGIN
|
||||
: 'demo@demo.com',
|
||||
@@ -46,7 +87,17 @@ module.exports = {
|
||||
? process.env.FULLSTACKPASS
|
||||
: 'demo',
|
||||
|
||||
// IPFS settings.
|
||||
// END WALLET CONFIGURATION
|
||||
|
||||
// BEGIN IPFS CONFIGURATION
|
||||
|
||||
// Debug verbosity level of helia-coord.
|
||||
// 0 = no debug logs. 3 = maximum logs.
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
|
||||
|
||||
// Enable/Disable the IPFS node at startup. Enabled by default.
|
||||
useIpfs: process.env.DISABLE_IPFS ? false : true,
|
||||
|
||||
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
|
||||
// SSL domain used for websocket connection via browsers.
|
||||
crDomain: process.env.CR_DOMAIN ? process.env.CR_DOMAIN : '',
|
||||
@@ -72,18 +123,35 @@ module.exports = {
|
||||
},
|
||||
|
||||
// IPFS Ports
|
||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4001,
|
||||
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4003,
|
||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4101,
|
||||
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4103,
|
||||
|
||||
// BCH Mnemonic for generating encryption keys and payment address
|
||||
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
|
||||
// Settings for production, using external go-ipfs node.
|
||||
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
|
||||
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
|
||||
ipfsApiPort: process.env.IPFS_API_PORT
|
||||
? parseInt(process.env.IPFS_API_PORT)
|
||||
: 5001,
|
||||
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
|
||||
chatPubSubChan: 'psf-ipfs-chat-001',
|
||||
|
||||
// This can add specific Circuit Relay v2 servers to connect to.
|
||||
bootstrapRelays: [
|
||||
// v2 Circuit Relay (Token Tiger)
|
||||
// '/ip4/137.184.93.145/tcp/8001/p2p/12D3KooWGMEKkdJfyZbwdH9EafZbRTtMn7FnhWPrE4MhRty2763g',
|
||||
|
||||
// v2 Circuit Relay server (FullStack.cash)
|
||||
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
|
||||
],
|
||||
|
||||
// END IPFS CONFIGURATION
|
||||
|
||||
// Preferred provider running ipfs-bch-wallet-service for BCH wallet.
|
||||
preferredProvider: process.env.PREFERRED_PROVIDER
|
||||
? process.env.PREFERRED_PROVIDER
|
||||
: '',
|
||||
// If true, users will not be able to change the provider via a REST API call.
|
||||
freezeProvider: process.env.FREEZE_PROVIDER ? true : false,
|
||||
// : 'QmdBGB8S6sEr19gaAxJYjhRbz5ZmMiH5a1JyyMmaxcRKnY'
|
||||
// : 'QmUTx6KqYKVZbKpKxR7vGDUgZFYVvVVyEWDeCYq4GwBCff'
|
||||
// Used for development
|
||||
@@ -92,4 +160,5 @@ module.exports = {
|
||||
preferredP2wdbProvider: process.env.PREFERRED_P2WDB_PROVIDER
|
||||
? process.env.PREFERRED_P2WDB_PROVIDER
|
||||
: ''
|
||||
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
specified.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/ipfs-bch-consumer-dev',
|
||||
|
||||
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
||||
so as not to conflict with the default host port of 27017 for MongoDB.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
// database: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
|
||||
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
This is the environment run by the test suite.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/ipfs-bch-consumer-test',
|
||||
|
||||
+15
-4
@@ -1,6 +1,17 @@
|
||||
const common = require('./env/common')
|
||||
import common from './env/common.js'
|
||||
|
||||
const env = process.env.CONSUMER_ENV || 'development'
|
||||
const config = require(`./env/${env}`)
|
||||
import development from './env/development.js'
|
||||
import production from './env/production.js'
|
||||
import test from './env/test.js'
|
||||
|
||||
module.exports = Object.assign({}, common, config)
|
||||
const env = process.env.SVC_ENV || 'development'
|
||||
console.log(`Loading config for this environment: ${env}`)
|
||||
|
||||
let config = development
|
||||
if (env === 'test') {
|
||||
config = test
|
||||
} else if (env === 'prod') {
|
||||
config = production
|
||||
}
|
||||
|
||||
export default Object.assign({}, common, config)
|
||||
|
||||
+32
-29
@@ -1,30 +1,6 @@
|
||||
const passport = require('koa-passport')
|
||||
const User = require('../src/adapters/localdb/models/users')
|
||||
const Strategy = require('passport-local')
|
||||
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
try {
|
||||
const user = await User.findById(id, '-password')
|
||||
done(null, user)
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
|
||||
passport.use(
|
||||
'local',
|
||||
new Strategy(
|
||||
{
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
},
|
||||
passportCallback
|
||||
)
|
||||
)
|
||||
// import passport from 'koa-passport';
|
||||
import User from '../src/adapters/localdb/models/users.js'
|
||||
import Strategy from 'passport-local'
|
||||
|
||||
async function passportCallback (email, password, done) {
|
||||
try {
|
||||
@@ -49,5 +25,32 @@ async function passportCallback (email, password, done) {
|
||||
}
|
||||
}
|
||||
|
||||
// For testing
|
||||
module.exports = { passport, passportCallback }
|
||||
function applyPassportMods (passport) {
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
try {
|
||||
const user = await User.findById(id, '-password')
|
||||
done(null, user)
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
|
||||
passport.use(
|
||||
'local',
|
||||
new Strategy(
|
||||
{
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
},
|
||||
passportCallback
|
||||
)
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export { applyPassportMods, passportCallback }
|
||||
|
||||
@@ -7,3 +7,5 @@ This directory contains documentation for developers.
|
||||
This app provides a bridge between the Web 2.0 REST API world and the Web3.0 IPFS world. Web apps (like wallet.fullstack.cash) can interact with the REST API provided by the this web server app. This app then pulls the back-end infrastructure through a the IPFS network.
|
||||
|
||||
When started, this app will spin up an IPFS node and connect to the PSF network using [ipfs-coord](https://www.npmjs.com/package/ipfs-coord). It will find and connect with a BCH wallet service provider via IPFS. Clients connecting to the REST API can then access BCH wallet infrastructure via the REST API.
|
||||
|
||||
More details at [CashStack.info](https://cashstack.info).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const Server = require('./bin/server.js')
|
||||
import Server from './bin/server.js'
|
||||
const server = new Server()
|
||||
|
||||
server.startServer()
|
||||
|
||||
Generated
+14616
-35698
File diff suppressed because it is too large
Load Diff
+34
-24
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"name": "ipfs-bch-wallet-consumer",
|
||||
"version": "1.0.0",
|
||||
"version": "3.0.0",
|
||||
"description": "A (local) REST API that proxies BCH wallet services delivered over IPFS (global).",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "npm run test:all",
|
||||
"test:all": "export CONSUMER_ENV=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:all": "export CONSUMER_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:unit": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:e2e:auto": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
|
||||
"test:temp": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
|
||||
"lint": "standard --env mocha --fix",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export CONSUMER_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
"coverage": "c8 report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export CONSUMER_ENV=test && c8 --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
},
|
||||
"author": "Chris Troutner <chris.troutner@gmail.com>",
|
||||
"license": "MIT",
|
||||
@@ -23,12 +24,24 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
|
||||
"dependencies": {
|
||||
"@chris.troutner/ipfs": "2.0.2",
|
||||
"@psf/bch-js": "4.20.26",
|
||||
"axios": "0.21.1",
|
||||
"@chainsafe/libp2p-gossipsub": "11.0.1",
|
||||
"@chainsafe/libp2p-noise": "14.1.0",
|
||||
"@chainsafe/libp2p-yamux": "6.0.1",
|
||||
"@helia/unixfs": "3.0.1",
|
||||
"@libp2p/bootstrap": "10.0.7",
|
||||
"@libp2p/circuit-relay-v2": "1.0.13",
|
||||
"@libp2p/identify": "1.0.6",
|
||||
"@libp2p/keychain": "4.0.7",
|
||||
"@libp2p/logger": "4.0.5",
|
||||
"@libp2p/tcp": "9.0.7",
|
||||
"@libp2p/webrtc": "4.0.17",
|
||||
"axios": "0.27.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blockstore-fs": "1.1.8",
|
||||
"datastore-fs": "9.1.6",
|
||||
"glob": "7.1.6",
|
||||
"ipfs-coord": "6.7.5",
|
||||
"helia": "2.1.0",
|
||||
"helia-coord": "1.5.6",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-bch-lib": "1.3.0",
|
||||
@@ -42,33 +55,30 @@
|
||||
"koa-passport": "4.1.3",
|
||||
"koa-router": "10.0.0",
|
||||
"koa-static": "5.0.0",
|
||||
"koa2-ratelimit": "0.9.0",
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "1.2.1",
|
||||
"line-reader": "0.4.0",
|
||||
"mongoose": "5.11.15",
|
||||
"minimal-slp-wallet": "5.11.2",
|
||||
"mongoose": "5.13.14",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.4.17",
|
||||
"nodemailer": "6.7.5",
|
||||
"passport-local": "1.0.0",
|
||||
"public-ip": "4.0.4",
|
||||
"semver": "^7.3.5",
|
||||
"public-ip": "6.0.1",
|
||||
"semver": "7.3.5",
|
||||
"winston": "3.3.3",
|
||||
"winston-daily-rotate-file": "4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "0.26.0",
|
||||
"apidoc": "0.51.1",
|
||||
"c8": "7.12.0",
|
||||
"chai": "4.3.0",
|
||||
"coveralls": "3.1.0",
|
||||
"eslint": "7.19.0",
|
||||
"eslint-config-prettier": "7.2.0",
|
||||
"eslint-config-standard": "16.0.2",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"eslint-plugin-standard": "4.0.0",
|
||||
"husky": "4.3.8",
|
||||
"mocha": "8.2.1",
|
||||
"nyc": "15.1.0",
|
||||
"semantic-release": "17.4.4",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"mocha": "10.0.0",
|
||||
"semantic-release": "19.0.3",
|
||||
"sinon": "9.2.4",
|
||||
"standard": "16.0.3",
|
||||
"standard": "17.0.0",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"release": {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
COMPOSE_PROJECT_NAME=bch-wallet-consumer
|
||||
@@ -12,9 +12,12 @@ RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
RUN sudo apt-get install -y ca-certificates curl gnupg
|
||||
RUN sudo mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN sudo apt-get update
|
||||
RUN sudo apt-get install -y nodejs build-essential
|
||||
|
||||
#Create the user 'safeuser' and add them to the sudo group.
|
||||
#RUN useradd -ms /bin/bash safeuser
|
||||
@@ -33,8 +36,8 @@ WORKDIR /home/safeuser
|
||||
#RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
||||
|
||||
# Update to the latest version of npm.
|
||||
# Working with npm@7.21.1
|
||||
RUN npm install -g npm@7.23.0
|
||||
#RUN npm install -g npm@7.23.0
|
||||
#RUN npm install -g npm
|
||||
|
||||
# npm mirror to prevent direct dependency on npm.
|
||||
RUN npm set registry http://94.130.170.209:4873/
|
||||
@@ -46,11 +49,11 @@ RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/ipfs-service-provider
|
||||
WORKDIR /home/safeuser/ipfs-bch-wallet-consumer
|
||||
|
||||
# For development: switch to unstable branch
|
||||
RUN git checkout ct-unstable
|
||||
@@ -66,10 +69,10 @@ RUN npm run docs
|
||||
VOLUME /home/safeuser/keys
|
||||
|
||||
# Expose the port the API will be served on.
|
||||
EXPOSE 5001
|
||||
EXPOSE 5010
|
||||
|
||||
# Start the application.
|
||||
COPY start-production.sh start-production.sh
|
||||
#COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
#CMD ["npm", "start"]
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Docker Containers
|
||||
|
||||
The 'production' environment is assumed to be a set of Docker containers orchestrated with Docker Compose. The files in this directory will stand up three Docker containers:
|
||||
|
||||
1 An instance of go-ipfs.
|
||||
2 An instance of MongoDB.
|
||||
3 The JavaScript software in this repository.
|
||||
|
||||
The software in this repository depends on the first two containers, so if they aren't running correctly, the application won't run correctly either.
|
||||
|
||||
## IPFS
|
||||
|
||||
IPFS can be a little tricky to set up. By default, the container uses the following ports:
|
||||
|
||||
- 4001 for TCP connections, exposed publicly.
|
||||
- 5001 for control by the application, exposed privately.
|
||||
- 8080 for an IPFS gateway, consumed by the application, exposed privately.
|
||||
|
||||
If you already have an IPFS node running on a the computer, you will need to change the ports to avaid a conflict. To change the ports from the default, you'll need to perform a series of steps, and the order of the steps matter.
|
||||
|
||||
1. Edit the `docker-compose.yml` file and change the ports. Then save the file. Here is an example:
|
||||
|
||||
```
|
||||
ports:
|
||||
- 4101:4101
|
||||
- 172.17.0.1:5101:5101
|
||||
- 172.17.0.1:8180:8180
|
||||
```
|
||||
|
||||
2. Bring the Docker containers up, and then back down. This will allow the IPFS container to create the config file that you'll need to edit.
|
||||
|
||||
- `docker-compose up -d`
|
||||
- Wait a few seconds.
|
||||
- `docker-compose down`
|
||||
|
||||
3. Update the generated config file at `../data/go-ipfs/data/config`, to update the ports in the config file, like this:
|
||||
|
||||
```
|
||||
"Addresses": {
|
||||
"API": "/ip4/0.0.0.0/tcp/5101",
|
||||
"Announce": [],
|
||||
"AppendAnnounce": [],
|
||||
"Gateway": "/ip4/0.0.0.0/tcp/8180",
|
||||
"NoAnnounce": [],
|
||||
"Swarm": [
|
||||
"/ip4/0.0.0.0/tcp/4101",
|
||||
"/ip6/::/tcp/4101",
|
||||
"/ip4/0.0.0.0/udp/4101/quic",
|
||||
"/ip6/::/udp/4101/quic"
|
||||
]
|
||||
},
|
||||
|
||||
```
|
||||
|
||||
4. Update the port changes in the `start-production.sh` shell script. This tells the application which ports to use, in order to control the IPFS node, are are used when signaling other nodes.
|
||||
|
||||
5. Quickly rebuild the containers, to add the modified `start-production.sh` shell script to the application Docker container:
|
||||
|
||||
- `docker-compose build`
|
||||
|
||||
6. Now start the containers, and the port changes to IPFS should be complete.
|
||||
@@ -1,11 +1,11 @@
|
||||
# Start the service with the command 'docker-compose up -d'
|
||||
|
||||
version: '2'
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
mongo-ipfs-service:
|
||||
image: mongo
|
||||
container_name: mongo-ipfs-service
|
||||
mongo-bch-consumer:
|
||||
image: mongo:4.2.0
|
||||
container_name: mongo-bch-consumer
|
||||
ports:
|
||||
- '5555:27017' # <host port>:<container port>
|
||||
volumes:
|
||||
@@ -13,9 +13,10 @@ services:
|
||||
command: mongod --logpath=/dev/null # -- quiet
|
||||
restart: always
|
||||
|
||||
ipfs-service:
|
||||
ipfs-bch-consumer:
|
||||
build: .
|
||||
container_name: ipfs-service
|
||||
#image: christroutner/ipfs-service-provider:v5.0.1
|
||||
container_name: ipfs-bch-consumer
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
@@ -23,11 +24,12 @@ services:
|
||||
max-file: '10'
|
||||
mem_limit: 500mb
|
||||
links:
|
||||
- mongo-ipfs-service
|
||||
- mongo-bch-consumer
|
||||
ports:
|
||||
- '5001:5001' # <host port>:<container port>
|
||||
- '5268:5268' # IPFS TCP
|
||||
- '5269:5269' # IPFS WS
|
||||
- '5010:5010' # <host port>:<container port>
|
||||
- '4001:4001'
|
||||
- '4003:4003'
|
||||
volumes:
|
||||
- ../data/ipfsdata:/home/safeuser/ipfs-service-provider/.ipfsdata
|
||||
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-consumer/.ipfsdata
|
||||
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-consumer/start-production.sh
|
||||
restart: always
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
export MNEMONIC="olive two muscle bottom coral ancient wait legend bronze useful process session"
|
||||
|
||||
# The human readable name this IPFS node identifies as.
|
||||
export COORD_NAME=ipfs-service-provider-generic
|
||||
export COORD_NAME=bch-consumer-generic
|
||||
|
||||
# Allow this node to function as a circuit relay. It must not be behind a firewall.
|
||||
#export ENABLE_CIRCUIT_RELAY=true
|
||||
@@ -23,15 +23,20 @@ export DEBUG_LEVEL=1
|
||||
|
||||
|
||||
# Production database connection string.
|
||||
export DBURL=mongodb://172.17.0.1:5555/ipfs-service-prod
|
||||
|
||||
# Configure IPFS ports
|
||||
export IPFS_TCP_PORT=5268
|
||||
export IPFS_WS_PORT=5269
|
||||
|
||||
export DBURL=mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod
|
||||
|
||||
# Configure REST API port
|
||||
export PORT=5001
|
||||
export PORT=5010
|
||||
|
||||
# Production settings using external go-ipfs node.
|
||||
export SVC_ENV=prod
|
||||
export IPFS_HOST=172.17.0.1
|
||||
export IPFS_API_PORT=5001
|
||||
export IPFS_TCP_PORT=4001
|
||||
export IPFS_WS_PORT=4003
|
||||
|
||||
# Set the debug level for helia-coord. 0-3.
|
||||
# 0 = no debug logs. 3 = maximum debug logs.
|
||||
export DEBUG_LEVEL=0
|
||||
|
||||
export SVC_ENV=production
|
||||
npm start
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# This script is an example for running a generic ipfs-service-provider instance.
|
||||
|
||||
# Ports
|
||||
export PORT=5001 # REST API port
|
||||
export PORT=5005 # REST API port
|
||||
export IPFS_TCP_PORT=5268
|
||||
export IPFS_WS_PORT=5269
|
||||
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is an example for running a production environment, which is
|
||||
# defined by running an external go-ipfs node.
|
||||
|
||||
# Ports
|
||||
export PORT=5005 # REST API port
|
||||
|
||||
# The human-readible name that is used when displaying data about this node.
|
||||
export COORD_NAME=trout-dev-bch-wallet-consumer
|
||||
|
||||
# This is used for end-to-end encryption (e2ee).
|
||||
export MNEMONIC="stomach mean catch march census author cousin size south romance pumpkin trap"
|
||||
|
||||
# 0 = less verbose. 3 = most verbose
|
||||
export DEBUG_LEVEL=0
|
||||
|
||||
# Production settings that use external IPFS node.
|
||||
# https://github.com/christroutner/docker-ipfs
|
||||
export SVC_ENV=production
|
||||
export IPFS_HOST=localhost
|
||||
export IPFS_API_PORT=5001
|
||||
|
||||
# Configure IPFS ports
|
||||
export IPFS_TCP_PORT=4001
|
||||
#export IPFS_WS_PORT=5269
|
||||
|
||||
# MongoDB connection string.
|
||||
export DBURL=mongodb://localhost:27017/ipfs-service-dev
|
||||
|
||||
export CONSUMER_ENV=production
|
||||
|
||||
# Lock the consumer into a preferred service provider.
|
||||
#export PREFERRED_PROVIDER=12D3KooWEzE3HNH86WCb2Xocu6PgBCnSGUD7myJmuTHvoedMwp7K
|
||||
|
||||
npm start
|
||||
+18
-8
@@ -13,16 +13,24 @@
|
||||
in the Adapter directory.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
const axios = require('axios').default
|
||||
const mongoose = require('mongoose')
|
||||
const User = require('../adapters/localdb/models/users')
|
||||
const config = require('../../config')
|
||||
const JsonFiles = require('../adapters/json-files')
|
||||
// Global npm libraries
|
||||
import axios from 'axios'
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
// Local libraries
|
||||
import User from '../adapters/localdb/models/users.js'
|
||||
import config from '../../config/index.js'
|
||||
import JsonFiles from '../adapters/json-files.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const jsonFiles = new JsonFiles()
|
||||
|
||||
const JSON_FILE = `system-user-${config.env}.json`
|
||||
const JSON_PATH = `${__dirname.toString()}/../../config/${JSON_FILE}`
|
||||
const JSON_PATH = `${__dirname.toString()}../../config/${JSON_FILE}`
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
const context = {}
|
||||
@@ -82,6 +90,8 @@ class Admin {
|
||||
// applications like the Task Manager and the test scripts can access.
|
||||
|
||||
await jsonFiles.writeJSON(context, JSON_PATH)
|
||||
// console.log('context: ', context)
|
||||
// console.log('JSON_PATH: ', JSON_PATH)
|
||||
|
||||
return context
|
||||
} catch (err) {
|
||||
@@ -169,4 +179,4 @@ class Admin {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Admin
|
||||
export default Admin
|
||||
|
||||
+268
-29
@@ -4,11 +4,11 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const { v4: uid } = require('uuid')
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import { v4 as uid } from 'uuid'
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
const { wlogger } = require('../wlogger')
|
||||
import wlogger from '../wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -21,15 +21,15 @@ class BchAdapter {
|
||||
'An instance of IPFS must be passed when instantiating the BCH Adapter library.'
|
||||
)
|
||||
}
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Connect the RPC handler when the event fires with new data.
|
||||
this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
// this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.uid = uid
|
||||
@@ -102,7 +102,7 @@ class BchAdapter {
|
||||
return outObj
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getStatus()')
|
||||
wlogger.error('Error in adapters/bch.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ class BchAdapter {
|
||||
return true
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getStatus()')
|
||||
wlogger.error('Error in adapters/bch.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -160,13 +160,17 @@ class BchAdapter {
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getBalances()')
|
||||
wlogger.error('Error in adapters/bch.js/getBalances()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getUtxos (addr) {
|
||||
try {
|
||||
if (!addr) {
|
||||
throw new Error('addr required when calling getUtxos')
|
||||
}
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
@@ -198,17 +202,76 @@ class BchAdapter {
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
return data[0]
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getUtxos()')
|
||||
wlogger.error('Error in adapters/bch.js/getUtxos()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Query the UTXOs associated with an array of up to 20 addresses.
|
||||
async getUtxosBulk (addrs) {
|
||||
try {
|
||||
// console.log('getUtxosBulk() addrs: ', addrs)
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
// Input validation
|
||||
if (!Array.isArray(addrs)) {
|
||||
throw new Error('addresses parameter must be an array')
|
||||
}
|
||||
if (addrs.length > 20) {
|
||||
throw new Error('addresses parameter must not exceed 20 elements')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'utxosBulk',
|
||||
addresses: addrs
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
// console.log(`getUtxosBulk() data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/getUtxosBulk()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async broadcast (hex) {
|
||||
try {
|
||||
if (!hex) {
|
||||
throw new Error('hex required when calling broadcast')
|
||||
}
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
@@ -249,8 +312,15 @@ class BchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async getTransactions (address) {
|
||||
// Get the transaction history for an address.
|
||||
async getTransactions (address, sortOrder = 'DESCENDING', page = 0) {
|
||||
try {
|
||||
console.log('Executing getTransaction() bch adapter')
|
||||
|
||||
if (!address) {
|
||||
throw new Error('address required when calling getTransactions()')
|
||||
}
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
@@ -260,8 +330,10 @@ class BchAdapter {
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'transactions',
|
||||
addresses: [address]
|
||||
endpoint: 'txHistory',
|
||||
address,
|
||||
sortOrder,
|
||||
page
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
@@ -270,7 +342,7 @@ class BchAdapter {
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
@@ -291,8 +363,17 @@ class BchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async getTransaction (txid) {
|
||||
// Get details on an array of TXIDs.
|
||||
async getTransaction (txids) {
|
||||
try {
|
||||
if (!txids) {
|
||||
throw new Error('txids required when calling getTransaction()')
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Getting txData on these txids: ${JSON.stringify(txids, null, 2)}`
|
||||
)
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
@@ -302,8 +383,8 @@ class BchAdapter {
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'transaction',
|
||||
txid
|
||||
endpoint: 'txData',
|
||||
txids
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
@@ -312,7 +393,7 @@ class BchAdapter {
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
@@ -324,17 +405,22 @@ class BchAdapter {
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/transaction()')
|
||||
wlogger.error('Error in adapters/bch.js/getTransaction()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getPubKey (address) {
|
||||
try {
|
||||
if (!address) {
|
||||
throw new Error('address required when calling getPubKey()')
|
||||
}
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
@@ -375,9 +461,154 @@ class BchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async utxoIsValid (utxo) {
|
||||
try {
|
||||
if (!utxo) {
|
||||
throw new Error('utxo required when calling utxoIsValid()')
|
||||
}
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'utxoIsValid',
|
||||
utxo
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.log('utxoIsValid() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/utxoIsValid()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getTokenData (tokenId, withTxHistory = false) {
|
||||
try {
|
||||
if (!tokenId) {
|
||||
throw new Error('tokenId required when calling getTokenData()')
|
||||
}
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'getTokenData',
|
||||
tokenId,
|
||||
withTxHistory
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.log('getTokenData() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/getTokenData()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get token icon and other media associated with the token.
|
||||
async getTokenData2 (tokenId, updateCache) {
|
||||
try {
|
||||
if (!tokenId) {
|
||||
throw new Error('tokenId required when calling getTokenData2()')
|
||||
}
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'getTokenData2',
|
||||
tokenId,
|
||||
updateCache
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.log('getTokenData2() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/getTokenData2()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that resolves to data when the RPC response is recieved.
|
||||
async waitForRPCResponse (rpcId) {
|
||||
try {
|
||||
if (!rpcId) {
|
||||
throw new Error('rpcId can not be false or undefined')
|
||||
}
|
||||
|
||||
// Initialize variables for tracking the return data.
|
||||
let dataFound = false
|
||||
let cnt = 0
|
||||
@@ -400,6 +631,9 @@ class BchAdapter {
|
||||
dataFound = true
|
||||
// console.log('data was found in the queue')
|
||||
|
||||
// console.log(
|
||||
// `rawData.payload: ${JSON.stringify(rawData.payload, null, 2)}`
|
||||
// )
|
||||
data = rawData.payload.result.value
|
||||
|
||||
// Remove the data from the queue
|
||||
@@ -410,21 +644,26 @@ class BchAdapter {
|
||||
}
|
||||
|
||||
// Wait between loops.
|
||||
// await this.sleep(1000)
|
||||
await this.ipfs.ipfsCoordAdapter.bchjs.Util.sleep(2000)
|
||||
await this.sleep(2000)
|
||||
// await this.ipfs.ipfsCoordAdapter.bchjs.Util.sleep(2000)
|
||||
|
||||
cnt++
|
||||
|
||||
// Exit if data was returned, or the window for a response expires.
|
||||
} while (!dataFound && cnt < 10)
|
||||
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
|
||||
console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error('Error in waitForRPCResponse()')
|
||||
console.error('Error in waitForRPCResponse(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BchAdapter
|
||||
// module.exports = BchAdapter
|
||||
export default BchAdapter
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable no-useless-escape */
|
||||
import config from '../../config/index.js'
|
||||
import NodeMailer from '../adapters/nodemailer.js'
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
const NodeMailer = require('../adapters/nodemailer')
|
||||
const nodemailer = new NodeMailer()
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
@@ -60,4 +59,4 @@ class ContactLib {
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = ContactLib
|
||||
export default ContactLib
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
for this file.
|
||||
*/
|
||||
|
||||
const JwtLib = require('jwt-bch-lib')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
import JwtLib from 'jwt-bch-lib'
|
||||
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
class FullStackJWT {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -52,12 +53,6 @@ class FullStackJWT {
|
||||
// Get's a JWT token from FullStack.cash.
|
||||
async getJWT () {
|
||||
try {
|
||||
// Skip connecting FullStack.cash auth server to the network if this is an E2E test.
|
||||
if (process.env.TEST_TYPE === 'e2e') {
|
||||
this.apiToken = 'faketoken'
|
||||
return this.apiToken
|
||||
}
|
||||
|
||||
// Log into the auth server.
|
||||
await this.jwtLib.register()
|
||||
|
||||
@@ -104,4 +99,4 @@ class FullStackJWT {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FullStackJWT
|
||||
export default FullStackJWT
|
||||
|
||||
+43
-21
@@ -5,31 +5,32 @@
|
||||
*/
|
||||
|
||||
// Public NPM libraries
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
// Load individual adapter libraries.
|
||||
const IPFSAdapter = require('./ipfs')
|
||||
const LocalDB = require('./localdb')
|
||||
const LogsAPI = require('./logapi')
|
||||
const Passport = require('./passport')
|
||||
const Nodemailer = require('./nodemailer')
|
||||
// const { wlogger } = require('./wlogger')
|
||||
const JSONFiles = require('./json-files')
|
||||
const FullStackJWT = require('./fullstack-jwt')
|
||||
const BCH = require('./bch')
|
||||
const P2WDB = require('./p2wdb')
|
||||
import IPFSAdapter from './ipfs/index.js'
|
||||
import LocalDB from './localdb/index.js'
|
||||
import LogsAPI from './logapi.js'
|
||||
import Passport from './passport.js'
|
||||
import Nodemailer from './nodemailer.js'
|
||||
import BCH from './bch/index.js'
|
||||
import P2WDB from './p2wdb/index.js'
|
||||
|
||||
const config = require('../../config')
|
||||
// const { wlogger } = require('./wlogger')
|
||||
import JSONFiles from './json-files.js'
|
||||
import FullStackJWT from './fullstack-jwt.js'
|
||||
import config from '../../config/index.js'
|
||||
import Wallet from './wallet.adapter.js'
|
||||
|
||||
class Adapters {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency injection.
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.ipfs = new IPFSAdapter(localConfig)
|
||||
@@ -43,6 +44,7 @@ class Adapters {
|
||||
this.config = config
|
||||
this.bch = new BCH(localConfig)
|
||||
this.p2wdb = new P2WDB(localConfig)
|
||||
this.wallet = new Wallet(localConfig)
|
||||
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
this.fullStackJwt = new FullStackJWT(config)
|
||||
@@ -50,16 +52,36 @@ class Adapters {
|
||||
|
||||
async start () {
|
||||
try {
|
||||
let apiToken
|
||||
if (this.config.getJwtAtStartup) {
|
||||
// Get a JWT token and instantiate bch-js with it. Then pass that instance
|
||||
// to all the rest of the apps controllers and adapters.
|
||||
await this.fullStackJwt.getJWT()
|
||||
apiToken = await this.fullStackJwt.getJWT()
|
||||
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
|
||||
this.bchjs = await this.fullStackJwt.instanceBchjs()
|
||||
}
|
||||
|
||||
// Create a default instance of minimal-slp-wallet without initializing it
|
||||
// (without retrieving the wallets UTXOs). This instance will be overwritten
|
||||
// if the operator has configured BCH payments.
|
||||
console.log('\nCreating default startup wallet. This wallet may be overwritten.')
|
||||
await this.wallet.instanceWalletWithoutInitialization({}, { apiToken })
|
||||
this.bchjs = this.wallet.bchWallet.bchjs
|
||||
|
||||
// Start the IPFS node.
|
||||
await this.ipfs.start()
|
||||
// Do not start these adapters if this is an e2e test.
|
||||
if (this.config.env !== 'test') {
|
||||
if (this.config.useIpfs) {
|
||||
await this.ipfs.start()
|
||||
}
|
||||
} else {
|
||||
// These lines are here to ensure code coverage hits 100%.
|
||||
console.log('Not starting IPFS node since this is an e2e test.')
|
||||
}
|
||||
|
||||
console.log('Async Adapters have been started.')
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/index.js/start()')
|
||||
throw err
|
||||
@@ -67,4 +89,4 @@ class Adapters {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Adapters
|
||||
export default Adapters
|
||||
|
||||
+52
-34
@@ -2,23 +2,27 @@
|
||||
top-level IPFS library that combines the individual IPFS-based libraries.
|
||||
*/
|
||||
|
||||
const IpfsAdapter = require('./ipfs')
|
||||
const IpfsCoordAdapter = require('./ipfs-coord')
|
||||
// Local libraries
|
||||
import IpfsAdapter from './ipfs.js'
|
||||
|
||||
import IpfsCoordAdapter from './ipfs-coord.js'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
class IPFS {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency injection.
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.ipfsAdapter = new IpfsAdapter()
|
||||
this.IpfsCoordAdapter = IpfsCoordAdapter
|
||||
this.process = process
|
||||
this.config = config
|
||||
|
||||
this.ipfsCoordAdapter = {} // placeholder
|
||||
|
||||
@@ -40,11 +44,15 @@ class IPFS {
|
||||
// Start ipfs-coord
|
||||
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
|
||||
ipfs: this.ipfs,
|
||||
eventEmitter: this.eventEmitter
|
||||
tcpPort: this.config.ipfsTcpPort,
|
||||
wsPort: this.config.ipfsWsPort
|
||||
})
|
||||
await this.ipfsCoordAdapter.start()
|
||||
console.log('ipfs-coord is ready.')
|
||||
|
||||
// Subscribe to the chat pubsub channel
|
||||
await this.ipfsCoordAdapter.subscribeToChat()
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs/index.js/start()')
|
||||
@@ -60,13 +68,10 @@ class IPFS {
|
||||
}
|
||||
|
||||
// Get the status of this IPFS node.
|
||||
// Create an object with different metrics about this IPFS node, then return
|
||||
// the object.
|
||||
getStatus () {
|
||||
try {
|
||||
// console.log(
|
||||
// 'this.ipfsCoordAdapter.ipfsCoord.thisNode: ',
|
||||
// this.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
// )
|
||||
|
||||
const statusObj = {
|
||||
ipfsId: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsId,
|
||||
multiAddrs: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsMultiaddrs,
|
||||
@@ -97,25 +102,30 @@ class IPFS {
|
||||
ipfsPeers = this._removeDuplicatePeers(ipfsPeers)
|
||||
// console.log('filtered ipfsPeers: ', ipfsPeers)
|
||||
|
||||
const peerDataOut = []
|
||||
|
||||
// Loop through each IPFS peer and hydrate it with data from the peerData.
|
||||
for (let i = 0; i < ipfsPeers.length; i++) {
|
||||
const thisPeer = ipfsPeers[i]
|
||||
|
||||
if (!showAll) {
|
||||
// Delete properties that don't contain good info.
|
||||
delete thisPeer.muxer
|
||||
delete thisPeer.latency
|
||||
delete thisPeer.streams
|
||||
const thisPeer = {
|
||||
peer: ipfsPeers[i]
|
||||
}
|
||||
|
||||
// if (!showAll) {
|
||||
// // Delete properties that don't contain good info.
|
||||
// delete thisPeer.muxer
|
||||
// delete thisPeer.latency
|
||||
// delete thisPeer.streams
|
||||
// }
|
||||
|
||||
// Get the ipfs-coord peer data for this peer.
|
||||
let thisPeerData = peerData.filter((x) =>
|
||||
x.from.includes(thisPeer.peer)
|
||||
)
|
||||
thisPeerData = thisPeerData[0]
|
||||
// console.log('thisPeerData: ', thisPeerData)
|
||||
|
||||
// Skip if peerData for this IPFS peer could not be found.
|
||||
if (!thisPeerData) continue
|
||||
// if (!thisPeerData) continue
|
||||
|
||||
try {
|
||||
// console.log('thisPeerData: ', thisPeerData)
|
||||
@@ -124,25 +134,36 @@ class IPFS {
|
||||
thisPeer.name = thisPeerData.data.jsonLd.name
|
||||
thisPeer.protocol = thisPeerData.data.jsonLd.protocol
|
||||
thisPeer.version = thisPeerData.data.jsonLd.version
|
||||
thisPeer.connectionAddr = thisPeerData.data.connectionAddr
|
||||
|
||||
if (showAll) {
|
||||
// Add all the peer data.
|
||||
thisPeer.peerData = thisPeerData
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`Error trying to hydrate peer ${thisPeer.peer}: ${err.message}`
|
||||
console.warn(
|
||||
`warning: ${thisPeer.peer}: ${err.message}`
|
||||
)
|
||||
}
|
||||
|
||||
peerDataOut.push(thisPeer)
|
||||
}
|
||||
|
||||
return ipfsPeers
|
||||
return peerDataOut
|
||||
} catch (err) {
|
||||
console.error('Error in getPeers(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Expects an array of peers and returns an array of peers with duplicates
|
||||
// removed.
|
||||
// This function is used by getPeers()
|
||||
_removeDuplicatePeers (arr) {
|
||||
// https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
|
||||
return arr.filter((v, i, a) => a.findIndex((t) => t === v) === i)
|
||||
}
|
||||
|
||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||
getRelays () {
|
||||
try {
|
||||
@@ -163,25 +184,22 @@ class IPFS {
|
||||
// If the peer couldn't be found, skip.
|
||||
if (!thisPeer.length) {
|
||||
thisRelay.name = ''
|
||||
thisRelay.description = ''
|
||||
continue
|
||||
}
|
||||
|
||||
thisRelay.name = thisPeer[0].data.jsonLd.name
|
||||
thisRelay.description = thisPeer[0].data.jsonLd.description
|
||||
}
|
||||
|
||||
return relayData
|
||||
const v1Relays = this.config.v1Relays
|
||||
|
||||
return { v2Relays: relayData, v1Relays }
|
||||
} catch (err) {
|
||||
console.error('Error in getRelays(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Expects an array of peers and returns an array of peers with duplicates
|
||||
// removed.
|
||||
_removeDuplicatePeers (arr) {
|
||||
// https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
|
||||
return arr.filter((v, i, a) => a.findIndex((t) => t.peer === v.peer) === i)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IPFS
|
||||
export default IPFS
|
||||
|
||||
@@ -5,13 +5,17 @@
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const IpfsCoord = require('ipfs-coord')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const publicIp = require('public-ip')
|
||||
const semver = require('semver')
|
||||
// import IpfsCoord from 'ipfs-coord-esm'
|
||||
import IpfsCoord from 'helia-coord'
|
||||
|
||||
// import BCHJS from '@psf/bch-js'
|
||||
import SlpWallet from 'minimal-slp-wallet'
|
||||
import semver from 'semver'
|
||||
import { publicIpv4 } from 'public-ip'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
// const JSONRPC = require('../../controllers/json-rpc/')
|
||||
|
||||
// The minimum version of ipfs-bch-wallet-service that this wallet can work with.
|
||||
@@ -31,27 +35,31 @@ class IpfsCoordAdapter {
|
||||
'Instance of IPFS must be passed when instantiating ipfs-coord.'
|
||||
)
|
||||
}
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.IpfsCoord = IpfsCoord
|
||||
this.ipfsCoord = {}
|
||||
this.bchjs = new BCHJS()
|
||||
// this.bchjs = new BCHJS()
|
||||
this.wallet = new SlpWallet()
|
||||
this.config = config
|
||||
this.publicIp = publicIp
|
||||
this.semver = semver
|
||||
this.publicIp = publicIpv4
|
||||
|
||||
// Properties of this class instance.
|
||||
this.isReady = false
|
||||
|
||||
// Periodically poll services for available wallet service providers.
|
||||
this.pollBchServiceInterval = setInterval(this.pollForBchServices, 10000)
|
||||
this.pollP2wdbServiceInterval = setInterval(this.pollForP2wdbServices, 11000)
|
||||
this.pollP2wdbServiceInterval = setInterval(
|
||||
this.pollForP2wdbServices,
|
||||
11000
|
||||
)
|
||||
|
||||
// State object. TODO: Make this more robust.
|
||||
this.state = {
|
||||
@@ -67,14 +75,18 @@ class IpfsCoordAdapter {
|
||||
async start () {
|
||||
const circuitRelayInfo = {}
|
||||
|
||||
// Wait for the BCH wallet to create the wallet.
|
||||
await this.wallet.walletInfoPromise
|
||||
|
||||
// If configured as a Circuit Relay, get the public IP addresses for this node.
|
||||
if (this.config.isCircuitRelay) {
|
||||
try {
|
||||
const ip4 = await this.publicIp.v4()
|
||||
const ip4 = await this.publicIp()
|
||||
// const ip6 = await publicIp.v6()
|
||||
|
||||
circuitRelayInfo.ip4 = ip4
|
||||
circuitRelayInfo.tcpPort = this.config.ipfsTcpPort
|
||||
circuitRelayInfo.wsPort = this.config.ipfsWsPort
|
||||
|
||||
// Domain used by browser-based secure websocket connections.
|
||||
circuitRelayInfo.crDomain = this.config.crDomain
|
||||
@@ -83,18 +95,30 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
this.ipfsCoord = new this.IpfsCoord({
|
||||
const nullLog = () => {}
|
||||
|
||||
const ipfsCoordOptions = {
|
||||
ipfs: this.ipfs,
|
||||
type: 'node.js',
|
||||
// type: 'browser',
|
||||
bchjs: this.bchjs,
|
||||
privateLog: this.peerInputHandler,
|
||||
wallet: this.wallet,
|
||||
// privateLog: console.log, // Default to console.log
|
||||
privateLog: nullLog,
|
||||
isCircuitRelay: this.config.isCircuitRelay,
|
||||
circuitRelayInfo,
|
||||
apiInfo: this.config.apiInfo,
|
||||
announceJsonLd: this.config.announceJsonLd,
|
||||
debugLevel: this.config.debugLevel
|
||||
})
|
||||
debugLevel: this.config.debugLevel,
|
||||
v1Relays: this.config.v1Relays,
|
||||
tcpPort: this.config.ipfsTcpPort
|
||||
}
|
||||
|
||||
// Production env uses external go-ipfs node.
|
||||
// if (this.config.isProduction) {
|
||||
// ipfsCoordOptions.nodeType = 'external'
|
||||
// }
|
||||
|
||||
this.ipfsCoord = new this.IpfsCoord(ipfsCoordOptions)
|
||||
|
||||
// Wait for the ipfs-coord library to signal that it is ready.
|
||||
await this.ipfsCoord.start()
|
||||
@@ -110,7 +134,7 @@ class IpfsCoordAdapter {
|
||||
attachRPCRouter (router) {
|
||||
try {
|
||||
_this.ipfsCoord.privateLog = router
|
||||
_this.ipfsCoord.adapters.orbit.privateLog = router
|
||||
_this.ipfsCoord.adapters.pubsub.privateLog = router
|
||||
} catch (err) {
|
||||
console.error('Error in attachRPCRouter()')
|
||||
throw err
|
||||
@@ -169,7 +193,7 @@ class IpfsCoordAdapter {
|
||||
// to the preferred provider when it's discovered.
|
||||
if (
|
||||
_this.config.preferredP2wdbProvider &&
|
||||
thisPeer === _this.config.preferredP2wdbProvider
|
||||
thisPeer === _this.config.preferredP2wdbProvider
|
||||
) {
|
||||
_this.state.selectedP2wdbProvider = thisPeer
|
||||
}
|
||||
@@ -187,8 +211,15 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// catch and handle known failure mode.
|
||||
if (
|
||||
err.message.includes("Cannot read property 'peerList' of undefined")
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
console.error('Error in pollForP2wdbServices()')
|
||||
throw err
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +227,8 @@ class IpfsCoordAdapter {
|
||||
// providers. This method is called periodically by a timer-interval.
|
||||
pollForBchServices () {
|
||||
try {
|
||||
// console.log('pollForBchServices() polling for BCH service')
|
||||
|
||||
// An array of IPFS IDs of other nodes in the coordination pubsub channel.
|
||||
const peers = _this.ipfsCoord.thisNode.peerList
|
||||
// console.log(`peers: ${JSON.stringify(peers, null, 2)}`)
|
||||
@@ -237,6 +270,8 @@ class IpfsCoordAdapter {
|
||||
console.log(`---->BCH wallet service selected: ${thisPeer}`)
|
||||
}
|
||||
|
||||
// console.log('preferredProvider: ', _this.config.preferredProvider)
|
||||
|
||||
// If a preferred provider is set in the config file, then connect
|
||||
// to the preferred provider when it's discovered.
|
||||
if (
|
||||
@@ -246,6 +281,8 @@ class IpfsCoordAdapter {
|
||||
_this.state.selectedServiceProvider = thisPeer
|
||||
}
|
||||
|
||||
// console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider)
|
||||
|
||||
// Check if the peer has already been added to the list of providers.
|
||||
const alreadyExists = _this.state.serviceProviders.filter(
|
||||
(x) => x === thisPeer
|
||||
@@ -259,6 +296,13 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// catch and handle known failure mode.
|
||||
if (
|
||||
err.message.includes("Cannot read property 'peerList' of undefined")
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
console.error('Error in pollForBchServices(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
@@ -266,16 +310,28 @@ class IpfsCoordAdapter {
|
||||
|
||||
// This method handles input coming in from other IPFS peers.
|
||||
// It passes the data on to the REST API library by emitting an event.
|
||||
peerInputHandler (data) {
|
||||
try {
|
||||
// console.log('peerInputHandler triggered with this data: ', data)
|
||||
// peerInputHandler (data) {
|
||||
// try {
|
||||
// // console.log('peerInputHandler triggered with this data: ', data)
|
||||
//
|
||||
// this.eventEmitter.emit('rpcData', data)
|
||||
// } catch (err) {
|
||||
// console.error('Error in ipfs-coord.js/peerInputHandler(): ', err)
|
||||
// // Do not throw error. This is a top-level function.
|
||||
// }
|
||||
// }
|
||||
|
||||
this.eventEmitter.emit('rpcData', data)
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs-coord.js/peerInputHandler(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
// Subscribe to the chat pubsub channel
|
||||
async subscribeToChat () {
|
||||
// TODO: Allow user to replace nullog with their own log handler at startup.
|
||||
const nullLog = () => {}
|
||||
|
||||
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
|
||||
this.config.chatPubSubChan,
|
||||
nullLog,
|
||||
this.ipfsCoord.thisNode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsCoordAdapter
|
||||
export default IpfsCoordAdapter
|
||||
|
||||
+233
-60
@@ -9,83 +9,229 @@
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
// const IPFS = require('ipfs')
|
||||
const IPFS = require('@chris.troutner/ipfs')
|
||||
const fs = require('fs')
|
||||
import { createHelia } from 'helia'
|
||||
import fs from 'fs'
|
||||
import { FsBlockstore } from 'blockstore-fs'
|
||||
import { FsDatastore } from 'datastore-fs'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { tcp } from '@libp2p/tcp'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
// import { bootstrap } from '@libp2p/bootstrap'
|
||||
// import { identifyService } from 'libp2p/identify'
|
||||
import { identify } from '@libp2p/identify'
|
||||
// import { circuitRelayServer, circuitRelayTransport } from 'libp2p/circuit-relay'
|
||||
import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
||||
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import { publicIpv4 } from 'public-ip'
|
||||
import { multiaddr } from '@multiformats/multiaddr'
|
||||
import { webRTC } from '@libp2p/webrtc'
|
||||
import { keychain } from '@libp2p/keychain'
|
||||
import { defaultLogger } from '@libp2p/logger'
|
||||
import { unixfs } from '@helia/unixfs'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
import config from '../../../config/index.js'
|
||||
import JsonFiles from '../json-files.js'
|
||||
|
||||
const IPFS_DIR = './.ipfsdata/ipfs'
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
// console.log('__dirname: ', __dirname)
|
||||
|
||||
const ROOT_DIR = `${__dirname}../../../`
|
||||
const IPFS_DIR = `${__dirname}../../../.ipfsdata/ipfs`
|
||||
|
||||
class IpfsAdapter {
|
||||
constructor (localConfig) {
|
||||
// Encapsulate dependencies
|
||||
this.IPFS = IPFS
|
||||
this.config = config
|
||||
this.fs = fs
|
||||
this.createLibp2p = createLibp2p
|
||||
this.publicIp = publicIpv4
|
||||
this.multiaddr = multiaddr
|
||||
this.jsonFiles = new JsonFiles()
|
||||
this.keychain = keychain
|
||||
this.createHelia = createHelia
|
||||
|
||||
// Properties of this class instance.
|
||||
this.isReady = false
|
||||
this.config = config
|
||||
this.fs = fs
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.start = this.start.bind(this)
|
||||
this.createNode = this.createNode.bind(this)
|
||||
this.stop = this.stop.bind(this)
|
||||
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
|
||||
this.getSeed = this.getSeed.bind(this)
|
||||
this.getKeychain = this.getKeychain.bind(this)
|
||||
}
|
||||
|
||||
// Start an IPFS node.
|
||||
async start () {
|
||||
try {
|
||||
// Ipfs Options
|
||||
const ipfsOptions = {
|
||||
repo: IPFS_DIR,
|
||||
start: true,
|
||||
config: {
|
||||
relay: {
|
||||
enabled: true, // enable circuit relay dialer and listener
|
||||
hop: {
|
||||
enabled: config.isCircuitRelay // enable circuit relay HOP (make this node a relay)
|
||||
}
|
||||
},
|
||||
pubsub: true, // enable pubsub
|
||||
Swarm: {
|
||||
ConnMgr: {
|
||||
HighWater: 30,
|
||||
LowWater: 10
|
||||
}
|
||||
},
|
||||
Addresses: {
|
||||
Swarm: [
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`
|
||||
]
|
||||
},
|
||||
Datastore: {
|
||||
StorageMax: '2GB',
|
||||
StorageGCWatermark: 50,
|
||||
GCPeriod: '15m'
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure the directory structure exists that is needed by the IPFS node to store data.
|
||||
this.ensureBlocksDir()
|
||||
|
||||
// Create a new IPFS node.
|
||||
this.ipfs = await this.IPFS.create(ipfsOptions)
|
||||
// Create an IPFS node
|
||||
const ipfs = await this.createNode()
|
||||
// console.log('ipfs: ', ipfs)
|
||||
|
||||
// Set the 'server' profile so the node does not scan private networks.
|
||||
await this.ipfs.config.profiles.apply('server')
|
||||
this.id = ipfs.libp2p.peerId.toString()
|
||||
console.log('IPFS ID: ', this.id)
|
||||
|
||||
// Debugging: Display IPFS config settings.
|
||||
// const configSettings = await this.ipfs.config.getAll()
|
||||
// console.log(`configSettings: ${JSON.stringify(configSettings, null, 2)}`)
|
||||
// Attempt to guess our ip4 IP address.
|
||||
const ip4 = await this.publicIp()
|
||||
let detectedMultiaddr = `/ip4/${ip4}/tcp/${this.config.ipfsTcpPort}/p2p/${this.id}`
|
||||
detectedMultiaddr = this.multiaddr(detectedMultiaddr)
|
||||
|
||||
// Get the multiaddrs for the node.
|
||||
const multiaddrs = ipfs.libp2p.getMultiaddrs()
|
||||
multiaddrs.push(detectedMultiaddr)
|
||||
console.log('Multiaddrs: ', multiaddrs)
|
||||
|
||||
this.multiaddrs = multiaddrs
|
||||
|
||||
// Signal that this adapter is ready.
|
||||
this.isReady = true
|
||||
|
||||
this.ipfs = ipfs
|
||||
|
||||
return this.ipfs
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs.js/start()')
|
||||
|
||||
// If IPFS crashes because the /blocks directory is full, wipe the directory.
|
||||
if (err.message.includes('No space left on device')) {
|
||||
this.rmBlocksDir()
|
||||
// if (err.message.includes('No space left on device')) {
|
||||
// this.rmBlocksDir()
|
||||
// }
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getKeychain (datastore) {
|
||||
const keychainInit = {
|
||||
pass: await this.getSeed()
|
||||
}
|
||||
|
||||
const chain = this.keychain(keychainInit)({
|
||||
datastore,
|
||||
logger: defaultLogger()
|
||||
})
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// This function creates an IPFS node using Helia.
|
||||
// It returns the node as an object.
|
||||
async createNode () {
|
||||
try {
|
||||
// Create block and data stores.
|
||||
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
|
||||
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
|
||||
|
||||
// const keychainInit = {
|
||||
// pass: await this.getSeed()
|
||||
// }
|
||||
|
||||
// Create an identity
|
||||
let peerId
|
||||
// console.log('this.keychain: ', this.keychain)
|
||||
// const chain = this.keychain(keychainInit)({
|
||||
// datastore,
|
||||
// logger: defaultLogger()
|
||||
// })
|
||||
const chain = await this.getKeychain(datastore)
|
||||
try {
|
||||
peerId = await chain.exportPeerId('myKey')
|
||||
} catch (err) {
|
||||
await chain.createKey('myKey', 'Ed25519', 4096)
|
||||
peerId = await chain.exportPeerId('myKey')
|
||||
}
|
||||
|
||||
// Configure services
|
||||
const services = {
|
||||
identify: identify(),
|
||||
pubsub: gossipsub({ allowPublishToZeroPeers: true })
|
||||
}
|
||||
if (this.config.isCircuitRelay) {
|
||||
console.log('Helia (IPFS) node IS configured as Circuit Relay')
|
||||
services.relay = circuitRelayServer({ // makes the node function as a relay server
|
||||
hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit
|
||||
advertise: true,
|
||||
reservations: {
|
||||
maxReservations: 15, // how many peers are allowed to reserve relay slots on this server
|
||||
reservationClearInterval: 300 * 1000, // how often to reclaim stale reservations
|
||||
applyDefaultLimit: true, // whether to apply default data/duration limits to each relayed connection
|
||||
defaultDurationLimit: 2 * 60 * 1000, // the default maximum amount of time a relayed connection can be open for
|
||||
defaultDataLimit: BigInt(2 << 7), // the default maximum number of bytes that can be transferred over a relayed connection
|
||||
maxInboundHopStreams: 32, // how many inbound HOP streams are allow simultaneously
|
||||
maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
|
||||
}
|
||||
|
||||
// Configure transports
|
||||
let transports
|
||||
if (process.env.CONNECT_PREF === 'direct') {
|
||||
transports = [
|
||||
tcp(),
|
||||
webSockets(),
|
||||
webRTC()
|
||||
]
|
||||
} else {
|
||||
transports = [
|
||||
tcp(),
|
||||
webSockets(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 3,
|
||||
reservationConcurrency: 3
|
||||
}),
|
||||
webRTC()
|
||||
]
|
||||
}
|
||||
|
||||
// libp2p is the networking layer that underpins Helia
|
||||
const libp2p = await this.createLibp2p({
|
||||
peerId,
|
||||
datastore,
|
||||
addresses: {
|
||||
listen: [
|
||||
'/ip4/127.0.0.1/tcp/0',
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`,
|
||||
'/webrtc'
|
||||
]
|
||||
},
|
||||
transports,
|
||||
connectionEncryption: [
|
||||
noise()
|
||||
],
|
||||
streamMuxers: [
|
||||
yamux()
|
||||
],
|
||||
services
|
||||
})
|
||||
|
||||
// create a Helia node
|
||||
const helia = await this.createHelia({
|
||||
blockstore,
|
||||
datastore,
|
||||
libp2p
|
||||
})
|
||||
|
||||
// Attach IPFS file system.
|
||||
const fs = unixfs(helia)
|
||||
helia.fs = fs
|
||||
|
||||
return helia
|
||||
} catch (err) {
|
||||
console.error('Error creating Helia node: ', err)
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -96,25 +242,52 @@ class IpfsAdapter {
|
||||
return true
|
||||
}
|
||||
|
||||
// Remove the '/blocks' directory that is used to store IPFS data.
|
||||
// Dev Note: It's assumed this node is not pinning any data and that
|
||||
// everything in this directory is transient. This folder will regularly
|
||||
// fill up and prevent IPFS from starting.
|
||||
rmBlocksDir () {
|
||||
// Ensure that the directories exist to store blocks from the IPFS network.
|
||||
// This function is called at startup, before the IPFS node is started.
|
||||
ensureBlocksDir () {
|
||||
try {
|
||||
const dir = `${IPFS_DIR}/blocks`
|
||||
console.log(`Deleting ${dir} directory...`)
|
||||
!this.fs.existsSync(`${ROOT_DIR}.ipfsdata`) && this.fs.mkdirSync(`${ROOT_DIR}.ipfsdata`)
|
||||
|
||||
this.fs.rmdirSync(dir, { recursive: true })
|
||||
!this.fs.existsSync(`${IPFS_DIR}`) && this.fs.mkdirSync(`${IPFS_DIR}`)
|
||||
|
||||
console.log(`${dir} directory is deleted!`)
|
||||
!this.fs.existsSync(`${IPFS_DIR}/blockstore`) && this.fs.mkdirSync(`${IPFS_DIR}/blockstore`)
|
||||
|
||||
return true // Signal successful execution.
|
||||
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.log('Error in rmBlocksDir()')
|
||||
console.error('Error in adapters/ipfs.js/ensureBlocksDir(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function opens the seed used to generate the key for this IPFS peer.
|
||||
// The seed is stored in a JSON file. If it doesn't exist, a new one is created.
|
||||
async getSeed () {
|
||||
try {
|
||||
let seed
|
||||
|
||||
const filename = `${IPFS_DIR}/seed.json`
|
||||
|
||||
try {
|
||||
// Try to read the JSON file containing the seed.
|
||||
seed = await this.jsonFiles.readJSON(filename)
|
||||
} catch (err) {
|
||||
const seedNum = Math.floor(Math.random() * 1000000000000000000000)
|
||||
seed = seedNum.toString()
|
||||
|
||||
// Save the newly generated seed
|
||||
await this.jsonFiles.writeJSON(seed, filename)
|
||||
}
|
||||
|
||||
// console.log('getSeed() seed: ', seed)
|
||||
|
||||
return seed
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs/ipfs.js/getSeed()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsAdapter
|
||||
export default IpfsAdapter
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
A utility file for reading and writing JSON files.
|
||||
*/
|
||||
'use strict'
|
||||
const fs = require('fs')
|
||||
|
||||
import fs from 'fs'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -57,7 +57,8 @@ class JsonFiles {
|
||||
console.log(`err: ${JSON.stringify(err, null, 2)}`)
|
||||
}
|
||||
|
||||
throw err
|
||||
// throw err
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
const obj = JSON.parse(data)
|
||||
@@ -72,4 +73,4 @@ class JsonFiles {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JsonFiles
|
||||
export default JsonFiles
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
// Load Mongoose models.
|
||||
const Users = require('./models/users')
|
||||
import Users from './models/users.js'
|
||||
|
||||
class LocalDB {
|
||||
constructor () {
|
||||
@@ -12,4 +12,4 @@ class LocalDB {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LocalDB
|
||||
export default LocalDB
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
const mongoose = require('mongoose')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const config = require('../../../../config')
|
||||
const jwt = require('jsonwebtoken')
|
||||
// Global npm libraries
|
||||
import mongoose from 'mongoose'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
// Local libraries
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
const User = new mongoose.Schema({
|
||||
type: { type: String, default: 'user' },
|
||||
@@ -51,4 +54,4 @@ User.methods.generateToken = function generateToken () {
|
||||
}
|
||||
|
||||
// export default mongoose.model('user', User)
|
||||
module.exports = mongoose.model('user', User)
|
||||
export default mongoose.model('user', User)
|
||||
|
||||
+12
-5
@@ -1,9 +1,14 @@
|
||||
const lineReader = require('line-reader')
|
||||
const fs = require('fs')
|
||||
import lineReader from 'line-reader'
|
||||
import fs from 'fs'
|
||||
|
||||
const config = require('../../config')
|
||||
import config from '../../config/index.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
|
||||
let _this
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
class LogsApi {
|
||||
constructor () {
|
||||
@@ -135,8 +140,10 @@ class LogsApi {
|
||||
if (!filename || typeof filename !== 'string') {
|
||||
throw new Error('filename must be a string')
|
||||
}
|
||||
// Throw an error if the file does not exist.
|
||||
|
||||
console.log('readLines() filename: ', filename)
|
||||
|
||||
// Throw an error if the file does not exist.
|
||||
if (!_this.fs.existsSync(filename)) {
|
||||
throw new Error('file does not exist')
|
||||
}
|
||||
@@ -167,4 +174,4 @@ class LogsApi {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogsApi
|
||||
export default LogsApi
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
const nodemailer = require('nodemailer')
|
||||
import nodemailer from 'nodemailer'
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
const { wlogger } = require('./wlogger')
|
||||
import config from '../../config/index.js'
|
||||
import wlogger from './wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -150,4 +149,4 @@ class NodeMailer {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NodeMailer
|
||||
export default NodeMailer
|
||||
|
||||
+19
-19
@@ -4,11 +4,11 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const { v4: uid } = require('uuid')
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import { v4 as uid } from 'uuid'
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
const { wlogger } = require('../wlogger')
|
||||
import wlogger from '../wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -21,15 +21,15 @@ class P2wdbAdapter {
|
||||
'An instance of IPFS must be passed when instantiating the P2WDB Adapter library.'
|
||||
)
|
||||
}
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Connect the RPC handler when the event fires with new data.
|
||||
this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
// this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.uid = uid
|
||||
@@ -49,12 +49,12 @@ class P2wdbAdapter {
|
||||
// Convert string input into an object.
|
||||
// const jsonData = JSON.parse(data)
|
||||
|
||||
console.log(`JSON RPC response for ID ${data.payload.id} received.`)
|
||||
// console.log(`JSON RPC response for ID ${data.payload.id} received.`)
|
||||
|
||||
_this.rpcDataQueue.push(data)
|
||||
} catch (err) {
|
||||
console.error('Error in p2wdb/rpcHandler(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class P2wdbAdapter {
|
||||
|
||||
// Add names to the IPFS IDs for each provider.
|
||||
const initialServiceProviders =
|
||||
this.ipfs.ipfsCoordAdapter.state.p2wdbProviders
|
||||
this.ipfs.ipfsCoordAdapter.state.p2wdbProviders
|
||||
const serviceProviders = []
|
||||
for (let i = 0; i < initialServiceProviders.length; i++) {
|
||||
const thisProvider = initialServiceProviders[i]
|
||||
@@ -94,8 +94,7 @@ class P2wdbAdapter {
|
||||
|
||||
const outObj = {
|
||||
serviceProviders,
|
||||
selectedProvider:
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
selectedProvider: this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
}
|
||||
// console.log('outObj: ', outObj)
|
||||
|
||||
@@ -131,7 +130,7 @@ class P2wdbAdapter {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No P2WDB Service provider available yet.')
|
||||
}
|
||||
@@ -174,7 +173,7 @@ class P2wdbAdapter {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No P2WDB Service provider available yet.')
|
||||
}
|
||||
@@ -253,7 +252,7 @@ class P2wdbAdapter {
|
||||
|
||||
cnt++
|
||||
|
||||
// Exit if data was returned, or the window for a response expires.
|
||||
// Exit if data was returned, or the window for a response expires.
|
||||
} while (!dataFound && cnt < 10)
|
||||
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
|
||||
|
||||
@@ -265,4 +264,5 @@ class P2wdbAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2wdbAdapter
|
||||
// module.exports = P2wdbAdapter
|
||||
export default P2wdbAdapter
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
koa-passport is an authorization library used for different authentication schemes.
|
||||
*/
|
||||
|
||||
const passport = require('koa-passport')
|
||||
import passport from 'koa-passport'
|
||||
|
||||
let _this
|
||||
class Passport {
|
||||
@@ -32,4 +32,4 @@ class Passport {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Passport
|
||||
export default Passport
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
Wallet adapter. This library instantiates minimal-slp-wallet based on different
|
||||
configuration settings. That single instance of minimal-slp-wallet is then
|
||||
easily passed around the rest of this application.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import BchWallet from 'minimal-slp-wallet'
|
||||
|
||||
// Local libraries
|
||||
import config from '../../config/index.js'
|
||||
import JsonFiles from './json-files.js'
|
||||
|
||||
class Wallet {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
this.BchWallet = BchWallet
|
||||
this.jsonFiles = new JsonFiles()
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
|
||||
}
|
||||
|
||||
// This is used for initializing the wallet, without waiting to update the wallet
|
||||
// UTXOs from the blockchain.
|
||||
// This is useful when the wallet is simply needed to make calls to the blockchain,
|
||||
// and there is no need to hydrate it with UTXO data.
|
||||
async instanceWalletWithoutInitialization (walletData = {}, advancedConfig = {}) {
|
||||
try {
|
||||
// Use the apiToken from the config settings, if one is not passed-in at
|
||||
// run-time.
|
||||
if (!advancedConfig.apiToken) {
|
||||
advancedConfig.apiToken = this.config.apiToken
|
||||
}
|
||||
|
||||
// If an authentication password is passed in the config, add it.
|
||||
if (this.config.authPass) {
|
||||
advancedConfig.authPass = this.config.authPass
|
||||
}
|
||||
|
||||
// Detect and configure different blockchain infrastructure settings.
|
||||
if (this.config.walletInterface === 'web2') {
|
||||
advancedConfig.interface = 'rest-api'
|
||||
advancedConfig.restURL = this.config.apiServer
|
||||
} else {
|
||||
// By default use the web3 Cash Stack (https://cashstack.info)
|
||||
advancedConfig.interface = 'consumer-api'
|
||||
advancedConfig.restURL = this.config.apiServer
|
||||
}
|
||||
|
||||
console.log('advancedConfig setting when creating wallet: ', advancedConfig)
|
||||
|
||||
// Instantiate minimal-slp-wallet.
|
||||
if (walletData.mnemonic) {
|
||||
// Instance the wallet using the mnemonic passed in to this function.
|
||||
console.log('Mnemonic provided to wallet library from calling function.')
|
||||
this.bchWallet = await this._instanceWallet(walletData.mnemonic, advancedConfig)
|
||||
} else if (this.config.mnemonic) {
|
||||
// Otherwise use the mnemonic in the config setting or passed as an environment variable.
|
||||
console.log('Mnemonic retrieved from config file or environment variable.')
|
||||
this.bchWallet = await this._instanceWallet(this.config.mnemonic, advancedConfig)
|
||||
} else {
|
||||
// If no mnemonic is provided, then generate a new mnemonic to create the wallet.
|
||||
console.log('New mnemonic generated.')
|
||||
this.bchWallet = await this._instanceWallet(undefined, advancedConfig)
|
||||
}
|
||||
|
||||
// Wait for wallet to initialize.
|
||||
await this.bchWallet.walletInfoPromise
|
||||
console.log('BCH wallet initialized.')
|
||||
console.log(`Wallet address: ${this.bchWallet.walletInfo.cashAddress}`)
|
||||
console.log(`Wallet mnemonic: ${this.bchWallet.walletInfo.mnemonic}`)
|
||||
|
||||
return this.bchWallet
|
||||
} catch (err) {
|
||||
console.error('Error in instanceWalletWithoutInitialization()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function is used for easier mocking of unit tests.
|
||||
async _instanceWallet (mnemonic, config) {
|
||||
const wallet = new this.BchWallet(mnemonic, config)
|
||||
await wallet.walletInfoPromise
|
||||
|
||||
return wallet
|
||||
}
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
// Does not instance the wallet. The output of this function is expected to
|
||||
// be passed to instanceWallet().
|
||||
async openWallet (advancedConfig = {}) {
|
||||
try {
|
||||
let walletData
|
||||
|
||||
// Try to open the wallet.json file.
|
||||
try {
|
||||
// console.log('this.config.walletFile: ', this.config.walletFile)
|
||||
walletData = await this.jsonFiles.readJSON(this.config.walletFile)
|
||||
} catch (err) {
|
||||
// Create a new wallet file if one does not already exist.
|
||||
console.log('Wallet file not found. Creating new wallet.json file.')
|
||||
|
||||
// Create a new wallet.
|
||||
// No-Update flag creates wallet without making any network calls.
|
||||
const walletInstance = await this.instanceWalletWithoutInitialization({}, advancedConfig)
|
||||
|
||||
walletData = walletInstance.walletInfo
|
||||
|
||||
// Add the nextAddress property
|
||||
walletData.nextAddress = 1
|
||||
|
||||
// Write the wallet data to the JSON file.
|
||||
await this.jsonFiles.writeJSON(walletData, this.config.walletFile)
|
||||
}
|
||||
|
||||
// console.log('walletData: ', walletData)
|
||||
return walletData
|
||||
} catch (err) {
|
||||
console.error('Error in openWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Create an instance of minimal-slp-wallet. Same as
|
||||
// instanceWalletWithoutInitialization(), but waits for the wallet to initialize
|
||||
// its UTXOs (wallet balance and tokens).
|
||||
async instanceWallet (walletData = {}, advancedConfig = {}) {
|
||||
try {
|
||||
// Instance the wallet without initialization.
|
||||
await this.instanceWalletWithoutInitialization(walletData, advancedConfig)
|
||||
|
||||
// Initialize the wallet
|
||||
await this.bchWallet.initialize()
|
||||
|
||||
return this.bchWallet
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/instanceWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Increments the 'nextAddress' property in the wallet file. This property
|
||||
// indicates the HD index that should be used to generate a key pair for
|
||||
// storing funds for Offers.
|
||||
// This function opens the wallet file, increments the nextAddress property,
|
||||
// then saves the change to the wallet file.
|
||||
async incrementNextAddress () {
|
||||
try {
|
||||
const walletData = await this.openWallet()
|
||||
// console.log('original walletdata: ', walletData)
|
||||
walletData.nextAddress++
|
||||
// console.log('walletData finish: ', walletData)
|
||||
await this.jsonFiles.writeJSON(walletData, this.WALLET_FILE)
|
||||
// Update the working instance of the wallet.
|
||||
this.bchWallet.walletInfo.nextAddress++
|
||||
// console.log('this.bchWallet.walletInfo: ', this.bchWallet.walletInfo)
|
||||
return walletData.nextAddress
|
||||
} catch (err) {
|
||||
console.error('Error in incrementNextAddress()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This method returns an object that contains a private key WIF, public address,
|
||||
// and the index of the HD wallet that the key pair was generated from.
|
||||
// TODO: Allow input integer. If input is used, use that as the index. If no
|
||||
// input is provided, then call incrementNextAddress().
|
||||
async getKeyPair (hdIndex = 0) {
|
||||
try {
|
||||
if (!hdIndex) {
|
||||
// Increment the HD index and generate a new key pair.
|
||||
hdIndex = await this.incrementNextAddress()
|
||||
}
|
||||
const mnemonic = this.bchWallet.walletInfo.mnemonic
|
||||
// root seed buffer
|
||||
const rootSeed = await this.bchWallet.bchjs.Mnemonic.toSeed(mnemonic)
|
||||
const masterHDNode = this.bchWallet.bchjs.HDNode.fromSeed(rootSeed)
|
||||
// HDNode of BIP44 account
|
||||
// const account = this.bchWallet.bchjs.HDNode.derivePath(masterHDNode, "m/44'/245'/0'")
|
||||
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
|
||||
const cashAddress = this.bchWallet.bchjs.HDNode.toCashAddress(childNode)
|
||||
console.log('Generating a new key pair for cashAddress: ', cashAddress)
|
||||
const wif = this.bchWallet.bchjs.HDNode.toWIF(childNode)
|
||||
const outObj = {
|
||||
cashAddress,
|
||||
wif,
|
||||
hdIndex
|
||||
}
|
||||
return outObj
|
||||
} catch (err) {
|
||||
console.error('Error in getKeyPair()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize the wallet by consolidating the UTXOs.
|
||||
async optimize () {
|
||||
const UTXO_THREASHOLD = 7
|
||||
// Do a dry-run first to see if there are enough UTXOs worth consolidating.
|
||||
const dryRunOut = await this.bchWallet.optimize(true)
|
||||
if (dryRunOut.bchUtxoCnt > UTXO_THREASHOLD) {
|
||||
// Consolidate BCH UTXOs if the count is above the threashold.
|
||||
const txids = await this.bchWallet.optimize()
|
||||
console.log(`Wallet optimized with these return values: ${JSON.stringify(txids, null, 2)}`)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the balance of the wallet in sats and PSF tokens.
|
||||
// This function is called by the GET /entry/balance controller.
|
||||
async getBalance () {
|
||||
const balance = await this.bchWallet.getBalance()
|
||||
// console.log('balance: ', balance)
|
||||
const tokens = await this.bchWallet.listTokens()
|
||||
// console.log('tokens: ', tokens)
|
||||
// Find the array entry for the PSF token
|
||||
const psfTokens = tokens.find(x => x.tokenId === '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0')
|
||||
// console.log('psfTokens: ', psfTokens)
|
||||
let psfBalance = 0
|
||||
if (psfTokens) {
|
||||
psfBalance = psfTokens.qty
|
||||
}
|
||||
const outObj = {
|
||||
satBalance: balance,
|
||||
psfBalance,
|
||||
success: true
|
||||
}
|
||||
return outObj
|
||||
}
|
||||
}
|
||||
|
||||
export default Wallet
|
||||
+12
-4
@@ -6,10 +6,17 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const winston = require('winston')
|
||||
require('winston-daily-rotate-file')
|
||||
// Global npm libraries
|
||||
import winston from 'winston'
|
||||
import 'winston-daily-rotate-file'
|
||||
|
||||
const config = require('../../config')
|
||||
// Local libraries
|
||||
import config from '../../config/index.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
class Wlogger {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -69,4 +76,5 @@ logger.outputToConsole()
|
||||
|
||||
const wlogger = logger.wlogger
|
||||
|
||||
module.exports = { wlogger, Wlogger }
|
||||
export { wlogger as default, Wlogger }
|
||||
// export default wlogger
|
||||
|
||||
+32
-25
@@ -5,41 +5,34 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const EventEmitter = require('events')
|
||||
// const EventEmitter = require('events')
|
||||
|
||||
// Load the Clean Architecture Adapters library
|
||||
const Adapters = require('../adapters')
|
||||
|
||||
// Load the JSON RPC Controller.
|
||||
const JSONRPC = require('./json-rpc')
|
||||
|
||||
// Load the Clean Architecture Use Case libraries.
|
||||
const UseCases = require('../use-cases')
|
||||
// const useCases = new UseCases({ adapters })
|
||||
|
||||
// Load the REST API Controllers.
|
||||
const RESTControllers = require('./rest-api')
|
||||
// Local libraries
|
||||
import Adapters from '../adapters/index.js'
|
||||
import JSONRPC from './json-rpc/index.js'
|
||||
import UseCases from '../use-cases/index.js'
|
||||
import RESTControllers from './rest-api/index.js'
|
||||
import TimerControllers from './timer-controllers.js'
|
||||
import config from '../../config/index.js'
|
||||
|
||||
class Controllers {
|
||||
constructor (localConfig = {}) {
|
||||
// Initialize EventEmitters used to pass event-driven data around the app.
|
||||
this.eventEmitter = new EventEmitter()
|
||||
localConfig.eventEmitter = this.eventEmitter
|
||||
|
||||
// Initialize Clean Architecture classes. Inject dependencies.
|
||||
// Encapsulate dependencies
|
||||
this.adapters = new Adapters(localConfig)
|
||||
localConfig.adapters = this.adapters
|
||||
this.useCases = new UseCases(localConfig)
|
||||
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
||||
this.config = config
|
||||
}
|
||||
|
||||
async attachControllers (app) {
|
||||
// Wait for any startup processes to complete for the Adapters libraries.
|
||||
// Spin up any adapter libraries that have async startup needs.
|
||||
async initAdapters () {
|
||||
await this.adapters.start()
|
||||
}
|
||||
|
||||
// Attach the REST controllers to the Koa app.
|
||||
// this.attachRESTControllers(app)
|
||||
|
||||
this.attachRPCControllers()
|
||||
// Run any Use Cases to startup the app.
|
||||
async initUseCases () {
|
||||
await this.useCases.start()
|
||||
}
|
||||
|
||||
// Top-level function for this library.
|
||||
@@ -54,6 +47,20 @@ class Controllers {
|
||||
restControllers.attachRESTControllers(app)
|
||||
}
|
||||
|
||||
// Attach any other controllers other than REST API controllers.
|
||||
async attachControllers (app) {
|
||||
// Wait for any startup processes to complete for the Adapters libraries.
|
||||
// await this.adapters.start()
|
||||
|
||||
if (this.config.useIpfs) {
|
||||
// Attach JSON RPC controllers
|
||||
this.attachRPCControllers()
|
||||
}
|
||||
|
||||
// Attach and start the timer controllers
|
||||
this.timerControllers.startTimers()
|
||||
}
|
||||
|
||||
// Add the JSON RPC router to the ipfs-coord adapter.
|
||||
attachRPCControllers () {
|
||||
const jsonRpcController = new JSONRPC({
|
||||
@@ -68,4 +75,4 @@ class Controllers {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Controllers
|
||||
export default Controllers
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
/*
|
||||
This is the JSON RPC router for the users API
|
||||
|
||||
CT 3/5/22: This library can probably be deleted. Handling of the /about endpoint
|
||||
is now directly controlled by the ipfs-coord library.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../../config')
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
class AboutRPC {
|
||||
constructor (localConfig) {
|
||||
@@ -33,6 +36,8 @@ class AboutRPC {
|
||||
// This is a bit different than other router libraries, because there is
|
||||
// only one response, which is a string about this node.
|
||||
async aboutRouter (rpcData) {
|
||||
console.log('debugging: aboutRouter from ipfs-service-provider triggered')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
@@ -43,4 +48,4 @@ class AboutRPC {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AboutRPC
|
||||
export default AboutRPC
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
// const AuthLib = require('../../lib/auth')
|
||||
// const UserLib = require('../../../use-cases/user')
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
const RateLimit = require('../rate-limit')
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
import RateLimit from '../rate-limit.js'
|
||||
|
||||
class AuthRPC {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -173,4 +174,4 @@ class AuthRPC {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthRPC
|
||||
export default AuthRPC
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local support libraries
|
||||
const { wlogger } = require('../../adapters/wlogger')
|
||||
const UserController = require('./users')
|
||||
const AuthController = require('./auth')
|
||||
const AboutController = require('./about')
|
||||
import wlogger from '../../adapters/wlogger.js'
|
||||
|
||||
import UserController from './users/index.js'
|
||||
import AuthController from './auth/index.js'
|
||||
import AboutController from './about/index.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -90,13 +91,14 @@ class JSONRPC {
|
||||
}
|
||||
|
||||
// Process response from REST API.
|
||||
_this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
|
||||
// _this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
|
||||
|
||||
// Log the incoming JSON RPC command.
|
||||
wlogger.info(
|
||||
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}`
|
||||
)
|
||||
}
|
||||
// console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
|
||||
|
||||
// Added the property "from" to the parsedData object;
|
||||
// necessary for calculating rate limits (based on the IPFS ID).
|
||||
@@ -105,6 +107,16 @@ class JSONRPC {
|
||||
// Default return string
|
||||
let retObj = _this.defaultResponse()
|
||||
|
||||
// Forward data on to bch adapter if this is the response of a BCH query.
|
||||
try {
|
||||
if (parsedData.payload.result.method === 'bch') {
|
||||
console.log('routing to BCH adapter')
|
||||
retObj = await _this.adapters.bch.rpcHandler(parsedData)
|
||||
}
|
||||
} catch (err) {
|
||||
/* exit quietly */
|
||||
}
|
||||
|
||||
// Route the command to the appropriate route handler.
|
||||
switch (parsedData.payload.method) {
|
||||
case 'users':
|
||||
@@ -116,6 +128,8 @@ class JSONRPC {
|
||||
case 'about':
|
||||
retObj = await _this.aboutController.aboutRouter(parsedData)
|
||||
break
|
||||
case 'bch':
|
||||
retObj = await _this.adapters.bch.rpcHandler(parsedData)
|
||||
// default:
|
||||
// // Process response from REST API.
|
||||
// _this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
|
||||
@@ -200,4 +214,4 @@ class JSONRPC {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JSONRPC
|
||||
export default JSONRPC
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/* eslint no-useless-catch: 0 */
|
||||
|
||||
// Local libraries
|
||||
const RateLimitLib = require('koa2-ratelimit').RateLimit
|
||||
import { RateLimit as RateLimitLib } from 'koa2-ratelimit'
|
||||
|
||||
class RateLimit {
|
||||
constructor (options) {
|
||||
@@ -80,4 +80,4 @@ class RateLimit {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RateLimit
|
||||
export default RateLimit
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
// const UserLib = require('../../../use-cases/user')
|
||||
const Validators = require('../validators')
|
||||
const RateLimit = require('../rate-limit')
|
||||
import Validators from '../validators.js'
|
||||
|
||||
import RateLimit from '../rate-limit.js'
|
||||
|
||||
class UserRPC {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -533,4 +534,4 @@ class UserRPC {
|
||||
// TODO create deleteUser()
|
||||
}
|
||||
|
||||
module.exports = UserRPC
|
||||
export default UserRPC
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
/* eslint no-useless-catch: 0 */
|
||||
|
||||
// Public npm libraries
|
||||
const jwt = require('jsonwebtoken')
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
// const UserModel = require('../../adapters/localdb/models/users')
|
||||
|
||||
class Validators {
|
||||
@@ -93,4 +94,4 @@ class Validators {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Validators
|
||||
export default Validators
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const Passport = require('../../../adapters/passport')
|
||||
import Passport from '../../../adapters/passport.js'
|
||||
const passport = new Passport()
|
||||
|
||||
let _this
|
||||
@@ -96,4 +96,4 @@ class AuthRESTController {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthRESTController
|
||||
export default AuthRESTController
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const AuthRESTController = require('./controller')
|
||||
import AuthRESTController from './controller.js'
|
||||
|
||||
class AuthRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -48,4 +48,4 @@ class AuthRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthRouter
|
||||
export default AuthRouter
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
REST API Controller library for the /bch route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
// let _this
|
||||
|
||||
@@ -23,10 +24,7 @@ class BchRESTControllerLib {
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// _this = this
|
||||
this.config = config
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +64,11 @@ class BchRESTControllerLib {
|
||||
try {
|
||||
const providerId = ctx.request.body.provider
|
||||
|
||||
// Throw an error, if the provider has been set by an environment variable.
|
||||
if (this.config.freezeProvider) {
|
||||
throw new Error('Consumer has preferredProvider set in environment variable. Refusing to switch providers.')
|
||||
}
|
||||
|
||||
await this.adapters.bch.selectProvider(providerId)
|
||||
|
||||
const body = {
|
||||
@@ -231,6 +234,103 @@ class BchRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/utxosBulk UTXOs Bulk
|
||||
* @apiName UTXOs Bulk
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
|
||||
* with token information.
|
||||
* @apiDescription This endpoint is the same as the /bch/utxos endpoint, but
|
||||
* it allows an array of up to 20 addresses to be queried at the same time.
|
||||
* This reduces the number of JSON RPC calls, and is very handy
|
||||
* for HD wallets that need to quickly scan a lot of addresses.
|
||||
*
|
||||
* Given an address, this endpoint will return an object with thre following
|
||||
* properties:
|
||||
*
|
||||
* - addresses: [] - the addresses these UTXOs are associated with
|
||||
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
|
||||
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
|
||||
* not spent, to be safe.
|
||||
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
|
||||
* - type1: {}
|
||||
* - tokens: [] - SLP token Type 1 tokens.
|
||||
* - mintBatons: [] - SLP token Type 1 mint batons.
|
||||
* - nft: {}
|
||||
* - tokens: [] - NFT tokens
|
||||
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
|
||||
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/utxos
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [
|
||||
* {
|
||||
* "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"],
|
||||
* "bchUtxos":[
|
||||
* {
|
||||
* "height":631219,
|
||||
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "tx_pos":0,
|
||||
* "value":1000,
|
||||
* "txid":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "vout":0,
|
||||
* "isValid":false
|
||||
* }
|
||||
* ],
|
||||
* "nullUtxos":[
|
||||
*
|
||||
* ],
|
||||
* "slpUtxos":{
|
||||
* "type1":{
|
||||
* "mintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* },
|
||||
* "nft":{
|
||||
* "groupMintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "groupTokens":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async utxosBulk (ctx) {
|
||||
try {
|
||||
const addrs = ctx.request.body.addresses
|
||||
// console.log('address: ', address)
|
||||
|
||||
const utxos = await this.adapters.bch.getUtxosBulk(addrs)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
ctx.body = utxos
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/broadcast Broadcast
|
||||
* @apiName Broadcast
|
||||
@@ -271,10 +371,17 @@ class BchRESTControllerLib {
|
||||
|
||||
/**
|
||||
*
|
||||
* @api {post} /bch/transactions Transactions
|
||||
* @apiName Transactions
|
||||
* @api {post} /bch/txHistory TX History
|
||||
* @apiName TX History
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
|
||||
* It returns the transaction history for an address. This list of TXIDs is
|
||||
* sorted and paginated.
|
||||
*
|
||||
* There are three possible inputs:
|
||||
* - address: (required) the address to query for a transaction history
|
||||
* - sortOrder: (optional) will sort results in 'DECENDING' (default) or 'ASCENDING' order.
|
||||
* - page: (optional) will return a 'page' of 100 results. Default is 0
|
||||
*
|
||||
* Given the 'addresses' property returns an array of objects
|
||||
* with the following properties
|
||||
@@ -289,7 +396,7 @@ class BchRESTControllerLib {
|
||||
* Note: For a single address pass the 'addresses' of string type
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"] }' localhost:5001/bch/transactions
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/txHistory
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
@@ -317,11 +424,20 @@ class BchRESTControllerLib {
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async transactions (ctx) {
|
||||
async txHistory (ctx) {
|
||||
try {
|
||||
const addr = ctx.request.body.address
|
||||
// console.log('transactions REST API handler called.')
|
||||
// console.log(`body: ${JSON.stringify(ctx.request.body, null, 2)}`)
|
||||
|
||||
const data = await this.adapters.bch.getTransactions(addr)
|
||||
const addr = ctx.request.body.address
|
||||
const sortOrder = ctx.request.body.sortOrder
|
||||
const page = ctx.request.body.page
|
||||
|
||||
const data = await this.adapters.bch.getTransactions(
|
||||
addr,
|
||||
sortOrder,
|
||||
page
|
||||
)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
@@ -331,11 +447,14 @@ class BchRESTControllerLib {
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/transaction Transaction
|
||||
* @apiName Transaction
|
||||
* @api {post} /bch/txData TX Data
|
||||
* @apiName txData
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get data about a specific transaction.
|
||||
* Given a transaction id this endpoint will return an object
|
||||
* @apiDescription Get expanded transaction data for an array of transaction
|
||||
* IDs. Each call is limited to 20 TXIDs or less.
|
||||
*
|
||||
* Get data about specific transactions.
|
||||
* Given an array of transaction IDs this endpoint will return an array of objects
|
||||
* with the following properties
|
||||
*
|
||||
* - txid: "" - Transaction ID
|
||||
@@ -353,11 +472,11 @@ class BchRESTControllerLib {
|
||||
* - isValidSLPTx: - Determines if the transaction was under SLP
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/transaction
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "txids": ["01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b"] }' localhost:5001/bch/transaction
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* [{
|
||||
* "txid":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "hash":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "version":1,
|
||||
@@ -406,7 +525,7 @@ class BchRESTControllerLib {
|
||||
* "time":1568338904,
|
||||
* "blocktime":1568338904,
|
||||
* "isValidSLPTx":false
|
||||
* }
|
||||
* }]
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
@@ -417,11 +536,19 @@ class BchRESTControllerLib {
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async transaction (ctx) {
|
||||
async txData (ctx) {
|
||||
try {
|
||||
const txid = ctx.request.body.txid
|
||||
console.log(
|
||||
`txData called with this body data: ${JSON.stringify(
|
||||
ctx.request.body,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
)
|
||||
|
||||
const data = await this.adapters.bch.getTransaction(txid)
|
||||
const txids = ctx.request.body.txids
|
||||
|
||||
const data = await this.adapters.bch.getTransaction(txids)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
@@ -462,6 +589,8 @@ class BchRESTControllerLib {
|
||||
* "pubkey": {
|
||||
* "success": true,
|
||||
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@@ -478,6 +607,122 @@ class BchRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {REST} /bch/utxoIsValid utxoIsValid
|
||||
* @apiPermission public
|
||||
* @apiName utxoIsValid
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Verify if UTXO is valid
|
||||
* Given a UTXO object (txid and vout), a full node is queried to verify that
|
||||
* the UTXO still exists in the mempool (true), or if it has been spent (false).
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - success: - Request status
|
||||
* - isValid: - Boolean: true or false
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxoIsValid", "utxo": {"tx_hash": "17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a", "tx_pos": 0}}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "success": true,
|
||||
* "status": 200,
|
||||
* "endpoint": "utxoIsValid",
|
||||
* "isValid": true
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*/
|
||||
async utxoIsValid (ctx) {
|
||||
try {
|
||||
const utxo = ctx.request.body.utxo
|
||||
|
||||
const data = await this.adapters.bch.utxoIsValid(utxo)
|
||||
// console.log(`utxoIsValid data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {REST} /bch getTokenData
|
||||
* @apiPermission public
|
||||
* @apiName getTokenData
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get data associated with a token
|
||||
*
|
||||
* Given a token ID, this endpoint will retrieve the IPFS CIDs associated with
|
||||
* the tokens mutable and immutable data. This is extension of the PS002
|
||||
* specification for mutable data for tokens:
|
||||
* https://github.com/Permissionless-Software-Foundation/specifications/blob/master/ps002-slp-mutable-data.md
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d", "withTxHistory": true }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData
|
||||
*/
|
||||
async getTokenData (ctx) {
|
||||
try {
|
||||
const tokenId = ctx.request.body.tokenId
|
||||
const withTxHistory = !!ctx.request.body.withTxHistory
|
||||
|
||||
const data = await this.adapters.bch.getTokenData(tokenId, withTxHistory)
|
||||
console.log(`getTokenData data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {REST} /bch getTokenData2
|
||||
* @apiPermission public
|
||||
* @apiName getTokenData2
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get token icon and other media
|
||||
*
|
||||
* Get the icon for a token, given it's token ID.
|
||||
* This function expects a string input of a token ID property.
|
||||
* This function returns an object with a tokenIcon property that contains
|
||||
* the URL to the icon.
|
||||
*
|
||||
* The output object always have these properties:
|
||||
* - tokenIcon: A url to the token icon, if it exists.
|
||||
* - tokenStats: Data about the token from psf-slp-indexer.
|
||||
* - optimizedTokenIcon: An alternative, potentially more optimal, url to the token icon, if it exists.
|
||||
* - iconRepoCompatible: true if the token icon is available via token.bch.sx
|
||||
* - ps002Compatible: true if the token icon is compatible with PS007 specification.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d" }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData2
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d", "updateCache": true }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData2
|
||||
*/
|
||||
async getTokenData2 (ctx) {
|
||||
try {
|
||||
const tokenId = ctx.request.body.tokenId
|
||||
const updateCache = ctx.request.body.updateCache
|
||||
console.log('getTokenData2() updateCache: ', updateCache)
|
||||
|
||||
const data = await this.adapters.bch.getTokenData2(tokenId, updateCache)
|
||||
console.log(`getTokenData2 data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
@@ -494,4 +739,5 @@ class BchRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BchRESTControllerLib
|
||||
// module.exports = BchRESTControllerLib
|
||||
export default BchRESTControllerLib
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const BchRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
import BchRESTControllerLib from './controller.js'
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -55,10 +55,14 @@ class BchRouter {
|
||||
this.router.post('/provider', this.postProvider)
|
||||
this.router.post('/balance', this.postBalance)
|
||||
this.router.post('/utxos', this.postUtxos)
|
||||
this.router.post('/utxosBulk', this.postUtxosBulk)
|
||||
this.router.post('/broadcast', this.postBroadcast)
|
||||
this.router.post('/transactions', this.postTransactions)
|
||||
this.router.post('/transaction', this.postTransaction)
|
||||
this.router.post('/txHistory', this.postTxHistory)
|
||||
this.router.post('/txData', this.postTxData)
|
||||
this.router.post('/pubkey', this.postPubKey)
|
||||
this.router.post('/utxoIsValid', this.utxoIsValid)
|
||||
this.router.post('/getTokenData', this.getTokenData)
|
||||
this.router.post('/getTokenData2', this.getTokenData2)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
@@ -81,21 +85,38 @@ class BchRouter {
|
||||
await _this.bchRESTController.utxos(ctx, next)
|
||||
}
|
||||
|
||||
async postUtxosBulk (ctx, next) {
|
||||
await _this.bchRESTController.utxosBulk(ctx, next)
|
||||
}
|
||||
|
||||
async postBroadcast (ctx, next) {
|
||||
await _this.bchRESTController.broadcast(ctx, next)
|
||||
}
|
||||
|
||||
async postTransactions (ctx, next) {
|
||||
await _this.bchRESTController.transactions(ctx, next)
|
||||
async postTxHistory (ctx, next) {
|
||||
await _this.bchRESTController.txHistory(ctx, next)
|
||||
}
|
||||
|
||||
async postTransaction (ctx, next) {
|
||||
await _this.bchRESTController.transaction(ctx, next)
|
||||
async postTxData (ctx, next) {
|
||||
await _this.bchRESTController.txData(ctx, next)
|
||||
}
|
||||
|
||||
async postPubKey (ctx, next) {
|
||||
await _this.bchRESTController.pubKey(ctx, next)
|
||||
}
|
||||
|
||||
async utxoIsValid (ctx, next) {
|
||||
await _this.bchRESTController.utxoIsValid(ctx, next)
|
||||
}
|
||||
|
||||
async getTokenData (ctx, next) {
|
||||
await _this.bchRESTController.getTokenData(ctx, next)
|
||||
}
|
||||
|
||||
async getTokenData2 (ctx, next) {
|
||||
await _this.bchRESTController.getTokenData2(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BchRouter
|
||||
// module.exports = BchRouter
|
||||
export default BchRouter
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable no-useless-escape */
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
import ContactLib from '../../../adapters/contact.js'
|
||||
|
||||
const ContactLib = require('../../../adapters/contact')
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
const contactLib = new ContactLib()
|
||||
|
||||
let _this
|
||||
@@ -74,4 +74,4 @@ class ContactController {
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = ContactController
|
||||
export default ContactController
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const ContactRESTControllerLib = require('./controller')
|
||||
import ContactRESTControllerLib from './controller.js'
|
||||
|
||||
class ContactRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -53,4 +53,4 @@ class ContactRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ContactRouter
|
||||
export default ContactRouter
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
|
||||
// Public npm libraries.
|
||||
|
||||
// Load the REST API Controllers.
|
||||
const AuthRESTController = require('./auth')
|
||||
const UserRouter = require('./users')
|
||||
const ContactRESTController = require('./contact')
|
||||
const LogsRESTController = require('./logs')
|
||||
const BchRESTController = require('./bch')
|
||||
const IpfsRESTController = require('./ipfs')
|
||||
const P2wdbRESTController = require('./p2wdb')
|
||||
// Local libraries
|
||||
import AuthRESTController from './auth/index.js'
|
||||
import UserRouter from './users/index.js'
|
||||
import ContactRESTController from './contact/index.js'
|
||||
import LogsRESTController from './logs/index.js'
|
||||
import BchRESTController from './bch/index.js'
|
||||
import IpfsRESTController from './ipfs/index.js'
|
||||
import P2wdbRESTController from './p2wdb/index.js'
|
||||
import PriceRESTController from './price/index.js'
|
||||
|
||||
class RESTControllers {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -67,7 +68,11 @@ class RESTControllers {
|
||||
// Attach the REST API Controllers associated with the /p2wdb route
|
||||
const p2wdbRESTController = new P2wdbRESTController(dependencies)
|
||||
p2wdbRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /price route
|
||||
const priceRESTController = new PriceRESTController(dependencies)
|
||||
priceRESTController.attach(app)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RESTControllers
|
||||
export default RESTControllers
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
REST API Controller library for the /ipfs route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
// Global npm libraries
|
||||
|
||||
let _this
|
||||
// Local libraries
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
class IpfsRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -26,7 +27,13 @@ class IpfsRESTControllerLib {
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
_this = this
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.getStatus = this.getStatus.bind(this)
|
||||
this.getPeers = this.getPeers.bind(this)
|
||||
this.getRelays = this.getRelays.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
this.connect = this.connect.bind(this)
|
||||
this.getThisNode = this.getThisNode.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,13 +48,13 @@ class IpfsRESTControllerLib {
|
||||
*/
|
||||
async getStatus (ctx) {
|
||||
try {
|
||||
const status = await _this.adapters.ipfs.getStatus()
|
||||
const status = await this.adapters.ipfs.getStatus()
|
||||
|
||||
ctx.body = { status }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getStatus(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
_this.handleError(ctx, err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,26 +63,65 @@ class IpfsRESTControllerLib {
|
||||
try {
|
||||
const showAll = ctx.request.body.showAll
|
||||
|
||||
const peers = await _this.adapters.ipfs.getPeers(showAll)
|
||||
const peers = await this.adapters.ipfs.getPeers(showAll)
|
||||
|
||||
ctx.body = { peers }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getPeers(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
_this.handleError(ctx, err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||
async getRelays (ctx) {
|
||||
try {
|
||||
const relays = await _this.adapters.ipfs.getRelays()
|
||||
const relays = await this.adapters.ipfs.getRelays()
|
||||
|
||||
ctx.body = { relays }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getRelays(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
_this.handleError(ctx, err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
async connect (ctx) {
|
||||
try {
|
||||
const multiaddr = ctx.request.body.multiaddr
|
||||
const getDetails = ctx.request.body.getDetails
|
||||
|
||||
// console.log('this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs: ', this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs)
|
||||
const result = await this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.connectToPeer({ multiaddr, getDetails })
|
||||
// console.log('result: ', result)
|
||||
|
||||
ctx.body = result
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/connect():', err)
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord
|
||||
* @apiPermission public
|
||||
* @apiName GetThisNode
|
||||
* @apiGroup REST BCH
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node
|
||||
*
|
||||
*/
|
||||
async getThisNode (ctx) {
|
||||
try {
|
||||
const thisNode = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
|
||||
ctx.body = { thisNode }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getThisNode(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,4 +141,5 @@ class IpfsRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsRESTControllerLib
|
||||
// module.exports = IpfsRESTControllerLib
|
||||
export default IpfsRESTControllerLib
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const IPFSRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
import IPFSRESTControllerLib from './controller.js'
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
// let _this
|
||||
|
||||
class BchRouter {
|
||||
class IpfsRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
@@ -54,15 +54,14 @@ class BchRouter {
|
||||
this.router.get('/', this.ipfsRESTController.getStatus)
|
||||
this.router.post('/peers', this.ipfsRESTController.getPeers)
|
||||
this.router.post('/relays', this.ipfsRESTController.getRelays)
|
||||
this.router.post('/connect', this.ipfsRESTController.connect)
|
||||
this.router.get('/node', this.ipfsRESTController.getThisNode)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
|
||||
// async getStatus (ctx, next) {
|
||||
// await _this.ipfsRESTController.getStatus(ctx, next)
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = BchRouter
|
||||
// module.exports = BchRouter
|
||||
export default IpfsRouter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const LogsApiLib = require('../../../adapters/logapi')
|
||||
import LogsApiLib from '../../../adapters/logapi.js'
|
||||
const logsApiLib = new LogsApiLib()
|
||||
let _this
|
||||
|
||||
@@ -62,4 +62,4 @@ class LogsApi {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogsApi
|
||||
export default LogsApi
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const LogsRESTControllerLib = require('./controller')
|
||||
import LogsRESTControllerLib from './controller.js'
|
||||
|
||||
class LogsRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -52,4 +52,4 @@ class LogsRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogsRouter
|
||||
export default LogsRouter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function errorMiddleware () {
|
||||
export default function errorMiddleware () {
|
||||
return async (ctx, next) => {
|
||||
try {
|
||||
await next()
|
||||
@@ -8,4 +8,4 @@ module.exports = function errorMiddleware () {
|
||||
ctx.app.emit('error', err, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
/*
|
||||
REST API validator middleware.
|
||||
|
||||
These are a series of functions that ensure the user making a REST API
|
||||
matches a user in the database (or not). In can do fine-grain user control
|
||||
such as telling the difference between an admin, a normal user, and an
|
||||
anonymous user.
|
||||
|
||||
This middleware is used to gatekeep access to different REST API resources.
|
||||
|
||||
CT 9/17/22:
|
||||
This library was lightly refactored to make it work with the new unit tests.
|
||||
This not and the commented code below can be deleted once it is verified that
|
||||
this refactor did not result in any breaking changes.
|
||||
*/
|
||||
|
||||
const User = require('../../../adapters/localdb/models/users')
|
||||
const config = require('../../../../config')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
import User from '../../../adapters/localdb/models/users.js'
|
||||
|
||||
import config from '../../../../config/index.js'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -20,12 +33,10 @@ class Validators {
|
||||
|
||||
async ensureUser (ctx, next) {
|
||||
try {
|
||||
// console.log(`getToken: ${typeof (getToken)}`)
|
||||
const token = _this.getToken(ctx)
|
||||
|
||||
if (!token) {
|
||||
// console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
throw new Error('Token could not be retrieved from header')
|
||||
}
|
||||
|
||||
let decoded = null
|
||||
@@ -34,20 +45,23 @@ class Validators {
|
||||
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
|
||||
decoded = _this.jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
// console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
throw new Error('Could not verify JWT')
|
||||
}
|
||||
|
||||
ctx.state.user = await _this.User.findById(decoded.id, '-password')
|
||||
|
||||
if (!ctx.state.user) {
|
||||
// console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
// console.log('Err: Could not find user.')
|
||||
throw new Error('Could not find user')
|
||||
}
|
||||
|
||||
// return next()
|
||||
return true
|
||||
} catch (error) {
|
||||
ctx.throw(401)
|
||||
// console.log('Ensure user error: ', error)
|
||||
// console.log('ctx: ', ctx)
|
||||
ctx.status = 401
|
||||
ctx.throw(401, error.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +74,8 @@ class Validators {
|
||||
|
||||
if (!token) {
|
||||
// console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Token could not be retrieved from header')
|
||||
}
|
||||
|
||||
let decoded = null
|
||||
@@ -70,22 +85,26 @@ class Validators {
|
||||
decoded = _this.jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
// console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not verify JWT')
|
||||
}
|
||||
|
||||
ctx.state.user = await _this.User.findById(decoded.id, '-password')
|
||||
if (!ctx.state.user) {
|
||||
// console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not find user')
|
||||
}
|
||||
|
||||
if (ctx.state.user.type !== 'admin') {
|
||||
ctx.throw(401, 'not admin')
|
||||
// ctx.throw(401, 'not admin')
|
||||
throw new Error('User is not an admin')
|
||||
}
|
||||
|
||||
// return next()
|
||||
return true
|
||||
} catch (error) {
|
||||
ctx.status = 401
|
||||
ctx.throw(401, error.message)
|
||||
}
|
||||
}
|
||||
@@ -101,7 +120,8 @@ class Validators {
|
||||
|
||||
if (!token) {
|
||||
// console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Token could not be retrieved from header')
|
||||
}
|
||||
|
||||
// The user ID targeted in this API call.
|
||||
@@ -114,14 +134,16 @@ class Validators {
|
||||
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
|
||||
decoded = _this.jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
// console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
console.log(`Err: Token could not be decoded: ${err}`)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not verify JWT')
|
||||
}
|
||||
|
||||
ctx.state.user = await _this.User.findById(decoded.id, '-password')
|
||||
if (!ctx.state.user) {
|
||||
// console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not find user')
|
||||
}
|
||||
// console.log('ctx.state.user: ', ctx.state.user)
|
||||
|
||||
@@ -135,7 +157,8 @@ class Validators {
|
||||
|
||||
// If they don't match, then the calling user better be an admin.
|
||||
if (ctx.state.user.type !== 'admin') {
|
||||
ctx.throw(401, 'not admin')
|
||||
// ctx.throw(401, 'not admin')
|
||||
throw new Error('User is not an admin')
|
||||
} else {
|
||||
wlogger.verbose("It's ok. The user is an admin.")
|
||||
}
|
||||
@@ -144,6 +167,8 @@ class Validators {
|
||||
// return next()
|
||||
return true
|
||||
} catch (error) {
|
||||
// console.log('Error in ensureTargetUserOrAdmin(): ', error)
|
||||
ctx.status = 401
|
||||
ctx.throw(401, error.message)
|
||||
}
|
||||
}
|
||||
@@ -166,4 +191,4 @@ class Validators {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Validators
|
||||
export default Validators
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
REST API Controller library for the /p2wdb route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
// let _this
|
||||
|
||||
@@ -135,4 +135,5 @@ class P2wdbRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2wdbRESTControllerLib
|
||||
// module.exports = P2wdbRESTControllerLib
|
||||
export default P2wdbRESTControllerLib
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const P2wdbRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
import P2wdbRESTControllerLib from './controller.js'
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -98,4 +98,5 @@ class P2wdbRouter {
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = P2wdbRouter
|
||||
// module.exports = P2wdbRouter
|
||||
export default P2wdbRouter
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
REST API Controller library for the /bch route
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
// let _this
|
||||
|
||||
class PriceRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /price REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /price REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.axios = axios
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// _this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /price/usd Get the USD price of BCH
|
||||
* @apiName Get the USD price of BCH
|
||||
* @apiGroup Price
|
||||
* @apiDescription Get the USD price of BCH from Coinbase.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "http://localhost:5005/price/usd" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getUSD (ctx) {
|
||||
try {
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: 'https://api.coinbase.com/v2/',
|
||||
url: '/exchange-rates?currency=BCH',
|
||||
timeout: 15000
|
||||
}
|
||||
|
||||
const response = await this.axios.request(opt)
|
||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
ctx.body = { usd: Number(response.data.data.rates.USD) }
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in GET /price/usd.', err)
|
||||
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the XEC price.
|
||||
// TODO: Add API docs.
|
||||
async getXecPrice (ctx) {
|
||||
try {
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: 'https://api.coinex.com',
|
||||
url: '/v1/market/ticker?market=xecusdt',
|
||||
timeout: 15000
|
||||
}
|
||||
//
|
||||
|
||||
const response = await this.axios.request(opt)
|
||||
console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
ctx.body = { usd: Number(response.data.data.ticker.last) }
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in GET /price/xecusd.', err)
|
||||
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
ctx.throw(err.status, err.message)
|
||||
} else {
|
||||
ctx.throw(err.status)
|
||||
}
|
||||
} else {
|
||||
// By default use a 422 error if the HTTP status is not specified.
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = PriceRESTControllerLib
|
||||
export default PriceRESTControllerLib
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
REST API library for /bch route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
import PriceRESTControllerLib from './controller.js'
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
let _this
|
||||
|
||||
class PriceRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Price REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating Price REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.priceRESTController = new PriceRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/price'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error(
|
||||
'Must pass app object when attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.get('/usd', this.getPrice)
|
||||
this.router.get('/xecusd', this.getXecPrice)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
|
||||
async getPrice (ctx, next) {
|
||||
await _this.priceRESTController.getUSD(ctx, next)
|
||||
}
|
||||
|
||||
async getXecPrice (ctx, next) {
|
||||
await _this.priceRESTController.getXecPrice(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = PriceRouter
|
||||
export default PriceRouter
|
||||
@@ -2,7 +2,7 @@
|
||||
REST API Controller library for the /user route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -36,7 +36,7 @@ class UserRESTControllerLib {
|
||||
* @apiGroup REST Users
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5001/users
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5010/users
|
||||
*
|
||||
* @apiParam {Object} user User object (required)
|
||||
* @apiParam {String} user.email Email
|
||||
@@ -124,7 +124,7 @@ class UserRESTControllerLib {
|
||||
|
||||
ctx.body = { users }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in users/controller.js/getUsers(): '.err)
|
||||
wlogger.error('Error in users/controller.js/getUsers(): ', err)
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ class UserRESTControllerLib {
|
||||
* @apiGroup REST Users
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5010/users/56bd1da600a526986cf65c80
|
||||
*
|
||||
* @apiSuccess {Object} users User object
|
||||
* @apiSuccess {ObjectId} users._id User id
|
||||
@@ -281,4 +281,4 @@ class UserRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRESTControllerLib
|
||||
export default UserRESTControllerLib
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const UserRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
import UserRESTControllerLib from './controller.js'
|
||||
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -85,4 +86,4 @@ class UserRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRouter
|
||||
export default UserRouter
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
This Controller library is concerned with timer-based functions that are
|
||||
kicked off periodicially.
|
||||
*/
|
||||
|
||||
import config from '../../config/index.js'
|
||||
|
||||
class TimerControllers {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Timer Controller libraries.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating Timer Controller libraries.'
|
||||
)
|
||||
}
|
||||
|
||||
this.debugLevel = localConfig.debugLevel
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
|
||||
// Bind 'this' object to all subfunctions.
|
||||
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
|
||||
|
||||
// this.startTimers()
|
||||
}
|
||||
|
||||
// Start all the time-based controllers.
|
||||
startTimers () {
|
||||
// Any new timer control functions can be added here. They will be started
|
||||
// when the server starts.
|
||||
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 10)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
stopTimers () {
|
||||
clearInterval(this.optimizeWalletHandle)
|
||||
}
|
||||
|
||||
// Replace this example function with your own timer handler.
|
||||
exampleTimerFunc (negativeTest) {
|
||||
try {
|
||||
console.log('Example timer controller executed.')
|
||||
|
||||
if (negativeTest) throw new Error('test error')
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in exampleTimerFunc(): ', err)
|
||||
|
||||
// Note: Do not throw an error. This is a top-level function.
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TimerControllers
|
||||
@@ -21,4 +21,4 @@ class User {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = User
|
||||
export default User
|
||||
|
||||
+15
-8
@@ -4,7 +4,7 @@
|
||||
https://troutsblog.com/blog/clean-architecture
|
||||
*/
|
||||
|
||||
const UserUseCases = require('./user')
|
||||
import UserUseCases from './user.js'
|
||||
|
||||
class UseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -14,16 +14,23 @@ class UseCases {
|
||||
'Instance of adapters must be passed in when instantiating Use Cases library.'
|
||||
)
|
||||
}
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'Instance of Event Emitter must be passed in when instantiating Use Cases library.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'Instance of Event Emitter must be passed in when instantiating Use Cases library.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// console.log('use-cases/index.js localConfig: ', localConfig)
|
||||
this.user = new UserUseCases(localConfig)
|
||||
}
|
||||
|
||||
// Run any startup Use Cases at the start of the app.
|
||||
async start () {
|
||||
console.log('Async Use Cases have been started.')
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UseCases
|
||||
export default UseCases
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
functions are called by the /user REST API endpoints.
|
||||
*/
|
||||
|
||||
const UserEntity = require('../entities/user')
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
import UserEntity from '../entities/user.js'
|
||||
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
|
||||
class UserLib {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -181,4 +182,4 @@ class UserLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserLib
|
||||
export default UserLib
|
||||
|
||||
@@ -5,14 +5,18 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const axios = require('axios').default
|
||||
import { assert } from 'chai'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
// const sinon = require('sinon')
|
||||
|
||||
// Local support libraries
|
||||
const config = require('../../../config')
|
||||
const Server = require('../../../bin/server')
|
||||
const testUtils = require('../../utils/test-utils')
|
||||
const AdminLib = require('../../../src/adapters/admin')
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
import Server from '../../../bin/server.js'
|
||||
import testUtils from '../../utils/test-utils.js'
|
||||
import AdminLib from '../../../src/adapters/admin.js'
|
||||
const adminLib = new AdminLib()
|
||||
|
||||
// const request = supertest.agent(app.listen())
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
const testUtils = require('../../utils/test-utils')
|
||||
const assert = require('chai').assert
|
||||
const config = require('../../../config')
|
||||
const axios = require('axios').default
|
||||
const sinon = require('sinon')
|
||||
import testUtils from '../../utils/test-utils.js'
|
||||
import { assert } from 'chai'
|
||||
import config from '../../../config/index.js'
|
||||
import axios from 'axios'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
const util = require('util')
|
||||
import UserController from '../../../src/controllers/rest-api/users/controller.js'
|
||||
import Adapters from '../../../src/adapters/index.js'
|
||||
import UseCases from '../../../src/use-cases/index.js'
|
||||
|
||||
// import UserController from '../../../src/controllers/rest-api/users/controller.js'
|
||||
// import Adapters from '../../../src/adapters/index.js'
|
||||
import EventEmitter from 'events'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const context = {}
|
||||
|
||||
const UserController = require('../../../src/controllers/rest-api/users/controller')
|
||||
const Adapters = require('../../../src/adapters')
|
||||
const EventEmitter = require('events')
|
||||
const adapters = new Adapters({ eventEmitter: new EventEmitter() })
|
||||
const UseCases = require('../../../src/use-cases/')
|
||||
// import UseCases from '../../../src/use-cases/index.js'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -39,9 +43,9 @@ describe('Users', () => {
|
||||
context.id2 = testUser.user._id
|
||||
|
||||
// Get the JWT used to log in as the admin 'system' user.
|
||||
const adminJWT = await testUtils.getAdminJWT()
|
||||
// console.log(`adminJWT: ${admi nJWT}`)
|
||||
context.adminJWT = adminJWT
|
||||
// const adminJWT = await testUtils.getAdminJWT()
|
||||
// console.log(`adminJWT: ${adminJWT}`)
|
||||
// context.adminJWT = adminJWT
|
||||
|
||||
// const admin = await testUtils.loginAdminUser()
|
||||
// context.adminJWT = admin.token
|
||||
@@ -72,9 +76,6 @@ describe('Users', () => {
|
||||
|
||||
await axios(options)
|
||||
|
||||
/* console.log(
|
||||
`result stringified: ${JSON.stringify(result.data, null, 2)}`
|
||||
) */
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 422, 'Error code 422 expected.')
|
||||
@@ -599,30 +600,30 @@ describe('Users', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should be able to update other user when admin', async () => {
|
||||
const adminJWT = context.adminJWT
|
||||
// it('should be able to update other user when admin', async () => {
|
||||
// const adminJWT = context.adminJWT
|
||||
|
||||
const options = {
|
||||
method: 'PUT',
|
||||
url: `${LOCALHOST}/users/${context.user2._id.toString()}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${adminJWT}`
|
||||
},
|
||||
data: {
|
||||
user: {
|
||||
name: 'This should work',
|
||||
email: 'test4@test.com',
|
||||
password: 'password'
|
||||
}
|
||||
}
|
||||
}
|
||||
// const options = {
|
||||
// method: 'PUT',
|
||||
// url: `${LOCALHOST}/users/${context.user2._id.toString()}`,
|
||||
// headers: {
|
||||
// Authorization: `Bearer ${adminJWT}`
|
||||
// },
|
||||
// data: {
|
||||
// user: {
|
||||
// name: 'This should work',
|
||||
// email: 'test4@test.com',
|
||||
// password: 'password'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
const result = await axios(options)
|
||||
// console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
|
||||
// const result = await axios(options)
|
||||
// // console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
const userName = result.data.user.name
|
||||
assert.equal(userName, 'This should work')
|
||||
})
|
||||
// const userName = result.data.user.name
|
||||
// assert.equal(userName, 'This should work')
|
||||
// })
|
||||
|
||||
it('should update user with minimum inputs', async () => {
|
||||
const _id = context.user._id
|
||||
@@ -771,22 +772,22 @@ describe('Users', () => {
|
||||
assert.equal(result.data.success, true)
|
||||
})
|
||||
|
||||
it('should be able to delete other users when admin', async () => {
|
||||
const id = context.id2
|
||||
const adminJWT = context.adminJWT
|
||||
// it('should be able to delete other users when admin', async () => {
|
||||
// const id = context.id2
|
||||
// const adminJWT = context.adminJWT
|
||||
|
||||
const options = {
|
||||
method: 'DELETE',
|
||||
url: `${LOCALHOST}/users/${id}`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${adminJWT}`
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
// console.log(`result: ${util.inspect(result.data)}`)
|
||||
// const options = {
|
||||
// method: 'DELETE',
|
||||
// url: `${LOCALHOST}/users/${id}`,
|
||||
// headers: {
|
||||
// Accept: 'application/json',
|
||||
// Authorization: `Bearer ${adminJWT}`
|
||||
// }
|
||||
// }
|
||||
// const result = await axios(options)
|
||||
// // console.log(`result: ${util.inspect(result.data)}`)
|
||||
|
||||
assert.equal(result.data.success, true)
|
||||
})
|
||||
// assert.equal(result.data.success, true)
|
||||
// })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
const config = require('../../../config')
|
||||
const axios = require('axios').default
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import config from '../../../config/index.js'
|
||||
import axios from 'axios'
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import { context as mockContext } from '../../unit/mocks/ctx-mock.js'
|
||||
import ContactController from '../../../src/controllers/rest-api/contact/controller.js'
|
||||
|
||||
// Mock data
|
||||
// const mockData = require('./mocks/contact-mocks')
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
const ContactController = require('../../../src/controllers/rest-api/contact/controller')
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
const config = require('../../../config')
|
||||
const assert = require('chai').assert
|
||||
import config from '../../../config/index.js'
|
||||
import { assert } from 'chai'
|
||||
import axios from 'axios'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
const axios = require('axios').default
|
||||
const sinon = require('sinon')
|
||||
|
||||
const util = require('util')
|
||||
import LogsController from '../../../src/controllers/rest-api/logs/controller.js'
|
||||
import { context as mockContext } from '../../unit/mocks/ctx-mock.js'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const LogsController = require('../../../src/controllers/rest-api/logs/controller')
|
||||
const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
describe('LogsApi', () => {
|
||||
@@ -39,27 +37,25 @@ describe('LogsApi', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should return log', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/logs`,
|
||||
data: {
|
||||
password: 'test'
|
||||
}
|
||||
}
|
||||
// it('should return log', async () => {
|
||||
|
||||
const result = await axios(options)
|
||||
// const options = {
|
||||
// method: 'post',
|
||||
// url: `${LOCALHOST}/logs`,
|
||||
// data: {
|
||||
// password: 'test'
|
||||
// }
|
||||
// }
|
||||
|
||||
assert.isTrue(result.data.success)
|
||||
assert.isArray(result.data.data)
|
||||
assert.property(result.data.data[0], 'message')
|
||||
assert.property(result.data.data[0], 'level')
|
||||
assert.property(result.data.data[0], 'timestamp')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
// const result = await axios(options)
|
||||
|
||||
// assert.isTrue(result.data.success)
|
||||
// assert.isArray(result.data.data)
|
||||
// assert.property(result.data.data[0], 'message')
|
||||
// assert.property(result.data.data[0], 'level')
|
||||
// assert.property(result.data.data[0], 'timestamp')
|
||||
|
||||
// })
|
||||
|
||||
it('should return false if files are not found!', async () => {
|
||||
try {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
const assert = require('chai').assert
|
||||
const testUtils = require('../../utils/test-utils')
|
||||
|
||||
const Validators = require('../../../src/controllers/rest-api/middleware/validators')
|
||||
|
||||
const sinon = require('sinon')
|
||||
const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
|
||||
const util = require('util')
|
||||
/*
|
||||
import { assert } from 'chai';
|
||||
import testUtils from '../../utils/test-utils.js';
|
||||
import Validators from '../../../src/controllers/rest-api/middleware/validators.js';
|
||||
import sinon from 'sinon';
|
||||
import { context as mockContext } from '../../unit/mocks/ctx-mock.js';
|
||||
import util from 'util';
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const context = {}
|
||||
@@ -303,3 +301,4 @@ describe('Validators', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
const assert = require('chai').assert
|
||||
|
||||
const Admin = require('../../../src/adapters/admin')
|
||||
|
||||
const sinon = require('sinon')
|
||||
|
||||
const util = require('util')
|
||||
import { assert } from 'chai'
|
||||
import Admin from '../../../src/adapters/admin.js'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
let sandbox
|
||||
|
||||
+97
-38
@@ -7,7 +7,7 @@
|
||||
This test will query each REST API endpoint and ensure they are working correctly.
|
||||
*/
|
||||
|
||||
const SERVER = 'http://localhost:5001'
|
||||
const SERVER = 'http://localhost:5005'
|
||||
|
||||
const axios = require('axios')
|
||||
const assert = require('chai').assert
|
||||
@@ -23,8 +23,8 @@ describe('#BCH E2E Tests', () => {
|
||||
const result = await axios.get(url)
|
||||
// console.log(`data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.isAbove(result.data.status.state.serviceProviders.length, 0)
|
||||
assert.isOk(result.data.status.state.selectedServiceProvider)
|
||||
assert.isAbove(result.data.status.serviceProviders.length, 0)
|
||||
assert.isOk(result.data.status.selectedProvider)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -56,9 +56,9 @@ describe('#BCH E2E Tests', () => {
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.address, body.address)
|
||||
assert.property(result.data, 'bchUtxos')
|
||||
assert.property(result.data, 'slpUtxos')
|
||||
assert.equal(result.data[0].address, body.address)
|
||||
assert.property(result.data[0], 'bchUtxos')
|
||||
assert.property(result.data[0], 'slpUtxos')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -74,42 +74,11 @@ describe('#BCH E2E Tests', () => {
|
||||
|
||||
assert.equal(result.data.status, 422)
|
||||
assert.equal(result.data.success, false)
|
||||
assert.equal(result.data.message, 'TX decode failed')
|
||||
assert.equal(result.data.message, 'Missing inputs')
|
||||
assert.equal(result.data.endpoint, 'broadcast')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transactions', () => {
|
||||
it('should get transaction history for an address', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transactions`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.success, true)
|
||||
assert.property(result.data, 'transactions')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transaction', () => {
|
||||
it('should get transaction details for a txid', async () => {
|
||||
const body = {
|
||||
txid: '01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transaction`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.property(result.data, 'txid')
|
||||
assert.property(result.data, 'vin')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#pubkey', () => {
|
||||
it('should return "not found" if pubkey is not on chain', async () => {
|
||||
const body = {
|
||||
@@ -146,4 +115,94 @@ describe('#BCH E2E Tests', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#txHistory', () => {
|
||||
it('should get transaction history for an address, and sort by default in descending order.', async () => {
|
||||
const address = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
|
||||
const body = {
|
||||
address
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/txHistory`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
// Assert expected values and properties.
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.address, address)
|
||||
assert.isArray(result.data.txs)
|
||||
|
||||
// Assert expected sorting
|
||||
assert.isAbove(result.data.txs[0].height, result.data.txs[1].height)
|
||||
})
|
||||
|
||||
it('should sort in ascending order.', async () => {
|
||||
const address = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
|
||||
|
||||
const body = {
|
||||
address,
|
||||
sortOrder: 'ASCENDING'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/txHistory`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
// Assert expected values and properties.
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.address, address)
|
||||
assert.isArray(result.data.txs)
|
||||
|
||||
// Assert expected sorting
|
||||
assert.isAbove(result.data.txs[1].height, result.data.txs[0].height)
|
||||
})
|
||||
|
||||
it('should paginate an address with deep tx history', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/txHistory`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
// Assert there is no more than 100 entries
|
||||
assert.isBelow(result.data.txs.length, 101)
|
||||
})
|
||||
|
||||
it('should get the second page of results', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d',
|
||||
page: 1
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/txHistory`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
// Assert there is no more than 100 entries
|
||||
assert.isBelow(result.data.txs.length, 101)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#txData', () => {
|
||||
it('should get transaction details for a txid', async () => {
|
||||
const body = {
|
||||
txids: [
|
||||
'01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
|
||||
]
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/txData`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.isArray(result.data.txData)
|
||||
assert.property(result.data.txData[0], 'txid')
|
||||
assert.property(result.data.txData[0], 'vin')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Unit tests for the adapters index.js library
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local libraries
|
||||
import Adapters from '../../../src/adapters/index.js'
|
||||
|
||||
describe('#adapters', () => {
|
||||
let uut, sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new Adapters()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should start the async adapters', async () => {
|
||||
// Mock dependencies
|
||||
uut.config.getJwtAtStartup = true
|
||||
uut.config.useIpfs = true
|
||||
uut.config.env = 'not-a-test'
|
||||
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
|
||||
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
|
||||
sandbox.stub(uut.ipfs, 'start').resolves()
|
||||
|
||||
const result = await uut.start()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.config.getJwtAtStartup = false
|
||||
uut.config.env = 'dev'
|
||||
sandbox.stub(uut.ipfs, 'start').rejects(new Error('test error'))
|
||||
|
||||
await uut.start()
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,19 +5,20 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
const EventEmitter = require('events')
|
||||
const cloneDeep = require('lodash.clonedeep')
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import EventEmitter from 'events'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
|
||||
// Local support libraries
|
||||
// const testUtils = require('../../utils/test-utils')
|
||||
|
||||
// Unit under test (uut)
|
||||
const BchAdapter = require('../../../src/adapters/bch')
|
||||
const adapters = require('../mocks/adapters')
|
||||
import BchAdapter from '../../../src/adapters/bch/index.js'
|
||||
import adapters from '../mocks/adapters/index.js'
|
||||
import mockDataLib from '../mocks/use-cases/rest-api-mocks.js'
|
||||
|
||||
const eventEmitter = new EventEmitter()
|
||||
const mockDataLib = require('../mocks/use-cases/rest-api-mocks')
|
||||
|
||||
describe('#bch-use-case', () => {
|
||||
let uut
|
||||
@@ -54,19 +55,19 @@ describe('#bch-use-case', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
const ipfs = {}
|
||||
uut = new BchAdapter({ ipfs })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
)
|
||||
}
|
||||
})
|
||||
// it('should throw an error if adapters are not passed in', () => {
|
||||
// try {
|
||||
// const ipfs = {}
|
||||
// uut = new BchAdapter({ ipfs })
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(
|
||||
// err.message,
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
})
|
||||
|
||||
describe('#waitForRPCResponse', () => {
|
||||
@@ -93,25 +94,12 @@ describe('#bch-use-case', () => {
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.ipfs.ipfsCoordAdapter.bchjs = {
|
||||
Util: {
|
||||
sleep: () => {
|
||||
throw new Error('test error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
uut.rpcDataQueue.push(mockData.rpcData)
|
||||
|
||||
await uut.waitForRPCResponse(rpcId)
|
||||
await uut.waitForRPCResponse()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('error: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
assert.include(err.message, 'rpcId can not be false or undefined')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -155,6 +143,9 @@ describe('#bch-use-case', () => {
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut, 'uid').throws(new Error('test error'))
|
||||
|
||||
await uut.getBalances()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
@@ -165,6 +156,82 @@ describe('#bch-use-case', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getUtxos', () => {
|
||||
it('should get the UTXOs of an address', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const addr = 'addr'
|
||||
|
||||
const result = await uut.getUtxos(addr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getUtxos()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'addr required when calling getUtxos')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getUtxosBulk', () => {
|
||||
it('should get the UTXOs of an address', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const addrs = ['addr']
|
||||
|
||||
const result = await uut.getUtxosBulk(addrs)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should throw an error if addresses is not an array', async () => {
|
||||
try {
|
||||
await uut.getUtxosBulk()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'addresses parameter must be an array')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if addresses array is larger than 20 elements', async () => {
|
||||
try {
|
||||
const addrs = []
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addrs.push(i)
|
||||
}
|
||||
|
||||
await uut.getUtxosBulk(addrs)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'addresses parameter must not exceed 20 elements')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should broadcast a transaction', async () => {
|
||||
// Force connection to a wallet service
|
||||
@@ -190,7 +257,7 @@ describe('#bch-use-case', () => {
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
assert.equal(err.message, 'hex required when calling broadcast')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -220,7 +287,7 @@ describe('#bch-use-case', () => {
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
assert.equal(err.message, 'address required when calling getTransactions()')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -250,7 +317,7 @@ describe('#bch-use-case', () => {
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
assert.equal(err.message, 'txids required when calling getTransaction()')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -280,7 +347,97 @@ describe('#bch-use-case', () => {
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
assert.equal(err.message, 'address required when calling getPubKey()')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxoIsValid', () => {
|
||||
it('should validate a utxo', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const utxo = { a: 'b' }
|
||||
|
||||
const result = await uut.utxoIsValid(utxo)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.utxoIsValid()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'utxo required when calling utxoIsValid()')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData', () => {
|
||||
it('should validate a utxo', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const tokenId = 'blah'
|
||||
|
||||
const result = await uut.getTokenData(tokenId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getTokenData()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'tokenId required when calling getTokenData()')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData2', () => {
|
||||
it('should validate a utxo', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const tokenId = 'blah'
|
||||
|
||||
const result = await uut.getTokenData2(tokenId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getTokenData2()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'tokenId required when calling getTokenData2()')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const ContactLib = require('../../../src/adapters/contact')
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import ContactLib from '../../../src/adapters/contact.js'
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
|
||||
const FullStackJWT = require('../../../src/adapters/fullstack-jwt')
|
||||
import sinon from 'sinon'
|
||||
import FullStackJWT from '../../../src/adapters/fullstack-jwt.js'
|
||||
|
||||
describe('#FullStackJWT', () => {
|
||||
let sandbox
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
Unit tests for the IPFS Adapter.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
const cloneDeep = require('lodash.clonedeep')
|
||||
|
||||
const IPFSCoordAdapter = require('../../../src/adapters/ipfs/ipfs-coord')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
|
||||
const EventEmitter = require('events')
|
||||
const mockDataLib = require('../mocks/adapters/ipfs-coord-mocks')
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import IPFSCoordAdapter from '../../../src/adapters/ipfs/ipfs-coord.js'
|
||||
// import IPFSMock from '../mocks/ipfs-mock.js'
|
||||
import create from '../mocks/ipfs-mock.js'
|
||||
import IPFSCoordMock from '../mocks/ipfs-coord-mock.js'
|
||||
import config from '../../../config/index.js'
|
||||
import mockDataLib from '../mocks/adapters/ipfs-coord-mocks.js'
|
||||
|
||||
describe('#IPFS', () => {
|
||||
let uut
|
||||
@@ -18,8 +18,8 @@ describe('#IPFS', () => {
|
||||
let mockData
|
||||
|
||||
beforeEach(() => {
|
||||
const ipfs = IPFSMock.create()
|
||||
uut = new IPFSCoordAdapter({ ipfs, eventEmitter: new EventEmitter() })
|
||||
const ipfs = create()
|
||||
uut = new IPFSCoordAdapter({ ipfs })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
@@ -47,19 +47,19 @@ describe('#IPFS', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if EventEmitter instance is not included', () => {
|
||||
try {
|
||||
const ipfs = IPFSMock.create()
|
||||
uut = new IPFSCoordAdapter({ ipfs })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
)
|
||||
}
|
||||
})
|
||||
// it('should throw an error if EventEmitter instance is not included', () => {
|
||||
// try {
|
||||
// const ipfs = IPFSMock.create()
|
||||
// uut = new IPFSCoordAdapter({ ipfs })
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(
|
||||
// err.message,
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
@@ -76,7 +76,7 @@ describe('#IPFS', () => {
|
||||
it('should get the public IP address if this node is a Circuit Relay', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
sandbox.stub(uut.publicIp, 'v4').returns('123')
|
||||
sandbox.stub(uut, 'publicIp').resolves('123')
|
||||
|
||||
// Force Circuit Relay
|
||||
uut.config.isCircuitRelay = true
|
||||
@@ -86,6 +86,31 @@ describe('#IPFS', () => {
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should exit quietly if this node is a Circuit Relay and there is an issue getting the IP address', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
sandbox.stub(uut, 'publicIp').rejects(new Error('test error'))
|
||||
|
||||
// Force Circuit Relay
|
||||
uut.config.isCircuitRelay = true
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return a promise that resolves into an instance of IPFS in production mode', async () => {
|
||||
uut.config.isProduction = true
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
assert.equal(result, true)
|
||||
config.isProduction = false
|
||||
})
|
||||
})
|
||||
|
||||
describe('#attachRPCRouter', () => {
|
||||
@@ -99,8 +124,9 @@ describe('#IPFS', () => {
|
||||
}
|
||||
},
|
||||
adapters: {
|
||||
orbit: {
|
||||
privateLog: () => {}
|
||||
pubsub: {
|
||||
privateLog: () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +147,7 @@ describe('#IPFS', () => {
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -151,22 +177,38 @@ describe('#IPFS', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#peerInputHandler', () => {
|
||||
it('should emit an event trigger', () => {
|
||||
const data = 'some data'
|
||||
// describe('#peerInputHandler', () => {
|
||||
// it('should emit an event trigger', () => {
|
||||
// const data = 'some data'
|
||||
//
|
||||
// uut.peerInputHandler(data)
|
||||
//
|
||||
// assert.isOk(true, 'Not throwing an error is a success')
|
||||
// })
|
||||
//
|
||||
// it('should catch and report errors', () => {
|
||||
// // Force an error
|
||||
// sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
|
||||
//
|
||||
// uut.peerInputHandler()
|
||||
//
|
||||
// assert.isOk(true, 'Not throwing an error is a success.')
|
||||
// })
|
||||
// })
|
||||
|
||||
uut.peerInputHandler(data)
|
||||
describe('#subscribeToChat', () => {
|
||||
it('should subscribe to the chat channel', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
adapters: {
|
||||
pubsub: {
|
||||
subscribeToPubsubChannel: async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert.isOk(true, 'Not throwing an error is a success')
|
||||
})
|
||||
|
||||
it('should catch and report errors', () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
|
||||
|
||||
uut.peerInputHandler()
|
||||
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
await uut.subscribeToChat()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,20 +2,22 @@
|
||||
Unit tests for the index.js file for the IPFS and ipfs-coord libraries.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
|
||||
const IPFSLib = require('../../../src/adapters/ipfs')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
|
||||
const EventEmitter = require('events')
|
||||
import sinon from 'sinon'
|
||||
import IPFSLib from '../../../src/adapters/ipfs/index.js'
|
||||
// import IPFSMock from '../mocks/ipfs-mock.js'
|
||||
// import create from '../mocks/ipfs-mock.js'
|
||||
import IPFSCoordMock from '../mocks/ipfs-coord-mock.js'
|
||||
// import EventEmiter from 'events'
|
||||
|
||||
describe('#IPFS-adapter-index', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new IPFSLib({ eventEmitter: new EventEmitter() })
|
||||
// uut = new IPFSLib({ eventEmitter: new EventEmitter() })
|
||||
uut = new IPFSLib({ })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
@@ -25,7 +27,9 @@ describe('#IPFS-adapter-index', () => {
|
||||
describe('#start', () => {
|
||||
it('should return a promise that resolves into an instance of IPFS.', async () => {
|
||||
// Mock dependencies.
|
||||
uut.ipfsAdapter = new IPFSMock()
|
||||
uut.ipfsAdapter = {
|
||||
start: async () => {}
|
||||
}
|
||||
uut.IpfsCoordAdapter = IPFSCoordMock
|
||||
|
||||
const result = await uut.start()
|
||||
@@ -65,4 +69,155 @@ describe('#IPFS-adapter-index', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should return an object with node metrics', () => {
|
||||
// Force uut to have the needed properties
|
||||
uut.ipfsCoordAdapter = {
|
||||
ipfsCoord: {
|
||||
thisNode: {
|
||||
ipfsId: 'fake-id',
|
||||
ipfsMultiaddrs: [],
|
||||
bchAddr: 'fake-bch-addr',
|
||||
slpAddr: 'fake-slp-addr',
|
||||
pubKey: 'fake-pubkey',
|
||||
peerList: [],
|
||||
relayData: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = uut.getStatus()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'ipfsId')
|
||||
assert.property(result, 'multiAddrs')
|
||||
assert.property(result, 'bchAddr')
|
||||
assert.property(result, 'slpAddr')
|
||||
assert.property(result, 'pubKey')
|
||||
assert.property(result, 'peers')
|
||||
assert.property(result, 'relays')
|
||||
})
|
||||
|
||||
it('should catch, report, and throw errors', () => {
|
||||
try {
|
||||
uut.getStatus()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err.message: ', err.message)
|
||||
assert.include(err.message, 'Cannot read properties')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_removeDuplicatePeers', () => {
|
||||
it('should return the same array when there are no duplicates', () => {
|
||||
const inArray = [
|
||||
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
|
||||
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA',
|
||||
'12D3KooWGsgHWyDLKuV4ZSfRJfsxQJj77rxx3i8Px3qXKHsLN7a2',
|
||||
'12D3KooWJyc54njjeZGbLew4D8u1ghrmZTTPyh3QpBF7dxtd3zGY',
|
||||
'12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu'
|
||||
]
|
||||
|
||||
const result = uut._removeDuplicatePeers(inArray)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.length, inArray.length)
|
||||
})
|
||||
|
||||
it('should remove duplicates from an array', () => {
|
||||
const inArray = [
|
||||
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
|
||||
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA',
|
||||
'12D3KooWGsgHWyDLKuV4ZSfRJfsxQJj77rxx3i8Px3qXKHsLN7a2',
|
||||
'12D3KooWJyc54njjeZGbLew4D8u1ghrmZTTPyh3QpBF7dxtd3zGY',
|
||||
'12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu',
|
||||
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
|
||||
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA'
|
||||
]
|
||||
|
||||
const result = uut._removeDuplicatePeers(inArray)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.length, inArray.length - 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getPeers', () => {
|
||||
it('should return an array of current JSON data about each peer when showAll is true', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoordAdapter.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerData: [{ from: 'a', data: { jsonLd: { name: 'a', protocol: 'test', version: '1' } } }, { from: 'b' }]
|
||||
},
|
||||
adapters: {
|
||||
ipfs: {
|
||||
getPeers: async () => ['a', 'b']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.getPeers(true)
|
||||
|
||||
assert.isArray(result)
|
||||
})
|
||||
|
||||
it('should catch, report, and throw errors', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.ipfsCoordAdapter.ipfsCoord = {
|
||||
thisNode: {},
|
||||
adapters: {
|
||||
ipfs: {
|
||||
getPeers: async () => { throw new Error('test error') }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await uut.getPeers()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err.message: ', err.message)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getRelays', () => {
|
||||
it('should return known relays', () => {
|
||||
uut.ipfsCoordAdapter.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerData: [{ from: 'a', data: { jsonLd: { name: 'a', description: 'test' } } }, { from: 'b' }],
|
||||
relayData: [{ ipfsId: 'c' }, { ipfsId: 'a' }]
|
||||
}
|
||||
}
|
||||
|
||||
const result = uut.getRelays()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isArray(result.v2Relays)
|
||||
assert.equal(result.v2Relays.length, 2)
|
||||
assert.property(result.v2Relays[0], 'name')
|
||||
assert.property(result.v2Relays[0], 'description')
|
||||
assert.property(result.v2Relays[1], 'name')
|
||||
assert.property(result.v2Relays[1], 'description')
|
||||
})
|
||||
|
||||
it('should catch, report, and throw errors', () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.ipfsCoordAdapter.ipfsCoord = { thisNode: {} }
|
||||
|
||||
uut.getRelays()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err.message: ', err.message)
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,44 +2,79 @@
|
||||
Unit tests for the IPFS Adapter.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
// Global npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import { peerIdFromString } from '@libp2p/peer-id'
|
||||
|
||||
const IPFSLib = require('../../../src/adapters/ipfs/ipfs')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
// Local libraries
|
||||
import IPFSLib from '../../../src/adapters/ipfs/ipfs.js'
|
||||
// import create from '../mocks/ipfs-mock.js'
|
||||
import config from '../../../config/index.js'
|
||||
import createHeliaLib from '../mocks/helia-mock.js'
|
||||
|
||||
// config.isProduction = true;
|
||||
describe('#IPFS-adapter', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let ipfs
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new IPFSLib()
|
||||
|
||||
ipfs = cloneDeep(createHeliaLib)
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should instantiate IPFS Lib in dev mode.', async () => {
|
||||
const _uut = new IPFSLib()
|
||||
assert.exists(_uut)
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
})
|
||||
|
||||
it('should instantiate dev IPFS Lib in production mode.', async () => {
|
||||
config.isProduction = true
|
||||
const _uut = new IPFSLib()
|
||||
assert.exists(_uut)
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
config.isProduction = false
|
||||
})
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should return a promise that resolves into an instance of IPFS.', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IPFS = IPFSMock
|
||||
sandbox.stub(uut, 'createNode').resolves(ipfs)
|
||||
sandbox.stub(uut, 'publicIp').resolves('192.168.2.4')
|
||||
sandbox.stub(uut, 'multiaddr').returns('/ip4/fake-multiaddr')
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
// Assert properties of the instance are set.
|
||||
assert.equal(uut.isReady, true)
|
||||
assert.property(uut, 'multiaddrs')
|
||||
assert.property(uut, 'id')
|
||||
|
||||
assert.property(result, 'config')
|
||||
// Output should be an instance of IPFS
|
||||
assert.property(result, 'libp2p')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.IPFS, 'create').rejects(new Error('test error'))
|
||||
sandbox.stub(uut, 'createNode').rejects(new Error('test error'))
|
||||
|
||||
await uut.start()
|
||||
|
||||
assert.fail('Unexpected code path.')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
@@ -52,7 +87,8 @@ describe('#IPFS-adapter', () => {
|
||||
it('should stop the IPFS node', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs = {
|
||||
stop: () => {}
|
||||
stop: () => {
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.stop()
|
||||
@@ -61,23 +97,145 @@ describe('#IPFS-adapter', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rmBlocksDir', () => {
|
||||
it('should delete the /blocks directory', () => {
|
||||
const result = uut.rmBlocksDir()
|
||||
describe('#ensureBlocksDir', () => {
|
||||
it('should create directory if it does not exist', () => {
|
||||
// Force desired code path
|
||||
sandbox.stub(uut.fs, 'existsSync').returns(false)
|
||||
sandbox.stub(uut.fs, 'mkdirSync').returns(true)
|
||||
|
||||
const result = uut.ensureBlocksDir()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.fs, 'rmdirSync').throws(new Error('test error'))
|
||||
it('should report and throw errors', () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut.fs, 'existsSync').throws(new Error('test error'))
|
||||
|
||||
uut.rmBlocksDir()
|
||||
try {
|
||||
uut.ensureBlocksDir()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.equal(err.message, 'test error')
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createNode', () => {
|
||||
it('should report and throw errors', async () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut, 'createLibp2p').rejects(new Error('test error'))
|
||||
|
||||
try {
|
||||
await uut.createNode()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should create an IPFS node from Helia', async () => {
|
||||
uut.config.isCircuitRelay = false
|
||||
const result = await uut.createNode()
|
||||
// console.log('result: ', result)
|
||||
|
||||
// Assert the returned IPFS node has expected properties
|
||||
assert.property(result, 'libp2p')
|
||||
assert.property(result, 'blockstore')
|
||||
|
||||
// Stop the IPFS node
|
||||
await result.stop()
|
||||
})
|
||||
|
||||
it('should create a Circuit Relay if configured', async () => {
|
||||
uut.config.isCircuitRelay = true
|
||||
const result = await uut.createNode()
|
||||
|
||||
// Assert the returned IPFS node has expected properties
|
||||
assert.property(result, 'libp2p')
|
||||
assert.property(result, 'blockstore')
|
||||
|
||||
// Stop the IPFS node
|
||||
await result.stop()
|
||||
})
|
||||
|
||||
it('should create a new private key on first run', async () => {
|
||||
uut.config.isCircuitRelay = false
|
||||
|
||||
// Mock dependencies and force desired code path.
|
||||
let beenCalled = false
|
||||
sandbox.stub(uut, 'getKeychain').resolves({
|
||||
exportPeerId: async () => {
|
||||
if (!beenCalled) {
|
||||
beenCalled = true
|
||||
throw new Error('test error')
|
||||
}
|
||||
return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP')
|
||||
},
|
||||
createKey: async () => {}
|
||||
})
|
||||
sandbox.stub(uut, 'createLibp2p').resolves()
|
||||
sandbox.stub(uut, 'createHelia').resolves({})
|
||||
|
||||
const result = await uut.createNode()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'fs')
|
||||
})
|
||||
|
||||
it('should not use circuit relay when CONNECT_PREF is set to direct', async () => {
|
||||
process.env.CONNECT_PREF = 'direct'
|
||||
|
||||
uut.config.isCircuitRelay = false
|
||||
const result = await uut.createNode()
|
||||
// console.log('result: ', result)
|
||||
|
||||
delete process.env.CONNECT_PREF
|
||||
|
||||
// Assert the returned IPFS node has expected properties
|
||||
assert.property(result, 'libp2p')
|
||||
assert.property(result, 'blockstore')
|
||||
|
||||
// Stop the IPFS node
|
||||
await result.stop()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getSeed', () => {
|
||||
it('should read the seed from the JSON file', async () => {
|
||||
// Mock dependencies and force desired code path
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').resolves('12345678')
|
||||
|
||||
const result = await uut.getSeed()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should generate a new seed if the JSON file is not found', async () => {
|
||||
// Mock dependencies and force desired code path
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error'))
|
||||
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves()
|
||||
|
||||
const result = await uut.getSeed()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should catch, report, and throw errors', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error'))
|
||||
sandbox.stub(uut.jsonFiles, 'writeJSON').rejects(new Error('test error'))
|
||||
|
||||
await uut.getSeed()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
const assert = require('chai').assert
|
||||
const fs = require('fs')
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
import fs from 'fs'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
const util = require('util')
|
||||
import JsonFiles from '../../../src/adapters/json-files.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const JsonFiles = require('../../../src/adapters/json-files')
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const JSON_FILE = 'test-json-file.json'
|
||||
const JSON_PATH = `${__dirname.toString()}/${JSON_FILE}`
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
const assert = require('chai').assert
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
const sinon = require('sinon')
|
||||
import LogsApiLib from '../../../src/adapters/logapi.js'
|
||||
import mockData from '../mocks/log-api-mock.js'
|
||||
|
||||
const util = require('util')
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LogsApiLib = require('../../../src/adapters/logapi')
|
||||
const mockData = require('../mocks/log-api-mock')
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const context = {}
|
||||
let sandbox
|
||||
@@ -32,19 +35,18 @@ describe('#LogsApiLib', () => {
|
||||
})
|
||||
|
||||
it('should return log', async () => {
|
||||
try {
|
||||
const pass = 'test'
|
||||
const result = await uut.getLogs(pass)
|
||||
// console.log('result', result)
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'generateFileName').returns(`${__dirname.toString()}/../mocks/adapters/fake-log`)
|
||||
|
||||
assert.isTrue(result.success)
|
||||
assert.isArray(result.data)
|
||||
assert.property(result.data[0], 'message')
|
||||
assert.property(result.data[0], 'level')
|
||||
assert.property(result.data[0], 'timestamp')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
const pass = 'test'
|
||||
const result = await uut.getLogs(pass)
|
||||
// console.log('result', result)
|
||||
|
||||
assert.isTrue(result.success)
|
||||
assert.isArray(result.data)
|
||||
assert.property(result.data[0], 'message')
|
||||
assert.property(result.data[0], 'level')
|
||||
assert.property(result.data[0], 'timestamp')
|
||||
})
|
||||
|
||||
it('should return false if files are not found!', async () => {
|
||||
@@ -201,31 +203,25 @@ describe('#LogsApiLib', () => {
|
||||
})
|
||||
|
||||
it('should ignore fileReader callback errors', async () => {
|
||||
try {
|
||||
// https://sinonjs.org/releases/latest/stubs/
|
||||
// About yields
|
||||
sandbox.stub(uut.lineReader, 'eachLine').yieldsRight({}, true)
|
||||
// https://sinonjs.org/releases/latest/stubs/
|
||||
// About yields
|
||||
sandbox.stub(uut.lineReader, 'eachLine').yieldsRight({}, true)
|
||||
|
||||
const fileName = context.fileName
|
||||
const result = await uut.readLines(fileName)
|
||||
assert.isArray(result)
|
||||
} catch (err) {
|
||||
assert.fail('Unexpected result')
|
||||
}
|
||||
const fileName = `${__dirname.toString()}/../mocks/adapters/fake-log`
|
||||
|
||||
const result = await uut.readLines(fileName)
|
||||
assert.isArray(result)
|
||||
})
|
||||
|
||||
it('should return data', async () => {
|
||||
try {
|
||||
const fileName = context.fileName
|
||||
const result = await uut.readLines(fileName)
|
||||
const fileName = `${__dirname.toString()}/../mocks/adapters/fake-log`
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[1], 'message')
|
||||
assert.property(result[1], 'level')
|
||||
assert.property(result[1], 'timestamp')
|
||||
} catch (err) {
|
||||
assert.fail('Unexpected result')
|
||||
}
|
||||
const result = await uut.readLines(fileName)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[1], 'message')
|
||||
assert.property(result[1], 'level')
|
||||
assert.property(result[1], 'timestamp')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
|
||||
const NodeMailer = require('../../../src/adapters/nodemailer')
|
||||
import sinon from 'sinon'
|
||||
import NodeMailer from '../../../src/adapters/nodemailer.js'
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const assert = require('chai').assert
|
||||
const PassportLib = require('../../../src/adapters/passport')
|
||||
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
import PassportLib from '../../../src/adapters/passport.js'
|
||||
import sinon from 'sinon'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
Unit tests for the users Mongoose model.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
const mongoose = require('mongoose')
|
||||
import { assert } from 'chai'
|
||||
|
||||
import sinon from 'sinon'
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
import User from '../../../src/adapters/localdb/models/users.js'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
// Set the environment variable to signal this is a test.
|
||||
process.env.SVC_ENV = 'test'
|
||||
|
||||
const User = require('../../../src/adapters/localdb/models/users')
|
||||
const config = require('../../../config')
|
||||
|
||||
describe('#User-Adapter', () => {
|
||||
// let uut
|
||||
let sandbox
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
Unit tests for the Wallet Adapter library.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import BchWallet from 'minimal-slp-wallet'
|
||||
import fs from 'fs'
|
||||
|
||||
// Local libraries
|
||||
import WalletAdapter from '../../../src/adapters/wallet.adapter.js'
|
||||
import { MockBchWallet } from '../mocks/adapters/wallet.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
// Global constants
|
||||
const testWalletFile = `${__dirname.toString()}/test-wallet.json`
|
||||
|
||||
describe('#wallet', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
before(() => {
|
||||
// Delete the test file if it exists.
|
||||
try {
|
||||
deleteFile(testWalletFile)
|
||||
} catch (err) { }
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new WalletAdapter()
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
after(() => {
|
||||
// Delete the test file if it exists.
|
||||
try {
|
||||
deleteFile(testWalletFile)
|
||||
} catch (err) { }
|
||||
})
|
||||
|
||||
describe('#_instanceWallet', () => {
|
||||
it('should create a wallet given a mnemonic', async () => {
|
||||
const mnemonic = 'wagon tray learn flat erase laugh lonely rug check captain jacket morning'
|
||||
const result = await uut._instanceWallet(mnemonic)
|
||||
// console.log('result: ', result)
|
||||
assert.equal(result.walletInfo.mnemonic, mnemonic)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#openWallet', () => {
|
||||
it('should create a new wallet when wallet file does not exist', async () => {
|
||||
// Mock dependencies
|
||||
uut.BchWallet = MockBchWallet
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.walletFile = testWalletFile
|
||||
const result = await uut.openWallet()
|
||||
// console.log('result: ', result)
|
||||
assert.property(result, 'mnemonic')
|
||||
assert.property(result, 'privateKey')
|
||||
assert.property(result, 'publicKey')
|
||||
assert.property(result, 'cashAddress')
|
||||
assert.property(result, 'address')
|
||||
assert.property(result, 'slpAddress')
|
||||
assert.property(result, 'legacyAddress')
|
||||
assert.property(result, 'hdPath')
|
||||
})
|
||||
|
||||
it('should open existing wallet file', async () => {
|
||||
// This test case uses the file created in the previous test case.
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.walletFile = testWalletFile
|
||||
const result = await uut.openWallet()
|
||||
// console.log('result: ', result)
|
||||
assert.property(result, 'mnemonic')
|
||||
assert.property(result, 'privateKey')
|
||||
assert.property(result, 'publicKey')
|
||||
assert.property(result, 'cashAddress')
|
||||
assert.property(result, 'address')
|
||||
assert.property(result, 'slpAddress')
|
||||
assert.property(result, 'legacyAddress')
|
||||
assert.property(result, 'hdPath')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.config.walletFile = ''
|
||||
uut.BchWallet = () => {
|
||||
}
|
||||
await uut.openWallet()
|
||||
// console.log('result: ', result)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'this.BchWallet is not a constructor')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#instanceWalletWithoutInitialization', () => {
|
||||
it('should create an instance of BchWallet', async () => {
|
||||
// Create a mock wallet.
|
||||
const mockWallet = new BchWallet()
|
||||
await mockWallet.walletInfoPromise
|
||||
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||
uut.config.authPass = 'fake-auth-pass'
|
||||
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.config.walletFile = testWalletFile
|
||||
const walletData = await uut.openWallet()
|
||||
|
||||
// console.log('walletData: ', walletData)
|
||||
const result = await uut.instanceWalletWithoutInitialization(walletData)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'walletInfoPromise')
|
||||
assert.property(result, 'walletInfo')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut, '_instanceWallet').rejects(new Error('test error'))
|
||||
|
||||
await uut.instanceWalletWithoutInitialization()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should create an instance of BchWallet using web2 infra', async () => {
|
||||
// Create a mock wallet.
|
||||
const mockWallet = new BchWallet()
|
||||
await mockWallet.walletInfoPromise
|
||||
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.config.walletFile = testWalletFile
|
||||
const walletData = await uut.openWallet()
|
||||
// console.log('walletData: ', walletData)
|
||||
|
||||
// Force desired code path
|
||||
uut.config.walletInterface = 'web2'
|
||||
const result = await uut.instanceWalletWithoutInitialization(walletData)
|
||||
|
||||
// console.log('result: ', result)
|
||||
assert.property(result, 'walletInfoPromise')
|
||||
assert.property(result, 'walletInfo')
|
||||
})
|
||||
|
||||
it('should generate wallet from mnemonic in config', async () => {
|
||||
// Create a mock wallet.
|
||||
const mockWallet = new BchWallet()
|
||||
await mockWallet.walletInfoPromise
|
||||
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.config.walletFile = testWalletFile
|
||||
const walletData = await uut.openWallet()
|
||||
// console.log('walletData: ', walletData)
|
||||
|
||||
const originalConfig = uut.config.mnemonic
|
||||
uut.config.mnemonic = walletData.mnemonic
|
||||
|
||||
const result = await uut.instanceWalletWithoutInitialization({})
|
||||
// console.log('result: ', result)
|
||||
|
||||
uut.config.mnemonic = originalConfig
|
||||
|
||||
assert.property(result, 'walletInfoPromise')
|
||||
assert.property(result, 'walletInfo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#instanceWallet', () => {
|
||||
it('should create an instance of BchWallet', async () => {
|
||||
// Create a mock wallet.
|
||||
const mockWallet = new BchWallet()
|
||||
await mockWallet.walletInfoPromise
|
||||
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.WALLET_FILE = testWalletFile
|
||||
const walletData = await uut.openWallet()
|
||||
// console.log('walletData: ', walletData)
|
||||
const result = await uut.instanceWallet(walletData)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'walletInfoPromise')
|
||||
assert.property(result, 'walletInfo')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut, 'instanceWalletWithoutInitialization').rejects(new Error('test error'))
|
||||
|
||||
await uut.instanceWallet()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should create an instance of BchWallet using web2 infra', async () => {
|
||||
// Create a mock wallet.
|
||||
const mockWallet = new BchWallet()
|
||||
await mockWallet.walletInfoPromise
|
||||
sandbox.stub(mockWallet, 'initialize').resolves()
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
|
||||
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.WALLET_FILE = testWalletFile
|
||||
const walletData = await uut.openWallet()
|
||||
// console.log('walletData: ', walletData)
|
||||
|
||||
// Force desired code path
|
||||
uut.config.useFullStackCash = true
|
||||
const result = await uut.instanceWallet(walletData)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'walletInfoPromise')
|
||||
assert.property(result, 'walletInfo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#incrementNextAddress', () => {
|
||||
it('should increment the nextAddress property', async () => {
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.WALLET_FILE = testWalletFile
|
||||
// mock instance of minimal-slp-wallet
|
||||
uut.bchWallet = new MockBchWallet()
|
||||
const result = await uut.incrementNextAddress()
|
||||
assert.equal(result, 2)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut, 'openWallet').rejects(new Error('test error'))
|
||||
await uut.incrementNextAddress()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getKeyPair', () => {
|
||||
it('should return an object with a key pair', async () => {
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.WALLET_FILE = testWalletFile
|
||||
// mock instance of minimal-slp-wallet
|
||||
uut.bchWallet = new MockBchWallet()
|
||||
const result = await uut.getKeyPair()
|
||||
// console.log('result: ', result)
|
||||
assert.property(result, 'cashAddress')
|
||||
assert.property(result, 'wif')
|
||||
assert.property(result, 'hdIndex')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut, 'incrementNextAddress')
|
||||
.rejects(new Error('test error'))
|
||||
await uut.getKeyPair()
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#optimize', () => {
|
||||
it('should call the wallet optimize function', async () => {
|
||||
// mock instance of minimal-slp-wallet
|
||||
uut.bchWallet = new MockBchWallet()
|
||||
sandbox.stub(uut.bchWallet, 'optimize').resolves({ bchUtxoCnt: 10 })
|
||||
const result = await uut.optimize()
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getBalance', () => {
|
||||
it('should get the balance for the wallet', async () => {
|
||||
// mock instance of minimal-slp-wallet
|
||||
uut.bchWallet = new MockBchWallet()
|
||||
// Mock dependencies and force desired code path
|
||||
sandbox.stub(uut.bchWallet, 'getBalance').resolves(41012)
|
||||
sandbox.stub(uut.bchWallet, 'listTokens').resolves([{
|
||||
tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
ticker: 'PSF',
|
||||
name: 'Permissionless Software Foundation',
|
||||
decimals: 8,
|
||||
tokenType: 1,
|
||||
url: 'psfoundation.cash',
|
||||
qty: 2
|
||||
}])
|
||||
const result = await uut.getBalance()
|
||||
// console.log('result: ', result)
|
||||
// Assert the expected properties exist and have the expected values.
|
||||
assert.equal(result.satBalance, 41012)
|
||||
assert.equal(result.psfBalance, 2)
|
||||
assert.equal(result.success, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const deleteFile = (filepath) => {
|
||||
try {
|
||||
// Delete state if exist
|
||||
fs.unlinkSync(filepath)
|
||||
} catch (err) {
|
||||
// console.error('Error trying to delete file: ', err)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
const assert = require('chai').assert
|
||||
const { Wlogger } = require('../../../src/adapters/wlogger')
|
||||
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
import { Wlogger } from '../../../src/adapters/wlogger.js'
|
||||
import sinon from 'sinon'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
// Public npm libraries
|
||||
// const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import sinon from 'sinon'
|
||||
|
||||
const Controllers = require('../../../src/controllers')
|
||||
import Controllers from '../../../src/controllers/index.js'
|
||||
|
||||
describe('#Controllers', () => {
|
||||
let uut
|
||||
@@ -28,6 +28,9 @@ describe('#Controllers', () => {
|
||||
attachRPCRouter: () => {}
|
||||
}
|
||||
|
||||
// Mock the timer controllers
|
||||
sandbox.stub(uut.timerControllers, 'startTimers').returns()
|
||||
|
||||
const app = {
|
||||
use: () => {}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,21 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
const sinon = require('sinon')
|
||||
const { v4: uid } = require('uuid')
|
||||
import { assert } from 'chai'
|
||||
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
import sinon from 'sinon'
|
||||
import { v4 as uid } from 'uuid'
|
||||
|
||||
// Local libraries.
|
||||
import JSONRPC from '../../../../src/controllers/json-rpc/index.js'
|
||||
|
||||
import adapters from '../../mocks/adapters/index.js'
|
||||
import UseCasesMock from '../../mocks/use-cases/index.js'
|
||||
|
||||
// Set the environment variable to signal this is a test.
|
||||
process.env.SVC_ENV = 'test'
|
||||
|
||||
// Local libraries.
|
||||
const JSONRPC = require('../../../../src/controllers/json-rpc')
|
||||
const adapters = require('../../mocks/adapters')
|
||||
const UseCasesMock = require('../../mocks/use-cases')
|
||||
|
||||
describe('#JSON RPC', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -5,18 +5,20 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
const sinon = require('sinon')
|
||||
const assert = require('chai').assert
|
||||
const { v4: uid } = require('uuid')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
import sinon from 'sinon'
|
||||
import { assert } from 'chai'
|
||||
import { v4 as uid } from 'uuid'
|
||||
|
||||
// Local libraries
|
||||
import Validators from '../../../../src/controllers/json-rpc/validators.js'
|
||||
|
||||
import adapters from '../../mocks/adapters/index.js'
|
||||
|
||||
// Set the environment variable to signal this is a test.
|
||||
process.env.SVC_ENV = 'test'
|
||||
|
||||
// Local libraries
|
||||
const Validators = require('../../../../src/controllers/json-rpc/validators')
|
||||
const adapters = require('../../mocks/adapters')
|
||||
|
||||
describe('#validators', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const sinon = require('sinon')
|
||||
const assert = require('chai').assert
|
||||
import sinon from 'sinon'
|
||||
|
||||
import { assert } from 'chai'
|
||||
|
||||
// Local libraries
|
||||
import RateLimit from '../../../../src/controllers/json-rpc/rate-limit.js'
|
||||
|
||||
// Set the environment variable to signal this is a test.
|
||||
process.env.SVC_ENV = 'test'
|
||||
|
||||
// Local libraries
|
||||
const RateLimit = require('../../../../src/controllers/json-rpc/rate-limit')
|
||||
|
||||
describe('#rate-limit', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const sinon = require('sinon')
|
||||
const assert = require('chai').assert
|
||||
import sinon from 'sinon'
|
||||
|
||||
import { assert } from 'chai'
|
||||
|
||||
// Local libraries
|
||||
const AboutRPC = require('../../../../src/controllers/json-rpc/about')
|
||||
import AboutRPC from '../../../../src/controllers/json-rpc/about/index.js'
|
||||
|
||||
describe('#AboutRPC', () => {
|
||||
let uut
|
||||
|
||||
@@ -3,20 +3,22 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
const sinon = require('sinon')
|
||||
const assert = require('chai').assert
|
||||
const { v4: uid } = require('uuid')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
import sinon from 'sinon'
|
||||
import { assert } from 'chai'
|
||||
import { v4 as uid } from 'uuid'
|
||||
|
||||
// Local libraries
|
||||
import AuthRPC from '../../../../src/controllers/json-rpc/auth/index.js'
|
||||
|
||||
import RateLimit from '../../../../src/controllers/json-rpc/rate-limit.js'
|
||||
import adapters from '../../mocks/adapters/index.js'
|
||||
import UseCasesMock from '../../mocks/use-cases/index.js'
|
||||
|
||||
// Set the environment variable to signal this is a test.
|
||||
process.env.SVC_ENV = 'test'
|
||||
|
||||
// Local libraries
|
||||
const AuthRPC = require('../../../../src/controllers/json-rpc/auth')
|
||||
const RateLimit = require('../../../../src/controllers/json-rpc/rate-limit')
|
||||
const adapters = require('../../mocks/adapters')
|
||||
const UseCasesMock = require('../../mocks/use-cases')
|
||||
|
||||
describe('#AuthRPC', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -3,19 +3,21 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
const sinon = require('sinon')
|
||||
const assert = require('chai').assert
|
||||
const { v4: uid } = require('uuid')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
import sinon from 'sinon'
|
||||
import { assert } from 'chai'
|
||||
import { v4 as uid } from 'uuid'
|
||||
|
||||
// Local libraries
|
||||
import UserRPC from '../../../../src/controllers/json-rpc/users/index.js'
|
||||
|
||||
import adapters from '../../mocks/adapters/index.js'
|
||||
import UseCasesMock from '../../mocks/use-cases/index.js'
|
||||
|
||||
// Set the environment variable to signal this is a test.
|
||||
process.env.SVC_ENV = 'test'
|
||||
|
||||
// Local libraries
|
||||
const UserRPC = require('../../../../src/controllers/json-rpc/users')
|
||||
const adapters = require('../../mocks/adapters')
|
||||
const UseCasesMock = require('../../mocks/use-cases')
|
||||
|
||||
describe('#UserRPC', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
@@ -333,7 +335,7 @@ describe('#UserRPC', () => {
|
||||
assert.equal(result.endpoint, 'updateUser')
|
||||
assert.equal(result.success, false)
|
||||
assert.equal(result.status, 422)
|
||||
assert.include(result.message, 'Cannot read property')
|
||||
assert.include(result.message, 'Cannot read')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -375,7 +377,7 @@ describe('#UserRPC', () => {
|
||||
assert.equal(result.endpoint, 'getUser')
|
||||
assert.equal(result.success, false)
|
||||
assert.equal(result.status, 422)
|
||||
assert.include(result.message, 'Cannot read property')
|
||||
assert.include(result.message, 'Cannot read')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -3,21 +3,25 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local support libraries
|
||||
const adapters = require('../../../mocks/adapters')
|
||||
const UseCasesMock = require('../../../mocks/use-cases')
|
||||
import adapters from '../../../mocks/adapters/index.js'
|
||||
|
||||
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||
|
||||
// const app = require('../../../mocks/app-mock')
|
||||
|
||||
const AuthRESTController = require('../../../../../src/controllers/rest-api/auth/controller')
|
||||
import AuthRESTController from '../../../../../src/controllers/rest-api/auth/controller.js'
|
||||
|
||||
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
let ctx
|
||||
|
||||
const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('#Auth-REST-Router', () => {
|
||||
// const testUser = {}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user