@@ -5,14 +5,34 @@ import type { BrowserActionContext } from "./actions-shared.js"
55import type { DashboardData } from "./api.js"
66import { browserMenuIndex } from "./menu.js"
77import { projectPickerScreen } from "./screen.js"
8+ import { reusableProjectTerminalSessionId } from "./terminal-state.js"
9+ import type { ActiveTerminalSession } from "./terminal.js"
810
911type SshLinkArgs = {
1012 readonly actionContext : BrowserActionContext
13+ readonly activeTerminalSessionId : string | null
1114 readonly busyLabel : string | null
1215 readonly dashboard : DashboardData
16+ readonly selectTerminalSession : ( sessionId : string ) => void
17+ readonly terminalSessions : ReadonlyArray < ActiveTerminalSession >
1318}
1419
1520const sshPathPrefix = "/ssh/"
21+ type ConnectTimerRef = { current : ReturnType < typeof globalThis . setTimeout > | null }
22+ type SshTokenRef = { current : string | null }
23+ type DashboardProject = DashboardData [ "projects" ] [ number ]
24+ type SshLinkEffectArgs = Omit < SshLinkArgs , "dashboard" > & {
25+ readonly connectTimerRef : ConnectTimerRef
26+ readonly handledTokenRef : SshTokenRef
27+ readonly projects : DashboardData [ "projects" ]
28+ }
29+
30+ const clearConnectTimer = ( connectTimerRef : ConnectTimerRef ) : void => {
31+ if ( connectTimerRef . current !== null ) {
32+ globalThis . clearTimeout ( connectTimerRef . current )
33+ connectTimerRef . current = null
34+ }
35+ }
1636
1737const readSshLinkToken = ( ) : string | null => {
1838 const url = new URL ( globalThis . location . href )
@@ -25,48 +45,99 @@ const readSshLinkToken = (): string | null => {
2545 return queryToken . length === 0 ? null : queryToken
2646}
2747
28- export const useSshLink = ( { actionContext, busyLabel, dashboard } : SshLinkArgs ) => {
48+ const findProjectBySshToken = (
49+ projects : DashboardData [ "projects" ] ,
50+ token : string
51+ ) : DashboardProject | undefined =>
52+ projects . find ( ( candidate ) => candidate . projectKey === token || candidate . id === token )
53+
54+ const showProjectTerminalScreen = ( actionContext : BrowserActionContext , projectId : string ) : void => {
55+ actionContext . setSelectedMenuIndex ( browserMenuIndex ( "Select" ) )
56+ actionContext . setActiveScreen ( projectPickerScreen ( ) )
57+ actionContext . setSelectedProjectId ( projectId )
58+ }
59+
60+ const selectReusableProjectTerminal = ( args : SshLinkEffectArgs , project : DashboardProject ) : boolean => {
61+ const reusableSessionId = reusableProjectTerminalSessionId (
62+ args . terminalSessions ,
63+ args . activeTerminalSessionId ,
64+ project . id
65+ )
66+ if ( reusableSessionId === null ) {
67+ return false
68+ }
69+ clearConnectTimer ( args . connectTimerRef )
70+ args . selectTerminalSession ( reusableSessionId )
71+ args . actionContext . setMessage ( `Opened existing SSH terminal for ${ project . displayName } .` )
72+ return true
73+ }
74+
75+ const scheduleProjectTerminalConnect = ( args : SshLinkEffectArgs , projectId : string ) : void => {
76+ clearConnectTimer ( args . connectTimerRef )
77+ args . connectTimerRef . current = globalThis . setTimeout ( ( ) => {
78+ args . connectTimerRef . current = null
79+ connectProjectById ( projectId , args . actionContext )
80+ } , 0 )
81+ }
82+
83+ const handleSshLinkEffect = ( args : SshLinkEffectArgs ) : void => {
84+ const token = readSshLinkToken ( )
85+ if ( token === null ) {
86+ clearConnectTimer ( args . connectTimerRef )
87+ args . handledTokenRef . current = null
88+ return
89+ }
90+ if ( args . busyLabel !== null || args . handledTokenRef . current === token ) {
91+ return
92+ }
93+
94+ const project = findProjectBySshToken ( args . projects , token )
95+ if ( project === undefined ) {
96+ args . actionContext . setMessage ( `Project link was not found: ${ token } .` )
97+ return
98+ }
99+
100+ args . handledTokenRef . current = token
101+ showProjectTerminalScreen ( args . actionContext , project . id )
102+ if ( ! selectReusableProjectTerminal ( args , project ) ) {
103+ scheduleProjectTerminalConnect ( args , project . id )
104+ }
105+ }
106+
107+ export const useSshLink = ( {
108+ actionContext,
109+ activeTerminalSessionId,
110+ busyLabel,
111+ dashboard,
112+ selectTerminalSession,
113+ terminalSessions
114+ } : SshLinkArgs ) => {
29115 const connectTimerRef = useRef < ReturnType < typeof globalThis . setTimeout > | null > ( null )
30116 const handledTokenRef = useRef < string | null > ( null )
31117 const locationSignature = `${ globalThis . location . pathname } ${ globalThis . location . search } `
32118
33119 useEffect ( ( ) => ( ) => {
34- if ( connectTimerRef . current !== null ) {
35- globalThis . clearTimeout ( connectTimerRef . current )
36- connectTimerRef . current = null
37- }
120+ clearConnectTimer ( connectTimerRef )
38121 } , [ ] )
39122
40123 useEffect ( ( ) => {
41- const token = readSshLinkToken ( )
42- if ( token === null ) {
43- if ( connectTimerRef . current !== null ) {
44- globalThis . clearTimeout ( connectTimerRef . current )
45- connectTimerRef . current = null
46- }
47- handledTokenRef . current = null
48- return
49- }
50- if ( busyLabel !== null || handledTokenRef . current === token ) {
51- return
52- }
53-
54- const project = dashboard . projects . find ( ( candidate ) => candidate . projectKey === token || candidate . id === token )
55- if ( project === undefined ) {
56- actionContext . setMessage ( `Project link was not found: ${ token } .` )
57- return
58- }
59-
60- handledTokenRef . current = token
61- actionContext . setSelectedMenuIndex ( browserMenuIndex ( "Select" ) )
62- actionContext . setActiveScreen ( projectPickerScreen ( ) )
63- actionContext . setSelectedProjectId ( project . id )
64- if ( connectTimerRef . current !== null ) {
65- globalThis . clearTimeout ( connectTimerRef . current )
66- }
67- connectTimerRef . current = globalThis . setTimeout ( ( ) => {
68- connectTimerRef . current = null
69- connectProjectById ( project . id , actionContext )
70- } , 0 )
71- } , [ actionContext , busyLabel , dashboard . projects , locationSignature ] )
124+ handleSshLinkEffect ( {
125+ actionContext,
126+ activeTerminalSessionId,
127+ busyLabel,
128+ connectTimerRef,
129+ handledTokenRef,
130+ projects : dashboard . projects ,
131+ selectTerminalSession,
132+ terminalSessions
133+ } )
134+ } , [
135+ actionContext ,
136+ activeTerminalSessionId ,
137+ busyLabel ,
138+ dashboard . projects ,
139+ locationSignature ,
140+ selectTerminalSession ,
141+ terminalSessions
142+ ] )
72143}
0 commit comments