AWS Contact Center

Create a mobile chat solution with the Amazon Connect mobile SDK

With the growth in smartphone usage, more and more customers are using phones as their primary means of communication. Given people are frequently using apps, customers may prefer to interact with a company via chatting through an app compared to calling customer service. By providing customers with an option to chat directly from an app they are familiar with using, customers have an easy and comfortable way to ask questions, which can increase their brand loyalty and improve their experience. With the launch of Amazon Connect chat, you are able to add chat capabilities directly into your iOS and Android application using the mobile SDK. This blog post guides you through the necessary steps to establish a chat in an iOS app. Although we don’t cover Android devices, the same principles apply.

To learn more about the new Amazon Connect chat offering, read our blog Reaching More Customers with Web and Mobile Chat on Amazon Connect written by Randall Hunt. He talks about why Amazon Connect decided to launch chat, the benefits of enabling it for your customers, and best practices when implementing the chat offering.

Overview of solution

In order to establish a chat, it requires chaining together several API calls. The process to establish a chat on a website is the same as it is with the mobile SDK. The diagram below shows the sequence of calls required to establish a chat.

Sequence diagram for API calls required for chat

First, a call is made to the Amazon Connect Service (ACS) API, StartChatContact. In order to invoke the StartChatContact API, you need IAM credentials that grant the user permission to call StartChatContact on the specified instance and contact flow. StartChatContact returns a participant token and a contact id. Using the participant token, you then invoke the Amazon Connect Participant Service (ACPS) API, CreateParticipantConnection. This returns a secure websocket URL that you must subscribe to. After subscribing to the websocket, messages and events can be sent and received. You can also make subsequent calls to the other APIs in ACPS with the participant token. To learn more about the ACPS APIs and how to invoke them, read the API documentation.

Walkthrough

This solution takes you through how to create a simple app that establishes a chat and displays the messages received by the websocket. At a high level, these are the steps we are going to cover:

  1. Gather information needed for the rest of the steps.
  2. Create the XCode project.
  3. Set up AWS logging.
  4. Create the ContentView.
  5. Implement the ChatWrapper.
  6. Create the Message object.
  7. Implement the WebsocketManager.
  8. Implement the ContentView.
  9. Test your solution.

Prerequisites

This solution uses:

Gather information

Paragraph with link to service documentation topics for basic procedures or more information.
Get the Amazon Connect instance ID

  1. Go to the Amazon Connect console and select the instance you want to use.
  2. Copy the instance ID from the overview page. This is the last section of the instance Amazon Resource Name (ARN).

Screenshot of Amazon Connect console showing Instance ARN

Get the Amazon Connect contact flow ID

  1. Log in to your Amazon Connect instance and navigate to the Contact flows page.
  2. Select the flow you want to use. If you haven’t created a flow yet, use the default Sample inbound flow (first call experience), added when you created your instance.
  3. Open the section on the left-hand side of the page that says Show additional flow information. From here, you can get the contact flow id from the last section of the ARN.

Screenshot showing sample inbound contact flow ARN

Get an access and secret key of an IAM user

Finally, you need the access key and secret access key of an IAM user that has permission to call the ACS APIs StartChatContact and StopContact on your instance. From a best practice standpoint, it is recommended to use Amazon Cognito to access AWS resources. You can follow the documentation on how to set up Amazon Cognito with your mobile app. However, for this blog post, just create an IAM user and use its secret key and access key. Follow this blog to see how to create an IAM user and get the access key and secret access key. Instead of using the DatabaseAdministrator policy in that blog, provide the user with the following permissions:


  {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": [
          "connect:StartChatContact",
          "connect:StopContact"       
        ],
        "Resource": [
          "arn:aws:connect:us-west-2:ACCOUNT_ID:instance/INSTANCE_ID",
          "arn:aws:connect:us-west-2:ACCOUNT_ID:instance/INSTANCE_ID/contact-flow/CONTACT_FLOW_ID",
          "arn:aws:connect:us-west-2:ACCOUNT_ID:instance/INSTANCE_ID/contact",
          "arn:aws:connect:us-west-2:ACCOUNT_ID:instance/INSTANCE_ID/contact/*"
        ],
        "Effect": "Allow"
      }
    ]
  }

