diff --git a/webclient/src/containers/App/AppShell.tsx b/webclient/src/containers/App/AppShell.tsx
index 855c84e5..ee2d5ecb 100644
--- a/webclient/src/containers/App/AppShell.tsx
+++ b/webclient/src/containers/App/AppShell.tsx
@@ -6,6 +6,7 @@ import CssBaseline from '@material-ui/core/CssBaseline';
import { store } from 'store';
import { Header } from 'components';
import Routes from './AppShellRoutes';
+import FeatureDetection from './FeatureDetection';
import './AppShell.css';
@@ -26,6 +27,8 @@ class AppShell extends Component {
+
+
diff --git a/webclient/src/containers/App/AppShellRoutes.tsx b/webclient/src/containers/App/AppShellRoutes.tsx
index b141c029..699281ba 100644
--- a/webclient/src/containers/App/AppShellRoutes.tsx
+++ b/webclient/src/containers/App/AppShellRoutes.tsx
@@ -10,7 +10,8 @@ import {
Room,
Server,
Login,
- Logs
+ Logs,
+ Unsupported
} from 'containers';
const Routes = () => (
@@ -24,6 +25,7 @@ const Routes = () => (
{ } />}
} />
} />
+ } />
diff --git a/webclient/src/containers/App/FeatureDetection.tsx b/webclient/src/containers/App/FeatureDetection.tsx
new file mode 100644
index 00000000..4342f516
--- /dev/null
+++ b/webclient/src/containers/App/FeatureDetection.tsx
@@ -0,0 +1,26 @@
+import { useEffect, useState } from 'react';
+import { Redirect } from 'react-router-dom';
+import { dexieService } from 'services';
+import { RouteEnum } from 'types';
+
+const FeatureDetection = () => {
+ const [unsupported, setUnsupported] = useState(false);
+
+ useEffect(() => {
+ const features: Promise[] = [
+ detectIndexedDB(),
+ ];
+
+ Promise.all(features).catch((e) => setUnsupported(true));
+ }, []);
+
+ return unsupported
+ ?
+ : <>>;
+
+ function detectIndexedDB() {
+ return dexieService.testConnection();
+ }
+};
+
+export default FeatureDetection;
diff --git a/webclient/src/containers/Login/Login.tsx b/webclient/src/containers/Login/Login.tsx
index f7a75427..8a54a348 100644
--- a/webclient/src/containers/Login/Login.tsx
+++ b/webclient/src/containers/Login/Login.tsx
@@ -88,7 +88,7 @@ const Login = ({ state, description }: LoginProps) => {
useReduxEffect(() => {
resetSubmitButton();
- }, [ServerTypes.LOGIN_FAILED], []);
+ }, [ServerTypes.CONNECTION_FAILED, ServerTypes.LOGIN_FAILED], []);
useReduxEffect(({ options: { hashedPassword } }) => {
if (hostIdToRemember) {
diff --git a/webclient/src/containers/Unsupported/Unsupported.css b/webclient/src/containers/Unsupported/Unsupported.css
new file mode 100644
index 00000000..46752e74
--- /dev/null
+++ b/webclient/src/containers/Unsupported/Unsupported.css
@@ -0,0 +1,18 @@
+.Unsupported {
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+}
+
+.Unsupported-paper {
+ width: 600px;
+ max-width: 100%;
+ padding: 40px;
+ text-align: center;
+}
+
+.Unsupported-paper__header {
+ margin-bottom: 40px;
+}
diff --git a/webclient/src/containers/Unsupported/Unsupported.tsx b/webclient/src/containers/Unsupported/Unsupported.tsx
new file mode 100644
index 00000000..16c2e0ca
--- /dev/null
+++ b/webclient/src/containers/Unsupported/Unsupported.tsx
@@ -0,0 +1,27 @@
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+import Paper from '@material-ui/core/Paper';
+import Typography from '@material-ui/core/Typography';
+
+import './Unsupported.css';
+
+const Unsupported = () => {
+ return (
+
+
+
+ Unsupported Browser
+ Please update your browser and/or check your permissions.
+
+
+ Note: Private browsing causes some browsers to disable certain permissions or features.
+
+
+ );
+};
+
+const mapStateToProps = state => ({
+
+});
+
+export default withRouter(connect(mapStateToProps)(Unsupported));
diff --git a/webclient/src/containers/index.ts b/webclient/src/containers/index.ts
index b6437753..de43d8ec 100644
--- a/webclient/src/containers/index.ts
+++ b/webclient/src/containers/index.ts
@@ -7,3 +7,4 @@ export { default as Player } from './Player/Player';
export { default as Server } from './Server/Server';
export { default as Logs } from './Logs/Logs';
export { default as Login } from './Login/Login';
+export { default as Unsupported } from './Unsupported/Unsupported';
diff --git a/webclient/src/services/dexie/DexieService.ts b/webclient/src/services/dexie/DexieService.ts
index 71b69c5a..9dcd3b94 100644
--- a/webclient/src/services/dexie/DexieService.ts
+++ b/webclient/src/services/dexie/DexieService.ts
@@ -28,6 +28,10 @@ class DexieService {
get hosts() {
return this.db.table(Stores.HOSTS);
}
+
+ testConnection() {
+ return this.db.open();
+ }
}
export const dexieService = new DexieService();
diff --git a/webclient/src/services/dexie/index.ts b/webclient/src/services/dexie/index.ts
index f27c81b5..593f1939 100644
--- a/webclient/src/services/dexie/index.ts
+++ b/webclient/src/services/dexie/index.ts
@@ -1 +1,2 @@
export * from './DexieDTOs';
+export * from './DexieService';
diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts
index 3789d1e1..62b79da6 100644
--- a/webclient/src/store/server/server.actions.ts
+++ b/webclient/src/store/server/server.actions.ts
@@ -18,6 +18,9 @@ export const Actions = {
type: Types.CONNECTION_CLOSED,
reason
}),
+ connectionFailed: () => ({
+ type: Types.CONNECTION_FAILED,
+ }),
serverMessage: message => ({
type: Types.SERVER_MESSAGE,
message
diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts
index 581fe2ed..3e637bbe 100644
--- a/webclient/src/store/server/server.dispatch.ts
+++ b/webclient/src/store/server/server.dispatch.ts
@@ -15,6 +15,9 @@ export const Dispatch = {
connectionClosed: reason => {
store.dispatch(Actions.connectionClosed(reason));
},
+ connectionFailed: () => {
+ store.dispatch(Actions.connectionFailed());
+ },
updateBuddyList: buddyList => {
store.dispatch(Actions.updateBuddyList(buddyList));
},
diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts
index c797f365..b1adcdf0 100644
--- a/webclient/src/store/server/server.types.ts
+++ b/webclient/src/store/server/server.types.ts
@@ -3,6 +3,7 @@ export const Types = {
LOGIN_SUCCESSFUL: '[Server] Login Successful',
LOGIN_FAILED: '[Server] Login Failed',
CONNECTION_CLOSED: '[Server] Connection Closed',
+ CONNECTION_FAILED: '[Server] Connection Failed',
SERVER_MESSAGE: '[Server] Server Message',
UPDATE_BUDDY_LIST: '[Server] Update Buddy List',
ADD_TO_BUDDY_LIST: '[Server] Add to Buddy List',
diff --git a/webclient/src/types/routes.tsx b/webclient/src/types/routes.tsx
index 386471a7..51dde695 100644
--- a/webclient/src/types/routes.tsx
+++ b/webclient/src/types/routes.tsx
@@ -10,4 +10,5 @@ export enum RouteEnum {
ACCOUNT = '/account',
ADMINISTRATION = '/administration',
REPLAYS = '/replays',
+ UNSUPPORTED = '/unsupported',
}
diff --git a/webclient/src/websocket/commands/SessionCommands.ts b/webclient/src/websocket/commands/SessionCommands.ts
index 742d8d27..648a8a00 100644
--- a/webclient/src/websocket/commands/SessionCommands.ts
+++ b/webclient/src/websocket/commands/SessionCommands.ts
@@ -141,11 +141,6 @@ export class SessionCommands {
const passwordSalt = raw['.Response_PasswordSalt.ext']?.passwordSalt;
switch (webClient.options.reason) {
- case WebSocketConnectReason.REGISTER: {
- SessionCommands.register(passwordSalt);
- break;
- }
-
case WebSocketConnectReason.ACTIVATE_ACCOUNT: {
SessionCommands.activateAccount(passwordSalt);
break;
@@ -174,11 +169,6 @@ export class SessionCommands {
}
switch (webClient.options.reason) {
- case WebSocketConnectReason.REGISTER: {
- SessionPersistence.registrationFailed('Failed to retrieve password salt');
- break;
- }
-
case WebSocketConnectReason.ACTIVATE_ACCOUNT: {
SessionPersistence.accountActivationFailed();
break;
diff --git a/webclient/src/websocket/events/SessionEvents.ts b/webclient/src/websocket/events/SessionEvents.ts
index 723745ad..4636470a 100644
--- a/webclient/src/websocket/events/SessionEvents.ts
+++ b/webclient/src/websocket/events/SessionEvents.ts
@@ -3,7 +3,7 @@ import { Room, StatusEnum, User, WebSocketConnectReason } from 'types';
import { SessionCommands } from '../commands';
import { RoomPersistence, SessionPersistence } from '../persistence';
import { ProtobufEvents } from '../services/ProtobufService';
-import { passwordSaltSupported } from '../utils';
+import { generateSalt, passwordSaltSupported } from '../utils';
import webClient from '../WebClient';
export const SessionEvents: ProtobufEvents = {
@@ -130,11 +130,8 @@ function serverIdentification(info: ServerIdentificationData) {
}
break;
case WebSocketConnectReason.REGISTER:
- if (passwordSaltSupported(serverOptions, webClient)) {
- SessionCommands.requestPasswordSalt();
- } else {
- SessionCommands.register();
- }
+ const passwordSalt = passwordSaltSupported(serverOptions, webClient) ? generateSalt() : null;
+ SessionCommands.register(passwordSalt);
break;
case WebSocketConnectReason.ACTIVATE_ACCOUNT:
if (passwordSaltSupported(serverOptions, webClient)) {
diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts
index f168a868..d9a772eb 100644
--- a/webclient/src/websocket/persistence/SessionPersistence.ts
+++ b/webclient/src/websocket/persistence/SessionPersistence.ts
@@ -21,6 +21,10 @@ export class SessionPersistence {
ServerDispatch.connectionClosed(reason);
}
+ static connectionFailed() {
+ ServerDispatch.connectionFailed();
+ }
+
static updateBuddyList(buddyList) {
ServerDispatch.updateBuddyList(buddyList);
}
diff --git a/webclient/src/websocket/services/WebSocketService.ts b/webclient/src/websocket/services/WebSocketService.ts
index 135e7407..f1b98eeb 100644
--- a/webclient/src/websocket/services/WebSocketService.ts
+++ b/webclient/src/websocket/services/WebSocketService.ts
@@ -4,6 +4,7 @@ import { ServerStatus, StatusEnum, WebSocketConnectOptions } from 'types';
import { KeepAliveService } from './KeepAliveService';
import { WebClient } from '../WebClient';
+import { SessionPersistence } from '../persistence';
export class WebSocketService {
private socket: WebSocket;
@@ -60,7 +61,10 @@ export class WebSocketService {
const socket = new WebSocket(url);
socket.binaryType = 'arraybuffer';
+ const connectionTimer = setTimeout(() => socket.close(), this.keepalive);
+
socket.onopen = () => {
+ clearTimeout(connectionTimer);
this.updateStatus(StatusEnum.CONNECTED, 'Connected');
this.keepAliveService.startPingLoop(this.keepalive, (pingReceived: Function) => {
@@ -79,6 +83,7 @@ export class WebSocketService {
socket.onerror = () => {
this.updateStatus(StatusEnum.DISCONNECTED, 'Connection Failed');
+ SessionPersistence.connectionFailed();
};
socket.onmessage = (event: MessageEvent) => {