Compare commits

..
35 Commits
Author SHA1 Message Date
Chris Troutner 7a620892d7 Merge pull request #57 from Permissionless-Software-Foundation/ct-unstable
Updating helia-coord
2024-07-21 15:54:31 -07:00
Chris Troutner 4a1524629c Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-05-21 20:33:37 -07:00
Chris Troutner f7aaba71d1 Merge pull request #153 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.8
2024-05-04 09:38:28 -07:00
Chris Troutner 418a2e2ad2 fix(helia-coord): Updating to v1.5.8 2024-05-04 09:37:10 -07:00
Chris Troutner 74248ea0c7 Merge pull request #56 from Permissionless-Software-Foundation/ct-unstable
Improvements from production testing
2024-04-28 20:40:25 -07:00
Chris Troutner 70a69e98bb fix(pollForBchServices()): filtering for only wallet-services, not consumers 2024-04-28 16:53:34 -07:00
Chris Troutner 8cffd7fae3 Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-04-27 13:22:07 -07:00
Chris Troutner a1023e8224 Merge pull request #152 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Setting memory limit at 4GB
2024-04-27 13:20:20 -07:00
Chris Troutner a32ed63d47 fix(Docker): Setting memory limit at 4GB 2024-04-27 13:17:57 -07:00
Chris Troutner 934fb46ffc Merge pull request #151 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Removing Volume statement
2024-04-26 14:40:41 -07:00
Chris Troutner b27136d03b fix(Docker): Removing Volume statement 2024-04-26 14:39:18 -07:00
Chris Troutner 9837d6a7fb Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-04-13 07:15:57 -07:00
Chris Troutner 60a3d67c0b Merge pull request #150 from Permissionless-Software-Foundation/ct-unstable
fix(config): web2Api can be set with env var
2024-04-13 07:11:20 -07:00
Chris Troutner a03c5d8c91 fix(config): web2Api can be set with env var 2024-04-13 07:09:45 -07:00
Chris Troutner 0e7afb5f51 Merge remote-tracking branch 'upstream/master' into ct-unstable 2024-04-13 07:07:44 -07:00
Chris Troutner bcc90da49b Merge pull request #149 from Permissionless-Software-Foundation/ct-unstable
fix(config): Adding web2Api property to jsonLd config
2024-04-13 07:04:02 -07:00
Chris Troutner b473a3803c fix(config): Adding web2Api property to jsonLd config 2024-04-13 07:02:32 -07:00
Chris Troutner 38ed9a8dac Merge pull request #55 from Permissionless-Software-Foundation/ct-unstable
fix(getPins): Adding getPins endpoint for File Pin Service
2024-04-12 10:46:01 -07:00
Chris Troutner 0ca46df43e fix(getPins): Adding getPins endpoint for File Pin Service 2024-04-12 10:44:03 -07:00
Chris Troutner b5f09fbab9 Sycing with upstream ipfs-service-provider 2024-04-12 08:35:49 -07:00
Chris Troutner e3e928b33d Adding getPins endpoint for File Pin Service 2024-04-12 08:34:39 -07:00
Chris Troutner 9614f186b4 Merge pull request #148 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.7
2024-04-12 08:32:13 -07:00
Chris Troutner 0390f2d956 fix(helia-coord): Updating to v1.5.7 2024-04-12 08:30:14 -07:00
Chris Troutner 43025f0265 Merge pull request #54 from Permissionless-Software-Foundation/ct-unstable
Adding endpoints needed for metrics
2024-04-11 05:36:56 -07:00
Chris Troutner 7ea17fb57c Removing example timer controller 2024-04-11 05:34:25 -07:00
Chris Troutner efb29104d7 feat(/ipfs/file-info/:cid): GET endpoint to get CID pin metadata from ipfs-file-pin-server over JSON RPC 2024-04-10 19:41:43 -07:00
Chris Troutner 971474d493 linting 2024-04-10 11:05:55 -07:00
Chris Troutner 71a6de03b3 feat(/bch/service): GET endpoint to get ipfs-bch-wallet-service consumed by this app 2024-04-10 11:05:46 -07:00
Chris Troutner 98dd904fa5 feat(/ipfs/service): GET endpoint to retrieve File Pin service used by this app 2024-04-10 10:52:07 -07:00
Chris Troutner b5c7e2efb3 Using filename from file metadata 2024-04-09 22:00:58 -07:00
Chris Troutner 3c9bbb097e Successfully recieving result from file pin JSON RPC call 2024-04-09 20:56:40 -07:00
Chris Troutner 8956968027 Commenting out downloadCid2() 2024-04-09 08:08:53 -07:00
Chris Troutner 14c724bf9c Prototyping IPFS file download 2024-04-09 06:36:23 -07:00
Chris Troutner a9f7e609e9 Merge pull request #53 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Changing ports to comply with cashstack.info
2024-04-07 14:41:49 -07:00
Chris Troutner fee68e0f59 fix(docker): Changing ports to comply with cashstack.info 2024-04-07 14:38:40 -07:00
19 changed files with 19851 additions and 30 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright (c) 2021-2023 Permissionless Software Foundation
Copyright (c) 2021-2024 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:
+10 -2
View File
@@ -27,7 +27,7 @@ const ipfsCoordName = process.env.COORD_NAME
export default {
// Configure TCP port.
port: process.env.PORT || 5005,
port: process.env.PORT || 5015,
// Password for HTML UI that displays logs.
logPass: 'test',
@@ -119,7 +119,10 @@ export default {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
}
},
// If this node has an IP4 address or domain name used to provide a REST API.
web2Api: process.env.WEB2_API ? process.env.WEB2_API : null
},
// IPFS Ports
@@ -159,6 +162,11 @@ export default {
// Preferred P2WDB provider
preferredP2wdbProvider: process.env.PREFERRED_P2WDB_PROVIDER
? process.env.PREFERRED_P2WDB_PROVIDER
: '',
// Preferred P2WDB provider
preferredIpfsFileProvider: process.env.PREFERRED_IPFS_FILE_PROVIDER
? process.env.PREFERRED_IPFS_FILE_PROVIDER
: ''
}
+19106 -7
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -41,7 +41,7 @@
"datastore-fs": "9.1.6",
"glob": "7.1.6",
"helia": "2.1.0",
"helia-coord": "1.5.6",
"helia-coord": "1.5.8",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0",
@@ -58,6 +58,7 @@
"koa2-ratelimit": "0.9.1",
"libp2p": "1.2.1",
"line-reader": "0.4.0",
"mime-types": "2.1.35",
"minimal-slp-wallet": "5.11.2",
"mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
+1 -3
View File
@@ -66,10 +66,8 @@ RUN npm install
# Generate the API docs
RUN npm run docs
VOLUME /home/safeuser/keys
# Expose the port the API will be served on.
EXPOSE 5010
#EXPOSE 5010
# Start the application.
#COPY start-production.sh start-production.sh
+5 -5
View File
@@ -7,7 +7,7 @@ services:
image: mongo:4.2.0
container_name: mongo-bch-consumer
ports:
- '5555:27017' # <host port>:<container port>
- '5558:27017' # <host port>:<container port>
volumes:
- ../data/database:/data/db
command: mongod --logpath=/dev/null # -- quiet
@@ -22,13 +22,13 @@ services:
options:
max-size: '10m'
max-file: '10'
#mem_limit: 500mb
mem_limit: 4000mb
links:
- mongo-bch-consumer
ports:
- '5010:5010' # <host port>:<container port>
- '4001:4001'
- '4003:4003'
- '5015:5015' # <host port>:<container port>
- '4101:4101'
- '4103:4103'
volumes:
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-consumer/.ipfsdata
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-consumer/start-production.sh
+4 -4
View File
@@ -20,17 +20,17 @@ export COORD_NAME=bch-consumer-generic
# Production database connection string.
export DBURL=mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod
export DBURL=mongodb://172.17.0.1:5558/ipfs-bch-consumer-prod
# Configure REST API port
export PORT=5010
export PORT=5015
# Production settings using external go-ipfs node.
export SVC_ENV=prod
export IPFS_HOST=172.17.0.1
export IPFS_API_PORT=5001
export IPFS_TCP_PORT=4001
export IPFS_WS_PORT=4003
export IPFS_TCP_PORT=4101
export IPFS_WS_PORT=4103
# Set the debug level for helia-coord. 0-3.
# 0 = no debug logs. 3 = maximum debug logs.
+2
View File
@@ -15,6 +15,7 @@ import Passport from './passport.js'
import Nodemailer from './nodemailer.js'
import BCH from './bch/index.js'
import P2WDB from './p2wdb/index.js'
import IpfsFiles from './ipfs-files/index.js'
// const { wlogger } = require('./wlogger')
import JSONFiles from './json-files.js'
@@ -45,6 +46,7 @@ class Adapters {
this.bch = new BCH(localConfig)
this.p2wdb = new P2WDB(localConfig)
this.wallet = new Wallet(localConfig)
this.ipfsFiles = new IpfsFiles(localConfig)
// Get a valid JWT API key and instance bch-js.
this.fullStackJwt = new FullStackJWT(config)
+274
View File
@@ -0,0 +1,274 @@
/*
This library contains code for interfacing to the ipfs-file-pin-service using
JSON RPC over IPFS. Most of these functions are called by the /files REST API
endpoints and the IPFS Use Cases library.
*/
// Public npm libraries
import { v4 as uid } from 'uuid'
import jsonrpc from 'jsonrpc-lite'
// Local libraries
import wlogger from '../wlogger.js'
// let _this
class IpfsFilesAdapter {
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 IPFS Files 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.
// Bind 'this' object to all subfunctions for this Class
this.rpcHandler = this.rpcHandler.bind(this)
this.getStatus = this.getStatus.bind(this)
this.selectProvider = this.selectProvider.bind(this)
}
// 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 files/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.ipfsFileProviders
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.selectedIpfsFileProvider
}
// console.log('outObj: ', outObj)
return outObj
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/files/getStatus()')
throw err
}
}
// Choose the ipfs-file-pin-service to use.
async selectProvider (providerId) {
try {
this.ipfs.ipfsCoordAdapter.config.selectedIpfsFileProvider = providerId
return true
} catch (err) {
// console.log('createUser() error: ', err)
wlogger.error('Error in adapters/files/getStatus()')
throw err
}
}
// Given a CID, query the ipfs-file-pin-service to get the file metadata, like
// the file name.
async getFileMetadata (inObj = {}) {
try {
const { cid } = inObj
// Input validation.
if (!cid || typeof cid !== 'string') {
throw new Error('getFileMetadata() cid 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.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Pin Service provider available yet.')
}
const rpcData = {
endpoint: 'getFileMetadata',
cid
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'file-pin', 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('getFileMetadata() data: ', data)
return data
} catch (err) {
wlogger.error('Error in adapters/files/getFileMetadata()')
throw err
}
}
// Get an array of the latest 20 pinned files.
async getPins (inObj = {}) {
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.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Pin Service provider available yet.')
}
const rpcData = {
endpoint: 'getPins'
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'file-pin', 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('getFileMetadata() data: ', data)
return data
} catch (err) {
wlogger.error('Error in adapters/files/getPins()')
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.wallet.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
export default IpfsFilesAdapter
+105 -3
View File
@@ -20,9 +20,11 @@ import config from '../../../config/index.js'
// 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 WALLET_PROTOCOL = 'bch-wallet-service'
const MIN_P2WDB_VERSION = '1.4.0'
const P2WDB_PROTOCOL = 'p2wdb'
const MIN_FILE_PIN_VERSION = '1.0.0'
const FILE_PIN_PROTOCOL = 'ipfs-file-pin-service'
let _this
@@ -56,9 +58,13 @@ class IpfsCoordAdapter {
// Periodically poll services for available wallet service providers.
this.pollBchServiceInterval = setInterval(this.pollForBchServices, 10000)
this.pollIpfsFileServiceInterval = setInterval(
this.pollForIpfsFileServices,
11000
)
this.pollP2wdbServiceInterval = setInterval(
this.pollForP2wdbServices,
11000
12000
)
// State object. TODO: Make this more robust.
@@ -66,7 +72,9 @@ class IpfsCoordAdapter {
serviceProviders: [],
selectedServiceProvider: '',
p2wdbProviders: [],
selectedP2wdbProvider: ''
selectedP2wdbProvider: '',
ipfsFileProviders: [],
selectedIpfsFileProvider: ''
}
_this = this
@@ -278,6 +286,10 @@ class IpfsCoordAdapter {
_this.config.preferredProvider &&
thisPeer === _this.config.preferredProvider
) {
if (_this.state.selectedServiceProvider !== thisPeer) {
console.log(`---->BCH wallet service switched to preferred peer: ${thisPeer}`)
}
_this.state.selectedServiceProvider = thisPeer
}
@@ -308,6 +320,96 @@ class IpfsCoordAdapter {
}
}
// Poll the ipfs-coord coordination channel for available ipfs-file-pin-service
// providers. This method is called periodically by a timer-interval.
pollForIpfsFileServices () {
try {
// console.log('pollForIpfsFileServices() polling for BCH service')
// An array of IPFS IDs of other nodes in the coordination pubsub channel.
const peers = _this.ipfsCoord.thisNode.peerList
// console.log(`peers: ${JSON.stringify(peers, null, 2)}`)
// 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_FILE_PIN_VERSION)
versionMatches = _this.semver.satisfies(version, `>=${MIN_FILE_PIN_VERSION}`)
}
// Ignore any peers that don't match the fingerprint for a BCH wallet
// service.
if (protocol && protocol.includes(FILE_PIN_PROTOCOL) && versionMatches) {
// console.log('Matching peer: ', thisPeerData)
// Temporary business logic.
// Use the first available wallet service detected.
if (_this.state.ipfsFileProviders.length === 0) {
_this.state.selectedIpfsFileProvider = thisPeer
// Persist the config setting, so it can be used by other commands.
// _this.conf.set('selectedService', thisPeer)
console.log(`---->IPFS File service selected: ${thisPeer}`)
}
// console.log('preferredProvider: ', _this.config.preferredProvider)
// If a preferred provider is set in the config file, then connect
// to the preferred provider when it's discovered.
if (
_this.config.preferredIpfsFileProvider &&
thisPeer === _this.config.preferredIpfsFileProvider
) {
if (_this.state.selectedIpfsFileProvider !== thisPeer) {
console.log(`---->IPFS File service switched to preferred peer: ${thisPeer}`)
}
_this.state.selectedIpfsFileProvider = thisPeer
}
// console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider)
// Check if the peer has already been added to the list of providers.
const alreadyExists = _this.state.ipfsFileProviders.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.ipfsFileProviders.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 pollForIpfsFileServices(): ', 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) {
+6
View File
@@ -97,6 +97,7 @@ class JSONRPC {
wlogger.info(
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}`
)
// console.log('parsedData: ', JSON.stringify(parsedData, null, 2))
}
// console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
@@ -113,6 +114,11 @@ class JSONRPC {
console.log('routing to BCH adapter')
retObj = await _this.adapters.bch.rpcHandler(parsedData)
}
if (parsedData.payload.result.method === 'file-pin') {
console.log('routing to IPFS Files adapter')
retObj = await _this.adapters.ipfsFiles.rpcHandler(parsedData)
}
} catch (err) {
/* exit quietly */
}
@@ -723,6 +723,33 @@ class BchRESTControllerLib {
}
}
/**
* @api {get} /bch/service Get the IPFS ID for the Wallet service
* @apiPermission public
* @apiName getService
* @apiGroup REST BCH
* @apiDescription Get the IPFS ID for the ipfs-bch-wallet-service node that
* this app is using to retrieve blockchain data from.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/bch/service
*
*/
async getService (ctx) {
try {
const selectedServiceProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
ctx.body = {
success: true,
selectedServiceProvider
}
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/getService(): ', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
+5
View File
@@ -63,6 +63,7 @@ class BchRouter {
this.router.post('/utxoIsValid', this.utxoIsValid)
this.router.post('/getTokenData', this.getTokenData)
this.router.post('/getTokenData2', this.getTokenData2)
this.router.get('/service', this.getService)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
@@ -116,6 +117,10 @@ class BchRouter {
async getTokenData2 (ctx, next) {
await _this.bchRESTController.getTokenData2(ctx, next)
}
async getService (ctx, next) {
await _this.bchRESTController.getService(ctx, next)
}
}
// module.exports = BchRouter
+130 -2
View File
@@ -3,6 +3,7 @@
*/
// Global npm libraries
import mime from 'mime-types'
// Local libraries
import wlogger from '../../../adapters/wlogger.js'
@@ -34,13 +35,17 @@ class IpfsRESTControllerLib {
this.handleError = this.handleError.bind(this)
this.connect = this.connect.bind(this)
this.getThisNode = this.getThisNode.bind(this)
this.viewFile = this.viewFile.bind(this)
this.getService = this.getService.bind(this)
this.getFileInfo = this.getFileInfo.bind(this)
this.getPins = this.getPins.bind(this)
}
/**
* @api {get} /ipfs Get status on IPFS infrastructure
* @apiPermission public
* @apiName GetIpfsStatus
* @apiGroup REST BCH
* @apiGroup REST IPFS
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs
@@ -107,7 +112,7 @@ class IpfsRESTControllerLib {
* @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord
* @apiPermission public
* @apiName GetThisNode
* @apiGroup REST BCH
* @apiGroup REST IPFS
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node
@@ -125,6 +130,129 @@ class IpfsRESTControllerLib {
}
}
/**
* @api {get} /ipfs/view/:cid Retrieve and display a file via its IPFS CID
* @apiPermission public
* @apiName GetCidView
* @apiGroup REST IPFS
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/view/bafkreieaqtdhfywyddomswogynzymukosqqgqo7lkt5lch2zwfnc55m6om
*
*/
async viewFile (ctx) {
try {
const { cid } = ctx.params
// const file = await this.adapters.ipfs.ipfs.blockstore.get(cid)
// return file
// const cid = ctx.params.cid
const { filename, readStream } = await this.useCases.ipfs.downloadCid({ cid })
// ctx.body = ctx.req.pipe(readStream)
// Lookup the mime type from the filename.
const contentType = mime.lookup(filename)
ctx.set('Content-Type', contentType)
ctx.set(
'Content-Disposition',
// 'inline; filename="' + filename + '"'
`inline; filename="${filename}"`
)
ctx.body = readStream
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/viewFile(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/service Get the IPFS ID for the File Pin service
* @apiPermission public
* @apiName GetService
* @apiGroup REST IPFS
* @apiDescription Get the IPFS ID for the ipfs-file-pin-service node that
* this app is using to retrieve file data from.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/service
*
*/
async getService (ctx) {
try {
const selectedIpfsFileProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
ctx.body = {
success: true,
selectedIpfsFileProvider
}
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/getService(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/file-info/:cid Get file pin info about a CID
* @apiPermission public
* @apiName GetFileInfo
* @apiGroup REST IPFS
* @apiDescription Get file metadata and pin status information give a CID.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/file-info/bafkreieaqtdhfywyddomswogynzymukosqqgqo7lkt5lch2zwfnc55m6om
*
*/
async getFileInfo (ctx) {
try {
const { cid } = ctx.params
const ipfsFiles = this.adapters.ipfsFiles
const metadata = await ipfsFiles.getFileMetadata({ cid })
console.log('getCidMetadata() metadata: ', metadata)
ctx.body = metadata
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/getService(): ', err)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/pins Get info on latest 20 pinned files
* @apiPermission public
* @apiName GetPins
* @apiGroup REST IPFS
* @apiDescription Returns an array of the latest 20 pinned files.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/pins
*
*/
async getPins (ctx) {
try {
// const { cid } = ctx.params
const ipfsFiles = this.adapters.ipfsFiles
const pinData = await ipfsFiles.getPins()
console.log('getPins() pinData: ', pinData)
ctx.body = pinData
} catch (err) {
// wlogger.error('Error in ipfs/controller.js/viewFile(): ', err)
console.log('Error in ipfs/controller.js/getPins(): ', err)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
+4
View File
@@ -56,6 +56,10 @@ class IpfsRouter {
this.router.post('/relays', this.ipfsRESTController.getRelays)
this.router.post('/connect', this.ipfsRESTController.connect)
this.router.get('/node', this.ipfsRESTController.getThisNode)
this.router.get('/view/:cid', this.ipfsRESTController.viewFile)
this.router.get('/service', this.ipfsRESTController.getService)
this.router.get('/file-info/:cid', this.ipfsRESTController.getFileInfo)
this.router.get('/pins', this.ipfsRESTController.getPins)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
+1 -1
View File
@@ -48,7 +48,7 @@ class TimerControllers {
// Replace this example function with your own timer handler.
exampleTimerFunc (negativeTest) {
try {
console.log('Example timer controller executed.')
// console.log('Example timer controller executed.')
if (negativeTest) throw new Error('test error')
+3
View File
@@ -4,7 +4,9 @@
https://troutsblog.com/blog/clean-architecture
*/
// Local libraries
import UserUseCases from './user.js'
import IpfsUseCases from './ipfs-use-cases.js'
class UseCases {
constructor (localConfig = {}) {
@@ -23,6 +25,7 @@ class UseCases {
// console.log('use-cases/index.js localConfig: ', localConfig)
this.user = new UserUseCases(localConfig)
this.ipfs = new IpfsUseCases(localConfig)
}
// Run any startup Use Cases at the start of the app.
+164
View File
@@ -0,0 +1,164 @@
/*
Use cases for working with IPFS.
*/
// Global npm libraries
// import Wallet from 'minimal-slp-wallet'
// import { CID } from 'multiformats'
// import RetryQueue from '@chris.troutner/retry-queue'
// import { exporter } from 'ipfs-unixfs-exporter'
import { Duplex } from 'stream'
// Local libraries
// import PinEntity from '../entities/pin.js'
// import config from '../../config/index.js'
// const PSF_TOKEN_ID = '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
class IpfsUseCases {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of adapters must be passed in when instantiating IPFS Use Cases library.'
)
}
// Encapsulate dependencies
// this.exporter = exporter
// Bind 'this' object to all class subfunctions.
this.downloadCid = this.downloadCid.bind(this)
// this.downloadCid2 = this.downloadCid2.bind(this)
}
// Download a pinned file, given its CID.
// Returns a readable stream.
async downloadCid (inObj = {}) {
try {
const { cid } = inObj
if (!cid) throw new Error('CID is undefined')
// const Pins = this.adapters.localdb.Pins
// let existingModel = await Pins.find({ cid })
// existingModel = existingModel[0]
// console.log('existingModel: ', existingModel)
// if (!existingModel) {
// throw new Error(`Database model for CID ${cid} does not exist.`)
// }
//
// if (!existingModel.dataPinned) {
// throw new Error('File has not been pinned. Not available.')
// }
const filename = await this.getCidMetadata({ cid })
const helia = this.adapters.ipfs.ipfs
// Convert the file to a Buffer.
const fileChunks = []
for await (const chunk of helia.fs.cat(cid)) {
fileChunks.push(chunk)
}
const fileBuf = Buffer.concat(fileChunks)
// Convert the Buffer into a readable stream
const bufferToStream = (myBuffer) => {
const tmp = new Duplex()
tmp.push(myBuffer)
tmp.push(null)
return tmp
}
const readStream = bufferToStream(fileBuf)
// const filename = 'test.jpg'
return { filename, readStream }
} catch (err) {
console.error('Error in use-cases/ipfs.js/dowloadCid()')
throw err
}
}
// Given a CID, this function will retrieve file metadata from the
// ipfs-file-pin-service, using the IPFS JSON-RPC. This metadata includes
// the filename, which can be used to infer mime-type, so that it can be
// delivered to a web browser.
async getCidMetadata (inObj = {}) {
try {
const { cid } = inObj
const ipfsFiles = this.adapters.ipfsFiles
const metadata = await ipfsFiles.getFileMetadata({ cid })
console.log('getCidMetadata() metadata: ', metadata)
if (metadata.success === false) {
throw new Error(metadata.message)
}
const filename = metadata.fileMetadata.filename
return filename
} catch (err) {
console.error('Error in getCidMetadata(): ', err)
throw err
}
}
// async downloadCid2 (inObj = {}) {
// try {
// const { cid } = inObj
//
// // console.log(`downloadFile() retrieving this CID: ${cid}, with fileName: ${fileName}, and path: ${path}`)
//
// const blockstore = this.adapters.ipfs.ipfs.blockstore
// const entry = await this.exporter(cid, blockstore)
//
// console.info(entry.cid) // Qmqux
// console.log('entry: ', entry)
// // console.info(entry.unixfs.fileSize()) // 4
//
// // const filePath = `${path}/${fileName}`
// // console.log(`filePath: ${filePath}`)
// // const writableStream = this.fs.createWriteStream(filePath)
// //
// // writableStream.on('error', this.writeStreamError)
// //
// // writableStream.on('finish', this.writeStreamFinished)
// //
//
// const fileChunks = []
// for await (const buf of entry.content()) {
// fileChunks.push(buf)
// }
// const fileBuf = Buffer.concat(fileChunks)
//
// //
// // writableStream.end()
//
// // Convert the Buffer into a readable stream
// const bufferToStream = (myBuffer) => {
// const tmp = new Duplex()
// tmp.push(myBuffer)
// tmp.push(null)
// return tmp
// }
// const readStream = bufferToStream(fileBuf)
//
// const filename = 'test.jpg'
//
// return { filename, readStream }
//
// // return { cid }
// } catch (err) {
// console.error('Error in ipfs-use-cases.js/downloadCid()')
// throw err
// }
// }
}
export default IpfsUseCases
+1 -1
View File
@@ -47,7 +47,7 @@ const peerData = [
'@type': 'WebAPI',
name: 'trout-bch-wallet-service-dev',
version: '1.11.12',
protocol: 'bch-wallet',
protocol: 'bch-wallet-service',
description:
'IPFS service providing BCH blockchain access needed by a wallet.',
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',