@@ -13,25 +13,34 @@ import {
1313} from "./actions-shared.js"
1414import { loadSelectedProjectTasks } from "./actions-tasks.js"
1515import {
16+ type ApiEvent ,
1617 applyAllProjects ,
1718 applyProject ,
18- type ApiEvent ,
1919 deleteProject ,
2020 downAllProjects ,
2121 downProject ,
2222 loadProjectDetails ,
2323 loadProjectLogs ,
2424 loadProjectPs ,
2525 loadProjectTerminalSession ,
26- startProjectTerminalSession
26+ startProjectTerminalSession ,
27+ type TerminalSession
2728} from "./api.js"
2829import type { BrowserMenuTag } from "./menu.js"
30+ import { readEventPayloadString } from "./project-event-payload.js"
2931import { openProjectEventStream } from "./project-events.js"
3032import { outputScreen } from "./screen.js"
3133import { buildPendingProjectActiveTerminalSession , buildProjectActiveTerminalSession } from "./terminal.js"
3234
3335export { submitCreateInputs } from "./actions-project-create.js"
3436
37+ type BrowserRandomSource = {
38+ readonly getRandomValues : ( values : Uint8Array ) => Uint8Array
39+ readonly randomUUID ?: ( ) => string
40+ }
41+
42+ const browserRandomSource = ( ) : BrowserRandomSource => globalThis . crypto
43+
3544export const loadSelectedProjectInfo = (
3645 context : BrowserActionContext ,
3746 options ?: {
@@ -79,43 +88,20 @@ const resolveProjectTerminalKey = (
7988}
8089
8190const randomHex = ( bytes : number ) : string => {
82- const getRandomValues = globalThis . crypto ?. getRandomValues
83- if ( typeof getRandomValues === "function" ) {
84- const values = new Uint8Array ( bytes )
85- getRandomValues . call ( globalThis . crypto , values )
86- return Array . from ( values , ( value ) => value . toString ( 16 ) . padStart ( 2 , "0" ) ) . join ( "" )
87- }
88-
89- let fallback = ""
90- while ( fallback . length < bytes * 2 ) {
91- fallback += Math . floor ( Math . random ( ) * 0x1_0000_0000 )
92- . toString ( 16 )
93- . padStart ( 8 , "0" )
94- }
95- return fallback . slice ( 0 , bytes * 2 )
91+ const values = new Uint8Array ( bytes )
92+ browserRandomSource ( ) . getRandomValues ( values )
93+ return Array . from ( values , ( value ) => value . toString ( 16 ) . padStart ( 2 , "0" ) ) . join ( "" )
9694}
9795
9896const createPendingTerminalSessionId = ( ) : string => {
99- const randomUUID = globalThis . crypto ?. randomUUID
100- if ( typeof randomUUID === "function" ) {
101- return randomUUID . call ( globalThis . crypto )
97+ const source = browserRandomSource ( )
98+ if ( source . randomUUID !== undefined ) {
99+ return source . randomUUID ( )
102100 }
103101
104102 return `pending-${ Date . now ( ) . toString ( 16 ) } -${ randomHex ( 8 ) } `
105103}
106104
107- const readEventPayloadString = (
108- event : ApiEvent ,
109- key : string
110- ) : string | null => {
111- const payload = event . payload
112- if ( payload === null || typeof payload !== "object" || Array . isArray ( payload ) ) {
113- return null
114- }
115- const value = Object . entries ( payload ) . find ( ( [ name ] ) => name === key ) ?. [ 1 ]
116- return typeof value === "string" ? value : null
117- }
118-
119105const readTerminalSessionCreatedId = (
120106 event : ApiEvent ,
121107 requestId : string
@@ -148,6 +134,31 @@ const readTerminalStartupFailure = (
148134 return readEventPayloadString ( event , "message" ) ?? "SSH session startup failed."
149135}
150136
137+ const addAttachedProjectTerminalSession = (
138+ {
139+ context,
140+ projectDisplayName,
141+ projectId,
142+ projectKey,
143+ session
144+ } : {
145+ readonly context : BrowserActionContext
146+ readonly projectDisplayName : string
147+ readonly projectId : string
148+ readonly projectKey : string
149+ readonly session : TerminalSession
150+ }
151+ ) : void => {
152+ context . addTerminalSession ( buildProjectActiveTerminalSession ( {
153+ onExit : context . reloadDashboard ,
154+ onReady : context . reloadDashboard ,
155+ projectDisplayName,
156+ projectId,
157+ projectKey,
158+ session
159+ } ) )
160+ }
161+
151162export const connectProjectById = (
152163 projectId : string ,
153164 context : BrowserActionContext ,
@@ -188,6 +199,11 @@ export const connectProjectById = (
188199 stream ?. close ( )
189200 stream = null
190201 }
202+ const failPendingTerminalSession = ( error : string ) => {
203+ pendingSessionFinalized = true
204+ appendOutputLine ( context , `[error] ${ error } ` )
205+ context . addTerminalSession ( renderPendingTerminalSession ( error , "error" ) )
206+ }
191207 const attachCreatedSession = ( sessionId : string ) => {
192208 if ( attachedSessionId !== null ) {
193209 return
@@ -198,23 +214,20 @@ export const connectProjectById = (
198214 effect : loadProjectTerminalSession ( resolvedProjectKey , sessionId ) ,
199215 label : "Attaching SSH terminal" ,
200216 onFailure : ( error ) => {
201- pendingSessionFinalized = true
202- appendOutputLine ( context , `[error] ${ error } ` )
203- context . addTerminalSession ( renderPendingTerminalSession ( error , "error" ) )
217+ failPendingTerminalSession ( error )
204218 closeStream ( )
205219 } ,
206220 onSuccess : ( session ) => {
207221 pendingSessionFinalized = true
208222 context . reloadDashboard ( )
209223 context . closeTerminalSession ( pendingSessionId )
210- context . addTerminalSession ( buildProjectActiveTerminalSession ( {
211- onExit : context . reloadDashboard ,
212- onReady : context . reloadDashboard ,
224+ addAttachedProjectTerminalSession ( {
225+ context,
213226 projectDisplayName,
214227 projectId,
215228 projectKey : resolvedProjectKey ,
216229 session
217- } ) )
230+ } )
218231 context . setMessage ( `Project is ready. SSH terminal is connecting for ${ projectDisplayName } .` )
219232 closeStream ( )
220233 }
@@ -225,9 +238,7 @@ export const connectProjectById = (
225238 effect : startProjectTerminalSession ( resolvedProjectKey , pendingSessionId ) ,
226239 label : "Opening SSH terminal" ,
227240 onFailure : ( error ) => {
228- pendingSessionFinalized = true
229- appendOutputLine ( context , `[error] ${ error } ` )
230- context . addTerminalSession ( renderPendingTerminalSession ( error , "error" ) )
241+ failPendingTerminalSession ( error )
231242 } ,
232243 onSuccess : ( accepted ) => {
233244 appendOutputLine ( context , `[ssh.prepare] SSH terminal request accepted (${ accepted . requestId } )` )
@@ -237,9 +248,7 @@ export const connectProjectById = (
237248 onEvent : ( event ) => {
238249 const failure = readTerminalStartupFailure ( event , accepted . requestId )
239250 if ( failure !== null ) {
240- pendingSessionFinalized = true
241- appendOutputLine ( context , `[error] ${ failure } ` )
242- context . addTerminalSession ( renderPendingTerminalSession ( failure , "error" ) )
251+ failPendingTerminalSession ( failure )
243252 context . setMessage ( failure )
244253 closeStream ( )
245254 return
@@ -306,14 +315,13 @@ export const attachProjectTerminalById = (
306315 effect : loadProjectTerminalSession ( resolvedProjectKey , sessionId ) ,
307316 label : "Attaching SSH terminal" ,
308317 onSuccess : ( session ) => {
309- context . addTerminalSession ( buildProjectActiveTerminalSession ( {
310- onExit : context . reloadDashboard ,
311- onReady : context . reloadDashboard ,
318+ addAttachedProjectTerminalSession ( {
319+ context,
312320 projectDisplayName,
313321 projectId,
314322 projectKey : resolvedProjectKey ,
315323 session
316- } ) )
324+ } )
317325 context . setMessage ( `Attached SSH terminal for ${ projectDisplayName } .` )
318326 }
319327 } )
0 commit comments