原文链接:Getting Started With Ceramic
本指南基于 js-ceramic 包 Ceramic V2.0.0 版本编写。
在这篇初学者友好的指南中,我将为您提供将 Ceramic Network 集成到您的 Web3 dapps 所需的所有工具和知识。
Ceramic Network 是一个去中心化的数据网络,旨在为 Web3 dapps 带来可组合的数据。Ceramic 可以处理多种类型的数据,但对于本指南,我们可以将 Ceramic 视为去中心化的 NoSQL 文档数据库。
本指南旨在让您边学边做,因此在继续阅读时会有图表和代码示例出现。
除了这份书面指南,我还提供了一个GitHub 仓库,其中包含我将参考的所有代码。
如果您更喜欢视频而不是书面教程,您可以在Ceramic Youtube 频道观看视频演练。
在开始之前,您应该具有如下的通用 Web 开发技能:
本指南中使用的技能
可选技能
所需工具(需要提前安装)
在开始之前,我将介绍将在本指南中使用的一些关键术语。
通常称为 DID.
DID 是包含您的元数据的唯一的标识符。例如您的公钥,一些验证信息,允许您访问的服务点以及其他一些事情。
简而言之,DID 被用作 Ceramic 的账户标识符。
此处用到的依赖包有:
DIDs
DID 解析器将 DID 作为输入,并返回 DID Document.
此解析过程将 DID 从通用事物转变为文档,该文档能准确描述一个身份以及该身份允许执行哪些方法和功能。
简而言之,解析器将 DID 与它能够执行的操作结合起来。
此处用到的依赖包有:
key-did-resolver
@glazed/did-datastore
如果您希望您的应用程序能够访问区块链,则需要使用提供者。
本指南连接到以太坊区块链,因此使用以太坊提供者。
提供者用于代替您自己运行的区块链节点。提供者有两个主要任务:
Metamask 是最受欢迎的区块链提供者之一,我们将用它作为提供者把我们的应用程序连接到以太坊区块链。
简而言之,提供者对用户进行身份验证以在区块链上执行操作。
此处用到的依赖包有:
key-did-provider-ed25519
@glazed/did-session
@ceramicnetwork/blockchain-utils-linking
当我提及数据流时,我并不是从消费的角度谈论流数据。Ceramic 将它的数据结构称为流。请随意阅读有关流的更多信息。
StreamType 只是流的一种可能的数据结构。在本指南中,我们将间接使用TileDocument
StreamType,您可以将其视为 JSON 对象。 这些 StreamTypes 处理与数据相关的一切,它们在 Ceramic 节点 上运行。
简而言之,StreamTypes 定义了数据结构以及数据的状态被允许如何改变。
此处用到的依赖包有:
@glazed/did-datastore
数据模型通常用于表示应用程序功能。例如笔记、用户档案、博客文章甚至是社交图。
数据模型是可组合数据的核心。单个应用程序使用多个数据模型很常见,而单个数据模型跨多个应用程序使用也很常见!
以这种方式完成的可组合性也会为开发人员带来更好的体验。在 Ceramic 上构建应用程序就像浏览数据模型市场,将它们插入您的应用程序,并自动获取存储在网络上符合这些数据模型的所有数据的访问权限。
简而言之,数据模型使应用程序中的数据可组合性成为可能。
您将构建一个简单的 Web 应用程序,该应用程序对 Ceramic 网络上的数据执行简单的读写操作。要使此应用程序正常工作,它需要按序完成以下步骤。
TileDocument
流执行读写操作。我在“我们需要谈论的事情”部分中提到了上面的一些依赖项,但在进一步讨论之前,还有其他一些依赖项值得一提。
This is the web client that allows your application to connect to Ceramic nodes that are a part of the network.
这个 Web 客户端允许您的应用程序连接到作为网络一部分的Ceramic 节点。
此处用到的依赖包有:
@ceramicnetwork/http-client
您将编写的 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 等实用程序,您应该会看到如下内容:
现在你的应用程序不能做任何事情。它没有内置的逻辑,只是一个带有一些内容和一些样式的静态页面。
在这一步中,我将向您展示如何使用提供者、解析器和 Ceramic 来把此应用程序从静态站点变成 web3 dapp!
首先,使用 NPM 或 Yarn 初始化一个新的 NodeJS 项目:
NPM
npm init -y
Yarn
yarn init -y
接下来,安装前文所述依赖项:
NPM
开发依赖 Dev dependencies
npm install -D buffer dids key-did-provider-ed25519 key-did-resolver webpack webpack-cli
普通依赖 Regular dependencies
npm install @ceramicnetwork/blockchain-utils-linking @ceramicnetwork/http-client @glazed/did-datastore @glazed/did-session
Yarn
开发依赖 Dev dependencies
yarn add -D buffer dids key-did-provider-ed25519 key-did-resolver webpack webpack-cli
普通依赖 Regular dependencies
yarn add @ceramicnetwork/blockchain-utils-linking @ceramicnetwork/http-client @glazed/did-datastore @glazed/did-session
现在,在 getting-started-with-ceramic
根目录下创建 main.js
文件。
从引入普通依赖开始:
//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 协议的一部分,它们被称为
middleware
,为开发人员提供一些附加功能和便利。
导入依赖项后,您应该设置一系列 DOM 元素选择器。这不仅能让我们编写的代码更易于阅读,而且在更大的应用程序中,这种技术可以增加性能优势。将以下内容添加到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
, 创建一个新的 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 客户端连接。点击链接了解各个网络的详情。
- 主网 Mainnet
- Clay 测试网 Clay Testnet (推荐,我们的程序正在使用)
- Dev 测试网 Dev Unstable
- 本地网络 Local
接下来创建一个名为 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 模式。
definitions
: 将用户友好的模型名称和描述链接到特定模式。
tiles
: 基于模式内设置的参数的单个数据记录 (records)
DIDDataStore
允许应用程序从 Ceramic 中读写数据。 DIDDataStore
基于数据模型。 添加以下内容以配置 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 })
根据您的 dapp 的需要,您可以通过添加必要的
schema
,definition
和tiles
来向aliases
变量中添加更多数据模型!
很好!您已经具备了启动和运行此应用程序所需的基本基础。Ceramic 客户端和数据模型的所有配置均已完成。
下一部分将指导您使用以太坊提供者 Metamask 通过以太坊区块链对用户进行身份验证。
将要使用的身份验证流程称为 使用以太坊登录 Sign-In With Ethereum, 但从这里开始我将其简称为 SIWE。
查看这篇精彩的文章以了解更多信息:为什么 Sign-In With Ethereum 是游戏规则改变者.
让我们将 SIWE 添加到此应用程序中!
这个应用程序需要一个异步函数,我将命名它authenticateWithEthereum
,它使用提供者(Provider)、解析器 (Resovler),最后将 DID 分配给您之前创建的 Ceramic 客户端。在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 身份验证流程。
在启动身份验证流程之前,我们的应用程序通常需要进行一些逻辑检查。在开发 dapps 时,一个常见的检查是确保提供者可用。在本例中,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
}
// 新添加的函数:
async function auth() {
if (window.ethereum == null) {
throw new Error('No injected Ethereum provider found')
}
await authenticateWithEthereum(window.ethereum)
}
在尝试调用 authenticateWithEthereum()
之前,auth()
首先检查 window.ethereum
是否存在。这可以防止程序在用户没有注入提供者 (injected provider) 时处于悬空状态!
如果您想检查您的工作,完整的main.js
文件当前应该如下所示:
//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'
// 引用 DOM 元素
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')
// 创建新 CeramicClient 实例
const ceramic = new CeramicClient("https://ceramic-clay.3boxlabs.com")
// 引用数据模型 data model,将在本应用程序中使用
const aliases = {
schemas: {
basicProfile: 'ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio',
},
definitions: {
BasicProfile: 'kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic',
},
tiles: {},
}
// 使用 ceramic 实例和数据模型配置数据存储 datastore
const datastore = new DIDDataStore({ ceramic, model: aliases })
// 这个函数使用 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
}
// 检查提供者, 如果存在, 则对用户进行验证
async function auth() {
if (window.ethereum == null) {
throw new Error('No injected Ethereum provider found')
}
await authenticateWithEthereum(window.ethereum)
}
您编写的下一个函数将使用DIDDatastore
来从 Ceramic 网络获取数据,我称之为getProfileFromCeramic
. 类似前面的函数,它将是异步的。
该函数将在main.js
文件中声明。
将getProfileFromCeramic
函数添加到main.js
:
//main.js
async function getProfileFromCeramic() {
try {
//使用 DIDDatastore 从 Ceramic 获取 profile 数据
const profile = await datastore.get('BasicProfile')
//向 DOM 渲染 profile 数据 (未修改)
renderProfileData(profile)
} catch (error) {
console.error(error)
}
}
如您所见,通过调用datastore.get()
方法,您可以简单地引用您希望从中读取数据的数据模型的definition
.
DIDDatastore 使用分配给 Ceramic 客户端的 DID 来执行此调用。它返回 profile 对象并保存在 profile
变量中。
您将需要创建renderProfileData
函数来提取此配置文件数据并将其显示在浏览器窗口中。由于本教程不是 Web 开发指南,因此我不会详细介绍此功能的作用。将以下内容添加到您的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
对象。data
的属性在BasicProfile
数据模型中定义。查看 Ceramic 数据模型仓库 以获取完整属性列表。
就是这样!这就是使用DIDDataStore
从 Ceramic 网络读取数据的全部内容!
目前,完整的main.js
应该像这样:
//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'
// 引用 DOM 元素
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')
// 创建新 CeramicClient 实例
const ceramic = new CeramicClient("https://ceramic-clay.3boxlabs.com")
// 引用数据模型 data model,将在本应用程序中使用
const aliases = {
schemas: {
basicProfile: 'ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio',
},
definitions: {
BasicProfile: 'kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic',
},
tiles: {},
}
// 使用 ceramic 实例和数据模型配置数据存储 datastore
const datastore = new DIDDataStore({ ceramic, model: aliases })
// 这个函数使用 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
}
// 检查提供者, 如果存在, 则对用户进行验证
async function auth() {
if (window.ethereum == null) {
throw new Error('No injected Ethereum provider found')
}
await authenticateWithEthereum(window.ethereum)
}
// 使用 DIDDatastore 从 Ceramic 获取 BasicProfile 数据
async function getProfileFromCeramic() {
try {
//使用 DIDDatastore 从 Ceramic 获取 profile 数据
const profile = await datastore.get('BasicProfile')
//向 DOM 渲染 profile 数据 (未修改)
renderProfileData(profile)
} catch (error) {
console.error(error)
}
}
// 做一些有趣的 web 开发者工作,在 DOM 中展示 BasicProfile
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: "
}
下一个要实现的部分是使用DIDDatastore
向 Ceramic 网络写入数据。
与已编写的其他一些函数一样,updateProfileOnCeramic
函数也应是异步的。将以下内容添加到main.js
:
async function updateProfileOnCeramic() {
try {
const updatedProfile = getFormProfile()
submitBtn.value = "Updating..."
//使用 DIDDatastore 合并 Ceramic 中的 profile 数据
await datastore.merge('BasicProfile', updatedProfile)
//使用 DIDDatastore 从 Ceramic 获取 profile 数据
const profile = await datastore.get('BasicProfile')
renderProfileData(profile)
submitBtn.value = "Submit"
} catch (error) {
console.error(error)
}
}
在继续之前,有两件重要的事情要谈。
首先,
DIDDatastore
有两种写入数据模型的方法:
merge()
,仅写入已更改的字段set()
,它会覆盖所有字段,包括那些未更改的。这可能会导致数据被不必要地删除。出于这个原因,建议使用merge
而不是set
。其次, 在这种情况下,使用
renderProfileData()
从 DIDDatastore 读取数据,仅为了将其渲染到 DOM,其实是次优的。在这个阶段从 Ceramic 读取数据并没有必要。这是为了向您展示读取和写入可以多么简单,因为在使用 DIDDatastore 时这两种操作都仅占一行。
您可能已经注意到,上述代码块中出现了getFormProfile()
的调用。该函数目前尚不存在。让我们现在添加它。将以下代码添加到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 所需的全部内容。您现在知道的足够多,能够创建令人惊叹的 dapps.
不过,现在还没有完成。为了让这个应用程序完全工作,还要做其他一些小事情。
本节和下一节“配置Webpack”,和 Ceramic 并不相关。这两节涵盖了一些必须执行的任务,让应用程序的按钮正常工作,并将服务器端转换为浏览器可以理解的内容。
按钮的工作原理
此应用程序的按钮元素将使用事件侦听器以在被单击时执行函数。
将下面所有代码加入 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
(译注:innerHTML
属性表示元素的后代,设置 innerHTML
的值可以让你轻松地将当前元素的内容替换为新的内容。参考MDN文档 innerHTML),让我们在继续进行前修复它。在那些 DOM 元素引用的后面添加这一行:
walletBtn.innerHTML = "Connect Wallet"
另一个缺少的东西是文本占位符,它们应该在 profile 数据被渲染的地方。 在 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
完整文件仔细检查您的工作:
//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'
// 引用 DOM 元素
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')
// 给钱包按钮赋初始值
walletBtn.innerHTML = "Connect Wallet"
// 设置 profile 占位文本
walletBtn.innerHTML = "Connect Wallet"
profileName.innerHTML = "Name: "
profileGender.innerHTML = "Gender: "
profileCountry.innerHTML = "Country: "
// 创建新 CeramicClient 实例
const ceramic = new CeramicClient("https://ceramic-clay.3boxlabs.com")
// 引用数据模型 data model,将在本应用程序中使用
const aliases = {
schemas: {
basicProfile: 'ceramic://k3y52l7qbv1frxt706gqfzmq6cbqdkptzk8uudaryhlkf6ly9vx21hqu4r6k1jqio',
},
definitions: {
BasicProfile: 'kjzl6cwe1jw145cjbeko9kil8g9bxszjhyde21ob8epxuxkaon1izyqsu8wgcic',
},
tiles: {},
}
// 使用 ceramic 实例和数据模型配置数据存储 datastore
const datastore = new DIDDataStore({ ceramic, model: aliases })
// 这个函数使用 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
}
// 检查提供者, 如果存在, 则对用户进行验证
async function auth() {
if (window.ethereum == null) {
throw new Error('No injected Ethereum provider found')
}
await authenticateWithEthereum(window.ethereum)
}
// 使用 DIDDatastore 从 Ceramic 获取 BasicProfile 数据
async function getProfileFromCeramic() {
try {
//使用 DIDDatastore 从 Ceramic 获取 profile 数据
const profile = await datastore.get('BasicProfile')
//向 DOM 渲染 profile 数据 (未修改)
renderProfileData(profile)
} catch (error) {
console.error(error)
}
}
// 做一些有趣的 web 开发者工作,在 DOM 中展示 BasicProfile
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: "
}
// 这个函数使用 datastore 向 Ceramic 网络写入数据,并在向 DOM 中应用更改前从 Ceramic 网络读取(修改后的)数据
async function updateProfileOnCeramic() {
try {
const updatedProfile = getFormProfile()
submitBtn.value = "Updating..."
//使用 DIDDatastore 合并 Ceramic 中的 profile 数据
await datastore.merge('BasicProfile', updatedProfile)
//使用 DIDDatastore 从 Ceramic 获取 profile 数据
const profile = await datastore.get('BasicProfile')
renderProfileData(profile)
submitBtn.value = "Submit"
} catch (error) {
console.error(error)
}
}
// 解析表单并返回值, 让 BasicProfile 能够更新
function getFormProfile() {
const name = document.getElementById('name').value
const country = document.getElementById('country').value
const gender = document.getElementById('gender').value
// 对象需要与数据模型相一致
// name -> 存在
// hair-color -> **不存在**
return {
name,
country,
gender
}
}
// 一个简单的功能函数,会被 附加到"连接钱包"按钮的事件侦听器 调用
async function connectWallet(authFunction, callback) {
try {
walletBtn.innerHTML = "Connecting..."
await authFunction()
await callback()
walletBtn.innerHTML = "Wallet Connected"
} catch (error) {
console.error(error)
}
}
// 将事件侦听器附加到按钮
walletBtn.addEventListener('click', async () => await connectWallet(auth, getProfileFromCeramic))
profileForm.addEventListener('submit', async (e) => {
e.preventDefault()
await updateProfileOnCeramic()
})
本节将为此应用程序配置 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
部分。
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
}
清楚起见,这里所做的更改是添加了一个名为 build 的 script, 它调用 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
恭喜!您现在可以在浏览器中或使用 LiveShare 重新打开index.html
文件。
使用您的 Metamask 钱包,您将能够使用以太坊登录,从 Ceramic 获取您的BasicProfile
并更改该 profile 的一组有限属性!
如果您未在 Ceramic 网络上配置过
BasicProfile
,最初将不会收到任何数据。您需要使用您的钱包帐户,在 Self.id 或直接利用此应用程序的表单来创建个人资料!
请务必加入 Ceramic Discord 以获得更多帮助并与开发团队交流!
祝您好运,happy building!