Custom Websocket in Redux Architecture

2019-07-16 19:00发布

The more I read about this subject it seems like going down a rabbit hole. This is a new Trading application which receives realtime data through web sockets which is based on a request-response paradigm. There are three separate SPA's in which apart from initial load, every user action triggers a call to the dataStore with a new MDXQuery. So in turn I would need to make fresh subscriptions on a componentDidMount() as well as in the respective ActionCreators.I would like to streamline the code to avoid duplicate code and redundancy.

The below code helps establish a new subscription channel to streams the response through web-socket.(Unlike, most sockets.io code where it comes with a designated open,close,send)

    this.subscription = bus.channel(PATH, { mode: bus.wsModes.PULL }).createListener(this.onResponse.bind(this));
this.subscription.subscribe(MDXQuery);

If I read the REDUX documentation as to where should I place the web socket code? It mentions to create a custom middleware. LINK: https://redux.js.org/faq/codestructure#where-should-websockets-and-other-persistent-connections-live

But I am not very sure how could I go about using this custom web socket code framing my own middleware or doing at the component level would help to mimic this strategy.

const createMySocketMiddleware = (url) => {
return storeAPI => {
    let socket = createMyWebsocket(url);

    socket.on("message", (message) => {
        storeAPI.dispatch({
            type : "SOCKET_MESSAGE_RECEIVED",
            payload : message
        });
    });

    return next => action => {
        if(action.type == "SEND_WEBSOCKET_MESSAGE") {
            socket.send(action.payload);
            return;
        }

        return next(action);
    }
}

}

Any design inputs would really help!!

2条回答
聊天终结者
2楼-- · 2019-07-16 19:33

I tried out your solution for my own problem which involves creating a socket instance only when a user is logged and here is how my code looks:

const socketMiddleWare = url => store => {
  const socket = new SockJS(url, [], {
    sessionId: () => custom_token_id
  });
  return next => action => {
    switch (action.type) {
      case types.USER_LOGGED_IN:
        {
          socket.onopen = e => {
            console.log("Connection", e.type);

            store.dispatch({
              type: types.TOGGLE_SOCK_OPENING
            });

            if (e.type === "open") {
              store.dispatch({
                type: types.TOGGLE_SOCK_OPENED
              });
              createSession(custom_token_id, store);
              const data = {
                type: "GET_ACTIVE_SESSIONS",
                JWT_TOKEN: Cookies.get("agentClientToken")
              };
              store.dispatch({
                type: types.GET_ACTIVE_SESSIONS,
                payload: data
              });
            }
          };
          socket.onclose = () => {
            console.log("Connection closed");
            store.dispatch({
              type: types.POLL_ACTIVE_SESSIONS_STOP
            });
            // store.dispatch({ type: TOGGLE_SOCK_OPEN, payload: false });
          };
          socket.onmessage = e => {
            console.log(e)
          };
          if (
            action.type === types.SEND_SOCKET_MESSAGE
          ) {
            socket.send(JSON.stringify(action.payload));
            return;
          } else if (action.type === types.USER_LOGGED_OUT) {
            socket.close();
          }
          next(action);
        }
      default:
        next(action);
        break;
    }
  };
};

It doesn't work though but could you point me in the right direction. Thanks.

查看更多
狗以群分
3楼-- · 2019-07-16 19:40

I wrote that FAQ entry and example.

If I understand your question, you're asking about how to dynamically create additional subscriptions at runtime?

Since a Redux middleware can see every dispatched action that is passed through the middleware pipeline, you can dispatch actions that are only intended as commands for a middleware to do something. Now, I'm not sure what an MDXQuery is, and it's also not clear what you're wanting to do with the messages received from these subscriptions. For sake of the example, I'll assume that you want to either dispatch Redux actions whenever a subscription message is received, or potentially do some custom logic with them.

You can write a custom middleware that listens for actions like "CREATE_SUBSCRIPTION" and "CLOSE_SUBSCRIPTION", and potentially accepts a callback function to run when a message is received.

Here's what that might look like:

// Add this to the store during setup
const subscriptionMiddleware = (storeAPI) => {
    let nextSubscriptionId = 0;
    const subscriptions = {};
    const bus = createBusSomehow();

    return (next) => (action) => {
        switch(action.type) {
            case "CREATE_SUBSCRIPTION" : {
                const {callback} = action;
                const subscriptionId = nextSubscriptionId;
                nextSubscriptionId++;

                const subscription = bus.channel(PATH, { mode: bus.wsModes.PULL })
                                        .createListener((...args) => {
                                            callback(dispatch, getState, ...args);
                                        });
                subscriptions[subscriptionId] = subscription;
                return subscriptionId;
            }
            case "CLOSE_SUBSCRIPTION" : {
                const {subscriptionId} = action;
                const subscription = subscriptions[subscriptionId];

                if(subscription) {
                    subscription.close();
                    delete subscriptions[subscriptionId];
                }
                return;
            }
        }
    }
}

// Use over in your components file
function createSubscription(callback) {
    return {type : "CREATE_SUBSCRIPTION", callback };
}

function closeSubscription(subscriptionId) {
    return {type :  "CLOSE_SUBSCRIPTION", subscriptionId};
}

// and in your component:
const actionCreators = {createSubscription, closeSubscription};

class MyComponent extends React.Component {
    componentDidMount() {
        this.subscriptionId = this.props.createSubscription(this.onMessageReceived);
    }

    componentWillUnmount() {
        this.props.closeSubscription(this.subscriptionId);
    }
}

export default connect(null, actionCreators)(MyComponent);
查看更多
登录 后发表回答