ocpp-ws-io 2.1.7 → 2.1.8
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/README.md +1 -1
- package/dist/adapters/redis.d.mts +2 -1
- package/dist/adapters/redis.d.ts +2 -1
- package/dist/adapters/redis.js +27 -6
- package/dist/adapters/redis.js.map +1 -1
- package/dist/adapters/redis.mjs +27 -6
- package/dist/adapters/redis.mjs.map +1 -1
- package/dist/browser.d.mts +1129 -2
- package/dist/browser.d.ts +1129 -2
- package/dist/browser.js +4 -0
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +4 -0
- package/dist/browser.mjs.map +1 -1
- package/dist/index-C0mn42-8.d.mts +161 -0
- package/dist/index-s9f97CmV.d.ts +161 -0
- package/dist/index.d.mts +131 -287
- package/dist/index.d.ts +131 -287
- package/dist/index.js +628 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +642 -56
- package/dist/index.mjs.map +1 -1
- package/dist/plugins.d.mts +246 -0
- package/dist/plugins.d.ts +246 -0
- package/dist/plugins.js +370 -0
- package/dist/plugins.js.map +1 -0
- package/dist/plugins.mjs +350 -0
- package/dist/plugins.mjs.map +1 -0
- package/dist/{index-BRblF1XV.d.mts → types-BZXEmDQ1.d.mts} +468 -89
- package/dist/{index-BRblF1XV.d.ts → types-BZXEmDQ1.d.ts} +468 -89
- package/package.json +7 -3
- package/assets/banner.svg +0 -31
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// ../../node_modules/tsup/assets/esm_shims.js
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
var getFilename = () => fileURLToPath(import.meta.url);
|
|
12
|
+
var getDirname = () => path.dirname(getFilename());
|
|
13
|
+
var __dirname = /* @__PURE__ */ getDirname();
|
|
14
|
+
var __filename = /* @__PURE__ */ getFilename();
|
|
15
|
+
|
|
1
16
|
// src/adapters/adapter.ts
|
|
2
17
|
var InMemoryAdapter = class {
|
|
3
18
|
_channels = /* @__PURE__ */ new Map();
|
|
@@ -54,6 +69,145 @@ function defineAdapter(adapter) {
|
|
|
54
69
|
return adapter;
|
|
55
70
|
}
|
|
56
71
|
|
|
72
|
+
// src/adapters/redis/cluster-driver.ts
|
|
73
|
+
var ClusterDriver = class {
|
|
74
|
+
_cluster;
|
|
75
|
+
_subscriber;
|
|
76
|
+
_handlers = /* @__PURE__ */ new Map();
|
|
77
|
+
constructor(_options) {
|
|
78
|
+
try {
|
|
79
|
+
const { createRequire } = __require("module");
|
|
80
|
+
const dynamicRequire = createRequire(__filename);
|
|
81
|
+
const Redis = dynamicRequire("ioredis");
|
|
82
|
+
const redisOpts = _options.redisOptions ?? {};
|
|
83
|
+
if (_options.natMap) {
|
|
84
|
+
redisOpts.natMap = _options.natMap;
|
|
85
|
+
}
|
|
86
|
+
this._cluster = new Redis.Cluster(
|
|
87
|
+
_options.nodes.map((n) => ({ host: n.host, port: n.port })),
|
|
88
|
+
{ redisOptions: redisOpts }
|
|
89
|
+
);
|
|
90
|
+
this._subscriber = new Redis.Cluster(
|
|
91
|
+
_options.nodes.map((n) => ({ host: n.host, port: n.port })),
|
|
92
|
+
{ redisOptions: redisOpts }
|
|
93
|
+
);
|
|
94
|
+
this._subscriber.on("message", (channel, message) => {
|
|
95
|
+
const handler = this._handlers.get(channel);
|
|
96
|
+
if (handler) handler(message);
|
|
97
|
+
});
|
|
98
|
+
} catch {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"ClusterDriver requires 'ioredis' as a peer dependency. Install it with: npm i ioredis"
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async publish(channel, message) {
|
|
105
|
+
await this._cluster.publish(channel, message);
|
|
106
|
+
}
|
|
107
|
+
async subscribe(channel, handler) {
|
|
108
|
+
this._handlers.set(channel, handler);
|
|
109
|
+
await this._subscriber.subscribe(channel);
|
|
110
|
+
}
|
|
111
|
+
async unsubscribe(channel) {
|
|
112
|
+
await this._subscriber.unsubscribe(channel);
|
|
113
|
+
this._handlers.delete(channel);
|
|
114
|
+
}
|
|
115
|
+
async set(key, value, ttlSeconds) {
|
|
116
|
+
if (ttlSeconds) {
|
|
117
|
+
await this._cluster.set(key, value, "EX", ttlSeconds);
|
|
118
|
+
} else {
|
|
119
|
+
await this._cluster.set(key, value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async get(key) {
|
|
123
|
+
return await this._cluster.get(key) || null;
|
|
124
|
+
}
|
|
125
|
+
async mget(keys) {
|
|
126
|
+
if (keys.length === 0) return [];
|
|
127
|
+
try {
|
|
128
|
+
return await this._cluster.mget(...keys);
|
|
129
|
+
} catch {
|
|
130
|
+
return await Promise.all(keys.map((k) => this.get(k)));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async del(key) {
|
|
134
|
+
await this._cluster.del(key);
|
|
135
|
+
}
|
|
136
|
+
async xadd(stream, args, maxLen) {
|
|
137
|
+
const flatArgs = [];
|
|
138
|
+
if (maxLen) {
|
|
139
|
+
flatArgs.push("MAXLEN", "~", maxLen.toString());
|
|
140
|
+
}
|
|
141
|
+
flatArgs.push("*");
|
|
142
|
+
for (const [k, v] of Object.entries(args)) {
|
|
143
|
+
flatArgs.push(k, v);
|
|
144
|
+
}
|
|
145
|
+
return await this._cluster.xadd(stream, ...flatArgs);
|
|
146
|
+
}
|
|
147
|
+
async xaddBatch(messages, maxLen) {
|
|
148
|
+
if (messages.length === 0) return;
|
|
149
|
+
const pipeline = this._cluster.pipeline();
|
|
150
|
+
for (const msg of messages) {
|
|
151
|
+
const flatArgs = [];
|
|
152
|
+
if (maxLen) {
|
|
153
|
+
flatArgs.push("MAXLEN", "~", maxLen.toString());
|
|
154
|
+
}
|
|
155
|
+
flatArgs.push("*");
|
|
156
|
+
for (const [k, v] of Object.entries(msg.args)) {
|
|
157
|
+
flatArgs.push(k, v);
|
|
158
|
+
}
|
|
159
|
+
pipeline.xadd(msg.stream, ...flatArgs);
|
|
160
|
+
}
|
|
161
|
+
await pipeline.exec();
|
|
162
|
+
}
|
|
163
|
+
async xread(streams, count, block) {
|
|
164
|
+
const args = [];
|
|
165
|
+
if (count) args.push("COUNT", count);
|
|
166
|
+
if (typeof block === "number") args.push("BLOCK", block);
|
|
167
|
+
args.push("STREAMS");
|
|
168
|
+
for (const s of streams) args.push(s.key);
|
|
169
|
+
for (const s of streams) args.push(s.id);
|
|
170
|
+
const result = await this._cluster.xread(...args);
|
|
171
|
+
if (!result) return null;
|
|
172
|
+
return result.map(([stream, messages]) => ({
|
|
173
|
+
stream,
|
|
174
|
+
messages: messages.map(([id, fields]) => {
|
|
175
|
+
const data = {};
|
|
176
|
+
for (let i = 0; i < fields.length; i += 2) {
|
|
177
|
+
data[fields[i]] = fields[i + 1];
|
|
178
|
+
}
|
|
179
|
+
return { id, data };
|
|
180
|
+
})
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
async xlen(stream) {
|
|
184
|
+
return await this._cluster.xlen(stream);
|
|
185
|
+
}
|
|
186
|
+
async disconnect() {
|
|
187
|
+
this._handlers.clear();
|
|
188
|
+
await Promise.allSettled([this._cluster.quit(), this._subscriber.quit()]);
|
|
189
|
+
}
|
|
190
|
+
async setPresenceBatch(entries) {
|
|
191
|
+
if (entries.length === 0) return;
|
|
192
|
+
const pipeline = this._cluster.pipeline();
|
|
193
|
+
for (const { key, value, ttlSeconds } of entries) {
|
|
194
|
+
pipeline.set(key, value, "EX", ttlSeconds);
|
|
195
|
+
}
|
|
196
|
+
await pipeline.exec();
|
|
197
|
+
}
|
|
198
|
+
async expire(key, ttlSeconds) {
|
|
199
|
+
await this._cluster.expire(key, ttlSeconds);
|
|
200
|
+
}
|
|
201
|
+
onError(handler) {
|
|
202
|
+
this._cluster.on("error", handler);
|
|
203
|
+
return () => this._cluster.removeListener("error", handler);
|
|
204
|
+
}
|
|
205
|
+
onReconnect(handler) {
|
|
206
|
+
this._cluster.on("reconnecting", handler);
|
|
207
|
+
return () => this._cluster.removeListener("reconnecting", handler);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
57
211
|
// src/adapters/redis/helpers.ts
|
|
58
212
|
var IoRedisDriver = class {
|
|
59
213
|
constructor(pub, sub, blocking) {
|
|
@@ -317,6 +471,9 @@ var RedisAdapter = class {
|
|
|
317
471
|
_unsubReconnect;
|
|
318
472
|
// Stored presence entries for rehydration on reconnect
|
|
319
473
|
_presenceCache = /* @__PURE__ */ new Map();
|
|
474
|
+
// Connection pool
|
|
475
|
+
_driverPool;
|
|
476
|
+
_nextPoolIndex;
|
|
320
477
|
constructor(options) {
|
|
321
478
|
this._prefix = options.prefix ?? "ocpp-ws-io:";
|
|
322
479
|
this._streamMaxLen = options.streamMaxLen ?? 1e3;
|
|
@@ -327,6 +484,14 @@ var RedisAdapter = class {
|
|
|
327
484
|
options.subClient,
|
|
328
485
|
options.blockingClient
|
|
329
486
|
);
|
|
487
|
+
const poolSize = options.poolSize ?? 1;
|
|
488
|
+
this._driverPool = [this._driver];
|
|
489
|
+
this._nextPoolIndex = 0;
|
|
490
|
+
if (poolSize > 1 && options.driverFactory) {
|
|
491
|
+
for (let i = 1; i < poolSize; i++) {
|
|
492
|
+
this._driverPool.push(options.driverFactory());
|
|
493
|
+
}
|
|
494
|
+
}
|
|
330
495
|
if (this._driver.onError) {
|
|
331
496
|
this._unsubError = this._driver.onError((err) => {
|
|
332
497
|
console.error("[RedisAdapter] Redis error:", err.message);
|
|
@@ -339,6 +504,13 @@ var RedisAdapter = class {
|
|
|
339
504
|
});
|
|
340
505
|
}
|
|
341
506
|
}
|
|
507
|
+
/** Get the next driver from the pool (round-robin) */
|
|
508
|
+
_getPoolDriver() {
|
|
509
|
+
if (this._driverPool.length === 1) return this._driver;
|
|
510
|
+
const driver = this._driverPool[this._nextPoolIndex];
|
|
511
|
+
this._nextPoolIndex = (this._nextPoolIndex + 1) % this._driverPool.length;
|
|
512
|
+
return driver;
|
|
513
|
+
}
|
|
342
514
|
async publish(channel, data) {
|
|
343
515
|
const prefixedChannel = this._prefix + channel;
|
|
344
516
|
const payload = data;
|
|
@@ -349,11 +521,12 @@ var RedisAdapter = class {
|
|
|
349
521
|
}
|
|
350
522
|
const message = JSON.stringify(data);
|
|
351
523
|
if (channel.startsWith("ocpp:node:")) {
|
|
352
|
-
|
|
353
|
-
await
|
|
524
|
+
const poolDriver = this._getPoolDriver();
|
|
525
|
+
await poolDriver.xadd(prefixedChannel, { message }, this._streamMaxLen);
|
|
526
|
+
await poolDriver.expire(prefixedChannel, this._streamTtlSeconds).catch(() => {
|
|
354
527
|
});
|
|
355
528
|
} else {
|
|
356
|
-
await this.
|
|
529
|
+
await this._getPoolDriver().publish(prefixedChannel, message);
|
|
357
530
|
}
|
|
358
531
|
}
|
|
359
532
|
async publishBatch(messages) {
|
|
@@ -370,13 +543,15 @@ var RedisAdapter = class {
|
|
|
370
543
|
}
|
|
371
544
|
const promises = [];
|
|
372
545
|
if (streamMessages.length > 0) {
|
|
373
|
-
promises.push(
|
|
546
|
+
promises.push(
|
|
547
|
+
this._getPoolDriver().xaddBatch(streamMessages, this._streamMaxLen)
|
|
548
|
+
);
|
|
374
549
|
}
|
|
375
550
|
if (broadcastMessages.length > 0) {
|
|
376
551
|
promises.push(
|
|
377
552
|
Promise.all(
|
|
378
553
|
broadcastMessages.map(
|
|
379
|
-
(bm) => this.
|
|
554
|
+
(bm) => this._getPoolDriver().publish(bm.channel, bm.message)
|
|
380
555
|
)
|
|
381
556
|
).then(() => {
|
|
382
557
|
})
|
|
@@ -421,7 +596,7 @@ var RedisAdapter = class {
|
|
|
421
596
|
this._sequenceCounters.clear();
|
|
422
597
|
if (this._unsubError) this._unsubError();
|
|
423
598
|
if (this._unsubReconnect) this._unsubReconnect();
|
|
424
|
-
await this.
|
|
599
|
+
await Promise.allSettled(this._driverPool.map((d) => d.disconnect()));
|
|
425
600
|
}
|
|
426
601
|
_handleMessage(channel, message) {
|
|
427
602
|
const handlers = this._handlers.get(channel);
|
|
@@ -450,7 +625,7 @@ var RedisAdapter = class {
|
|
|
450
625
|
async _pollLoop() {
|
|
451
626
|
while (!this._closed) {
|
|
452
627
|
if (this._streams.size === 0) {
|
|
453
|
-
await new Promise((
|
|
628
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1e3));
|
|
454
629
|
continue;
|
|
455
630
|
}
|
|
456
631
|
const streamsArg = Array.from(this._streams).map((key) => ({
|
|
@@ -472,7 +647,7 @@ var RedisAdapter = class {
|
|
|
472
647
|
}
|
|
473
648
|
}
|
|
474
649
|
} catch (_err) {
|
|
475
|
-
await new Promise((
|
|
650
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1e3));
|
|
476
651
|
}
|
|
477
652
|
}
|
|
478
653
|
this._polling = false;
|
|
@@ -551,8 +726,84 @@ var RedisAdapter = class {
|
|
|
551
726
|
}
|
|
552
727
|
};
|
|
553
728
|
|
|
554
|
-
// src/
|
|
729
|
+
// src/adaptive-limiter.ts
|
|
555
730
|
import { EventEmitter } from "events";
|
|
731
|
+
import { cpus, freemem, totalmem } from "os";
|
|
732
|
+
var AdaptiveLimiter = class extends EventEmitter {
|
|
733
|
+
_cpuThreshold;
|
|
734
|
+
_memThreshold;
|
|
735
|
+
_cooldownMs;
|
|
736
|
+
_sampleInterval;
|
|
737
|
+
_timer = null;
|
|
738
|
+
_lastOverloadTime = 0;
|
|
739
|
+
_multiplier = 1;
|
|
740
|
+
_prevCpuUsage = null;
|
|
741
|
+
_prevTimestamp = 0;
|
|
742
|
+
constructor(options = {}) {
|
|
743
|
+
super();
|
|
744
|
+
this._cpuThreshold = options.cpuThresholdPercent ?? 70;
|
|
745
|
+
this._memThreshold = options.memThresholdPercent ?? 85;
|
|
746
|
+
this._cooldownMs = options.cooldownMs ?? 1e4;
|
|
747
|
+
this._sampleInterval = options.sampleIntervalMs ?? 2e3;
|
|
748
|
+
}
|
|
749
|
+
/** Current rate multiplier: 1.0 = normal, 0.25 = heavily throttled */
|
|
750
|
+
get multiplier() {
|
|
751
|
+
return this._multiplier;
|
|
752
|
+
}
|
|
753
|
+
/** Start periodic sampling */
|
|
754
|
+
start() {
|
|
755
|
+
if (this._timer) return;
|
|
756
|
+
this._prevCpuUsage = process.cpuUsage();
|
|
757
|
+
this._prevTimestamp = Date.now();
|
|
758
|
+
this._timer = setInterval(() => this._sample(), this._sampleInterval);
|
|
759
|
+
if (this._timer.unref) this._timer.unref();
|
|
760
|
+
}
|
|
761
|
+
/** Stop sampling and reset multiplier */
|
|
762
|
+
stop() {
|
|
763
|
+
if (this._timer) {
|
|
764
|
+
clearInterval(this._timer);
|
|
765
|
+
this._timer = null;
|
|
766
|
+
}
|
|
767
|
+
this._multiplier = 1;
|
|
768
|
+
}
|
|
769
|
+
_sample() {
|
|
770
|
+
const now = Date.now();
|
|
771
|
+
const cpuUsage = process.cpuUsage(this._prevCpuUsage ?? void 0);
|
|
772
|
+
const elapsedMs = now - this._prevTimestamp;
|
|
773
|
+
const cpuPercent = (cpuUsage.user + cpuUsage.system) / 1e3 / elapsedMs / cpus().length * 100;
|
|
774
|
+
this._prevCpuUsage = process.cpuUsage();
|
|
775
|
+
this._prevTimestamp = now;
|
|
776
|
+
const total = totalmem();
|
|
777
|
+
const free = freemem();
|
|
778
|
+
const memPercent = (total - free) / total * 100;
|
|
779
|
+
const cpuOverload = cpuPercent > this._cpuThreshold;
|
|
780
|
+
const memOverload = memPercent > this._memThreshold;
|
|
781
|
+
const prevMultiplier = this._multiplier;
|
|
782
|
+
if (cpuOverload || memOverload) {
|
|
783
|
+
this._lastOverloadTime = now;
|
|
784
|
+
this._multiplier = Math.max(0.25, this._multiplier * 0.5);
|
|
785
|
+
} else if (now - this._lastOverloadTime > this._cooldownMs) {
|
|
786
|
+
this._multiplier = Math.min(1, this._multiplier + 0.1);
|
|
787
|
+
}
|
|
788
|
+
if (this._multiplier !== prevMultiplier) {
|
|
789
|
+
this.emit("adapted", {
|
|
790
|
+
multiplier: this._multiplier,
|
|
791
|
+
cpuPercent: Math.round(cpuPercent * 100) / 100,
|
|
792
|
+
memPercent: Math.round(memPercent * 100) / 100
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
// Typed event emitter overrides
|
|
797
|
+
on(event, listener) {
|
|
798
|
+
return super.on(event, listener);
|
|
799
|
+
}
|
|
800
|
+
emit(event, data) {
|
|
801
|
+
return super.emit(event, data);
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
// src/client.ts
|
|
806
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
556
807
|
import { createId } from "@paralleldrive/cuid2";
|
|
557
808
|
import WebSocket from "ws";
|
|
558
809
|
|
|
@@ -690,6 +941,9 @@ var RPCFrameworkError = class extends RPCGenericError {
|
|
|
690
941
|
function defineMiddleware(mw) {
|
|
691
942
|
return mw;
|
|
692
943
|
}
|
|
944
|
+
function createPlugin(plugin) {
|
|
945
|
+
return plugin;
|
|
946
|
+
}
|
|
693
947
|
function defineRpcMiddleware(mw) {
|
|
694
948
|
return mw;
|
|
695
949
|
}
|
|
@@ -1018,10 +1272,10 @@ var Queue = class {
|
|
|
1018
1272
|
this._drain();
|
|
1019
1273
|
}
|
|
1020
1274
|
push(fn) {
|
|
1021
|
-
return new Promise((
|
|
1275
|
+
return new Promise((resolve2, reject) => {
|
|
1022
1276
|
this._queue.push({
|
|
1023
1277
|
fn,
|
|
1024
|
-
resolve,
|
|
1278
|
+
resolve: resolve2,
|
|
1025
1279
|
reject
|
|
1026
1280
|
});
|
|
1027
1281
|
this._drain();
|
|
@@ -37121,7 +37375,7 @@ ${body}`
|
|
|
37121
37375
|
|
|
37122
37376
|
// src/client.ts
|
|
37123
37377
|
var { CONNECTING, OPEN, CLOSING, CLOSED } = ConnectionState;
|
|
37124
|
-
var OCPPClient = class _OCPPClient extends
|
|
37378
|
+
var OCPPClient = class _OCPPClient extends EventEmitter2 {
|
|
37125
37379
|
// Static connection states
|
|
37126
37380
|
static CONNECTING = CONNECTING;
|
|
37127
37381
|
static OPEN = OPEN;
|
|
@@ -37280,7 +37534,7 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37280
37534
|
return this._connectInternal();
|
|
37281
37535
|
}
|
|
37282
37536
|
async _connectInternal() {
|
|
37283
|
-
return new Promise((
|
|
37537
|
+
return new Promise((resolve2, reject) => {
|
|
37284
37538
|
const endpoint = this._buildEndpoint();
|
|
37285
37539
|
const wsOptions = this._buildWsOptions();
|
|
37286
37540
|
this._logger?.debug?.("Connecting", { url: endpoint });
|
|
@@ -37313,7 +37567,7 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37313
37567
|
response
|
|
37314
37568
|
};
|
|
37315
37569
|
this.emit("open", result);
|
|
37316
|
-
|
|
37570
|
+
resolve2(result);
|
|
37317
37571
|
};
|
|
37318
37572
|
const onError = (err) => {
|
|
37319
37573
|
cleanup();
|
|
@@ -37377,16 +37631,16 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37377
37631
|
this._stopPing();
|
|
37378
37632
|
if (!force && awaitPending) {
|
|
37379
37633
|
const pendingPromises = Array.from(this._pendingCalls.values()).map(
|
|
37380
|
-
(p) => new Promise((
|
|
37634
|
+
(p) => new Promise((resolve2) => {
|
|
37381
37635
|
const origResolve = p.resolve;
|
|
37382
37636
|
const origReject = p.reject;
|
|
37383
37637
|
p.resolve = (v) => {
|
|
37384
37638
|
origResolve(v);
|
|
37385
|
-
|
|
37639
|
+
resolve2();
|
|
37386
37640
|
};
|
|
37387
37641
|
p.reject = (r) => {
|
|
37388
37642
|
origReject(r);
|
|
37389
|
-
|
|
37643
|
+
resolve2();
|
|
37390
37644
|
};
|
|
37391
37645
|
})
|
|
37392
37646
|
);
|
|
@@ -37394,13 +37648,13 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37394
37648
|
await Promise.allSettled(pendingPromises);
|
|
37395
37649
|
}
|
|
37396
37650
|
}
|
|
37397
|
-
return new Promise((
|
|
37651
|
+
return new Promise((resolve2) => {
|
|
37398
37652
|
if (!this._ws || this._ws.readyState === WebSocket.CLOSED) {
|
|
37399
37653
|
this._state = CLOSED;
|
|
37400
37654
|
this._cleanup();
|
|
37401
37655
|
const result = { code, reason };
|
|
37402
37656
|
this.emit("close", result);
|
|
37403
|
-
|
|
37657
|
+
resolve2(result);
|
|
37404
37658
|
return;
|
|
37405
37659
|
}
|
|
37406
37660
|
const onClose = (closeCode, closeReason) => {
|
|
@@ -37409,7 +37663,7 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37409
37663
|
this._cleanup();
|
|
37410
37664
|
const result = { code: closeCode, reason: closeReason.toString() };
|
|
37411
37665
|
this.emit("close", result);
|
|
37412
|
-
|
|
37666
|
+
resolve2(result);
|
|
37413
37667
|
};
|
|
37414
37668
|
this._ws.on("close", onClose);
|
|
37415
37669
|
if (force) {
|
|
@@ -37484,7 +37738,7 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37484
37738
|
}
|
|
37485
37739
|
if (this._state !== OPEN) {
|
|
37486
37740
|
if (this._options.offlineQueue && (this._state === CLOSED || this._state === CONNECTING)) {
|
|
37487
|
-
return new Promise((
|
|
37741
|
+
return new Promise((resolve2, reject) => {
|
|
37488
37742
|
const maxSize = this._options.offlineQueueMaxSize ?? 100;
|
|
37489
37743
|
if (this._offlineQueue.length >= maxSize) {
|
|
37490
37744
|
this._offlineQueue.shift();
|
|
@@ -37496,7 +37750,7 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37496
37750
|
}
|
|
37497
37751
|
);
|
|
37498
37752
|
}
|
|
37499
|
-
this._offlineQueue.push({ method, params, options, resolve, reject });
|
|
37753
|
+
this._offlineQueue.push({ method, params, options, resolve: resolve2, reject });
|
|
37500
37754
|
this._logger?.debug?.("Call queued offline", {
|
|
37501
37755
|
method,
|
|
37502
37756
|
queueSize: this._offlineQueue.length
|
|
@@ -37555,7 +37809,7 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37555
37809
|
ctxvals.params
|
|
37556
37810
|
];
|
|
37557
37811
|
const messageStr = JSON.stringify(message);
|
|
37558
|
-
callResult = await new Promise((
|
|
37812
|
+
callResult = await new Promise((resolve2, reject) => {
|
|
37559
37813
|
const timeoutHandle = setTimeout(() => {
|
|
37560
37814
|
this._pendingCalls.delete(msgId);
|
|
37561
37815
|
reject(
|
|
@@ -37573,7 +37827,7 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37573
37827
|
options.signal.addEventListener("abort", abortHandler);
|
|
37574
37828
|
}
|
|
37575
37829
|
this._pendingCalls.set(msgId, {
|
|
37576
|
-
resolve,
|
|
37830
|
+
resolve: resolve2,
|
|
37577
37831
|
reject,
|
|
37578
37832
|
timeoutHandle,
|
|
37579
37833
|
abortHandler: options.signal ? () => options.signal?.removeEventListener("abort", abortHandler) : void 0,
|
|
@@ -37653,11 +37907,15 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
37653
37907
|
});
|
|
37654
37908
|
}
|
|
37655
37909
|
// ─── Internal: Message handling ──────────────────────────────
|
|
37656
|
-
_onMessage(rawData) {
|
|
37910
|
+
_onMessage(rawData, preParsed) {
|
|
37657
37911
|
this._recordActivity();
|
|
37658
37912
|
let message;
|
|
37659
37913
|
try {
|
|
37660
|
-
|
|
37914
|
+
if (preParsed !== void 0) {
|
|
37915
|
+
message = preParsed;
|
|
37916
|
+
} else {
|
|
37917
|
+
message = JSON.parse(rawData);
|
|
37918
|
+
}
|
|
37661
37919
|
if (!Array.isArray(message)) throw new Error("Message is not an array");
|
|
37662
37920
|
} catch (err) {
|
|
37663
37921
|
this._onBadMessage(
|
|
@@ -38176,6 +38434,23 @@ var OCPPClient = class _OCPPClient extends EventEmitter {
|
|
|
38176
38434
|
if (tls.passphrase) opts.passphrase = tls.passphrase;
|
|
38177
38435
|
}
|
|
38178
38436
|
}
|
|
38437
|
+
const compression = this._options.compression;
|
|
38438
|
+
if (compression) {
|
|
38439
|
+
opts.perMessageDeflate = compression === true ? {
|
|
38440
|
+
zlibDeflateOptions: { level: 6, memLevel: 8 },
|
|
38441
|
+
zlibInflateOptions: {},
|
|
38442
|
+
clientNoContextTakeover: true,
|
|
38443
|
+
serverNoContextTakeover: true
|
|
38444
|
+
} : {
|
|
38445
|
+
zlibDeflateOptions: {
|
|
38446
|
+
level: compression.level ?? 6,
|
|
38447
|
+
memLevel: compression.memLevel ?? 8
|
|
38448
|
+
},
|
|
38449
|
+
zlibInflateOptions: {},
|
|
38450
|
+
clientNoContextTakeover: compression.clientNoContextTakeover ?? true,
|
|
38451
|
+
serverNoContextTakeover: compression.serverNoContextTakeover ?? true
|
|
38452
|
+
};
|
|
38453
|
+
}
|
|
38179
38454
|
return opts;
|
|
38180
38455
|
}
|
|
38181
38456
|
// ─── Internal: Cleanup ───────────────────────────────────────
|
|
@@ -38241,7 +38516,7 @@ var LRUMap = class extends Map {
|
|
|
38241
38516
|
};
|
|
38242
38517
|
|
|
38243
38518
|
// src/router.ts
|
|
38244
|
-
import { EventEmitter as
|
|
38519
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
38245
38520
|
async function executeMiddlewareChain(middlewares, ctx) {
|
|
38246
38521
|
let index = -1;
|
|
38247
38522
|
const dispatch = async (i, payload) => {
|
|
@@ -38265,7 +38540,7 @@ async function executeMiddlewareChain(middlewares, ctx) {
|
|
|
38265
38540
|
};
|
|
38266
38541
|
await dispatch(0);
|
|
38267
38542
|
}
|
|
38268
|
-
var OCPPRouter = class extends
|
|
38543
|
+
var OCPPRouter = class extends EventEmitter3 {
|
|
38269
38544
|
/** Raw registered patterns (strings and/or RegExp) for reference. */
|
|
38270
38545
|
patterns;
|
|
38271
38546
|
/** Connection middlewares attached to this router. */
|
|
@@ -38361,7 +38636,7 @@ function createRouter(...patterns) {
|
|
|
38361
38636
|
}
|
|
38362
38637
|
|
|
38363
38638
|
// src/server.ts
|
|
38364
|
-
import { EventEmitter as
|
|
38639
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
38365
38640
|
import {
|
|
38366
38641
|
createServer as createHttpServer
|
|
38367
38642
|
} from "http";
|
|
@@ -38445,8 +38720,8 @@ var TrieNode = class {
|
|
|
38445
38720
|
/** Routers registered at this exact node (leaf). */
|
|
38446
38721
|
routers = [];
|
|
38447
38722
|
};
|
|
38448
|
-
function normalizePath(
|
|
38449
|
-
return
|
|
38723
|
+
function normalizePath(path2) {
|
|
38724
|
+
return path2.replace(/\/+/g, "/").replace(/^\/|\/$/g, "").split("/").filter(Boolean);
|
|
38450
38725
|
}
|
|
38451
38726
|
var RadixTrie = class {
|
|
38452
38727
|
root = new TrieNode();
|
|
@@ -38593,6 +38868,8 @@ var OCPPServerClient = class extends OCPPClient {
|
|
|
38593
38868
|
super(options);
|
|
38594
38869
|
this._serverSession = context.session;
|
|
38595
38870
|
this._serverHandshake = context.handshake;
|
|
38871
|
+
this._adaptiveMultiplier = context.adaptiveMultiplier ?? null;
|
|
38872
|
+
this._workerPool = context.workerPool ?? null;
|
|
38596
38873
|
this._state = ConnectionState.OPEN;
|
|
38597
38874
|
this._identity = this._options.identity;
|
|
38598
38875
|
this._ws = context.ws;
|
|
@@ -38602,6 +38879,8 @@ var OCPPServerClient = class extends OCPPClient {
|
|
|
38602
38879
|
}
|
|
38603
38880
|
// ─── Rate Limiting State ──────────────────────────────────────────
|
|
38604
38881
|
_rateLimits = {};
|
|
38882
|
+
_adaptiveMultiplier = null;
|
|
38883
|
+
_workerPool = null;
|
|
38605
38884
|
_checkRateLimit(method) {
|
|
38606
38885
|
const limits = this._options.rateLimit;
|
|
38607
38886
|
if (!limits) return true;
|
|
@@ -38613,7 +38892,8 @@ var OCPPServerClient = class extends OCPPClient {
|
|
|
38613
38892
|
this._rateLimits[key] = bucket;
|
|
38614
38893
|
} else {
|
|
38615
38894
|
const timePassed = now - bucket.lastRefill;
|
|
38616
|
-
const
|
|
38895
|
+
const adaptiveScale = this._adaptiveMultiplier?.() ?? 1;
|
|
38896
|
+
const refillRate = limit / windowMs * adaptiveScale;
|
|
38617
38897
|
const tokensToAdd = timePassed * refillRate;
|
|
38618
38898
|
if (tokensToAdd > 0) {
|
|
38619
38899
|
bucket.tokens = Math.min(limit, bucket.tokens + tokensToAdd);
|
|
@@ -38658,6 +38938,19 @@ var OCPPServerClient = class extends OCPPClient {
|
|
|
38658
38938
|
this._handleRateLimitExceeded(pData || data.toString());
|
|
38659
38939
|
return;
|
|
38660
38940
|
}
|
|
38941
|
+
if (pData !== void 0) {
|
|
38942
|
+
this._onMessage(data, pData);
|
|
38943
|
+
return;
|
|
38944
|
+
}
|
|
38945
|
+
}
|
|
38946
|
+
if (this._workerPool) {
|
|
38947
|
+
const raw = typeof data === "string" ? data : data;
|
|
38948
|
+
this._workerPool.parse(raw).then((result) => {
|
|
38949
|
+
this._onMessage(data, result.message);
|
|
38950
|
+
}).catch(() => {
|
|
38951
|
+
this._onMessage(data);
|
|
38952
|
+
});
|
|
38953
|
+
return;
|
|
38661
38954
|
}
|
|
38662
38955
|
this._onMessage(data);
|
|
38663
38956
|
});
|
|
@@ -38758,8 +39051,110 @@ var OCPPServerClient = class extends OCPPClient {
|
|
|
38758
39051
|
}
|
|
38759
39052
|
};
|
|
38760
39053
|
|
|
39054
|
+
// src/worker-pool.ts
|
|
39055
|
+
import { cpus as cpus2 } from "os";
|
|
39056
|
+
import { resolve } from "path";
|
|
39057
|
+
import { Worker } from "worker_threads";
|
|
39058
|
+
var WorkerPool = class {
|
|
39059
|
+
_workers = [];
|
|
39060
|
+
_nextWorker = 0;
|
|
39061
|
+
_taskId = 0;
|
|
39062
|
+
_pending = /* @__PURE__ */ new Map();
|
|
39063
|
+
_maxQueueSize;
|
|
39064
|
+
_terminated = false;
|
|
39065
|
+
constructor(options = {}) {
|
|
39066
|
+
const poolSize = options.poolSize ?? Math.max(2, cpus2().length - 2);
|
|
39067
|
+
this._maxQueueSize = options.maxQueueSize ?? 1e4;
|
|
39068
|
+
const workerPath = resolve(__dirname, "parse-worker.js");
|
|
39069
|
+
for (let i = 0; i < poolSize; i++) {
|
|
39070
|
+
const worker = new Worker(workerPath);
|
|
39071
|
+
worker.on(
|
|
39072
|
+
"message",
|
|
39073
|
+
(response) => {
|
|
39074
|
+
const task = this._pending.get(response.id);
|
|
39075
|
+
if (!task) return;
|
|
39076
|
+
this._pending.delete(response.id);
|
|
39077
|
+
if (response.error) {
|
|
39078
|
+
task.reject(new Error(response.error));
|
|
39079
|
+
} else {
|
|
39080
|
+
task.resolve({
|
|
39081
|
+
message: response.message,
|
|
39082
|
+
validationError: response.validationError
|
|
39083
|
+
});
|
|
39084
|
+
}
|
|
39085
|
+
}
|
|
39086
|
+
);
|
|
39087
|
+
worker.on("error", (err) => {
|
|
39088
|
+
console.error(`[WorkerPool] Worker ${i} error:`, err.message);
|
|
39089
|
+
});
|
|
39090
|
+
this._workers.push(worker);
|
|
39091
|
+
}
|
|
39092
|
+
}
|
|
39093
|
+
/** Number of worker threads in the pool */
|
|
39094
|
+
get size() {
|
|
39095
|
+
return this._workers.length;
|
|
39096
|
+
}
|
|
39097
|
+
/** Number of pending (unresolved) parse tasks */
|
|
39098
|
+
get pendingTasks() {
|
|
39099
|
+
return this._pending.size;
|
|
39100
|
+
}
|
|
39101
|
+
/**
|
|
39102
|
+
* Send raw data to a worker for JSON parsing + optional validation.
|
|
39103
|
+
* Uses round-robin worker selection.
|
|
39104
|
+
*/
|
|
39105
|
+
parse(data, schemaInfo) {
|
|
39106
|
+
if (this._terminated) {
|
|
39107
|
+
return Promise.reject(new Error("WorkerPool has been shut down"));
|
|
39108
|
+
}
|
|
39109
|
+
if (this._pending.size >= this._maxQueueSize) {
|
|
39110
|
+
return Promise.reject(
|
|
39111
|
+
new Error(
|
|
39112
|
+
`WorkerPool queue full (${this._maxQueueSize} pending tasks)`
|
|
39113
|
+
)
|
|
39114
|
+
);
|
|
39115
|
+
}
|
|
39116
|
+
return new Promise((resolve2, reject) => {
|
|
39117
|
+
const id = this._taskId++;
|
|
39118
|
+
this._pending.set(id, {
|
|
39119
|
+
resolve: resolve2,
|
|
39120
|
+
reject
|
|
39121
|
+
});
|
|
39122
|
+
const worker = this._workers[this._nextWorker % this._workers.length];
|
|
39123
|
+
this._nextWorker = (this._nextWorker + 1) % this._workers.length;
|
|
39124
|
+
worker.postMessage({ id, buffer: data, schemaInfo });
|
|
39125
|
+
});
|
|
39126
|
+
}
|
|
39127
|
+
/** Gracefully terminate all workers */
|
|
39128
|
+
async shutdown() {
|
|
39129
|
+
if (this._terminated) return;
|
|
39130
|
+
this._terminated = true;
|
|
39131
|
+
for (const [id, task] of this._pending) {
|
|
39132
|
+
task.reject(new Error("WorkerPool shutting down"));
|
|
39133
|
+
this._pending.delete(id);
|
|
39134
|
+
}
|
|
39135
|
+
const terminatePromises = this._workers.map(async (worker) => {
|
|
39136
|
+
try {
|
|
39137
|
+
await Promise.race([
|
|
39138
|
+
worker.terminate(),
|
|
39139
|
+
new Promise((resolve2) => setTimeout(resolve2, 5e3))
|
|
39140
|
+
]);
|
|
39141
|
+
} catch {
|
|
39142
|
+
}
|
|
39143
|
+
});
|
|
39144
|
+
await Promise.allSettled(terminatePromises);
|
|
39145
|
+
this._workers = [];
|
|
39146
|
+
}
|
|
39147
|
+
};
|
|
39148
|
+
function createWorkerPool(options = {}) {
|
|
39149
|
+
try {
|
|
39150
|
+
return new WorkerPool(options);
|
|
39151
|
+
} catch {
|
|
39152
|
+
return null;
|
|
39153
|
+
}
|
|
39154
|
+
}
|
|
39155
|
+
|
|
38761
39156
|
// src/server.ts
|
|
38762
|
-
var OCPPServer = class extends
|
|
39157
|
+
var OCPPServer = class extends EventEmitter4 {
|
|
38763
39158
|
_options;
|
|
38764
39159
|
/** Radix trie for O(k) route matching (string patterns). */
|
|
38765
39160
|
_trie = new RadixTrie();
|
|
@@ -38778,6 +39173,9 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
38778
39173
|
_globalCORS;
|
|
38779
39174
|
// Connection-level rate limiting (per-IP token bucket)
|
|
38780
39175
|
_connectionBuckets = /* @__PURE__ */ new Map();
|
|
39176
|
+
_adaptiveLimiter = null;
|
|
39177
|
+
_plugins = [];
|
|
39178
|
+
_workerPool = null;
|
|
38781
39179
|
// Robustness & Clustering
|
|
38782
39180
|
_nodeId = createId2();
|
|
38783
39181
|
_sessions;
|
|
@@ -38810,7 +39208,8 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
38810
39208
|
this._sessions = new LRUMap(maxSessions);
|
|
38811
39209
|
this._wss = new WebSocketServer({
|
|
38812
39210
|
noServer: true,
|
|
38813
|
-
maxPayload: this._options.maxPayloadBytes ?? 65536
|
|
39211
|
+
maxPayload: this._options.maxPayloadBytes ?? 65536,
|
|
39212
|
+
perMessageDeflate: this._buildCompressionConfig()
|
|
38814
39213
|
});
|
|
38815
39214
|
this._gcInterval = setInterval(() => {
|
|
38816
39215
|
const now = Date.now();
|
|
@@ -38823,6 +39222,32 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
38823
39222
|
this._logger = initLogger(this._options.logging, {
|
|
38824
39223
|
component: "OCPPServer"
|
|
38825
39224
|
});
|
|
39225
|
+
const rl = this._options.rateLimit;
|
|
39226
|
+
if (rl?.adaptive) {
|
|
39227
|
+
this._adaptiveLimiter = new AdaptiveLimiter({
|
|
39228
|
+
cpuThresholdPercent: rl.cpuThresholdPercent,
|
|
39229
|
+
memThresholdPercent: rl.memThresholdPercent,
|
|
39230
|
+
cooldownMs: rl.cooldownMs
|
|
39231
|
+
});
|
|
39232
|
+
this._adaptiveLimiter.on(
|
|
39233
|
+
"adapted",
|
|
39234
|
+
(event) => {
|
|
39235
|
+
this._logger?.info?.("Adaptive rate limit adjusted", event);
|
|
39236
|
+
this.emit("rateLimit:adapted", event);
|
|
39237
|
+
}
|
|
39238
|
+
);
|
|
39239
|
+
this._adaptiveLimiter.start();
|
|
39240
|
+
}
|
|
39241
|
+
const wt = this._options.workerThreads;
|
|
39242
|
+
if (wt) {
|
|
39243
|
+
const poolOpts = typeof wt === "object" ? wt : {};
|
|
39244
|
+
this._workerPool = createWorkerPool(poolOpts);
|
|
39245
|
+
if (this._workerPool) {
|
|
39246
|
+
this._logger?.info?.("Worker thread pool initialized", {
|
|
39247
|
+
poolSize: this._workerPool.size
|
|
39248
|
+
});
|
|
39249
|
+
}
|
|
39250
|
+
}
|
|
38826
39251
|
}
|
|
38827
39252
|
// ─── Getters ─────────────────────────────────────────────────
|
|
38828
39253
|
get log() {
|
|
@@ -38945,8 +39370,44 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
38945
39370
|
return this;
|
|
38946
39371
|
}
|
|
38947
39372
|
/**
|
|
38948
|
-
* Registers
|
|
38949
|
-
*
|
|
39373
|
+
* Registers one or more plugins for server lifecycle hooks.
|
|
39374
|
+
* Plugins are called in registration order for all lifecycle events.
|
|
39375
|
+
*
|
|
39376
|
+
* @example Single plugin
|
|
39377
|
+
* ```ts
|
|
39378
|
+
* server.plugin(metricsPlugin);
|
|
39379
|
+
* ```
|
|
39380
|
+
*
|
|
39381
|
+
* @example Multiple plugins
|
|
39382
|
+
* ```ts
|
|
39383
|
+
* server.plugin(metricsPlugin, loggingPlugin, otelPlugin);
|
|
39384
|
+
* ```
|
|
39385
|
+
*/
|
|
39386
|
+
plugin(...plugins) {
|
|
39387
|
+
for (const plugin of plugins) {
|
|
39388
|
+
this._plugins.push(plugin);
|
|
39389
|
+
this._logger?.info?.("Plugin registered", { name: plugin.name });
|
|
39390
|
+
if (plugin.onInit) {
|
|
39391
|
+
const result = plugin.onInit(this);
|
|
39392
|
+
if (result instanceof Promise) {
|
|
39393
|
+
result.catch((err) => {
|
|
39394
|
+
this._logger?.error?.("Plugin onInit error", {
|
|
39395
|
+
name: plugin.name,
|
|
39396
|
+
error: err.message
|
|
39397
|
+
});
|
|
39398
|
+
});
|
|
39399
|
+
}
|
|
39400
|
+
}
|
|
39401
|
+
}
|
|
39402
|
+
return this;
|
|
39403
|
+
}
|
|
39404
|
+
/**
|
|
39405
|
+
* Registers middleware chain(s) as a wildcard/catch-all router.
|
|
39406
|
+
*
|
|
39407
|
+
* @example
|
|
39408
|
+
* ```ts
|
|
39409
|
+
* server.use(myMiddleware).route("/api").on("client", ...);
|
|
39410
|
+
* ```
|
|
38950
39411
|
*/
|
|
38951
39412
|
use(...middlewares) {
|
|
38952
39413
|
const router = new OCPPRouter();
|
|
@@ -39102,7 +39563,7 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
39102
39563
|
);
|
|
39103
39564
|
}
|
|
39104
39565
|
if (!options?.server) {
|
|
39105
|
-
await new Promise((
|
|
39566
|
+
await new Promise((resolve2, reject) => {
|
|
39106
39567
|
httpServer.on("error", reject);
|
|
39107
39568
|
httpServer.listen(port, host, () => {
|
|
39108
39569
|
httpServer.removeListener("error", reject);
|
|
@@ -39111,7 +39572,7 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
39111
39572
|
port: typeof addr === "object" ? addr?.port : port,
|
|
39112
39573
|
host: host ?? "0.0.0.0"
|
|
39113
39574
|
});
|
|
39114
|
-
|
|
39575
|
+
resolve2();
|
|
39115
39576
|
});
|
|
39116
39577
|
});
|
|
39117
39578
|
}
|
|
@@ -39420,13 +39881,13 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
39420
39881
|
selectedProtocol = handshake.protocols.values().next().value ?? void 0;
|
|
39421
39882
|
} else {
|
|
39422
39883
|
acceptOptions = await new Promise(
|
|
39423
|
-
(
|
|
39884
|
+
(resolve2, reject) => {
|
|
39424
39885
|
let settled = false;
|
|
39425
39886
|
const accept = (opts) => {
|
|
39426
39887
|
if (settled) return;
|
|
39427
39888
|
settled = true;
|
|
39428
39889
|
if (opts?.protocol) selectedProtocol = opts.protocol;
|
|
39429
|
-
|
|
39890
|
+
resolve2(opts);
|
|
39430
39891
|
};
|
|
39431
39892
|
const rejectAuth = (code = 401, message = "Unauthorized") => {
|
|
39432
39893
|
if (!settled) {
|
|
@@ -39558,7 +40019,9 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
39558
40019
|
ws,
|
|
39559
40020
|
handshake,
|
|
39560
40021
|
session: finalSession,
|
|
39561
|
-
protocol: selectedProtocol
|
|
40022
|
+
protocol: selectedProtocol,
|
|
40023
|
+
adaptiveMultiplier: this._adaptiveLimiter ? () => this._adaptiveLimiter.multiplier : void 0,
|
|
40024
|
+
workerPool: this._workerPool ?? void 0
|
|
39562
40025
|
});
|
|
39563
40026
|
this._updateSessionActivity(identity, client.session);
|
|
39564
40027
|
const existingClient = this._clientsByIdentity.get(identity);
|
|
@@ -39590,21 +40053,52 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
39590
40053
|
remoteAddress: req.socket.remoteAddress,
|
|
39591
40054
|
protocol: selectedProtocol
|
|
39592
40055
|
});
|
|
39593
|
-
client.on(
|
|
39594
|
-
|
|
39595
|
-
|
|
39596
|
-
this.
|
|
40056
|
+
client.on(
|
|
40057
|
+
"close",
|
|
40058
|
+
({ code, reason }) => {
|
|
40059
|
+
this._clients.delete(client);
|
|
40060
|
+
if (this._clientsByIdentity.get(identity) === client) {
|
|
40061
|
+
this._clientsByIdentity.delete(identity);
|
|
40062
|
+
}
|
|
40063
|
+
if (this?._adapter?.removePresence) {
|
|
40064
|
+
this._adapter.removePresence(identity).catch((err) => {
|
|
40065
|
+
this._logger?.error?.("Error removing presence", {
|
|
40066
|
+
identity,
|
|
40067
|
+
error: err
|
|
40068
|
+
});
|
|
40069
|
+
});
|
|
40070
|
+
}
|
|
40071
|
+
for (const plugin of this._plugins) {
|
|
40072
|
+
try {
|
|
40073
|
+
plugin.onDisconnect?.(client, code, reason);
|
|
40074
|
+
} catch (err) {
|
|
40075
|
+
this._logger?.error?.("Plugin onDisconnect error", {
|
|
40076
|
+
name: plugin.name,
|
|
40077
|
+
error: err.message
|
|
40078
|
+
});
|
|
40079
|
+
}
|
|
40080
|
+
}
|
|
40081
|
+
this._logger?.info?.("Client disconnected", { identity });
|
|
39597
40082
|
}
|
|
39598
|
-
|
|
39599
|
-
|
|
39600
|
-
|
|
39601
|
-
|
|
39602
|
-
|
|
40083
|
+
);
|
|
40084
|
+
for (const plugin of this._plugins) {
|
|
40085
|
+
try {
|
|
40086
|
+
const result = plugin.onConnection?.(client);
|
|
40087
|
+
if (result instanceof Promise) {
|
|
40088
|
+
result.catch((err) => {
|
|
40089
|
+
this._logger?.error?.("Plugin onConnection error", {
|
|
40090
|
+
name: plugin.name,
|
|
40091
|
+
error: err.message
|
|
40092
|
+
});
|
|
39603
40093
|
});
|
|
40094
|
+
}
|
|
40095
|
+
} catch (err) {
|
|
40096
|
+
this._logger?.error?.("Plugin onConnection error", {
|
|
40097
|
+
name: plugin.name,
|
|
40098
|
+
error: err.message
|
|
39604
40099
|
});
|
|
39605
40100
|
}
|
|
39606
|
-
|
|
39607
|
-
});
|
|
40101
|
+
}
|
|
39608
40102
|
this.emit("client", client);
|
|
39609
40103
|
for (const router of matchedRouters) {
|
|
39610
40104
|
router.emit("client", client);
|
|
@@ -39639,13 +40133,13 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
39639
40133
|
identity: client.identity,
|
|
39640
40134
|
bufferedAmount: ws.bufferedAmount
|
|
39641
40135
|
});
|
|
39642
|
-
await new Promise((
|
|
40136
|
+
await new Promise((resolve2) => {
|
|
39643
40137
|
let elapsed = 0;
|
|
39644
40138
|
const check = setInterval(() => {
|
|
39645
40139
|
elapsed += 50;
|
|
39646
40140
|
if (!ws || ws.bufferedAmount === 0 || elapsed >= drainTimeout) {
|
|
39647
40141
|
clearInterval(check);
|
|
39648
|
-
|
|
40142
|
+
resolve2();
|
|
39649
40143
|
}
|
|
39650
40144
|
}, 50);
|
|
39651
40145
|
});
|
|
@@ -39667,12 +40161,28 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
39667
40161
|
this._wss = new WebSocketServer({ noServer: true });
|
|
39668
40162
|
}
|
|
39669
40163
|
const serverClosePromises = Array.from(this._httpServers).map(
|
|
39670
|
-
(server) => new Promise((
|
|
39671
|
-
server.close(() =>
|
|
40164
|
+
(server) => new Promise((resolve2) => {
|
|
40165
|
+
server.close(() => resolve2());
|
|
39672
40166
|
})
|
|
39673
40167
|
);
|
|
39674
40168
|
await Promise.allSettled(serverClosePromises);
|
|
39675
40169
|
this._httpServers.clear();
|
|
40170
|
+
if (this._adaptiveLimiter) {
|
|
40171
|
+
this._adaptiveLimiter.stop();
|
|
40172
|
+
}
|
|
40173
|
+
for (const plugin of this._plugins) {
|
|
40174
|
+
try {
|
|
40175
|
+
const result = plugin.onClose?.();
|
|
40176
|
+
if (result instanceof Promise) {
|
|
40177
|
+
await result;
|
|
40178
|
+
}
|
|
40179
|
+
} catch (err) {
|
|
40180
|
+
this._logger?.error?.("Plugin onClose error", {
|
|
40181
|
+
name: plugin.name,
|
|
40182
|
+
error: err.message
|
|
40183
|
+
});
|
|
40184
|
+
}
|
|
40185
|
+
}
|
|
39676
40186
|
if (this._adapter) {
|
|
39677
40187
|
await this._adapter.disconnect();
|
|
39678
40188
|
}
|
|
@@ -39734,6 +40244,55 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
39734
40244
|
return void 0;
|
|
39735
40245
|
}
|
|
39736
40246
|
}
|
|
40247
|
+
// ─── Batch Calls ──────────────────────────────────────────────
|
|
40248
|
+
/**
|
|
40249
|
+
* Pipeline multiple calls to a single client into a concurrent batch.
|
|
40250
|
+
* Useful for reconnection warm-up (e.g. GetConfiguration, ChangeAvailability, etc.)
|
|
40251
|
+
* where sequential calls would add unnecessary round-trip latency.
|
|
40252
|
+
*
|
|
40253
|
+
* @param identity The client identity to send calls to
|
|
40254
|
+
* @param calls Array of { method, params, options? } to execute concurrently
|
|
40255
|
+
* @returns Array of results in the same order as the calls array.
|
|
40256
|
+
* Each element is the call result, or `undefined` if that individual call failed.
|
|
40257
|
+
*
|
|
40258
|
+
* @example
|
|
40259
|
+
* ```ts
|
|
40260
|
+
* const results = await server.sendBatch('CP-101', [
|
|
40261
|
+
* { method: 'GetConfiguration', params: { key: ['MeterInterval'] } },
|
|
40262
|
+
* { method: 'ChangeAvailability', params: { type: 'Operative' } },
|
|
40263
|
+
* { method: 'TriggerMessage', params: { requestedMessage: 'StatusNotification' } },
|
|
40264
|
+
* ]);
|
|
40265
|
+
* ```
|
|
40266
|
+
*/
|
|
40267
|
+
async sendBatch(identity, calls) {
|
|
40268
|
+
if (calls.length === 0) return [];
|
|
40269
|
+
const client = this._clientsByIdentity.get(identity);
|
|
40270
|
+
if (!client) {
|
|
40271
|
+
this._logger?.warn?.("sendBatch: client not found locally", { identity });
|
|
40272
|
+
return calls.map(() => void 0);
|
|
40273
|
+
}
|
|
40274
|
+
const originalConcurrency = client.options.callConcurrency ?? 1;
|
|
40275
|
+
if (calls.length > originalConcurrency) {
|
|
40276
|
+
client.reconfigure({ callConcurrency: calls.length });
|
|
40277
|
+
}
|
|
40278
|
+
try {
|
|
40279
|
+
const results = await Promise.allSettled(
|
|
40280
|
+
calls.map((c) => client.call(c.method, c.params, c.options ?? {}))
|
|
40281
|
+
);
|
|
40282
|
+
return results.map((r) => {
|
|
40283
|
+
if (r.status === "fulfilled") return r.value;
|
|
40284
|
+
this._logger?.warn?.("sendBatch: individual call failed", {
|
|
40285
|
+
identity,
|
|
40286
|
+
error: r.reason?.message
|
|
40287
|
+
});
|
|
40288
|
+
return void 0;
|
|
40289
|
+
});
|
|
40290
|
+
} finally {
|
|
40291
|
+
if (calls.length > originalConcurrency) {
|
|
40292
|
+
client.reconfigure({ callConcurrency: originalConcurrency });
|
|
40293
|
+
}
|
|
40294
|
+
}
|
|
40295
|
+
}
|
|
39737
40296
|
// ─── Pub/Sub Adapter ─────────────────────────────────────────
|
|
39738
40297
|
async setAdapter(adapter) {
|
|
39739
40298
|
this._adapter = adapter;
|
|
@@ -39875,8 +40434,34 @@ var OCPPServer = class extends EventEmitter3 {
|
|
|
39875
40434
|
}
|
|
39876
40435
|
await Promise.all(localPromises);
|
|
39877
40436
|
}
|
|
40437
|
+
// ─── Internal: Compression Config ───────────────────────────────
|
|
40438
|
+
_buildCompressionConfig() {
|
|
40439
|
+
const compression = this._options.compression;
|
|
40440
|
+
if (!compression) return false;
|
|
40441
|
+
if (compression === true) {
|
|
40442
|
+
return {
|
|
40443
|
+
threshold: 1024,
|
|
40444
|
+
zlibDeflateOptions: { level: 6, memLevel: 8 },
|
|
40445
|
+
zlibInflateOptions: {},
|
|
40446
|
+
serverNoContextTakeover: true,
|
|
40447
|
+
clientNoContextTakeover: true
|
|
40448
|
+
};
|
|
40449
|
+
}
|
|
40450
|
+
return {
|
|
40451
|
+
threshold: compression.threshold ?? 1024,
|
|
40452
|
+
zlibDeflateOptions: {
|
|
40453
|
+
level: compression.level ?? 6,
|
|
40454
|
+
memLevel: compression.memLevel ?? 8
|
|
40455
|
+
},
|
|
40456
|
+
zlibInflateOptions: {},
|
|
40457
|
+
serverNoContextTakeover: compression.serverNoContextTakeover ?? true,
|
|
40458
|
+
clientNoContextTakeover: compression.clientNoContextTakeover ?? true
|
|
40459
|
+
};
|
|
40460
|
+
}
|
|
39878
40461
|
};
|
|
39879
40462
|
export {
|
|
40463
|
+
AdaptiveLimiter,
|
|
40464
|
+
ClusterDriver,
|
|
39880
40465
|
ConnectionState,
|
|
39881
40466
|
InMemoryAdapter,
|
|
39882
40467
|
LRUMap,
|
|
@@ -39908,6 +40493,7 @@ export {
|
|
|
39908
40493
|
WebsocketUpgradeError,
|
|
39909
40494
|
combineAuth,
|
|
39910
40495
|
createLoggingMiddleware,
|
|
40496
|
+
createPlugin,
|
|
39911
40497
|
createRPCError,
|
|
39912
40498
|
createRouter,
|
|
39913
40499
|
createValidator,
|