mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-22 09:11:59 -07:00
fix(upstream): Syncing with upstream ipfs-service-provider
This commit is contained in:
@@ -64,6 +64,7 @@ database/
|
||||
system-user-*.json
|
||||
orbitdb
|
||||
ipfsdata
|
||||
.ipfsdata
|
||||
ipfs-service-provider.sh
|
||||
|
||||
!README.md
|
||||
|
||||
+90
-68
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
This Koa server has two interfaces:
|
||||
- REST API over HTTP
|
||||
- JSON RPC over IPFS
|
||||
|
||||
The architecture of the code follows the Clean Architecture pattern:
|
||||
https://troutsblog.com/blog/clean-architecture
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
const Koa = require('koa')
|
||||
const bodyParser = require('koa-bodyparser')
|
||||
@@ -12,9 +21,8 @@ const cors = require('kcors')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../config') // this first.
|
||||
// const IPFSLib = require('../src/lib/ipfs')
|
||||
const AdminLib = require('../src/adapters/admin')
|
||||
const adminLib = new AdminLib()
|
||||
// const adminLib = new AdminLib()
|
||||
|
||||
const WebHookLib = require('../src/adapters/webhook')
|
||||
const webHookLib = new WebHookLib()
|
||||
@@ -23,74 +31,88 @@ const webHookLib = new WebHookLib()
|
||||
// const rpc = new JSONRPC()
|
||||
|
||||
const errorMiddleware = require('../src/controllers/rest-api/middleware/error')
|
||||
const { wlogger } = require('../src/adapters/wlogger')
|
||||
// const { wlogger } = require('../src/adapters/wlogger')
|
||||
|
||||
async function startServer () {
|
||||
// Create a Koa instance.
|
||||
const app = new Koa()
|
||||
app.keys = [config.session]
|
||||
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
await mongoose.connect(config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
|
||||
// MIDDLEWARE START
|
||||
|
||||
app.use(convert(logger()))
|
||||
app.use(bodyParser())
|
||||
app.use(session())
|
||||
app.use(errorMiddleware())
|
||||
|
||||
// Used to generate the docs.
|
||||
app.use(mount('/', serve(`${process.cwd()}/docs`)))
|
||||
|
||||
// Mount the page for displaying logs.
|
||||
app.use(mount('/logs', serve(`${process.cwd()}/config/logs`)))
|
||||
|
||||
// User Authentication
|
||||
require('../config/passport')
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
const Controllers = require('../src/controllers')
|
||||
const controllers = new Controllers()
|
||||
controllers.attachControllers(app)
|
||||
|
||||
// Enable CORS for testing
|
||||
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
|
||||
app.use(cors({ origin: '*' }))
|
||||
|
||||
// MIDDLEWARE END
|
||||
|
||||
console.log(`Running server in environment: ${config.env}`)
|
||||
wlogger.info(`Running server in environment: ${config.env}`)
|
||||
|
||||
await app.listen(config.port)
|
||||
console.log(`Server started on ${config.port}`)
|
||||
|
||||
// Create the system admin user.
|
||||
const success = await adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
|
||||
// Create webhook
|
||||
try {
|
||||
await webHookLib.createWebHook('http://localhost:5002/entry')
|
||||
console.log('Webhook created')
|
||||
} catch (error) {
|
||||
console.log('Webhook cant be created')
|
||||
class Server {
|
||||
constructor () {
|
||||
this.adminLib = new AdminLib()
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
// startServer()
|
||||
async startServer () {
|
||||
try {
|
||||
// Create a Koa instance.
|
||||
const app = new Koa()
|
||||
app.keys = [config.session]
|
||||
|
||||
// export default app
|
||||
// module.exports = app
|
||||
module.exports = {
|
||||
startServer
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
console.log(
|
||||
`Connecting to MongoDB with this connection string: ${config.database}`
|
||||
)
|
||||
await mongoose.connect(config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
|
||||
console.log(`Starting environment: ${config.env}`)
|
||||
console.log(`Debug level: ${config.debugLevel}`)
|
||||
|
||||
// MIDDLEWARE START
|
||||
|
||||
app.use(convert(logger()))
|
||||
app.use(bodyParser())
|
||||
app.use(session())
|
||||
app.use(errorMiddleware())
|
||||
|
||||
// Used to generate the docs.
|
||||
app.use(mount('/', serve(`${process.cwd()}/docs`)))
|
||||
|
||||
// Mount the page for displaying logs.
|
||||
app.use(mount('/logs', serve(`${process.cwd()}/config/logs`)))
|
||||
|
||||
// User Authentication
|
||||
require('../config/passport')
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
const Controllers = require('../src/controllers')
|
||||
const controllers = new Controllers()
|
||||
await controllers.attachRESTControllers(app)
|
||||
|
||||
app.controllers = controllers
|
||||
|
||||
// Enable CORS for testing
|
||||
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
|
||||
app.use(cors({ origin: '*' }))
|
||||
|
||||
// MIDDLEWARE END
|
||||
|
||||
// Create webhook
|
||||
try {
|
||||
await webHookLib.createWebHook('http://localhost:5002/entry')
|
||||
console.log('Webhook created')
|
||||
} catch (error) {
|
||||
console.log('Webhook cant be created')
|
||||
}
|
||||
|
||||
// startServer()
|
||||
await app.listen(config.port)
|
||||
console.log(`Server started on ${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)
|
||||
|
||||
return app
|
||||
} catch (err) {
|
||||
console.error('Could not start server. Error: ', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Server
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
This file should be customized for your app. The string in this file is
|
||||
displayed when a user makes the 'about' JSON RPC call.
|
||||
*/
|
||||
|
||||
module.exports = `
|
||||
This is the about string for the ipfs-service-provider repository:
|
||||
https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
|
||||
|
||||
Brought to you by the Permissionless Software Foundation:
|
||||
https://PSFoundation.cash
|
||||
|
||||
Documentation:
|
||||
https://ipfs-service-provider.fullstack.cash/
|
||||
`
|
||||
Vendored
+37
-2
@@ -5,6 +5,14 @@
|
||||
|
||||
/* eslint no-unneeded-ternary:0 */
|
||||
|
||||
// Get the version from the package.json file.
|
||||
const pkgInfo = require('../../package.json')
|
||||
const version = pkgInfo.version
|
||||
|
||||
const ipfsCoordName = process.env.COORD_NAME
|
||||
? process.env.COORD_NAME
|
||||
: 'ipfs-torlist-service-generic'
|
||||
|
||||
module.exports = {
|
||||
// Configure TCP port.
|
||||
port: process.env.PORT || 5002,
|
||||
@@ -23,6 +31,21 @@ module.exports = {
|
||||
? process.env.EMAILPASS
|
||||
: 'emailpassword',
|
||||
|
||||
// FullStack.cash account information, used for automatic JWT handling.
|
||||
getJwtAtStartup: false,
|
||||
authServer: process.env.AUTHSERVER
|
||||
? process.env.AUTHSERVER
|
||||
: 'https://auth.fullstack.cash',
|
||||
apiServer: process.env.APISERVER
|
||||
? process.env.APISERVER
|
||||
: 'https://api.fullstack.cash/v5/',
|
||||
fullstackLogin: process.env.FULLSTACKLOGIN
|
||||
? process.env.FULLSTACKLOGIN
|
||||
: 'demo@demo.com',
|
||||
fullstackPassword: process.env.FULLSTACKPASS
|
||||
? process.env.FULLSTACKPASS
|
||||
: 'demo',
|
||||
|
||||
// IPFS settings.
|
||||
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
|
||||
|
||||
@@ -33,7 +56,9 @@ module.exports = {
|
||||
announceJsonLd: {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'ipfs-service-provider',
|
||||
name: ipfsCoordName,
|
||||
version,
|
||||
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/',
|
||||
@@ -43,8 +68,18 @@ module.exports = {
|
||||
url: 'https://PSFoundation.cash'
|
||||
}
|
||||
},
|
||||
|
||||
// P2WDB webhook endpoint
|
||||
webhookService: process.env.WEBHOOKSERVICE
|
||||
? process.env.WEBHOOKSERVICE
|
||||
: 'http://localhost:5001/webhook' // P2WDB.
|
||||
: 'http://localhost:5001/webhook', // P2WDB.
|
||||
|
||||
// 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,
|
||||
|
||||
// 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) : 1
|
||||
}
|
||||
|
||||
Vendored
+4
-1
@@ -10,6 +10,9 @@
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://172.17.0.1:5555/torlist-service-prod',
|
||||
// database: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
|
||||
database: process.env.DBURL
|
||||
? process.env.DBURL
|
||||
: 'mongodb://172.17.0.1:5555/torlist-service-prod',
|
||||
env: 'prod'
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This is where the mongodb docker container stores its db files.
|
||||
@@ -1,25 +0,0 @@
|
||||
# Start the testnet server with the command 'docker-compose up -d'
|
||||
|
||||
koa-mongodb:
|
||||
image: mongo
|
||||
container_name: mongo-koa
|
||||
ports:
|
||||
- "5555:27017" # <host port>:<container port>
|
||||
volumes:
|
||||
- ./database:/data/db
|
||||
command: mongod --smallfiles --logpath=/dev/null # -- quiet
|
||||
restart: always
|
||||
|
||||
koa:
|
||||
build: ./production/
|
||||
dockerfile: Dockerfile
|
||||
container_name: koa
|
||||
links:
|
||||
- koa-mongodb
|
||||
ports:
|
||||
- "5001:5001" # <host port>:<container port>
|
||||
volumes:
|
||||
# - ./logs:/home/coinjoin/consolidating-coinjoin/logs
|
||||
- ./keys:/home/safeuser/keys
|
||||
|
||||
restart: always
|
||||
@@ -1,3 +1,4 @@
|
||||
const server = require('./bin/server.js')
|
||||
const Server = require('./bin/server.js')
|
||||
const server = new Server()
|
||||
|
||||
server.startServer()
|
||||
|
||||
Generated
+18022
-19077
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -23,15 +23,16 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/ipfs-torlist-service",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "^4.20.1",
|
||||
"@psf/bch-js": "^4.20.3",
|
||||
"axios": "^0.21.1",
|
||||
"bch-message-lib": "^1.13.9",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"glob": "^7.1.6",
|
||||
"ipfs": "^0.54.4",
|
||||
"ipfs-coord": "^3.2.0",
|
||||
"@chris.troutner/ipfs": "2.0.2",
|
||||
"ipfs-coord": "^6.6.4",
|
||||
"jsonrpc-lite": "^2.2.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwt-bch-lib": "^1.3.0",
|
||||
"kcors": "^2.2.2",
|
||||
"koa": "^2.13.1",
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
@@ -45,6 +46,7 @@
|
||||
"koa2-ratelimit": "^0.9.0",
|
||||
"line-reader": "^0.4.0",
|
||||
"mongoose": "^5.11.15",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@^2.6.7",
|
||||
"nodemailer": "^6.4.17",
|
||||
"passport-local": "^1.0.0",
|
||||
"winston": "^3.3.3",
|
||||
|
||||
@@ -23,24 +23,24 @@ WORKDIR /home/safeuser
|
||||
#RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
||||
|
||||
# Switch to user account.
|
||||
USER safeuser
|
||||
#USER safeuser
|
||||
# Prep 'sudo' commands.
|
||||
RUN echo 'abcd8765' | sudo -S pwd
|
||||
#RUN echo 'abcd8765' | sudo -S pwd
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/christroutner/koa-api-boilerplate
|
||||
RUN mv koa-api-boilerplate koa
|
||||
RUN mkdir keys
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/koa
|
||||
WORKDIR /home/safeuser/ipfs-service-provider
|
||||
|
||||
# For development: switch to unstable branch
|
||||
# RUN git checkout unstable
|
||||
#RUN git checkout ct-unstable
|
||||
|
||||
# Install dependencies
|
||||
#RUN mkdir .ipfsdata
|
||||
RUN npm install -g @mapbox/node-pre-gyp
|
||||
RUN npm install
|
||||
|
||||
# Generate the API docs
|
||||
@@ -52,7 +52,7 @@ VOLUME /home/safeuser/keys
|
||||
EXPOSE 5001
|
||||
|
||||
# Start the application.
|
||||
COPY start-production start-production
|
||||
CMD ["./start-production"]
|
||||
COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
#CMD ["npm", "start"]
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Remove all untagged docker images.
|
||||
docker rmi $(docker images | grep "^<none>" | awk '{print $3}')
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Start the service with the command 'docker-compose up -d'
|
||||
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
mongo-ipfs-service:
|
||||
image: mongo
|
||||
container_name: mongo-ipfs-service
|
||||
ports:
|
||||
- '5555:27017' # <host port>:<container port>
|
||||
volumes:
|
||||
- ../data/database:/data/db
|
||||
command: mongod --logpath=/dev/null # -- quiet
|
||||
restart: always
|
||||
|
||||
ipfs-service:
|
||||
build: .
|
||||
container_name: ipfs-service
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
links:
|
||||
- mongo-ipfs-service
|
||||
ports:
|
||||
- '5001:5001' # <host port>:<container port>
|
||||
- '4001:4001' # IPFS TCP
|
||||
- '4003:4003' # IPFS WS
|
||||
volumes:
|
||||
- ../data/ipfsdata:/home/safeuser/ipfs-service-provider/.ipfsdata
|
||||
restart: always
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# BEGIN: Optional configuration settings
|
||||
|
||||
# This mnemonic is used to set up persistent public key for e2ee
|
||||
# Replace this with your own 12-word mnemonic.
|
||||
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
|
||||
|
||||
# Allow this node to function as a circuit relay. It must not be behind a firewall.
|
||||
#export ENABLE_CIRCUIT_RELAY=true
|
||||
|
||||
# Debug level. 0 = minimal info. 2 = max info.
|
||||
export DEBUG_LEVEL=1
|
||||
|
||||
# END: Optional configuration settings
|
||||
|
||||
|
||||
# Production database connection string.
|
||||
export DBURL=mongodb://172.17.0.1:5555/ipfs-service-prod
|
||||
|
||||
# Configure IPFS ports
|
||||
export IPFS_TCP_PORT=4001
|
||||
export IPFS_WS_PORT=4003
|
||||
|
||||
# Configure REST API port
|
||||
export PORT=5001
|
||||
|
||||
export SVC_ENV=production
|
||||
npm start
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
export KOA_ENV=production
|
||||
npm start
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
A library of utility functions for working with FullStack.cash JWT tokens.
|
||||
|
||||
Feel free to copy this library into your own app, as well as the unit tests
|
||||
for this file.
|
||||
*/
|
||||
|
||||
const JwtLib = require('jwt-bch-lib')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
|
||||
class FullStackJWT {
|
||||
constructor (localConfig = {}) {
|
||||
// Input Validation
|
||||
this.authServer = localConfig.authServer
|
||||
if (!this.authServer || typeof this.authServer !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a url for the AUTH server when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
this.apiServer = localConfig.apiServer
|
||||
if (!this.apiServer || typeof this.apiServer !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a url for the API server when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
this.login = localConfig.fullstackLogin
|
||||
if (!this.login || typeof this.login !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a FullStack.cash login (email) instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
this.password = localConfig.fullstackPassword
|
||||
if (!this.password || typeof this.password !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a FullStack.cash account password when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.jwtLib = new JwtLib({
|
||||
// Overwrite default values with the values in the config file.
|
||||
server: this.authServer,
|
||||
login: this.login,
|
||||
password: this.password
|
||||
})
|
||||
|
||||
// State
|
||||
this.apiToken = '' // Default value.
|
||||
this.bchjs = {}
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
this.apiToken = this.jwtLib.userData.apiToken
|
||||
if (!this.apiToken) {
|
||||
throw new Error('This account does not have a JWT')
|
||||
}
|
||||
console.log(`Retrieved JWT token: ${this.apiToken}\n`)
|
||||
|
||||
// Ensure the JWT token is valid to use.
|
||||
const isValid = await this.jwtLib.validateApiToken()
|
||||
|
||||
// Get a new token with the same API level, if the existing token is not
|
||||
// valid (probably expired).
|
||||
if (!isValid.isValid) {
|
||||
this.apiToken = await this.jwtLib.getApiToken(
|
||||
this.jwtLib.userData.apiLevel
|
||||
)
|
||||
console.log(
|
||||
`The JWT token was not valid. Retrieved new JWT token: ${this.apiToken}\n`
|
||||
)
|
||||
} else {
|
||||
console.log('JWT token is valid.\n')
|
||||
}
|
||||
|
||||
return this.apiToken
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Error trying to log into ${this.server} and retrieve JWT token.`
|
||||
)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Create an instance of bchjs with the validated JWT token. Returns this
|
||||
// instance of bch-js.
|
||||
instanceBchjs () {
|
||||
this.bchjs = new BCHJS({
|
||||
restURL: this.apiServer,
|
||||
apiToken: this.apiToken
|
||||
})
|
||||
|
||||
return this.bchjs
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FullStackJWT
|
||||
+61
-19
@@ -4,32 +4,74 @@
|
||||
https://troutsblog.com/blog/clean-architecture
|
||||
*/
|
||||
|
||||
// Public NPM libraries
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
|
||||
// Load individual adapter libraries.
|
||||
const IPFSAdapter = require('./ipfs')
|
||||
const LocalDB = require('./localdb')
|
||||
const LogsAPI = require('./logapi')
|
||||
const Passport = require('./passport')
|
||||
const Nodemailer = require('./nodemailer')
|
||||
const { wlogger } = require('./wlogger')
|
||||
// const { wlogger } = require('./wlogger')
|
||||
const JSONFiles = require('./json-files')
|
||||
const BCHJSAdapter = require('./bch')
|
||||
const FullStackJWT = require('./fullstack-jwt')
|
||||
// const BCHJSAdapter = require('./bch')
|
||||
//
|
||||
// // Instantiate adapter libraries.
|
||||
// const ipfs = new IPFSAdapter()
|
||||
// const localdb = new LocalDB()
|
||||
// const logapi = new LogsAPI()
|
||||
// const passport = new Passport()
|
||||
// const nodemailer = new Nodemailer()
|
||||
// const jsonFiles = new JSONFiles()
|
||||
// const bchjs = new BCHJSAdapter()
|
||||
//
|
||||
// module.exports = {
|
||||
// ipfs,
|
||||
// localdb,
|
||||
// logapi,
|
||||
// passport,
|
||||
// nodemailer,
|
||||
// wlogger,
|
||||
// jsonFiles,
|
||||
// bchjs
|
||||
|
||||
// Instantiate adapter libraries.
|
||||
const ipfs = new IPFSAdapter()
|
||||
const localdb = new LocalDB()
|
||||
const logapi = new LogsAPI()
|
||||
const passport = new Passport()
|
||||
const nodemailer = new Nodemailer()
|
||||
const jsonFiles = new JSONFiles()
|
||||
const bchjs = new BCHJSAdapter()
|
||||
const config = require('../../config')
|
||||
|
||||
module.exports = {
|
||||
ipfs,
|
||||
localdb,
|
||||
logapi,
|
||||
passport,
|
||||
nodemailer,
|
||||
wlogger,
|
||||
jsonFiles,
|
||||
bchjs
|
||||
class Adapters {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
this.ipfs = new IPFSAdapter()
|
||||
this.localdb = new LocalDB()
|
||||
this.logapi = new LogsAPI()
|
||||
this.passport = new Passport()
|
||||
this.nodemailer = new Nodemailer()
|
||||
this.jsonFiles = new JSONFiles()
|
||||
this.bchjs = new BCHJS()
|
||||
this.config = config
|
||||
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
this.fullStackJwt = new FullStackJWT(config)
|
||||
}
|
||||
|
||||
async start () {
|
||||
try {
|
||||
if (this.config.getJwtAtStartup) {
|
||||
// Get a JWT token and instantiate bch-js with it. Then pass that instance
|
||||
// to all the rest of the apps controllers and adapters.
|
||||
await this.fullStackJwt.getJWT()
|
||||
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
|
||||
this.bchjs = await this.fullStackJwt.instanceBchjs()
|
||||
}
|
||||
|
||||
// Start the IPFS node.
|
||||
await this.ipfs.start()
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/index.js/start()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Adapters
|
||||
|
||||
@@ -38,6 +38,13 @@ class IPFS {
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs/index.js/start()')
|
||||
|
||||
// If error is due to a lock file issue. Kill the process, so that
|
||||
// Docker or pm2 has a chance to restart the service.
|
||||
if (err.message.includes('Lock already being held')) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,12 @@ class IpfsCoordAdapter {
|
||||
privateLog: console.log, // Default to console.log
|
||||
isCircuitRelay: this.config.isCircuitRelay,
|
||||
apiInfo: this.config.apiInfo,
|
||||
announceJsonLd: this.config.announceJsonLd
|
||||
announceJsonLd: this.config.announceJsonLd,
|
||||
debugLevel: this.config.debugLevel
|
||||
})
|
||||
|
||||
// Wait for the ipfs-coord library to signal that it is ready.
|
||||
await this.ipfsCoord.isReady()
|
||||
await this.ipfsCoord.start()
|
||||
|
||||
// Signal that this adapter is ready.
|
||||
this.isReady = true
|
||||
@@ -63,7 +64,7 @@ class IpfsCoordAdapter {
|
||||
attachRPCRouter (router) {
|
||||
try {
|
||||
_this.ipfsCoord.privateLog = router
|
||||
_this.ipfsCoord.ipfs.orbitdb.privateLog = router
|
||||
_this.ipfsCoord.adapters.orbit.privateLog = router
|
||||
} catch (err) {
|
||||
console.error('Error in attachRPCRouter()')
|
||||
throw err
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const IPFS = require('ipfs')
|
||||
// const IPFS = require('ipfs')
|
||||
const IPFS = require('@chris.troutner/ipfs')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
@@ -25,7 +26,7 @@ class IpfsAdapter {
|
||||
try {
|
||||
// Ipfs Options
|
||||
const ipfsOptions = {
|
||||
repo: './ipfsdata',
|
||||
repo: './.ipfsdata/ipfs',
|
||||
start: true,
|
||||
config: {
|
||||
relay: {
|
||||
@@ -56,16 +57,6 @@ class IpfsAdapter {
|
||||
// Set the 'server' profile so the node does not scan private networks.
|
||||
await this.ipfs.config.profiles.apply('server')
|
||||
|
||||
// const nodeConfig = await this.ipfs.config.getAll()
|
||||
// console.log(
|
||||
// `IPFS node configuration: ${JSON.stringify(nodeConfig, null, 2)}`
|
||||
// )
|
||||
|
||||
// Stop the IPFS node if we're running tests.
|
||||
if (this.config.env === 'test') {
|
||||
await this.ipfs.stop()
|
||||
}
|
||||
|
||||
// Signal that this adapter is ready.
|
||||
this.isReady = true
|
||||
|
||||
@@ -75,6 +66,10 @@ class IpfsAdapter {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async stop () {
|
||||
await this.ipfs.stop()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsAdapter
|
||||
|
||||
+4
-35
@@ -62,42 +62,11 @@ class Wlogger {
|
||||
}
|
||||
}
|
||||
|
||||
// transport.on('rotate', notifyRotation)
|
||||
|
||||
// function notifyRotation (oldFilename, newFilename) {
|
||||
// wlogger.info('Rotating log files')
|
||||
// }
|
||||
|
||||
// This controls what goes into the log FILES
|
||||
// const wlogger = winston.createLogger({
|
||||
// level: 'verbose',
|
||||
// format: winston.format.json(),
|
||||
// transports: [
|
||||
// //
|
||||
// // - Write to all logs with level `info` and below to `combined.log`
|
||||
// // - Write all logs error (and below) to `error.log`.
|
||||
// //
|
||||
// // new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
|
||||
// // new winston.transports.File({ filename: 'logs/combined.log' })
|
||||
// transport
|
||||
// ]
|
||||
// })
|
||||
|
||||
// function outputToConsole () {
|
||||
// wlogger.add(
|
||||
// new winston.transports.Console({
|
||||
// format: winston.format.simple(),
|
||||
// level: 'info'
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
|
||||
// This controls the logs to CONSOLE
|
||||
// if (config.env !== 'test') {
|
||||
// outputToConsole()
|
||||
// }
|
||||
|
||||
const logger = new Wlogger()
|
||||
|
||||
// Allow the logger to write to the console.
|
||||
logger.outputToConsole()
|
||||
|
||||
const wlogger = logger.wlogger
|
||||
|
||||
module.exports = { wlogger, Wlogger }
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Public npm libraries.
|
||||
|
||||
// Load the Clean Architecture Adapters library
|
||||
const adapters = require('../adapters')
|
||||
const Adapters = require('../adapters')
|
||||
|
||||
// Load the JSON RPC Controller.
|
||||
const JSONRPC = require('./json-rpc')
|
||||
@@ -21,16 +21,16 @@ const RESTControllers = require('./rest-api')
|
||||
|
||||
class Controllers {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = adapters
|
||||
this.useCases = new UseCases({ adapters })
|
||||
this.adapters = new Adapters()
|
||||
this.useCases = new UseCases({ adapters: this.adapters })
|
||||
}
|
||||
|
||||
async attachControllers (app) {
|
||||
// Attach the REST controllers to the Koa app.
|
||||
this.attachRESTControllers(app)
|
||||
// Wait for any startup processes to complete for the Adapters libraries.
|
||||
await this.adapters.start()
|
||||
|
||||
// Start IPFS.
|
||||
await this.adapters.ipfs.start()
|
||||
// Attach the REST controllers to the Koa app.
|
||||
// this.attachRESTControllers(app)
|
||||
|
||||
this.attachRPCControllers()
|
||||
}
|
||||
@@ -38,13 +38,13 @@ class Controllers {
|
||||
// Top-level function for this library.
|
||||
// Start the various Controllers and attach them to the app.
|
||||
attachRESTControllers (app) {
|
||||
const rESTControllers = new RESTControllers({
|
||||
const restControllers = new RESTControllers({
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
})
|
||||
|
||||
// Attach the REST API Controllers associated with the boilerplate code to the Koa app.
|
||||
rESTControllers.attachRESTControllers(app)
|
||||
restControllers.attachRESTControllers(app)
|
||||
}
|
||||
|
||||
// Add the JSON RPC router to the ipfs-coord adapter.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
|
||||
// Local libraries
|
||||
const aboutStr = require('../../../../config/about')
|
||||
const config = require('../../../../config')
|
||||
|
||||
class AboutRPC {
|
||||
constructor (localConfig) {
|
||||
@@ -36,7 +36,8 @@ class AboutRPC {
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
message: aboutStr,
|
||||
// message: aboutStr,
|
||||
message: JSON.stringify(config.announceJsonLd),
|
||||
endpoint: 'about'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@ class JSONRPC {
|
||||
this.authController = new AuthController(localConfig)
|
||||
this.aboutController = new AboutController()
|
||||
|
||||
// Cache to store IDs of processed JSON RPC commands. Used to prevent
|
||||
// duplicate processing.
|
||||
this.msgCache = []
|
||||
this.MSG_CACHE_SIZE = 30
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
@@ -44,25 +49,47 @@ class JSONRPC {
|
||||
async router (str, from) {
|
||||
try {
|
||||
// console.log('router str: ', str)
|
||||
// console.log('router from: ', from)
|
||||
console.log('JSON RPC router recieved data from: ', from)
|
||||
|
||||
// Exit quietly if 'from' is not specified.
|
||||
if (!from || typeof from !== 'string') {
|
||||
// console.warn(
|
||||
// 'Warning: Can not send JSON RPC response. Can not determine which peer this message came from.'
|
||||
// )
|
||||
wlogger.info(
|
||||
'Warning: Can not send JSON RPC response. Can not determine which peer this message came from.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Attempt to parse the incoming data as a JSON RPC string.
|
||||
const parsedData = _this.jsonrpc.parse(str)
|
||||
// console.log('parsedData: ', parsedData)
|
||||
// wlogger.debug(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
|
||||
|
||||
// Exit quietly if the incoming string is an invalid JSON RPC string.
|
||||
if (parsedData.type === 'invalid') {
|
||||
wlogger.info('Rejecting invalid JSON RPC command.')
|
||||
return
|
||||
}
|
||||
|
||||
// Check for duplicate entries with same 'id' value.
|
||||
const alreadyProcessed = _this._checkIfAlreadyProcessed(parsedData)
|
||||
if (alreadyProcessed) {
|
||||
return
|
||||
} else {
|
||||
// This node will regularly ping known circuit relays with an /about
|
||||
// JSON RPC call. These will be handled by ipfs-coord, but will percolate
|
||||
// up to ipfs-coord. Ignore these messages.
|
||||
if (
|
||||
parsedData.type.includes('success') &&
|
||||
parsedData.payload.method === undefined
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Log the incoming JSON RPC command.
|
||||
wlogger.info(
|
||||
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}, method: ${parsedData.payload.method}`
|
||||
)
|
||||
}
|
||||
|
||||
// Added the property "from" to the parsedData object;
|
||||
// necessary for calculating rate limits (based on the IPFS ID).
|
||||
parsedData.from = from
|
||||
@@ -95,8 +122,19 @@ class JSONRPC {
|
||||
|
||||
// Encrypt and publish the response to the originators private OrbitDB,
|
||||
// if ipfs-coord has been initialized and the peers ID is registered.
|
||||
if (_this.ipfsCoord.ipfs) {
|
||||
await _this.ipfsCoord.ipfs.orbitdb.sendToDb(from, retStr)
|
||||
|
||||
// console.log('responding to JSON RPC command')
|
||||
const thisNode = _this.ipfsCoord.thisNode
|
||||
// console.log('thisNode: ', thisNode)
|
||||
|
||||
try {
|
||||
await _this.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
from,
|
||||
retStr,
|
||||
thisNode
|
||||
)
|
||||
} catch (err) {
|
||||
console.log('sendPrivateMessage() err: ', err)
|
||||
}
|
||||
|
||||
// Return the response and originator. Useful for testing.
|
||||
@@ -108,6 +146,34 @@ class JSONRPC {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks the ID of the JSON RPC call, to see if the message has already been
|
||||
// processed. Returns true if the ID exists in the cache of processed messages.
|
||||
// If the ID is new, the function adds it to the cache and return false.
|
||||
_checkIfAlreadyProcessed (data) {
|
||||
try {
|
||||
const id = data.payload.id
|
||||
|
||||
// Check if the hash is in the array of already processed message.
|
||||
const alreadyProcessed = this.msgCache.includes(id)
|
||||
|
||||
// Update the msgCache if this is a new message.
|
||||
if (!alreadyProcessed) {
|
||||
// Add the hash to the array.
|
||||
this.msgCache.push(id)
|
||||
|
||||
// If the array is at its max size, then remove the oldest element.
|
||||
if (this.msgCache.length > this.MSG_CACHE_SIZE) {
|
||||
this.msgCache.shift()
|
||||
}
|
||||
}
|
||||
|
||||
return alreadyProcessed
|
||||
} catch (err) {
|
||||
console.error('Error in _checkIfAlreadyProcessed: ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// The default JSON RPC response if the incoming command could not be routed.
|
||||
defaultResponse () {
|
||||
const errorObj = {
|
||||
|
||||
@@ -10,7 +10,7 @@ const axios = require('axios').default
|
||||
|
||||
// Local support libraries
|
||||
const config = require('../../../config')
|
||||
const app = require('../../../bin/server')
|
||||
const Server = require('../../../bin/server')
|
||||
const testUtils = require('../../utils/test-utils')
|
||||
const AdminLib = require('../../../src/adapters/admin')
|
||||
const adminLib = new AdminLib()
|
||||
@@ -22,9 +22,14 @@ const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
const app = new Server()
|
||||
|
||||
// This should be the first instruction. It starts the REST API server.
|
||||
await app.startServer()
|
||||
|
||||
// Stop the IPFS node for the rest of the e2e tests.
|
||||
// await app.controllers.adapters.ipfs.stop()
|
||||
|
||||
// Delete all previous users in the database.
|
||||
await testUtils.deleteAllUsers()
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ const LOCALHOST = `http://localhost:${config.port}`
|
||||
const context = {}
|
||||
|
||||
const UserController = require('../../../src/controllers/rest-api/users/controller')
|
||||
const adapters = require('../../../src/adapters')
|
||||
const Adapters = require('../../../src/adapters')
|
||||
const adapters = new Adapters()
|
||||
const UseCases = require('../../../src/use-cases/')
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
Unit tests for the jwt-bch-lib and fullstack-jwt.js adapter library.
|
||||
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const FullStackJWT = require('../../../src/adapters/fullstack-jwt')
|
||||
|
||||
describe('#FullStackJWT', () => {
|
||||
let sandbox
|
||||
let uut
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
const localConfig = {
|
||||
authServer: 'someserver',
|
||||
apiServer: 'someserver',
|
||||
fullstackLogin: 'somelogin',
|
||||
fullstackPassword: 'somepassword'
|
||||
}
|
||||
uut = new FullStackJWT(localConfig)
|
||||
})
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if auth server is not specified', () => {
|
||||
try {
|
||||
uut = new FullStackJWT()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // For linting.
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass a url for the AUTH server when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if api server is not specified', () => {
|
||||
try {
|
||||
const localConfig = {
|
||||
authServer: 'someserver'
|
||||
}
|
||||
|
||||
uut = new FullStackJWT(localConfig)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // For linting.
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass a url for the API server when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if login is not specified', () => {
|
||||
try {
|
||||
const localConfig = {
|
||||
authServer: 'someserver',
|
||||
apiServer: 'someserver'
|
||||
}
|
||||
uut = new FullStackJWT(localConfig)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // For linting.
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass a FullStack.cash login (email) instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if login is not specified', () => {
|
||||
try {
|
||||
const localConfig = {
|
||||
authServer: 'someserver',
|
||||
apiServer: 'someserver',
|
||||
fullstackLogin: 'somelogin'
|
||||
}
|
||||
uut = new FullStackJWT(localConfig)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // For linting.
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass a FullStack.cash account password when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getJWT', () => {
|
||||
it('should return the JWT token', async () => {
|
||||
// Mock dependencies to force a code path.
|
||||
sandbox.stub(uut.jwtLib, 'register').resolves({})
|
||||
uut.jwtLib.userData.apiToken = 'abc123'
|
||||
sandbox.stub(uut.jwtLib, 'validateApiToken').resolves({ isValid: true })
|
||||
|
||||
const result = await uut.getJWT()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'abc123')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.jwtLib, 'register').rejects(new Error('test error'))
|
||||
|
||||
await uut.getJWT()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if user does not have a JWT', async () => {
|
||||
try {
|
||||
// Mock dependencies to force a code path.
|
||||
sandbox.stub(uut.jwtLib, 'register').resolves({})
|
||||
|
||||
await uut.getJWT()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err.message: ', err.message)
|
||||
assert.include(err.message, 'This account does not have a JWT')
|
||||
}
|
||||
})
|
||||
|
||||
it('should retrieve a new JWT token if the old one invalid', async () => {
|
||||
// Mock dependencies to force a code path.
|
||||
sandbox.stub(uut.jwtLib, 'register').resolves({})
|
||||
uut.jwtLib.userData.apiToken = 'abc123'
|
||||
uut.jwtLib.userData.apiLevel = 30
|
||||
sandbox.stub(uut.jwtLib, 'validateApiToken').resolves({ isValid: false })
|
||||
sandbox.stub(uut.jwtLib, 'getApiToken').resolves('xyz789')
|
||||
|
||||
const result = await uut.getJWT()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'xyz789')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#instanceBchjs', () => {
|
||||
it('should return an instance of bch-js', () => {
|
||||
const result = uut.instanceBchjs()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'restURL')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -58,6 +58,11 @@ describe('#IPFS', () => {
|
||||
orbitdb: {
|
||||
privateLog: {}
|
||||
}
|
||||
},
|
||||
adapters: {
|
||||
orbit: {
|
||||
privateLog: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,16 +71,16 @@ describe('#IPFS', () => {
|
||||
uut.attachRPCRouter(router)
|
||||
})
|
||||
|
||||
it('should throw an error if ipfs-coord has not been instantiated', () => {
|
||||
try {
|
||||
const router = console.log
|
||||
|
||||
uut.attachRPCRouter(router)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
}
|
||||
})
|
||||
// it('should throw an error if ipfs-coord has not been instantiated', () => {
|
||||
// try {
|
||||
// const router = console.log
|
||||
//
|
||||
// uut.attachRPCRouter(router)
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, 'Cannot read property')
|
||||
// }
|
||||
// })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
// const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const adapters = require('../../../src/adapters')
|
||||
// const { attachControllers } = require('../../../src/controllers')
|
||||
const Controllers = require('../../../src/controllers')
|
||||
|
||||
describe('#Controllers', () => {
|
||||
@@ -25,8 +23,8 @@ describe('#Controllers', () => {
|
||||
describe('#attachControllers', () => {
|
||||
it('should attach the controllers', async () => {
|
||||
// mock IPFS
|
||||
sandbox.stub(adapters.ipfs, 'start').resolves({})
|
||||
adapters.ipfs.ipfsCoordAdapter = {
|
||||
sandbox.stub(uut.adapters, 'start').resolves({})
|
||||
uut.adapters.ipfs.ipfsCoordAdapter = {
|
||||
attachRPCRouter: () => {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user