Blockstack.js 工具教程

在本教程中,我们将使用Gaia存储系统构建一个区块链微博应用程序,从而使用户提交的文字和图片没有发布到中心服务器中,而是在自己的本地电脑。
此应用程序将是一个完全分散且无需服务器的access . js应用程序。标识和存储服务将由blockstack提供,

应用程序将能够执行以下操作:

  • 使用blockstack验证用户
  • 用户发布类似传统微博(微信朋友圈)样式的新状态
  • 在用户配置文件中显示状态
  • 查找其他用户并查看其配置文件和状态

我们将使用以下工具:

  • 用于管理依赖关系和脚本的NPM
  • yo为块堆栈反应应用程序生成样板文件
  • 用于验证用户和访问Gaia存储的blockstack

对于经验丰富的blockstack开发人员:

  • 添加publish _ data作用域以登录请求
  • 使用getFile ( ' filename . JSON ',{username: ' username . id ',decrypt: false } )从其他用户读取文件。
  • 使用lookupProfile ( 'username. id ' )查找用户配置文件
  • 如前所述使用putFile ( ' filename . JSON ',file,options ),其中选项设置为{ encrypt : false },以便禁用加密,其他人可以读取您的文件。

安装与生成

首先,安装Yeoman以及blockstack应用程序生成器:

npm install -g yo generator-blockstack

接下来,为我们的应用程序创建一个目录,取个新的名字Publik :

mkdir publik && cd publik

然后,使用blockstack应用程序生成器生成一个简单的blockstack应用程序:

yo blockstack:react

响应提示后,应用程序生成器将创建所有应用程序文件,然后安装所有依赖项。

要在本地运行应用程序:

npm start

并将浏览器打开到http://localhost : 8080。现在您应该看到一个简单的react应用程序,您可以使用blockstack ID登录。

多用户层数据存储

在多用户层数据存储中,存储在Gaia上的用户文件通过用户配置文件中的apps属性对其他用户可见。使用多用户层数据存储的每个应用程序都必须将其自身添加到用户的配置文件. JSON文件中。在身份验证期间请求publish _ data作用域时,blockstack浏览器将自动处理此部分。

因此,我们需要做的第一件事是修改身份验证请求以包括publish _ data范围。
打开src/components/App.jsx并找到下面的方法:

handleSignIn(e) {
  e.preventDefault();
  redirectToSignIn();
}

将方法修改为:

handleSignIn(e) {
  e.preventDefault();
  const origin = window.location.origin
  redirectToSignIn(origin, origin + '/manifest.json', ['store_write', 'publish_data'])
}

请注意,默认情况下,身份验证请求包括启用存储的store _ write范围。
如果您想注销并再次登录,程序将为提示用户身份验证的请求,你将会允许该程序发布你本地所存储的文字或图片。

multi-player-storage-auth.png

发布状态:

在此步骤中,我们将添加允许发布和显示“状态”的功能。
让我们打开src / components / profile . jsx,然后从blockstack. js中导入我们将使用的几个方法。这些方法是putFile ( )、getFile ( )和lookupProfile ( )。将它们添加到文件顶部附近blockstack的import语句中:

import {
  isSignInPending,
  loadUserData,
  Person,
  getFile,
  putFile,
  lookupProfile
} from 'blockstack';

然后,我们需要在构造函数( )的初始状态中添加一些属性。构造函数应如下所示:

constructor(props) {
  super(props);

  this.state = {
    person: {
      name() {
        return 'Anonymous';
      },
      avatarUrl() {
        return avatarFallbackImage;
      },
    },
    username: "",
    newStatus: "",
    statuses: [],
    statusIndex: 0,
    isLoading: false    
  };
}

现在,让我们修改render ( )方法以添加文本输入和提交按钮,以便可以发布用户的“微博状态”。将render ( )方法替换为以下内容:

render() {
  const { handleSignOut } = this.props;
  const { person } = this.state;
  const { username } = this.state;

  return (
    !isSignInPending() && person ?
    <div className="container">
      <div className="row">
        <div className="col-md-offset-3 col-md-6">
          <div className="col-md-12">
            <div className="avatar-section">
              <img
                src={ person.avatarUrl() ? person.avatarUrl() : avatarFallbackImage }
                className="img-rounded avatar"
                id="avatar-image"
              />
              <div className="username">
                <h1>
                  <span id="heading-name">{ person.name() ? person.name()
                    : 'Nameless Person' }</span>
                  </h1>
                <span>{username}</span>
                <span>
                  &nbsp;|&nbsp;
                  <a onClick={ handleSignOut.bind(this) }>(Logout)</a>
                </span>
              </div>
            </div>
          </div>

          <div className="new-status">
            <div className="col-md-12">
              <textarea className="input-status"
                value={this.state.newStatus}
                onChange={e => this.handleNewStatusChange(e)}
                placeholder="What's on your mind?"
              />
            </div>
            <div className="col-md-12">
              <button
                className="btn btn-primary btn-lg"
                onClick={e => this.handleNewStatusSubmit(e)}
              >
                Submit
              </button>
            </div>
          </div>

        </div>
      </div>
    </div> : null
  );
}

