From 9a5c564bd779cdb57e414114865bc02ac1c75f4b Mon Sep 17 00:00:00 2001
From: William Wong
Date: Thu, 1 Nov 2018 03:55:11 -0700
Subject: [PATCH 01/27] Send join event and various fixes
---
CHANGELOG.md | 9 +++
packages/component/src/Composer.js | 2 +
packages/core/src/actions/connect.js | 2 +
packages/core/src/actions/sendEvent.js | 10 ++++
packages/core/src/index.js | 2 +
packages/core/src/sagas.js | 2 +
packages/core/src/sagas/connectSaga.js | 2 +
.../core/src/sagas/effects/whileConnected.js | 4 +-
.../src/sagas/sendEventToPostActivitySaga.js | 23 ++++++++
.../src/sagas/sendFilesToPostActivitySaga.js | 8 +--
.../sagas/sendMessageToPostActivitySaga.js | 8 +--
.../sagas/sendPostBackToPostActivitySaga.js | 8 +--
.../src/sagas/stopSpeakActivityOnInputSaga.js | 33 ++++++-----
.../05.c.presentation-mode-styling/index.html | 14 +++--
.../index.html | 9 ++-
.../11.customization-redux-actions/index.html | 3 +-
samples/send-join-event/index.html | 56 +++++++++++++++++++
17 files changed, 153 insertions(+), 42 deletions(-)
create mode 100644 packages/core/src/actions/sendEvent.js
create mode 100644 packages/core/src/sagas/sendEventToPostActivitySaga.js
create mode 100644 samples/send-join-event/index.html
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e513478297..f723a50309 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fix [#1383](https://github.com/Microsoft/BotFramework-WebChat/issues/1383). Added options to hide upload button, by [@compulim](https://github.com/compulim) in PR [#1491](https://github.com/Microsoft/BotFramework-WebChat/pull/1491)
- Added support of avatar image, thru `styleOptions.botAvatarImage` and `styleOptions.userAvatarImage`, in PR [#1486](https://github.com/Microsoft/BotFramework-WebChat/pull/1486)
- Added ability to style sendbox background and text color, thru `styleOptions.sendBoxBackground` and `styleOptions.sendBoxTextColor`, in PR [#1575](https://github.com/Microsoft/BotFramework-WebChat/pull/1575)
+- Core: Added `sendEvent`, in PR [#1286](https://github.com/Microsoft/BotFramework-WebChat/pull/1286)
+- Core: Added `CONNECT_FULFILLING` action to workaround `redux-saga` [design decision](https://github.com/redux-saga/redux-saga/issues/1651), in PR [#1286](https://github.com/Microsoft/BotFramework-WebChat/pull/1286)
### Changed
- Moved `botAvatarImage` and `userAvatarImage` to `styleOptions.botAvatarImage` and `styleOptions.userAvatarImage` respectively, in PR [#1486](https://github.com/Microsoft/BotFramework-WebChat/pull/1486)
@@ -43,6 +45,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `component`: Fix [#1560](https://github.com/Microsoft/BotFramework-WebChat/issues/1560). Fixed carousel layout did not show date and alignment issues, by [@compulim](https://github.com/compulim) in PR [#1561](https://github.com/Microsoft/BotFramework-WebChat/pull/1561)
- `playground`: Fix [#1562](https://github.com/Microsoft/BotFramework-WebChat/issues/1562). Fixed timestamp grouping "Don't group" and added "Don't show timestamp", by [@compulim](https://github.com/compulim) in PR [#1563](https://github.com/Microsoft/BotFramework-WebChat/pull/1563)
- `component`: Fix [#1576](https://github.com/Microsoft/BotFramework-WebChat/issues/1576). Rich card without `tap` should be rendered properly, by [@compulim](https://github.com/compulim) in PR [#1577](https://github.com/Microsoft/BotFramework-WebChat/pull/1577)
+- `core`: Some sagas missed handling successive actions, in PR [#1286](https://github.com/Microsoft/BotFramework-WebChat/pull/1286)
+ - `sendFilesToPostActivitySaga`
+ - `sendMessageToPostActivitySaga`
+ - `sendPostBackToPostActivitySaga`
+ - `stopSpeakActivityOnInputSaga`
+- `core`: `incomingActivitySaga` may null-ref if the first activity is from user, in PR [#1286](https://github.com/Microsoft/BotFramework-WebChat/pull/1286)
### Removed
- `botAvatarImage` and `userAvatarImage` props, as they are moved inside `styleOptions`, in PR [#1486](https://github.com/Microsoft/BotFramework-WebChat/pull/1486)
@@ -52,6 +60,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `component`: [Hide upload button](https://microsoft.github.io/BotFramework-WebChat/05.d.hide-upload-button-styling/), in [#1491](https://github.com/Microsoft/BotFramework-WebChat/pull/1491)
- `component`: [Avatar image](https://microsoft.github.io/BotFramework-WebChat/04.b.display-user-bot-images-styling/), in [#1486](https://github.com/Microsoft/BotFramework-WebChat/pull/1486)
- `core`: [Incoming activity to JavaScript event](https://microsoft.github.io/BotFramework-WebChat/15.b.incoming-activity-event/), in [#1567](https://github.com/Microsoft/BotFramework-WebChat/pull/1567)
+- Backchannel: New send join event sample, in PR [#1286](https://github.com/Microsoft/BotFramework-WebChat/pull/1286)
## [4.2.0] - 2018-12-11
### Added
diff --git a/packages/component/src/Composer.js b/packages/component/src/Composer.js
index eec02f387e..6e2ce35249 100644
--- a/packages/component/src/Composer.js
+++ b/packages/component/src/Composer.js
@@ -15,6 +15,7 @@ import {
disconnect,
markActivity,
postActivity,
+ sendEvent,
sendFiles,
sendMessage,
sendPostBack,
@@ -44,6 +45,7 @@ const EMPTY_ARRAY = [];
const DISPATCHERS = {
markActivity,
postActivity,
+ sendEvent,
sendFiles,
sendMessage,
sendPostBack,
diff --git a/packages/core/src/actions/connect.js b/packages/core/src/actions/connect.js
index 45ac4111ac..0c6f680ea2 100644
--- a/packages/core/src/actions/connect.js
+++ b/packages/core/src/actions/connect.js
@@ -1,6 +1,7 @@
const CONNECT = 'DIRECT_LINE/CONNECT';
const CONNECT_PENDING = `${ CONNECT }_PENDING`;
const CONNECT_REJECTED = `${ CONNECT }_REJECTED`;
+const CONNECT_FULFILLING = `${ CONNECT }_FULFILLING`;
const CONNECT_FULFILLED = `${ CONNECT }_FULFILLED`;
export default function ({ directLine, userID }) {
@@ -14,5 +15,6 @@ export {
CONNECT,
CONNECT_PENDING,
CONNECT_REJECTED,
+ CONNECT_FULFILLING,
CONNECT_FULFILLED
}
diff --git a/packages/core/src/actions/sendEvent.js b/packages/core/src/actions/sendEvent.js
new file mode 100644
index 0000000000..c9f7b76d5d
--- /dev/null
+++ b/packages/core/src/actions/sendEvent.js
@@ -0,0 +1,10 @@
+const SEND_EVENT = 'WEB_CHAT/SEND_EVENT';
+
+export default function sendEvent(name, value) {
+ return {
+ type: SEND_EVENT,
+ payload: { name, value }
+ };
+}
+
+export { SEND_EVENT }
diff --git a/packages/core/src/index.js b/packages/core/src/index.js
index 333dc309ba..bc00f61782 100644
--- a/packages/core/src/index.js
+++ b/packages/core/src/index.js
@@ -3,6 +3,7 @@ import createStore from './createStore';
import disconnect from './actions/disconnect';
import markActivity from './actions/markActivity';
import postActivity from './actions/postActivity';
+import sendEvent from './actions/sendEvent';
import sendFiles from './actions/sendFiles';
import sendMessage from './actions/sendMessage';
import sendPostBack from './actions/sendPostBack';
@@ -31,6 +32,7 @@ export {
disconnect,
markActivity,
postActivity,
+ sendEvent,
sendFiles,
sendMessage,
sendPostBack,
diff --git a/packages/core/src/sagas.js b/packages/core/src/sagas.js
index 7f7fedc687..159a4f9c8a 100644
--- a/packages/core/src/sagas.js
+++ b/packages/core/src/sagas.js
@@ -7,6 +7,7 @@ import incomingActivitySaga from './sagas/incomingActivitySaga';
import incomingTypingSaga from './sagas/incomingTypingSaga';
import markActivityForSpeakSaga from './sagas/markActivityForSpeakSaga';
import postActivitySaga from './sagas/postActivitySaga';
+import sendEventToPostActivitySaga from './sagas/sendEventToPostActivitySaga';
import sendFilesToPostActivitySaga from './sagas/sendFilesToPostActivitySaga';
import sendMessageToPostActivitySaga from './sagas/sendMessageToPostActivitySaga';
import sendPostBackToPostActivitySaga from './sagas/sendPostBackToPostActivitySaga';
@@ -24,6 +25,7 @@ export default function* () {
yield fork(incomingTypingSaga);
yield fork(markActivityForSpeakSaga);
yield fork(postActivitySaga);
+ yield fork(sendEventToPostActivitySaga);
yield fork(sendFilesToPostActivitySaga);
yield fork(sendMessageToPostActivitySaga);
yield fork(sendPostBackToPostActivitySaga);
diff --git a/packages/core/src/sagas/connectSaga.js b/packages/core/src/sagas/connectSaga.js
index 9e49ae1449..ec12bc8127 100644
--- a/packages/core/src/sagas/connectSaga.js
+++ b/packages/core/src/sagas/connectSaga.js
@@ -18,6 +18,7 @@ import {
CONNECT,
CONNECT_PENDING,
CONNECT_REJECTED,
+ CONNECT_FULFILLING,
CONNECT_FULFILLED
} from '../actions/connect';
@@ -83,6 +84,7 @@ function* connectSaga(directLine, userID) {
try {
try {
yield callUntil(connectionStatusQueue.shift, [], connectionStatus => connectionStatus === ONLINE);
+ yield put({ type: CONNECT_FULFILLING, meta, payload: { directLine } });
yield put({ type: CONNECT_FULFILLED, meta, payload: { directLine } });
} catch (err) {
yield put({ type: CONNECT_REJECTED, error: true, meta, payload: err });
diff --git a/packages/core/src/sagas/effects/whileConnected.js b/packages/core/src/sagas/effects/whileConnected.js
index 95baeb9450..7adb6c73f2 100644
--- a/packages/core/src/sagas/effects/whileConnected.js
+++ b/packages/core/src/sagas/effects/whileConnected.js
@@ -5,13 +5,13 @@ import {
take
} from 'redux-saga/effects';
-import { CONNECT_FULFILLED } from '../../actions/connect';
+import { CONNECT_FULFILLING } from '../../actions/connect';
import { DISCONNECT_FULFILLED } from '../../actions/disconnect';
export default function (fn) {
return call(function* () {
for (;;) {
- const { meta: { userID }, payload: { directLine } } = yield take(CONNECT_FULFILLED);
+ const { meta: { userID }, payload: { directLine } } = yield take(CONNECT_FULFILLING);
const task = yield fork(fn, directLine, userID);
yield take(DISCONNECT_FULFILLED);
diff --git a/packages/core/src/sagas/sendEventToPostActivitySaga.js b/packages/core/src/sagas/sendEventToPostActivitySaga.js
new file mode 100644
index 0000000000..780df192fa
--- /dev/null
+++ b/packages/core/src/sagas/sendEventToPostActivitySaga.js
@@ -0,0 +1,23 @@
+import {
+ put,
+ takeEvery
+} from 'redux-saga/effects';
+
+import whileConnected from './effects/whileConnected';
+
+import { SEND_EVENT } from '../actions/sendEvent';
+import postActivity from '../actions/postActivity';
+
+export default function* () {
+ yield whileConnected(function* () {
+ yield takeEvery(SEND_EVENT, function* ({ payload: { name, value } }) {
+ if (name) {
+ yield put(postActivity({
+ name,
+ type: 'event',
+ value
+ }));
+ }
+ });
+ });
+}
diff --git a/packages/core/src/sagas/sendFilesToPostActivitySaga.js b/packages/core/src/sagas/sendFilesToPostActivitySaga.js
index 2612b99fbc..d4f7acff1a 100644
--- a/packages/core/src/sagas/sendFilesToPostActivitySaga.js
+++ b/packages/core/src/sagas/sendFilesToPostActivitySaga.js
@@ -1,6 +1,6 @@
import {
put,
- take
+ takeEvery
} from 'redux-saga/effects';
import mime from 'mime';
@@ -15,9 +15,7 @@ const getType = mime.getType.bind(mime);
export default function* () {
yield whileConnected(function* () {
- for (;;) {
- const { payload: { files } } = yield take(SEND_FILES);
-
+ yield takeEvery(SEND_FILES, function* ({ payload: { files } }) {
if (files.length) {
yield put(postActivity({
attachments: [].map.call(files, file => ({
@@ -33,6 +31,6 @@ export default function* () {
yield put(stopSpeakingActivity());
}
- }
+ });
});
}
diff --git a/packages/core/src/sagas/sendMessageToPostActivitySaga.js b/packages/core/src/sagas/sendMessageToPostActivitySaga.js
index 2c63b3ca39..9547cc5d6e 100644
--- a/packages/core/src/sagas/sendMessageToPostActivitySaga.js
+++ b/packages/core/src/sagas/sendMessageToPostActivitySaga.js
@@ -1,6 +1,6 @@
import {
put,
- take
+ takeEvery
} from 'redux-saga/effects';
import whileConnected from './effects/whileConnected';
@@ -12,9 +12,7 @@ import stopSpeakingActivity from '../actions/stopSpeakingActivity';
export default function* () {
yield whileConnected(function* () {
- for (;;) {
- const { payload: { text, via } } = yield take(SEND_MESSAGE);
-
+ yield takeEvery(SEND_MESSAGE, function* ({ payload: { text, via } }) {
if (text) {
yield put(postActivity({
text,
@@ -28,6 +26,6 @@ export default function* () {
yield put(stopSpeakingActivity());
}
}
- }
+ });
});
}
diff --git a/packages/core/src/sagas/sendPostBackToPostActivitySaga.js b/packages/core/src/sagas/sendPostBackToPostActivitySaga.js
index 4e45e952a1..79bada0d49 100644
--- a/packages/core/src/sagas/sendPostBackToPostActivitySaga.js
+++ b/packages/core/src/sagas/sendPostBackToPostActivitySaga.js
@@ -1,6 +1,6 @@
import {
put,
- take
+ takeEvery
} from 'redux-saga/effects';
import whileConnected from './effects/whileConnected';
@@ -10,9 +10,7 @@ import postActivity from '../actions/postActivity';
export default function* () {
yield whileConnected(function* () {
- for (;;) {
- const { payload: { value } } = yield take(SEND_POST_BACK);
-
+ yield takeEvery(SEND_POST_BACK, function* ({ payload: { value } }) {
if (value) {
yield put(postActivity({
channelData: {
@@ -23,6 +21,6 @@ export default function* () {
value: typeof value !== 'string' ? value : undefined
}));
}
- }
+ });
});
}
diff --git a/packages/core/src/sagas/stopSpeakActivityOnInputSaga.js b/packages/core/src/sagas/stopSpeakActivityOnInputSaga.js
index 511c5a4db4..b83e0a0d80 100644
--- a/packages/core/src/sagas/stopSpeakActivityOnInputSaga.js
+++ b/packages/core/src/sagas/stopSpeakActivityOnInputSaga.js
@@ -1,7 +1,7 @@
import {
put,
select,
- take
+ takeEvery
} from 'redux-saga/effects';
import whileConnected from './effects/whileConnected';
@@ -14,26 +14,25 @@ import { START_DICTATE } from '../actions/startDictate';
export default function* () {
yield whileConnected(function* () {
- for (;;) {
- yield take(
- ({ payload, type }) => type === START_DICTATE
- || (type === SET_SEND_BOX && payload.text && payload.via !== 'speech')
+ yield takeEvery(
+ ({ payload, type }) => type === START_DICTATE
+ || (type === SET_SEND_BOX && payload.text && payload.via !== 'speech')
- // We want to stop speaking activity when the user click on a card action
- // But currently there are no actions generated out of a card action
- // So, right now, we are using best-effort by listening to POST_ACTIVITY_PENDING with a "message" event
- || (type === POST_ACTIVITY_PENDING && payload.activity.type === 'message')
- );
+ // We want to stop speaking activity when the user click on a card action
+ // But currently there are no actions generated out of a card action
+ // So, right now, we are using best-effort by listening to POST_ACTIVITY_PENDING with a "message" event
+ || (type === POST_ACTIVITY_PENDING && payload.activity.type === 'message'),
+ function* () {
+ yield put(stopSpeakingActivity());
- yield put(stopSpeakingActivity());
+ const activities = yield select(({ activities }) => activities);
- const activities = yield select(({ activities }) => activities);
-
- for (let activity of activities) {
- if (activity.channelData && activity.channelData.speak) {
- yield put(markActivity(activity, 'speak', false));
+ for (let activity of activities) {
+ if (activity.channelData && activity.channelData.speak) {
+ yield put(markActivity(activity, 'speak', false));
+ }
}
}
- }
+ );
});
}
diff --git a/samples/05.c.presentation-mode-styling/index.html b/samples/05.c.presentation-mode-styling/index.html
index 81dc342ede..70c94c431e 100644
--- a/samples/05.c.presentation-mode-styling/index.html
+++ b/samples/05.c.presentation-mode-styling/index.html
@@ -30,8 +30,17 @@
const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
const { token } = await res.json();
+ const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
+ // The following code is for convenient when trying out this sample, not required for production.
+ if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
+ dispatch({ type: 'WEB_CHAT/SEND_MESSAGE', payload: { text: 'card inputs' } });
- const store = window.WebChat.createStore();
+ // Although suggested actions were sent from the bot, we hide the send box, thus, all suggested actions are also hidden
+ dispatch({ type: 'WEB_CHAT/SEND_MESSAGE', payload: { text: 'suggested-actions' } });
+ }
+
+ return next(action);
+ });
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
@@ -41,9 +50,6 @@
hideSendBox: true
}
}, document.getElementById('webchat'));
-
- setTimeout(() => store.dispatch({ type: 'WEB_CHAT/SEND_MESSAGE', payload: { text: 'card inputs' } }), 1000);
- setTimeout(() => store.dispatch({ type: 'WEB_CHAT/SEND_MESSAGE', payload: { text: 'suggested-actions' } }), 1500);
})().catch(err => console.error(err));
+
+
+
diff --git a/samples/07.customization-timestamp-grouping/index.html b/samples/07.customization-timestamp-grouping/index.html
index b4311e7389..8768d14400 100644
--- a/samples/07.customization-timestamp-grouping/index.html
+++ b/samples/07.customization-timestamp-grouping/index.html
@@ -87,8 +87,14 @@
const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
const { token } = await res.json();
+ const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
+ // The following code is for convenient when trying out this sample, not required for production.
+ if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
+ dispatch({ type: 'WEB_CHAT/SEND_MESSAGE', payload: { text: 'timestamp' } });
+ }
- const store = window.WebChat.createStore();
+ return next(action);
+ });
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
@@ -103,7 +109,6 @@
// The following code is for convenient when trying out this sample, not required for production.
document.querySelector('#webchat > *').focus();
- setTimeout(() => store.dispatch({ type: 'WEB_CHAT/SEND_MESSAGE', payload: { text: 'timestamp' } }), 1000);
})().catch(err => console.error(err));
diff --git a/samples/11.customization-redux-actions/index.html b/samples/11.customization-redux-actions/index.html
index 9523f0e31f..8e56685c57 100644
--- a/samples/11.customization-redux-actions/index.html
+++ b/samples/11.customization-redux-actions/index.html
@@ -35,8 +35,7 @@
const store = window.WebChat.createStore(
{},
({ dispatch }) => next => action => {
- // TODO: [P4] Investigate why we need to wait until DIRECT_LINE/CONNECTION_STATUS_UPDATE, instead of DIRECT_LINE/CONNECT_FULFILLED.
- if (action.type === 'DIRECT_LINE/CONNECTION_STATUS_UPDATE' && action.payload.connectionStatus === 2) {
+ if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
// After connected, we will send a message by dispatching a Redux action.
dispatch({ type: 'WEB_CHAT/SEND_MESSAGE', payload: { text: 'sample:backchannel' } });
} else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
diff --git a/samples/send-join-event/index.html b/samples/send-join-event/index.html
new file mode 100644
index 0000000000..2bd72027fa
--- /dev/null
+++ b/samples/send-join-event/index.html
@@ -0,0 +1,56 @@
+
+
+