ddd009

ddd009

twitter

Dfinity 前端開發教程

title: Dfinity 前端開發教程#

date: 2021-08-05 15:58:32

Dfinity 前端開發教程#

作者 转载请注明出处

前段時間 Uniswap 對某些幣種的前端限制引起了很大的爭議,Dfinity 是一個很好的全棧去中心化解決方案,本篇教程從 dfx new helloworld 開始講起,詳細的介紹了如何在 dfinity 上進行前端開發,並講解了如何使用官方提供的 identity 服務(identity.ic0.app),一起來加入去中心化前端開發的世界吧!
閱讀本文前需熟悉 dfx 開發工具的使用

教程目錄:#

  • 從 hello world 入手

  • 前端腳手架

  • 關於 webpack 配置和 dev server

  • 如何調用第三方 canister

  • 如何使用 Internet Identity 服務

從 hello world 入手#

在任意目錄下輸入dfx new helloworld, 你會得到一個初始項目。運行dfx start & dfx deploy

在瀏覽器輸入前端id.localhost:8000(推薦) 或 localhost:8000/?canisterId=前端id可以得到類似於下圖的界面

截屏 2021-07-28 下午 5.54.03.png

js 代碼如下

import { Actor, HttpAgent } from "@dfinity/agent";
import {
  idlFactory as helloworld_idl,
  canisterId as helloworld_id,
} from "dfx-generated/helloworld";

const agent = new HttpAgent();
const helloworld = Actor.createActor(helloworld_idl, {
  agent,
  canisterId: helloworld_id,
});

document.getElementById("clickMeBtn").addEventListener("click", async () => {
  const name = document.getElementById("name").value.toString();
  const greeting = await helloworld.greet(name);
  document.getElementById("greeting").innerText = greeting;
});

這段代碼先是創建了一個 HttpAgent,用於發送網絡請求。這裡 agent 默認的 host 是你網絡環境的地址,例如你在 localhost:8000 部署了前端,這裡 agent 就會向 localhost:8000 發送請求。所以當你使用 dev server 時一定要注意手動設置 host,下文中會講解 dev server 的使用。
如下是設置 host 的代碼(生產環境中不要使用)

const agent = new HttpAgent({
     host: "http://localhost:8000",
})

因為 dfinity 中的 canister 使用的 Actor 模型,前端在調用 canister 時需要先創建一個 actor,這裡你可以簡單理解為調用後端的工具。

接下來的就是創建 actor 的代碼,這裡需要三個參數,IDL,agnet,canisterId,agent 就是我們剛才所創建的 HttpAgent,另外兩個參數将在如何調用第三方 canister中介紹

當我們完成了 actor 創建後就可以通過 actor 對後端的 public method 進行調用,注意這裡的調用都是異步調用。對於異步調用的處理本篇教程就不再展開,比較推薦的方法是使用 async/await 語法。這段代碼調用了 greeting 方法並返回了一段文本。

以上就是一個最簡單的 dfinity 前端應用程序,了解了這些過程就可以進行前後端的交互。

前端腳手架#

我們使用 vue,react 等框架進行前端開發時,一般會使用腳手架。dfinity 社區中有許多可以使用的腳手架,但目前大多處於早期階段,使用起來可能會遇到一些問題
create-ic-app
create-ic-app 使用了新型前端構建工具 Vite,支持 react、vue、typescript、vanilla 等,並還在持續更新,Vite 的 dev server 用起來比 webpack 快很多,推薦嘗試
dfinity-vue
vue 腳手架,在一個分支上集成了 vuetify,如需使用切換到 vuetify 分之即可
cra-template-dfx
react 腳手架,上次更新是七個月前,僅供參考不推薦使用

關於 webpack 配置和 dev server#

dfinity 項目默認使用 webpack 打包,並自動生成了配置文件,我們需要做的其實非常少。
在講 webpack 之前我們需要打開 dfx.json 來了解一下默認配置,如果你經常使用 dfx 這個工具對於 dfx.json 應該很熟悉,不過這裡還是簡單講一下

