@@ -176,69 +176,81 @@ export async function startWebServer(config: Partial<ServerConfig> = {}): Promis
176176
177177 const staticRoutes = await buildStaticRoutes ( )
178178
179- server = Bun . serve ( {
180- hostname : finalConfig . hostname ,
181- port : finalConfig . port ,
182-
183- routes : {
184- ...staticRoutes ,
185- '/' : wrapWithSecurityHeaders (
186- ( ) => new Response ( null , { status : 302 , headers : { Location : '/index.html' } } )
187- ) ,
188- '/ws' : ( req : Request ) => {
189- if ( req . headers . get ( 'upgrade' ) === 'websocket' ) {
190- const success = server ! . upgrade ( req )
191- if ( success ) {
192- return undefined // Upgrade succeeded, Bun sends 101 automatically
179+ const createServer = ( port : number ) => {
180+ return Bun . serve ( {
181+ hostname : finalConfig . hostname ,
182+ port,
183+
184+ routes : {
185+ ...staticRoutes ,
186+ '/' : wrapWithSecurityHeaders (
187+ ( ) => new Response ( null , { status : 302 , headers : { Location : '/index.html' } } )
188+ ) ,
189+ '/ws' : ( req : Request ) => {
190+ if ( req . headers . get ( 'upgrade' ) === 'websocket' ) {
191+ const success = server ! . upgrade ( req )
192+ if ( success ) {
193+ return undefined // Upgrade succeeded, Bun sends 101 automatically
194+ }
195+ return new Response ( 'WebSocket upgrade failed' , { status : 400 } )
196+ } else {
197+ return new Response ( 'WebSocket endpoint - use WebSocket upgrade' , { status : 426 } )
193198 }
194- return new Response ( 'WebSocket upgrade failed' , { status : 400 } )
195- } else {
196- return new Response ( 'WebSocket endpoint - use WebSocket upgrade' , { status : 426 } )
197- }
199+ } ,
200+ '/health' : wrapWithSecurityHeaders ( handleHealth ) ,
201+ '/api/sessions' : wrapWithSecurityHeaders ( async ( req : Request ) => {
202+ if ( req . method === 'GET' ) return getSessions ( )
203+ if ( req . method === 'POST' ) return createSession ( req )
204+ return new Response ( 'Method not allowed' , { status : 405 } )
205+ } ) ,
206+ '/api/sessions/clear' : wrapWithSecurityHeaders ( async ( req : Request ) => {
207+ if ( req . method === 'POST' ) return clearSessions ( )
208+ return new Response ( 'Method not allowed' , { status : 405 } )
209+ } ) ,
210+ '/api/sessions/:id' : wrapWithSecurityHeaders ( async ( req : Request ) => {
211+ if ( req . method === 'GET' ) return getSession ( req as BunRequest < '/api/sessions/:id' > )
212+ return new Response ( 'Method not allowed' , { status : 405 } )
213+ } ) ,
214+ '/api/sessions/:id/input' : wrapWithSecurityHeaders ( async ( req : Request ) => {
215+ if ( req . method === 'POST' ) return sendInput ( req as BunRequest < '/api/sessions/:id/input' > )
216+ return new Response ( 'Method not allowed' , { status : 405 } )
217+ } ) ,
218+ '/api/sessions/:id/kill' : wrapWithSecurityHeaders ( async ( req : Request ) => {
219+ if ( req . method === 'POST' ) return killSession ( req as BunRequest < '/api/sessions/:id/kill' > )
220+ return new Response ( 'Method not allowed' , { status : 405 } )
221+ } ) ,
222+ '/api/sessions/:id/buffer/raw' : wrapWithSecurityHeaders ( async ( req : Request ) => {
223+ if ( req . method === 'GET' )
224+ return getRawBuffer ( req as BunRequest < '/api/sessions/:id/buffer/raw' > )
225+ return new Response ( 'Method not allowed' , { status : 405 } )
226+ } ) ,
227+ '/api/sessions/:id/buffer/plain' : wrapWithSecurityHeaders ( async ( req : Request ) => {
228+ if ( req . method === 'GET' )
229+ return getPlainBuffer ( req as BunRequest < '/api/sessions/:id/buffer/plain' > )
230+ return new Response ( 'Method not allowed' , { status : 405 } )
231+ } ) ,
198232 } ,
199- '/health' : wrapWithSecurityHeaders ( handleHealth ) ,
200- '/api/sessions' : wrapWithSecurityHeaders ( async ( req : Request ) => {
201- if ( req . method === 'GET' ) return getSessions ( )
202- if ( req . method === 'POST' ) return createSession ( req )
203- return new Response ( 'Method not allowed' , { status : 405 } )
204- } ) ,
205- '/api/sessions/clear' : wrapWithSecurityHeaders ( async ( req : Request ) => {
206- if ( req . method === 'POST' ) return clearSessions ( )
207- return new Response ( 'Method not allowed' , { status : 405 } )
208- } ) ,
209- '/api/sessions/:id' : wrapWithSecurityHeaders ( async ( req : Request ) => {
210- if ( req . method === 'GET' ) return getSession ( req as BunRequest < '/api/sessions/:id' > )
211- return new Response ( 'Method not allowed' , { status : 405 } )
212- } ) ,
213- '/api/sessions/:id/input' : wrapWithSecurityHeaders ( async ( req : Request ) => {
214- if ( req . method === 'POST' ) return sendInput ( req as BunRequest < '/api/sessions/:id/input' > )
215- return new Response ( 'Method not allowed' , { status : 405 } )
216- } ) ,
217- '/api/sessions/:id/kill' : wrapWithSecurityHeaders ( async ( req : Request ) => {
218- if ( req . method === 'POST' ) return killSession ( req as BunRequest < '/api/sessions/:id/kill' > )
219- return new Response ( 'Method not allowed' , { status : 405 } )
220- } ) ,
221- '/api/sessions/:id/buffer/raw' : wrapWithSecurityHeaders ( async ( req : Request ) => {
222- if ( req . method === 'GET' )
223- return getRawBuffer ( req as BunRequest < '/api/sessions/:id/buffer/raw' > )
224- return new Response ( 'Method not allowed' , { status : 405 } )
225- } ) ,
226- '/api/sessions/:id/buffer/plain' : wrapWithSecurityHeaders ( async ( req : Request ) => {
227- if ( req . method === 'GET' )
228- return getPlainBuffer ( req as BunRequest < '/api/sessions/:id/buffer/plain' > )
229- return new Response ( 'Method not allowed' , { status : 405 } )
230- } ) ,
231- } ,
232-
233- websocket : {
234- perMessageDeflate : true ,
235- ...wsHandler ,
236- } ,
237-
238- fetch : handleRequest ,
239- } )
240233
241- return `http://${ finalConfig . hostname } :${ finalConfig . port } `
234+ websocket : {
235+ perMessageDeflate : true ,
236+ ...wsHandler ,
237+ } ,
238+
239+ fetch : handleRequest ,
240+ } )
241+ }
242+
243+ try {
244+ server = createServer ( finalConfig . port )
245+ } catch ( error : any ) {
246+ if ( error . code === 'EADDRINUSE' || error . message ?. includes ( 'EADDRINUSE' ) ) {
247+ server = createServer ( 0 )
248+ } else {
249+ throw error
250+ }
251+ }
252+
253+ return `http://${ server . hostname } :${ server . port } `
242254}
243255
244256export function stopWebServer ( ) : void {
0 commit comments