Giter VIP home page Giter VIP logo

amazon-connect-chatjs's Introduction

Amazon Connect ChatJS npm License Node.js CI

Important note: Amazon Connect has migrated the Connection Acknowledgement(ConnAck) from the SendEvent API to the CreateParticipant API. Please upgrade your ChatJS to 1.4.0 or a newer version to complete the migration by 12/31/2024.

Table of contents

About

The Amazon Connect Chat javascript library (ChatJS) gives you the power to build your own chat widget to customize the chat experience. This can be used for both the agent user interface, in conjunction with Amazon Connect Streams, and for the customer chat interface.

There is a Chat UI reference implementation here. This will help you deploy an API Gateway and Lambda function for initiating chat from your webpage. From there you can use the ChatJS library to build a custom widget.

Learn More

To learn more about Amazon Connect and its capabilities, please check out the Amazon Connect User Guide.

New to Amazon Connect and looking to onboard with Chat/Messaging capabilities? Refer to the “Amazon Connect Chat Open Source Walkthrough” documentation, and “Hosted Widget vs Custom Builder Solution” if building a customer-facing chat interface.

Getting Started

A note about the AWS-SDK and ChatJS

The AWS-SDK is, by default, included in ChatJS as a "baked-in" dependency. You can view it at ./client/aws-sdk-connectparticipant.js. In ./client/client.js we import ConnectParticipant from this file. This file and import can be removed while using the AWS SDK imported through a script in the page file of your application, assuming that version of the AWS SDK has the ConnectParticipant service included. Incidentally, Amazon Connect Streams also contains a "baked-in" AWS SDK. This SDK cannot be removed, as it contains unreleased APIs that will not be available in the SDK you include as a script in the page file. Therefore, there are several occasions where implementations can run into AWS SDK issues.

Scenario 1: Streams and ChatJS are used. You are not importing the AWS SDK

Ensure you import ChatJS after Streams.

Scenario 2: Streams and ChatJS are used. You are importing the AWS SDK

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

Scenario 3: ChatJS only, no AWS SDK import

No need to worry here, this will always work.

Scenario 4: ChatJS only, with AWS SDK import

Import ChatJS before the AWS SDK, and ensure the AWS SDK version you are using contains the ConnectParticipant Service.

A note for Scenarios 2 and 4

When using the SDK and ChatJS, you may remove 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.

Using AWS SDK ConnectParticipant Client

If you have replaced ./client/aws-sdk-connectparticipant.js and use @aws-sdk/client-connectparticipant, make sure to import the aws-sdk after ChatJS

import 'amazon-connect-streams'; // <-- (optional) MUST be before ChatJS
import 'amazon-connect-chatjs';
import '@aws-sdk/client-connect'; // or 'aws-sdk'
import '@aws-sdk/clients/connectparticipant'; // <-- IMPORTANT - should be last

Usage

Using ChatJS from npm

npm install amazon-connect-chatjs

Using ChatJS from CDN Link

amazon-connect-chat.js bundle file is also available over a CDN.

<script src="https://unpkg.com/[email protected]"></script>
<!-- OR -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/amazon-connect-chat.js"></script>

<!-- Specify exact version -->
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://unpkg.com/amazon-connect-chatjs@1"></script>
<script src="https://unpkg.com/amazon-connect-chatjs"></script>

<!-- Use crossorigin if needed -->
<script crossorigin src="https://unpkg.com/amazon-connect-chatjs"></script>

Importing using npm and ES6

import "amazon-connect-chatjs" Note: this will apply the global connect variable to your current scope.

TypeScript Support

amazon-connect-chatjs is compatible with TypeScript. You'll need to use version typescript@^3.0.1 or higher:

import "amazon-connect-streams";

connect.ChatSession.create({ /* ... */ });

Using ChatJS from Github

git clone https://github.com/amazon-connect/amazon-connect-chatjs

