Giter Club home page Giter Club logo

amazon-connect-chatjs's People

Contributors

amazon-auto avatar bbolek avatar ctwomblyamzn avatar dependabot[bot] avatar doreechi avatar haomingli2020 avatar jaakkotulkki avatar jiahaoyu6666 avatar johnmryan avatar labelson avatar marcogrcr avatar mhiaror avatar mliao95 avatar mrajatttt avatar seiyako avatar shanshanxu2021 avatar spencerlepine avatar spenlep-amzn avatar sriramchivukula avatar sseidel16 avatar swiszm-amazon avatar tscheuneman avatar wriferreiro avatar xiajon avatar yaminli-aws avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

amazon-connect-chatjs's Issues

Zombie Contacts— Stopping chat contact on browser close

Our agents get a lot of sessions where the user is no longer there when they answer a contact. This occurs when a user closes their browser before the agent picks up. Their contact session remains in the queue, even if they aren't physically there anymore.

Is there any way to automatically end a chat session if/when the user closes their browser window? We've tried hooking into browser unload events and calling the stopContact API, but this doesn't always work.

Is there any sort of heartbeat that exists on the chat websocket stream that can automatically close a chat after it goes silent?

Adding @types Definition

It would be good to have @types/amazon-connect-chatjs similar to how there is a @types/amazon-connect-streams types library.

ParticipantRole in API not working

i am creating a custom Chat application with help of amazon-connect-chat (https://apc01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Famazon-connect%2Famazon-connect-chatjs%2Fblob%2Fmaster%2Fdist%2Famazon-connect-chat.js&data=04%7C01%7Cjohnalbert.d%40servion.com%7Cc2b1624ba35b4837345c08d8e3cdada6%7C0eb7ab7502264f22876b7b29fe557678%7C0%7C0%7C637509819844144821%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=O7GFs4pkLqBwUnH%2FMBmqv2Bko1djQhZHF21H%2BYbWbaI%3D&reserved=0) and connect-streams
After upgrading to latest version of amazon-connect-chat, the chat functionality got broken

The ParticipantRole form API is always customer "CUSTOMER" , when is received message is from Agent or customer, so we are not able to identify if the received message is form agent or customer

Can you please help in resolving this issue

getTranscript issue for agent chat session

Hi,
I ran into an issue with calling chatSession.getTranscript(). Different errors were found based on the way how I create chatSession.

contact.onConnected( async () => {
                    const cnn = contact.getAgentConnection() as connect.ChatConnection
                    const agentChatSession = await cnn.getMediaController();

                    agentChatSession.getTranscript({
                      maxResults: 100,
                      sortOrder: "ASCENDING"
                    });
}

I got the below error:
POST https://participant.connect.na.amazonaws.com/participant/transcript net::ERR_NAME_NOT_RESOLVED

In this way, it seems like the invoke URL for getTranscript() is incorrect. The correct url should be https://participant.connect.us-east-1.amazonaws.com/participant/transcript for our connect instance

  1. Thus, I changed to alternative way to create chatSession as below
contact.onConnected(async () => {
                    const cnn = contact.getAgentConnection() as connect.ChatConnection
                    
                    const agentChatSession = connect.ChatSession.create({
                        chatDetails: cnn.getMediaInfo(), // REQUIRED
                        options: { 
                          region: "us-east-1", // REQUIRED, must match the value provided to `connect.core.initCCP()`
                        },
                        type: connect.ChatSession.SessionTypes.AGENT, 
                        websocketManager: connect.core.getWebSocketManager() 
                      });

                     await agentChatSession.getTranscript({
                         maxResults: 100,
                         sortOrder: "ASCENDING"
                    });
}

Then I got the below error:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'getConnectionToken')
    at e.value (chatController.js:124:51)
    at n.value (chatSession.js:116:28)
    at PanelChatroom.componentWillLoad (panel-chatroom.entry.js:28:56)
    at safeCall (index-ef333fab.js:1582:36)
    at dispatchHooks (index-ef333fab.js:1347:23)
    at Array.dispatch (index-ef333fab.js:1329:28)
    at consume (index-ef333fab.js:2991:21)
    at flush (index-ef333fab.js:3046:9)

Using amazon-connect-chat.js in a React compoent

I am building a React component that presents a custom chat interface to a customer. My aim is to use APIs available in amazon-connect-chat.js and documented in README.md in this repository like so -

  componentDidMount() {
    connect.ChatSession.setGlobalConfig({
      region: AWS_CONNECT_REGION
    });
  }

In the React JSX file, I have tried importing the ChatJS file various ways such as -

require("./amazon-connect-chat.js");

I am seeing errors when I run yarn start --

./src/components/App/amazon-connect-chat.js
  Line 1:1:       Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 1:157:     Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 1:265:     Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 1:1080:    Expected an assignment or function call and instead saw an expression  no-unused-expressions
  Line 1:2654:    Expected an assignment or function call and instead saw an expression  no-unused-expressions

Is there something I am missing in the setup of the ChatJS script?

Multiple calls to _fetchConnectionDetails and therefore participant connection

Hi, we have observed the chat js library making multiple calls, sometimes up to 3 calls, to the participant connection endpoint. Apart from adding to the overall latency of the connection, this seems to impact clients by potentially generating throttling responses from connect. I have a fix for this just raising the issue before the pull request

TypeError: AWS.ConnectParticipant is not a constructor

Hi Team,

I am facing
TypeError: AWS.ConnectParticipant is not a constructor

while integrating with chat

I am using ReactJs application.
As mentioned in doc, I followed these steps.
Import Streams, then ChatJS, then the SDK. Ensure that your AWS SDK includes the ConnectParticipant Service (it is relatively new, so make sure you have an up-to-date AWS SDK version [^2.597.0]). - versions are
"amazon-connect-chatjs": "^1.0.6",
"amazon-connect-streams": "^1.5.0",
"aws-sdk": "^2.739.0",

For this step. I could not see the AWS SDK in ChatJS, (attached is the screenshot)

  • When using the SDK from ChatJS to ensure lack of import conflicts. However, this should not be relevant if the order in which you are importing these libraries is the order reflected above.

Screen Shot 1942-06-03 at 11 40 29 PM

Please help me to fix this issue.

Thanks & Regards
Swetha M,R

Build process issues

I've run into a few issues during the build process and will document them + their fixes here so the documentation in the readme may be updated.

System Info:

  • Windows 10
  • Running commands from PowerShell
  • Node Version: v12.18.4 (Latest LTS as of writing this ticket)

Issue 1: Jest missing

Encountered this on step 4 of Building

$ npm run devo

> [email protected] devo C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs
> jest && webpack --mode=development

'jest' is not recognized as an internal or external command,
operable program or batch file.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] devo: `jest && webpack --mode=development`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] devo script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\MyUser\AppData\Roaming\npm-cache\_logs\2020-09-24T16_10_29_984Z-debug.log

Fix

npm install -g jest
Source: https://stackoverflow.com/questions/53638220/jest-error-jest-is-not-recognized-as-an-internal-or-external-command-operabl

Issue 2: Failed to write coverage reports:

Got this after running npm run devo
Got this after running npm run release
Got this after running npm run test

        Failed to write coverage reports:
        ERROR: Error: Path contains invalid characters: C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\coverage\src\lib\webpack:\webpack
        STACK: Error: Path contains invalid characters: C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\coverage\src\lib\webpack:\webpack
    at checkPath (C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\node_modules\make-dir\index.js:21:18)
    at Function.module.exports.sync (C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\node_modules\make-dir\index.js:91:2)
    at FileWriter.writeFile (C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\node_modules\istanbul-lib-report\lib\file-writer.js:190:12)
    at HtmlReport.onSummary (C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\node_modules\istanbul-reports\lib\html\index.js:213:44)
    at Visitor.<computed> [as onSummary] (C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\node_modules\istanbul-lib-report\lib\tree.js:34:30)
    at ReportNode.Node.visit (C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\node_modules\istanbul-lib-report\lib\tree.js:112:17)
    at C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\node_modules\istanbul-lib-report\lib\tree.js:118:15
    at Array.forEach (<anonymous>)
    at ReportNode.Node.visit (C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\node_modules\istanbul-lib-report\lib\tree.js:117:24)
    at Tree.visit (C:\Users\MyUser\Documents\GitHub\amazon-connect-chatjs\node_modules\istanbul-lib-report\lib\tree.js:150:20)

####Fix
Didn't bother to fix this. Looks like the issue is that the path has a : in it at webpack:.

Issue 3: NPM Run Clean fail

$ npm run clean

> [email protected] clean C:\Users\chart\Documents\GitHub\amazon-connect-chatjs
> rm -rf build/ node_modules build

'rm' is not recognized as an internal or external command,
operable program or batch file.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] clean: `rm -rf build/ node_modules build`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] clean script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\chart\AppData\Roaming\npm-cache\_logs\2020-09-24T16_31_37_888Z-debug.log

####Fix
Didn't bother to fix this. Looks like clean is trying to use Unix style commands instead of PowerShell.

Bot name?

I've created a Lex bot and hooked it up to a contact flow that I'm testing with the sample chat UI. Whenever my bot says something, the name shown above it is just "BOT". Where is the name of the bot controlled?

Screen Shot 2019-12-22 at 1 56 09 PM

Move aws js sdk as a seperate dependency.

File - src/client/aws-client.js contains custom bundled sdk contents, if we need to include other dependencies like Lex, including the full sdk conflicts with the contents inside the src/client/aws-client.js. It's hard to debug what are included when custom bundled.

Is it possible to move amazon-connect-chat.js and aws-sdk as two seperate dependencies ?

Including like below Overrides the AWS global contents, Which leads to AWS.LexRuntime is not a constructor error. Had to remove all the contents inside src/client/aws-client.js and rebuilt the amazon-connect-chat.js to make it work.

<script src="js/aws-sdk-2.591.0.min.js"></script>
<script src="js/amazon-connect-chat.js"></script>

Loosing connectivity for more than 10 seconds stops heartbeats from being sent

Steps to reproduce:

/* eslint-disable */
import "amazon-connect-chatjs"; // v1.3.1

import {
  ConnectClient,
  StartChatContactCommand,
} from "@aws-sdk/client-connect"; // v3.254.0

// start a chat contact
const client = new ConnectClient({
  region: "us-east-1",
  credentials: {
    accessKeyId: "...",
    secretAccessKey: "...",
    sessionToken: "...",
  },
});

const { ContactId, ParticipantId, ParticipantToken } = await client.send(
  new StartChatContactCommand({
    InstanceId: "...",
    ContactFlowId: "...",
    ParticipantDetails: { DisplayName: "Customer" },
  })
);

