1+ ///<reference path="../../.d.ts"/>
2+ "use strict" ;
3+
4+ import { PacketStream } from "./packet-stream" ;
5+ import * as net from "net" ;
6+ import semver = require( "semver" ) ;
7+ import temp = require( "temp" ) ;
8+ import ws = require( "ws" ) ;
9+
10+ export class SocketProxyFactory implements ISocketProxyFactory {
11+ constructor ( private $logger : ILogger ,
12+ private $projectData : IProjectData ,
13+ private $projectDataService : IProjectDataService ) { }
14+
15+ public createSocketProxy ( factory : ( ) => net . Socket , socketFactoryAction : ( backendSocket : net . Socket , frontendSocket : net . Socket ) => void ) : IFuture < any > {
16+ return ( ( ) => {
17+ let socketFactory = ( callback : ( _socket : net . Socket ) => void ) => this . connectEventually ( factory , callback ) ;
18+
19+ this . $projectDataService . initialize ( this . $projectData . projectDir ) ;
20+ let frameworkVersion = this . $projectDataService . getValue ( "tns-ios" ) . wait ( ) . version ;
21+ let result : any ;
22+
23+ if ( semver . gte ( frameworkVersion , "1.4.0" ) ) {
24+ result = this . createTcpSocketProxy ( socketFactory , socketFactoryAction ) ;
25+ } else {
26+ result = this . createWebSocketProxy ( socketFactory ) ;
27+ }
28+
29+ return result ;
30+ } ) . future < any > ( ) ( ) ;
31+ }
32+
33+ private connectEventually ( factory : ( ) => net . Socket , handler : ( _socket : net . Socket ) => void ) { // TODO: Add socketProxyBase
34+ function tryConnect ( ) {
35+ let tryConnectAfterTimeout = setTimeout . bind ( undefined , tryConnect , 1000 ) ;
36+
37+ let socket = factory ( ) ;
38+ socket . on ( "connect" , ( ) => {
39+ socket . removeListener ( "error" , tryConnectAfterTimeout ) ;
40+ handler ( socket ) ;
41+ } ) ;
42+ socket . on ( "error" , tryConnectAfterTimeout ) ;
43+ }
44+
45+ tryConnect ( ) ;
46+ }
47+
48+ private createWebSocketProxy ( socketFactory : ( handler : ( socket : net . Socket ) => void ) => void ) : ws . Server {
49+ // NOTE: We will try to provide command line options to select ports, at least on the localhost.
50+ let localPort = 8080 ;
51+
52+ this . $logger . info ( "\nSetting up debugger proxy...\nPress Ctrl + C to terminate, or disconnect.\n" ) ;
53+
54+ // NB: When the inspector frontend connects we might not have connected to the inspector backend yet.
55+ // That's why we use the verifyClient callback of the websocket server to stall the upgrade request until we connect.
56+ // We store the socket that connects us to the device in the upgrade request object itself and later on retrieve it
57+ // in the connection callback.
58+
59+ let server = ws . createServer ( < any > {
60+ port : localPort ,
61+ verifyClient : ( info : any , callback : any ) => {
62+ this . $logger . info ( "Frontend client connected." ) ;
63+ socketFactory ( ( _socket : any ) => {
64+ this . $logger . info ( "Backend socket created." ) ;
65+ info . req [ "__deviceSocket" ] = _socket ;
66+ callback ( true ) ;
67+ } ) ;
68+ }
69+ } ) ;
70+ server . on ( "connection" , ( webSocket ) => {
71+ let deviceSocket : net . Socket = ( < any > webSocket . upgradeReq ) [ "__deviceSocket" ] ;
72+ let packets = new PacketStream ( ) ;
73+ deviceSocket . pipe ( packets ) ;
74+
75+ packets . on ( "data" , ( buffer : Buffer ) => {
76+ webSocket . send ( buffer . toString ( "utf16le" ) ) ;
77+ } ) ;
78+
79+ webSocket . on ( "message" , ( message , flags ) => {
80+ let length = Buffer . byteLength ( message , "utf16le" ) ;
81+ let payload = new Buffer ( length + 4 ) ;
82+ payload . writeInt32BE ( length , 0 ) ;
83+ payload . write ( message , 4 , length , "utf16le" ) ;
84+ deviceSocket . write ( payload ) ;
85+ } ) ;
86+
87+ deviceSocket . on ( "end" , ( ) => {
88+ this . $logger . info ( "Backend socket closed!" ) ;
89+ process . exit ( 0 ) ;
90+ } ) ;
91+
92+ webSocket . on ( "close" , ( ) => {
93+ this . $logger . info ( 'Frontend socket closed!' ) ;
94+ process . exit ( 0 ) ;
95+ } ) ;
96+ } ) ;
97+
98+ this . $logger . info ( "Opened localhost " + localPort ) ;
99+ return server ;
100+ }
101+
102+ private createTcpSocketProxy ( socketFactory : ( handler : ( socket : net . Socket ) => void ) => void , socketFactoryAction : ( backendSocket : net . Socket , frontendSocket : net . Socket ) => void ) : string {
103+ this . $logger . info ( "\nSetting up debugger proxy...\nPress Ctrl + C to terminate, or disconnect.\n" ) ;
104+
105+ let server = net . createServer ( {
106+ allowHalfOpen : true
107+ } ) ;
108+
109+ server . on ( "connection" , ( frontendSocket : net . Socket ) => {
110+ this . $logger . info ( "Frontend client connected." ) ;
111+
112+ frontendSocket . on ( "end" , ( ) => {
113+ this . $logger . info ( 'Frontend socket closed!' ) ;
114+ process . exit ( 0 ) ;
115+ } ) ;
116+
117+ socketFactory ( ( backendSocket : net . Socket ) => {
118+ this . $logger . info ( "Backend socket created." ) ;
119+
120+ backendSocket . on ( "end" , ( ) => {
121+ this . $logger . info ( "Backend socket closed!" ) ;
122+ process . exit ( 0 ) ;
123+ } ) ;
124+
125+ socketFactoryAction ( backendSocket , frontendSocket ) ;
126+ } ) ;
127+ } ) ;
128+
129+ let socketFileLocation = temp . path ( { suffix : ".sock" } ) ;
130+ server . listen ( socketFileLocation ) ;
131+
132+ return socketFileLocation ;
133+ }
134+ }
135+ $injector . register ( "socketProxyFactory" , SocketProxyFactory ) ;
0 commit comments