@@ -18,10 +18,54 @@ const rclnodejs = require('rclnodejs');
1818const WebSocket = require ( 'ws' ) ;
1919const Bridge = require ( './lib/bridge.js' ) ;
2020const debug = require ( 'debug' ) ( 'ros2-web-bridge:index' ) ;
21+
22+ // rclnodejs node
23+ let node ;
24+
25+ // Websocket server (or client if client mode set via --address)
26+ let server ;
27+ let connectionAttempts = 0 ;
28+
29+ // Map of bridge IDs to Bridge objects
30+ let bridgeMap = new Map ( ) ;
31+
32+ function closeAllBridges ( ) {
33+ bridgeMap . forEach ( ( bridge , bridgeId ) => {
34+ bridge . close ( ) ;
35+ } ) ;
36+ }
37+
38+ function shutDown ( error ) {
39+ // Closing the server triggers the individual connections to be closed.
40+ if ( server ) {
41+ server . close ( ) ;
42+ }
43+ if ( ! rclnodejs . isShutdown ( ) ) {
44+ rclnodejs . shutdown ( ) ;
45+ }
46+ if ( error ) {
47+ throw error ;
48+ }
49+ }
50+
2151function createServer ( options ) {
2252 options = options || { } ;
2353 options . address = options . address || null ;
24- let server ;
54+ process . on ( 'exit' , ( ) => {
55+ debug ( 'Application will exit.' ) ;
56+ shutDown ( ) ;
57+ } ) ;
58+ return rclnodejs . init ( )
59+ . then ( ( ) => {
60+ node = rclnodejs . createNode ( 'ros2_web_bridge' ) ;
61+ rclnodejs . spin ( node ) ;
62+ debug ( 'ROS2 node started' ) ;
63+ createConnection ( options ) ;
64+ } )
65+ . catch ( error => shutDown ( error ) ) ;
66+ }
67+
68+ function createConnection ( options ) {
2569 if ( options . address != null ) {
2670 debug ( 'Starting in client mode; connecting to ' + options . address ) ;
2771 server = new WebSocket ( options . address ) ;
@@ -31,63 +75,53 @@ function createServer(options) {
3175 server = new WebSocket . Server ( { port : options . port } ) ;
3276 }
3377
34- process . on ( 'exit' , ( ) => {
35- debug ( 'Application will exit.' ) ;
36- // Closing the server will trigger the individual connections to be closed and cleaned.
37- server . close ( ) ;
38- } ) ;
78+ const makeBridge = ( ws ) => {
79+ let bridge = new Bridge ( node , ws , options . status_level ) ;
80+ bridgeMap . set ( bridge . bridgeId , bridge ) ;
3981
40- return rclnodejs . init ( ) . then ( ( ) => {
41- let node = rclnodejs . createNode ( 'ros2_web_bridge' ) ;
42- let bridgeMap = new Map ( ) ;
82+ bridge . on ( 'error' , ( error ) => {
83+ debug ( `Bridge ${ bridge . bridgeId } closing with error: ${ error } ` ) ;
84+ bridge . close ( ) ;
85+ bridgeMap . delete ( bridge . bridgeId ) ;
86+ } ) ;
4387
44- function closeAllBridges ( ) {
45- bridgeMap . forEach ( ( bridge , bridgeId ) => {
46- bridge . close ( ) ;
47- } ) ;
48- }
88+ bridge . on ( 'close' , ( bridgeId ) => {
89+ bridgeMap . delete ( bridgeId ) ;
90+ } ) ;
91+ } ;
4992
50- const makeBridge = ( ws ) => {
51- let bridge = new Bridge ( node , ws , options . status_level ) ;
52- bridgeMap . set ( bridge . bridgeId , bridge ) ;
53-
54- bridge . on ( 'error' , ( error ) => {
55- let bridge = error . bridge ;
56- if ( bridge ) {
57- debug ( `Error happened, the bridge ${ error . bridge . bridgeId } will be closed.` ) ;
58- bridge . close ( ) ;
59- bridgeMap . delete ( bridge . bridgeId ) ;
60- } else {
61- debug ( `Unknown error happened: ${ error } .` ) ;
62- }
63- } ) ;
64-
65- bridge . on ( 'close' , ( bridgeId ) => {
66- bridgeMap . delete ( bridgeId ) ;
67- } ) ;
68- } ;
69- server . on ( 'open' , ( ) => debug ( 'Connected as client' ) ) ;
70- if ( options . address ) {
71- makeBridge ( server ) ;
72- } else {
73- server . on ( 'connection' , makeBridge ) ;
74- }
93+ server . on ( 'open' , ( ) => {
94+ debug ( 'Connected as client' ) ;
95+ connectionAttempts = 0 ;
96+ } ) ;
97+
98+ if ( options . address ) {
99+ makeBridge ( server ) ;
100+ } else {
101+ server . on ( 'connection' , makeBridge ) ;
102+ }
75103
76- server . on ( 'error' , ( error ) => {
77- closeAllBridges ( ) ;
78- rclnodejs . shutdown ( ) ;
79- debug ( `WebSocket server error: ${ error } , the module will be terminated.` ) ;
80- } ) ;
104+ server . on ( 'error' , ( error ) => {
105+ closeAllBridges ( ) ;
106+ debug ( `WebSocket error: ${ error } ` ) ;
107+ } ) ;
81108
82- rclnodejs . spin ( node ) ;
83- debug ( 'The ros2-web-bridge has started.' ) ;
84- let wsAddr = ( options . address ) ? options . address : `ws://localhost:${ options . port } ` ;
85- console . log ( `Websocket started on ${ wsAddr } ` ) ;
86- } ) . catch ( error => {
87- debug ( `Unknown error happened: ${ error } , the module will be terminated.` ) ;
88- server . close ( ) ;
89- rclnodejs . shutdown ( ) ;
109+ server . on ( 'close' , ( event ) => {
110+ debug ( `Websocket closed: ${ event } ` ) ;
111+ if ( options . address ) {
112+ closeAllBridges ( ) ;
113+ connectionAttempts ++ ;
114+ // Gradually increase reconnection interval to prevent
115+ // overwhelming the server, up to a maximum delay of ~1 minute
116+ // https://en.wikipedia.org/wiki/Exponential_backoff
117+ const delay = Math . pow ( 1.5 , Math . min ( 10 , Math . floor ( Math . random ( ) * connectionAttempts ) ) ) ;
118+ debug ( `Reconnecting to ${ options . address } in ${ delay . toFixed ( 2 ) } seconds` ) ;
119+ setTimeout ( ( ) => createConnection ( options ) , delay * 1000 ) ;
120+ }
90121 } ) ;
122+
123+ let wsAddr = options . address || `ws://localhost:${ options . port } ` ;
124+ console . log ( `Websocket started on ${ wsAddr } ` ) ;
91125}
92126
93127module . exports = {
0 commit comments