Building

  1. Install latest LTS version of NodeJS
  2. Checkout this package into workspace and navigate to root folder
  3. npm install
  4. To build (non-minified):
    1. npm run devo for a non-minified build.
    2. Find build artifacts in dist directory.
  5. To build (minified):
    1. npm run release for a minified build.
    2. Find build artifacts in dist directory.
  6. To run unit tests:
    1. npm run test
  7. To clean node_modules:
    1. npm run clean
  8. To make webpack watch all files:
    1. npm run watch

Find build artifacts in dist directory - This will generate a file called amazon-connect-chat.js - this is the full Connect ChatJS API which you will want to include in your page.

React Native Support

Additional configuration is required to support ChatJS in React Native applications. Use amazon-connect-chatjs@^1.5.0 and follow the documenation: ReactNativeSupport.md

A demo application implementing basic ChatJS functionality is also available in the ui-examples repository: connectReactNativeChat

API

connect.ChatSession API

This is the main entry point to amazon-connect-chatjs. All your interactions with the library start here.

connect.ChatSession.setGlobalConfig()

connect.ChatSession.setGlobalConfig({
  loggerConfig: { // optional, the logging configuration. If omitted, no logging occurs
    // You can provide your own logger here, otherwise this property is optional
    customizedLogger: {
      debug: (...msg) => console.debug(...msg), // REQUIRED, can be any function
      info: (...msg) => console.info(...msg), // REQUIRED, can be any function
      warn: (...msg) => console.warn(...msg), // REQUIRED, can be any function
      error: (...msg) => console.error(...msg) // REQUIRED, can be any function
    },
    // There are five levels available - DEBUG, INFO, WARN, ERROR, ADVANCED_LOG. Default is INFO
    level: connect.LogLevel.INFO,
    // Choose if you want to use the default logger
    useDefaultLogger: true
  },
  region: "us-east-1", // optional, defaults to: "us-west-2"
  //Control switch for enabling/disabling message-receipts (Read/Delivered) for messages
  //message receipts use sendEvent API for sending Read/Delivered events https://docs.aws.amazon.com/connect-participant/latest/APIReference/API_SendEvent.html
  features: {
    messageReceipts: {
      shouldSendMessageReceipts: true, // DEFAULT: true, set to false to disable Read/Delivered receipts
      throttleTime: 5000 //default throttle time - time to wait before sending Read/Delivered receipt.
    }
  }
});

Set the global configuration to use. If this method is not called, the defaults of loggerConfig and region are used. This method should be called before connect.ChatSession.create().

Customizing loggerConfig for ChatJS:

  • If you don't want to use any logger, you can skip this field.
  • There are five log levels available - DEBUG, INFO, WARN, ERROR, ADVANCED_LOG.
  • If you want to use your own logger, you can add them into customizedLogger, and add customizedLogger object as the value of loggerConfig.customizedLogger, then set the lowest logger level. globalConfig.loggerConfig.useDefaultLogger is not required.
  • If you want to use the default logger provided by ChatJS, you can set the logger level, and set useDefaultLogger to true. loggerConfig.customizedLogger is not required.
  • If you not only provide your own logger, but also set useDefaultLogger to true, your own logger will be overwritten by the default logger.
  • amazon-connect-chatjs/src/log.js - has the logic to select LogLevel. Default value is INFO - which cause all logs with higher priority than INFO to be logged. eg: by default info, warn, error and advancedLog messages will be logged.
  • Priority of logs: 10: "DEBUG" 20: "INFO" 30: "WARN" 40: "ERROR" 50: "ADVANCED_LOG"

connect.ChatSession.create()

const customerChatSession = connect.ChatSession.create({
  chatDetails: { // REQUIRED
    contactId: "...", // REQUIRED
    participantId: "...", // REQUIRED
    participantToken: "...", // REQUIRED
  },
  options: { // optional
    region: "us-east-1", // optional, defaults to `region` set in `connect.ChatSession.setGlobalConfig()`
  },
  type: "CUSTOMER", // REQUIRED
});

