Handle connection loss gracefully

master
Drew DeVault 2017-09-08 13:55:47 +09:00
parent 54030bb341
commit a1aa140848
7 changed files with 61 additions and 23 deletions

View File

@ -1,5 +1,10 @@
export const SOCKET_CONNECTED = "SOCKET_CONNECTED";
export const SOCKET_DISCONNECTED = "SOCKET_DISCONNECTED";
export const SOCKET_STATE = {
CONNECTED: "SOCKET_CONNECTED",
CONNECTING: "SOCKET_CONNECTING",
DISCONNECTED: "SOCKET_DISCONNECTED"
};
export const socket_connected = () => ({ type: SOCKET_CONNECTED });
export const socket_disconnected = () => ({ type: SOCKET_DISCONNECTED });
export const SOCKET_UPDATE = "SOCKET_UPDATE";
export const socket_update = (state, reason=null) =>
({ type: SOCKET_UPDATE, state, reason });

View File

@ -6,21 +6,25 @@ import { ConnectedRouter } from 'react-router-redux';
import 'preact/devtools';
import './polyfills';
import store, { history } from './store';
import store, { create, history } from './store';
import scss from '../scss/main.scss';
import { ws_init } from './socket';
import { filter_subscribe } from './actions/filter_subscribe';
import { socket_connected, socket_disconnected } from './actions/socket';
import { socket_update, SOCKET_STATE } from './actions/socket';
import Nav from './ui/navigation';
import Main from './ui/main';
import Connection from './ui/connection';
export function initialize(uri) {
store.dispatch(socket_update(SOCKET_STATE.CONNECTING));
ws_init(uri, () => {
store.dispatch(socket_connected());
store.dispatch(socket_update(SOCKET_STATE.CONNECTED));
store.dispatch(filter_subscribe());
store.dispatch(filter_subscribe('server'));
}, () => {
store.dispatch(socket_update(SOCKET_STATE.DISCONNECTED,
"You were disconnected."));
});
}

View File

@ -1,4 +1,5 @@
import { UPDATE_RESOURCES, RESOURCES_REMOVED } from '../actions/resources';
import { SOCKET_STATE, SOCKET_UPDATE } from '../actions/socket';
export default function resourceReducer(type) {
return (state = {}, action) => {
@ -17,6 +18,9 @@ export default function resourceReducer(type) {
return Object.values(state)
.filter(r => action.ids.indexOf(r.id) === -1)
.reduce((s, r) => ({ ...s, [r.id]: r }), {});
case SOCKET_UPDATE:
const _state = action.state;
return _state === SOCKET_STATE.CONNECTING ? {} : state;
}
return state;
};

View File

@ -1,12 +1,14 @@
import { SOCKET_CONNECTED, SOCKET_DISCONNECTED } from '../actions/socket';
import { SOCKET_STATE, SOCKET_UPDATE } from '../actions/socket';
export default function socket(state = { connected: false }, action) {
export default function socket(_state = {
state: SOCKET_STATE.DISCONNECTED,
reason: null
}, action) {
const { state, reason } = action;
switch (action.type) {
case SOCKET_CONNECTED:
return { ...state, connected: true };
case SOCKET_DISCONNECTED:
return { ...state, connected: false };
case SOCKET_UPDATE:
return { ..._state, state, reason };
default:
return state;
return _state;
}
}

View File

@ -35,9 +35,9 @@ function ws_recv(e) {
handler && handler(msg);
}
export function ws_init(uri, cb) {
export function ws_init(uri, open, close) {
ws = new WebSocket(uri);
ws.addEventListener("open", cb);
ws.addEventListener("open", open);
ws.addEventListener("message", ws_recv);
ws.addEventListener("close", () => console.log("ws closed"));
ws.addEventListener("close", close);
}

View File

@ -13,10 +13,16 @@ export const history = createHistory();
const _compose =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|| compose;
const store = createStore(
reducer,
_compose(applyMiddleware(thunk, routerMiddleware(history))),
);
let store;
export const create = () => {
store = createStore(
reducer,
_compose(applyMiddleware(thunk, routerMiddleware(history))),
);
};
create();
if (module.hot) {
// Enable webpack hot module replacement for reducers

View File

@ -1,14 +1,17 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
Alert,
Card,
CardHeader,
CardBlock,
FormGroup,
Progress,
Label,
Input
} from 'reactstrap';
import { initialize } from '..';
import { SOCKET_STATE } from '../actions/socket';
class ConnectionOverlay extends Component {
constructor() {
@ -25,15 +28,29 @@ class ConnectionOverlay extends Component {
render() {
const { socket } = this.props;
if (socket.connected) {
if (socket.state === SOCKET_STATE.CONNECTED) {
return null;
}
if (socket.state === SOCKET_STATE.CONNECTING) {
return (
<div className="connection-overlay">
<Card>
<CardHeader>Connect to synapse</CardHeader>
<CardBlock>
<p className="text-center">Connecting...</p>
<Progress value={100} animated />
</CardBlock>
</Card>
</div>
);
}
const { uri, autoconnect } = this.state;
return (
<div className="connection-overlay">
<Card>
<CardHeader>Connect to synapse</CardHeader>
<CardBlock>
{socket.reason && <Alert color="info">{socket.reason}</Alert>}
<FormGroup>
<Label for="socket-uri">Server URI</Label>
<Input
@ -56,7 +73,7 @@ class ConnectionOverlay extends Component {
<button
className="btn btn-primary"
onClick={() => initialize(this.state.uri)}
>Connect</button>
>{socket.reason ? "Reconnect" : "Connect"}</button>
</CardBlock>
</Card>
</div>