@@ -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,96 @@ 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
+ 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
+ }
909
996
}
910
997
911
998
private async onGitpodRemoteConnection ( ) {
@@ -939,27 +1026,15 @@ export default class RemoteConnector extends Disposable {
939
1026
940
1027
const gitpodVersion = await getGitpodVersion ( connectionInfo . gitpodHost , this . logger ) ;
941
1028
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 ) ;
960
1030
} else {
961
1031
this . logger . warn ( `Local heatbeat not supported in ${ connectionInfo . gitpodHost } , using version ${ gitpodVersion . version } ` ) ;
962
1032
}
1033
+
1034
+ const syncExtensions = vscode . workspace . getConfiguration ( 'gitpod' ) . get < boolean > ( 'remote.syncExtensions' ) ! ;
1035
+ if ( syncExtensions ) {
1036
+ this . initializeRemoteExtensions ( ) ;
1037
+ }
963
1038
}
964
1039
965
1040
public override async dispose ( ) : Promise < void > {
@@ -980,17 +1055,6 @@ function isGitpodRemoteWindow(context: vscode.ExtensionContext) {
980
1055
return false ;
981
1056
}
982
1057
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
1058
function getServiceURL ( gitpodHost : string ) : string {
995
1059
return new URL ( gitpodHost ) . toString ( ) . replace ( / \/ $ / , '' ) ;
996
1060
}
0 commit comments