Creates an instance of AgentChatSession or CustomerChatSession, depending on the specified type.

If you're creating a CustomerChatSession, the chatDetails field should be populated with the response of the StartChatContact API.

If you're creating an AgentChatSession, you must also include amazon-connect-streams. For example:

// order is important, alternatively use <script> tags
import "amazon-connect-streams";
import "amazon-connect-chatjs";

connect.contact(contact => {
  if (contact.getType() !== connect.ContactType.CHAT) {
    // applies only to CHAT contacts
    return;
  }

  // recommended: calls `connect.ChatSession.setGlobalConfig()` and `connect.ChatSession.create()` internally
  contact.onAccepted(async () => {
    const cnn = contact.getConnections().find(cnn => cnn.getType() === connect.ConnectionType.AGENT);

    const agentChatSession = await cnn.getMediaController();
  });

  // alternative: if you want control over the args of `connect.ChatSession.setGlobalConfig()` and `connect.ChatSession.create()`
  contact.onAccepted(() => {
    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
    });
  });
});

See the amazon-connect-streams API documentation for more information on the methods not documented here.

Note: AgentChatSession and CustomerChatSession are logical concepts. As a result, the instanceof operator will not work how you expect:

if (connect.ChatSession.create(/* ... */) instanceof connect.ChatSession) {
  // this will never execute
}

connect.ChatSession.LogLevel

connect.ChatSession.LogLevel = {
  DEBUG: /* ... */,
  INFO: /* ... */,
  WARN: /* ... */,
  ERROR: /* ... */
};

Enumerates the logging levels.

connect.ChatSession.SessionTypes

connect.ChatSession.SessionTypes = {
  AGENT: /* ... */,
  CUSTOMER: /* ... */
};

Enumerates the session types.

ChatSession API

The ChatSession API divided into three sections: Amazon Connect Participant Service API wrappers, events, and other.

Amazon Connect Participant Service API wrappers

Functions in this section:

  • Wrap the APIs of the Amazon Connect Participant Service.
  • Return a Promise<Response> (except for chatSession.connect()), where:
    • Response is an aws-sdk Response object.
    • If the Promise rejects, the error will still be a Response object. However, the data field will not be populated while the error field will.
  • Can optionally specify a metadata arg field (except for customerChatSession.disconnectParticipant()). The metadata arg field is not used directly by amazon-connect-chatjs, rather it's merely copied to the response object for usage by developers.

For example:

function handleResponse(response) {
  // `response` is an aws-sdk `Response` object
  // `data` contains the response data
  // `metadata` === "foo"
  const { data, metadata } = response;
  // ...
}

function handleError(response) {
  // `response` is an aws-sdk `Response` object
  // `error` contains the response error
  // `metadata` === "foo"
  const { error, metadata } = response;
  // ...
}

chatSession
  .getTranscript({ metadata: "foo" })
  .then(handleResponse, handleError);
chatSession.connect()
// connectCalled: indicates whether the Amazon Connect Participant Service was called
// connectSuccess: indicates whether the operation succeeded
const { connectCalled, connectSuccess } = await chatSession.connect();

Wraps the CreateParticipantConnection API.

The arguments and response do not overlap with the API request or response.

Note: If the operation fails, the Promise will reject, but the error will have the same schema as a successful response.

chatSession.getTranscript()
const awsSdkResponse = await chatSession.getTranscript({
  maxResults: 100,
  sortOrder: "ASCENDING"
});
const { InitialContactId, NextToken, Transcript } = awsSdkResponse.data;

Wraps the GetTranscript API.

The arguments are based on the API request body with the following differences:

  • Fields are in camelCase.
  • MaxResults defaults to 15.
  • ScanDirection defaults to BACKWARD always.
  • SortOrder defaults to ASCENDING.