在上面的render ( )方法中,我们还显示了用户的blockstack ID。我们需要从用户配置文件数据中提取这一点。找到componentWillMount ( )方法,并在person属性下面添加用户名属性:

componentWillMount() {
  this.setState({
    person: new Person(loadUserData().profile),
    username: loadUserData().username
  });
}

接下来,我们将添加两种方法来处理输入事件:

handleNewStatusChange(event) {
  this.setState({newStatus: event.target.value})
}

handleNewStatusSubmit(event) {
  this.saveNewStatus(this.state.newStatus)
  this.setState({
    newStatus: ""
  })
}

然后执行所需存储操作,下面是保存一个“微博状态”的方法:

saveNewStatus(statusText) {
  let statuses = this.state.statuses

  let status = {
    id: this.state.statusIndex++,
    text: statusText.trim(),
    created_at: Date.now()
  }

  statuses.unshift(status)
  const options = { encrypt: false }
  putFile('statuses.json', JSON.stringify(statuses), options)
    .then(() => {
      this.setState({
        statuses: statuses
      })
    })
}

现在,您应该可以在文本框中键入一个状态,然后按“提交”按钮将其保存。
当您按下submit (提交)按钮时,您将看到什么也不会发生。因为我们没有添加任何代码来显示状态。

显示状态

返回render ( )方法,并在包含文本输入和提交按钮的div元素正下方添加以下块。

<div className="col-md-12 statuses">
  {this.state.isLoading && <span>Loading...</span>}
  {this.state.statuses.map((status) => (
      <div className="status" key={status.id}>
        {status.text}
      </div>
    )
  )}
</div>

我们还需要在页面加载时获取状态,所以我们添加一个名为fetchData ( )的新方法,并从componentDidMount ( )方法调用它

componentDidMount() {
  this.fetchData()
}

fetchData() {
  this.setState({ isLoading: true })
  const options = { decrypt: false }
  getFile('statuses.json', options)
    .then((file) => {
      var statuses = JSON.parse(file || '[]')
      this.setState({
        person: new Person(loadUserData().profile),
        username: loadUserData().username,
        statusIndex: statuses.length,
        statuses: statuses,
      })
    })
    .finally(() => {
      this.setState({ isLoading: false })
    })
}

此时,我们有一个基本的“区块链微博”应用程序,可以用来发布和查看自己的状态。但是,无法查看其他用户的状态。在接下来的步骤中,我们将进入“多用户数据存储”部分。但是首先,让我们花点时间来完善我们的应用程序。
打开src/styles/style.css,并用以下内容替换现有样式:

