feat(ipfs-coord): Pull out ipfs-coord stuff

This commit is contained in:
Daniel Gonzalez
2021-11-29 21:02:36 -04:00
parent bebba0a8a1
commit 0c0e20484c
9 changed files with 6341 additions and 47349 deletions
+6326 -46125
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -37,7 +37,6 @@
"gatsby-source-filesystem": "^4.1.0", "gatsby-source-filesystem": "^4.1.0",
"gatsby-transformer-sharp": "^4.1.0", "gatsby-transformer-sharp": "^4.1.0",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"ipfs-coord": "^6.7.4",
"jsonrpc-lite": "^2.2.0", "jsonrpc-lite": "^2.2.0",
"p-queue": "^7.1.0", "p-queue": "^7.1.0",
"p-retry": "^5.0.0", "p-retry": "^5.0.0",
@@ -1,404 +0,0 @@
import React from 'react'
import { Content, Row, Col, Inputs, Box } from 'adminlte-2-react'
import IpfsControl from '../lib/ipfs-control'
import IPFSTabs from './ipfs-tabs'
import CommandRouter from '../lib/commands'
import './ipfs.css'
import RetryQueue from '../../../../lib/retry-queue.mjs'
const queue = new RetryQueue()
const WalletService = require('../lib/wallet-service')
const BchWallet = typeof window !== 'undefined' ? window.SlpWallet : null
const { Checkbox } = Inputs
let _this
class IPFS extends React.Component {
constructor (props) {
super(props)
this.state = {
isStarted: false,
ipfsConnection: false,
appStatusOutput: '',
statusOutput: '',
commandOutput: "Enter 'help' to see available commands.",
commandInput: '',
peers: [],
chatOutputs: {
All: {
output: '',
nickname: ''
}
}
}
_this = this
_this.BchWallet = BchWallet
_this.WalletService = WalletService
this.initIPFSControl()
}
render () {
const { ipfsConnection } = _this.state
return (
<Content>
<Row>
<Col sm={12}>
<Box className='text-center ipfs-checkbox-container'>
<Checkbox
id='ipfs-checkbox'
className='ipfs-checkbox'
value={ipfsConnection} // mark as checked
text='Connect to wallet services over IPFS'
labelPosition='none'
labelXs={0}
name='ipfsConnection'
onChange={this.handleIpfs}
/>
</Box>
</Col>
</Row>
{ipfsConnection && (
<IPFSTabs
ipfsControl={_this.ipfsControl}
handleCommandLog={_this.onCommandLog}
commandOutput={_this.state.commandOutput}
statusOutput={_this.state.statusOutput}
appStatusOutput={_this.state.appStatusOutput}
/>
)}
</Content>
)
}
async handleIpfs () {
const connect = !_this.state.ipfsConnection
const { isStarted } = _this.state
_this.setState(prevState => ({
ipfsConnection: connect
}))
// Save checkbox state into localstorage
const { walletInfo } = _this.props
walletInfo.ipfsService = connect
_this.props.setWalletInfo(walletInfo)
if (!isStarted && connect) {
try {
await _this.ipfsControl.startIpfs()
const nodeInfo = _this.ipfsControl.getNodeInfo()
console.log('nodeInfo', nodeInfo)
_this.setState({
isStarted: true
})
} catch (error) {
console.warn(error.message)
}
}
}
async componentDidMount () {
await _this.getLastIpfsCoordInstance()
}
async componentWillUnmount () {
try {
const data = {
ipfsInfo: {
ipfsIsStarted: true,
savedState: _this.state,
ipfsControl: _this.ipfsControl
}
}
// Save the current state
_this.props.setMenuNavigation({ data })
} catch (error) {
console.warn(error)
}
}
async getLastIpfsCoordInstance () {
try {
const { menuNavigation, walletInfo } = _this.props
const data = menuNavigation.data
// console.log('menuNavigation', menuNavigation)
// console.log('walletInfo', walletInfo)
// console.log('data', data)
// Get local info and
// Verify if checkbox has been marked
// Return for unmarked checkbox
if (!walletInfo.ipfsService) return
_this.setState(prevState => ({
ipfsConnection: true
}))
// Don't start ipfs if it has started already
if (!data || !data.ipfsInfo.ipfsIsStarted) {
await this.ipfsControl.startIpfs()
_this.setState({
isStarted: true
})
}
// Loads the previous information and states
if (data && data.ipfsInfo) {
const { savedState } = data.ipfsInfo
_this.setState(savedState)
}
} catch (error) {
console.warn(error)
}
}
initIPFSControl (bchWallet) {
try {
const ipfsConfig = {
statusLog: _this.onStatusLog,
// handleChatLog: _this.onCommandLog
handleChatLog: _this.incommingChat,
bchWallet: bchWallet || _this.props.bchWallet, // bch wallet instance
privateLog: _this.privLogChat,
pollLog: _this.onAppStatus
}
// Retrieve last ipfs control
const { menuNavigation } = _this.props
if (
menuNavigation &&
menuNavigation.data &&
menuNavigation.data.ipfsInfo.ipfsControl
) {
this.ipfsControl = menuNavigation.data.ipfsInfo.ipfsControl
} else {
// Instantiate a new ipfs control
this.ipfsControl = new IpfsControl(ipfsConfig)
}
this.commandRouter = new CommandRouter({ ipfsControl: this.ipfsControl })
} catch (err) {
console.error(err)
}
}
// Adds a line to the Status terminal
onStatusLog (str) {
console.log('onStatusLog', str)
try {
// Update the Status terminal
_this.setState({
statusOutput: _this.state.statusOutput + ' ' + str + '\n'
})
// If a new peer is found, trigger handleNewPeer()
if (str.includes('New peer found:')) {
const ipfsId = str.substring(24)
_this.handleNewPeer(ipfsId)
}
} catch (error) {
console.warn(error)
}
}
// Handle chat messages coming in from the IPFS network.
incommingChat (str) {
try {
const { chatOutputs, connectedPeer } = _this.state
// console.log(`connectedPeer: ${JSON.stringify(connectedPeer, null, 2)}`)
// console.log(`incommingChat str: ${JSON.stringify(str, null, 2)}`)
const msg = str.data.data.message
const handle = str.data.data.handle
const terminalOut = `${handle}: ${msg}`
if (str.data && str.data.apiName && str.data.apiName.includes('chat')) {
// If the message is marked as 'chat' data, then post it to the public
// chat terminal.
chatOutputs.All.output = chatOutputs.All.output + terminalOut + '\n'
} else {
// Asigns the output to the corresponding peer
chatOutputs[connectedPeer].output =
chatOutputs[connectedPeer].output + terminalOut + '\n'
}
_this.setState({
chatOutputs
})
} catch (err) {
console.warn(err)
// Don't throw an error as this is a top-level handler.
}
}
// Handle decrypted, private messages and send them to the right terminal.
privLogChat (str, from) {
try {
console.log(`privLogChat str: ${str}`)
// console.log(`privLogChat from: ${from}`)
_this.handleRpcDataQueue(str)
const { chatOutputs } = _this.state
const terminalOut = `peer: ${str}`
// Asigns the output to the corresponding peer
chatOutputs[from].output = chatOutputs[from].output + terminalOut + '\n'
_this.setState({
chatOutputs
})
} catch (err) {
console.warn('Error in privLogChat():', err)
}
}
// This function is triggered when a new peer is detected.
handleNewPeer (ipfsId) {
try {
console.log(`New IPFS peer discovered. ID: ${ipfsId}`)
// Use the peer IPFS ID to identify the peers state.
const { peers, chatOutputs } = _this.state
// Add the new peer to the peers array.
peers.push(ipfsId)
// Add a chatOutput entry for the new peer.
const obj = {
output: '',
nickname: ''
}
// chatOutputs[shortIpfsId] = obj
chatOutputs[ipfsId] = obj
_this.setState({
peers,
chatOutputs
})
} catch (err) {
console.warn('Error in handleNewPeer(): ', err)
}
}
// Adds a line to the Command terminal
onCommandLog (msg) {
try {
let commandOutput
if (!msg) {
commandOutput = ''
} else {
commandOutput = _this.state.commandOutput + ' ' + msg + '\n'
}
_this.setState({
commandOutput
})
} catch (error) {
console.warn(error)
}
}
// pollForServices callback
// This method is called from lib/ipfs-control.js. It executes when a BCH
// wallet service is found.
async onAppStatus (msg) {
try {
let output
if (!msg) {
output = ''
} else {
output = _this.state.appStatusOutput + ' ' + msg + '\n'
}
_this.setState({
appStatusOutput: output
})
// Updates the bchWallet instance so it works
// under the ipfs services
await _this.reInitialize()
} catch (error) {
console.warn(error)
// Don't throw an error as this is a top-level handler.
}
}
// Updates the bchWallet instance so it works
// under the ipfs services
async reInitialize () {
try {
_this.onStatusLog('waiting for re-initialize...')
const currentWallet = _this.props.walletInfo
const { mnemonic } = currentWallet
const walletService = new _this.WalletService({
ipfsControl: this.ipfsControl
})
const advancedConfig = {
interface: 'json-rpc',
jsonRpcWalletService: walletService
}
// Initialize the wallet, using auth-rety on failure.
const walletIn = { mnemonic, advancedConfig }
await queue.retryWrapper(_this.initWallet, walletIn)
// Update redux state
_this.props.setBchWallet(_this.bchWalletLib)
_this.onStatusLog('re-initialize success!')
} catch (error) {
_this.onStatusLog('Error in reInitialize()')
_this.onStatusLog(error.message)
// Don't throw an error as this is a top-level handler.
}
}
// Initialize the wallet and retrieve the UTXOs for the wallet.
// This function is called by the queue library, to do automatic retry on
// network failure.
async initWallet (walletIn) {
try {
const { mnemonic, advancedConfig } = walletIn
_this.bchWalletLib = new _this.BchWallet(mnemonic, advancedConfig)
// Wait for wallet to be created.
await _this.bchWalletLib.walletInfoPromise
// If auto UTXO initialization fails, do it manually.
let utxos = _this.bchWalletLib.utxos.utxoStore
if (!utxos) {
utxos = await _this.bchWalletLib.getUtxos()
}
_this.onStatusLog(
`utxo initialization succeeded: ${JSON.stringify(utxos, null, 2)}`
)
return _this.bchWalletLib
} catch (err) {
console.error('Error in initWallet(): ', err)
}
}
// Fill the queue with the incoming data (private messages) from the petitions
// So it will be sweeped by the lib/wallet-service/waitForRPCResponse() function
handleRpcDataQueue (data) {
try {
_this.bchWalletLib.ar.jsonRpcWalletService.rpcHandler(data)
} catch (error) {
console.warn('Error in handleRpcDataQueue', error)
// Don't throw an error as this is a top-level handler.
}
}
sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
IPFS.propTypes = {}
export default IPFS
@@ -1,180 +0,0 @@
import React from 'react'
import { Row, Col, Inputs } from 'adminlte-2-react'
import { Tabs, Tab } from 'react-bootstrap'
import PropTypes from 'prop-types'
import CommandRouter from '../lib/commands'
import 'adminlte-2-react/src/adminlte/css/AdminLTE.css'
const { Text } = Inputs
let _this
class IPFSTabs extends React.Component {
constructor (props) {
super(props)
this.state = {
commandOutput: "Enter 'help' to see available commands."
}
_this = this
// Starts ipfs control if there is a wallet registered already
// console.log('props', props)
if (props && props.ipfsControl) {
this.ipfsControl = props.ipfsControl
this.commandRouter = new CommandRouter({ ipfsControl: this.ipfsControl })
// console.log('this.commandRouter: ', this.commandRouter)
}
}
render () {
const { commandOutput } = _this.state
const { statusOutput, appStatusOutput } = _this.props
return (
<Row>
<Col md={12}>
<Tabs
defaultActiveKey='status'
id='ipfs-coord-tabs'
className='mb-3 nav-tabs-custom'
>
<Tab eventKey='status' title='Status'>
<Text
id='statusLog'
name='statusLog'
inputType='textarea'
labelPosition='none'
rows={20}
readOnly
value={appStatusOutput}
onChange={() => {
// Prevents DOM error
}}
/>
</Tab>
<Tab eventKey='ipfs-coord' title='IPFS Coord'>
<Text
id='ipfsCoordLog'
name='ipfsCoordLog'
inputType='textarea'
labelPosition='none'
rows={20}
readOnly
value={statusOutput}
onChange={() => {
// Prevents DOM error
}}
/>
</Tab>
<Tab eventKey='command' title='Command'>
<Text
id='commandLog'
name='commandLog'
inputType='textarea'
labelPosition='none'
rows={20}
readOnly
value={`${commandOutput ? `${commandOutput}>` : '>'}`}
onChange={() => {
// Prevents DOM error
}}
/>
<Text
id='commandInput'
name='commandInput'
inputType='tex'
labelPosition='none'
value={this.state.commandInput}
onChange={this.handleTextInput}
onKeyDown={_this.handleCommandKeyDown}
/>
</Tab>
</Tabs>
</Col>
</Row>
)
}
// Handles text typed into the input box.
handleTextInput (event) {
event.preventDefault()
const target = event.target
const value = target.value
const name = target.name
// console.log('value: ', value)
_this.setState({
[name]: value
})
}
componentDidUpdate () {
if (_this.state.commandOutput !== _this.props.commandOutput) {
_this.setState({
commandOutput: _this.props.commandOutput
})
}
}
/* START command handling functions */
// Handles when the Enter key is pressed while in the chat input box.
async handleCommandKeyDown (e) {
if (e.key === 'Enter') {
// _this.submitMsg()
// console.log("Enter key");
// Send a chat message to the chat pubsub room.
// const now = new Date();
// const msg = `Message from BROWSER at ${now.toLocaleString()}`
const msg = _this.state.commandInput
// console.log(`Sending this message: ${msg}`);
// _this.handleCommandLog(`me: ${msg}`);
// console.log('_this.commandRouter: ', _this.commandRouter)
const outMsg = await _this.commandRouter.route(msg, _this.ipfsControl)
if (outMsg === 'clear') {
_this.props.handleCommandLog('')
} else {
_this.handleCommandLog(`\n${outMsg}`)
}
// Clear the input text box.
_this.setState({
commandInput: ''
})
}
}
// Adds a line to the terminal
async handleCommandLog (msg) {
try {
// console.log("msg: ", msg);
_this.props.handleCommandLog(msg)
// Add a slight delay, to give the browser time to render the DOM.
await this.sleep(250)
// _this.keepScrolled();
// _this.keepCommandScrolled()
} catch (error) {
console.warn(error)
}
}
sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
}
IPFSTabs.propTypes = {}
IPFSTabs.propTypes = {
handleCommandLog: PropTypes.func,
commandOutput: PropTypes.string,
statusOutput: PropTypes.string,
appStatusOutput: PropTypes.string
}
export default IPFSTabs
@@ -1,14 +0,0 @@
.ipfs-checkbox-container > div > div > div >input{
height: 1em;
width: 1em;
cursor: pointer;
margin-right: 5px;
}
.ipfs-checkbox-container > div > div > div {
display: flex;
justify-content: center;
align-items: center;
}
@@ -1,139 +0,0 @@
/*
Handles commands from command terminal.
*/
let _this
class CommandRouter {
constructor (cmdConfig) {
if (cmdConfig && cmdConfig.ipfsControl) {
this.ipfsControl = cmdConfig.ipfsControl
}
_this = this
}
// Parse and route a command to the proper handler.
async route (command, appIpfs) {
try {
// console.log(`command: ${command}`)
// Split the command into an array of words separated by a space
const words = command.toString().split(' ')
// console.log(`words: ${JSON.stringify(words, null, 2)}`)
switch (words[0]) {
case 'help':
return this.help()
case 'list':
return await this.list(command, appIpfs)
case 'clear':
return 'clear'
case 'pubsub':
return await this.pubsub(command, appIpfs)
default:
return ''
}
} catch (err) {
console.error('Error in commandRouter()')
throw err
}
}
// Display the help
help () {
const msg = `
Available commands:
- help - this help.
- clear - clear the command terminal.
- list peers - list all known ipfs-coord peers.
- list relays - list all known circuit relays and their state.
- pubsub list - list all subscribed pubsub channels.
`
return msg
}
async list (command, appIpfs) {
const words = command.toString().split(' ')
switch (words[1]) {
case 'relays':
return this.listRelays(_this.ipfsControl)
case 'peers':
return this.listPeers(_this.ipfsControl)
default:
return ''
}
}
async pubsub (command, appIpfs) {
const words = command.toString().split(' ')
switch (words[1]) {
case 'list':
return this.listPubsubChannels(appIpfs)
default:
return ''
}
}
// List known ipfs-coord peers.
async listPeers (appIpfs) {
try {
// console.log('appIpfs: ', appIpfs)
const relays = `Known ipfs-coord peers:\n${JSON.stringify(
appIpfs.ipfsCoord.thisNode.peerData,
null,
2
)}`
return relays
// return "test"
} catch (err) {
console.error('Error in listPeers(): ', err)
return 'Error in listPeers()'
}
}
// List the relays connected to this IPFS node.
async listRelays (appIpfs) {
try {
// console.log('appIpfs: ', appIpfs)
const relays = `Known Circuit Relays:\n${JSON.stringify(
appIpfs.ipfsCoord.thisNode.relayData,
null,
2
)}`
return relays
// return "test"
} catch (err) {
console.error('Error in listRelays(): ', err)
return 'Error in listRelays()'
}
}
async listPubsubChannels (appIpfs) {
try {
// console.log('appIpfs: ', appIpfs)
const channels = await appIpfs.ipfs.pubsub.ls()
const outStr = `Pubsub Subscriptions:\n${JSON.stringify(
channels,
null,
2
)}`
return outStr
} catch (err) {
console.error('Error in listPubsubChannels(): ', err)
return 'Error in listPubsubChannels()'
}
}
}
export default CommandRouter
@@ -1,235 +0,0 @@
/*
This library controls the IPFS interface for the app.
*/
/*
This library contains the logic around the browser-based IPFS full node.
*/
import IPFS from '@chris.troutner/ipfs'
import IpfsCoord from 'ipfs-coord'
const semver = require('semver')
// CHANGE THESE VARIABLES
const CHAT_ROOM_NAME = 'psf-ipfs-chat-001'
const MIN_BCH_WALLET_VERSION = '1.11.0'
const WALLET_PROTOCOL = 'bch-wallet'
// JSON-LD schema used in announcement.
// Customize this data for your own app.
const name = 'Browser Chat ' + Math.floor(Math.random() * 1000)
const announceJsonLd = {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: name,
description: 'This is a browser-based IPFS node.',
documentation: '',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
}
}
let _this
class IpfsControl {
constructor (ipfsConfig) {
this.statusLog = ipfsConfig.statusLog
this.handleChatLog = ipfsConfig.handleChatLog
this.wallet = ipfsConfig.bchWallet
this.privateLog = ipfsConfig.privateLog
this.pollLog = ipfsConfig.pollLog
this.serviceProviders = []
this.selectedServiceProvider = null
this.semver = semver
_this = this
}
// Top level function for controlling the IPFS node. This funciton is called
// by the componentDidMount() function of the page.
async startIpfs () {
try {
console.log('Setting up instance of IPFS...')
this.statusLog('Setting up instance of IPFS...')
// Use DHT routing and ipfs.io delegates.
const ipfsOptions = {
config: {
Bootstrap: [],
Swarm: {
ConnMgr: {
HighWater: 30,
LowWater: 10
},
AddrFilters: []
},
Routing: {
Type: 'dhtclient'
},
preload: {
enabled: false
},
offline: true
},
libp2p: {
config: {
dht: {
enabled: true,
clientMode: true
}
}
}
}
// const ipfsOptions = {
// Bootstrap: [],
// Swarm: {
// ConnMgr: {
// HighWater: 30,
// LowWater: 10
// },
// AddrFilters: []
// }
// }
this.ipfs = await IPFS.create(ipfsOptions)
this.statusLog('IPFS node created.')
// Set a 'low-power' profile for the IPFS node.
await this.ipfs.config.profiles.apply('lowpower')
// Generate a new wallet.
// this.wallet = new BchWallet()
// console.log("this.wallet: ", this.wallet);
if (!this.wallet) {
throw new Error('Wallet Not Found.! . Create or import a wallet')
}
// Wait for the wallet to initialize.
await this.wallet.walletInfoPromise
// Instantiate the IPFS Coordination library.
this.ipfsCoord = new IpfsCoord({
ipfs: this.ipfs,
type: 'browser',
statusLog: this.statusLog, // Status log
bchjs: this.wallet.bchjs,
mnemonic: this.wallet.walletInfo.mnemonic,
privateLog: this.privateLog,
announceJsonLd
})
this.statusLog('ipfs-coord library instantiated.')
// Wait for the coordination stuff to be setup.
await this.ipfsCoord.start()
const nodeConfig = await this.ipfs.config.getAll()
console.log(
`IPFS node configuration: ${JSON.stringify(nodeConfig, null, 2)}`
)
// subscribe to the 'chat' chatroom.
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
CHAT_ROOM_NAME,
this.handleChatLog,
this.ipfsCoord.thisNode
)
// Pass the IPFS instance to the window object. Makes it easy to debug IPFS
// issues in the browser console.
if (typeof window !== 'undefined') window.ipfs = this.ipfs
// Get this nodes IPFS ID
const id = await this.ipfs.id()
this.ipfsId = id.id
this.statusLog(`This IPFS node ID: ${this.ipfsId}`)
console.log('IPFS node setup complete.')
this.statusLog('IPFS node setup complete.')
_this.statusLog(' ')
setInterval(this.pollForServices, 10000)
} catch (err) {
console.error('Error in startIpfs(): ', err)
this.statusLog(
'Error trying to initialize IPFS node! Have you created a wallet?'
)
}
}
// This funciton handles incoming chat messages.
handleChatMsg (msg) {
try {
console.log('handleChatMsg msg: ', msg)
} catch (err) {
console.error('Error in handleChatMsg(): ', err)
}
}
getNodeInfo () {
return {
ipfsId: this.ipfsId,
announceJsonLd
}
}
// Poll the ipfs-coord coordination channel for available service providers.
pollForServices () {
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.serviceProviders.length === 0) {
_this.selectedServiceProvider = thisPeer
// Persist the config setting, so it can be used by other commands.
// _this.conf.set('selectedService', thisPeer)
const pollLog = `---->BCH wallet service selected: ${thisPeer}`
console.log(pollLog)
_this.pollLog(pollLog)
}
// Add the peer to the list of serviceProviders.
_this.serviceProviders.push(thisPeer)
}
}
} catch (err) {
console.error('Error in pollForServices(): ', err)
// Do not throw error. This is a top-level function.
}
}
}
// module.exports = AppIpfs
export default IpfsControl
@@ -1,225 +0,0 @@
/*
This library interacts with the ipfs-bch-wallet-service via the JSON RPC
over IPFS.
*/
const { v4: uid } = require('uuid')
const jsonrpc = require('jsonrpc-lite')
// Public npm libraries.
const axios = require('axios')
// const Conf = require('conf')
class WalletService {
constructor (localConfig = {}) {
// Encapsulate dependencies
this.axios = axios
// this.conf = new Conf()
// this.ipfsControl = localConfig.ipfsControl
this.uid = uid
this.jsonrpc = jsonrpc
this.ipfsControl = localConfig.ipfsControl
// A queue for holding RPC data that has arrived.
this.rpcDataQueue = []
}
// This handler is triggered when RPC data comes in over IPFS.
// Handle RPC input, and match the input to the RPC queue.
// NOTE: This function is called when a private message
// is sent to this node
rpcHandler (data) {
try {
// Convert string input into an object.
const jsonData = JSON.parse(data)
// console.log(
// 'rest-api.js/rpcHandler() data: ',
// JSON.stringify(jsonData, null, 2),
// )
console.log(`JSON RPC response for ID ${jsonData.id} received.`)
this.rpcDataQueue.push(jsonData)
} catch (err) {
console.error('Error in rest-api.js/rpcHandler(): ', err)
// Do not throw error. This is a top-level function.
}
}
checkServiceId () {
try {
// this.conf = new Conf()
const serviceId = this.ipfsControl.selectedServiceProvider
console.log(`serviceId : ${serviceId}`)
if (!serviceId) {
throw new Error('Wallet service ID does not exist')
}
return serviceId
} catch (error) {
console.error('Error in checkServiceId()')
throw error
}
}
// Get up to 20 addresses.
async getBalances (addrs) {
try {
// Input validation.
if (!addrs || !Array.isArray(addrs)) {
throw new Error(
'addrs input to getBalance() must be an array, of up to 20 addresses.'
)
}
const serviceId = this.checkServiceId()
// console.log(`serviceId: ${serviceId}`)
const rpcId = this.uid()
const rpcData = {
endpoint: 'balance',
addresses: addrs
}
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
const thisNode = this.ipfsControl.ipfsCoord.thisNode
await this.ipfsControl.ipfsCoord.useCases.peer.sendPrivateMessage(
serviceId,
cmdStr,
thisNode
)
const data = await this.waitForRPCResponse(rpcId)
return data
} catch (err) {
console.error('Error in getBalance()')
throw err
}
}
// Get hydrated UTXOs for an address
async getUtxos (addr) {
try {
// Input validation
if (!addr || typeof addr !== 'string') {
throw new Error('getUtxos() input address must be a string.')
}
const serviceId = this.checkServiceId()
// console.log(`serviceId: ${serviceId}`)
const rpcId = this.uid()
const rpcData = {
endpoint: 'utxos',
address: addr
}
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
const thisNode = this.ipfsControl.ipfsCoord.thisNode
console.log('cmdStr', cmdStr)
await this.ipfsControl.ipfsCoord.useCases.peer.sendPrivateMessage(
serviceId,
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 getUtxos()', err)
throw err
}
}
// Broadcast a transaction to the network.
async sendTx (hex) {
try {
// Input validation
if (!hex || typeof hex !== 'string') {
throw new Error('sendTx() input hex must be a string.')
}
const serviceId = this.checkServiceId()
// console.log(`serviceId: ${serviceId}`)
const rpcId = this.uid()
const rpcData = {
endpoint: 'broadcast',
hex
}
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
const cmdStr = JSON.stringify(cmd)
console.log('cmdStr', cmdStr)
const thisNode = this.ipfsControl.ipfsCoord.thisNode
await this.ipfsControl.ipfsCoord.useCases.peer.sendPrivateMessage(
serviceId,
cmdStr,
thisNode
)
const data = await this.waitForRPCResponse(rpcId)
return data
} catch (err) {
console.error('Error in sendTx()')
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
let data = {
success: false,
message: 'request timed out',
data: ''
}
// Loop that waits for a response from the service provider.
do {
for (let i = 0; i < this.rpcDataQueue.length; i++) {
const rawData = this.rpcDataQueue[i]
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
if (rawData.id === rpcId) {
dataFound = true
// console.log('data was found in the queue')
data = rawData.result.value
// Remove the data from the queue
this.rpcDataQueue.splice(i, 1)
break
}
}
// Wait between loops.
// await this.sleep(1000)
await this.ipfsControl.wallet.bchjs.Util.sleep(4500)
cnt++
// Exit if data was returned, or the window for a response expires.
} while (!dataFound && cnt < 10)
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
console.log('waitForRPCResponse', data)
return data
} catch (err) {
console.error('Error in waitForRPCResponse()')
throw err
}
}
}
module.exports = WalletService
@@ -1,16 +1,5 @@
import React from 'react' // import React from 'react'
import Ipfs from './ipfs-tab'
const MenuComponents = props => { const MenuComponents = props => {
return [ return []
{
key: 'IPFS',
icon: 'fas-message',
component: (
<>
<Ipfs {...props} />
</>
)
}
]
} }
export default MenuComponents export default MenuComponents