通过React这个js前端框架来实现一个简易的页面,利用官方提供的js-ipfs-api,我们可以在页面的文本框中输入任意文本,然后提交上传到IPFS节点网络中,再利用hash参数把上传的文本数据显示出来。

React简介及安装

React是由Facebook和Instagram来开发的一种用来创建用户界面的JavaScript库。

React的简单入门见https://reactjs.org/tutorial/tutorial.html

首先你要保证电脑上已经安装了Node.js的最近版本,然后可以根据安装教程来创建新项目。

npm install -g create-react-app
sily@lyg-sily:~$ npm install -g create-react-app
/home/sily/.nvm/nvm/versions/node/v9.3.0/bin/create-react-app -> /home/sily/.nvm/nvm/versions/node/v9.3.0/lib/node_modules/create-react-app/index.js
+ create-react-app@1.5.2
added 114 packages in 26.067s
sily@lyg-sily:~$ create-react-app --version
1.5.2
React项目创建
sily@lyg-sily:~$ create-react-app ipfs-demo

Creating a new React app in /home/sily/ipfs-demo.

Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts...


> uglifyjs-webpack-plugin@0.4.6 postinstall /home/sily/ipfs-demo/node_modules/uglifyjs-webpack-plugin
> node lib/post_install.js

+ react-scripts@1.1.1
+ react@16.2.0
+ react-dom@16.2.0
added 1155 packages in 131.72s

Success! Created ipfs-demo at /home/sily/ipfs-demo
Inside that directory, you can run several commands:

  npm start
    Starts the development server.

  npm run build
    Bundles the app into static files for production.

  npm test
    Starts the test runner.

  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd ipfs-demo
  npm start

Happy hacking!

在项目根目录输入npm start命令,查看项目在浏览器的显示效果:

mark

安装js-ipfs-api
npm install --save ipfs-api

安装成功会在项目node_modules目录下多出ipfs-api文件夹,在package.json下增加依赖包信息:

mark

添加UI逻辑

修改src/App.js里面的代码,主要是render()中的页面布局:

import React, { Component } from 'react';
import './App.css';

class App extends Component {


      constructor(props) {
          super(props);
          this.state = {
            strHash: null,
            strContent: null
          }
      }

    render() {
      return (
        <div className="App">
          <input
            ref="ipfsContent"
            style=/>
          <button onClick={() => {
            let ipfsContent = this.refs.ipfsContent.value;
            console.log(ipfsContent);
          }}>提交到IPFS</button>

          <p>{this.state.strHash}</p>

          <button onClick={() => {
            console.log('从ipfs读取数据。')
           }}>读取数据</button>
           <h1>{this.state.strContent}</h1>
        </div>
      );
    }
}

export default App;

以上代码实现的功能是,当在输入框中输入文本时,再点击提交到IPFS按钮,将文本框中的内容取出来打印,后续需要将这个数据上传到IPFS。点击读取数据按钮,控制台打印出字符串,后面需要从IPFS读取数据,然后将读取的数据存储到状态机变量strContent中并展示出来。

导入IPFS
const ipfsAPI = require('ipfs-api');
const ipfs = ipfs = ipfsAPI({host: 'localhost', port: '5001', protocol: 'http'});
编写上传大文本字符串到IPFS的Promise函数
saveTextBlobOnIpfs = (blob) => {
    return new Promise(function(resolve, reject) {
      const descBuffer = Buffer.from(blob, 'utf-8');
      ipfs.add(descBuffer).then((response) => {
        console.log(response)
        resolve(response[0].hash);
      }).catch((err) => {
        console.error(err)
        reject(err);
      })
    })
  }

response[0].hash返回的是数据上传到IPFS后返回的HASH字符串。

上传数据到IPFS
this.saveTextBlobOnIpfs(ipfsContent).then((hash) => {
    console.log(hash);
    this.setState({strHash: hash});
});

