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.
- package/LICENSE +21 -0
- package/README.md +32 -6
- package/dist/adapters/redis.d.mts +1 -1
- package/dist/adapters/redis.d.ts +1 -1
- package/dist/adapters/redis.js +104 -0
- package/dist/adapters/redis.js.map +1 -1
- package/dist/adapters/redis.mjs +104 -0
- package/dist/adapters/redis.mjs.map +1 -1
- package/dist/browser.d.mts +21 -0
- package/dist/browser.d.ts +21 -0
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs.map +1 -1
- package/dist/{index-BixJj_yJ.d.mts → index-1QBeqAuc.d.mts} +145 -4
- package/dist/{index-BixJj_yJ.d.ts → index-1QBeqAuc.d.ts} +145 -4
- package/dist/index.d.mts +64 -8
- package/dist/index.d.ts +64 -8
- package/dist/index.js +502 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +500 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -27
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
ConnectionState: () => ConnectionState,
|
|
34
34
|
InMemoryAdapter: () => InMemoryAdapter,
|
|
35
|
+
LRUMap: () => LRUMap,
|
|
35
36
|
MessageType: () => MessageType,
|
|
36
37
|
MiddlewareStack: () => MiddlewareStack,
|
|
37
38
|
NOREPLY: () => NOREPLY,
|
|
@@ -69,7 +70,7 @@ __export(src_exports, {
|
|
|
69
70
|
defineRpcMiddleware: () => defineRpcMiddleware,
|
|
70
71
|
getErrorPlainObject: () => getErrorPlainObject,
|
|
71
72
|
getPackageIdent: () => getPackageIdent,
|
|
72
|
-
|
|
73
|
+
getStandardValidators: () => getStandardValidators
|
|
73
74
|
});
|
|
74
75
|
module.exports = __toCommonJS(src_exports);
|
|
75
76
|
|
|
@@ -119,6 +120,11 @@ var InMemoryAdapter = class {
|
|
|
119
120
|
async removePresence(identity) {
|
|
120
121
|
this._presence.delete(identity);
|
|
121
122
|
}
|
|
123
|
+
async setPresenceBatch(entries) {
|
|
124
|
+
for (const { identity, nodeId } of entries) {
|
|
125
|
+
this._presence.set(identity, nodeId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
122
128
|
};
|
|
123
129
|
function defineAdapter(adapter) {
|
|
124
130
|
return adapter;
|
|
@@ -234,6 +240,25 @@ var IoRedisDriver = class {
|
|
|
234
240
|
};
|
|
235
241
|
await Promise.all([close(this.pub), close(this.sub)]);
|
|
236
242
|
}
|
|
243
|
+
async setPresenceBatch(entries) {
|
|
244
|
+
if (entries.length === 0) return;
|
|
245
|
+
const pipeline = this.pub.pipeline();
|
|
246
|
+
for (const { key, value, ttlSeconds } of entries) {
|
|
247
|
+
pipeline.set(key, value, "EX", ttlSeconds);
|
|
248
|
+
}
|
|
249
|
+
await pipeline.exec();
|
|
250
|
+
}
|
|
251
|
+
async expire(key, ttlSeconds) {
|
|
252
|
+
await this.pub.expire(key, ttlSeconds);
|
|
253
|
+
}
|
|
254
|
+
onError(handler) {
|
|
255
|
+
this.pub.on("error", handler);
|
|
256
|
+
return () => this.pub.removeListener("error", handler);
|
|
257
|
+
}
|
|
258
|
+
onReconnect(handler) {
|
|
259
|
+
this.pub.on("connect", handler);
|
|
260
|
+
return () => this.pub.removeListener("connect", handler);
|
|
261
|
+
}
|
|
237
262
|
};
|
|
238
263
|
var NodeRedisDriver = class {
|
|
239
264
|
constructor(pub, sub, blocking) {
|
|
@@ -320,6 +345,25 @@ var NodeRedisDriver = class {
|
|
|
320
345
|
async disconnect() {
|
|
321
346
|
await Promise.all([this.pub.disconnect(), this.sub.disconnect()]);
|
|
322
347
|
}
|
|
348
|
+
async setPresenceBatch(entries) {
|
|
349
|
+
if (entries.length === 0) return;
|
|
350
|
+
const multi = this.pub.multi();
|
|
351
|
+
for (const { key, value, ttlSeconds } of entries) {
|
|
352
|
+
multi.set(key, value, { EX: ttlSeconds });
|
|
353
|
+
}
|
|
354
|
+
await multi.exec();
|
|
355
|
+
}
|
|
356
|
+
async expire(key, ttlSeconds) {
|
|
357
|
+
await this.pub.expire(key, ttlSeconds);
|
|
358
|
+
}
|
|
359
|
+
onError(handler) {
|
|
360
|
+
this.pub.on("error", handler);
|
|
361
|
+
return () => this.pub.removeListener("error", handler);
|
|
362
|
+
}
|
|
363
|
+
onReconnect(handler) {
|
|
364
|
+
this.pub.on("connect", handler);
|
|
365
|
+
return () => this.pub.removeListener("connect", handler);
|
|
366
|
+
}
|
|
323
367
|
};
|
|
324
368
|
function createDriver(pub, sub, blocking) {
|
|
325
369
|
if (sub.isOpen !== void 0 && typeof sub.subscribe === "function") {
|
|
@@ -333,6 +377,8 @@ var RedisAdapter = class {
|
|
|
333
377
|
_driver;
|
|
334
378
|
_prefix;
|
|
335
379
|
_streamMaxLen;
|
|
380
|
+
_streamTtlSeconds;
|
|
381
|
+
_presenceTtlSeconds;
|
|
336
382
|
_handlers = /* @__PURE__ */ new Map();
|
|
337
383
|
_streamOffsets = /* @__PURE__ */ new Map();
|
|
338
384
|
// streamKey -> lastId
|
|
@@ -340,20 +386,48 @@ var RedisAdapter = class {
|
|
|
340
386
|
// Active streams to poll
|
|
341
387
|
_polling = false;
|
|
342
388
|
_closed = false;
|
|
389
|
+
// Per-stream sequence counter for message ordering
|
|
390
|
+
_sequenceCounters = /* @__PURE__ */ new Map();
|
|
391
|
+
// Rehydration callbacks
|
|
392
|
+
_unsubError;
|
|
393
|
+
_unsubReconnect;
|
|
394
|
+
// Stored presence entries for rehydration on reconnect
|
|
395
|
+
_presenceCache = /* @__PURE__ */ new Map();
|
|
343
396
|
constructor(options) {
|
|
344
397
|
this._prefix = options.prefix ?? "ocpp-ws-io:";
|
|
345
398
|
this._streamMaxLen = options.streamMaxLen ?? 1e3;
|
|
399
|
+
this._streamTtlSeconds = options.streamTtlSeconds ?? 300;
|
|
400
|
+
this._presenceTtlSeconds = options.presenceTtlSeconds ?? 300;
|
|
346
401
|
this._driver = createDriver(
|
|
347
402
|
options.pubClient,
|
|
348
403
|
options.subClient,
|
|
349
404
|
options.blockingClient
|
|
350
405
|
);
|
|
406
|
+
if (this._driver.onError) {
|
|
407
|
+
this._unsubError = this._driver.onError((err) => {
|
|
408
|
+
console.error("[RedisAdapter] Redis error:", err.message);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (this._driver.onReconnect) {
|
|
412
|
+
this._unsubReconnect = this._driver.onReconnect(() => {
|
|
413
|
+
this._rehydratePresence().catch(() => {
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
}
|
|
351
417
|
}
|
|
352
418
|
async publish(channel, data) {
|
|
353
419
|
const prefixedChannel = this._prefix + channel;
|
|
420
|
+
const payload = data;
|
|
421
|
+
if (payload && typeof payload === "object" && channel.startsWith("ocpp:node:")) {
|
|
422
|
+
const seq = (this._sequenceCounters.get(channel) ?? 0) + 1;
|
|
423
|
+
this._sequenceCounters.set(channel, seq);
|
|
424
|
+
payload.__seq = seq;
|
|
425
|
+
}
|
|
354
426
|
const message = JSON.stringify(data);
|
|
355
427
|
if (channel.startsWith("ocpp:node:")) {
|
|
356
428
|
await this._driver.xadd(prefixedChannel, { message }, this._streamMaxLen);
|
|
429
|
+
await this._driver.expire(prefixedChannel, this._streamTtlSeconds).catch(() => {
|
|
430
|
+
});
|
|
357
431
|
} else {
|
|
358
432
|
await this._driver.publish(prefixedChannel, message);
|
|
359
433
|
}
|
|
@@ -419,6 +493,10 @@ var RedisAdapter = class {
|
|
|
419
493
|
this._closed = true;
|
|
420
494
|
this._handlers.clear();
|
|
421
495
|
this._streams.clear();
|
|
496
|
+
this._presenceCache.clear();
|
|
497
|
+
this._sequenceCounters.clear();
|
|
498
|
+
if (this._unsubError) this._unsubError();
|
|
499
|
+
if (this._unsubReconnect) this._unsubReconnect();
|
|
422
500
|
await this._driver.disconnect();
|
|
423
501
|
}
|
|
424
502
|
_handleMessage(channel, message) {
|
|
@@ -478,6 +556,7 @@ var RedisAdapter = class {
|
|
|
478
556
|
// ─── Presence Registry ─────────────────────────────────────────────
|
|
479
557
|
async setPresence(identity, nodeId, ttl) {
|
|
480
558
|
const key = `${this._prefix}presence:${identity}`;
|
|
559
|
+
this._presenceCache.set(identity, { nodeId, ttl });
|
|
481
560
|
await this._driver.set(key, nodeId, ttl);
|
|
482
561
|
}
|
|
483
562
|
async getPresence(identity) {
|
|
@@ -515,6 +594,37 @@ var RedisAdapter = class {
|
|
|
515
594
|
streamDetails
|
|
516
595
|
};
|
|
517
596
|
}
|
|
597
|
+
// ─── C1: Batch Presence Pipeline ────────────────────────────────────
|
|
598
|
+
/**
|
|
599
|
+
* Set multiple presence entries in a single Redis pipeline.
|
|
600
|
+
* Reduces N network round-trips to 1 for bulk presence updates.
|
|
601
|
+
*/
|
|
602
|
+
async setPresenceBatch(entries) {
|
|
603
|
+
if (entries.length === 0) return;
|
|
604
|
+
const batchEntries = entries.map(({ identity, nodeId, ttl }) => {
|
|
605
|
+
const key = `${this._prefix}presence:${identity}`;
|
|
606
|
+
const ttlSeconds = ttl ?? this._presenceTtlSeconds;
|
|
607
|
+
this._presenceCache.set(identity, { nodeId, ttl: ttlSeconds });
|
|
608
|
+
return { key, value: nodeId, ttlSeconds };
|
|
609
|
+
});
|
|
610
|
+
await this._driver.setPresenceBatch(batchEntries);
|
|
611
|
+
}
|
|
612
|
+
// ─── C3: Redis Failure Rehydration ──────────────────────────────────
|
|
613
|
+
/**
|
|
614
|
+
* Re-syncs all cached presence entries to Redis after a reconnection.
|
|
615
|
+
* Called automatically when the Redis client reconnects.
|
|
616
|
+
*/
|
|
617
|
+
async _rehydratePresence() {
|
|
618
|
+
if (this._presenceCache.size === 0) return;
|
|
619
|
+
const entries = Array.from(this._presenceCache.entries()).map(
|
|
620
|
+
([identity, { nodeId, ttl }]) => ({
|
|
621
|
+
key: `${this._prefix}presence:${identity}`,
|
|
622
|
+
value: nodeId,
|
|
623
|
+
ttlSeconds: ttl
|
|
624
|
+
})
|
|
625
|
+
);
|
|
626
|
+
await this._driver.setPresenceBatch(entries);
|
|
627
|
+
}
|
|
518
628
|
};
|
|
519
629
|
|
|
520
630
|
// src/client.ts
|
|
@@ -36881,6 +36991,8 @@ var Validator = class {
|
|
|
36881
36991
|
/**
|
|
36882
36992
|
* Validate a payload against a schema identified by its $id.
|
|
36883
36993
|
* Throws a typed RPCError if validation fails.
|
|
36994
|
+
*
|
|
36995
|
+
* E2: Schema is compiled on first call to this method (lazy).
|
|
36884
36996
|
*/
|
|
36885
36997
|
validate(schemaId, params) {
|
|
36886
36998
|
const resolvedId = this._normalizeSchemaId(schemaId);
|
|
@@ -36903,16 +37015,26 @@ var Validator = class {
|
|
|
36903
37015
|
return !!this._ajv.getSchema(this._normalizeSchemaId(schemaId));
|
|
36904
37016
|
}
|
|
36905
37017
|
};
|
|
37018
|
+
var _validatorRegistry = /* @__PURE__ */ new Map();
|
|
36906
37019
|
function createValidator(subprotocol, schemas) {
|
|
36907
|
-
|
|
37020
|
+
const existing = _validatorRegistry.get(subprotocol);
|
|
37021
|
+
if (existing) return existing;
|
|
37022
|
+
const validator = new Validator(subprotocol, schemas);
|
|
37023
|
+
_validatorRegistry.set(subprotocol, validator);
|
|
37024
|
+
return validator;
|
|
36908
37025
|
}
|
|
36909
37026
|
|
|
36910
37027
|
// src/standard-validators.ts
|
|
36911
|
-
var
|
|
36912
|
-
|
|
36913
|
-
|
|
36914
|
-
|
|
36915
|
-
|
|
37028
|
+
var _cached = null;
|
|
37029
|
+
function getStandardValidators() {
|
|
37030
|
+
if (_cached) return _cached;
|
|
37031
|
+
_cached = [
|
|
37032
|
+
createValidator("ocpp1.6", ocpp1_6_default),
|
|
37033
|
+
createValidator("ocpp2.0.1", ocpp2_0_1_default),
|
|
37034
|
+
createValidator("ocpp2.1", ocpp2_1_default)
|
|
37035
|
+
];
|
|
37036
|
+
return _cached;
|
|
37037
|
+
}
|
|
36916
37038
|
|
|
36917
37039
|
// src/types.ts
|
|
36918
37040
|
var ConnectionState = {
|
|
@@ -37095,6 +37217,7 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
37095
37217
|
_badMessageCount = 0;
|
|
37096
37218
|
_lastActivity = 0;
|
|
37097
37219
|
_outboundBuffer = [];
|
|
37220
|
+
_offlineQueue = [];
|
|
37098
37221
|
_middleware;
|
|
37099
37222
|
_validators = [];
|
|
37100
37223
|
_strictProtocols = null;
|
|
@@ -37104,6 +37227,7 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
37104
37227
|
_prettify = false;
|
|
37105
37228
|
constructor(options) {
|
|
37106
37229
|
super();
|
|
37230
|
+
this.setMaxListeners(0);
|
|
37107
37231
|
if (!options.identity) {
|
|
37108
37232
|
throw new Error("identity is required");
|
|
37109
37233
|
}
|
|
@@ -37249,6 +37373,7 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
37249
37373
|
}
|
|
37250
37374
|
this._attachWebsocket(ws);
|
|
37251
37375
|
this._startPing();
|
|
37376
|
+
this._flushOfflineQueue();
|
|
37252
37377
|
if (this._outboundBuffer.length > 0) {
|
|
37253
37378
|
const buffer = this._outboundBuffer;
|
|
37254
37379
|
this._outboundBuffer = [];
|
|
@@ -37430,8 +37555,32 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
37430
37555
|
options = args[2] ?? {};
|
|
37431
37556
|
}
|
|
37432
37557
|
if (this._state !== OPEN) {
|
|
37558
|
+
if (this._options.offlineQueue && (this._state === CLOSED || this._state === CONNECTING)) {
|
|
37559
|
+
return new Promise((resolve, reject) => {
|
|
37560
|
+
const maxSize = this._options.offlineQueueMaxSize ?? 100;
|
|
37561
|
+
if (this._offlineQueue.length >= maxSize) {
|
|
37562
|
+
this._offlineQueue.shift();
|
|
37563
|
+
this._logger?.warn?.(
|
|
37564
|
+
"Offline queue full \u2014 dropping oldest message",
|
|
37565
|
+
{
|
|
37566
|
+
method,
|
|
37567
|
+
queueSize: this._offlineQueue.length
|
|
37568
|
+
}
|
|
37569
|
+
);
|
|
37570
|
+
}
|
|
37571
|
+
this._offlineQueue.push({ method, params, options, resolve, reject });
|
|
37572
|
+
this._logger?.debug?.("Call queued offline", {
|
|
37573
|
+
method,
|
|
37574
|
+
queueSize: this._offlineQueue.length
|
|
37575
|
+
});
|
|
37576
|
+
});
|
|
37577
|
+
}
|
|
37433
37578
|
throw new Error(`Cannot call: client is in state ${this._state}`);
|
|
37434
37579
|
}
|
|
37580
|
+
const maxRetries = options.retries ?? 0;
|
|
37581
|
+
if (maxRetries > 0) {
|
|
37582
|
+
return this._callWithRetry(method, params, options, maxRetries);
|
|
37583
|
+
}
|
|
37435
37584
|
return this._callQueue.push(() => this._sendCall(method, params, options));
|
|
37436
37585
|
}
|
|
37437
37586
|
async safeCall(...args) {
|
|
@@ -37456,7 +37605,7 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
37456
37605
|
}
|
|
37457
37606
|
}
|
|
37458
37607
|
async _sendCall(method, params, options) {
|
|
37459
|
-
const msgId = (0, import_cuid2.createId)();
|
|
37608
|
+
const msgId = options.idempotencyKey ?? (0, import_cuid2.createId)();
|
|
37460
37609
|
const timeoutMs = options.timeoutMs ?? this._options.callTimeoutMs;
|
|
37461
37610
|
const ctx = {
|
|
37462
37611
|
type: "outgoing_call",
|
|
@@ -37504,7 +37653,7 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
37504
37653
|
sentAt: Date.now()
|
|
37505
37654
|
});
|
|
37506
37655
|
if (this._ws?.readyState === import_ws.default.OPEN) {
|
|
37507
|
-
this._ws
|
|
37656
|
+
this._safeSend(this._ws, messageStr, (err) => {
|
|
37508
37657
|
if (err) {
|
|
37509
37658
|
clearTimeout(timeoutHandle);
|
|
37510
37659
|
this._pendingCalls.delete(msgId);
|
|
@@ -37580,11 +37729,13 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
37580
37729
|
this._recordActivity();
|
|
37581
37730
|
let message;
|
|
37582
37731
|
try {
|
|
37583
|
-
|
|
37584
|
-
message = JSON.parse(str);
|
|
37732
|
+
message = JSON.parse(rawData);
|
|
37585
37733
|
if (!Array.isArray(message)) throw new Error("Message is not an array");
|
|
37586
37734
|
} catch (err) {
|
|
37587
|
-
this._onBadMessage(
|
|
37735
|
+
this._onBadMessage(
|
|
37736
|
+
typeof rawData === "string" ? rawData : rawData.toString(),
|
|
37737
|
+
err
|
|
37738
|
+
);
|
|
37588
37739
|
return;
|
|
37589
37740
|
}
|
|
37590
37741
|
const messageType = message[0];
|
|
@@ -37851,6 +38002,91 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
37851
38002
|
}
|
|
37852
38003
|
}, delayMs);
|
|
37853
38004
|
}
|
|
38005
|
+
// ─── Internal: Offline Queue ──────────────────────────────────
|
|
38006
|
+
/**
|
|
38007
|
+
* Atomically drains the offline queue and sends each message via _sendCall.
|
|
38008
|
+
* Uses splice(0) to prevent re-entry bugs (double billing) if the connection
|
|
38009
|
+
* drops again mid-flush — the queue is empty before any sends begin.
|
|
38010
|
+
*/
|
|
38011
|
+
_flushOfflineQueue() {
|
|
38012
|
+
if (this._offlineQueue.length === 0) return;
|
|
38013
|
+
const snapshot = this._offlineQueue.splice(0, this._offlineQueue.length);
|
|
38014
|
+
this._logger?.info?.("Flushing offline queue", { count: snapshot.length });
|
|
38015
|
+
for (const entry of snapshot) {
|
|
38016
|
+
this._callQueue.push(() => this._sendCall(entry.method, entry.params, entry.options)).then(entry.resolve).catch(entry.reject);
|
|
38017
|
+
}
|
|
38018
|
+
}
|
|
38019
|
+
// ─── Internal: Call Retry with Full Jitter ───────────────────
|
|
38020
|
+
/**
|
|
38021
|
+
* Retry wrapper using Full Jitter exponential backoff.
|
|
38022
|
+
* delay = random(0, min(retryMaxDelayMs, retryDelayMs * 2^attempt))
|
|
38023
|
+
* Only retries on TimeoutError — all other errors propagate immediately.
|
|
38024
|
+
*/
|
|
38025
|
+
async _callWithRetry(method, params, options, maxRetries) {
|
|
38026
|
+
const baseDelay = options.retryDelayMs ?? 1e3;
|
|
38027
|
+
const maxDelay = options.retryMaxDelayMs ?? 3e4;
|
|
38028
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
38029
|
+
try {
|
|
38030
|
+
return await this._callQueue.push(
|
|
38031
|
+
() => this._sendCall(method, params, options)
|
|
38032
|
+
);
|
|
38033
|
+
} catch (err) {
|
|
38034
|
+
if (attempt === maxRetries || !(err instanceof TimeoutError)) {
|
|
38035
|
+
throw err;
|
|
38036
|
+
}
|
|
38037
|
+
const expDelay = Math.min(maxDelay, baseDelay * 2 ** attempt);
|
|
38038
|
+
const jitteredDelay = Math.random() * expDelay;
|
|
38039
|
+
this._logger?.warn?.("Call retry", {
|
|
38040
|
+
method,
|
|
38041
|
+
attempt: attempt + 1,
|
|
38042
|
+
maxRetries,
|
|
38043
|
+
delayMs: Math.round(jitteredDelay)
|
|
38044
|
+
});
|
|
38045
|
+
await new Promise((r) => setTimeout(r, jitteredDelay));
|
|
38046
|
+
}
|
|
38047
|
+
}
|
|
38048
|
+
throw new Error("Retry exhausted");
|
|
38049
|
+
}
|
|
38050
|
+
// ─── Internal: Backpressure-Aware Send ───────────────────────
|
|
38051
|
+
/** Maximum bytes allowed in the ws send buffer before applying backpressure (512KB) */
|
|
38052
|
+
static _BACKPRESSURE_THRESHOLD = 512 * 1024;
|
|
38053
|
+
/**
|
|
38054
|
+
* Wraps ws.send() with backpressure protection.
|
|
38055
|
+
* If bufferedAmount exceeds the threshold, waits for the buffer to drain
|
|
38056
|
+
* before sending. Prevents OOM on slow 2G/3G charger connections.
|
|
38057
|
+
*/
|
|
38058
|
+
_safeSend(ws, data, cb) {
|
|
38059
|
+
if (!ws || ws.readyState !== import_ws.default.OPEN) {
|
|
38060
|
+
cb?.(new Error("WebSocket is not open"));
|
|
38061
|
+
return;
|
|
38062
|
+
}
|
|
38063
|
+
if (ws.bufferedAmount > _OCPPClient._BACKPRESSURE_THRESHOLD) {
|
|
38064
|
+
this._logger?.warn?.("Backpressure \u2014 pausing send", {
|
|
38065
|
+
identity: this._identity,
|
|
38066
|
+
bufferedAmount: ws.bufferedAmount,
|
|
38067
|
+
threshold: _OCPPClient._BACKPRESSURE_THRESHOLD
|
|
38068
|
+
});
|
|
38069
|
+
this.emit("backpressure", {
|
|
38070
|
+
identity: this._identity,
|
|
38071
|
+
bufferedAmount: ws.bufferedAmount
|
|
38072
|
+
});
|
|
38073
|
+
let waited = 0;
|
|
38074
|
+
const drainCheck = setInterval(() => {
|
|
38075
|
+
waited += 50;
|
|
38076
|
+
if (!ws || ws.readyState !== import_ws.default.OPEN) {
|
|
38077
|
+
clearInterval(drainCheck);
|
|
38078
|
+
cb?.(new Error("WebSocket closed during backpressure wait"));
|
|
38079
|
+
return;
|
|
38080
|
+
}
|
|
38081
|
+
if (ws.bufferedAmount <= _OCPPClient._BACKPRESSURE_THRESHOLD || waited >= 1e4) {
|
|
38082
|
+
clearInterval(drainCheck);
|
|
38083
|
+
ws.send(data, cb);
|
|
38084
|
+
}
|
|
38085
|
+
}, 50);
|
|
38086
|
+
} else {
|
|
38087
|
+
ws.send(data, cb);
|
|
38088
|
+
}
|
|
38089
|
+
}
|
|
37854
38090
|
// ─── Internal: Ping/Pong ─────────────────────────────────────
|
|
37855
38091
|
_startPing() {
|
|
37856
38092
|
if (this._options.pingIntervalMs <= 0) return;
|
|
@@ -37897,7 +38133,7 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
37897
38133
|
if (this._options.strictModeValidators) {
|
|
37898
38134
|
this._validators = this._options.strictModeValidators;
|
|
37899
38135
|
} else {
|
|
37900
|
-
this._validators =
|
|
38136
|
+
this._validators = getStandardValidators();
|
|
37901
38137
|
}
|
|
37902
38138
|
if (Array.isArray(this._options.strictMode)) {
|
|
37903
38139
|
this._strictProtocols = this._options.strictMode;
|
|
@@ -38000,6 +38236,52 @@ var OCPPClient = class _OCPPClient extends import_node_events.EventEmitter {
|
|
|
38000
38236
|
}
|
|
38001
38237
|
};
|
|
38002
38238
|
|
|
38239
|
+
// src/lru-map.ts
|
|
38240
|
+
var LRUMap = class extends Map {
|
|
38241
|
+
_maxSize;
|
|
38242
|
+
constructor(maxSize) {
|
|
38243
|
+
super();
|
|
38244
|
+
if (maxSize < 1) throw new RangeError("LRUMap maxSize must be >= 1");
|
|
38245
|
+
this._maxSize = maxSize;
|
|
38246
|
+
}
|
|
38247
|
+
/**
|
|
38248
|
+
* Returns the configured maximum capacity of this LRU cache.
|
|
38249
|
+
*/
|
|
38250
|
+
get maxSize() {
|
|
38251
|
+
return this._maxSize;
|
|
38252
|
+
}
|
|
38253
|
+
/**
|
|
38254
|
+
* Sets a key-value pair. If the key already exists, it is promoted to the
|
|
38255
|
+
* most-recently-used position. If inserting a new key would exceed capacity,
|
|
38256
|
+
* the oldest (least-recently-used) entry is evicted.
|
|
38257
|
+
*/
|
|
38258
|
+
set(key, value) {
|
|
38259
|
+
if (this.has(key)) {
|
|
38260
|
+
this.delete(key);
|
|
38261
|
+
}
|
|
38262
|
+
super.set(key, value);
|
|
38263
|
+
if (this.size > this._maxSize) {
|
|
38264
|
+
const oldest = this.keys().next().value;
|
|
38265
|
+
if (oldest !== void 0) {
|
|
38266
|
+
this.delete(oldest);
|
|
38267
|
+
}
|
|
38268
|
+
}
|
|
38269
|
+
return this;
|
|
38270
|
+
}
|
|
38271
|
+
/**
|
|
38272
|
+
* Gets a value by key and promotes it to the most-recently-used position.
|
|
38273
|
+
* Uses `this.has(key)` instead of a value truthiness check to correctly
|
|
38274
|
+
* handle stored values of `undefined`, `null`, `0`, `""`, etc.
|
|
38275
|
+
*/
|
|
38276
|
+
get(key) {
|
|
38277
|
+
if (!this.has(key)) return void 0;
|
|
38278
|
+
const value = super.get(key);
|
|
38279
|
+
this.delete(key);
|
|
38280
|
+
super.set(key, value);
|
|
38281
|
+
return value;
|
|
38282
|
+
}
|
|
38283
|
+
};
|
|
38284
|
+
|
|
38003
38285
|
// src/router.ts
|
|
38004
38286
|
var import_node_events2 = require("events");
|
|
38005
38287
|
async function executeMiddlewareChain(middlewares, ctx) {
|
|
@@ -38356,6 +38638,7 @@ var OCPPServerClient = class extends OCPPClient {
|
|
|
38356
38638
|
this._ws = context.ws;
|
|
38357
38639
|
this._protocol = context.protocol ?? context.ws.protocol;
|
|
38358
38640
|
this._attachServerWebsocket(context.ws);
|
|
38641
|
+
this._startPing();
|
|
38359
38642
|
}
|
|
38360
38643
|
// ─── Rate Limiting State ──────────────────────────────────────────
|
|
38361
38644
|
_rateLimits = {};
|
|
@@ -38404,8 +38687,7 @@ var OCPPServerClient = class extends OCPPClient {
|
|
|
38404
38687
|
let pData;
|
|
38405
38688
|
if (limits.methods) {
|
|
38406
38689
|
try {
|
|
38407
|
-
|
|
38408
|
-
pData = JSON.parse(str);
|
|
38690
|
+
pData = JSON.parse(data);
|
|
38409
38691
|
if (Array.isArray(pData) && pData[0] === 2) {
|
|
38410
38692
|
method = pData[2];
|
|
38411
38693
|
}
|
|
@@ -38523,13 +38805,16 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
38523
38805
|
_httpServerAbortControllers = /* @__PURE__ */ new Set();
|
|
38524
38806
|
_logger = null;
|
|
38525
38807
|
_globalCORS;
|
|
38808
|
+
// Connection-level rate limiting (per-IP token bucket)
|
|
38809
|
+
_connectionBuckets = /* @__PURE__ */ new Map();
|
|
38526
38810
|
// Robustness & Clustering
|
|
38527
38811
|
_nodeId = (0, import_cuid22.createId)();
|
|
38528
|
-
_sessions
|
|
38812
|
+
_sessions;
|
|
38529
38813
|
_gcInterval = null;
|
|
38530
38814
|
_sessionTimeoutMs;
|
|
38531
38815
|
constructor(options = {}) {
|
|
38532
38816
|
super();
|
|
38817
|
+
this.setMaxListeners(0);
|
|
38533
38818
|
if (options.strictMode) {
|
|
38534
38819
|
if (!options.strictModeValidators && !options.protocols?.length) {
|
|
38535
38820
|
throw new Error(
|
|
@@ -38550,7 +38835,12 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
38550
38835
|
...options
|
|
38551
38836
|
};
|
|
38552
38837
|
this._sessionTimeoutMs = this._options.sessionTtlMs;
|
|
38553
|
-
|
|
38838
|
+
const maxSessions = this._options.maxSessions ?? 5e4;
|
|
38839
|
+
this._sessions = new LRUMap(maxSessions);
|
|
38840
|
+
this._wss = new import_ws2.WebSocketServer({
|
|
38841
|
+
noServer: true,
|
|
38842
|
+
maxPayload: this._options.maxPayloadBytes ?? 65536
|
|
38843
|
+
});
|
|
38554
38844
|
this._gcInterval = setInterval(() => {
|
|
38555
38845
|
const now = Date.now();
|
|
38556
38846
|
for (const [identity, session] of this._sessions.entries()) {
|
|
@@ -38764,6 +39054,69 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
38764
39054
|
};
|
|
38765
39055
|
httpServer.on("upgrade", upgradeHandler);
|
|
38766
39056
|
this._httpServers.add(httpServer);
|
|
39057
|
+
if (this._options.healthEndpoint) {
|
|
39058
|
+
httpServer.on("request", (req, res) => {
|
|
39059
|
+
const url = req.url ?? "";
|
|
39060
|
+
if (url === "/health") {
|
|
39061
|
+
const s = this.stats();
|
|
39062
|
+
const body = JSON.stringify({
|
|
39063
|
+
status: this._state === "OPEN" ? "ok" : "degraded",
|
|
39064
|
+
state: this._state,
|
|
39065
|
+
connectedClients: s.connectedClients,
|
|
39066
|
+
activeSessions: s.activeSessions,
|
|
39067
|
+
uptimeSeconds: Math.round(s.uptimeSeconds),
|
|
39068
|
+
pid: s.pid
|
|
39069
|
+
});
|
|
39070
|
+
res.writeHead(200, {
|
|
39071
|
+
"Content-Type": "application/json",
|
|
39072
|
+
"Cache-Control": "no-cache"
|
|
39073
|
+
});
|
|
39074
|
+
res.end(body);
|
|
39075
|
+
return;
|
|
39076
|
+
}
|
|
39077
|
+
if (url === "/metrics") {
|
|
39078
|
+
const s = this.stats();
|
|
39079
|
+
const lines = [
|
|
39080
|
+
"# HELP ocpp_connected_clients Number of currently connected OCPP clients",
|
|
39081
|
+
"# TYPE ocpp_connected_clients gauge",
|
|
39082
|
+
`ocpp_connected_clients ${s.connectedClients}`,
|
|
39083
|
+
"",
|
|
39084
|
+
"# HELP ocpp_active_sessions Number of active in-memory sessions",
|
|
39085
|
+
"# TYPE ocpp_active_sessions gauge",
|
|
39086
|
+
`ocpp_active_sessions ${s.activeSessions}`,
|
|
39087
|
+
"",
|
|
39088
|
+
"# HELP ocpp_uptime_seconds Process uptime in seconds",
|
|
39089
|
+
"# TYPE ocpp_uptime_seconds gauge",
|
|
39090
|
+
`ocpp_uptime_seconds ${Math.round(s.uptimeSeconds)}`,
|
|
39091
|
+
"",
|
|
39092
|
+
"# HELP ocpp_memory_rss_bytes Resident set size in bytes",
|
|
39093
|
+
"# TYPE ocpp_memory_rss_bytes gauge",
|
|
39094
|
+
`ocpp_memory_rss_bytes ${s.memoryUsage.rss}`,
|
|
39095
|
+
"",
|
|
39096
|
+
"# HELP ocpp_memory_heap_used_bytes V8 heap used in bytes",
|
|
39097
|
+
"# TYPE ocpp_memory_heap_used_bytes gauge",
|
|
39098
|
+
`ocpp_memory_heap_used_bytes ${s.memoryUsage.heapUsed}`,
|
|
39099
|
+
"",
|
|
39100
|
+
"# HELP ocpp_memory_heap_total_bytes V8 heap total in bytes",
|
|
39101
|
+
"# TYPE ocpp_memory_heap_total_bytes gauge",
|
|
39102
|
+
`ocpp_memory_heap_total_bytes ${s.memoryUsage.heapTotal}`,
|
|
39103
|
+
"",
|
|
39104
|
+
"# HELP ocpp_ws_buffered_bytes Total buffered WebSocket bytes",
|
|
39105
|
+
"# TYPE ocpp_ws_buffered_bytes gauge",
|
|
39106
|
+
`ocpp_ws_buffered_bytes ${s.webSockets?.bufferedAmount ?? 0}`,
|
|
39107
|
+
""
|
|
39108
|
+
];
|
|
39109
|
+
res.writeHead(200, {
|
|
39110
|
+
"Content-Type": "text/plain; version=0.0.4; charset=utf-8",
|
|
39111
|
+
"Cache-Control": "no-cache"
|
|
39112
|
+
});
|
|
39113
|
+
res.end(lines.join("\n"));
|
|
39114
|
+
return;
|
|
39115
|
+
}
|
|
39116
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
39117
|
+
res.end("Not Found");
|
|
39118
|
+
});
|
|
39119
|
+
}
|
|
38767
39120
|
if (options?.signal) {
|
|
38768
39121
|
const ac = new AbortController();
|
|
38769
39122
|
this._httpServerAbortControllers.add(ac);
|
|
@@ -38793,6 +39146,51 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
38793
39146
|
}
|
|
38794
39147
|
return httpServer;
|
|
38795
39148
|
}
|
|
39149
|
+
/**
|
|
39150
|
+
* Hot-reloads the TLS certificate on all active HTTPS servers without
|
|
39151
|
+
* dropping any existing WebSocket connections.
|
|
39152
|
+
*
|
|
39153
|
+
* **When to use:** Call this whenever your TLS certificate is renewed —
|
|
39154
|
+
* for example, after a Let's Encrypt auto-renewal (every ~90 days).
|
|
39155
|
+
* Without this, you would need to restart the Node.js process to pick up
|
|
39156
|
+
* the new certificate, disconnecting all connected charging stations.
|
|
39157
|
+
*
|
|
39158
|
+
* **How to use:**
|
|
39159
|
+
* ```ts
|
|
39160
|
+
* server.updateTLS({ cert: newCert, key: newKey });
|
|
39161
|
+
* ```
|
|
39162
|
+
*
|
|
39163
|
+
* **Optional:** Only relevant if you are terminating TLS directly in Node.js
|
|
39164
|
+
* (i.e. `SecurityProfile.TLS_BASIC_AUTH` or `TLS_CLIENT_CERT`). If you are
|
|
39165
|
+
* running behind a reverse proxy (Nginx, AWS ALB, etc.) that handles TLS,
|
|
39166
|
+
* you do not need this method — just rotate the cert on the proxy.
|
|
39167
|
+
*
|
|
39168
|
+
* @throws If the server is not using a TLS Security Profile.
|
|
39169
|
+
*/
|
|
39170
|
+
updateTLS(tlsOpts) {
|
|
39171
|
+
const profile = this._options.securityProfile ?? 0 /* NONE */;
|
|
39172
|
+
if (profile !== 2 /* TLS_BASIC_AUTH */ && profile !== 3 /* TLS_CLIENT_CERT */) {
|
|
39173
|
+
throw new Error(
|
|
39174
|
+
"updateTLS() requires a TLS Security Profile (TLS_BASIC_AUTH or TLS_CLIENT_CERT)"
|
|
39175
|
+
);
|
|
39176
|
+
}
|
|
39177
|
+
this._options.tls = { ...this._options.tls, ...tlsOpts };
|
|
39178
|
+
const httpsOptions = {};
|
|
39179
|
+
if (tlsOpts.cert) httpsOptions.cert = tlsOpts.cert;
|
|
39180
|
+
if (tlsOpts.key) httpsOptions.key = tlsOpts.key;
|
|
39181
|
+
if (tlsOpts.ca) httpsOptions.ca = tlsOpts.ca;
|
|
39182
|
+
if (tlsOpts.passphrase) httpsOptions.passphrase = tlsOpts.passphrase;
|
|
39183
|
+
let updated = 0;
|
|
39184
|
+
for (const srv of this._httpServers) {
|
|
39185
|
+
if ("setSecureContext" in srv && typeof srv.setSecureContext === "function") {
|
|
39186
|
+
srv.setSecureContext(httpsOptions);
|
|
39187
|
+
updated++;
|
|
39188
|
+
}
|
|
39189
|
+
}
|
|
39190
|
+
this._logger?.info?.(
|
|
39191
|
+
`TLS context hot-reloaded across ${updated} active server(s)`
|
|
39192
|
+
);
|
|
39193
|
+
}
|
|
38796
39194
|
// ─── Handle Upgrade ──────────────────────────────────────────
|
|
38797
39195
|
get handleUpgrade() {
|
|
38798
39196
|
return (req, socket, head) => {
|
|
@@ -38827,6 +39225,36 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
38827
39225
|
abortHandshake(socket, 503, "Server is shutting down");
|
|
38828
39226
|
return;
|
|
38829
39227
|
}
|
|
39228
|
+
const connRateLimit = this._options.connectionRateLimit;
|
|
39229
|
+
if (connRateLimit) {
|
|
39230
|
+
const ip = req.socket.remoteAddress ?? "unknown";
|
|
39231
|
+
const now = Date.now();
|
|
39232
|
+
let bucket = this._connectionBuckets.get(ip);
|
|
39233
|
+
if (!bucket) {
|
|
39234
|
+
bucket = { tokens: connRateLimit.limit, lastRefill: now };
|
|
39235
|
+
this._connectionBuckets.set(ip, bucket);
|
|
39236
|
+
} else {
|
|
39237
|
+
const elapsed = now - bucket.lastRefill;
|
|
39238
|
+
const refillRate = connRateLimit.limit / connRateLimit.windowMs;
|
|
39239
|
+
bucket.tokens = Math.min(
|
|
39240
|
+
connRateLimit.limit,
|
|
39241
|
+
bucket.tokens + elapsed * refillRate
|
|
39242
|
+
);
|
|
39243
|
+
bucket.lastRefill = now;
|
|
39244
|
+
}
|
|
39245
|
+
if (bucket.tokens < 1) {
|
|
39246
|
+
this._logger?.warn?.("Connection rate limit exceeded", { ip });
|
|
39247
|
+
this.emit("securityEvent", {
|
|
39248
|
+
type: "CONNECTION_RATE_LIMIT",
|
|
39249
|
+
ip,
|
|
39250
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39251
|
+
details: { tokensRemaining: bucket.tokens }
|
|
39252
|
+
});
|
|
39253
|
+
abortHandshake(socket, 429, "Too Many Requests");
|
|
39254
|
+
return;
|
|
39255
|
+
}
|
|
39256
|
+
bucket.tokens -= 1;
|
|
39257
|
+
}
|
|
38830
39258
|
if (socket.readyState !== "open") {
|
|
38831
39259
|
this._logger?.debug?.("Socket not open at upgrade start");
|
|
38832
39260
|
if (!socket.destroyed) socket.destroy();
|
|
@@ -39078,6 +39506,13 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
39078
39506
|
if (ac.signal.aborted) {
|
|
39079
39507
|
const reason = err instanceof Error ? err.message : "Unknown abort";
|
|
39080
39508
|
this._logger?.warn?.("Handshake aborted", { identity, reason });
|
|
39509
|
+
this.emit("securityEvent", {
|
|
39510
|
+
type: "UPGRADE_ABORTED",
|
|
39511
|
+
identity,
|
|
39512
|
+
ip: req.socket.remoteAddress,
|
|
39513
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39514
|
+
details: { reason }
|
|
39515
|
+
});
|
|
39081
39516
|
this.emit("upgradeAborted", {
|
|
39082
39517
|
identity,
|
|
39083
39518
|
reason,
|
|
@@ -39091,6 +39526,13 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
39091
39526
|
const code = typeof errObj?.code === "number" ? errObj.code : 401;
|
|
39092
39527
|
const message = typeof errObj?.message === "string" ? errObj.message : "Unauthorized";
|
|
39093
39528
|
this._logger?.warn?.("Auth rejected", { identity, code });
|
|
39529
|
+
this.emit("securityEvent", {
|
|
39530
|
+
type: "AUTH_FAILED",
|
|
39531
|
+
identity,
|
|
39532
|
+
ip: req.socket.remoteAddress,
|
|
39533
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39534
|
+
details: { code, message }
|
|
39535
|
+
});
|
|
39094
39536
|
abortHandshake(socket, code, message);
|
|
39095
39537
|
return;
|
|
39096
39538
|
} finally {
|
|
@@ -39114,7 +39556,10 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
39114
39556
|
return;
|
|
39115
39557
|
}
|
|
39116
39558
|
if (!this._wss) {
|
|
39117
|
-
this._wss = new import_ws2.WebSocketServer({
|
|
39559
|
+
this._wss = new import_ws2.WebSocketServer({
|
|
39560
|
+
noServer: true,
|
|
39561
|
+
maxPayload: this._options.maxPayloadBytes ?? 65536
|
|
39562
|
+
});
|
|
39118
39563
|
}
|
|
39119
39564
|
this._wss.handleUpgrade(req, socket, head, (ws) => {
|
|
39120
39565
|
const clientOptions = {
|
|
@@ -39145,6 +39590,20 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
39145
39590
|
protocol: selectedProtocol
|
|
39146
39591
|
});
|
|
39147
39592
|
this._updateSessionActivity(identity, client.session);
|
|
39593
|
+
const existingClient = this._clientsByIdentity.get(identity);
|
|
39594
|
+
if (existingClient && existingClient !== client) {
|
|
39595
|
+
this._logger?.warn?.("Evicting stale connection for identity", {
|
|
39596
|
+
identity,
|
|
39597
|
+
reason: "Duplicate identity replaced by new connection"
|
|
39598
|
+
});
|
|
39599
|
+
existingClient.close({
|
|
39600
|
+
code: 4e3,
|
|
39601
|
+
reason: "Evicted by new connection",
|
|
39602
|
+
force: true
|
|
39603
|
+
}).catch(() => {
|
|
39604
|
+
});
|
|
39605
|
+
this._clients.delete(existingClient);
|
|
39606
|
+
}
|
|
39148
39607
|
this._clients.add(client);
|
|
39149
39608
|
this._clientsByIdentity.set(identity, client);
|
|
39150
39609
|
if (this._adapter?.setPresence) {
|
|
@@ -39200,6 +39659,29 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
39200
39659
|
clearInterval(this._gcInterval);
|
|
39201
39660
|
this._gcInterval = null;
|
|
39202
39661
|
}
|
|
39662
|
+
if (!options.force) {
|
|
39663
|
+
const drainTimeout = 5e3;
|
|
39664
|
+
const drainPromises = Array.from(this._clients).map(async (client) => {
|
|
39665
|
+
const ws = client._ws;
|
|
39666
|
+
if (ws && ws.bufferedAmount > 0) {
|
|
39667
|
+
this._logger?.debug?.("Waiting for client buffer to drain", {
|
|
39668
|
+
identity: client.identity,
|
|
39669
|
+
bufferedAmount: ws.bufferedAmount
|
|
39670
|
+
});
|
|
39671
|
+
await new Promise((resolve) => {
|
|
39672
|
+
let elapsed = 0;
|
|
39673
|
+
const check = setInterval(() => {
|
|
39674
|
+
elapsed += 50;
|
|
39675
|
+
if (!ws || ws.bufferedAmount === 0 || elapsed >= drainTimeout) {
|
|
39676
|
+
clearInterval(check);
|
|
39677
|
+
resolve();
|
|
39678
|
+
}
|
|
39679
|
+
}, 50);
|
|
39680
|
+
});
|
|
39681
|
+
}
|
|
39682
|
+
});
|
|
39683
|
+
await Promise.allSettled(drainPromises);
|
|
39684
|
+
}
|
|
39203
39685
|
const closePromises = Array.from(this._clients).map(
|
|
39204
39686
|
(client) => client.close(options).catch(() => {
|
|
39205
39687
|
})
|
|
@@ -39427,6 +39909,7 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
39427
39909
|
0 && (module.exports = {
|
|
39428
39910
|
ConnectionState,
|
|
39429
39911
|
InMemoryAdapter,
|
|
39912
|
+
LRUMap,
|
|
39430
39913
|
MessageType,
|
|
39431
39914
|
MiddlewareStack,
|
|
39432
39915
|
NOREPLY,
|
|
@@ -39464,6 +39947,6 @@ var OCPPServer = class extends import_node_events3.EventEmitter {
|
|
|
39464
39947
|
defineRpcMiddleware,
|
|
39465
39948
|
getErrorPlainObject,
|
|
39466
39949
|
getPackageIdent,
|
|
39467
|
-
|
|
39950
|
+
getStandardValidators
|
|
39468
39951
|
});
|
|
39469
39952
|
//# sourceMappingURL=index.js.map
|