11import 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' ;
39import {
410 ObjectFetcherContext ,
511 ObjectFetchManager ,
@@ -11,6 +17,7 @@ import {
1117import type { dh } from '@deephaven/jsapi-types' ;
1218import Log from '@deephaven/log' ;
1319import { assertNotNull } from '@deephaven/utils' ;
20+ import { vsDebugDisconnect } from '@deephaven/icons' ;
1421import ConnectionContext from './ConnectionContext' ;
1522
1623const 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