ocpp-ws-io 2.1.3 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4486,6 +4486,7 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4486
4486
  private _badMessageCount;
4487
4487
  private _lastActivity;
4488
4488
  private _outboundBuffer;
4489
+ private _offlineQueue;
4489
4490
  private _middleware;
4490
4491
  private _validators;
4491
4492
  private _strictProtocols;
@@ -4557,7 +4558,7 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4557
4558
  *
4558
4559
  * @throws {Error} If a handler for this version and method is already registered on this client instance.
4559
4560
  */
4560
- handle<V extends OCPPProtocol, M extends AllMethodNames<V>>(version: V, method: M, handler: (context: HandlerContext<OCPPRequestType<V, M>>) => OCPPResponseType<V, M> | Promise<OCPPResponseType<V, M>>): void;
4561
+ handle<V extends OCPPProtocol, M extends AllMethodNames<V>>(version: V, method: M, handler: (context: HandlerContext<OCPPRequestType<V, M>>) => OCPPResponseType<V, M> | Promise<OCPPResponseType<V, M>> | typeof NOREPLY): void;
4561
4562
  /**
4562
4563
  * Register a handler for a custom/extension protocol/method not in the typed OCPP method maps.
4563
4564
  * `handle("my-protocol", "my-method", handler)`
@@ -4573,7 +4574,7 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4573
4574
  *
4574
4575
  * @throws {Error} If a handler for this method is already registered on this client instance.
4575
4576
  */
4576
- handle<M extends AllMethodNames<P>>(method: M, handler: (context: HandlerContext<OCPPRequestType<P, M>>) => OCPPResponseType<P, M> | Promise<OCPPResponseType<P, M>>): void;
4577
+ handle<M extends AllMethodNames<P>>(method: M, handler: (context: HandlerContext<OCPPRequestType<P, M>>) => OCPPResponseType<P, M> | Promise<OCPPResponseType<P, M>> | typeof NOREPLY): void;
4577
4578
  /**
4578
4579
  * Register a handler for a custom/extension method not in the typed OCPP method maps.
4579
4580
  *
@@ -4653,6 +4654,26 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4653
4654
  /** Errors that should stop reconnection immediately */
4654
4655
  private static readonly _INTOLERABLE_ERRORS;
4655
4656
  private _scheduleReconnect;
4657
+ /**
4658
+ * Atomically drains the offline queue and sends each message via _sendCall.
4659
+ * Uses splice(0) to prevent re-entry bugs (double billing) if the connection
4660
+ * drops again mid-flush — the queue is empty before any sends begin.
4661
+ */
4662
+ private _flushOfflineQueue;
4663
+ /**
4664
+ * Retry wrapper using Full Jitter exponential backoff.
4665
+ * delay = random(0, min(retryMaxDelayMs, retryDelayMs * 2^attempt))
4666
+ * Only retries on TimeoutError — all other errors propagate immediately.
4667
+ */
4668
+ private _callWithRetry;
4669
+ /** Maximum bytes allowed in the ws send buffer before applying backpressure (512KB) */
4670
+ private static readonly _BACKPRESSURE_THRESHOLD;
4671
+ /**
4672
+ * Wraps ws.send() with backpressure protection.
4673
+ * If bufferedAmount exceeds the threshold, waits for the buffer to drain
4674
+ * before sending. Prevents OOM on slow 2G/3G charger connections.
4675
+ */
4676
+ private _safeSend;
4656
4677
  private _startPing;
4657
4678
  private _stopPing;
4658
4679
  private _recordActivity;
