这个是一份适合初学者的开发者指南,我们将为你提供将[Ceramic Network](https://developers.ceramic.network/)集成到你的Web 3 [DApps](https://learnblockchain.cn/tags/DApp)所需的所有工具和知识。
如果你喜欢看视频可以点击[Ceramic版本V2.0.0 On Youtube](https://www.youtube.com/watch?v=rpyokDPnyUs)
Ceramic网络是一个去中心化的数据网络,旨在为Web 3应用程序带来可组合的数据。Ceramic可以处理很多类型的数据,但在本指南中,我们可以把Ceramic当作一个去中心化的NOSQL文档数据库。
本指南的目的是让你跟着一起实践,因为本文中会有图表和代码示例。
## 开发技能要求
除了这个书面指南,我还提供了一个[GitHub](https://www.github.com/ceramicstudio/guide-getting-started-with-ceramic)仓库,其中包含我参考的所有代码。
如果你喜欢视频指南,而不是书面指南,你可以在[Ceramic Youtube Channel](https://www.youtube.com/channel/UCgCLq5dx7sX-yUrrEbtYqVw)上观看视频演练。
在你开始之前,需要你已经具备了下面列出一般web开发技能。
**本指南中使用的技能有**
– [基础JavaScript](https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/JavaScript_basics)
– 了解[客户端JS与服务器端JS](https://computersciencewiki.org/index.php/Client-side_scripting_and_server-side_scripting)之间的基本差异
– JavaScript包管理
– 对[Webpack](https://webpack.js.org/)有基本了解。
**可选的技能**
– [Git](https://git-scm.com/)
– 版本控制(如GitHub、GitLab、BitBucket)。
**必要的工具(在继续之前需要安装)**
– 文本编辑器(如VS Code, Sublime, vim)。
– [NodeJS](https://www.nodejs.org/) v16或更高版本
– [NPM](https://www.npmjs.com/) v8或更高版本或[Yarn](https://yarnpkg.com/)
## 明确关键术语
在开始之前,我将介绍一些将在本指南中使用的关键术语。
**[去中心化的身份](https://www.w3.org/TR/did-core/)**
通常被称为[DID](https://www.w3.org/TR/did-core/#dfn-decentralized-identifiers)。
一个[DID](https://www.w3.org/TR/did-core/#dfn-decentralized-identifiers)是一个独特的身份标识符,包含关于你的元数据。诸如你的[公钥](https://en.wikipedia.org/wiki/Public-key_cryptography),一些验证信息,以及被你允许访问哪些服务点和其他一些东西。
简单地说,[DID](https://www.w3.org/TR/did-core/#dfn-decentralized-identifiers)被用来作为Ceramic账户的身份标识。
> 使用此功能的依赖库是`dids `
**[DID解析器](https://www.w3.org/TR/did-core/#dfn-did-resolvers)**
DID解析器接受一个DID作为输入并返回一个[DID文档](https://www.w3.org/TR/did-core/#dfn-did-documents)。
这个解析过程将DID从一般的东西变成一个文件,准确地描述一个身份以及该身份允许执行的方法和能力。
简单地说,解析者将一个DID与*它能够执行的*行动结合起来。
> 使用此功能的依赖库是:
>
> – `key-did-resolver`
> – `@glazed/did-datastore`
**[以太坊 Providers](https://docs.ethers.io/v4/api-providers.html#providers)**
如果你想让你的应用程序能够访问区块链,你需要使用一个提供者。
本指南将连接到以太坊区块链,因此使用了一个以太坊提供者。
提供者是用来代替自己运行区块链节点的。提供者有两个主要任务:
1. 告诉你的应用程序要连接到什么区块链。
2. 连接之后,就可以运行查询、以及发送修改区块链状态的签名交易。
Metamask是最流行的区块链提供者之一,它是将用于将我们的应用程序连接到以太坊区块链
简单地说,提供者*认证*用户在区块链上执行操作。
> 使用此功能的依赖库是:
>
> – `key-did-provider-ed25519`
> – `@glazed/did-session`
> – `@ceramicnetwork/blockchain-utils-link`
**数据的流类型([StreamTypes](https://developers.ceramic.network/learn/glossary/#streamtypes) **)
当我说到数据流的时候,我不是在说从消费的角度来看的[流数据](https://en.wikipedia.org/wiki/Streaming_data)。流是Ceramic对其数据结构的称呼。请随意阅读更多关于[流](https://developers.ceramic.network/learn/glossary/#streams)的信息。
[StreamType](https://developers.ceramic.network/learn/glossary/#streamtypes)只是一个流的可能数据结构之一。在本指南中,我们将间接地使用 `TileDocument`[流类型](https://developers.ceramic.network/learn/glossary/#streamtypes),你可以把它看作是一个[JSON Object](https://www.json.org/json-en.html)。这些StreamTypes是处理与数据有关的所有事情,它们在[Ceramic nodes](https://developers.ceramic.network/learn/glossary/#nodes)上运行。
简单地说,StreamTypes定义了*数据结构*和数据的状态被允许改变的方式。
> 使用此功能的依赖库是:
>
> – `@glazed/did-datastore`
**[数据模型](https://developers.ceramic.network/docs/advanced/standards/data-models/#data-models)**
数据模型通常用于表示一个应用程序的功能:比如笔记、用户资料、博客文章,甚至是社交图谱。
数据模型是可组合数据的核心。一个应用程序使用多个数据模型是很常见的,而一个数据模型在多个应用程序中使用也是很常见的!
这样做的可组合性也使开发者的体验更好。在Ceramic上构建一个应用程序看起来就像浏览一个数据模型的市场,将它们插入你的应用程序,并自动获得网络上存储在这些模型中的所有数据。
简单地说,数据模型是在一个应用程序中实现*数据可组合性*的东西。
## 构建应用程序
你将建立一个简单的网络应用,对Ceramic网络上的数据进行简单的读写操作。为了使这个应用程序正常工作,它需要按照以下罗列的顺序完成步骤:
1. 使用一个以太坊 Provider来验证区块链。
2. 一旦通过认证,获取一个DID,以便与Ceramic一起使用。
3. 使用一个Ceramic实例,用提供的DID来读写一个`TileDocument`流。
我在[关键术语](#明确关键术语)一节中提到了一些依赖关系,但在你进一步了解之前,还有一些其他的依赖关系需要了解:
**[Ceramic客户端](https://developers.ceramic.network/reference/core-clients/ceramic-http/)**
这是一个Web客户端,它允许你的应用程序连接到作为网络一部分的[Ceramic节点](https://developers.ceramic.network/learn/glossary/#nodes)。
> 用于此功能的依赖是:
>
> – `@ceramicnetwork/http-client`
**[Webpack](https://webpack.js.org/)**
你将编写的JavaScript使用Node包,使其成为服务器端的代码。然而,Web浏览器则需要客户端的代码。
Webpack是一个很好的模块,它将把你将要编写的服务器端JavaScript转换成浏览器可以理解的客户端JavaScript。
为了达到这个目的,我们需要一些依赖库。
> 用于此功能的依赖库是:
>
> – `webpack `
> – `webpack-cli`
> – `buffer`。
## 构建前端
我将引导你通过使用简单的HTML和CSS来构建这个应用程序前端的主要步骤
1. 让我们开始为这个项目创建一个新的目录。这个过程会根据你的操作系统而有所不同,所以选择最适合你环境的方案。
**Windows**
“`
md getting-started-with-ceramic
“`
**MacOS/Linux**
“`
mkdir getting-started-with-ceramic
“`
2. 现在,在该目录下创建一个名为`index.html`的文件。`index.html`文件应该包含以下内容:
“`html
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<meta http-equiv=”X-UA-Compatible” content=”IE=edge”>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<link rel=”stylesheet” href=”style.css”>
<link rel=”shortcut icon” href=”/favicon.ico”>
<title>Getting Started</title>
</head>
<body>
<!– create header with connect button –>
<header class=”SiteHeader”>
<div class=”HeaderContainer”>
<h1 id=”pageTitle”>Getting Start With Ceramic</h1>
</div>
<div class=”HeaderContainer”>
<button id=”walletBtn”></button>
</div>
</header>
<div class=”MainCont”>
<div class=”DataBlocks”>
<div class=”DataBlock”>
<div id=”basicProfile”>
<div class=”BodyContainer”>
<h2>Basic Profile</h2>
<p>Read from Ceramic Datamodel</p>
<br>
<p class=”ProfileData” id=”profileName”></p>
<p class=”ProfileData” id=”profileGender”></p>
<p class=”ProfileData” id=”profileCountry”></p>
</div>
</div>
</div>
</div>
<div class=”ProfileForm”>
<div class=”BodyContainer”>
<h2>Update Basic Profile on Ceramic</h2>
<br>
<form id=”profileForm”>
<div class=”formfield”>
<label class=”formLabel” for=”name”>Name:</label>
<input class=”forminput” type=”text” id=”name” placeholder=”John Doe”>
</div>
<div class=”formfield”>
<label class=”formLabel” for=”country”>Country:</label>
<input class=”forminput” type=”text” id=”country” placeholder=”USA”>
</div>
<div class=”formfield”>
<label class=”formLabel” for=”gender”>Gender:</label>
<select class=”forminput” id=”gender”>
<option value=”female”>Female</option>
<option value=”male”>Male</option>
<option value=”non-binary”>Non-Binary</option>
<option value=”other”>Other</option>
</select>
</div>
<div class=”formfield”>
<input class=”forminput” type=”submit” id=”submitBtn” value=”Submit”>
</div>
</form>
</div>
</div>
</div>
<!– <button id=”setBasicProf”>Set Profile</button>
<button id=”getBasicProf”>Get Profile</button> –>
<script src=”dist/bundle.js” type=”module”></script>
</body>
</html>
“`
3. 接下来,在`getting-started-with-ceramic`目录下创建一个名为`style.css`的文件。这个文件应该包含以下内容:
“`css
* {
margin: 0;
padding: 0;
}
.SiteHeader {
display: flex;
justify-content: space-between;
padding: 10px;
background-color: orange;
}
.HeaderContainer {
display: flex;
align-items: center;
}
.MainCont {
display: flex;
justify-content: space-around;
padding: 10px;
}
.DataBlock {
margin-bottom: 10px;
}
.BodyContainer {
background-color: lightsalmon;
border: 1px solid black;
border-radius: 30px;
padding: 20px;
min-width: 250px;
}
.ProfileForm {
min-width: 400px;
}
.formfield {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.forminput {
min-width: 150px;
}
#submitBtn {
display: block;
margin: auto;
width: auto;
}
.ProfileData {
font-weight: bold;
}
“`
现在,如果你在浏览器中打开`index.html`文件,或使用[LiveShare](https://visualstudio.microsoft.com/services/live-share/)这样的工具,你应该看到这样的内容:

## 添加JavaScript和Ceramic
现在应用程序还没有实际功能,它没有内置的逻辑,它只是一个有一些内容和一些样式的静态页面。
在这一步,我将向你展示如何使用提供者、解析器和Ceramic将这个应用程序从一个静态网站转变为一个web 3 dapp!
1. 首先,使用[NPM](https://www.npmjs.com/)或[Yarn](https://yarnpkg.com/)初始化一个新的[NodeJS](https://www.nodejs.org/)项目。
**NPM**
“`
npm init -y
“`
**Yarn**
“`
yarn init -y
“`
2. 接下来,安装上述的依赖项:
**NPM**
开发中依赖项
“`
npm install -D buffer dids key-did-provider-ed25519 key-did-resolver webpack webpack-cli
“`
常规依赖项
“`
npm install @ceramicnetwork/blockchain-utils-linking @ceramicnetwork/http-client @glazed/did-datastore @glazed/did-session
“`
**Yarn**
开发依赖项
“`
yarn add -D buffer dids key-did-provider-ed25519 key-did-resolver webpack webpack-cli
“`
常规依赖项
“`
yarn add @ceramicnetwork/blockchain-utils-linking @ceramicnetwork/http-client @glazed/did-datastore @glazed/did-session
“`
3. 现在,在`getting-started-with-ceramic`目录下创建一个名为`main.js`的文件。
4. 首先,将需要的depndencies导入该文件:
“`javascript
//main.js
import { CeramicClient } from ‘@ceramicnetwork/http-client’
import { EthereumAuthProvider } from ‘@ceramicnetwork/blockchain-utils-linking’
import { DIDDataStore } from ‘@glazed/did-datastore’
import { DIDSession } from ‘@glazed/did-session’
“`
> 你是否注意到,有些软件包来自`@ceramicnetwork`,有些来自`@glazed`?
>
> 来自@ceramicnetwork的包是Ceramic核心协议的一部分。它们帮助应用程序连接到Ceramic节点。
>
> 来自@glazed的软件包不是核心Ceramic协议的一部分,它们被称为 `中间件`,为开发者提供一些额外的功能和便利。
5. 在依赖库导入之后,你应该设置一系列的DOM元素选择器(selctors)。这不仅使我们的代码在编写时更容易阅读,而且在更大的应用程序中,这种技术可以增加性能优势。在`main.js`中添加以下内容:
“`javascript
import { CeramicClient } from ‘@ceramicnetwork/http-client’
import { EthereumAuthProvider } from ‘@ceramicnetwork/blockchain-utils-linking’
import { DIDDataStore } from ‘@glazed/did-datastore’
import { DIDSession } from ‘@glazed/did-session’
const profileForm = document.getElementById(‘profileForm’)
const walletBtn = document.getElementById(‘walletBtn’)
const profileName = document.getElementById(‘profileName’)
const profileGender = document.getElementById(‘profileGender’)
const profileCountry = document.getElementById(‘profileCountry’)
const submitBtn = document.getElementById(‘submitBtn’)
“`
6. 使用刚刚导入的`CeramiClient`,通过在`main.js`文件中添加以下代码,创建一个新的Ceramic客户端实例:
“`javascript
//main.js
import { CeramicClient } from ‘@ceramicnetwork/http-client’
import { EthereumAuthProvider } from ‘@ceramicnetwork/blockchain-utils-linking’
import { DIDDataStore } from ‘@glazed/did-datastore’
import { DIDSession } from ‘@glazed/did-session’
const profileForm = document.getElementById(‘profileForm’)
const walletBtn = document.getElementById(‘walletBtn’)
const profileName = document.getElementById(‘profileName’)
const profileGender = document.getElementById(‘profileGender’)
const profileCountry = document.getElementById(‘profileCountry’)
const submitBtn = document.getElementById(‘submitBtn’)
const ceramic = new CeramicClient(“https://ceramic-clay.3boxlabs.com”)
“`
> 目前有4种可能的网络来供Ceramic HTTP客户端连接。你可以点击每个链接来了解更多关于网络的信息。
>
> – [主网](https://developers.ceramic.network/learn/networks/#mainnet)
> – [Clay Testnet](https://developers.ceramic.network/learn/networks/#clay-testnet) (推荐使用,目前正被我们的应用程序使用)
> – [Dev Unstable](https://developers.ceramic.network/learn/networks/#dev-unstable)
> – [本地网络](https://developers.ceramic.network/learn/networks/#local)
7. 接下来,创建一个名为`aliases`的变量,它将保存`BasicProfile`数据模型的引用:
“`javascript
//main.js
import { CeramicClient } from ‘@ceramicnetwork/http-client’
import { EthereumAuthProvider } from ‘@ceramicnetwork/blockchain-utils-linking’
import { DIDDataStore } from ‘@glazed/did-datastore’
import { DIDSession } from ‘@glazed/did-session’
const profileForm = document.getElementById(‘profileForm’)
const walletBtn = document.getElementById(‘walletBtn’)
const profileName = document.getElementById(‘profileName’)
const profileGender = document.getElementById(‘profileGender’)
const profileCountry = document.getElementById(‘profileCountry’)
const submitBtn = document.getElementById(‘submitBtn’)
const ceramic = new CeramicClient(“https://ceramic-clay.3boxlabs.com”)
const aliases = {
schemas: {
basicProfile: ‘ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio’,
},
definitions: {
BasicProfile: ‘kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic’,
},
tiles: {},
}
“`
> **数据模型的各个部分**:
>
> `schemas`:定义数据模型的JSON schema
>
> `definitions`: 将一个用户友好的模型名称和描述链接到一个特定的schema。
>
> `tiles`: 在schema内参数集的各条数据记录。

8. `DIDDataStore`允许应用程序从Ceramic写入和读取数据。`DIDDataStore`是基于数据模型的。在`main.js`中添加以下代码,以配置`DIDDataStore`,它使用到上面定义的`aliases`和`ceramic instance`。
“`javascript
//main.js
import { CeramicClient } from ‘@ceramicnetwork/http-client’
import { EthereumAuthProvider } from ‘@ceramicnetwork/blockchain-utils-linking’
import { DIDDataStore } from ‘@glazed/did-datastore’
import { DIDSession } from ‘@glazed/did-session’
const profileForm = document.getElementById(‘profileForm’)
const walletBtn = document.getElementById(‘walletBtn’)
const profileName = document.getElementById(‘profileName’)
const profileGender = document.getElementById(‘profileGender’)
const profileCountry = document.getElementById(‘profileCountry’)
const submitBtn = document.getElementById(‘submitBtn’)
const ceramic = new CeramicClient(“https://ceramic-clay.3boxlabs.com”)
const aliases = {
schemas: {
basicProfile: ‘ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio’,
},
definitions: {
BasicProfile: ‘kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic’,
},
tiles: {},
}
const datastore = new DIDDataStore({ ceramic, model: aliases })
“`
> 如果你的应用程序需要,你可以通过添加必要的 `schema`、`definition `和 `tiles `向 `aliases `变量添加更多的数据模型!
你现在有了启动和运行这个应用程序所需的基本基础。Ceramic客户端和数据模型的所有配置已经完成。
## 使用区块链进行认证
接下来的部分将指导你使用以太坊 Provider, [Metamask](https://metamask.io/),用以太坊区块链来验证用户。
正在使用的认证流程被称为[Sign-In With Ethereum](https://login.xyz/),以下简称为SIWE。
> 请看这篇很棒的文章以了解更多。[为什么用以太坊登录是一个游戏规则改变者](https://blog.spruceid.com/sign-in-with-ethereum-is-a-game-changer-part-1/)。
让我们把SIWE添加到这个应用中吧!
1. 这个应用程序需要一个[异步函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function),把它命名为`authenticateWithEthereum`,它使用提供者,然后使用Resovler,最后把DID分配给你之前创建的Ceramic Client。在`main.js`中添加这段代码来完成这些任务:
“`javascript
//main.js
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: ‘eth_requestAccounts’,
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
“`
> `DIDSession`是它在这个代码片断中为你处理SIWE认证流程的。
2. 在认证流程开始之前,通常有一些逻辑检查需要应用程序来完成。当开发dapp时,一个常见的检查是确保提供者是可用的。在我们的案例下,使用[Metamask](https://metamask.io/)会在浏览器`window`对象中注入自己作为提供者。它可以通过`window.ethereum`引用。如果应用程序的最终用户没有安装[Metamask](https://metamask.io/),或其他提供者,我们的应用程序将无法连接到区块链上。让我们把这些知识应用于一个新的[异步函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function),称为`auth`。将下面的代码添加到`main.js`中:
“`javascript
//main.js
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: ‘eth_requestAccounts’,
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
// newly added auth function here:
async function auth() {
if (window.ethereum == null) {
throw new Error(‘No injected Ethereum provider found’)
}
await authenticateWithEthereum(window.ethereum)
}
“`
`auth()`首先检查`window.ethereum`是否存在,然后再尝试调用`authenticateWithEthereum()`。这可以防止应用程序在没有注入提供者的情况下挂起!
完整的`main.js`文件目前看起来应该是这样的:
“`javascript
//main.js
// import all dependencies:
import { CeramicClient } from ‘@ceramicnetwork/http-client’
import { EthereumAuthProvider } from ‘@ceramicnetwork/blockchain-utils-linking’
import { DIDDataStore } from ‘@glazed/did-datastore’
import { DIDSession } from ‘@glazed/did-session’
//cache a reference to the DOM Elements
const profileForm = document.getElementById(‘profileForm’)
const walletBtn = document.getElementById(‘walletBtn’)
const profileName = document.getElementById(‘profileName’)
const profileGender = document.getElementById(‘profileGender’)
const profileCountry = document.getElementById(‘profileCountry’)
const submitBtn = document.getElementById(‘submitBtn’)
// create a new CeramicClient instance:
const ceramic = new CeramicClient(“https://ceramic-clay.3boxlabs.com”)
// reference the data models this application will use:
const aliases = {
schemas: {
basicProfile: ‘ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio’,
},
definitions: {
BasicProfile: ‘kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic’,
},
tiles: {},
}
// configure the datastore to use the ceramic instance and data models referenced above:
const datastore = new DIDDataStore({ ceramic, model: aliases })
// this function authenticates the user using SIWE
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: ‘eth_requestAccounts’,
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
// check for a provider, then authenticate if the user has one injected:
async function auth() {
if (window.ethereum == null) {
throw new Error(‘No injected Ethereum provider found’)
}
await authenticateWithEthereum(window.ethereum)
}
“`
—
## 使用Ceramic读取数据
接下来的函数将使用`DIDDatastore`来从Ceramic网络中获取数据。我将称它为`getProfileFromCeramic`,它也是一个[异步函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)。
函数将在`main.js`文件中声明。
1. 在`main.js`中添加`getProfileFromCeramic`函数:
“`javascript
//main.js
async function getProfileFromCeramic() {
try {
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get(‘BasicProfile’)
//render profile data to the DOM (not written yet)
renderProfileData(profile)
} catch (error) {
console.error(error)
}
}
“`
正如你所看到的,通过调用`datastore.get()`方法,你可以引用希望读取数据模型的`定义`。
DIDDatastore使用分配给Ceramic客户端的DID来进行这个调用。它返回的profile对象存储在`profile`变量中。
2. 你将需要创建`renderProfileData`函数来提取这些资料数据并在浏览器窗口中显示。由于这不是一个网页开发的指南,我将不详细介绍这个函数的作用。在你的`main.js`文件中加入以下内容:
“`javascript
function renderProfileData(data) {
if (!data) return
data.name ? profileName.innerHTML = “Name: ” + data.name : profileName.innerHTML = “Name: ”
data.gender ? profileGender.innerHTML = “Gender: ” + data.gender : profileGender.innerHTML = “Gender: ”
data.country ? profileCountry.innerHTML = “Country: ” + data.country : profileCountry.innerHTML = “Country: ”
}
“`
> 我想指出的是,`data`是`datastore.get()`调用返回的`profile`对象。数据的属性在 “BasicProfile “数据模型中定义。查看[Ceramic 数据模型仓库](https://github.com/ceramicstudio/datamodels)中的数据模型,可以查看[完整的属性列表](https://github.com/ceramicstudio/datamodels/tree/main/models/identity-profile-basic)。
这就是使用 `DIDDataStore `从Ceramic网络中读取数据的全部内容!
`main.js`完整代码如下:
“`javascript
//main.js
// import all dependencies:
import { CeramicClient } from ‘@ceramicnetwork/http-client’
import { EthereumAuthProvider } from ‘@ceramicnetwork/blockchain-utils-linking’
import { DIDDataStore } from ‘@glazed/did-datastore’
import { DIDSession } from ‘@glazed/did-session’
//cache a reference to the DOM Elements
const profileForm = document.getElementById(‘profileForm’)
const walletBtn = document.getElementById(‘walletBtn’)
const profileName = document.getElementById(‘profileName’)
const profileGender = document.getElementById(‘profileGender’)
const profileCountry = document.getElementById(‘profileCountry’)
const submitBtn = document.getElementById(‘submitBtn’)
// create a new CeramicClient instance:
const ceramic = new CeramicClient(“https://ceramic-clay.3boxlabs.com”)
// reference the data models this application will use:
const aliases = {
schemas: {
basicProfile: ‘ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio’,
},
definitions: {
BasicProfile: ‘kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic’,
},
tiles: {},
}
// configure the datastore to use the ceramic instance and data models referenced above:
const datastore = new DIDDataStore({ ceramic, model: aliases })
// this function authenticates the user using SIWE
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: ‘eth_requestAccounts’,
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
// check for a provider, then authenticate if the user has one injected:
async function auth() {
if (window.ethereum == null) {
throw new Error(‘No injected Ethereum provider found’)
}
await authenticateWithEthereum(window.ethereum)
}
//retrieve BasicProfile data from ceramic using the DIDDatastore
async function getProfileFromCeramic() {
try {
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get(‘BasicProfile’)
//render profile data to the DOM (not written yet)
renderProfileData(profile)
} catch (error) {
console.error(error)
}
}
//Do some fun web dev stuff to present the BasicProfile in the DOM
function renderProfileData(data) {
if (!data) return
data.name ? profileName.innerHTML = “Name: ” + data.name : profileName.innerHTML = “Name: ”
data.gender ? profileGender.innerHTML = “Gender: ” + data.gender : profileGender.innerHTML = “Gender: ”
data.country ? profileCountry.innerHTML = “Country: ” + data.country : profileCountry.innerHTML = “Country: ”
}
“`
## 使用Ceramic写入数据
接下来要实现的是使用 `DIDDatastore `向Ceramic网络写数据。
1. 像其他一些已经写好的函数一样,`updateProfileOnCeramic`函数应该是一个[异步函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)。在`main.js`中添加以下内容:
“`javascript
async function updateProfileOnCeramic() {
try {
const updatedProfile = getFormProfile()
submitBtn.value = “Updating…”
//use the DIDDatastore to merge profile data to Ceramic
await datastore.merge(‘BasicProfile’, updatedProfile)
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get(‘BasicProfile’)
renderProfileData(profile)
submitBtn.value = “Submit”
} catch (error) {
console.error(error)
}
}
“`
> 在继续前进之前,有两件重要的事情要了解:
>
> 首先:`DIDDatastore`有两个方法允许向数据模型写入:
>
> – `merge()`:只写已经改变的字段。
> – `set()`:覆盖所有字段,包括那些没有改变的字段。这可能导致数据以不需要的方式被删除。由于这个原因,我们建议使用merge而不是set。
>
> 其次:在本案例下,从DIDDatastore中读取数据并使用`renderProfileData()`将其渲染到DOM中并不是优秀的方式。在这个阶段没有真正的必要从Ceramic读取数据。这样做是为了向你展示读和写是多么简单,因为在使用DIDDatastore时,每一个都只需要一行代码。
2. 你可能注意到在上述代码块中对`getFormProfile()`的调用。这个函数目前并不存在。现在让我们把它添加到`main.js`中。在`main.js`中加入以下代码:
“`javascript
function getFormProfile() {
const name = document.getElementById(‘name’).value
const country = document.getElementById(‘country’).value
const gender = document.getElementById(‘gender’).value
return {
name,
country,
gender
}
}
“`
> 如果你想知道如何想出 `name`、`country `和 `gender `这些对象属性的,它们都可以在[BasicProfile](https://github.com/ceramicstudio/datamodels/tree/main/models/identity-profile-basic)数据模型中找到。BasicProfile还有一些额外的属性,在这个项目中没有被引用。你应该在自己的项目中探索这些属性的使用!
你成功了! 这就是你开始使用Ceramic所需要的一切。你现在知道的足够多了,足够去创造惊人的dapp。
不过你还没有完全完成。有一些小东西必须建立起来才能使这个应用程序完全工作。
## 完善引用
本节以及下一节[配置Webpack](https://blog.ceramic.network/getting-started-with-ceramic/#configuring-webpack),与Ceramic没有必然联系。这些部分涵盖了一些必要的操作,例如在应用程序的按钮上的操作,以及将服务器端转换成浏览器可以理解的代码。
**按钮是如何工作的**
应用程序的按钮元素将使用[Event Listeners](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)来让它们被点击时执行功能。
以下所有的代码都需要放在`main.js`中:
1. 让我们首先创建一个函数,当用户点击 `连接钱包 `按钮时,事件监听器可以调用这个函数:
“`javascript
async function connectWallet(authFunction, callback) {
try {
walletBtn.innerHTML = “Connecting…”
await authFunction()
await callback()
walletBtn.innerHTML = “Wallet Connected”
} catch (error) {
console.error(error)
}
}
“`
2. 目前,按钮元素没有显示任何`innerHTML`,所以在继续之前,让我们先解决这个问题。在之前发生在`main.js`的DOM缓存下,添加以下一行:
“`javascript
walletBtn.innerHTML = “Connect Wallet”
“`
3. 另一个缺少的东西是文本占位符,配置文件数据应该在这里呈现。你可以通过在`walletBtn.innerHTML`行下添加这段代码来设置该占位符文本:
“`javascript
walletBtn.innerHTML = “Connect Wallet”
profileName.innerHTML = “Name: ”
profileGender.innerHTML = “Gender: ”
profileCountry.innerHTML = “Country: ”
“`
4. 最后一件事是添加两个事件监听器。一个是 “连接钱包 “按钮,它将调用上面定义的`connectWallet`函数。另一个将放在作为`profileForm`元素一部分的按钮上。在`main.js`中添加这些代码:
“`javascript
walletBtn.addEventListener(‘click’, async () => await connectWallet(auth, getProfileFromCeramic))
profileForm.addEventListener(‘submit’, async (e) => {
e.preventDefault()
await updateProfileOnCeramic()
})
“`
好了!这就是应用程序需要的所有JavaScript:
“`javascript
//main.js
// import all dependencies:
import { CeramicClient } from ‘@ceramicnetwork/http-client’
import { EthereumAuthProvider } from ‘@ceramicnetwork/blockchain-utils-linking’
import { DIDDataStore } from ‘@glazed/did-datastore’
import { DIDSession } from ‘@glazed/did-session’
//cache a reference to the DOM Elements
const profileForm = document.getElementById(‘profileForm’)
const walletBtn = document.getElementById(‘walletBtn’)
const profileName = document.getElementById(‘profileName’)
const profileGender = document.getElementById(‘profileGender’)
const profileCountry = document.getElementById(‘profileCountry’)
const submitBtn = document.getElementById(‘submitBtn’)
// give the wallet button an initial value to display
walletBtn.innerHTML = “Connect Wallet”
// setup placeholder text where profile should render
walletBtn.innerHTML = “Connect Wallet”
profileName.innerHTML = “Name: ”
profileGender.innerHTML = “Gender: ”
profileCountry.innerHTML = “Country: ”
// create a new CeramicClient instance:
const ceramic = new CeramicClient(“https://ceramic-clay.3boxlabs.com”)
// reference the data models this application will use:
const aliases = {
schemas: {
basicProfile: ‘ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio’,
},
definitions: {
BasicProfile: ‘kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic’,
},
tiles: {},
}
// configure the datastore to use the ceramic instance and data models referenced above:
const datastore = new DIDDataStore({ ceramic, model: aliases })
// this function authenticates the user using SIWE
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: ‘eth_requestAccounts’,
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
// check for a provider, then authenticate if the user has one injected:
async function auth() {
if (window.ethereum == null) {
throw new Error(‘No injected Ethereum provider found’)
}
await authenticateWithEthereum(window.ethereum)
}
//retrieve BasicProfile data from ceramic using the DIDDatastore
async function getProfileFromCeramic() {
try {
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get(‘BasicProfile’)
//render profile data to the DOM (not written yet)
renderProfileData(profile)
} catch (error) {
console.error(error)
}
}
//Do some fun web dev stuff to present the BasicProfile in the DOM
function renderProfileData(data) {
if (!data) return
data.name ? profileName.innerHTML = “Name: ” + data.name : profileName.innerHTML = “Name: ”
data.gender ? profileGender.innerHTML = “Gender: ” + data.gender : profileGender.innerHTML = “Gender: ”
data.country ? profileCountry.innerHTML = “Country: ” + data.country : profileCountry.innerHTML = “Country: ”
}
//this function uses the datastore to write data to the Ceramic Network as well as read data back before populating the changes in the DOM
async function updateProfileOnCeramic() {
try {
const updatedProfile = getFormProfile()
submitBtn.value = “Updating…”
//use the DIDDatastore to merge profile data to Ceramic
await datastore.merge(‘BasicProfile’, updatedProfile)
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get(‘BasicProfile’)
renderProfileData(profile)
submitBtn.value = “Submit”
} catch (error) {
console.error(error)
}
}
// Parse the form and return the values so the BasicProfile can be updated
function getFormProfile() {
const name = document.getElementById(‘name’).value
const country = document.getElementById(‘country’).value
const gender = document.getElementById(‘gender’).value
// object needs to conform to the datamodel
// name -> exists
// hair-color -> DOES NOT EXIST
return {
name,
country,
gender
}
}
//a simple utility funciton that will get called from the event listener attached to the connect wallet button
async function connectWallet(authFunction, callback) {
try {
walletBtn.innerHTML = “Connecting…”
await authFunction()
await callback()
walletBtn.innerHTML = “Wallet Connected”
} catch (error) {
console.error(error)
}
}
//add both event listeners to that the buttons work when they are clicked
walletBtn.addEventListener(‘click’, async () => await connectWallet(auth, getProfileFromCeramic))
profileForm.addEventListener(‘submit’, async (e) => {
e.preventDefault()
await updateProfileOnCeramic()
})
“`
## 配置Webpack
下面的部分将为这个应用程序配置[Webpack](https://webpack.js.org/)。
1. 在`getting-started-with-ceramic`目录下创建一个名为`webpack.config.js`的新文件,并在其中放置以下内容。
“`javascript
const path = require(‘path’);
module.exports = {
entry: ‘./main.js’,
output: {
path: path.resolve(__dirname, ‘dist’),
filename: ‘bundle.js’
},
mode: ‘development’,
resolve: {
fallback: { buffer: require.resolve(‘buffer’) }
}
}
“`
> 如果你想知道这段代码的作用,请务必查看[Webpack](https://webpack.js.org/)。
2. 接下来,你需要编辑目前存在于根目录下的`package.json`文件。你将只修改这个文件的`scripts`。对`package.json`做如下修改:
“`json
“scripts”: {
“test”: “echo \”Error: no test specified\” && exit 1″,
“build”: “webpack”
}
“`
> 为了清楚起见,这里的改动是增加了一个脚本,名为build,用来调用webpack。
完整的`package.json`可以在下面找到:
“`json
{
“name”: “getting-started-ceramic”,
“version”: “1.0.0”,
“description”: “”,
“main”: “utils.js”,
“scripts”: {
“test”: “echo \”Error: no test specified\” && exit 1″,
“build”: “webpack”
},
“keywords”: [],
“author”: “”,
“license”: “ISC”,
“devDependencies”: {
“buffer”: “^6.0.3”,
“dids”: “^3.1.0”,
“key-did-provider-ed25519”: “^2.0.0”,
“key-did-resolver”: “^2.0.4”,
“webpack”: “^5.72.1”,
“webpack-cli”: “^4.9.2”
},
“dependencies”: {
“@ceramicnetwork/blockchain-utils-linking”: “^2.0.4”,
“@ceramicnetwork/http-client”: “^2.0.4”,
“@glazed/did-datastore”: “^0.3.1”,
“@glazed/did-session”: “^0.0.1”
}
}
“`
> 根据你完成本指南的时间,文件上可能有小的版本差异。这是正常的,没有什么可担心的。
3. 最后一步是在终端或命令行中运行这个新添加的脚本。运行这个脚本就可以把以前所有的JavaScript打包成一个你的浏览器可以解释的版本。不管是什么操作系统,命令都是一样的:
**NPM**
“`
npm run build
“`
**Yarn**
“`
yarn run build
“`
## 恭喜你
恭喜你! 你现在可以在浏览器中重新打开`index.html`文件,或者通过使用[LiveShare](https://visualstudio.microsoft.com/services/live-share/)。
使用你的[Metamask](https://metamask.io/)钱包,你将能够[用Ethereum登录](https://login.xyz/),从Ceramic检索你的`BasicProfile`,并对该配置文件的一组有限属性进行修改!
> 如果你从未在[Ceramic Network](https://developers.ceramic.network/)上配置过 `BasicProfile`,你最初将不会收到数据。你需要使用选择的钱包账户,通过[Self.id](https://clay.self.id/)应用程序或使用本应用程序所包含的表格来创建一个配置文件!
—
本翻译由 [Duet Protocol](https://duet.finance/?utm_souce=learnblockchain) 赞助支持。
- 原文:https://blog.ceramic.network/getting-started-with-ceramic/ 作者:Kait Hobson
- 译文出自:区块链开发网翻译计划
- 译者:翻译小组
- 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
这个是一份适合初学者的开发者指南,我们将为你提供将Ceramic Network集成到你的Web 3 DApps所需的所有工具和知识。
如果你喜欢看视频可以点击Ceramic版本V2.0.0 On Youtube
Ceramic网络是一个去中心化的数据网络,旨在为Web 3应用程序带来可组合的数据。Ceramic可以处理很多类型的数据,但在本指南中,我们可以把Ceramic当作一个去中心化的NOSQL文档数据库。
本指南的目的是让你跟着一起实践,因为本文中会有图表和代码示例。
开发技能要求
除了这个书面指南,我还提供了一个GitHub仓库,其中包含我参考的所有代码。
如果你喜欢视频指南,而不是书面指南,你可以在Ceramic Youtube Channel上观看视频演练。
在你开始之前,需要你已经具备了下面列出一般web开发技能。
本指南中使用的技能有
- 基础JavaScript
- 了解客户端JS与服务器端JS之间的基本差异
- JavaScript包管理
- 对Webpack有基本了解。
可选的技能
- Git
- 版本控制(如GitHub、GitLab、BitBucket)。
必要的工具(在继续之前需要安装)
- 文本编辑器(如VS Code, Sublime, vim)。
- NodeJS v16或更高版本
- NPM v8或更高版本或Yarn
明确关键术语
在开始之前,我将介绍一些将在本指南中使用的关键术语。
去中心化的身份
通常被称为DID。
一个DID是一个独特的身份标识符,包含关于你的元数据。诸如你的公钥,一些验证信息,以及被你允许访问哪些服务点和其他一些东西。
简单地说,DID被用来作为Ceramic账户的身份标识。
使用此功能的依赖库是
dids
DID解析器
DID解析器接受一个DID作为输入并返回一个DID文档。
这个解析过程将DID从一般的东西变成一个文件,准确地描述一个身份以及该身份允许执行的方法和能力。
简单地说,解析者将一个DID与它能够执行的行动结合起来。
使用此功能的依赖库是:
key-did-resolver
@glazed/did-datastore
以太坊 Providers
如果你想让你的应用程序能够访问区块链,你需要使用一个提供者。
本指南将连接到以太坊区块链,因此使用了一个以太坊提供者。
提供者是用来代替自己运行区块链节点的。提供者有两个主要任务:
- 告诉你的应用程序要连接到什么区块链。
- 连接之后,就可以运行查询、以及发送修改区块链状态的签名交易。
Metamask是最流行的区块链提供者之一,它是将用于将我们的应用程序连接到以太坊区块链
简单地说,提供者认证用户在区块链上执行操作。
使用此功能的依赖库是:
key-did-provider-ed25519
@glazed/did-session
@ceramicnetwork/blockchain-utils-link
数据的流类型(StreamTypes )
当我说到数据流的时候,我不是在说从消费的角度来看的流数据。流是Ceramic对其数据结构的称呼。请随意阅读更多关于流的信息。
StreamType只是一个流的可能数据结构之一。在本指南中,我们将间接地使用 TileDocument
流类型,你可以把它看作是一个JSON Object。这些StreamTypes是处理与数据有关的所有事情,它们在Ceramic nodes上运行。
简单地说,StreamTypes定义了数据结构和数据的状态被允许改变的方式。
使用此功能的依赖库是:
@glazed/did-datastore
数据模型
数据模型通常用于表示一个应用程序的功能:比如笔记、用户资料、博客文章,甚至是社交图谱。
数据模型是可组合数据的核心。一个应用程序使用多个数据模型是很常见的,而一个数据模型在多个应用程序中使用也是很常见的!
这样做的可组合性也使开发者的体验更好。在Ceramic上构建一个应用程序看起来就像浏览一个数据模型的市场,将它们插入你的应用程序,并自动获得网络上存储在这些模型中的所有数据。
简单地说,数据模型是在一个应用程序中实现数据可组合性的东西。
构建应用程序
你将建立一个简单的网络应用,对Ceramic网络上的数据进行简单的读写操作。为了使这个应用程序正常工作,它需要按照以下罗列的顺序完成步骤:
- 使用一个以太坊 Provider来验证区块链。
- 一旦通过认证,获取一个DID,以便与Ceramic一起使用。
- 使用一个Ceramic实例,用提供的DID来读写一个
TileDocument
流。
我在关键术语一节中提到了一些依赖关系,但在你进一步了解之前,还有一些其他的依赖关系需要了解:
Ceramic客户端
这是一个Web客户端,它允许你的应用程序连接到作为网络一部分的Ceramic节点。
用于此功能的依赖是:
@ceramicnetwork/http-client
Webpack
你将编写的JavaScript使用Node包,使其成为服务器端的代码。然而,Web浏览器则需要客户端的代码。
Webpack是一个很好的模块,它将把你将要编写的服务器端JavaScript转换成浏览器可以理解的客户端JavaScript。
为了达到这个目的,我们需要一些依赖库。
用于此功能的依赖库是:
webpack
webpack-cli
buffer
。
构建前端
我将引导你通过使用简单的HTML和CSS来构建这个应用程序前端的主要步骤
-
让我们开始为这个项目创建一个新的目录。这个过程会根据你的操作系统而有所不同,所以选择最适合你环境的方案。
Windows
md getting-started-with-ceramic
MacOS/Linux
mkdir getting-started-with-ceramic
- 现在,在该目录下创建一个名为
index.html
的文件。index.html
文件应该包含以下内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<link rel="shortcut icon" href="/favicon.ico">
<title>Getting Started</title>
</head>
<body>
<!-- create header with connect button -->
<header class="SiteHeader">
<div class="HeaderContainer">
<h1 id="pageTitle">Getting Start With Ceramic</h1>
</div>
<div class="HeaderContainer">
<button id="walletBtn"></button>
</div>
</header>
<div class="MainCont">
<div class="DataBlocks">
<div class="DataBlock">
<div id="basicProfile">
<div class="BodyContainer">
<h2>Basic Profile</h2>
<p>Read from Ceramic Datamodel</p>
<br>
<p class="ProfileData" id="profileName"></p>
<p class="ProfileData" id="profileGender"></p>
<p class="ProfileData" id="profileCountry"></p>
</div>
</div>
</div>
</div>
<div class="ProfileForm">
<div class="BodyContainer">
<h2>Update Basic Profile on Ceramic</h2>
<br>
<form id="profileForm">
<div class="formfield">
<label class="formLabel" for="name">Name:</label>
<input class="forminput" type="text" id="name" placeholder="John Doe">
</div>
<div class="formfield">
<label class="formLabel" for="country">Country:</label>
<input class="forminput" type="text" id="country" placeholder="USA">
</div>
<div class="formfield">
<label class="formLabel" for="gender">Gender:</label>
<select class="forminput" id="gender">
<option value="female">Female</option>
<option value="male">Male</option>
<option value="non-binary">Non-Binary</option>
<option value="other">Other</option>
</select>
</div>
<div class="formfield">
<input class="forminput" type="submit" id="submitBtn" value="Submit">
</div>
</form>
</div>
</div>
</div>
<!-- <button id="setBasicProf">Set Profile</button>
<button id="getBasicProf">Get Profile</button> -->
<script src="dist/bundle.js" type="module"></script>
</body>
</html>
- 接下来,在
getting-started-with-ceramic
目录下创建一个名为style.css
的文件。这个文件应该包含以下内容:
* {
margin: 0;
padding: 0;
}
.SiteHeader {
display: flex;
justify-content: space-between;
padding: 10px;
background-color: orange;
}
.HeaderContainer {
display: flex;
align-items: center;
}
.MainCont {
display: flex;
justify-content: space-around;
padding: 10px;
}
.DataBlock {
margin-bottom: 10px;
}
.BodyContainer {
background-color: lightsalmon;
border: 1px solid black;
border-radius: 30px;
padding: 20px;
min-width: 250px;
}
.ProfileForm {
min-width: 400px;
}
.formfield {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.forminput {
min-width: 150px;
}
#submitBtn {
display: block;
margin: auto;
width: auto;
}
.ProfileData {
font-weight: bold;
}
现在,如果你在浏览器中打开index.html
文件,或使用LiveShare这样的工具,你应该看到这样的内容:
添加JavaScript和Ceramic
现在应用程序还没有实际功能,它没有内置的逻辑,它只是一个有一些内容和一些样式的静态页面。
在这一步,我将向你展示如何使用提供者、解析器和Ceramic将这个应用程序从一个静态网站转变为一个web 3 dapp!
-
首先,使用NPM或Yarn初始化一个新的NodeJS项目。
NPM
npm init -y
Yarn
yarn init -y
-
接下来,安装上述的依赖项:
NPM
开发中依赖项
npm install -D buffer dids key-did-provider-ed25519 key-did-resolver webpack webpack-cli
常规依赖项
npm install @ceramicnetwork/blockchain-utils-linking @ceramicnetwork/http-client @glazed/did-datastore @glazed/did-session
Yarn
开发依赖项
yarn add -D buffer dids key-did-provider-ed25519 key-did-resolver webpack webpack-cli
常规依赖项
yarn add @ceramicnetwork/blockchain-utils-linking @ceramicnetwork/http-client @glazed/did-datastore @glazed/did-session
- 现在,在
getting-started-with-ceramic
目录下创建一个名为main.js
的文件。 - 首先,将需要的depndencies导入该文件:
//main.js
import { CeramicClient } from '@ceramicnetwork/http-client'
import { EthereumAuthProvider } from '@ceramicnetwork/blockchain-utils-linking'
import { DIDDataStore } from '@glazed/did-datastore'
import { DIDSession } from '@glazed/did-session'
你是否注意到,有些软件包来自
@ceramicnetwork
,有些来自@glazed
?来自@ceramicnetwork的包是Ceramic核心协议的一部分。它们帮助应用程序连接到Ceramic节点。
来自@glazed的软件包不是核心Ceramic协议的一部分,它们被称为
中间件
,为开发者提供一些额外的功能和便利。
- 在依赖库导入之后,你应该设置一系列的DOM元素选择器(selctors)。这不仅使我们的代码在编写时更容易阅读,而且在更大的应用程序中,这种技术可以增加性能优势。在
main.js
中添加以下内容:
import { CeramicClient } from '@ceramicnetwork/http-client'
import { EthereumAuthProvider } from '@ceramicnetwork/blockchain-utils-linking'
import { DIDDataStore } from '@glazed/did-datastore'
import { DIDSession } from '@glazed/did-session'
const profileForm = document.getElementById('profileForm')
const walletBtn = document.getElementById('walletBtn')
const profileName = document.getElementById('profileName')
const profileGender = document.getElementById('profileGender')
const profileCountry = document.getElementById('profileCountry')
const submitBtn = document.getElementById('submitBtn')
- 使用刚刚导入的
CeramiClient
,通过在main.js
文件中添加以下代码,创建一个新的Ceramic客户端实例:
//main.js
import { CeramicClient } from '@ceramicnetwork/http-client'
import { EthereumAuthProvider } from '@ceramicnetwork/blockchain-utils-linking'
import { DIDDataStore } from '@glazed/did-datastore'
import { DIDSession } from '@glazed/did-session'
const profileForm = document.getElementById('profileForm')
const walletBtn = document.getElementById('walletBtn')
const profileName = document.getElementById('profileName')
const profileGender = document.getElementById('profileGender')
const profileCountry = document.getElementById('profileCountry')
const submitBtn = document.getElementById('submitBtn')
const ceramic = new CeramicClient("https://ceramic-clay.3boxlabs.com")
目前有4种可能的网络来供Ceramic HTTP客户端连接。你可以点击每个链接来了解更多关于网络的信息。
- 主网
- Clay Testnet (推荐使用,目前正被我们的应用程序使用)
- Dev Unstable
- 本地网络
- 接下来,创建一个名为
aliases
的变量,它将保存BasicProfile
数据模型的引用:
//main.js
import { CeramicClient } from '@ceramicnetwork/http-client'
import { EthereumAuthProvider } from '@ceramicnetwork/blockchain-utils-linking'
import { DIDDataStore } from '@glazed/did-datastore'
import { DIDSession } from '@glazed/did-session'
const profileForm = document.getElementById('profileForm')
const walletBtn = document.getElementById('walletBtn')
const profileName = document.getElementById('profileName')
const profileGender = document.getElementById('profileGender')
const profileCountry = document.getElementById('profileCountry')
const submitBtn = document.getElementById('submitBtn')
const ceramic = new CeramicClient("https://ceramic-clay.3boxlabs.com")
const aliases = {
schemas: {
basicProfile: 'ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio',
},
definitions: {
BasicProfile: 'kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic',
},
tiles: {},
}
数据模型的各个部分:
schemas
:定义数据模型的JSON schema
definitions
: 将一个用户友好的模型名称和描述链接到一个特定的schema。
tiles
: 在schema内参数集的各条数据记录。
DIDDataStore
允许应用程序从Ceramic写入和读取数据。DIDDataStore
是基于数据模型的。在main.js
中添加以下代码,以配置DIDDataStore
,它使用到上面定义的aliases
和ceramic instance
。
//main.js
import { CeramicClient } from '@ceramicnetwork/http-client'
import { EthereumAuthProvider } from '@ceramicnetwork/blockchain-utils-linking'
import { DIDDataStore } from '@glazed/did-datastore'
import { DIDSession } from '@glazed/did-session'
const profileForm = document.getElementById('profileForm')
const walletBtn = document.getElementById('walletBtn')
const profileName = document.getElementById('profileName')
const profileGender = document.getElementById('profileGender')
const profileCountry = document.getElementById('profileCountry')
const submitBtn = document.getElementById('submitBtn')
const ceramic = new CeramicClient("https://ceramic-clay.3boxlabs.com")
const aliases = {
schemas: {
basicProfile: 'ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio',
},
definitions: {
BasicProfile: 'kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic',
},
tiles: {},
}
const datastore = new DIDDataStore({ ceramic, model: aliases })
如果你的应用程序需要,你可以通过添加必要的
schema
、definition
和tiles
向aliases
变量添加更多的数据模型!
你现在有了启动和运行这个应用程序所需的基本基础。Ceramic客户端和数据模型的所有配置已经完成。
使用区块链进行认证
接下来的部分将指导你使用以太坊 Provider, Metamask,用以太坊区块链来验证用户。
正在使用的认证流程被称为Sign-In With Ethereum,以下简称为SIWE。
请看这篇很棒的文章以了解更多。为什么用以太坊登录是一个游戏规则改变者。
让我们把SIWE添加到这个应用中吧!
- 这个应用程序需要一个异步函数,把它命名为
authenticateWithEthereum
,它使用提供者,然后使用Resovler,最后把DID分配给你之前创建的Ceramic Client。在main.js
中添加这段代码来完成这些任务:
//main.js
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: 'eth_requestAccounts',
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
DIDSession
是它在这个代码片断中为你处理SIWE认证流程的。
- 在认证流程开始之前,通常有一些逻辑检查需要应用程序来完成。当开发dapp时,一个常见的检查是确保提供者是可用的。在我们的案例下,使用Metamask会在浏览器
window
对象中注入自己作为提供者。它可以通过window.ethereum
引用。如果应用程序的最终用户没有安装Metamask,或其他提供者,我们的应用程序将无法连接到区块链上。让我们把这些知识应用于一个新的异步函数,称为auth
。将下面的代码添加到main.js
中:
//main.js
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: 'eth_requestAccounts',
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
// newly added auth function here:
async function auth() {
if (window.ethereum == null) {
throw new Error('No injected Ethereum provider found')
}
await authenticateWithEthereum(window.ethereum)
}
auth()
首先检查window.ethereum
是否存在,然后再尝试调用authenticateWithEthereum()
。这可以防止应用程序在没有注入提供者的情况下挂起!
完整的main.js
文件目前看起来应该是这样的:
//main.js
// import all dependencies:
import { CeramicClient } from '@ceramicnetwork/http-client'
import { EthereumAuthProvider } from '@ceramicnetwork/blockchain-utils-linking'
import { DIDDataStore } from '@glazed/did-datastore'
import { DIDSession } from '@glazed/did-session'
//cache a reference to the DOM Elements
const profileForm = document.getElementById('profileForm')
const walletBtn = document.getElementById('walletBtn')
const profileName = document.getElementById('profileName')
const profileGender = document.getElementById('profileGender')
const profileCountry = document.getElementById('profileCountry')
const submitBtn = document.getElementById('submitBtn')
// create a new CeramicClient instance:
const ceramic = new CeramicClient("https://ceramic-clay.3boxlabs.com")
// reference the data models this application will use:
const aliases = {
schemas: {
basicProfile: 'ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio',
},
definitions: {
BasicProfile: 'kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic',
},
tiles: {},
}
// configure the datastore to use the ceramic instance and data models referenced above:
const datastore = new DIDDataStore({ ceramic, model: aliases })
// this function authenticates the user using SIWE
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: 'eth_requestAccounts',
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
// check for a provider, then authenticate if the user has one injected:
async function auth() {
if (window.ethereum == null) {
throw new Error('No injected Ethereum provider found')
}
await authenticateWithEthereum(window.ethereum)
}
使用Ceramic读取数据
接下来的函数将使用DIDDatastore
来从Ceramic网络中获取数据。我将称它为getProfileFromCeramic
,它也是一个异步函数。
函数将在main.js
文件中声明。
- 在
main.js
中添加getProfileFromCeramic
函数:
//main.js
async function getProfileFromCeramic() {
try {
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get('BasicProfile')
//render profile data to the DOM (not written yet)
renderProfileData(profile)
} catch (error) {
console.error(error)
}
}
正如你所看到的,通过调用datastore.get()
方法,你可以引用希望读取数据模型的定义
。
DIDDatastore使用分配给Ceramic客户端的DID来进行这个调用。它返回的profile对象存储在profile
变量中。
- 你将需要创建
renderProfileData
函数来提取这些资料数据并在浏览器窗口中显示。由于这不是一个网页开发的指南,我将不详细介绍这个函数的作用。在你的main.js
文件中加入以下内容:
function renderProfileData(data) {
if (!data) return
data.name ? profileName.innerHTML = "Name: " + data.name : profileName.innerHTML = "Name: "
data.gender ? profileGender.innerHTML = "Gender: " + data.gender : profileGender.innerHTML = "Gender: "
data.country ? profileCountry.innerHTML = "Country: " + data.country : profileCountry.innerHTML = "Country: "
}
我想指出的是,
data
是datastore.get()
调用返回的profile
对象。数据的属性在 “BasicProfile “数据模型中定义。查看Ceramic 数据模型仓库中的数据模型,可以查看完整的属性列表。
这就是使用 DIDDataStore
从Ceramic网络中读取数据的全部内容!
main.js
完整代码如下:
//main.js
// import all dependencies:
import { CeramicClient } from '@ceramicnetwork/http-client'
import { EthereumAuthProvider } from '@ceramicnetwork/blockchain-utils-linking'
import { DIDDataStore } from '@glazed/did-datastore'
import { DIDSession } from '@glazed/did-session'
//cache a reference to the DOM Elements
const profileForm = document.getElementById('profileForm')
const walletBtn = document.getElementById('walletBtn')
const profileName = document.getElementById('profileName')
const profileGender = document.getElementById('profileGender')
const profileCountry = document.getElementById('profileCountry')
const submitBtn = document.getElementById('submitBtn')
// create a new CeramicClient instance:
const ceramic = new CeramicClient("https://ceramic-clay.3boxlabs.com")
// reference the data models this application will use:
const aliases = {
schemas: {
basicProfile: 'ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio',
},
definitions: {
BasicProfile: 'kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic',
},
tiles: {},
}
// configure the datastore to use the ceramic instance and data models referenced above:
const datastore = new DIDDataStore({ ceramic, model: aliases })
// this function authenticates the user using SIWE
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: 'eth_requestAccounts',
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
// check for a provider, then authenticate if the user has one injected:
async function auth() {
if (window.ethereum == null) {
throw new Error('No injected Ethereum provider found')
}
await authenticateWithEthereum(window.ethereum)
}
//retrieve BasicProfile data from ceramic using the DIDDatastore
async function getProfileFromCeramic() {
try {
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get('BasicProfile')
//render profile data to the DOM (not written yet)
renderProfileData(profile)
} catch (error) {
console.error(error)
}
}
//Do some fun web dev stuff to present the BasicProfile in the DOM
function renderProfileData(data) {
if (!data) return
data.name ? profileName.innerHTML = "Name: " + data.name : profileName.innerHTML = "Name: "
data.gender ? profileGender.innerHTML = "Gender: " + data.gender : profileGender.innerHTML = "Gender: "
data.country ? profileCountry.innerHTML = "Country: " + data.country : profileCountry.innerHTML = "Country: "
}
使用Ceramic写入数据
接下来要实现的是使用 DIDDatastore
向Ceramic网络写数据。
- 像其他一些已经写好的函数一样,
updateProfileOnCeramic
函数应该是一个异步函数。在main.js
中添加以下内容:
async function updateProfileOnCeramic() {
try {
const updatedProfile = getFormProfile()
submitBtn.value = "Updating..."
//use the DIDDatastore to merge profile data to Ceramic
await datastore.merge('BasicProfile', updatedProfile)
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get('BasicProfile')
renderProfileData(profile)
submitBtn.value = "Submit"
} catch (error) {
console.error(error)
}
}
在继续前进之前,有两件重要的事情要了解:
首先:
DIDDatastore
有两个方法允许向数据模型写入:
merge()
:只写已经改变的字段。set()
:覆盖所有字段,包括那些没有改变的字段。这可能导致数据以不需要的方式被删除。由于这个原因,我们建议使用merge而不是set。其次:在本案例下,从DIDDatastore中读取数据并使用
renderProfileData()
将其渲染到DOM中并不是优秀的方式。在这个阶段没有真正的必要从Ceramic读取数据。这样做是为了向你展示读和写是多么简单,因为在使用DIDDatastore时,每一个都只需要一行代码。
- 你可能注意到在上述代码块中对
getFormProfile()
的调用。这个函数目前并不存在。现在让我们把它添加到main.js
中。在main.js
中加入以下代码:
function getFormProfile() {
const name = document.getElementById('name').value
const country = document.getElementById('country').value
const gender = document.getElementById('gender').value
return {
name,
country,
gender
}
}
如果你想知道如何想出
name
、country
和gender
这些对象属性的,它们都可以在BasicProfile数据模型中找到。BasicProfile还有一些额外的属性,在这个项目中没有被引用。你应该在自己的项目中探索这些属性的使用!
你成功了! 这就是你开始使用Ceramic所需要的一切。你现在知道的足够多了,足够去创造惊人的dapp。
不过你还没有完全完成。有一些小东西必须建立起来才能使这个应用程序完全工作。
完善引用
本节以及下一节配置Webpack,与Ceramic没有必然联系。这些部分涵盖了一些必要的操作,例如在应用程序的按钮上的操作,以及将服务器端转换成浏览器可以理解的代码。
按钮是如何工作的
应用程序的按钮元素将使用Event Listeners来让它们被点击时执行功能。
以下所有的代码都需要放在main.js
中:
- 让我们首先创建一个函数,当用户点击
连接钱包
按钮时,事件监听器可以调用这个函数:
async function connectWallet(authFunction, callback) {
try {
walletBtn.innerHTML = "Connecting..."
await authFunction()
await callback()
walletBtn.innerHTML = "Wallet Connected"
} catch (error) {
console.error(error)
}
}
- 目前,按钮元素没有显示任何
innerHTML
,所以在继续之前,让我们先解决这个问题。在之前发生在main.js
的DOM缓存下,添加以下一行:
walletBtn.innerHTML = "Connect Wallet"
- 另一个缺少的东西是文本占位符,配置文件数据应该在这里呈现。你可以通过在
walletBtn.innerHTML
行下添加这段代码来设置该占位符文本:
walletBtn.innerHTML = "Connect Wallet"
profileName.innerHTML = "Name: "
profileGender.innerHTML = "Gender: "
profileCountry.innerHTML = "Country: "
- 最后一件事是添加两个事件监听器。一个是 “连接钱包 “按钮,它将调用上面定义的
connectWallet
函数。另一个将放在作为profileForm
元素一部分的按钮上。在main.js
中添加这些代码:
walletBtn.addEventListener('click', async () => await connectWallet(auth, getProfileFromCeramic))
profileForm.addEventListener('submit', async (e) => {
e.preventDefault()
await updateProfileOnCeramic()
})
好了!这就是应用程序需要的所有JavaScript:
//main.js
// import all dependencies:
import { CeramicClient } from '@ceramicnetwork/http-client'
import { EthereumAuthProvider } from '@ceramicnetwork/blockchain-utils-linking'
import { DIDDataStore } from '@glazed/did-datastore'
import { DIDSession } from '@glazed/did-session'
//cache a reference to the DOM Elements
const profileForm = document.getElementById('profileForm')
const walletBtn = document.getElementById('walletBtn')
const profileName = document.getElementById('profileName')
const profileGender = document.getElementById('profileGender')
const profileCountry = document.getElementById('profileCountry')
const submitBtn = document.getElementById('submitBtn')
// give the wallet button an initial value to display
walletBtn.innerHTML = "Connect Wallet"
// setup placeholder text where profile should render
walletBtn.innerHTML = "Connect Wallet"
profileName.innerHTML = "Name: "
profileGender.innerHTML = "Gender: "
profileCountry.innerHTML = "Country: "
// create a new CeramicClient instance:
const ceramic = new CeramicClient("https://ceramic-clay.3boxlabs.com")
// reference the data models this application will use:
const aliases = {
schemas: {
basicProfile: 'ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio',
},
definitions: {
BasicProfile: 'kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic',
},
tiles: {},
}
// configure the datastore to use the ceramic instance and data models referenced above:
const datastore = new DIDDataStore({ ceramic, model: aliases })
// this function authenticates the user using SIWE
async function authenticateWithEthereum(ethereumProvider) {
const accounts = await ethereumProvider.request({
method: 'eth_requestAccounts',
})
const authProvider = new EthereumAuthProvider(ethereumProvider, accounts[0])
const session = new DIDSession({ authProvider })
const did = await session.authorize()
ceramic.did = did
}
// check for a provider, then authenticate if the user has one injected:
async function auth() {
if (window.ethereum == null) {
throw new Error('No injected Ethereum provider found')
}
await authenticateWithEthereum(window.ethereum)
}
//retrieve BasicProfile data from ceramic using the DIDDatastore
async function getProfileFromCeramic() {
try {
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get('BasicProfile')
//render profile data to the DOM (not written yet)
renderProfileData(profile)
} catch (error) {
console.error(error)
}
}
//Do some fun web dev stuff to present the BasicProfile in the DOM
function renderProfileData(data) {
if (!data) return
data.name ? profileName.innerHTML = "Name: " + data.name : profileName.innerHTML = "Name: "
data.gender ? profileGender.innerHTML = "Gender: " + data.gender : profileGender.innerHTML = "Gender: "
data.country ? profileCountry.innerHTML = "Country: " + data.country : profileCountry.innerHTML = "Country: "
}
//this function uses the datastore to write data to the Ceramic Network as well as read data back before populating the changes in the DOM
async function updateProfileOnCeramic() {
try {
const updatedProfile = getFormProfile()
submitBtn.value = "Updating..."
//use the DIDDatastore to merge profile data to Ceramic
await datastore.merge('BasicProfile', updatedProfile)
//use the DIDDatastore to get profile data from Ceramic
const profile = await datastore.get('BasicProfile')
renderProfileData(profile)
submitBtn.value = "Submit"
} catch (error) {
console.error(error)
}
}
// Parse the form and return the values so the BasicProfile can be updated
function getFormProfile() {
const name = document.getElementById('name').value
const country = document.getElementById('country').value
const gender = document.getElementById('gender').value
// object needs to conform to the datamodel
// name -> exists
// hair-color -> DOES NOT EXIST
return {
name,
country,
gender
}
}
//a simple utility funciton that will get called from the event listener attached to the connect wallet button
async function connectWallet(authFunction, callback) {
try {
walletBtn.innerHTML = "Connecting..."
await authFunction()
await callback()
walletBtn.innerHTML = "Wallet Connected"
} catch (error) {
console.error(error)
}
}
//add both event listeners to that the buttons work when they are clicked
walletBtn.addEventListener('click', async () => await connectWallet(auth, getProfileFromCeramic))
profileForm.addEventListener('submit', async (e) => {
e.preventDefault()
await updateProfileOnCeramic()
})
配置Webpack
下面的部分将为这个应用程序配置Webpack。
- 在
getting-started-with-ceramic
目录下创建一个名为webpack.config.js
的新文件,并在其中放置以下内容。
const path = require('path');
module.exports = {
entry: './main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
mode: 'development',
resolve: {
fallback: { buffer: require.resolve('buffer') }
}
}
如果你想知道这段代码的作用,请务必查看Webpack。
- 接下来,你需要编辑目前存在于根目录下的
package.json
文件。你将只修改这个文件的scripts
。对package.json
做如下修改:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
}
为了清楚起见,这里的改动是增加了一个脚本,名为build,用来调用webpack。
完整的package.json
可以在下面找到:
{
"name": "getting-started-ceramic",
"version": "1.0.0",
"description": "",
"main": "utils.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"buffer": "^6.0.3",
"dids": "^3.1.0",
"key-did-provider-ed25519": "^2.0.0",
"key-did-resolver": "^2.0.4",
"webpack": "^5.72.1",
"webpack-cli": "^4.9.2"
},
"dependencies": {
"@ceramicnetwork/blockchain-utils-linking": "^2.0.4",
"@ceramicnetwork/http-client": "^2.0.4",
"@glazed/did-datastore": "^0.3.1",
"@glazed/did-session": "^0.0.1"
}
}
根据你完成本指南的时间,文件上可能有小的版本差异。这是正常的,没有什么可担心的。
- 最后一步是在终端或命令行中运行这个新添加的脚本。运行这个脚本就可以把以前所有的JavaScript打包成一个你的浏览器可以解释的版本。不管是什么操作系统,命令都是一样的:
NPM
npm run build
Yarn
yarn run build
恭喜你
恭喜你! 你现在可以在浏览器中重新打开index.html
文件,或者通过使用LiveShare。
使用你的Metamask钱包,你将能够用Ethereum登录,从Ceramic检索你的BasicProfile
,并对该配置文件的一组有限属性进行修改!
如果你从未在Ceramic Network上配置过
BasicProfile
,你最初将不会收到数据。你需要使用选择的钱包账户,通过Self.id应用程序或使用本应用程序所包含的表格来创建一个配置文件!
本翻译由 Duet Protocol 赞助支持。
本文参与区块链开发网写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
- 发表于 2022-06-30 15:52
- 阅读 ( 1385 )
- 学分 ( 67 )
- 分类:DApp