Skip to content

Commit afb4b21

Browse files
committed
Add onConnect props
1 parent 2411d58 commit afb4b21

File tree

11 files changed

+241
-34
lines changed

11 files changed

+241
-34
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!DOCTYPE html>
2+
<html lang="en-US">
3+
<head>
4+
<script crossorigin="anonymous" src="/__dist__/testharness.js"></script>
5+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
6+
</head>
7+
<body>
8+
<div id="webchat"></div>
9+
<script type="text/babel" data-presets="env,stage-3,react">
10+
const {
11+
WebChat: { createDirectLine },
12+
WebChatTest: { conditions, createStore, expect, host, pageObjects, timeouts, token }
13+
} = window;
14+
15+
(async function () {
16+
let connectedChatAdapter;
17+
18+
window.WebChat.renderWebChat(
19+
{
20+
directLine: createDirectLine({ token: await token.fetchDirectLineToken() }),
21+
onConnect: ({ chatAdapter }) => {
22+
connectedChatAdapter = chatAdapter;
23+
},
24+
store: createStore()
25+
},
26+
document.getElementById('webchat')
27+
);
28+
29+
await pageObjects.wait(conditions.uiConnected(), timeouts.directLine);
30+
31+
expect(connectedChatAdapter).toBeTruthy();
32+
expect(connectedChatAdapter.activities).toHaveLength(0);
33+
34+
await host.done();
35+
})().catch(async err => {
36+
console.error(err);
37+
38+
await host.error(err);
39+
});
40+
</script>
41+
</body>
42+
</html>

