ocpp-ws-io 2.1.4 → 2.1.6

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.
package/dist/index.mjs CHANGED
@@ -310,9 +310,9 @@ var RedisAdapter = class {
310
310
  // Active streams to poll
311
311
  _polling = false;
312
312
  _closed = false;
313
- // C4: Per-stream sequence counter for message ordering
313
+ // Per-stream sequence counter for message ordering
314
314
  _sequenceCounters = /* @__PURE__ */ new Map();
315
- // C3: Rehydration callbacks
315
+ // Rehydration callbacks
316
316
  _unsubError;
317
317
  _unsubReconnect;
318
318
  // Stored presence entries for rehydration on reconnect
@@ -36919,6 +36919,8 @@ var Validator = class {
36919
36919
  /**
36920
36920
  * Validate a payload against a schema identified by its $id.
36921
36921
  * Throws a typed RPCError if validation fails.
36922
+ *
36923
+ * E2: Schema is compiled on first call to this method (lazy).
36922
36924
  */
36923
36925
  validate(schemaId, params) {
36924
36926
  const resolvedId = this._normalizeSchemaId(schemaId);
@@ -36941,16 +36943,26 @@ var Validator = class {
36941
36943
  return !!this._ajv.getSchema(this._normalizeSchemaId(schemaId));
36942
36944
  }
36943
36945
  };
36946
+ var _validatorRegistry = /* @__PURE__ */ new Map();
36944
36947
  function createValidator(subprotocol, schemas) {
36945
- return new Validator(subprotocol, schemas);
36948
+ const existing = _validatorRegistry.get(subprotocol);
36949
+ if (existing) return existing;
36950
+ const validator = new Validator(subprotocol, schemas);
36951
+ _validatorRegistry.set(subprotocol, validator);
36952
+ return validator;
36946
36953
  }
36947
36954
 
36948
36955
  // src/standard-validators.ts
36949
- var standardValidators = [
36950
- createValidator("ocpp1.6", ocpp1_6_default),
36951
- createValidator("ocpp2.0.1", ocpp2_0_1_default),
36952
- createValidator("ocpp2.1", ocpp2_1_default)
36953
- ];
36956
+ var _cached = null;
36957
+ function getStandardValidators() {
36958
+ if (_cached) return _cached;
36959
+ _cached = [
36960
+ createValidator("ocpp1.6", ocpp1_6_default),
36961
+ createValidator("ocpp2.0.1", ocpp2_0_1_default),
36962
+ createValidator("ocpp2.1", ocpp2_1_default)
36963
+ ];
36964
+ return _cached;
36965
+ }
36954
36966
 
36955
36967
  // src/types.ts
36956
36968
  var ConnectionState = {
@@ -37143,6 +37155,7 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
37143
37155
  _prettify = false;
37144
37156
  constructor(options) {
37145
37157
  super();
37158
+ this.setMaxListeners(0);
37146
37159
  if (!options.identity) {
37147
37160
  throw new Error("identity is required");
37148
37161
  }
@@ -37644,11 +37657,13 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
37644
37657
  this._recordActivity();
37645
37658
  let message;
37646
37659
  try {
37647
- const str = rawData.toString();
37648
- message = JSON.parse(str);
37660
+ message = JSON.parse(rawData);
37649
37661
  if (!Array.isArray(message)) throw new Error("Message is not an array");
37650
37662
  } catch (err) {
37651
- this._onBadMessage(rawData.toString(), err);
37663
+ this._onBadMessage(
37664
+ typeof rawData === "string" ? rawData : rawData.toString(),
37665
+ err
37666
+ );
37652
37667
  return;
37653
37668
  }
37654
37669
  const messageType = message[0];
@@ -37975,10 +37990,14 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
37975
37990
  }
37976
37991
  if (ws.bufferedAmount > _OCPPClient._BACKPRESSURE_THRESHOLD) {
37977
37992
  this._logger?.warn?.("Backpressure \u2014 pausing send", {
37993
+ identity: this._identity,
37978
37994
  bufferedAmount: ws.bufferedAmount,
37979
37995
  threshold: _OCPPClient._BACKPRESSURE_THRESHOLD
37980
37996
  });
37981
- this.emit("backpressure", { bufferedAmount: ws.bufferedAmount });
37997
+ this.emit("backpressure", {
37998
+ identity: this._identity,
37999
+ bufferedAmount: ws.bufferedAmount
38000
+ });
37982
38001
  let waited = 0;
37983
38002
  const drainCheck = setInterval(() => {
37984
38003
  waited += 50;
@@ -38042,7 +38061,7 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
38042
38061
  if (this._options.strictModeValidators) {
38043
38062
  this._validators = this._options.strictModeValidators;
38044
38063
  } else {
38045
- this._validators = standardValidators;
38064
+ this._validators = getStandardValidators();
38046
38065
  }
38047
38066
  if (Array.isArray(this._options.strictMode)) {
38048
38067
  this._strictProtocols = this._options.strictMode;
@@ -38598,8 +38617,7 @@ var OCPPServerClient = class extends OCPPClient {
38598
38617
  let pData;
38599
38618
  if (limits.methods) {
38600
38619
  try {
38601
- const str = data.toString();
38602
- pData = JSON.parse(str);
38620
+ pData = JSON.parse(data);
38603
38621
  if (Array.isArray(pData) && pData[0] === 2) {
38604
38622
  method = pData[2];
38605
38623
  }
@@ -38726,6 +38744,7 @@ var OCPPServer = class extends EventEmitter3 {
38726
38744
  _sessionTimeoutMs;
38727
38745
  constructor(options = {}) {
38728
38746
  super();
38747
+ this.setMaxListeners(0);
38729
38748
  if (options.strictMode) {
38730
38749
  if (!options.strictModeValidators && !options.protocols?.length) {
38731
38750
  throw new Error(
@@ -38748,7 +38767,10 @@ var OCPPServer = class extends EventEmitter3 {
38748
38767
  this._sessionTimeoutMs = this._options.sessionTtlMs;
38749
38768
  const maxSessions = this._options.maxSessions ?? 5e4;
38750
38769
  this._sessions = new LRUMap(maxSessions);
38751
- this._wss = new WebSocketServer({ noServer: true });
38770
+ this._wss = new WebSocketServer({
38771
+ noServer: true,
38772
+ maxPayload: this._options.maxPayloadBytes ?? 65536
38773
+ });
38752
38774
  this._gcInterval = setInterval(() => {
38753
38775
  const now = Date.now();
38754
38776
  for (const [identity, session] of this._sessions.entries()) {
@@ -39054,6 +39076,51 @@ var OCPPServer = class extends EventEmitter3 {
39054
39076
  }
39055
39077
  return httpServer;
39056
39078
  }
39079
+ /**
39080
+ * Hot-reloads the TLS certificate on all active HTTPS servers without
39081
+ * dropping any existing WebSocket connections.
39082
+ *
39083
+ * **When to use:** Call this whenever your TLS certificate is renewed —
39084
+ * for example, after a Let's Encrypt auto-renewal (every ~90 days).
39085
+ * Without this, you would need to restart the Node.js process to pick up
39086
+ * the new certificate, disconnecting all connected charging stations.
39087
+ *
39088
+ * **How to use:**
39089
+ * ```ts
39090
+ * server.updateTLS({ cert: newCert, key: newKey });
39091
+ * ```
39092
+ *
39093
+ * **Optional:** Only relevant if you are terminating TLS directly in Node.js
39094
+ * (i.e. `SecurityProfile.TLS_BASIC_AUTH` or `TLS_CLIENT_CERT`). If you are
39095
+ * running behind a reverse proxy (Nginx, AWS ALB, etc.) that handles TLS,
39096
+ * you do not need this method — just rotate the cert on the proxy.
39097
+ *
39098
+ * @throws If the server is not using a TLS Security Profile.
39099
+ */
39100
+ updateTLS(tlsOpts) {
39101
+ const profile = this._options.securityProfile ?? 0 /* NONE */;
39102
+ if (profile !== 2 /* TLS_BASIC_AUTH */ && profile !== 3 /* TLS_CLIENT_CERT */) {
39103
+ throw new Error(
39104
+ "updateTLS() requires a TLS Security Profile (TLS_BASIC_AUTH or TLS_CLIENT_CERT)"
39105
+ );
39106
+ }
39107
+ this._options.tls = { ...this._options.tls, ...tlsOpts };
39108
+ const httpsOptions = {};
39109
+ if (tlsOpts.cert) httpsOptions.cert = tlsOpts.cert;
39110
+ if (tlsOpts.key) httpsOptions.key = tlsOpts.key;
39111
+ if (tlsOpts.ca) httpsOptions.ca = tlsOpts.ca;
39112
+ if (tlsOpts.passphrase) httpsOptions.passphrase = tlsOpts.passphrase;
39113
+ let updated = 0;
39114
+ for (const srv of this._httpServers) {
39115
+ if ("setSecureContext" in srv && typeof srv.setSecureContext === "function") {
39116
+ srv.setSecureContext(httpsOptions);
39117
+ updated++;
39118
+ }
39119
+ }
39120
+ this._logger?.info?.(
39121
+ `TLS context hot-reloaded across ${updated} active server(s)`
39122
+ );
39123
+ }
39057
39124
  // ─── Handle Upgrade ──────────────────────────────────────────
39058
39125
  get handleUpgrade() {
39059
39126
  return (req, socket, head) => {
@@ -39107,6 +39174,12 @@ var OCPPServer = class extends EventEmitter3 {
39107
39174
  }
39108
39175
  if (bucket.tokens < 1) {
39109
39176
  this._logger?.warn?.("Connection rate limit exceeded", { ip });
39177
+ this.emit("securityEvent", {
39178
+ type: "CONNECTION_RATE_LIMIT",
39179
+ ip,
39180
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
39181
+ details: { tokensRemaining: bucket.tokens }
39182
+ });
39110
39183
  abortHandshake(socket, 429, "Too Many Requests");
39111
39184
  return;
39112
39185
  }
@@ -39363,6 +39436,13 @@ var OCPPServer = class extends EventEmitter3 {
39363
39436
  if (ac.signal.aborted) {
39364
39437
  const reason = err instanceof Error ? err.message : "Unknown abort";
39365
39438
  this._logger?.warn?.("Handshake aborted", { identity, reason });
39439
+ this.emit("securityEvent", {
39440
+ type: "UPGRADE_ABORTED",
39441
+ identity,
39442
+ ip: req.socket.remoteAddress,
39443
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
39444
+ details: { reason }
39445
+ });
39366
39446
  this.emit("upgradeAborted", {
39367
39447
  identity,
39368
39448
  reason,
@@ -39376,6 +39456,13 @@ var OCPPServer = class extends EventEmitter3 {
39376
39456
  const code = typeof errObj?.code === "number" ? errObj.code : 401;
39377
39457
  const message = typeof errObj?.message === "string" ? errObj.message : "Unauthorized";
39378
39458
  this._logger?.warn?.("Auth rejected", { identity, code });
39459
+ this.emit("securityEvent", {
39460
+ type: "AUTH_FAILED",
39461
+ identity,
39462
+ ip: req.socket.remoteAddress,
39463
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
39464
+ details: { code, message }
39465
+ });
39379
39466
  abortHandshake(socket, code, message);
39380
39467
  return;
39381
39468
  } finally {
@@ -39399,7 +39486,10 @@ var OCPPServer = class extends EventEmitter3 {
39399
39486
  return;
39400
39487
  }
39401
39488
  if (!this._wss) {
39402
- this._wss = new WebSocketServer({ noServer: true });
39489
+ this._wss = new WebSocketServer({
39490
+ noServer: true,
39491
+ maxPayload: this._options.maxPayloadBytes ?? 65536
39492
+ });
39403
39493
  }
39404
39494
  this._wss.handleUpgrade(req, socket, head, (ws) => {
39405
39495
  const clientOptions = {
@@ -39786,6 +39876,6 @@ export {
39786
39876
  defineRpcMiddleware,
39787
39877
  getErrorPlainObject,
39788
39878
  getPackageIdent,
39789
- standardValidators
39879
+ getStandardValidators
39790
39880
  };
39791
39881
  //# sourceMappingURL=index.mjs.map