メインコンテンツまでスキップ

lesson-4_ノートの取得・追加機能を完成させよう

📞 バックエンドキャニスターと通信する準備をしよう

前回のレッスンで、認証機能が完成しました。ここからは、実際にバックエンドキャニスターの関数を呼び出してノートを取得・追加する処理を実装していきましょう! ノートの暗号化はまだ行わないので、データはそのまま保存します。

hooks/authContext.tsのsetupService関数/** STEP3: バックエンドキャニスターを呼び出す準備をします。 */の下に、コードを追加します。

/** STEP3: バックエンドキャニスターを呼び出す準備をします。 */
// 取得した`identity`を使用して、ICと対話する`agent`を作成します。
const newAgent = new HttpAgent({ identity });
if (process.env.DFX_NETWORK === "local") {
newAgent.fetchRootKey();
}
// 認証したユーザーの情報で`actor`を作成します。
const options = { agent: newAgent };
const actor = createActor(canisterId, options);

更新したコードを確認していきましょう。

バックエンドキャニスターと通信を行うためには、仲介者が必要です。その役割を果たすのがHttpAgentと呼ばれるクラスです。

Internet Identityによる認証で取得したユーザーの情報(identity)を元に、HttpAgentを初期化します。初期化に用いたデータが、バックエンドキャニスターへのメッセージ送信に使用されるプリンシパルとなります。

// 取得した`identity`を使用して、ICと対話する`agent`を作成します。
const newAgent = new HttpAgent({ identity });

デプロイ先のネットワークがローカルの時、fetchRootKey関数を呼び出します。デフォルトでは、エージェントはメインネットと通信するように設定されており、ハードコードされた公開鍵を使用してレスポンスを検証します。そのため、ローカルネットワークにデプロイしたキャニスターと通信する際には、この関数を呼び出してキャニスターの公開鍵を取得する必要があります(⚠️ この関数はメインネットと通信していない時のみに使用します)。

if (process.env.DFX_NETWORK === "local") {
newAgent.fetchRootKey();
}

最後に、アクターを作成します。アクターは、キャニスター間の通信でやり取りされるメッセージを処理するオブジェクトのことで、非同期にメッセージを処理します。アクターを作成するには、createActor関数を使用します。

// 認証したユーザーの情報で`actor`を作成します。
const options = { agent: newAgent };
const actor = createActor(canisterId, options);

なお、createActor関数はバックエンドキャニスターをデプロイした際に生成されるIDLファイルから取得します。

import {
canisterId,
createActor,
} from "../../../declarations/encrypted_notes_backend";

最後に、setupService関数の一番下に定義しているsetAuth({ status: 'SYNCHRONIZING' });を下記のコードに更新しましょう。setAuth関数に取得したデータを設定して、認証状態を更新します。

setAuth({ actor, authClient, cryptoService, status: "SYNCED" });

🎤 バックエンドキャニスターの関数を呼び出そう

バックエンドキャニスターと通信する準備ができたので、実際にバックエンドキャニスターの関数を呼び出してみましょう。

routes/notes/index.tsxのNotesコンポーネントを更新します。まずは、getNotes関数内で空配列を設定しているsetNotes([]);の部分を、下記のように更新しましょう。

const getNotes = async () => {
if (auth.status !== "SYNCED") {
console.error(`CryptoService is not synced.`);
return;
}

try {
// バックエンドキャニスターからノート一覧を取得します。
const notes = await auth.actor.getNotes();
setNotes(notes);
} catch (err) {
showMessage({
title: "Failed to get notes",
status: "error",
});
}
};

getNotes関数は、はじめにauth.statusを確認しています。この値がSYNCEDではない時、つまり認証が未完了の時はすぐに終了します。認証が完了している時はauth.actorを用いて、バックエンドキャニスターのgetNotes関数を呼び出します。取得したノート一覧は、setNotes関数を用いてnotesにセットします。実行中にエラーが発生した場合は、showMessage関数を用いてエラーメッセージを表示します。

