@@ -38,8 +38,10 @@ const DEVTOOLS_PANEL_DEFAULT_WIDTH = 720;
3838const DEVTOOLS_PANEL_MIN_WIDTH = 420 ;
3939const DEVTOOLS_PANEL_MIN_VIEWPORT_WIDTH = 340 ;
4040const DEVTOOLS_PANEL_WIDTH_STEP = 40 ;
41+ const NOT_CONNECTED_MESSAGE = "Not connected" ;
4142
4243interface DevToolsPanelProps {
44+ disconnected : boolean ;
4345 onClose : ( ) => void ;
4446 overviewRequestKey : number ;
4547 selectedSimulator : SimulatorMetadata | null ;
@@ -73,8 +75,16 @@ interface DevToolsDiscovery {
7375type ChromeDiscoveryResult =
7476 PromiseSettledResult < ChromeDevToolsTargetDiscovery > ;
7577type WebKitDiscoveryResult = PromiseSettledResult < WebKitTargetDiscovery > ;
78+ type WebKitSocketState =
79+ | ""
80+ | "connecting"
81+ | "connected"
82+ | "reconnecting"
83+ | "disconnected"
84+ | "failed" ;
7685
7786export function DevToolsPanel ( {
87+ disconnected,
7888 onClose,
7989 overviewRequestKey,
8090 selectedSimulator,
@@ -89,6 +99,8 @@ export function DevToolsPanel({
8999 const [ error , setError ] = useState ( "" ) ;
90100 const [ frameLoaded , setFrameLoaded ] = useState ( false ) ;
91101 const [ overviewVisible , setOverviewVisible ] = useState ( false ) ;
102+ const [ webKitSocketState , setWebKitSocketState ] =
103+ useState < WebKitSocketState > ( "" ) ;
92104 const discoveryRef = useRef < DevToolsDiscovery | null > ( null ) ;
93105 const frameRef = useRef < HTMLIFrameElement | null > ( null ) ;
94106 const loadingTargetsRef = useRef ( false ) ;
@@ -142,6 +154,15 @@ export function DevToolsPanel({
142154 return ;
143155 }
144156
157+ if ( disconnected ) {
158+ applyDiscovery ( null ) ;
159+ applySelectedTargetId ( "" ) ;
160+ setError ( "" ) ;
161+ setIsLoading ( false ) ;
162+ setIsWebKitLoading ( false ) ;
163+ return ;
164+ }
165+
145166 if ( ! selectedSimulator ) {
146167 applyDiscovery ( null ) ;
147168 applySelectedTargetId ( "" ) ;
@@ -160,7 +181,7 @@ export function DevToolsPanel({
160181 }
161182 const requestId = ++ requestIdRef . current ;
162183 setIsLoading ( true ) ;
163- setError ( "" ) ;
184+ setError ( ( current ) => ( current === NOT_CONNECTED_MESSAGE ? current : "" ) ) ;
164185 try {
165186 const chromeTargets = requestWithTimeout (
166187 ( signal ) =>
@@ -350,6 +371,7 @@ export function DevToolsPanel({
350371 } , [
351372 applyDiscovery ,
352373 applySelectedTargetId ,
374+ disconnected ,
353375 selectedSimulator ?. isBooted ,
354376 selectedSimulator ?. udid ,
355377 ] ) ;
@@ -369,8 +391,24 @@ export function DevToolsPanel({
369391 setIsLoading ( false ) ;
370392 setIsWebKitLoading ( false ) ;
371393 setOverviewVisible ( false ) ;
394+ setWebKitSocketState ( "" ) ;
372395 } , [ applyDiscovery , applySelectedTargetId , selectedSimulator ?. udid ] ) ;
373396
397+ useEffect ( ( ) => {
398+ if ( ! disconnected ) {
399+ return ;
400+ }
401+ requestIdRef . current += 1 ;
402+ applyDiscovery ( null ) ;
403+ applySelectedTargetId ( "" ) ;
404+ setError ( "" ) ;
405+ setFrameLoaded ( false ) ;
406+ setIsLoading ( false ) ;
407+ setIsWebKitLoading ( false ) ;
408+ setOverviewVisible ( false ) ;
409+ setWebKitSocketState ( "" ) ;
410+ } , [ applyDiscovery , applySelectedTargetId , disconnected ] ) ;
411+
374412 useEffect ( ( ) => {
375413 if ( ! visible ) {
376414 return ;
@@ -384,8 +422,38 @@ export function DevToolsPanel({
384422
385423 useEffect ( ( ) => {
386424 setFrameLoaded ( false ) ;
425+ setWebKitSocketState ( "" ) ;
387426 } , [ frameUrl ] ) ;
388427
428+ useEffect ( ( ) => {
429+ function handleWebKitSocketState ( event : MessageEvent ) {
430+ if ( frameRef . current ?. contentWindow !== event . source ) {
431+ return ;
432+ }
433+ const data = event . data ;
434+ if (
435+ ! data ||
436+ typeof data !== "object" ||
437+ data . type !== "simdeck:webkit-inspector:socket"
438+ ) {
439+ return ;
440+ }
441+ const state = data . state ;
442+ if (
443+ state === "connecting" ||
444+ state === "connected" ||
445+ state === "reconnecting" ||
446+ state === "disconnected" ||
447+ state === "failed"
448+ ) {
449+ setWebKitSocketState ( state ) ;
450+ }
451+ }
452+
453+ window . addEventListener ( "message" , handleWebKitSocketState ) ;
454+ return ( ) => window . removeEventListener ( "message" , handleWebKitSocketState ) ;
455+ } , [ ] ) ;
456+
389457 useEffect ( ( ) => {
390458 if ( overviewRequestKey <= 0 ) {
391459 return ;
@@ -518,20 +586,34 @@ export function DevToolsPanel({
518586 }
519587
520588 const isDiscoveringTargets = isLoading || isWebKitLoading ;
521- const statusMessage =
522- error ||
523- ( ! selectedSimulator
524- ? "No simulator selected."
525- : isDiscoveringTargets && targets . length === 0
526- ? "Loading..."
527- : targets . length === 0
528- ? selectedSimulator . isBooted
529- ? "No DevTools targets. Open Safari, enable inspectable WKWebViews, start Metro, or launch a Chrome remote debugging target."
530- : "No DevTools targets. Boot the simulator for Safari/WebKit, or start Metro or Chrome remote debugging."
531- : "" ) ;
532- const emptyOverviewMessage = isDiscoveringTargets
533- ? "Loading..."
534- : "No targets" ;
589+ const effectivelyDisconnected =
590+ disconnected || error === NOT_CONNECTED_MESSAGE ;
591+ const chromeDevToolsBlocked = Boolean (
592+ selectedTarget && isChromeTarget ( selectedTarget ) && isSafariBrowser ( ) ,
593+ ) ;
594+ const webKitConnectionMessage =
595+ selectedTarget && isWebKitTarget ( selectedTarget )
596+ ? webKitSocketStatusMessage ( webKitSocketState )
597+ : "" ;
598+ const statusMessage = effectivelyDisconnected
599+ ? NOT_CONNECTED_MESSAGE
600+ : chromeDevToolsBlocked
601+ ? "Chrome DevTools don't work in Safari"
602+ : error ||
603+ ( ! selectedSimulator
604+ ? "No simulator selected."
605+ : isDiscoveringTargets && targets . length === 0
606+ ? "Loading..."
607+ : targets . length === 0
608+ ? selectedSimulator . isBooted
609+ ? "No DevTools targets. Open Safari, enable inspectable WKWebViews, start Metro, or launch a Chrome remote debugging target."
610+ : "No DevTools targets. Boot the simulator for Safari/WebKit, or start Metro or Chrome remote debugging."
611+ : "" ) ;
612+ const emptyOverviewMessage = effectivelyDisconnected
613+ ? NOT_CONNECTED_MESSAGE
614+ : isDiscoveringTargets
615+ ? "Loading..."
616+ : "No targets" ;
535617 const panelStyle = {
536618 "--webkit-panel-width" : `${ panelWidth } px` ,
537619 } as CSSProperties ;
@@ -618,7 +700,11 @@ export function DevToolsPanel({
618700 ) : null }
619701
620702 < div className = "webkit-frame-wrap" >
621- { overviewVisible ? (
703+ { effectivelyDisconnected || chromeDevToolsBlocked ? (
704+ < div className = { `webkit-status ${ error ? "error" : "" } ` } >
705+ { statusMessage }
706+ </ div >
707+ ) : overviewVisible ? (
622708 < DevToolsOverview
623709 emptyMessage = { emptyOverviewMessage }
624710 targets = { targets }
@@ -638,6 +724,10 @@ export function DevToolsPanel({
638724 < div className = "webkit-status" role = "status" >
639725 Loading...
640726 </ div >
727+ ) : webKitConnectionMessage ? (
728+ < div className = "webkit-status" role = "status" >
729+ { webKitConnectionMessage }
730+ </ div >
641731 ) : null }
642732 </ >
643733 ) : (
@@ -830,6 +920,31 @@ function isWebKitTarget(target: DevToolsTarget): boolean {
830920 ) ;
831921}
832922
923+ function isSafariBrowser ( ) : boolean {
924+ if ( typeof navigator === "undefined" ) {
925+ return false ;
926+ }
927+ const userAgent = navigator . userAgent ;
928+ return (
929+ / s a f a r i / i. test ( userAgent ) &&
930+ ! / c h r o m e | c h r o m i u m | c r i o s | f x i o s | e d g / i. test ( userAgent )
931+ ) ;
932+ }
933+
934+ function webKitSocketStatusMessage ( state : WebKitSocketState ) : string {
935+ switch ( state ) {
936+ case "connecting" :
937+ return "Connecting..." ;
938+ case "reconnecting" :
939+ case "failed" :
940+ return "Reconnecting..." ;
941+ case "disconnected" :
942+ return "Not connected" ;
943+ default :
944+ return "" ;
945+ }
946+ }
947+
833948function readStoredPanelWidth ( ) : number {
834949 if ( typeof window === "undefined" ) {
835950 return DEVTOOLS_PANEL_DEFAULT_WIDTH ;
@@ -1037,6 +1152,9 @@ function userFacingDevToolsMessage(message: string): string {
10371152 }
10381153
10391154 const lower = normalized . toLowerCase ( ) ;
1155+ if ( isProviderDisconnectedMessage ( normalized ) ) {
1156+ return NOT_CONNECTED_MESSAGE ;
1157+ }
10401158 if (
10411159 lower . includes ( "no app inspector found" ) ||
10421160 lower . includes ( "no connected websocket inspector found" ) ||
@@ -1058,6 +1176,18 @@ function userFacingDevToolsMessage(message: string): string {
10581176 return normalized ;
10591177}
10601178
1179+ function isProviderDisconnectedMessage ( message : string ) : boolean {
1180+ const lower = message . trim ( ) . toLowerCase ( ) ;
1181+ return (
1182+ lower . includes ( "failed to fetch" ) ||
1183+ lower . includes ( "load failed" ) ||
1184+ lower . includes ( "networkerror" ) ||
1185+ lower . includes ( "network error" ) ||
1186+ lower . includes ( "timed out loading chrome devtools targets" ) ||
1187+ lower . includes ( "timed out loading webkit targets" )
1188+ ) ;
1189+ }
1190+
10611191function cleanDevToolsMessages ( messages : string [ ] ) : string [ ] {
10621192 return messages . flatMap ( ( message ) => {
10631193 const nextMessage = userFacingDevToolsMessage ( message ) ;
0 commit comments