はじめに
AppSyncのサンプルアプリとかはあちこちありますが、自分のデータだけ Subscriptionする方法が、最初取り組んだときに分かりにくかったので、ここにまとめます。
サンプル
チャットアプリを考えます。
ここでは簡単のために、1対1のチャットを考えます。
以下は、まだSubscriptionを定義していないサンプルスキーマです。
type ChatRoom {
id: ID!
userIds: [Int!]!
}
type Message {
chatRoomId: ID!
fromUserId: Int!
sendTimestamp: AWSTimestamp!
content: AWSJSON!
}
type Mutation {
addMessage(chatRoomId: ID!, toUserId: Int!, content: AWSJSON!): Message!
}
type Query {
# 本当はページネーションとか考慮いるけど省略
chatRooms: [ChatRoom!]!
messages(chatRoomId: ID!):[Message!]!
}
schema {
query: Query
mutation: Mutation
}
Subscriptionの設定
チャット相手がメッセージを送信したことを検知できるように、addMessageをSubscriptionします。
とりあえずSubscription定義
type Subscription {
onAddMessage: Message @aws_subscribe(mutations: ["addMessage"])
}
とりあえずsubscriptionを定義しました!!
ただこの状態だと、自分のデータだけでなく他人のデータを含む全てのメッセージをSubscriptionします😆😆😆
Subscriptionの引数を定義
自分のデータだけSubscriptionするようにしましょう。以下はスキーマの関連する部分を抜き出しています。
type Message {
chatRoomId: ID!
toUserId: Int! # 追加!!
fromUserId: Int!
sendTimestamp: AWSTimestamp!
content: AWSJSON!
}
type Mutation {
addMessage(chatRoomId: ID!, toUserId: Int!, content: AWSJSON!): Message!
}
type Subscription {
onAddMessage(toUserId: Int!): Message @aws_subscribe(mutations: ["addMessage"])
}
AppSyncでは、Subscriptionに引数を追加することで、特定のデータだけSubscriptionする仕組みがあります。
https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/real-time-data.html#using-subscription-arguments
ここでは「自分に送信された」メッセージだけSubscriptionしたいので、引数にtoUserId
を追加します。
さらに、toUserId
で絞り込むためには、MutationのaddMessageの戻り値にtoUserId
を含む必要があります。
(1対1のチャットを前提としているので、MessageはfromUserIdだけあれば問題ないのですが、SubscriptionのためにtoUserIdを冗長に持たせています。改善の余地はありますが、とりあえず説明用のサンプルとして)
これで送信先が一致する場合のみSubscriptionできるようになりました!!
が、まだダメです、、toUserIdに自分のIDではなく他人のIDを指定した場合、他人のメッセージをSubscriptuonできちゃいます😆😆😆
AppSync側でtoUserIdを確認する
ここでは、AppSyncのAPIはCognito認証が必要であり、Cogniteユーザの属性に自分のuserIdを含む前提となります。
まずは、Subscription(onAddMessage)にリゾルバーをアタッチします。
リゾルバーのリクエストマッピングテンプレートは何もする必要はありません。
レスポンスマッピングテンプレートで、
Subscriptionの引数で指定されたtoUserIdと、認証トークンに含まれるuserIdが一致するか確認し、不一致の場合は認可エラーを返します。
toUserIdに自分以外のidを設定した場合は、エラーを返すようになりました。
これで完成です!!
まとめ
Subscriptionを含めたスキーマ全体です。
type ChatRoom {
id: ID!
userIds: [Int!]!
}
type Message {
chatRoomId: ID!
toUserId: Int!
fromUserId: Int!
sendTimestamp: AWSTimestamp!
content: AWSJSON!
}
type Mutation {
addMessage(chatRoomId: ID!, toUserId: Int!, content: AWSJSON!): Message!
}
type Query {
chatRooms: [ChatRoom!]!
messages(chatRoomId: ID!):[Message!]!
}
type Subscription {
onAddMessage(toUserId: Int!): Message @aws_subscribe(mutations: ["addMessage"])
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
↓レスポンスマッピングテンプレートのコピペ用
#if($context.identity.claims.get("custom:user_id") != \${context.arguments.toUserId})
$utils.unauthorized()
#else
null
#end