@@ -12,23 +12,28 @@ function isClosed(ws: WebSocket | undefined): boolean {
1212	return  ! ws  ||  ws . readyState  ===  WebSocket . CLOSED ; 
1313} 
1414
15+ export  type  SessionInfo  =  { 
16+ 	wsEndpoint : string ; 
17+ 	sessionId : string ; 
18+ 	startTime : number ; 
19+ 	connectionId ?: string ; 
20+ 	connectionStartTime ?: number ; 
21+ } ; 
22+ 
1523export  class  BrowserSession  extends  DurableObject < Env >  { 
16- 	endpoint ?: string ; 
24+ 	sessionInfo ?: SessionInfo ; 
1725	ws ?: WebSocket ; 
1826	server ?: WebSocket ; 
1927
2028	async  fetch ( _request : Request )  { 
2129		assert ( 
22- 			this . endpoint  !==  undefined , 
23- 			"endpoint  must be set before connecting" 
30+ 			this . sessionInfo  !==  undefined , 
31+ 			"sessionInfo  must be set before connecting" 
2432		) ; 
2533
2634		// sometimes the websocket doesn't get the close event, so we need to close them explicitly if needed 
2735		if  ( isClosed ( this . ws )  ||  isClosed ( this . server ) )  { 
28- 			this . ws ?. close ( ) ; 
29- 			this . server ?. close ( ) ; 
30- 			this . ws  =  undefined ; 
31- 			this . server  =  undefined ; 
36+ 			this . closeWebSockets ( ) ; 
3237		}  else  { 
3338			assert . fail ( "WebSocket already initialized" ) ; 
3439		} 
@@ -38,7 +43,7 @@ export class BrowserSession extends DurableObject<Env> {
3843
3944		server . accept ( ) ; 
4045
41- 		const  wsEndpoint  =  this . endpoint . replace ( "ws://" ,  "http://" ) ; 
46+ 		const  wsEndpoint  =  this . sessionInfo . wsEndpoint . replace ( "ws://" ,  "http://" ) ; 
4247
4348		const  response  =  await  fetch ( wsEndpoint ,  { 
4449			headers : { 
@@ -85,37 +90,51 @@ export class BrowserSession extends DurableObject<Env> {
8590		} ) ; 
8691		this . ws  =  ws ; 
8792		this . server  =  server ; 
93+ 		this . sessionInfo . connectionId  =  crypto . randomUUID ( ) ; 
94+ 		this . sessionInfo . connectionStartTime  =  Date . now ( ) ; 
8895
8996		return  new  Response ( null ,  { 
9097			status : 101 , 
9198			webSocket : client , 
9299		} ) ; 
93100	} 
94- 	async  setEndpoint ( endpoint : string )  { 
95- 		this . endpoint  =  endpoint ; 
101+ 
102+ 	async  setSessionInfo ( sessionInfo : SessionInfo )  { 
103+ 		this . sessionInfo  =  sessionInfo ; 
104+ 	} 
105+ 
106+ 	async  getSessionInfo ( ) : Promise < SessionInfo  |  undefined >  { 
107+ 		if  ( isClosed ( this . ws )  ||  isClosed ( this . server ) )  { 
108+ 			this . closeWebSockets ( ) ; 
109+ 		} 
110+ 		return  this . sessionInfo ; 
96111	} 
97112
98113	async  #checkStatus( )  { 
99- 		if  ( this . endpoint )  { 
114+ 		if  ( this . sessionInfo )  { 
100115			const  url  =  new  URL ( "http://example.com/browser/status" ) ; 
101- 			url . searchParams . set ( "wsEndpoint " ,  this . endpoint ) ; 
116+ 			url . searchParams . set ( "sessionId " ,  this . sessionInfo . sessionId ) ; 
102117			const  resp  =  await  this . env [ CoreBindings . SERVICE_LOOPBACK ] . fetch ( url ) ; 
103- 			const  {  stopped }  =  resp . ok 
104- 				? ( ( await  resp . json ( ) )  as  {  stopped : boolean  } ) 
105- 				: { } ; 
106118
107- 			if  ( stopped )  { 
119+ 			if  ( ! resp . ok )  { 
108120				// Browser process has exited, we should close the WebSocket 
109121				// TODO should we send a error code? 
110- 				this . ws ?. close ( ) ; 
111- 				this . server ?. close ( ) ; 
112- 				this . ws  =  undefined ; 
113- 				this . server  =  undefined ; 
114- 				this . ctx . storage . deleteAll ( ) ; 
122+ 				this . closeWebSockets ( ) ; 
115123				return ; 
116124			} 
117125		} 
118126	} 
127+ 
128+ 	closeWebSockets ( )  { 
129+ 		this . ws ?. close ( ) ; 
130+ 		this . server ?. close ( ) ; 
131+ 		this . ws  =  undefined ; 
132+ 		this . server  =  undefined ; 
133+ 		if  ( this . sessionInfo )  { 
134+ 			this . sessionInfo . connectionId  =  undefined ; 
135+ 			this . sessionInfo . connectionStartTime  =  undefined ; 
136+ 		} 
137+ 	} 
119138} 
120139
121140export  default  { 
@@ -126,18 +145,39 @@ export default {
126145				const  resp  =  await  env [ CoreBindings . SERVICE_LOOPBACK ] . fetch ( 
127146					"http://example.com/browser/launch" 
128147				) ; 
129- 				const  wsEndpoint  =  await  resp . text ( ) ; 
130- 				const  sessionId  =  crypto . randomUUID ( ) ; 
131- 				const  id  =  env . BrowserSession . idFromName ( sessionId ) ; 
132- 				await  env . BrowserSession . get ( id ) . setEndpoint ( wsEndpoint ) ; 
133- 				return  Response . json ( {  sessionId } ) ; 
148+ 				const  sessionInfo : SessionInfo  =  await  resp . json ( ) ; 
149+ 				const  id  =  env . BrowserSession . idFromName ( sessionInfo . sessionId ) ; 
150+ 				await  env . BrowserSession . get ( id ) . setSessionInfo ( sessionInfo ) ; 
151+ 				return  Response . json ( {  sessionId : sessionInfo . sessionId  } ) ; 
134152			} 
135153			case  "/v1/connectDevtools" : { 
136154				const  sessionId  =  url . searchParams . get ( "browser_session" ) ; 
137155				assert ( sessionId  !==  null ,  "browser_session must be set" ) ; 
138156				const  id  =  env . BrowserSession . idFromName ( sessionId ) ; 
139157				return  env . BrowserSession . get ( id ) . fetch ( request ) ; 
140158			} 
159+ 			case  "/v1/sessions" : { 
160+ 				const  sessionIds  =  ( await  env [ CoreBindings . SERVICE_LOOPBACK ] 
161+ 					. fetch ( "http://example.com/browser/sessionIds" ) 
162+ 					. then ( ( resp )  =>  resp . json ( ) ) )  as  string [ ] ; 
163+ 				const  sessions  =  await  Promise . all ( 
164+ 					sessionIds . map ( async  ( sessionId )  =>  { 
165+ 						const  id  =  env . BrowserSession . idFromName ( sessionId ) ; 
166+ 						return  env . BrowserSession . get ( id ) 
167+ 							. getSessionInfo ( ) 
168+ 							. then ( ( sessionInfo )  =>  { 
169+ 								if  ( ! sessionInfo )  return  null ; 
170+ 								return  { 
171+ 									sessionId : sessionInfo . sessionId , 
172+ 									startTime : sessionInfo . startTime , 
173+ 									connectionId : sessionInfo . connectionId , 
174+ 									connectionStartTime : sessionInfo . connectionStartTime , 
175+ 								} ; 
176+ 							} ) ; 
177+ 					} ) 
178+ 				) . then ( ( results )  =>  results . filter ( Boolean ) ) ; 
179+ 				return  Response . json ( {  sessions } ) ; 
180+ 			} 
141181			default :
142182				return  new  Response ( "Not implemented" ,  {  status : 405  } ) ; 
143183		} 
0 commit comments