I am new to redux-saga and have been searching for a solution to my issue for a couple days now so I figured I'd ask here since I've come up empty.
I am making edits to the microsoft botframework-webchat library for a project I am working on. Instead of using direct line, I am trying to make the botframework-webchat library compatible with signalR. Specifically, I am making changes to the core and component packages. My goal is to mimic the direct line redux flow so signalR works in context of the botframework-webchat library.
The basic flow starts at my root project, a simple React web app.
rootproject/WebChat.js
import ReactWebChat from 'botframework-webchat';
render() {
...
<ReactWebChat
className={`${className || ''} web-chat`}
signalR={this.signalRConnection}
//directLine={this.createDirectLine(token)}
store={store}
styleSet={styleSet}
/>
}
For my purposes, I send either a signalR connection or directLine connection object. Then within 'botframework-webchat' in the component package, ReactWebChat goes through a series of hand offs and eventually gets here:
rootproject/botframework-webchat/botframework-webchat-component/Composer.js
useEffect(() => {
if (signalR) {
console.log("Dispatch signalR");
dispatch(
connectSignalRAction({
signalR
})
);
}
else {
dispatch(
createConnectAction({
directLine,
userID,
username
})
);
}
return () => {
dispatch(disconnect());
};
}, [dispatch, signalR, userID, username]);
Both when sending a directline or signalr connection object, this correctly dispatches the corresponding action. Then comes intercepting this action. This is done in the botframework-webchat core package. I'll showcase this flow working through directline first.
rootproject/botframework-webchat/botframework-webchat-component/botframework-webchat-core/connect.js
const CONNECT = 'DIRECT_LINE/CONNECT';
const CONNECT_FULFILLED = `${CONNECT}_FULFILLED`;
const CONNECT_FULFILLING = `${CONNECT}_FULFILLING`;
const CONNECT_PENDING = `${CONNECT}_PENDING`;
const CONNECT_REJECTED = `${CONNECT}_REJECTED`;
const CONNECT_STILL_PENDING = `${CONNECT}_STILL_PENDING`;
export default function connect({ directLine, userID, username }) {
return {
type: CONNECT,
payload: {
directLine,
userID,
username
}
};
}
export { CONNECT, CONNECT_FULFILLED, CONNECT_FULFILLING, CONNECT_PENDING, CONNECT_REJECTED, CONNECT_STILL_PENDING };
This fires CONNECT and is picked up here:
rootproject/botframework-webchat/botframework-webchat-component/botframework-webchat-core/connectionStatusToNotificationSaga.js
/* eslint no-magic-numbers: ["error", { "ignore": [0, 1, 2, 3, 4] }] */
import { call, put, takeLatest } from 'redux-saga/effects';
import { CONNECT } from '../actions/connect';
import createPromiseQueue from '../createPromiseQueue';
import setNotification from '../actions/setNotification';
const CONNECTIVITY_STATUS_NOTIFICATION_ID = 'connectivitystatus';
function subscribeToPromiseQueue(observable) {
const { push, shift } = createPromiseQueue();
const subscription = observable.subscribe({ next: push });
return {
shift,
unsubscribe() {
subscription.unsubscribe();
}
};
}
function* connectionStatusToNotification({ payload: { directLine } }) {
const { shift, unsubscribe } = subscribeToPromiseQueue(directLine.connectionStatus$);
console.log("subscribe connection status: " + directLine.connectionStatus$);
try {
let reconnecting;
for (;;) {
const value = yield call(shift);
switch (value) {
case 0:
case 1:
yield put(
setNotification({
id: CONNECTIVITY_STATUS_NOTIFICATION_ID,
level: 'info',
message: reconnecting ? 'reconnecting' : 'connecting'
})
);
break;
case 2:
reconnecting = 1;
yield put(
setNotification({
id: CONNECTIVITY_STATUS_NOTIFICATION_ID,
level: 'success',
message: 'connected'
})
);
break;
case 3:
case 4:
reconnecting = 1;
yield put(
setNotification({
id: CONNECTIVITY_STATUS_NOTIFICATION_ID,
level: 'error',
message: 'failedtoconnect'
})
);
break;
default:
break;
}
}
} finally {
unsubscribe();
}
}
export default function*() {
yield takeLatest(CONNECT, connectionStatusToNotification);
}
I was able to set breakpoints in the project and step through to the above code so this is where it goes first. For reference, here is the store and sagas.
rootproject/botframework-webchat/botframework-webchat-component/botframework-webchat-core/createStore.ts
// This is for the racing between sagaMiddleware and store
/* eslint no-use-before-define: "off" */
import { applyMiddleware, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import sagaError from './actions/sagaError';
import sagas from './sagas';
export default function createWebChatStore(initialState, ...middlewares):any {
const sagaMiddleware = createSagaMiddleware({
onError: (...args) => {
const [err] = args;
console.error(err);
store.dispatch(sagaError());
}
});
const store = createStore(
reducer,
initialState || {},
applyMiddleware(...middlewares, sagaMiddleware)
);
sagaMiddleware.run(sagas);
return store;
}
rootproject/botframework-webchat/botframework-webchat-component/botframework-webchat-core/sagas.js
import { fork } from 'redux-saga/effects';
import clearSuggestedActionsOnPostActivitySaga from './sagas/clearSuggestedActionsOnPostActivitySaga';
import connectionStatusToNotificationSaga from './sagas/connectionStatusToNotificationSaga';
import connectionStatusUpdateSaga from './sagas/connectionStatusUpdateSaga';
import connectSaga from './sagas/connectSaga';
import connectSignalRSaga from './sagas/connectSignalRSaga';
import detectSlowConnectionSaga from './sagas/detectSlowConnectionSaga';
import emitTypingIndicatorToPostActivitySaga from './sagas/emitTypingIndicatorToPostActivitySaga';
import incomingActivitySaga from './sagas/incomingActivitySaga';
import markAllAsSpokenOnStopSpeakActivitySaga from './sagas/markAllAsSpokenOnStopSpeakActivitySaga';
import postActivitySaga from './sagas/postActivitySaga';
import sendEventToPostActivitySaga from './sagas/sendEventToPostActivitySaga';
import sendFilesToPostActivitySaga from './sagas/sendFilesToPostActivitySaga';
import sendMessageBackToPostActivitySaga from './sagas/sendMessageBackToPostActivitySaga';
import sendMessageToPostActivitySaga from './sagas/sendMessageToPostActivitySaga';
import sendPostBackToPostActivitySaga from './sagas/sendPostBackToPostActivitySaga';
import sendTypingIndicatorOnSetSendBoxSaga from './sagas/sendTypingIndicatorOnSetSendBoxSaga';
import speakActivityAndStartDictateOnIncomingActivityFromOthersSaga from './sagas/speakActivityAndStartDictateOnIncomingActivityFromOthersSaga';
import startDictateOnSpeakCompleteSaga from './sagas/startDictateOnSpeakCompleteSaga';
import startSpeakActivityOnPostActivitySaga from './sagas/startSpeakActivityOnPostActivitySaga';
import stopDictateOnCardActionSaga from './sagas/stopDictateOnCardActionSaga';
import stopSpeakingActivityOnInputSaga from './sagas/stopSpeakingActivityOnInputSaga';
import submitSendBoxSaga from './sagas/submitSendBoxSaga';
import submitSendBoxSagaSignalR from './sagas/submitSendBoxSagaSignalR';
import postActivitySagaSignalR from './sagas/postActivitySagaSignalR';
import sendMessageBackToPostActivitySagaSignalR from './sagas/sendMessageToPostActivitySagaSignalR';
import testSaga from './sagas/TestSaga';
export default function* sagas() {
// TODO: [P2] Since fork() silently catches all exceptions, we need to find a way to console.error them out.
yield fork(testSaga);
yield fork(clearSuggestedActionsOnPostActivitySaga);
yield fork(connectionStatusToNotificationSaga);
yield fork(connectionStatusUpdateSaga);
yield fork(connectSaga);
yield fork(connectSignalRSaga);
yield fork(detectSlowConnectionSaga);
yield fork(emitTypingIndicatorToPostActivitySaga);
yield fork(incomingActivitySaga);
yield fork(markAllAsSpokenOnStopSpeakActivitySaga);
yield fork(postActivitySaga);
yield fork(sendEventToPostActivitySaga);
yield fork(sendFilesToPostActivitySaga);
yield fork(sendMessageBackToPostActivitySaga);
yield fork(sendMessageToPostActivitySaga);
yield fork(sendPostBackToPostActivitySaga);
yield fork(sendTypingIndicatorOnSetSendBoxSaga);
yield fork(speakActivityAndStartDictateOnIncomingActivityFromOthersSaga);
yield fork(startDictateOnSpeakCompleteSaga);
yield fork(startSpeakActivityOnPostActivitySaga);
yield fork(stopDictateOnCardActionSaga);
yield fork(stopSpeakingActivityOnInputSaga);
yield fork(submitSendBoxSaga);
yield fork(submitSendBoxSagaSignalR);
yield fork(postActivitySagaSignalR);
yield fork(sendMessageBackToPostActivitySagaSignalR);
}
The flow works when using directline, but then when I send over a signalR connection object which dispatches
dispatch(
connectSignalRAction({
signalR
})
the action is dispatched but not picked up by the saga. Specifically, here is the action and saga:
rootproject/botframework-webchat/botframework-webchat-component/botframework-webchat-core/connectSignalR.js
const CONNECT_SIGNALR = 'SIGNALR/CONNECT';
const CONNECT_SIGNALR_FULFILLED = `${CONNECT_SIGNALR}_FULFILLED`;
const CONNECT_SIGNALR_FULFILLING = `${CONNECT_SIGNALR}_FULFILLING`;
const CONNECT_SIGNALR_PENDING = `${CONNECT_SIGNALR}_PENDING`;
const CONNECT_SIGNALR_REJECTED = `${CONNECT_SIGNALR}_REJECTED`;
const CONNECT_SIGNALR_STILL_PENDING = `${CONNECT_SIGNALR}_STILL_PENDING`;
export default function connectSignalR({ signalR }) {
return {
type: CONNECT_SIGNALR,
payload: {
signalR
}
};
}
export { CONNECT_SIGNALR, CONNECT_SIGNALR_FULFILLED, CONNECT_SIGNALR_FULFILLING, CONNECT_SIGNALR_PENDING, CONNECT_SIGNALR_REJECTED, CONNECT_SIGNALR_STILL_PENDING };
rootproject/botframework-webchat/botframework-webchat-component/botframework-webchat-core/connectSignalRSaga.js
/* eslint no-magic-numbers: ["error", { "ignore": [0, 10] }] */
import { call, cancel, cancelled, fork, put, race, take, takeEvery, takeLatest } from 'redux-saga/effects';
import { CONNECT_SIGNALR, CONNECT_SIGNALR_PENDING } from '../actions/connectSignalR';
function* workerSaga() {
console.log("Hello from worker saga");
yield put({ type: CONNECT_SIGNALR_PENDING });
}
export default function*() {
// for (;;) {
// const {
// payload: { signalR }
// } = yield takeEvery(CONNECT_SIGNALR);
// const {
// payload: { signalR }
// } = yield takeEvery(CONNECT_SIGNALR, workerSaga);
yield takeLatest(CONNECT_SIGNALR, workerSaga);
}
I have tried a few different approaches in 'connectSignalRSaga.js' but cannot seem to get workerSaga() to run which makes me believe the default function never yields. I have tried using a breakpoints and debugger statement and it does not stop in that function (but breakpoints and debugger statements have not been working for the most part anyway for some reason).
So my questions are:
Do you have any idea why when using directline, the 'CONNECT' action is picked up, but when using signalr the 'CONNECT_SIGNALR' action is not picked up?
Slightly off topic, but I am having trouble setting up Redux DevTools for this project (it is an isomorphic React app). I've followed instructions from https://github.com/zalmoxisus/redux-devtools-extension#usage but the extension keeps telling me "No store found". I've tried wrapping the store with 'redux-devtools-extension' npm package as well as setting compose manually. I presume getting this set up would be helpful in figuring out #1.