2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Web3.0検証(20)-MeteorでTODO管理アプリの開発(publish/subscribeによるデータ制御)

Last updated at Posted at 2022-05-31
[前回] Web3.0検証(19)-MeteorでTODO管理アプリの開発(データアクセス制御)

はじめに

前回に続き、セキュリティ関連のデータ保護機能です。
publish/subscribe機能を用いて、データ同期を制御することで、
サーバー側の機微な情報が、誤ってクライアントに送信されないように保護します。

理論: publish(公開)/subscribe(購読)によるデータ同期

[クライアント(subscribe)] <--- (データ) ---> [(publish)サーバー]

  • Meteorでは,サーバー上のデータはクライアントにキャッシュされ,リアルタイムに同期される
    • 同期は自動的に、双方向で行われる
  • 開発者が明示的に同期処理を記述する必要はない
    • Meteor.publish()Meteor.subscribe()を使用し,データ同期の範囲を指定するのみ

準備

  • 端末を二つ用意

    • 端末1: meteor起動/確認
    • 端末2: コード修正
  • 端末1から、Meteorアプリを実行

$ cd simple-todos-react
$ meteor run
  • ブラウザでアプリを確認
    • http://localhost:3000/にアクセス
    • デベロッパーツールを開く(F12)
    • デバイスのツールバーを切り替えアイコンをクリック
    • デバイスを選択
      • 今回は、iPhone SEを選択

publish/subscribeの実装

  • 今まで、クライアントにデータベースのデータが自動同期されていた
    • Tasks.find()を呼び出すと、コレクション内のすべてのタスクが取得される
    • クライアントに個人情報など保存すると、プライバシーが侵害される恐れがある
  • クライアント側に送信するデータを制御する必要あり

autopublishパッケージを削除

  • autopublishパッケージにより、データベースコンテンツがクライアントに自動同期される
  • 端末2から、安全でないパッケージを削除
$ cd simple-todos-react
$ meteor remove autopublish
  • アプリが更新されると、タスクリストは空になる
    • autopublishパッケージがないので、サーバーからクライアントへの送信データを明示的に指定する必要があるため
  • データ同期に使用されるMeteor関数
    • Meteor.publish: サーバーからクライアントにデータを公開する
    • Meteor.subscribe: クライアントで、サーバーから公開されたデータを購読(⁠同期して取り込む)

タスクの公開(publish)

  • 最初にパブリケーションをサーバーに追加
    • 認証されたユーザーからのすべてのタスクを公開
    • Methodsと同様、パブリケーション関数でthis.userIdを使用し、認証されたuserIdを取得可能
  • apiフォルダーにtasksPublications.jsファイルを新規作成
imports/api/tasksPublications.js
import { Meteor } from 'meteor/meteor';
import { TasksCollection } from '/imports/db/TasksCollection';

Meteor.publish('tasks', function publishTasks() {
  return TasksCollection.find({ userId: this.userId });
});
  • この関数内でthisを使用しているため、アロー関数(arrow function)=>は使用できない
    • アロー関数はthisコンテキストを提供しないため
    • 代わりに、functionキーワードを用いて、従来の関数定義を使用
  • サーバーがパブリケーションを登録していることを確認
    • server/main.jsで、tasksPublicationsをインポートし、強制的に評価
server/main.js
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';
import '/imports/api/tasksPublications';

タスクの購読(subscribe)

  • クライアントで上述パブリケーションをサブスクライブ
    • パブリケーションから変更を受け取るため、useTrackerフック内でsubscribeを呼び出す
  • 単一のuseTrackerを使用し、TasksCollectionからデータ取得するように、ついでにコードをリファクタリング
