fix(app): jitter gateway reconnects and recover status nagbar (#2439)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-03 23:21:32 +02:00
1 parent b0645300ec
commit cc3a9c8613
3 files changed
+24 -13

No files matched your search

@@ -86,6 +86,12 @@ export function useConnectionNotice(): ConnectionNotice | null {
}, BOOT_NOTICE_DELAY_MS);
return () => window.clearTimeout(timer);
}, [booting]);
useEffect(() => {
if (!reconnecting) {
return;
}
StatusPage.refreshForConnectionIssue();
}, [reconnecting]);
if (Nagbar.forceHideConnectionNotice) {
return null;
}
@@ -32,6 +32,7 @@ const GATEWAY_TIMEOUTS = {
ResumeWindow: 180000,
MinReconnect: 1000,
MaxReconnect: 60000,
ReconnectSpread: 2000,
Hello: 20000,
} as const;
export const GatewayState = {
@@ -1034,7 +1035,7 @@ export class GatewaySocket extends EventEmitter<GatewaySocketEvents> {
}
const allowImmediate = options.allowImmediate ?? true;
const wasImmediate = allowImmediate && this.shouldReconnectImmediately;
const delay = wasImmediate ? 0 : this.nextReconnectDelay();
const delay = wasImmediate ? this.reconnectSpread() : this.nextReconnectDelay();
this.shouldReconnectImmediately = false;
this.log.info(`Scheduling reconnect in ${delay}ms${wasImmediate ? ' (immediate)' : ''}`);
this.reconnectTimeoutId = window.setTimeout(() => {
@@ -1047,12 +1048,16 @@ export class GatewaySocket extends EventEmitter<GatewaySocketEvents> {
}, delay);
}
private reconnectSpread(): number {
return Math.floor(Math.random() * GATEWAY_TIMEOUTS.ReconnectSpread);
}
private nextReconnectDelay(): number {
const now = Date.now();
const elapsed = now - this.lastReconnectAt;
if (elapsed < GATEWAY_TIMEOUTS.MinReconnect) {
this.log.debug(`Last reconnect ${elapsed}ms ago, enforcing minimum delay (${GATEWAY_TIMEOUTS.MinReconnect}ms)`);
return GATEWAY_TIMEOUTS.MinReconnect;
return GATEWAY_TIMEOUTS.MinReconnect + this.reconnectSpread();
}
this.lastReconnectAt = now;
const delay = this.reconnectBackoff.next();
@@ -90,7 +90,11 @@ const POLL_ACTIVE_MAINTENANCE_JITTER_MS = 15 * 1000;
const POLL_AFTER_MAINTENANCE_START_MS = 5 * 1000;
const POLL_MIN_DELAY_MS = 10 * 1000;
const POLL_RESUME_STALE_MS = 30 * 1000;
const STATUS_PAGE_FETCH_OPTIONS: RequestInit = {cache: 'no-store'};
const STATUS_PAGE_FETCH_TIMEOUT_MS = 10 * 1000;
function statusPageFetchOptions(): RequestInit {
return {cache: 'no-store', signal: AbortSignal.timeout(STATUS_PAGE_FETCH_TIMEOUT_MS)};
}
export function computePollDelay(scheduledMaintenance: StatusPageMaintenance | null): number {
if (scheduledMaintenance?.status === 'in_progress') {
@@ -256,12 +260,8 @@ export class StatusPage {
private async fetchIncidents(): Promise<void> {
try {
const response = await fetch(`${ExternalUrls.SERVICE_STATUS}/summary.json`, STATUS_PAGE_FETCH_OPTIONS);
const response = await fetch(`${ExternalUrls.SERVICE_STATUS}/summary.json`, statusPageFetchOptions());
if (!response.ok) {
runInAction(() => {
this.incident = null;
this.scheduledMaintenance = null;
});
return;
}
const data: InstatusSummary = await response.json();
@@ -296,16 +296,12 @@ export class StatusPage {
});
} catch {
logger.warn('Failed to fetch status page');
runInAction(() => {
this.incident = null;
this.scheduledMaintenance = null;
});
}
}
private async fetchComponentMaintenances(): Promise<Array<InstatusMaintenance>> {
try {
const response = await fetch(`${ExternalUrls.SERVICE_STATUS}/components.json`, STATUS_PAGE_FETCH_OPTIONS);
const response = await fetch(`${ExternalUrls.SERVICE_STATUS}/components.json`, statusPageFetchOptions());
if (!response.ok) {
return [];
}
@@ -321,6 +317,10 @@ export class StatusPage {
this.incident = null;
}
refreshForConnectionIssue(): void {
this.refreshIfStale();
}
private async refreshAndReschedule(): Promise<void> {
try {
await this.checkIncidents();