fix: Incorrect error shown to user when connection limit is reached (#5695)

This commit is contained in:
Tom Moor
2023-08-16 15:39:56 -04:00
committed by GitHub
parent fd600ced09
commit 5f00b4f744
6 changed files with 89 additions and 30 deletions

View File

@@ -14,15 +14,48 @@ function ConnectionStatus() {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const codeToMessage = {
1009: {
title: t("Document is too large"),
body: t(
"This document has reached the maximum size and can no longer be edited"
),
},
4401: {
title: t("Authentication failed"),
body: t("Please try logging out and back in again"),
},
4403: {
title: t("Authorization failed"),
body: t("You may have lost access to this document, try reloading"),
},
4503: {
title: t("Too many users connected to document"),
body: t("Your edits will sync once other users leave the document"),
},
};
const message = ui.multiplayerErrorCode
? codeToMessage[ui.multiplayerErrorCode]
: undefined;
return ui.multiplayerStatus === "connecting" || return ui.multiplayerStatus === "connecting" ||
ui.multiplayerStatus === "disconnected" ? ( ui.multiplayerStatus === "disconnected" ? (
<Tooltip <Tooltip
tooltip={ tooltip={
<Centered> message ? (
<strong>{t("Server connection lost")}</strong> <Centered>
<br /> <strong>{message.title}</strong>
{t("Edits you make will sync once youre online")} <br />
</Centered> {message.body}
</Centered>
) : (
<Centered>
<strong>{t("Server connection lost")}</strong>
<br />
{t("Edits you make will sync once youre online")}
</Centered>
)
} }
placement="bottom" placement="bottom"
> >

View File

@@ -135,13 +135,10 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
}); });
provider.on("close", (ev: MessageEvent) => { provider.on("close", (ev: MessageEvent) => {
if ("code" in ev.event && ev.event.code === 1009) { if ("code" in ev.event) {
provider.shouldConnect = false; provider.shouldConnect =
showToast( ev.event.code !== 1009 && ev.event.code !== 4401;
t( ui.setMultiplayerStatus("disconnected", ev.event.code);
"Sorry, this document is too large - edits will no longer be persisted."
)
);
} }
}); });
@@ -164,9 +161,11 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
); );
} }
provider.on("status", (ev: ConnectionStatusEvent) => provider.on("status", (ev: ConnectionStatusEvent) => {
ui.setMultiplayerStatus(ev.status) if (ui.multiplayerStatus !== ev.status) {
); ui.setMultiplayerStatus(ev.status, undefined);
}
});
setRemoteProvider(provider); setRemoteProvider(provider);
@@ -177,7 +176,7 @@ function MultiplayerEditor({ onSynced, ...props }: Props, ref: any) {
provider?.destroy(); provider?.destroy();
void localProvider?.destroy(); void localProvider?.destroy();
setRemoteProvider(null); setRemoteProvider(null);
ui.setMultiplayerStatus(undefined); ui.setMultiplayerStatus(undefined, undefined);
}; };
}, [ }, [
history, history,

View File

@@ -69,6 +69,9 @@ class UiStore {
@observable @observable
multiplayerStatus: ConnectionStatus; multiplayerStatus: ConnectionStatus;
@observable
multiplayerErrorCode?: number;
constructor() { constructor() {
// Rehydrate // Rehydrate
const data: Partial<UiStore> = Storage.get(UI_STORE) || {}; const data: Partial<UiStore> = Storage.get(UI_STORE) || {};
@@ -133,8 +136,12 @@ class UiStore {
}; };
@action @action
setMultiplayerStatus = (status: ConnectionStatus): void => { setMultiplayerStatus = (
status: ConnectionStatus,
errorCode?: number
): void => {
this.multiplayerStatus = status; this.multiplayerStatus = status;
this.multiplayerErrorCode = errorCode;
}; };
@action @action

View File

@@ -0,0 +1,4 @@
export const TooManyConnections = {
code: 4503,
reason: "Too Many Connections",
};

View File

@@ -6,28 +6,36 @@ import {
import env from "@server/env"; import env from "@server/env";
import Logger from "@server/logging/Logger"; import Logger from "@server/logging/Logger";
import { trace } from "@server/logging/tracing"; import { trace } from "@server/logging/tracing";
import { TooManyConnections } from "./CloseEvents";
@trace() @trace()
export class ConnectionLimitExtension implements Extension { export class ConnectionLimitExtension implements Extension {
/** /**
* Map of documentId -> connection count * Map of documentId -> connection count
*/ */
connectionsByDocument: Map<string, number> = new Map(); connectionsByDocument: Map<string, Set<string>> = new Map();
/** /**
* onDisconnect hook * onDisconnect hook
* @param data The disconnect payload * @param data The disconnect payload
*/ */
onDisconnect(data: onDisconnectPayload) { onDisconnect(data: onDisconnectPayload) {
const { documentName } = data; const { documentName, socketId } = data;
const currConnections = this.connectionsByDocument.get(documentName) || 0; const connections = this.connectionsByDocument.get(documentName);
const newConnections = currConnections - 1; if (connections) {
this.connectionsByDocument.set(documentName, newConnections); connections.delete(socketId);
if (connections.size === 0) {
this.connectionsByDocument.delete(documentName);
} else {
this.connectionsByDocument.set(documentName, connections);
}
}
Logger.debug( Logger.debug(
"multiplayer", "multiplayer",
`${newConnections} connections to "${documentName}"` `${connections?.size} connections to "${documentName}"`
); );
return Promise.resolve(); return Promise.resolve();
@@ -40,23 +48,24 @@ export class ConnectionLimitExtension implements Extension {
onConnect(data: onConnectPayload) { onConnect(data: onConnectPayload) {
const { documentName } = data; const { documentName } = data;
const currConnections = this.connectionsByDocument.get(documentName) || 0; const connections =
if (currConnections >= env.COLLABORATION_MAX_CLIENTS_PER_DOCUMENT) { this.connectionsByDocument.get(documentName) || new Set();
if (connections?.size >= env.COLLABORATION_MAX_CLIENTS_PER_DOCUMENT) {
Logger.info( Logger.info(
"multiplayer", "multiplayer",
`Rejected connection to "${documentName}" because it has reached the maximum number of connections` `Rejected connection to "${documentName}" because it has reached the maximum number of connections`
); );
// Rejecting the promise will cause the connection to be dropped. // Rejecting the promise will cause the connection to be dropped.
return Promise.reject(); return Promise.reject(TooManyConnections);
} }
const newConnections = currConnections + 1; connections.add(data.socketId);
this.connectionsByDocument.set(documentName, newConnections); this.connectionsByDocument.set(documentName, connections);
Logger.debug( Logger.debug(
"multiplayer", "multiplayer",
`${newConnections} connections to "${documentName}"` `${connections.size} connections to "${documentName}"`
); );
return Promise.resolve(); return Promise.resolve();

View File

@@ -117,6 +117,14 @@
"Type a command or search": "Type a command or search", "Type a command or search": "Type a command or search",
"Are you sure you want to permanently delete this entire comment thread?": "Are you sure you want to permanently delete this entire comment thread?", "Are you sure you want to permanently delete this entire comment thread?": "Are you sure you want to permanently delete this entire comment thread?",
"Are you sure you want to permanently delete this comment?": "Are you sure you want to permanently delete this comment?", "Are you sure you want to permanently delete this comment?": "Are you sure you want to permanently delete this comment?",
"Document is too large": "Document is too large",
"This document has reached the maximum size and can no longer be edited": "This document has reached the maximum size and can no longer be edited",
"Authentication failed": "Authentication failed",
"Please try logging out and back in again": "Please try logging out and back in again",
"Authorization failed": "Authorization failed",
"You may have lost access to this document, try reloading": "You may have lost access to this document, try reloading",
"Too many users connected to document": "Too many users connected to document",
"Your edits will sync once other users leave the document": "Your edits will sync once other users leave the document",
"Server connection lost": "Server connection lost", "Server connection lost": "Server connection lost",
"Edits you make will sync once youre online": "Edits you make will sync once youre online", "Edits you make will sync once youre online": "Edits you make will sync once youre online",
"Submenu": "Submenu", "Submenu": "Submenu",
@@ -524,7 +532,6 @@
"Viewed {{ count }} times by {{ teamMembers }} people_plural": "Viewed {{ count }} times by {{ teamMembers }} people", "Viewed {{ count }} times by {{ teamMembers }} people_plural": "Viewed {{ count }} times by {{ teamMembers }} people",
"Viewer insights": "Viewer insights", "Viewer insights": "Viewer insights",
"As an admin you can manage if team members can see who has viewed this document": "As an admin you can manage if team members can see who has viewed this document", "As an admin you can manage if team members can see who has viewed this document": "As an admin you can manage if team members can see who has viewed this document",
"Sorry, this document is too large - edits will no longer be persisted.": "Sorry, this document is too large - edits will no longer be persisted.",
"Sorry, the last change could not be persisted please reload the page": "Sorry, the last change could not be persisted please reload the page", "Sorry, the last change could not be persisted please reload the page": "Sorry, the last change could not be persisted please reload the page",
"This template will be permanently deleted in <2></2> unless restored.": "This template will be permanently deleted in <2></2> unless restored.", "This template will be permanently deleted in <2></2> unless restored.": "This template will be permanently deleted in <2></2> unless restored.",
"This document will be permanently deleted in <2></2> unless restored.": "This document will be permanently deleted in <2></2> unless restored.", "This document will be permanently deleted in <2></2> unless restored.": "This document will be permanently deleted in <2></2> unless restored.",