Headless Chat SDK to build your own chat widget against Dynamics 365 Omnichannel Services.
Перейти к файлу
dependabot[bot] 935a7e19a7
Bump minimist from 1.2.5 to 1.2.6 (#119)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Edward Tran <edtran@microsoft.com>
2022-04-14 01:23:19 -07:00
.github/workflows Rename tag (#92) 2021-11-22 19:02:46 -08:00
__tests__ update locale export 2022-04-12 18:39:28 -07:00
docs Add troubleshooting guide (#21) 2021-08-30 10:50:26 -07:00
src update UT 2022-04-12 18:45:15 -07:00
.eslintrc.js Telemetry (#25) 2021-04-08 04:40:52 -07:00
.gitignore Initial Commit (#1) 2020-09-15 13:09:11 -07:00
CHANGELOG.md Merge branch 'main' into main 2022-04-04 20:56:55 -07:00
CODE_OF_CONDUCT.md Initial CODE_OF_CONDUCT.md commit 2020-09-04 18:21:22 -07:00
LICENSE Initial LICENSE commit 2020-09-04 18:21:24 -07:00
README.md Calling error handling (#122) 2022-04-01 00:57:01 -07:00
SECURITY.md Initial SECURITY.md commit 2020-09-04 18:21:25 -07:00
azure-pipelines.yml Set up CI with Azure Pipelines 2020-11-19 14:25:56 -08:00
jest.config.js Live Chat V2 Support (#77) 2021-10-07 15:31:23 -07:00
jestSetup.js Live Chat V2 Support (#77) 2021-10-07 15:31:23 -07:00
package-lock.json Bump minimist from 1.2.5 to 1.2.6 (#119) 2022-04-14 01:23:19 -07:00
package.json Update Libraries (#98) 2022-01-04 11:21:51 -08:00
tsconfig.json Initial Commit (#1) 2020-09-15 13:09:11 -07:00

README.md

Omnichannel Chat SDK

npm version Release CI npm

Headless Chat SDK to build your own chat widget against Dynamics 365 Omnichannel Services.

Please make sure you have a chat widget configured before using this package or you can follow this link

Table of Contents

Live Chat Widget vs. Chat SDK

Omnichannel offers an live chat widget (LCW) by default. You can use the Chat SDK to build your custom chat widget if:

  • You want to fully customize the user interface of the chat widget to conform with your branding.
  • You want to integrate Omnichannel in your mobile app using React Native.
  • You want to integrate additional functionalities that LCW does not offer.
  • Some other cool ideas. Please share with us on what you've achieved with the Chat SDK! 🙂

Feature Comparisons

Feature Live Chat Widget Chat SDK Notes
Bring Your Own Widget
Web Support
React Native Support
Escalation to Voice & Video Only supported on Web
Co-browse 3rd party add-on Only supported on Web
Screen Sharing 3rd party add-on Only supported on Web
Authenticated Chat
Pre-chat Survey
Post-chat Survey
Download Transcript
Email Transcript
Data Masking
File Attachments
Custom Context
Proactive Chat BYOI *
Persistent Chat
Chat Reconnect
Operating Hours
Queue Position No SDK method. Handled as system message
Average Wait Time No SDK method. Handled as system message

* BYOI: Bring Your Own Implementation

Installation

    npm install @microsoft/omnichannel-chat-sdk --save

Installation on React Native

The following steps will be required to run Omnichannel Chat SDK on React Native:

  1. Install node-libs-react-native

    npm install node-libs-react-native --save-dev
    
  2. Install react-native-randomBytes

    npm install react-native-randombytes --save-dev
    
  3. Install react-native-get-random-values

    npm install react-native-get-random-values --save-dev
    
  4. Install react-native-url-polyfill

    npm install react-native-url-polyfill --save-dev
    
  5. Update metro.config.js to use React Native compatible Node Core modules

    module.exports = {
        // ...
        resolver: {
            extraNodeModules: {
                ...require('node-libs-react-native'),
                net: require.resolve('node-libs-react-native/mock/net'),
                tls: require.resolve('node-libs-react-native/mock/tls')
            }
        }
    };
    
  6. Add following import on top of your entry point file

    import 'node-libs-react-native/globals';
    import 'react-native-get-random-values';
    import 'react-native-url-polyfill';
    

API Reference

Method Description Notes
OmnichannelChatSDK.initialize() Initializes ChatSDK internal data
OmnichannelChatSDK.startChat() Starts OC chat, handles prechat response
OmnichannelChatSDK.endChat() Ends OC chat
OmnichannelChatSDK.getPreChatSurvey() Adaptive card data of PreChat survey
OmnichannelChatSDK.getLiveChatConfig() Get live chat config
OmnichannelChatSDK.getDataMaskingRules() Get active data masking rules
OmnichannelChatSDK.getCurrentLiveChatContext() Get current live chat context information to reconnect to the same chat
OmnichannelChatSDK.getChatReconnectContext() Get current reconnectable chat context information to reconnect to a previous existing chat session
OmnichannelChatSDK.getConversationDetails() Get details of the current conversation such as its state & when the agent joined the conversation
OmnichannelChatSDK.getChatToken() Get chat token
OmnichannelChatSDK.getCallingToken() Get calling token
OmnichannelChatSDK.getMessages() Get all messages
OmnichannelChatSDK.sendMessage() Send message
OmnichannelChatSDK.onNewMessage() Handles system message, client/agent messages, adaptive cards, attachments to download
OmnichannelChatSDK.onTypingEvent() Handles agent typing event
OmnichannelChatSDK.onAgentEndSession() Handler when agent ends session
OmnichannelChatSDK.sendTypingEvent() Sends customer typing event
OmnichannelChatSDK.emailLiveChatTranscript() Email transcript
OmnichannelChatSDK.getLiveChatTranscript() Get transcript data (JSON)
OmnichannelChatSDK.uploadFileAttachment() Send file attachment
OmnichannelChatSDK.downloadFileAttachment() Download file attachment
OmnichannelChatSDK.createChatAdapter() Get Chat Adapter Web only
OmnichannelChatSDK.getVoiceVideoCalling() Get VoiceVideoCall SDK for Escalation to Voice & Video Web only
OmnichannelChatSDK.getPostChatSurveyContext() Get post chat survey link, survey locale, and whether an agent has joined the survey

API examples

Import

    import OmnichannelChatSDK from '@microsoft/omnichannel-chat-sdk';

Initialization

    const omnichannelConfig = {
        orgUrl: "",
        orgId: "",
        widgetId: ""
    };

    const chatSDKConfig = { // Optional
        dataMasking: {
            disable: false,
            maskingCharacter: '#'
        }
    };

    const chatSDK = new OmnichannelChatSDK.OmnichannelChatSDK(omnichannelConfig, chatSDKConfig);
    await chatSDK.initialize();

Get Current Live Chat Context

    const liveChatContext = await chatSDK.getCurrentLiveChatContext();

Get Current Chat Reconnect Context

    const optionalParams = {
        reconnectId: '', // reconnect Id
    };

    const chatReconnectContext = await chatSDK.getChatReconnectContext(optionalParams);

Get Conversation Details

    const conversationDetails = await chatSDK.getConversationDetails();

Get Chat Token

    const chatToken = await chatSDK.getChatToken();

Get Calling Token

    const callingToken = await chatSDK.getCallingToken();

Get Live Chat Config

    const liveChatConfig = await chatSDK.getLiveChatConfig();

Get Data Masking Rules

    const dataMaskingRules = await chatSDK.getDataMaskingRules();

Get PreChat Survey

Option 1

    const preChatSurvey = await getPreChatSurvey(); // Adaptive Cards JSON payload data

Option 2

    const parseToJSON = false;
    const preChatSurvey = await getPreChatSurvey(parseToJSON); // Adaptive Cards payload data as string

Get PostChat Survey

    try {
        const context = await chatSDK.getPostChatSurveyContext();
        if (context.participantJoined) { // participantJoined will be true if an agent has joined the conversation, or a bot has joined the conversation and the bot survey flag has been turned on on the admin side.
            // formsProLocale is the default language you have set on the CustomerVoice portal. You can override this url parameter with any locale that CustomerVoice supports.
            // If "&lang=" is not set on the url, the locale will be English.
            const linkToSend = context.surveyInviteLink + "&lang=" + context.formsProLocale;
            // This link is accessible and will redirect to the survey page. Use it as you see fit.
        }
    } catch (ex) {
        // If the post chat should not be shown by any reason (e.g. post chat is not enabled), promise will be rejected.
    }

Start Chat

    const customContext = {
        'contextKey1': {'value': 'contextValue1', 'isDisplayable': true},
        'contextKey2': {'value': 12.34, 'isDisplayable': false},
        'contextKey3': {'value': true}
    };

    const optionalParams = {
        preChatResponse: '', // PreChatSurvey response
        liveChatContext: {}, // EXISTING chat context data
        customContext // Custom Context
    };
    await chatSDK.startChat(optionalParams);

End Chat

    await chatSDK.endChat();

On New Message Handler

    const optionalParams = {
        rehydrate: true, // Rehydrate all previous messages of existing conversation (false by default)
    }

    chatSDK.onNewMessage((message) => {
      console.log(`[NewMessage] ${message.content}`); // IC3 protocol message data
      console.log(message);
    }, optionalParams);

On Agent End Session

    chatSDK.onAgentEndSession(() => {
      console.log("Session ended!");
    });

On Typing Event

    chatSDK.onTypingEvent(() => {
      console.log("Agent is typing...");
    })

Get Messages

    const messages = await chatSDK.getMessages();

Send Message

    import {DeliveryMode, MessageContentType, MessageType, PersonType} from '@microsoft/omnichannel-chat-sdk';

    ...

    const displayName = "Contoso"
    const message = "Sample message from customer";
    const messageToSend = {
      content: message
    };

    await chatSDK.sendMessage(messageToSend);

Send Typing

    await chatSDK.sendTypingEvent();

Upload Attachment

    const fileInfo = {
        name: '',
        type: '',
        size: '',
        data: ''
    };
    await chatSDK.uploadFileAttachment(fileInfo);

Download Attachment

    const blobResponse = await chatsdk.downloadFileAttachment(message.fileMetadata);

    ...

    // React Native implementation
    const fileReaderInstance = new FileReader();
    fileReaderInstance.readAsDataURL(blobResponse);
    fileReaderInstance.onload = () => {
        const base64data = fileReaderInstance.result;
        return <Image source={{uri: base64data}}/>
    }

Get Live Chat Transcript

    await chatSDK.getLiveChatTranscript();

Email Live Chat Transcript

    const body = {
        emailAddress: 'contoso@microsoft.com',
        attachmentMessage: 'Attachment Message'
    };
    await chatSDK.emailLiveChatTranscript(body);

Common Scenarios

PreChatSurvey

    import * as AdaptiveCards, { Action } from "adaptivecards";

    ...

    const preChatSurvey = await chatSDK.getPreChatSurvey();

    ...

    // Web implementation
    const renderPreChatSurvey = () => {
        const adaptiveCard = new AdaptiveCards.AdaptiveCard();
        adaptiveCard.parse(preChatSurvey); // Parses Adaptive Card JSON data
        adaptiveCard.onExecuteAction = async (action: Action) => { // Adaptive Card event handler
            const preChatResponse = (action as any).data;
            const optionalParams: any = {};
            if (preChatResponse) {
                optionalParams.preChatResponse = preChatResponse;
            }
            await chatSDK.startChat(optionalParams);
        }

        const renderedCard = adaptiveCard.render(); // Renders as HTML element
        return <div ref={(n) => { // Returns React element
            n && n.firstChild && n.removeChild(n.firstChild); // Removes duplicates fix
            renderedCard && n && n.appendChild(renderedCard);
        }} />
    }

Reconnect to existing Chat

    await chatSDK.startChat(); // Starts NEW chat

    const liveChatContext = await chatSDK.getCurrentLiveChatContext(); // Gets chat context

    cache.saveChatContext(liveChatContext); // Custom logic to save chat context to cache

    ...

    // Page/component reloads, ALL previous states are GONE

    ...

    const liveChatContext = cache.loadChatContext() // Custom logic to load chat context from cache

    const optionalParams = {};
    optionalParams.liveChatContext = liveChatContext;

    await chatSDK.startChat(optionalParams); // Reconnects to EXISTING chat

    ...

    const messages = await chatSDK.getMessages(); // Gets all messages from EXISTING chat
    messages.reverse().forEach((message: any) => renderMessage(message)); // Logic to render all messages to UI

Authenticated Chat

    // add if using against an authenticated chat endpoint
    // see https://docs.microsoft.com/en-us/dynamics365/omnichannel/administrator/create-chat-auth-settings on how to set up an authenticated chat widget

    const chatSDKConfig = {
        getAuthToken: async () => {
            const response = await fetch("http://contosohelp.com/token");
            if (response.ok) {
                return await response.text();
            }
            else {
                return null
            }
        }
    }

    const chatSDK = new OmnichannelChatSDK.OmnichannelChatSDK(omnichannelConfig, chatSDKConfig);
    await chatSDK.initialize();

    // from this point, this acts like a regular chat widget

Persistent Chat

    const chatSDKConfig = {
        persistentChat: {
            disable: false,
            tokenUpdateTime: 21600000
        },
        getAuthToken: async () => {
            const response = await fetch("http://contosohelp.com/token");
            if (response.ok) {
                return await response.text();
            }
            else {
                return null
            }
        }
    }

    const chatSDK = new OmnichannelChatSDK.OmnichannelChatSDK(omnichannelConfig, chatSDKConfig);
    await chatSDK.initialize();

    // from this point, this acts like a persistent chat

Chat Reconnect with Authenticated User

    const chatSDKConfig = {
        chatReconnect: {
            disable: false,
        },
        getAuthToken: async () => {
            const response = await fetch("http://contosohelp.com/token");
            if (response.ok) {
                return await response.text();
            }
            else {
                return null
            }
        }
    }

    const chatSDK = new OmnichannelChatSDK.OmnichannelChatSDK(omnichannelConfig, chatSDKConfig);
    await chatSDK.initialize();

    ...

    const chatReconnectContext = await chatSDK.getChatReconnectContext();

    if (chatReconnectContext.reconnectId) {
       // Add UX with options to reconnect to previous existing chat or start new chat
    }

    // Reconnect chat option
    const optionalParams = {};
    optionalParams.reconnectId = chatReconnectContext.reconnectId;
    chatSDK.startChat(optionalParams);

    // Start new chat option
    chatSDK.startChat();

Chat Reconnect with Unauthenticated User

    const chatSDKConfig = {
        chatReconnect: {
            disable: false,
        },
    }

    const chatSDK = new OmnichannelChatSDK.OmnichannelChatSDK(omnichannelConfig, chatSDKConfig);
    await chatSDK.initialize();

    ....

    const optionalParams: any = {};

    // Retrieve reconnect id from the URL
    const urlParams = new URLSearchParams(window.location.search);
    const reconnectId = urlParams.get('oc.reconnectid');

    const params = {
        reconnectId
    };

    // Validate reconnect id
    const chatReconnectContext = await chatSDK.getChatReconnectContext(params);

    // If the reconnect id is invalid or expired, redirect URL if there is any URL set in the configuration
    if (chatReconnectContext.redirectURL) {
        window.location.replace(chatReconnectContext.redirectURL);
    }

    // Valid reconnect id, reconnect to previous chat
    if (chatReconnectContext.reconnectId) {
        await chatSDK.startChat({
            reconnectId: chatReconnectContext.reconnectId
        });
    } else {  // Reconnect id from URL is not valid, start new chat session
        await chatSDK.startChat();
    }

Operating Hours

    const chatConfig = await chatSDK.getLiveChatConfig();
    const {LiveWSAndLiveChatEngJoin: liveWSAndLiveChatEngJoin} = liveChatConfig;
    const {OutOfOperatingHours: outOfOperatingHours} = liveWSAndLiveChatEngJoin;

    if (outOfOperatingHours === "True") {
        // Handles UX on Out of Operating Hours
    } else {
        await chatSDK.startChat();
        // Renders Custom Chat Widget
    }

Use BotFramework-WebChat

NOTE: Currently supported on web only

    import OmnichannelChatSDK from '@microsoft/omnichannel-chat-sdk';
    import ReactWebChat from 'botframework-webchat';

    ...

    const chatSDK = new OmnichannelChatSDK.OmnichannelChatSDK(omnichannelConfig, chatSDKConfig);
    await chatSDK.initialize();

    const optionalParams = {
        preChatResponse: '' // PreChatSurvey response
    };

    await chatSDK.startChat(optionalParams);
    const chatAdapter = await chatSDK.createChatAdapter();

    // Subscribes to incoming message
    chatSDK.onNewMessage((message) => {
      console.log(`[NewMessage] ${message.content}`); // IC3 protocol message data
      console.log(message);
    });

    ...

    <ReactWebChat
        userID="teamsvisitor"
        directLine={chatAdapter}
        sendTypingIndicator={true}
    />

Escalation to Voice & Video

NOTE: Currently supported on web only

    import OmnichannelChatSDK from '@microsoft/omnichannel-chat-sdk';

    ...

    const chatSDK = new OmnichannelChatSDK.OmnichannelChatSDK(omnichannelConfig, chatSDKConfig);
    await chatSDK.initialize();

    let VoiceVideoCallingSDK;
    try {
        VoiceVideoCallingSDK = await chatSDK.getVoiceVideoCalling();
        console.log("VoiceVideoCalling loaded");
    } catch (e) {
        console.log(`Failed to load VoiceVideoCalling: ${e}`);

        if (e.message === 'UnsupportedPlatform') {
            // Voice Video Calling feature is not supported on this platform
        }

        if (e.message === 'FeatureDisabled') {
            // Voice Video Calling feature is disabled on admin side
        }
    }

    await chatSDK.startChat();

    const chatToken: any = await chatSDK.getChatToken();

    // Initialize only if VoiceVideoCallingSDK is defined
    if (VoiceVideoCallingSDK) {
        try {
            await VoiceVideoCallingSDK.initialize({
                chatToken,
                selfVideoHTMLElementId: 'selfVideo', // HTML element id where video stream of the agent will be rendered
                remoteVideoHTMLElementId: 'remoteVideo', // HTML element id where video stream of the customer will be rendered
                OCClient: chatSDK.OCClient
            });
        } catch (e) {
            console.error("Failed to initialize VoiceVideoCalling!");
        }

        // Triggered when there's an incoming call
        VoiceVideoCallingSDK.onCallAdded(() => {
            ...
        });

        // Triggered when local video stream is available (e.g.: Local video added succesfully in selfVideoHTMLElement)
        VoiceVideoCallingSDK.onLocalVideoStreamAdded(() => {
            ...
        });

        // Triggered when local video stream is unavailable (e.g.: Customer turning off local video)
        VoiceVideoCallingSDK.onLocalVideoStreamRemoved(() => {
            ...
        });

        // Triggered when remote video stream is available (e.g.: Remote video added succesfully in remoteVideoHTMLElement)
        VoiceVideoCallingSDK.onRemoteVideoStreamAdded(() => {
            ...
        });

        // Triggered when remote video stream is unavailable (e.g.: Agent turning off remote video)
        VoiceVideoCallingSDK.onRemoteVideoStreamRemoved(() => {
            ...
        });

        // Triggered when current call has ended or disconnected regardless the party
        VoiceVideoCalling.onCallDisconnected(() => {
            ...
        });

        // Check if microphone is muted
        const isMicrophoneMuted = VoiceVideoCallingSDK.isMicrophoneMuted();

        // Check if remote video is available
        const isRemoteVideoEnabled = VoiceVideoCallingSDK.isRemoteVideoEnabled();

        // Check if local video is available
        const isLocalVideoEnabled = VoiceVideoCallingSDK.isLocalVideoEnabled();

        // Accepts incoming call
        const acceptCallConfig = {
            withVideo: true // Accept call with/without video stream
        };
        await VoiceVideoCallingSDK.acceptCall(acceptCallConfig);

        // Rejects incoming call
        await VoiceVideoCallingSDK.rejectCall();

        // Ends/Stops current call
        await VoiceVideoCallingSDK.stopCall();

        // Mute/Unmute current call
        await VoiceVideoCallingSDK.toggleMute()

        // Display/Hide local video of current call
        await VoiceVideoCallingSDK.toggleLocalVideo()

        // Clean up VoiceVideoCallingSDK (e.g.: Usually called when customer ends chat session)
        VoiceVideoCallingSDK.close();
    }

Feature Comparisons

Web

Custom Control WebChat Control
Features
Chat Widget UI Not provided Basic chat client provided
Data Masking Embedded Requires Data Masking Middleware implementation
Send Typing indicator Embedded Requires sendTypingIndicator flag set to true
PreChat Survey Requires Adaptive Cards renderer Requires Adaptive Cards renderer
Display Attachments Requires implementation Basic interface provided & Customizable
Incoming messages handling IC3 protocol message data DirectLine activity data

React Native

Custom Control Gifted Chat Control WebChat Control
Features Currently not supported
Chat Widget UI Not provided Basic chat client provided X
Data Masking Embedded Embedded X
Send Typing indicator Embedded Requires Implementation X
PreChat Survey Requires Adaptive Cards renderer Requires Adaptive Cards renderer X
Display Attachments Requires implementation Embedded X
Incoming messages handling IC3 protocol message data IC3 protocol message data X

Telemetry

Omnichannel Chat SDK collects telemetry by default to improve the features capabilities, reliability, and performance over time by helping Microsoft understand usage patterns, plan new features, and troubleshoot and fix problem areas.

Some of the data being collected are the following:

Field Sample
Organization Id e00e67ee-a60e-4b49-b28c-9d279bf42547
Organization Url org60082947.crm.oc.crmlivetie.com
Widget Id 1893e4ae-2859-4ac4-9cf5-97cffbb9c01b
Browser Name Edge
Os Name Windows
Anonymized IP Address (last octet redacted) 19.207.000.000

If your organization is concerned about the data collected by the Chat SDK, you have the option to turn off automatic data collection by adding a flag in the ChatSDKConfig.

    const omnichannelConfig = {
        orgUrl: "",
        orgId: "",
        widgetId: ""
    };

    const chatSDKConfig = {
        telemetry: {
            disable: true // Disable telemetry
        }
    };

    const chatSDK = new OmnichannelChatSDK.OmnichannelChatSDK(omnichannelConfig, chatSDKConfig);
    await chatSDK.initialize();

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.