connection lost banner
if there is no connection to the home server Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
"{{name}} is talking…": "{{name}} is talking…",
|
"{{name}} is talking…": "{{name}} is talking…",
|
||||||
"{{names}}, {{name}}": "{{names}}, {{name}}",
|
"{{names}}, {{name}}": "{{names}}, {{name}}",
|
||||||
"{{roomName}} - Walkie-talkie call": "{{roomName}} - Walkie-talkie call",
|
"{{roomName}} - Walkie-talkie call": "{{roomName}} - Walkie-talkie call",
|
||||||
|
"<0>{children}Connectivity to the server has been lost.</0>": "<0>{children}Connectivity to the server has been lost.</0>",
|
||||||
"<0></0><1></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></0><1></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></0><1></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></0><1></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?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>": "<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>",
|
"<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>": "<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>",
|
||||||
"<0>Create an account</0> Or <2>Access as a guest</2>": "<0>Create an account</0> Or <2>Access as a guest</2>",
|
"<0>Create an account</0> Or <2>Access as a guest</2>": "<0>Create an account</0> Or <2>Access as a guest</2>",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { usePageFocusStyle } from "./usePageFocusStyle";
|
|||||||
import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
|
import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
|
||||||
import { InspectorContextProvider } from "./room/GroupCallInspector";
|
import { InspectorContextProvider } from "./room/GroupCallInspector";
|
||||||
import { CrashView, LoadingView } from "./FullScreenView";
|
import { CrashView, LoadingView } from "./FullScreenView";
|
||||||
|
import { DisconnectedBanner } from "./DisconnectedBanner";
|
||||||
import { Initializer } from "./initializer";
|
import { Initializer } from "./initializer";
|
||||||
import { MediaHandlerProvider } from "./settings/useMediaHandler";
|
import { MediaHandlerProvider } from "./settings/useMediaHandler";
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ export default function App({ history }: AppProps) {
|
|||||||
<InspectorContextProvider>
|
<InspectorContextProvider>
|
||||||
<Sentry.ErrorBoundary fallback={errorPage}>
|
<Sentry.ErrorBoundary fallback={errorPage}>
|
||||||
<OverlayProvider>
|
<OverlayProvider>
|
||||||
|
<DisconnectedBanner />
|
||||||
<Switch>
|
<Switch>
|
||||||
<SentryRoute exact path="/">
|
<SentryRoute exact path="/">
|
||||||
<HomePage />
|
<HomePage />
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
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 { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
|
||||||
|
|
||||||
import { ErrorView } from "./FullScreenView";
|
import { ErrorView } from "./FullScreenView";
|
||||||
import {
|
import {
|
||||||
@@ -70,7 +71,8 @@ const loadSession = (): Session => {
|
|||||||
const saveSession = (session: Session) =>
|
const saveSession = (session: Session) =>
|
||||||
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
||||||
const clearSession = () => localStorage.removeItem("matrix-auth-store");
|
const clearSession = () => localStorage.removeItem("matrix-auth-store");
|
||||||
|
const isDisconnected = (syncState, syncData) =>
|
||||||
|
syncState === "ERROR" && syncData?.error?.name === "ConnectionError";
|
||||||
interface ClientState {
|
interface ClientState {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
@@ -81,6 +83,7 @@ interface ClientState {
|
|||||||
logout: () => void;
|
logout: () => void;
|
||||||
setClient: (client: MatrixClient, session: Session) => void;
|
setClient: (client: MatrixClient, session: Session) => void;
|
||||||
error?: Error;
|
error?: Error;
|
||||||
|
disconnected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClientContext = createContext<ClientState>(null);
|
const ClientContext = createContext<ClientState>(null);
|
||||||
@@ -98,7 +101,15 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const initializing = useRef(false);
|
const initializing = useRef(false);
|
||||||
const [
|
const [
|
||||||
{ loading, isAuthenticated, isPasswordlessUser, client, userName, error },
|
{
|
||||||
|
loading,
|
||||||
|
isAuthenticated,
|
||||||
|
isPasswordlessUser,
|
||||||
|
client,
|
||||||
|
userName,
|
||||||
|
error,
|
||||||
|
disconnected,
|
||||||
|
},
|
||||||
setState,
|
setState,
|
||||||
] = useState<ClientProviderState>({
|
] = useState<ClientProviderState>({
|
||||||
loading: true,
|
loading: true,
|
||||||
@@ -107,8 +118,20 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
client: undefined,
|
client: undefined,
|
||||||
userName: null,
|
userName: null,
|
||||||
error: undefined,
|
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(() => {
|
useEffect(() => {
|
||||||
// In case the component is mounted, unmounted, and remounted quickly (as
|
// 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
|
// React does in strict mode), we need to make sure not to doubly initialize
|
||||||
@@ -183,9 +206,10 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let clientWithListener;
|
||||||
init()
|
init()
|
||||||
.then(({ client, isPasswordlessUser }) => {
|
.then(({ client, isPasswordlessUser }) => {
|
||||||
|
clientWithListener = client;
|
||||||
setState({
|
setState({
|
||||||
client,
|
client,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -193,7 +217,12 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
isPasswordlessUser,
|
isPasswordlessUser,
|
||||||
userName: client?.getUserIdLocalpart(),
|
userName: client?.getUserIdLocalpart(),
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
disconnected: isDisconnected(
|
||||||
|
client?.getSyncState,
|
||||||
|
client?.getSyncStateData
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
clientWithListener?.on(ClientEvent.Sync, onSync);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
@@ -204,9 +233,13 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
isPasswordlessUser: false,
|
isPasswordlessUser: false,
|
||||||
userName: null,
|
userName: null,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
disconnected: false,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.finally(() => (initializing.current = false));
|
.finally(() => (initializing.current = false));
|
||||||
|
return () => {
|
||||||
|
clientWithListener?.removeListener(ClientEvent.Sync, onSync);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const changePassword = useCallback(
|
const changePassword = useCallback(
|
||||||
@@ -235,6 +268,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
isPasswordlessUser: false,
|
isPasswordlessUser: false,
|
||||||
userName: client.getUserIdLocalpart(),
|
userName: client.getUserIdLocalpart(),
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
disconnected: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[client]
|
[client]
|
||||||
@@ -256,6 +290,10 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
isPasswordlessUser: session.passwordlessUser,
|
isPasswordlessUser: session.passwordlessUser,
|
||||||
userName: newClient.getUserIdLocalpart(),
|
userName: newClient.getUserIdLocalpart(),
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
disconnected: isDisconnected(
|
||||||
|
newClient.getSyncState(),
|
||||||
|
newClient.getSyncStateData()
|
||||||
|
),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
clearSession();
|
clearSession();
|
||||||
@@ -267,6 +305,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
isPasswordlessUser: false,
|
isPasswordlessUser: false,
|
||||||
userName: null,
|
userName: null,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
disconnected: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -284,6 +323,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
isPasswordlessUser: true,
|
isPasswordlessUser: true,
|
||||||
userName: "",
|
userName: "",
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
disconnected: false,
|
||||||
});
|
});
|
||||||
history.push("/");
|
history.push("/");
|
||||||
PosthogAnalytics.instance.setRegistrationType(RegistrationType.Guest);
|
PosthogAnalytics.instance.setRegistrationType(RegistrationType.Guest);
|
||||||
@@ -326,6 +366,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
userName,
|
userName,
|
||||||
setClient,
|
setClient,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
disconnected,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
loading,
|
loading,
|
||||||
@@ -336,6 +377,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
|||||||
logout,
|
logout,
|
||||||
userName,
|
userName,
|
||||||
setClient,
|
setClient,
|
||||||
|
disconnected,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
27
src/DisconnectedBanner.module.css
Normal file
27
src/DisconnectedBanner.module.css
Normal file
@@ -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%;
|
||||||
|
}
|
||||||
50
src/DisconnectedBanner.tsx
Normal file
50
src/DisconnectedBanner.tsx
Normal file
@@ -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<HTMLElement> {
|
||||||
|
children?: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DisconnectedBanner({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}: DisconnectedBannerProps) {
|
||||||
|
const clientrp = useClient();
|
||||||
|
logger.log(clientrp);
|
||||||
|
const disconnected = clientrp.disconnected;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{disconnected && (
|
||||||
|
<Trans>
|
||||||
|
<div className={classNames(styles.banner, className)} {...rest}>
|
||||||
|
{children}
|
||||||
|
Connectivity to the server has been lost.
|
||||||
|
</div>
|
||||||
|
</Trans>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -45,6 +45,12 @@ class DependencyLoadStates {
|
|||||||
|
|
||||||
export class Initializer {
|
export class Initializer {
|
||||||
private static internalInstance: Initializer;
|
private static internalInstance: Initializer;
|
||||||
|
private isInitialized = false;
|
||||||
|
|
||||||
|
public static isInitialized(): boolean {
|
||||||
|
return Boolean(Initializer.internalInstance?.isInitialized);
|
||||||
|
}
|
||||||
|
|
||||||
public static initBeforeReact() {
|
public static initBeforeReact() {
|
||||||
// this maybe also needs to return a promise in the future,
|
// this maybe also needs to return a promise in the future,
|
||||||
// if we have to do async inits before showing the loading screen
|
// if we have to do async inits before showing the loading screen
|
||||||
@@ -223,6 +229,7 @@ export class Initializer {
|
|||||||
if (this.loadStates.allDepsAreLoaded()) {
|
if (this.loadStates.allDepsAreLoaded()) {
|
||||||
// resolve if there is no dependency that is not loaded
|
// resolve if there is no dependency that is not loaded
|
||||||
resolve();
|
resolve();
|
||||||
|
this.isInitialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private initPromise: Promise<void> | null;
|
private initPromise: Promise<void> | null;
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ function waitForSync(client: MatrixClient) {
|
|||||||
data: ISyncStateData
|
data: ISyncStateData
|
||||||
) => {
|
) => {
|
||||||
if (state === "PREPARED") {
|
if (state === "PREPARED") {
|
||||||
|
client.removeListener(ClientEvent.Sync, onSync);
|
||||||
resolve();
|
resolve();
|
||||||
client.removeListener(ClientEvent.Sync, onSync);
|
|
||||||
} else if (state === "ERROR") {
|
} else if (state === "ERROR") {
|
||||||
reject(data?.error);
|
|
||||||
client.removeListener(ClientEvent.Sync, onSync);
|
client.removeListener(ClientEvent.Sync, onSync);
|
||||||
|
reject(data?.error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
client.on(ClientEvent.Sync, onSync);
|
client.on(ClientEvent.Sync, onSync);
|
||||||
|
|||||||
Reference in New Issue
Block a user