The response data is the same as the API response body.

Important note: In order to specify scanDirection as FORWARD, you need to explicitly include a startPosition. This is because the default startPosition is at the most recent update to the transcript, so requesting a transcript in the FORWARD direction from the default startPosition is equivalent to asking for a transcript containing only messages more recent than the present (you are asking for messages in the future!).

chatSession.sendEvent()
const awsSdkResponse = await chatSession.sendEvent({
  contentType: "application/vnd.amazonaws.connect.event.typing"
});
const { AbsoluteTime, Id } = awsSdkResponse.data;

Wraps the SendEvent API.

The arguments are based on the API request body with the following differences:

  • Fields are in camelCase.
  • ClientToken cannot be specified.
  • ContentType allows the following values:
    • "application/vnd.amazonaws.connect.event.typing"
    • "application/vnd.amazonaws.connect.event.connection.acknowledged"
    • "application/vnd.amazonaws.connect.event.message.delivered"
    • "application/vnd.amazonaws.connect.event.message.read"

The response data is the same as the API response body.

chatSession.sendMessage()
const awsSdkResponse = await chatSession.sendMessage({
  contentType: "text/plain",
  message: "Hello World!"
});
const { AbsoluteTime, Id } = awsSdkResponse.data;

Wraps the SendMessage API.

The arguments are based on the API request body with the following differences:

  • Fields are in camelCase.
  • ClientToken cannot be specified.

The response data is the same as the API response body.

chatSession.sendAttachment()
/**
 * Attachment Object - the actual file to be sent between the agent and end-customer.
 * Documentation: https://developer.mozilla.org/en-US/docs/Web/API/File
 * @property {number} lastModified - The last modified timestamp of the file.
 * @property {string} name - The name of the file.
 * @property {number} size - The size of the file.
 * @property {string} type - The type of the file.
 * @property {string} webkitRelativePath - The relative path of the file specific to the WebKit engine.
 */
const awsSdkResponse = await chatSession.sendAttachment({
  attachment: attachment
});

// Example usage
var input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', (e) => {
  const file = e.target.files[0];
  chatSession.sendAttachment({ attachment: file })
});

Wraps the StartAttachmentUpload and CompleteAttachmentUpload API. The arguments are based on the StartAttachmentUpload and CompleteAttachmentUpload API request body with the following differences:

  • Fields are in camelCase. The response data is the same as the StartAttachmentUpload and CompleteAttachmentUpload API response body. chatSession.sendAttachment() invokes the StartAttachmentUpload API, uploads the Attachment to the S3 bucket using the pre-signed URL received in the StartAttachmentUpload API response and invokes the CompleteAttachmentUpload API to finish the Attachment upload process.
chatSession.downloadAttachment()
const awsSdkResponse = await chatSession.downloadAttachment({
  attachmentId: "string"
});
const { attachment } = awsSdkResponse.data;
/* 
Attachment Object - This is the actual file that will be downloaded by either agent or end-customer.
attachment => {
  lastModified: long
  name: "string"
  size: long
  type: "string"
  webkitRelativePath: "string"
}
*/

Wraps the GetAttachment API. The arguments are based on the API request body with the following differences:

  • Fields are in camelCase. The response data is the same as the API response body. chatSession.downloadAttachment() invokes the GetAttachment using the AttachmentId as a request parameter and fetches the Attachment from the S3 bucket using the pre-signed URL received in the GetAttachment API response.
customerChatSession.disconnectParticipant()
const awsSdkResponse = await customerChatSession.disconnectParticipant();

Wraps the DisconnectParticipant API.

The arguments and response do not overlap with the API request or response.

Once this method is called, the CustomerChatSession cannot be used anymore.

Applies only for CustomerChatSession. See connect.ChatSession.create() for more info.

Events