imports/ui/App.jsx
... ...
  const { tasks, pendingTasksCount, isLoading } = useTracker(() => {
    const noDataAvailable = { tasks: [], pendingTasksCount: 0 };
    if (!Meteor.user()) {
      return noDataAvailable;
    }
    const handler = Meteor.subscribe('tasks');

    if (!handler.ready()) {
      return { ...noDataAvailable, isLoading: true };
    }

    const tasks = TasksCollection.find(
      hideCompleted ? pendingOnlyFilter : userFilter,
      {
        sort: { createdAt: -1 },
      }
    ).fetch();
    const pendingTasksCount = TasksCollection.find(pendingOnlyFilter).count();

    return { tasks, pendingTasksCount };
  });
... ...
  <div className="main">
    {user ? (
      <Fragment>
    <div className="user" onClick={logout}>
      {user.username || user.services ? user.services.github.username : ''} 🚪
    </div>

    <TaskForm />

読み込み中ステータス表示

  • サブスクリプションデータが準備できていない場合、ユーザーに知らせる必要あり
    • アプリのローディングステータスを画面表示
  • サブスクリプションの準備ができているかの確認方法
    • subscribe呼び出しの結果を確認
      • リターン値は、サブスクリプション状態オブジェクトで、booleanを返すready関数が含まれる
imports/ui/App.jsx
... ...
            <div className="filter">
              <button onClick={() => setHideCompleted(!hideCompleted)}>
                {hideCompleted ? 'Show All' : 'Hide Completed'}
              </button>
            </div>

            {isLoading && <div className="loading">loading...</div>}

            <ul className="tasks">
... ...
  • 読み込み中表示のスタイル設定
client/main.css
.loading {
  display: flex;
  flex-direction: column;
  height: 100%;

  justify-content: center;
  align-items: center;

  font-weight: bold;
}
  • 上記修正が反映されると、すべてのタスクが再表示される
    • サーバーで、Meteor.publishを呼び出すと
      • tasksという名前のパブリケーションが登録される
    • クライアントで、Meteor.subscribeをパブリケーション名で呼び出すと
      • クライアントはパブリケーションからの下記データをサブスクライブ
        • 認証されたユーザーが所有するデータベース内の全タスク

ユーザー権限を確認

  • 現状、誰でもブラウザconsoleを使用し、MeteorMethodsを呼び出せる
    • 例えば、removeメソッドからvalidationを削除し、有効なtask _idを渡すと、削除できてしまう
    • 試しに、DevToolsコンソールタブを使用し、以下のようなコマンドを入力し、Enter
      Meteor.call('tasks.remove'、'xtPTsNECC3KPuMnDu');
  • タスク所有者のみ、特定タスクを変更可能に制限する必要あり
    • メソッドを変更し、認証されたユーザーがタスク作成ユーザーと一致するか確認
imports/api/tasksMethods.js
... ...
  'tasks.remove'(taskId) {
    check(taskId, String);

    if (!this.userId) {
      throw new Meteor.Error('Not authorized.');
    }

    const task = TasksCollection.findOne({ _id: taskId, userId: this.userId });

    if (!task) {
      throw new Meteor.Error('Access denied.');
    }

    TasksCollection.remove(taskId);
  },

  'tasks.setIsChecked'(taskId, isChecked) {
    check(taskId, String);
    check(isChecked, Boolean);

    if (!this.userId) {
      throw new Meteor.Error('Not authorized.');
    }

    const task = TasksCollection.findOne({ _id: taskId, userId: this.userId });

    if (!task) {
      throw new Meteor.Error('Access denied.');
    }

    TasksCollection.update(taskId, {
      $set: {
        isChecked,
      },
    });
  },
... ...

再度、ブラウザからアプリを確認

  • ログイン
    • ユーザー名: meteorite
    • パスワード: password

image.png

ログインユーザーのタスク一覧が正常に表示されました。

おわりに

publish/subscribeを用いて、クライアント/サーバー間のデータやり取りを制御しました。
次回も続きます。お楽しみに。

[次回] Web3.0検証(21)-MeteorでTODO管理アプリの開発(リグレッションテスト)
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?