次に、addNote関数内でログ出力をしているconsole.log('add note');の部分を下記のように更新しましょう。

const addNote = async () => {
if (auth.status !== "SYNCED") {
console.error(`CryptoService is not synced.`);
return;
}

setIsLoading(true);

try {
// バックエンドキャニスターにノートを追加します。
await auth.actor.addNote(currentNote.data);
await getNotes();
} catch (err) {
showMessage({
title: "Failed to add note",
status: "error",
});
} finally {
onCloseNoteModal();
setIsLoading(false);
}
};

バックエンドキャニスターのaddNote関数を呼び出します。実行中にエラーが発生した場合の処理も、getNotes関数と同様です。auth.actor.addNote関数には、currentNote.dataを引数として渡しています。この変数は、useStateで定義されたステート変数です。

const [currentNote, setCurrentNote] = useState<EncryptedNote | undefined>(
undefined
);

このステート変数の値はcomponents/NoteModal/index.tsxに定義されているNoteModalコンポーネントで更新されます。setCurrentNote関数を辿ってみてください!

✅ 動作確認をしよう

まずは、ノートを追加してみましょう。ノートを追加するには、右上の「+ New Note」をクリックします。

モーダルが開いたら、テキストを入力して「Save」をクリックします。

追加したノートが表示されていたら実装は完成です!

📝 このレッスンで追加したコード

  • src/hooks/authContext.ts
  const setupService = async (authClient: AuthClient) => {
/** STEP2: 認証したユーザーのデータを取得します。 */
const identity = authClient.getIdentity();

/** STEP3: バックエンドキャニスターを呼び出す準備をします。 */
+ // 取得した`identity`を使用して、ICと対話する`agent`を作成します。
+ const newAgent = new HttpAgent({ identity });
+ if (process.env.DFX_NETWORK === 'local') {
+ newAgent.fetchRootKey();
+ }
+ // 認証したユーザーの情報で`actor`を作成します。
+ const options = { agent: newAgent };
+ const actor = createActor(canisterId, options);
+
/** STEP5: CryptoServiceクラスのインスタンスを生成します。 */
const cryptoService = new CryptoService();

/** STEP7: デバイスデータの設定を行います。 */

- setAuth({ status: 'SYNCHRONIZING' });
+ setAuth({ actor, authClient, cryptoService, status: 'SYNCED' });
};
  • src/routes/notes/index.tsx
export const Notes = () => {

...

const addNote = async () => {
if (auth.status !== 'SYNCED') {
console.error(`CryptoService is not synced.`);
return;
}

setIsLoading(true);

try {
- // バックエンドキャニスターにノートを追加します。
- console.log('add note');
+ await auth.actor.addNote(currentNote.data);
+ await getNotes();
} catch (err) {
showMessage({
title: 'Failed to add note',
status: 'error',
});
} finally {
onCloseNoteModal();
setIsLoading(false);
}
};

...

const getNotes = async () => {
if (auth.status !== 'SYNCED') {
console.error(`CryptoService is not synced.`);
return;
}

try {
// バックエンドキャニスターからノート一覧を取得します。
- setNotes([]);
+ const notes = await auth.actor.getNotes();
+ setNotes(notes);
} catch (err) {
showMessage({
title: 'Failed to get notes',
status: 'error',
});
}
};

...

};

🙋‍♂️ 質問する

ここまでの作業で何かわからないことがある場合は、Discordの#icpで質問をしてください。

ヘルプをするときのフローが円滑になるので、エラーレポートには下記の4点を記載してください ✨

1. 質問が関連しているセクション番号とレッスン番号
2. 何をしようとしていたか
3. エラー文をコピー&ペースト
4. エラー画面のスクリーンショット

次のレッスンに進み、ノートの編集・削除機能を完成させましょう!