// create chat session
const session = connect.ChatSession.create({
  chatDetails: {
    contactId: ContactId,
    participantId: ParticipantId,
    participantToken: ParticipantToken,
  },
  options: { region: "us-east-1" },
  type: connect.ChatSession.SessionTypes.CUSTOMER,
});

// subscribe to `onConnectionEstablished` events
session.onConnectionEstablished(() => {
  console.log("Connection established");
});

// subscribe to `onConnectionLost` events
// see: https://github.com/amazon-connect/amazon-connect-chatjs/issues/116
session.controller.subscribe("CONNECTION_LOST", () => {
  console.log("Connection lost");
});

// subscribe to `onConnectionBroken` events
session.onConnectionBroken(() => {
  console.log("Connection broken");
});

// connect to chat
await session.connect();
  1. Open Google Chrome (e.g. 109.0.5414.119 (Official Build) (arm64) in MacOS 12.6.3).
  2. Open Developer Tools.
  3. Ensure the browser: establishes a WebSocket connection, sends a { topic: "aws/heartbeat" } message every 10 seconds and receives a message with the same payload.
  4. Wait for the next heartbeat message to be sent (and the corresponding message to be received) and set the browser as offline (using the throttle menu).
  5. Wait for the next heartbeat message to be send and verify no response message is received.
  6. Set the browser as online about ~5 seconds later and verify the response message is received.
  7. Repeat steps 4 and 5.
  8. Set the browser as online about ~20 seconds later and verify the response message is received.

network-tab

Expected result:

Heartbeat message should continue to be sent by the browser. Alternatively, the onConnectionLost or onConnectionBroken events should be immediately fired.

Actual result:

No heartbeat messages are sent anymore. Additionally, onConnectionLost and/or onConnectionBroken are not immediately fired.

~10 minutes later, the onConnectionLost and onConnectionEstablished events are fired, and a new connection gets established.

events

network-tab-2

Analysis:

Searching for "aws/heartbeat" in the source code shows a match in:

