0.0.35-beta.28 • Published 3 months ago

acs_webchat-chat-adapter v0.0.35-beta.28

Weekly downloads
54
License
ISC
Repository
-
Last release
3 months ago

Introduction

ACS Webchat Adapter is a project for connecting Webchat UX to ACS chat.

Creating an adapter

createACSAdapter = (
  token: string,
  id: string,
  threadId: string,
  environmentUrl: string,
  fileManager: IFileManager,
  pollingInterval: number,
  eventSubscriber: IErrorEventSubscriber,
  displayName?: string,
  chatClient?: ChatClient,
  logger?: ILogger,
  adapterOptions?: AdapterOptions
): SealedAdapter<IDirectLineActivity, ACSAdapterState>

token An ACS user access token
id The ACS user's id
threadId Id of the chat thread to join
environmentUrl ACS resource endpoint
fileManager IFileManager instance for filesharing
pollingInterval Interval in milliseconds that the adapter will poll for messages, polling is only done when notifications are not enabled. The minimum value is 5 seconds. Default value is 30 seconds. eventSubscriber IErrorEventSubscriber instance to send error events to the caller. displayName User's displayname
chatClient Chat Client
logger Logger instance
adapterOptions Adapter options, see Feature Options for more detail

Before you start:

ACS Webchat Adapter requires CommunicationUserToken and EnvironmentUrl. You can either directly use ConnectionString of ACS service or can host token management service project.

Option1: Use ConnectionString Directly

Get the connection string from ACS Communication Service In local.settings.json replace CONNECTION_STRING with connection string

   {
    "ConnectionString": "endpoint=https://********;accesskey=******"
  }

Option2: Token Management Service Project

If the token management project is running, paste the service URL to:

In webpack.dev.config.js, replace hosted_function_url with your own connection string

    {
      search: 'TOKEN_MANAGEMENT_SERVICE_URL',
      replace: '' // URL of token management service
    }

Getting Started

  1. Install nodejs
  2. npm install
  3. npm run start
  4. open localhost:8080 in browser
  5. copy&paste address bar to another tab to turn on another chat client
  6. 2 clients can now talk together!

Project structure

src
├── egress
│   └── createEgress[ActivityType]ActivityMiddleware.ts
├── ingress
│   └── subsribe[Event].ts
├── sdk
├── index.html

ingress contains all the event subscribe middleware which listens to ACS event and dispatchs activity to Webchat

egress contains all the middles which applied to WebChat and listen to different actions and call sdks

sdk contains api wrappers for sdk including authentication

index.html is a demo html running by npm run start, which could be used as sample code

How it works

Install VSCode mermaid extension to view this diagram in VSCode

There are 2 token types:

  1. User Token is what the web app uses to validate a login user
  2. ACS Token is what ACS Adapter use to communicate with Azure Communication Service

Handshake process:

sequenceDiagram
    participant ACS Token Service
    participant App Web Server
    participant Web App
    participant WebChat
    participant ACS Adapter
    participant ACS Chat SDK
    participant Azure Communication Service
    Web App-->>App Web Server: Send User Token for authentication and ask for an ACS token
    App Web Server -->> App Web Server: Validate the user using User Token
    App Web Server -->> ACS Token Service: Create an ACS token
    ACS Token Service -->> App Web Server: Return an ACS token
    App Web Server -->> App Web Server: Bind User Identity with ACS token
    App Web Server -->> Web App: Return an ACS token
    Web App -->> ACS Adapter: Use ACS token to create adapter
    Web App -->> WebChat: Render Webchat and bind with adapter
    ACS Adapter -->> ACS Chat SDK: Create ChatClient using ACS token
    ACS Chat SDK -->> Azure Communication Service: Network request to service

Notes:

  1. ACS doesn't provide a login service, all authentications depends on the original app authentication, and App Server is responsible for assign an ACS token after user validation(then bind ACS token with user info)
  2. Every time when ACS Token Service is asked for creating a new User Token, a new ACS user is created, token binding should happen again

  3. Thread Creation logic could happen either in App Web Server or in Adapter, it is just a preference of whether developers want it to be a heavy server depending or light server depending app

Message sending and receiving:

