https://github.com/nichitaa/rxjs-ws
WebSocket library to simply integrate with RxJS
https://github.com/nichitaa/rxjs-ws
observables rxjs streams websocket
Last synced: about 1 year ago
JSON representation
WebSocket library to simply integrate with RxJS
- Host: GitHub
- URL: https://github.com/nichitaa/rxjs-ws
- Owner: nichitaa
- License: mit
- Created: 2023-07-26T11:36:42.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2024-09-06T06:56:26.000Z (almost 2 years ago)
- Last Synced: 2025-03-10T19:42:38.378Z (over 1 year ago)
- Topics: observables, rxjs, streams, websocket
- Language: TypeScript
- Homepage:
- Size: 86.9 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
Awesome Lists containing this project
README
# rxjs-ws
WebSocket library to simply integrate with RxJS.
> Inspired by: [rxjs-websockets](https://github.com/insidewhy/rxjs-websockets). It is an extension of it, that covers most of WebSocket manipulation and provides flexible utilities/streams to work with WebSocket events.
### Features 🎯
- Manage WebSocket connection / provides real time connection status
- Manage reconnection / retries (via rxjs operators) / easy API to conditionally reconnect
- Custom serialization / deserialization
- ✨ `getStreamHandler` - Powerful utility to create stream handlers from WebSocket events
For example on application side you have WS events `events/posts` & `events/users`. You can create handlers for each of them, for example:
```ts
const postsHandler = handler.getStreamHandler({ default: [] })
const usersHandler = handler.getStreamHandler({ default: undefined, awaitReadyStatusBeforeNextRequest: false })
```
### Getting Started 🎉
#### Peer Dependencies
* `rxjs`: `^7.x`,
### Installation
```bash
npm install @nichitaa/rxjs-ws
```
### Create `WebSocketConnector`
```ts
const connector = new WebSocketConnector({
url: 'wss://...',
});
```
Default WebSocket instance is `window.WebSocket`
Params:
- `url`, `protocols` - [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
- `createWebSocketInstance` - create custom WebSocket instance (for example socket.io)
- `serializer` - provide custom serializer function (defaults to `JSON.stringify`)
- `deserializer` - provide custom deserializer function (defaults to `JSON.parse`)
### Manage WebSocket connection
With optionally `retryConfig`
```ts
connector.connect({
retryConfig: {
count: 10,
delay: of(true).pipe(delay(1000)),
onSuccess: () => {
console.log('successfully retried websocket connection')
}
}
});
```
Params:
- `count`, `delay` - [RxJS `RetryConfig` docs](https://rxjs.dev/api/index/interface/RetryConfig)
- `onSuccess` - callback called when retry was successfully
Disconnect.
```ts
connector.disconnect();
```
Force trigger reconnection, might be usefully on stale connections.
```ts
connector.forceReconnect("optional custom message");
// OR
connector.forceReconnect();
```
Subscribe to status changes.
```ts
import { CONN_STATUS } from '@nichitaa/rxjs-ws';
connector.status$.subscribe((status) => {
if (CONN_STATUS.connected) {
console.log('just connected')
}
})
```
`status$` is stream with possible values (`CONN_STATUS`)
- `uninitialized` - connection not yet initialized
- `connected` - connection successfully established
- `reconnecting` - reconnection is in progress
- `disconnected` - dropped socket connection (not active)
Send events (aka requests).
```ts
type Request = {
payload: string;
streamId: number
}
conn.send({ payload: '...', streamId: 1 });
```
Receive incoming messages.
```ts
// create a custom stream that filteres events for a specific method
const users$ = connector
.messages<{ method: string; data: string }>()
.pipe(filter((event) => event.method === 'v1/users/events'));
// subscribe to users events
users$.subscribe((event) => {
console.log('users just received: ', event);
});
```
✨ Utility to create streams - `getStreamHandler`
```ts
// create a custom stream that filteres events for a specific method
import { STREAM_STATUS } from '@nichitaa/rxjs-ws';
const usersHandler = connector.getStreamHandler({
default: [],
})
const scanUsers = (request) = (source$) => {
return source$.pipe(
// filter only users types of events
filter(event => event.method === request.method), // e.g.: request.method === 'v1/users'
// throw error if so that handler can catch it
tap((event) => {
if (isError(event)) throw x
}),
// for example accumulate them
scan(
(acc, event) => {
if (event.isLastMessage) acc.snapshotEnd = true;
acc.events.push(event.user);
return acc;
},
{ snapshotEnd: false, events: [] },
),
filter((x) => x.snapshotEnd),
map((x) => x.events),
);
};
// send a request
usersHandler.send({
request: { method: 'v1.users' },
transformResponse: scanUsers,
});
usersHandler.$.subscribe(({ response, request, status, error }) => {
// react to stream emissions
if (status === STREAM_STATUS.loading) {
// loading
}
if (status === STREAM_STATUS.ready && error === undefined) {
const userNames = response.map(x => x.username);
}
if (status === STREAM_STATUS.ready && error !== undefined) {
console.error(error);
}
})
```
Arguments of the `getStreamHandler` function:
- `default` - default `response` (defaults to `undefined`)
- `transformRequests` - custom RxJS operator that can change the requests that are processed/send to WebSocket (defaults to [`identity`](https://rxjs.dev/api/index/function/identity))
- `resetResponseOnNextRequest` - reset `response` back to `default` for the next request (`handler.send({request: {...}})`) (defaults to `true`)
- `resetErrorOnNextRequest` - similar to `resetResponseOnNextRequest`, but resets `error` field (defaults to `true`)
- `awaitReadyStatusBeforeNextRequest` - allows to process next request without waiting for at least one `ready` status emission from current processed request
Because `handler.send()` can be called in any order at any time, it is important to process them sequentially, `awaitReadyStatusBeforeNextRequest` allows to
drop the execution (waiting for response) for current request and continue with execution of the new request, for example:
- with `awaitReadyStatusBeforeNextRequest: false`:
- ```ts
const handler = connector.getStreamHandler({
awaitReadyStatusBeforeNextRequest: false
})
handler.send({request: 1});
handler.send({request: 2});
handler.send({request: 3});
```
In the above example `handler.$` will emit:
1. `{status: 'uninitialized', response: undefined}`
2. `{status: 'loading', request: 1, response: undefined}`
3. `{status: 'loading', request: 2, response: undefined}`
4. `{status: 'loading', request: 3, response: undefined}`
5. `{status: 'ready', request: 3, response: undefined}` When response for 3rd request will arrive, and all previous request will be correctly send
- with `awaitReadyStatusBeforeNextRequest: true`:
- ```ts
const handler = connector.getStreamHandler({})
handler.send({request: 1});
handler.send({request: 2});
```
In the above example `handler.$` will emit:
1. `{status: 'uninitialized', response: undefined}`
2. `{status: 'loading', request: 1, response: undefined}`
3. `{status: 'ready', request: 1, response: ... }`
4. `{status: 'loading', request: 2, response: undefined}`
5. `{status: 'ready', request: 2, response: ...}` All requests are send in order and each next request is started only after current has at least one emission with status `ready`