/** * Debug Dashboard — Control Push (optional, on top of polling) * * By default a source learns about control changes by polling GET /control. * That costs the source a request every few seconds even when nothing changes, * and a button press only arrives at the next poll. A source that runs its own * HTTP server can instead SUBSCRIBE (POST /control/subscribe) with a callback * URL; the hub then POSTs every change to that URL right away. * * Push is an ADDITION, never a replacement: GET /control keeps working, and a * source should keep a slow poll as a safety net (a missed push is repaired by * the next poll — for buttons that is exactly why they are counters). * * Rules the hub follows when calling out: * - Fire & forget. Delivery never blocks the dashboard's request. * - One request in flight per subscriber. Changes that arrive meanwhile are * coalesced (latest value per id wins) and sent as one follow-up batch, so * a fast slider drag can never arrive out of order. * - Short timeout. After MAX_FAILURES failed deliveries in a row the * subscription is dropped — the hub must not keep knocking on a dead * device. The source re-subscribes whenever it likes (idempotent). * - Callback URLs are foreign input: plain http to an IP literal (or * localhost) in loopback/private/link-local ranges only, no redirects. * * Payload: a flat { id: value } object — the same shape GET /control returns, * so a source can feed both through the same parser. */ export type ControlValue = number | string; export interface SubscribeRequest { url?: string; port?: number; path?: string; ids?: string[]; } export interface SubscriberInfo { url: string; ids: string[] | null; failures: number; delivered: number; lastError?: string; } export type PushEventLogger = (level: 'info' | 'warn', message: string) => void; export declare class ControlPush { private readonly log; private subs; private closed; constructor(log?: PushEventLogger); /** * Register (or refresh) a callback. `remoteAddress` is the socket address * of the subscribing request, used when only a port is given. Returns the * normalized URL or an error text. Re-subscribing the same URL replaces the * id filter and resets the failure count. */ subscribe(req: SubscribeRequest, remoteAddress: string | undefined): { url: string; } | { error: string; }; /** Remove a subscription by URL. Returns true if it existed. */ unsubscribe(url: string): boolean; /** A control changed — queue it for every interested subscriber. */ notify(id: string, value: ControlValue): void; list(): SubscriberInfo[]; get size(): number; /** Hub shutdown: forget everything, stop sending follow-up batches. */ close(): void; private flush; } /** * Socket address → plain host. Express/Node report IPv4 peers on a dual-stack * socket as "::ffff:192.168.1.50" (IPv4-mapped IPv6), which is not usable as * a URL host as-is. */ export declare function normalizeRemoteAddress(addr: string | undefined): string | null; /** Only plain http to loopback / private / link-local IP literals (or localhost). */ export declare function validateCallbackUrl(raw: string): { url: string; } | { error: string; }; //# sourceMappingURL=control-push.d.ts.map