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