Create the XCode project

Note: This blog post was written with XCode version 11.1, Swift version 5, and iOS version 13.3. We recommend implementing this blog with the same versions, as there may be changes to certain components with later versions. You can modify the Swift and iOS versions in the Build Settings of your project.

  1. Open XCode and create a new project.
  2. Select Single View App for the project type.
  3. Give your project a name, such as AmazonConnectChatiOS
  4. Fill in the Organization identifier with your company name, and leave the default settings to use the Swift language and the SwiftUI user interface.
  5. Once the project has been created, close XCode.
  6. Open up a terminal, navigate to the project root, and initialize the Podfile. If you don’t have cocoa pods installed, install cocoa pods as well.
  7. $ sudo gem install cocoapods
    $ cd ./AmazonConnectChatiOS
    $ pod init
  8. `pod init` generates a file called Podfile. Open the Podfile and add the following pods:
  9. target : 'AmazonConnectChatiOS' do
            use_frameworks!
            pod 'AWSCore'
            pod 'AWSConnect'
            pod 'AWSConnectParticipant'
            pod 'Starscream', '~> 3.1'
    end
        

    Starscream is an open source websocket library to manage the websocket connections.

  10. Save the file and install the pods by running the following command:
  11. $ pod install
  12. Once the install is complete, open your workspace by opening the file that was generated within the project structure with the .xcworkspace extension.
  13. Make sure everything builds by selecting Product > Build or running ⌘B.

Set up AWS logging

An essential next step is enabling AWS logs. This is helpful for debugging any failures encountered while calling AWS.

  1. Open the AppDelegate.swift file.
  2. Import AWSCore at the top of the file before the class definition.
  3. import AWSCore
  4. Add the following two lines to the application() function that returns a Boolean:
  5. AWSDDLog.sharedInstance.logLevel = .verbose
    AWSDDLog.add(AWSDDTTYLogger.sharedInstance)

Create the ContentView

Next, set up the layout of the chat application. The layout is straightforward and creates three buttons, a text field, and a text object in a vertical stack. For now, the buttons are only printing out statements so you can see that the actions are taken. After you implement the classes with the core logic, we will come back to this file to add the invocations.

  1. Open the ContentView Swift file and add the following code:
  2. 
      import SwiftUI
      struct ContentView: View {
        @State private var message: String = ""
        
    	var body: some View {
          VStack {
            HStack {
              Button(action: {
                 print("start chat button clicked")
               }) {
                 Text("Start Chat")
                   .fontWeight(.bold)
                   .font(.body)
                   .padding()
                   .background(Color.green)
                   .foregroundColor(.white)
                   .padding(10)
              }
              Button(action: {
                print("end chat button clicked")
              }) {
                Text("End Chat")
                  .fontWeight(.bold)
                  .font(.body)
                  .padding()
                  .background(Color.red)
                  .foregroundColor(.white)
                  .padding(10)
              }
            } 
            Button(action: {
              print("send chat button clicked with \(self.message)")
            }) {
              Text("Send Chat")
                .fontWeight(.bold)
                .font(.body)
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .padding(10)
            }
            TextField("Enter your chat message here", text: $message)
              .padding(10)
            Text("Messages received in the chat will show up here:")
              .padding(10)
          }
        }
      }
      
      struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
          ContentView()
        }
      }
    
  3. Once you add this code to the ContentView file, build and run it to ensure it is set up properly. First, build the project.
  4. To run the project, click the play button in the upper left-hand corner to run in the Simulator. The three buttons and text field components appear.
  5. Enter text into the text field in the running application and click the buttons. When the buttons are clicked, the log statements appear in the output in XCode.
  6. Screenshot of runnng project app

Implement the ChatWrapper

Next, create a new Swift file called ChatWrapper to be a wrapper around the calls to the Amazon Connect Service and Amazon Connect Participant Service (ACPS) APIs. We first create this class and then make calls to it in a later section of this blog. There are five functions in this class that perform the following actions: start the chat, create the connection using the participant token, send chat messages, send typing events, and end the chat. There are additional calls to ACPS available, such as getting the entire transcript, not covered here.

Set up the class

  1. Create a file called ChatWrapper.
  2. Add this code with the empty function definitions:
  3. import Foundation
    import AWSConnect
    import AWSConnectParticipant
    
    class ChatWrapper {
        
        var connectServiceClient: AWSConnect?
        var connectChatClient: AWSConnectParticipant?
        var participantToken: String?
        var connectionToken: String?
        var contactId: String?
        var websocketUrl: String?
        
        init() { }
        
        func startChatContact(name: String) { }
        
        func createParticipantConnection() { }
        
        func sendChatMessage(messageContent: String) { }
        
        func sendTypingEvent() { }
        
        func endChat() { }
    }
        
  4. Register with both Amazon Connect Service and Amazon Connect Participant Service using your AWS credentials by replacing the access key and secret key. Specify the Region you are testing in.
  5. init() {
      let credentials = AWSStaticCredentialsProvider(
                          accessKey: "ACCESS_KEY_ID",
                          secretKey: "SECREY_KEY_ID")
    
      let participantService = AWSServiceConfiguration(
                                 region: .USWest2, // Use the region your instance is based in
                                 credentialsProvider: credentials)!
      let connectService = AWSServiceConfiguration(
                             region: .USWest2, // Use the region your instance is based in
                             credentialsProvider: credentials)!
      AWSConnect.register(with: connectService, forKey: "test")
      AWSConnectParticipant.register(with: participantService, forKey: "test")
            
      connectServiceClient = AWSConnect.init(forKey: "test")
      connectChatClient = AWSConnectParticipant.init(forKey: "test")
    }
        

Start the chat
The first step to creating a chat is to call the Amazon Connect Service StartChatContact API. Instance id, contact id, and display name are required in addition to any optional parameters like contact attributes. The response from the API call contains the participant token and contact id for the new chat. These values are used in the other functions, so save them once you get the response.

  1. Add the function implementation for startChatContact() and replace the instance id and contact flow id with your values:
  2. func startChatContact(name: String) {
      let startChatContactRequest = AWSConnectStartChatContactRequest()
      startChatContactRequest?.instanceId = INSTANCE_ID
      startChatContactRequest?.contactFlowId = CONTACT_FLOW_ID
            
      let participantDetail = AWSConnectParticipantDetails()
      participantDetail?.displayName = name
      startChatContactRequest?.participantDetails = participantDetail
       
      connectServiceClient?
        .startChatContact(startChatContactRequest!)
        .continueWith(block: { 
          (task) -> Any? in
            self.participantToken = task.result!.participantToken
            self.contactId = task.result!.contactId
            return nil
          }
        ).waitUntilFinished()
    }
        

Establish the connection

Next, establish the chat by creating a connection with the Amazon Connect Participant Service by calling the CreateParticipantConnection API. Use the participant token returned by the StartChatContact API. The call to the CreateParticipantConnection API returns a websocket URL and a connection authentication token. You must subscribe to the websocket URL before any messages can be sent across it. We will cover that in the next section, but for now finish filling out the remaining functions in this class. The connect authentication token returned is used on all subsequent calls to ACPS.

  1. Add the function implementation for createParticipantConnection():
  2. func createParticipantConnection() {
      var connectionAuthenticationToken: String = ""
      let createParticipantConnectionRequest = AWSConnectParticipant CreateParticipantConnectionRequest()
      createParticipantConnectionRequest?.participantToken = self.participantToken
      createParticipantConnectionRequest?.types = ["WEBSOCKET", "CONNECTION_CREDENTIALS"]
    
      connectChatClient?
        . createParticipantConnection (createParticipantConnectionRequest!)
        .continueWith(block: { 
          (task) -> Any? in
            self.connectionToken = task.result!.connectionCredentials!.connectionToken
            self.websocketUrl = task.result!.websocket!.url
              return nil
          }
        ).waitUntilFinished()
    }
        

