@@ -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