mirror of
https://github.com/fullstack-cash/fullstack-gatsby-theme-bch-wallet.git
synced 2026-09-21 16:52:06 -07:00
feat(IPFS): Added IPFS tab to web wallet
This commit is contained in:
Generated
+22893
-35232
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -38,7 +38,9 @@
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-jdenticon": "0.0.9",
|
||||
"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": {
|
||||
"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"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
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import Ipfs from './ipfs-tab'
|
||||
export default [
|
||||
{
|
||||
key: '',
|
||||
key: 'IPFS',
|
||||
icon: 'fas-message',
|
||||
component: <><p>test</p></>
|
||||
component: (
|
||||
<>
|
||||
<Ipfs />
|
||||
</>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user