!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=10)}([function(e,t){function n(t){return e.exports=n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,n(t)}e.exports=n,e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}e.exports=function(e,t,o){return t&&n(e.prototype,t),o&&n(e,o),Object.defineProperty(e,"prototype",{writable:!1}),e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){function n(t){return e.exports=n=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)},e.exports.__esModule=!0,e.exports.default=e.exports,n(t)}e.exports=n,e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){var o;!function(){"use strict";var r={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function i(e){return c(u(e),arguments)}function a(e,t){return i.apply(null,[e].concat(t||[]))}function c(e,t){var n,o,a,c,s,u,l,d,p,f=1,g=e.length,b="";for(o=0;o<g;o++)if("string"==typeof e[o])b+=e[o];else if("object"==typeof e[o]){if((c=e[o]).keys)for(n=t[f],a=0;a<c.keys.length;a++){if(null==n)throw new Error(i('[sprintf] Cannot access property "%s" of undefined value "%s"',c.keys[a],c.keys[a-1]));n=n[c.keys[a]]}else n=c.param_no?t[c.param_no]:t[f++];if(r.not_type.test(c.type)&&r.not_primitive.test(c.type)&&n instanceof Function&&(n=n()),r.numeric_arg.test(c.type)&&"number"!=typeof n&&isNaN(n))throw new TypeError(i("[sprintf] expecting number but found %T",n));switch(r.number.test(c.type)&&(d=n>=0),c.type){case"b":n=parseInt(n,10).toString(2);break;case"c":n=String.fromCharCode(parseInt(n,10));break;case"d":case"i":n=parseInt(n,10);break;case"j":n=JSON.stringify(n,null,c.width?parseInt(c.width):0);break;case"e":n=c.precision?parseFloat(n).toExponential(c.precision):parseFloat(n).toExponential();break;case"f":n=c.precision?parseFloat(n).toFixed(c.precision):parseFloat(n);break;case"g":n=c.precision?String(Number(n.toPrecision(c.precision))):parseFloat(n);break;case"o":n=(parseInt(n,10)>>>0).toString(8);break;case"s":n=String(n),n=c.precision?n.substring(0,c.precision):n;break;case"t":n=String(!!n),n=c.precision?n.substring(0,c.precision):n;break;case"T":n=Object.prototype.toString.call(n).slice(8,-1).toLowerCase(),n=c.precision?n.substring(0,c.precision):n;break;case"u":n=parseInt(n,10)>>>0;break;case"v":n=n.valueOf(),n=c.precision?n.substring(0,c.precision):n;break;case"x":n=(parseInt(n,10)>>>0).toString(16);break;case"X":n=(parseInt(n,10)>>>0).toString(16).toUpperCase()}r.json.test(c.type)?b+=n:(!r.number.test(c.type)||d&&!c.sign?p="":(p=d?"+":"-",n=n.toString().replace(r.sign,"")),u=c.pad_char?"0"===c.pad_char?"0":c.pad_char.charAt(1):" ",l=c.width-(p+n).length,s=c.width&&l>0?u.repeat(l):"",b+=c.align?p+n+s:"0"===u?p+s+n:s+p+n)}return b}var s=Object.create(null);function u(e){if(s[e])return s[e];for(var t,n=e,o=[],i=0;n;){if(null!==(t=r.text.exec(n)))o.push(t[0]);else if(null!==(t=r.modulo.exec(n)))o.push("%");else{if(null===(t=r.placeholder.exec(n)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){i|=1;var a=[],c=t[2],u=[];if(null===(u=r.key.exec(c)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(a.push(u[1]);""!==(c=c.substring(u[0].length));)if(null!==(u=r.key_access.exec(c)))a.push(u[1]);else{if(null===(u=r.index_access.exec(c)))throw new SyntaxError("[sprintf] failed to parse named argument key");a.push(u[1])}t[2]=a}else i|=2;if(3===i)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");o.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}n=n.substring(t[0].length)}return s[e]=o}t.sprintf=i,t.vsprintf=a,"undefined"!=typeof window&&(window.sprintf=i,window.vsprintf=a,void 0===(o=function(){return{sprintf:i,vsprintf:a}}.call(t,n,t,e))||(e.exports=o))}()},function(e,t,n){var o=n(8);e.exports=function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&o(e,t)},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){var o=n(0).default,r=n(9);e.exports=function(e,t){if(t&&("object"===o(t)||"function"==typeof t))return t;if(void 0!==t)throw new TypeError("Derived constructors may only return object or undefined");return r(e)},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){e.exports=function(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){function n(t,o){return e.exports=n=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},e.exports.__esModule=!0,e.exports.default=e.exports,n(t,o)}e.exports=n,e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){e.exports=function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){"use strict";n.r(t),n.d(t,"WebSocketManager",(function(){return We}));var o=n(0),r=n.n(o),i=n(4),a="NULL",c="CLIENT_LOGGER",s="DEBUG",u="AMZ_WEB_SOCKET_MANAGER:",l="Network offline",d="Network online, connecting to WebSocket server",p="Network offline, ignoring this getWebSocketConnConfig request",f="Heartbeat response not received",g="aws/ping deep heartbeat response not received",b="Heartbeat response received",v="aws/ping deep heartbeat received",y="Sending heartbeat",m="Sending aws/ping deep heartbeat",h="Failed to send heartbeat since WebSocket is not open",S="Failed to send aws/ping deep heartbeat since WebSocket is not open",k="Deep Heartbeat is successful. WebSocketManager has received 200 response from aws/ping",w="Deep Heartbeat failed. WebSocketManager does not receive 200 response from aws/ping",C="Generic topic failed.",O="WebSocket connection established!",L="WebSocket connection is closed",T="WebSocketManager Error, error_event: ",_="Scheduling WebSocket reinitialization, after delay ",x="WebSocket URL cannot be used to establish connection",W="WebSocket Initialization failed - Terminating and cleaning subscriptions",I="Terminating WebSocket Manager",N="Fetching new WebSocket connection configuration",M="Successfully fetched webSocket connection configuration",F="Failed to fetch webSocket connection configuration",E="Retrying fetching new WebSocket connection configuration",D="Initializing Websocket Manager",R="Initializing Websocket Manager Failure callback registered",A="Websocket connection open callback registered",j="Websocket connection close callback registered",H="Websocket connection gain callback registered",P="Websocket connection lost callback registered",G="Websocket subscription failure callback registered",z="Reset Websocket state",q="WebSocketManager Message Error",U="Message received for topic ",J="Invalid incoming message",B="WebsocketManager invoke callbacks for topic success ",V="aws/subscribe",X="aws/unsubscribe",$="aws/heartbeat",K="aws/ping",Z="connected",Q="disconnected",Y={assertTrue:function(e,t){if(!e)throw new Error(t)},assertNotNull:function(e,t){return Y.assertTrue(null!==e&&void 0!==r()(e),Object(i.sprintf)("%s must be provided",t||"A value")),e},isNonEmptyString:function(e){return"string"==typeof e&&e.length>0},assertIsList:function(e,t){if(!Array.isArray(e))throw new Error(t+" is not an array")},isFunction:function(e){return!!(e&&e.constructor&&e.call&&e.apply)},isObject:function(e){return!("object"!==r()(e)||null===e)},isString:function(e){return"string"==typeof e},isNumber:function(e){return"number"==typeof e}},ee=new RegExp("^(wss://)\\w*"),te=new RegExp("^(ws://127.0.0.1:)");Y.validWSUrl=function(e){return ee.test(e)||te.test(e)},Y.getSubscriptionResponse=function(e,t,n){return{topic:e,content:{status:t?"success":"failure",topics:n}}},Y.assertIsObject=function(e,t){if(!Y.isObject(e))throw new Error(t+" is not an object!")},Y.addJitter=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;t=Math.min(t,1);var n=Math.random()>.5?1:-1;return Math.floor(e+n*e*Math.random()*t)},Y.isNetworkOnline=function(){return navigator.onLine},Y.isNetworkFailure=function(e){return!(!e._debug||!e._debug.type)&&"NetworkingError"===e._debug.type};var ne=Y,oe=n(5),re=n.n(oe),ie=n(6),ae=n.n(ie),ce=n(3),se=n.n(ce),ue=n(7),le=n.n(ue),de=n(1),pe=n.n(de),fe=n(2),ge=n.n(fe);function be(e){var t=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}();return function(){var n,o=se()(e);if(t){var r=se()(this).constructor;n=Reflect.construct(o,arguments,r)}else n=o.apply(this,arguments);return ae()(this,n)}}function ve(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function ye(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?ve(Object(n),!0).forEach((function(t){le()(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):ve(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}var me=function(){function e(){pe()(this,e)}return ge()(e,[{key:"debug",value:function(e){}},{key:"info",value:function(e){}},{key:"warn",value:function(e){}},{key:"error",value:function(e){}},{key:"advancedLog",value:function(e){}}]),e}(),he=u,Se={DEBUG:10,INFO:20,WARN:30,ERROR:40,ADVANCED_LOG:50},ke=function(){function e(t){pe()(this,e),this.logMetaData=t,this.updateLoggerConfig()}return ge()(e,[{key:"hasLogMetaData",value:function(){return!!this.logMetaData}},{key:"writeToClientLogger",value:function(e,t){if(this.hasClientLogger()){var n=function(e){switch(e){case 10:return"DEBUG";case 20:return"INFO";case 30:return"WARN";case 40:return"ERROR";case 50:return"ADVANCED_LOG"}}(e);switch(e){case Se.DEBUG:return this._clientLogger.debug(n,t,this.logMetaData)||t;case Se.INFO:return this._clientLogger.info(n,t,this.logMetaData)||t;case Se.WARN:return this._clientLogger.warn(n,t,this.logMetaData)||t;case Se.ERROR:return this._clientLogger.error(n,t,this.logMetaData)||t;case Se.ADVANCED_LOG:return this._advancedLogWriter?this._clientLogger[this._advancedLogWriter](n,t,this.logMetaData)||t:""}}}},{key:"isLevelEnabled",value:function(e){return e>=this._level}},{key:"hasClientLogger",value:function(){return null!==this._clientLogger}},{key:"getLogger",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.prefix||he;return e.logMetaData&&this.setLogMetaData(e.logMetaData),this.logMetaData||console.info("*********Missing required option: WebSocketManager:logMetaData**********"),new Ce(this,ye({prefix:t,logMetaData:this.logMetaData},e))}},{key:"setLogMetaData",value:function(e){this.logMetaData=e}},{key:"updateLoggerConfig",value:function(e){var t=e||{};this._level=t.level||Se.INFO,this._advancedLogWriter="warn",t.advancedLogWriter&&(this._advancedLogWriter=t.advancedLogWriter),t.customizedLogger&&"object"===r()(t.customizedLogger)&&(this.useClientLogger=!0),this._clientLogger=t.logger||this.selectLogger(t),this._logsDestination=a,t.debug&&(this._logsDestination=s),t.logger&&(this._logsDestination=c)}},{key:"selectLogger",value:function(e){return e.customizedLogger&&"object"===r()(e.customizedLogger)?e.customizedLogger:e.useDefaultLogger?Oe():null}}]),e}(),we=function(){function e(){pe()(this,e)}return ge()(e,[{key:"debug",value:function(){}},{key:"info",value:function(){}},{key:"warn",value:function(){}},{key:"error",value:function(){}},{key:"advancedLog",value:function(){}}]),e}(),Ce=function(e){re()(n,e);var t=be(n);function n(e,o){var r;return pe()(this,n),(r=t.call(this)).options=o||{},r.prefix=o.prefix||he,r.logManager=e,r}return ge()(n,[{key:"debug",value:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return this._log(Se.DEBUG,t)}},{key:"info",value:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return this._log(Se.INFO,t)}},{key:"warn",value:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return this._log(Se.WARN,t)}},{key:"error",value:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return this._log(Se.ERROR,t)}},{key:"advancedLog",value:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return this._log(Se.ADVANCED_LOG,t)}},{key:"_shouldLog",value:function(e){return this.logManager.hasClientLogger()&&this.logManager.isLevelEnabled(e)}},{key:"_writeToClientLogger",value:function(e,t){return this.logManager.writeToClientLogger(e,t)}},{key:"_log",value:function(e,t){if(this._shouldLog(e)){var n=this.logManager.useClientLogger?t:this._convertToSingleStatement(t);return this._writeToClientLogger(e,n)}}},{key:"_convertToSingleStatement",value:function(e){var t=new Date(Date.now()).toISOString(),n="[".concat(t,"]");this.prefix&&(n+=this.prefix+" "),this.options&&(this.options.prefix?n+=" "+this.options.prefix+":":n+="");for(var o=0;o<e.length;o++){var r=e[o];n+=this._convertToString(r)+" "}return n}},{key:"_convertToString",value:function(e){try{if(!e)return"";if(ne.isString(e))return e;if(ne.isObject(e)&&ne.isFunction(e.toString)){var t=e.toString();if("[object Object]"!==t)return t}return JSON.stringify(e)}catch(t){return console.error("Error while converting argument to string",e,t),""}}}]),n}(we);var Oe=function(){var e=new we;return e.debug=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return console.debug.apply(window.console,[].concat(t))},e.info=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return console.info.apply(window.console,[].concat(t))},e.warn=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return console.warn.apply(window.console,[].concat(t))},e.error=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return console.error.apply(window.console,[].concat(t))},e},Le=function(){function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:2e3;pe()(this,e),this.numAttempts=0,this.executor=t,this.hasActiveReconnection=!1,this.defaultRetry=n}return ge()(e,[{key:"retry",value:function(){var e=this;this.hasActiveReconnection||(this.hasActiveReconnection=!0,setTimeout((function(){e._execute()}),this._getDelay()))}},{key:"_execute",value:function(){this.hasActiveReconnection=!1,this.executor(),this.numAttempts++}},{key:"connected",value:function(){this.numAttempts=0}},{key:"_getDelay",value:function(){var e=Math.pow(2,this.numAttempts)*this.defaultRetry;return e<=3e4?e:3e4}},{key:"getIsConnected",value:function(){return!this.numAttempts}}]),e}(),Te=null,_e=function(){var e=!1,t=Te.getLogger({prefix:u}),n=ne.isNetworkOnline(),o={primary:null,secondary:null},r={reconnectWebSocket:!0,websocketInitFailed:!1,exponentialBackOffTime:1e3,exponentialTimeoutHandle:null,lifeTimeTimeoutHandle:null,webSocketInitCheckerTimeoutId:null,connState:null},i={connectWebSocketRetryCount:0,connectionAttemptStartTime:null,noOpenConnectionsTimestamp:null},a={pendingResponse:!1,intervalHandle:null},c={pendingResponse:!1,intervalHandle:null},s={initFailure:new Set,getWebSocketTransport:null,subscriptionUpdate:new Set,subscriptionFailure:new Set,topic:new Map,allMessage:new Set,connectionGain:new Set,connectionLost:new Set,connectionOpen:new Set,connectionClose:new Set,deepHeartbeatSuccess:new Set,deepHeartbeatFailure:new Set,topicFailure:new Set},Y={connConfig:null,promiseHandle:null,promiseCompleted:!0},ee={subscribed:new Set,pending:new Set,subscriptionHistory:new Set},te={responseCheckIntervalId:null,requestCompleted:!0,reSubscribeIntervalId:null,consecutiveFailedSubscribeAttempts:0,consecutiveNoResponseRequest:0},oe=new Le((function(){Ie()})),re=new Set([V,X,$,K]),ie=setInterval((function(){if(n!==ne.isNetworkOnline()){if(!(n=ne.isNetworkOnline()))return t.advancedLog(l),void Fe(t.info(l));var e=pe();n&&(!e||ue(e,WebSocket.CLOSING)||ue(e,WebSocket.CLOSED))&&(t.advancedLog(d),Fe(t.info(d)),Ie())}}),250),ae=function(e,n){e.forEach((function(e){try{e(n)}catch(e){Fe(t.error("Error executing callback",e))}}))},ce=function(e){if(null===e)return"NULL";switch(e.readyState){case WebSocket.CONNECTING:return"CONNECTING";case WebSocket.OPEN:return"OPEN";case WebSocket.CLOSING:return"CLOSING";case WebSocket.CLOSED:return"CLOSED";default:return"UNDEFINED"}},se=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";Fe(t.debug("["+e+"] Primary WebSocket: "+ce(o.primary)+" | Secondary WebSocket: "+ce(o.secondary)))},ue=function(e,t){return e&&e.readyState===t},le=function(e){return ue(e,WebSocket.OPEN)},de=function(e){return null===e||void 0===e.readyState||ue(e,WebSocket.CLOSED)},pe=function(){return null!==o.secondary?o.secondary:o.primary},fe=function(){return le(pe())},ge=function(){if(e&&c.pendingResponse&&(t.advancedLog(g),Fe(t.warn(g)),ae(s.deepHeartbeatFailure,{timestamp:Date.now(),error:"aws/ping response is not received"}),clearInterval(c.intervalHandle),c.pendingResponse=!1),a.pendingResponse)return t.advancedLog(f),Fe(t.warn(f)),clearInterval(a.intervalHandle),a.pendingResponse=!1,void Ie();fe()?(e&&(Fe(t.debug(m)),pe().send(xe(K)),c.pendingResponse=!0),Fe(t.debug(y)),pe().send(xe($)),a.pendingResponse=!0):(e&&(t.advancedLog(S),Fe(t.warn(S)),ae(s.deepHeartbeatFailure,{timestamp:Date.now(),error:"Unable to send message to aws/ping because websocket connection is not established."})),t.advancedLog(h),Fe(t.warn(h)),se("sendHeartBeat"),Ie())},be=function(){t.advancedLog(z),r.exponentialBackOffTime=1e3,a.pendingResponse=!1,c.pendingResponse=!1,r.reconnectWebSocket=!0,clearTimeout(r.lifeTimeTimeoutHandle),clearInterval(a.intervalHandle),clearInterval(c.intervalHandle),clearTimeout(r.exponentialTimeoutHandle),clearTimeout(r.webSocketInitCheckerTimeoutId)},ve=function(){te.consecutiveFailedSubscribeAttempts=0,te.consecutiveNoResponseRequest=0,clearInterval(te.responseCheckIntervalId),clearInterval(te.reSubscribeIntervalId)},ye=function(){i.connectWebSocketRetryCount=0,i.connectionAttemptStartTime=null,i.noOpenConnectionsTimestamp=null},me=function(){oe.connected();try{t.advancedLog(O),Fe(t.info(O)),se("webSocketOnOpen"),null!==r.connState&&r.connState!==Q||ae(s.connectionGain),r.connState=Z;var e=Date.now();ae(s.connectionOpen,{connectWebSocketRetryCount:i.connectWebSocketRetryCount,connectionAttemptStartTime:i.connectionAttemptStartTime,noOpenConnectionsTimestamp:i.noOpenConnectionsTimestamp,connectionEstablishedTime:e,timeToConnect:e-i.connectionAttemptStartTime,timeWithoutConnection:i.noOpenConnectionsTimestamp?e-i.noOpenConnectionsTimestamp:null}),ye(),be(),pe().openTimestamp=Date.now(),0===ee.subscribed.size&&le(o.secondary)&&we(o.primary,"[Primary WebSocket] Closing WebSocket"),(ee.subscribed.size>0||ee.pending.size>0)&&(le(o.secondary)&&Fe(t.info("Subscribing secondary websocket to topics of primary websocket")),ee.subscribed.forEach((function(e){ee.subscriptionHistory.add(e),ee.pending.add(e)})),ee.subscribed.clear(),ke()),ge(),a.intervalHandle=setInterval(ge,1e4);var n=1e3*Y.connConfig.webSocketTransport.transportLifeTimeInSeconds;Fe(t.debug("Scheduling WebSocket manager reconnection, after delay "+n+" ms")),r.lifeTimeTimeoutHandle=setTimeout((function(){Fe(t.debug("Starting scheduled WebSocket manager reconnection")),Ie()}),n)}catch(e){Fe(t.error("Error after establishing WebSocket connection",e))}},he=function(e){se("webSocketOnError"),t.advancedLog(T,JSON.stringify(e)),Fe(t.error(T,JSON.stringify(e))),oe.getIsConnected()?Ie():oe.retry()},Se=function(e){var n=JSON.parse(e.data);switch(n.topic){case V:if(Fe(t.debug("Subscription Message received from webSocket server",e.data)),te.requestCompleted=!0,te.consecutiveNoResponseRequest=0,"success"===n.content.status)te.consecutiveFailedSubscribeAttempts=0,n.content.topics.forEach((function(e){ee.subscriptionHistory.delete(e),ee.pending.delete(e),ee.subscribed.add(e)})),0===ee.subscriptionHistory.size?le(o.secondary)&&(Fe(t.info("Successfully subscribed secondary websocket to all topics of primary websocket")),we(o.primary,"[Primary WebSocket] Closing WebSocket")):ke(),ae(s.subscriptionUpdate,n);else{if(clearInterval(te.reSubscribeIntervalId),++te.consecutiveFailedSubscribeAttempts,5===te.consecutiveFailedSubscribeAttempts)return ae(s.subscriptionFailure,n),void(te.consecutiveFailedSubscribeAttempts=0);te.reSubscribeIntervalId=setInterval((function(){ke()}),500)}break;case $:Fe(t.debug(b)),a.pendingResponse=!1;break;case K:Fe(t.debug(v)),c.pendingResponse=!1,200===n.statusCode?ae(s.deepHeartbeatSuccess,{timestamp:Date.now()}):ae(s.deepHeartbeatFailure,{timestamp:Date.now(),statusCode:n.statusCode,statusContent:n.statusContent});break;default:if(n.topic){if(t.advancedLog(U,n.topic),Fe(t.debug(U+n.topic)),le(o.primary)&&le(o.secondary)&&0===ee.subscriptionHistory.size&&this===o.primary)return void Fe(t.warn("Ignoring Message for Topic "+n.topic+", to avoid duplicates"));if(0===s.allMessage.size&&0===s.topic.size)return void Fe(t.warn("No registered callback listener for Topic",n.topic));t.advancedLog(B,n.topic),ae(s.allMessage,n),s.topic.has(n.topic)&&ae(s.topic.get(n.topic),n)}else n.message?(t.advancedLog(q,n),Fe(t.warn(q,n)),ae(s.topicFailure,{timestamp:Date.now(),errorMessage:n.message,connectionId:n.connectionId,requestId:n.requestId})):(t.advancedLog(J,n),Fe(t.warn(J,n)))}},ke=function e(){if(te.consecutiveNoResponseRequest>3)return Fe(t.warn("Ignoring subscribePendingTopics since we have exhausted max subscription retries with no response")),void ae(s.subscriptionFailure,ne.getSubscriptionResponse(V,!1,Array.from(ee.pending)));fe()?0!==Array.from(ee.pending).length&&(clearInterval(te.responseCheckIntervalId),pe().send(xe(V,{topics:Array.from(ee.pending)})),te.requestCompleted=!1,te.responseCheckIntervalId=setInterval((function(){te.requestCompleted||(++te.consecutiveNoResponseRequest,e())}),1e3)):Fe(t.warn("Ignoring subscribePendingTopics call since Default WebSocket is not open"))},we=function(e,n){ue(e,WebSocket.CONNECTING)||ue(e,WebSocket.OPEN)?e.close(1e3,n):Fe(t.warn("Ignoring WebSocket Close request, WebSocket State: "+ce(e)))},Ce=function(e){we(o.primary,"[Primary] WebSocket "+e),we(o.secondary,"[Secondary] WebSocket "+e)},Oe=function(){i.connectWebSocketRetryCount++;var e=ne.addJitter(r.exponentialBackOffTime,.3);Date.now()+e<=Y.connConfig.urlConnValidTime?(t.advancedLog(_),Fe(t.debug(_+e+" ms")),r.exponentialTimeoutHandle=setTimeout((function(){return Ne()}),e),r.exponentialBackOffTime*=2):(t.advancedLog(x),Fe(t.warn(x)),Ie())},_e=function(e){be(),ve(),t.advancedLog(W,e),Fe(t.error(W)),r.websocketInitFailed=!0,Ce(I),clearInterval(ie),ae(s.initFailure,{connectWebSocketRetryCount:i.connectWebSocketRetryCount,connectionAttemptStartTime:i.connectionAttemptStartTime,reason:e}),ye()},xe=function(e,t){return JSON.stringify({topic:e,content:t})},We=function(e){return!!(ne.isObject(e)&&ne.isObject(e.webSocketTransport)&&ne.isNonEmptyString(e.webSocketTransport.url)&&ne.validWSUrl(e.webSocketTransport.url)&&1e3*e.webSocketTransport.transportLifeTimeInSeconds>=3e5)||(Fe(t.error("Invalid WebSocket Connection Configuration",e)),!1)},Ie=function(){if(!ne.isNetworkOnline())return t.advancedLog(p),void Fe(t.info(p));if(r.websocketInitFailed)Fe(t.debug("WebSocket Init had failed, ignoring this getWebSocketConnConfig request"));else{if(Y.promiseCompleted)return be(),t.advancedLog(N),Fe(t.info(N)),i.connectionAttemptStartTime=i.connectionAttemptStartTime||Date.now(),Y.promiseCompleted=!1,Y.promiseHandle=s.getWebSocketTransport(),Y.promiseHandle.then((function(e){return Y.promiseCompleted=!0,t.advancedLog(M),Fe(t.debug(M,e)),We(e)?(Y.connConfig=e,Y.connConfig.urlConnValidTime=Date.now()+85e3,Ne()):(_e("Invalid WebSocket connection configuration: "+e),{webSocketConnectionFailed:!0})}),(function(e){return Y.promiseCompleted=!0,t.advancedLog(F),Fe(t.error(F,e)),ne.isNetworkFailure(e)?(t.advancedLog(E+JSON.stringify(e)),Fe(t.info(E+JSON.stringify(e))),oe.retry()):_e("Failed to fetch webSocket connection configuration: "+JSON.stringify(e)),{webSocketConnectionFailed:!0}}));Fe(t.debug("There is an ongoing getWebSocketConnConfig request, this request will be ignored"))}},Ne=function(){if(r.websocketInitFailed)return Fe(t.info("web-socket initializing had failed, aborting re-init")),{webSocketConnectionFailed:!0};if(!ne.isNetworkOnline())return Fe(t.warn("System is offline aborting web-socket init")),{webSocketConnectionFailed:!0};t.advancedLog(D),Fe(t.info(D)),se("initWebSocket");try{if(We(Y.connConfig)){var e=null;return le(o.primary)?(Fe(t.debug("Primary Socket connection is already open")),ue(o.secondary,WebSocket.CONNECTING)||(Fe(t.debug("Establishing a secondary web-socket connection")),oe.numAttempts=0,o.secondary=Me()),e=o.secondary):(ue(o.primary,WebSocket.CONNECTING)||(Fe(t.debug("Establishing a primary web-socket connection")),o.primary=Me()),e=o.primary),r.webSocketInitCheckerTimeoutId=setTimeout((function(){le(e)||Oe()}),1e3),{webSocketConnectionFailed:!1}}}catch(e){return Fe(t.error("Error Initializing web-socket-manager",e)),_e("Failed to initialize new WebSocket: "+e.message),{webSocketConnectionFailed:!0}}},Me=function(){var e=new WebSocket(Y.connConfig.webSocketTransport.url);return e.addEventListener("open",me),e.addEventListener("message",Se),e.addEventListener("error",he),e.addEventListener("close",(function(n){return function(e,n){t.advancedLog(L,JSON.stringify(e)),Fe(t.info(L,JSON.stringify(e))),se("webSocketOnClose before-cleanup"),ae(s.connectionClose,{openTimestamp:n.openTimestamp,closeTimestamp:Date.now(),connectionDuration:Date.now()-n.openTimestamp,code:e.code,reason:e.reason}),de(o.primary)&&(o.primary=null),de(o.secondary)&&(o.secondary=null),r.reconnectWebSocket&&(le(o.primary)||le(o.secondary)?de(o.primary)&&le(o.secondary)&&(Fe(t.info("[Primary] WebSocket Cleanly Closed")),o.primary=o.secondary,o.secondary=null):(Fe(t.warn("Neither primary websocket and nor secondary websocket have open connections, attempting to re-establish connection")),r.connState===Q?Fe(t.info("Ignoring connectionLost callback invocation")):(ae(s.connectionLost,{openTimestamp:n.openTimestamp,closeTimestamp:Date.now(),connectionDuration:Date.now()-n.openTimestamp,code:e.code,reason:e.reason}),i.noOpenConnectionsTimestamp=Date.now()),r.connState=Q,Ie()),se("webSocketOnClose after-cleanup"))}(n,e)})),e},Fe=function(e){return e&&"function"==typeof e.sendInternalLogToServer&&e.sendInternalLogToServer(),e};this.init=function(e){if(ne.assertTrue(ne.isFunction(e),"transportHandle must be a function"),null===s.getWebSocketTransport)return s.getWebSocketTransport=e,Ie();Fe(t.warn("Web Socket Manager was already initialized"))},this.onInitFailure=function(e){return t.advancedLog(R),ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.initFailure.add(e),r.websocketInitFailed&&e(),function(){return s.initFailure.delete(e)}},this.onConnectionOpen=function(e){return t.advancedLog(A),ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.connectionOpen.add(e),function(){return s.connectionOpen.delete(e)}},this.onConnectionClose=function(e){return t.advancedLog(j),ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.connectionClose.add(e),function(){return s.connectionClose.delete(e)}},this.onConnectionGain=function(e){return t.advancedLog(H),ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.connectionGain.add(e),fe()&&e(),function(){return s.connectionGain.delete(e)}},this.onConnectionLost=function(e){return t.advancedLog(P),ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.connectionLost.add(e),r.connState===Q&&e(),function(){return s.connectionLost.delete(e)}},this.onSubscriptionUpdate=function(e){return ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.subscriptionUpdate.add(e),function(){return s.subscriptionUpdate.delete(e)}},this.onSubscriptionFailure=function(e){return t.advancedLog(G),ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.subscriptionFailure.add(e),function(){return s.subscriptionFailure.delete(e)}},this.onMessage=function(e,t){return ne.assertNotNull(e,"topicName"),ne.assertTrue(ne.isFunction(t),"cb must be a function"),s.topic.has(e)?s.topic.get(e).add(t):s.topic.set(e,new Set([t])),function(){return s.topic.get(e).delete(t)}},this.onAllMessage=function(e){return ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.allMessage.add(e),function(){return s.allMessage.delete(e)}},this.subscribeTopics=function(e){ne.assertNotNull(e,"topics"),ne.assertIsList(e),e.forEach((function(e){ee.subscribed.has(e)||ee.pending.add(e)})),te.consecutiveNoResponseRequest=0,ke()},this.sendMessage=function(e){if(ne.assertIsObject(e,"payload"),void 0===e.topic||re.has(e.topic))Fe(t.warn("Cannot send message, Invalid topic",e));else{try{e=JSON.stringify(e)}catch(n){return void Fe(t.warn("Error stringify message",e))}fe()?pe().send(e):Fe(t.warn("Cannot send message, web socket connection is not open"))}},this.deepHeartbeatHandler=function(){e=!0},this.onDeepHeartbeatSuccess=function(e){return t.advancedLog(k),ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.deepHeartbeatSuccess.add(e),function(){return s.deepHeartbeatSuccess.delete(e)}},this.onDeepHeartbeatFailure=function(e){return t.advancedLog(w),ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.deepHeartbeatFailure.add(e),function(){return s.deepHeartbeatFailure.delete(e)}},this.onTopicFailure=function(e){return t.advancedLog(C),ne.assertTrue(ne.isFunction(e),"cb must be a function"),s.topicFailure.add(e),function(){return s.topicFailure.delete(e)}},this.closeWebSocket=function(){be(),ve(),r.reconnectWebSocket=!1,clearInterval(ie),Ce("User request to close WebSocket")},this.terminateWebSocketManager=_e},xe={create:function(e){return Te||(Te=new ke(e)),Te.hasLogMetaData()||Te.setLogMetaData(e),e||console.info("********Missing metaData for logs from websocketManager: initialize websocketManager using create(metaData)*******"),new _e},setGlobalConfig:function(e){var t=e&&e.loggerConfig;Te||(Te=new ke),Te.updateLoggerConfig(t)},LogLevel:Se,Logger:me};global.connect=global.connect||{},connect.WebSocketManager=xe;var We=xe;t.default=We}]);

However, since this file is only in its minified version, I can't debug why this problem occurs.

Invoking ChatSession.getTranscript() without arguments fails even though all arguments are optional

ChatSession.getTranscript() must always be invoked with an Object argument, even though all arguments are optional:

const session = connect.ChatSession.create({ /* (...) */});

// specifying an empty object (i.e. `{}`) is required, even though all arguments are optional
const transcript = await session.getTranscript({});

Ideally, this method should be allowed to be invoked without any arguments to achieve the same purpose:

const session = connect.ChatSession.create({ /* (...) */});

// this should be allowed, yet this is thrown instead:
// Uncaught TypeError: Cannot read properties of undefined (reading 'metadata')
const transcript = await session.getTranscript();

This happens because the inputArgs parameter of ChatController.prototype.getTranscript() is used without any validation:

getTranscript(inputArgs) {
const startTime = new Date().getTime();
const metadata = inputArgs.metadata || null;

Proposed fix:

Update ChatController.prototype.getTranscript() to make inputArgs optional with {} as the default value:

getTranscript(inputArgs = {}) {
  // (...)
}

Expose CHAT_EVENTS.CONNECTION_LOST through ChatSession.onConnectionLost()

Problem:

Currently, the ChatSession API exposes two methods related to the underlying WebSocket connection(s)' health:

  • onConnectionBroken(): Triggered when the underlying WebSocket connection has been broken or failed to be established (e.g. the participantToken is invalid).
  • onConnectionEstablished(): Triggered when the underlying WebSocket connection has been established or re-established after being lost.

Internally, these methods subscribe to the CHAT_EVENTS.CONNECTION_BROKEN and CHAT_EVENTS.CONNECTION_ESTABLISHED events respectively.

onConnectionBroken(callback) {
this.controller.subscribe(CHAT_EVENTS.CONNECTION_BROKEN, callback);
}
onConnectionEstablished(callback) {
this.controller.subscribe(CHAT_EVENTS.CONNECTION_ESTABLISHED, callback);
}

Upon closer examination of the CHAT_EVENTS constant, there seems to be a useful CHAT_EVENTS.CONNECTION_LOST event as well.

export const CHAT_EVENTS = {
INCOMING_MESSAGE: "INCOMING_MESSAGE",
INCOMING_TYPING: "INCOMING_TYPING",
INCOMING_READ_RECEIPT: "INCOMING_READ_RECEIPT",
INCOMING_DELIVERED_RECEIPT: "INCOMING_DELIVERED_RECEIPT",
CONNECTION_ESTABLISHED: "CONNECTION_ESTABLISHED",
CONNECTION_LOST: "CONNECTION_LOST",
CONNECTION_BROKEN: "CONNECTION_BROKEN",
CONNECTION_ACK: "CONNECTION_ACK",
CHAT_ENDED: "CHAT_ENDED",
MESSAGE_METADATA: "MESSAGEMETADATA",
PARTICIPANT_IDLE: "PARTICIPANT_IDLE",
PARTICIPANT_RETURNED: "PARTICIPANT_RETURNED",
AUTODISCONNECTION: "AUTODISCONNECTION"
};

During my tests, I was able to verify that the event gets triggered whenever the underlying WebSocket connection(s) get(s) closed, and once it's/they're re-established, the onConnectionEstablished (a.k.a. CHAT_EVENTS.CONNECTION_ESTABLISHED) event gets triggered.

One can currently subscribe to this event with the following code:

const session = connect.ChatSession.create(/* ... */);
session.controller.subscribe("CONNECTION_LOST", (event) => {
  const { chatDetails } = event;
  // notify user that they have temporarily lost the connection to the chat
});

However, this code is fragile as it depends on a private implementation detail that could change on a newer version.

Proposal:

Thus, I'm proposing that a new publicly documented method gets added to class ChatSession as follows:

chatSession.js

export class ChatSession {
  // (...)

  onConnectionLost(callback) {
    this.controller.subscribe(CHAT_EVENTS.CONNECTION_LOST, callback);
  }
}

index.d.ts

declare namespace connect {
  interface ChatSession {
    // (...)

    /**
     * Subscribes an event handler that triggers when the session connection is lost.
     * @param handler The event handler.
     */
    onConnectionEstablished(
      handler: (event: ChatConnectionLostEvent) => void
    ): void;
  }

  interface ChatConnectionLostEvent {
    readonly chatDetails: ChatDetails;
  }
}

getTranscript not working for agent chat session

Hi,

When i try to get the chat transcript i get the below error:

access to XMLHttpRequest at 'https://participant.connect.us-west-2.amazonaws.com/participant/event' from origin 'http://127.0.0.1:5501' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Code:
<script src="lib/connect-streams-min.js"></script>
<script src="lib/amazon-connect-chat.js"></script>

async function handleContactIncoming(contact) {
    console.log(contact, 'handleContactIncoming', 'Contact is incoming');
    console.log('connection: ', currentContact);
    console.log('CurrentQ: ', contact.getQueue().name);
    console.log('customerNo: ', currentContact.customerNo);
    console.log('QueueType: ', contact.getType());
    if (contact.getType() === connect.ContactType.CHAT) {
        // applies only to CHAT contacts
        console.log('connect.ConnectionType.AGENT', connect.ConnectionType.AGENT)
        const cnn = contact.getConnections().find(cnn => cnn.getType() === connect.ConnectionType.AGENT);
        const agentChatSession = connect.ChatSession.create({
            chatDetails: cnn.getMediaInfo(), // REQUIRED
            options: { // REQUIRED
                region: "us-east-1", // REQUIRED, must match the value provided to `connect.core.initCCP()`
            },
            type: connect.ChatSession.SessionTypes.AGENT, // REQUIRED
            websocketManager: connect.core.getWebSocketManager() // REQUIRED
        });
        const awsSdkResponse = await agentChatSession.getTranscript({
            maxResults: 100,
            sortOrder: "ASCENDING"
        });
        const { InitialContactId, NextToken, Transcript } = awsSdkResponse.data;
        console.log(Transcript);
        return;
    }
}

After invoking CustomerChatSession.disconnectParticipant() other methods should not be allowed to be invoked

Steps to reproduce:

import "amazon-connect-chatjs"; // v1.3.1
import {
  ConnectClient,
  StartChatContactCommand,
} from "@aws-sdk/client-connect"; // v3.254.0

// start a chat contact
const client = new ConnectClient({
  region: "us-east-1",
  credentials: {
    accessKeyId: "...",
    secretAccessKey: "...",
    sessionToken: "...",
  },
});

const { ContactId, ParticipantId, ParticipantToken } = await client.send(
  new StartChatContactCommand({
    InstanceId: "...",
    ContactFlowId: "...",
    ParticipantDetails: { DisplayName: "Customer" },
  })
);

// create chat session
const session = connect.ChatSession.create({
  chatDetails: {
    contactId: ContactId,
    participantId: ParticipantId,
    participantToken: ParticipantToken,
  },
  options: { region: "us-east-1" },
  type: connect.ChatSession.SessionTypes.CUSTOMER,
});

// connect to chat
await session.connect();

// disconnect from chat
await session.disconnectParticipant();

// try to disconnect from chat again
try {
  await session.disconnectParticipant();
} catch (e) {
  console.error(e);
}

// try to send message
try {
  await session.sendMessage({
    contentType: "text/plain",
    message: "Hello World!"
  });
} catch (e) {
  console.error(e);
}

// try to connect to chat again
try {
  await session.connect();
} catch (e) {
  console.error(e);
}

Expected result:

All the try/catch blocks after the await session.disconnectParticipant() invocation fail with an Error indicating that the participant has already been disconnected. No additional HTTP requests are sent.

Actual result:

All the try/catch blocks after the await session.disconnectParticipant() invocation fail with an Object with an AccessDeniedException error. Each method invocation sends an HTTP request.

// session.disconnectParticipant()
{
    "type": "AccessDeniedException",
    "message": "Access denied",
    "stack": [
        "AccessDeniedException: Access denied",
        ...
    ],
    "statusCode": 403
}

// session.sendMessage()
{
    "type": "AccessDeniedException",
    "message": "Access denied",
    "stack": [
        "AccessDeniedException: Access denied",
        ...
    ],
    "statusCode": 403
}

// session.connect()
{
    "_debug": {
        "reason": "Failed to fetch connectionDetails with createParticipantConnection",
        "_debug": {
            "type": "AccessDeniedException",
            "message": "Access denied",
            "stack": [
                "AccessDeniedException: Access denied",
                ...
            ],
            "statusCode": 403
        }
    },
    "connectSuccess": false,
    "connectCalled": true,
    "metadata": null
}

Analysis:

ChatController already has a _participantDisconnected field it uses to keep track of whether the participant has been disconnected. The field is initialized to false in the constructor:

class ChatController {
constructor(args) {
this.argsValidator = new ChatServiceArgsValidator();
this.pubsub = new EventBus();
this.sessionType = args.sessionType;
this.getConnectionToken = args.chatDetails.getConnectionToken;
this.connectionDetails = args.chatDetails.connectionDetails;
this.initialContactId = args.chatDetails.initialContactId;
this.contactId = args.chatDetails.contactId;
this.participantId = args.chatDetails.participantId;
this.chatClient = args.chatClient;
this.participantToken = args.chatDetails.participantToken;
this.websocketManager = args.websocketManager;
this._participantDisconnected = false;

It also sets it to true upon successfully invoking ChatController.prototype.disconnectParticipant():

disconnectParticipant() {
const startTime = new Date().getTime();
const connectionToken = this.connectionHelper.getConnectionToken();
return this.chatClient
.disconnectParticipant(connectionToken)
.then(response => {
this._sendInternalLogToServer(this.logger.info("Disconnect participant successfully"));
this._participantDisconnected = true;

However, the field is not used anywhere to prevent the ChatController to continue being used after the participant has been disconnected.

Proposed fix:

Ensure all methods in ChatController can only be invoked if _participantDisconnected === true, and throw an Error indicating that the participant is already disconnected otherwise.

Additionally, set the _participantDisconnected field to true when the connection gets broken (i.e. the participant token is no longer valid):

_handleEndedConnection(eventData) {
this._forwardChatEvent(CHAT_EVENTS.CONNECTION_BROKEN, {
data: eventData,
chatDetails: this.getChatDetails()
});
this.breakConnection();
}

SendMessage from AgentChatSession by clicking through button.

I am trying to send message from AgentChatSession by clicking through button.

I referred this article - https://docs.aws.amazon.com/connect-participant/latest/APIReference/API_SendEvent.html

I am not aware of ConnectionToken and ClientToken. If Anyone know, Please mention your valuable comments on how to send message through button from Agent side.

My Code :

sendMessage(){
connect.contact((contact) => {
contact.onAccepted(async () => {
const cnn = contact.getAgentConnection() as connect.ChatConnection
if(cnn){
const agentChatSession = await cnn.getMediaController();
agentChatSession.sendMessage({ message:'Good Morning' , contentType: 'text/plain' }).then((res) =>{
console.log(res.status)
})
}

    });
})

}

but its not sending message and not showing any response

Can anyone help me on this.

Documentation for downloadAttachment

The documentation is very vague for this. All that is returned for me is a blob, even though I seethe url object in network logs. Any advice on how to pass the URL

IE11 support for chatjs

It looks like IE11 is not supported. There seems to be something wrong with the websocket method once the connection is established and we cannot see any message coming through with onMessage(). Can anyone advise us what to do, please?

Purpose of connect.ChatSession.connect args metadata property

I was curious if there was more information on what the purpose of this metadata object is as the documentation is pretty lacking in that regard. Will it ever be used to set contact flow attributes down the line?

I have a requirement around collecting some user details before starting a chat session and it would be good to have the ability to pass them to contact flow via this property. If that is not possible I am assuming the best way to pass this properties to the contact flow is straight after the StartChatContact call by using the UpdateContactAttributes endpoint.

Get the active contact for an agent

Hi

I am trying to integrate canned responses into an interface to populate messages for agents in their active chat window. I can get all of the agent's connections but I cant seem to be able to determine which contact is active within the chat window.

Is there a simple way to do this? I am using jQuery to iterate through the active connections as per below:

connect.agent(function(agent) {
  // get the agent name
  var name  = agent.getName();
  var contacts= agent.getContacts();
  $.each(contacts, function(a, contact) {
    console.log(contact);
    console.log('state', contact.getStatus());
  });
});

Thank you for any help.

sendAttachment error

I have a problem with the method in question > chatSession.sendAttachment()
This is what is reported in the documentation:

attachment object is the actual file that will be sent to agent from end-customer and vice versa.

const awsSdkResponse = await chatSession.sendAttachment({
  attachment: attachment
})

I tried to send the attachment both as a File and as a Blob type.
In both cases I get this error

TypeError: Cannot read properties of undefined (reading 'type')

I have noticed that the documentation is quite vague about the attachments.

Has anyone used the method successfully and can you help me?

Connect not defined in vanilla JS

I am attempting to serve CCP from flask and CCP will not load...
vanilla js.
Uncaught ReferenceError: connect is not defined

When I the index.html is read as a file file:///index.html it will load just fine.

Non-minified example chat app?

We've been evaluating a few different providers (Intercom, Twilio, etc) and AWS Connect seems the most configurable and fits our use case the best, but am disappointed that the only web chat client I've been able to find is this minified example here: https://github.com/amazon-connect/amazon-connect-chat-ui-examples

Is there a reason that sample is minified? Are there any other "official" AWS Connect web chat clients? Is there a plan to release anything soon in this regard, or an non-minified version of the sample UI that we can build off of?

Library contains hardcoded usage of console.* methods

The library has the concept of a LogManager that can be configured by the user to specify how logging is performed:

const LogManager = new LogManagerImpl();

this.logger = LogManager.getLogger({
prefix: "ChatJS-ChatController",
logMetaData: args.logMetaData
});
this.logMetaData = args.logMetaData;
this.messageReceiptUtil = new MessageReceiptsUtil(args.logMetaData);
this.logger.info("Browser info:", window.navigator.userAgent);

However, usage of LogManager in the library is not consistent and there are multiple instances of hardcoded usages of console.* logging methods instead of using LogManager. This defeats the purpose of allowing users to customize the logging on these places:

src/core/chatController.js

console.warn("onConnectionSuccess response", response);

console.warn("onConnectionSuccess responseObject", responseObject);

src/core/chatSession.js

console.warn("enabling message-receipts by default; to disable set config.features.messageReceipts.shouldSendMessageReceipts = false");

src/globalConfig.js

console.log("new features added, initialValue: "
+ target[property] + " , newValue: " + value, Array.isArray(target[property]));

src/lib/connect-csm.js

console.log('Starting csm shared worker with', params.sharedWorkerUrl);

console.log('Failed to initialize csm shared worker with', params.sharedWorkerUrl);
console.log(e.message);

console.log('[FATAL] CSM initialization failed! Please make sure the sharedWorkerUrl is reachable.');

console.log('Error emitting default metrics', err);

src/lib/connect-csm-worker.js

console.error('Error in shouldDedupe', err);

console.error('Failed to add event to metricMap', err);


Note: I understand that src/lib/connect-csm-worker.js may be a bit more complicated given that code runs in a SharedWorker and it won't have access to the LogManager. However, it's probably worth sending the data back to the port. For example:

// log-worker.js
globalThis.addEventListener("connect", (event) => {
  LogManager.ports.push(...event.ports);
});

export class LogManager {
  static ports = [];

  error(...args) {
    LogManager.ports.forEach(p => p.postMessage(JSON.stringify({
      action: "LogManager",
      data: {
        level: "ERROR",
        message: args
      }
    })));
  }

  // (...)
}

// src/lib/connect-csm-worker.js
import { LogManager } from "(...)/log-worker";

const logger = LogManager.getLogger(/* ... */);

// (...)
logger.error('Error in shouldDedupe', err); 

Sync onTyping Event

Hello,
Does anybody know the best way to display a bubble to customer for agent on typing and have it stop of the agent stops typing? So far we are able to display it but it doesn't go away until a message is sent

User is not authorized to access this resource with an explicit deny

I import amazon-connect-chatjs library in my code.

with the help of library I am trying to get Customer messages, Sending Messages from Agent side but I am getting this error because its connecting to 'us-west-2' but I gave 'us-east-1' in my code

myCode

import "amazon-connect-streams";
import "amazon-connect-chatjs"

contact.onAccepted(() => {
const cnn = contact.getAgentConnection() as connect.ChatConnection;
console.log(cnn)

    const agentChatSession = connect.ChatSession.create({
      chatDetails: cnn.getMediaInfo(), // REQUIRED
      options: { // REQUIRED
        region: "us-east-1", // REQUIRED, must match the value provided to `connect.core.initCCP()`
      },
      type: connect.ChatSession.SessionTypes.AGENT, // REQUIRED
      websocketManager: connect.core.getWebSocketManager() // REQUIRED
    });

    console.log(agentChatSession)

    agentChatSession.onMessage((response) => {
      console.log(response)
    })
    
    agentChatSession.sendMessage({message:'so and so', content:'text/plain' }).then((res) => { console.log(res) })

  });

core.js:4442 ERROR Error: Uncaught (in promise): Object: {"reason":"Failed to fetch connectionDetails with createParticipantConnection","_debug":{"type":"UnknownError","message":"User is not authorized to access this resource with an explicit deny","stack":["UnknownError: User is not authorized to access this resource with an explicit deny"," at Object.extractError (https://10.224.58.9:4200/vendor.js:16616:97122)"," at constructor.extractError (https://10.224.58.9:4200/vendor.js:16616:102318)"," at constructor.callListeners (https://10.224.58.9:4200/vendor.js:16616:120044)"," at constructor.emit (https://10.224.58.9:4200/vendor.js:16616:119755)"," at constructor.emitEvent (https://10.224.58.9:4200/vendor.js:16616:113435)"," at constructor.e (https://10.224.58.9:4200/vendor.js:16616:108941)"," at r.runTo (https://10.224.58.9:4200/vendor.js:16616:146652)"," at https://10.224.58.9:4200/vendor.js:16616:146858"," at constructor. (https://10.224.58.9:4200/vendor.js:16616:109230)"," at constructor. (https://10.224.58.9:4200/vendor.js:16616:113491)"],"statusCode":403}}
at resolvePromise (zone-evergreen.js:798:1)
at resolvePromise (zone-evergreen.js:750:1)
at zone-evergreen.js:860:1
at ZoneDelegate.invokeTask (zone-evergreen.js:399:1)
at Object.onInvokeTask (core.js:27533:1)
at ZoneDelegate.invokeTask (zone-evergreen.js:398:1)
at Zone.runTask (zone-evergreen.js:167:1)
at drainMicroTaskQueue (zone-evergreen.js:569:1)
at ZoneTask.invokeTask [as invoke] (zone-evergreen.js:484:1)
at invokeTask (zone-evergreen.js:1621:1)

POST https://participant.connect.us-west-2.amazonaws.com/participant/connection 403
POST https://participant.connect.us-west-2.amazonaws.com/participant/event 403
POST https://participant.connect.us-west-2.amazonaws.com/participant/messages 403

Can any one Know where I did mistake.

Question about chatbox

Hello;
We are using chatbox and also we display some data to agent based on bot context.
Is there any event that we can get, when user switches chats? For example in screenshot, agent has 2 chats, and when agent clicks to second chat, we want to display data about second contact. Is there a way to get contactid, when agent clicks that chat tab?

Thanks

image

Any support for "response buttons"?

Is there any support with the Chat SDK for supplying "response buttons" that users can choose to avoid having to type common requests out, such as shown here from Amazon's own support chat system:

Screen Shot 2019-12-22 at 1 49 22 PM

Support for React Native applications

I building out a React Native chat demo using Expo I found that everything worked right up until receiving messages back down the web-socket when running on Android.

The exact same code worked fine using webpack or via remote debugging (actually runs inside Chrome) so I assume there is a window dependency or perhaps other web-socket browser dependency breaking the use of this library in React Native projects

I dropped back to using raw web-sockets and the participant API with no problems, it just would have been nice to use chat-js

Josh

Invoking ChatSession.connect() a second time has undesired side-effects

Steps to reproduce:

import "amazon-connect-chatjs"; // v1.3.1
import {
  ConnectClient,
  StartChatContactCommand,
} from "@aws-sdk/client-connect"; // v3.254.0

// start a chat contact
const client = new ConnectClient({
  region: "us-east-1",
  credentials: {
    accessKeyId: "...",
    secretAccessKey: "...",
    sessionToken: "...",
  },
});

const { ContactId, ParticipantId, ParticipantToken } = await client.send(
  new StartChatContactCommand({
    InstanceId: "...",
    ContactFlowId: "...",
    ParticipantDetails: { DisplayName: "Customer" },
  })
);

// create chat session
const session = connect.ChatSession.create({
  chatDetails: {
    contactId: ContactId,
    participantId: ParticipantId,
    participantToken: ParticipantToken,
  },
  options: { region: "us-east-1" },
  type: connect.ChatSession.SessionTypes.CUSTOMER,
});

// subscribe to messages
session.onMessage((event) => {
  console.log("Got message:", event.data.Content);
});

// invoke connect twice
await session.connect();
await session.connect();

// send a message once
await session.sendMessage({
  contentType: "text/plain",
  message: "Hello World!",
});

Expected behavior:

The Hello World! message gets logged once:

Got message: Hello World!

Actual behavior:

The Hello World! message gets logged twice:

Got message: Hello World!

Got message: Hello World!

Analysis:

Even though LpcConnectionHelper.constructor() detects duplicate invocations and re-uses the LpcConnectionHelperBase instance which prevents duplicate WebSocketManager (i.e. WebSocket connections) from being created:

if (this.customerConnection) {
// ensure customer base instance exists for this contact ID
if (!LpcConnectionHelper.customerBaseInstances[contactId]) {
LpcConnectionHelper.customerBaseInstances[contactId] =
new LpcConnectionHelperBase(connectionDetailsProvider, undefined, logMetaData, connectionDetails);
}
this.baseInstance = LpcConnectionHelper.customerBaseInstances[contactId];
} else {

ChatController.prototype.connect() has not logic to detect whether the method is invoked more than once:

connect(args={}) {
this.sessionMetadata = args.metadata || null;
this.argsValidator.validateConnectChat(args);
const connectionDetailsProvider = this._getConnectionDetailsProvider();
return connectionDetailsProvider.fetchConnectionDetails()
.then(
(connectionDetails) =>
this._initConnectionHelper(connectionDetailsProvider, connectionDetails)
)
.then(response => this._onConnectSuccess(response, connectionDetailsProvider))
.catch(err => {
return this._onConnectFailure(err);
});
}

Which results in undesired side-effects like:

  1. Multiple calls to connectparticipant:CreateParticipantConnection:

const connectionDetailsProvider = this._getConnectionDetailsProvider();
return connectionDetailsProvider.fetchConnectionDetails()

  1. Duplicate event listeners to LpcConnectionHelperBase events:

this.subscriptions = [
this.baseInstance.onEnded(this.handleEnded.bind(this)),
this.baseInstance.onConnectionGain(this.handleConnectionGain.bind(this)),
this.baseInstance.onConnectionLost(this.handleConnectionLost.bind(this)),
this.baseInstance.onMessage(this.handleMessage.bind(this))
];

Proposed fix:

The ChatController.prototype.connect() should have logic to detect if the method is invoked more than once and either:

  • Throw an Error indicating that it can only be called once.
  • Have no effect (i.e. be a no-op).

Getting chat transcript during ACW

Hi

I'm using ChatJS and Amazon Connect Streams with a slightly customized agent CCP. The agents want to be able to send the chat participants a follow-up email containing the transcript of their chat.

Using the Amazon Connect Streams API my code looks like this

// When new contacts are created, this assigns a callback function
connect.contact(function(contact){    
    console.log("LSA: new contact, set AWC listener");
    contact.onACW(handleACW);
}); 
// The callback function
function handleACW(contact) {
    console.log("On contact AWC");
    var connection  = contact.getAgentConnection()
    connection.getConnectionToken()
                .then( function(r) { 
                    var token = r;
                    var creds = new AWS.Credentials('','');
                    var config = new AWS.Config({
                        region: "us-east-1",
                        endpoint: "https://participant.connect.us-east-1.amazonaws.com",
                        credentials: creds
                     });
                    var participant = new AWS.ConnectParticipant(config);
                    var params = {"MaxResults":100,"ScanDirection":"BACKWARD","SortOrder":"ASCENDING","StartPosition":{}}
                    params['ContactId'] = contact.contactId;
                    params['ConnectionToken'] = token.chatTokenTransport.participantToken;
                    participant.getTranscript(params, function(err, data) {
                        if (err) {
                            console.log("Transcript Error")
                            console.log(err, err.stack); // an error occurred
                        } else {
                            console.log("Transcript Success")
                            console.log(data);           // successful response
                        }
                    });
    });
}

The problem I have is that participant.getTranscript fails when called during ACW with the response being Access Denied. In my testing, this call only succeeds when registered on the contact.onRefresh event while the chat is actively in progress.

In speaking with Amazon technical support, they suggested raising the question here. Please let me know your ideas.

Thanks!

Typescript Property 'getMediaController' does not exist

I am trying to create AgentChat panel in react TS. I am importing the streamsJS then chatJS, but still 'getMediaController' throws error.

import "amazon-connect-streams";
import "amazon-connect-chatjs";
import { CCP_URL } from "../Constants";
export default class ContactCenter {
  constructor() {
    try {
      var containerDiv = document.getElementById("ccpContainer");
      this.connect.core.initCCP(containerDiv as HTMLElement, {
        ccpUrl: CCP_URL,
        loginPopup: true,
        loginPopupAutoClose: true,
        loginOptions: {
          autoClose: true,
        },
        softphone: {
          allowFramedSoftphone: true,
          disableRingtone: false, 
        },
      });      
      connect.contact(function (contact) {
        const c = contact;
        if (contact.getType() !== connect.ContactType.CHAT) {
          return;
        }
        c.onConnecting(function (c) {
          console.log("incoming");
          c.accept();
        });
        c.onAccepted(async () => {
          const cnn = contact.getConnections().find(cnn => cnn.getType() === connect.ConnectionType.AGENT);
          if(cnn){
          const agentChatSession = await cnn.getMediaController();
          console.log(cnn)
          }
        });
      });
      return this.connect;
    } catch (error) {
      console.log(error);
      return;
    }
  }
}

Receiving bellow error.

Property 'getMediaController' does not exist on type 'BaseConnection'.  TS2339

    33 |           const cnn = contact.getConnections().find(cnn => cnn.getType() === connect.ConnectionType.AGENT);
    34 |           if(cnn){
  > 35 |           const agentChatSession = await cnn.getMediaController();
       |                                              ^
    36 |           console.log(agentChatSession)
    37 |           }
    38 |         });

Please help me out, what am I missing.

chatSession.getTranscript fails when scanDirection === "FORWARD"

According to chatSession.getTranscript's documentation, this is a valid request:

await chatSession.getTranscript({
  scanDirection: "FORWARD"
});

However, I get the following error:

{
  type: "ValidationException",
  message: "Validation exception due to invalid parameter",
  stack: /* ... */,
  metadata: null
}

When I check the network request, here's what I see (headers omitted for simplification):

Request:

POST /participant/transcript HTTP/1.1
host: participant.connect.us-east-1.amazonaws.com
content-type: application/json

{
  "MaxResults": 15,
  "ScanDirection": "FORWARD",
  "SortOrder": "ASCENDING",
  "StartPosition": {}
}

Response:

HTTP/1.1 400
content-type: application/json

{
  "message": "Validation exception due to invalid parameter"
}

Environment:

  • amazon-connect-chatjs: 1.0.6

`getTranscript()` not working for other contactIds

Previously it was possible to fetch the transcript for a different contactId than the currently connected contact. However now when I call getTranscript() for a valid contactId (with full transcript available through the Amazon Connect dashboard) i get a response like:

{
    "InitialContactId": "...",
    "NextToken": "",
    "Transcript": []
}

Is it no longer possible to get the transcript for a different contactId?

How to see whether chat attachments are enabled

Hey!

How may I check whether the chat attachments functionality is enabled or not in the connect instance?

I am building a custom CCP and I need to conditionally show the attachment icon button.

Thanks!

Access denied on chat events APIs.

When an agent accepts a chat that has been declined/missed by the them. The chat APIs are returning an error with status code 403 (Access Denied). But the same API calls are working fine if the agent accepts the chat the first time it is presented. This is how I am making the calls.
image
image
image
image

I have been struggling with it for sometime now. Any and all help is much appreciated.

Note : I have made sure to follow Chatjs to the dot. And the looked into the possible causes of 403 and none of that is happening in my case.

Agent chat session ended abruptly if agent reloads the page and streams takes more than 20 seconds to reconnect

Following is the scenario
An agent is handling a chat and he refreshes the page.
Sometimes amazon connect streams takes more than 20 seconds to re-establish the connection, (usually the authorize API response is delayed).
The agent disconnected from the chat session.

Expected behavior:
The agent chat session should remain intact for a longer period.
Is there any configuration where this agent reconnect time can be configured to more than 20 seconds?

AWS Connect Drop In Widget?

Thanks for this repo. As this is a paid product is there a ready to go example of this widget that can be dropped straight into a page without any work/hosting. This seem to be described in this page https://aws.amazon.com/blogs/aws/new-omnichannel-contact-center-web-and-mobile-chat-for-amazon-connect/ and illustrated in this this image:
image
image

Would like to have this to make this process easier as its seems to be a non-starter in current state.

Here is an example of this from Salesforce, and even this can be way optimized but it better than nothing:

var sfscript = document.createElement('script');
sfscript.setAttribute('src','https://service.force.com/embeddedservice/5.0/esw.min.js');
document.head.appendChild(sfscript);
sfscript.onload = function() {
var initESW = function(gslbBaseURL) {
embedded_svc.settings.displayHelpButton = true;
embedded_svc.settings.language = '';
embedded_svc.settings.enabledFeatures = ['LiveAgent'];
embedded_svc.settings.entryFeature = 'LiveAgent';
embedded_svc.init(
'https://XXXX.my.salesforce.com',
'https://XXX.force.com/liveAgentSetupFlow',
gslbBaseURL,
'XXXX',
'XXXX',
{
baseLiveAgentContentURL: 'https://XXX.salesforceliveagent.com/content',
deploymentId: 'XXX',
buttonId: 'XXX',
baseLiveAgentURL: 'https://XXX.salesforceliveagent.com/chat',
eswLiveAgentDevName: 'XXXX',
isOfflineSupportEnabled: false
}
);
};
if (!window.embedded_svc) {
var s = document.createElement('script');
s.setAttribute('src', 'https://XXXX.my.salesforce.com/embeddedservice/5.0/esw.min.js');
s.onload = function() {
initESW(null);
};
document.body.appendChild(s);
} else {
initESW('https://service.force.com');
}
};

Good example would be how google analytics code works and instructions around it are straight forward, here is a link for this https://developers.google.com/analytics/devguides/collection/analyticsjs

agentChatSession.onEnded() does not trigger on contact transfers

I have a need for allowing agents to access their chat transcripts immediately after finishing a chat session. Given that streaming CTRs is not a suitable solution for our use case due to its asynchronous nature, I have developed a solution around this by using a combination of chatSession.getTranscript() and chatSession.onMessage(). A simplification of the solution looks like this:

connect.contact(contact => {
  if (contact.getType() === connect.ContactType.CHAT) {
    contact.onAccepted(async () => {
      const session = await contact
        .getConnections()
        .find((c) => c.getType() === connect.ConnectionType.AGENT)
        .getMediaController();

      const transcript = new TranscriptCustomClass();

      // uses session.getTranscript()
      transcript.populatePreviousMessages(session);

      // dedupes message if present in session.getTranscript(), sorts messages
      session.onMessage(event => transcript.appendMessage(event));

      // waits for transcript.populatePreviousMessages() to complete
      session.onEnded(event => transcript.complete(event));
    });
  }
});

So far, I've used the chatSession.onEnded() event to detect when the chat transcript can be considered "complete" and be made available for consumption. However, I've realized that this method is not called when the agent uses /connect/ccp-v2's "quick connect" feature to perform a transfer.

For the purposes explaining the problem, I'm going to refer to the transferred contact (i.e. initial contact) chat session as initialSession and the current contact (i.e. transferred contact) as currentSession.

Currently, the following behaviors occur:

  1. When the contact is transferred and accepted by another agent, initialSession.onEnded() event is never triggered (I wonder whether this translates to a memory leak).
  2. When the contact is transferred and accepted by the initial agent (e.g. the queue has only one available agent), a new contact is created (as expected), however:
    1. When events/messages are sent to the current contact, both initialSession.onMessage() and currentSession.onMessage() trigger. I believe only currentSession.onMessage() should trigger.
    2. When the current contact session finishes, both initialSession.onEnded() and currentSession.onEnded() trigger. I believe initialSession.onEnded() should trigger when the transfer completes, and only currentSession.onEnded() should trigger.

I've worked around this problem by examining the received messages and stop capturing in initialSession when I receive the following sequence of events I've noticed always occur before a transfer:

// event 1
{
  ContentType: "application/vnd.amazonaws.connect.event.transfer.succeeded"
}

// event 2
{
  ContentType: "application/vnd.amazonaws.connect.event.participant.left",
  ParticipantRole: "AGENT",
}

Note: I've also noticed these events/messages can sometimes come out-of-order to .onMessage(), so I have to sort them first by their AbsoluteTime before performing the detection.


Questions:

  1. Is this by design?
  2. If so, is the workaround guaranteed to always be true (i.e. the sequence of events before a transfer will not change)?

ChatSession.onConnectionEstablished() is fired twice after invoking ChatSession.connect()

Steps to reproduce:

import "amazon-connect-chatjs"; // v1.3.1
import {
  ConnectClient,
  StartChatContactCommand,
} from "@aws-sdk/client-connect"; // v3.254.0

// start a chat contact
const client = new ConnectClient({
  region: "us-east-1",
  credentials: {
    accessKeyId: "...",
    secretAccessKey: "...",
    sessionToken: "...",
  },
});

const { ContactId, ParticipantId, ParticipantToken } = await client.send(
  new StartChatContactCommand({
    InstanceId: "...",
    ContactFlowId: "...",
    ParticipantDetails: { DisplayName: "Customer" },
  })
);

// create chat session
const session = connect.ChatSession.create({
  chatDetails: {
    contactId: ContactId,
    participantId: ParticipantId,
    participantToken: ParticipantToken,
  },
  options: { region: "us-east-1" },
  type: connect.ChatSession.SessionTypes.CUSTOMER,
});

// subscribe to `onConnectionEstablished` events
session.onConnectionEstablished((event) => {
  console.log("Connection established:", event);
});

// connect to chat
await session.connect();

Expected result:

The onConnectionEstablished listener gets invoked once:

Connection established: {
  chatDetails: { ... },
  data: {}
}

Actual result:

The onConnectionEstablished listener gets invoked twice with different event payloads:

Connection established: {
  chatDetails: { ... },
  connectCalled: true,
  connectSuccess: true,
  metadata: null,
  _debug: { webSocketStatus: "Starting" }
}

Connection established: {
  chatDetails: { ... },
  data: {}
}

Analysis:

This occurs because ChatController actually dispatches the event twice from two different places:

  1. As part of the ChatController.prototype.connect() call:

connect(args={}) {
this.sessionMetadata = args.metadata || null;
this.argsValidator.validateConnectChat(args);
const connectionDetailsProvider = this._getConnectionDetailsProvider();
return connectionDetailsProvider.fetchConnectionDetails()
.then(
(connectionDetails) =>
this._initConnectionHelper(connectionDetailsProvider, connectionDetails)
)
.then(response => this._onConnectSuccess(response, connectionDetailsProvider))

_onConnectSuccess(response, connectionDetailsProvider) {
this._sendInternalLogToServer(this.logger.info("Connect successful!"));
console.warn("onConnectionSuccess response", response);
const responseObject = {
_debug: response,
connectSuccess: true,
connectCalled: true,
metadata: this.sessionMetadata
};
const eventData = Object.assign({
chatDetails: this.getChatDetails()
}, responseObject);
this.pubsub.triggerAsync(CHAT_EVENTS.CONNECTION_ESTABLISHED, eventData);

  1. Via an event listener of LpcConnectionHelper.prototype.handleConnectionGain():

connect(args={}) {
this.sessionMetadata = args.metadata || null;
this.argsValidator.validateConnectChat(args);
const connectionDetailsProvider = this._getConnectionDetailsProvider();
return connectionDetailsProvider.fetchConnectionDetails()
.then(
(connectionDetails) =>
this._initConnectionHelper(connectionDetailsProvider, connectionDetails)

_initConnectionHelper(connectionDetailsProvider, connectionDetails) {
this.connectionHelper = new LpcConnectionHelper(
this.contactId,
this.initialContactId,
connectionDetailsProvider,
this.websocketManager,
this.logMetaData,
connectionDetails
);
this.connectionHelper.onEnded(this._handleEndedConnection.bind(this));
this.connectionHelper.onConnectionLost(this._handleLostConnection.bind(this));
this.connectionHelper.onConnectionGain(this._handleGainedConnection.bind(this));

_handleGainedConnection(eventData) {
this._forwardChatEvent(CHAT_EVENTS.CONNECTION_ESTABLISHED, {
data: eventData,
chatDetails: this.getChatDetails()
});
}

_forwardChatEvent(eventName, eventData) {
this.pubsub.triggerAsync(eventName, eventData);
}

Having duplicate event invocations can be problematic when the user assumes it will be invoked only once and the handler implementation is not idempotent (i.e. invoking it again may have undesired side-effects).

Proposed fix:

The event dispatch that is part of ChatController.prototype.connect() should be removed, so that only the LpcConnectionHelper.prototype.handleConnectionGain() remains. This is because the latter event will be triggered whenever the connection is lost and later regained.

Stock messages able to be displayed in other languages

Is it possible to configure the stock messages? We would like to display them in the language appropriate to the chat. The two we're primarily concerned with are

  • "The chat has disconnected"
  • "The agent has disconnected"

Error connect is not defined In react js

Hi There,

I am implementing a custom chat widget with amazon-connect-chatjs by using reactjs (create-react-app).

I have installed
npm install amazon-connect-chatjs
and including in my component
import "amazon-connect-chatjs"
but getting error connect is not defined.
please check screenshot
20200805_001010

Getting an Error while trying to get connection for chat

Hi,

While trying to get connection (contact.getAgentConnection()) to create agent chat session in OnConnected event, getting following error in connect-stream.js -

Error - "AWS.ConnectParticipant is not a constructor"

I am importing the libs in following order:

<script src="https://sdk.amazonaws.com/js/aws-sdk-2.824.0.min.js"></script> <script type="text/javascript" src="js/connect-streams.js"></script> <script type="text/javascript" src="js/amazon-connect-chat.js"></script>

can you please help?

Docs to built custom UI for agent chat?

My use case is I want to create custom chat UI for the agent and end user. I have working end user chat deployed using cloud formation template.
Now, I want to create a custom chat for the agent on my webpage.
I am referring to the docs and not able to figure out where would following data come from.

args = {
  "chatDetails": {
    "ContactId": "string", // I can get this from streams using connection object
    "ParticipantId": "string", // Where do I get this?
    "ParticipantToken": "string" // Where do I get this?
  },
  "type": connect.ChatSession.SessionTypes.AGENT,
  "options": {
    region: "string"
  },
  "websocketManager": WebSocketManager // Where do I get this?
};

var chatSession = connect.ChatSession.create(args);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.