Skip to content

Commit

Permalink
add authenticate customer event handlers and UI (#278)
Browse files Browse the repository at this point in the history
* add authenticate customer event handlers and UI

* fix github actions build

* fix github actions build
  • Loading branch information
xiajon authored Jan 6, 2025
1 parent 17a82ce commit 042b6ed
Show file tree
Hide file tree
Showing 16 changed files with 840 additions and 1,393 deletions.
2 changes: 1 addition & 1 deletion local-testing/amazon-connect-chat-interface.js

Large diffs are not rendered by default.

1,663 changes: 288 additions & 1,375 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@
"@emotion/core": "^10.0.35",
"@svgr/webpack": "^6.2.1",
"@types/jest": "^28.0.0",
"amazon-connect-chatjs": "^3.0.2",
"amazon-connect-chatjs": "^3.0.3",
"braces": "^3.0.3",
"core-js": "^3.8.3",
"dompurify": "^3.1.3",
"draft-js": "^0.11.7",
Expand All @@ -118,8 +119,7 @@
"styled-components": "^4.1.1",
"webpack": "^4.46.0",
"whatwg-fetch": "^3.2.0",
"workbox-webpack-plugin": "^7.0.0",
"braces": "^3.0.3"
"workbox-webpack-plugin": "^7.0.0"
},
"devDependencies": {
"@babel/core": "^7.23.2",
Expand All @@ -128,7 +128,7 @@
"@babel/preset-env": "^7.23.2",
"@emotion/babel-plugin": "^11.11.0",
"@testing-library/dom": "7.29.4",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^12.1.5",
"@testing-library/user-event": "13.0.7",
"@types/react": "^16.14.35",
Expand Down Expand Up @@ -177,8 +177,8 @@
"postcss-preset-env": "^9.5.2",
"postcss-safe-parser": "^7.0.0",
"prop-types": "^15.8.1",
"react-intl": "^6.3.2",
"react-dev-utils": "^12.0.0",
"react-intl": "^6.3.2",
"react-render-html": "^0.6.0",
"react-test-renderer": "^16.6.3",
"resolve": "1.8.1",
Expand Down
14 changes: 9 additions & 5 deletions src/components/Chat/ChatContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React, { Component } from "react";
import styled from "styled-components";
import { Button, Loader } from "connect-core";
import Chat from "./Chat";
import ChatSession from "./ChatSession";
import ChatSession, { setCurrentChatSessionInstance } from "./ChatSession";
import { initiateChat } from "./ChatInitiator";
import EventBus from "./eventbus";
import "./ChatInterface";
Expand Down Expand Up @@ -95,10 +95,14 @@ class ChatContainer extends Component {
*/
async submitChatInitiation(input, success, failure) {
this.setState({ status: "Initiating" });

const customizationParams = {
authenticationRedirectUri: input.authenticationRedirectUri || '',
authenticationIdentityProvider: input.authenticationIdentityProvider || ''
}
try {
const chatDetails = await initiateChat(input);
const chatSession = await this.openChatSession(chatDetails, input.name, input.region, input.stage);
const chatSession = await this.openChatSession(chatDetails, input.name, input.region, input.stage, customizationParams);
setCurrentChatSessionInstance(chatSession);
const attachmentsEnabled =
(input.featurePermissions && input.featurePermissions[CHAT_FEATURE_TYPES.ATTACHMENTS]) ||
(chatDetails.featurePermissions && chatDetails.featurePermissions[CHAT_FEATURE_TYPES.ATTACHMENTS]);
Expand All @@ -121,8 +125,8 @@ class ChatContainer extends Component {
}
}

openChatSession(chatDetails, name, region, stage) {
const chatSession = new ChatSession(chatDetails, name, region, stage);
openChatSession(chatDetails, name, region, stage, customizationParams) {
const chatSession = new ChatSession(chatDetails, name, region, stage, customizationParams);
chatSession.onChatClose(() => {
EventBus.trigger("endChat", {});
});
Expand Down
6 changes: 6 additions & 0 deletions src/components/Chat/ChatContainer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ describe("<ChatContainer />", () => {
onParticipantIdle: jest.fn().mockResolvedValue("aaa"),
onChatRehydrated: jest.fn().mockResolvedValue("aaa"),
onDeliveredReceipt: jest.fn().mockResolvedValue("aaa"),
onAuthenticationInitiated: jest.fn().mockResolvedValue("aaa"),
onAuthenticationTimeout: jest.fn().mockResolvedValue("aaa"),
onAuthenticationSuccessful: jest.fn().mockResolvedValue("aaa"),
onAuthenticationCanceled: jest.fn().mockResolvedValue("aaa"),
onAuthenticationFailed: jest.fn().mockResolvedValue("aaa"),
onParticipantDisplayNameUpdated: jest.fn().mockResolvedValue("aaa"),
onEnded: jest.fn().mockResolvedValue("aaa"),
onConnectionEstablished: jest.fn().mockResolvedValue("aaa"),
connect: jest.fn().mockResolvedValue("aaa"),
Expand Down
11 changes: 11 additions & 0 deletions src/components/Chat/ChatEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class ChatEvents {
);
EventBus.on('agentEndChat', this.agentEndChat.bind(this));
EventBus.on('escalateToVoice', this.escalateToVoice.bind(this));
EventBus.on("authenticationComplete", this.authenticationComplete.bind(this))
}

_eventHandlers = {
Expand All @@ -23,6 +24,7 @@ export class ChatEvents {
'push-notification-eligible-message-received': [],
'agent-end-chat': [],
'voice-escalation': [],
'authentication-complete': []
};

onVoiceEscalation(callback) {
Expand Down Expand Up @@ -54,6 +56,12 @@ export class ChatEvents {
});
}

onAuthenticationComplete(callback) {
this.on("authentication-complete", function(...rest){
callback(...rest);
});
}

onAgentEndChat(callback) {
this.on('agent-end-chat', function (...rest) {
callback(...rest);
Expand Down Expand Up @@ -93,6 +101,9 @@ export class ChatEvents {
agentEndChat() {
this._triggerEvent('agent-end-chat');
}
authenticationComplete() {
this._triggerEvent("authentication-complete");
}
}

window.connect = window.connect || {};
Expand Down
5 changes: 5 additions & 0 deletions src/components/Chat/ChatEvents.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ describe('ChatEvents', () => {
expect(myFn).toBeCalled();
});

test('authenticationComplete event', () => {
myChatEvent.onAuthenticationComplete(myFn);
EventBus.trigger("authenticationComplete", {});
})

test('escalateToVoice event', () => {
myChatEvent.onVoiceEscalation(myFn);
EventBus.trigger('escalateToVoice', {});
Expand Down
114 changes: 113 additions & 1 deletion src/components/Chat/ChatSession.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import isJson from "is-json";

const SYSTEM_EVENTS = Object.values(ContentType.EVENT_CONTENT_TYPE);
const DEFAULT_PREFIX = "Amazon-Connect-ChatInterface-ChatSession";
var CurrentChatSessionInstance = {};
export function getCurrentChatSessionInstance () {
return CurrentChatSessionInstance;
}

export function setCurrentChatSessionInstance (chatSession) {
CurrentChatSessionInstance = chatSession;
}
// Low-level abstraction on top of Chat.JS
class ChatJSClient {
session = null;
Expand Down Expand Up @@ -52,6 +60,30 @@ class ChatJSClient {
return this.session.onChatRehydrated(handler);
}

onAuthenticationInitiated(handler) {
return this.session.onAuthenticationInitiated(handler);
}

onAuthenticationTimeout(handler) {
return this.session.onAuthenticationTimeout(handler);
}

onAuthenticationFailed(handler) {
return this.session.onAuthenticationFailed(handler);
}

onAuthenticationSuccessful(handler) {
return this.session.onAuthenticationSuccessful(handler);
}

onAuthenticationCanceled(handler) {
return this.session.onAuthenticationCanceled(handler);
}

onParticipantDisplayNameUpdated(handler) {
return this.session.onParticipantDisplayNameUpdated(handler);
}

onTyping(handler) {
return this.session.onTyping(handler);
}
Expand Down Expand Up @@ -138,6 +170,14 @@ class ChatJSClient {
describeView(viewTokenObj) {
return this.session.describeView(viewTokenObj);
}

getAuthenticationUrl(authenticationConfiguration) {
return this.session.getAuthenticationUrl(authenticationConfiguration);
}

cancelParticipantAuthentication(sessionId) {
return this.session.cancelParticipantAuthentication(sessionId);
}
}

class ChatSession {
Expand Down Expand Up @@ -167,8 +207,9 @@ class ChatSession {
"chat-closed": [],
};

constructor(chatDetails, displayName, region, stage) {
constructor(chatDetails, displayName, region, stage, customizationParams) {
this.client = new ChatJSClient(chatDetails, region, stage);
this.customizationParams = customizationParams || {};
this.contactId = this.client.getContactId();
this.thisParticipant = {
participantId: this.client.getParticipantId(),
Expand Down Expand Up @@ -376,6 +417,19 @@ class ChatSession {
return this.client.describeView(viewTokenObj);
}

getAuthenticationUrl(sessionId) {
return this.client.getAuthenticationUrl({
redirectUri: this.customizationParams.authenticationRedirectUri,
sessionId: sessionId
});
}

cancelParticipantAuthentication(sessionId) {
return this.client.cancelParticipantAuthentication({
sessionId: sessionId
});
}

loadPreviousTranscript() {
console.log("loadPreviousTranscript in single");
var args = {};
Expand Down Expand Up @@ -453,6 +507,24 @@ class ChatSession {
this.client.onEnded((data) => {
this._handleEndedEvent(data);
});
this.client.onAuthenticationInitiated(async data => {
await this._handleAuthenticationInitiated(data);
});
this.client.onAuthenticationTimeout(async data => {
await this._handleAuthenticationLifecycleEvent(data);
});
this.client.onAuthenticationFailed(async data => {
await this._handleAuthenticationLifecycleEvent(data);
});
this.client.onAuthenticationSuccessful(async data => {
await this._handleAuthenticationLifecycleEvent(data);
});
this.client.onAuthenticationCanceled(async data => {
await this._handleAuthenticationLifecycleEvent(data);
});
this.client.onParticipantDisplayNameUpdated(async data => {
this.authenticatedParticipantDisplayName = data.data.DisplayName;
});
this.client.onConnectionEstablished(async () => {
await this._loadLatestTranscript();
});
Expand Down Expand Up @@ -793,6 +865,46 @@ class ChatSession {
Eventbus.trigger('agentEndChat', {});
}

async _handleAuthenticationInitiated(data) {
var eventDetails = data.data,
identityProvider = this.customizationParams.authenticationIdentityProvider,
content = {},
authenticationUrl = '',
sessionId = '',
item,
getAuthenticationUrlResponse;
try {
content = JSON.parse(eventDetails.Content || '{}');
} catch (error) {
console.error("Invalid JSON content", error);
}
sessionId = content.SessionId;
item = modelUtils.createItemFromIncoming(eventDetails);
if (item) {
try {
getAuthenticationUrlResponse = await this.getAuthenticationUrl(sessionId);
authenticationUrl = getAuthenticationUrlResponse.data.AuthenticationUrl
}
catch (error) {
console.error("Unable to get sign in URL", error)
}
item.authenticationUrl = authenticationUrl;
if(identityProvider){
item.authenticationUrl += `&identity_provider=${identityProvider}`;
}
this._shouldAddToTranscript(item) && this._addItemsToTranscript([item]);
}
}

async _handleAuthenticationLifecycleEvent(data) {
var eventDetails = data.data;
var item = modelUtils.createItemFromIncoming(eventDetails);
if (item) {
Eventbus.trigger('authenticationComplete', {});
this._shouldAddToTranscript(item) && this._addItemsToTranscript([item]);
}
}

// TYPING PARTICIPANTS

_handleTypingEvent(dataInput) {
Expand Down
Loading

0 comments on commit 042b6ed

Please sign in to comment.