PromucFlow_constructor/app/client/src/sagas/WebsocketSagas.ts
Rishabh Saxena a0d2e8533d
Initialise comments (#3328)
* Initial scaffolding for comments CRUD APIs

* add actions

* add assets

* state management for existing comments and creating new

* add ui components

* add overlay comments wrapper to baseWidget

* add toggle comment mode button at editor header

* trigger tests

* Disallow commenting as someone else

* Add applicationId for comments

* lint

* Add overlay blacklist to prevent component interaction while adding comments

* Comment thread style updates

* Placeholder comment context menu

* Controlled comment thread visibility for making new comments visible by default

* Update comment type description

* Reset input on save

* Resolve comment thread button ui

* fix close on esc key, dont create new comment on outside click

* Submit on enter

* add emoji picker

* Attempt at adding a websocket server in Java

* CRUD APIs for comment threads

* Add API for getting all threads in application

* Move types to a separate file

* Initial commit for real time server (RTS)

* Add script to start RTS

* Fix position property

* Use create comment thread API

* Use add comment to thread API

* Add custom cursor

* Dispatch logout init on 401 errors

* Allow CORS for real time connection

* Add more logs to RTS

* Fix construction of MongoClient

* WIP: Real time comments

* Enable comments

* Minor updates

* Read backend API base URL from environment

* Escape to reset comments mode

* Set popover position as auto and boundary as scroll parent

* Disable warning

* Added permissions for comment threads

* Add resolved API for comment threads

* Migration to set commenting permission on existing apps

* Fix updates bringing the RTS down

* Show view latest button, scroll to bottom on creating a new comment

* Cleanup comment reducer

* Move to typescript for RTS

* Add missing server.ts and tsconfig files

* Resolve / unresolve comment

* Scaffold app comments

* Minor fixes: comment on top of all widgets, add toggle button at viewer header

* Reconnect socket on creating a new app, set connected status in store

* Retry socket connection flow

* Integration tests for comments with api mocks using msw

* Fix circular depependency

* rm file

* Minor cleanup and comments

* Minor refactors: move isScrolledToBottom to common hooks, decouple prevent interactions overlay from comments wrapper

* Use policies when pushing updates in RTS

* ENV var to set if comments are enabled

* Fix: check if editor/viewer is initialised before waiting for init action

* Add tests for comments reducer

* Revert "ENV var to set if comments are enabled"

This reverts commit 988efeaa69d378d943a387e1e73510334958adc5.

* Enable comments for users with appsmith email

* lint

* fix

* Try running a socket.io server inside backend

* Update comment reducer tests

* Init mentions within comments

* Fix comment thread updates with email rooms

* Minor fixes

* Refactors / review suggestions

* lint

* increase cache limit for builds

* Comment out tests for feature that's under development

* Add Dockerfile for RTS

* Fix policies missing for first comment in threads

* Use draftJS for comments input with mentions support

* fix fixtures

* Use thread's policies when querying for threads

* Update socket.io to v4

* Add support for richer body with mentions

* Update comment body type to RawDraftContentState

* fix stale method

* Fix mentions search

* Minor cleanups

* Comment context menu and thread UI updates

* revert: Scaffold app comments

* Yarn dependencies

* Delete comment using id api added

* Init app comments

* Add test for creating thread

* Api for delete comment with id

* Test comment creation response and policies

* Copy comment links

* Fix reset editor state

* Delete valid comment testcase added

* Delete comment TC : code refactor

* Don't allow creating comments with an empty body

* Pin comments WIP[]

* Ignore dependency-reduced-pom.xml files from VCS

* Cleanup of some dev-only files, for review

* Delete comment

* Update socket.io to v4 in RTS

* Pin and resolve comment thread object added in commentThread

* Pin and resolve comment thread object added in commentThread

* Update comment thread API

* Added creationTime and updationTime in comment thread response

* Added creationTime and updationTime in comment thread response

* Added human readable id to comment threads, fallback to username for null name in user document

* Refactor

* lint

* fix test, rm duplicate selector

* comment out saga used for dev

* CommentThread viewed status, username fallback for getName=null, username field added in pin & resolve status

* lint

* trigger tests

Co-authored-by: Shrikant Sharat Kandula <shrikant@appsmith.com>
Co-authored-by: Abhijeet <abhi.nagarnaik@gmail.com>
2021-04-29 16:03:51 +05:30

135 lines
3.3 KiB
TypeScript

import { io } from "socket.io-client";
import { eventChannel } from "redux-saga";
import {
fork,
take,
call,
cancel,
put,
delay,
select,
} from "redux-saga/effects";
import {
ReduxActionTypes,
ReduxSagaChannels,
} from "constants/ReduxActionConstants";
import { ANONYMOUS_USERNAME } from "constants/userConstants";
import {
WEBSOCKET_EVENTS,
websocketDisconnectedEvent,
websocketConnectedEvent,
} from "constants/WebsocketConstants";
import { commentEvent } from "actions/commentActions";
import {
setIsWebsocketConnected,
retrySocketConnection,
} from "actions/websocketActions";
import { areCommentsEnabledForUser } from "selectors/commentsSelectors";
function connect() {
const socket = io();
return new Promise((resolve) => {
socket.on("connect", () => {
resolve(socket);
});
});
}
function subscribe(socket: any) {
return eventChannel((emit) => {
socket.onAny((event: any, ...args: any) => {
emit({
type: event,
payload: args,
});
});
socket.on("disconnect", () => {
emit(websocketDisconnectedEvent());
});
socket.on("connect", () => {
emit(websocketConnectedEvent());
});
return () => {
socket.disconnect();
};
});
}
function* read(socket: any) {
const channel = yield call(subscribe, socket);
while (true) {
const action = yield take(channel);
switch (action.type) {
case WEBSOCKET_EVENTS.DISCONNECTED:
yield put(setIsWebsocketConnected(false));
break;
case WEBSOCKET_EVENTS.CONNECTED:
yield put(setIsWebsocketConnected(true));
break;
default:
yield put(commentEvent(action));
}
}
}
function* write(socket: any) {
while (true) {
const { payload } = yield take(ReduxSagaChannels.WEBSOCKET_WRITE_CHANNEL);
// reconnect to reset connection at the server
if (payload.type === WEBSOCKET_EVENTS.RECONNECT) {
socket.disconnect().connect();
} else {
// handle other writes here:
// socket.emit(payload.type, payload.payload);
}
}
}
function* handleIO(socket: any) {
yield fork(read, socket);
yield fork(write, socket);
}
function* flow() {
while (true) {
const { payload } = yield take([
ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS,
ReduxActionTypes.RETRY_WEBSOCKET_CONNECTION,
]);
try {
/**
* Incase the socket is disconnected due to network latencies
* it reuses the same instance so we don't need to bind it again
* this is verified using the reconnect flow
* We only need to retry incase the socket connection isn't made
* in the first attempt itself
*/
if (payload.name !== ANONYMOUS_USERNAME) {
const commentsEnabled = yield select(areCommentsEnabledForUser);
if (!commentsEnabled) return;
const socket = yield call(connect);
const task = yield fork(handleIO, socket);
yield put(setIsWebsocketConnected(true));
yield take(ReduxActionTypes.LOGOUT_USER_INIT);
yield cancel(task);
socket.disconnect();
}
} catch (e) {
// this has to be non blocking
yield fork(function*() {
yield delay(3000);
yield put(retrySocketConnection());
});
}
}
}
export default function* rootSaga() {
yield fork(flow);
}