{
 "canisters": {
     ...
    //assets 是前端canister的名字,在dfinity中所有的代碼都部署在canister中
   "assets": {
       //前端canister的dependency
     "dependencies": [
       "backend"
     ],
    //frontend.entrypoint是前端文件的entry point,這個我們要在webpack中用到
     "frontend": {
       "entrypoint": "dist/index.html"
     },
   //source指定dist和src目錄
     "source": [
       "dist/"
     ],
   //指定canister的類型為前端canister
     "type": "assets"
   }
 }
}

截屏 2021-07-28 下午 7.47.19.png

如果你用過 dfx 就會知道它的開發體驗是非常差的,每改一下代碼就要重新編譯部署,對於前端開發來說我們一定是需要一個 dev server

安裝 webpack-dev-server 直接輸入npm i webpack-dev-server

安裝完成後就可以通過 webpack serve 或者是 webpack-dev-server 命令啟動 Dev Server, 我的版本是 3.11.2 , 需要使用 webpack serve啟動。

也可以在 package.json 中配置 dev 選項

截屏 2021-07-30 下午 12.24.21.png

這樣就可以輸入npm run dev 啟動 Dev server

在 webpack 中添加 pluginnew webpack.HotModuleReplacementPlugin(),

然後添加 dev server 配置

   devServer: {
      hot: true,
    }

就能實現修改文件後自動重新編譯

如何調用第三方 canister#

內容參考kyle 的博客

調用 canister 需要兩個參數,一個 IDL 接口描述,一個 canister ID

IDL 是對 canister 數據類型和接口的描述,在本地部署 canister 時會自動生成一個 canister.did.js 文件

截屏 2021-07-31 下午 2.33.38.png

export default ({ IDL }) => {
  return IDL.Service({ 'greet' : IDL.Func([IDL.Text], [IDL.Text], []) });
};
export const init = ({ IDL }) => { return []; };

hello world 的 IDL 長這樣

import { idlFactory } from "dfx-generated/helloworld";

import { idlFactory } from './helloworld.did.js';

導入 IDL

如果是第三方 canister 你可以到 ic.rocks 找到對應的 IDL 文件,以 identity 服務為例

截屏 2021-07-31 下午 2.46.28.png

截屏 2021-07-31 下午 2.46.52.png

選擇 javascript,新建一個 identity.did.js, 複製粘貼進去(ts 是 identity.did.d.ts)

// src/declarations/identity/index.js
import { Actor, HttpAgent } from "@dfinity/agent";

// Imports and re-exports candid interface
import { idlFactory } from './identity.did.js';
export { idlFactory } from './identity.did.js';
// CANISTER_ID is replaced by webpack based on node environment
export const canisterId = process.env.IDENTITY_CANISTER_ID;

/**
 * 
 * @param {string | import("@dfinity/principal").Principal} canisterId Canister ID of Agent
 * @param {{agentOptions?: import("@dfinity/agent").HttpAgentOptions; actorOptions?: import("@dfinity/agent").ActorConfig}} [options]
 * @return {import("@dfinity/agent").ActorSubclass<import("./identity.did.js")._SERVICE>}
 */
 export const createActor = (canisterId, options) => {
  const agent = new HttpAgent({ ...options?.agentOptions });
  
  // Fetch root key for certificate validation during development
  if(process.env.NODE_ENV !== "production") {
    agent.fetchRootKey().catch(err=>{
      console.warn("Unable to fetch root key. Check to ensure that your local replica is running");
      console.error(err);
    });
  }

  // Creates an actor with using the candid interface and the HttpAgent
  return Actor.createActor(idlFactory, {
    agent,
    canisterId,
    ...options?.actorOptions,
  });
};
  
/**
 * A ready-to-use agent for the identity canister
 * @type {import("@dfinity/agent").ActorSubclass<import("./identity.did.js")._SERVICE>}
 */
 export const identity = createActor(canisterId);

