@@ -180,6 +180,7 @@ export function resolveGatewayClientConnectChallengeTimeoutMs(
180180
181181const FORCE_STOP_TERMINATE_GRACE_MS = 250 ;
182182const STOP_AND_WAIT_TIMEOUT_MS = 1_000 ;
183+ const MAX_TRANSIENT_PREHELLO_NORMAL_CLOSES = 3 ;
183184
184185type PendingStop = {
185186 ws : WebSocket ;
@@ -202,7 +203,9 @@ export class GatewayClient {
202203 private deviceTokenRetryBudgetUsed = false ;
203204 private pendingConnectErrorDetailCode : string | null = null ;
204205 private helloOkReceived = false ;
205- private suppressNextConnectError = false ;
206+ private connectAttempt = 0 ;
207+ private suppressConnectErrorAttempts = new Set < number > ( ) ;
208+ private transientPrehelloNormalCloseCount = 0 ;
206209 // Track last tick to detect silent stalls.
207210 private lastTick : number | null = null ;
208211 private tickIntervalMs = 30_000 ;
@@ -313,9 +316,19 @@ export class GatewayClient {
313316 ws . on ( "message" , ( data ) => this . handleMessage ( rawDataToString ( data ) ) ) ;
314317 ws . on ( "close" , ( code , reason ) => {
315318 const reasonText = rawDataToString ( reason ) ;
316- const isTransientPrehelloNormalClose =
319+ const isPrehelloNormalCloseCandidate =
317320 ! this . closed && ! this . helloOkReceived && code === 1000 && reasonText . trim ( ) . length === 0 ;
318- this . suppressNextConnectError = isTransientPrehelloNormalClose ;
321+ if ( isPrehelloNormalCloseCandidate ) {
322+ this . transientPrehelloNormalCloseCount += 1 ;
323+ } else {
324+ this . transientPrehelloNormalCloseCount = 0 ;
325+ }
326+ const isTransientPrehelloNormalClose =
327+ isPrehelloNormalCloseCandidate &&
328+ this . transientPrehelloNormalCloseCount <= MAX_TRANSIENT_PREHELLO_NORMAL_CLOSES ;
329+ if ( isTransientPrehelloNormalClose && this . connectSent ) {
330+ this . suppressConnectErrorAttempts . add ( this . connectAttempt ) ;
331+ }
319332
320333 const connectErrorDetailCode = this . pendingConnectErrorDetailCode ;
321334 this . pendingConnectErrorDetailCode = null ;
@@ -405,6 +418,8 @@ export class GatewayClient {
405418
406419 private beginStop ( ) : Promise < void > | null {
407420 this . closed = true ;
421+ this . suppressConnectErrorAttempts . clear ( ) ;
422+ this . transientPrehelloNormalCloseCount = 0 ;
408423 this . pendingDeviceTokenRetry = false ;
409424 this . deviceTokenRetryBudgetUsed = false ;
410425 this . pendingConnectErrorDetailCode = null ;
@@ -550,10 +565,13 @@ export class GatewayClient {
550565 device,
551566 } ;
552567
568+ const connectAttempt = this . connectAttempt ;
569+
553570 void this . request < HelloOk > ( "connect" , params )
554571 . then ( ( helloOk ) => {
555572 this . helloOkReceived = true ;
556- this . suppressNextConnectError = false ;
573+ this . suppressConnectErrorAttempts . clear ( ) ;
574+ this . transientPrehelloNormalCloseCount = 0 ;
557575 this . pendingDeviceTokenRetry = false ;
558576 this . deviceTokenRetryBudgetUsed = false ;
559577 this . pendingConnectErrorDetailCode = null ;
@@ -576,8 +594,7 @@ export class GatewayClient {
576594 this . opts . onHelloOk ?.( helloOk ) ;
577595 } )
578596 . catch ( ( err ) => {
579- if ( this . suppressNextConnectError ) {
580- this . suppressNextConnectError = false ;
597+ if ( this . suppressConnectErrorAttempts . delete ( connectAttempt ) ) {
581598 return ;
582599 }
583600 this . pendingConnectErrorDetailCode =
@@ -830,8 +847,8 @@ export class GatewayClient {
830847 }
831848
832849 private beginPreauthHandshake ( ) {
850+ this . connectAttempt += 1 ;
833851 this . helloOkReceived = false ;
834- this . suppressNextConnectError = false ;
835852 if ( this . connectSent ) {
836853 return ;
837854 }
0 commit comments