Implement the remaining functions

Now, add the implementations for the final three functions of the class. They are all simply calls to ACPS with the required parameters. For more details on these three API calls, refer to the documentation.

  1. Add the following function implementations:
  2. func sendChatMessage(messageContent: String) {        
      let sendMessageRequest = AWSConnectParticipantSendMessageRequest()
      sendMessageRequest?.connectionToken = self.connectionToken
      sendMessageRequest?.content = messageContent
      sendMessageRequest?.contentType = "text/plain"
            
      connectChatClient?
        .sendMessage(sendMessageRequest!)
        .continueWith(block: { 
          (task) -> Any? in
             return nil
        })
    }
        
    func sendTypingEvent() {
      let sendEventRequest = AWSConnectParticipantSendEventRequest()
      sendEventRequest?.connectionToken = self.connectionToken
      sendEventRequest?.contentType = "application/vnd.amazonaws.connect.event.typing"
              
      connectChatClient?
        .sendEvent(sendEventRequest!)
        .continueWith(block: {
          (task) -> Any? in
               return nil
        })
    }
        
    func endChat() {
      let stopChatRequest = AWSConnectStopContactRequest()
      stopChatRequest?.instanceId = INSTANCE_ID
      stopChatRequest?.contactId = self.contactId!
            
      connectServiceClient?
        .stopContact(stopChatRequest!)
        .continueWith(block: { 
          (task) -> Any? in
                return nil
        }).waitUntilFinished()
    }
        
  3. Build the code to ensure that there are no errors.

Create the Message struct

Next you are going to create a struct that contains the contents of each message that is sent. This struct has three fields: participant, message, and an id. The id is necessary to add the messages to a List element created later.

  1. Create a new Swift file called Message.
  2. Enter the below code:
  3. import Foundation
    
    struct Message: Identifiable {
      var participant: String
      var text: String
      var id = UUID()
    }
        

Implement the WebsocketManager

Now, create a new Swift file that uses the Starscream library to manage the messages passed over the websocket.

Note: For the purpose of this tutorial, you are disabling the SSL certificate validation on the websocket. This is not a secure practice. It is recommended to add an SSL certificate for your application before continuing application development and testing.

Create the class outline

  1. Create a new swift file called WebsocketManager.
  2. Enter the following code as the outline of the class.
  3. import Foundation
    import Starscream
    
    class WebsocketManager : WebSocketDelegate {
      private let socket: WebSocket
      private let messageCallback: (Message)-> Void
    
      init(wsUrl: String, onRecievedMessage: @escaping (Message)-> Void) {
        self.messageCallback = onRecievedMessage
        self.socket = WebSocket(request: URLRequest(url: URL(string: wsUrl)!))
        self.socket.disableSSLCertValidation = true
    
        socket.delegate = self
        socket.connect()
      }
        
      func websocketDidConnect(socket: WebSocketClient) { }
        
      func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
        print("The websocket is disconnected: \(error?.localizedDescription)")
      }
     
      func websocketDidReceiveMessage(socket: WebSocketClient, text: String) { }
    
      func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
        print("Received data from the websocket: \(data.count)")
      }
    }
        

Add the function implementations

Now, add the function implementations. You only implement two of the functions in this tutorial, but it is recommended that you add implementations for the other events based on your application’s needs or according to websocket best practices.

The websocketDidConnect function is triggered once there has been a connection to the websocket. The WebsocketManager is created and connected to after the call to ACPS.CreateParticipantConnection(). Once a connection is established, you must subscribe to the Amazon Connect chat topic.

