From 14a32c0fb392750895c92dc0d113003ba6c4d3e5 Mon Sep 17 00:00:00 2001 From: Timo K Date: Fri, 9 Jun 2023 19:18:30 +0200 Subject: [PATCH 1/5] connection lost banner if there is no connection to the home server Signed-off-by: Timo K --- public/locales/en-GB/app.json | 1 + src/App.tsx | 2 ++ src/ClientContext.tsx | 50 ++++++++++++++++++++++++++++--- src/DisconnectedBanner.module.css | 27 +++++++++++++++++ src/DisconnectedBanner.tsx | 50 +++++++++++++++++++++++++++++++ src/initializer.tsx | 7 +++++ src/matrix-utils.ts | 4 +-- 7 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 src/DisconnectedBanner.module.css create mode 100644 src/DisconnectedBanner.tsx diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index ccd111b8..80f40d54 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -8,6 +8,7 @@ "{{name}} is talking…": "{{name}} is talking…", "{{names}}, {{name}}": "{{names}}, {{name}}", "{{roomName}} - Walkie-talkie call": "{{roomName}} - Walkie-talkie call", + "<0>{children}Connectivity to the server has been lost.": "<0>{children}Connectivity to the server has been lost.", "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.", "<0>Already have an account?<1><0>Log in Or <2>Access as a guest": "<0>Already have an account?<1><0>Log in Or <2>Access as a guest", "<0>Create an account Or <2>Access as a guest": "<0>Create an account Or <2>Access as a guest", diff --git a/src/App.tsx b/src/App.tsx index 2ae27b73..655e3789 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,6 +29,7 @@ import { usePageFocusStyle } from "./usePageFocusStyle"; import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage"; import { InspectorContextProvider } from "./room/GroupCallInspector"; import { CrashView, LoadingView } from "./FullScreenView"; +import { DisconnectedBanner } from "./DisconnectedBanner"; import { Initializer } from "./initializer"; import { MediaHandlerProvider } from "./settings/useMediaHandler"; @@ -60,6 +61,7 @@ export default function App({ history }: AppProps) { + diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 08593a4b..75c1bed3 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -25,9 +25,10 @@ import React, { useRef, } from "react"; import { useHistory } from "react-router-dom"; -import { MatrixClient } from "matrix-js-sdk/src/client"; +import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client"; import { logger } from "matrix-js-sdk/src/logger"; import { useTranslation } from "react-i18next"; +import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync"; import { ErrorView } from "./FullScreenView"; import { @@ -70,7 +71,8 @@ const loadSession = (): Session => { const saveSession = (session: Session) => localStorage.setItem("matrix-auth-store", JSON.stringify(session)); const clearSession = () => localStorage.removeItem("matrix-auth-store"); - +const isDisconnected = (syncState, syncData) => + syncState === "ERROR" && syncData?.error?.name === "ConnectionError"; interface ClientState { loading: boolean; isAuthenticated: boolean; @@ -81,6 +83,7 @@ interface ClientState { logout: () => void; setClient: (client: MatrixClient, session: Session) => void; error?: Error; + disconnected: boolean; } const ClientContext = createContext(null); @@ -98,7 +101,15 @@ export const ClientProvider: FC = ({ children }) => { const history = useHistory(); const initializing = useRef(false); const [ - { loading, isAuthenticated, isPasswordlessUser, client, userName, error }, + { + loading, + isAuthenticated, + isPasswordlessUser, + client, + userName, + error, + disconnected, + }, setState, ] = useState({ loading: true, @@ -107,8 +118,20 @@ export const ClientProvider: FC = ({ children }) => { client: undefined, userName: null, error: undefined, + disconnected: false, }); + const onSync = (state: SyncState, _old: SyncState, data: ISyncStateData) => { + setState((currentState) => { + logger.log("syntData.name", data?.error?.name, "state:", state); + console.log("Current state:", currentState); + return { + ...currentState, + disconnected: isDisconnected(state, data), + }; + }); + }; + useEffect(() => { // In case the component is mounted, unmounted, and remounted quickly (as // React does in strict mode), we need to make sure not to doubly initialize @@ -183,9 +206,10 @@ export const ClientProvider: FC = ({ children }) => { } } }; - + let clientWithListener; init() .then(({ client, isPasswordlessUser }) => { + clientWithListener = client; setState({ client, loading: false, @@ -193,7 +217,12 @@ export const ClientProvider: FC = ({ children }) => { isPasswordlessUser, userName: client?.getUserIdLocalpart(), error: undefined, + disconnected: isDisconnected( + client?.getSyncState, + client?.getSyncStateData + ), }); + clientWithListener?.on(ClientEvent.Sync, onSync); }) .catch((err) => { logger.error(err); @@ -204,9 +233,13 @@ export const ClientProvider: FC = ({ children }) => { isPasswordlessUser: false, userName: null, error: undefined, + disconnected: false, }); }) .finally(() => (initializing.current = false)); + return () => { + clientWithListener?.removeListener(ClientEvent.Sync, onSync); + }; }, []); const changePassword = useCallback( @@ -235,6 +268,7 @@ export const ClientProvider: FC = ({ children }) => { isPasswordlessUser: false, userName: client.getUserIdLocalpart(), error: undefined, + disconnected: false, }); }, [client] @@ -256,6 +290,10 @@ export const ClientProvider: FC = ({ children }) => { isPasswordlessUser: session.passwordlessUser, userName: newClient.getUserIdLocalpart(), error: undefined, + disconnected: isDisconnected( + newClient.getSyncState(), + newClient.getSyncStateData() + ), }); } else { clearSession(); @@ -267,6 +305,7 @@ export const ClientProvider: FC = ({ children }) => { isPasswordlessUser: false, userName: null, error: undefined, + disconnected: false, }); } }, @@ -284,6 +323,7 @@ export const ClientProvider: FC = ({ children }) => { isPasswordlessUser: true, userName: "", error: undefined, + disconnected: false, }); history.push("/"); PosthogAnalytics.instance.setRegistrationType(RegistrationType.Guest); @@ -326,6 +366,7 @@ export const ClientProvider: FC = ({ children }) => { userName, setClient, error: undefined, + disconnected, }), [ loading, @@ -336,6 +377,7 @@ export const ClientProvider: FC = ({ children }) => { logout, userName, setClient, + disconnected, ] ); diff --git a/src/DisconnectedBanner.module.css b/src/DisconnectedBanner.module.css new file mode 100644 index 00000000..aa6f6f5a --- /dev/null +++ b/src/DisconnectedBanner.module.css @@ -0,0 +1,27 @@ +/* +Copyright 2022 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.banner { + position: absolute; + padding: 29px; + background-color: var(--quaternary-content); + vertical-align: middle; + font-size: var(--font-size-body); + text-align: center; + z-index: 1; + top: 76px; + width: 100%; +} diff --git a/src/DisconnectedBanner.tsx b/src/DisconnectedBanner.tsx new file mode 100644 index 00000000..84ecc432 --- /dev/null +++ b/src/DisconnectedBanner.tsx @@ -0,0 +1,50 @@ +/* +Copyright 2022 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import classNames from "classnames"; +import React, { HTMLAttributes, ReactNode } from "react"; +import { logger } from "@sentry/utils"; +import { Trans } from "react-i18next"; + +import styles from "./DisconnectedBanner.module.css"; +import { useClient } from "./ClientContext"; + +interface DisconnectedBannerProps extends HTMLAttributes { + children?: ReactNode; + className?: string; +} + +export function DisconnectedBanner({ + children, + className, + ...rest +}: DisconnectedBannerProps) { + const clientrp = useClient(); + logger.log(clientrp); + const disconnected = clientrp.disconnected; + return ( + <> + {disconnected && ( + +
+ {children} + Connectivity to the server has been lost. +
+
+ )} + + ); +} diff --git a/src/initializer.tsx b/src/initializer.tsx index 37e659e7..c6507668 100644 --- a/src/initializer.tsx +++ b/src/initializer.tsx @@ -45,6 +45,12 @@ class DependencyLoadStates { export class Initializer { private static internalInstance: Initializer; + private isInitialized = false; + + public static isInitialized(): boolean { + return Boolean(Initializer.internalInstance?.isInitialized); + } + public static initBeforeReact() { // this maybe also needs to return a promise in the future, // if we have to do async inits before showing the loading screen @@ -223,6 +229,7 @@ export class Initializer { if (this.loadStates.allDepsAreLoaded()) { // resolve if there is no dependency that is not loaded resolve(); + this.isInitialized = true; } } private initPromise: Promise | null; diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index 8cec21d0..2830b823 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -61,11 +61,11 @@ function waitForSync(client: MatrixClient) { data: ISyncStateData ) => { if (state === "PREPARED") { + client.removeListener(ClientEvent.Sync, onSync); resolve(); - client.removeListener(ClientEvent.Sync, onSync); } else if (state === "ERROR") { - reject(data?.error); client.removeListener(ClientEvent.Sync, onSync); + reject(data?.error); } }; client.on(ClientEvent.Sync, onSync); From 532dddcb2bd35c1d30ee93211dc14a6755b8b831 Mon Sep 17 00:00:00 2001 From: Timo K Date: Mon, 12 Jun 2023 10:14:11 +0200 Subject: [PATCH 2/5] cleanup Signed-off-by: Timo K --- public/locales/en-GB/app.json | 2 +- src/ClientContext.tsx | 3 +-- src/DisconnectedBanner.tsx | 19 ++++++++----------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index f4892717..47bdbdb5 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -10,7 +10,6 @@ "{{name}} is talking…": "{{name}} is talking…", "{{names}}, {{name}}": "{{names}}, {{name}}", "{{roomName}} - Walkie-talkie call": "{{roomName}} - Walkie-talkie call", - "<0>{children}Connectivity to the server has been lost.": "<0>{children}Connectivity to the server has been lost.", "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0><1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.", "<0>Already have an account?<1><0>Log in Or <2>Access as a guest": "<0>Already have an account?<1><0>Log in Or <2>Access as a guest", "<0>Create an account Or <2>Access as a guest": "<0>Create an account Or <2>Access as a guest", @@ -37,6 +36,7 @@ "Close": "Close", "Confirm password": "Confirm password", "Connection lost": "Connection lost", + "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "Copied!": "Copied!", "Copy": "Copy", "Copy and share this call link": "Copy and share this call link", diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 75c1bed3..1ffced8f 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -73,6 +73,7 @@ const saveSession = (session: Session) => const clearSession = () => localStorage.removeItem("matrix-auth-store"); const isDisconnected = (syncState, syncData) => syncState === "ERROR" && syncData?.error?.name === "ConnectionError"; + interface ClientState { loading: boolean; isAuthenticated: boolean; @@ -123,8 +124,6 @@ export const ClientProvider: FC = ({ children }) => { const onSync = (state: SyncState, _old: SyncState, data: ISyncStateData) => { setState((currentState) => { - logger.log("syntData.name", data?.error?.name, "state:", state); - console.log("Current state:", currentState); return { ...currentState, disconnected: isDisconnected(state, data), diff --git a/src/DisconnectedBanner.tsx b/src/DisconnectedBanner.tsx index 84ecc432..9ba58700 100644 --- a/src/DisconnectedBanner.tsx +++ b/src/DisconnectedBanner.tsx @@ -16,8 +16,7 @@ limitations under the License. import classNames from "classnames"; import React, { HTMLAttributes, ReactNode } from "react"; -import { logger } from "@sentry/utils"; -import { Trans } from "react-i18next"; +import { useTranslation } from "react-i18next"; import styles from "./DisconnectedBanner.module.css"; import { useClient } from "./ClientContext"; @@ -32,18 +31,16 @@ export function DisconnectedBanner({ className, ...rest }: DisconnectedBannerProps) { - const clientrp = useClient(); - logger.log(clientrp); - const disconnected = clientrp.disconnected; + const { t } = useTranslation(); + const { disconnected } = useClient(); + return ( <> {disconnected && ( - -
- {children} - Connectivity to the server has been lost. -
-
+
+ {children} + {t("Connectivity to the server has been lost.")} +
)} ); From 24997f1d3a8deb7c70eeb4901a2303ddc3394ea1 Mon Sep 17 00:00:00 2001 From: Timo K Date: Mon, 12 Jun 2023 10:16:02 +0200 Subject: [PATCH 3/5] update date Signed-off-by: Timo K --- src/DisconnectedBanner.module.css | 2 +- src/DisconnectedBanner.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DisconnectedBanner.module.css b/src/DisconnectedBanner.module.css index aa6f6f5a..333c99d3 100644 --- a/src/DisconnectedBanner.module.css +++ b/src/DisconnectedBanner.module.css @@ -1,5 +1,5 @@ /* -Copyright 2022 New Vector Ltd +Copyright 2023 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/DisconnectedBanner.tsx b/src/DisconnectedBanner.tsx index 9ba58700..b96c0b01 100644 --- a/src/DisconnectedBanner.tsx +++ b/src/DisconnectedBanner.tsx @@ -1,5 +1,5 @@ /* -Copyright 2022 New Vector Ltd +Copyright 2023 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 49786762bf0016a16bb4e3f80a36484e4dbab626 Mon Sep 17 00:00:00 2001 From: Timo K Date: Tue, 13 Jun 2023 17:08:29 +0200 Subject: [PATCH 4/5] review Signed-off-by: Timo K --- src/ClientContext.tsx | 10 +++++----- src/DisconnectedBanner.module.css | 2 +- src/initializer.tsx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 1ffced8f..ebe84f7e 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -124,10 +124,10 @@ export const ClientProvider: FC = ({ children }) => { const onSync = (state: SyncState, _old: SyncState, data: ISyncStateData) => { setState((currentState) => { - return { - ...currentState, - disconnected: isDisconnected(state, data), - }; + const disconnected = isDisconnected(state, data); + return disconnected === currentState.disconnected + ? currentState + : { ...currentState, disconnected }; }); }; @@ -205,7 +205,7 @@ export const ClientProvider: FC = ({ children }) => { } } }; - let clientWithListener; + let clientWithListener: MatrixClient; init() .then(({ client, isPasswordlessUser }) => { clientWithListener = client; diff --git a/src/DisconnectedBanner.module.css b/src/DisconnectedBanner.module.css index 333c99d3..5827953d 100644 --- a/src/DisconnectedBanner.module.css +++ b/src/DisconnectedBanner.module.css @@ -23,5 +23,5 @@ limitations under the License. text-align: center; z-index: 1; top: 76px; - width: 100%; + width: calc(100% - 58px); } diff --git a/src/initializer.tsx b/src/initializer.tsx index c6507668..ceea035b 100644 --- a/src/initializer.tsx +++ b/src/initializer.tsx @@ -48,7 +48,7 @@ export class Initializer { private isInitialized = false; public static isInitialized(): boolean { - return Boolean(Initializer.internalInstance?.isInitialized); + return Initializer.internalInstance?.isInitialized; } public static initBeforeReact() { From 4643e167ce91ecf6b926aed215b18391f2f63e35 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 21 Jul 2023 20:08:46 +0100 Subject: [PATCH 5/5] i18n --- public/locales/en-GB/app.json | 1 - 1 file changed, 1 deletion(-) diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index df4daaa3..d697a70d 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -25,7 +25,6 @@ "Change layout": "Change layout", "Close": "Close", "Confirm password": "Confirm password", - "Connection lost": "Connection lost", "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "Copied!": "Copied!", "Copy": "Copy",