/******************************************************************************** * 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 { Emitter, type Event } from '@theia/core/lib/common/event'; import { SocketWriteBuffer } from '@theia/core/lib/common/messaging/socket-write-buffer'; import { type ConnectionResilienceOptions } from './connection-resilience-options'; /** * Prefix on every connection log line, deliberately shared by the browser and the * server so one pattern greps a console and a pod log at once. */ export declare const CONNECTION_LOG_PREFIX = "[connection]"; /** * The part of a socket this buffer uses. Narrower than the socket type Theia * declares, which keeps the dependency honest and lets the class be tested * without a connection. */ export interface MessageSink { /** `sequence` travels as a second socket.io argument, which a reader that does not expect it ignores. */ send(data: Uint8Array, sequence: number): void; } /** * Reported when the buffer runs out of room. The message that did not fit is * rejected, and so is every further one for as long as the buffer stays full. * Reconnecting drains it and sending resumes, but whatever was rejected * meanwhile is gone, so the two sides no longer agree about the session. */ export interface ConnectionBufferOverflow { /** Messages held when the limit was reached. */ readonly messages: number; /** Bytes held when the limit was reached. */ readonly bytes: number; /** The limit in force, so a reader can tell what to raise it to. */ readonly maxBytes: number; } /** * Holds messages while the peer is away and re-sends them when it returns. * * Theia copies them into one growing byte array and flushes that with a single * `send`. A websocket delivers whole messages rather than a byte stream, so * Theia's encoding adds no length prefix and every reader assumes one delivery * is one message. The concatenated flush breaks that assumption: the reader * decodes the first message and throws away the bytes behind it. * * Changed here: one entry per message, sent one at a time, so each arrives as * its own delivery. The buffer also decides whether a message may go out * directly — see {@link sendOrQueue} — because a caller that decides for itself * is how messages end up overtaking a backlog. * * Unchanged from Theia: exceeding the limit rejects the message rather than * dropping something silently. The limit is settable, since the right value * depends on how long an outage has to survive. * * Added here: every message sent is numbered and kept until the peer * acknowledges it, and {@link flush} resends what is still unacknowledged ahead * of the backlog. A socket whose far end died unnoticed still accepts writes, so * a message is not delivered just because it was sent. * * Logging goes to `console` rather than through a `Logger`. On the browser side * Theia's preloader builds this before any Output channel exists, so there is * nothing else to write to; the {@link CONNECTION_LOG_PREFIX} keeps the lines * findable on both sides. */ export declare class FramedSocketWriteBuffer extends SocketWriteBuffer { /** Theia's fixed limit, kept as the default. */ static readonly DEFAULT_MAX_BYTES: number; /** * Settable so a deployment can trade memory for longer outages. * * Consulted directly rather than through the base class's `maxBufferSize`, * which does not exist before Theia 1.71 — an `override` of it would not * compile against the oldest release this package supports. Nothing is lost: * `buffer`, `flush` and `drain` are all replaced here, so the base's own * limit is never reached. */ maxBytes: number; /** Kept copies past this are discarded, so a peer that never acknowledges cannot grow them without bound. */ static readonly DEFAULT_MAX_UNACKNOWLEDGED_BYTES: number; /** * Separate from {@link maxBytes}, which counts only what is produced while * the peer is away: copies kept on a healthy connection must not use up the * room an outage needs. */ maxUnacknowledgedBytes: number; protected pending: Uint8Array[]; protected pendingBytes: number; protected overflowReported: boolean; protected readonly onOverflowEmitter: Emitter; protected nextSequence: number; /** Oldest first: {@link acknowledge} stops at the first entry the peer has not accepted. */ protected unacknowledged: { readonly sequence: number; readonly data: Uint8Array; }[]; protected unacknowledgedBytes: number; protected unacknowledgedOverflowReported: boolean; /** * Whether the peer drops a message it already has. One without the hardening * applies a resent message a second time, so nothing is resent until the * peer has shown otherwise. */ protected peerDeduplicates: boolean; /** Fires once per outage when the limit is reached. */ get onOverflow(): Event; /** * Sends `data` now only if nothing is waiting, and queues it otherwise. * * Callers say whether the peer is ready for a message; the buffer decides * whether it may go ahead of one already waiting. Splitting it this way is * the point: a caller that answers both questions from one flag is how a * fresh message ends up overtaking a backlog. */ sendOrQueue(socket: MessageSink | undefined, data: Uint8Array): void; buffer(data: Uint8Array): void; /** * Kept only once `send` returns: a message whose send threw stays in the * backlog, and keeping it as well would send it again under a second number, * which the receiver cannot recognise as a duplicate. */ protected send(socket: MessageSink, data: Uint8Array): void; protected keep(sequence: number, data: Uint8Array): void; /** * Records that the peer numbers its messages, which only a peer that also * drops duplicates does, so resending to it is safe. */ markPeerDeduplicates(): void; /** Releases every kept message numbered up to `sequence`, which the peer has accepted. */ acknowledge(sequence: number): void; /** * Resends what the peer has not acknowledged, under the same numbers, then * sends the backlog. Some of the resent messages may have arrived already; * the receiver recognises them by number and drops them. * * Resends only to a peer known to drop duplicates, and otherwise discards the * copies, leaving any message lost with the old socket lost. */ flush(socket: MessageSink): void; drain(): void; /** Whether messages are still waiting to go out. */ get hasBacklog(): boolean; protected reportOverflow(): void; /** Also discards the kept copies: a drain ends the session, and resending them would replay it into the next one. */ protected reset(): void; } /** Named in the warning a skipped install emits, so the reader is told what to upgrade to. */ export declare const REQUIRED_THEIA_HINT = "@theia/core 1.71 or newer"; /** * Whether this Theia exposes the seam the reconnect hardening replaces. * * Theia only began binding {@link SocketWriteBuffer} in 1.71; before that each * connection built one privately, so there is no binding to rebind and no way * in. Every other API involved has been there throughout, which makes this one * probe the whole compatibility question — and it is a binding check rather * than a version parse, so it answers about the container actually in front of * us instead of a number that may have been overridden or vendored. * * Callers that get `false` must leave Theia's own wiring alone: rebinding half * of it would be worse than not rebinding at all. */ export declare function supportsConnectionResilience(isBound: (identifier: typeof SocketWriteBuffer) => boolean): boolean; /** * The warning a skipped install emits. Says what is lost rather than only what * is missing: the failures this guards against are silent, so an adopter who * never sees this line has no other way to learn the guard is absent. */ export declare function warnConnectionResilienceUnavailable(tier: 'frontend' | 'backend'): void; /** Builds a buffer with the configured limits, or the defaults for any not configured. */ export declare function createFramedSocketWriteBuffer(options?: ConnectionResilienceOptions): FramedSocketWriteBuffer; //# sourceMappingURL=framed-socket-write-buffer.d.ts.map