@@ -4719,6 +4740,9 @@ interface ValidatorSchema {
4719
4740
  /**
4720
4741
  * Schema validator using AJV for OCPP message validation.
4721
4742
  * Each validator is bound to a specific subprotocol version.
4743
+ *
4744
+ * E2: Schemas are registered at construction time but compiled lazily
4745
+ * on first use, reducing startup time from ~400ms to ~5ms.
4722
4746
  */
4723
4747
  declare class Validator {
4724
4748
  readonly subprotocol: string;
@@ -4732,6 +4756,8 @@ declare class Validator {
4732
4756
  /**
4733
4757
  * Validate a payload against a schema identified by its $id.
4734
4758
  * Throws a typed RPCError if validation fails.
4759
+ *
4760
+ * E2: Schema is compiled on first call to this method (lazy).
4735
4761
  */
4736
4762
  validate(schemaId: string, params: unknown): void;
4737
4763
  /**
@@ -4740,7 +4766,9 @@ declare class Validator {
4740
4766
  hasSchema(schemaId: string): boolean;
4741
4767
  }
4742
4768
  /**
4743
- * Create a validator for a specific subprotocol version.
4769
+ * Create or retrieve a cached validator for a specific subprotocol version.
4770
+ * E5: Returns an existing instance if one was already created for this subprotocol,
4771
+ * preventing duplicate AJV instances and saving memory.
4744
4772
  */
4745
4773
  declare function createValidator(subprotocol: string, schemas: ValidatorSchema[]): Validator;
4746
4774
 
@@ -4829,6 +4857,22 @@ interface CallOptions {
4829
4857
  timeoutMs?: number;
4830
4858
  /** Abort signal */
4831
4859
  signal?: AbortSignal;
4860
+ /**
4861
+ * Max retry attempts on TimeoutError (default: 0 = no retry).
4862
+ * Uses Full Jitter exponential backoff between retries.
4863
+ */
4864
+ retries?: number;
4865
+ /** Base delay in ms for exponential backoff between retries (default: 1000) */
4866
+ retryDelayMs?: number;
4867
+ /** Max delay cap in ms to prevent unbounded backoff (default: 30000) */
4868
+ retryMaxDelayMs?: number;
4869
+ /**
4870
+ * Idempotency key for deduplication. If provided, this value is used
4871
+ * as the OCPP messageId instead of generating a new random one.
4872
+ * Consumers can use the same key to guarantee exactly-once semantics
4873
+ * when retrying calls across reconnections.
4874
+ */
4875
+ idempotencyKey?: string;
4832
4876
  }
4833
4877
  interface CloseOptions {
4834
4878
  /** WebSocket close code (default: 1000) */
@@ -5014,6 +5058,16 @@ interface ClientOptions {
5014
5058
  logging?: LoggingConfig | false;
5015
5059
  /** Rate Limiting configuration (Token Bucket) */
5016
5060
  rateLimit?: RateLimitOptions;
5061
+ /**
5062
+ * If true, calls made while disconnected are queued in-memory
5063
+ * and flushed automatically on reconnect. (default: false)
5064
+ */
5065
+ offlineQueue?: boolean;
5066
+ /**
5067
+ * Maximum number of messages to queue while offline.
5068
+ * Oldest messages are dropped when exceeded. (default: 100)
5069
+ */
5070
+ offlineQueueMaxSize?: number;
5017
5071
  }
5018
5072
  interface RateLimitOptions {
5019
5073
  /** Maximum number of messages allowed within the window */
@@ -5109,6 +5163,36 @@ interface ServerOptions {
5109
5163
  * - `LoggingConfig` → custom configuration
5110
5164
  */
5111
5165
  logging?: LoggingConfig | false;
5166
+ /**
5167
+ * Connection-level rate limiting (per-IP) applied at the HTTP upgrade boundary,
5168
+ * before any auth, TLS or JSON parsing occurs — blocks DDoS connection floods in ~1µs.
5169
+ * - `limit`: Max upgrade requests per IP within `windowMs` (default: 20)
5170
+ * - `windowMs`: Sliding window in ms (default: 10000)
5171
+ */
5172
+ connectionRateLimit?: {
5173
+ limit: number;
5174
+ windowMs: number;
5175
+ };
5176
+ /**
5177
+ * Maximum number of inactive sessions to retain in the bounded LRU cache.
5178
+ * Prevents OOM under DDoS or reconnection storms with transient identities.
5179
+ * (default: 50000)
5180
+ */
5181
+ maxSessions?: number;
5182
+ /**
5183
+ * Enable the built-in HTTP health/metrics endpoint.
5184
+ * When enabled, non-upgrade HTTP requests to `/health` return a JSON health check,
5185
+ * and requests to `/metrics` return Prometheus-compatible text metrics.
5186
+ * (default: false)
5187
+ */
5188
+ healthEndpoint?: boolean;
5189
+ /**
5190
+ * I1: Maximum WebSocket payload size in bytes. Messages exceeding this limit
5191
+ * are rejected at the transport layer before JSON parsing, preventing OOM
5192
+ * from oversized or malicious payloads.
5193
+ * (default: 65536 / 64KB — sufficient for any standard OCPP message)
5194
+ */
5195
+ maxPayloadBytes?: number;
5112
5196
  }
5113
5197
  interface OCPPServerStats {
5114
5198
  /** Number of currently connected WebSockets */
@@ -5182,6 +5266,22 @@ interface ClientEvents {
5182
5266
  }];
5183
5267
  }
5184
5268
 
5269
+ /**
5270
+ * I3: Structured security event for SIEM integration.
5271
+ * Emitted by the server for audit-relevant actions.
5272
+ */
5273
+ interface SecurityEvent {
5274
+ /** Event type identifier */
5275
+ type: "AUTH_FAILED" | "RATE_LIMIT_EXCEEDED" | "UPGRADE_ABORTED" | "CONNECTION_RATE_LIMIT" | "INVALID_PAYLOAD";
5276
+ /** Station identity (if known) */
5277
+ identity?: string;
5278
+ /** Remote IP address */
5279
+ ip?: string;
5280
+ /** ISO 8601 timestamp */
5281
+ timestamp: string;
5282
+ /** Event-specific details */
5283
+ details?: Record<string, unknown>;
5284
+ }
5185
5285
  interface ServerEvents {
5186
5286
  client: [OCPPServerClient];
5187
5287
  error: [Error];
@@ -5199,6 +5299,8 @@ interface ServerEvents {
5199
5299
  ];
5200
5300
  closing: [];
5201
5301
  close: [];
5302
+ /** I3: Structured security event for SIEM/audit pipelines */
5303
+ securityEvent: [SecurityEvent];
5202
5304
  connection: [
5203
5305
  socket: WebSocket.WebSocket,
5204
5306
  request: node_http.IncomingMessage
@@ -5219,6 +5321,15 @@ interface EventAdapterInterface {
5219
5321
  getPresence?(identity: string): Promise<string | null>;
5220
5322
  getPresenceBatch?(identities: string[]): Promise<(string | null)[]>;
5221
5323
  removePresence?(identity: string): Promise<void>;
5324
+ /**
5325
+ * Batch set multiple presence entries in a single pipeline.
5326
+ * Reduces N network round-trips to 1 for bulk presence updates.
5327
+ */
5328
+ setPresenceBatch?(entries: {
5329
+ identity: string;
5330
+ nodeId: string;
5331
+ ttl?: number;
5332
+ }[]): Promise<void>;
5222
5333
  metrics?(): Promise<Record<string, unknown>>;
5223
5334
  }
5224
5335
  declare const NOREPLY: unique symbol;
@@ -5287,6 +5398,16 @@ interface RedisAdapterOptions {
5287
5398
  prefix?: string;
5288
5399
  /** StreamMaxLen for trimming (default: 1000) */
5289
5400
  streamMaxLen?: number;
5401
+ /**
5402
+ * TTL in seconds for ephemeral stream keys (default: 300).
5403
+ * Prevents abandoned channel keys from leaking memory in Redis.
5404
+ */
5405
+ streamTtlSeconds?: number;
5406
+ /**
5407
+ * Presence TTL in seconds (default: 300).
5408
+ * Used for batch presence heartbeat pipeline.
5409
+ */
5410
+ presenceTtlSeconds?: number;
5290
5411
  }
5291
5412
  /**
5292
5413
  * Redis adapter for cross-process event distribution.
@@ -5298,11 +5419,17 @@ declare class RedisAdapter implements EventAdapterInterface {
5298
5419
  private _driver;
5299
5420
  private _prefix;
5300
5421
  private _streamMaxLen;
5422
+ private _streamTtlSeconds;
5423
+ private _presenceTtlSeconds;
5301
5424
  private _handlers;
5302
5425
  private _streamOffsets;
5303
5426
  private _streams;
5304
5427
  private _polling;
5305
5428
  private _closed;
5429
+ private _sequenceCounters;
5430
+ private _unsubError?;
5431
+ private _unsubReconnect?;
5432
+ private _presenceCache;
5306
5433
  constructor(options: RedisAdapterOptions);
5307
5434
  publish(channel: string, data: unknown): Promise<void>;
5308
5435
  publishBatch(messages: {
@@ -5320,6 +5447,20 @@ declare class RedisAdapter implements EventAdapterInterface {
5320
5447
  getPresenceBatch(identities: string[]): Promise<(string | null)[]>;
5321
5448
  removePresence(identity: string): Promise<void>;
5322
5449
  metrics(): Promise<Record<string, unknown>>;
5450
+ /**
5451
+ * Set multiple presence entries in a single Redis pipeline.
5452
+ * Reduces N network round-trips to 1 for bulk presence updates.
5453
+ */
5454
+ setPresenceBatch(entries: {
5455
+ identity: string;
5456
+ nodeId: string;
5457
+ ttl?: number;
5458
+ }[]): Promise<void>;
5459
+ /**
5460
+ * Re-syncs all cached presence entries to Redis after a reconnection.
5461
+ * Called automatically when the Redis client reconnects.
5462
+ */
5463
+ private _rehydratePresence;
5323
5464
  }
5324
5465
 
5325
- export { createValidator as $, type AuthCallback as A, type OCPP16Methods as B, type ConnectionMiddleware as C, type OCPP201Methods as D, type EventAdapterInterface as E, type OCPP21Methods as F, type OCPPCall as G, type HandlerContext as H, type OCPPCallError as I, type OCPPCallResult as J, OCPPClient as K, type LoggerLike as L, type MiddlewareFunction as M, NOREPLY as N, type OCPPProtocol as O, type OCPPMessage as P, type OCPPMethodMap as Q, type RouterConfig as R, type ServerEvents as S, type TypedEventEmitter as T, type OCPPProtocolKey as U, Validator as V, RedisAdapter as W, SecurityProfile as X, type SessionData as Y, type TLSOptions as Z, type WildcardHandler as _, type LoggingConfig as a, type RedisAdapterOptions as a0, type MiddlewareContext as b, type CORSOptions as c, type AllMethodNames as d, type RouterHandlerContext as e, type OCPPRequestType as f, type OCPPResponseType as g, type RouterWildcardHandler as h, type ServerOptions as i, type LoggerLikeNotOptional as j, OCPPServerClient as k, type OCPPServerStats as l, type ListenOptions as m, type CloseOptions as n, type CallOptions as o, type AnyOCPPProtocol as p, type AuthAccept as q, type CallHandler as r, type ClientEvents as s, type ClientOptions as t, type ConnectionContext as u, ConnectionState as v, type HandshakeInfo as w, MessageType as x, type MiddlewareNext as y, MiddlewareStack as z };
5466
+ export { createValidator as $, type AuthCallback as A, MiddlewareStack as B, type ConnectionMiddleware as C, type OCPP16Methods as D, type EventAdapterInterface as E, type OCPP201Methods as F, type OCPP21Methods as G, type HandlerContext as H, type OCPPCall as I, type OCPPCallError as J, type OCPPCallResult as K, type LoggerLike as L, type MiddlewareFunction as M, NOREPLY as N, type OCPPProtocol as O, OCPPClient as P, type OCPPMessage as Q, type RouterConfig as R, type ServerEvents as S, type TypedEventEmitter as T, type OCPPMethodMap as U, Validator as V, type OCPPProtocolKey as W, RedisAdapter as X, SecurityProfile as Y, type SessionData as Z, type WildcardHandler as _, type LoggingConfig as a, type RedisAdapterOptions as a0, type MiddlewareContext as b, type CORSOptions as c, type AllMethodNames as d, type RouterHandlerContext as e, type OCPPRequestType as f, type OCPPResponseType as g, type RouterWildcardHandler as h, type ServerOptions as i, type LoggerLikeNotOptional as j, OCPPServerClient as k, type OCPPServerStats as l, type ListenOptions as m, type TLSOptions as n, type CloseOptions as o, type CallOptions as p, type AnyOCPPProtocol as q, type AuthAccept as r, type CallHandler as s, type ClientEvents as t, type ClientOptions as u, type ConnectionContext as v, ConnectionState as w, type HandshakeInfo as x, MessageType as y, type MiddlewareNext as z };
@@ -4486,6 +4486,7 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4486
4486
  private _badMessageCount;
4487
4487
  private _lastActivity;
4488
4488
  private _outboundBuffer;
4489
+ private _offlineQueue;
4489
4490
  private _middleware;
4490
4491
  private _validators;
4491
4492
  private _strictProtocols;
@@ -4557,7 +4558,7 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4557
4558
  *
4558
4559
  * @throws {Error} If a handler for this version and method is already registered on this client instance.
4559
4560
  */
4560
- handle<V extends OCPPProtocol, M extends AllMethodNames<V>>(version: V, method: M, handler: (context: HandlerContext<OCPPRequestType<V, M>>) => OCPPResponseType<V, M> | Promise<OCPPResponseType<V, M>>): void;
4561
+ handle<V extends OCPPProtocol, M extends AllMethodNames<V>>(version: V, method: M, handler: (context: HandlerContext<OCPPRequestType<V, M>>) => OCPPResponseType<V, M> | Promise<OCPPResponseType<V, M>> | typeof NOREPLY): void;
4561
4562
  /**
4562
4563
  * Register a handler for a custom/extension protocol/method not in the typed OCPP method maps.
4563
4564
  * `handle("my-protocol", "my-method", handler)`
@@ -4573,7 +4574,7 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4573
4574
  *
4574
4575
  * @throws {Error} If a handler for this method is already registered on this client instance.
4575
4576
  */
4576
- handle<M extends AllMethodNames<P>>(method: M, handler: (context: HandlerContext<OCPPRequestType<P, M>>) => OCPPResponseType<P, M> | Promise<OCPPResponseType<P, M>>): void;
4577
+ handle<M extends AllMethodNames<P>>(method: M, handler: (context: HandlerContext<OCPPRequestType<P, M>>) => OCPPResponseType<P, M> | Promise<OCPPResponseType<P, M>> | typeof NOREPLY): void;
4577
4578
  /**
4578
4579
  * Register a handler for a custom/extension method not in the typed OCPP method maps.
4579
4580
  *
@@ -4653,6 +4654,26 @@ declare class OCPPClient<P extends OCPPProtocol = OCPPProtocol> extends OCPPClie
4653
4654
  /** Errors that should stop reconnection immediately */
4654
4655
  private static readonly _INTOLERABLE_ERRORS;
4655
4656
  private _scheduleReconnect;
4657
+ /**
4658
+ * Atomically drains the offline queue and sends each message via _sendCall.
4659
+ * Uses splice(0) to prevent re-entry bugs (double billing) if the connection
4660
+ * drops again mid-flush — the queue is empty before any sends begin.
4661
+ */
4662
+ private _flushOfflineQueue;
4663
+ /**
4664
+ * Retry wrapper using Full Jitter exponential backoff.
4665
+ * delay = random(0, min(retryMaxDelayMs, retryDelayMs * 2^attempt))
4666
+ * Only retries on TimeoutError — all other errors propagate immediately.
4667
+ */
4668
+ private _callWithRetry;
4669
+ /** Maximum bytes allowed in the ws send buffer before applying backpressure (512KB) */
4670
+ private static readonly _BACKPRESSURE_THRESHOLD;
4671
+ /**
4672
+ * Wraps ws.send() with backpressure protection.
4673
+ * If bufferedAmount exceeds the threshold, waits for the buffer to drain
4674
+ * before sending. Prevents OOM on slow 2G/3G charger connections.
4675
+ */
4676
+ private _safeSend;
4656
4677
  private _startPing;
4657
4678
  private _stopPing;
4658
4679
  private _recordActivity;
@@ -4719,6 +4740,9 @@ interface ValidatorSchema {
4719
4740
  /**
4720
4741
  * Schema validator using AJV for OCPP message validation.
4721
4742
  * Each validator is bound to a specific subprotocol version.
4743
+ *
4744
+ * E2: Schemas are registered at construction time but compiled lazily
4745
+ * on first use, reducing startup time from ~400ms to ~5ms.
4722
4746
  */
4723
4747
  declare class Validator {
4724
4748
  readonly subprotocol: string;
@@ -4732,6 +4756,8 @@ declare class Validator {
4732
4756
  /**
4733
4757
  * Validate a payload against a schema identified by its $id.
4734
4758
  * Throws a typed RPCError if validation fails.
4759
+ *
4760
+ * E2: Schema is compiled on first call to this method (lazy).
4735
4761
  */
4736
4762
  validate(schemaId: string, params: unknown): void;
4737
4763
  /**
@@ -4740,7 +4766,9 @@ declare class Validator {
4740
4766
  hasSchema(schemaId: string): boolean;
4741
4767
  }
4742
4768
  /**
4743
- * Create a validator for a specific subprotocol version.
4769
+ * Create or retrieve a cached validator for a specific subprotocol version.
4770
+ * E5: Returns an existing instance if one was already created for this subprotocol,
4771
+ * preventing duplicate AJV instances and saving memory.
4744
4772
  */
4745
4773
  declare function createValidator(subprotocol: string, schemas: ValidatorSchema[]): Validator;
4746
4774
 
@@ -4829,6 +4857,22 @@ interface CallOptions {
4829
4857
  timeoutMs?: number;
4830
4858
  /** Abort signal */
4831
4859
  signal?: AbortSignal;
4860
+ /**
4861
+ * Max retry attempts on TimeoutError (default: 0 = no retry).
4862
+ * Uses Full Jitter exponential backoff between retries.
4863
+ */
4864
+ retries?: number;
4865
+ /** Base delay in ms for exponential backoff between retries (default: 1000) */
4866
+ retryDelayMs?: number;
4867
+ /** Max delay cap in ms to prevent unbounded backoff (default: 30000) */
4868
+ retryMaxDelayMs?: number;
4869
+ /**
4870
+ * Idempotency key for deduplication. If provided, this value is used
4871
+ * as the OCPP messageId instead of generating a new random one.
4872
+ * Consumers can use the same key to guarantee exactly-once semantics
4873
+ * when retrying calls across reconnections.
4874
+ */
4875
+ idempotencyKey?: string;
4832
4876
  }
4833
4877
  interface CloseOptions {
4834
4878
  /** WebSocket close code (default: 1000) */
@@ -5014,6 +5058,16 @@ interface ClientOptions {
5014
5058
  logging?: LoggingConfig | false;
5015
5059
  /** Rate Limiting configuration (Token Bucket) */
5016
5060
  rateLimit?: RateLimitOptions;
5061
+ /**
5062
+ * If true, calls made while disconnected are queued in-memory
5063
+ * and flushed automatically on reconnect. (default: false)
5064
+ */
5065
+ offlineQueue?: boolean;
5066
+ /**
5067
+ * Maximum number of messages to queue while offline.
5068
+ * Oldest messages are dropped when exceeded. (default: 100)
5069
+ */
5070
+ offlineQueueMaxSize?: number;
5017
5071
  }
5018
5072
  interface RateLimitOptions {
5019
5073
  /** Maximum number of messages allowed within the window */
@@ -5109,6 +5163,36 @@ interface ServerOptions {
5109
5163
  * - `LoggingConfig` → custom configuration
5110
5164
  */
5111
5165
  logging?: LoggingConfig | false;
5166
+ /**
5167
+ * Connection-level rate limiting (per-IP) applied at the HTTP upgrade boundary,
5168
+ * before any auth, TLS or JSON parsing occurs — blocks DDoS connection floods in ~1µs.
5169
+ * - `limit`: Max upgrade requests per IP within `windowMs` (default: 20)
5170
+ * - `windowMs`: Sliding window in ms (default: 10000)
5171
+ */
5172
+ connectionRateLimit?: {
5173
+ limit: number;
5174
+ windowMs: number;
5175
+ };
5176
+ /**
5177
+ * Maximum number of inactive sessions to retain in the bounded LRU cache.
5178
+ * Prevents OOM under DDoS or reconnection storms with transient identities.
5179
+ * (default: 50000)
5180
+ */
5181
+ maxSessions?: number;
5182
+ /**
5183
+ * Enable the built-in HTTP health/metrics endpoint.
5184
+ * When enabled, non-upgrade HTTP requests to `/health` return a JSON health check,
5185
+ * and requests to `/metrics` return Prometheus-compatible text metrics.
5186
+ * (default: false)
5187
+ */
5188
+ healthEndpoint?: boolean;
5189
+ /**
5190
+ * I1: Maximum WebSocket payload size in bytes. Messages exceeding this limit
5191
+ * are rejected at the transport layer before JSON parsing, preventing OOM
5192
+ * from oversized or malicious payloads.
5193
+ * (default: 65536 / 64KB — sufficient for any standard OCPP message)
5194
+ */
5195
+ maxPayloadBytes?: number;
5112
5196
  }
5113
5197
  interface OCPPServerStats {
5114
5198
  /** Number of currently connected WebSockets */
@@ -5182,6 +5266,22 @@ interface ClientEvents {
5182
5266
  }];
5183
5267
  }
5184
5268
 
5269
+ /**
5270
+ * I3: Structured security event for SIEM integration.
5271
+ * Emitted by the server for audit-relevant actions.
5272
+ */
5273
+ interface SecurityEvent {
5274
+ /** Event type identifier */
5275
+ type: "AUTH_FAILED" | "RATE_LIMIT_EXCEEDED" | "UPGRADE_ABORTED" | "CONNECTION_RATE_LIMIT" | "INVALID_PAYLOAD";
5276
+ /** Station identity (if known) */
5277
+ identity?: string;
5278
+ /** Remote IP address */
5279
+ ip?: string;
5280
+ /** ISO 8601 timestamp */
5281
+ timestamp: string;
5282
+ /** Event-specific details */
5283
+ details?: Record<string, unknown>;
5284
+ }
5185
5285
  interface ServerEvents {
5186
5286
  client: [OCPPServerClient];
5187
5287
  error: [Error];
@@ -5199,6 +5299,8 @@ interface ServerEvents {
5199
5299
  ];
5200
5300
  closing: [];
5201
5301
  close: [];
5302
+ /** I3: Structured security event for SIEM/audit pipelines */
5303
+ securityEvent: [SecurityEvent];
5202
5304
  connection: [
5203
5305
  socket: WebSocket.WebSocket,
5204
5306
  request: node_http.IncomingMessage
@@ -5219,6 +5321,15 @@ interface EventAdapterInterface {
5219
5321
  getPresence?(identity: string): Promise<string | null>;
5220
5322
  getPresenceBatch?(identities: string[]): Promise<(string | null)[]>;
5221
5323
  removePresence?(identity: string): Promise<void>;
5324
+ /**
5325
+ * Batch set multiple presence entries in a single pipeline.
5326
+ * Reduces N network round-trips to 1 for bulk presence updates.
5327
+ */
5328
+ setPresenceBatch?(entries: {
5329
+ identity: string;
5330
+ nodeId: string;
5331
+ ttl?: number;
5332
+ }[]): Promise<void>;
5222
5333
  metrics?(): Promise<Record<string, unknown>>;
5223
5334
  }
5224
5335
  declare const NOREPLY: unique symbol;
@@ -5287,6 +5398,16 @@ interface RedisAdapterOptions {
5287
5398
  prefix?: string;
5288
5399
  /** StreamMaxLen for trimming (default: 1000) */
5289
5400
  streamMaxLen?: number;
5401
+ /**
5402
+ * TTL in seconds for ephemeral stream keys (default: 300).
5403
+ * Prevents abandoned channel keys from leaking memory in Redis.
5404
+ */
5405
+ streamTtlSeconds?: number;
5406
+ /**
5407
+ * Presence TTL in seconds (default: 300).
5408
+ * Used for batch presence heartbeat pipeline.
5409
+ */
5410
+ presenceTtlSeconds?: number;
5290
5411
  }
5291
5412
  /**
5292
5413
  * Redis adapter for cross-process event distribution.
@@ -5298,11 +5419,17 @@ declare class RedisAdapter implements EventAdapterInterface {
5298
5419
  private _driver;
5299
5420
  private _prefix;
5300
5421
  private _streamMaxLen;
5422
+ private _streamTtlSeconds;
5423
+ private _presenceTtlSeconds;
5301
5424
  private _handlers;
5302
5425
  private _streamOffsets;
5303
5426
  private _streams;
5304
5427
  private _polling;
5305
5428
  private _closed;
5429
+ private _sequenceCounters;
5430
+ private _unsubError?;
5431
+ private _unsubReconnect?;
5432
+ private _presenceCache;
5306
5433
  constructor(options: RedisAdapterOptions);
5307
5434
  publish(channel: string, data: unknown): Promise<void>;
5308
5435
  publishBatch(messages: {
@@ -5320,6 +5447,20 @@ declare class RedisAdapter implements EventAdapterInterface {
5320
5447
  getPresenceBatch(identities: string[]): Promise<(string | null)[]>;
5321
5448
  removePresence(identity: string): Promise<void>;
5322
5449
  metrics(): Promise<Record<string, unknown>>;
5450
+ /**
5451
+ * Set multiple presence entries in a single Redis pipeline.
5452
+ * Reduces N network round-trips to 1 for bulk presence updates.
5453
+ */
5454
+ setPresenceBatch(entries: {
5455
+ identity: string;
5456
+ nodeId: string;
5457
+ ttl?: number;
5458
+ }[]): Promise<void>;
5459
+ /**
5460
+ * Re-syncs all cached presence entries to Redis after a reconnection.
5461
+ * Called automatically when the Redis client reconnects.
5462
+ */
5463
+ private _rehydratePresence;
5323
5464
  }
5324
5465
 
5325
- export { createValidator as $, type AuthCallback as A, type OCPP16Methods as B, type ConnectionMiddleware as C, type OCPP201Methods as D, type EventAdapterInterface as E, type OCPP21Methods as F, type OCPPCall as G, type HandlerContext as H, type OCPPCallError as I, type OCPPCallResult as J, OCPPClient as K, type LoggerLike as L, type MiddlewareFunction as M, NOREPLY as N, type OCPPProtocol as O, type OCPPMessage as P, type OCPPMethodMap as Q, type RouterConfig as R, type ServerEvents as S, type TypedEventEmitter as T, type OCPPProtocolKey as U, Validator as V, RedisAdapter as W, SecurityProfile as X, type SessionData as Y, type TLSOptions as Z, type WildcardHandler as _, type LoggingConfig as a, type RedisAdapterOptions as a0, type MiddlewareContext as b, type CORSOptions as c, type AllMethodNames as d, type RouterHandlerContext as e, type OCPPRequestType as f, type OCPPResponseType as g, type RouterWildcardHandler as h, type ServerOptions as i, type LoggerLikeNotOptional as j, OCPPServerClient as k, type OCPPServerStats as l, type ListenOptions as m, type CloseOptions as n, type CallOptions as o, type AnyOCPPProtocol as p, type AuthAccept as q, type CallHandler as r, type ClientEvents as s, type ClientOptions as t, type ConnectionContext as u, ConnectionState as v, type HandshakeInfo as w, MessageType as x, type MiddlewareNext as y, MiddlewareStack as z };
5466
+ export { createValidator as $, type AuthCallback as A, MiddlewareStack as B, type ConnectionMiddleware as C, type OCPP16Methods as D, type EventAdapterInterface as E, type OCPP201Methods as F, type OCPP21Methods as G, type HandlerContext as H, type OCPPCall as I, type OCPPCallError as J, type OCPPCallResult as K, type LoggerLike as L, type MiddlewareFunction as M, NOREPLY as N, type OCPPProtocol as O, OCPPClient as P, type OCPPMessage as Q, type RouterConfig as R, type ServerEvents as S, type TypedEventEmitter as T, type OCPPMethodMap as U, Validator as V, type OCPPProtocolKey as W, RedisAdapter as X, SecurityProfile as Y, type SessionData as Z, type WildcardHandler as _, type LoggingConfig as a, type RedisAdapterOptions as a0, type MiddlewareContext as b, type CORSOptions as c, type AllMethodNames as d, type RouterHandlerContext as e, type OCPPRequestType as f, type OCPPResponseType as g, type RouterWildcardHandler as h, type ServerOptions as i, type LoggerLikeNotOptional as j, OCPPServerClient as k, type OCPPServerStats as l, type ListenOptions as m, type TLSOptions as n, type CloseOptions as o, type CallOptions as p, type AnyOCPPProtocol as q, type AuthAccept as r, type CallHandler as s, type ClientEvents as t, type ClientOptions as u, type ConnectionContext as v, ConnectionState as w, type HandshakeInfo as x, MessageType as y, type MiddlewareNext as z };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { E as EventAdapterInterface, A as AuthCallback, L as LoggerLike, a as LoggingConfig, M as MiddlewareFunction, b as MiddlewareContext, C as ConnectionMiddleware, T as TypedEventEmitter, S as ServerEvents, c as CORSOptions, R as RouterConfig, O as OCPPProtocol, d as AllMethodNames, e as RouterHandlerContext, f as OCPPRequestType, g as OCPPResponseType, h as RouterWildcardHandler, i as ServerOptions, j as LoggerLikeNotOptional, k as OCPPServerClient, l as OCPPServerStats, m as ListenOptions, n as CloseOptions, o as CallOptions, V as Validator } from './index-BixJj_yJ.mjs';
2
- export { p as AnyOCPPProtocol, q as AuthAccept, r as CallHandler, s as ClientEvents, t as ClientOptions, u as ConnectionContext, v as ConnectionState, H as HandlerContext, w as HandshakeInfo, x as MessageType, y as MiddlewareNext, z as MiddlewareStack, N as NOREPLY, B as OCPP16Methods, D as OCPP201Methods, F as OCPP21Methods, G as OCPPCall, I as OCPPCallError, J as OCPPCallResult, K as OCPPClient, P as OCPPMessage, Q as OCPPMethodMap, U as OCPPProtocolKey, W as RedisAdapter, X as SecurityProfile, Y as SessionData, Z as TLSOptions, _ as WildcardHandler, $ as createValidator } from './index-BixJj_yJ.mjs';
1
+ import { E as EventAdapterInterface, A as AuthCallback, L as LoggerLike, a as LoggingConfig, M as MiddlewareFunction, b as MiddlewareContext, C as ConnectionMiddleware, T as TypedEventEmitter, S as ServerEvents, c as CORSOptions, R as RouterConfig, O as OCPPProtocol, d as AllMethodNames, e as RouterHandlerContext, f as OCPPRequestType, g as OCPPResponseType, h as RouterWildcardHandler, i as ServerOptions, j as LoggerLikeNotOptional, k as OCPPServerClient, l as OCPPServerStats, m as ListenOptions, n as TLSOptions, o as CloseOptions, p as CallOptions, V as Validator } from './index-1QBeqAuc.mjs';
2
+ export { q as AnyOCPPProtocol, r as AuthAccept, s as CallHandler, t as ClientEvents, u as ClientOptions, v as ConnectionContext, w as ConnectionState, H as HandlerContext, x as HandshakeInfo, y as MessageType, z as MiddlewareNext, B as MiddlewareStack, N as NOREPLY, D as OCPP16Methods, F as OCPP201Methods, G as OCPP21Methods, I as OCPPCall, J as OCPPCallError, K as OCPPCallResult, P as OCPPClient, Q as OCPPMessage, U as OCPPMethodMap, W as OCPPProtocolKey, X as RedisAdapter, Y as SecurityProfile, Z as SessionData, _ as WildcardHandler, $ as createValidator } from './index-1QBeqAuc.mjs';
3
3
  import { Server, IncomingMessage } from 'node:http';
4
4
  import { Duplex } from 'node:stream';
5
5
  import 'ws';
@@ -28,6 +28,11 @@ declare class InMemoryAdapter implements EventAdapterInterface {
28
28
  getPresence(identity: string): Promise<string | null>;
29
29
  getPresenceBatch(identities: string[]): Promise<(string | null)[]>;
30
30
  removePresence(identity: string): Promise<void>;
31
+ setPresenceBatch(entries: {
32
+ identity: string;
33
+ nodeId: string;
34
+ ttl?: number;
35
+ }[]): Promise<void>;
31
36
  }
32
37
  /**
33
38
  * Helper function to create a custom EventAdapter without needing to define a rigid Class.
@@ -160,6 +165,38 @@ declare function combineAuth(...cbs: AuthCallback[]): AuthCallback;
160
165
  */
161
166
  declare function createLoggingMiddleware(logger: LoggerLike, identity: string, config?: LoggingConfig | boolean): MiddlewareFunction<MiddlewareContext, any>;
162
167
 
168
+ /**
169
+ * LRUMap — A zero-dependency, drop-in Map replacement with a maximum capacity.
170
+ *
171
+ * Evicts the **least recently used** entry when the capacity is exceeded.
172
+ * Uses native Map insertion-order semantics for O(1) LRU tracking
173
+ * (delete + re-insert moves a key to the "most recent" end).
174
+ *
175
+ * @remarks
176
+ * This is used by OCPPServer to bound the `_sessions` map and prevent OOM under
177
+ * DDoS or reconnection storms with transient identities.
178
+ */
179
+ declare class LRUMap<K, V> extends Map<K, V> {
180
+ private _maxSize;
181
+ constructor(maxSize: number);
182
+ /**
183
+ * Returns the configured maximum capacity of this LRU cache.
184
+ */
185
+ get maxSize(): number;
186
+ /**
187
+ * Sets a key-value pair. If the key already exists, it is promoted to the
188
+ * most-recently-used position. If inserting a new key would exceed capacity,
189
+ * the oldest (least-recently-used) entry is evicted.
190
+ */
191
+ set(key: K, value: V): this;
192
+ /**
193
+ * Gets a value by key and promotes it to the most-recently-used position.
194
+ * Uses `this.has(key)` instead of a value truthiness check to correctly
195
+ * handle stored values of `undefined`, `null`, `0`, `""`, etc.
196
+ */
197
+ get(key: K): V | undefined;
198
+ }
199
+
163
200
  /**
164
201
  * Compiled regex pattern for RegExp-based route fallback.
165
202
  * Only used when a user registers a RegExp pattern (not string patterns).
@@ -280,6 +317,7 @@ declare class OCPPServer extends OCPPServer_base {
280
317
  private _httpServerAbortControllers;
281
318
  private _logger;
282
319
  private _globalCORS?;
320
+ private _connectionBuckets;
283
321
  private readonly _nodeId;
284
322
  private _sessions;
285
323
  private _gcInterval;
@@ -361,6 +399,28 @@ declare class OCPPServer extends OCPPServer_base {
361
399
  */
362
400
  private _registerRouter;
363
401
  listen(port?: number, host?: string, options?: ListenOptions): Promise<Server>;
402
+ /**
403
+ * Hot-reloads the TLS certificate on all active HTTPS servers without
404
+ * dropping any existing WebSocket connections.
405
+ *
406
+ * **When to use:** Call this whenever your TLS certificate is renewed —
407
+ * for example, after a Let's Encrypt auto-renewal (every ~90 days).
408
+ * Without this, you would need to restart the Node.js process to pick up
409
+ * the new certificate, disconnecting all connected charging stations.
410
+ *
411
+ * **How to use:**
412
+ * ```ts
413
+ * server.updateTLS({ cert: newCert, key: newKey });
414
+ * ```
415
+ *
416
+ * **Optional:** Only relevant if you are terminating TLS directly in Node.js
417
+ * (i.e. `SecurityProfile.TLS_BASIC_AUTH` or `TLS_CLIENT_CERT`). If you are
418
+ * running behind a reverse proxy (Nginx, AWS ALB, etc.) that handles TLS,
419
+ * you do not need this method — just rotate the cert on the proxy.
420
+ *
421
+ * @throws If the server is not using a TLS Security Profile.
422
+ */
423
+ updateTLS(tlsOpts: TLSOptions): void;
364
424
  get handleUpgrade(): (req: IncomingMessage, socket: Duplex, head: Buffer) => Promise<void>;
365
425
  /**
366
426
  * Core upgrade handler. Follows a strict pipeline:
@@ -418,11 +478,7 @@ declare class OCPPServer extends OCPPServer_base {
418
478
  broadcastBatch<V extends AllMethodNames<any>>(identities: string[], method: V, params: OCPPRequestType<any, V>, options?: CallOptions): Promise<void>;
419
479
  }
420
480
 
421
- /**
422
- * Pre-built validators for all supported OCPP protocol versions.
423
- * These are automatically registered when strict mode is enabled.
424
- */
425
- declare const standardValidators: Validator[];
481
+ declare function getStandardValidators(): Validator[];
426
482
 
427
483
  /**
428
484
  * Instantiate a typed RPCError from a string error code.
@@ -446,4 +502,4 @@ declare function getErrorPlainObject(err: Error): Record<string, unknown>;
446
502
  */
447
503
  declare function getPackageIdent(): string;
448
504
 
449
- export { AllMethodNames, AuthCallback, CORSOptions, CallOptions, CloseOptions, ConnectionMiddleware, EventAdapterInterface, InMemoryAdapter, ListenOptions, LoggerLike, LoggingConfig, MiddlewareFunction, OCPPProtocol, OCPPRequestType, OCPPResponseType, OCPPRouter, OCPPServer, OCPPServerClient, type RPCError, RPCFormatViolationError, RPCFormationViolationError, RPCFrameworkError, RPCGenericError, RPCInternalError, RPCMessageTypeNotSupportedError, RPCNotImplementedError, RPCNotSupportedError, RPCOccurrenceConstraintViolationError, RPCPropertyConstraintViolationError, RPCProtocolError, RPCSecurityError, RPCTypeConstraintViolationError, RouterConfig, ServerEvents, ServerOptions, TimeoutError, TypedEventEmitter, UnexpectedHttpResponse, Validator, WebsocketUpgradeError, combineAuth, createLoggingMiddleware, createRPCError, createRouter, defineAdapter, defineAuth, defineMiddleware, defineRpcMiddleware, getErrorPlainObject, getPackageIdent, standardValidators };
505
+ export { AllMethodNames, AuthCallback, CORSOptions, CallOptions, CloseOptions, ConnectionMiddleware, EventAdapterInterface, InMemoryAdapter, LRUMap, ListenOptions, LoggerLike, LoggingConfig, MiddlewareFunction, OCPPProtocol, OCPPRequestType, OCPPResponseType, OCPPRouter, OCPPServer, OCPPServerClient, type RPCError, RPCFormatViolationError, RPCFormationViolationError, RPCFrameworkError, RPCGenericError, RPCInternalError, RPCMessageTypeNotSupportedError, RPCNotImplementedError, RPCNotSupportedError, RPCOccurrenceConstraintViolationError, RPCPropertyConstraintViolationError, RPCProtocolError, RPCSecurityError, RPCTypeConstraintViolationError, RouterConfig, ServerEvents, ServerOptions, TLSOptions, TimeoutError, TypedEventEmitter, UnexpectedHttpResponse, Validator, WebsocketUpgradeError, combineAuth, createLoggingMiddleware, createRPCError, createRouter, defineAdapter, defineAuth, defineMiddleware, defineRpcMiddleware, getErrorPlainObject, getPackageIdent, getStandardValidators };