Search code examples
reduxreact-reduxsignalrmiddleware

Invoking a method on SignalR connection in Redux middleware to send message to server


I'm using middleware in Redux to connect to my signalR hub, which works perfectly and I can receive messages no problem.

Now I have got to the point where I want to invoke a method on the server to "Join a Group".

What is the best way to do so? I have a connection, so recreating the connection on the action "JOIN_GROUP" seems counter intuitive.

I thought I might be onto something when I read xxx, so I could chain

import {
JsonHubProtocol,
  HttpTransportType,
  HubConnectionBuilder,
  LogLevel,
} from '@microsoft/signalr';

import actionTypes from '../actions/actionTypes';
const startSignalRConnection = (connection) =>
  connection
    .start()
    .then(() => console.info('SignalR Connected'))
    .catch((err) => console.error('SignalR Connection Error: ', err));

const signalRMiddleware =
  ({ dispatch, getState }) =>
  (next) =>
  async (action) => {
    let connection;
    // register signalR after the user logged in
    if (action.type === actionTypes.USER_SIGNED_IN) {
      const connectionHub =
        window.globalConfig?.hubUrl || process.env.REACT_APP_HUB_URL;

      const protocol = new JsonHubProtocol();
      // let transport to fall back to to LongPolling if it needs to
      const transport =
        HttpTransportType.WebSockets | HttpTransportType.LongPolling;
      const options = {
        transport,
        logMessageContent: true,
        logger: LogLevel.Critical,
        accessTokenFactory: () => action.user.access_token,
      };

      // create the connection instance
      connection = new HubConnectionBuilder()
        .withUrl(`${connectionHub}/hub/notificationhub`, options)
        .withHubProtocol(protocol)
        .build();

      //add "on" events here...

      // re-establish the connection if connection dropped
      connection.onclose(() =>
        setTimeout(startSignalRConnection(connection), 5000)
      );

      startSignalRConnection(connection);
    } else if (action.type === actionTypes.JOIN_GROUP) {
      connection.invoke('JoinGroup', action.groupName);
    }

    return next(action);
  };
export const joinGroup = (groupName) => (dispatch, getState, invoke) => {
  invoke('JoinGroup', groupName);
};
export default signalRMiddleware;

When I dispatch the "JOIN_GROUP" action, the connection is undefined:

} else if (action.type === actionTypes.JOIN_GROUP) {
          // connection is undefined
          connection.invoke('JoinGroup', action.groupName);
}

I did read somewhere about being able to pass the invoke method from the connection onto another method. Hence:

export const joinGroup = (groupName) => (dispatch, getState, invoke) => {
      invoke('JoinGroup', groupName);
    };

But I have no idea how to use it, or populate the invoke in order to use it in my component.

Any help would be much appreciated.

Alex


Solution

  • Managed to work it out. In the end it was a very small tweak, in that the let connection needed to be before the initial (next)

    import {
      JsonHubProtocol,
      HttpTransportType,
      HubConnectionBuilder,
      LogLevel,
    } from '@microsoft/signalr';
    
    import actionTypes from '../actions/actionTypes';
    const startSignalRConnection = (connection) =>
      connection
        .start()
        .then(() => console.info('SignalR Connected'))
        .catch((err) => console.error('SignalR Connection Error: ', err));
    
    const signalRMiddleware = ({ dispatch, getState }) => {
      let connection;
      return (next) => async (action) => {
        // register signalR after the user logged in
        if (action.type === actionTypes.USER_SIGNED_IN) {
          const connectionHub =
            window.globalConfig?.hubUrl || process.env.REACT_APP_HUB_URL;
    
          const protocol = new JsonHubProtocol();
          // let transport to fall back to to LongPolling if it needs to
          const transport =
            HttpTransportType.WebSockets | HttpTransportType.LongPolling;
          const options = {
            transport,
            logMessageContent: true,
            logger: LogLevel.Critical,
            accessTokenFactory: () => action.user.access_token,
          };
    
          // create the connection instance
          connection = new HubConnectionBuilder()
            .withUrl(`${connectionHub}/hub/notificationhub`, options)
            .withHubProtocol(protocol)
            .build();
    
          //add "on" events here...
    
          // re-establish the connection if connection dropped
          connection.onclose(() =>
            setTimeout(startSignalRConnection(connection), 5000)
          );
    
          startSignalRConnection(connection);
        } else if (action.type === actionTypes.JOIN_GROUP) {
          connection.invoke('JoinGroup', action.groupName);
          return;
        }
        return next(action);
      };
    };
    export default signalRMiddleware;