ipfsContent是从文本框中取到的数据,调用this.saveTextBlobOnIpfs方法将数据上传后,会返回字符串hash,并且将hash存储到状态机变量strHash中。

跨域资源共享CORS配置

跨域资源共享(CORS)配置,依次在终端执行以下代码:

$ ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST", "OPTIONS"]'

$ ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["*"]'

$ ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]'

$ ipfs config --json API.HTTPHeaders.Access-Control-Allow-Headers '["Authorization"]'

$ ipfs config --json API.HTTPHeaders.Access-Control-Expose-Headers '["Location"]'

用正确的端口运行daemon
$ ipfs config Addresses.API
/ip4/127.0.0.1/tcp/5001
$ ipfs config Addresses.API /ip4/127.0.0.1/tcp/5001
$ ipfs daemon
从IPFS读取数据
ipfs.cat(this.state.strHash).then((stream) => {
    console.log(stream);
    let strContent = Utf8ArrayToStr(stream);
    console.log(strContent);
    this.setState({strContent: strContent});
});

编写Utf8ArrayToStr()方法将Uint8Array类型的stream变量转换成string字符串:

function Utf8ArrayToStr(array) {
  var out, i, len, c;
  var char2, char3;
  out = "";
  len = array.length;
  i = 0;
  while(i < len) {
    c = array[i++];
    switch(c >> 4) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        // 0xxxxxxx
        out += String.fromCharCode(c);
        break;
      case 12:
      case 13:
        // 110x xxxx 10xx xxxx
        char2 = array[i++];
        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
        break;
      case 14:
        // 1110 xxxx 10xx xxxx 10xx xxxx
        char2 = array[i++];
        char3 = array[i++];
        out += String.fromCharCode(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0));
        break;
      default:
        break;
    }
  }
  return out;
}

最终完整的App.js代码如下:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

const ipfsAPI = require('ipfs-api');
const ipfs = ipfsAPI({host:'localhost', port:'5001', protocal:'http'});

function Utf8ArrayToStr(array) {
  var out, i, len, c;
  var char2, char3;
  out = "";
  len = array.length;
  i = 0;
  while(i < len) {
    c = array[i++];
    switch(c >> 4) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        // 0xxxxxxx
        out += String.fromCharCode(c);
        break;
      case 12:
      case 13:
        // 110x xxxx 10xx xxxx
        char2 = array[i++];
        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
        break;
      case 14:
        // 1110 xxxx 10xx xxxx 10xx xxxx
        char2 = array[i++];
        char3 = array[i++];
        out += String.fromCharCode(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0));
        break;
      default:
        break;
    }
  }
  return out;
}

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      strHash: null,
      strContent: null
    }
  }

  saveTextBlobOnIpfs = (blob) => {
    return new Promise(function(resolve, reject) {
      const descBuffer = Buffer.from(blob, 'utf-8');
      ipfs.add(descBuffer).then((response) => {
        console.log(response)
        resolve(response[0].hash);
      }).catch((err) => {
        console.error(err)
        reject(err);
      })
    })
  }

  render() {
    return (
      <div className="App">
        <input
          ref="ipfsContent"
          style={{width:200,height:30}}/>
        <button onClick={() => {
          let ipfsContent = this.refs.ipfsContent.value;
          console.log(ipfsContent);
          this.saveTextBlobOnIpfs(ipfsContent).then((hash) => {
            console.log(hash);
            this.setState({strHash: hash});
          });
        }}>提交到IPFS</button>

        <p>{this.state.strHash}</p>

        <button onClick={() => {
          console.log('从ipfs读取数据')
          ipfs.cat(this.state.strHash).then((stream) => {
            console.log(stream);
            let strContent = Utf8ArrayToStr(stream);
            console.log(strContent);
            this.setState({strContent: strContent});
          });
        }}>读取数据</button>
        <h1>{this.state.strContent}</h1>
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

运行效果测试

mark

参考