{"id":17909383,"url":"https://github.com/ajuhos/redux-socket-server","last_synced_at":"2026-02-24T07:03:49.333Z","repository":{"id":57351650,"uuid":"136337895","full_name":"ajuhos/redux-socket-server","owner":"ajuhos","description":"Lightweight framework for building shared redux stores using socket.io","archived":false,"fork":false,"pushed_at":"2023-10-02T18:53:53.000Z","size":51,"stargazers_count":1,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-18T23:12:07.492Z","etag":null,"topics":["react","redux","server","shared","socket-io","store"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ajuhos.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-06T14:08:48.000Z","updated_at":"2020-11-19T15:30:52.000Z","dependencies_parsed_at":"2024-10-18T20:00:04.863Z","dependency_job_id":"d254586f-a068-4a75-a137-d53d5c93f4c0","html_url":"https://github.com/ajuhos/redux-socket-server","commit_stats":{"total_commits":84,"total_committers":3,"mean_commits":28.0,"dds":"0.023809523809523836","last_synced_commit":"223a40f8375c76e8a33e11df7f1caed48b3e49d2"},"previous_names":[],"tags_count":47,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajuhos%2Fredux-socket-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajuhos%2Fredux-socket-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajuhos%2Fredux-socket-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ajuhos%2Fredux-socket-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ajuhos","download_url":"https://codeload.github.com/ajuhos/redux-socket-server/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221675301,"owners_count":16861860,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["react","redux","server","shared","socket-io","store"],"created_at":"2024-10-28T19:25:13.287Z","updated_at":"2026-02-24T07:03:49.239Z","avatar_url":"https://github.com/ajuhos.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"### Redux Socket Server\n\nLightweight framework for building distributed redux stores using socket.io.\n\n## Features\n\n - Shared store and actions with a single middleware\n - Managers and clients for advanced scenarios\n - Permission management\n - Server-side broadcast\n - Synchronisation using locks\n\n## Installation\n\n**Redux Socket Server is in ``beta`` and NOT ready for production use.**\n\nTo install the latest version, use NPM:\n\n```bash\n$ npm install redux-socket-server\n```\n\n## Basics\n\nThe primary aim of this component is to provide a distributed redux store for\nreal time web apps running on multiple devices at the same time. With a distributed\nstore a group of clients and the server (which can be a cluster) can share a redux store. \n\nThis means that an action dispatched at a client, can cause a change at other clients, or\ncan have an effect on the server. This concept can be useful for distributing changes \nreal time across the devices of a single user, or for real time collaboration apps.\n\nThe distributed store has a predefined base structure and actions can be tagged to be\ndistributed, otherwise the distributed store works just like any ordinary redux store,\nboth on the client and the server.\n\n### Store Structure\n\nThe distributed store consists of two parts: `shared` and `clients`. The former provides\na shared storage, which is available for every client, while the latter is the list of the\nclient-specific stores. Every client has access to it's own store, while managers and the \nserver has access to whole list of client stores. Also managers and the server, has write \naccess to the shared part, while other clients only have read access.\n\nThe content of the shared part and each client part are specified by the application.\n\nThe store from a **standard client**:\n\n```javascript\n{ \n    shared: { \n        // Read-only data, same for every client    \n    }, \n    client: { \n        id: 'client-a',\n        // Unique data of this client (also available on the server)   \n    },\n    // Anything else, only available locally for this client  \n}\n```\n\nThe store from a **manager client** or a **server node**:\n\n```javascript\n{ \n    shared: { \n        // Shared data with write access\n    }, \n    clients: { \n        items: [\n            {\n                id: 'client-a',\n                //Unique data of Client A\n            },\n            {\n                id: 'client-b',\n                //Unique data of Client B\n            }\n        ],\n        mappings: {\n            'client-a': 0,\n            'client-b': 1\n        } \n    },\n    // Anything else, only available locally\n}\n```\n\n### Actions and reducers\n\nA standard redux action will only be executed locally, as always. To make it \ndistributed, it have to be tagged as a `client` or a `shared` action.\n\nBoth tags will transfer the action to the server, but for permission management \nto function as expected, it is recommended to make sure `client` actions has no\ndirect effect on the shared part of the store. (Of course `shared` actions can \nhave an effect on the client part too.)\n\nTo tag an action, it's action creator must be tagged on declaration:\n\n```javascript\nimport { client, shared } from 'redux-socket-client';\n\nexport const addPrivateNote = client((value) =\u003e ({\n    type: 'ADD_PRIVATE_NOTE',\n    payload: {\n        note: value\n    }\n}));\n\nexport const addPublicNote = shared((value) =\u003e ({\n    type: 'ADD_PUBLIC_NOTE',\n    payload: {\n        note: value\n    }\n}));\n```\n\nReducers work just like in a normal react app, except, here distributed reducers\nwill be executed not just on every client, but also on every server node. Also\nthere are some predefined actions, which must be handled correctly:\n \n - `PRESENT`: When a client (including managers) connects to the server, the \n current distributed state will be retrieved from the server node as a PRESENT action.\n - `ADD_CLIENT`: When a non-manager client connects to the server, the ADD_CLIENT \n action will be dispatched. The reducer must initialize the client part as the effect\n of this action.\n\nYou should provide at least one reducer for each part of the store. While the shared\nreducer is straightforward, the client reducer MUST be implemented to handle a single\nclient and not the array of clients.\n\nThe minimal client reducer:\n\n```javascript\nimport { ADD_CLIENT, PRESENT } from 'redux-socket-client';\n\nexport const client = (state = {}, action) =\u003e {\n    switch(action.type) {\n        case ADD_CLIENT:\n            return {\n                id: payload.id,\n                ...payload.details\n                // Anything else you need for every client store...\n            }\n\n        case PRESENT:\n            return payload.state.client\n\n        default:\n            return state\n    }\n}\n```\n\nThe minimal shared reducer:\n\n```javascript\nimport { PRESENT } from 'redux-socket-client';\n\nexport const shared = (state = {}, action) =\u003e {\n    switch(action.type) {\n        case PRESENT:\n            return payload.state.shared\n\n        default:\n            return state\n    }\n}\n```\n\n## Setup\n\n### Setup in React apps\n\nThe store setup in react apps is mostly the same as for normal react apps, the only\nimportant exception is the `sharedStoreMiddleware`, which handles tagged actions.\n\nSetup for **standard clients**:\n\n```javascript\nimport {createStore, applyMiddleware, combineReducers} from 'redux';\nimport {sharedStoreMiddleware} from 'redux-socket-client';\nimport {connect} from 'socket.io-client';\nimport {shared, client} from './reducers';\n\nconst socket = connect('wss://...');\n\nconst store = createStore(\n    combineReducers({ shared, client }),\n    applyMiddleware(sharedStoreMiddleware(socket, { clientFirst: true }))\n)\n```  \n\nIf it is important to execute actions on client side as soon as possible, you should add\n`{ clientFirst: true }`, otherwise actions will be sent to the server and only executed \non the client, once the server processed and sent those back.\n\nSetup for **manager clients**:\n\n```javascript\nimport {createStore, applyMiddleware, combineReducers} from 'redux';\nimport {sharedStoreMiddleware, combineClients} from 'redux-socket-client';\nimport {connect} from 'socket.io-client';\nimport {shared, client} from './reducers';\n\nconst socket = connect('wss://...');\n\nthis.store = createStore(\n    combineReducers({ shared, clients: combineClients(client) }),\n    applyMiddleware(sharedStoreMiddleware(socket))\n)\n```  \n\nPlease notice `clients: combineClients(client)`. This makes possible to handle the \narray of clients with the reducer built for single clients.\n\n### Setup for server nodes\n\nOn the server side, you have to use the `SharedStore` class as a wrapper around your\nredux store.\n\n```javascript\nimport {createStore, combineReducers} from 'redux';\nimport {SharedStore, combineClients} from 'redux-socket-server';\nimport {shared, client} from './reducers';\n\nconst store = new SharedStore(\n    io, //Your socket.io instance for communication with the clients.\n    createStore(\n        combineReducers({ shared, clients: combineClients(client) }),\n        {\n            shared: {\n               // The initial state of the shared part of the store.\n            }\n        }\n    ),\n    queue // [optional] Distributed queue\n);\n```\n\nIf you use multiple server nodes, you must use a distributed queue implementation for\nthe store. A Redis based implementation is built into the library:\n\n```javascript\nimport {RedisQueue} from 'redux-socket-server';\n\nconst queue = new RedisQueue(\n    redisClient1,\n    redisClient2, \n    'prefix' // [optional] Prefix to be used for redis keys. \n);\n```\n\n#### Authentication\n\nThe server must authenticate every socket connection before the store can be used by a\nclient.\n\n```javascript\nstore.on('authentication', (socket, authorize) =\u003e {\n    if(isAuthenticated(socket)) {\n        authorize(\n            isManager(socket),     // Decide whether this user is a manager.\n            getUserId(socket),     // Provide an optional user id.\n            getUserDetails(socket) // [optional] User details.\n        )\n    }\n    else {\n        //Kick out the unauthorized socket.\n        socket.disconnect(true)\n    }\n});\n```\n\nIf no user id is provided the socket id will be used instead, but to handle multiple sockets \nfor the same user it must be provided.\n\n## Server side reaction to actions\n\nThe `SharedStore` implementation supports almost every method specified by the Redux Store API:\n \n - `dispatch(action)`\n - `getState()`\n - `subscribe(listener)`\n \nIt also adds a custom method for dispatching actions to a specified client: `dispatchToClient(clientId, action)`, \nplus one for stopping the store: `stop()`.\n\nHandling incoming actions on the server side can be implemented using `subscribe`, which provides\nsome useful parameters for the `listener`:\n - `action`: The action which caused the call of the listener\n - `clientId`: The id of the client, if it is an action tagged as `client`.\n - `prevPresent`: The previous state and version number. (`{ state: { shared, clients }, version }`)\n - `present`: The current state and version number. (`{ state: { shared, clients }, version }`)\n\nIf it is required to handle actions, or execute specific tasks only on a single master \nnode, the `lock` event of the `RedisQueue` class can be used. This will be fired every\ntime a new master is selected:\n\n```javascript\nqueue.on('lock', () =\u003e {\n    // Actions/tasks only on the master node...\n})\n```\n\nOnce a master gets selected, it won't loose it status, unless it crashes. In this case\na new master will be assigned automatically within 1 second.\n\n## License\n\nThe [MIT License](https://github.com/ajuhos/api-core/blob/master/LICENSE).\nFree forever. :)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajuhos%2Fredux-socket-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fajuhos%2Fredux-socket-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fajuhos%2Fredux-socket-server/lists"}