__tests__/html/props.onConnect.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @jest-environment ./__tests__/html/__jest__/WebChatEnvironment.js
3+
*/
4+
5+
describe('"onConnect" props', () => {
6+
test('should be called after it is connected', () =>
7+
runHTMLTest('props.onConnect.html'));
8+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { ChatAdapter } from 'botframework-webchat-core';
2+
import PropTypes from 'prop-types';
3+
import React, { FC, useRef } from 'react';
4+
5+
import ChatAdapterRefContext from '../contexts/ChatAdapterRefContext';
6+
7+
const ChatAdapterRefComposer: FC<{ children: any } & ChatAdapter> = ({
8+
activities,
9+
children,
10+
emitTyping,
11+
honorReadReceipts,
12+
notifications,
13+
resend,
14+
sendEvent,
15+
sendFiles,
16+
sendMessage,
17+
sendMessageBack,
18+
sendPostBack,
19+
setHonorReadReceipts,
20+
typingUsers,
21+
userId,
22+
username
23+
}) => {
24+
const ref = useRef<ChatAdapter>({});
25+
26+
ref.current.activities = activities;
27+
ref.current.emitTyping = emitTyping;
28+
ref.current.honorReadReceipts = honorReadReceipts;
29+
ref.current.notifications = notifications;
30+
ref.current.resend = resend;
31+
ref.current.sendEvent = sendEvent;
32+
ref.current.sendFiles = sendFiles;
33+
ref.current.sendMessage = sendMessage;
34+
ref.current.sendMessageBack = sendMessageBack;
35+
ref.current.sendPostBack = sendPostBack;
36+
ref.current.setHonorReadReceipts = setHonorReadReceipts;
37+
ref.current.typingUsers = typingUsers;
38+
ref.current.userId = userId;
39+
ref.current.username = username;
40+
41+
return <ChatAdapterRefContext.Provider value={ref}>{children}</ChatAdapterRefContext.Provider>;
42+
};
43+
44+
ChatAdapterRefComposer.defaultProps = {
45+
activities: undefined,
46+
children: undefined,
47+
emitTyping: undefined,
48+
honorReadReceipts: undefined,
49+
notifications: undefined,
50+
resend: undefined,
51+
sendEvent: undefined,
52+
sendFiles: undefined,
53+
sendMessage: undefined,
54+
sendMessageBack: undefined,
55+
sendPostBack: undefined,
56+
setHonorReadReceipts: undefined,
57+
typingUsers: undefined,
58+
userId: undefined,
59+
username: undefined
60+
};
61+
62+
ChatAdapterRefComposer.propTypes = {
63+
activities: PropTypes.any,
64+
children: PropTypes.any,
65+
emitTyping: PropTypes.any,
66+
honorReadReceipts: PropTypes.any,
67+
notifications: PropTypes.any,
68+
resend: PropTypes.any,
69+
sendEvent: PropTypes.any,
70+
sendFiles: PropTypes.any,
71+
sendMessage: PropTypes.any,
72+
sendMessageBack: PropTypes.any,
73+
sendPostBack: PropTypes.any,
74+
setHonorReadReceipts: PropTypes.any,
75+
typingUsers: PropTypes.any,
76+
userId: PropTypes.any,
77+
username: PropTypes.any
78+
};
79+
80+
export default ChatAdapterRefComposer;

packages/api/src/composers/NotificationComposer.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ import updateIn from 'simple-update-in';
66
import createDebug from '../utils/debug';
77
import diffObject from '../utils/diffObject';
88
import NotificationContext from '../contexts/NotificationContext';
9+
import useChatAdapterRef from '../hooks/internal/useChatAdapterRef';
910
import useForceRender from '../hooks/internal/useForceRender';
1011
import usePrevious from '../hooks/internal/usePrevious';
1112

1213
let debug;
1314

14-
const NotificationComposer: FC<{ chatAdapterNotifications: Notifications; children: any }> = ({
15-
chatAdapterNotifications,
16-
children
17-
}) => {
15+
const NotificationComposer: FC<{
16+
chatAdapterNotifications: Notifications;
17+
children: any;
18+
onConnect: ({ chatAdapter }) => void;
19+
}> = ({ chatAdapterNotifications, children, onConnect }) => {
1820
debug || (debug = createDebug('<NotificationComposer>', { backgroundColor: 'yellow', color: 'black' }));
1921

2022
const notificationsWithMismatchId = Object.entries(chatAdapterNotifications).filter(
@@ -105,6 +107,13 @@ const NotificationComposer: FC<{ chatAdapterNotifications: Notifications; childr
105107
// [{ chatAdapterNotifications, localNotifications, notifications, ourChatAdapterNotificationsRef }]
106108
// );
107109

110+
const connectivityStatus = notifications?.connectivitystatus?.data;
111+
const chatAdapterRef = useChatAdapterRef();
112+
113+
useMemo(() => {
114+
connectivityStatus === 'connected' && onConnect && onConnect({ chatAdapter: chatAdapterRef.current });
115+
}, [chatAdapterRef, connectivityStatus, onConnect]);
116+
108117
const context = useMemo(
109118
() => ({
110119
dismissNotification,
@@ -119,12 +128,14 @@ const NotificationComposer: FC<{ chatAdapterNotifications: Notifications; childr
119128

120129
NotificationComposer.defaultProps = {
121130
chatAdapterNotifications: undefined,
122-
children: undefined
131+
children: undefined,
132+
onConnect: undefined
123133
};
124134

125135
NotificationComposer.propTypes = {
126136
chatAdapterNotifications: WebChatPropTypes.Notifications,
127-
children: PropTypes.any
137+
children: PropTypes.any,
138+
onConnect: PropTypes.func
128139
};
129140

130141
export default NotificationComposer;

packages/api/src/composers/TypingComposer.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ const TypingComposer = ({
8181
() =>
8282
emitTypingFromProps &&
8383
((start?: boolean) => {
84+
// TODO: I think checking the "sendTypingIndicator" should be handled by the SendBox, instead of in this hook.
85+
// Dev can still force emit typing indicator if they set "sendTypingIndicator" to false.
86+
// If they really want to disable that feature, they should remove it from chat adapter.
8487
if (!sendTypingIndicator) {
8588
return;
8689
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ChatAdapter } from 'botframework-webchat-core';
2+
import { createContext, MutableRefObject } from 'react';
3+
4+
const context = createContext<MutableRefObject<ChatAdapter>>(undefined);
5+
6+
context.displayName = 'WebChat.ChatAdapterRefContext';
7+
8+
export default context;

packages/api/src/hooks/Composer.js

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import updateIn from 'simple-update-in';
66
import ActivitiesComposer from '../composers/ActivitiesComposer';
77
import APIContext from '../contexts/APIContext';
88
import CardActionComposer from '../composers/CardActionComposer';
9+
import ChatAdapterRefComposer from '../composers/ChatAdapterRefComposer';
910
import createCustomEvent from '../utils/createCustomEvent';
1011
import createDebug from '../utils/debug';
1112
import createDefaultGroupActivitiesMiddleware from './middleware/createDefaultGroupActivitiesMiddleware';
@@ -88,6 +89,7 @@ const Composer = ({
8889
internalErrorBoxClass,
8990
locale,
9091
notifications,
92+
onConnect,
9193
onTelemetry,
9294
overrideLocalizedStrings,
9395
renderMarkdown,
@@ -411,37 +413,54 @@ const Composer = ({
411413

412414
return (
413415
<APIContext.Provider value={context}>
414-
<ActivitiesComposer
416+
<ChatAdapterRefComposer
415417
activities={activities}
416-
honorReadReceipts={patchedHonorReadReceipts}
418+
emitTyping={emitTyping}
419+
honorReadReceipts={honorReadReceipts}
420+
notifications={notifications}
421+
resend={resend}
422+
sendEvent={sendEvent}
423+
sendFiles={sendFiles}
424+
sendMessage={sendMessage}
425+
sendMessageBack={sendMessageBack}
426+
sendPostBack={sendPostBack}
417427
setHonorReadReceipts={setHonorReadReceipts}
428+
typingUsers={typingUsers}
429+
userId={userId}
430+
username={username}
418431
>
419-
<NotificationComposer chatAdapterNotifications={notifications}>
420-
<TypingComposer emitTyping={emitTyping} sendTypingIndicator={sendTypingIndicator} typingUsers={typingUsers}>
421-
<InputComposer
422-
resend={resend}
423-
sendEvent={sendEvent}
424-
sendFiles={sendFiles}
425-
sendMessage={sendMessage}
426-
sendMessageBack={sendMessageBack}
427-
sendPostBack={sendPostBack}
428-
>
429-
<SpeechComposer
430-
directLineReferenceGrammarId={directLineReferenceGrammarId}
431-
webSpeechPonyfillFactory={webSpeechPonyfillFactory}
432+
<ActivitiesComposer
433+
activities={activities}
434+
honorReadReceipts={patchedHonorReadReceipts}
435+
setHonorReadReceipts={setHonorReadReceipts}
436+
>
437+
<NotificationComposer chatAdapterNotifications={notifications} onConnect={onConnect}>
438+
<TypingComposer emitTyping={emitTyping} sendTypingIndicator={sendTypingIndicator} typingUsers={typingUsers}>
439+
<InputComposer
440+
resend={resend}
441+
sendEvent={sendEvent}
442+
sendFiles={sendFiles}
443+
sendMessage={sendMessage}
444+
sendMessageBack={sendMessageBack}
445+
sendPostBack={sendPostBack}
432446
>
433-
<CardActionComposer
434-
cardActionMiddleware={cardActionMiddleware}
435-
getDirectLineOAuthCodeChallenge={getDirectLineOAuthCodeChallenge}
447+
<SpeechComposer
448+
directLineReferenceGrammarId={directLineReferenceGrammarId}
449+
webSpeechPonyfillFactory={webSpeechPonyfillFactory}
436450
>
437-
{typeof children === 'function' ? children(context) : children}
438-
{onTelemetry && <Tracker />}
439-
</CardActionComposer>
440-
</SpeechComposer>
441-
</InputComposer>
442-
</TypingComposer>
443-
</NotificationComposer>
444-
</ActivitiesComposer>
451+
<CardActionComposer
452+
cardActionMiddleware={cardActionMiddleware}
453+
getDirectLineOAuthCodeChallenge={getDirectLineOAuthCodeChallenge}
454+
>
455+
{typeof children === 'function' ? children(context) : children}
456+
{onTelemetry && <Tracker />}
457+
</CardActionComposer>
458+
</SpeechComposer>
459+
</InputComposer>
460+
</TypingComposer>
461+
</NotificationComposer>
462+
</ActivitiesComposer>
463+
</ChatAdapterRefComposer>
445464
</APIContext.Provider>
446465
);
447466
};
@@ -567,6 +586,7 @@ Composer.defaultProps = {
567586
internalErrorBoxClass: undefined,
568587
locale: window.navigator.language || 'en-US',
569588
notifications: undefined,
589+
onConnect: undefined,
570590
onTelemetry: undefined,
571591
overrideLocalizedStrings: undefined,
572592
renderMarkdown: undefined,
@@ -616,6 +636,7 @@ Composer.propTypes = {
616636
internalErrorBoxClass: PropTypes.func, // This is for internal use only. We don't allow customization of error box.
617637
locale: PropTypes.string,
618638
notifications: WebChatPropTypes.Notifications,
639+
onConnect: PropTypes.func,
619640
onTelemetry: PropTypes.func,
620641
overrideLocalizedStrings: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
621642
renderMarkdown: PropTypes.func,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ChatAdapter } from 'botframework-webchat-core';
2+
import { MutableRefObject, useContext } from 'react';
3+
4+
import ChatAdapterRefContext from '../../contexts/ChatAdapterRefContext';
5+
6+
export default function useChatAdapterRef(): MutableRefObject<ChatAdapter> {
7+
return useContext(ChatAdapterRefContext);
8+
}

packages/core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// import clearSuggestedActions from './actions/clearSuggestedActions';
22
import connect from './actions/connect';
33
import createStore, { withDevTools as createStoreWithDevTools } from './createStore';
4+
import deprecation from './utils/deprecation';
45
import disconnect from './actions/disconnect';
56
import dismissNotification from './actions/dismissNotification';
67
import emitTypingIndicator from './actions/emitTypingIndicator';
@@ -82,6 +83,7 @@ export {
8283
Constants,
8384
createStore,
8485
createStoreWithDevTools,
86+
deprecation,
8587
disconnect,
8688
dismissNotification,
8789
emitTypingIndicator,

samples/04.api/b.piggyback-on-outgoing-activities/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@
6363
{
6464
directLine: window.WebChat.createDirectLine({ token }),
6565
// We will use a custom version of Redux store, which we added middleware to handle backchannel messages.
66+
// extraChannelData: {
67+
// email: 'johndoe@example.com'
68+
// },
69+
extraChannelData: async ({ activity }) => {
70+
console.log(activity);
71+
72+
return {
73+
email: 'johndoe@example.com'
74+
};
75+
},
6676
store
6777
},
6878
document.getElementById('webchat')

0 commit comments

Comments
 (0)