@@ -27,9 +27,25 @@ import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common
27
27
28
28
const MISMATCH_LOCAL_PORT_COOLDOWN = 10 * 1000 ; // 10 seconds
29
29
const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore' ;
30
+ const TUNNELS_TO_RESTORE_EXPIRATION = 'remote.tunnels.toRestoreExpiration' ;
31
+ const RESTORE_EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 14 ; // 2 weeks
30
32
export const ACTIVATION_EVENT = 'onTunnel' ;
31
33
export const forwardedPortsViewEnabled = new RawContextKey < boolean > ( 'forwardedPortsViewEnabled' , false , nls . localize ( 'tunnel.forwardedPortsViewEnabled' , "Whether the Ports view is enabled." ) ) ;
32
34
35
+ export interface RestorableTunnel {
36
+ remoteHost : string ;
37
+ remotePort : number ;
38
+ localAddress : string ;
39
+ localUri : URI ;
40
+ protocol : TunnelProtocol ;
41
+ localPort ?: number ;
42
+ name ?: string ;
43
+ source : {
44
+ source : TunnelSource ;
45
+ description : string ;
46
+ } ;
47
+ }
48
+
33
49
export interface Tunnel {
34
50
remoteHost : string ;
35
51
remotePort : number ;
@@ -406,7 +422,7 @@ export class TunnelModel extends Disposable {
406
422
private knownPortsRestoreValue : string | undefined ;
407
423
private restoreComplete = false ;
408
424
private onRestoreComplete : Emitter < void > = new Emitter ( ) ;
409
- private unrestoredExtensionTunnels : Map < string , Tunnel > = new Map ( ) ;
425
+ private unrestoredExtensionTunnels : Map < string , RestorableTunnel > = new Map ( ) ;
410
426
private sessionCachedProperties : Map < string , Partial < TunnelProperties > > = new Map ( ) ;
411
427
412
428
private portAttributesProviders : PortAttributesProvider [ ] = [ ] ;
@@ -526,14 +542,22 @@ export class TunnelModel extends Disposable {
526
542
return URI . parse ( `${ protocol } ://${ localAddress } ` ) ;
527
543
}
528
544
529
- private async getStorageKey ( ) : Promise < string | undefined > {
545
+ private async addStorageKeyPostfix ( prefix : string ) : Promise < string | undefined > {
530
546
const workspace = this . workspaceContextService . getWorkspace ( ) ;
531
547
const workspaceHash = workspace . configuration ? hash ( workspace . configuration . path ) : ( workspace . folders . length > 0 ? hash ( workspace . folders [ 0 ] . uri . path ) : undefined ) ;
532
548
if ( workspaceHash === undefined ) {
533
549
this . logService . debug ( 'Could not get workspace hash for forwarded ports storage key.' ) ;
534
550
return undefined ;
535
551
}
536
- return `${ TUNNELS_TO_RESTORE } .${ this . environmentService . remoteAuthority } .${ workspaceHash } ` ;
552
+ return `${ prefix } .${ this . environmentService . remoteAuthority } .${ workspaceHash } ` ;
553
+ }
554
+
555
+ private async getTunnelRestoreStorageKey ( ) : Promise < string | undefined > {
556
+ return this . addStorageKeyPostfix ( TUNNELS_TO_RESTORE ) ;
557
+ }
558
+
559
+ private async getRestoreExpirationStorageKey ( ) : Promise < string | undefined > {
560
+ return this . addStorageKeyPostfix ( TUNNELS_TO_RESTORE_EXPIRATION ) ;
537
561
}
538
562
539
563
private async getTunnelRestoreValue ( ) : Promise < string | undefined > {
@@ -543,18 +567,19 @@ export class TunnelModel extends Disposable {
543
567
await this . storeForwarded ( ) ;
544
568
return deprecatedValue ;
545
569
}
546
- const storageKey = await this . getStorageKey ( ) ;
570
+ const storageKey = await this . getTunnelRestoreStorageKey ( ) ;
547
571
if ( ! storageKey ) {
548
572
return undefined ;
549
573
}
550
574
return this . storageService . get ( storageKey , StorageScope . PROFILE ) ;
551
575
}
552
576
553
577
async restoreForwarded ( ) {
578
+ this . cleanupExpiredTunnelsForRestore ( ) ;
554
579
if ( this . configurationService . getValue ( 'remote.restoreForwardedPorts' ) ) {
555
580
const tunnelRestoreValue = await this . tunnelRestoreValue ;
556
581
if ( tunnelRestoreValue && ( tunnelRestoreValue !== this . knownPortsRestoreValue ) ) {
557
- const tunnels = < Tunnel [ ] | undefined > JSON . parse ( tunnelRestoreValue ) ?? [ ] ;
582
+ const tunnels = < RestorableTunnel [ ] | undefined > JSON . parse ( tunnelRestoreValue ) ?? [ ] ;
558
583
this . logService . trace ( `ForwardedPorts: (TunnelModel) restoring ports ${ tunnels . map ( tunnel => tunnel . remotePort ) . join ( ', ' ) } ` ) ;
559
584
for ( const tunnel of tunnels ) {
560
585
const alreadyForwarded = mapHasAddressLocalhostOrAllInterfaces ( this . detected , tunnel . remoteHost , tunnel . remotePort ) ;
@@ -564,7 +589,6 @@ export class TunnelModel extends Disposable {
564
589
remote : { host : tunnel . remoteHost , port : tunnel . remotePort } ,
565
590
local : tunnel . localPort ,
566
591
name : tunnel . name ,
567
- privacy : tunnel . privacy ,
568
592
elevateIfNeeded : true ,
569
593
source : tunnel . source
570
594
} ) ;
@@ -580,7 +604,7 @@ export class TunnelModel extends Disposable {
580
604
581
605
if ( ! this . restoreListener ) {
582
606
// It's possible that at restore time the value hasn't synced.
583
- const key = await this . getStorageKey ( ) ;
607
+ const key = await this . getTunnelRestoreStorageKey ( ) ;
584
608
this . restoreListener = this . _register ( new DisposableStore ( ) ) ;
585
609
this . restoreListener . add ( this . storageService . onDidChangeValue ( StorageScope . PROFILE , undefined , this . restoreListener ) ( async ( e ) => {
586
610
if ( e . key === key ) {
@@ -591,17 +615,50 @@ export class TunnelModel extends Disposable {
591
615
}
592
616
}
593
617
618
+ private cleanupExpiredTunnelsForRestore ( ) {
619
+ const keys = this . storageService . keys ( StorageScope . PROFILE , StorageTarget . USER ) . filter ( key => key . startsWith ( TUNNELS_TO_RESTORE_EXPIRATION ) ) ;
620
+ for ( const key of keys ) {
621
+ const expiration = this . storageService . getNumber ( key , StorageScope . PROFILE ) ;
622
+ if ( expiration && expiration < Date . now ( ) ) {
623
+ this . tunnelRestoreValue = Promise . resolve ( undefined ) ;
624
+ const storageKey = key . replace ( TUNNELS_TO_RESTORE_EXPIRATION , TUNNELS_TO_RESTORE ) ;
625
+ this . storageService . remove ( key , StorageScope . PROFILE ) ;
626
+ this . storageService . remove ( storageKey , StorageScope . PROFILE ) ;
627
+ }
628
+ }
629
+ }
630
+
594
631
@debounce ( 1000 )
595
632
private async storeForwarded ( ) {
596
633
if ( this . configurationService . getValue ( 'remote.restoreForwardedPorts' ) ) {
597
- const valueToStore = JSON . stringify ( Array . from ( this . forwarded . values ( ) ) ) ;
598
- if ( valueToStore !== this . knownPortsRestoreValue ) {
599
- this . knownPortsRestoreValue = valueToStore ;
600
- const key = await this . getStorageKey ( ) ;
601
- if ( key ) {
602
- this . storageService . store ( key , this . knownPortsRestoreValue , StorageScope . PROFILE , StorageTarget . USER ) ;
603
- }
634
+ const forwarded = Array . from ( this . forwarded . values ( ) ) ;
635
+ const restorableTunnels : RestorableTunnel [ ] = forwarded . map ( tunnel => {
636
+ return {
637
+ remoteHost : tunnel . remoteHost ,
638
+ remotePort : tunnel . remotePort ,
639
+ localPort : tunnel . localPort ,
640
+ name : tunnel . name ,
641
+ localAddress : tunnel . localAddress ,
642
+ localUri : tunnel . localUri ,
643
+ protocol : tunnel . protocol ,
644
+ source : tunnel . source ,
645
+ } ;
646
+ } ) ;
647
+ let valueToStore : string | undefined ;
648
+ if ( forwarded . length > 0 ) {
649
+ valueToStore = JSON . stringify ( restorableTunnels ) ;
650
+ }
651
+
652
+ const key = await this . getTunnelRestoreStorageKey ( ) ;
653
+ const expirationKey = await this . getRestoreExpirationStorageKey ( ) ;
654
+ if ( ! valueToStore && key && expirationKey ) {
655
+ this . storageService . remove ( key , StorageScope . PROFILE ) ;
656
+ this . storageService . remove ( expirationKey , StorageScope . PROFILE ) ;
657
+ } else if ( ( valueToStore !== this . knownPortsRestoreValue ) && key && expirationKey ) {
658
+ this . storageService . store ( key , valueToStore , StorageScope . PROFILE , StorageTarget . USER ) ;
659
+ this . storageService . store ( expirationKey , Date . now ( ) + RESTORE_EXPIRATION_TIME , StorageScope . PROFILE , StorageTarget . USER ) ;
604
660
}
661
+ this . knownPortsRestoreValue = valueToStore ;
605
662
}
606
663
}
607
664
@@ -699,7 +756,7 @@ export class TunnelModel extends Disposable {
699
756
if ( updateProps ) {
700
757
tunnelProperties . name = updateProps . name ?? tunnelProperties . name ;
701
758
tunnelProperties . local = ( ( 'local' in updateProps ) ? updateProps . local : ( ( 'localPort' in updateProps ) ? updateProps . localPort : undefined ) ) ?? tunnelProperties . local ;
702
- tunnelProperties . privacy = updateProps . privacy ?? tunnelProperties . privacy ;
759
+ tunnelProperties . privacy = tunnelProperties . privacy ;
703
760
}
704
761
}
705
762
return tunnelProperties ;
0 commit comments