Browse Source

Update storage tutorial to blockstack.js v19 API

feat/clarity-updates
Ken 6 years ago
parent
commit
9b42bdb84e
  1. 173
      _browser/blockstack_storage.md

173
_browser/blockstack_storage.md

@ -47,7 +47,8 @@ $ which npm
If you don't find `npm` in your system, [install If you don't find `npm` in your system, [install
it](https://www.npmjs.com/get-npm). Finally, if you get stuck at any point it](https://www.npmjs.com/get-npm). Finally, if you get stuck at any point
while working on the tutorial, the completed [source code is available for while working on the tutorial, the completed [source code is available for
you](https://github.com/larrysalibra/publik) to check your work against. you](https://github.com/yknl/publik) to check your work against. You can also
try out the [live build](https://publik.test-blockstack.com) of the app.
## Use npm to install Yeoman and the Blockstack App Generator ## Use npm to install Yeoman and the Blockstack App Generator
@ -163,31 +164,25 @@ Modify your authentication request to include the `publish_data` scope.
1. Open `src/components/App.jsx` file. 1. Open `src/components/App.jsx` file.
2. Locate the `handleSignIn` handler method. 2. Locate the `AppConfig` declaration near the beginning of the file.
```javascript ```javascript
handleSignIn(e) { const appConfig = new AppConfig()
e.preventDefault();
redirectToSignIn();
}
``` ```
2. Modify the method to this: 3. Change it to this:
```javascript ```javascript
handleSignIn(e) { const appConfig = new AppConfig(scopes:['store_write', 'publish_data'])
e.preventDefault();
const origin = window.location.origin
redirectToSignIn(origin, origin + '/manifest.json', ['store_write', 'publish_data'])
}
``` ```
By default, authentication requests include the `store_write` scope which By default, authentication requests include the `store_write` scope which
enables storage. This is what allows you to store information to Gaia. enables storage. This is what allows you to store information to Gaia. Adding
the `publish_data` scope allows your app to share data between users.
3. Save your changes. 4. Save your changes.
4. Go back to your app at `http://localhost:8080/`. 5. Go back to your app at `http://localhost:8080/`.
5. Log out and sign in again. 6. Log out and sign in again.
The authentication request now prompts the user for permission to **Publish The authentication request now prompts the user for permission to **Publish
data stored for the app**. data stored for the app**.
@ -197,10 +192,10 @@ Modify your authentication request to include the `publish_data` scope.
## Understand Gaia storage methods ## Understand Gaia storage methods
Once you authenticate a user with `store_write` and `publish_data`, you can Once you authenticate a user with `store_write` and `publish_data`, you can
begin to manage data for your users. Blockstack JS provides two methods begin to manage data for your users. Blockstack JS provides two methods within
`getFile()` and `putFile()` for interacting with Gaia storage. The storage the `UserSession` class, `UserSession.getFile` and `UserSession.putFile` for
methods support all file types. This means you can store SQL, Markdown, JSON, or interacting with Gaia storage. The storage methods support all file types.
even a custom format. This means you can store markdown, JSON, or even a custom format.
You can create a meaningful and complex data layer using these two methods. You can create a meaningful and complex data layer using these two methods.
Before creating an application, consider fundamental data architecture and make Before creating an application, consider fundamental data architecture and make
@ -250,29 +245,12 @@ or remove a grocery list; updating a list has no impact.
## Add support for user status submission and lookup ## Add support for user status submission and lookup
In this step, you add three `blockstack.js` methods that support posting of "statuses". These are the `putFile()`, `getFile()`, and `lookupProfile()` methods. In this step, you add three `blockstack.js` methods that support posting of "statuses".
These are the `UserSession.putFile`, `UserSession.getFile`, and `lookupProfile` methods.
1. Open the `src/components/Profile.jsx` file. 1. Open the `src/components/Profile.jsx` file.
2. Expand the `import from blockstack` statement with data methods. 2. Replace the initial state in the `constructor()` method so that it holds the key properties required by the app.
The `Person` object holds a Blockstack profile. Add `putFile`, `getFile`,
and `lookupProfile` after `Person`.
When you are done, the import statement should look like the following:
```javascript
import {
isSignInPending,
loadUserData,
Person,
getFile,
putFile,
lookupProfile
} from 'blockstack';
```
3. Replace the `constructor()` initial state so that it holds the key properties required by the app.
This code constructs a Blockstack `Person` object to hold the profile. Your constructor should look like this: This code constructs a Blockstack `Person` object to hold the profile. Your constructor should look like this:
@ -299,20 +277,20 @@ In this step, you add three `blockstack.js` methods that support posting of "sta
``` ```
4. Locate the `render()` method. 3. Locate the `render()` method.
5. Modify the `render()` method to add a text input and submit button to the application. 4. Modify the `render()` method to add a text input and submit button to the application.
The following code echos the `person.name` and `person.avatarURL` The following code renders the `person.name` and `person.avatarURL`
properties from the profile on the display: properties from the profile on the display:
```javascript ```javascript
render() { render() {
const { handleSignOut } = this.props; const { handleSignOut, userSession } = this.props;
const { person } = this.state; const { person } = this.state;
const { username } = this.state; const { username } = this.state;
return ( return (
!isSignInPending() && person ? !userSession.isSignInPending() && person ?
<div className="container"> <div className="container">
<div className="row"> <div className="row">
<div className="col-md-offset-3 col-md-6"> <div className="col-md-offset-3 col-md-6">
@ -366,22 +344,26 @@ In this step, you add three `blockstack.js` methods that support posting of "sta
user's Blockstack ID. To display this, your app must extract the ID from the user's Blockstack ID. To display this, your app must extract the ID from the
user profile data. user profile data.
6. Locate the `componentWillMount()` method. Notice that the `userSession` property passed into our profile renderer contains
7. Add the `username` property below the `person` property. the `isSignInPending()` method which checks if a sign in operation is pending.
5. Locate the `componentWillMount()` method.
6. Add the `username` property below the `person` property.
You'll use the Blockstack `loadUserData()` method to access the `username`. You'll use the Blockstack `loadUserData()` method in our user session to access the `username`.
```javascript ```javascript
componentWillMount() { componentWillMount() {
const { userSession } = this.props
this.setState({ this.setState({
person: new Person(loadUserData().profile), person: new Person(userSession.loadUserData().profile),
username: loadUserData().username username: userSession.loadUserData().username
}); });
} }
``` ```
7. Add two methods to handle the status input events: 7. Add two methods in the `Profile` class to handle the status input events:
```javascript ```javascript
handleNewStatusChange(event) { handleNewStatusChange(event) {
@ -400,6 +382,7 @@ In this step, you add three `blockstack.js` methods that support posting of "sta
```javascript ```javascript
saveNewStatus(statusText) { saveNewStatus(statusText) {
const { userSession } = this.props
let statuses = this.state.statuses let statuses = this.state.statuses
let status = { let status = {
@ -410,7 +393,7 @@ In this step, you add three `blockstack.js` methods that support posting of "sta
statuses.unshift(status) statuses.unshift(status)
const options = { encrypt: false } const options = { encrypt: false }
putFile('statuses.json', JSON.stringify(statuses), options) userSession.putFile('statuses.json', JSON.stringify(statuses), options)
.then(() => { .then(() => {
this.setState({ this.setState({
statuses: statuses statuses: statuses
@ -427,8 +410,8 @@ In this step, you add three `blockstack.js` methods that support posting of "sta
10. Enter your status in the text box and press the **Submit** button. 10. Enter your status in the text box and press the **Submit** button.
At this point, nothing is blogged. In the next section you add code to display At this point, the status you've just submitted isn't being displayed.
the statuses back to the user as a blog entry. In the next section you add code to display the statuses back to the user as a blog entry.
## Fetch and display statuses ## Fetch and display statuses
@ -436,7 +419,7 @@ Update `Profile.jsx` again.
1. Go back to the `render()` method. 1. Go back to the `render()` method.
2. Locate the `<div className="new-status">` containing the text input and **Submit** button. 2. Locate the `<div className="new-status">` containing the text input and **Submit** button.
3. Right after this opening `div` element, add this block. 3. Right after the matching closing `</div>` element in this section, add this block.
```javascript ```javascript
<div className="col-md-12 statuses"> <div className="col-md-12 statuses">
@ -449,21 +432,21 @@ Update `Profile.jsx` again.
)} )}
</div> </div>
``` ```
This loads existing state. Your code needs to fetch statuses on page load. This displays existing state. Your code needs to fetch statuses on page load.
4. Add a new method called `fetchData()` after the `statuses.unshift(status)` section. 4. Add a new method called `fetchData()` after the `saveNewStatus()` method.
```javascript ```javascript
fetchData() { fetchData() {
const { userSession } = this.props
this.setState({ isLoading: true }) this.setState({ isLoading: true })
const options = { decrypt: false } const options = { decrypt: false }
getFile('statuses.json', options) userSession.getFile('statuses.json', options)
.then((file) => { .then((file) => {
var statuses = JSON.parse(file || '[]') var statuses = JSON.parse(file || '[]')
this.setState({ this.setState({
person: new Person(loadUserData().profile), person: new Person(userSession.loadUserData().profile),
username: loadUserData().username, username: userSession.loadUserData().username,
statusIndex: statuses.length, statusIndex: statuses.length,
statuses: statuses, statuses: statuses,
}) })
@ -474,7 +457,7 @@ Update `Profile.jsx` again.
} }
``` ```
5. Call `fetchData()` from the `componentDidMount()` method 5. Call `fetchData()` from the `componentDidMount()` method.
```javascript ```javascript
@ -591,7 +574,7 @@ other users' profiles by visiting `http://localhost:8080/other_user.id`
9. Locate this line below in the `render()` method: 9. Locate this line below in the `render()` method:
```javascript ```javascript
: <Profile handleSignOut={ this.handleSignOut } /> : <Profile userSession={userSession} handleSignOut={ this.handleSignOut } />
``` ```
10. Replace it with the following: 10. Replace it with the following:
@ -602,20 +585,25 @@ other users' profiles by visiting `http://localhost:8080/other_user.id`
<Route <Route
path='/:username?' path='/:username?'
render={ render={
routeProps => <Profile handleSignOut={ this.handleSignOut } {...routeProps} /> routeProps =>
<Profile
userSession={userSession}
handleSignOut={ this.handleSignOut }
{...routeProps}
/>
} }
/> />
</Switch> </Switch>
``` ```
This sets up a route and captures the route parameter the app will use as the profile lookup username. This sets up a route and captures the route path in the URL as the profile lookup username.
11. Save and close the the `src/components/App.jsx` file. 11. Save and close the the `src/components/App.jsx` file.
### Add a rule to process URL paths with . (dot) ### Add a rule to process URL paths with . (dot)
You also need to add a rule to your webpack config so that you can properly You also need to add a rule to your app's webpack config so that you can properly
process URL paths that contain the `.` (dot) character for example, process URL paths that contain the `.` (dot) character for example,
`http://localhost:8080/other_user.id` `http://localhost:8080/other_user.id`
@ -641,8 +629,22 @@ process URL paths that contain the `.` (dot) character for example,
3. Save and close the `webpack.config.js` file. 3. Save and close the `webpack.config.js` file.
4. Edit the `src/components/Profile.jsx` file. 4. Open the `src/components/Profile.jsx` file.
5. Add a single method that determines if the app is viewing the local user's profile or another user's profile.
5. Expand the `import from blockstack` statement to include the `lookupProfile` method.
Add `lookupProfile` after `Person`.
When you are done, the import statement should look like the following:
```javascript
import {
Person,
lookupProfile
} from 'blockstack';
```
6. Add a single method to the `Profile` class that determines if the app is viewing the local user's profile or another user's profile.
```javascript ```javascript
isLocal() { isLocal() {
@ -652,19 +654,20 @@ process URL paths that contain the `.` (dot) character for example,
You use `isLocal()` to check if the user is viewing the local user profile or another user's profile. If it's the local user profile, the app runs the `getFile()` function you added in an earlier step. Otherwise, the app looks up the profile belonging to the `username` using the `lookupProfile()` method. You use `isLocal()` to check if the user is viewing the local user profile or another user's profile. If it's the local user profile, the app runs the `getFile()` function you added in an earlier step. Otherwise, the app looks up the profile belonging to the `username` using the `lookupProfile()` method.
6. Modify the `fetchData()` method like so: 7. Modify the `fetchData()` method like so:
```javascript ```javascript
fetchData() { fetchData() {
const { userSession } = this.props
this.setState({ isLoading: true }) this.setState({ isLoading: true })
if (this.isLocal()) { if (this.isLocal()) {
const options = { decrypt: false } const options = { decrypt: false }
getFile('statuses.json', options) userSession.getFile('statuses.json', options)
.then((file) => { .then((file) => {
var statuses = JSON.parse(file || '[]') var statuses = JSON.parse(file || '[]')
this.setState({ this.setState({
person: new Person(loadUserData().profile), person: new Person(userSession.loadUserData().profile),
username: loadUserData().username, username: userSession.loadUserData().username,
statusIndex: statuses.length, statusIndex: statuses.length,
statuses: statuses, statuses: statuses,
}) })
@ -696,11 +699,11 @@ process URL paths that contain the `.` (dot) character for example,
documentation](http://blockstack.github.io/blockstack.js/#getfile) for documentation](http://blockstack.github.io/blockstack.js/#getfile) for
details. details.
7. Add the following block to `fetchData()` right after the call to `lookupProfile(username)... catch((error)=>{..}` block: 8. Add the following block to `fetchData()` right after the call to `lookupProfile(username)... catch((error)=>{..}` block:
```javascript ```javascript
const options = { username: username, decrypt: false } const options = { username: username, decrypt: false }
getFile('statuses.json', options) userSession.getFile('statuses.json', options)
.then((file) => { .then((file) => {
var statuses = JSON.parse(file || '[]') var statuses = JSON.parse(file || '[]')
this.setState({ this.setState({
@ -720,16 +723,16 @@ process URL paths that contain the `.` (dot) character for example,
Finally, you must conditionally render the logout button, status input textbox, and submit button so they don't show up when viewing another user's profile. Finally, you must conditionally render the logout button, status input textbox, and submit button so they don't show up when viewing another user's profile.
8. Replace the `render()` method with the following: 9. Replace the `render()` method with the following:
```javascript ```javascript
render() { render() {
const { handleSignOut } = this.props; const { handleSignOut, userSession } = this.props;
const { person } = this.state; const { person } = this.state;
const { username } = this.state; const { username } = this.state;
return ( return (
!isSignInPending() && person ? !userSession.isSignInPending() && person ?
<div className="container"> <div className="container">
<div className="row"> <div className="row">
<div className="col-md-offset-3 col-md-6"> <div className="col-md-offset-3 col-md-6">
@ -790,22 +793,28 @@ process URL paths that contain the `.` (dot) character for example,
} }
``` ```
### This checks to ensure that users are viewing their own profile, by wrapping the **Logout** button and inputs with the `{isLocal() && ...}` condition. This checks to ensure that users are viewing their own profile, by wrapping the **Logout** button and inputs with the `{isLocal() && ...}` condition.
### Put it all together ### Put it all together
1. Stop the running application by sending a CTL-C. 1. Stop the running application in terminal by sending a CTRL-C.
2. Restart the application so that the disabling of the `.` (dot) rule takes effect. 2. Restart the application so that the disabling of the `.` (dot) rule takes effect.
```bash ```bash
npm start npm start
``` ```
3. Point your browser to `http://localhost:8080/your_blockstack.id` to see the final application. 3. Point your browser to `http://localhost:8080/your_username.id.blockstack` to see the final application.
## Wrapping up ## Wrapping up
Congratulations, you are all done! We hope you've enjoyed learning a bit more Congratulations, you are all done! We hope you've enjoyed learning a bit more
about Blockstack. about Blockstack.
A few things to note, you'll notice that in our `putFile()` and `getFile()` calls, we chose not to encrypt/decrypt because our app is meant to share statuses publicly. By default, `putFile()` and `getFile()` will encrypt all data stored, making it unreadable by everyone except the logged in user.
## Resources
[Complete source code](https://github.com/yknl/publik)
[Live demo](https://publik.test-blockstack.com)

Loading…
Cancel
Save