Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Reconnect Auth Fail Fix - embed-widget #2023

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading