Handle SSE event stream reconnection (#17523)
This PR handles SSE event stream edge cases. - When a pod restarts, the front clients have to reconnect to SSE - When the dev server restarts or is hot reloaded, the front client has to reconnect to SSE - When redis server restarts or the redis key is cleared for any reason, the server has to recreate the event stream in redis, this can happen when navigating for example. - Log in / log out flow With this PR we avoid error messages in the front end due to TTL or pod crash, we implement a resilient way of reconnecting silently. To avoid DDoSing our servers if pods crash or a full restart of the cluster is made, we evenly space retry attempts to reconnect from all the clients, to avoid n clients reconnection at the same time, we use a random wait time between 0 and a constant max wait time (set to 2 mins for now). This is the cheapest and most effective solution, clients who want to force reconnect have to refresh or navigate to another page. Fixes https://github.com/twentyhq/core-team-issues/issues/2045
This commit is contained in:
@@ -7,6 +7,7 @@ import { CustomException } from 'src/utils/custom-exception';
|
||||
export enum EventStreamExceptionCode {
|
||||
EVENT_STREAM_ALREADY_EXISTS = 'EVENT_STREAM_ALREADY_EXISTS',
|
||||
NOT_AUTHORIZED = 'NOT_AUTHORIZED',
|
||||
EVENT_STREAM_DOES_NOT_EXIST = 'EVENT_STREAM_DOES_NOT_EXIST',
|
||||
}
|
||||
|
||||
const getEventStreamExceptionUserFriendlyMessage = (
|
||||
@@ -14,6 +15,7 @@ const getEventStreamExceptionUserFriendlyMessage = (
|
||||
) => {
|
||||
switch (code) {
|
||||
case EventStreamExceptionCode.EVENT_STREAM_ALREADY_EXISTS:
|
||||
case EventStreamExceptionCode.EVENT_STREAM_DOES_NOT_EXIST:
|
||||
return msg`Failed to receive real time updates.`;
|
||||
case EventStreamExceptionCode.NOT_AUTHORIZED:
|
||||
return msg`You are not authorized to perform this action.`;
|
||||
|
||||
@@ -157,23 +157,12 @@ export class EventStreamService implements OnModuleInit {
|
||||
}
|
||||
|
||||
async isAuthorized({
|
||||
workspaceId,
|
||||
eventStreamChannelId,
|
||||
authContext,
|
||||
streamData,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
eventStreamChannelId: string;
|
||||
authContext: SerializableAuthContext;
|
||||
streamData: EventStreamData;
|
||||
}): Promise<boolean> {
|
||||
const streamData = await this.getStreamData(
|
||||
workspaceId,
|
||||
eventStreamChannelId,
|
||||
);
|
||||
|
||||
if (!isDefined(streamData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isDefined(authContext.userWorkspaceId)) {
|
||||
return (
|
||||
streamData.authContext.userWorkspaceId === authContext.userWorkspaceId
|
||||
@@ -262,7 +251,7 @@ export class EventStreamService implements OnModuleInit {
|
||||
return `workspace:${workspaceId}:activeStreams`;
|
||||
}
|
||||
|
||||
private async getStreamData(
|
||||
async getStreamData(
|
||||
workspaceId: string,
|
||||
eventStreamChannelId: string,
|
||||
): Promise<EventStreamData | undefined> {
|
||||
|
||||
Reference in New Issue
Block a user