sequenceDiagram
    participant WebChat
    participant ACS Adapter
    participant ACS Chat SDK
    participant Azure Communication Service
    WebChat -->> WebChat: User sends messages
    WebChat -->> ACS Adapter: Pass message as an Activity to ACS Adapter
    ACS Adapter -->> ACS Chat SDK: Call chatClient.sendMessage(message)
    ACS Chat SDK -->> Azure Communication Service: Send post message network request
    Azure Communication Service -->> ACS Chat SDK: Receive message from network
    ACS Chat SDK -->> ACS Adapter: Trigger receive message event
    ACS Adapter -->> WebChat: Dispatch IncomingActivity

Details for waiting queue:

sequenceDiagram
    participant ACS Adapter
    participant ChatWidget
    participant Edge Server
    participant Azure Communication Service
    Edge Server -->> Azure Communication Service: Join Conversation (using ACS Chat SDK)
    Edge Server -->> Azure Communication Service: Create token and add customer into thread
    Edge Server -->> ChatWidget: token & threadId
    ChatWidget -->> ACS Adapter: token & threadId
    Edge Server -->> Azure Communication Service: Send queue information (using chatClient.sendMessage)
    Azure Communication Service -->> ACS Adapter: Dispatch queue messgae
    ACS Adapter -->> ChatWidget: Dispatch Activity with ChannelData
    Edge Server -->> Azure Communication Service: Agent ready, create token and join agent to thread
    Edge Server -->> Azure Communication Service: Agent ready, create token and join the agent to the thread
    Azure Communication Service -->> ACS Adapter: trigger threadUpdate

Details for idle status:

sequenceDiagram
    participant ACS Adapter
    participant ChatWidget
    participant Edge Server
    participant Azure Communication Service
    ACS Adapter -->> Edge Server: Send heartbeat
    ACS Adapter -->> Edge Server: Stop heartbeat
    Edge Server -->> Edge Server: no heartbeat received for 90s
    Edge Server -->> Azure Communication Service: kick user out of the thread
    Azure Communication Service -->> ACS Adapter: Notify thread update
    ACS Adapter -->> ChatWidget: User left the chat

Build and Test

Build a dev test js file

Dev Test js file can work without real server logic - check before you start in readme for more details

  1. Run npm run build:dev
  2. Get webchat-adapter-dev.js in dist file
  3. Add webchat-adapter-dev.js as <script> tag in html, and call window.ChatAdapter.initializeThread()
  4. Call window.ChatAdapter.createACSAdapter (check sample code in index.html)

Build a consumable js file

  1. Run npm run build
  2. Get webchat-adapter.js in dist file
  3. Get all the parameters for createACSAdapter from server side
  4. Add webchat-adapter.js as <script> tag in html, and call window.ChatAdapter.createACSAdapter (check sample code in index.html)

Telemetry

To use telemetry for adapter, you will need to implement Logger api for adapter

interface ILogger {
  logEvent(loglevel: LogLevel, event: ACSLogData): void;
}

Error event notifier

To use the error event notifier, you will need to implement the following method

interface IErrorEventSubscriber {
  notifyErrorEvent(adapterErrorEvent: AdapterErrorEvent): void;
}

And pass it when you call window.ChatAdapter.createACSAdapter (put it in the 6th parameter), check our demo index.html for reference

Feature Options

The following features can be enabled by injecting AdapterOptions interface.

export interface AdapterOptions {
  enableAdaptiveCards: boolean; // to enable adaptive card payload in adapter (which will convert content payload into a json string)
  enableThreadMemberUpdateNotification: boolean; // to enable chat thread member join/leave notification
  enableLeaveThreadOnWindowClosed: boolean; // to remove user on browser close event
  enableSenderDisplayNameInTypingNotification?: boolean; // Whether to send sender display name in typing notification,
  historyPageSizeLimit?: number; // If requested paged responses of messages otherwise undefined
  serverPageSizeLimit?: number; // Number of messages to fetch from the server at once
  shouldFileAttachmentDownloadTimeout?: boolean; // Whether file attachment download be timed out.
  fileAttachmentDownloadTimeout?: number; // If shouldFileAttachmentDownloadTimeout is set then timeout value in milliseconds when attachment download should be timed out. Default value 90s.
}

Adapter options are injected to directline while creating ACS adapter.

        const featuresOption = {
          enableAdaptiveCards: true,
          enableThreadMemberUpdateNotification: true,
          enableLeaveThreadOnWindowClosed: true,
          enableSenderDisplayNameInTypingNotification: true,
          historyPageSizeLimit: 5,
          serverPageSizeLimit: 60,
          shouldFileAttachmentDownloadTimeout: true,
          fileAttachmentDownloadTimeout: 120000
        };

        const directLine = createACSAdapter(token, userId, threadId, environmentUrl, fileManager, displayName, logger, featuresOption);
OptionTypeDefaultDescription
enableThreadMemberUpdateNotificationbooleanfalseSend chat thread member update notification activities to WebChat when a new user joins thread or a user leave the thread.
enableAdaptiveCardsbooleanfalseThe Adaptive Cards are sent as attachments in activity. The format is followd as per guidelines. Development Panel has adaptive card implementation.The example code can be found at location src/development/react-componets/.adaptiveCard.tsx
enableLeaveThreadOnWindowClosedbooleanfalseOn browser close, whether users will remove themselves from the chat thread.
enableSenderDisplayNameInTypingNotificationbooleanfalseWhether send user display name when sending typing indicator.
historyPageSizeLimitnumber or undefinedundefinedHistory message pagination can be turned on via setting a valid historyPageSizeLimit. To send a pagination event: window.dispatchEvent(new Event('acs-adapter-loadnextpage'));
serverPageSizeLimitnumber or undefinedundefinedNumber of messages to fetch from the server at once. Putting a high value like 60 will result in a fewer calls to the server to fetch all the messages on a thread.
shouldFileAttachmentDownloadTimeoutboolean or undefinedWhether file attachment download be timed out.
fileAttachmentDownloadTimeoutnumber or undefinedIf the shouldFileAttachmentDownloadTimeout is set then the value of timeout when file download should be timed out. Default will be 90000ms enforced only when shouldFileAttachmentDownloadTimeout is true, otherwise will wait for browser timeout.

Leave Chat Event

Instead of leaving the thread in Server side, we also allow user to leave thread using our adapter. To leave chat raise a 'acs-adapter-leavechat' event. The sample code is written in file 'src\development\react-component\leavechat.tsx'. Development Panel has leave chat button implemented.

window.dispatchEvent(new Event('acs-adapter-leavechat'));

Egress Error

When egress ACS backend throws an exception, an activity with channel data of type error will be triggered.

