@@ -16,53 +16,14 @@ define([
1616 const { CommandFailedError} = Errors ;
1717 const isNodeJs = typeof window === 'undefined' ;
1818 const WebSocket = isNodeJs ? require ( 'ws' ) : window . WebSocket ;
19+ let numSessions = 1 ;
1920
2021 class InteractiveSession {
21- constructor ( computeID , config = { } ) {
22+ constructor ( channel ) {
2223 this . currentTask = null ;
23- const address = gmeConfig . extensions . InteractiveComputeHost ||
24- this . getDefaultServerURL ( ) ;
25- this . ws = new WebSocket ( address ) ;
26- this . connected = defer ( ) ;
27- this . ws . onopen = ( ) => {
28- this . ws . send ( JSON . stringify ( [ computeID , config , this . getGMEToken ( ) ] ) ) ;
29- this . ws . onmessage = async ( wsMsg ) => {
30- const data = await Task . getMessageData ( wsMsg ) ;
31-
32- const msg = Message . decode ( data ) ;
33- if ( msg . type === Message . COMPLETE ) {
34- const err = msg . data ;
35- this . channel = new MessageChannel ( this . ws ) ;
36- if ( err ) {
37- this . connected . reject ( err ) ;
38- } else {
39- this . connected . resolve ( ) ;
40- this . checkReady ( ) ;
41- }
42- }
43- } ;
44- } ;
45-
46- this . ready = null ;
47- }
48-
49- getDefaultServerURL ( ) {
50- const isSecure = ! isNodeJs && location . protocol . includes ( 's' ) ;
51- const protocol = isSecure ? 'wss' : 'ws' ;
52- const defaultHost = isNodeJs ? '127.0.0.1' :
53- location . origin
54- . replace ( location . protocol + '//' , '' )
55- . replace ( / : [ 0 - 9 ] + $ / , '' ) ;
56- return `${ protocol } ://${ defaultHost } :${ gmeConfig . server . port + 1 } ` ;
57- }
58-
59- getGMEToken ( ) {
60- if ( isNodeJs ) {
61- return '' ;
62- }
63-
64- const [ , token ] = ( document . cookie || '' ) . split ( '=' ) ;
65- return token ;
24+ this . id = numSessions ++ ;
25+ this . channel = channel ;
26+ this . channel . onClientConnect ( this . id ) ;
6627 }
6728
6829 checkReady ( ) {
@@ -72,7 +33,7 @@ define([
7233 }
7334
7435 isIdle ( ) {
75- return ! this . currentTask && this . ws . readyState === WebSocket . OPEN ;
36+ return ! this . currentTask && this . channel . isOpen ( ) ;
7637 }
7738
7839 ensureIdle ( action ) {
@@ -84,7 +45,7 @@ define([
8445 spawn ( cmd ) {
8546 this . ensureIdle ( 'spawn a task' ) ;
8647
87- const msg = new Message ( Message . RUN , cmd ) ;
48+ const msg = new Message ( this . id , Message . RUN , cmd ) ;
8849 const task = new Task ( this . channel , msg ) ;
8950 this . runTask ( task ) ;
9051 return task ;
@@ -111,7 +72,7 @@ define([
11172
11273 async exec ( cmd ) {
11374 this . ensureIdle ( 'exec a task' ) ;
114- const msg = new Message ( Message . RUN , cmd ) ;
75+ const msg = new Message ( this . id , Message . RUN , cmd ) ;
11576 const task = new Task ( this . channel , msg ) ;
11677 const result = {
11778 stdout : '' ,
@@ -131,14 +92,14 @@ define([
13192 async addArtifact ( name , dataInfo , type , auth ) {
13293 auth = auth || { } ;
13394 this . ensureIdle ( 'add artifact' ) ;
134- const msg = new Message ( Message . ADD_ARTIFACT , [ name , dataInfo , type , auth ] ) ;
95+ const msg = new Message ( this . id , Message . ADD_ARTIFACT , [ name , dataInfo , type , auth ] ) ;
13596 const task = new Task ( this . channel , msg ) ;
13697 await this . runTask ( task ) ;
13798 }
13899
139100 async saveArtifact ( /*path, name, storageId, config*/ ) {
140101 this . ensureIdle ( 'save artifact' ) ;
141- const msg = new Message ( Message . SAVE_ARTIFACT , [ ...arguments ] ) ;
102+ const msg = new Message ( this . id , Message . SAVE_ARTIFACT , [ ...arguments ] ) ;
142103 const task = new Task ( this . channel , msg ) ;
143104 const [ exitCode , dataInfo ] = await this . runTask ( task ) ;
144105 if ( exitCode ) {
@@ -149,21 +110,21 @@ define([
149110
150111 async addFile ( filepath , content ) {
151112 this . ensureIdle ( 'add file' ) ;
152- const msg = new Message ( Message . ADD_FILE , [ filepath , content ] ) ;
113+ const msg = new Message ( this . id , Message . ADD_FILE , [ filepath , content ] ) ;
153114 const task = new Task ( this . channel , msg ) ;
154115 await this . runTask ( task ) ;
155116 }
156117
157118 async removeFile ( filepath ) {
158119 this . ensureIdle ( 'remove file' ) ;
159- const msg = new Message ( Message . REMOVE_FILE , [ filepath ] ) ;
120+ const msg = new Message ( this . id , Message . REMOVE_FILE , [ filepath ] ) ;
160121 const task = new Task ( this . channel , msg ) ;
161122 await this . runTask ( task ) ;
162123 }
163124
164125 async setEnvVar ( name , value ) {
165126 this . ensureIdle ( 'set env var' ) ;
166- const msg = new Message ( Message . SET_ENV , [ name , value ] ) ;
127+ const msg = new Message ( this . id , Message . SET_ENV , [ name , value ] ) ;
167128 const task = new Task ( this . channel , msg ) ;
168129 await this . runTask ( task ) ;
169130 }
@@ -174,24 +135,75 @@ define([
174135 'Cannot kill task. Must be a RUN task.'
175136 ) ;
176137 if ( task === this . currentTask ) {
177- const msg = new Message ( Message . KILL , task . msg . data ) ;
138+ const msg = new Message ( this . id , Message . KILL , task . msg . data ) ;
178139 const killTask = new Task ( this . channel , msg ) ;
179140 await killTask . run ( ) ;
180141 this . checkReady ( ) ;
181142 }
182143 }
183144
184145 close ( ) {
185- this . ws . close ( ) ;
146+ this . channel . onClientExit ( this . id ) ;
147+ }
148+
149+ fork ( ) {
150+ const Session = this . constructor ;
151+ return new Session ( this . channel ) ;
186152 }
187153
188154 static async new ( computeID , config = { } , SessionClass = InteractiveSession ) {
189- const session = new SessionClass ( computeID , config ) ;
190- await session . whenConnected ( ) ;
155+ const channel = await createMessageChannel ( computeID , config ) ;
156+ const session = new SessionClass ( channel ) ;
191157 return session ;
192158 }
193159 }
194160
161+ async function createMessageChannel ( computeID , config ) {
162+ const address = gmeConfig . extensions . InteractiveComputeHost ||
163+ getDefaultServerURL ( ) ;
164+
165+ const connectedWs = await new Promise ( ( resolve , reject ) => {
166+ const ws = new WebSocket ( address ) ;
167+ ws . onopen = ( ) => {
168+ ws . send ( JSON . stringify ( [ computeID , config , getGMEToken ( ) ] ) ) ;
169+ ws . onmessage = async ( wsMsg ) => {
170+ const data = await Task . getMessageData ( wsMsg ) ;
171+
172+ const msg = Message . decode ( data ) ;
173+ if ( msg . type === Message . COMPLETE ) {
174+ const err = msg . data ;
175+ if ( err ) {
176+ reject ( err ) ;
177+ } else {
178+ resolve ( ws ) ;
179+ }
180+ }
181+ } ;
182+ } ;
183+ } ) ;
184+
185+ return new MessageChannel ( connectedWs ) ;
186+ }
187+
188+ function getDefaultServerURL ( ) {
189+ const isSecure = ! isNodeJs && location . protocol . includes ( 's' ) ;
190+ const protocol = isSecure ? 'wss' : 'ws' ;
191+ const defaultHost = isNodeJs ? '127.0.0.1' :
192+ location . origin
193+ . replace ( location . protocol + '//' , '' )
194+ . replace ( / : [ 0 - 9 ] + $ / , '' ) ;
195+ return `${ protocol } ://${ defaultHost } :${ gmeConfig . server . port + 1 } ` ;
196+ }
197+
198+ function getGMEToken ( ) {
199+ if ( isNodeJs ) {
200+ return '' ;
201+ }
202+
203+ const [ , token ] = ( document . cookie || '' ) . split ( '=' ) ;
204+ return token ;
205+ }
206+
195207 function assert ( cond , msg ) {
196208 if ( ! cond ) {
197209 throw new Error ( msg ) ;
@@ -208,6 +220,7 @@ define([
208220 this . ws . onmessage = message => {
209221 this . listeners . forEach ( fn => fn ( message ) ) ;
210222 } ;
223+ this . clients = [ ] ;
211224 }
212225
213226 send ( data ) {
@@ -224,6 +237,26 @@ define([
224237 this . listeners . splice ( index , 1 ) ;
225238 }
226239 }
240+
241+ isOpen ( ) {
242+ return this . ws . readyState === WebSocket . OPEN ;
243+ }
244+
245+ onClientConnect ( id ) {
246+ this . clients . push ( id ) ;
247+ }
248+
249+ onClientExit ( id ) {
250+ const index = this . clients . indexOf ( id ) ;
251+ if ( index === - 1 ) {
252+ throw new Error ( `Client not found: ${ id } ` ) ;
253+ }
254+ this . clients . splice ( index , 1 ) ;
255+
256+ if ( this . clients . length === 0 ) {
257+ this . ws . close ( ) ;
258+ }
259+ }
227260 }
228261
229262 return InteractiveSession ;
0 commit comments