Skip to content

Commit 3e52242

Browse files
authored
fix: Reconnect Auth Fail Fix - embed-widget (#2023)
Resolves #1256 **Changes Implemented:** - Refactored disconnection, shutdown, and auth fail handling logic to now be `ConnectionBootstrap` in order to properly handle auth fail event in the `embed-widget` **Visual of modal that appears on auth fail:** ![image](https://github.com/deephaven/web-client-ui/assets/69530774/a0874c7e-a4f3-40fb-ad1f-23fe75012b1f)
1 parent b090f20 commit 3e52242

2 files changed

Lines changed: 119 additions & 120 deletions

File tree

packages/app-utils/src/components/ConnectionBootstrap.tsx

Lines changed: 117 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import React, { useCallback, useEffect, useMemo, useState } from 'react';
2-
import { LoadingOverlay } from '@deephaven/components';
2+
import {
3+
BasicModal,
4+
DebouncedModal,
5+
InfoModal,
6+
LoadingOverlay,
7+
LoadingSpinner,
8+
} from '@deephaven/components';
39
import {
410
ObjectFetcherContext,
511
ObjectFetchManager,
@@ -11,6 +17,7 @@ import {
1117
import type { dh } from '@deephaven/jsapi-types';
1218
import Log from '@deephaven/log';
1319
import { assertNotNull } from '@deephaven/utils';
20+
import { vsDebugDisconnect } from '@deephaven/icons';
1421
import ConnectionContext from './ConnectionContext';
1522

1623
const log = Log.module('@deephaven/app-utils.ConnectionBootstrap');
@@ -33,6 +40,18 @@ export function ConnectionBootstrap({
3340
const client = useClient();
3441
const [error, setError] = useState<unknown>();
3542
const [connection, setConnection] = useState<dh.IdeConnection>();
43+
const [connectionState, setConnectionState] = useState<
44+
| 'not_connecting'
45+
| 'connecting'
46+
| 'connected'
47+
| 'reconnecting'
48+
| 'failed'
49+
| 'shutdown'
50+
>('connecting');
51+
const isAuthFailed = connectionState === 'failed';
52+
const isShutdown = connectionState === 'shutdown';
53+
const isReconnecting = connectionState === 'reconnecting';
54+
const isNotConnecting = connectionState === 'not_connecting';
3655

3756
useEffect(
3857
function initConnection() {
@@ -44,11 +63,13 @@ export function ConnectionBootstrap({
4463
return;
4564
}
4665
setConnection(newConnection);
66+
setConnectionState('connected');
4767
} catch (e) {
4868
if (isCanceled) {
4969
return;
5070
}
5171
setError(e);
72+
setConnectionState('not_connecting');
5273
}
5374
}
5475
loadConnection();
@@ -59,25 +80,93 @@ export function ConnectionBootstrap({
5980
[api, client]
6081
);
6182

83+
useEffect(
84+
function listenForDisconnect() {
85+
if (connection == null || isShutdown) return;
86+
87+
// handles the disconnect event
88+
function handleDisconnect(event: CustomEvent): void {
89+
const { detail } = event;
90+
log.info('Disconnect', `${JSON.stringify(detail)}`);
91+
setConnectionState('reconnecting');
92+
}
93+
const removerFn = connection.addEventListener(
94+
api.IdeConnection.EVENT_DISCONNECT,
95+
handleDisconnect
96+
);
97+
98+
return removerFn;
99+
},
100+
[api, connection, isShutdown]
101+
);
102+
103+
useEffect(
104+
function listenForReconnect() {
105+
if (connection == null || isShutdown) return;
106+
107+
// handles the reconnect event
108+
function handleReconnect(event: CustomEvent): void {
109+
const { detail } = event;
110+
log.info('Reconnect', `${JSON.stringify(detail)}`);
111+
setConnectionState('connected');
112+
}
113+
const removerFn = connection.addEventListener(
114+
api.CoreClient.EVENT_RECONNECT,
115+
handleReconnect
116+
);
117+
118+
return removerFn;
119+
},
120+
[api, connection, isShutdown]
121+
);
122+
62123
useEffect(
63124
function listenForShutdown() {
64125
if (connection == null) return;
65126

127+
// handles the shutdown event
66128
function handleShutdown(event: CustomEvent): void {
67129
const { detail } = event;
68130
log.info('Shutdown', `${JSON.stringify(detail)}`);
69131
setError(`Server shutdown: ${detail ?? 'Unknown reason'}`);
132+
setConnectionState('shutdown');
70133
}
71-
72134
const removerFn = connection.addEventListener(
73135
api.IdeConnection.EVENT_SHUTDOWN,
74136
handleShutdown
75137
);
138+
76139
return removerFn;
77140
},
78141
[api, connection]
79142
);
80143

144+
useEffect(
145+
function listenForAuthFailed() {
146+
if (connection == null || isShutdown) return;
147+
148+
// handles the auth failed event
149+
function handleAuthFailed(event: CustomEvent): void {
150+
const { detail } = event;
151+
log.warn(
152+
'Reconnect authentication failed',
153+
`${JSON.stringify(detail)}`
154+
);
155+
setError(
156+
`Reconnect authentication failed: ${detail ?? 'Unknown reason'}`
157+
);
158+
setConnectionState('failed');
159+
}
160+
const removerFn = connection.addEventListener(
161+
api.CoreClient.EVENT_RECONNECT_AUTH_FAILED,
162+
handleAuthFailed
163+
);
164+
165+
return removerFn;
166+
},
167+
[api, connection, isShutdown]
168+
);
169+
81170
const objectFetcher = useCallback(
82171
async (descriptor: dh.ide.VariableDescriptor) => {
83172
assertNotNull(connection, 'No connection available to fetch object with');
@@ -104,21 +193,44 @@ export function ConnectionBootstrap({
104193
[objectFetcher]
105194
);
106195

107-
if (connection == null || error != null) {
196+
function handleRefresh(): void {
197+
log.info('Refreshing application');
198+
window.location.reload();
199+
}
200+
201+
if (isShutdown || connectionState === 'connecting' || isNotConnecting) {
108202
return (
109203
<LoadingOverlay
110204
data-testid="connection-bootstrap-loading"
111-
isLoading={connection == null}
205+
isLoading={false}
112206
errorMessage={error != null ? `${error}` : undefined}
113207
/>
114208
);
115209
}
116210

117211
return (
118-
<ConnectionContext.Provider value={connection}>
212+
<ConnectionContext.Provider value={connection ?? null}>
119213
<ObjectFetcherContext.Provider value={objectFetcher}>
120214
<ObjectFetchManagerContext.Provider value={objectManager}>
121215
{children}
216+
<DebouncedModal isOpen={isReconnecting} debounceMs={1000}>
217+
<InfoModal
218+
icon={vsDebugDisconnect}
219+
title={
220+
<>
221+
<LoadingSpinner /> Attempting to reconnect...
222+
</>
223+
}
224+
subtitle="Please check your network connection."
225+
/>
226+
</DebouncedModal>
227+
<BasicModal
228+
confirmButtonText="Refresh"
229+
onConfirm={handleRefresh}
230+
isOpen={isAuthFailed}
231+
headerText="Authentication failed"
232+
bodyText="Credentials are invalid. Please refresh your browser to try and reconnect."
233+
/>
122234
</ObjectFetchManagerContext.Provider>
123235
</ObjectFetcherContext.Provider>
124236
</ConnectionContext.Provider>

0 commit comments

Comments
 (0)