Function in this section:

  • When invoked, register an event handler that is triggered whenever the event occurs.
  • Can be called multiple times (i.e. register multiple event handlers).
  • Receive an event object that contains a chatDetails field. See chatSession.getChatDetails() for more info.
chatSession.onConnectionBroken()
chatSession.onConnectionBroken(event => {
  const { chatDetails } = event;
  // ...
});

Subscribes an event handler that triggers when the session connection is broken.

chatSession.onConnectionEstablished()
chatSession.onConnectionEstablished(event => {
  const { chatDetails } = event;
  // ...
});

Subscribes an event handler that triggers when the session connection is established.

chatSession.onEnded()
chatSession.onEnded(event => {
  const { chatDetails, data } = event;
  // ...
});

Subscribes an event handler that triggers when the session is ended.

chatSession.onMessage()
chatSession.onMessage(event => {
  const { chatDetails, data } = event;
  switch (data.ContentType) {
    // ...
  }
});

Subscribes an event handler that triggers whenever a message or an event (except for application/vnd.amazonaws.connect.event.typing) is created by any participant. The data field has the same schema as the Item data type from the Amazon Connect Participant Service with the addition of the following optional fields: ContactId, InitialContactId.

Warning The messages received over websocket are not guranteed to be in order!

Here is the code reference on how messages should be handled in the FrontEnd Amazon-Connect-Chat-Interface (This code handles removing messages with missing messageIds, duplicate messageIds and, sorting messages to handle display order of messages):

this.ChatJSClient.onMessage((data) => {
  //deserialize message based on what UI component understands
  const message = createTranscriptItem(
        PARTICIPANT_MESSAGE,
      {
        data: data.text,
        type: data.type || ContentType.MESSAGE_CONTENT_TYPE.TEXT_PLAIN,
      },
      this.thisParticipant
  );

  this._addItemsToTranscript([message]);
});

const _addItemsToTranscript = (items) => {
  //filter and ignore messages not required for display
  items = items.filter((item) => !_isSystemEvent(item));

  //remove duplicate messageIds
  const newItemMap = items.reduce((acc, item) => 
                      ({ ...acc, [item.id]: item }), {});
  //remove messages missing messageIds
  const newTranscript = this.transcript.filter((item) =>
                         newItemMap[item.id] === undefined);

  newTranscript.push(...items);
  newTranscript.sort((a, b) => {
    const isASending = a.transportDetails.status === Status.Sending;
    const isBSending = b.transportDetails.status === Status.Sending;
    if ((isASending && !isBSending) || (!isASending && isBSending)) {
      return isASending ? 1 : -1;
    }
    return a.transportDetails.sentTime - b.transportDetails.sentTime;
  });

 this._updateTranscript(newTranscript);
}

const _isSystemEvent = (item) {
  return Object.values(EVENT_CONTENT_TYPE).indexOf(item.contentType) !== -1;
}

const EVENT_CONTENT_TYPE: {
    TYPING: "application/vnd.amazonaws.connect.event.typing",
    READ_RECEIPT: "application/vnd.amazonaws.connect.event.message.read",
    DELIVERED_RECEIPT: "application/vnd.amazonaws.connect.event.message.delivered",
    PARTICIPANT_JOINED: "application/vnd.amazonaws.connect.event.participant.joined",
    PARTICIPANT_LEFT: "application/vnd.amazonaws.connect.event.participant.left",
    TRANSFER_SUCCEEDED: "application/vnd.amazonaws.connect.event.transfer.succeed",
    TRANSFER_FAILED: "application/vnd.amazonaws.connect.event.transfer.failed",
    CONNECTION_ACKNOWLEDGED: "application/vnd.amazonaws.connect.event.connection.acknowledged",
    CHAT_ENDED: "application/vnd.amazonaws.connect.event.chat.ended"
}


//send data to update Store or UI component waiting for next chat-message
_updateTranscript(transcript) {
  this._triggerEvent("transcript-changed", transcript);
}
chatSession.onTyping()
chatSession.onTyping(event => {
  const { chatDetails, data } = event;
  if (data.ParticipantRole === "AGENT") {
    // ...
  }
});

Subscribes an event handler that triggers whenever a application/vnd.amazonaws.connect.event.typing event is created by any participant. The data field has the same schema as chatSession.onMessage().

chatSession.onParticipantIdle()
/**
 * Subscribes an event handler that triggers whenever a "application/vnd.amazonaws.connect.event.participant.idle" event is created by any participant. 
 * @param {
    AbsoluteTime?: string,
    ContentType?: string,
    Type?: string,
    ParticipantId?: string,
    DisplayName?: string,
    ParticipantRole?: string,
    InitialContactId?: string
 } event.data
 */
chatSession.onParticipantIdle(event => {
  const { chatDetails, data } = event;
  if (data.ParticipantRole === "AGENT") {
    // ...
  }
});
chatSession.onParticipantReturned()
/**
 * Subscribes an event handler that triggers whenever a "application/vnd.amazonaws.connect.event.participant.returned" event is created by any participant. 
 * @param {
    AbsoluteTime?: string,
    ContentType?: string,
    Type?: string,
    ParticipantId?: string,
    DisplayName?: string,
    ParticipantRole?: string,
    InitialContactId?: string
 } event.data
 */
chatSession.onParticipantReturned(event => {
  const { chatDetails, data } = event;
  if (data.ParticipantRole === "AGENT") {
    // ...
  }
});
chatSession.onAutoDisconnection()
/**
 * Subscribes an event handler that triggers whenever a "application/vnd.amazonaws.connect.event.participant.autodisconnection" event is created by any participant. 
 * @param {
    AbsoluteTime?: string,
    ContentType?: string,
    Type?: string,
    ParticipantId?: string,
    DisplayName?: string,
    ParticipantRole?: string,
    InitialContactId?: string
 } event.data
 */
chatSession.onAutoDisconnection(event => {
  const { chatDetails, data } = event;
  if (data.ParticipantRole === "AGENT") {
    // ...
  }
});

onParticipantIdle, onParticipantReturned, and onAutoDisconnection are related to set up chat timeouts for chat participants.

chatSession.onConnectionLost()
chatSession.onConnectionLost(event => {
  const { chatDetails, data } = event;
  // ...
});

Subscribes an event handler that triggers when the session is lost.

chatSession.onDeepHeartbeatFailure()
chatSession.onDeepHeartbeatFailure(event => {
  const { chatDetails, data } = event;
  // ...
});

Subscribes an event handler that triggers when deep heartbeat fails.

Client side metric

In version 1.2.0 the client side metric(CSM) service is added into this library. Client side metric can provide insights into the real performance and usability, it helps us to understand how customers are actually using the website and what UI experiences they prefer. This feature is enabled by default. User can also disable this feature by passing a flag: disableCSM when they create a new chat session:

const customerChatSession = connect.ChatSession.create({
  ...,
  disableCSM: true
});

Other

This section contains all the functions that do not fall under the previous two categories.

chatSession.getChatDetails()
const {
  contactId,
  initialContactId,
  participantId,
  participantToken,
} = chatSession.getChatDetails();

Gets the chat session details.

