DEV Community

Kihara, Takuya
Kihara, Takuya

Posted on

AWS Amplify Subscriptions Usage / 3. Closed Chat

Previous articles are "Open Chat", this means "Not authenticated users can post and read".

Then, this article shows "Closed Chat".

First, we just use @auth directive.
Second, we add a Subscription type with an argument.
Third, we update both @auth directive and a Subscription type.

TOC

3.1 Use @auth directive and perform Authentication

Add and use auth

$ amplify add auth
Scanning for plugins...
Plugin scan successful

Using service: Cognito, provided by: awscloudformation

 The current configured provider is Amazon Cognito. 

 Do you want to use the default authentication and security configuration? Default configuration
 Warning: you will not be able to edit these selections. 
 How do you want users to be able to sign in? Username
 Do you want to configure advanced settings? No, I am done.
Successfully added auth resource sampleamplifysubscriXXXXXXXX locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

$
Enter fullscreen mode Exit fullscreen mode

And, push project.

$ amplify push


✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name                | Operation | Provider plugin   |
| -------- | ---------------------------- | --------- | ----------------- |
| Auth     | sampleamplifysubscriXXXXXXXX | Create    | awscloudformation |
| Api      | sampleamplifysubscri         | No Change | awscloudformation |
? Are you sure you want to continue? Yes
⠙ Updating resources in the cloud. This may take a few minutes...

(snip)

✔ All resources are updated in the cloud


$
Enter fullscreen mode Exit fullscreen mode

Then, update api.

$ amplify update api
? Please select from one of the below mentioned services: GraphQL
? Select from the options below Update auth settings
? Choose the default authorization type for the API Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project.
? Configure additional auth types? No

The following types do not have '@auth' enabled. Consider using @auth with @model
         - OpenChat
         - RoomChat
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/auth


GraphQL schema compiled successfully.

Edit your schema at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema.graphql or place .graphql files in a directory at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema
The API_KEY auth type has been removed from the API.
If other resources depend on this API, run "amplify update <category>" and reselect this API to remove the dependency on the API key.
⚠️  This must be done before running "amplify push" to prevent a push failure
Successfully updated resource
$
Enter fullscreen mode Exit fullscreen mode

This mean, we can use @auth directive and can only use userPools.

Update graphql schema file.

amplify/backend/api/sampleamplifysubscri/schema.graphql

type CloseRoomChat
  @model
  @auth(rules: [{ allow: owner, provider: userPools }]) {
  id: ID!
  roomName: String!
  message: String!
}
Enter fullscreen mode Exit fullscreen mode

And, push project again.

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name                | Operation | Provider plugin   |
| -------- | ---------------------------- | --------- | ----------------- |
| Api      | sampleamplifysubscri         | Update    | awscloudformation |
| Auth     | sampleamplifysubscriXXXXXXXX | No Change | awscloudformation |
? Are you sure you want to continue? Yes

The following types do not have '@auth' enabled. Consider using @auth with @model
         - OpenChat
         - RoomChat
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/auth


GraphQL schema compiled successfully.

Edit your schema at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema.graphql or place .graphql files in a directory at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema
? Do you want to update code for your updated GraphQL API Yes
? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types?
This will overwrite your current graphql queries, mutations and subscriptions Yes
⠴ Updating resources in the cloud. This may take a few minutes...

(snip)

✔ Generated GraphQL operations successfully and saved at src/graphql
✔ All resources are updated in the cloud

GraphQL endpoint: https://XXXXXXXXXXXXXXXXXXXXXXXXXX.appsync-api.ap-northeast-1.amazonaws.com/graphql


$
Enter fullscreen mode Exit fullscreen mode

Implement Sign-in page.

Please see official document.
https://docs.amplify.aws/ui/auth/authenticator/q/framework/vue

And this code.
src/views/SignIn.vue

Implement Closed Chat page.

src/views/CloseChat.vue

<template>
  <v-container>
    <v-row>
      <v-col cols="12">
        <v-card>
          <v-card-title>Multi-room Close Chat</v-card-title>
          <v-card-text
            >Only authenticated users can use this chat. All subscriptions are
            received.
          </v-card-text>
        </v-card>
      </v-col>
    </v-row>
    <v-row>
      <v-col cols="12">
        <v-text-field
          v-model="inputMessage"
          label="New Message"
          outlined
          clearable
          append-outer-icon="mdi-send"
          @click:append-outer="sendMessage"
        ></v-text-field>
      </v-col>
    </v-row>
    <v-tabs
      v-model="roomName"
      background-color="primary"
      center-active
      centered
      dark
    >
      <v-tab
        v-for="(room, index) in rooms"
        :key="index"
        :href="'#' + room"
        @click="setSubscribeByRoomName(room)"
        >{{ room }}</v-tab
      >
    </v-tabs>
    <v-card flat>
      <v-tabs-items v-model="roomName">
        <v-tab-item v-for="(room, index) in rooms" :key="index" :value="room">
          <v-row class="pa-2">
            <v-col cols="6">
              <ChatList title="Input" :list="messages[room]"></ChatList>
            </v-col>
            <v-col cols="6">
              <ChatList
                title="Subscriptions"
                :list="subscriptionMessages[room]"
              ></ChatList>
            </v-col>
          </v-row>
        </v-tab-item>
      </v-tabs-items>
    </v-card>
  </v-container>
