@@ -5,22 +5,39 @@ import ws = require("ws");
5
5
import stream = require( "stream" ) ;
6
6
import path = require( "path" ) ;
7
7
import http = require( "http" ) ;
8
+ import Future = require( "fibers/future" ) ;
8
9
9
10
module notification {
11
+ function formatNotification ( bundleId : string , notification : string ) {
12
+ return `${ bundleId } :NativeScript.Debug.${ notification } ` ;
13
+ }
14
+
10
15
export function waitForDebug ( bundleId : string ) : string {
11
- return bundleId + ":NativeScript.Debug. WaitForDebugger";
16
+ return formatNotification ( bundleId , " WaitForDebugger") ;
12
17
}
13
18
14
19
export function attachRequest ( bundleId : string ) : string {
15
- return bundleId + ":NativeScript.Debug. AttachRequest";
20
+ return formatNotification ( bundleId , " AttachRequest") ;
16
21
}
17
22
18
23
export function appLaunching ( bundleId : string ) : string {
19
- return bundleId + ":NativeScript.Debug. AppLaunching";
24
+ return formatNotification ( bundleId , " AppLaunching") ;
20
25
}
21
26
22
27
export function readyForAttach ( bundleId : string ) : string {
23
- return bundleId + ":NativeScript.Debug.ReadyForAttach" ;
28
+ return formatNotification ( bundleId , "ReadyForAttach" ) ;
29
+ }
30
+
31
+ export function attachAvailabilityQuery ( bundleId : string ) {
32
+ return formatNotification ( bundleId , "AttachAvailabilityQuery" ) ;
33
+ }
34
+
35
+ export function alreadyConnected ( bundleId : string ) {
36
+ return formatNotification ( bundleId , "AlreadyConnected" ) ;
37
+ }
38
+
39
+ export function attachAvailable ( bundleId : string ) {
40
+ return formatNotification ( bundleId , "AttachAvailable" ) ;
24
41
}
25
42
}
26
43
@@ -111,12 +128,27 @@ class IOSDebugService implements IDebugService {
111
128
return ( ( ) => {
112
129
this . $devicesServices . initialize ( { platform : this . platform , deviceId : this . $options . device } ) . wait ( ) ;
113
130
this . $devicesServices . execute ( device => ( ( ) => {
114
- this . $platformService . deployOnDevice ( this . platform ) . wait ( ) ;
115
-
116
- var iosDevice = < iOSDevice . IOSDevice > device ;
117
- iosDevice . runApplication ( this . $projectData . projectId /* , ["--nativescript-debug-brk"] */ ) . wait ( ) ;
131
+ // we intentionally do not wait on this here, because if we did, we'd miss the AppLaunching notification
132
+ let deploy = this . $platformService . deployOnDevice ( this . platform ) ;
133
+
134
+ let iosDevice = < iOSDevice . IOSDevice > device ;
135
+ let projectId = this . $projectData . projectId ;
136
+ let npc = new iOSProxyServices . NotificationProxyClient ( iosDevice , this . $injector ) ;
137
+
138
+ try {
139
+ awaitNotification ( npc , notification . appLaunching ( projectId ) , 60000 ) . wait ( ) ;
140
+ process . nextTick ( ( ) => {
141
+ npc . postNotificationAndAttachForData ( notification . waitForDebug ( projectId ) ) ;
142
+ npc . postNotificationAndAttachForData ( notification . attachRequest ( projectId ) ) ;
143
+ } ) ;
144
+ awaitNotification ( npc , notification . readyForAttach ( projectId ) , 5000 ) . wait ( ) ;
145
+ } catch ( e ) {
146
+ this . $errors . failWithoutHelp ( "Timeout waiting for NativeScript debugger." ) ;
147
+ }
148
+
118
149
createWebSocketProxy ( this . $logger , ( callback ) => connectEventually ( ( ) => iosDevice . connectToPort ( InspectorBackendPort ) , callback ) ) ;
119
150
this . executeOpenDebuggerClient ( ) . wait ( ) ;
151
+ deploy . wait ( ) ;
120
152
} ) . future < void > ( ) ( ) ) . wait ( ) ;
121
153
} ) . future < void > ( ) ( ) ;
122
154
}
@@ -125,9 +157,38 @@ class IOSDebugService implements IDebugService {
125
157
return ( ( ) => {
126
158
this . $devicesServices . initialize ( { platform : this . platform , deviceId : this . $options . device } ) . wait ( ) ;
127
159
this . $devicesServices . execute ( device => ( ( ) => {
128
- var iosDevice = < iOSDevice . IOSDevice > device ;
129
- createWebSocketProxy ( this . $logger , ( callback ) => connectEventually ( ( ) => iosDevice . connectToPort ( InspectorBackendPort ) , callback ) ) ;
130
- this . executeOpenDebuggerClient ( ) . wait ( ) ;
160
+ let iosDevice = < iOSDevice . IOSDevice > device ;
161
+ let projectId = this . $projectData . projectId ;
162
+ let npc = new iOSProxyServices . NotificationProxyClient ( iosDevice , this . $injector ) ;
163
+
164
+ let [ alreadyConnected , readyForAttach , attachAvailable ] = [
165
+ notification . alreadyConnected ( projectId ) ,
166
+ notification . readyForAttach ( projectId ) ,
167
+ notification . attachAvailable ( projectId )
168
+ ] . map ( ( notification ) => awaitNotification ( npc , notification , 2000 ) ) ;
169
+
170
+ npc . postNotificationAndAttachForData ( notification . attachAvailabilityQuery ( projectId ) ) ;
171
+
172
+ let receivedNotification : IFuture < string > ;
173
+ try {
174
+ receivedNotification = whenAny ( alreadyConnected , readyForAttach , attachAvailable ) . wait ( ) ;
175
+ } catch ( e ) {
176
+ this . $errors . failWithoutHelp ( `The application ${ projectId } does not appear to be running on ${ device . getDisplayName ( ) } or is not built with debugging enabled.` ) ;
177
+ }
178
+
179
+ switch ( receivedNotification ) {
180
+ case alreadyConnected :
181
+ this . $errors . failWithoutHelp ( "A debugger is already connected." ) ;
182
+ case attachAvailable :
183
+ process . nextTick ( ( ) => npc . postNotificationAndAttachForData ( notification . attachRequest ( projectId ) ) ) ;
184
+ try { awaitNotification ( npc , notification . readyForAttach ( projectId ) , 2000 ) . wait ( ) ; }
185
+ catch ( e ) {
186
+ this . $errors . failWithoutHelp ( `The application ${ projectId } timed out when performing the NativeScript debugger handshake.` ) ;
187
+ }
188
+ case readyForAttach :
189
+ createWebSocketProxy ( this . $logger , ( callback ) => connectEventually ( ( ) => iosDevice . connectToPort ( InspectorBackendPort ) , callback ) ) ;
190
+ this . executeOpenDebuggerClient ( ) . wait ( ) ;
191
+ }
131
192
} ) . future < void > ( ) ( ) ) . wait ( ) ;
132
193
} ) . future < void > ( ) ( ) ;
133
194
}
@@ -183,18 +244,16 @@ function createWebSocketProxy($logger: ILogger, socketFactory: (handler: (socket
183
244
var server = ws . createServer ( < any > {
184
245
port : localPort ,
185
246
verifyClient : ( info : any , callback : any ) => {
247
+ $logger . info ( "Frontend client connected." ) ;
186
248
socketFactory ( ( socket ) => {
249
+ $logger . info ( "Backend socket created." ) ;
187
250
info . req [ "__deviceSocket" ] = socket ;
188
251
callback ( true ) ;
189
252
} ) ;
190
253
}
191
254
} ) ;
192
255
server . on ( "connection" , ( webSocket ) => {
193
- $logger . info ( "Frontend client connected." ) ;
194
-
195
256
var deviceSocket : net . Socket = ( < any > webSocket . upgradeReq ) [ "__deviceSocket" ] ;
196
-
197
- $logger . info ( "Backend socket created." ) ;
198
257
var packets = new PacketStream ( ) ;
199
258
deviceSocket . pipe ( packets ) ;
200
259
@@ -225,48 +284,50 @@ function createWebSocketProxy($logger: ILogger, socketFactory: (handler: (socket
225
284
return server ;
226
285
}
227
286
228
- class IOSDeviceDebugging {
229
- private $notificationProxyClient : iOSProxyServices . NotificationProxyClient ;
230
-
231
- constructor ( private bundleId : string ,
232
- private $iOSDevice : iOSDevice . IOSDevice ,
233
- private $logger : ILogger ,
234
- private $injector : IInjector ) {
235
-
236
- this . $notificationProxyClient = this . $injector . resolve ( iOSProxyServices . NotificationProxyClient , { device : this . $iOSDevice } )
237
- }
238
-
239
- public debugApplicationOnStart ( ) {
240
- var appLaunchMessage = notification . appLaunching ( this . bundleId ) ;
241
- this . $notificationProxyClient . addObserver ( appLaunchMessage , ( ) => {
242
- this . $logger . info ( "Got AppLaunching" ) ;
243
- this . proxyDebuggingTraffic ( ) ;
244
- var waitForDebuggerMessage = notification . waitForDebug ( this . bundleId ) ;
245
- this . $notificationProxyClient . postNotificationAndAttachForData ( waitForDebuggerMessage ) ;
246
- } ) ;
287
+ function awaitNotification ( npc : iOSProxyServices . NotificationProxyClient , notification : string , timeout : number ) : IFuture < string > {
288
+ let future = new Future < string > ( ) ;
289
+
290
+ let timeoutObject = setTimeout ( ( ) => {
291
+ detachObserver ( ) ;
292
+ future . throw ( new Error ( "Timeout receiving notification." ) ) ;
293
+ } , timeout ) ;
294
+
295
+ function notificationObserver ( notification : string ) {
296
+ clearTimeout ( timeoutObject ) ;
297
+ detachObserver ( ) ;
298
+ future . return ( notification ) ;
247
299
}
248
-
249
- public debugRunningApplication ( ) {
250
- this . proxyDebuggingTraffic ( ) ;
251
- var attachRequestMessage = notification . attachRequest ( this . bundleId ) ;
252
- this . $notificationProxyClient . postNotificationAndAttachForData ( attachRequestMessage ) ;
300
+
301
+ function detachObserver ( ) {
302
+ process . nextTick ( ( ) => npc . removeObserver ( notification , notificationObserver ) ) ;
253
303
}
304
+
305
+ npc . addObserver ( notification , notificationObserver ) ;
306
+
307
+ return future ;
308
+ }
254
309
255
- private proxyDebuggingTraffic ( ) : void {
256
- var identifier = this . $iOSDevice . getIdentifier ( ) ;
257
- this . $logger . info ( "Device Identifier: " + identifier ) ;
310
+ function whenAny < T > ( ... futures : IFuture < T > [ ] ) : IFuture < IFuture < T > > {
311
+ let resultFuture = new Future < IFuture < T > > ( ) ;
312
+ let futuresLeft = futures . length ;
258
313
259
- var readyForAttachMessage = notification . readyForAttach ( this . bundleId ) ;
260
- this . $notificationProxyClient . addObserver ( readyForAttachMessage , ( ) => {
261
- createWebSocketProxy ( this . $logger , ( callback ) => callback ( this . $iOSDevice . connectToPort ( InspectorBackendPort ) ) ) ;
314
+ for ( let future of futures ) {
315
+ var futureLocal = future ;
316
+ future . resolve ( ( error , result ?) => {
317
+ futuresLeft -- ;
318
+
319
+ if ( ! resultFuture . isResolved ( ) ) {
320
+ if ( typeof error === "undefined" ) {
321
+ resultFuture . return ( futureLocal ) ;
322
+ } else if ( futuresLeft == 0 ) {
323
+ resultFuture . throw ( new Error ( "None of the futures succeeded." ) ) ;
324
+ }
325
+ }
262
326
} ) ;
263
327
}
264
-
265
- private printHowToTerminate ( ) {
266
- this . $logger . info ( "\nSetting up debugger proxy...\n\nPress Ctrl + C to terminate, or disconnect.\n" ) ;
267
- }
328
+
329
+ return resultFuture ;
268
330
}
269
- $injector . register ( "iosDeviceDebugging" , IOSDeviceDebugging ) ;
270
331
271
332
class PacketStream extends stream . Transform {
272
333
private buffer : Buffer ;
0 commit comments