fix(routes): preserve remote route identity
- commit
- fe8474f964a8ea7e979c15cd50bb3b0677782fcf
- author
- brice · · 2026-08-10 04:38 UTC
- parent
- 217c05a
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
16 files · +116 -37
M internal/app/run.go +9 -0
| @@ -855,6 +855,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 855 | 855 | factory := deps.remoteDialerFactory | |
| 856 | 856 | var remoteSelection *domain.RemoteSessionTarget | |
| 857 | 857 | var remoteEnvironmentPolicy ports.EnvironmentPolicy | |
| 858 | + | remoteDisplayOrigin := remoteTarget | |
| 858 | 859 | routeOrigin := ports.RouteOriginLocal | |
| 859 | 860 | routeOriginKey := "local" | |
| 860 | 861 | if remoteTarget != "" { | |
| @@ -894,6 +895,10 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 894 | 895 | return nil, client.AttachRequest{}, err | |
| 895 | 896 | } | |
| 896 | 897 | policy := pickerEnvironmentPolicy(selection, target.EnvironmentPolicy) | |
| 898 | + | displayOrigin := target.Endpoint | |
| 899 | + | if selection != nil { | |
| 900 | + | displayOrigin = selection.DisplayOrigin | |
| 901 | + | } | |
| 897 | 902 | return dialer, client.AttachRequest{ | |
| 898 | 903 | Intent: target.Intent, | |
| 899 | 904 | SessionName: target.Session, | |
| @@ -901,6 +906,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 901 | 906 | Origin: ports.RouteOriginDiscovery, | |
| 902 | 907 | OriginKey: target.Endpoint, | |
| 903 | 908 | RemoteTarget: selection, | |
| 909 | + | RemoteOrigin: displayOrigin, | |
| 904 | 910 | EnvironmentPolicy: policy, | |
| 905 | 911 | }, nil | |
| 906 | 912 | } | |
| @@ -934,6 +940,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 934 | 940 | Origin: routeOrigin, | |
| 935 | 941 | OriginKey: routeOriginKey, | |
| 936 | 942 | RemoteTarget: remoteSelection, | |
| 943 | + | RemoteOrigin: remoteDisplayOrigin, | |
| 937 | 944 | EnvironmentPolicy: remoteEnvironmentPolicy, | |
| 938 | 945 | }) | |
| 939 | 946 | } else { | |
| @@ -964,6 +971,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 964 | 971 | return fmt.Errorf("vev: invalid remote attach handoff: %w", err) | |
| 965 | 972 | } | |
| 966 | 973 | remoteTarget = handoffErr.Target.Endpoint | |
| 974 | + | remoteDisplayOrigin = remoteTarget | |
| 967 | 975 | routeOrigin = ports.RouteOriginDiscovery | |
| 968 | 976 | routeOriginKey = remoteTarget | |
| 969 | 977 | name = handoffErr.Target.Session | |
| @@ -972,6 +980,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||
| 972 | 980 | if handoffErr.Target.RemoteTarget != nil { | |
| 973 | 981 | copyTarget := *handoffErr.Target.RemoteTarget | |
| 974 | 982 | remoteSelection = ©Target | |
| 983 | + | remoteDisplayOrigin = copyTarget.DisplayOrigin | |
| 975 | 984 | } | |
| 976 | 985 | remoteEnvironmentPolicy = pickerEnvironmentPolicy(remoteSelection, handoffErr.Target.EnvironmentPolicy) | |
| 977 | 986 | if modeErr != nil { | |
| @@ -855,6 +855,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 855 | factory := deps.remoteDialerFactory | 855 | factory := deps.remoteDialerFactory | ||
| 856 | var remoteSelection *domain.RemoteSessionTarget | 856 | var remoteSelection *domain.RemoteSessionTarget | ||
| 857 | var remoteEnvironmentPolicy ports.EnvironmentPolicy | 857 | var remoteEnvironmentPolicy ports.EnvironmentPolicy | ||
| 858 | + | remoteDisplayOrigin := remoteTarget | |||
| 858 | routeOrigin := ports.RouteOriginLocal | 859 | routeOrigin := ports.RouteOriginLocal | ||
| 859 | routeOriginKey := "local" | 860 | routeOriginKey := "local" | ||
| 860 | if remoteTarget != "" { | 861 | if remoteTarget != "" { | ||
| @@ -894,6 +895,10 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 894 | return nil, client.AttachRequest{}, err | 895 | return nil, client.AttachRequest{}, err | ||
| 895 | } | 896 | } | ||
| 896 | policy := pickerEnvironmentPolicy(selection, target.EnvironmentPolicy) | 897 | policy := pickerEnvironmentPolicy(selection, target.EnvironmentPolicy) | ||
| 898 | + | displayOrigin := target.Endpoint | |||
| 899 | + | if selection != nil { | |||
| 900 | + | displayOrigin = selection.DisplayOrigin | |||
| 901 | + | } | |||
| 897 | return dialer, client.AttachRequest{ | 902 | return dialer, client.AttachRequest{ | ||
| 898 | Intent: target.Intent, | 903 | Intent: target.Intent, | ||
| 899 | SessionName: target.Session, | 904 | SessionName: target.Session, | ||
| @@ -901,6 +906,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 901 | Origin: ports.RouteOriginDiscovery, | 906 | Origin: ports.RouteOriginDiscovery, | ||
| 902 | OriginKey: target.Endpoint, | 907 | OriginKey: target.Endpoint, | ||
| 903 | RemoteTarget: selection, | 908 | RemoteTarget: selection, | ||
| 909 | + | RemoteOrigin: displayOrigin, | |||
| 904 | EnvironmentPolicy: policy, | 910 | EnvironmentPolicy: policy, | ||
| 905 | }, nil | 911 | }, nil | ||
| 906 | } | 912 | } | ||
| @@ -934,6 +940,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 934 | Origin: routeOrigin, | 940 | Origin: routeOrigin, | ||
| 935 | OriginKey: routeOriginKey, | 941 | OriginKey: routeOriginKey, | ||
| 936 | RemoteTarget: remoteSelection, | 942 | RemoteTarget: remoteSelection, | ||
| 943 | + | RemoteOrigin: remoteDisplayOrigin, | |||
| 937 | EnvironmentPolicy: remoteEnvironmentPolicy, | 944 | EnvironmentPolicy: remoteEnvironmentPolicy, | ||
| 938 | }) | 945 | }) | ||
| 939 | } else { | 946 | } else { | ||
| @@ -964,6 +971,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 964 | return fmt.Errorf("vev: invalid remote attach handoff: %w", err) | 971 | return fmt.Errorf("vev: invalid remote attach handoff: %w", err) | ||
| 965 | } | 972 | } | ||
| 966 | remoteTarget = handoffErr.Target.Endpoint | 973 | remoteTarget = handoffErr.Target.Endpoint | ||
| 974 | + | remoteDisplayOrigin = remoteTarget | |||
| 967 | routeOrigin = ports.RouteOriginDiscovery | 975 | routeOrigin = ports.RouteOriginDiscovery | ||
| 968 | routeOriginKey = remoteTarget | 976 | routeOriginKey = remoteTarget | ||
| 969 | name = handoffErr.Target.Session | 977 | name = handoffErr.Target.Session | ||
| @@ -972,6 +980,7 @@ func runAttachWithDeps(ctx context.Context, intent uint8, name, remoteTarget, ac | |||||
| 972 | if handoffErr.Target.RemoteTarget != nil { | 980 | if handoffErr.Target.RemoteTarget != nil { | ||
| 973 | copyTarget := *handoffErr.Target.RemoteTarget | 981 | copyTarget := *handoffErr.Target.RemoteTarget | ||
| 974 | remoteSelection = ©Target | 982 | remoteSelection = ©Target | ||
| 983 | + | remoteDisplayOrigin = copyTarget.DisplayOrigin | |||
| 975 | } | 984 | } | ||
| 976 | remoteEnvironmentPolicy = pickerEnvironmentPolicy(remoteSelection, handoffErr.Target.EnvironmentPolicy) | 985 | remoteEnvironmentPolicy = pickerEnvironmentPolicy(remoteSelection, handoffErr.Target.EnvironmentPolicy) | ||
| 977 | if modeErr != nil { | 986 | if modeErr != nil { | ||
M internal/app/run_test.go +1 -1
| @@ -946,7 +946,7 @@ func TestRunAttachWithDepsLocalPickerHandoffAttachesSelectedRemote(t *testing.T) | |||
| 946 | 946 | runClient: func(_ context.Context, deps client.Dependencies, request client.AttachRequest) error { | |
| 947 | 947 | if deps.Remote { | |
| 948 | 948 | remoteCalls++ | |
| 949 | - | require.Equal(t, client.AttachRequest{Intent: ports.IntentAttach, SessionName: "picked", Remote: true, Origin: ports.RouteOriginDiscovery, OriginKey: "selected.example", EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned}, request) | |
| 949 | + | require.Equal(t, client.AttachRequest{Intent: ports.IntentAttach, SessionName: "picked", Remote: true, Origin: ports.RouteOriginDiscovery, OriginKey: "selected.example", RemoteOrigin: "selected.example", EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned}, request) | |
| 950 | 950 | return nil | |
| 951 | 951 | } | |
| 952 | 952 | localCalls++ | |
| @@ -946,7 +946,7 @@ func TestRunAttachWithDepsLocalPickerHandoffAttachesSelectedRemote(t *testing.T) | |||||
| 946 | runClient: func(_ context.Context, deps client.Dependencies, request client.AttachRequest) error { | 946 | runClient: func(_ context.Context, deps client.Dependencies, request client.AttachRequest) error { | ||
| 947 | if deps.Remote { | 947 | if deps.Remote { | ||
| 948 | remoteCalls++ | 948 | remoteCalls++ | ||
| 949 | - | require.Equal(t, client.AttachRequest{Intent: ports.IntentAttach, SessionName: "picked", Remote: true, Origin: ports.RouteOriginDiscovery, OriginKey: "selected.example", EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned}, request) | 949 | + | require.Equal(t, client.AttachRequest{Intent: ports.IntentAttach, SessionName: "picked", Remote: true, Origin: ports.RouteOriginDiscovery, OriginKey: "selected.example", RemoteOrigin: "selected.example", EnvironmentPolicy: ports.EnvironmentPolicyDaemonOwned}, request) |
| 950 | return nil | 950 | return nil | ||
| 951 | } | 951 | } | ||
| 952 | localCalls++ | 952 | localCalls++ | ||
M internal/ports/frame.go +1 -1
| @@ -12,7 +12,7 @@ import ( | |||
| 12 | 12 | const HandshakeTimeout = 15 * time.Second | |
| 13 | 13 | ||
| 14 | 14 | // ProtocolVersion is the current vev IPC wire protocol version. | |
| 15 | - | const ProtocolVersion uint16 = 28 | |
| 15 | + | const ProtocolVersion uint16 = 29 | |
| 16 | 16 | ||
| 17 | 17 | // MaxFrameLen is the largest permitted frame length, including the type byte | |
| 18 | 18 | // and excluding the four-byte length prefix. | |
| @@ -12,7 +12,7 @@ import ( | |||||
| 12 | const HandshakeTimeout = 15 * time.Second | 12 | const HandshakeTimeout = 15 * time.Second | ||
| 13 | 13 | ||||
| 14 | // ProtocolVersion is the current vev IPC wire protocol version. | 14 | // ProtocolVersion is the current vev IPC wire protocol version. | ||
| 15 | - | const ProtocolVersion uint16 = 28 | 15 | + | const ProtocolVersion uint16 = 29 |
| 16 | 16 | ||||
| 17 | // MaxFrameLen is the largest permitted frame length, including the type byte | 17 | // MaxFrameLen is the largest permitted frame length, including the type byte | ||
| 18 | // and excluding the four-byte length prefix. | 18 | // and excluding the four-byte length prefix. | ||
M internal/ports/frame_test.go +2 -2
| @@ -3,8 +3,8 @@ package ports | |||
| 3 | 3 | import "testing" | |
| 4 | 4 | ||
| 5 | 5 | func TestProtocolVersion(t *testing.T) { | |
| 6 | - | if ProtocolVersion != 28 { | |
| 7 | - | t.Fatalf("ProtocolVersion = %d, want 28", ProtocolVersion) | |
| 6 | + | if ProtocolVersion != 29 { | |
| 7 | + | t.Fatalf("ProtocolVersion = %d, want 29", ProtocolVersion) | |
| 8 | 8 | } | |
| 9 | 9 | } | |
| 10 | 10 | ||
| @@ -3,8 +3,8 @@ package ports | |||||
| 3 | import "testing" | 3 | import "testing" | ||
| 4 | 4 | ||||
| 5 | func TestProtocolVersion(t *testing.T) { | 5 | func TestProtocolVersion(t *testing.T) { | ||
| 6 | - | if ProtocolVersion != 28 { | 6 | + | if ProtocolVersion != 29 { |
| 7 | - | t.Fatalf("ProtocolVersion = %d, want 28", ProtocolVersion) | 7 | + | t.Fatalf("ProtocolVersion = %d, want 29", ProtocolVersion) |
| 8 | } | 8 | } | ||
| 9 | } | 9 | } | ||
| 10 | 10 | ||||
M internal/ports/navigation_test.go +1 -1
| @@ -62,7 +62,7 @@ func TestHelloNavigationValidationTable(t *testing.T) { | |||
| 62 | 62 | {name: "client-owned home picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker}, valid: false}, | |
| 63 | 63 | {name: "daemon-owned back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | |
| 64 | 64 | {name: "remote-target back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | |
| 65 | - | {name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "001c02" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0201"}, | |
| 65 | + | {name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "001d02" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0000" + "0201"}, | |
| 66 | 66 | {name: "new rejects navigation", hello: Hello{Version: ProtocolVersion, Intent: IntentNew, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: false}, | |
| 67 | 67 | {name: "unknown capability", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: 4}, valid: false}, | |
| 68 | 68 | {name: "back without picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack}, valid: false}, | |
| @@ -62,7 +62,7 @@ func TestHelloNavigationValidationTable(t *testing.T) { | |||||
| 62 | {name: "client-owned home picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker}, valid: false}, | 62 | {name: "client-owned home picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityHomePicker}, valid: false}, | ||
| 63 | {name: "daemon-owned back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | 63 | {name: "daemon-owned back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | ||
| 64 | {name: "remote-target back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | 64 | {name: "remote-target back picker rejected", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Name: "work", Size: domain.Size{Cols: 80, Rows: 24}, RemoteTarget: remoteTarget, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker, EnvironmentPolicy: EnvironmentPolicyDaemonOwned}, valid: false}, | ||
| 65 | - | {name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "001c02" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0201"}, | 65 | + | {name: "back with startup picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: true, capability: NavigationCapabilityBack, overlay: StartupOverlaySessionPicker, wantPayload: "001d02" + "00000000000000000000000000000000" + "0000000000000000" + "0000" + "00500018" + "0000" + "0000" + "00" + "00" + "00000000" + "00" + "00" + "00" + "0000" + "0000" + "0201"}, |
| 66 | {name: "new rejects navigation", hello: Hello{Version: ProtocolVersion, Intent: IntentNew, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: false}, | 66 | {name: "new rejects navigation", hello: Hello{Version: ProtocolVersion, Intent: IntentNew, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack, StartupOverlay: StartupOverlaySessionPicker}, valid: false}, | ||
| 67 | {name: "unknown capability", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: 4}, valid: false}, | 67 | {name: "unknown capability", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: 4}, valid: false}, | ||
| 68 | {name: "back without picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack}, valid: false}, | 68 | {name: "back without picker", hello: Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}, NavigationCapabilities: NavigationCapabilityBack}, valid: false}, | ||
M internal/ports/remote_target_wire_test.go +1 -1
| @@ -115,7 +115,7 @@ func TestRemoteTargetWireRejectsClientOwnedEnvironmentPolicy(t *testing.T) { | |||
| 115 | 115 | }) | |
| 116 | 116 | }, | |
| 117 | 117 | unmarshal: func(payload []byte) error { _, err := UnmarshalHello(payload); return err }, | |
| 118 | - | policyOffset: 6, | |
| 118 | + | policyOffset: 8, | |
| 119 | 119 | wantErr: ErrInvalidHello, | |
| 120 | 120 | }, | |
| 121 | 121 | { | |
| @@ -115,7 +115,7 @@ func TestRemoteTargetWireRejectsClientOwnedEnvironmentPolicy(t *testing.T) { | |||||
| 115 | }) | 115 | }) | ||
| 116 | }, | 116 | }, | ||
| 117 | unmarshal: func(payload []byte) error { _, err := UnmarshalHello(payload); return err }, | 117 | unmarshal: func(payload []byte) error { _, err := UnmarshalHello(payload); return err }, | ||
| 118 | - | policyOffset: 6, | 118 | + | policyOffset: 8, |
| 119 | wantErr: ErrInvalidHello, | 119 | wantErr: ErrInvalidHello, | ||
| 120 | }, | 120 | }, | ||
| 121 | { | 121 | { | ||
M internal/ports/wire.go +13 -0
| @@ -169,6 +169,7 @@ type Hello struct { | |||
| 169 | 169 | // PreferredTabID is a client-owned route cursor. The daemon treats it as an | |
| 170 | 170 | // attachment-local hint and falls back when the stable tab no longer exists. | |
| 171 | 171 | PreferredTabID domain.TabStableID | |
| 172 | + | RemoteOrigin string | |
| 172 | 173 | EnvironmentPolicy EnvironmentPolicy | |
| 173 | 174 | NavigationCapabilities NavigationCapabilities | |
| 174 | 175 | StartupOverlay StartupOverlay | |
| @@ -728,6 +729,11 @@ func ValidateHello(h Hello) error { | |||
| 728 | 729 | return ErrInvalidHello | |
| 729 | 730 | } | |
| 730 | 731 | } | |
| 732 | + | if h.RemoteOrigin != "" { | |
| 733 | + | if err := domain.ValidateRemoteDisplayOrigin(h.RemoteOrigin); err != nil { | |
| 734 | + | return fmt.Errorf("%w: remote origin: %v", ErrInvalidHello, err) | |
| 735 | + | } | |
| 736 | + | } | |
| 731 | 737 | if h.PreferredTabID != "" { | |
| 732 | 738 | if h.Intent != IntentAttach && h.Intent != IntentResume { | |
| 733 | 739 | return ErrInvalidHello | |
| @@ -864,6 +870,7 @@ func MarshalHello(h Hello) []byte { | |||
| 864 | 870 | marshalRemoteTargetSection(&w, h.RemoteTarget, h.EnvironmentPolicy) | |
| 865 | 871 | marshalExactTargetSection(&w, h.ExactTarget) | |
| 866 | 872 | w.putString(string(h.PreferredTabID)) | |
| 873 | + | w.putString(h.RemoteOrigin) | |
| 867 | 874 | w.putUint8(uint8(h.NavigationCapabilities)) | |
| 868 | 875 | w.putUint8(uint8(h.StartupOverlay)) | |
| 869 | 876 | return w.b | |
| @@ -930,6 +937,9 @@ func preflightHello(b []byte) error { | |||
| 930 | 937 | if err := r.skipString(); err != nil { | |
| 931 | 938 | return err | |
| 932 | 939 | } | |
| 940 | + | if err := r.skipString(); err != nil { | |
| 941 | + | return err | |
| 942 | + | } | |
| 933 | 943 | if _, err := r.getUint8(); err != nil { | |
| 934 | 944 | return err | |
| 935 | 945 | } | |
| @@ -1019,6 +1029,9 @@ func UnmarshalHello(b []byte) (Hello, error) { | |||
| 1019 | 1029 | return Hello{}, err | |
| 1020 | 1030 | } | |
| 1021 | 1031 | h.PreferredTabID = domain.TabStableID(preferredTabID) | |
| 1032 | + | if h.RemoteOrigin, err = r.getString(); err != nil { | |
| 1033 | + | return Hello{}, err | |
| 1034 | + | } | |
| 1022 | 1035 | capabilities, err := r.getUint8() | |
| 1023 | 1036 | if err != nil { | |
| 1024 | 1037 | return Hello{}, err | |
| @@ -169,6 +169,7 @@ type Hello struct { | |||||
| 169 | // PreferredTabID is a client-owned route cursor. The daemon treats it as an | 169 | // PreferredTabID is a client-owned route cursor. The daemon treats it as an | ||
| 170 | // attachment-local hint and falls back when the stable tab no longer exists. | 170 | // attachment-local hint and falls back when the stable tab no longer exists. | ||
| 171 | PreferredTabID domain.TabStableID | 171 | PreferredTabID domain.TabStableID | ||
| 172 | + | RemoteOrigin string | |||
| 172 | EnvironmentPolicy EnvironmentPolicy | 173 | EnvironmentPolicy EnvironmentPolicy | ||
| 173 | NavigationCapabilities NavigationCapabilities | 174 | NavigationCapabilities NavigationCapabilities | ||
| 174 | StartupOverlay StartupOverlay | 175 | StartupOverlay StartupOverlay | ||
| @@ -728,6 +729,11 @@ func ValidateHello(h Hello) error { | |||||
| 728 | return ErrInvalidHello | 729 | return ErrInvalidHello | ||
| 729 | } | 730 | } | ||
| 730 | } | 731 | } | ||
| 732 | + | if h.RemoteOrigin != "" { | |||
| 733 | + | if err := domain.ValidateRemoteDisplayOrigin(h.RemoteOrigin); err != nil { | |||
| 734 | + | return fmt.Errorf("%w: remote origin: %v", ErrInvalidHello, err) | |||
| 735 | + | } | |||
| 736 | + | } | |||
| 731 | if h.PreferredTabID != "" { | 737 | if h.PreferredTabID != "" { | ||
| 732 | if h.Intent != IntentAttach && h.Intent != IntentResume { | 738 | if h.Intent != IntentAttach && h.Intent != IntentResume { | ||
| 733 | return ErrInvalidHello | 739 | return ErrInvalidHello | ||
| @@ -864,6 +870,7 @@ func MarshalHello(h Hello) []byte { | |||||
| 864 | marshalRemoteTargetSection(&w, h.RemoteTarget, h.EnvironmentPolicy) | 870 | marshalRemoteTargetSection(&w, h.RemoteTarget, h.EnvironmentPolicy) | ||
| 865 | marshalExactTargetSection(&w, h.ExactTarget) | 871 | marshalExactTargetSection(&w, h.ExactTarget) | ||
| 866 | w.putString(string(h.PreferredTabID)) | 872 | w.putString(string(h.PreferredTabID)) | ||
| 873 | + | w.putString(h.RemoteOrigin) | |||
| 867 | w.putUint8(uint8(h.NavigationCapabilities)) | 874 | w.putUint8(uint8(h.NavigationCapabilities)) | ||
| 868 | w.putUint8(uint8(h.StartupOverlay)) | 875 | w.putUint8(uint8(h.StartupOverlay)) | ||
| 869 | return w.b | 876 | return w.b | ||
| @@ -930,6 +937,9 @@ func preflightHello(b []byte) error { | |||||
| 930 | if err := r.skipString(); err != nil { | 937 | if err := r.skipString(); err != nil { | ||
| 931 | return err | 938 | return err | ||
| 932 | } | 939 | } | ||
| 940 | + | if err := r.skipString(); err != nil { | |||
| 941 | + | return err | |||
| 942 | + | } | |||
| 933 | if _, err := r.getUint8(); err != nil { | 943 | if _, err := r.getUint8(); err != nil { | ||
| 934 | return err | 944 | return err | ||
| 935 | } | 945 | } | ||
| @@ -1019,6 +1029,9 @@ func UnmarshalHello(b []byte) (Hello, error) { | |||||
| 1019 | return Hello{}, err | 1029 | return Hello{}, err | ||
| 1020 | } | 1030 | } | ||
| 1021 | h.PreferredTabID = domain.TabStableID(preferredTabID) | 1031 | h.PreferredTabID = domain.TabStableID(preferredTabID) | ||
| 1032 | + | if h.RemoteOrigin, err = r.getString(); err != nil { | |||
| 1033 | + | return Hello{}, err | |||
| 1034 | + | } | |||
| 1022 | capabilities, err := r.getUint8() | 1035 | capabilities, err := r.getUint8() | ||
| 1023 | if err != nil { | 1036 | if err != nil { | ||
| 1024 | return Hello{}, err | 1037 | return Hello{}, err | ||
M internal/ports/wire_final_test.go +4 -4
| @@ -11,8 +11,8 @@ import ( | |||
| 11 | 11 | ) | |
| 12 | 12 | ||
| 13 | 13 | func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | |
| 14 | - | if ProtocolVersion != 28 { | |
| 15 | - | t.Fatalf("ProtocolVersion = %d, want 28", ProtocolVersion) | |
| 14 | + | if ProtocolVersion != 29 { | |
| 15 | + | t.Fatalf("ProtocolVersion = %d, want 29", ProtocolVersion) | |
| 16 | 16 | } | |
| 17 | 17 | payload := MarshalHello(Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}) | |
| 18 | 18 | if len(payload) < 2 { | |
| @@ -26,8 +26,8 @@ func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | |||
| 26 | 26 | ||
| 27 | 27 | func TestFinalHelloGoldenStrict(t *testing.T) { | |
| 28 | 28 | msg := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}} | |
| 29 | - | want := append([]byte{0, 28, 2}, make([]byte, 16+8)...) | |
| 30 | - | want = append(want, 0, 0, 0, 80, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | |
| 29 | + | want := append([]byte{0, 29, 2}, make([]byte, 16+8)...) | |
| 30 | + | want = append(want, 0, 0, 0, 80, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | |
| 31 | 31 | got := MarshalHello(msg) | |
| 32 | 32 | if !bytes.Equal(got, want) { | |
| 33 | 33 | t.Fatalf("Hello bytes = %x, want %x", got, want) | |
| @@ -11,8 +11,8 @@ import ( | |||||
| 11 | ) | 11 | ) | ||
| 12 | 12 | ||||
| 13 | func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | 13 | func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | ||
| 14 | - | if ProtocolVersion != 28 { | 14 | + | if ProtocolVersion != 29 { |
| 15 | - | t.Fatalf("ProtocolVersion = %d, want 28", ProtocolVersion) | 15 | + | t.Fatalf("ProtocolVersion = %d, want 29", ProtocolVersion) |
| 16 | } | 16 | } | ||
| 17 | payload := MarshalHello(Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}) | 17 | payload := MarshalHello(Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}}) | ||
| 18 | if len(payload) < 2 { | 18 | if len(payload) < 2 { | ||
| @@ -26,8 +26,8 @@ func TestFinalProtocolVersionAndNoV21Hello(t *testing.T) { | |||||
| 26 | 26 | ||||
| 27 | func TestFinalHelloGoldenStrict(t *testing.T) { | 27 | func TestFinalHelloGoldenStrict(t *testing.T) { | ||
| 28 | msg := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}} | 28 | msg := Hello{Version: ProtocolVersion, Intent: IntentAttach, Size: domain.Size{Cols: 80, Rows: 24}} | ||
| 29 | - | want := append([]byte{0, 28, 2}, make([]byte, 16+8)...) | 29 | + | want := append([]byte{0, 29, 2}, make([]byte, 16+8)...) |
| 30 | - | want = append(want, 0, 0, 0, 80, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | 30 | + | want = append(want, 0, 0, 0, 80, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) |
| 31 | got := MarshalHello(msg) | 31 | got := MarshalHello(msg) | ||
| 32 | if !bytes.Equal(got, want) { | 32 | if !bytes.Equal(got, want) { | ||
| 33 | t.Fatalf("Hello bytes = %x, want %x", got, want) | 33 | t.Fatalf("Hello bytes = %x, want %x", got, want) | ||
M internal/ports/wire_test.go +9 -9
| @@ -54,9 +54,9 @@ func assertTrailingGarbageFails[T any](t *testing.T, full []byte, unmarshal func | |||
| 54 | 54 | } | |
| 55 | 55 | ||
| 56 | 56 | func appendNoNavigationTail(payload []byte) []byte { | |
| 57 | - | // exact-target absent, preferred-tab absent, navigation capabilities none, | |
| 58 | - | // startup overlay none. | |
| 59 | - | return append(payload, 0, 0, 0, 0, 0) | |
| 57 | + | // exact-target absent, preferred-tab absent, remote-origin absent, | |
| 58 | + | // navigation capabilities none, startup overlay none. | |
| 59 | + | return append(payload, 0, 0, 0, 0, 0, 0, 0) | |
| 60 | 60 | } | |
| 61 | 61 | ||
| 62 | 62 | func TestHelloGoldenAndRoundTrip(t *testing.T) { | |
| @@ -131,7 +131,7 @@ func TestHelloEnvironmentCodec(t *testing.T) { | |||
| 131 | 131 | } | |
| 132 | 132 | got := MarshalHello(msg) | |
| 133 | 133 | want := []byte{ | |
| 134 | - | 0x00, 0x1c, 0x00, // version, intent, first client ID byte | |
| 134 | + | 0x00, 0x1d, 0x00, // version, intent, first client ID byte | |
| 135 | 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 136 | 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // client ID | |
| 137 | 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // resume token | |
| @@ -350,7 +350,7 @@ func TestThemeGenerationClearedWireGoldenPreservesProtocolVersion(t *testing.T) | |||
| 350 | 350 | } | |
| 351 | 351 | want := append([]byte{0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00}, make([]byte, 48)...) | |
| 352 | 352 | require.Equal(t, want, MarshalTheme(cleared)) | |
| 353 | - | require.Equal(t, uint16(28), ProtocolVersion) | |
| 353 | + | require.Equal(t, uint16(29), ProtocolVersion) | |
| 354 | 354 | } | |
| 355 | 355 | ||
| 356 | 356 | func TestResizeGoldenAndRoundTrip(t *testing.T) { | |
| @@ -511,7 +511,7 @@ func TestCommandRequestGoldenAndRoundTrip(t *testing.T) { | |||
| 511 | 511 | name: "minimal", | |
| 512 | 512 | msg: CommandRequest{Version: ProtocolVersion, Slug: "split-right"}, | |
| 513 | 513 | want: []byte{ | |
| 514 | - | 0x00, 0x1c, | |
| 514 | + | 0x00, 0x1d, | |
| 515 | 515 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | |
| 516 | 516 | 0x00, // attached | |
| 517 | 517 | 0x00, // self | |
| @@ -534,7 +534,7 @@ func TestCommandRequestGoldenAndRoundTrip(t *testing.T) { | |||
| 534 | 534 | JSON: true, | |
| 535 | 535 | }, | |
| 536 | 536 | want: []byte{ | |
| 537 | - | 0x00, 0x1c, | |
| 537 | + | 0x00, 0x1d, | |
| 538 | 538 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | |
| 539 | 539 | 0x00, // attached | |
| 540 | 540 | 0x01, // self | |
| @@ -963,7 +963,7 @@ func TestCommandCorrelationGoldenAndStrict(t *testing.T) { | |||
| 963 | 963 | name: "attached", | |
| 964 | 964 | msg: CommandRequest{Version: ProtocolVersion, RequestID: 0x0102030405060708, Attached: true, Self: true, Slug: "split-right", Args: []string{"--vertical"}}, | |
| 965 | 965 | want: []byte{ | |
| 966 | - | 0x00, 0x1c, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x01, | |
| 966 | + | 0x00, 0x1d, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x01, | |
| 967 | 967 | 0x00, 0x0b, 's', 'p', 'l', 'i', 't', '-', 'r', 'i', 'g', 'h', 't', | |
| 968 | 968 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, '-', '-', 'v', 'e', 'r', 't', 'i', 'c', 'a', 'l', | |
| 969 | 969 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| @@ -973,7 +973,7 @@ func TestCommandCorrelationGoldenAndStrict(t *testing.T) { | |||
| 973 | 973 | name: "control", | |
| 974 | 974 | msg: CommandRequest{Version: ProtocolVersion, Slug: "ls"}, | |
| 975 | 975 | want: []byte{ | |
| 976 | - | 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 976 | + | 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 977 | 977 | 0x00, 0x02, 'l', 's', 0x00, 0x00, | |
| 978 | 978 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 979 | 979 | }, | |
| @@ -54,9 +54,9 @@ func assertTrailingGarbageFails[T any](t *testing.T, full []byte, unmarshal func | |||||
| 54 | } | 54 | } | ||
| 55 | 55 | ||||
| 56 | func appendNoNavigationTail(payload []byte) []byte { | 56 | func appendNoNavigationTail(payload []byte) []byte { | ||
| 57 | - | // exact-target absent, preferred-tab absent, navigation capabilities none, | 57 | + | // exact-target absent, preferred-tab absent, remote-origin absent, |
| 58 | - | // startup overlay none. | 58 | + | // navigation capabilities none, startup overlay none. |
| 59 | - | return append(payload, 0, 0, 0, 0, 0) | 59 | + | return append(payload, 0, 0, 0, 0, 0, 0, 0) |
| 60 | } | 60 | } | ||
| 61 | 61 | ||||
| 62 | func TestHelloGoldenAndRoundTrip(t *testing.T) { | 62 | func TestHelloGoldenAndRoundTrip(t *testing.T) { | ||
| @@ -131,7 +131,7 @@ func TestHelloEnvironmentCodec(t *testing.T) { | |||||
| 131 | } | 131 | } | ||
| 132 | got := MarshalHello(msg) | 132 | got := MarshalHello(msg) | ||
| 133 | want := []byte{ | 133 | want := []byte{ | ||
| 134 | - | 0x00, 0x1c, 0x00, // version, intent, first client ID byte | 134 | + | 0x00, 0x1d, 0x00, // version, intent, first client ID byte |
| 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 135 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // client ID | 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // client ID | ||
| 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // resume token | 137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // resume token | ||
| @@ -350,7 +350,7 @@ func TestThemeGenerationClearedWireGoldenPreservesProtocolVersion(t *testing.T) | |||||
| 350 | } | 350 | } | ||
| 351 | want := append([]byte{0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00}, make([]byte, 48)...) | 351 | want := append([]byte{0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00}, make([]byte, 48)...) | ||
| 352 | require.Equal(t, want, MarshalTheme(cleared)) | 352 | require.Equal(t, want, MarshalTheme(cleared)) | ||
| 353 | - | require.Equal(t, uint16(28), ProtocolVersion) | 353 | + | require.Equal(t, uint16(29), ProtocolVersion) |
| 354 | } | 354 | } | ||
| 355 | 355 | ||||
| 356 | func TestResizeGoldenAndRoundTrip(t *testing.T) { | 356 | func TestResizeGoldenAndRoundTrip(t *testing.T) { | ||
| @@ -511,7 +511,7 @@ func TestCommandRequestGoldenAndRoundTrip(t *testing.T) { | |||||
| 511 | name: "minimal", | 511 | name: "minimal", | ||
| 512 | msg: CommandRequest{Version: ProtocolVersion, Slug: "split-right"}, | 512 | msg: CommandRequest{Version: ProtocolVersion, Slug: "split-right"}, | ||
| 513 | want: []byte{ | 513 | want: []byte{ | ||
| 514 | - | 0x00, 0x1c, | 514 | + | 0x00, 0x1d, |
| 515 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | 515 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | ||
| 516 | 0x00, // attached | 516 | 0x00, // attached | ||
| 517 | 0x00, // self | 517 | 0x00, // self | ||
| @@ -534,7 +534,7 @@ func TestCommandRequestGoldenAndRoundTrip(t *testing.T) { | |||||
| 534 | JSON: true, | 534 | JSON: true, | ||
| 535 | }, | 535 | }, | ||
| 536 | want: []byte{ | 536 | want: []byte{ | ||
| 537 | - | 0x00, 0x1c, | 537 | + | 0x00, 0x1d, |
| 538 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | 538 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // request ID | ||
| 539 | 0x00, // attached | 539 | 0x00, // attached | ||
| 540 | 0x01, // self | 540 | 0x01, // self | ||
| @@ -963,7 +963,7 @@ func TestCommandCorrelationGoldenAndStrict(t *testing.T) { | |||||
| 963 | name: "attached", | 963 | name: "attached", | ||
| 964 | msg: CommandRequest{Version: ProtocolVersion, RequestID: 0x0102030405060708, Attached: true, Self: true, Slug: "split-right", Args: []string{"--vertical"}}, | 964 | msg: CommandRequest{Version: ProtocolVersion, RequestID: 0x0102030405060708, Attached: true, Self: true, Slug: "split-right", Args: []string{"--vertical"}}, | ||
| 965 | want: []byte{ | 965 | want: []byte{ | ||
| 966 | - | 0x00, 0x1c, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x01, | 966 | + | 0x00, 0x1d, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x01, |
| 967 | 0x00, 0x0b, 's', 'p', 'l', 'i', 't', '-', 'r', 'i', 'g', 'h', 't', | 967 | 0x00, 0x0b, 's', 'p', 'l', 'i', 't', '-', 'r', 'i', 'g', 'h', 't', | ||
| 968 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, '-', '-', 'v', 'e', 'r', 't', 'i', 'c', 'a', 'l', | 968 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, '-', '-', 'v', 'e', 'r', 't', 'i', 'c', 'a', 'l', | ||
| 969 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 969 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| @@ -973,7 +973,7 @@ func TestCommandCorrelationGoldenAndStrict(t *testing.T) { | |||||
| 973 | name: "control", | 973 | name: "control", | ||
| 974 | msg: CommandRequest{Version: ProtocolVersion, Slug: "ls"}, | 974 | msg: CommandRequest{Version: ProtocolVersion, Slug: "ls"}, | ||
| 975 | want: []byte{ | 975 | want: []byte{ | ||
| 976 | - | 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 976 | + | 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 977 | 0x00, 0x02, 'l', 's', 0x00, 0x00, | 977 | 0x00, 0x02, 'l', 's', 0x00, 0x00, | ||
| 978 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 978 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 979 | }, | 979 | }, | ||
M internal/usecase/client/client.go +16 -17
| @@ -181,6 +181,7 @@ type AttachRequest struct { | |||
| 181 | 181 | RemoteTarget *domain.RemoteSessionTarget | |
| 182 | 182 | ExactTarget *ports.ExactSessionTarget | |
| 183 | 183 | PreferredTabID domain.TabStableID | |
| 184 | + | RemoteOrigin string | |
| 184 | 185 | EnvironmentPolicy ports.EnvironmentPolicy | |
| 185 | 186 | NavigationCapabilities ports.NavigationCapabilities | |
| 186 | 187 | StartupOverlay ports.StartupOverlay | |
| @@ -616,7 +617,10 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||
| 616 | 617 | enterHomePicker() | |
| 617 | 618 | continue | |
| 618 | 619 | case ports.NavigationBack: | |
| 619 | - | if attemptRequest.StartupOverlay != ports.StartupOverlaySessionPicker || returnRoute == nil || attemptRequest.NavigationCapabilities&ports.NavigationCapabilityBack == 0 { | |
| 620 | + | // As with Home, the daemon accepted Back in Hello before sending | |
| 621 | + | // this action. The retained concrete route is the durable client | |
| 622 | + | // prerequisite; request metadata may have been rebased meanwhile. | |
| 623 | + | if returnRoute == nil { | |
| 620 | 624 | return errors.New("vev: stale return navigation action") | |
| 621 | 625 | } | |
| 622 | 626 | route := *returnRoute | |
| @@ -691,14 +695,11 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||
| 691 | 695 | restoreReturnRoute(route) | |
| 692 | 696 | continue | |
| 693 | 697 | } | |
| 694 | - | if routeNavigationPending && routeNavigationResumeFallback { | |
| 695 | - | var protocolErr *ProtocolError | |
| 696 | - | if errors.As(result.err, &protocolErr) && (protocolErr.Code == ports.ErrNoSuchSession || protocolErr.Code == ports.ErrNoSuchTarget) { | |
| 697 | - | attemptRequest.Intent = ports.IntentAttach | |
| 698 | - | resumeToken = 0 | |
| 699 | - | routeNavigationResumeFallback = false | |
| 700 | - | continue | |
| 701 | - | } | |
| 698 | + | if routeNavigationPending && routeNavigationResumeFallback && resumeNeedsExactAttach(result.err) { | |
| 699 | + | attemptRequest.Intent = ports.IntentAttach | |
| 700 | + | resumeToken = 0 | |
| 701 | + | routeNavigationResumeFallback = false | |
| 702 | + | continue | |
| 702 | 703 | } | |
| 703 | 704 | if routeNavigationPending && routeNavigationFallback != nil { | |
| 704 | 705 | if routeNavigationAction != nil { | |
| @@ -715,14 +716,11 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||
| 715 | 716 | restoreReturnRoute(route) | |
| 716 | 717 | continue | |
| 717 | 718 | } | |
| 718 | - | if returnResumeFallback { | |
| 719 | - | var protocolErr *ProtocolError | |
| 720 | - | if errors.As(result.err, &protocolErr) && (protocolErr.Code == ports.ErrNoSuchSession || protocolErr.Code == ports.ErrNoSuchTarget) { | |
| 721 | - | attemptRequest.Intent = ports.IntentAttach | |
| 722 | - | resumeToken = 0 | |
| 723 | - | returnResumeFallback = false | |
| 724 | - | continue | |
| 725 | - | } | |
| 719 | + | if returnResumeFallback && resumeNeedsExactAttach(result.err) { | |
| 720 | + | attemptRequest.Intent = ports.IntentAttach | |
| 721 | + | resumeToken = 0 | |
| 722 | + | returnResumeFallback = false | |
| 723 | + | continue | |
| 726 | 724 | } | |
| 727 | 725 | if returnNavigationPending && !returnResumeFallback && homeRoute != nil { | |
| 728 | 726 | returnRoute = nil | |
| @@ -1040,6 +1038,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||
| 1040 | 1038 | RemoteTarget: request.RemoteTarget, | |
| 1041 | 1039 | ExactTarget: exactTarget, | |
| 1042 | 1040 | PreferredTabID: request.PreferredTabID, | |
| 1041 | + | RemoteOrigin: request.RemoteOrigin, | |
| 1043 | 1042 | EnvironmentPolicy: request.EnvironmentPolicy, | |
| 1044 | 1043 | NavigationCapabilities: request.NavigationCapabilities, | |
| 1045 | 1044 | StartupOverlay: request.StartupOverlay, | |
| @@ -181,6 +181,7 @@ type AttachRequest struct { | |||||
| 181 | RemoteTarget *domain.RemoteSessionTarget | 181 | RemoteTarget *domain.RemoteSessionTarget | ||
| 182 | ExactTarget *ports.ExactSessionTarget | 182 | ExactTarget *ports.ExactSessionTarget | ||
| 183 | PreferredTabID domain.TabStableID | 183 | PreferredTabID domain.TabStableID | ||
| 184 | + | RemoteOrigin string | |||
| 184 | EnvironmentPolicy ports.EnvironmentPolicy | 185 | EnvironmentPolicy ports.EnvironmentPolicy | ||
| 185 | NavigationCapabilities ports.NavigationCapabilities | 186 | NavigationCapabilities ports.NavigationCapabilities | ||
| 186 | StartupOverlay ports.StartupOverlay | 187 | StartupOverlay ports.StartupOverlay | ||
| @@ -616,7 +617,10 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||||
| 616 | enterHomePicker() | 617 | enterHomePicker() | ||
| 617 | continue | 618 | continue | ||
| 618 | case ports.NavigationBack: | 619 | case ports.NavigationBack: | ||
| 619 | - | if attemptRequest.StartupOverlay != ports.StartupOverlaySessionPicker || returnRoute == nil || attemptRequest.NavigationCapabilities&ports.NavigationCapabilityBack == 0 { | 620 | + | // As with Home, the daemon accepted Back in Hello before sending |
| 621 | + | // this action. The retained concrete route is the durable client | |||
| 622 | + | // prerequisite; request metadata may have been rebased meanwhile. | |||
| 623 | + | if returnRoute == nil { | |||
| 620 | return errors.New("vev: stale return navigation action") | 624 | return errors.New("vev: stale return navigation action") | ||
| 621 | } | 625 | } | ||
| 622 | route := *returnRoute | 626 | route := *returnRoute | ||
| @@ -691,14 +695,11 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||||
| 691 | restoreReturnRoute(route) | 695 | restoreReturnRoute(route) | ||
| 692 | continue | 696 | continue | ||
| 693 | } | 697 | } | ||
| 694 | - | if routeNavigationPending && routeNavigationResumeFallback { | 698 | + | if routeNavigationPending && routeNavigationResumeFallback && resumeNeedsExactAttach(result.err) { |
| 695 | - | var protocolErr *ProtocolError | 699 | + | attemptRequest.Intent = ports.IntentAttach |
| 696 | - | if errors.As(result.err, &protocolErr) && (protocolErr.Code == ports.ErrNoSuchSession || protocolErr.Code == ports.ErrNoSuchTarget) { | 700 | + | resumeToken = 0 |
| 697 | - | attemptRequest.Intent = ports.IntentAttach | 701 | + | routeNavigationResumeFallback = false |
| 698 | - | resumeToken = 0 | 702 | + | continue |
| 699 | - | routeNavigationResumeFallback = false | |||
| 700 | - | continue | |||
| 701 | - | } | |||
| 702 | } | 703 | } | ||
| 703 | if routeNavigationPending && routeNavigationFallback != nil { | 704 | if routeNavigationPending && routeNavigationFallback != nil { | ||
| 704 | if routeNavigationAction != nil { | 705 | if routeNavigationAction != nil { | ||
| @@ -715,14 +716,11 @@ func (r *Runner) Run(ctx context.Context, request AttachRequest) (retErr error) | |||||
| 715 | restoreReturnRoute(route) | 716 | restoreReturnRoute(route) | ||
| 716 | continue | 717 | continue | ||
| 717 | } | 718 | } | ||
| 718 | - | if returnResumeFallback { | 719 | + | if returnResumeFallback && resumeNeedsExactAttach(result.err) { |
| 719 | - | var protocolErr *ProtocolError | 720 | + | attemptRequest.Intent = ports.IntentAttach |
| 720 | - | if errors.As(result.err, &protocolErr) && (protocolErr.Code == ports.ErrNoSuchSession || protocolErr.Code == ports.ErrNoSuchTarget) { | 721 | + | resumeToken = 0 |
| 721 | - | attemptRequest.Intent = ports.IntentAttach | 722 | + | returnResumeFallback = false |
| 722 | - | resumeToken = 0 | 723 | + | continue |
| 723 | - | returnResumeFallback = false | |||
| 724 | - | continue | |||
| 725 | - | } | |||
| 726 | } | 724 | } | ||
| 727 | if returnNavigationPending && !returnResumeFallback && homeRoute != nil { | 725 | if returnNavigationPending && !returnResumeFallback && homeRoute != nil { | ||
| 728 | returnRoute = nil | 726 | returnRoute = nil | ||
| @@ -1040,6 +1038,7 @@ func (a *attachAttempt) run(ctx context.Context) attachResult { | |||||
| 1040 | RemoteTarget: request.RemoteTarget, | 1038 | RemoteTarget: request.RemoteTarget, | ||
| 1041 | ExactTarget: exactTarget, | 1039 | ExactTarget: exactTarget, | ||
| 1042 | PreferredTabID: request.PreferredTabID, | 1040 | PreferredTabID: request.PreferredTabID, | ||
| 1041 | + | RemoteOrigin: request.RemoteOrigin, | |||
| 1043 | EnvironmentPolicy: request.EnvironmentPolicy, | 1042 | EnvironmentPolicy: request.EnvironmentPolicy, | ||
| 1044 | NavigationCapabilities: request.NavigationCapabilities, | 1043 | NavigationCapabilities: request.NavigationCapabilities, | ||
| 1045 | StartupOverlay: request.StartupOverlay, | 1044 | StartupOverlay: request.StartupOverlay, | ||
M internal/usecase/client/reconnect.go +8 -0
| @@ -49,6 +49,14 @@ func nextReconnectBackoff(cur, limit time.Duration) time.Duration { | |||
| 49 | 49 | return cur | |
| 50 | 50 | } | |
| 51 | 51 | ||
| 52 | + | func resumeNeedsExactAttach(err error) bool { | |
| 53 | + | if errors.Is(err, errRouteTargetChanged) { | |
| 54 | + | return true | |
| 55 | + | } | |
| 56 | + | protocolErr, ok := errors.AsType[*ProtocolError](err) | |
| 57 | + | return ok && (protocolErr.Code == ports.ErrNoSuchSession || protocolErr.Code == ports.ErrNoSuchTarget) | |
| 58 | + | } | |
| 59 | + | ||
| 52 | 60 | func shouldReconnect(err error) bool { | |
| 53 | 61 | if err == nil { | |
| 54 | 62 | return false | |
| @@ -49,6 +49,14 @@ func nextReconnectBackoff(cur, limit time.Duration) time.Duration { | |||||
| 49 | return cur | 49 | return cur | ||
| 50 | } | 50 | } | ||
| 51 | 51 | ||||
| 52 | + | func resumeNeedsExactAttach(err error) bool { | |||
| 53 | + | if errors.Is(err, errRouteTargetChanged) { | |||
| 54 | + | return true | |||
| 55 | + | } | |||
| 56 | + | protocolErr, ok := errors.AsType[*ProtocolError](err) | |||
| 57 | + | return ok && (protocolErr.Code == ports.ErrNoSuchSession || protocolErr.Code == ports.ErrNoSuchTarget) | |||
| 58 | + | } | |||
| 59 | + | ||||
| 52 | func shouldReconnect(err error) bool { | 60 | func shouldReconnect(err error) bool { | ||
| 53 | if err == nil { | 61 | if err == nil { | ||
| 54 | return false | 62 | return false | ||
M internal/usecase/client/reconnect_toast_test.go +18 -0
| @@ -354,6 +354,24 @@ func newReconnectAttachAttempt(term ports.Terminal, transport ports.Transport, c | |||
| 354 | 354 | } | |
| 355 | 355 | } | |
| 356 | 356 | ||
| 357 | + | func TestResumeNeedsExactAttach(t *testing.T) { | |
| 358 | + | for _, test := range []struct { | |
| 359 | + | name string | |
| 360 | + | err error | |
| 361 | + | want bool | |
| 362 | + | }{ | |
| 363 | + | {name: "moved resume token", err: errRouteTargetChanged, want: true}, | |
| 364 | + | {name: "missing target", err: &ProtocolError{Code: ports.ErrNoSuchTarget}, want: true}, | |
| 365 | + | {name: "missing session", err: &ProtocolError{Code: ports.ErrNoSuchSession}, want: true}, | |
| 366 | + | {name: "internal", err: &ProtocolError{Code: ports.ErrInternal}}, | |
| 367 | + | {name: "transport", err: io.EOF}, | |
| 368 | + | } { | |
| 369 | + | t.Run(test.name, func(t *testing.T) { | |
| 370 | + | require.Equal(t, test.want, resumeNeedsExactAttach(test.err)) | |
| 371 | + | }) | |
| 372 | + | } | |
| 373 | + | } | |
| 374 | + | ||
| 357 | 375 | func TestProbingToastReconcilesDaemonOutputAndDismissal(t *testing.T) { | |
| 358 | 376 | out := newReconnectToastOutputRecorder() | |
| 359 | 377 | var flushes atomic.Int32 | |
| @@ -354,6 +354,24 @@ func newReconnectAttachAttempt(term ports.Terminal, transport ports.Transport, c | |||||
| 354 | } | 354 | } | ||
| 355 | } | 355 | } | ||
| 356 | 356 | ||||
| 357 | + | func TestResumeNeedsExactAttach(t *testing.T) { | |||
| 358 | + | for _, test := range []struct { | |||
| 359 | + | name string | |||
| 360 | + | err error | |||
| 361 | + | want bool | |||
| 362 | + | }{ | |||
| 363 | + | {name: "moved resume token", err: errRouteTargetChanged, want: true}, | |||
| 364 | + | {name: "missing target", err: &ProtocolError{Code: ports.ErrNoSuchTarget}, want: true}, | |||
| 365 | + | {name: "missing session", err: &ProtocolError{Code: ports.ErrNoSuchSession}, want: true}, | |||
| 366 | + | {name: "internal", err: &ProtocolError{Code: ports.ErrInternal}}, | |||
| 367 | + | {name: "transport", err: io.EOF}, | |||
| 368 | + | } { | |||
| 369 | + | t.Run(test.name, func(t *testing.T) { | |||
| 370 | + | require.Equal(t, test.want, resumeNeedsExactAttach(test.err)) | |||
| 371 | + | }) | |||
| 372 | + | } | |||
| 373 | + | } | |||
| 374 | + | ||||
| 357 | func TestProbingToastReconcilesDaemonOutputAndDismissal(t *testing.T) { | 375 | func TestProbingToastReconcilesDaemonOutputAndDismissal(t *testing.T) { | ||
| 358 | out := newReconnectToastOutputRecorder() | 376 | out := newReconnectToastOutputRecorder() | ||
| 359 | var flushes atomic.Int32 | 377 | var flushes atomic.Int32 | ||
M internal/usecase/client/routes.go +1 -0
| @@ -132,6 +132,7 @@ func routeCandidateForAttach(request AttachRequest, identity ports.CommittedRout | |||
| 132 | 132 | request.OriginKey = originKey | |
| 133 | 133 | request.SessionName = identity.Target.SessionName | |
| 134 | 134 | request.ExactTarget = &identity.Target | |
| 135 | + | request.RemoteOrigin = hostLabel | |
| 135 | 136 | // Discovery targets are point-in-time attach authority, including a live | |
| 136 | 137 | // tab selector. Once Welcome commits an exact route, mutable tab memory is | |
| 137 | 138 | // carried independently by PreferredTabID. | |
| @@ -132,6 +132,7 @@ func routeCandidateForAttach(request AttachRequest, identity ports.CommittedRout | |||||
| 132 | request.OriginKey = originKey | 132 | request.OriginKey = originKey | ||
| 133 | request.SessionName = identity.Target.SessionName | 133 | request.SessionName = identity.Target.SessionName | ||
| 134 | request.ExactTarget = &identity.Target | 134 | request.ExactTarget = &identity.Target | ||
| 135 | + | request.RemoteOrigin = hostLabel | |||
| 135 | // Discovery targets are point-in-time attach authority, including a live | 136 | // Discovery targets are point-in-time attach authority, including a live | ||
| 136 | // tab selector. Once Welcome commits an exact route, mutable tab memory is | 137 | // tab selector. Once Welcome commits an exact route, mutable tab memory is | ||
| 137 | // carried independently by PreferredTabID. | 138 | // carried independently by PreferredTabID. | ||
M internal/usecase/client/routes_test.go +12 -0
| @@ -33,6 +33,18 @@ func routeTestCandidate(index byte, origin ports.RouteOrigin) routeCandidate { | |||
| 33 | 33 | } | |
| 34 | 34 | } | |
| 35 | 35 | ||
| 36 | + | func TestRouteCandidateRetainsRemoteOriginWithoutDiscoveryTarget(t *testing.T) { | |
| 37 | + | target := routeTestTarget(1) | |
| 38 | + | candidate := routeCandidateForAttach(AttachRequest{ | |
| 39 | + | Intent: ports.IntentAttach, SessionName: target.SessionName, Remote: true, | |
| 40 | + | Origin: ports.RouteOriginRemote, OriginKey: "remote", | |
| 41 | + | }, ports.CommittedRouteIdentity{Target: target}, nil, 0) | |
| 42 | + | ||
| 43 | + | require.Nil(t, candidate.request.RemoteTarget) | |
| 44 | + | require.Equal(t, "remote", candidate.request.RemoteOrigin) | |
| 45 | + | require.Equal(t, "remote", candidate.presentation.hostLabel) | |
| 46 | + | } | |
| 47 | + | ||
| 36 | 48 | func TestRouteLedgerBoundsAndImmutableSnapshot(t *testing.T) { | |
| 37 | 49 | ledger := newRouteLedger() | |
| 38 | 50 | var keys []routeKey | |
| @@ -33,6 +33,18 @@ func routeTestCandidate(index byte, origin ports.RouteOrigin) routeCandidate { | |||||
| 33 | } | 33 | } | ||
| 34 | } | 34 | } | ||
| 35 | 35 | ||||
| 36 | + | func TestRouteCandidateRetainsRemoteOriginWithoutDiscoveryTarget(t *testing.T) { | |||
| 37 | + | target := routeTestTarget(1) | |||
| 38 | + | candidate := routeCandidateForAttach(AttachRequest{ | |||
| 39 | + | Intent: ports.IntentAttach, SessionName: target.SessionName, Remote: true, | |||
| 40 | + | Origin: ports.RouteOriginRemote, OriginKey: "remote", | |||
| 41 | + | }, ports.CommittedRouteIdentity{Target: target}, nil, 0) | |||
| 42 | + | ||||
| 43 | + | require.Nil(t, candidate.request.RemoteTarget) | |||
| 44 | + | require.Equal(t, "remote", candidate.request.RemoteOrigin) | |||
| 45 | + | require.Equal(t, "remote", candidate.presentation.hostLabel) | |||
| 46 | + | } | |||
| 47 | + | ||||
| 36 | func TestRouteLedgerBoundsAndImmutableSnapshot(t *testing.T) { | 48 | func TestRouteLedgerBoundsAndImmutableSnapshot(t *testing.T) { | ||
| 37 | ledger := newRouteLedger() | 49 | ledger := newRouteLedger() | ||
| 38 | var keys []routeKey | 50 | var keys []routeKey | ||
M internal/usecase/daemon/daemon.go +2 -1
| @@ -1288,8 +1288,9 @@ func (d *Daemon) finishAttach(sess *session, tr ports.Transport, sz domain.Size, | |||
| 1288 | 1288 | maxOutputInFlight: normalizeOutputWindow(h.MaxOutputInFlight), | |
| 1289 | 1289 | navigationCapabilities: h.NavigationCapabilities, | |
| 1290 | 1290 | startupOverlay: h.StartupOverlay, | |
| 1291 | + | remoteOrigin: h.RemoteOrigin, | |
| 1291 | 1292 | } | |
| 1292 | - | if h.RemoteTarget != nil { | |
| 1293 | + | if opts.remoteOrigin == "" && h.RemoteTarget != nil { | |
| 1293 | 1294 | opts.remoteOrigin = h.RemoteTarget.DisplayOrigin | |
| 1294 | 1295 | } | |
| 1295 | 1296 | ac := d.prepareAttachedClientLocked(tr, sz, opts) | |
| @@ -1288,8 +1288,9 @@ func (d *Daemon) finishAttach(sess *session, tr ports.Transport, sz domain.Size, | |||||
| 1288 | maxOutputInFlight: normalizeOutputWindow(h.MaxOutputInFlight), | 1288 | maxOutputInFlight: normalizeOutputWindow(h.MaxOutputInFlight), | ||
| 1289 | navigationCapabilities: h.NavigationCapabilities, | 1289 | navigationCapabilities: h.NavigationCapabilities, | ||
| 1290 | startupOverlay: h.StartupOverlay, | 1290 | startupOverlay: h.StartupOverlay, | ||
| 1291 | + | remoteOrigin: h.RemoteOrigin, | |||
| 1291 | } | 1292 | } | ||
| 1292 | - | if h.RemoteTarget != nil { | 1293 | + | if opts.remoteOrigin == "" && h.RemoteTarget != nil { |
| 1293 | opts.remoteOrigin = h.RemoteTarget.DisplayOrigin | 1294 | opts.remoteOrigin = h.RemoteTarget.DisplayOrigin | ||
| 1294 | } | 1295 | } | ||
| 1295 | ac := d.prepareAttachedClientLocked(tr, sz, opts) | 1296 | ac := d.prepareAttachedClientLocked(tr, sz, opts) | ||
M internal/usecase/daemon/route_position_test.go +18 -0
| @@ -10,6 +10,24 @@ import ( | |||
| 10 | 10 | "github.com/bnema/vev/internal/ports" | |
| 11 | 11 | ) | |
| 12 | 12 | ||
| 13 | + | func TestRouteRetainsRemoteOriginWithoutDiscoveryTarget(t *testing.T) { | |
| 14 | + | d := newTestDaemon(t, nil, stubClock{}) | |
| 15 | + | sess := addControlSession(d, "work", "tab-1", "pane-1") | |
| 16 | + | sess.incarnation = domain.SessionLifecycleID{1} | |
| 17 | + | tr := &closeTrackingTransport{} | |
| 18 | + | target := ports.ExactSessionTarget{LifecycleID: sess.incarnation, SessionName: sess.name} | |
| 19 | + | hello := ports.Hello{ | |
| 20 | + | Version: ports.ProtocolVersion, Intent: ports.IntentAttach, Name: sess.name, | |
| 21 | + | Size: defaultSize, ExactTarget: &target, RemoteOrigin: "remote", | |
| 22 | + | EnvironmentPolicy: ports.EnvironmentPolicyClientOwned, | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | _, ac, err := d.routeWithContext(context.Background(), hello, tr) | |
| 26 | + | require.NoError(t, err) | |
| 27 | + | require.Equal(t, "remote", ac.remoteOrigin) | |
| 28 | + | d.clientGone(sess, ac, tr, false) | |
| 29 | + | } | |
| 30 | + | ||
| 13 | 31 | func TestRouteAppliesPreferredTabPerAttachment(t *testing.T) { | |
| 14 | 32 | for _, test := range []struct { | |
| 15 | 33 | name string | |
| @@ -10,6 +10,24 @@ import ( | |||||
| 10 | "github.com/bnema/vev/internal/ports" | 10 | "github.com/bnema/vev/internal/ports" | ||
| 11 | ) | 11 | ) | ||
| 12 | 12 | ||||
| 13 | + | func TestRouteRetainsRemoteOriginWithoutDiscoveryTarget(t *testing.T) { | |||
| 14 | + | d := newTestDaemon(t, nil, stubClock{}) | |||
| 15 | + | sess := addControlSession(d, "work", "tab-1", "pane-1") | |||
| 16 | + | sess.incarnation = domain.SessionLifecycleID{1} | |||
| 17 | + | tr := &closeTrackingTransport{} | |||
| 18 | + | target := ports.ExactSessionTarget{LifecycleID: sess.incarnation, SessionName: sess.name} | |||
| 19 | + | hello := ports.Hello{ | |||
| 20 | + | Version: ports.ProtocolVersion, Intent: ports.IntentAttach, Name: sess.name, | |||
| 21 | + | Size: defaultSize, ExactTarget: &target, RemoteOrigin: "remote", | |||
| 22 | + | EnvironmentPolicy: ports.EnvironmentPolicyClientOwned, | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | _, ac, err := d.routeWithContext(context.Background(), hello, tr) | |||
| 26 | + | require.NoError(t, err) | |||
| 27 | + | require.Equal(t, "remote", ac.remoteOrigin) | |||
| 28 | + | d.clientGone(sess, ac, tr, false) | |||
| 29 | + | } | |||
| 30 | + | ||||
| 13 | func TestRouteAppliesPreferredTabPerAttachment(t *testing.T) { | 31 | func TestRouteAppliesPreferredTabPerAttachment(t *testing.T) { | ||
| 14 | for _, test := range []struct { | 32 | for _, test := range []struct { | ||
| 15 | name string | 33 | name string | ||