/******************************************************************************** * Copyright (c) 2026 CrossBreeze, EclipseSource and others. * * This program and the accompanying materials are made available under the * terms of the MIT License which is available in the project root. * * SPDX-License-Identifier: MIT ********************************************************************************/ import { type Stopwatch } from '@hydranium/protocol'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { AbstractChannel } from '@theia/core/lib/common/message-rpc/channel'; import { type WriteBuffer } from '@theia/core/lib/common/message-rpc/message-buffer'; import { WebsocketFrontendConnectionService } from '@theia/core/lib/node/messaging/websocket-frontend-connection-service'; import { type interfaces } from '@theia/core/shared/inversify'; import { Clock } from '../common/clock'; import { type ConnectionResilienceOptions } from '../common/connection-resilience-options'; import { FramedSocketWriteBuffer } from '../common/framed-socket-write-buffer'; import { InboundMessageSequence } from '../common/inbound-message-sequence'; /** * Socket Theia hands the disconnect handler, read off the base signature rather * than imported, which spares a direct `socket.io` dependency carried purely * for a parameter type. */ export type FrontendSocket = Parameters[0]; /** * Channel Theia hands the disconnect handler, read off the base signature so * this package's emitted declarations name only * `WebsocketFrontendConnectionService`. * * `ReconnectableSocketChannel` is not exported by every Theia release in the * supported range, so importing it would make those declarations unresolvable * for an adopter on one of them — a compile error for a feature they may not * even be using. */ export type FrontendChannel = Parameters[1]; /** * The server end of one frontend's session, carried across the sockets the * frontend reconnects on. * * Theia's own channel sends straight into any socket that reports itself * connected. A socket whose far end died unnoticed still does, so whatever is * sent until the server notices is lost. This one numbers and keeps every * message through {@link FramedSocketWriteBuffer}, resends what is still * unacknowledged when the frontend reconnects, and drops what it has received * twice. Otherwise it behaves as Theia's. */ export declare class AcknowledgedSocketChannel extends AbstractChannel { protected readonly socketBuffer: FramedSocketWriteBuffer; protected socket: FrontendSocket | undefined; protected socketDisposables: DisposableCollection; protected readonly inbound: InboundMessageSequence; constructor(socketBuffer: FramedSocketWriteBuffer); /** Flushes in the same step that attaches the listeners, so nothing new can overtake the resend. */ connect(socket: FrontendSocket): void; disconnect(): void; drainBuffer(): void; getWriteBuffer(): WriteBuffer; } /** * Ignores a disconnect from a socket that no longer belongs to the frontend. * * A frontend that loses its network reconnects on a new socket within seconds; * the server only notices the old one is gone when its next heartbeat write * fails, which is a whole ping interval later. For that stretch both sockets are * registered against the same session, and Theia's disconnect handler tears the * session off its socket without checking which one just died. The late * notification from the abandoned socket therefore cuts the healthy one loose. * The frontend sees a connected socket and never retries, so the session is dead * until the page is reloaded. * * Changed here: only the socket the session is currently using may act on a * disconnect. The handler is rewritten rather than wrapped because the problem * sits inside the listener Theia installs; the rest of the body is Theia's. * * Re-diff against Theia when the supported range moves: `override` catches a changed signature, * not a changed body, so this copy can go stale in silence. */ export declare class SessionBoundFrontendConnectionService extends WebsocketFrontendConnectionService { /** Times the gaps the log reports: the container's {@link Clock}, or a `SystemClock`. */ protected readonly clock: Clock; /** Socket each frontend's channel is currently bound to, with how long it has been. */ protected readonly bindings: Map; /** Running since a frontend's live socket went away, so the reconnect gap can be reported. */ protected readonly sinceOffline: Map; handleSocketDisconnect(socket: FrontendSocket, channel: FrontendChannel, frontEndId: string): void; /** * Theia's body, building an {@link AcknowledgedSocketChannel} in place of its own channel. * * Handed over through a cast, because Theia types the slot as its own channel class. The cast * also hides from the compiler what Theia calls on the channel, so a release that calls a member * this one lacks fails at runtime: re-diff with the disconnect handler. */ protected createConnection(socket: FrontendSocket, frontEndId: string): FrontendChannel; /** * One buffer per session, from the container Theia resolves its own channel's buffer from. * Reached through a structural view because Theia 1.70 does not declare `container`. */ protected createWriteBuffer(): FramedSocketWriteBuffer; protected closeConnection(frontEndId: string, reason: string): void; /** Mirrors Theia's private `frontendConnectionTimeout`, including `Number('')` resolving to 0. * Private there, so the copy is forced; re-diff it with the handler above. */ protected connectionTimeout(): number; } /** * Replaces the server pieces that decide how a session survives a reconnect. * * `FrontendConnectionService` is bound `toService(WebsocketFrontendConnectionService)`, * so rebinding the concrete class is enough. The buffer stays transient (one per * connection), matching Theia's `bind(SocketWriteBuffer).toSelf()`; the frontend * counterpart is bound by `bindConnectionResilience` in a `frontendPreload` * module, which is the only point early enough on that side. * * Does nothing but warn on a Theia too old to expose the buffer binding, so the * package keeps one supported range rather than splitting its floor for this * feature. Returns whether the hardening was installed, for an adopter that * wants to branch on it. */ export declare function bindConnectionResilience(bind: interfaces.Bind, isBound: interfaces.IsBound, rebind: interfaces.Rebind, options?: ConnectionResilienceOptions): boolean; //# sourceMappingURL=session-bound-frontend-connection-service.d.ts.map