@@ -308,78 +308,85 @@ var SignalROpenApiPlugin = function (system) {
308308 return Promise . resolve ( _hubs [ hubPath ] ) ;
309309 }
310310
311- var token = _getAccessToken ( ) ;
312- var configs = system . getConfigs ? system . getConfigs ( ) : { } ;
313- var options = { } ;
314- if ( token ) {
315- options . accessTokenFactory = function ( ) { return token ; } ;
316- }
317-
318- // Apply custom headers from configuration (e.g. "X-Custom-Header")
319- var configuredHeaders = configs . signalRHeaders ;
320- if ( configuredHeaders && typeof configuredHeaders === "object" ) {
321- options . headers = { } ;
322- Object . keys ( configuredHeaders ) . forEach ( function ( key ) {
323- options . headers [ key ] = configuredHeaders [ key ] ;
324- } ) ;
325- }
311+ // Wrap in try/catch so synchronous errors (e.g. URL resolution
312+ // failures in Electron/Node-like environments) are surfaced as
313+ // rejected promises instead of uncaught exceptions.
314+ try {
315+ var token = _getAccessToken ( ) ;
316+ var configs = system . getConfigs ? system . getConfigs ( ) : { } ;
317+ var options = { } ;
318+ if ( token ) {
319+ options . accessTokenFactory = function ( ) { return token ; } ;
320+ }
326321
327- // Merge apiKey headers entered via the SwaggerUI Authorize dialog
328- var apiKeyHeaders = _getApiKeyHeaders ( ) ;
329- if ( apiKeyHeaders ) {
330- if ( ! options . headers ) {
322+ // Apply custom headers from configuration (e.g. "X-Custom-Header")
323+ var configuredHeaders = configs . signalRHeaders ;
324+ if ( configuredHeaders && typeof configuredHeaders === "object" ) {
331325 options . headers = { } ;
326+ Object . keys ( configuredHeaders ) . forEach ( function ( key ) {
327+ options . headers [ key ] = configuredHeaders [ key ] ;
328+ } ) ;
332329 }
333330
334- Object . keys ( apiKeyHeaders ) . forEach ( function ( key ) {
335- options . headers [ key ] = apiKeyHeaders [ key ] ;
336- } ) ;
337- }
331+ // Merge apiKey headers entered via the SwaggerUI Authorize dialog
332+ var apiKeyHeaders = _getApiKeyHeaders ( ) ;
333+ if ( apiKeyHeaders ) {
334+ if ( ! options . headers ) {
335+ options . headers = { } ;
336+ }
338337
339- // Browsers cannot send custom HTTP headers on WebSocket or
340- // Server-Sent Events connections. When custom headers are
341- // configured, fall back to Long Polling which includes headers
342- // with every HTTP request so the server can read them on each
343- // hub invocation.
344- if ( options . headers && Object . keys ( options . headers ) . length > 0 ) {
345- options . transport = signalR . HttpTransportType . LongPolling ;
346- }
338+ Object . keys ( apiKeyHeaders ) . forEach ( function ( key ) {
339+ options . headers [ key ] = apiKeyHeaders [ key ] ;
340+ } ) ;
341+ }
347342
348- var connection = new signalR . HubConnectionBuilder ( )
349- . withUrl ( hubPath , options )
350- . withAutomaticReconnect ( )
351- . build ( ) ;
343+ // Browsers cannot send custom HTTP headers on WebSocket or
344+ // Server-Sent Events connections. When custom headers are
345+ // configured, fall back to Long Polling which includes headers
346+ // with every HTTP request so the server can read them on each
347+ // hub invocation.
348+ if ( options . headers && Object . keys ( options . headers ) . length > 0 ) {
349+ options . transport = signalR . HttpTransportType . LongPolling ;
350+ }
352351
353- _hubs [ hubPath ] = connection ;
352+ var connection = new signalR . HubConnectionBuilder ( )
353+ . withUrl ( _resolveHubUrl ( hubPath ) , options )
354+ . withAutomaticReconnect ( )
355+ . build ( ) ;
354356
355- // Store the auth fingerprint so we can detect changes later
356- _hubAuthFingerprints [ hubPath ] = currentFingerprint ;
357+ _hubs [ hubPath ] = connection ;
357358
358- // Track connection state changes for UI updates
359- connection . onreconnecting ( function ( ) {
360- if ( _eventListeners [ hubPath ] ) {
361- _eventListeners [ hubPath ] . forEach ( function ( fn ) { fn ( ) ; } ) ;
362- }
363- } ) ;
359+ // Store the auth fingerprint so we can detect changes later
360+ _hubAuthFingerprints [ hubPath ] = currentFingerprint ;
364361
365- connection . onreconnected ( function ( ) {
366- if ( _eventListeners [ hubPath ] ) {
367- _eventListeners [ hubPath ] . forEach ( function ( fn ) { fn ( ) ; } ) ;
368- }
369- } ) ;
362+ // Track connection state changes for UI updates
363+ connection . onreconnecting ( function ( ) {
364+ if ( _eventListeners [ hubPath ] ) {
365+ _eventListeners [ hubPath ] . forEach ( function ( fn ) { fn ( ) ; } ) ;
366+ }
367+ } ) ;
370368
371- connection . onclose ( function ( ) {
372- if ( _eventListeners [ hubPath ] ) {
373- _eventListeners [ hubPath ] . forEach ( function ( fn ) { fn ( ) ; } ) ;
374- }
375- } ) ;
369+ connection . onreconnected ( function ( ) {
370+ if ( _eventListeners [ hubPath ] ) {
371+ _eventListeners [ hubPath ] . forEach ( function ( fn ) { fn ( ) ; } ) ;
372+ }
373+ } ) ;
374+
375+ connection . onclose ( function ( ) {
376+ if ( _eventListeners [ hubPath ] ) {
377+ _eventListeners [ hubPath ] . forEach ( function ( fn ) { fn ( ) ; } ) ;
378+ }
379+ } ) ;
376380
377- // Subscribe to client events once connected
378- _subscribeClientEvents ( hubPath , connection ) ;
381+ // Subscribe to client events once connected
382+ _subscribeClientEvents ( hubPath , connection ) ;
379383
380- return connection . start ( ) . then ( function ( ) {
381- return connection ;
382- } ) ;
384+ return connection . start ( ) . then ( function ( ) {
385+ return connection ;
386+ } ) ;
387+ } catch ( err ) {
388+ return Promise . reject ( err ) ;
389+ }
383390 } ;
384391
385392 // Parse the x-signalr extension from an operation
@@ -406,6 +413,39 @@ var SignalROpenApiPlugin = function (system) {
406413 return signalrExt . hubPath || ( "/" + signalrExt . hub . toLowerCase ( ) ) ;
407414 } ;
408415
416+ // Resolve a relative hub path to an absolute URL so the SignalR client
417+ // does not need to use its platform-specific URL resolution logic.
418+ // In Electron / Electron.NET renderers the SignalR isBrowser check
419+ // returns false (Node.js process global is present), causing
420+ // "Cannot resolve '/...'" errors for relative paths. Absolute URLs
421+ // starting with http:// or https:// bypass that check entirely.
422+ var _resolveHubUrl = function ( hubPath ) {
423+ if ( hubPath . indexOf ( "http://" ) === 0 || hubPath . indexOf ( "https://" ) === 0 ) {
424+ return hubPath ;
425+ }
426+
427+ // Prefer the first server URL from the OpenAPI spec (matches what
428+ // SwaggerUI shows the user and handles reverse-proxy scenarios).
429+ var specIm = system . specSelectors . specJson ( ) ;
430+ var serversIm = specIm && specIm . get ( "servers" ) ;
431+ if ( serversIm && serversIm . size > 0 ) {
432+ var serverUrl = serversIm . getIn ( [ 0 , "url" ] ) ;
433+ if ( serverUrl && typeof serverUrl === "string"
434+ && ( serverUrl . indexOf ( "http://" ) === 0 || serverUrl . indexOf ( "https://" ) === 0 ) ) {
435+ // Strip trailing slash from server URL before appending path
436+ return serverUrl . replace ( / \/ + $ / , "" ) + hubPath ;
437+ }
438+ }
439+
440+ // Fall back to the current page origin
441+ if ( typeof window !== "undefined" && window . location && window . location . origin ) {
442+ return window . location . origin + hubPath ;
443+ }
444+
445+ // Last resort: return as-is (standard browser environments resolve it fine)
446+ return hubPath ;
447+ } ;
448+
409449 // Discover all unique hub paths from the spec's x-signalr extensions.
410450 // Returns an array of { hubPath, hubName } objects.
411451 var _getHubPaths = function ( ) {
@@ -663,6 +703,10 @@ var SignalROpenApiPlugin = function (system) {
663703 var isManualConnecting = connectingHook [ 0 ] ;
664704 var setManualConnecting = connectingHook [ 1 ] ;
665705
706+ var errorHook = React . useState ( null ) ;
707+ var connectionError = errorHook [ 0 ] ;
708+ var setConnectionError = errorHook [ 1 ] ;
709+
666710 React . useEffect ( function ( ) {
667711 var listener = function ( ) { forceUpdate ( function ( n ) { return n + 1 ; } ) ; } ;
668712
@@ -682,19 +726,23 @@ var SignalROpenApiPlugin = function (system) {
682726
683727 var handleConnect = function ( ) {
684728 setManualConnecting ( true ) ;
729+ setConnectionError ( null ) ;
685730 _getOrCreateHub ( hubPath )
686731 . then ( function ( ) {
687732 setManualConnecting ( false ) ;
688733 forceUpdate ( function ( n ) { return n + 1 ; } ) ;
689734 } )
690735 . catch ( function ( err ) {
691736 setManualConnecting ( false ) ;
737+ var message = err && ( err . message || err . toString ( ) ) || "Unknown error" ;
692738 console . error ( "[SignalR OpenAPI] Manual connect failed:" , err ) ;
739+ setConnectionError ( message ) ;
693740 forceUpdate ( function ( n ) { return n + 1 ; } ) ;
694741 } ) ;
695742 } ;
696743
697744 var handleDisconnect = function ( ) {
745+ setConnectionError ( null ) ;
698746 _disconnectHub ( hubPath ) . then ( function ( ) {
699747 forceUpdate ( function ( n ) { return n + 1 ; } ) ;
700748 } ) ;
@@ -716,16 +764,29 @@ var SignalROpenApiPlugin = function (system) {
716764 ? handleConnect
717765 : undefined ;
718766
719- return React . createElement ( "div" , {
720- className : "signalr-hub-connection-bar" ,
721- onClick : function ( e ) { e . stopPropagation ( ) ; } ,
722- } ,
767+ var children = [
723768 React . createElement ( "button" , {
769+ key : "btn" ,
724770 className : buttonClass ,
725771 onClick : handleClick ,
726772 disabled : showConnecting ,
727- } , buttonText )
728- ) ;
773+ } , buttonText ) ,
774+ ] ;
775+
776+ if ( connectionError ) {
777+ children . push (
778+ React . createElement ( "span" , {
779+ key : "err" ,
780+ className : "signalr-connection-error" ,
781+ title : connectionError ,
782+ } , connectionError )
783+ ) ;
784+ }
785+
786+ return React . createElement ( "div" , {
787+ className : "signalr-hub-connection-bar" ,
788+ onClick : function ( e ) { e . stopPropagation ( ) ; } ,
789+ } , children ) ;
729790 }
730791
731792 // React component for client event log panel
@@ -744,9 +805,14 @@ var SignalROpenApiPlugin = function (system) {
744805 var isConnected = _hubs [ hubPath ] && _hubs [ hubPath ] . state === signalR . HubConnectionState . Connected ;
745806
746807 var connectAndListen = function ( ) {
747- _getOrCreateHub ( hubPath ) . then ( function ( ) {
748- forceUpdate ( function ( n ) { return n + 1 ; } ) ;
749- } ) ;
808+ _getOrCreateHub ( hubPath )
809+ . then ( function ( ) {
810+ forceUpdate ( function ( n ) { return n + 1 ; } ) ;
811+ } )
812+ . catch ( function ( err ) {
813+ console . error ( "[SignalR OpenAPI] Connect & Listen failed:" , err ) ;
814+ forceUpdate ( function ( n ) { return n + 1 ; } ) ;
815+ } ) ;
750816 } ;
751817
752818 var clearLog = function ( ) {
0 commit comments