redux-socket-auth-middleware

1.0.3 • Public • Published

Redux socket.io authentication middleware


Redux middleware for handle socket.io authentication.

Inspiration from redux-promise-middleware.


Demo project: chat with authentication

Middleware are the right place for persistent connections like websockets in a Redux app, for several reasons:

  • Middleware exist for the lifetime of the application
  • Like with the store itself, you probably only need a single instance of a given connection that the whole app can use
  • Middleware can see all dispatched actions and dispatch actions themselves. This means a middleware can take dispatched actions and turn those into messages sent over the websocket, and dispatch new actions when a message is received over the websocket.

Given a single action with a socketIOClient.connect payload, the middleware transforms the action to a separate a pending action and a separate connected/rejected/disconnected action.

  import socketIOClient from 'socket.io-client';
    
  export const connectingToServer = () => ({
      type: 'SOCKET',
      payload: socketIOClient.connect(uri, opts),
    });

Installation

First, install the middleware:

npm i -S redux-socket-auth-middleware

Setup Store

Import the middleware and include it in applyMiddleware when creating the Redux store:

[TIP] You can also add redux-logger middleware.

  import {applyMiddleware, createStore} from 'redux';
  import socketAuth from 'redux-socket-auth-middleware';
  import logger from 'redux-logger';
  
  // Take our rootReducer/appReducer.
  import rootReducer from './reducers';
  
  // Apply middlewares, logger always in the end.
  const middleware = applyMiddleware(socketAuth, logger);
  
  // Create store with reducers and middlewares.
  const store = createStore(rootReducer, middleware);
  
  // Now we can use store in App
  export default store;

Action

Dispatch a socketIOClient.connect as the value of the payload property of the action.

  import socketIOClient from 'socket.io-client';
  
  // Type of action:
  const SOCKET = 'SOCKET';
  
  // URL where is running a server with socket.io
  const url = 'http://localhost:8000';
  
  // Function for connecting to a server
  // with that connection we passing query string: user 
  const connect = user => socketIOClient.connect(url, {query: `user=${user}`});
  
  // const user = JSON.stringify({name: 'Soda', password: 'secret'})
  export const connectingToServer = (user = null) => ({
    type: SOCKET,
    payload: connect(user),
  });
  
    /*  Now we can use this action in App
    * user argument hold name and password as object string
    * for adding into connection as query
    */
    
    // That's it for dispatch action!

Reducer

  // Action types suffixes for socket:
  const suffix = {
    PENDING: '_PENDING',
    CONNECTED: '_CONNECTED',
    REJECTED: '_REJECTED',
    DISCONNECTED: '_DISCONNECTED',
  };

  // Action type that dispatched connectingToServer action
  const SOCKET = 'SOCKET';

  // Initialization state:
  const initState = {
    disconnected: null,
    error: null,
    fetching: false,
    io: null,
  };
  
  // Reducer for handle socket.io connection.
  export default function socket(state = initState, {type, payload}) {
    switch (type) {
  
      // Handle action socket.io connecting to a server:
      case SOCKET + suffix.PENDING:
        return ({
          ...state,
          fetching: true,
        });
  
      // Handle action socket.io connected to a server:
      // payload hold connected socket.io.
      case SOCKET + suffix.CONNECTED:
        return ({
          ...state,
          io: payload,
          fetching: false,
        });
  
      // Handle action server rejected connection:
      // payload hold error from a server.
      case SOCKET + suffix.REJECTED:
        return ({
          ...state,
          error: payload,
          fetching: false,
        });
  
      // Handle action server disconnect client-socket:
      // payload hold reason disconnected from a server.
      case SOCKET + suffix.DISCONNECTED:
        return ({
          ...state,
          disconnected: payload,
          fetching: false,
        });
    }
  
    return state;
  }

  // That's it!

APP

  import React, {useState} from 'react';
  import {bindActionCreators} from 'redux';
  import {connect} from 'react-redux';
  // import redux action function for connecting to server
  import {connectingToServer} from '../path to redux action connectingToServer';
  
  function App({socket, connectingToServer}) {
    const [name, setName] = useState('');
    const [password, setPassword] = useState('');
    
    const handleChangeName = ({target}) => setName(target.value);
    const handleChangePassword = ({target}) => setPassword(target.value);
    
    const handleLogin = () => {
        const user = JSON.stringify({name, password});
        
        // Dispatch action 
        connectingToServer(user);
    };
    
    const renderStatus = () => (
      // If is a socket in redux store and is this socket connected.
      socket.io && socket.io.connected
        ? 'Connected'
        : 'Not connected'
    );
    
    // If is fetching from a server? 
    if (socket.fetching) return <h1>Loading...</h1>
      
    return (
      <div>
        <h1>{renderStatus()}</h1>
        <input value={name} onChange={handleChangeName}/>
        <input value={password} onChange={handleChangePassword}/> 
        <button onClick={handleLogin}>Login</button>
      </div>
    )
  }
  
  const mapStateToProps = ({socket}) => ({
    socket,
  });
  
 
  const mapDispatchToProps = dispatch => bindActionCreators({
    connectingToServer,
  }, dispatch);
  
  export default connect(mapStateToProps, mapDispatchToProps)(App);
  
          /* When we get a connection with a server:
           * redux store will hold socket.io,
           * so we can listen or emit events.
           * socket.io.on('some event', callback)
           * socket.io.emit('some event', arguments) 
           * just connect redux to component 
           * and mapStateToProps to get access to socket.io
           */

 io.use(function(socket, next) {
   var handshakeData = socket.request;
   // make sure the handshake data looks good as before
   // if error do this:
     // next(new Error('not authorized'));
   // else just call next
   next();
 });

Example of implementation.


Issues

For bug reports and feature requests, file an issue on GitHub.


License

Code licensed with the MIT License (MIT).


Package Sidebar

Install

npm i redux-socket-auth-middleware

Weekly Downloads

0

Version

1.0.3

License

MIT

Unpacked Size

14 kB

Total Files

5

Last publish

Collaborators

  • sakals