chatSession.describeView()
const {
  View
} = chatSession.describeView({
  viewToken: "QVFJREFIaGIyaHZJWUZzNlVmMGVIY2NXdUVMMzdBTnprOGZkc3huRzhkSXR6eExOeXdGYTFwYitiOGRybklmMEZHbjBGZU1sQUFBQWJqQnNCZ2txaGtpRzl3MEJCd2FnWHpCZEFnRUFNRmdHQ1NxR1NJYjNEUUVIQVRBZUJnbGdoa2dCWlFNRUFTNHdFUVFNKys3ei9KTU41dG1BMWF4UkFnRVFnQ3NLckhKTEdXMUsyR1kvVHBUWWp0SmZxSG83VlcvOTg5WGZvckpMWDhOcGVJVHcrWUtnWjVwN3NxNGk6OlJ6STltam5rZjBiNENhOVdzM0wwaWxjR1dWUUxnb1Y1dmxNaEE5aGRkemZuV09hY0JEZFlpWFhpWnRPQlowNW9HT28xb0VnZ3JWV21aeWN0anhZZi9lOUdrYklSZVR5N2tpQmRRelFXSGpXZHpFSUExRCtvcWl5VGMzMzJoaWRldU5IaWwreEkvNmNmWUtpMXd5Qnh1aG0yY1AyWmk2byt2bTRDalFhWGxaM0Zrb1dqLy91aDVvRmtZcDY4UERuU0ZVQ1AyUU0zcjhNazI1ckZ2M0p6Z210bnMrSzVYY2VPN0xqWE1JMHZ0RE5uVEVYR1ZDcnc3SE82R0JITjV4NWporWGM9\\\", //REQUIRED
  metadata: "foo" //OPTIONAL
}).data;

Wraps the DescribeView API.

The arguments are based on the API model with the following differences:

  • All fields are in camelCase.

ChatJS automatically supplies the connectionToken via the session's internal data. This api will only function after chatSession.connect() succeeds.

agentChatSession.cleanUpOnParticipantDisconnect()
agentChatSession.cleanUpOnParticipantDisconnect();

Cleans up all event handlers.

Applies only for AgentChatSession. See connect.ChatSession.create() for more info.

Handle Browser Refresh

Reconnect to an active chat after refreshing the browser. Call the CreateParticipantConnection API on refresh with the same ParticipantToken generated from the initial ShatChatContact request.

Reference

  • StartChatContact API: initiate the chat contact [Documentation]
  • CreateParticipantConnection API: create the participant's connection [Documentation]
  • "Enable real-time chat message streaming": further streaming capabilities [Documentation]

Walkthrough

  1. Initial StartChatContact request is made, ParticipantToken gets stored
// Option #1 - Invoking the startChatContactAPI lambda CloudFormation template

// https://github.com/amazon-connect/amazon-connect-chat-ui-examples/tree/master/cloudformationTemplates/startChatContactAPI
var contactFlowId = "12345678-1234-1234-1234-123456789012";
var instanceId = "12345678-1234-1234-1234-123456789012";
var apiGatewayEndpoint = "https://<api-id>.execute-api.<region>.amazonaws.com/Prod/";
var region = "<region>";

// Details passed to the lambda
const initiateChatRequest = {
  ParticipantDetails: {
    DisplayName: name
  },
  ContactFlowId: contactFlowId,
  InstanceId: instanceId,
  Attributes: JSON.stringify({
    "customerName": name // pass this to the Contact flow
  }),
  SupportedMessagingContentTypes: ["text/plain", "text/markdown"]
};

window.fetch(apiGatewayEndpoint, {
  method: 'post',
  body: JSON.stringify(initiateChatRequest),
})
    .then((res) => res.json())
    .then((res) => {
      return res.data.startChatResult;
    })
    .catch((err) => {
      console.error('StartChatContact Failure', err)
    });

// StartChatContact response gets stored
const initialStartChatResponse = {
    ContactId,
    ParticipantId,
    ParticipantToken
};
// Option #2 - Invoking the AWK SDK `connect.startChatContact`

var AWS = require('aws-sdk');
AWS.config.update({region: process.env.REGION});
var connect = new AWS.Connect();

// https://docs.aws.amazon.com/connect/latest/APIReference/API_StartChatContact.html
const startChatRequest = {
  ParticipantDetails: {
    DisplayName: "Customer1"
  },
  ContactFlowId: contactFlowId,
  InstanceId: instanceId,
  Attributes: JSON.stringify({
    customerName: "Customer1" // pass this to the Contact flow
  }),
  SupportedMessagingContentTypes: ["text/plain", "text/markdown"]
};

