mirror of
https://github.com/fullstack-cash/fullstack-gatsby-theme-bch-wallet.git
synced 2026-09-21 16:52:06 -07:00
Merge pull request #1 from Permissionless-Software-Foundation/dh-ipfs-coord
feat(ipfs-coord): Pull out ipfs-coord stuff
This commit is contained in:
Generated
+6339
-46138
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,6 @@
|
||||
"gatsby-source-filesystem": "^4.1.0",
|
||||
"gatsby-transformer-sharp": "^4.1.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"ipfs-coord": "^6.7.4",
|
||||
"jsonrpc-lite": "^2.2.0",
|
||||
"p-queue": "^7.1.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 Ipfs from './ipfs-tab'
|
||||
// import React from 'react'
|
||||
const MenuComponents = props => {
|
||||
return [
|
||||
{
|
||||
key: 'IPFS',
|
||||
icon: 'fas-message',
|
||||
component: (
|
||||
<>
|
||||
<Ipfs {...props} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
]
|
||||
return []
|
||||
}
|
||||
export default MenuComponents
|
||||
|
||||
Reference in New Issue
Block a user