mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer.git
synced 2026-09-22 09:12:03 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
433931111c | ||
|
|
3edc945487 | ||
|
|
c66194ceeb | ||
|
|
e35e130816 | ||
|
|
bba4bd5931 | ||
|
|
48d13ed6bc | ||
|
|
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 | ||
|
|
bb44b75973 | ||
|
|
698cca555b | ||
|
|
76b3740e74 | ||
|
|
2622038df5 | ||
|
|
f1fbe34b58 | ||
|
|
d232b6404d | ||
|
|
9f92cef122 | ||
|
|
18e98f9a3b | ||
|
|
938cc755f1 | ||
|
|
6db6db8187 | ||
|
|
35af9c08fa | ||
|
|
2caf18977e | ||
|
|
8857242570 | ||
|
|
4f0e59ea61 | ||
|
|
a1a910c3b5 | ||
|
|
1bf8f756e5 | ||
|
|
1e513cc133 | ||
|
|
2f48f1bb61 | ||
|
|
70c81af88e | ||
|
|
cdbb8b18b6 | ||
|
|
b3c3bb4ae2 | ||
|
|
4d291f7310 | ||
|
|
8641a4100c | ||
|
|
3a30dd6b03 | ||
|
|
762ed8dd43 | ||
|
|
17914f2937 | ||
|
|
aa5b921dee | ||
|
|
717744094e | ||
|
|
123754f6ea | ||
|
|
766826d86e | ||
|
|
74c6bc5737 | ||
|
|
bac6c8b1e9 | ||
|
|
9e7d955bc4 | ||
|
|
fc3a2fe57f | ||
|
|
14150f81e9 | ||
|
|
52ca71ec16 | ||
|
|
ab5aa94353 | ||
|
|
453918a606 | ||
|
|
e73afdcd6c | ||
|
|
43759b7a3e | ||
|
|
d25545ae83 |
@@ -7,6 +7,7 @@
|
||||
#logs
|
||||
logs/*.json
|
||||
*.log
|
||||
*.log.*
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
@@ -65,5 +66,7 @@ system-user-*.json
|
||||
orbitdb
|
||||
ipfsdata
|
||||
.ipfsdata
|
||||
run-dev.sh
|
||||
data/
|
||||
|
||||
!README.md
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2021 Permissionless Software Foundation
|
||||
Copyright (c) 2022 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:
|
||||
|
||||
|
||||
@@ -8,13 +8,17 @@ When started, this web server starts an [IPFS](https://ipfs.io) node and connect
|
||||
|
||||
## Requirements
|
||||
|
||||
- node **^14.17.0**
|
||||
- npm **^7.13.0**
|
||||
- node **^14.18.2**
|
||||
- npm **^8.3.0**
|
||||
- Docker **^20.10.8**
|
||||
- Docker Compose **^1.27.4**
|
||||
|
||||
## Installation
|
||||
|
||||
### Development Environment
|
||||
|
||||
**Note:** This software now uses an external go-ipfs IPFS node. The instructions below have not been updated to reflect this.
|
||||
|
||||
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.
|
||||
|
||||
- [Video: Installing ipfs-service-provider](https://youtu.be/Z0NsboIVN44)
|
||||
@@ -31,17 +35,25 @@ npm install
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
You can bring the containers back up with `docker-compose up -d`.
|
||||
|
||||
### Operation Notes
|
||||
|
||||
- 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.
|
||||
|
||||
- The PSF network operates as a private network. It does not connect or interact with the wider PSF network, relying instead on gateways to bridge the two networks, when they need to share content. This improves the performance and experience for everyone in the PSF network. To join the network, you'll need to add the [swarm.key](./swarm.key) file to the IPFS data folder.
|
||||
|
||||
- [Instructions on setting up IPFS private networks.](https://github.com/ipfs/go-ipfs/blob/master/docs/experimental-features.md#private-networks)
|
||||
- For external installations, the swarm.key file will typically go in `~/.ipfs/swarm.key`
|
||||
- For production Docker containers, the key would go in `ipfs-service-provider/production/data/go-ipfs/data/swarm.key`
|
||||
|
||||
## Structure
|
||||
|
||||
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).
|
||||
@@ -60,6 +72,8 @@ API documentation is written inline and generated by [apidoc](http://apidocjs.co
|
||||
|
||||
Visit `http://localhost:5000/docs/` 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)
|
||||
@@ -83,5 +97,3 @@ Snapshots pinned to IPFS will be listed here.
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE.md)
|
||||
|
||||
test
|
||||
|
||||
+49
-26
@@ -24,31 +24,37 @@ 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')
|
||||
const Controllers = require('../src/controllers')
|
||||
|
||||
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
|
||||
|
||||
@@ -68,44 +74,61 @@ class Server {
|
||||
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
|
||||
|
||||
Vendored
+26
-4
@@ -15,7 +15,7 @@ const ipfsCoordName = process.env.COORD_NAME
|
||||
|
||||
module.exports = {
|
||||
// Configure TCP port.
|
||||
port: process.env.PORT || 5001,
|
||||
port: process.env.PORT || 5005,
|
||||
|
||||
// Password for HTML UI that displays logs.
|
||||
logPass: 'test',
|
||||
@@ -72,11 +72,33 @@ 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 : '',
|
||||
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
|
||||
|
||||
// Preferred provider running ipfs-bch-wallet-service for BCH wallet.
|
||||
preferredProvider: process.env.PREFERRED_PROVIDER
|
||||
? process.env.PREFERRED_PROVIDER
|
||||
: '',
|
||||
// : 'QmdBGB8S6sEr19gaAxJYjhRbz5ZmMiH5a1JyyMmaxcRKnY'
|
||||
// : 'QmUTx6KqYKVZbKpKxR7vGDUgZFYVvVVyEWDeCYq4GwBCff'
|
||||
// Used for development
|
||||
|
||||
// Preferred P2WDB provider
|
||||
preferredP2wdbProvider: process.env.PREFERRED_P2WDB_PROVIDER
|
||||
? process.env.PREFERRED_P2WDB_PROVIDER
|
||||
: '',
|
||||
|
||||
// Settings for production, using external go-ipfs node.
|
||||
isProduction: process.env.CONSUMER_ENV === 'production' ? 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,
|
||||
|
||||
chatPubSubChan: 'psf-ipfs-chat-001'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -7,6 +7,6 @@
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/ipfs-service-dev',
|
||||
database: 'mongodb://localhost:27017/ipfs-bch-consumer-dev',
|
||||
env: 'dev'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -13,6 +13,6 @@ module.exports = {
|
||||
// database: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
|
||||
database: process.env.DBURL
|
||||
? process.env.DBURL
|
||||
: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
|
||||
: 'mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod',
|
||||
env: 'prod'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -7,6 +7,6 @@
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/ipfs-service-test',
|
||||
database: 'mongodb://localhost:27017/ipfs-bch-consumer-test',
|
||||
env: 'test'
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
const common = require('./env/common')
|
||||
|
||||
const env = process.env.SVC_ENV || 'development'
|
||||
const env = process.env.CONSUMER_ENV || 'development'
|
||||
const config = require(`./env/${env}`)
|
||||
|
||||
module.exports = Object.assign({}, common, config)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Developer Documentation
|
||||
|
||||
This directory contains documentation for developers.
|
||||
|
||||
## Theory of Operation
|
||||
|
||||
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).
|
||||
Generated
+14050
-21741
File diff suppressed because it is too large
Load Diff
+18
-15
@@ -6,14 +6,14 @@
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "npm run test:all",
|
||||
"test:all": "export SVC_ENV=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:unit": "export SVC_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
|
||||
"test:temp": "export SVC_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
|
||||
"test:all": "export CONSUMER_ENV=test && nyc --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 SVC_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
"coverage:report": "export CONSUMER_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
},
|
||||
"author": "Chris Troutner <chris.troutner@gmail.com>",
|
||||
"license": "MIT",
|
||||
@@ -23,12 +23,13 @@
|
||||
},
|
||||
"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",
|
||||
"@psf/bch-js": "6.5.1",
|
||||
"axios": "0.27.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs-coord": "6.7.4",
|
||||
"ipfs": "0.62.3",
|
||||
"ipfs-coord": "8.0.5",
|
||||
"ipfs-http-client": "55.0.0",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-bch-lib": "1.3.0",
|
||||
@@ -42,18 +43,19 @@
|
||||
"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",
|
||||
"line-reader": "0.4.0",
|
||||
"mongoose": "5.11.15",
|
||||
"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",
|
||||
"winston": "3.3.3",
|
||||
"winston-daily-rotate-file": "4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "0.26.0",
|
||||
"apidoc": "0.51.1",
|
||||
"chai": "4.3.0",
|
||||
"coveralls": "3.1.0",
|
||||
"eslint": "7.19.0",
|
||||
@@ -63,9 +65,10 @@
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"eslint-plugin-standard": "4.0.0",
|
||||
"husky": "4.3.8",
|
||||
"mocha": "8.2.1",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"mocha": "10.0.0",
|
||||
"nyc": "15.1.0",
|
||||
"semantic-release": "17.4.4",
|
||||
"semantic-release": "19.0.3",
|
||||
"sinon": "9.2.4",
|
||||
"standard": "16.0.3",
|
||||
"uuid": "8.3.2"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
COMPOSE_PROJECT_NAME=bch-wallet-consumer
|
||||
@@ -12,7 +12,7 @@ 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 curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
|
||||
@@ -33,8 +33,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 +46,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 +66,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,40 @@ services:
|
||||
command: mongod --logpath=/dev/null # -- quiet
|
||||
restart: always
|
||||
|
||||
ipfs-service:
|
||||
build: .
|
||||
container_name: ipfs-service
|
||||
ipfs-bch-consumer:
|
||||
image: ipfs/go-ipfs:v0.14.0
|
||||
container_name: ipfs-bch-consumer
|
||||
environment:
|
||||
MY_ENV_VAR: 'placeholder'
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 2000mb
|
||||
ports:
|
||||
- 4001:4001
|
||||
- 4003:4003
|
||||
- 172.17.0.1:5001:5001
|
||||
- 172.17.0.1:8080:8080
|
||||
volumes:
|
||||
- ../data/go-ipfs/data:/data/ipfs
|
||||
# - ../../swarm.key:/data/ipfs/swarm.key
|
||||
# https://docs.docker.com/compose/compose-file/compose-file-v3/#command
|
||||
# https://github.com/ipfs/go-ipfs/blob/91c52657166bcf86f2476926e4fe56694dc26562/Dockerfile#L115
|
||||
command:
|
||||
[
|
||||
'daemon',
|
||||
'--migrate=true',
|
||||
'--agent-version-suffix=docker',
|
||||
'--enable-pubsub-experiment'
|
||||
]
|
||||
restart: always
|
||||
|
||||
bch-consumer:
|
||||
#build: .
|
||||
image: christroutner/ipfs-bch-wallet-consumer:v1.8.4
|
||||
container_name: bch-consumer
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
@@ -23,11 +54,11 @@ services:
|
||||
max-file: '10'
|
||||
mem_limit: 500mb
|
||||
links:
|
||||
- mongo-ipfs-service
|
||||
- mongo-bch-consumer
|
||||
- ipfs-bch-consumer
|
||||
ports:
|
||||
- '5001:5001' # <host port>:<container port>
|
||||
- '5268:5268' # IPFS TCP
|
||||
- '5269:5269' # IPFS WS
|
||||
- '5010:5010' # <host port>:<container port>
|
||||
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,16 @@ 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 CONSUMER_ENV=production
|
||||
export IPFS_HOST=172.17.0.1
|
||||
export IPFS_API_PORT=5001
|
||||
export IPFS_TCP_PORT=4001
|
||||
export IPFS_WS_PORT=4003
|
||||
|
||||
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
+33
@@ -0,0 +1,33 @@
|
||||
#!/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=1
|
||||
|
||||
# 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
|
||||
|
||||
npm start
|
||||
@@ -0,0 +1,573 @@
|
||||
/*
|
||||
This library contains business-logic for dealing with BCH wallet service
|
||||
providers. Most of these functions are called by the /bch REST API endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const { v4: uid } = require('uuid')
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
|
||||
// Local libraries
|
||||
const { wlogger } = require('../wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
class BchAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('BCH localConfig: ', localConfig)
|
||||
this.ipfs = localConfig.ipfs
|
||||
if (!this.ipfs) {
|
||||
throw new Error(
|
||||
'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.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Connect the RPC handler when the event fires with new data.
|
||||
// this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.uid = uid
|
||||
this.jsonrpc = jsonrpc
|
||||
|
||||
// A queue for holding RPC data that has arrived.
|
||||
this.rpcDataQueue = []
|
||||
|
||||
_this = this // Global handle on instance of this Class.
|
||||
}
|
||||
|
||||
// This handler is triggered when RPC data comes in over IPFS.
|
||||
// Handle RPC input, and add the response to the RPC queue.
|
||||
// Once in the queue, it will get processed by waitForRPCResponse()
|
||||
rpcHandler (data) {
|
||||
try {
|
||||
// Convert string input into an object.
|
||||
// const jsonData = JSON.parse(data)
|
||||
|
||||
console.log(`JSON RPC response for ID ${data.payload.id} received.`)
|
||||
|
||||
_this.rpcDataQueue.push(data)
|
||||
} catch (err) {
|
||||
console.error('Error in rest-api.js/rpcHandler(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
|
||||
// Get the status of BCH wallet services this node can talk to. Returns an
|
||||
// array of BCH wallet service providers.
|
||||
async getStatus () {
|
||||
try {
|
||||
const peerData = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
// const status = {
|
||||
// state: this.ipfs.ipfsCoordAdapter.state
|
||||
// }
|
||||
|
||||
// Add names to the IPFS IDs for each provider.
|
||||
const initialServiceProviders =
|
||||
this.ipfs.ipfsCoordAdapter.state.serviceProviders
|
||||
const serviceProviders = []
|
||||
for (let i = 0; i < initialServiceProviders.length; i++) {
|
||||
const thisProvider = initialServiceProviders[i]
|
||||
|
||||
const providerData = peerData.filter((x) => x.from === thisProvider)
|
||||
|
||||
if (providerData.length) {
|
||||
const provObj = {
|
||||
ipfsId: thisProvider,
|
||||
name: providerData[0].data.jsonLd.name
|
||||
}
|
||||
|
||||
serviceProviders.push(provObj)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// `serviceProviders: ${JSON.stringify(serviceProviders, null, 2)}`
|
||||
// )
|
||||
|
||||
const outObj = {
|
||||
serviceProviders,
|
||||
selectedProvider:
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
}
|
||||
// console.log('outObj: ', outObj)
|
||||
|
||||
return outObj
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Choose the BCH wallet service to use.
|
||||
async selectProvider (providerId) {
|
||||
try {
|
||||
this.ipfs.ipfsCoordAdapter.config.preferredProvider = providerId
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the BCH balance for an array of addresses.
|
||||
async getBalances (addrs) {
|
||||
try {
|
||||
// console.log('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.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'balance',
|
||||
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)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getBalances()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getUtxos (addr) {
|
||||
try {
|
||||
// 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: 'utxos',
|
||||
address: addr
|
||||
}
|
||||
|
||||
// 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(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getUtxos()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async broadcast (hex) {
|
||||
try {
|
||||
// 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: 'broadcast',
|
||||
hex
|
||||
}
|
||||
|
||||
// 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('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/broadcast()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the transaction history for an address.
|
||||
async getTransactions (address, sortOrder = 'DESCENDING', page = 0) {
|
||||
try {
|
||||
console.log('Executing getTransaction() bch adapter')
|
||||
|
||||
// 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: 'txHistory',
|
||||
address: address,
|
||||
sortOrder,
|
||||
page
|
||||
}
|
||||
|
||||
// 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('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/transactions()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get details on an array of TXIDs.
|
||||
async getTransaction (txids) {
|
||||
try {
|
||||
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 =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'txData',
|
||||
txids
|
||||
}
|
||||
|
||||
// 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(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/getTransaction()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getPubKey (address) {
|
||||
try {
|
||||
// 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: 'pubkey',
|
||||
address
|
||||
}
|
||||
|
||||
// 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('getPubKey() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/getPubKey()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async utxoIsValid (utxo) {
|
||||
try {
|
||||
// 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 {
|
||||
// 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) {
|
||||
try {
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Initialize variables for tracking the return data.
|
||||
let dataFound = false
|
||||
let cnt = 0
|
||||
|
||||
// Default return value, if the remote computer does not respond in time.
|
||||
let data = {
|
||||
success: false,
|
||||
message: 'request timed out',
|
||||
data: ''
|
||||
}
|
||||
|
||||
// Loop that waits for a response from the service provider.
|
||||
do {
|
||||
// console.log(`this.rpcDataQueue.length: ${this.rpcDataQueue.length}`)
|
||||
for (let i = 0; i < this.rpcDataQueue.length; i++) {
|
||||
const rawData = this.rpcDataQueue[i]
|
||||
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
|
||||
|
||||
if (rawData.payload.id === rpcId) {
|
||||
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
|
||||
this.rpcDataQueue.splice(i, 1)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait between loops.
|
||||
// await this.sleep(1000)
|
||||
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}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error('Error in waitForRPCResponse()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BchAdapter
|
||||
@@ -52,12 +52,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()
|
||||
|
||||
|
||||
+22
-2
@@ -16,13 +16,24 @@ 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')
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
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.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.ipfs = new IPFSAdapter()
|
||||
this.ipfs = new IPFSAdapter(localConfig)
|
||||
localConfig.ipfs = this.ipfs
|
||||
this.localdb = new LocalDB()
|
||||
this.logapi = new LogsAPI()
|
||||
this.passport = new Passport()
|
||||
@@ -30,6 +41,8 @@ class Adapters {
|
||||
this.jsonFiles = new JSONFiles()
|
||||
this.bchjs = new BCHJS()
|
||||
this.config = config
|
||||
this.bch = new BCH(localConfig)
|
||||
this.p2wdb = new P2WDB(localConfig)
|
||||
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
this.fullStackJwt = new FullStackJWT(config)
|
||||
@@ -46,7 +59,14 @@ class Adapters {
|
||||
}
|
||||
|
||||
// Start the IPFS node.
|
||||
await this.ipfs.start()
|
||||
// Do not start these adapters if this is an e2e test.
|
||||
if (this.config.env !== 'test') {
|
||||
await this.ipfs.start()
|
||||
}
|
||||
|
||||
console.log('Async Adapters have been started.')
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/index.js/start()')
|
||||
throw err
|
||||
|
||||
+141
-1
@@ -2,15 +2,26 @@
|
||||
top-level IPFS library that combines the individual IPFS-based libraries.
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
const IpfsAdapter = require('./ipfs')
|
||||
const IpfsCoordAdapter = require('./ipfs-coord')
|
||||
const config = require('../../../config')
|
||||
|
||||
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.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.ipfsAdapter = new IpfsAdapter()
|
||||
this.IpfsCoordAdapter = IpfsCoordAdapter
|
||||
this.process = process
|
||||
this.config = config
|
||||
|
||||
this.ipfsCoordAdapter = {} // placeholder
|
||||
|
||||
@@ -31,11 +42,16 @@ class IPFS {
|
||||
|
||||
// Start ipfs-coord
|
||||
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
|
||||
ipfs: this.ipfs
|
||||
ipfs: this.ipfs,
|
||||
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()')
|
||||
@@ -49,6 +65,130 @@ class IPFS {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the status of this IPFS node.
|
||||
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,
|
||||
bchAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.bchAddr,
|
||||
slpAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.slpAddr,
|
||||
pubKey: this.ipfsCoordAdapter.ipfsCoord.thisNode.pubKey,
|
||||
peers: this.ipfsCoordAdapter.ipfsCoord.thisNode.peerList.length,
|
||||
relays: this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData.length
|
||||
}
|
||||
|
||||
return statusObj
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs-coord.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get details on the other peers this node is connected to.
|
||||
async getPeers (showAll) {
|
||||
try {
|
||||
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
let ipfsPeers =
|
||||
await this.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.getPeers()
|
||||
// console.log('ipfsPeers: ', ipfsPeers)
|
||||
|
||||
ipfsPeers = this._removeDuplicatePeers(ipfsPeers)
|
||||
// console.log('filtered ipfsPeers: ', ipfsPeers)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Get the ipfs-coord peer data for this peer.
|
||||
let thisPeerData = peerData.filter((x) =>
|
||||
x.from.includes(thisPeer.peer)
|
||||
)
|
||||
thisPeerData = thisPeerData[0]
|
||||
|
||||
// Skip if peerData for this IPFS peer could not be found.
|
||||
if (!thisPeerData) continue
|
||||
|
||||
try {
|
||||
// console.log('thisPeerData: ', thisPeerData)
|
||||
|
||||
// Add data to the IPFS peer data.
|
||||
thisPeer.name = thisPeerData.data.jsonLd.name
|
||||
thisPeer.protocol = thisPeerData.data.jsonLd.protocol
|
||||
thisPeer.version = thisPeerData.data.jsonLd.version
|
||||
|
||||
if (showAll) {
|
||||
// Add all the peer data.
|
||||
thisPeer.peerData = thisPeerData
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`Error trying to hydrate peer ${thisPeer.peer}: ${err.message}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return ipfsPeers
|
||||
} catch (err) {
|
||||
console.error('Error in getPeers(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||
getRelays () {
|
||||
try {
|
||||
const relayData = this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData
|
||||
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`relayData: ${JSON.stringify(relayData, null, 2)}`)
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
for (let i = 0; i < relayData.length; i++) {
|
||||
const thisRelay = relayData[i]
|
||||
|
||||
// Find the peer that corresponds to this relay.
|
||||
const thisPeer = peerData.filter((x) =>
|
||||
x.from.includes(thisRelay.ipfsId)
|
||||
)
|
||||
// console.log('thisPeer: ', thisPeer)
|
||||
|
||||
// If the peer couldn't be found, skip.
|
||||
if (!thisPeer.length) {
|
||||
thisRelay.name = ''
|
||||
continue
|
||||
}
|
||||
|
||||
thisRelay.name = thisPeer[0].data.jsonLd.name
|
||||
}
|
||||
|
||||
return relayData
|
||||
} 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
|
||||
|
||||
@@ -8,11 +8,18 @@
|
||||
const IpfsCoord = require('ipfs-coord')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const publicIp = require('public-ip')
|
||||
const semver = require('semver')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
// const JSONRPC = require('../../controllers/json-rpc/')
|
||||
|
||||
// The minimum version of ipfs-bch-wallet-service that this wallet can work with.
|
||||
const MIN_BCH_WALLET_VERSION = '1.11.11'
|
||||
const WALLET_PROTOCOL = 'bch-wallet'
|
||||
const MIN_P2WDB_VERSION = '1.4.0'
|
||||
const P2WDB_PROTOCOL = 'p2wdb'
|
||||
|
||||
let _this
|
||||
|
||||
class IpfsCoordAdapter {
|
||||
@@ -24,6 +31,12 @@ 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.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.IpfsCoord = IpfsCoord
|
||||
@@ -31,10 +44,26 @@ class IpfsCoordAdapter {
|
||||
this.bchjs = new BCHJS()
|
||||
this.config = config
|
||||
this.publicIp = publicIp
|
||||
this.semver = semver
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
// State object. TODO: Make this more robust.
|
||||
this.state = {
|
||||
serviceProviders: [],
|
||||
selectedServiceProvider: '',
|
||||
p2wdbProviders: [],
|
||||
selectedP2wdbProvider: ''
|
||||
}
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
@@ -49,6 +78,7 @@ class IpfsCoordAdapter {
|
||||
|
||||
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
|
||||
@@ -57,18 +87,25 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
this.ipfsCoord = new this.IpfsCoord({
|
||||
const ipfsCoordOptions = {
|
||||
ipfs: this.ipfs,
|
||||
type: 'node.js',
|
||||
// type: 'browser',
|
||||
bchjs: this.bchjs,
|
||||
privateLog: console.log, // Default to console.log
|
||||
privateLog: this.peerInputHandler,
|
||||
isCircuitRelay: this.config.isCircuitRelay,
|
||||
circuitRelayInfo,
|
||||
apiInfo: this.config.apiInfo,
|
||||
announceJsonLd: this.config.announceJsonLd,
|
||||
debugLevel: this.config.debugLevel
|
||||
})
|
||||
}
|
||||
|
||||
// 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()
|
||||
@@ -84,12 +121,195 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
// Poll the ipfs-coord coordination channel for available P2WDB service
|
||||
// providers. This method is called periodically by a timer-interval.
|
||||
pollForP2wdbServices () {
|
||||
try {
|
||||
// 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)}`)
|
||||
|
||||
// Array of objects. Each object is the IPFS ID of the peer and contains
|
||||
// data about that peer.
|
||||
const peerData = _this.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
for (let i = 0; i < peers.length; i++) {
|
||||
const thisPeer = peers[i]
|
||||
const thisData = peerData.filter((x) => x.from === thisPeer)
|
||||
const thisPeerData = thisData[0]
|
||||
|
||||
// Create a 'fingerprint' that defines the wallet service.
|
||||
const protocol = thisPeerData.data.jsonLd.protocol
|
||||
const version = thisPeerData.data.jsonLd.version
|
||||
// console.log(
|
||||
// `debug: peer ${thisPeer} uses protocol: ${protocol} v${version}`
|
||||
// )
|
||||
|
||||
let versionMatches = false
|
||||
if (version) {
|
||||
versionMatches = _this.semver.gt(version, MIN_P2WDB_VERSION)
|
||||
}
|
||||
|
||||
// p2wdbProviders: [],
|
||||
// selectedP2wdbProvider: ''
|
||||
|
||||
// Ignore any peers that don't match the fingerprint for a BCH wallet
|
||||
// service.
|
||||
if (protocol && protocol.includes(P2WDB_PROTOCOL) && versionMatches) {
|
||||
// console.log('Matching peer: ', thisPeerData)
|
||||
|
||||
// Temporary business logic.
|
||||
// Use the first available wallet service detected.
|
||||
if (_this.state.p2wdbProviders.length === 0) {
|
||||
_this.state.selectedP2wdbProvider = thisPeer
|
||||
|
||||
// Persist the config setting, so it can be used by other commands.
|
||||
// _this.conf.set('selectedService', thisPeer)
|
||||
console.log(`---->P2WDB service selected: ${thisPeer}`)
|
||||
}
|
||||
|
||||
// If a preferred provider is set in the config file, then connect
|
||||
// to the preferred provider when it's discovered.
|
||||
if (
|
||||
_this.config.preferredP2wdbProvider &&
|
||||
thisPeer === _this.config.preferredP2wdbProvider
|
||||
) {
|
||||
_this.state.selectedP2wdbProvider = thisPeer
|
||||
}
|
||||
|
||||
// Check if the peer has already been added to the list of providers.
|
||||
const alreadyExists = _this.state.p2wdbProviders.filter(
|
||||
(x) => x === thisPeer
|
||||
)
|
||||
|
||||
// Add the peer to the list of serviceProviders if it doesn't already
|
||||
// exist in the list.
|
||||
if (!alreadyExists.length) {
|
||||
_this.state.p2wdbProviders.push(thisPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// catch and handle known failure mode.
|
||||
if (
|
||||
err.message.includes("Cannot read property 'peerList' of undefined")
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
console.error('Error in pollForP2wdbServices()')
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
|
||||
// Poll the ipfs-coord coordination channel for available BCH wallet service
|
||||
// providers. This method is called periodically by a timer-interval.
|
||||
pollForBchServices () {
|
||||
try {
|
||||
// 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)}`)
|
||||
|
||||
// Array of objects. Each object is the IPFS ID of the peer and contains
|
||||
// data about that peer.
|
||||
const peerData = _this.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
for (let i = 0; i < peers.length; i++) {
|
||||
const thisPeer = peers[i]
|
||||
const thisData = peerData.filter((x) => x.from === thisPeer)
|
||||
const thisPeerData = thisData[0]
|
||||
|
||||
// Create a 'fingerprint' that defines the wallet service.
|
||||
const protocol = thisPeerData.data.jsonLd.protocol
|
||||
const version = thisPeerData.data.jsonLd.version
|
||||
// console.log(
|
||||
// `debug: peer ${thisPeer} uses protocol: ${protocol} v${version}`,
|
||||
// )
|
||||
|
||||
let versionMatches = false
|
||||
if (version) {
|
||||
versionMatches = _this.semver.gt(version, MIN_BCH_WALLET_VERSION)
|
||||
}
|
||||
|
||||
// Ignore any peers that don't match the fingerprint for a BCH wallet
|
||||
// service.
|
||||
if (protocol && protocol.includes(WALLET_PROTOCOL) && versionMatches) {
|
||||
// console.log('Matching peer: ', thisPeerData)
|
||||
|
||||
// Temporary business logic.
|
||||
// Use the first available wallet service detected.
|
||||
if (_this.state.serviceProviders.length === 0) {
|
||||
_this.state.selectedServiceProvider = thisPeer
|
||||
|
||||
// Persist the config setting, so it can be used by other commands.
|
||||
// _this.conf.set('selectedService', thisPeer)
|
||||
console.log(`---->BCH wallet service selected: ${thisPeer}`)
|
||||
}
|
||||
|
||||
// If a preferred provider is set in the config file, then connect
|
||||
// to the preferred provider when it's discovered.
|
||||
if (
|
||||
_this.config.preferredProvider &&
|
||||
thisPeer === _this.config.preferredProvider
|
||||
) {
|
||||
_this.state.selectedServiceProvider = thisPeer
|
||||
}
|
||||
|
||||
// Check if the peer has already been added to the list of providers.
|
||||
const alreadyExists = _this.state.serviceProviders.filter(
|
||||
(x) => x === thisPeer
|
||||
)
|
||||
|
||||
// Add the peer to the list of serviceProviders if it doesn't already
|
||||
// exist in the list.
|
||||
if (!alreadyExists.length) {
|
||||
_this.state.serviceProviders.push(thisPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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.
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
//
|
||||
// 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 () {
|
||||
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
|
||||
this.config.chatPubSubChan,
|
||||
console.log,
|
||||
this.ipfsCoord.thisNode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsCoordAdapter
|
||||
|
||||
@@ -10,27 +10,40 @@
|
||||
|
||||
// Global npm libraries
|
||||
// const IPFS = require('ipfs')
|
||||
const IPFS = require('@chris.troutner/ipfs')
|
||||
// const IPFS = require('@chris.troutner/ipfs')
|
||||
const IPFSembedded = require('ipfs')
|
||||
const IPFSexternal = require('ipfs-http-client')
|
||||
const fs = require('fs')
|
||||
const http = require('http')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
|
||||
const IPFS_DIR = './.ipfsdata/ipfs'
|
||||
|
||||
class IpfsAdapter {
|
||||
constructor (localConfig) {
|
||||
// Encapsulate dependencies
|
||||
this.IPFS = IPFS
|
||||
this.config = config
|
||||
|
||||
// Choose the IPFS constructor based on the config settings.
|
||||
this.IPFS = IPFSembedded // default
|
||||
if (this.config.isProduction) {
|
||||
this.IPFS = IPFSexternal
|
||||
}
|
||||
|
||||
// Properties of this class instance.
|
||||
this.isReady = false
|
||||
this.config = config
|
||||
|
||||
this.fs = fs
|
||||
}
|
||||
|
||||
// Start an IPFS node.
|
||||
async start () {
|
||||
try {
|
||||
// Ipfs Options
|
||||
const ipfsOptions = {
|
||||
repo: './.ipfsdata/ipfs',
|
||||
const ipfsOptionsEmbedded = {
|
||||
repo: IPFS_DIR,
|
||||
start: true,
|
||||
config: {
|
||||
relay: {
|
||||
@@ -51,10 +64,26 @@ class IpfsAdapter {
|
||||
`/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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ipfsOptionsExternal = {
|
||||
host: this.config.ipfsHost,
|
||||
port: this.config.ipfsApiPort,
|
||||
agent: http.Agent({ keepAlive: true, maxSockets: 200 })
|
||||
}
|
||||
|
||||
let ipfsOptions = ipfsOptionsEmbedded
|
||||
if (this.config.isProduction) {
|
||||
ipfsOptions = ipfsOptionsExternal
|
||||
}
|
||||
|
||||
// Create a new IPFS node.
|
||||
this.ipfs = await this.IPFS.create(ipfsOptions)
|
||||
|
||||
@@ -71,6 +100,12 @@ class IpfsAdapter {
|
||||
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()
|
||||
// }
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -80,6 +115,26 @@ 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 () {
|
||||
// try {
|
||||
// const dir = `${IPFS_DIR}/blocks`
|
||||
// console.log(`Deleting ${dir} directory...`)
|
||||
//
|
||||
// this.fs.rmdirSync(dir, { recursive: true })
|
||||
//
|
||||
// console.log(`${dir} directory is deleted!`)
|
||||
//
|
||||
// return true // Signal successful execution.
|
||||
// } catch (err) {
|
||||
// console.log('Error in rmBlocksDir()')
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = IpfsAdapter
|
||||
|
||||
@@ -135,8 +135,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')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
This library contains business-logic for dealing with P2WDB service
|
||||
providers. Most of these functions are called by the /p2wdb REST API endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const { v4: uid } = require('uuid')
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
|
||||
// Local libraries
|
||||
const { wlogger } = require('../wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
class P2wdbAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('BCH localConfig: ', localConfig)
|
||||
this.ipfs = localConfig.ipfs
|
||||
if (!this.ipfs) {
|
||||
throw new Error(
|
||||
'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.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Connect the RPC handler when the event fires with new data.
|
||||
// this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.uid = uid
|
||||
this.jsonrpc = jsonrpc
|
||||
|
||||
// A queue for holding RPC data that has arrived.
|
||||
this.rpcDataQueue = []
|
||||
|
||||
_this = this // Global handle on instance of this Class.
|
||||
}
|
||||
|
||||
// This handler is triggered when RPC data comes in over IPFS.
|
||||
// Handle RPC input, and add the response to the RPC queue.
|
||||
// Once in the queue, it will get processed by waitForRPCResponse()
|
||||
rpcHandler (data) {
|
||||
try {
|
||||
// Convert string input into an object.
|
||||
// const jsonData = JSON.parse(data)
|
||||
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
|
||||
// Get the status of BCH wallet services this node can talk to. Returns an
|
||||
// array of BCH wallet service providers.
|
||||
async getStatus () {
|
||||
try {
|
||||
const peerData = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
// const status = {
|
||||
// state: this.ipfs.ipfsCoordAdapter.state
|
||||
// }
|
||||
|
||||
// Add names to the IPFS IDs for each provider.
|
||||
const initialServiceProviders =
|
||||
this.ipfs.ipfsCoordAdapter.state.p2wdbProviders
|
||||
const serviceProviders = []
|
||||
for (let i = 0; i < initialServiceProviders.length; i++) {
|
||||
const thisProvider = initialServiceProviders[i]
|
||||
|
||||
const providerData = peerData.filter((x) => x.from === thisProvider)
|
||||
|
||||
if (providerData.length) {
|
||||
const provObj = {
|
||||
ipfsId: thisProvider,
|
||||
name: providerData[0].data.jsonLd.name
|
||||
}
|
||||
|
||||
serviceProviders.push(provObj)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// `serviceProviders: ${JSON.stringify(serviceProviders, null, 2)}`
|
||||
// )
|
||||
|
||||
const outObj = {
|
||||
serviceProviders,
|
||||
selectedProvider: this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
}
|
||||
// console.log('outObj: ', outObj)
|
||||
|
||||
return outObj
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/p2wdb.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Choose the BCH wallet service to use.
|
||||
async selectProvider (providerId) {
|
||||
try {
|
||||
this.ipfs.ipfsCoordAdapter.config.preferredP2wdbProvider = providerId
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/p2wdb/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Read an entry from the P2WDB, given an entry hash.
|
||||
async getEntryByHash (hash) {
|
||||
try {
|
||||
// Input validation.
|
||||
if (!hash || typeof hash !== 'string') {
|
||||
throw new Error('getEntry() input hash must be a string.')
|
||||
}
|
||||
|
||||
// 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
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No P2WDB Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'getByHash',
|
||||
hash
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'p2wdb', 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.error('Error in getEntryByHash()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async writeEntry (writeObj) {
|
||||
try {
|
||||
const { txid, signature, message, data } = writeObj
|
||||
|
||||
// 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
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No P2WDB Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'write',
|
||||
txid,
|
||||
signature,
|
||||
message,
|
||||
data
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'p2wdb', 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 result = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/p2wdb/writeEntry()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that resolves to data when the RPC response is recieved.
|
||||
async waitForRPCResponse (rpcId) {
|
||||
try {
|
||||
// Initialize variables for tracking the return data.
|
||||
let dataFound = false
|
||||
let cnt = 0
|
||||
|
||||
// Default return value, if the remote computer does not respond in time.
|
||||
let data = {
|
||||
success: false,
|
||||
message: 'request timed out',
|
||||
data: ''
|
||||
}
|
||||
|
||||
// Loop that waits for a response from the service provider.
|
||||
do {
|
||||
// console.log(`this.rpcDataQueue.length: ${this.rpcDataQueue.length}`)
|
||||
for (let i = 0; i < this.rpcDataQueue.length; i++) {
|
||||
const rawData = this.rpcDataQueue[i]
|
||||
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
|
||||
|
||||
if (rawData.payload.id === rpcId) {
|
||||
dataFound = true
|
||||
// console.log('data was found in the queue')
|
||||
|
||||
data = rawData.payload.result.value
|
||||
|
||||
// Remove the data from the queue
|
||||
this.rpcDataQueue.splice(i, 1)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait between loops.
|
||||
// await this.sleep(1000)
|
||||
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}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error('Error in waitForRPCResponse()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2wdbAdapter
|
||||
+31
-16
@@ -5,34 +5,38 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
// const EventEmitter = require('events')
|
||||
|
||||
// Load the Clean Architecture Adapters library
|
||||
// Local libraries
|
||||
const config = require('../../config')
|
||||
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')
|
||||
|
||||
class Controllers {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = new Adapters()
|
||||
this.useCases = new UseCases({ adapters: this.adapters })
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
|
||||
// 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.
|
||||
this.adapters = new Adapters(localConfig)
|
||||
localConfig.adapters = this.adapters
|
||||
this.useCases = new UseCases(localConfig)
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -47,6 +51,17 @@ 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()
|
||||
|
||||
// Attach the REST controllers to the Koa app.
|
||||
// this.attachRESTControllers(app)
|
||||
|
||||
this.attachRPCControllers()
|
||||
}
|
||||
|
||||
// Add the JSON RPC router to the ipfs-coord adapter.
|
||||
attachRPCControllers () {
|
||||
const jsonRpcController = new JSONRPC({
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/*
|
||||
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
|
||||
@@ -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,
|
||||
|
||||
@@ -62,6 +62,7 @@ class JSONRPC {
|
||||
// Attempt to parse the incoming data as a JSON RPC string.
|
||||
const parsedData = _this.jsonrpc.parse(str)
|
||||
// wlogger.debug(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
|
||||
// console.log(`parsedData; ${JSON.stringify(parsedData, null, 2)}`)
|
||||
|
||||
// Exit quietly if the incoming string is an invalid JSON RPC string.
|
||||
if (parsedData.type === 'invalid') {
|
||||
@@ -71,6 +72,7 @@ class JSONRPC {
|
||||
|
||||
// Check for duplicate entries with same 'id' value.
|
||||
const alreadyProcessed = _this._checkIfAlreadyProcessed(parsedData)
|
||||
// console.log(`alreadyProcessed: ${alreadyProcessed}`)
|
||||
if (alreadyProcessed) {
|
||||
return false
|
||||
} else {
|
||||
@@ -81,16 +83,21 @@ class JSONRPC {
|
||||
// up to this library. Ignore these messages.
|
||||
if (
|
||||
parsedData.type.includes('success') &&
|
||||
parsedData.payload.method === undefined
|
||||
// parsedData.payload.method === undefined
|
||||
parsedData.payload.id.includes('metrics')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Process response from REST API.
|
||||
// _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}, method: ${parsedData.payload.method}`
|
||||
`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).
|
||||
@@ -99,6 +106,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':
|
||||
@@ -109,6 +126,12 @@ class JSONRPC {
|
||||
break
|
||||
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)
|
||||
}
|
||||
|
||||
// console.log('retObj: ', retObj)
|
||||
|
||||
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
REST API Controller library for the /bch route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
// let _this
|
||||
|
||||
class BchRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /bch REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /bch REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// _this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /bch Get status on BCH infrastructure
|
||||
* @apiPermission public
|
||||
* @apiName GetBchStatus
|
||||
* @apiGroup REST BCH
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/bch
|
||||
*
|
||||
*/
|
||||
async getStatus (ctx) {
|
||||
try {
|
||||
const status = await this.adapters.bch.getStatus()
|
||||
|
||||
ctx.body = { status }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in bch/controller.js/getStatus(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/provider Provider
|
||||
* @apiPermission public
|
||||
* @apiName Provider
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Select a different BCH wallet service on the IPFS network,
|
||||
* for interacting with the BCH blockchain.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "provider": "QmXtHADckCmT6jodpAgn3TcDQWjC29gQd2fKKHDTpo8DJT" }' localhost:5001/bch/provider
|
||||
*/
|
||||
async postProvider (ctx) {
|
||||
try {
|
||||
const providerId = ctx.request.body.provider
|
||||
|
||||
await this.adapters.bch.selectProvider(providerId)
|
||||
|
||||
const body = {
|
||||
success: true
|
||||
}
|
||||
|
||||
ctx.body = body
|
||||
} catch (err) {
|
||||
wlogger.error('Error in bch/controller.js/postProvider(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/balance Balance
|
||||
* @apiName Balance
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Returns the BCH balance for an array of addresses.
|
||||
*
|
||||
* Given the 'addresses' property returns an array of objects
|
||||
* with the following properties
|
||||
*
|
||||
* - success : - Petition status
|
||||
* - balances : [] - Balance of the provided addresses
|
||||
* - balance : {} - Object with the balance types of an address
|
||||
* - confirmed : - Confirmed balance
|
||||
* - unconfirmed : - Unconfirmed Balance
|
||||
* - address : "" - Address related to the balance
|
||||
*
|
||||
* 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/balance
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "success":true,
|
||||
* "balances":[
|
||||
* {
|
||||
* "balance":{
|
||||
* "confirmed":1000,
|
||||
* "unconfirmed":0
|
||||
* },
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async balance (ctx) {
|
||||
try {
|
||||
const addrs = ctx.request.body.addresses
|
||||
|
||||
const data = await this.adapters.bch.getBalances(addrs)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/utxos UTXOs
|
||||
* @apiName UTXOs
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
|
||||
* with token information.
|
||||
*
|
||||
* Given an address, this endpoint will return an object with thre following
|
||||
* properties:
|
||||
*
|
||||
* - address: "" - the address 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
|
||||
* [
|
||||
* {
|
||||
* "address":"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 utxos (ctx) {
|
||||
try {
|
||||
const address = ctx.request.body.address
|
||||
// console.log('address: ', address)
|
||||
|
||||
const utxos = await this.adapters.bch.getUtxos(address)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
ctx.body = [utxos]
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/broadcast Broadcast
|
||||
* @apiName Broadcast
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Broadcast a transaction to the BCH network.
|
||||
* The transaction should be encoded as a hexidecimal string.
|
||||
*
|
||||
* This endpoint will return a transaction id
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000" }' localhost:5001/bch/broadcast
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* "951299775f68a599b95239bfc385423f87a33e11747c299a22ef9dcf3d1557ec"
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async broadcast (ctx) {
|
||||
try {
|
||||
const hex = ctx.request.body.hex
|
||||
|
||||
const txid = await this.adapters.bch.broadcast(hex)
|
||||
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
||||
|
||||
ctx.body = txid
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* - success : - Petition status
|
||||
* - transactions : [] - Transaction of the provided address
|
||||
* - transactions: [] - Transaction details
|
||||
* - height : - Reference to the blockchain size
|
||||
* - tx_hash: "" - Transaction hash
|
||||
* - address : "" - Address associated to the transactions
|
||||
*
|
||||
* Note: For a single address pass the 'addresses' of string type
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* 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
|
||||
* {
|
||||
* "success":true,
|
||||
* "transactions":[
|
||||
* {
|
||||
* "transactions":[
|
||||
* {
|
||||
* "height":631219,
|
||||
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f"
|
||||
* }
|
||||
* ],
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async txHistory (ctx) {
|
||||
try {
|
||||
// console.log('transactions REST API handler called.')
|
||||
// console.log(`body: ${JSON.stringify(ctx.request.body, null, 2)}`)
|
||||
|
||||
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
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/txData TX Data
|
||||
* @apiName txData
|
||||
* @apiGroup REST BCH
|
||||
* @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
|
||||
* - hash: "" - Transaction hash
|
||||
* - version: - Version number
|
||||
* - size: - Transaction size
|
||||
* - locktime: -
|
||||
* - vin: [] - Transaction inputs
|
||||
* - vout: [] - Transaction outputs
|
||||
* - hex: "" - hexadecimal script
|
||||
* - blockhash: "" - Reference to the block register
|
||||
* - confirmations : "" - Transaction confirmations
|
||||
* - time: - Execution time
|
||||
* - blocktime: - Execution time
|
||||
* - isValidSLPTx: - Determines if the transaction was under SLP
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* 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,
|
||||
* "size":272,
|
||||
* "locktime":0,
|
||||
* "vin":[
|
||||
* {
|
||||
* "txid":"4deef6de4b973706cd6e8fc8105a41a84be349e4e9717225ee5e7c63538e95e8",
|
||||
* "vout":1,
|
||||
* "scriptSig":{
|
||||
* "asm":"3045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f2496[ALL|FORKID] 0467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3",
|
||||
* "hex":"483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3"
|
||||
* },
|
||||
* "sequence":4294967295,
|
||||
* "address":"bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq",
|
||||
* "value":0.00001824
|
||||
* }
|
||||
* ],
|
||||
* "vout":[
|
||||
* {
|
||||
* "value":0,
|
||||
* "n":0,
|
||||
* "scriptPubKey":{
|
||||
* "asm":"OP_RETURN -385055325 46226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
|
||||
* "hex":"6a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
|
||||
* "type":"nulldata"
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* "value":0.0000155,
|
||||
* "n":1,
|
||||
* "scriptPubKey":{
|
||||
* "asm":"OP_DUP OP_HASH160 066ebee590278f32aedc8a4865700c49e717f1d7 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
* "hex":"76a914066ebee590278f32aedc8a4865700c49e717f1d788ac",
|
||||
* "reqSigs":1,
|
||||
* "type":"pubkeyhash",
|
||||
* "addresses":[
|
||||
* "bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq"
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ],
|
||||
* "hex":"0100000001e8958e53637c5eee257271e9e449e34ba8415a10c88f6ecd0637974bdef6ee4d010000008b483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3ffffffff020000000000000000276a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a0e060000000000001976a914066ebee590278f32aedc8a4865700c49e717f1d788ac00000000",
|
||||
* "blockhash":"0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536",
|
||||
* "confirmations":97988,
|
||||
* "time":1568338904,
|
||||
* "blocktime":1568338904,
|
||||
* "isValidSLPTx":false
|
||||
* }]
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async txData (ctx) {
|
||||
try {
|
||||
console.log(
|
||||
`txData called with this body data: ${JSON.stringify(
|
||||
ctx.request.body,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
)
|
||||
|
||||
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
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {REST} /bch/pubkey PubKey
|
||||
* @apiPermission public
|
||||
* @apiName PubKey
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get the public key from an address.
|
||||
* Given an address the endpoint will return an object with the
|
||||
* following properties
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - success: - Request status
|
||||
* - publickey: - Address public key
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/pubkey
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "success": true,
|
||||
* "status": 200,
|
||||
* "endpoint": "pubkey",
|
||||
* "pubkey": {
|
||||
* "success": true,
|
||||
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
async pubKey (ctx) {
|
||||
try {
|
||||
const address = ctx.request.body.address
|
||||
|
||||
const data = await this.adapters.bch.getPubKey(address)
|
||||
console.log(`pubkey data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
async getTokenData2 (ctx) {
|
||||
try {
|
||||
const tokenId = ctx.request.body.tokenId
|
||||
|
||||
const data = await this.adapters.bch.getTokenData2(tokenId)
|
||||
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.
|
||||
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 = BchRESTControllerLib
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
REST API library for /bch route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
|
||||
// Local libraries.
|
||||
const BchRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
|
||||
let _this
|
||||
|
||||
class BchRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating BCH REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating BCH REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.bchRESTController = new BchRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/bch'
|
||||
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('/', this.getStatus)
|
||||
this.router.post('/provider', this.postProvider)
|
||||
this.router.post('/balance', this.postBalance)
|
||||
this.router.post('/utxos', this.postUtxos)
|
||||
this.router.post('/broadcast', this.postBroadcast)
|
||||
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())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
|
||||
async getStatus (ctx, next) {
|
||||
await _this.bchRESTController.getStatus(ctx, next)
|
||||
}
|
||||
|
||||
async postProvider (ctx, next) {
|
||||
await _this.bchRESTController.postProvider(ctx, next)
|
||||
}
|
||||
|
||||
async postBalance (ctx, next) {
|
||||
await _this.bchRESTController.balance(ctx, next)
|
||||
}
|
||||
|
||||
async postUtxos (ctx, next) {
|
||||
await _this.bchRESTController.utxos(ctx, next)
|
||||
}
|
||||
|
||||
async postBroadcast (ctx, next) {
|
||||
await _this.bchRESTController.broadcast(ctx, next)
|
||||
}
|
||||
|
||||
async postTxHistory (ctx, next) {
|
||||
await _this.bchRESTController.txHistory(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
|
||||
@@ -11,6 +11,10 @@ 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')
|
||||
const PriceRESTController = require('./price')
|
||||
|
||||
class RESTControllers {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -52,6 +56,22 @@ class RESTControllers {
|
||||
// Attach the REST API Controllers associated with the /logs route
|
||||
const logsRESTController = new LogsRESTController(dependencies)
|
||||
logsRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /bch route
|
||||
const bchRESTController = new BchRESTController(dependencies)
|
||||
bchRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /ipfs route
|
||||
const ipfsRESTController = new IpfsRESTController(dependencies)
|
||||
ipfsRESTController.attach(app)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
REST API Controller library for the /ipfs route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
class IpfsRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /ipfs REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /ipfs REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /ipfs Get status on IPFS infrastructure
|
||||
* @apiPermission public
|
||||
* @apiName GetIpfsStatus
|
||||
* @apiGroup REST BCH
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs
|
||||
*
|
||||
*/
|
||||
async getStatus (ctx) {
|
||||
try {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Return information on IPFS peers this node is connected to.
|
||||
async getPeers (ctx) {
|
||||
try {
|
||||
const showAll = ctx.request.body.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)
|
||||
}
|
||||
}
|
||||
|
||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||
async getRelays (ctx) {
|
||||
try {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = IpfsRESTControllerLib
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
REST API library for the /ipfs route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
|
||||
// Local libraries.
|
||||
const IPFSRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
|
||||
// let _this
|
||||
|
||||
class BchRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating IPFS REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.ipfsRESTController = new IPFSRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/ipfs'
|
||||
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('/', this.ipfsRESTController.getStatus)
|
||||
this.router.post('/peers', this.ipfsRESTController.getPeers)
|
||||
this.router.post('/relays', this.ipfsRESTController.getRelays)
|
||||
|
||||
// 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
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
REST API Controller library for the /p2wdb route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
// let _this
|
||||
|
||||
class P2wdbRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// _this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /p2wdb Get status on P2WDB infrastructure
|
||||
* @apiPermission public
|
||||
* @apiName GetP2wdbStatus
|
||||
* @apiGroup REST BCH
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/p2wdb
|
||||
*
|
||||
*/
|
||||
async getStatus (ctx) {
|
||||
try {
|
||||
const status = await this.adapters.p2wdb.getStatus()
|
||||
|
||||
ctx.body = { status }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in bch/controller.js/getStatus(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /p2wdb/provider Provider
|
||||
* @apiName Provider
|
||||
* @apiGroup REST P2WDB
|
||||
* @apiDescription Select a different P2WDB service on the IPFS network, for
|
||||
* interacting with the P2WDB.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "provider": "QmXtHADckCmT6jodpAgn3TcDQWjC29gQd2fKKHDTpo8DJT" }' localhost:5001/p2wdb/provider
|
||||
*/
|
||||
async postProvider (ctx) {
|
||||
try {
|
||||
const providerId = ctx.request.body.provider
|
||||
|
||||
await this.adapters.p2wdb.selectProvider(providerId)
|
||||
|
||||
const body = {
|
||||
success: true
|
||||
}
|
||||
|
||||
ctx.body = body
|
||||
} catch (err) {
|
||||
wlogger.error('Error in p2wdb/controller.js/postProvider(): ', err)
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// curl -H "Content-Type: application/json" -X POST -d '{ "hash": "zdpuAzNVEHYegiazAmbnQtvQpEvJGsdXDbcH7vHFYx4NWDUqk" }' localhost:5001/p2wdb/entryFromHash
|
||||
async getEntryByHash (ctx) {
|
||||
try {
|
||||
const hash = ctx.request.body.hash
|
||||
|
||||
const data = await this.adapters.p2wdb.getEntryByHash(hash)
|
||||
|
||||
const body = {
|
||||
success: true,
|
||||
data
|
||||
}
|
||||
|
||||
ctx.body = body
|
||||
} catch (err) {
|
||||
wlogger.error('Error in p2wdb/controller.js/getEntryByHash(): ', err)
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
async writeEntry (ctx) {
|
||||
try {
|
||||
const txid = ctx.request.body.txid
|
||||
const signature = ctx.request.body.signature
|
||||
const message = ctx.request.body.message
|
||||
const data = ctx.request.body.data
|
||||
const writeObj = { txid, signature, message, data }
|
||||
|
||||
const hash = await this.adapters.p2wdb.writeEntry(writeObj)
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
hash
|
||||
}
|
||||
} catch (err) {
|
||||
wlogger.error('Error in p2wdb/controller.js/writeEntry(): ', 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 = P2wdbRESTControllerLib
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
REST API library for /p2wdb route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
|
||||
// Local libraries.
|
||||
const P2wdbRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
|
||||
let _this
|
||||
|
||||
class P2wdbRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating P2WDB REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating P2WDB REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.p2wdbRESTController = new P2wdbRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/p2wdb'
|
||||
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('/', this.getStatus)
|
||||
this.router.post('/provider', this.postProvider)
|
||||
this.router.post('/entryFromHash', this.entryFromHash)
|
||||
this.router.post('/write', this.write)
|
||||
// this.router.post('/broadcast', this.postBroadcast)
|
||||
// this.router.post('/transactions', this.postTransactions)
|
||||
// this.router.post('/transaction', this.postTransaction)
|
||||
// this.router.post('/pubkey', this.postPubKey)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
|
||||
async getStatus (ctx, next) {
|
||||
await _this.p2wdbRESTController.getStatus(ctx, next)
|
||||
}
|
||||
|
||||
async postProvider (ctx, next) {
|
||||
await _this.p2wdbRESTController.postProvider(ctx, next)
|
||||
}
|
||||
|
||||
async entryFromHash (ctx, next) {
|
||||
await _this.p2wdbRESTController.getEntryByHash(ctx, next)
|
||||
}
|
||||
|
||||
async write (ctx, next) {
|
||||
await _this.p2wdbRESTController.writeEntry(ctx, next)
|
||||
}
|
||||
|
||||
// async postBroadcast (ctx, next) {
|
||||
// await _this.bchRESTController.broadcast(ctx, next)
|
||||
// }
|
||||
//
|
||||
// async postTransactions (ctx, next) {
|
||||
// await _this.bchRESTController.transactions(ctx, next)
|
||||
// }
|
||||
//
|
||||
// async postTransaction (ctx, next) {
|
||||
// await _this.bchRESTController.transaction(ctx, next)
|
||||
// }
|
||||
//
|
||||
// async postPubKey (ctx, next) {
|
||||
// await _this.bchRESTController.pubKey(ctx, next)
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = P2wdbRouter
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
REST API Controller library for the /bch route
|
||||
*/
|
||||
|
||||
const axios = require('axios')
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
// 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
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
REST API library for /bch route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
|
||||
// Local libraries.
|
||||
const PriceRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
|
||||
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
|
||||
@@ -14,10 +14,29 @@ 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.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// 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 () {
|
||||
// try {
|
||||
console.log('Async Use Cases have been started.')
|
||||
|
||||
return true
|
||||
// } catch (err) {
|
||||
// console.error('Error in use-cases/index.js/start()')
|
||||
// // console.log(err)
|
||||
// throw err
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UseCases
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/key/swarm/psk/1.0.0/
|
||||
/base16/
|
||||
bbd935b70105b03ebd0c6a3c2d2730cd22fc0d18c490ccf689b6c8a22e1bed2a
|
||||
@@ -7,6 +7,7 @@
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const axios = require('axios').default
|
||||
// const sinon = require('sinon')
|
||||
|
||||
// Local support libraries
|
||||
const config = require('../../../config')
|
||||
|
||||
@@ -13,7 +13,8 @@ const context = {}
|
||||
|
||||
const UserController = require('../../../src/controllers/rest-api/users/controller')
|
||||
const Adapters = require('../../../src/adapters')
|
||||
const adapters = new Adapters()
|
||||
const EventEmitter = require('events')
|
||||
const adapters = new Adapters({ eventEmitter: new EventEmitter() })
|
||||
const UseCases = require('../../../src/use-cases/')
|
||||
let uut
|
||||
let sandbox
|
||||
@@ -50,7 +51,7 @@ describe('Users', () => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCases({ adapters })
|
||||
const useCases = new UseCases({ adapters, eventEmitter: new EventEmitter() })
|
||||
uut = new UserController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Manual End-to-End Tests
|
||||
Each test in this directory is used to debug live systems.
|
||||
Read the directions at the top of each test file.
|
||||
Run each test with `node <testname>.js`
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
To run this end-to-end test:
|
||||
|
||||
- Start the REST API server with `npm start`, wait for it to find a BCH wallet server.
|
||||
- Run this test app with `mocha --timeout=30000 bch-e2e.js`
|
||||
|
||||
This test will query each REST API endpoint and ensure they are working correctly.
|
||||
*/
|
||||
|
||||
const SERVER = 'http://localhost:5005'
|
||||
|
||||
const axios = require('axios')
|
||||
const assert = require('chai').assert
|
||||
|
||||
describe('#BCH E2E Tests', () => {
|
||||
console.log(
|
||||
'Ensure the REST API app has been started and has found a BCH wallet service provider, before running this test.'
|
||||
)
|
||||
|
||||
describe('#status', () => {
|
||||
it('should report at least one service provider', async () => {
|
||||
const url = `${SERVER}/bch`
|
||||
const result = await axios.get(url)
|
||||
// console.log(`data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.isAbove(result.data.status.serviceProviders.length, 0)
|
||||
assert.isOk(result.data.status.selectedProvider)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#balance', () => {
|
||||
it('should get the balance for an address', async () => {
|
||||
const body = {
|
||||
addresses: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/balance`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.property(result.data, 'balance')
|
||||
assert.property(result.data.balance, 'confirmed')
|
||||
assert.property(result.data.balance, 'unconfirmed')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxos', () => {
|
||||
it('should get UTXOs for an address', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/utxos`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data[0].address, body.address)
|
||||
assert.property(result.data[0], 'bchUtxos')
|
||||
assert.property(result.data[0], 'slpUtxos')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should attempt to broadcast a transaction', async () => {
|
||||
const body = {
|
||||
hex: '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/broadcast`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.status, 422)
|
||||
assert.equal(result.data.success, false)
|
||||
assert.equal(result.data.message, 'Missing inputs')
|
||||
assert.equal(result.data.endpoint, 'broadcast')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#pubkey', () => {
|
||||
it('should return "not found" if pubkey is not on chain', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/pubkey`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.endpoint, 'pubkey')
|
||||
assert.equal(result.data.pubkey.success, false)
|
||||
assert.equal(result.data.pubkey.publicKey, 'not found')
|
||||
})
|
||||
|
||||
it('should get a public key for an address', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qznxh6yegf7nfrsd0zeksed4jcqzvg7tzqlcqw3v97'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/pubkey`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.endpoint, 'pubkey')
|
||||
assert.equal(result.data.pubkey.success, true)
|
||||
assert.equal(
|
||||
result.data.pubkey.publicKey,
|
||||
'03ebd6d6aae05da1c5905445e3886b30f6a31b26a88de5980de236bd22380fa942'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
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,54 @@
|
||||
/*
|
||||
Unit tests for the adapters index.js library
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
// Local libraries
|
||||
const Adapters = require('../../../src/adapters')
|
||||
|
||||
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
|
||||
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')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
Unit tests for the src/lib/users.js business logic library.
|
||||
|
||||
TODO: verify that an admin can change the type of a user
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
const EventEmitter = require('events')
|
||||
const cloneDeep = require('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')
|
||||
const eventEmitter = new EventEmitter()
|
||||
const mockDataLib = require('../mocks/use-cases/rest-api-mocks')
|
||||
|
||||
describe('#bch-use-case', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let mockData
|
||||
|
||||
before(async () => {
|
||||
// Delete all previous users in the database.
|
||||
// await testUtils.deleteAllUsers()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
mockData = cloneDeep(mockDataLib)
|
||||
|
||||
const ipfs = adapters.ipfs
|
||||
uut = new BchAdapter({ ipfs, eventEmitter })
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if ipfs is not passed in', () => {
|
||||
try {
|
||||
uut = new BchAdapter()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'An instance of IPFS must be passed when instantiating the BCH Adapter library.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// 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', () => {
|
||||
it('should resolve when data is received', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs.ipfsCoordAdapter.bchjs = {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
uut.rpcDataQueue.push(mockData.rpcData)
|
||||
|
||||
const result = await uut.waitForRPCResponse(rpcId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'success')
|
||||
assert.equal(result.success, true)
|
||||
assert.property(result, 'balances')
|
||||
assert.isArray(result.balances)
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('error: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rpcHandler', () => {
|
||||
it('should add RPC data to queue', () => {
|
||||
const data = {
|
||||
payload: {
|
||||
id: '123'
|
||||
}
|
||||
}
|
||||
|
||||
uut.rpcHandler(data)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should get the status from ipfs-coord', async () => {
|
||||
const result = await uut.getStatus()
|
||||
console.log('result: ', result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getBalances', () => {
|
||||
it('should get the balance 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 = ['addr1']
|
||||
|
||||
const result = await uut.getBalances(addrs)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getBalances()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should broadcast a transaction', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const hex = 'abc123'
|
||||
|
||||
const result = await uut.broadcast(hex)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.broadcast()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTransactions', () => {
|
||||
it('should get transactions history for 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 = 'abc123'
|
||||
|
||||
const result = await uut.getTransactions(addr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getTransactions()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTransaction', () => {
|
||||
it('should get transaction details for a txid', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const txid = 'abc123'
|
||||
|
||||
const result = await uut.getTransaction(txid)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getTransaction()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getPubKey', () => {
|
||||
it('should get transaction details for a txid', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const addr = 'abc123'
|
||||
|
||||
const result = await uut.getPubKey(addr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getPubKey()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -4,23 +4,34 @@
|
||||
|
||||
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 config = require('../../../config')
|
||||
const mockDataLib = require('../mocks/adapters/ipfs-coord-mocks')
|
||||
|
||||
describe('#IPFS', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let mockData
|
||||
|
||||
beforeEach(() => {
|
||||
const ipfs = IPFSMock.create()
|
||||
uut = new IPFSCoordAdapter({ ipfs })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
mockData = cloneDeep(mockDataLib)
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
|
||||
clearInterval(uut.pollBchServiceInterval)
|
||||
clearInterval(uut.pollP2wdbServiceInterval)
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if ipfs instance is not included', () => {
|
||||
@@ -35,6 +46,20 @@ 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.'
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
@@ -61,6 +86,16 @@ describe('#IPFS', () => {
|
||||
|
||||
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', () => {
|
||||
@@ -74,8 +109,9 @@ describe('#IPFS', () => {
|
||||
}
|
||||
},
|
||||
adapters: {
|
||||
orbit: {
|
||||
privateLog: () => {}
|
||||
pubsub: {
|
||||
privateLog: () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,8 +132,68 @@ describe('#IPFS', () => {
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#pollForBchServices', () => {
|
||||
it('should find and select the wallet service', () => {
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerList: mockData.peers,
|
||||
peerData: mockData.peerData
|
||||
}
|
||||
}
|
||||
|
||||
uut.pollForBchServices()
|
||||
|
||||
// It should fine the service in the mocked data.
|
||||
assert.equal(
|
||||
uut.state.selectedServiceProvider,
|
||||
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
|
||||
)
|
||||
})
|
||||
|
||||
it('should catch and report errors', () => {
|
||||
uut.pollForBchServices()
|
||||
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
})
|
||||
})
|
||||
|
||||
// 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.')
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('#subscribeToChat', () => {
|
||||
it('should subscribe to the chat channel', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
adapters: {
|
||||
pubsub: {
|
||||
subscribeToPubsubChannel: async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await uut.subscribeToChat()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,13 +8,14 @@ const sinon = require('sinon')
|
||||
const IPFSLib = require('../../../src/adapters/ipfs')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
|
||||
const EventEmitter = require('events')
|
||||
|
||||
describe('#IPFS-adapter-index', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new IPFSLib()
|
||||
uut = new IPFSLib({ eventEmitter: new EventEmitter() })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const IPFSLib = require('../../../src/adapters/ipfs/ipfs')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const config = require('../../../config')
|
||||
|
||||
// config.isProduction = true;
|
||||
describe('#IPFS-adapter', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
@@ -18,7 +19,27 @@ describe('#IPFS-adapter', () => {
|
||||
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 () => {
|
||||
@@ -33,6 +54,18 @@ describe('#IPFS-adapter', () => {
|
||||
assert.property(result, 'config')
|
||||
})
|
||||
|
||||
it('should return a promise that resolves into an instance of IPFS in production mode.', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IPFS = IPFSMock
|
||||
uut.config.isProduction = true
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(uut.isReady, true)
|
||||
|
||||
assert.property(result, 'config')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
@@ -52,7 +85,8 @@ describe('#IPFS-adapter', () => {
|
||||
it('should stop the IPFS node', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs = {
|
||||
stop: () => {}
|
||||
stop: () => {
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.stop()
|
||||
@@ -60,4 +94,25 @@ describe('#IPFS-adapter', () => {
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
|
||||
// describe('#rmBlocksDir', () => {
|
||||
// it('should delete the /blocks directory', () => {
|
||||
// const result = uut.rmBlocksDir()
|
||||
//
|
||||
// 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'))
|
||||
//
|
||||
// uut.rmBlocksDir()
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.equal(err.message, 'test error')
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
@@ -32,19 +32,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 +200,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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -74,10 +74,17 @@ describe('#JSON RPC', () => {
|
||||
})
|
||||
|
||||
it('should return default response if routing is not possible', async () => {
|
||||
// Force desired code path
|
||||
// sandbox
|
||||
// .stub(uut.adapters.ipfs.ipfsCoordAdapter, 'peerInputHandler')
|
||||
// .returns()
|
||||
// uut.adapters.ipfs.ipfsCoordAdapter.peer
|
||||
|
||||
const id = uid()
|
||||
const json = jsonrpc.request(id, 'unknownMethod', {})
|
||||
|
||||
const str = JSON.stringify(json)
|
||||
// console.log(`str: ${str}`)
|
||||
|
||||
const result = await uut.router(str, 'peerA')
|
||||
// console.log('result: ', result)
|
||||
@@ -204,7 +211,9 @@ describe('#JSON RPC', () => {
|
||||
// Force the RPC type to be 'success', to indicate an RPC that was
|
||||
// processed internal to ipfs-coord.
|
||||
sandbox.stub(uut.jsonrpc, 'parse').returns({
|
||||
payload: {},
|
||||
payload: {
|
||||
id: 'metrics5323'
|
||||
},
|
||||
type: 'success'
|
||||
})
|
||||
|
||||
|
||||
@@ -333,7 +333,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 +375,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')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /bch endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
// Local support libraries
|
||||
const adapters = require('../../../mocks/adapters')
|
||||
const UseCasesMock = require('../../../mocks/use-cases')
|
||||
|
||||
const BchController = require('../../../../../src/controllers/rest-api/bch/controller')
|
||||
let uut
|
||||
let sandbox
|
||||
let ctx
|
||||
|
||||
const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('#BCH-REST-Controller', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new BchController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
ctx = mockContext()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new BchController()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating /bch REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new BchController({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating /bch REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#handleError', () => {
|
||||
it('should still throw error if there is no message', () => {
|
||||
try {
|
||||
const err = {
|
||||
status: 404
|
||||
}
|
||||
|
||||
uut.handleError(ctx, err)
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Not Found')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should return 422 status on arbitrary biz logic error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getStatus')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await uut.getStatus(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getStatus').resolves('bch')
|
||||
|
||||
await uut.getStatus(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
|
||||
// Assert that expected properties exist in the returned data.
|
||||
assert.equal(ctx.response.body.status, 'bch')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#balance', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getBalances')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
addresses: 'blah'
|
||||
}
|
||||
|
||||
await uut.balance(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getBalances').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
addresses: 'blah'
|
||||
}
|
||||
|
||||
await uut.balance(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxos', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getUtxos')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxos(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getUtxos').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxos(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'broadcast')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
hex: 'blah'
|
||||
}
|
||||
|
||||
await uut.broadcast(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'broadcast').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
hex: 'blah'
|
||||
}
|
||||
|
||||
await uut.broadcast(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#txHistory', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTransactions')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.txHistory(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getUtxos').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.txHistory(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#txData', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTransaction')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
txid: 'blah'
|
||||
}
|
||||
|
||||
await uut.txData(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTransactions')
|
||||
.resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
txid: 'blah'
|
||||
}
|
||||
|
||||
await uut.txData(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#pubKey', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getPubKey')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
addr: 'blah'
|
||||
}
|
||||
|
||||
await uut.pubKey(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getPubKey').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
addr: 'blah'
|
||||
}
|
||||
|
||||
await uut.pubKey(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxoIsValid', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'utxoIsValid')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
utxo: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxoIsValid(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'utxoIsValid').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
utxo: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxoIsValid(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTokenData')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
tokenId: 'blah'
|
||||
}
|
||||
|
||||
await uut.getTokenData(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getTokenData').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
tokenId: 'blah'
|
||||
}
|
||||
|
||||
await uut.getTokenData(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData2', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTokenData2')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
tokenId: 'blah'
|
||||
}
|
||||
|
||||
await uut.getTokenData2(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getTokenData2').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
tokenId: 'blah'
|
||||
}
|
||||
|
||||
await uut.getTokenData2(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Unit tests for the REST API handler for the /users endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
// Local support libraries
|
||||
const adapters = require('../../../mocks/adapters')
|
||||
const UseCasesMock = require('../../../mocks/use-cases')
|
||||
// const app = require('../../../mocks/app-mock')
|
||||
|
||||
const BchRouter = require('../../../../../src/controllers/rest-api/bch')
|
||||
let uut
|
||||
let sandbox
|
||||
// let ctx
|
||||
|
||||
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('#Users-REST-Router', () => {
|
||||
// const testUser = {}
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCasesMock()
|
||||
uut = new BchRouter({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Mock the context object.
|
||||
// ctx = mockContext()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new BchRouter()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Adapters library required when instantiating BCH REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if useCases are not passed in', () => {
|
||||
try {
|
||||
uut = new BchRouter({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of Use Cases library required when instantiating BCH REST Controller.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#attach', () => {
|
||||
it('should throw an error if app is not passed in.', () => {
|
||||
try {
|
||||
uut.attach()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass app object when attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -14,7 +14,8 @@ let ctx
|
||||
const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('Contact', () => {
|
||||
before(async () => {})
|
||||
before(async () => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new ContactController()
|
||||
@@ -36,7 +37,7 @@ describe('Contact', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ let ctx
|
||||
const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('Logapi', () => {
|
||||
before(async () => {})
|
||||
before(async () => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new LogsApiController()
|
||||
@@ -35,7 +36,7 @@ describe('Logapi', () => {
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('#Users-REST-Controller', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -91,9 +91,9 @@ describe('#Users-REST-Controller', () => {
|
||||
assert.property(ctx.response.body, 'user')
|
||||
assert.property(ctx.response.body, 'token')
|
||||
|
||||
// Used by downstream tests.
|
||||
// testUser = ctx.response.body.user
|
||||
// console.log('testUser: ', testUser)
|
||||
// Used by downstream tests.
|
||||
// testUser = ctx.response.body.user
|
||||
// console.log('testUser: ', testUser)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -181,7 +181,7 @@ describe('#Users-REST-Controller', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -223,7 +223,7 @@ describe('#Users-REST-Controller', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Unit tests for the bin/server.js file
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
// Local libraries
|
||||
const Server = require('../../../bin/server')
|
||||
|
||||
describe('#server', () => {
|
||||
let uut, sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
uut = new Server()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#startServer', () => {
|
||||
it('should start the server', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.mongoose, 'connect').resolves()
|
||||
sandbox.stub(uut.controllers, 'initAdapters').resolves()
|
||||
sandbox.stub(uut.controllers, 'initUseCases').resolves()
|
||||
sandbox.stub(uut.controllers, 'attachRESTControllers').resolves()
|
||||
sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true)
|
||||
sandbox.stub(uut.controllers, 'attachControllers').resolves()
|
||||
uut.config.env = 'dev'
|
||||
|
||||
const result = await uut.startServer()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'env')
|
||||
|
||||
// Turn off the server.
|
||||
uut.server.close()
|
||||
|
||||
// Restor config env
|
||||
uut.config.env = 'test'
|
||||
})
|
||||
|
||||
it('should exit on failure', async () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut.mongoose, 'connect').rejects(new Error('test error'))
|
||||
|
||||
// Prevent default behavior of exiting the program.
|
||||
sandbox.stub(uut, 'sleep').resolves()
|
||||
sandbox.stub(uut.process, 'exit').returns()
|
||||
|
||||
await uut.startServer()
|
||||
|
||||
// Not throwing an error is a success
|
||||
})
|
||||
})
|
||||
|
||||
describe('#sleep', () => {
|
||||
it('should execute', async () => {
|
||||
await uut.sleep(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,70 @@
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.907Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.909Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.910Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.912Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.915Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.979Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.980Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.981Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.982Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.983Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/validateEmailArray()","timestamp":"2022-06-27T17:36:59.002Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/validateEmailArray()","timestamp":"2022-06-27T17:36:59.003Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/getHtmlFromObject()","timestamp":"2022-06-27T17:36:59.005Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/getHtmlFromObject()","timestamp":"2022-06-27T17:36:59.006Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/getHtmlFromObject()","timestamp":"2022-06-27T17:36:59.008Z"}
|
||||
{"level":"info","message":"Warning: Can not send JSON RPC response. Can not determine which peer this message came from.","timestamp":"2022-06-27T17:36:59.353Z"}
|
||||
{"level":"info","message":"Rejecting invalid JSON RPC command.","timestamp":"2022-06-27T17:36:59.354Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: ea442e78-2297-466a-823b-9cbb12c8ddd9, type: request, method: unknownMethod","timestamp":"2022-06-27T17:36:59.355Z"}
|
||||
{"level":"error","message":"Error in rpc router(): test error","stack":"Error: test error\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/a10-rpc.unit.js:110:49)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate._onImmediate (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:36:59.357Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: 9ef6ad90-7fc3-4cb3-8769-4498e5512b3d, type: request, method: users","timestamp":"2022-06-27T17:36:59.358Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: bd57a1cf-7f2f-4ff2-93d4-6727eec4d778, type: request, method: auth","timestamp":"2022-06-27T17:36:59.359Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: 8fd9edc8-6a45-4042-ac06-9ace60567cbf, type: request, method: about","timestamp":"2022-06-27T17:36:59.361Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: 1b27624a-e698-4bbe-9183-b860a1a0d78d, type: request, method: unknownMethod","timestamp":"2022-06-27T17:36:59.363Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: 40fabddf-59c6-4910-a22c-63da7387f99a, type: request, method: unknownMethod","timestamp":"2022-06-27T17:36:59.365Z"}
|
||||
{"level":"error","message":"Error in authUser(): Login credential do not match","stack":"Error: Login credential do not match\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js:144:18)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:37:04.421Z"}
|
||||
{"level":"error","message":"Error in authUser(): login must be specified","stack":"Error: login must be specified\n at AuthRPC.authUser (/home/trout/work/psf/code/ipfs-service-provider/src/controllers/json-rpc/auth/index.js:78:93)\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js:165:34)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate._onImmediate (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:37:04.423Z"}
|
||||
{"level":"error","message":"Error in authUser(): password must be specified","stack":"Error: password must be specified\n at AuthRPC.authUser (/home/trout/work/psf/code/ipfs-service-provider/src/controllers/json-rpc/auth/index.js:78:284)\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js:185:34)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate._onImmediate (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:37:04.425Z"}
|
||||
{"level":"error","timestamp":"2022-06-27T17:37:04.466Z"}
|
||||
{"level":"info","message":"Running server in environment: dev","timestamp":"2022-06-27T17:37:04.487Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.495Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.495Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.496Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.497Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.497Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/getAllUsers()","timestamp":"2022-06-27T17:37:04.499Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.502Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.503Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.503Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.504Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.505Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.506Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/deleteUser()","timestamp":"2022-06-27T17:37:04.508Z"}
|
||||
{"level":"info","message":"Running server in environment: test","timestamp":"2022-06-27T17:37:04.514Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.638Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.273Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.277Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.280Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.286Z"}
|
||||
{"level":"error","timestamp":"2022-06-27T17:37:05.412Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 1","timestamp":"2022-06-27T17:37:05.441Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.448Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.452Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.457Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.463Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.467Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.473Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac0b4f08950b955d274, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.478Z"}
|
||||
{"level":"verbose","message":"It's ok. The user is an admin.","timestamp":"2022-06-27T17:37:05.478Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 1","timestamp":"2022-06-27T17:37:05.600Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.603Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac0b4f08950b955d274, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.615Z"}
|
||||
{"level":"verbose","message":"It's ok. The user is an admin.","timestamp":"2022-06-27T17:37:05.615Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.619Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.622Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.624Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.627Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d2a6, Target user: Target Id","timestamp":"2022-06-27T17:37:05.754Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac0b4f08950b955d274, Target user: 62b9eac1b4f08950b955d2a6","timestamp":"2022-06-27T17:37:05.756Z"}
|
||||
{"level":"verbose","message":"It's ok. The user is an admin.","timestamp":"2022-06-27T17:37:05.756Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:06.060Z"}
|
||||
@@ -2,21 +2,44 @@
|
||||
Mocks for the Adapter library.
|
||||
*/
|
||||
|
||||
const ipfs = {
|
||||
ipfsAdapter: {
|
||||
ipfs: {}
|
||||
},
|
||||
ipfsCoordAdapter: {
|
||||
ipfsCoord: {
|
||||
useCases: {
|
||||
peer: {
|
||||
sendPrivateMessage: () => {}
|
||||
}
|
||||
class IpfsAdapter {
|
||||
constructor () {
|
||||
this.ipfs = {
|
||||
files: {
|
||||
stat: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IpfsCoordAdapter {
|
||||
constructor () {
|
||||
|
||||
this.ipfsCoord = {
|
||||
useCases: {
|
||||
peer: {
|
||||
sendPrivateMessage: () => {}
|
||||
}
|
||||
},
|
||||
thisNode: {
|
||||
peerData: []
|
||||
}
|
||||
},
|
||||
|
||||
this.peerInputHandler = () => {}
|
||||
|
||||
this.state = {
|
||||
serviceProviders: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ipfs = {
|
||||
ipfsAdapter: new IpfsAdapter(),
|
||||
ipfsCoordAdapter: new IpfsCoordAdapter()
|
||||
}
|
||||
ipfs.ipfs = ipfs.ipfsAdapter.ipfs
|
||||
|
||||
const localdb = {
|
||||
Users: class Users {
|
||||
static findById () {}
|
||||
@@ -53,4 +76,55 @@ const localdb = {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ipfs, localdb }
|
||||
class BchUseCaseMock {
|
||||
rpcHandler () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getStatus () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getBalances () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getUtxos () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async broadcast () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getTransaction () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getTransactions () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getPubKey () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async utxoIsValid() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getTokenData() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getTokenData2() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async waitForRPCResponse () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
const bch = new BchUseCaseMock()
|
||||
|
||||
module.exports = { ipfs, localdb, bch }
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
Mock data for the ipfs-coord.unit.js unit tests.
|
||||
*/
|
||||
|
||||
const peers = [
|
||||
'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN'
|
||||
]
|
||||
|
||||
const peerData = [
|
||||
{
|
||||
from: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
channel: 'psf-ipfs-coordination-001',
|
||||
data: {
|
||||
apiName: 'ipfs-coord-announce',
|
||||
apiVersion: '1.3.2',
|
||||
apiInfo:
|
||||
'You should put an IPFS hash or web URL here to your documentation.',
|
||||
ipfsId: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
type: 'node.js',
|
||||
ipfsMultiaddrs: [
|
||||
'/ip4/127.0.0.1/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'/ip4/127.0.0.1/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'/ip4/192.168.0.2/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2',
|
||||
'/ip4/192.168.0.2/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
|
||||
],
|
||||
orbitdb:
|
||||
'/orbitdb/zdpuAntCMCcmZuFy2hq5F7kpcti3yHa34LLZHzyVqorkoSpAJ/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ221082417',
|
||||
circuitRelays: [],
|
||||
isCircuitRelay: false,
|
||||
cryptoAddresses: [
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'cashAddr',
|
||||
address: 'bitcoincash:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5gjzggc37'
|
||||
},
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'slpAddr',
|
||||
address: 'simpleledger:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5yffnac0q'
|
||||
}
|
||||
],
|
||||
encryptPubKey:
|
||||
'021db6e97650659653ba61dd493dc348a7429cdcbafd4fb73b08b1223bf5bd98df',
|
||||
jsonLd: {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'trout-bch-wallet-service-dev',
|
||||
version: '1.11.12',
|
||||
protocol: 'bch-wallet',
|
||||
description:
|
||||
'IPFS service providing BCH blockchain access needed by a wallet.',
|
||||
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'Permissionless Software Foundation',
|
||||
url: 'https://PSFoundation.cash'
|
||||
},
|
||||
identifier: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2'
|
||||
},
|
||||
updatedAt: '2021-08-24T23:13:19.306Z'
|
||||
}
|
||||
},
|
||||
{
|
||||
from: 'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
|
||||
channel: 'psf-ipfs-coordination-001',
|
||||
data: {
|
||||
apiName: 'ipfs-coord-announce',
|
||||
apiVersion: '1.3.2',
|
||||
apiInfo: 'https://ipfs-service-provider.fullstack.cash/',
|
||||
ipfsId: 'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
|
||||
type: 'node.js',
|
||||
ipfsMultiaddrs: [
|
||||
'/ip4/127.0.0.1/tcp/5668/p2p/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
|
||||
'/ip4/127.0.0.1/tcp/5669/ws/p2p/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
|
||||
'/ip4/172.17.0.4/tcp/5668/p2p/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN',
|
||||
'/ip4/172.17.0.4/tcp/5669/ws/p2p/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN'
|
||||
],
|
||||
orbitdb:
|
||||
'/orbitdb/zdpuB2H7Eqv63qWnEisR9SkW71h1GHTu32xdvza4gjBtD9AD8/QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN21072122',
|
||||
circuitRelays: [],
|
||||
isCircuitRelay: false,
|
||||
cryptoAddresses: [
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'cashAddr',
|
||||
address: 'bitcoincash:qq4pk63gngzxnnhne39n0sl7kn2ekhnxngm5fyrgyd'
|
||||
},
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'slpAddr',
|
||||
address: 'simpleledger:qq4pk63gngzxnnhne39n0sl7kn2ekhnxngh0zlkg6n'
|
||||
}
|
||||
],
|
||||
encryptPubKey:
|
||||
'037676cdeac8376a75c19f62b40aa06feebc788a6704e1a1d13dcf478d090d7205',
|
||||
jsonLd: {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'ipfs-bch-wallet-service-dsl',
|
||||
description:
|
||||
'IPFS service providing BCH blockchain access needed by a wallet.',
|
||||
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'Permissionless Software Foundation',
|
||||
url: 'https://PSFoundation.cash'
|
||||
},
|
||||
identifier: 'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN'
|
||||
},
|
||||
updatedAt: '2021-08-24T23:13:20.411Z'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
peers,
|
||||
peerData
|
||||
}
|
||||
@@ -8,6 +8,8 @@ class IPFSCoord {
|
||||
}
|
||||
|
||||
async start () {}
|
||||
|
||||
async subscribeToChat() {}
|
||||
}
|
||||
|
||||
module.exports = IPFSCoord
|
||||
|
||||
@@ -31,12 +31,33 @@ class UserUseCaseMock {
|
||||
}
|
||||
}
|
||||
|
||||
class BchUseCaseMock {
|
||||
rpcHandler() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getStatus() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getBalances() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async waitForRPCResponse() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
class UseCasesMock {
|
||||
constuctor(localConfig = {}) {
|
||||
// this.user = new UserUseCaseMock(localConfig)
|
||||
// this.user = new UserUseCaseMock()
|
||||
// this.bch = new BchUseCaseMock()
|
||||
}
|
||||
|
||||
user = new UserUseCaseMock()
|
||||
bch = new BchUseCaseMock()
|
||||
}
|
||||
|
||||
module.exports = UseCasesMock
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
Mock data for the rest-api.unit.js unit tests.
|
||||
*/
|
||||
|
||||
const rpcData = {
|
||||
payload: {
|
||||
id: '123',
|
||||
result: {
|
||||
value: {
|
||||
success: true,
|
||||
balances: [
|
||||
{
|
||||
balance: {},
|
||||
address: 'addressString'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mockRelayData = [
|
||||
{
|
||||
multiaddr:
|
||||
'/ip4/139.162.76.54/tcp/5269/ws/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
|
||||
connected: true,
|
||||
updatedAt: '2021-10-11T15:59:31.701Z',
|
||||
ipfsId: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
|
||||
isBootstrap: false,
|
||||
metrics: {
|
||||
aboutLatency: [596, 769, 1029]
|
||||
},
|
||||
latencyScore: 682
|
||||
},
|
||||
{
|
||||
multiaddr:
|
||||
'/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
connected: true,
|
||||
updatedAt: '2021-10-11T15:59:31.701Z',
|
||||
ipfsId: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
isBootstrap: false,
|
||||
metrics: {
|
||||
aboutLatency: [1080, 530, 1137]
|
||||
},
|
||||
latencyScore: 805
|
||||
},
|
||||
{
|
||||
multiaddr:
|
||||
'/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKq',
|
||||
connected: true,
|
||||
updatedAt: '2021-10-11T15:59:31.701Z',
|
||||
ipfsId: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKq',
|
||||
isBootstrap: false,
|
||||
metrics: {
|
||||
aboutLatency: [1080, 530, 1137]
|
||||
},
|
||||
latencyScore: 805
|
||||
}
|
||||
]
|
||||
|
||||
const mockPeerData = [
|
||||
{
|
||||
from: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
|
||||
channel: 'psf-ipfs-coordination-001',
|
||||
data: {
|
||||
apiName: 'ipfs-coord-announce',
|
||||
apiVersion: '1.3.2',
|
||||
apiInfo:
|
||||
'You should put an IPFS hash or web URL here to your documentation.',
|
||||
broadcastedAt: '2021-10-11T15:59:59.598Z',
|
||||
ipfsId: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
|
||||
type: 'node.js',
|
||||
ipfsMultiaddrs: [
|
||||
'/ip4/127.0.0.1/tcp/5268/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
|
||||
'/ip4/127.0.0.1/tcp/5269/ws/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
|
||||
'/ip4/139.162.76.54/tcp/5268/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
|
||||
'/ip4/139.162.76.54/tcp/5269/ws/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77'
|
||||
],
|
||||
orbitdb:
|
||||
'/orbitdb/zdpuApooyo2rWMuDkuTrEcxQghJKzmyQSpfUEvBhx45UNgLJX/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK7721101115',
|
||||
circuitRelays: [],
|
||||
isCircuitRelay: true,
|
||||
cryptoAddresses: [
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'cashAddr',
|
||||
address: 'bitcoincash:qpsaewu50nnm9gjn7e0ejmusrflfd8lrdy8szg2564'
|
||||
},
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'slpAddr',
|
||||
address: 'simpleledger:qpsaewu50nnm9gjn7e0ejmusrflfd8lrdyttfnl5yt'
|
||||
}
|
||||
],
|
||||
encryptPubKey:
|
||||
'0338122208e2842f39afabd23deadbe2acfdcbe69d127c24835e05603771f1e85d',
|
||||
jsonLd: {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'ipfs-relay-tokyo-pfs-0945772',
|
||||
version: '1.3.0',
|
||||
protocol: 'generic-service',
|
||||
description:
|
||||
'This is a generic IPFS Serivice Provider that uses JSON RPC over IPFS to communicate with it. This instance has not been customized. Source code: https://github.com/Permissionless-Software-Foundation/ipfs-service-provider',
|
||||
documentation: 'https://ipfs-service-provider.fullstack.cash/',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'Permissionless Software Foundation',
|
||||
url: 'https://PSFoundation.cash'
|
||||
},
|
||||
identifier: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77'
|
||||
},
|
||||
updatedAt: '2021-10-11T15:59:59.857Z'
|
||||
}
|
||||
},
|
||||
{
|
||||
from: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
channel: 'psf-ipfs-coordination-001',
|
||||
data: {
|
||||
apiName: 'ipfs-coord-announce',
|
||||
apiVersion: '1.3.2',
|
||||
apiInfo:
|
||||
'You should put an IPFS hash or web URL here to your documentation.',
|
||||
broadcastedAt: '2021-10-11T15:59:51.277Z',
|
||||
ipfsId: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
type: 'node.js',
|
||||
ipfsMultiaddrs: [
|
||||
'/ip4/10.124.0.2/tcp/4001/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
'/ip4/10.124.0.2/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
'/ip4/10.48.0.5/tcp/4001/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
'/ip4/10.48.0.5/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
'/ip4/127.0.0.1/tcp/4001/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
'/ip4/127.0.0.1/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
'/ip4/143.198.60.119/tcp/4001/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
'/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp'
|
||||
],
|
||||
orbitdb:
|
||||
'/orbitdb/zdpuAxU8itEdbqjDxCeF3C5pRN9HSXmtivJi7ygMhnew3nAPf/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp21101011',
|
||||
circuitRelays: [],
|
||||
isCircuitRelay: true,
|
||||
circuitRelayInfo: {
|
||||
ip4: '143.198.60.119',
|
||||
tcpPort: '4001',
|
||||
crDomain: 'relayer.fullstackcash.nl'
|
||||
},
|
||||
cryptoAddresses: [
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'cashAddr',
|
||||
address: 'bitcoincash:qzyvlftxcmwwca6qgg3l0zqqrk33hlcxhcpuv84awv'
|
||||
},
|
||||
{
|
||||
blockchain: 'BCH',
|
||||
type: 'slpAddr',
|
||||
address: 'simpleledger:qzyvlftxcmwwca6qgg3l0zqqrk33hlcxhcd88uqasj'
|
||||
}
|
||||
],
|
||||
encryptPubKey:
|
||||
'038264a85c945601ce96a5727f6ec524f16b3228fffd7c2d26ce047111be5598ad',
|
||||
jsonLd: {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'trout-dev-railgun-relay',
|
||||
version: '1.0.1',
|
||||
protocol: 'railgun-relayer',
|
||||
description:
|
||||
'This is a generic Railgun Relayer. It has not been customized.',
|
||||
documentation: 'https://www.railgun.org/',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'Railgun DAO',
|
||||
url: 'https://www.railgun.org/'
|
||||
},
|
||||
identifier: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp'
|
||||
},
|
||||
updatedAt: '2021-10-11T15:59:51.430Z'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const ipfsMockPeers = [
|
||||
{
|
||||
addr: '/ip4/139.162.76.54/tcp/5269/ws/p2p/QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
|
||||
peer: 'QmaKzQTAtoJWYMiG5ATx41uWsMajr1kSxRdtg919s8fK77',
|
||||
direction: 'outbound',
|
||||
muxer: '/mplex/6.7.0',
|
||||
latency: 'n/a',
|
||||
streams: []
|
||||
},
|
||||
{
|
||||
addr: '/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
peer: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKp',
|
||||
direction: 'outbound',
|
||||
muxer: '/mplex/6.7.0',
|
||||
latency: 'n/a',
|
||||
streams: []
|
||||
},
|
||||
// This data is used to exercise some of the exception code paths.
|
||||
{
|
||||
addr: '/ip4/143.198.60.119/tcp/4003/ws/p2p/QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKo',
|
||||
peer: 'QmcewynF2DMxuvK7zk1E5es1cvBwZrfnYEaiN995KVYaKo',
|
||||
direction: 'outbound',
|
||||
muxer: '/mplex/6.7.0',
|
||||
latency: 'n/a',
|
||||
streams: []
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
rpcData,
|
||||
mockRelayData,
|
||||
mockPeerData,
|
||||
ipfsMockPeers
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
const EventEmitter = require('events')
|
||||
|
||||
// Local support libraries
|
||||
// const testUtils = require('../../utils/test-utils')
|
||||
@@ -25,7 +26,7 @@ describe('#use-cases', () => {
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
uut = new UseCases({ adapters })
|
||||
uut = new UseCases({ adapters, eventEmitter: new EventEmitter() })
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
@@ -47,4 +48,17 @@ describe('#use-cases', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should initialize async use cases', async () => {
|
||||
const result = await uut.start()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
// it('should catch and throw errors', async () => {
|
||||
// // Force an error
|
||||
// sandbox.stub()
|
||||
// })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -153,7 +153,7 @@ describe('#users-use-case', () => {
|
||||
await uut.getAllUsers()
|
||||
// console.log(`users: ${JSON.stringify(users, null, 2)}`)
|
||||
|
||||
// assert.isArray(users)
|
||||
// assert.isArray(users)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
@@ -228,8 +228,8 @@ describe('#users-use-case', () => {
|
||||
// Assert that the expected properties for the user model exist.
|
||||
// assert.property(result, 'type')
|
||||
assert.property(result, '_id')
|
||||
// assert.property(result, 'email')
|
||||
// assert.property(result, 'name')
|
||||
// assert.property(result, 'email')
|
||||
// assert.property(result, 'name')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -241,7 +241,7 @@ describe('#users-use-case', () => {
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -346,7 +346,7 @@ describe('#users-use-case', () => {
|
||||
// assert.equal(result.name, 'testy tester')
|
||||
// })
|
||||
|
||||
// TODO: verify that an admin can change the type of a user
|
||||
// TODO: verify that an admin can change the type of a user
|
||||
})
|
||||
|
||||
describe('#authUser', () => {
|
||||
@@ -356,9 +356,9 @@ describe('#users-use-case', () => {
|
||||
await uut.authUser('test@test.com', 'password')
|
||||
// console.log('user: ', user)
|
||||
|
||||
// assert.property(user, '_id')
|
||||
// assert.property(user, 'email')
|
||||
// assert.property(user, 'name')
|
||||
// assert.property(user, '_id')
|
||||
// assert.property(user, 'email')
|
||||
// assert.property(user, 'name')
|
||||
})
|
||||
|
||||
it('should throw an error if no user matches the login', async () => {
|
||||
@@ -399,7 +399,7 @@ describe('#users-use-case', () => {
|
||||
assert.fail('Unexpected code path.')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user