Skip to content

Commit

Permalink
fix: Reconnect Auth Fail Fix - embed-widget (deephaven#2023)
Browse files Browse the repository at this point in the history
Resolves deephaven#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)
  • Loading branch information
AkshatJawne authored Jun 12, 2024
1 parent b090f20 commit 3e52242
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 120 deletions.
122 changes: 117 additions & 5 deletions packages/app-utils/src/components/ConnectionBootstrap.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { LoadingOverlay } from '@deephaven/components';
import {
BasicModal,
DebouncedModal,
InfoModal,
LoadingOverlay,
LoadingSpinner,
} from '@deephaven/components';
import {
ObjectFetcherContext,
ObjectFetchManager,
Expand All @@ -11,6 +17,7 @@ import {
import type { dh } from '@deephaven/jsapi-types';
import Log from '@deephaven/log';
import { assertNotNull } from '@deephaven/utils';
import { vsDebugDisconnect } from '@deephaven/icons';
import ConnectionContext from './ConnectionContext';

const log = Log.module('@deephaven/app-utils.ConnectionBootstrap');
Expand All @@ -33,6 +40,18 @@ export function ConnectionBootstrap({
const client = useClient();
const [error, setError] = useState<unknown>();
const [connection, setConnection] = useState<dh.IdeConnection>();
const [connectionState, setConnectionState] = useState<
| 'not_connecting'
| 'connecting'
| 'connected'
| 'reconnecting'
| 'failed'
| 'shutdown'
>('connecting');
const isAuthFailed = connectionState === 'failed';
const isShutdown = connectionState === 'shutdown';
const isReconnecting = connectionState === 'reconnecting';
const isNotConnecting = connectionState === 'not_connecting';

useEffect(
function initConnection() {
Expand All @@ -44,11 +63,13 @@ export function ConnectionBootstrap({
return;
}
setConnection(newConnection);
setConnectionState('connected');
} catch (e) {
if (isCanceled) {
return;
}
setError(e);
setConnectionState('not_connecting');
}
}
loadConnection();
Expand All @@ -59,25 +80,93 @@ export function ConnectionBootstrap({
[api, client]
);

useEffect(
function listenForDisconnect() {
if (connection == null || isShutdown) return;

// handles the disconnect event
function handleDisconnect(event: CustomEvent): void {
const { detail } = event;
log.info('Disconnect', `${JSON.stringify(detail)}`);
setConnectionState('reconnecting');
}
const removerFn = connection.addEventListener(
api.IdeConnection.EVENT_DISCONNECT,
handleDisconnect
);

return removerFn;
},
[api, connection, isShutdown]
);

useEffect(
function listenForReconnect() {
if (connection == null || isShutdown) return;

// handles the reconnect event
function handleReconnect(event: CustomEvent): void {
const { detail } = event;
log.info('Reconnect', `${JSON.stringify(detail)}`);
setConnectionState('connected');
}
const removerFn = connection.addEventListener(
api.CoreClient.EVENT_RECONNECT,
handleReconnect
);

return removerFn;
},
[api, connection, isShutdown]
);

useEffect(
function listenForShutdown() {
if (connection == null) return;

// handles the shutdown event
function handleShutdown(event: CustomEvent): void {
const { detail } = event;
log.info('Shutdown', `${JSON.stringify(detail)}`);
setError(`Server shutdown: ${detail ?? 'Unknown reason'}`);
setConnectionState('shutdown');
}

const removerFn = connection.addEventListener(
api.IdeConnection.EVENT_SHUTDOWN,
handleShutdown
);

return removerFn;
},
[api, connection]
);

useEffect(
function listenForAuthFailed() {
if (connection == null || isShutdown) return;

// handles the auth failed event
function handleAuthFailed(event: CustomEvent): void {
const { detail } = event;
log.warn(
'Reconnect authentication failed',
`${JSON.stringify(detail)}`
);
setError(
`Reconnect authentication failed: ${detail ?? 'Unknown reason'}`
);
setConnectionState('failed');
}
const removerFn = connection.addEventListener(
api.CoreClient.EVENT_RECONNECT_AUTH_FAILED,
handleAuthFailed
);

return removerFn;
},
[api, connection, isShutdown]
);

const objectFetcher = useCallback(
async (descriptor: dh.ide.VariableDescriptor) => {
assertNotNull(connection, 'No connection available to fetch object with');
Expand All @@ -104,21 +193,44 @@ export function ConnectionBootstrap({
[objectFetcher]
);

if (connection == null || error != null) {
function handleRefresh(): void {
log.info('Refreshing application');
window.location.reload();
}

if (isShutdown || connectionState === 'connecting' || isNotConnecting) {
return (
<LoadingOverlay
data-testid="connection-bootstrap-loading"
isLoading={connection == null}
isLoading={false}
errorMessage={error != null ? `${error}` : undefined}
/>
);
}

return (
<ConnectionContext.Provider value={connection}>
<ConnectionContext.Provider value={connection ?? null}>
<ObjectFetcherContext.Provider value={objectFetcher}>
<ObjectFetchManagerContext.Provider value={objectManager}>
{children}
<DebouncedModal isOpen={isReconnecting} debounceMs={1000}>
<InfoModal
icon={vsDebugDisconnect}
title={
<>
<LoadingSpinner /> Attempting to reconnect...
</>
}
subtitle="Please check your network connection."
/>
</DebouncedModal>
<BasicModal
confirmButtonText="Refresh"
onConfirm={handleRefresh}
isOpen={isAuthFailed}
headerText="Authentication failed"
bodyText="Credentials are invalid. Please refresh your browser to try and reconnect."
/>
</ObjectFetchManagerContext.Provider>
</ObjectFetcherContext.Provider>
</ConnectionContext.Provider>
Expand Down
Loading

0 comments on commit 3e52242

Please sign in to comment.