Retry websocket connect if error returned is 502

* Retry websocket connect if error returned is 502

* Introduce connect button on 'Disconnected' left-pane dialog

* NetworkStatus: If user clicks connect, show connecting for 5s
This commit is contained in:
Scott Nonnenberg 2020-03-20 11:01:55 -07:00
parent c44176f7f3
commit 6bd5587d50
5 changed files with 89 additions and 15 deletions

View file

@ -571,6 +571,10 @@
"message": "Connecting", "message": "Connecting",
"description": "Displayed when the desktop client is currently connecting to the server." "description": "Displayed when the desktop client is currently connecting to the server."
}, },
"connect": {
"message": "Connect",
"description": "Shown to allow the user to manually attempt a reconnect."
},
"connectingHangOn": { "connectingHangOn": {
"message": "Shouldn't be long...", "message": "Shouldn't be long...",
"description": "Subtext description for when the client is connecting to the server." "description": "Subtext description for when the client is connecting to the server."

View file

@ -1446,6 +1446,7 @@
new textsecure.SyncRequest(textsecure.messaging, messageReceiver); new textsecure.SyncRequest(textsecure.messaging, messageReceiver);
let disconnectTimer = null; let disconnectTimer = null;
let reconnectTimer = null;
function onOffline() { function onOffline() {
window.log.info('offline'); window.log.info('offline');
@ -1499,7 +1500,12 @@
let connectCount = 0; let connectCount = 0;
async function connect(firstRun) { async function connect(firstRun) {
window.log.info('connect', firstRun); window.log.info('connect', { firstRun, connectCount });
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
// Bootstrap our online/offline detection, only the first time we connect // Bootstrap our online/offline detection, only the first time we connect
if (connectCount === 0 && navigator.onLine) { if (connectCount === 0 && navigator.onLine) {
@ -1799,6 +1805,11 @@
} }
} }
Whisper.events.on('manualConnect', manualConnect);
function manualConnect() {
connect();
}
function onConfiguration(ev) { function onConfiguration(ev) {
ev.confirm(); ev.confirm();
@ -2435,11 +2446,15 @@
return; return;
} }
if (error && error.name === 'HTTPError' && error.code === -1) { if (
error &&
error.name === 'HTTPError' &&
(error.code === -1 || error.code === 502)
) {
// Failed to connect to server // Failed to connect to server
if (navigator.onLine) { if (navigator.onLine) {
window.log.info('retrying in 1 minute'); window.log.info('retrying in 1 minute');
setTimeout(connect, 60000); reconnectTimer = setTimeout(connect, 60000);
Whisper.events.trigger('reconnectTimer'); Whisper.events.trigger('reconnectTimer');
} }

View file

@ -19,6 +19,7 @@ const defaultProps = {
isRegistrationDone: true, isRegistrationDone: true,
socketStatus: 0, socketStatus: 0,
relinkDevice: action('relink-device'), relinkDevice: action('relink-device'),
manualReconnect: action('manual-reconnect'),
withinConnectingGracePeriod: false, withinConnectingGracePeriod: false,
}; };
@ -41,12 +42,6 @@ const permutations = [
socketStatus: 3, socketStatus: 3,
}, },
}, },
{
title: 'Offline',
props: {
isOnline: false,
},
},
{ {
title: 'Unlinked (online)', title: 'Unlinked (online)',
props: { props: {
@ -60,6 +55,12 @@ const permutations = [
isRegistrationDone: false, isRegistrationDone: false,
}, },
}, },
{
title: 'Offline',
props: {
isOnline: false,
},
},
]; ];
storiesOf('Components/NetworkStatus', module) storiesOf('Components/NetworkStatus', module)

View file

@ -3,11 +3,14 @@ import React from 'react';
import { LocalizerType } from '../types/Util'; import { LocalizerType } from '../types/Util';
import { NetworkStateType } from '../state/ducks/network'; import { NetworkStateType } from '../state/ducks/network';
const FIVE_SECONDS = 5 * 1000;
export interface PropsType extends NetworkStateType { export interface PropsType extends NetworkStateType {
hasNetworkDialog: boolean; hasNetworkDialog: boolean;
i18n: LocalizerType; i18n: LocalizerType;
isRegistrationDone: boolean; isRegistrationDone: boolean;
relinkDevice: () => void; relinkDevice: () => void;
manualReconnect: () => void;
} }
type RenderDialogTypes = { type RenderDialogTypes = {
@ -39,17 +42,41 @@ export const NetworkStatus = ({
isRegistrationDone, isRegistrationDone,
socketStatus, socketStatus,
relinkDevice, relinkDevice,
manualReconnect,
}: PropsType): JSX.Element | null => { }: PropsType): JSX.Element | null => {
if (!hasNetworkDialog) { if (!hasNetworkDialog) {
return null; return null;
} }
if (!isOnline) { const [isConnecting, setIsConnecting] = React.useState<boolean>(false);
return renderDialog({ React.useEffect(() => {
subtext: i18n('checkNetworkConnection'), let timeout: NodeJS.Timeout;
title: i18n('offline'),
}); if (isConnecting) {
} else if (!isRegistrationDone) { timeout = setTimeout(() => {
setIsConnecting(false);
}, FIVE_SECONDS);
}
return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [isConnecting, setIsConnecting]);
const reconnect = () => {
setIsConnecting(true);
manualReconnect();
};
const manualReconnectButton = (): JSX.Element => (
<div className="module-left-pane-dialog__actions">
<button onClick={reconnect}>{i18n('connect')}</button>
</div>
);
if (!isRegistrationDone) {
return renderDialog({ return renderDialog({
renderActionableButton: (): JSX.Element => ( renderActionableButton: (): JSX.Element => (
<div className="module-left-pane-dialog__actions"> <div className="module-left-pane-dialog__actions">
@ -59,10 +86,22 @@ export const NetworkStatus = ({
subtext: i18n('unlinkedWarning'), subtext: i18n('unlinkedWarning'),
title: i18n('unlinked'), title: i18n('unlinked'),
}); });
} else if (isConnecting) {
return renderDialog({
subtext: i18n('connectingHangOn'),
title: i18n('connecting'),
});
} else if (!isOnline) {
return renderDialog({
renderActionableButton: manualReconnectButton,
subtext: i18n('checkNetworkConnection'),
title: i18n('offline'),
});
} }
let subtext = ''; let subtext = '';
let title = ''; let title = '';
let renderActionableButton;
switch (socketStatus) { switch (socketStatus) {
case WebSocket.CONNECTING: case WebSocket.CONNECTING:
@ -72,11 +111,13 @@ export const NetworkStatus = ({
case WebSocket.CLOSED: case WebSocket.CLOSED:
case WebSocket.CLOSING: case WebSocket.CLOSING:
default: default:
renderActionableButton = manualReconnectButton;
title = i18n('disconnected'); title = i18n('disconnected');
subtext = i18n('checkNetworkConnection'); subtext = i18n('checkNetworkConnection');
} }
return renderDialog({ return renderDialog({
renderActionableButton,
subtext, subtext,
title, title,
}); });

View file

@ -1,3 +1,6 @@
import { trigger } from '../../shims/events';
import { NoopActionType } from './noop';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
// State // State
@ -34,6 +37,7 @@ export type UserActionType = UserChangedActionType;
export const actions = { export const actions = {
userChanged, userChanged,
manualReconnect,
}; };
function userChanged(attributes: { function userChanged(attributes: {
@ -49,6 +53,15 @@ function userChanged(attributes: {
}; };
} }
function manualReconnect(): NoopActionType {
trigger('manualConnect');
return {
type: 'NOOP',
payload: null,
};
}
// Reducer // Reducer
function getEmptyState(): UserStateType { function getEmptyState(): UserStateType {