// Initial StartChatContact call
connect.startChatContact(startChatRequest, function(err, data) {
    if (err) {
        console.log("Error starting the chat.", err);
        reject(err);
    } else {
        console.log("Start chat succeeded with the response: " + JSON.stringify(data));
        resolve(data);
    }
});
  1. Initial CreateParticipantConnection request is made, customer initializes chat session
// global "connect" imported from `amazon-connect-chatjs`

var chatSession;

// Initial CreateParticipantConnection call
chatSession = await connect.ChatSession.create({
  options: { // optional
    region: "us-west-2", // optional, defaults to `region` set in `connect.ChatSession.setGlobalConfig()`
  },
  chatDetails: {
    ContactId
    ParticipantId
    ParticipantToken     //  <---- from initialStartChatResponse
  },
  type: "CUSTOMER",
});
  1. Events and messages are sent in the current chat session from customer
await chatSession.connect();

await chatSession.sendMessage({
  contentType: "text/plain",
  message: "Hello World!"
});
  1. Customer refreshes the browser tab and loses websocket connection
// Browser is refreshed
location.reload();
  1. Another CreateParticipantConnection request is made with the initial ParticipantToken
// Second CreateParticipantConnection request
chatSession = await connect.ChatSession.create({
  chatDetails: {
    ContactId
    ParticipantId
    ParticipantToken     //  <---- from initialStartChatResponse
  },
  type: "CUSTOMER",
});
  1. Events and messages are sent in the same current chat session
await chatSession.connect();

await chatSession.sendMessage({
  contentType: "text/plain",
  message: "Hello World!"
});

Enabling Persistent Chat

For latest documentation, please follow instructions in "Admin guide: Enable persistent chat"

Persistent chats enable customers to resume previous conversations with the context, metadata, and transcripts carried over, eliminating the need for customers to repeat themselves and allowing agents to provide personalized service with access to the entire conversation history. To set up persistent chat experiences, simply provide a previous contact id when calling the StartChatContact API to create a new chat contact.

Learn more about persistent chat: https://docs.aws.amazon.com/connect/latest/adminguide/chat-persistence.html

Reference

Configuration

⚠️ Only chat sessions that have ended are allowed to rehydrate onto a new chat session.

Chat transcripts are pulled from previous chat contacts, and displayed to the customer and agent.

To enable persistent chat, provide the previous contactId in the SourceContactId parameter of StartChatContact API.

PUT /contact/chat HTTP/1.1
Content-type: application/json
{
   "Attributes": {
      "string" : "string"
   },
   "ContactFlowId": "string",
   "InitialMessage": {
      "Content": "string",
      "ContentType": "string"
   },
   "InstanceId": "string",
   ... // other chat fields

   // NEW Attribute for persistent chat
   "PersistentChat" : {
       "SourceContactId": "2222222-aaaa-bbbb-2222-222222222222222"
       "RehydrationType": "FROM_SEGMENT" // ENTIRE_PAST_SESSION | FROM_SEGMENT
   }
}

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

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  avatar

amazon-connect-chatjs's Issues

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.

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

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);

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

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.

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.

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)

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();
}

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!

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

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?

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

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.

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?

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?

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?

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)?

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

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&amp;data=04%7C01%7Cjohnalbert.d%40servion.com%7Cc2b1624ba35b4837345c08d8e3cdada6%7C0eb7ab7502264f22876b7b29fe557678%7C0%7C0%7C637509819844144821%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&amp;sdata=O7GFs4pkLqBwUnH%2FMBmqv2Bko1djQhZHF21H%2BYbWbaI%3D&amp;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

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

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?

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.

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.

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); 

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?

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

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!

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

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

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>

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

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.

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?

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

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.

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

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

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"

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

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 = {}) {
  // (...)
}

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.

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.

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.