feat(IPFS): Added IPFS tab to web wallet

This commit is contained in:
Daniel Gonzalez
2021-09-08 18:30:28 -04:00
parent 66a6c39d14
commit b7699c37d6
8 changed files with 23444 additions and 35236 deletions
+22893 -35232
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -38,7 +38,9 @@
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-jdenticon": "0.0.9", "react-jdenticon": "0.0.9",
"react-qr-reader": "^2.2.1", "react-qr-reader": "^2.2.1",
"react-redux": "^7.2.4" "react-redux": "^7.2.4",
"@chris.troutner/ipfs": "2.0.2",
"ipfs-coord": "^6.6.4"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^7.18.0", "eslint": "^7.18.0",
@@ -0,0 +1,165 @@
import React from 'react'
import { Content, Row, Col, Inputs, Box } from 'adminlte-2-react'
import IPFSTabs from './ipfs-tabs'
import IpfsControl from '../lib/ipfs-control'
// const BchWallet = typeof window !== 'undefined' ? window.SlpWallet : null
const { Checkbox } = Inputs
let _this
class IPFS extends React.Component {
constructor (props) {
super(props)
this.state = {
ipfsConnection: false
}
_this = this
}
render () {
const { ipfsConnection } = _this.state
return (
<Content>
<Row>
<Col sm={2} />
<Col sm={8}>
<Box className='text-center'>
<Checkbox
value={ipfsConnection} // mark as checked
text='Connect to wallet services over IPFS'
labelPosition='none'
labelXs={0}
name='ipfsConnection'
onChange={this.handleIpfs}
/>
</Box>
</Col>
<Col sm={2} />
</Row>
{ipfsConnection && <IPFSTabs />}
</Content>
)
}
handleIpfs () {
_this.setState(prevState => ({
ipfsConnection: !_this.state.ipfsConnection
}))
}
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
}
// Retrieve last ipfs control
const { data } = _this.props.menuNavigation
if (data && data.chatInfo.ipfsControl) {
this.ipfsControl = data.chatInfo.ipfsControl
} else {
// Instantiate a new ipfs control
this.ipfsControl = new IpfsControl(ipfsConfig)
}
} catch (err) {
console.error(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)
}
}
// Adds a line to the Status terminal
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 decrypted, private messages and send them to the right terminal.
privLogChat (str, from) {
try {
// console.log(`privLogChat str: ${str}`)
// console.log(`privLogChat from: ${from}`)
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)
}
}
// 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.
}
}
}
IPFS.propTypes = {}
export default IPFS
@@ -0,0 +1,67 @@
import React from 'react'
import { Tabs, Row, Col, TabContent } from 'adminlte-2-react'
// let _this
class IPFSTabs extends React.Component {
constructor (props) {
super(props)
this.state = {}
// _this = this
}
render () {
return (
<Row>
<Col md={12}>
<Tabs defaultActiveKey='tab_1' activeKey='tab_2'>
<TabContent title='Tab 1' eventKey='tab_1'>
<b>How to use:</b>
<p>
Exactly like the original bootstrap tabs except you should use
the custom wrapper <code>.nav-tabs-custom</code> to achieve this
style.
</p>
A wonderful serenity has taken possession of my entire soul, like
these sweet mornings of spring which I enjoy with my whole heart.
I am alone, and feel the charm of existence in this spot, which
was created for the bliss of souls like mine. I am so happy, my
dear friend, so absorbed in the exquisite sense of mere tranquil
existence, that I neglect my talents. I should be incapable of
drawing a single stroke at the present moment; and yet I feel that
I never was a greater artist than now.
</TabContent>
<TabContent title='Tab 2' eventKey='tab_2'>
The European languages are members of the same family. Their
separate existence is a myth. For science, music, sport, etc,
Europe uses the same vocabulary. The languages only differ in
their grammar, their pronunciation and their most common words.
Everyone realizes why a new common language would be desirable:
one could refuse to pay expensive translators. To achieve this, it
would be necessary to have uniform grammar, pronunciation and more
common words. If several languages coalesce, the grammar of the
resulting language is more simple and regular than that of the
individual languages.
</TabContent>
<TabContent title='Tab 3' eventKey='tab_3'>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry&quot;s standard dummy
text ever since the 1500s, when an unknown printer took a galley
of type and scrambled it to make a type specimen book. It has
survived not only five centuries, but also the leap into
electronic typesetting, remaining essentially unchanged. It was
popularised in the 1960s with the release of Letraset sheets
containing Lorem Ipsum passages, and more recently with desktop
publishing software like Aldus PageMaker including versions of
Lorem Ipsum.
</TabContent>
</Tabs>
</Col>
</Row>
)
}
}
IPFSTabs.propTypes = {}
export default IPFSTabs
@@ -0,0 +1,139 @@
/*
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.ipfs.peers.state.peers,
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.ipfs.cr.state.relays,
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
@@ -0,0 +1,170 @@
/*
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'
// CHANGE THESE VARIABLES
const CHAT_ROOM_NAME = 'psf-ipfs-chat-001'
// 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 = 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(' ')
} 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
}
}
}
// module.exports = AppIpfs
export default IpfsControl
@@ -3,11 +3,15 @@
// export default MenuComponents // export default MenuComponents
import React from 'react' import React from 'react'
import Ipfs from './ipfs-tab'
export default [ export default [
{ {
key: '', key: 'IPFS',
icon: 'fas-message', icon: 'fas-message',
component: <><p>test</p></> component: (
<>
<Ipfs />
</>
)
} }
] ]