Skip to content

Instantly share code, notes, and snippets.

@0bx
Last active April 29, 2021 09:04
Show Gist options
  • Save 0bx/fa581fe079611f8225e1e3514a168570 to your computer and use it in GitHub Desktop.
Save 0bx/fa581fe079611f8225e1e3514a168570 to your computer and use it in GitHub Desktop.
Redux aware WebSocket client
/**
* Redux aware WebSocket client compatible with spring-boot-starter-websocket configuration
* It uses Stomp client over SockJS socket implementation. It allows for providing map of topic
* names to their respective handlers/callbacks. Handlers are called with custom props object
* that mixes Stomp properties with Redux state/dispatch.
*
* @author: https://github.com/0bx
**/
// Example usage:
//
// new WebSocketHandler({
// store,
// sockJsEndpoint: 'http://localhost:4000/ws',
// subscriptions: {
// '/topic/hello': ({ data, ack, nack, state, dispatch }) => {
// // dispatch(someReduxAction({foo: !state.value, bar: data}))
// }
// },
// onStompError: (frame) => {
// console.error('Stomp error', frame)
// }
// })
import { Dispatch, Store } from 'redux';
import * as SockJS from 'sockjs-client';
import { Client, StompHeaders, IFrame } from '@stomp/stompjs'
/**
* Topic destination/path
*/
type Destination = string
/**
* Properties for Message Handler function
*/
interface HandlerProps<Data, RootState> {
state: RootState
dispatch: Dispatch
data: Data
headers: StompHeaders
ack: (headers?: StompHeaders) => void
nack: (headers?: StompHeaders) => void
}
/**
* Message Handler function
*/
type Handler<Data, RootState> = (props: HandlerProps<Data, RootState>) => void
/**
* Subscriptions
*/
export type Subscriptions = Record<Destination, Handler<any, any>>
/**
* Websocket Endpoint Props
*/
export interface WebSocketClientProps<RootState> {
store: Store<RootState>
sockJsEndpoint: string
subscriptions: Subscriptions
onStompError: (receipt: IFrame) => void
}
/**
* Websocket Endpoint Handler
*/
export class WebSocketClient<RootState> {
// Redux Store
private props: WebSocketClientProps<RootState>
// StompClient
private client: Client
constructor(props: WebSocketClientProps<RootState>) {
this.props = props
this.client = new Client({
onConnect: () => {
this.onConnect()
},
onStompError: (receipt: IFrame) => {
props.onStompError(receipt)
},
webSocketFactory: () => {
return new SockJS(props.sockJsEndpoint);
}
});
// Connect
this.client.activate()
}
onConnect() {
const { store, subscriptions } = this.props
Object.entries(subscriptions).forEach(([destination, handler]) => {
this.client.subscribe(destination, (message) => {
const { ack, nack, headers } = message
handler({
ack,
nack,
data: JSON.parse(JSON.stringify(message.body)),
headers,
state: store.getState(),
dispatch: store.dispatch
})
})
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment