Skip to content

Instantly share code, notes, and snippets.

@diego3g
Created November 17, 2020 13:32
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save diego3g/b11d7c9f63100af250b8748d7fc902d5 to your computer and use it in GitHub Desktop.
Save diego3g/b11d7c9f63100af250b8748d7fc902d5 to your computer and use it in GitHub Desktop.
import Ws from '@adonisjs/websocket-client';
import { toast } from 'react-toastify';
import { eventChannel } from 'redux-saga';
import { all, takeLatest, call, take, put, select } from 'redux-saga/effects';
import { history } from '../../../services';
import {
subscribeUserRequest,
subscribeChatRequest,
subscribeChatSuccess,
closeOpenedChatConnections,
chatConnectionLost,
reconnect,
subscribeUserSuccess,
connectionLost,
} from './actions';
import {
newMessageReceived,
getChatDataRequest,
messageUpdatedSuccess,
} from '../chat/actions';
import { signOutRequest } from '../auth/actions';
import { saveUserData } from '../user/actions';
import { notificationNew } from '../notification/actions';
import { connectionNew } from '../connection/actions';
function connect(ws) {
return eventChannel((emitter) => {
ws.connect();
ws.on('open', () => {
return emitter(reconnect());
});
ws.on('close', (connection) => {
if (connection._connectionState !== 'terminated') {
return emitter(connectionLost());
}
});
return () => {
ws.close();
};
});
}
function* reconnectSocket() {
const connectionStatus = yield select(
(state) => state.websocket.connectionStatus,
);
if (connectionStatus === 'lost') {
const profileId = yield select((state) => state.user.data.id);
if (profileId) {
yield put(subscribeUserRequest(profileId));
}
}
}
function* closeChannels(ws, channel) {
const id = yield select((state) => state.user.data.id);
const userChannel = ws.getSubscription(`user:${id}`);
if (userChannel) {
userChannel.close();
}
const notificationChannel = ws.getSubscription(`notifications:${id}`);
if (notificationChannel) {
notificationChannel.close();
}
const connectionsChannel = ws.getSubscription(`connections:${id}`);
if (connectionsChannel) {
connectionsChannel.close();
}
const chatChannel = yield select((state) => state.websocket.chat);
if (chatChannel) {
chatChannel.close();
}
channel.close();
}
function subscribeUser({ ws, id }) {
return eventChannel((emitter) => {
const channel =
ws.getSubscription(`user:${id}`) || ws.subscribe(`user:${id}`);
channel.on('ready', () => {
return emitter(subscribeUserSuccess());
});
channel.on('logout', ({ forced } = { forced: false }) => {
if (!forced) {
toast.error('Login realizado em outro navegador');
}
return emitter(signOutRequest());
});
channel.on('discordUpdateUser', (data) => {
return emitter(saveUserData(data));
});
return () => {
channel.close();
};
});
}
function subscribeNotification({ ws, id }) {
return eventChannel((emitter) => {
const channel =
ws.getSubscription(`notifications:${id}`) ||
ws.subscribe(`notifications:${id}`);
channel.on('new', (data) => {
return emitter(notificationNew(data));
});
return () => {
channel.close();
};
});
}
function subscribeConnection({ ws, id }) {
return eventChannel((emitter) => {
const channel =
ws.getSubscription(`connections:${id}`) ||
ws.subscribe(`connections:${id}`);
channel.on('new', (data) => {
return emitter(connectionNew(data));
});
return () => {
channel.close();
};
});
}
function* watchNotificationSubscription(ws, { payload: { id } }) {
const channel = yield call(subscribeNotification, { ws, id });
while (true) {
const action = yield take(channel);
yield put(action);
}
}
function* watchConnectionSubscription(ws, { payload: { id } }) {
const channel = yield call(subscribeConnection, { ws, id });
while (true) {
const action = yield take(channel);
yield put(action);
}
}
function* watchUserSubscription(ws, { payload: { id } }) {
const channel = yield call(subscribeUser, { ws, id });
while (true) {
const action = yield take(channel);
yield put(action);
}
}
function subscribeChat(ws, id, type, userId) {
return eventChannel((emitter) => {
ws.on('open', () => {
emitter({ type: 'RECONNECT' });
});
const chat =
ws.getSubscription(`${type}:${id}`) || ws.subscribe(`${type}:${id}`);
chat.on(
'message',
(message) =>
(message.user.user_id !== userId || message.type === 'topic') &&
emitter({ type: 'MESSAGE', payload: message }),
);
chat.on(
'reaction',
(message) =>
(message.reactedBy !== userId || message.type === 'topic') &&
emitter({ type: 'REACTION', payload: message }),
);
chat.on('answer', (message) => {
return (
(message.answeredBy !== userId || message.type === 'topic') &&
emitter({ type: 'ANSWER', payload: message })
);
});
chat.on('close', () => emitter({ type: 'CLOSE' }));
chat.on('error', (error) => error.message);
return () => {
chat.close();
};
});
}
function* chatReconnect(id, type) {
const connectionStatus = yield select(
(state) => state.websocket.connectionStatus,
);
if (connectionStatus !== 'lost') return;
yield put(getChatDataRequest());
yield put(subscribeChatRequest(id, type));
}
function* chatSubscribeRequest(ws, { payload }) {
try {
const signed = yield select((state) => state.auth.signed);
const profileId = yield select((state) => state.user.data.id);
yield put(closeOpenedChatConnections());
if (!signed) return;
const { id, type } = payload;
const chat = subscribeChat(ws, id, type, profileId);
yield put(subscribeChatSuccess(chat));
try {
while (true) {
const action = yield take(chat);
switch (action.type) {
case 'MESSAGE':
yield put(newMessageReceived(action.payload));
break;
case 'REACTION':
yield put(messageUpdatedSuccess(action.payload));
break;
case 'ANSWER':
yield put(messageUpdatedSuccess(action.payload));
break;
case 'CLOSE':
yield put(chatConnectionLost());
break;
case 'RECONNECT':
yield call(chatReconnect, id, type);
break;
default:
break;
}
}
} catch (err) {
chat.close();
}
} catch (err) {
history.push('/dashboard');
}
}
function* closeChatConnections() {
try {
const chat = yield select((state) => state.websocket.chat);
yield call(chat.close);
} catch (err) {
toast.error('Houve um erro ao encerrar a conversa anterior.');
}
}
function* initWebsocket() {
const protocol = process.env.NODE_ENV === 'development' ? 'ws' : 'wss';
const ws = Ws(`${protocol}://${process.env.REACT_APP_WS_URL}`, {
reconnectionAttempts: 15,
});
const token = localStorage.getItem('@Skylab:token');
if (token) {
ws.withApiToken(token);
}
const channel = yield call(connect, ws);
yield all([
takeLatest(subscribeUserRequest().type, watchUserSubscription, ws),
takeLatest(subscribeUserRequest().type, watchNotificationSubscription, ws),
takeLatest(subscribeUserRequest().type, watchConnectionSubscription, ws),
takeLatest(reconnect().type, reconnectSocket),
takeLatest(subscribeChatRequest().type, chatSubscribeRequest, ws),
takeLatest(closeOpenedChatConnections().type, closeChatConnections),
takeLatest(signOutRequest().type, closeChannels, ws, channel),
]);
while (true) {
const action = yield take(channel);
yield put(action);
}
}
export default all([takeLatest(saveUserData().type, initWebsocket)]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment