├── .babelrc ├── .eslintignore ├── .eslintrc.yml ├── .gitignore ├── .istanbul.yml ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── esdoc.json ├── manual ├── configuration.md ├── examples │ ├── example1.md │ └── example2.md ├── faq.md ├── installation.md ├── overview.md ├── tutorial.md └── usage.md ├── package.json ├── src └── index.js └── test └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "transform-runtime", 5 | { 6 | "polyfill": true, 7 | "regenerator": true 8 | } 9 | ], 10 | "transform-class-properties", 11 | "transform-decorators-legacy", 12 | "transform-object-rest-spread", 13 | "pipe-composition" 14 | ], 15 | "presets": [ 16 | "latest", 17 | "react", 18 | "stage-3" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | es6: true 4 | node: true 5 | mocha: true 6 | extends: 'eslint:recommended' 7 | parser: babel-eslint 8 | parserOptions: 9 | ecmaFeatures: 10 | experimentalObjectRestSpread: true 11 | sourceType: module 12 | rules: 13 | indent: 14 | - error 15 | - 2 16 | linebreak-style: 17 | - error 18 | - unix 19 | quotes: 20 | - error 21 | - double 22 | semi: 23 | - error 24 | - always 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | build/ 3 | coverage/ 4 | docs/ 5 | node_modules/ 6 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | --- 2 | instrumentation: 3 | root: src 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | coverage/ 3 | docs/ 4 | node_modules/ 5 | test/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "6" 5 | - "5" 6 | - "4" 7 | - "0.12" 8 | before_script: 9 | - npm run snyk:protect 10 | after_success: 11 | - npm run test:cov 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/) 4 | 5 | 18 | 19 | ## [Unreleased] 20 | 21 | [Unreleased]: https://github.com/lirmes/redux-middleware-socket.io-connect/compare/...HEAD 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (C) 2016 Michael Mitchell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redux-socket.io-connect 2 | Redux socket.io connect provides a simple api for handling client-server communication that should feel very familiar to existing redux users. 3 | ## How to use 4 | ### Installation 5 | ``` 6 | npm install redux-socket.io-connect --save 7 | ``` 8 | ### Example usage 9 | #### Client setup 10 | Redux socket.io connect uses a higher order [redux reducer](https://github.com/reactjs/redux/blob/master/docs/Glossary.md) to automatically emit action events to the server and a [redux middleware](http://redux.js.org/docs/advanced/Middleware.html) that listens to the server and dispatches actions it receives to the redux store. `createClient` returns a [store enhancer](https://github.com/reactjs/redux/blob/master/docs/Glossary.md) that automatically applies both. 11 | ```js 12 | import React from 'react'; 13 | import { applyMiddleware, createStore, compose } from 'redux'; 14 | import { createClient } from 'redux-socket.io-connect'; 15 | import io from 'socket.io-client'; 16 | 17 | import reducers from './reducers'; 18 | 19 | const socket = io(); 20 | const client = createClient(socket); 21 | 22 | const store = createStore(reducers, compose( 23 | client, 24 | // devTools 25 | )); 26 | ... 27 | ``` 28 | #### Server setup 29 | The server listens for dispatched actions and uses **handlers** which are very similar to [redux reducers](http://redux.js.org/docs/basics/Reducers.html) to perform server side tasks and have the ability to dispatch new actions back to one or more connected clients. 30 | ```js 31 | import express from 'express'; 32 | import http from 'http'; 33 | import io from 'socket.io'; 34 | import { createServer } from 'redux-socket.io-connect'; 35 | 36 | import handlers from './handlers'; 37 | 38 | const app = express(); 39 | const server = http.createServer(app); 40 | const socket = io(server); 41 | 42 | createServer(socket, handlers); 43 | ... 44 | ``` 45 | #### Action examples 46 | By default actions are not dispatched to the server unless explicitly configured to do so. In this example the action when dispatched to the redux store will also be dispatched to the server. 47 | ```js 48 | export function load(path) { 49 | return { 50 | type: 'LOAD', 51 | payload: { 52 | path 53 | }, 54 | meta: { 55 | emit: true 56 | } 57 | }; 58 | } 59 | ``` 60 | You can change the default behaviour to emit all action events by configuring the event emitter during client setup. 61 | ```js 62 | const client = createClient(socket, { emitAll: true }); 63 | ``` 64 | Now by default all actions will be dispatched to the server unless explicitly configured not to. 65 | ```js 66 | export function load(path) { 67 | return { 68 | type: 'LOAD', 69 | payload: { 70 | path 71 | }, 72 | meta: { 73 | emit: false 74 | } 75 | }; 76 | } 77 | ``` 78 | #### Handler examples 79 | The handlers use a similar approach to [redux reducers](http://redux.js.org/docs/basics/Reducers.html), however instead of being passed and returning state the handlers are passed context which allows them to perform tasks and dispatch new actions back to one or more connected clients. 80 | ```js 81 | (context, action) => { 82 | // perform server side task 83 | // dispatch new actions 84 | } 85 | ``` 86 | You can use the `createHandler` and `combineHandlers` functions that behave in a similar way to their [redux counterparts](http://redux.js.org/docs/api/combineReducers.html) to aid in creating and composing handlers. The context provides access to a simple dispatch function that will relay an action back to the client who dispatched the original action. 87 | ```js 88 | import { createHandler } from 'redux-socket.io-connect'; 89 | 90 | export default createHandler({ 91 | LOAD: (context, action) => { 92 | const { dispatch } = context; 93 | const { path } = action.payload; 94 | const payload = getDataFromPath(path); 95 | 96 | dispatch({ 97 | type: 'DATA', 98 | payload 99 | }); 100 | } 101 | }); 102 | ``` 103 | The context also provides functions for dispatching of actions to specific clients or all connected clients. It also gives direct access to the socket.io client and server objects for advanced use. 104 | ```js 105 | (context, action) => { 106 | const { client, server dispatch, dispatchTo, dispatchAll } = context; 107 | 108 | dispatch(...action); 109 | dispatchTo('id', ...action); 110 | dispatchAll(...action); 111 | 112 | client.emit('hello', 'hello and thank you for the action'); 113 | server.to('id').emit('hello', 'hello to you specific client'); 114 | server.sockets.emit('hello', 'hello to all the socket.io clients'); 115 | } 116 | ``` 117 | The rest is left up to your imagination, redux socket.io connect provides you with a very simple api that should feel very familiar to existing redux users and hopefully make you feel like you are writing one cohesive synchronous app rather than separate frontend and backends glued together with async calls. 118 | 119 | ## API Reference 120 | #### `createClient(client, [userOptions])` 121 | #### `createClient(client, [reducer], [userOptions])` 122 | ##### Paramaters 123 | * `client` ([Client](http://socket.io/docs/client-api/)) the socket.io client used to send and receive events. 124 | * `[eventReducer]` (Function) --- optional reducer for manipulating the default socket.io actions dispatched by the middleware. See `createReduxEventEmitter` for an example. 125 | * `[userOptions]` (Object) --- optional configuration. 126 | * `dispatchedBy` (String) --- optional override to the value used to fill the `dispatchedBy` property that is automatically added to the `meta` property of actions dispatched by the client. 127 | * `emitAll` (Boolean) --- if `true` all actions will be dispatched to the server by default otherwise the default value is `false`. 128 | * `eventName` (String) --- optional override to the event name used by the event emitter when dispatching actions to the server, this should match the `eventName` used by the server. 129 | 130 | ##### Returns 131 | * (Function) --- returns a redux store enhancer that applies the `eventEmitter` higher order reducer and middleware. 132 | 133 | #### `createReduxMiddleware(client, [reducer], [userOptions])` 134 | ##### Paramaters 135 | * `client` ([Client](http://socket.io/docs/client-api/)) the socket.io client used to send and receive events. 136 | * `[eventReducer]` (Function) --- optional reducer for manipulating the default socket.io actions dispatched by the middleware. 137 | ```js 138 | import { actionTypes } from 'redux-socket.io-connect'; 139 | 140 | const actionReducer = (state, action) => { 141 | switch (action.type) { 142 | case actionTypes.CONNECT: 143 | case actionTypes.RECONNECT: 144 | return { 145 | ...action 146 | meta: { 147 | ...action.meta, 148 | auth: localStorage.getItem('authToken') 149 | } 150 | }; 151 | } 152 | 153 | return action; 154 | } 155 | 156 | const middleware = createReduxMiddleware(socket, actionReducer); 157 | ``` 158 | * `[userOptions]` (Object) --- optional configuration. 159 | * `eventName` (String) --- optional override to the event name used by the event emitter when sending requests to the server, this should match the `eventName` used by the server. 160 | 161 | ##### Returns 162 | * (Function) --- the redux middleware that listens for incoming action events and dispatches them to the store. 163 | 164 | ##### Actions 165 | The middeware dispatches the following redux actions. 166 | * `@@redux-socket.io-connect/CONNECT` --- dispatched only once on first `connect` event from socket.io. 167 | * `@@redux-socket.io-connect/CONNECT_ERROR` --- dispatched only once on first `connect_error` event from socket.io. 168 | * `@@redux-socket.io-connect/CONNECT_TIMEOUT` --- dispatched only once on first `connect_timeout` event from socket.io. 169 | * `@@redux-socket.io-connect/RECONNECT` --- dispatched on every `reconnect` event from socket.io. 170 | * `@@redux-socket.io-connect/RECONNECTING` --- dispatched on every `reconnecting` event from socket.io. 171 | * `@@redux-socket.io-connect/RECONNECT_ERROR` --- dispatched on every `reconnect_error` event from socket.io. 172 | * `@@redux-socket.io-connect/RECONNECT_FAILED` --- dispatched on every `reconnect_failed` event from socket.io. 173 | 174 | ```js 175 | import { actionTypes } from 'redux-socket.io-connect'; 176 | import { createReducer } from 'redux-create-reducer'; 177 | 178 | const initialState = { connected: false, error: false }; 179 | 180 | export default reducer = createReducer(initialState, { 181 | [actionTypes.CONNECT]: (state, action) => { 182 | return { ...state, connected: true }; 183 | }, 184 | [actionTypes.CONNECT_ERROR]: (state, action) => { 185 | return { ...state, error: true }; 186 | }, 187 | ... 188 | } 189 | ``` 190 | 191 | #### `createReduxEventEmitter(client, [userOptions])` 192 | ##### Paramaters 193 | * `client` ([Client](http://socket.io/docs/client-api/)) the socket.io client used to send and receive events. 194 | * `[userOptions]` (Object) --- optional configuration 195 | * `dispatchedBy` (String) --- optional override to the value used to fill the `dispatchedBy` property that is automatically added to the `meta` property of actions dispatched by the client. [redux-socket.io-connect](https://github.com/michaelmitchell/redux-socket.io-connect) 196 | * `emitAll` (Boolean) --- if `true` all actions will be dispatched to the server by default otherwise the default value is `false`. 197 | * `eventName` (String) --- optional override to the event name used by the event emitter when sending requests to the server, this should match the `eventName` used by the server. 198 | 199 | ##### Returns 200 | * (Function) --- the redux higher order reducer that dispatches actions to the server. 201 | 202 | ##### Actions 203 | The event emitter dispatches redux actions to the server under the following conditions. 204 | 205 | * If `userOptions.emitAll` is `true` then all actions will be dispatched to the server by default except where the following conditions are met. 206 | * `action.meta.emit` is `false`. 207 | * `action.meta.emit` is set to a `'customEventName'`. 208 | * `action.meta.emit` is `true` will always dispatch an action to the server. 209 | * If `action.meta.emit` is set to a `'customEventName'` it will only be dispatched if it matches the event emitters `eventName`. 210 | 211 | #### `createServer(server, handler, [enhancer], [userOptions])` 212 | ##### Paramaters 213 | * `server` ([Server](http://socket.io/docs/server-api/)) the socket.io server used to send and recieve events. 214 | * `handler` (Function) --- a handler function for executing server side tasks and dispatching new events to the clients. 215 | * `[enhancer]` (Function) --- a higher-order function that allows you to alter the context that will be provided to the handler. 216 | * `[userOptions]` (Object) --- optional configuration 217 | *`dispatchedBy` (String) --- optional override to the value used to fill the `dispatchedBy` property that is automatically added to the `meta` property of actions dispatched by the server. 218 | * `eventName` (String) --- optional override to the event name used by the event emitter when sending requests to the server, this should match the `eventName` used by the client. 219 | 220 | #### `createHandler(actions)` 221 | `createHandler` is function that lets us express handlers as an object mapping of action types to handlers, it works the same way as [redux-create-reducer](https://github.com/kolodny/redux-create-reducer) which you might already be fimilar with. 222 | ##### Paramaters 223 | * `actions` (Object) --- an object mapping of action types to handlers 224 | ##### Returns 225 | * (Function) --- combined handler function that matches action types to handler functions 226 | 227 | #### `combineHandlers(handlers)` 228 | The combineHandlers helper function turns an object or array whose values are different handler functions into a single handler function you can pass to createServer, it works just like redux [combineReducers](http://redux.js.org/docs/api/combineReducers.html) without the reducing part. 229 | ##### Paramaters 230 | * `handlers` (Object|Array) an object or array whose values are different handler functions 231 | ##### Returns 232 | * (Function) --- a handler that invokes every handler inside the handlers object or array. 233 | 234 | ## License 235 | MIT 236 | 237 | --- 238 | [![Donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/michaelmitchell/donate) 239 | -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./docs", 4 | "plugins": [ 5 | { 6 | "name": "esdoc-es7-plugin" 7 | } 8 | ], 9 | "manual": { 10 | "overview": ["./manual/overview.md"], 11 | "installation": ["./manual/installation.md"], 12 | "tutorial": ["./manual/tutorial.md"], 13 | "usage": ["./manual/usage.md"], 14 | "configuration": ["./manual/configuration.md"], 15 | "example": [ 16 | "./manual/examples/example1.md", 17 | "./manual/examples/example2.md" 18 | ], 19 | "faq": ["./manual/faq.md"], 20 | "changelog": ["./CHANGELOG.md"] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /manual/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | -------------------------------------------------------------------------------- /manual/examples/example1.md: -------------------------------------------------------------------------------- 1 | # Example 1 2 | -------------------------------------------------------------------------------- /manual/examples/example2.md: -------------------------------------------------------------------------------- 1 | # Example 2 2 | -------------------------------------------------------------------------------- /manual/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | -------------------------------------------------------------------------------- /manual/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | -------------------------------------------------------------------------------- /manual/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | -------------------------------------------------------------------------------- /manual/tutorial.md: -------------------------------------------------------------------------------- 1 | #Tutorial 2 | -------------------------------------------------------------------------------- /manual/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-socket.io-connect", 3 | "version": "0.4.2", 4 | "description": "redux-socket.io-connect", 5 | "homepage": "https://github.com/michaelmitchell/redux-socket.io-connect", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/michaelmitchell/redux-socket.io-connect.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/michaelmitchell/redux-socket.io-connect/issues" 12 | }, 13 | "license": "MIT", 14 | "keywords": [ 15 | "redux", 16 | "thunk", 17 | "async", 18 | "middleware", 19 | "redux-middleware", 20 | "socket.io", 21 | "websockets", 22 | "flux" 23 | ], 24 | "author": { 25 | "name": "Michael Mitchell", 26 | "email": "michael@michaelmitchell.co.nz" 27 | }, 28 | "maintainers": [ 29 | { 30 | "name": "Michael Mitchell", 31 | "email": "michael@michaelmitchell.co.nz" 32 | } 33 | ], 34 | "main": "build/index.js", 35 | "devDependencies": { 36 | "babel-cli": "^6.14.0", 37 | "babel-core": "^6.9.1", 38 | "babel-eslint": "^6.0.4", 39 | "babel-istanbul": "^0.11.0", 40 | "babel-loader": "^6.2.4", 41 | "babel-plugin-implicit-return": "^1.0.0", 42 | "babel-plugin-pipe-composition": "^0.1.1", 43 | "babel-plugin-strict-equality": "^1.0.0", 44 | "babel-plugin-transform-class-properties": "^6.11.5", 45 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 46 | "babel-plugin-transform-object-rest-spread": "^6.8.0", 47 | "babel-plugin-transform-react-jsx": "^6.8.0", 48 | "babel-plugin-transform-runtime": "^6.9.0", 49 | "babel-preset-latest": "^6.14.0", 50 | "babel-preset-react": "^6.11.1", 51 | "babel-preset-stage-3": "^6.5.0", 52 | "babel-runtime": "^6.9.2", 53 | "chai": "^3.5.0", 54 | "coveralls": "^2.11.9", 55 | "esdoc": "^0.4.7", 56 | "esdoc-es7-plugin": "0.0.3", 57 | "eslint": "^3.5.0", 58 | "mocha": "^3.0.2", 59 | "snyk": "^1.14.3" 60 | }, 61 | "dependencies": { 62 | }, 63 | "scripts": { 64 | "prebuild": "npm run test && npm run build:clean", 65 | "build": "npm run build:js", 66 | "build:clean": "rm -rf ./build", 67 | "build:js": "./node_modules/.bin/babel src -d build --source-maps=inline", 68 | "docs": "./node_modules/.bin/esdoc -c ./esdoc.json", 69 | "lint": "./node_modules/.bin/eslint src test", 70 | "lint:fix": "npm run lint -- --fix", 71 | "prepush": "npm run build", 72 | "push": "npm publish", 73 | "snyk": "./node_modules/.bin/snyk", 74 | "snyk:auth": "npm run -s snyk -- auth $SNYK_TOKEN", 75 | "snyk:monitor": "npm run snyk -- monitor", 76 | "snyk:protect": "npm run snyk -- protect", 77 | "snyk:test": "npm run snyk -- test", 78 | "snyk:wizard": "npm run snyk -- wizard", 79 | "pretest": "npm run lint", 80 | "test": "npm run snyk:test && npm run test:js", 81 | "test:cov": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", 82 | "test:js": "NODE_ENV=test ./node_modules/.bin/babel-node ./node_modules/.bin/babel-istanbul cover ./node_modules/.bin/_mocha", 83 | "test:js:nocov": "NODE_ENV=test ./node_modules/.bin/babel-node ./node_modules/.bin/_mocha", 84 | "watch": "npm run watch:js", 85 | "watch:js": "npm run build:js -- --watch" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // 2 | export const namespace = "@@redux-socket.io-connect"; 3 | 4 | // 5 | export const actionTypes = { 6 | CONNECT: `${namespace}/CONNECT`, 7 | CONNECT_ERROR: `${namespace}/CONNECT_ERROR`, 8 | CONNECT_TIMEOUT: `${namespace}/CONNECT_TIMEOUT`, 9 | CONNECT_SUCCESS: `${namespace}/CONNECT_SUCCESS`, 10 | RECONNECT: `${namespace}/RECONNECT`, 11 | RECONNECTING: `${namespace}/RECONNECTING`, 12 | RECONNECT_ERROR: `${namespace}/RECONNECT_ERROR`, 13 | RECONNECT_FAILED: `${namespace}/RECONNECT_FAILED`, 14 | RECONNECT_SUCCESS: `${namespace}/RECONNECT_SUCCESS` 15 | }; 16 | 17 | // 18 | export const dispatcherTypes = { 19 | CLIENT: `${namespace}/CLIENT`, 20 | SERVER: `${namespace}/SERVER`, 21 | }; 22 | 23 | // 24 | export const defaultEventName = `${namespace}/EVENT`; 25 | 26 | // 27 | const defaultClientOptions = { 28 | eventName: defaultEventName, 29 | dispatchedBy: dispatcherTypes.CLIENT, 30 | emitAll: false 31 | }; 32 | 33 | // 34 | const defaultServerOptions = { 35 | eventName: defaultEventName, 36 | dispatchedBy: dispatcherTypes.SERVER 37 | }; 38 | 39 | // 40 | const defaultReducer = (state, action) => action; 41 | const defaultEnhancer = (context) => context; 42 | 43 | // createClient(socket) 44 | // createClient(socket, userOptions = {}) 45 | // createClient(socket, eventReducer = defaultReducer, userOptions = {}) 46 | export function createClient(socket, ...args) { 47 | const eventReducer = (args.length < 2) ? defaultReducer : args[0]; 48 | const userOptions = (args.length < 2) ? args[0] : args[1]; 49 | 50 | // 51 | const options = { ...defaultClientOptions, ...userOptions }; 52 | 53 | // 54 | const middleware = createReduxMiddleware(socket, eventReducer, options); 55 | const eventEmitter = createReduxEventEmitter(socket, options); 56 | 57 | // 58 | return (createStore) => (reducer, preloadedState, enhancer) => { 59 | // 60 | const store = createStore( 61 | eventEmitter(reducer), 62 | preloadedState, 63 | enhancer 64 | ); 65 | 66 | // 67 | const middlewareAPI = { 68 | dispatch: (action) => store.dispatch(action), 69 | getState: store.getState 70 | }; 71 | 72 | // 73 | const dispatch = middleware(middlewareAPI)(store.dispatch); 74 | 75 | // 76 | return { ...store, dispatch }; 77 | }; 78 | } 79 | 80 | // 81 | function handleEvent(store, reducer = defaultReducer) { 82 | return (type) => () => { 83 | const { dispatch, getState } = store; 84 | 85 | return dispatch(reducer(getState(), { type })); 86 | }; 87 | } 88 | 89 | // 90 | function handleAction(store) { 91 | return (action) => { 92 | const { dispatch } = store; 93 | 94 | return dispatch(action); 95 | }; 96 | } 97 | 98 | // 99 | export function createReduxMiddleware(socket, eventReducer = defaultReducer, userOptions = {}) { 100 | // 101 | const options = { ...defaultClientOptions, ...userOptions }; 102 | 103 | // 104 | return (store) => { 105 | const onEvent = handleEvent(store, eventReducer); 106 | 107 | // 108 | socket.once("connect", onEvent(actionTypes.CONNECT)); 109 | socket.once("connect_error", onEvent(actionTypes.CONNECT_ERROR)); 110 | socket.once("connect_timeout", onEvent(actionTypes.CONNECT_TIMEOUT)); 111 | 112 | // 113 | socket.on("reconnect", onEvent(actionTypes.RECONNECT)); 114 | socket.on("reconnecting", onEvent(actionTypes.RECONNECTING)); 115 | socket.on("reconnect_error", onEvent(actionTypes.RECONNECT_ERROR)); 116 | socket.on("reconnect_failed", onEvent(actionTypes.RECONNECT_FAILED)); 117 | 118 | // 119 | socket.on(options.eventName, handleAction(store)); 120 | 121 | // 122 | return (next) => (action) => next(action); 123 | }; 124 | } 125 | 126 | // 127 | export function createReduxEventEmitter(eventEmitter, userOptions = {}) { 128 | const options = { ...defaultClientOptions, ...userOptions }; 129 | 130 | return (reducer) => { 131 | return (state, action = {}) => { 132 | const newState = reducer(state, action); 133 | const { dispatchedBy, emit } = action.meta || {}; 134 | 135 | if (dispatchedBy === dispatcherTypes.SERVER) { 136 | return newState; 137 | } 138 | 139 | const canEmitAll = (options.emitAll === true && emit === undefined); 140 | 141 | if (emit === true || emit === options.eventName || canEmitAll) { 142 | eventEmitter.emit(options.eventName, { 143 | ...action, 144 | meta: { 145 | ...action.meta, 146 | dispatchedBy: options.dispatchedBy 147 | } 148 | }); 149 | } 150 | 151 | return newState; 152 | }; 153 | }; 154 | } 155 | 156 | // 157 | const defaultHandler = () => {}; 158 | 159 | // createServer(socket, handler = defaultReducer, enhancer = defaultEnhancer, userOptions = {}) 160 | export function createServer(server, handler = defaultHandler, enhancer = defaultEnhancer, userOptions = {}) { 161 | const options = { ...defaultServerOptions, ...userOptions }; 162 | 163 | server.on("connection", (client) => { 164 | const context = enhancer(createContext(server, client, options)); 165 | 166 | client.on(options.eventName, (action) => { 167 | handler(context, action); 168 | }); 169 | }); 170 | } 171 | 172 | // 173 | function createContext(server, client, userOptions = {}) { 174 | const options = { ...defaultServerOptions, ...userOptions }; 175 | 176 | // 177 | const actions = { 178 | dispatch: (action) => { 179 | client.emit(options.eventName, { 180 | ...action, 181 | meta: { 182 | ...action.meta, 183 | dispatchedBy: options.dispatchedBy 184 | } 185 | }); 186 | }, 187 | dispatchTo: (id, action) => { 188 | server.to(id).emit(options.eventName, { 189 | ...action, 190 | meta: { 191 | ...action.meta, 192 | dispatchedBy: options.dispatchedBy 193 | } 194 | }); 195 | }, 196 | dispatchAll: (action) => { 197 | server.sockets.emit(options.eventName, { 198 | ...action, 199 | meta: { 200 | ...action.meta, 201 | dispatchedBy: options.dispatchedBy 202 | } 203 | }); 204 | } 205 | }; 206 | 207 | // 208 | return { ...actions, server, client }; 209 | } 210 | 211 | // 212 | export function combineHandlers(handlers) { 213 | return (context, action) => { 214 | for (const key in handlers) { 215 | handlers[key](context, action); 216 | } 217 | }; 218 | } 219 | 220 | // 221 | export function createHandler(actions) { 222 | return (context, action) => { 223 | if (actions[action.type]) { 224 | actions[action.type](context, action); 225 | } 226 | }; 227 | } 228 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micahnz/redux-socket.io-connect/400a8263e1fe4437ee4fdc49590107ca91ab6cc3/test/index.js --------------------------------------------------------------------------------