Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
4 changes: 2 additions & 2 deletions packages/app-utils/src/components/AppBootstrap.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,8 @@ it('should log in automatically when the anonymous handler is supported', async
});

expect(screen.queryByTestId('auth-base-loading')).toBeNull();
expect(screen.queryByTestId('connection-bootstrap-loading')).not.toBeNull();
expect(screen.queryByText(mockChildText)).toBeNull();
expect(screen.queryByTestId('connection-bootstrap-loading')).toBeNull();
expect(screen.queryByText(mockChildText)).not.toBeNull();
Comment thread
AkshatJawne marked this conversation as resolved.
Outdated
expect(mockChannel.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
message: BROADCAST_LOGIN_MESSAGE,
Expand Down
145 changes: 133 additions & 12 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, useState } from 'react';
import { LoadingOverlay } from '@deephaven/components';
import {
BasicModal,
DebouncedModal,
InfoModal,
LoadingOverlay,
LoadingSpinner,
} from '@deephaven/components';
import {
ObjectFetcherContext,
sanitizeVariableDescriptor,
Expand All @@ -9,6 +15,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 @@ -29,8 +36,13 @@ export function ConnectionBootstrap({
}: ConnectionBootstrapProps): JSX.Element {
const api = useApi();
const client = useClient();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Comment thread
AkshatJawne marked this conversation as resolved.
Outdated
const [error, setError] = useState<unknown>();
Comment thread
AkshatJawne marked this conversation as resolved.
Outdated
const [connection, setConnection] = useState<dh.IdeConnection>();
const [isAuthFailed, setIsAuthFailed] = useState<boolean>(false);
const [isShutdown, setIsShutdown] = useState<boolean>(false);
const [isReconnecting, setIsReconnecting] = useState<boolean>(false);

useEffect(
function initConnection() {
let isCanceled = false;
Expand All @@ -56,25 +68,96 @@ 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)}`);
setIsReconnecting(true);
}
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)}`);
setIsReconnecting(false);
}
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'}`);
setIsShutdown(true);
setIsReconnecting(false);
setIsAuthFailed(false);
}

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'}`
);
setIsAuthFailed(true);
setIsReconnecting(false);
}
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 @@ -83,20 +166,58 @@ export function ConnectionBootstrap({
[connection]
);

if (connection == null || error != null) {
return (
<LoadingOverlay
data-testid="connection-bootstrap-loading"
isLoading={connection == null}
Comment thread
AkshatJawne marked this conversation as resolved.
errorMessage={error != null ? `${error}` : undefined}
/>
);
Comment thread
AkshatJawne marked this conversation as resolved.
function handleRefresh(): void {
log.info('Refreshing application');
window.location.reload();
}

return (
<ConnectionContext.Provider value={connection}>
<ConnectionContext.Provider value={connection ?? null}>
<ObjectFetcherContext.Provider value={objectFetcher}>
{children}
{(() => {
if (isShutdown) {
return (
Comment thread
AkshatJawne marked this conversation as resolved.
Outdated
<LoadingOverlay
isLoading={false}
errorMessage={error != null ? `${error}` : undefined}
/>
);
}
if (isReconnecting) {
return (
<>
{children}
<DebouncedModal isOpen={isReconnecting} debounceMs={1000}>
<InfoModal
icon={vsDebugDisconnect}
title={
<>
<LoadingSpinner /> Attempting to reconnect...
</>
}
subtitle="Please check your network connection."
/>
</DebouncedModal>
</>
);
}
if (isAuthFailed) {
return (
<>
{children}
<BasicModal
confirmButtonText="Refresh"
onConfirm={handleRefresh}
isOpen={isAuthFailed}
headerText="Authentication failed"
bodyText="Credentials are invalid. Please refresh your browser to try and reconnect."
/>
</>
);
}
// If none of the conditions are met, render the children
return children;
})()}
</ObjectFetcherContext.Provider>
</ConnectionContext.Provider>
);
Expand Down
Loading