AWS Amplify Subscriptions Usage / 3. Closed Chat
Tags: aws, amplify, vue, graphql
Published: 2021-02-16 14:28:06

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**

| Section | Title |
|:---:| --- |
| 3.1 | [Use `@auth` directive and perform Authentication](#section-3-1) |
| 3.2 | [Add a Subscription type with an argument](#section-3-2) |
| 3.3 | [Closed post, Open read chat (Update both `@auth` directive and a Subscription)](#section-3-3) |

<a name="section-3-1"></a>
## 3.1 Use `@auth` directive and perform Authentication

### Add and use auth

``` bash
$ 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

$
```

And, push project.

``` bash
$ 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


$
```

Then, update api.

``` bash
$ 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
$
```

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

Update graphql schema file.

[amplify/backend/api/sampleamplifysubscri/schema.graphql](https://github.com/tacck/sample-amplify-subscriptions/blob/3-1-close-chat/amplify/backend/api/sampleamplifysubscri/schema.graphql)

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

And, push project again.

``` bash
$ 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


$
```

### 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](https://github.com/tacck/sample-amplify-subscriptions/blob/3-1-close-chat/src/views/SignIn.vue)

### Implement Closed Chat page.

[src/views/CloseChat.vue](https://github.com/tacck/sample-amplify-subscriptions/blob/3-1-close-chat/src/views/CloseChat.vue)

``` 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>
```

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

The important point is here.

``` vue
(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)
```

`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
```

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.

<a name="section-3-2"></a>
## 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](https://github.com/tacck/sample-amplify-subscriptions/blob/3-2-multi-room-close-chat/amplify/backend/api/sampleamplifysubscri/schema.graphql)

``` graphql
type Subscription {
  onCreateRoomChatByRoomName(roomName: String!): RoomChat
    @aws_subscribe(mutations: ["createRoomChat"])
  onCreateCloseRoomChatByRoomName(roomName: String!): CloseRoomChat
    @aws_subscribe(mutations: ["createCloseRoomChat"])
}
```

And, push project.

``` bash
$ 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


$
```

### Update Closed Chat


[src/views/CloseChat.vue](https://github.com/tacck/sample-amplify-subscriptions/blob/3-1-close-chat/src/views/CloseChat.vue)

``` 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>
```

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

The important point is here.

``` vue
(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)
```

`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."

<a name="section-3-3"></a>
## 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`.

``` bash
$ 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
$
```

Then, update graphql schema file.

[amplify/backend/api/sampleamplifysubscri/schema.graphql](https://github.com/tacck/sample-amplify-subscriptions/blob/3-3-post-close-read-open-chat/amplify/backend/api/sampleamplifysubscri/schema.graphql)

``` 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
}
```

> 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.

``` bash
$ 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


$
```

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!