import { parseDockerUnixNanoseconds } from '../helpers.timestamp.js'; export interface IDockerEventActor { ID: string; Attributes?: Record; } export interface IDockerEvent { Type?: string; Action?: string; /** Raw until parsed as a container event; malformed actors must reach the validator. */ Actor?: unknown; status?: string; scope?: string; time?: number; timeNano?: number; /** The original Docker JSON integer token, retained without JS number rounding. */ timeNanoExact?: string; [key: string]: unknown; } export interface IContainerDockerEvent extends IDockerEvent { Type: 'container'; Action: string; Actor: IDockerEventActor; } export interface IContainerTimedDockerEvent extends IContainerDockerEvent { timeNanoExact: string; } const exactContainerId = /^[0-9a-f]{64}$/iu; /** * Return undefined for a different Docker object type. A malformed container event throws so a * missing `oom` actor cannot be mistaken for a harmless unrelated event. */ export const parseDockerContainerEvent = ( eventArg: IDockerEvent, ): IContainerDockerEvent | undefined => { if (eventArg.Type !== 'container') { if (typeof eventArg.Type === 'string' && eventArg.Type.length > 0) return undefined; throw new Error('Docker event object type is missing or malformed.'); } const action: unknown = eventArg.Action; const actor: unknown = eventArg.Actor; if (typeof action !== 'string' || action.length === 0) { throw new Error('Docker container event action is missing or malformed.'); } if (typeof actor !== 'object' || actor === null || Array.isArray(actor)) { throw new Error('Docker container event actor is missing or malformed.'); } const id: unknown = Reflect.get(actor, 'ID'); const attributes: unknown = Reflect.get(actor, 'Attributes'); if (typeof id !== 'string' || !exactContainerId.test(id)) { throw new Error('Docker container event actor ID is missing or malformed.'); } if ( attributes !== undefined && ( typeof attributes !== 'object' || attributes === null || Array.isArray(attributes) || Object.values(attributes).some((value) => typeof value !== 'string') ) ) { throw new Error('Docker container event actor attributes are malformed.'); } return { ...eventArg, Type: 'container', Action: action, Actor: { ID: id, ...(attributes === undefined ? {} : { Attributes: attributes as Record }), }, }; }; /** Require a lossless Docker event timestamp before comparing against a physical run. */ export const parseDockerTimedContainerEvent = ( eventArg: IDockerEvent, ): IContainerTimedDockerEvent | undefined => { const event = parseDockerContainerEvent(eventArg); if (!event) return undefined; parseDockerUnixNanoseconds(event.timeNanoExact, 'Docker container event timeNanoExact'); return event as IContainerTimedDockerEvent; }; export interface IDockerEventObservableOptions { filters?: Record; reconnect?: boolean; } export interface IDockerEventMonitorOptions { filters?: Record; /** HTTP readiness deadline. Defaults to 30 seconds; the active stream remains idle indefinitely. */ openTimeoutMs?: number; /** Called with listeners attached before openEventMonitor resolves. */ onEvent: (event: IDockerEvent) => void; /** Called once if the verified stream ends, errors, or delivers malformed data. */ onLost: (error: Error) => void; /** Aborts a pending open or intentionally closes an active monitor. */ signal?: AbortSignal; } export interface IDockerEventMonitor { readonly status: 'connected' | 'lost' | 'closed'; /** Idempotent; closes the stream without calling onLost. */ close(): void; }