# React Native + ChatJS Demo A demo [Expo](https://expo.dev/) app for building custom Amazon Connect Chat in React Native. This cross-platform (Android, iOS, macOS, Windows, & Web) solution implements basic ChatJS functionality and is fully customizable. > Refer to [#Specifications](#speficications) for details on compatibility, supported versions, and platforms. **Reference:** - ChatJS Repository: https://github.com/amazon-connect/amazon-connect-chatjs - NPM package: https://www.npmjs.com/package/amazon-connect-chatjs - Documentation: https://docs.aws.amazon.com/connect/latest/adminguide/enable-chat-in-app.html https://github.com/amazon-connect/amazon-connect-chat-ui-examples/assets/60903378/8887f54a-c121-4246-8981-23d05dd9fa01 ## Contents - [Prerequisites](#prerequisites) - [Mobile Support](#mobile-support) - [Local Development](#local-development) - [Production Build](#production-build) - [ChatJS Usage](#chatjs-usage) ## Prerequisites - Create an Amazon Connect Instance [[guide](https://docs.aws.amazon.com/connect/latest/adminguide/amazon-connect-instances.html)] - OR: enable chat experience for an existing Connect instance. [[guide](../README.md#enabling-chat-in-an-existing-amazon-connect-contact-center)] - Create an Amazon Connect Contact Flow, ready to receive chat contacts. [[guide](https://docs.aws.amazon.com/connect/latest/adminguide/chat.html)] - Note the `instanceId` [[guide](https://docs.aws.amazon.com/connect/latest/adminguide/find-instance-arn.html)] - Find the `contactFlowId` for the ["Sample Inbound Flow (First Contact)"](https://docs.aws.amazon.com/connect/latest/adminguide/sample-inbound-flow.html) [[guide](https://docs.aws.amazon.com/connect/latest/adminguide/find-contact-flow-id.html)] - Deploy a custom Amazon Connect Chat backend. [Refer to this backend template](../cloudformationTemplates/startChatContactAPI/README.md) - Deploy a StartChatContact template Lambda [[CloudFormation Template](https://github.com/amazon-connect/amazon-connect-chat-ui-examples/tree/master/cloudformationTemplates/startChatContactAPI)] - Add the `region`, `API_GATEWAY_ID`, `contactFlowId`, and `instanceId` to `endpoints.js`. ## Mobile Support Additional configuration is required to support ChatJS in React Native applications. Use `amazon-connect-chatjs@^1.5.0` and apply the changes below: Install the supported ChatJS library using either Yarn: ```sh $ yarn add amazon-connect-chatjs@^1.5.0 ``` or npm: ```sh $ npm install amazon-connect-chatjs@^1.5.0 ``` #### Override Browser Network Health Check If running ChatJS in a mobile React Native environment, override the default network setting online and check: > `amazon-connect-websocket-manager.js` depencency will use `navigator.onLine`. Legacy browsers will always return `true`, but unsupported or mobile runtime will return `null/undefined`. ```js /** * `amazon-connect-websocket-manager.js` depencency will use `navigator.onLine` * Unsupported or mobile runtime will return `null/undefined` - preventing websocket connections * Legacy browsers will always return `true` [ref: caniuse.com/netinfo] */ const customNetworkStatus = () => true; connect.ChatSession.setGlobalConfig({ webSocketManagerConfig: { isNetworkOnline: customNetworkStatus, // default: () => navigator.onLine } }); ``` #### Custom Network Health Check Extending this, device-native network health checks can be used for React Native applications. 1. First, install the `useNetInfo` react hook: ```sh $ npm install --save @react-native-community/netinfo # source: https://github.com/react-native-netinfo/react-native-netinfo ``` 2. Make sure to update permissions, Android requires the following line in `AndroidManifest.xml`: (for SDK version after 23) ```xml ``` 3. Set up the network event listener, and pass custom function to `setGlobalConfig`: > Note: To configure `WebSocketManager`, `setGlobalConfig` must be invoked ```js import ChatSession from "./ChatSession"; import NetInfo from "@react-native-community/netinfo"; import "amazon-connect-chatjs"; // ^1.5.0 - imports global "connect" object /** * By default, `isNetworkOnline` will be invoked every 250ms * Should only current status, and not make `NetInfo.fetch()` call * * @return {boolean} returns true if currently connected to network */ let isOnline = true; const customIsNetworkOnline = () => isOnline; const ReactNativeChatComponent = (props) => { /** * Network event listener native to device * Will update `isOnline` value asynchronously whenever network calls are made */ const unsubscribeNetworkEventListener = NetInfo.addEventListener(state => { console.log('NetInfo eventListener - isConnected:', state.isConnected); isOnline = state.isConnected; }); useEffect(() => { return unsubscribeNetworkEventListener(); }, []); const initializeChatJS = () => { // To configure WebSocketManager, setGlobalConfig must be invoked connect.ChatSession.setGlobalConfig({ // ... webSocketManagerConfig: { isNetworkOnline: customIsNetworkOnline, // default: () => navigator.onLine } }); } // ... } ``` 4. Optionally, this configuration can be dynamically set based on the `Platform` ```js import { Platform } from 'react-native'; const isMobile = Platform.OS === 'ios' || Platform.OS === 'android'; connect.ChatSession.setGlobalConfig({ // ... webSocketManagerConfig: { ...(isMobile ? { isNetworkOnline: () => true } : {}), // use default behavior for browsers } }); ``` ## Local Development > Versions: Expo@~48.0.6, react-native@0.71.3, react-native-gifted-chat@^2.0.0, react@^18.2.0 > Supported in Node v16+ > Setting up Android Emulator: https://docs.expo.dev/workflow/android-studio-emulator/ > Setting up iPhone Emulator: https://docs.expo.dev/workflow/ios-simulator/ 1. Deploy startChatContact backend (from CFN stack): https://github.com/amazon-connect/amazon-connect-chat-ui-examples/tree/master/cloudformationTemplates/startChatContactAPI ```sh $ cp endpoints.sample.js endpoints.js ``` ```js // /endpoints.js export const GATEWAY = { region: "us-west-2", apiGWId: "asdfasdf", }; const ENDPOINTS = { contactFlowId: "asdf-5056-asdf-a672-asdf6a81ca6", instanceId: "asdf-078b-asdf-9264-asdf98f3c28", region: GATEWAY.region, apiGatewayEndpoint: `https://${GATEWAY.apiGWId}.execute-api.${GATEWAY.region}.amazonaws.com/Prod`, ccpUrl: "https://.my.connect.aws/ccp-v2", // optional - for reference }; export default ENDPOINTS; ``` 2. Customize several global settings in the `config.js` file, including the `startChatRequestInput` request body. ```js // /config.js // Enable/disable ChatJS event logs export const ENABLE_CHATJS_LOGS = false; // Renders pop-up on device emulator screen from console.logs export const ENABLE_REACTNATIVE_LOGBOX = false; // Enable rich messaging, CCP sends "text/markdown" by default // doc: https://docs.aws.amazon.com/connect/latest/APIReference/API_StartChatContact.html#API_StartChatContact_RequestSyntax export const supportMessageContentTypes = ["text/plain"]; export const CUSTOMER_USER = { _id: 1, name: "Customer", avatar: "https://i.pravatar.cc/100?img=11", }; export const AGENT_USER = { _id: 2, name: "Agent", avatar: "https://www.bcbswy.com/wp-content/uploads/2020/05/20.06.26_bcbswy_avatar_@2.0x.png", }; export const startChatRequestInput = { ...ENDPOINTS, name: "John", contactAttributes: JSON.stringify({ customerName: "John", }), supportedMessagingContentTypes: supportMessageContentTypes.join(","), }; ``` 3. Run the Expo demo application on an emulator ```sh $ yarn $ yarn run ios $ yarn run web $ yarn run android $ npx expo run:ios -d # run on device plugged into laptop ``` 4. Edit code, regenerate bundle files, and refresh Expo app ### Open Debugger > If you need to clear cache, run `expo start -c` - Physical device: 👋 shake it. - iOS simulator: Cmd-Ctrl-Z in macOS. - Android emulator: Cmd-M in macOS or Ctrl-Min Windows. ## Production Build Create a production Expo build ``` $ rm app.json && cp app.prod.json app.json $ CI=1 npx expo prebuild --platform all ``` ## ChatJS Usage ChatScreen uses three components: - `initiateChat`: startChatContact request - `ChatSession`: low-level abstraction on top of ChatJS - `ChatWrapper`: manages chat state at the UI level, handling loading/disconnect - `ChatWidget`: renders chat composer and transcript ```js // src/api/initiateChat.js /** * Initiate a chat session within Amazon Connect, proxying initial StartChatContact request * through your API Gateway. * * https://docs.aws.amazon.com/connect/latest/APIReference/API_StartChatContact.html */ const initiateChat = (input) => { const requestBody = { InstanceId: "asdf-5056-asdf-a672-asdf6a81ca6", ContactFlowId: "asdf-5056-asdf-a672-asdf6a81ca6", ParticipantDetails: { DisplayName: "John", }, SupportedMessagingContentTypes: ["text/plain", "text/markdown"], ChatDurationInMinutes: 60, }; return window .fetch( input.apiGatewayEndpoint, { headers: input.headers ? input.headers : new Headers(), method: "post", body: JSON.stringify(requestBody), }, START_CHAT_CLIENT_TIMEOUT_MS // 5000 ) .then((res) => res.json.data) .catch((err) => console.error(err)); }; ``` ```js // src/components/ChatSession.js import "amazon-connect-chatjs"; // ^1.5.0 // Low-level abstraction on top of Chat.JS class ChatJSClient { session = null; constructor(chatDetails, region) { this.session = connect.ChatSession.create({ chatDetails: { contactId: chatDetails.startChatResult.ContactId, participantId: chatDetails.startChatResult.ParticipantId, participantToken: chatDetails.startChatResult.ParticipantToken, }, type: "CUSTOMER", options: { region }, }); } connect() { // Intiate the websocket connection. After the connection is established, the customer's chat request // will be routed to an agent who can then accept the request. return this.session.connect(); } disconnect() { return this.session.disconnectParticipant(); } sendMessage(content) { // Right now we are assuming only text messages, // later we will have to add functionality for other types. return this.session.sendMessage({ message: content.data, contentType: content.type, }); } } ``` ```js // src/components/ChatWrapper.js import initiateChat from "../api/initiateChat"; import filterIncomingMessages from "../utils/filterIncomingMessages"; const chatDetails = await initiateChat(); const chatSession = new ChatJSClient(chatDetails); await chatSession.openChatSession(); // Add event listeners to chat session chatSession.onIncoming(async () => { const latestTranscript = await chatSession.loadPreviousTranscript(); setMessages(filterIncomingMessages(latestTranscript)); }); chatSession.onOutgoing(async () => { const latestTranscript = await chatSession.loadPreviousTranscript(); setMessages(filterIncomingMessages(latestTranscript)); }); ``` ```js // src/components/ChatWidget.js import { GiftedChat } from "react-native-gifted-chat"; // ^2.0.0 const ChatWidget = ({ handleSendMessage, messages }) => { return ( { Keyboard.dismiss(); handleSendMessage(msgs[0].text); }} /> ); }; ``` ## Client Side Metrics (CSM) Support > ⚠️ NOT SUPPORTED - For more details please refer to the [tracking issue](https://github.com/amazon-connect/amazon-connect-chatjs/issues/171) The out-of-box ChatJS client side metrics are not currently supported in React Native. ChatJS is officially supported for browser environments, and may run into issues accessing the `document` DOM API. You can safely disable CSM without affecting other behavior: ```diff this.session = connect.ChatSession.create({ chatDetails: startChatDetails, + disableCSM: true, type: 'CUSTOMER', options: { region }, }); ``` ## Specifications For local development, please use Node v16, v18, or v19. > React Native (v0.71.3) apps may target iOS 12.4 and Android 5.0 (API 21) or newer. All platforms include Android, iOS, macOS, Windows, & Web. Built with: - `amazon-connect-chatjs@^1.5.0` - `expo@~48.0.6` - `react-native-gifted-chat@^2.0.0` - `react-native@^0.71.3` - `react@18.2.0`