/* Globals */
a,a:focus,a:hover{color:#fff;}
html,body{height:100%;text-align:center;background-color:#191b22;}
body{color:#fff}
.hide{display:none;}
.landing-heading{font-family:'Lato',Sans-Serif;font-weight:400;}

/* Buttons */
.btn{font-family:'Lato',Sans-Serif;padding:0.5625rem 2.5rem;font-size:0.8125rem;font-weight:400;line-height:1.75rem;border-radius:0!important;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}
.btn-lg{font-size:1.5rem;padding:0.6875rem 3.4375rem;line-height:2.5rem;}
.btn:focus,.btn:active:focus,.btn.active:focus{outline:none;}
.btn-primary{color:#fff;border:1px solid #2C96FF;background-color:#2C96FF;}
.btn-primary:hover,.btn-primary:focus,.btn-primary:active{color:#fff;border:1px solid #1a6ec0;background-color:#1a6ec0;}

/* Avatar */
.avatar{width:100px;height:100px;}
.avatar-section{margin-bottom:25px;display:flex;text-align:left;}
.username{margin-left:20px;}

/* Scaffolding */
.site-wrapper{display:table;width:100%;height:100vh;min-height:100%;}
.site-wrapper-inner{display:flex;flex-direction:column;justify-content:center;margin-right:auto;margin-left:auto;width:100%;height:100vh;}
.panel-authed{padding:0 0 0 0;}

/* Home button */
.btn-home-hello{position:absolute;font-family:'Source Code Pro',monospace;font-size:11px;font-weight:400;color:rgba(255,255,255,0.85);top:15px;left:15px;padding:3px 20px;background-color:rgba(255,255,255,0.15);border-radius:6px;-webkit-box-shadow:0px 0px 20px 0px rgba(0,0,0,0.15);-moz-box-shadow:0px 0px 20px 0px rgba(0,0,0,0.15);box-shadow:0px 0px 20px 0px rgba(0,0,0,0.15);}

/* Input */
input, textarea{color:#000;padding:10px;}
.input-status{width:100%;height:70px;border-radius:6px;}
.new-status{text-align:right;}

/* Statuses */
.statuses{padding-top:30px;}
.status{margin:15px 0px;padding:20px;background-color:#2e2e2e;border-radius:6px}

如果一切顺利,我们最终的结果应该是这样的:

multi-player-storage-status.png

用户配置文件查找

现在让我们修改 Profile.jsx以显示其他用户的配置文件。我们将使用blockstack . js提供的lookupProfile ( )方法,我们在前面的导入语句中添加了该方法。lookupProfile ( )接受单个参数,该参数是要查找的配置文件的blockstack ID,并返回配置文件对象。
首先,我们将对应用程序的路由结构进行一些更改,以便通过访问http:// localhost:8080/other_user.id来查看其他用户的配置文件
安装 react-router:

npm install --save react-router-dom

打开src/index.js并添加到文件顶部:

import { BrowserRouter } from 'react-router-dom'

接下来,将src / index . js中的reactdom . render ( )方法更改为:


    ReactDOM.render((
  <BrowserRouter>
    <App />
  </BrowserRouter>
), document.getElementById('root'));

Then we'll need to go back to src/components/App.jsx and add the new route. Open src/components/App.jsx and import the Switch and Route components from react-router-dom:

import { Switch, Route } from 'react-router-dom'

接下来,在render ( )方法中找到下面的行:

: <Profile handleSignOut={ this.handleSignOut } />

并将其替换为:

  :
  <Switch>
    <Route
      path='/:username?'
      render={
        routeProps => <Profile handleSignOut={ this.handleSignOut } {...routeProps} />
      }
    />
  </Switch>

这会设置路由并捕获要用作配置文件查找用户名的路由参数。
我们还需要在webpack配置中添加一个规则,以便正确处理包含的URL路径。角色。例如: http://localhost:8080/other_user.id 注意:在生产应用程序中,需要配置web服务器来处理此问题。
在根项目目录中打开webpack . config . js,然后找到以下行:

historyApiFallback: {
  disableDotRule: true
},

更改为:

historyApiFallback: {
  disableDotRule: true
},

**注:我们需要再次运行启动NPM,这样才能生效。
现在,我们跳回到src / components / profile . jsx,并添加一个方法来确定是查看本地用户的配置文件还是其他用户的配置文件。**

isLocal() {
  return this.props.match.params.username ? false : true
}

然后我们可以修改fetchData ( )方法,如下所示:

fetchData() {
  this.setState({ isLoading: true })
  if (this.isLocal()) {
    const options = { decrypt: false }
    getFile('statuses.json', options)
      .then((file) => {
        var statuses = JSON.parse(file || '[]')
        this.setState({
          person: new Person(loadUserData().profile),
          username: loadUserData().username,
          statusIndex: statuses.length,
          statuses: statuses,
        })
      })
      .finally(() => {
        this.setState({ isLoading: false })
      })
  } else {
    const username = this.props.match.params.username

    lookupProfile(username)
      .then((profile) => {
        this.setState({
          person: new Person(profile),
          username: username
        })
      })
      .catch((error) => {
        console.log('could not resolve profile')
      })
  }
}

我们首先使用isLocal ( )检查是否正在查看本地用户配置文件或其他用户的配置文件。如果是本地用户配置文件,我们将运行前面添加的getFile ( )函数。否则,我们使用lookupProfile ( )方法查找属于用户名的配置文件。

注意:对于https部署,用于名称查找的默认块堆栈核心API端点应更改为指向通过https提供的核心API。否则,由于浏览器阻止混合内容,名称查找将失败。有关详细信息,请参阅blockstack . js文档。

为了获取用户的状态,我们在调用lookupProfile (用户名)后立即向fetchData ( )添加以下lookupProfile(username)... catch((error)=>{..}块:

const options = { username: username, decrypt: false }
getFile('statuses.json', options)
  .then((file) => {
    var statuses = JSON.parse(file || '[]')
    this.setState({
      statusIndex: statuses.length,
      statuses: statuses
    })
  })
  .catch((error) => {
    console.log('could not fetch statuses')
  })
  .finally(() => {
    this.setState({ isLoading: false })
  })

最后,我们需要有条件地呈现注销按钮、状态输入文本框和提交按钮,以便它们在查看其他用户的配置文件时不会显示。在render ( )方法中,通过使用{ isLocal ( ) & &...}条件:

{this.isLocal() &&
  <span>
    &nbsp;|&nbsp;
    <a onClick={ handleSignOut.bind(this) }>(Logout)</a>
  </span>
}

//...

{this.isLocal() &&
  <div className="new-status">
    <div className="col-md-12">
      <textarea className="input-status"
        value={this.state.newStatus}
        onChange={this.handleNewStatusChange}
        placeholder="What's on your mind?"
      />
    </div>
    <div className="col-md-12 text-right">
      <button
        className="btn btn-primary btn-lg"
        onClick={this.handleNewStatusSubmit}
      >
        Submit
      </button>
    </div>
  </div>
}

ok!将浏览器指向http://localhost:8080/your_blockstack.id以查看配置文件。注意:您需要有一个注册的blockstack ID才能使此操作生效。

要查看本教程的完整源代码,请访问:https://github.com/larrysalib...

欢迎加入blockstack中国开发者群q(官方blockstack core开发者在内):加微信:six四九四叁壹六seven一


wdfrfee
183 声望1 粉丝