feat: Add idle detection and disconnect collaboration socket (#2629)

This commit is contained in:
Tom Moor
2021-10-06 20:37:21 -04:00
committed by GitHub
parent b39d4aade7
commit be905a6993
6 changed files with 116 additions and 15 deletions

View File

@@ -1,5 +1,5 @@
// @flow
import { HocuspocusProvider } from "@hocuspocus/provider";
import { HocuspocusProvider, WebSocketStatus } from "@hocuspocus/provider";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router";
@@ -9,9 +9,10 @@ import Editor, { type Props as EditorProps } from "components/Editor";
import env from "env";
import useCurrentToken from "hooks/useCurrentToken";
import useCurrentUser from "hooks/useCurrentUser";
import useIdle from "hooks/useIdle";
import usePageVisibility from "hooks/usePageVisibility";
import useStores from "hooks/useStores";
import useToasts from "hooks/useToasts";
import useUnmount from "hooks/useUnmount";
import MultiplayerExtension from "multiplayer/MultiplayerExtension";
import { homeUrl } from "utils/routeHelpers";
@@ -27,12 +28,13 @@ function MultiplayerEditor({ ...props }: Props, ref: any) {
const currentUser = useCurrentUser();
const { presence, ui } = useStores();
const token = useCurrentToken();
const [localProvider, setLocalProvider] = React.useState();
const [remoteProvider, setRemoteProvider] = React.useState();
const [isLocalSynced, setLocalSynced] = React.useState(false);
const [isRemoteSynced, setRemoteSynced] = React.useState(false);
const [ydoc] = React.useState(() => new Y.Doc());
const { showToast } = useToasts();
const isIdle = useIdle();
const isVisible = usePageVisibility();
// Provider initialization must be within useLayoutEffect rather than useState
// or useMemo as both of these are ran twice in React StrictMode resulting in
@@ -91,7 +93,15 @@ function MultiplayerEditor({ ...props }: Props, ref: any) {
provider.on("status", (ev) => ui.setMultiplayerStatus(ev.status));
setRemoteProvider(provider);
setLocalProvider(localProvider);
return () => {
provider?.destroy();
localProvider?.destroy();
setRemoteProvider(null);
ui.setMultiplayerStatus(undefined);
};
}, [history, showToast, t, documentId, ui, presence, token, ydoc]);
const user = React.useMemo(() => {
@@ -116,11 +126,26 @@ function MultiplayerEditor({ ...props }: Props, ref: any) {
];
}, [remoteProvider, user, ydoc]);
useUnmount(() => {
remoteProvider?.destroy();
localProvider?.destroy();
ui.setMultiplayerStatus(undefined);
});
// Disconnect the realtime connection while idle. `isIdle` also checks for
// page visibility and will immediately disconnect when a tab is hidden.
React.useEffect(() => {
if (!remoteProvider) {
return;
}
if (
isIdle &&
!isVisible &&
remoteProvider.status === WebSocketStatus.Connected
) {
remoteProvider.disconnect();
}
if (
(!isIdle || isVisible) &&
remoteProvider.status === WebSocketStatus.Disconnected
) {
remoteProvider.connect();
}
}, [remoteProvider, isIdle, isVisible]);
if (!extensions.length) {
return null;