@@ -25,11 +25,13 @@ import TelemetryReporter from './telemetryReporter';
25
25
import { addHostToHostFile , checkNewHostInHostkeys } from './ssh/hostfile' ;
26
26
import { DEFAULT_IDENTITY_FILES } from './ssh/identityFiles' ;
27
27
import { HeartbeatManager } from './heartbeat' ;
28
- import { getGitpodVersion , isFeatureSupported } from './featureSupport' ;
28
+ import { getGitpodVersion , GitpodVersion , isFeatureSupported } from './featureSupport' ;
29
29
import SSHConfiguration from './ssh/sshConfig' ;
30
30
import { isWindows } from './common/platform' ;
31
31
import { untildify } from './common/files' ;
32
32
import { ExperimentalSettings , isUserOverrideSetting } from './experiments' ;
33
+ import { ISyncExtension , NoSettingsSyncSession , NoSyncStoreError , parseSyncData , SettingsSync , SyncResource } from './settingsSync' ;
34
+ import { retry } from './common/async' ;
33
35
34
36
interface SSHConnectionParams {
35
37
workspaceId : string ;
@@ -121,6 +123,7 @@ export default class RemoteConnector extends Disposable {
121
123
122
124
constructor (
123
125
private readonly context : vscode . ExtensionContext ,
126
+ private readonly settingsSync : SettingsSync ,
124
127
private readonly experiments : ExperimentalSettings ,
125
128
private readonly logger : Log ,
126
129
private readonly telemetry : TelemetryReporter
@@ -900,12 +903,76 @@ export default class RemoteConnector extends Disposable {
900
903
}
901
904
}
902
905
903
- private startHeartBeat ( accessToken : string , connectionInfo : SSHConnectionParams ) {
906
+ private async startHeartBeat ( accessToken : string , connectionInfo : SSHConnectionParams , gitpodVersion : GitpodVersion ) {
904
907
if ( this . heartbeatManager ) {
905
908
return ;
906
909
}
907
910
908
911
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
+ return ;
947
+ }
948
+
949
+ const syncDataContent = parseSyncData ( syncData . content ) ;
950
+ if ( ! syncDataContent ) {
951
+ this . logger . error ( 'Error while parsing sync data' ) ;
952
+ return ;
953
+ }
954
+
955
+ let extensions : ISyncExtension [ ] ;
956
+ try {
957
+ extensions = JSON . parse ( syncDataContent . content ) ;
958
+ } catch {
959
+ this . logger . error ( 'Error while parsing settings sync extension data, malformed json' ) ;
960
+ return ;
961
+ }
962
+
963
+ extensions = extensions . filter ( e => e . installed ) ;
964
+ if ( ! extensions . length ) {
965
+ return ;
966
+ }
967
+
968
+ try {
969
+ this . logger . trace ( `Try installing extensions on remote: ` , extensions . map ( e => e . identifier . id ) . join ( '\n' ) ) ;
970
+ await retry ( async ( ) => {
971
+ await vscode . commands . executeCommand ( '__gitpod.initializeRemoteExtensions' , extensions ) ;
972
+ } , 3000 , 15 ) ;
973
+ } catch {
974
+ this . logger . error ( `Could not execute '__gitpod.initializeRemoteExtensions' command` ) ;
975
+ }
909
976
}
910
977
911
978
private async onGitpodRemoteConnection ( ) {
@@ -939,27 +1006,12 @@ export default class RemoteConnector extends Disposable {
939
1006
940
1007
const gitpodVersion = await getGitpodVersion ( connectionInfo . gitpodHost , this . logger ) ;
941
1008
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 ( ) ;
1009
+ this . startHeartBeat ( session . accessToken , connectionInfo , gitpodVersion ) ;
960
1010
} else {
961
1011
this . logger . warn ( `Local heatbeat not supported in ${ connectionInfo . gitpodHost } , using version ${ gitpodVersion . version } ` ) ;
962
1012
}
1013
+
1014
+ this . initializeRemoteExtensions ( ) ;
963
1015
}
964
1016
965
1017
public override async dispose ( ) : Promise < void > {
@@ -980,17 +1032,6 @@ function isGitpodRemoteWindow(context: vscode.ExtensionContext) {
980
1032
return false ;
981
1033
}
982
1034
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
-
994
1035
function getServiceURL ( gitpodHost : string ) : string {
995
1036
return new URL ( gitpodHost ) . toString ( ) . replace ( / \/ $ / , '' ) ;
996
1037
}
0 commit comments