Skip to content

Commit 8a62cd4

Browse files
committed
fix(web): satisfy ssh terminal lint and test diagnostics
Replace optional-chain access on globalThis.crypto with a typed cast so @typescript-eslint/no-unnecessary-condition stops flagging the SSH terminal id helpers. Drop the Math.random fallback in favor of a counter-based id to avoid sonarjs/pseudo-random while still producing unique placeholders when the Web Crypto API is missing. Type the hoisted vitest mocks for openProjectEventStream and startProjectTerminalSession so the test no longer triggers the @typescript-eslint/no-unsafe-* rules, and adjust the deterministic test to assert the new pending session id format.
1 parent 2db233c commit 8a62cd4

3 files changed

Lines changed: 48 additions & 28 deletions

File tree

‎packages/app/src/web/actions-projects.ts‎

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import {
1313
}from"./actions-shared.js"
1414
import{loadSelectedProjectTasks}from"./actions-tasks.js"
1515
import{
16+
typeApiEvent,
1617
applyAllProjects,
1718
applyProject,
18-
typeApiEvent,
1919
deleteProject,
2020
downAllProjects,
2121
downProject,
@@ -78,30 +78,30 @@ const resolveProjectTerminalKey = (
7878
returnnull
7979
}
8080

81-
constrandomHex=(bytes: number): string=>{
82-
constgetRandomValues=globalThis.crypto?.getRandomValues
83-
if(typeofgetRandomValues==="function"){
84-
constvalues=newUint8Array(bytes)
85-
getRandomValues.call(globalThis.crypto,values)
86-
returnArray.from(values,(value)=>value.toString(16).padStart(2,"0")).join("")
87-
}
81+
letpendingTerminalSessionCounter=0
8882

89-
letfallback=""
90-
while(fallback.length<bytes*2){
91-
fallback+=Math.floor(Math.random()*0x1_0000_0000)
92-
.toString(16)
93-
.padStart(8,"0")
83+
constcryptoRandomHex=(bytes: number): string|null=>{
84+
constcryptoApi=globalThis.cryptoasCrypto|undefined
85+
if(cryptoApi===undefined||typeofcryptoApi.getRandomValues!=="function"){
86+
returnnull
9487
}
95-
returnfallback.slice(0,bytes*2)
88+
constvalues=newUint8Array(bytes)
89+
cryptoApi.getRandomValues(values)
90+
returnArray.from(values,(value)=>value.toString(16).padStart(2,"0")).join("")
9691
}
9792

9893
constcreatePendingTerminalSessionId=(): string=>{
99-
constrandomUUID=globalThis.crypto?.randomUUID
100-
if(typeofrandomUUID==="function"){
101-
returnrandomUUID.call(globalThis.crypto)
94+
constcryptoApi=globalThis.cryptoasCrypto|undefined
95+
if(cryptoApi!==undefined&&typeofcryptoApi.randomUUID==="function"){
96+
returncryptoApi.randomUUID()
10297
}
103-
104-
return`pending-${Date.now().toString(16)}-${randomHex(8)}`
98+
constrandomSuffix=cryptoRandomHex(8)
99+
if(randomSuffix!==null){
100+
return`pending-${Date.now().toString(16)}-${randomSuffix}`
101+
}
102+
pendingTerminalSessionCounter=(pendingTerminalSessionCounter+1)>>>0
103+
constcounterSuffix=pendingTerminalSessionCounter.toString(16).padStart(8,"0")
104+
return`pending-${Date.now().toString(16)}-${counterSuffix}`
105105
}
106106

107107
constreadEventPayloadString=(

‎packages/app/src/web/api-types.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ import type {
2323
exporttypeProjectSummary=Schema.Schema.Type<typeofProjectSummarySchema>
2424
exporttypeProjectDetails=Schema.Schema.Type<typeofProjectDetailsSchema>
2525
exporttypeCreateProjectAcceptedResponse=Schema.Schema.Type<typeofCreateProjectAcceptedResponseSchema>
26-
exporttypeStartProjectTerminalSessionAccepted=
27-
Schema.Schema.Type<typeofStartProjectTerminalSessionAcceptedResponseSchema>
26+
exporttypeStartProjectTerminalSessionAccepted=Schema.Schema.Type<
27+
typeofStartProjectTerminalSessionAcceptedResponseSchema
28+
>
2829
exporttypeProjectPortForward=Schema.Schema.Type<typeofProjectPortForwardSchema>
2930
exporttypeProjectBrowserSession=Schema.Schema.Type<typeofProjectBrowserSessionSchema>
3031
exporttypeProjectDatabaseForward=Schema.Schema.Type<typeofProjectDatabaseForwardSchema>

‎packages/app/tests/docker-git/actions-projects.test.ts‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,36 @@ import { Effect } from "effect"
33
import{afterEach,beforeEach,vi}from"vitest"
44

55
import{applyProjectById,connectProjectById,runApplyAllProjects}from"../../src/web/actions-projects.js"
6-
importtype{ProjectDetails,StartProjectTerminalSessionAccepted,TerminalSession}from"../../src/web/api.js"
6+
importtype{
7+
ApiEvent,
8+
ProjectDetails,
9+
StartProjectTerminalSessionAccepted,
10+
TerminalSession
11+
}from"../../src/web/api.js"
712
importtype{ActiveTerminalSession}from"../../src/web/terminal.js"
813
import{makeBrowserActionContext,waitForAssertion}from"./browser-action-context-fixture.js"
914

15+
typeEventStreamHandlersFixture={
16+
readonlyonLine: (line: string)=>void
17+
readonlyonEvent?: (event: ApiEvent)=>void
18+
readonlyonRateLimit: ()=>void
19+
readonlyinitialCursor?: number|undefined
20+
}
21+
22+
typeEventStreamControlsFixture={
23+
readonlyclose: ()=>void
24+
}
25+
1026
constapplyAllProjectsMock=vi.hoisted(()=>vi.fn())
1127
constapplyProjectMock=vi.hoisted(()=>vi.fn())
1228
consteventStreamCloseMock=vi.hoisted(()=>vi.fn())
1329
constloadProjectTerminalSessionMock=vi.hoisted(()=>vi.fn())
14-
constopenProjectEventStreamMock=vi.hoisted(()=>vi.fn())
15-
conststartProjectTerminalSessionMock=vi.hoisted(()=>vi.fn())
30+
constopenProjectEventStreamMock=vi.hoisted(()=>
31+
vi.fn<(projectId: string,handlers: EventStreamHandlersFixture)=>EventStreamControlsFixture>()
32+
)
33+
conststartProjectTerminalSessionMock=vi.hoisted(()=>
34+
vi.fn<(projectKey: string,requestId: string)=>Effect.Effect<StartProjectTerminalSessionAccepted>>()
35+
)
1636

1737
vi.mock("../../src/web/api.js",()=>({
1838
applyAllProjects: applyAllProjectsMock,
@@ -194,8 +214,7 @@ describe("web project actions", () => {
194214

195215
it.effect("starts SSH terminal creation when randomUUID is unavailable",()=>
196216
Effect.gen(function*(_){
197-
constdateNowMock=vi.spyOn(Date,"now").mockReturnValue(0x1234)
198-
constmathRandomMock=vi.spyOn(Math,"random").mockReturnValue(0.5)
217+
constdateNowMock=vi.spyOn(Date,"now").mockReturnValue(0x12_34)
199218
vi.stubGlobal("crypto",{})
200219
startProjectTerminalSessionMock.mockImplementation((_projectKey,requestId: string)=>
201220
Effect.succeed(startTerminalAccepted(requestId))
@@ -215,10 +234,10 @@ describe("web project actions", () => {
215234
}))
216235

217236
constrequestId=startProjectTerminalSessionMock.mock.calls[0]?.[1]
218-
expect(requestId).toBe("pending-1234-8000000080000000")
237+
expect(typeofrequestId).toBe("string")
238+
expect(requestId).toMatch(/^pending-1234-[0-9a-f]{8}$/)
219239
expect(addTerminalSession).toHaveBeenCalledTimes(1)
220240
expect(openProjectEventStreamMock).toHaveBeenCalledTimes(1)
221-
mathRandomMock.mockRestore()
222241
dateNowMock.mockRestore()
223242
}))
224243

0 commit comments

Comments
 (0)