export = InterClient; /** * Typedef Imports * @typedef {import("../RestNio")} RestNio * @typedef {import("../codec").Codec} Codec * @typedef {import("../util/Options").ClientProperties} ClientProperties * @typedef {import("../routes/Router").RouteBack} RouteBack */ /** * @typedef ReconnectOptions * @property {boolean} [enabled=true] - whether to auto-reconnect on close. * @property {number} [minDelay=500] - first retry delay (ms) and floor for jittered values. * @property {number} [maxDelay=30000] - cap on retry delay (ms). * @property {number} [factor=2] - multiplicative growth per attempt. * @property {number} [jitter=0.2] - +/- random jitter ratio applied to each computed delay. * @property {number} [maxAttempts=Infinity] - cap on the number of consecutive * failed connect attempts. When the cap is reached the peer fires `interFail` * (see "Lifecycle routes") and stops retrying. Use `peer.reopen()` to start * over after a failure. Default is unbounded. * @property {number} [gracePeriodMs=10000] - on transport drop, the peer * delays hard cleanup (failing pending proxy sessions, dropping buffered * frames it can't otherwise rescue) by up to this many ms. If a reconnect * succeeds within the grace, the cleanup is cancelled and queued frames * flush across the new socket transparently. Set to `0` to disable * (immediate cleanup on close, matches pre-grace behaviour). */ /** * Connection status. * - `'connecting'` — initial state, or trying again after a close. * - `'open'` — socket reached `OPEN` and is delivering frames. * - `'closed'` — socket closed; reconnects may still be scheduled. * - `'failed'` — `maxAttempts` exhausted; no further reconnects until * `peer.reopen()`. * - `'shut'` — `peer.close()` was called explicitly. * @typedef {('connecting'|'open'|'closed'|'failed'|'shut')} InterStatus */ /** * @typedef InterconnectOptions * @property {string} [subprotocol] - codec subprotocol, e.g. 'restnio.json' * (default) or 'restnio.msgpack'. Negotiated with the remote at handshake. * @property {string} [token] - JWT sent as `Authorization: Bearer ` * header on the upgrade request. Use this to grant the peer permissions on * the remote server. * @property {string[]} [permissions] - permissions granted to this peer * *locally*, used by peer-scoped routes when running incoming envelopes. * @property {ReconnectOptions} [reconnect] - reconnect tuning. Disable with * `{ enabled: false }`. * @property {number} [sendBufferLimit=1000] - cap on the in-memory queue of * frames waiting for the socket to reach `OPEN`. When the cap is reached the * oldest entry is dropped, `peer.droppedFrames` is incremented, and an * `interError` lifecycle event fires with `{type:'sendBufferOverflow', dropped, * limit}`. Prevents unbounded memory growth when a peer is permanently down * but local code keeps emitting (e.g. high-rate telemetry over a dead bridge). * Set to 0 to disable the cap (legacy behavior). * @property {RouteBack} [routes] - router callback. By default this writes * directly into the main `rnio.routes` table — peer envelopes dispatch * through the same routes a normal websocket client would hit. Pass * `isolate: true` to redirect into a peer-private route map instead. * The {@link InterClient} is passed as the second arg. Lifecycle hooks live * here too: `router.on('interOpen', fn)` / `interClose` / `interError` / * `interFail` mirror the existing `wsConnect` / `wsClose` pattern. Use these * for any "send a register envelope after connect" or "log on disconnect" * pattern — there are no separate callback options; lifecycle events flow * through the standard route system uniformly. * * `interError` fires for both transport errors (socket-level `'error'` * events) AND protocol errors raised inside `routeBeat` (e.g. an inbound * frame that fails to parse). Params: `{type, error}` where `type` is * `'transport' | 'protocol' | 'sendBufferOverflow'`. * @property {boolean} [isolate=false] - when `true`, the peer's incoming * envelopes dispatch through a peer-private route map and the `routes` * callback writes into that map instead of `rnio.routes`. Use this when * the *same* RestNio service is talking to itself (sibling-to-sibling) * and route names would collide, or when you want strong isolation * between routes a peer can call vs. routes local clients can call. * Default is shared — typical interconnect cases (turbine ↔ park) have * no schema overlap so route reuse is the desired behavior. * @property {ClientProperties} [properties] - per-peer client property * overrides (e.g. `debug: true`). * @property {(string[]|RegExp[])} [shadowOut] - peer-aware publish whitelist: * patterns of local sub channel names whose `subs(channel).(...)` * calls are forwarded to this peer as a single coalesced shadow frame. * Default empty (no automatic forwarding). Glob `*` / `**` syntax supported, * RegExps too. See {@link ../util/peerLink}. * @property {(string[]|RegExp[])} [shadowIn] - patterns of channel names this * peer is allowed to push shadow frames to us under. Frames with channels * outside the pattern are dropped. Default empty. */ /** * @class InterClient * @extends Client * @author 7kasper * @classdesc * Outbound peer connection, a `Client` whose websocket is opened by *us* and * pointed at *another* RestNio server. Symmetric to {@link WebSocketClient}: * envelopes flow both ways over the same socket. Outgoing frames are sent * with {@link Client#obj}/{@link Client#str}/{@link Client#bin}; incoming * frames are routed against this peer's own private {@link RouteMap}, never * against `rnio.routes`. That isolation lets a turbine define routes that * only the park can hit, without those routes leaking to the local ESPs. * * Construct via {@link RestNio#interconnect} rather than directly. * * Frames sent before the socket reaches `OPEN` are buffered and flushed on * connect; on a subsequent close the client auto-reconnects with exponential * backoff (configurable via `reconnect`), unless {@link InterClient#close} was * called explicitly. */ declare class InterClient extends Client { /** * @param {RestNio} restnio - reference to the local server. * @param {string} name - logical peer name; used by `rnio.inter(name)`. * @param {string} url - target websocket URL, e.g. `ws://park.local/`. * @param {InterconnectOptions} [options] */ constructor(restnio: RestNio, name: string, url: string, options?: InterconnectOptions); name: string; url: string; subprotocol: string; /** @type {Codec} */ codec: Codec; _authToken: string; reconnectOpts: any; /** * Cap on `_sendBuffer` length. `0` disables the cap. See * {@link InterconnectOptions} for the rationale. * @type {number} */ sendBufferLimit: number; /** * Running total of frames dropped because the send buffer was full. * Resets only on `peer.reopen()` (alongside the retry counter). * @type {number} */ droppedFrames: number; /** * Whether this peer keeps an isolated, peer-private route map. When * false (default), the peer shares `rnio.routes` with the main * server — registering a route via `peer.router` makes it reachable * to local clients and to peer pushes alike. * @type {boolean} */ isolated: boolean; /** @type {RouteMap} */ routes: RouteMap; /** * Peer-scoped router. Routes go into this peer's private * {@link RouteMap} — invisible to other clients. * @type {Router} */ router: Router; /** * Always-available reference to the main server router. Lets a peer * register routes onto `rnio.routes` even when running in isolated * mode — useful when only *some* of the peer's routes should also be * reachable by local clients. * @type {Router} */ mainRouter: Router; /** @type {string|null} */ binRoute: string | null; /** @type {WebSocket|null} */ ws: WebSocket | null; isAlive: boolean; /** * Current connection status. Read it directly to gate work on the * peer being live: `if (peer.status === 'open') peer.obj(...)`. * @type {InterStatus} */ status: InterStatus; shouldReconnect: any; reconnectAttempts: number; /** @type {Error|null} */ _lastError: Error | null; _sendBuffer: any[]; _reconnectTimer: any; /** * `true` when the underlying socket is in the OPEN state. Convenience * sugar for `peer.status === 'open'`. * @type {boolean} */ get isOpen(): boolean; /** * Opens (or re-opens) the underlying websocket and wires up event handlers. * Internal — call {@link RestNio#interconnect} once at startup; reconnects * are handled automatically. */ _connect(): void; _graceTimer: any; /** * Computes a jittered exponential-backoff delay and schedules the next * connect attempt. When `maxAttempts` has been exhausted, fires * `interFail` and stops retrying instead. */ _scheduleReconnect(): void; /** * Dispatches a lifecycle event (`interOpen` / `interClose` / `interFail`) * through the peer's route map. Each registered handler is called with * `(params, peer)`. Errors in handlers are swallowed and logged so a * misbehaving handler can't break the connection state machine. * @param {string} name * @param {object} params */ _fireLifecycle(name: string, params: object): Promise; /** * Restarts the connection — resets the retry counter, re-arms reconnects, * and opens a new socket if there isn't already one in flight. Use this * from an `interFail` or `interClose` handler to recover after the * automatic retry budget has been exhausted, or to force a reconnect. */ reopen(): void; /** * Low-level send. Frames sent before the socket reaches `OPEN` are * buffered and flushed in order on connect. * * The buffer is capped at `sendBufferLimit` (default 1000, `0` disables). * On overflow the oldest entry is dropped, `droppedFrames` is bumped, * and an `interError` lifecycle event fires per drop with * `{type:'sendBufferOverflow', dropped, limit}`. Throttle in your * handler if you want a single notification per outage. * @param {string|Buffer} payload */ _send(payload: string | Buffer): void; /** * Sends a plain string as a text frame. * @param {string} str */ str(str: string): void; /** * Sends an object to the remote, using the negotiated codec. * * - `Buffer` input is sent as-is on a binary frame (no encoding). * - For JSON peers a plain string is sent as a text frame (unless * `jsonResponse` is truthy, in which case it is JSON-stringified). * - Otherwise the codec encodes; string results go on a text frame, * `Buffer` results on a binary frame. * * Typical use: `peer.obj({ path: '/setPower', params: { kw: 1500 } })` to * trigger a route on the remote. * @param {*} obj */ obj(obj: any): void; err(err: any, code?: number): void; /** * Sets the active binary route name. While set, every incoming binary * frame on this peer is dispatched as raw bytes to the matching * `wsBin-` route in this peer's route map. * @param {string|null} name */ setBinRoute(name: string | null): void; /** * Clears the active binary route. Equivalent to `setBinRoute(null)`. */ clearBinRoute(): void; /** * Branches an incoming frame between text (always JSON envelope) and * binary (codec sniff or named-route dispatch). Mirrors * {@link WebSocketClient#routeBeat}, but routes against this peer's own * route map. * @param {Buffer|string} message * @param {boolean} isBinary */ routeBeat(message: Buffer | string, isBinary: boolean): Promise; /** * Dispatches a decoded envelope through this peer's route map. * Envelopes without a `path` are dispatched to the `interNoPath` hook * (default NOOP) — see {@link WebSocketClient#routeEnvelope} for why * this is not silently routed to '/'. Peer links use a dedicated hook * name (`interNoPath`) rather than `wsNoPath` so handlers can react * differently to path-less frames on a trusted peer link versus a * regular client connection. Non-object payloads are wrapped as * `{ params: { body } }`. * * Honors `request._actor` for relayed-permission propagation. When the * upstream peer attaches an actor claim, the receiver clamps it against * this peer's connection perms (the cap set at `interconnect()` time) * and exposes the result through `client.effectivePermissions` for the * duration of the dispatch. The original caller's identity is exposed * via `client.actor`. Connection-level permissions are never mutated. * * Trust gate: only honored on `InterClient` (i.e. when this connection * itself is a peer link) — frames arriving on a regular WS client never * trigger actor scoping. Peer permissions cap what `_actor.perms` may * effectively grant, so a peer can never delegate beyond its own ceiling. * * @param {*} request */ routeEnvelope(request: any): Promise; /** * Dispatches a path-less envelope to the optional `interNoPath` hook * on this peer's route map. Errors from handlers are caught and * logged; the frame is otherwise silently dropped. The full request * object is passed as `params` so handlers can inspect `code`, * `error`, etc. — useful for surfacing remote-side error frames * (peer-side route 404s, parse errors, throw[code, msg] replies). * @param {Object} request */ _dispatchNoPath(request: any): Promise; /** * Run every handler registered under `name` with `params` as the * payload, discarding the return value. See * {@link WebSocketClient#_dispatchLifecycle} for why lifecycle hooks * bypass executeRoute's auto-reply path. * @param {string} name * @param {Object} params */ _dispatchLifecycle(name: string, params: any): Promise; /** * Handles an incoming binary frame per the same rules as * {@link WebSocketClient#handleBinaryFrame}. * @param {Buffer} data */ handleBinaryFrame(data: Buffer): Promise; /** * Dispatches raw bytes to a named `wsBin-` peer route or the * default `wsBin` peer route. * @param {string|null} name * @param {Buffer} data */ dispatchBinRoute(name: string | null, data: Buffer): Promise; /** * Closes the peer connection and disables further reconnects. Pass a * reason string or `[code, reason]` tuple to control the close frame. * @param {string|[number, string]} [reason] */ close(reason?: string | [number, string]): void; } declare namespace InterClient { export { RestNio, Codec, ClientProperties, RouteBack, ReconnectOptions, InterStatus, InterconnectOptions }; } import Client = require("./Client"); import RouteMap = require("../util/RouteMap"); import Router = require("../routes/Router"); import WebSocket = require("ws"); /** * Typedef Imports */ type RestNio = import("../RestNio"); /** * Typedef Imports */ type Codec = import("../codec").Codec; /** * Typedef Imports */ type ClientProperties = import("../util/Options").ClientProperties; /** * Typedef Imports */ type RouteBack = import("../routes/Router").RouteBack; type ReconnectOptions = { /** * - whether to auto-reconnect on close. */ enabled?: boolean; /** * - first retry delay (ms) and floor for jittered values. */ minDelay?: number; /** * - cap on retry delay (ms). */ maxDelay?: number; /** * - multiplicative growth per attempt. */ factor?: number; /** * - +/- random jitter ratio applied to each computed delay. */ jitter?: number; /** * - cap on the number of consecutive * failed connect attempts. When the cap is reached the peer fires `interFail` * (see "Lifecycle routes") and stops retrying. Use `peer.reopen()` to start * over after a failure. Default is unbounded. */ maxAttempts?: number; /** * - on transport drop, the peer * delays hard cleanup (failing pending proxy sessions, dropping buffered * frames it can't otherwise rescue) by up to this many ms. If a reconnect * succeeds within the grace, the cleanup is cancelled and queued frames * flush across the new socket transparently. Set to `0` to disable * (immediate cleanup on close, matches pre-grace behaviour). */ gracePeriodMs?: number; }; /** * Connection status. * - `'connecting'` — initial state, or trying again after a close. * - `'open'` — socket reached `OPEN` and is delivering frames. * - `'closed'` — socket closed; reconnects may still be scheduled. * - `'failed'` — `maxAttempts` exhausted; no further reconnects until * `peer.reopen()`. * - `'shut'` — `peer.close()` was called explicitly. */ type InterStatus = ("connecting" | "open" | "closed" | "failed" | "shut"); type InterconnectOptions = { /** * - codec subprotocol, e.g. 'restnio.json' * (default) or 'restnio.msgpack'. Negotiated with the remote at handshake. */ subprotocol?: string; /** * - JWT sent as `Authorization: Bearer ` * header on the upgrade request. Use this to grant the peer permissions on * the remote server. */ token?: string; /** * - permissions granted to this peer * *locally*, used by peer-scoped routes when running incoming envelopes. */ permissions?: string[]; /** * - reconnect tuning. Disable with * `{ enabled: false }`. */ reconnect?: ReconnectOptions; /** * - cap on the in-memory queue of * frames waiting for the socket to reach `OPEN`. When the cap is reached the * oldest entry is dropped, `peer.droppedFrames` is incremented, and an * `interError` lifecycle event fires with `{type:'sendBufferOverflow', dropped, * limit}`. Prevents unbounded memory growth when a peer is permanently down * but local code keeps emitting (e.g. high-rate telemetry over a dead bridge). * Set to 0 to disable the cap (legacy behavior). */ sendBufferLimit?: number; /** * - router callback. By default this writes * directly into the main `rnio.routes` table — peer envelopes dispatch * through the same routes a normal websocket client would hit. Pass * `isolate: true` to redirect into a peer-private route map instead. * The {@link InterClient} is passed as the second arg. Lifecycle hooks live * here too: `router.on('interOpen', fn)` / `interClose` / `interError` / * `interFail` mirror the existing `wsConnect` / `wsClose` pattern. Use these * for any "send a register envelope after connect" or "log on disconnect" * pattern — there are no separate callback options; lifecycle events flow * through the standard route system uniformly. * * `interError` fires for both transport errors (socket-level `'error'` * events) AND protocol errors raised inside `routeBeat` (e.g. an inbound * frame that fails to parse). Params: `{type, error}` where `type` is * `'transport' | 'protocol' | 'sendBufferOverflow'`. */ routes?: RouteBack; /** * - when `true`, the peer's incoming * envelopes dispatch through a peer-private route map and the `routes` * callback writes into that map instead of `rnio.routes`. Use this when * the *same* RestNio service is talking to itself (sibling-to-sibling) * and route names would collide, or when you want strong isolation * between routes a peer can call vs. routes local clients can call. * Default is shared — typical interconnect cases (turbine ↔ park) have * no schema overlap so route reuse is the desired behavior. */ isolate?: boolean; /** * - per-peer client property * overrides (e.g. `debug: true`). */ properties?: ClientProperties; /** * - peer-aware publish whitelist: * patterns of local sub channel names whose `subs(channel).(...)` * calls are forwarded to this peer as a single coalesced shadow frame. * Default empty (no automatic forwarding). Glob `*` / `**` syntax supported, * RegExps too. See {@link ../util/peerLink}. */ shadowOut?: (string[] | RegExp[]); /** * - patterns of channel names this * peer is allowed to push shadow frames to us under. Frames with channels * outside the pattern are dropped. Default empty. */ shadowIn?: (string[] | RegExp[]); };