The websocketDidReceiveMessage event is triggered when a message is received on the websocket. In this event you parse the JSON of the incoming message, set the values to a Message object, and return it to be displayed on the screen. This is only returning messages that contain a message or a disconnect event. You can add logic to display when participants join a chat and when a participant is typing.

  1. Implement the websocketDidConnect function.
  2. func websocketDidConnect(socket: WebSocketClient) {
      print("websocket is connected.")
      socket.write(string: "{\"topic\": \"aws/subscribe\", \"content\": {\"topics\": [\"aws/chat\"]}})")
    }
        
  3. Implement the websocketDidReceiveMessage event.
  4. func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
      if let json = try? JSONSerialization.jsonObject(with: Data(text.utf8), options: []) as? [String: Any] {
        let content = json["content"] // string wrapped json
        if let stringContent = content as? String,
          let innerJson = try? JSONSerialization.jsonObject(with: Data(stringContent.utf8), options: []) as? [String: Any] {
          let type = innerJson["Type"] as! String // MESSAGE, EVENT
          if type == "MESSAGE" {
            let participantRole = innerJson["ParticipantRole"] as! String // CUSTOMER, SYSTEM_MESSAGE, AGENT
            let message = Message(
              participant: participantRole,
              text: innerJson["Content"] as! String)
            messageCallback(message)
          } else if innerJson["ContentType"] as! String == "application/vnd.amazonaws.connect.event.chat.ended" {
            let message = Message(
              participant: "System Message",
              text: "The chat has ended.")
            messageCallback(message)
          }
        }
      }
    }
        

Implement the ContentView

Now that the two classes to handle communication with Amazon Connect and websockets have been implemented, you can add the implementations to the ContentView. The start chat function involves making three calls: starting the chat, creating the participant connection, and then connecting to the websocket. When you connect to the websocket, one of the parameters is a function that is invoked once a message is received. In this tutorial, when a message is received, add it to the list of messages displayed in the app.

  1. Open the ContentView Swift file.
  2. Add variables to the top of the ContentView struct after the message variable.
  3. @State private var message: String = ""
    @State var websocketManager: WebsocketManager!
    @State var messages: [Message] = []
    var chatWrapper = ChatWrapper()
    	
  4. Implement the start chat button.
  5. Button(action: {
      print("start chat button clicked")
      self.chatWrapper.startChatContact(name: "Customer")
      self.chatWrapper.createParticipantConnection()
      self.websocketManager = WebsocketManager(
        wsUrl: self.chatWrapper.websocketUrl!,
        onRecievedMessage: {
          [self] message in
          self.messages.append(message)
      })
    })
    	
  6. Implement the end chat button.
  7. Button(action: {
      print("end chat button clicked")
      self.chatWrapper.endChat()
    })
    	
  8. Implement the send chat button.
  9. Button(action: {
      print("send chat button clicked with \(self.message)")
      self.chatWrapper.sendChatMessage(messageContent: self.message)
    })
    	
  10. Add a List element beneath the Text element to list the messages as they are received by the websocket.
  11. Text("Messages received in the chat will show up here:")
      .padding(10)
    List (messages) { message in
      Text("\(message.participant): \(message.text)")
    }
    	

Test your solution

That is it! Now you can build the code, run it in the simulator, and send chats over Amazon Connect in your mobile app. Here is what the application looks like when we start a chat that invokes an Amazon Lex bot in the contact flow.

Screenshot of running application with chats

  1. To run in the Simulator, click the play button in the upper left-hand corner.
  2. Click Start Chat.
  3. Wait for messages to come through, then enter text and click Send Chat.
  4. Keep sending messages and interacting with your contact flow or agent.
  5. When finished, click End Chat.

Next Steps

We covered all of the basic steps necessary to establish the chat, but it is up to you to add the additional functionality to harden the solution, handle edge cases, handle reconnections, and use an SSL certificate for the websocket communication.

Conclusion

This blog post walked you through how to implement Amazon Connect chat with the iOS mobile SDK. Now that you understand how to establish a chat in a mobile application, you can migrate this solution over to your existing application.

If you want to see the source code of this solution, examples of how to implement chat in a web application, and best practices when enabling chat, visit the Amazon Connect GitHub organization.