redux-ws-middleware
Version:
This package makes web socket management much easier with redux
487 lines (337 loc) • 12.1 kB
Markdown
# Redux WebSocket Middleware
This package makes web socket management much easier with redux.<br>
The package is built over the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket">WebSocket</a> constructor from browser API.
<a href="https://npmjs.com/package/redux-ws-middleware" target="\_blank">
<img alt="npm version" src="https://img.shields.io/npm/v/redux-ws-middleware.svg" />
</a>
<a href="https://bundlephobia.com/result?p=redux-ws-middleware@latest" target="\_blank">
<img alt="size" src="https://flat-badgen.vercel.app/bundlephobia/minzip/redux-ws-middleware@latest" />
</a>
<a href="https://bundlephobia.com/result?p=redux-ws-middleware@latest" target="\_blank">
<img alt="size" src="https://flat-badgen.vercel.app/bundlephobia/min/redux-ws-middleware@latest" />
</a>
<a href="https://npmjs.com/package/redux-ws-middleware" target="\_blank">
<img alt="npm downloads" src="https://img.shields.io/npm/dm/redux-ws-middleware.svg" />
</a>
---
# Examples
- [Single socket](https://github.com/maxzinchenko/redux-ws-middleware/tree/master/examples/single-socket)
---
# Structure
- [Installation](#installation)
- [Options](#options)
- [url](#url)
- [actionTypes](#actionTypes)
- [completedActionTypes](#completedActionTypes)
- [onMessage](#onMessage)
- [autoConnect](#autoConnect)
- [protocols](#protocols)
- [shouldReconnect](#shouldReconnect)
- [shouldOpen](#shouldOpen)
- [shouldClose](#shouldClose)
- [reconnectionIntervals](#reconnectionInterval)
- [serialize](#serialize)
- [deserialize](#deserialize)
- [debug](#debug)
- [Usage](#usage)
- [Connecting](#connecting)
- [Disconnecting](#disconnecting)
- [Sending data](#sending-data)
- [MiddlewareOptions declaration](#middlewareoptions-declaration)
- [Passing own types to MiddlewareOptions type](#passing-own-types-to-middlewareoptions-type)
---
## Installation
```
# using npm
npm install redux-ws-middleware
# using yarn
yarn add redux-ws-middleware
```
---
## Options
| Name | Required | Type | Default |
| ---------------------------------------------- | -------- | --------------------------------------------------- | ----------- |
| [url](#url) | Yes | `string` | - |
| [actionTypes](#actionTypes) | Yes | `Array<string OR RegExp>` | - |
| [completedActionTypes](#completedActionTypes) | Yes | `Array<string>` | - |
| [onMessage](#onMessage) | Yes | `(res: Res, dispatch: Dispatch<AnyAction>) => void` | - |
| [autoConnect](#autoConnect) | No | `boolean` | `true` |
| [protocols](#protocols) | No | `string OR string[]` | - |
| [shouldReconnect](#shouldReconnect) | No | `((event: CloseEvent) => boolean) OR boolean` | `true` |
| [reconnectionIntervals](#reconnectionInterval) | No | `number OR number[]` | `1000` |
| [shouldOpen](#shouldReconnect) | No | `((req: Req) => boolean) OR boolean` | `false` |
| [shouldClose](#shouldReconnect) | No | `((res: DRes) => boolean) OR boolean` | `false` |
| [serialize](#serialize) | No | `(req: Req) => SReq` | - |
| [deserialize](#deserialize) | No | `(res: Res) => DRes` | - |
| [debug](#debug) | No | `boolean` | - |
--------
## url
<b>Required*</b>
Type: `string`
Url for the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket">WebSocket</a> constructor.
```ts
url: 'ws://localhost:3000'
```
```ts
url: 'wss://example.com'
```
## actionTypes
<b>Required*</b>
Type: `[RegExp | string, RegExp | string, RegExp | string]`
<b>WARNING: Sequence is important!</b><br>
Types that you are able to manage the socket with.<br>
You can have socket dispatching any of them.
The first element should be the `SEND` action type.<br>
Second - `CONNECT` type. <br>
Third - `DISCONNECT` type.
<b>IMPORTANT: Do not use `/g` at the end of RegExp!</b>
```ts
actionTypes: ['SEND', 'CONNECT', 'DISCONNECT']
```
```ts
actionTypes: [new RegExp(/_REQUEST$/), 'CONNECT', 'DISCONNECT']
```
If you don't need these: `CONNECT`, `DISCONNECT` so just don't send them.
```ts
actionTypes: ['SEND', 'CONNECT']
```
```ts
actionTypes: ['SEND']
```
```ts
actionTypes: [new RegExp(/_REQUEST$/)]
```
## completedActionTypes
<b>Required*</b>
Type: `[string, string]`
<b>WARNING: Sequence is important!</b>
Types that you receive back on actions.
```ts
completedActionTypes: ['CONNECTED', 'DISCONNECTED']
```
## onMessage
<b>Required*</b>
Type: `(res: Res, dispatch: Dispatch<AnyAction>) => void`
The callback gets called with deserialized data already, if you put deserialize function into options, or with a normal data if you don't. And with a `dispatch` so you can manage your store.
*(this is just an example of the `onMessage` handler)
```ts
onMessage: (data, dispatch) => {
switch (data.method) {
case 'posts':
if (data.error) {
dispatch(postsActions.getPostsRejected(data.error));
} else {
dispatch(postsActions.getPostsFulfilled(data.result));
}
break;
...
default:
break;
}
}
```
## autoConnect
Type: `boolean` - (`true` by default)
When `true` you don't need to send anything else to connect it.<br>
When `false` you need to dispatch the connect action with a type `actionTypes[1]`.
```ts
autoConnect: false
```
## debug
Type: `boolean`
When `true` the package shows additional logs.
```ts
debug: ture
```
## protocols
Type: `string | string[]`
Protocols for the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket">WebSocket</a> constructor.
```ts
protocols: 'some protocol'
```
```ts
protocols: ['some protocol']
```
## shouldReconnect
Type: `((event: CloseEvent) => boolean) | boolean` - (`true` by default)
When `true` the socket tries to reconnect if `event.code !== 1005`.<br>
When predicate is passed you are able to decide if the socket needs to be reconnected.
```ts
shouldReconnect: false
```
## reconnectionInterval
Type: `number | number[]` - (`1000` by default)
<b>In milliseconds.</b><br>
When array each new connection uses the next number from the array for a timeout to avoid DDOSing a server.
```ts
reconnectionInterval: 1000
```
When reconnection count reaches the last array element it uses it each the next time.<br>
When the socket connects back the next reconnection loop will start from the `0` index.
```ts
reconnectionInterval: [0, 1000, 2000, 3000, 4000, 5000, 10000]
```
## shouldOpen
Type: `((req: Req) => boolean) | boolean` - (`false` by default)
Req is a template of the generic MiddlewareOptions type
When `true` the socket opens on any `send` action if connection is closed`.<br>
When predicate is passed you are able to decide if socket needs to be open.
```ts
shouldOpen: true
```
When predicate is passed you are able to decide when socket needs to be open.
```ts
shouldOpen: (req: SomeReq) => req.method === 'load_session'
```
## shouldClose
Type: `((res: DRes) => boolean) | boolean` - (`false` by default)
`DRes` is a templates of the generic `MiddlewareOptions` type
When `true` the socket closes connection after each response from the server.<br>
When predicate is passed you are able to decide when the socket needs to be closed.
```ts
shouldClose: true
```
```ts
shouldClose: (res: SomeDeserializedRes) => res.method === 'logout'
```
## serialize
Type: `(req: Req) => SReq`<br>
`Req` and `SReq` are templates of the generic `MiddlewareOptions` type
The format function gets called to prepare the data to get submitted to the server. For example, `camelCase` to `snake_case` conversion.
```ts
serialize: req => {
return {
...req,
time: Date.now()
}
}
```
## deserialize
Type: `(res: Res) => DRes`<br>
`Res` and `DRes` are templates of the generic `MiddlewareOptions` type
The format function gets called to prepare the message to get submitted to the `onMessage` callback. For example, `snake_case` to `camelCase` conversion.
```ts
deserialize: res => {
return res.data
}
```
---
## Usage
### Connecting
```ts
const SOCKET_SEND = 'SCOKET_SEND';
const SOCKET_CONNECT = 'SOCKET_CONNECT';
const SOCKET_DISCONNECT = 'SOCKET_DISCONNECT';
const otpions = {
...
actionTypes: [SOCKET_SEND, SOCKET_CONNECT, SOCKET_DISCONNECT],
...
};
const connectAction = () => ({ type: SOCKET_CONNECT });
dispatch(connectAction());
```
### Disconnecting
```ts
import { CloseAction } from 'redux-ws-middleware';
const SOCKET_SEND = 'SCOKET_SEND';
const SOCKET_CONNECT = 'SOCKET_CONNECT';
const SOCKET_DISCONNECT = 'SOCKET_DISCONNECT';
const otpions = {
...
actionTypes: [SOCKET_SEND, SOCKET_CONNECT, SOCKET_DISCONNECT],
...
};
const disconnectAction = (code?: number): CloseAction<typeof SOCKET_DISCONNECT> => ({
type: SOCKET_DISCONNECT,
payload: { code }
});
OR
dispatch(disconnectAction(1000));
```
The `disconnectAction` can return:<br>
```ts
{
type: SOCKET_DISCONNECT,
code
}
```
```ts
{
type: SOCKET_DISCONNECT,
payload: { code }
}
```
```ts
{
type: SOCKET_DISCONNECT,
data: { code }
}
```
(all these are supported by `CloseAction<typeof SOCKET_DISCONNECT>` type)
### Sending data
The data can be sent in `payload` OR in `data` key.
```ts
import { SendAction } from 'redux-ws-middleware';
const GET_POSTS = 'GET_POSTS_REQUEST';
const otpions = {
...
actionTypes: [new RegExp(/_REQUEST$/)],
...
}
const getPostsAction = (offset: number, limit: number): SendAction<typeof GET_POSTS> => ({
type: GET_POSTS,
payload: { offset, limit }
});
dispatch(getPostsAction(0, 20));
```
The `getPostsAction` can return:<br>
```ts
{
type: GET_POSTS,
payload: { code }
}
```
```ts
{
type: GET_POSTS,
data: { code }
}
```
(all these are supported by `SendAction<typeof GET_POSTS>` type)
---
## MiddlewareOptions declaration
```ts
import { createSocketMiddleware, MiddlewareOptions } from 'redux-ws-middleware';
type ScoketReq = {
method: string
data: Record<string, unknown>
};
type SocketRes = {
[method: string]: Record<string, unknown>
};
type ScoketSerializedReq = {
[method: string]: Record<string, unknown>
};
type SocketDeserializedRes = Record<string, unknown>;
const options: MiddlewareOptions<ScoketReq, SocketRes, ScoketSerializedReq, SocketDeserializedRes> = {
url: 'ws://localhost:3000',
actionTypes: ['SEND', 'CONNECT', 'DISCONNECT'],
completedActionTypes: ['CONNECTED', 'DISCONNECTED'],
// serialize: (req: ScoketReq) => ScoketSerializedReq
serialize: ({ method, data }) => ({ [method]: data }),
// deserialize: (res: SocketRes) => SocketDeserializedRes
deserialize: (res: SocketRes) => res[Object.keys(res)[0]]
};
const socketMiddleware = createSocketMiddleware(options);
```
### Passing own types to MiddlewareOptions type
`MiddlewareOptions` is a generic type.
```ts
MiddlewareOptions<Req, Res, SReq = Req, DRes = Res>
```
`Req` - type of the socket request (required).
`Res` - type of the socket response (required).
`SReq` (default is `Req`) - type of serialized socket request which will be sent to the API (not required).<br>
**This type should be returned from the MiddlewareOptions.serialize function.**
`DRes` (default is `Res`) - type of deserialized socket response which is reachable by using hooks as `data` (not required).<br>
**This type should be returned from the MiddlewareOptions.deserialize function.**