To display message to user, you can detect activity of type error like below. The message and stack of the Error object is saved in the activity text property.

   if (
      action.payload &&
      action.payload.activity?.channelData &&
      action.payload.activity?.channelData.type == 'Error'
      ) {
          dispatch({
          type: 'WEB_CHAT/SET_NOTIFICATION',
            payload: {
            level: 'info',
            message: message: JSON.parse(action.payload.activity.text).payload.details.Message
          }
    });

Development Panel

Development Panel is integrated inside of dev mode js file, if you import dev mode in html(Check index.html for Reference):

  const {
    renderDebugPanel
  } = window.ChatAdapter;

/*
Create adapter and store...
*/

 window.WebChat.renderWebChat(
    {
      directLine,
      store,
    },
    document.getElementById('root')
  );

  renderDebugPanel(document.getElementById('devPannel'), store);

Development panel has Adaptive Card UI panel and Leave Chat button implementation.

To launch development panel in dev mode:

  1. Run npm run start
  2. Browse http://localhost:8080/index.html
  3. To show/hide (toggle) development panel press Ctrl+d

Test and unit test debug

All the unit test files are under /test/unit folder, run npm run test:unit to test

when you are writing test and wanna debug it:

  1. Run npm run test:debug to test
  2. Switch to VSCode debug tab and choose attach
  3. VSCode debug tool should be able to connect to debug thread, set breakpoints and have fun!

Integration Test

All the integration test files are under /test/integration folder

To test:

  1. Run npm run install:chromedriver to install chrome driver
  2. Run npm run execute:integrationtest to test

Filesharing

To enable filesharing implement the IFileManager interface and provide an instance of this when creating the adapter. See index.html for an example using the OneDriveFileManager implementation.

activity.attachments Contains the attachment data. Each attachment contains the following properties:

contentType The MIME-type
contentUrl The file contents URL
name The filename
thumbnailUrl Applicable to images and videos only, the image thumbnail URL

When sending attachments ensure the attachments property is populated on the Message activity that is sent to the adapter, this is done by Webchat component.

When receiving messages the adapter will populate this property if the ChatMessage contains attachments.

Note since filesharing is supported through metadata activity.channelData.metadata will contain the property returned by IFileManager's createChatMessageMetadata()method. You do NOT need to explicitly set this in the metadata yourself.

Example of an activity with attachments that Webchat component sends to adapter:

const activity: ACSDirectLineActivity = {
  ...
  attachments: [
    {
      filename: "<YOUR_FILENAME>",
      contentType: "<YOUR_FILETYPE>"
      contentURL: "<YOUR_URL>"
      thumbnailURL: "<YOUR_THUMBNAIL_URL>"
    }
  ]
  ...
}

Note that when using Webchat components built-in file attachment capabilities, you should not need to construct this activity yourself.

attachments are passed directly to IFileManager uploadFiles() method, if you require additional data for file upload you can modify the attachments in the activity.

For example in the attachments below, myAdditionalData will be passed inside attachments to uploadFile():

attachments: {
  filename: "<YOUR_FILENAME>",
  contentType: "<YOUR_FILETYPE>"
  contentURL: "<YOUR_URL>"
  thumbnailURL: "<YOUR_THUMBNAIL_URL>"
  myAdditionalData: "<YOUR_ADDITIONAL_DATA>"
}

Tags

Adapter supports message tagging. Tags are sent as ChatMessage metadata.

activity.channelData.tags: string To send tags to the adapter, populate this property inside the activity. When receiving activities from the adapter, this property will be populated if the ChatMessage contains tags.

Note since tags are sent as metadata activity.channelData.metadata.tags will also contain the tags. You do NOT need to explicitly set this in the metadata yourself.

Exampe of sending tags in an Activity:

const activity: ACSDirectLineActivity = {
  ...
  channelData: {
    tags: "tag1"
  }
  ...
}

And the resulting metadata on the ChatMessage object from ACS:

const message: ChatMessage = {
  ...
  metadata: {
    tags: "tag1"
  }
  ...
}

Metadata

ChatMessages may contain metadata, which is a property bag of string key-value pairs.

Message tagging and filesharing are supported through ChatMessage metadata, you do NOT need to explicitly set these in the metadata yourself, the adapter will do that for you. You can however send additional metadata if needed.

You can send additional ChatMessage metadata by populating the activity.channelData.metadata property which is of type Record<string, string>. When receiving ChatMessages, the adapter will populate this property if the message contains metadata.

Example Activity showing metadata format:

const activity: ACSDirectLineActivity = {
  ...
  channelData: {
    metadata: {
      "<YOUR_KEY>" : "<YOUR_VALUE>"
    }
  }
  ...
}

Message Edits

Note: Webchat component does not support message edits. Note: If adaptive cards are enabled, the adapter expects the message content to be a JSON object with text property. For example, valid content for an adaptive card:

JSON.stringify({
  text: "Hello world!"
})

The adapter listens for ChatMessageEditedEvents and will dispatch an MessageEdit activity when incoming events are received.

Since message edits are unsupported by Webchat, MessageEdit Activities are custom activity types that you must handle yourself.

MessageEdit activities contain the edited content (which may be the message content or metadata) as well as the timestamp when the message was edited. The MessageEdit Activity will have the same messageid as the original ChatMessage.

Below shows the format of a MessageEdit Activity, see the ACSDirectLineActivity for more type information:

    const activity: IDirectLineActivity = {
      attachment?s: any[],
      channelId: string,
      channelData?: {
        attachmentSizes: number[],
        tags: string,
        metadata: Record<string, string>;
      },
      conversation: { id: string },
      from: {
        id: string,
        name: string,
        role: string
      },
      messageid: string,
      text: string,
      timestamp: string,
      type: ActivityType.Message
    }

The following properties contain information about the message edit: text is the edited message's content
timestamp is the timestamp when the message was edited, in ISO 8601 format
messageid is the id of the edited message
type is ActivityType.Message which is equal to "message"

If the edited message contains metadata the following properties will be present: attachments is an array of objects containing file attachment data, see Filesharing for more detail.
channelData.tags contains the message tags (this is an optional metadata property)
channelData.metadata contains the message metadata

Adaptive Cards

Enable Adaptive Cards by setting enableAdaptiveCards inside AdapterOptions to true. When enabled, the adapter will determine if the received message is an adaptive card & format the activity appropriately. If adaptive cards are not enabled then Webchat will render messages as is.

ACS Adapter expects the ChatMessage for an adaptive card to contain: 1. The adaptive card JSON as the message content 2. The ChatMessage metadata to contain the key microsoft.azure.communication.chat.bot.contenttype with a value of azurebotservice.adaptivecard

Custom Middleware

NOTE Webchat currently supports activityMiddleware and createStore middleware, these can also be utilized for similar effect.

You can provide your own custom egress and ingress middlewares when initializing the adapter.
Inside AdapterOptions, provde ingressMiddleware and/or egressMiddleware.

ingressMiddleware: An array of middlewares that are invoked on incoming messages from ACS. The middlewares will be applied in the order they are provided. Ingress middleware can be used to intercept messages received from ACS.

egressMiddleware: An array of middlewares that are invoked on outgoing messages to ACS. The middlewares will be applied in the order they are provided. Egress middleware can be used to intercept messages before sending them to ACS.

Example:

const myEgressMiddleware = ({ getState }) => (next) => (activity) => {
  if (activity.type === ActivityType.Message) {
    // Do something with the message before sending it to ACS
  }
  return next(activity);
}

const options: AdapterOptions  = {
  enableAdaptiveCards: false,
  enableThreadMemberUpdateNotification: true,
  enableLeaveThreadOnWindowClosed: true,
  historyPageSizeLimit: 5,
  egressMiddleware: [myEgressMiddleware]
};

const adapter = createACSAdapter(
  token,
  userId,
  threadId,
  environmentUrl,
  fileManager,
  pollingInterval,
  displayName,
  logger,
  options
);

Contribute

TODO: Explain how other users and developers can contribute to make your code better.

If you want to learn more about creating good readme files then refer the following guidelines. You can also seek inspiration from the below readme files:

0.0.35-beta.28

3 months ago

0.0.35-beta.26

10 months ago

0.0.35-beta.27

9 months ago

0.0.35-beta.22

1 year ago

0.0.35-beta.25

11 months ago

0.0.35-beta.23

12 months ago

0.0.35-beta.24

11 months ago

0.0.35-beta.21

1 year ago

0.0.35-beta.20

1 year ago

0.0.35-beta.18

1 year ago

0.0.35-beta.19

1 year ago

0.0.35-beta.17

1 year ago

0.0.35-beta.10

2 years ago

0.0.35-beta.11

2 years ago

0.0.35-beta.5

2 years ago

0.0.35-beta.6

2 years ago

0.0.35-beta.4

2 years ago

0.0.35-beta.15

1 year ago

0.0.35-beta.12

2 years ago

0.0.35-beta.13

2 years ago

0.0.35-beta.9

2 years ago

0.0.35-beta.16

1 year ago

0.0.35-beta.7

2 years ago

0.0.35-beta.8

2 years ago

0.0.33-dev

3 years ago

0.0.34

3 years ago

0.0.35-beta.3

2 years ago

0.0.35-beta.1

3 years ago

0.0.35-beta.2

2 years ago

0.0.32

3 years ago

0.0.30

3 years ago

0.0.31

3 years ago

0.0.28

3 years ago

0.0.29

3 years ago

0.0.27

3 years ago

0.0.26

3 years ago

0.0.25

3 years ago

0.0.24

3 years ago

0.0.23

3 years ago

0.0.22

3 years ago

0.0.20

3 years ago

0.0.21

3 years ago

0.0.16

3 years ago

0.0.15

3 years ago

0.0.14

3 years ago

0.0.13

3 years ago

0.0.11

3 years ago

0.0.9

3 years ago

0.0.4

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago