@@ -25,11 +25,13 @@ import TelemetryReporter from './telemetryReporter';
2525import  {  addHostToHostFile ,  checkNewHostInHostkeys  }  from  './ssh/hostfile' ; 
2626import  {  DEFAULT_IDENTITY_FILES  }  from  './ssh/identityFiles' ; 
2727import  {  HeartbeatManager  }  from  './heartbeat' ; 
28- import  {  getGitpodVersion ,  isFeatureSupported  }  from  './featureSupport' ; 
28+ import  {  getGitpodVersion ,  GitpodVersion ,   isFeatureSupported  }  from  './featureSupport' ; 
2929import  SSHConfiguration  from  './ssh/sshConfig' ; 
3030import  {  isWindows  }  from  './common/platform' ; 
3131import  {  untildify  }  from  './common/files' ; 
3232import  {  ExperimentalSettings ,  isUserOverrideSetting  }  from  './experiments' ; 
33+ import  {  ISyncExtension ,  NoSettingsSyncSession ,  NoSyncStoreError ,  parseSyncData ,  SettingsSync ,  SyncResource  }  from  './settingsSync' ; 
34+ import  {  retry  }  from  './common/async' ; 
3335
3436interface  SSHConnectionParams  { 
3537	workspaceId : string ; 
@@ -121,6 +123,7 @@ export default class RemoteConnector extends Disposable {
121123
122124	constructor ( 
123125		private  readonly  context : vscode . ExtensionContext , 
126+ 		private  readonly  settingsSync : SettingsSync , 
124127		private  readonly  experiments : ExperimentalSettings , 
125128		private  readonly  logger : Log , 
126129		private  readonly  telemetry : TelemetryReporter 
@@ -900,12 +903,96 @@ export default class RemoteConnector extends Disposable {
900903		} 
901904	} 
902905
903- 	private  startHeartBeat ( accessToken : string ,  connectionInfo : SSHConnectionParams )  { 
906+ 	private  async   startHeartBeat ( accessToken : string ,  connectionInfo : SSHConnectionParams ,   gitpodVersion :  GitpodVersion )  { 
904907		if  ( this . heartbeatManager )  { 
905908			return ; 
906909		} 
907910
908911		this . heartbeatManager  =  new  HeartbeatManager ( connectionInfo . gitpodHost ,  connectionInfo . workspaceId ,  connectionInfo . instanceId ,  accessToken ,  this . logger ,  this . telemetry ) ; 
912+ 
913+ 		// gitpod remote extension installation is async so sometimes gitpod-desktop will activate before gitpod-remote 
914+ 		// let's try a few times for it to finish install 
915+ 		try  { 
916+ 			await  retry ( async  ( )  =>  { 
917+ 				await  vscode . commands . executeCommand ( '__gitpod.cancelGitpodRemoteHeartbeat' ) ; 
918+ 			} ,  3000 ,  15 ) ; 
919+ 			this . telemetry . sendTelemetryEvent ( 'vscode_desktop_heartbeat_state' ,  {  enabled : String ( true ) ,  gitpodHost : connectionInfo . gitpodHost ,  workspaceId : connectionInfo . workspaceId ,  instanceId : connectionInfo . instanceId ,  gitpodVersion : gitpodVersion . raw  } ) ; 
920+ 		}  catch  { 
921+ 			this . logger . error ( `Could not execute '__gitpod.cancelGitpodRemoteHeartbeat' command` ) ; 
922+ 			this . telemetry . sendTelemetryEvent ( 'vscode_desktop_heartbeat_state' ,  {  enabled : String ( false ) ,  gitpodHost : connectionInfo . gitpodHost ,  workspaceId : connectionInfo . workspaceId ,  instanceId : connectionInfo . instanceId ,  gitpodVersion : gitpodVersion . raw  } ) ; 
923+ 		} 
924+ 	} 
925+ 
926+ 	private  async  initializeRemoteExtensions ( )  { 
927+ 		let  syncData : {  ref : string ;  content : string  }  |  undefined ; 
928+ 		try  { 
929+ 			syncData  =  await  this . settingsSync . readResource ( SyncResource . Extensions ) ; 
930+ 		}  catch  ( e )  { 
931+ 			if  ( e  instanceof  NoSyncStoreError )  { 
932+ 				const  action  =  'Settings Sync: Enable Sign In with Gitpod' ; 
933+ 				const  result  =  await  vscode . window . showInformationMessage ( `Couldn't initialize remote extensions, Settings Sync with Gitpod is required.` ,  action ) ; 
934+ 				if  ( result  ===  action )  { 
935+ 					vscode . commands . executeCommand ( 'gitpod.syncProvider.add' ) ; 
936+ 				} 
937+ 			}  else  if  ( e  instanceof  NoSettingsSyncSession )  { 
938+ 				const  action  =  'Enable Settings Sync' ; 
939+ 				const  result  =  await  vscode . window . showInformationMessage ( `Couldn't initialize remote extensions, please enable Settings Sync.` ,  action ) ; 
940+ 				if  ( result  ===  action )  { 
941+ 					vscode . commands . executeCommand ( 'workbench.userDataSync.actions.turnOn' ) ; 
942+ 				} 
943+ 			}  else  { 
944+ 				this . logger . error ( 'Error while fetching settings sync extension data:' ,  e ) ; 
945+ 
946+ 				const  seeLogs  =  'See Logs' ; 
947+ 				const  action  =  await  vscode . window . showErrorMessage ( `Error while fetching settings sync extension data.` ,  seeLogs ) ; 
948+ 				if  ( action  ===  seeLogs )  { 
949+ 					this . logger . show ( ) ; 
950+ 				} 
951+ 			} 
952+ 			return ; 
953+ 		} 
954+ 
955+ 		const  syncDataContent  =  parseSyncData ( syncData . content ) ; 
956+ 		if  ( ! syncDataContent )  { 
957+ 			this . logger . error ( 'Error while parsing sync data' ) ; 
958+ 			return ; 
959+ 		} 
960+ 
961+ 		let  extensions : ISyncExtension [ ] ; 
962+ 		try  { 
963+ 			extensions  =  JSON . parse ( syncDataContent . content ) ; 
964+ 		}  catch  { 
965+ 			this . logger . error ( 'Error while parsing settings sync extension data, malformed json' ) ; 
966+ 			return ; 
967+ 		} 
968+ 
969+ 		extensions  =  extensions . filter ( e  =>  e . installed ) ; 
970+ 		if  ( ! extensions . length )  { 
971+ 			return ; 
972+ 		} 
973+ 
974+ 		try  { 
975+ 			await  vscode . window . withProgress < void > ( { 
976+ 				title : 'Installing extensions on remote' , 
977+ 				location : vscode . ProgressLocation . Notification 
978+ 			} ,  async  ( )  =>  { 
979+ 				try  { 
980+ 					this . logger . trace ( `Installing extensions on remote: ` ,  extensions . map ( e  =>  e . identifier . id ) . join ( '\n' ) ) ; 
981+ 					await  retry ( async  ( )  =>  { 
982+ 						await  vscode . commands . executeCommand ( '__gitpod.initializeRemoteExtensions' ,  extensions ) ; 
983+ 					} ,  3000 ,  15 ) ; 
984+ 				}  catch  ( e )  { 
985+ 					this . logger . error ( `Could not execute '__gitpod.initializeRemoteExtensions' command` ) ; 
986+ 					throw  e ; 
987+ 				} 
988+ 			} ) ; 
989+ 		}  catch  { 
990+ 			const  seeLogs  =  'See Logs' ; 
991+ 			const  action  =  await  vscode . window . showErrorMessage ( `Error while installing extensions on remote.` ,  seeLogs ) ; 
992+ 			if  ( action  ===  seeLogs )  { 
993+ 				this . logger . show ( ) ; 
994+ 			} 
995+ 		} 
909996	} 
910997
911998	private  async  onGitpodRemoteConnection ( )  { 
@@ -939,27 +1026,15 @@ export default class RemoteConnector extends Disposable {
9391026
9401027		const  gitpodVersion  =  await  getGitpodVersion ( connectionInfo . gitpodHost ,  this . logger ) ; 
9411028		if  ( isFeatureSupported ( gitpodVersion ,  'localHeartbeat' ) )  { 
942- 			// gitpod remote extension installation is async so sometimes gitpod-desktop will activate before gitpod-remote 
943- 			// let's try a few times for it to finish install 
944- 			let  retryCount  =  15 ; 
945- 			const  tryStopRemoteHeartbeat  =  async  ( )  =>  { 
946- 				// Check for gitpod remote extension version to avoid sending heartbeat in both extensions at the same time 
947- 				const  isGitpodRemoteHeartbeatCancelled  =  await  cancelGitpodRemoteHeartbeat ( ) ; 
948- 				if  ( isGitpodRemoteHeartbeatCancelled )  { 
949- 					this . telemetry . sendTelemetryEvent ( 'vscode_desktop_heartbeat_state' ,  {  enabled : String ( true ) ,  gitpodHost : connectionInfo . gitpodHost ,  workspaceId : connectionInfo . workspaceId ,  instanceId : connectionInfo . instanceId ,  gitpodVersion : gitpodVersion . raw  } ) ; 
950- 				}  else  if  ( retryCount  >  0 )  { 
951- 					retryCount -- ; 
952- 					setTimeout ( tryStopRemoteHeartbeat ,  3000 ) ; 
953- 				}  else  { 
954- 					this . telemetry . sendTelemetryEvent ( 'vscode_desktop_heartbeat_state' ,  {  enabled : String ( false ) ,  gitpodHost : connectionInfo . gitpodHost ,  workspaceId : connectionInfo . workspaceId ,  instanceId : connectionInfo . instanceId ,  gitpodVersion : gitpodVersion . raw  } ) ; 
955- 				} 
956- 			} ; 
957- 
958- 			this . startHeartBeat ( session . accessToken ,  connectionInfo ) ; 
959- 			tryStopRemoteHeartbeat ( ) ; 
1029+ 			this . startHeartBeat ( session . accessToken ,  connectionInfo ,  gitpodVersion ) ; 
9601030		}  else  { 
9611031			this . logger . warn ( `Local heatbeat not supported in ${ connectionInfo . gitpodHost } ${ gitpodVersion . version }  ) ; 
9621032		} 
1033+ 
1034+ 		const  syncExtensions  =  vscode . workspace . getConfiguration ( 'gitpod' ) . get < boolean > ( 'remote.syncExtensions' ) ! ; 
1035+ 		if  ( syncExtensions )  { 
1036+ 			this . initializeRemoteExtensions ( ) ; 
1037+ 		} 
9631038	} 
9641039
9651040	public  override  async  dispose ( ) : Promise < void >  { 
@@ -980,17 +1055,6 @@ function isGitpodRemoteWindow(context: vscode.ExtensionContext) {
9801055	return  false ; 
9811056} 
9821057
983- async  function  cancelGitpodRemoteHeartbeat ( )  { 
984- 	let  result  =  false ; 
985- 	try  { 
986- 		// Invoke command from gitpot-remote extension 
987- 		result  =  await  vscode . commands . executeCommand ( '__gitpod.cancelGitpodRemoteHeartbeat' ) ; 
988- 	}  catch  { 
989- 		// Ignore if not found 
990- 	} 
991- 	return  result ; 
992- } 
993- 
9941058function  getServiceURL ( gitpodHost : string ) : string  { 
9951059	return  new  URL ( gitpodHost ) . toString ( ) . replace ( / \/ $ / ,  '' ) ; 
9961060} 
0 commit comments