Search code examples
javascriptreactjsreduxwebsocketredux-saga

Reusing websocket connection object in Redux Saga


I have a problem. I am connecting to the server with the help of websocket inside React Redux eventChannel. I have no problems with receivng event from the server but i cant sent event to the server. I understand, that i need to get websocket connection object which i create inside eventChannel to send event from react component for example.

export function* wsWatcher() {
    yield takeEvery("LOGIN_SUCCESS", wsConnectionWorker);
}

function* wsConnectionWorker() {
    const channel: EventChannel<boolean> = yield call(initWebsocket);
    while (true) {
        const action: Action<any> = yield take(channel);
        yield put(action);
    }
}

function initWebsocket(): EventChannel<any> {
    return eventChannel((emitter) => {

    let id: string | undefined;
    let intervalId: number;

    const initConnection = () => {
        const connectionUrl: string = "ws://localhost:4000" 
            
        let ws = new WebSocket(connectionUrl);
        
        ws.onopen = () => {
            id && ws.send(JSON.stringify({ type: "GET_USER_ID", id }));

            intervalId = window.setInterval(() => {
                ws.send(JSON.stringify({ type: "PING" }))
            }, 30000)
        };

        ws.onclose = (e) => {
            console.log("Reconnect in 4s");
            intervalId && clearInterval(intervalId);
            id && setTimeout(initConnection, 4000); 
        };
    };

    initConnection();

    return () => {
        console.log("Socket off");
    };
});
}

Thanks in advance


Solution

  • You can create a variable in the module's scope and store connection object in it after first init. Later whenever you need the connection, just use this variable.

    This works since modules are cached in nodejs. That is, the code in modules in run only when they're first imported, and later imports use the cached module.

    For further reference, read more: Modules Caching

    You can also have a function for this, like getConnection function in this code. Whenever you need the websocket connection, call this function and it'll create connection only once.

    export function* wsWatcher() {
        yield takeEvery("LOGIN_SUCCESS", wsConnectionWorker);
    }
    
    function* wsConnectionWorker() {
        const channel: EventChannel<boolean> = yield call(getConnection);
        while (true) {
            const action: Action<any> = yield take(channel);
            yield put(action);
        }
    }
    
    // store the EventChannel after first init
    let wsConnection: EventChannel<any> = null;
    
    // create WS connection on first call
    // reuse that connection in further calls
    function getConnection(): EventChannel<any>{
      if(!wsConnection){
        wsConnection = initWebsocket();
      return wsConnection;
    }
    
    function initWebsocket(): EventChannel<any> {
        return eventChannel((emitter) => {
    
        let id: string | undefined;
        let intervalId: number;
    
        const initConnection = () => {
            const connectionUrl: string = "ws://localhost:4000" 
                
            let ws = new WebSocket(connectionUrl);
            
            ws.onopen = () => {
                id && ws.send(JSON.stringify({ type: "GET_USER_ID", id }));
    
                intervalId = window.setInterval(() => {
                    ws.send(JSON.stringify({ type: "PING" }))
                }, 30000)
            };
    
            ws.onclose = (e) => {
                console.log("Reconnect in 4s");
                intervalId && clearInterval(intervalId);
                id && setTimeout(initConnection, 4000); 
            };
        };
    
        initConnection();
    
        return () => {
            console.log("Socket off");
        };
    });
    }