@@ -31,6 +31,16 @@ import { forceSave } from './forcesave.js';
3131import { createCommentsController } from './plugins/comments/helpers/controller.js' ;
3232import { createCommentsStore } from './plugins/comments/helpers/comments-store.js' ;
3333
34+ // Rapid-reconnect guard (COR-44): y-websocket resets its backoff counter on
35+ // every successful onopen, so a close that follows a brief successful
36+ // handshake reschedules at 100ms. Single users behind corporate proxies can
37+ // sustain thousands of WS upgrades/sec/IP through this path. The guard below
38+ // detects open-then-close cycles shorter than MIN_HEALTHY_SESSION_MS and
39+ // applies a manual exponential backoff before letting the provider reconnect.
40+ const MIN_HEALTHY_SESSION_MS = 5000 ;
41+ const SHORT_SESSION_BASE_MS = 1000 ;
42+ const SHORT_SESSION_MAX_MS = 30000 ;
43+
3444async function checkDoc ( path ) {
3545 return daFetch ( path , { method : 'HEAD' } ) ;
3646}
@@ -58,6 +68,13 @@ export async function createConnection(path) {
5868 provider . maxBackoffTime = 30000 ;
5969
6070 let lastSentToken = token || null ;
71+ let lastOpenAt = 0 ;
72+ let failedShortSessions = 0 ;
73+
74+ provider . on ( 'status' , ( st ) => {
75+ if ( st ?. status === 'connected' ) lastOpenAt = Date . now ( ) ;
76+ } ) ;
77+
6178 provider . on ( 'connection-close' , async ( event ) => {
6279 if ( event ?. code === 4401 || event ?. code === 4403 ) {
6380 provider . shouldConnect = false ;
@@ -80,6 +97,29 @@ export async function createConnection(path) {
8097 provider . connect ( ) ;
8198 return ;
8299 }
100+
101+ // Non-auth close: rapid-reconnect guard. y-websocket's own 100ms
102+ // setTimeout(setupWS) still fires from onclose, but provider.disconnect()
103+ // flips shouldConnect=false so that timer's setupWS call no-ops. The
104+ // manual setTimeout below is what re-arms the connection.
105+ const sessionMs = lastOpenAt ? Date . now ( ) - lastOpenAt : 0 ;
106+ lastOpenAt = 0 ;
107+ if ( sessionMs >= MIN_HEALTHY_SESSION_MS ) {
108+ failedShortSessions = 0 ;
109+ } else {
110+ const delay = Math . min (
111+ 2 ** failedShortSessions * SHORT_SESSION_BASE_MS ,
112+ SHORT_SESSION_MAX_MS ,
113+ ) ;
114+ failedShortSessions += 1 ;
115+ provider . shouldConnect = false ;
116+ provider . disconnect ( ) ;
117+ setTimeout ( ( ) => {
118+ provider . shouldConnect = true ;
119+ provider . connect ( ) ;
120+ } , delay ) ;
121+ }
122+
83123 const fresh = await getAuthToken ( ) ;
84124 provider . protocols = fresh ? [ 'yjs' , fresh ] : [ 'yjs' ] ;
85125 lastSentToken = fresh ;
0 commit comments