@@ -27,8 +27,10 @@ export class Tailscale {
2727 private _vscode : vscodeModule ;
2828 private nonce ?: string ;
2929 public url ?: string ;
30+ private port ?: string ;
3031 public authkey ?: string ;
3132 private childProcess ?: cp . ChildProcess ;
33+ private notifyExit ?: ( ) => void ;
3234
3335 constructor ( vscode : vscodeModule ) {
3436 this . _vscode = vscode ;
@@ -40,22 +42,9 @@ export class Tailscale {
4042 return ts ;
4143 }
4244
43- async init ( ) {
45+ async init ( port ?: string , nonce ?: string ) {
4446 return new Promise < null > ( ( resolve ) => {
45- let arch = process . arch ;
46- let platform : string = process . platform ;
47- // See:
48- // https://goreleaser.com/customization/builds/#why-is-there-a-_v1-suffix-on-amd64-builds
49- if ( process . arch === 'x64' ) {
50- arch = 'amd64_v1' ;
51- }
52- if ( platform === 'win32' ) {
53- platform = 'windows' ;
54- }
55- let binPath = path . join (
56- __dirname ,
57- `../bin/vscode-tailscale_${ platform } _${ arch } /vscode-tailscale`
58- ) ;
47+ let binPath = this . tsrelayPath ( ) ;
5948 let args = [ ] ;
6049 if ( this . _vscode . env . logLevel === LogLevel . Debug ) {
6150 args . push ( '-v' ) ;
@@ -66,12 +55,22 @@ export class Tailscale {
6655 args = [ 'run' , '.' , ...args ] ;
6756 cwd = path . join ( cwd , '../tsrelay' ) ;
6857 }
69- Logger . info ( `path: ${ binPath } ` , LOG_COMPONENT ) ;
58+ if ( port ) {
59+ args . push ( `-port=${ this . port } ` ) ;
60+ }
61+ if ( nonce ) {
62+ args . push ( `-nonce=${ this . nonce } ` ) ;
63+ }
64+ Logger . debug ( `path: ${ binPath } ` , LOG_COMPONENT ) ;
65+ Logger . debug ( `args: ${ args . join ( ' ' ) } ` , LOG_COMPONENT ) ;
7066
7167 this . childProcess = cp . spawn ( binPath , args , { cwd : cwd } ) ;
7268
7369 this . childProcess . on ( 'exit' , ( code ) => {
7470 Logger . warn ( `child process exited with code ${ code } ` , LOG_COMPONENT ) ;
71+ if ( this . notifyExit ) {
72+ this . notifyExit ( ) ;
73+ }
7574 } ) ;
7675
7776 this . childProcess . on ( 'error' , ( err ) => {
@@ -83,6 +82,7 @@ export class Tailscale {
8382 const details = JSON . parse ( data . toString ( ) . trim ( ) ) as TSRelayDetails ;
8483 this . url = details . address ;
8584 this . nonce = details . nonce ;
85+ this . port = details . port ;
8686 this . authkey = Buffer . from ( `${ this . nonce } :` ) . toString ( 'base64' ) ;
8787 Logger . info ( `url: ${ this . url } ` , LOG_COMPONENT ) ;
8888
@@ -100,40 +100,108 @@ export class Tailscale {
100100 throw new Error ( 'childProcess.stdout is null' ) ;
101101 }
102102
103- if ( this . childProcess . stderr ) {
104- let buffer = '' ;
105- this . childProcess . stderr . on ( 'data' , ( data : Buffer ) => {
106- buffer += data . toString ( ) ; // Append the data to the buffer
103+ this . processStderr ( this . childProcess ) ;
104+ } ) ;
105+ }
107106
108- const lines = buffer . split ( '\n' ) ; // Split the buffer into lines
107+ processStderr ( childProcess : cp . ChildProcess ) {
108+ if ( ! childProcess . stderr ) {
109+ Logger . error ( 'childProcess.stderr is null' , LOG_COMPONENT ) ;
110+ throw new Error ( 'childProcess.stderr is null' ) ;
111+ }
112+ let buffer = '' ;
113+ childProcess . stderr . on ( 'data' , ( data : Buffer ) => {
114+ buffer += data . toString ( ) ; // Append the data to the buffer
109115
110- // Process all complete lines except the last one
111- for ( let i = 0 ; i < lines . length - 1 ; i ++ ) {
112- const line = lines [ i ] . trim ( ) ;
113- if ( line . length > 0 ) {
114- Logger . info ( line , LOG_COMPONENT ) ;
115- }
116- }
116+ const lines = buffer . split ( '\n' ) ; // Split the buffer into lines
117117
118- buffer = lines [ lines . length - 1 ] ;
119- } ) ;
118+ // Process all complete lines except the last one
119+ for ( let i = 0 ; i < lines . length - 1 ; i ++ ) {
120+ const line = lines [ i ] . trim ( ) ;
121+ if ( line . length > 0 ) {
122+ Logger . info ( line , LOG_COMPONENT ) ;
123+ }
124+ }
125+
126+ buffer = lines [ lines . length - 1 ] ;
127+ } ) ;
128+
129+ childProcess . stderr . on ( 'end' , ( ) => {
130+ // Process the remaining data in the buffer after the stream ends
131+ const line = buffer . trim ( ) ;
132+ if ( line . length > 0 ) {
133+ Logger . info ( line , LOG_COMPONENT ) ;
134+ }
135+ } ) ;
136+ }
120137
121- this . childProcess . stderr . on ( 'end' , ( ) => {
122- // Process the remaining data in the buffer after the stream ends
123- const line = buffer . trim ( ) ;
124- if ( line . length > 0 ) {
125- Logger . info ( line , LOG_COMPONENT ) ;
138+ async initSudo ( ) {
139+ return new Promise < null > ( ( resolve , err ) => {
140+ const binPath = this . tsrelayPath ( ) ;
141+ const args = [ `-nonce=${ this . nonce } ` , `-port=${ this . port } ` ] ;
142+ if ( this . _vscode . env . logLevel === LogLevel . Debug ) {
143+ args . push ( '-v' ) ;
144+ }
145+ Logger . info ( `path: ${ binPath } ` , LOG_COMPONENT ) ;
146+ this . notifyExit = ( ) => {
147+ Logger . info ( 'starting sudo tsrelay' ) ;
148+ const childProcess = cp . spawn ( `/usr/bin/pkexec` , [
149+ '--disable-internal-agent' ,
150+ binPath ,
151+ ...args ,
152+ ] ) ;
153+ childProcess . on ( 'exit' , async ( code ) => {
154+ Logger . warn ( `sudo child process exited with code ${ code } ` , LOG_COMPONENT ) ;
155+ if ( code === 0 ) {
156+ return ;
157+ } else if ( code === 126 ) {
158+ // authentication not successful
159+ this . _vscode . window . showErrorMessage (
160+ 'Creating a Funnel must be done by an administrator'
161+ ) ;
162+ } else {
163+ this . _vscode . window . showErrorMessage ( 'Could not run authenticator, please check logs' ) ;
126164 }
165+ await this . init ( this . port , this . nonce ) ;
166+ err ( 'unauthenticated' ) ;
127167 } ) ;
128- } else {
129- Logger . error ( 'childProcess.stderr is null' , LOG_COMPONENT ) ;
130- throw new Error ( 'childProcess.stderr is null' ) ;
131- }
168+ childProcess . on ( 'error' , ( err ) => {
169+ Logger . error ( `sudo child process error ${ err } ` , LOG_COMPONENT ) ;
170+ } ) ;
171+ childProcess . stdout . on ( 'data' , ( data : Buffer ) => {
172+ Logger . debug ( 'received data from sudo' ) ;
173+ const details = JSON . parse ( data . toString ( ) . trim ( ) ) as TSRelayDetails ;
174+ if ( this . url !== details . address ) {
175+ Logger . error ( `expected url to be ${ this . url } but got ${ details . address } ` ) ;
176+ return ;
177+ }
178+ this . runPortDisco ( ) ;
179+ Logger . debug ( 'resolving' ) ;
180+ resolve ( null ) ;
181+ } ) ;
182+ this . processStderr ( childProcess ) ;
183+ } ;
184+ this . dispose ( ) ;
132185 } ) ;
133186 }
134187
188+ tsrelayPath ( ) : string {
189+ let arch = process . arch ;
190+ let platform : string = process . platform ;
191+ // See:
192+ // https://goreleaser.com/customization/builds/#why-is-there-a-_v1-suffix-on-amd64-builds
193+ if ( process . arch === 'x64' ) {
194+ arch = 'amd64_v1' ;
195+ }
196+ if ( platform === 'win32' ) {
197+ platform = 'windows' ;
198+ }
199+ return path . join ( __dirname , `../bin/vscode-tailscale_${ platform } _${ arch } /vscode-tailscale` ) ;
200+ }
201+
135202 dispose ( ) {
136203 if ( this . childProcess ) {
204+ Logger . info ( 'shutting down tsrelay' ) ;
137205 this . childProcess . kill ( ) ;
138206 }
139207 }
@@ -227,7 +295,7 @@ export class Tailscale {
227295
228296 const ws = new WebSocket ( `ws://${ this . url . slice ( 'http://' . length ) } /portdisco` , {
229297 headers : {
230- Authorization : 'Basic ' + Buffer . from ( ` ${ this . nonce } :` ) . toString ( 'base64' ) ,
298+ Authorization : 'Basic ' + this . authkey ,
231299 } ,
232300 } ) ;
233301 ws . on ( 'error' , ( e ) => {
@@ -249,6 +317,9 @@ export class Tailscale {
249317 ) ;
250318 } ) ;
251319 } ) ;
320+ ws . on ( 'close' , ( ) => {
321+ Logger . info ( 'websocket is closed' ) ;
322+ } ) ;
252323 ws . on ( 'message' , async ( data ) => {
253324 Logger . info ( 'got message' ) ;
254325 const msg = JSON . parse ( data . toString ( ) ) ;
0 commit comments