1- import { useCallback , useEffect , useRef , useState } from 'react' ;
1+ import { Component , useCallback , useEffect , useRef , useState } from 'react' ;
2+ import type { ErrorInfo , ReactNode } from 'react' ;
23import { WebBridge } from '../bridge/transport' ;
34import type { Protocol } from '../bridge/utils' ;
45import WebviewApp from '@webview/App' ;
56import './RemoteSession.css' ;
67
8+ /** Error boundary to catch crashes in RemoteSession / WebviewApp. */
9+ class SessionErrorBoundary extends Component <
10+ { children : ReactNode } ,
11+ { error : Error | null }
12+ > {
13+ state : { error : Error | null } = { error : null } ;
14+
15+ static getDerivedStateFromError ( error : Error ) {
16+ return { error } ;
17+ }
18+
19+ componentDidCatch ( error : Error , info : ErrorInfo ) {
20+ console . error ( '[SessionErrorBoundary]' , error , info . componentStack ) ;
21+ }
22+
23+ render ( ) {
24+ if ( this . state . error ) {
25+ return (
26+ < div style = { {
27+ padding : '24px' , color : '#f44' , fontFamily : 'monospace' ,
28+ fontSize : '13px' , background : '#12121e' , flex : 1 , overflow : 'auto' ,
29+ } } >
30+ < h3 style = { { color : '#ff6' , margin : '0 0 12px' } } > Session crashed</ h3 >
31+ < div style = { { marginBottom : '8px' } } > < b > { this . state . error . name } :</ b > { this . state . error . message } </ div >
32+ < pre style = { { whiteSpace : 'pre-wrap' , color : '#888' , fontSize : '11px' } } >
33+ { this . state . error . stack }
34+ </ pre >
35+ < button
36+ onClick = { ( ) => this . setState ( { error : null } ) }
37+ style = { {
38+ marginTop : '16px' , padding : '8px 16px' , background : '#22223a' ,
39+ color : '#e0e4ea' , border : '1px solid #2a2a3e' , borderRadius : '4px' , cursor : 'pointer' ,
40+ } }
41+ >
42+ Retry
43+ </ button >
44+ </ div >
45+ ) ;
46+ }
47+ return this . props . children ;
48+ }
49+ }
50+
751export type SessionStatus = 'connecting' | 'connected' | 'error' ;
852
953interface RemoteSessionProps {
@@ -85,32 +129,23 @@ export function RemoteSession({ host, password, protocol, onStatusChange, onBrid
85129 </ div >
86130 ) ;
87131
88- if ( state . status === 'connected' ) {
89- return (
90- < div className = "remote-session" >
91- < WebviewApp />
92- { debugBanner }
93- </ div >
94- ) ;
95- }
96-
97- if ( state . status === 'error' ) {
98- return (
99- < div className = "remote-session-status" >
100- < div className = "remote-session-error" >
101- < i className = "codicon codicon-warning" />
102- < span > { state . message } </ span >
103- </ div >
104- < button className = "remote-session-retry" onClick = { connect } >
105- Retry
106- </ button >
107- { debugBanner }
132+ const content = state . status === 'connected' ? (
133+ < div className = "remote-session" >
134+ < WebviewApp />
135+ { debugBanner }
136+ </ div >
137+ ) : state . status === 'error' ? (
138+ < div className = "remote-session-status" >
139+ < div className = "remote-session-error" >
140+ < i className = "codicon codicon-warning" />
141+ < span > { state . message } </ span >
108142 </ div >
109- ) ;
110- }
111-
112- // Connecting
113- return (
143+ < button className = "remote-session-retry" onClick = { connect } >
144+ Retry
145+ </ button >
146+ { debugBanner }
147+ </ div >
148+ ) : (
114149 < div className = "remote-session-status" >
115150 < div className = "remote-session-connecting" >
116151 < div className = "remote-session-spinner" />
@@ -119,4 +154,6 @@ export function RemoteSession({ host, password, protocol, onStatusChange, onBrid
119154 { debugBanner }
120155 </ div >
121156 ) ;
157+
158+ return < SessionErrorBoundary > { content } </ SessionErrorBoundary > ;
122159}
0 commit comments