</template>

<script>
import { Auth, API, graphqlOperation } from 'aws-amplify'
import { createCloseRoomChat } from '@/graphql/mutations'
import { onCreateCloseRoomChat } from '@/graphql/subscriptions'

import ChatList from '@/components/ChatList'

export default {
  components: { ChatList },
  data: function() {
    return {
      user: null,
      roomName: null,
      inputMessage: '',
      rooms: ['room1', 'room2'],
      messages: {
        room1: [],
        room2: [],
      },
      subscriptionMessages: {
        room1: [],
        room2: [],
      },
      onCreateMultiRoomChatSubscriptions: {
        room1: null,
        room2: null,
      },
    }
  },
  created: async function() {
    this.user = await Auth.currentUserInfo()
    this.setSubscribeByRoomName('room1')
  },
  beforeDestroy: function() {
    this.clearSubscriptions()
  },
  methods: {
    sendMessage: async function() {
      const message = await API.graphql(
        graphqlOperation(createCloseRoomChat, {
          input: { message: this.inputMessage, roomName: this.roomName },
        }),
      )
      console.log(message)

      this.messages[this.roomName].push(message.data.createCloseRoomChat)
      this.inputMessage = ''
    },
    setSubscribeByRoomName(roomName) {
      this.clearSubscriptions()

      this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql(
        graphqlOperation(onCreateCloseRoomChat, { owner: this.user.username }),
      ).subscribe({
        next: ({ provider, value }) => {
          console.log({ provider, value })
          this.subscriptionMessages[
            value.data.onCreateCloseRoomChat.roomName
          ].push(value.data.onCreateCloseRoomChat)
        },
      })
    },
    clearSubscriptions() {
      this.rooms.forEach(room => {
        if (this.onCreateMultiRoomChatSubscriptions[room]) {
          this.onCreateMultiRoomChatSubscriptions[room].unsubscribe()
        }
        this.onCreateMultiRoomChatSubscriptions[room] = null
      })
    },
  },
}
</script>

<style></style>
Enter fullscreen mode Exit fullscreen mode

See other files:
https://github.com/tacck/sample-amplify-subscriptions/tree/3-1-close-chat

The important point is here.

(snip)

import { Auth, API, graphqlOperation } from 'aws-amplify'
import { createCloseRoomChat } from '@/graphql/mutations'
import { onCreateCloseRoomChat } from '@/graphql/subscriptions'

(snip)

  created: async function() {
    this.user = await Auth.currentUserInfo()
    this.setSubscribeByRoomName('room1')
  },

(snip)

    sendMessage: async function() {
      const message = await API.graphql(
        graphqlOperation(createCloseRoomChat, {
...
      this.messages[this.roomName].push(message.data.createCloseRoomChat)
...

(snip)

    setSubscribeByRoomName(roomName) {
      this.clearSubscriptions()

      this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql(
        graphqlOperation(onCreateCloseRoomChat, { owner: this.user.username }),
...
          this.subscriptionMessages[
            value.data.onCreateCloseRoomChat.roomName
          ].push(value.data.onCreateCloseRoomChat)
...

(snip)
Enter fullscreen mode Exit fullscreen mode

createCloseRoomChat use same as createRoomChat used previous article.

onCreateCloseRoomChat use instead of onCreateRoomChat used previous article.
And, onCreateCloseRoomChat needs with argument owner.

owner information is got like this.

this.user = await Auth.currentUserInfo()
this.user.username // your sign-in username
Enter fullscreen mode Exit fullscreen mode

Anyway, now we can check our first "Closed Chat".
But when you try this, You may be a little disappointed.

You post message and receive your own message.

Next, you open another browser, sign-in another user, open "Closed Chat", then post message.

You can't receive another message on original browser.

3.2 Add a Subscription type with an argument

Update graphql schema

So, we add a Subscription type with an argument roomName like onCreateRoomChatByRoomName that added by previous article.

Update graphql schema file.

amplify/backend/api/sampleamplifysubscri/schema.graphql

type Subscription {
  onCreateRoomChatByRoomName(roomName: String!): RoomChat
    @aws_subscribe(mutations: ["createRoomChat"])
  onCreateCloseRoomChatByRoomName(roomName: String!): CloseRoomChat
    @aws_subscribe(mutations: ["createCloseRoomChat"])
}
Enter fullscreen mode Exit fullscreen mode

And, push project.

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name                | Operation | Provider plugin   |
| -------- | ---------------------------- | --------- | ----------------- |
| Api      | sampleamplifysubscri         | Update    | awscloudformation |
| Auth     | sampleamplifysubscriXXXXXXXX | No Change | awscloudformation |
? Are you sure you want to continue? Yes

The following types do not have '@auth' enabled. Consider using @auth with @model
         - OpenChat
         - RoomChat
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/auth


GraphQL schema compiled successfully.

Edit your schema at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema.graphql or place .graphql files in a directory at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema
? Do you want to update code for your updated GraphQL API Yes
? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types?
This will overwrite your current graphql queries, mutations and subscriptions Yes
⠴ Updating resources in the cloud. This may take a few minutes...

(snip)

✔ All resources are updated in the cloud

GraphQL endpoint: https://XXXXXXXXXXXXXXXXXXXXXXXXXX.appsync-api.ap-northeast-1.amazonaws.com/graphql


$
Enter fullscreen mode Exit fullscreen mode

Update Closed Chat

src/views/CloseChat.vue

<template>
  <v-container>
    <v-row>
      <v-col cols="12">
        <v-card>
          <v-card-title>Multi-room Close Chat</v-card-title>
          <v-card-text
            >Only authenticated users can use this chat. All subscriptions are
            received.
          </v-card-text>
        </v-card>
      </v-col>
    </v-row>
    <v-row>
      <v-col cols="12">
        <v-text-field
          v-model="inputMessage"
          label="New Message"
          outlined
          clearable
          append-outer-icon="mdi-send"
          @click:append-outer="sendMessage"
        ></v-text-field>
      </v-col>
    </v-row>
    <v-tabs
      v-model="roomName"
      background-color="primary"
      center-active
      centered
      dark
    >
      <v-tab
        v-for="(room, index) in rooms"
        :key="index"
        :href="'#' + room"
        @click="setSubscribeByRoomName(room)"
        >{{ room }}</v-tab
      >
    </v-tabs>
    <v-card flat>
      <v-tabs-items v-model="roomName">
        <v-tab-item v-for="(room, index) in rooms" :key="index" :value="room">
          <v-row class="pa-2">
            <v-col cols="6">
              <ChatList title="Input" :list="messages[room]"></ChatList>
            </v-col>
            <v-col cols="6">
              <ChatList
                title="Subscriptions"
                :list="subscriptionMessages[room]"
              ></ChatList>
            </v-col>
          </v-row>
        </v-tab-item>
      </v-tabs-items>
    </v-card>
  </v-container>
</template>

<script>
import { Auth, API, graphqlOperation } from 'aws-amplify'
import { createCloseRoomChat } from '@/graphql/mutations'
import { onCreateCloseRoomChatByRoomName } from '@/graphql/subscriptions'

import ChatList from '@/components/ChatList'

export default {
  components: { ChatList },
  data: function() {
    return {
      user: null,
      roomName: null,
      inputMessage: '',
      rooms: ['room1', 'room2'],
      messages: {
        room1: [],
        room2: [],
      },
      subscriptionMessages: {
        room1: [],
        room2: [],
      },
      onCreateMultiRoomChatSubscriptions: {
        room1: null,
        room2: null,
      },
    }
  },
  created: async function() {
    this.user = await Auth.currentUserInfo()
    this.setSubscribeByRoomName('room1')
  },
  beforeDestroy: function() {
    this.clearSubscriptions()
  },
  methods: {
    sendMessage: async function() {
      const message = await API.graphql(
        graphqlOperation(createCloseRoomChat, {
          input: { message: this.inputMessage, roomName: this.roomName },
        }),
      )
      console.log(message)

      this.messages[this.roomName].push(message.data.createCloseRoomChat)
      this.inputMessage = ''
    },
    setSubscribeByRoomName(roomName) {
      this.clearSubscriptions()

      this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql(
        graphqlOperation(onCreateCloseRoomChatByRoomName, {
          roomName: roomName,
        }),
      ).subscribe({
        next: ({ provider, value }) => {
          console.log({ provider, value })
          this.subscriptionMessages[
            value.data.onCreateCloseRoomChatByRoomName.roomName
          ].push(value.data.onCreateCloseRoomChatByRoomName)
        },
      })
    },
    clearSubscriptions() {
      this.rooms.forEach(room => {
        if (this.onCreateMultiRoomChatSubscriptions[room]) {
          this.onCreateMultiRoomChatSubscriptions[room].unsubscribe()
        }
        this.onCreateMultiRoomChatSubscriptions[room] = null
      })
    },
  },
}
</script>

<style></style>
Enter fullscreen mode Exit fullscreen mode

See other files:
https://github.com/tacck/sample-amplify-subscriptions/tree/3-2-multi-room-close-chat

The important point is here.

(snip)

import { onCreateCloseRoomChatByRoomName } from '@/graphql/subscriptions'

(snip)

    setSubscribeByRoomName(roomName) {
...
      this.onCreateMultiRoomChatSubscriptions[roomName] = API.graphql(
        graphqlOperation(onCreateCloseRoomChatByRoomName, {
          roomName: roomName,
        }),
...
          this.subscriptionMessages[
            value.data.onCreateCloseRoomChatByRoomName.roomName
          ].push(value.data.onCreateCloseRoomChatByRoomName)
...

(snip)
Enter fullscreen mode Exit fullscreen mode

onCreateCloseRoomChatByRoomName use same as onCreateRoomChatByRoomName used previous article.

And, onCreateRoomChatByRoomName needs with argument roomName.

You try this program, you can receive another user's message.

This mean, "only authorized users can post and read this chat room."

3.3 Closed post, Open read chat (Update both @auth directive and a Subscription)

Previous program is not bad.

But, we often use "authorized users can post but all users can read" system.

So, we make it.

Update api for multi-authorization

Previously, we use only Amazon Cognito User Pool.
Here, we use Amazon Cognito User Pool and API key.

$ amplify update api
? Please select from one of the below mentioned services: GraphQL
? Select from the options below Update auth settings
? Choose the default authorization type for the API Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project.
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API API key
API key configuration
? Enter a description for the API key: 
? After how many days from now the API key should expire (1-365): 365

The following types do not have '@auth' enabled. Consider using @auth with @model
         - OpenChat
         - RoomChat
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/auth


GraphQL schema compiled successfully.

Edit your schema at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema.graphql or place .graphql files in a directory at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema
The API_KEY auth type has been added to the API.
⚠️  If other resources depend on this API and need access to the API key, run "amplify update <category>" and reselect this API as a dependency to add the API key dependency.
Successfully updated resource
$
Enter fullscreen mode Exit fullscreen mode

Then, update graphql schema file.

amplify/backend/api/sampleamplifysubscri/schema.graphql

type CloseRoomChat
  @model
  @auth(
    rules: [
      { allow: owner, provider: userPools }
      { allow: public, provider: apiKey, operations: [read] }
    ]
  ) {
  id: ID!
  roomName: String!
  message: String!
}

type Subscription {
  onCreateRoomChatByRoomName(roomName: String!): RoomChat
    @aws_subscribe(mutations: ["createRoomChat"])
  onCreateCloseRoomChatByRoomName(roomName: String!): CloseRoomChat
    @aws_subscribe(mutations: ["createCloseRoomChat"])
    @aws_api_key
}
Enter fullscreen mode Exit fullscreen mode

Notice:
@aws_api_key is not in "Amplify docs".
https://docs.amplify.aws/

This appears in "AppSync docs".
https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#using-additional-authorization-modes

And, push project.

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name                | Operation | Provider plugin   |
| -------- | ---------------------------- | --------- | ----------------- |
| Api      | sampleamplifysubscri         | Update    | awscloudformation |
| Auth     | sampleamplifysubscriXXXXXXXX | No Change | awscloudformation |
? Are you sure you want to continue? Yes

The following types do not have '@auth' enabled. Consider using @auth with @model
         - OpenChat
         - RoomChat
Learn more about @auth here: https://docs.amplify.aws/cli/graphql-transformer/auth


GraphQL schema compiled successfully.

Edit your schema at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema.graphql or place .graphql files in a directory at /[YOUR_DIRECTORY]/sample-amplify-subscriptions/amplify/backend/api/sampleamplifysubscri/schema
? Do you want to update code for your updated GraphQL API Yes
? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types?
This will overwrite your current graphql queries, mutations and subscriptions Yes
⠧ Updating resources in the cloud. This may take a few minutes...

(snip)

✔ All resources are updated in the cloud

GraphQL endpoint: https://XXXXXXXXXXXXXXXXXXXXXXXXXX.appsync-api.ap-northeast-1.amazonaws.com/graphql
GraphQL API KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX


$
Enter fullscreen mode Exit fullscreen mode

Finally, we get "authorized users can post but all users can read" chat system.

You open a browser and open "Closed Chat" (use not authorized user).
Then, you open another browser, sign-in another user, open "Closed Chat", then post message.

You can receive the message on original browser!

Top comments (0)