diff --git a/frontend/src/components/editor-page/editor-pane/editor-pane.tsx b/frontend/src/components/editor-page/editor-pane/editor-pane.tsx index 352208d59..faceaaddc 100644 --- a/frontend/src/components/editor-page/editor-pane/editor-pane.tsx +++ b/frontend/src/components/editor-page/editor-pane/editor-pane.tsx @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -19,6 +19,7 @@ import { useOnImageUploadFromRenderer } from './hooks/image-upload-from-renderer import { useCodeMirrorTablePasteExtension } from './hooks/table-paste/use-code-mirror-table-paste-extension' import { useApplyScrollState } from './hooks/use-apply-scroll-state' import { useCursorActivityCallback } from './hooks/use-cursor-activity-callback' +import { useDisconnectOnUserLoginStatusChange } from './hooks/use-disconnect-on-user-login-status-change' import { useUpdateCodeMirrorReference } from './hooks/use-update-code-mirror-reference' import { useBindYTextToRedux } from './hooks/yjs/use-bind-y-text-to-redux' import { useCodeMirrorYjsExtension } from './hooks/yjs/use-code-mirror-yjs-extension' @@ -56,6 +57,9 @@ export const EditorPane: React.FC = ({ scrollState, onScroll, o useApplyScrollState(scrollState) const messageTransporter = useRealtimeConnection() + + useDisconnectOnUserLoginStatusChange(messageTransporter) + const realtimeDoc = useRealtimeDoc() const editorScrollExtension = useCodeMirrorScrollWatchExtension(onScroll) const tablePasteExtensions = useCodeMirrorTablePasteExtension() diff --git a/frontend/src/components/editor-page/editor-pane/hooks/use-disconnect-on-user-login-status-change.spec.tsx b/frontend/src/components/editor-page/editor-pane/hooks/use-disconnect-on-user-login-status-change.spec.tsx new file mode 100644 index 000000000..08b30d19e --- /dev/null +++ b/frontend/src/components/editor-page/editor-pane/hooks/use-disconnect-on-user-login-status-change.spec.tsx @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import type { LoginUserInfo } from '../../../../api/me/types' +import * as UseApplicationStateModule from '../../../../hooks/common/use-application-state' +import type { ApplicationState } from '../../../../redux/application-state' +import { useDisconnectOnUserLoginStatusChange } from './use-disconnect-on-user-login-status-change' +import type { MessageTransporter } from '@hedgedoc/commons' +import { render } from '@testing-library/react' +import React, { Fragment } from 'react' +import { Mock } from 'ts-mockery' + +jest.mock('../../../../hooks/common/use-application-state') + +describe('use logout on user change', () => { + const TestComponent: React.FC<{ messageTransporter: MessageTransporter }> = ({ messageTransporter }) => { + useDisconnectOnUserLoginStatusChange(messageTransporter) + return + } + + const mockUseApplicationState = (userLoggedIn: boolean) => { + jest + .spyOn(UseApplicationStateModule, 'useApplicationState') + .mockImplementation((fn) => + fn(Mock.of({ user: userLoggedIn ? Mock.of({}) : null })) + ) + } + + let disconnectCallback: jest.Mock + let messageTransporter: MessageTransporter + + beforeEach(() => { + disconnectCallback = jest.fn() + messageTransporter = Mock.of({ disconnect: disconnectCallback }) + }) + + it("doesn't disconnect if user is logged in before", () => { + mockUseApplicationState(true) + render() + expect(disconnectCallback).not.toBeCalled() + }) + + it("doesn't disconnect if user is not logged in before", () => { + mockUseApplicationState(false) + render() + expect(disconnectCallback).not.toBeCalled() + }) + + it('disconnects if user switches from logged in to logged out', () => { + mockUseApplicationState(true) + const view = render() + expect(disconnectCallback).not.toBeCalled() + + mockUseApplicationState(false) + view.rerender() + expect(disconnectCallback).toBeCalled() + }) + + it('disconnects if user switches from logged out to logged in', () => { + mockUseApplicationState(false) + const view = render() + expect(disconnectCallback).not.toBeCalled() + + mockUseApplicationState(true) + view.rerender() + expect(disconnectCallback).toBeCalled() + }) +}) diff --git a/frontend/src/components/editor-page/editor-pane/hooks/use-disconnect-on-user-login-status-change.ts b/frontend/src/components/editor-page/editor-pane/hooks/use-disconnect-on-user-login-status-change.ts new file mode 100644 index 000000000..329871c86 --- /dev/null +++ b/frontend/src/components/editor-page/editor-pane/hooks/use-disconnect-on-user-login-status-change.ts @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { useApplicationState } from '../../../../hooks/common/use-application-state' +import type { MessageTransporter } from '@hedgedoc/commons' +import { useEffect, useRef } from 'react' + +/** + * Disconnects the given {@link MessageTransporter message transporter} if the user status changes through log-in or log-out. + * + * @param messageTransporter the message transporter to disconnect + */ +export const useDisconnectOnUserLoginStatusChange = (messageTransporter: MessageTransporter): void => { + const previousIsLoggedIn = useRef() + const isLoggedIn = useApplicationState((state) => state.user !== null) + useEffect(() => { + if (previousIsLoggedIn.current === undefined) { + previousIsLoggedIn.current = isLoggedIn + return + } + if (previousIsLoggedIn.current === isLoggedIn) { + return + } + previousIsLoggedIn.current = isLoggedIn + messageTransporter.disconnect() + }, [isLoggedIn, messageTransporter]) +}