@@ -161,9 +161,68 @@ interface ScreenshotCallback {
161
161
162
162
const screenshotCallbacks = new Map < string , ScreenshotCallback > ( ) ;
163
163
164
- const app = express ( ) ;
165
- const PORT = parseInt ( process . env . PORT || "3025" , 10 ) ;
164
+ // Function to get available port starting with the given port
165
+ async function getAvailablePort (
166
+ startPort : number ,
167
+ maxAttempts : number = 10
168
+ ) : Promise < number > {
169
+ let currentPort = startPort ;
170
+ let attempts = 0 ;
171
+
172
+ while ( attempts < maxAttempts ) {
173
+ try {
174
+ // Try to create a server on the current port
175
+ // We'll use a raw Node.js net server for just testing port availability
176
+ await new Promise < void > ( ( resolve , reject ) => {
177
+ const testServer = require ( "net" ) . createServer ( ) ;
178
+
179
+ // Handle errors (e.g., port in use)
180
+ testServer . once ( "error" , ( err : any ) => {
181
+ if ( err . code === "EADDRINUSE" ) {
182
+ console . log ( `Port ${ currentPort } is in use, trying next port...` ) ;
183
+ currentPort ++ ;
184
+ attempts ++ ;
185
+ resolve ( ) ; // Continue to next iteration
186
+ } else {
187
+ reject ( err ) ; // Different error, propagate it
188
+ }
189
+ } ) ;
190
+
191
+ // If we can listen, the port is available
192
+ testServer . once ( "listening" , ( ) => {
193
+ // Make sure to close the server to release the port
194
+ testServer . close ( ( ) => {
195
+ console . log ( `Found available port: ${ currentPort } ` ) ;
196
+ resolve ( ) ;
197
+ } ) ;
198
+ } ) ;
199
+
200
+ // Try to listen on the current port
201
+ testServer . listen ( currentPort , currentSettings . serverHost ) ;
202
+ } ) ;
203
+
204
+ // If we reach here without incrementing the port, it means the port is available
205
+ return currentPort ;
206
+ } catch ( error : any ) {
207
+ console . error ( `Error checking port ${ currentPort } :` , error ) ;
208
+ // For non-EADDRINUSE errors, try the next port
209
+ currentPort ++ ;
210
+ attempts ++ ;
211
+ }
212
+ }
213
+
214
+ // If we've exhausted all attempts, throw an error
215
+ throw new Error (
216
+ `Could not find an available port after ${ maxAttempts } attempts starting from ${ startPort } `
217
+ ) ;
218
+ }
219
+
220
+ // Start with requested port and find an available one
221
+ const REQUESTED_PORT = parseInt ( process . env . PORT || "3025" , 10 ) ;
222
+ let PORT = REQUESTED_PORT ;
166
223
224
+ // Create application and initialize middleware
225
+ const app = express ( ) ;
167
226
app . use ( cors ( ) ) ;
168
227
// Increase JSON body parser limit to 50MB to handle large screenshots
169
228
app . use ( bodyParser . json ( { limit : "50mb" } ) ) ;
@@ -824,38 +883,88 @@ export class BrowserConnector {
824
883
}
825
884
}
826
885
827
- // Move the server creation before BrowserConnector instantiation
828
- const server = app . listen ( PORT , currentSettings . serverHost , ( ) => {
829
- console . log ( `\n=== Browser Tools Server Started ===` ) ;
830
- console . log (
831
- `Aggregator listening on http://${ currentSettings . serverHost } :${ PORT } `
832
- ) ;
886
+ // Use an async IIFE to allow for async/await in the initial setup
887
+ ( async ( ) => {
888
+ try {
889
+ console . log ( `Starting Browser Tools Server...` ) ;
890
+ console . log ( `Requested port: ${ REQUESTED_PORT } ` ) ;
891
+
892
+ // Find an available port
893
+ try {
894
+ PORT = await getAvailablePort ( REQUESTED_PORT ) ;
895
+
896
+ if ( PORT !== REQUESTED_PORT ) {
897
+ console . log ( `\n====================================` ) ;
898
+ console . log ( `NOTICE: Requested port ${ REQUESTED_PORT } was in use.` ) ;
899
+ console . log ( `Using port ${ PORT } instead.` ) ;
900
+ console . log ( `====================================\n` ) ;
901
+ }
902
+ } catch ( portError ) {
903
+ console . error ( `Failed to find an available port:` , portError ) ;
904
+ process . exit ( 1 ) ;
905
+ }
833
906
834
- // Log all available network interfaces for easier discovery
835
- const networkInterfaces = os . networkInterfaces ( ) ;
836
- console . log ( "\nAvailable on the following network addresses:" ) ;
907
+ // Create the server with the available port
908
+ const server = app . listen ( PORT , currentSettings . serverHost , ( ) => {
909
+ console . log ( `\n=== Browser Tools Server Started ===` ) ;
910
+ console . log (
911
+ `Aggregator listening on http://${ currentSettings . serverHost } :${ PORT } `
912
+ ) ;
837
913
838
- Object . keys ( networkInterfaces ) . forEach ( ( interfaceName ) => {
839
- const interfaces = networkInterfaces [ interfaceName ] ;
840
- if ( interfaces ) {
841
- interfaces . forEach ( ( iface ) => {
842
- if ( ! iface . internal && iface . family === "IPv4" ) {
843
- console . log ( ` - http://${ iface . address } :${ PORT } ` ) ;
914
+ if ( PORT !== REQUESTED_PORT ) {
915
+ console . log (
916
+ `NOTE: Using fallback port ${ PORT } instead of requested port ${ REQUESTED_PORT } `
917
+ ) ;
918
+ }
919
+
920
+ // Log all available network interfaces for easier discovery
921
+ const networkInterfaces = os . networkInterfaces ( ) ;
922
+ console . log ( "\nAvailable on the following network addresses:" ) ;
923
+
924
+ Object . keys ( networkInterfaces ) . forEach ( ( interfaceName ) => {
925
+ const interfaces = networkInterfaces [ interfaceName ] ;
926
+ if ( interfaces ) {
927
+ interfaces . forEach ( ( iface ) => {
928
+ if ( ! iface . internal && iface . family === "IPv4" ) {
929
+ console . log ( ` - http://${ iface . address } :${ PORT } ` ) ;
930
+ }
931
+ } ) ;
844
932
}
845
933
} ) ;
846
- }
847
- } ) ;
848
934
849
- console . log ( `\nFor local access use: http://localhost:${ PORT } ` ) ;
850
- } ) ;
935
+ console . log ( `\nFor local access use: http://localhost:${ PORT } ` ) ;
936
+ } ) ;
851
937
852
- // Initialize the browser connector with the existing app AND server
853
- const browserConnector = new BrowserConnector ( app , server ) ;
938
+ // Handle server startup errors
939
+ server . on ( "error" , ( err : any ) => {
940
+ if ( err . code === "EADDRINUSE" ) {
941
+ console . error (
942
+ `ERROR: Port ${ PORT } is still in use, despite our checks!`
943
+ ) ;
944
+ console . error (
945
+ `This might indicate another process started using this port after our check.`
946
+ ) ;
947
+ } else {
948
+ console . error ( `Server error:` , err ) ;
949
+ }
950
+ process . exit ( 1 ) ;
951
+ } ) ;
854
952
855
- // Handle shutdown gracefully
856
- process . on ( "SIGINT" , ( ) => {
857
- server . close ( ( ) => {
858
- console . log ( "Server shut down" ) ;
859
- process . exit ( 0 ) ;
860
- } ) ;
953
+ // Initialize the browser connector with the existing app AND server
954
+ const browserConnector = new BrowserConnector ( app , server ) ;
955
+
956
+ // Handle shutdown gracefully
957
+ process . on ( "SIGINT" , ( ) => {
958
+ server . close ( ( ) => {
959
+ console . log ( "Server shut down" ) ;
960
+ process . exit ( 0 ) ;
961
+ } ) ;
962
+ } ) ;
963
+ } catch ( error ) {
964
+ console . error ( "Failed to start server:" , error ) ;
965
+ process . exit ( 1 ) ;
966
+ }
967
+ } ) ( ) . catch ( ( err ) => {
968
+ console . error ( "Unhandled error during server startup:" , err ) ;
969
+ process . exit ( 1 ) ;
861
970
} ) ;
0 commit comments