創建一個用於調用 identity 服務的 actor

如果只有 candid 文件,可以通過 dfx 工具生成一份 did.js,不過這種方式比較繁瑣,還有一種更簡單的方式是通過官方提供的didc 工具

自動安裝腳本:(複製保存為 sh 文件運行)

unameOut="$(uname -s)"
case "${unameOut}" in
    Linux*)     machine=Linux;;
    Darwin*)    machine=Mac;;
    *)          machine="UNKNOWN:${unameOut}"
esac

release=$(curl --silent "https://api.github.com/repos/dfinity/candid/releases/latest" | grep -e '"tag_name"' | cut -c 16-25)

if [ ${machine} = "Mac" ]
then
  echo "Downloading didc for Mac to ~/bin/didc"
  curl -fsSL https://github.com/dfinity/candid/releases/download/${release}/didc-macos > /usr/local/bin/didc
elif [ ${machine} = "Linux" ]
then
  echo "Downloading didc for Linux to ~/bin/didc"
  curl -fsSL https://github.com/dfinity/candid/releases/download/${release}/didc-linux64 > ~/bin/didc
else
  echo "Could not detect a supported operating system. Please note that didc is currently only supported for Mac and Linux"
fi

date

安裝完成後輸入didc bind ./identity.did -t js > ./identity.did.js就可以生成 did.js 文件 (didc bind ./identity.did -t ts > ./identity.did.d.ts ts 版本)

如何使用 Internet Identity 服務#

Internet Identity (簡稱 II) 是 dfinity 官方推出的 DID 服務,基於 WebAuth。在 dfinity 上的應用一般會支持使用 II 登錄。

如果想要在本地的開發環境調試 II,需要把Ineternet Identity clone 到本地,並部署到你本地用於開發的 dfx 網絡環境中

git clone [email protected]:dfinity/internet-identity.git
cd internet-identity
dfx start --clean

這裡推薦使用 --clean 參數啟動,因為 II 服務的默認 canister id 與本地錢包的默認 id 相同,所以先清掃一下環境避免衝突。注意因為本地能啟動多個 dfx 網絡,一定要保證 II 部署的網絡與項目的網絡相同(端口相同)

npm install
II_ENV=development dfx deploy --no-wallet --argument '(null)'

這樣就完成了 II 的本地部署

打開 package.json 確保你安裝了 @dfinity/auth-client、@dfinity/authentication

下面是前端調用 II 服務的代碼

const init = async () => {
  authClient = await AuthClient.create();
  await authClient.login({
      maxTimeToLive: BigInt("0x7f7f7f7f7f"),
      identityProvider:
        "http://localhost:8000/?canisterId=rwlgt-iiaaa-aaaaa-aaaaa-cai"
      onSuccess: async () => {
        handleAuthenticated(authClient);
      },
    });
}

這段代碼同樣非常容易理解,先是創建 authclient,然後調用 login 方法。
maxTimeToLive 是設置委託密鑰的有效時間,identityProvider 就是 II 部署到的 canister,如果在主網的話就是 identity.ic0.app,onSuccess 是認證完成後的回調函數。這裡還有很多參數可以指定,具體可以查看Agent JS Docs,不再展開。

下面講一下如何發起認證過的調用,前面 helloworld 的例子中講過如何發起調用,不過使用的是隨機生成的匿名身份。當用戶完成認證後我們就能獲取到他們的身份,如何使用用戶的身份發起認證過的調用呢,如下,在創建 agent 時傳入 identity 即可

 async function handleAuthenticated(authClient) {
     identity = await authClient.getIdentity(); 
      const agent = new HttpAgent({
        identity: identity,
        host: "http://localhost:8000",
      });
     //本地開發時需要獲取Root Key
      if(process.env.NODE_ENV !== "production") await agent.fetchRootKey();
      let actor = Actor.createActor(idlFactory, {
        agent,
        canisterId: canisterId,
      });
     const greeting = await actor.greet(name);
  }
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。