ocpp-ws-io 2.1.9 → 2.1.11
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/adapters/redis.js +1 -547
- package/dist/adapters/redis.js.map +1 -1
- package/dist/adapters/redis.mjs +1 -520
- package/dist/adapters/redis.mjs.map +1 -1
- package/dist/browser.js +1 -1317
- package/dist/browser.mjs +1 -1298
- package/dist/index.js +4826 -40572
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4826 -40513
- package/dist/index.mjs.map +1 -1
- package/dist/logger.js +1 -24
- package/dist/logger.js.map +1 -1
- package/dist/logger.mjs +1 -2
- package/dist/logger.mjs.map +1 -1
- package/dist/plugins.js +1 -369
- package/dist/plugins.js.map +1 -1
- package/dist/plugins.mjs +1 -349
- package/dist/plugins.mjs.map +1 -1
- package/package.json +2 -3
- package/dist/browser.js.map +0 -1
- package/dist/browser.mjs.map +0 -1
package/dist/adapters/redis.js
CHANGED
|
@@ -1,548 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/adapters/redis/index.ts
|
|
21
|
-
var redis_exports = {};
|
|
22
|
-
__export(redis_exports, {
|
|
23
|
-
RedisAdapter: () => RedisAdapter
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(redis_exports);
|
|
26
|
-
|
|
27
|
-
// src/adapters/redis/helpers.ts
|
|
28
|
-
var IoRedisDriver = class {
|
|
29
|
-
constructor(pub, sub, blocking) {
|
|
30
|
-
this.pub = pub;
|
|
31
|
-
this.sub = sub;
|
|
32
|
-
this.blocking = blocking;
|
|
33
|
-
if (this.sub.on) {
|
|
34
|
-
this.sub.on("message", (channel, message) => {
|
|
35
|
-
const handler = this._handlers.get(channel);
|
|
36
|
-
if (handler) handler(message);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
_handlers = /* @__PURE__ */ new Map();
|
|
41
|
-
async publish(channel, message) {
|
|
42
|
-
await this.pub.publish(channel, message);
|
|
43
|
-
}
|
|
44
|
-
async subscribe(channel, handler) {
|
|
45
|
-
this._handlers.set(channel, handler);
|
|
46
|
-
await this.sub.subscribe(channel);
|
|
47
|
-
}
|
|
48
|
-
async unsubscribe(channel) {
|
|
49
|
-
await this.sub.unsubscribe(channel);
|
|
50
|
-
this._handlers.delete(channel);
|
|
51
|
-
}
|
|
52
|
-
async set(key, value, ttlSeconds) {
|
|
53
|
-
if (ttlSeconds) {
|
|
54
|
-
await this.pub.set(key, value, "EX", ttlSeconds);
|
|
55
|
-
} else {
|
|
56
|
-
await this.pub.set(key, value);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
async get(key) {
|
|
60
|
-
return await this.pub.get(key) || null;
|
|
61
|
-
}
|
|
62
|
-
async mget(keys) {
|
|
63
|
-
if (keys.length === 0) return [];
|
|
64
|
-
return await this.pub.mget(...keys);
|
|
65
|
-
}
|
|
66
|
-
async del(key) {
|
|
67
|
-
await this.pub.del(key);
|
|
68
|
-
}
|
|
69
|
-
async xadd(stream, args, maxLen) {
|
|
70
|
-
const flatArgs = [];
|
|
71
|
-
if (maxLen) {
|
|
72
|
-
flatArgs.push("MAXLEN", "~", maxLen.toString());
|
|
73
|
-
}
|
|
74
|
-
flatArgs.push("*");
|
|
75
|
-
for (const [k, v] of Object.entries(args)) {
|
|
76
|
-
flatArgs.push(k, v);
|
|
77
|
-
}
|
|
78
|
-
return await this.pub.xadd(stream, ...flatArgs);
|
|
79
|
-
}
|
|
80
|
-
async xaddBatch(messages, maxLen) {
|
|
81
|
-
if (messages.length === 0) return;
|
|
82
|
-
const pipeline = this.pub.pipeline();
|
|
83
|
-
for (const msg of messages) {
|
|
84
|
-
const flatArgs = [];
|
|
85
|
-
if (maxLen) {
|
|
86
|
-
flatArgs.push("MAXLEN", "~", maxLen.toString());
|
|
87
|
-
}
|
|
88
|
-
flatArgs.push("*");
|
|
89
|
-
for (const [k, v] of Object.entries(msg.args)) {
|
|
90
|
-
flatArgs.push(k, v);
|
|
91
|
-
}
|
|
92
|
-
pipeline.xadd(msg.stream, ...flatArgs);
|
|
93
|
-
}
|
|
94
|
-
await pipeline.exec();
|
|
95
|
-
}
|
|
96
|
-
async xread(streams, count, block) {
|
|
97
|
-
const args = [];
|
|
98
|
-
if (count) {
|
|
99
|
-
args.push("COUNT", count);
|
|
100
|
-
}
|
|
101
|
-
if (typeof block === "number") {
|
|
102
|
-
args.push("BLOCK", block);
|
|
103
|
-
}
|
|
104
|
-
args.push("STREAMS");
|
|
105
|
-
streams.forEach((s) => {
|
|
106
|
-
args.push(s.key);
|
|
107
|
-
});
|
|
108
|
-
streams.forEach((s) => {
|
|
109
|
-
args.push(s.id);
|
|
110
|
-
});
|
|
111
|
-
const client = block && this.blocking ? this.blocking : this.pub;
|
|
112
|
-
const result = await client.xread(...args);
|
|
113
|
-
if (!result) return null;
|
|
114
|
-
return result.map(([stream, messages]) => ({
|
|
115
|
-
stream,
|
|
116
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
-
messages: messages.map(([id, fields]) => {
|
|
118
|
-
const data = {};
|
|
119
|
-
for (let i = 0; i < fields.length; i += 2) {
|
|
120
|
-
data[fields[i]] = fields[i + 1];
|
|
121
|
-
}
|
|
122
|
-
return { id, data };
|
|
123
|
-
})
|
|
124
|
-
}));
|
|
125
|
-
}
|
|
126
|
-
async xlen(stream) {
|
|
127
|
-
return await this.pub.xlen(stream);
|
|
128
|
-
}
|
|
129
|
-
async disconnect() {
|
|
130
|
-
this._handlers.clear();
|
|
131
|
-
const close = async (c) => {
|
|
132
|
-
if (c.quit) await c.quit();
|
|
133
|
-
else if (c.disconnect) await c.disconnect();
|
|
134
|
-
};
|
|
135
|
-
await Promise.all([close(this.pub), close(this.sub)]);
|
|
136
|
-
}
|
|
137
|
-
async setPresenceBatch(entries) {
|
|
138
|
-
if (entries.length === 0) return;
|
|
139
|
-
const pipeline = this.pub.pipeline();
|
|
140
|
-
for (const { key, value, ttlSeconds } of entries) {
|
|
141
|
-
pipeline.set(key, value, "EX", ttlSeconds);
|
|
142
|
-
}
|
|
143
|
-
await pipeline.exec();
|
|
144
|
-
}
|
|
145
|
-
async expire(key, ttlSeconds) {
|
|
146
|
-
await this.pub.expire(key, ttlSeconds);
|
|
147
|
-
}
|
|
148
|
-
onError(handler) {
|
|
149
|
-
this.pub.on("error", handler);
|
|
150
|
-
return () => this.pub.removeListener("error", handler);
|
|
151
|
-
}
|
|
152
|
-
onReconnect(handler) {
|
|
153
|
-
this.pub.on("connect", handler);
|
|
154
|
-
return () => this.pub.removeListener("connect", handler);
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
var NodeRedisDriver = class {
|
|
158
|
-
constructor(pub, sub, blocking) {
|
|
159
|
-
this.pub = pub;
|
|
160
|
-
this.sub = sub;
|
|
161
|
-
this.blocking = blocking;
|
|
162
|
-
}
|
|
163
|
-
async publish(channel, message) {
|
|
164
|
-
await this.pub.publish(channel, message);
|
|
165
|
-
}
|
|
166
|
-
async subscribe(channel, handler) {
|
|
167
|
-
await this.sub.subscribe(channel, handler);
|
|
168
|
-
}
|
|
169
|
-
async unsubscribe(channel) {
|
|
170
|
-
await this.sub.unsubscribe(channel);
|
|
171
|
-
}
|
|
172
|
-
async set(key, value, ttlSeconds) {
|
|
173
|
-
if (ttlSeconds) {
|
|
174
|
-
await this.pub.set(key, value, { EX: ttlSeconds });
|
|
175
|
-
} else {
|
|
176
|
-
await this.pub.set(key, value);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
async get(key) {
|
|
180
|
-
return await this.pub.get(key) || null;
|
|
181
|
-
}
|
|
182
|
-
async mget(keys) {
|
|
183
|
-
if (keys.length === 0) return [];
|
|
184
|
-
return await this.pub.mGet(keys);
|
|
185
|
-
}
|
|
186
|
-
async del(key) {
|
|
187
|
-
await this.pub.del(key);
|
|
188
|
-
}
|
|
189
|
-
async xadd(stream, args, maxLen) {
|
|
190
|
-
const options = {};
|
|
191
|
-
if (maxLen) {
|
|
192
|
-
options.MKSTREAM = true;
|
|
193
|
-
}
|
|
194
|
-
return await this.pub.xAdd(stream, "*", args, {
|
|
195
|
-
TRIM: maxLen ? {
|
|
196
|
-
strategy: "MAXLEN",
|
|
197
|
-
strategyModifier: "~",
|
|
198
|
-
threshold: maxLen
|
|
199
|
-
} : void 0
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
async xaddBatch(messages, maxLen) {
|
|
203
|
-
if (messages.length === 0) return;
|
|
204
|
-
const multi = this.pub.multi();
|
|
205
|
-
for (const msg of messages) {
|
|
206
|
-
multi.xAdd(msg.stream, "*", msg.args, {
|
|
207
|
-
TRIM: maxLen ? {
|
|
208
|
-
strategy: "MAXLEN",
|
|
209
|
-
strategyModifier: "~",
|
|
210
|
-
threshold: maxLen
|
|
211
|
-
} : void 0
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
await multi.exec();
|
|
215
|
-
}
|
|
216
|
-
async xread(streams, count, block) {
|
|
217
|
-
const options = {};
|
|
218
|
-
if (count) options.COUNT = count;
|
|
219
|
-
if (typeof block === "number") options.BLOCK = block;
|
|
220
|
-
const streamsParam = streams.map((s) => ({
|
|
221
|
-
key: s.key,
|
|
222
|
-
id: s.id
|
|
223
|
-
}));
|
|
224
|
-
const client = block && this.blocking ? this.blocking : this.pub;
|
|
225
|
-
const result = await client.xRead(streamsParam, options);
|
|
226
|
-
if (!result || result.length === 0) return null;
|
|
227
|
-
return result.map((entry) => ({
|
|
228
|
-
stream: entry.name,
|
|
229
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
230
|
-
messages: entry.messages.map((msg) => ({
|
|
231
|
-
id: msg.id,
|
|
232
|
-
data: msg.message
|
|
233
|
-
}))
|
|
234
|
-
}));
|
|
235
|
-
}
|
|
236
|
-
async xlen(stream) {
|
|
237
|
-
return await this.pub.xLen(stream);
|
|
238
|
-
}
|
|
239
|
-
async disconnect() {
|
|
240
|
-
await Promise.all([this.pub.disconnect(), this.sub.disconnect()]);
|
|
241
|
-
}
|
|
242
|
-
async setPresenceBatch(entries) {
|
|
243
|
-
if (entries.length === 0) return;
|
|
244
|
-
const multi = this.pub.multi();
|
|
245
|
-
for (const { key, value, ttlSeconds } of entries) {
|
|
246
|
-
multi.set(key, value, { EX: ttlSeconds });
|
|
247
|
-
}
|
|
248
|
-
await multi.exec();
|
|
249
|
-
}
|
|
250
|
-
async expire(key, ttlSeconds) {
|
|
251
|
-
await this.pub.expire(key, ttlSeconds);
|
|
252
|
-
}
|
|
253
|
-
onError(handler) {
|
|
254
|
-
this.pub.on("error", handler);
|
|
255
|
-
return () => this.pub.removeListener("error", handler);
|
|
256
|
-
}
|
|
257
|
-
onReconnect(handler) {
|
|
258
|
-
this.pub.on("connect", handler);
|
|
259
|
-
return () => this.pub.removeListener("connect", handler);
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
function createDriver(pub, sub, blocking) {
|
|
263
|
-
if (sub.isOpen !== void 0 && typeof sub.subscribe === "function") {
|
|
264
|
-
return new NodeRedisDriver(pub, sub, blocking);
|
|
265
|
-
}
|
|
266
|
-
return new IoRedisDriver(pub, sub, blocking);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// src/adapters/redis/index.ts
|
|
270
|
-
var RedisAdapter = class {
|
|
271
|
-
_driver;
|
|
272
|
-
_prefix;
|
|
273
|
-
_streamMaxLen;
|
|
274
|
-
_streamTtlSeconds;
|
|
275
|
-
_presenceTtlSeconds;
|
|
276
|
-
_handlers = /* @__PURE__ */ new Map();
|
|
277
|
-
_streamOffsets = /* @__PURE__ */ new Map();
|
|
278
|
-
// streamKey -> lastId
|
|
279
|
-
_streams = /* @__PURE__ */ new Set();
|
|
280
|
-
// Active streams to poll
|
|
281
|
-
_polling = false;
|
|
282
|
-
_closed = false;
|
|
283
|
-
// Per-stream sequence counter for message ordering
|
|
284
|
-
_sequenceCounters = /* @__PURE__ */ new Map();
|
|
285
|
-
// Rehydration callbacks
|
|
286
|
-
_unsubError;
|
|
287
|
-
_unsubReconnect;
|
|
288
|
-
// Stored presence entries for rehydration on reconnect
|
|
289
|
-
_presenceCache = /* @__PURE__ */ new Map();
|
|
290
|
-
// Connection pool
|
|
291
|
-
_driverPool;
|
|
292
|
-
_nextPoolIndex;
|
|
293
|
-
constructor(options) {
|
|
294
|
-
this._prefix = options.prefix ?? "ocpp-ws-io:";
|
|
295
|
-
this._streamMaxLen = options.streamMaxLen ?? 1e3;
|
|
296
|
-
this._streamTtlSeconds = options.streamTtlSeconds ?? 300;
|
|
297
|
-
this._presenceTtlSeconds = options.presenceTtlSeconds ?? 300;
|
|
298
|
-
this._driver = createDriver(
|
|
299
|
-
options.pubClient,
|
|
300
|
-
options.subClient,
|
|
301
|
-
options.blockingClient
|
|
302
|
-
);
|
|
303
|
-
const poolSize = options.poolSize ?? 1;
|
|
304
|
-
this._driverPool = [this._driver];
|
|
305
|
-
this._nextPoolIndex = 0;
|
|
306
|
-
if (poolSize > 1 && options.driverFactory) {
|
|
307
|
-
for (let i = 1; i < poolSize; i++) {
|
|
308
|
-
this._driverPool.push(options.driverFactory());
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
if (this._driver.onError) {
|
|
312
|
-
this._unsubError = this._driver.onError((err) => {
|
|
313
|
-
console.error("[RedisAdapter] Redis error:", err.message);
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
if (this._driver.onReconnect) {
|
|
317
|
-
this._unsubReconnect = this._driver.onReconnect(() => {
|
|
318
|
-
this._rehydratePresence().catch(() => {
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
/** Get the next driver from the pool (round-robin) */
|
|
324
|
-
_getPoolDriver() {
|
|
325
|
-
if (this._driverPool.length === 1) return this._driver;
|
|
326
|
-
const driver = this._driverPool[this._nextPoolIndex];
|
|
327
|
-
this._nextPoolIndex = (this._nextPoolIndex + 1) % this._driverPool.length;
|
|
328
|
-
return driver;
|
|
329
|
-
}
|
|
330
|
-
async publish(channel, data) {
|
|
331
|
-
const prefixedChannel = this._prefix + channel;
|
|
332
|
-
const payload = data;
|
|
333
|
-
if (payload && typeof payload === "object" && channel.startsWith("ocpp:node:")) {
|
|
334
|
-
const seq = (this._sequenceCounters.get(channel) ?? 0) + 1;
|
|
335
|
-
this._sequenceCounters.set(channel, seq);
|
|
336
|
-
payload.__seq = seq;
|
|
337
|
-
}
|
|
338
|
-
const message = JSON.stringify(data);
|
|
339
|
-
if (channel.startsWith("ocpp:node:")) {
|
|
340
|
-
const poolDriver = this._getPoolDriver();
|
|
341
|
-
await poolDriver.xadd(prefixedChannel, { message }, this._streamMaxLen);
|
|
342
|
-
await poolDriver.expire(prefixedChannel, this._streamTtlSeconds).catch(() => {
|
|
343
|
-
});
|
|
344
|
-
} else {
|
|
345
|
-
await this._getPoolDriver().publish(prefixedChannel, message);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
async publishBatch(messages) {
|
|
349
|
-
const streamMessages = [];
|
|
350
|
-
const broadcastMessages = [];
|
|
351
|
-
for (const msg of messages) {
|
|
352
|
-
const prefixedChannel = this._prefix + msg.channel;
|
|
353
|
-
const message = JSON.stringify(msg.data);
|
|
354
|
-
if (msg.channel.startsWith("ocpp:node:")) {
|
|
355
|
-
streamMessages.push({ stream: prefixedChannel, args: { message } });
|
|
356
|
-
} else {
|
|
357
|
-
broadcastMessages.push({ channel: prefixedChannel, message });
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
const promises = [];
|
|
361
|
-
if (streamMessages.length > 0) {
|
|
362
|
-
promises.push(
|
|
363
|
-
this._getPoolDriver().xaddBatch(streamMessages, this._streamMaxLen)
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
if (broadcastMessages.length > 0) {
|
|
367
|
-
promises.push(
|
|
368
|
-
Promise.all(
|
|
369
|
-
broadcastMessages.map(
|
|
370
|
-
(bm) => this._getPoolDriver().publish(bm.channel, bm.message)
|
|
371
|
-
)
|
|
372
|
-
).then(() => {
|
|
373
|
-
})
|
|
374
|
-
// Map `Promise<void[]>` to `Promise<void>`
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
await Promise.all(promises);
|
|
378
|
-
}
|
|
379
|
-
async subscribe(channel, handler) {
|
|
380
|
-
if (!this._handlers.has(channel)) {
|
|
381
|
-
this._handlers.set(channel, /* @__PURE__ */ new Set());
|
|
382
|
-
const prefixedChannel = this._prefix + channel;
|
|
383
|
-
if (channel.startsWith("ocpp:node:")) {
|
|
384
|
-
if (!this._streams.has(prefixedChannel)) {
|
|
385
|
-
this._streams.add(prefixedChannel);
|
|
386
|
-
this._streamOffsets.set(prefixedChannel, "0");
|
|
387
|
-
this._ensurePolling();
|
|
388
|
-
}
|
|
389
|
-
} else {
|
|
390
|
-
await this._driver.subscribe(prefixedChannel, (message) => {
|
|
391
|
-
this._handleMessage(channel, message);
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
this._handlers.get(channel)?.add(handler);
|
|
396
|
-
}
|
|
397
|
-
async unsubscribe(channel) {
|
|
398
|
-
const prefixedChannel = this._prefix + channel;
|
|
399
|
-
if (this._streams.has(prefixedChannel)) {
|
|
400
|
-
this._streams.delete(prefixedChannel);
|
|
401
|
-
this._streamOffsets.delete(prefixedChannel);
|
|
402
|
-
} else {
|
|
403
|
-
await this._driver.unsubscribe(prefixedChannel);
|
|
404
|
-
}
|
|
405
|
-
this._handlers.delete(channel);
|
|
406
|
-
}
|
|
407
|
-
async disconnect() {
|
|
408
|
-
this._closed = true;
|
|
409
|
-
this._handlers.clear();
|
|
410
|
-
this._streams.clear();
|
|
411
|
-
this._presenceCache.clear();
|
|
412
|
-
this._sequenceCounters.clear();
|
|
413
|
-
if (this._unsubError) this._unsubError();
|
|
414
|
-
if (this._unsubReconnect) this._unsubReconnect();
|
|
415
|
-
await Promise.allSettled(this._driverPool.map((d) => d.disconnect()));
|
|
416
|
-
}
|
|
417
|
-
_handleMessage(channel, message) {
|
|
418
|
-
const handlers = this._handlers.get(channel);
|
|
419
|
-
if (!handlers) return;
|
|
420
|
-
let data;
|
|
421
|
-
try {
|
|
422
|
-
data = JSON.parse(message);
|
|
423
|
-
} catch {
|
|
424
|
-
data = message;
|
|
425
|
-
}
|
|
426
|
-
for (const handler of handlers) {
|
|
427
|
-
try {
|
|
428
|
-
handler(data);
|
|
429
|
-
} catch {
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
// ─── Stream Polling ───────────────────────────────────────────────
|
|
434
|
-
_ensurePolling() {
|
|
435
|
-
if (this._polling || this._closed) return;
|
|
436
|
-
this._polling = true;
|
|
437
|
-
this._pollLoop().catch(() => {
|
|
438
|
-
this._polling = false;
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
async _pollLoop() {
|
|
442
|
-
while (!this._closed) {
|
|
443
|
-
if (this._streams.size === 0) {
|
|
444
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
const streamsArg = Array.from(this._streams).map((key) => ({
|
|
448
|
-
key,
|
|
449
|
-
id: this._streamOffsets.get(key) || "$"
|
|
450
|
-
}));
|
|
451
|
-
try {
|
|
452
|
-
const entries = await this._driver.xread(streamsArg, void 0, 1e3);
|
|
453
|
-
if (entries) {
|
|
454
|
-
for (const entry of entries) {
|
|
455
|
-
const channel = entry.stream.replace(this._prefix, "");
|
|
456
|
-
for (const msg of entry.messages) {
|
|
457
|
-
this._streamOffsets.set(entry.stream, msg.id);
|
|
458
|
-
const messageContent = msg.data.message;
|
|
459
|
-
if (messageContent) {
|
|
460
|
-
this._handleMessage(channel, messageContent);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
} catch (_err) {
|
|
466
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
this._polling = false;
|
|
470
|
-
}
|
|
471
|
-
// ─── Presence Registry ─────────────────────────────────────────────
|
|
472
|
-
async setPresence(identity, nodeId, ttl) {
|
|
473
|
-
const key = `${this._prefix}presence:${identity}`;
|
|
474
|
-
this._presenceCache.set(identity, { nodeId, ttl });
|
|
475
|
-
await this._driver.set(key, nodeId, ttl);
|
|
476
|
-
}
|
|
477
|
-
async getPresence(identity) {
|
|
478
|
-
const key = `${this._prefix}presence:${identity}`;
|
|
479
|
-
return await this._driver.get(key);
|
|
480
|
-
}
|
|
481
|
-
async getPresenceBatch(identities) {
|
|
482
|
-
if (identities.length === 0) return [];
|
|
483
|
-
const keys = identities.map((id) => `${this._prefix}presence:${id}`);
|
|
484
|
-
if (this._driver.mget) {
|
|
485
|
-
return await this._driver.mget(keys);
|
|
486
|
-
}
|
|
487
|
-
return await Promise.all(keys.map((k) => this._driver.get(k)));
|
|
488
|
-
}
|
|
489
|
-
async removePresence(identity) {
|
|
490
|
-
const key = `${this._prefix}presence:${identity}`;
|
|
491
|
-
await this._driver.del(key);
|
|
492
|
-
}
|
|
493
|
-
// ─── Observability Pipeline ────────────────────────────────────────
|
|
494
|
-
async metrics() {
|
|
495
|
-
let pendingMessages = 0;
|
|
496
|
-
const streamDetails = {};
|
|
497
|
-
for (const streamKey of this._streams) {
|
|
498
|
-
try {
|
|
499
|
-
const length = await this._driver.xlen(streamKey);
|
|
500
|
-
pendingMessages += length;
|
|
501
|
-
streamDetails[streamKey] = length;
|
|
502
|
-
} catch {
|
|
503
|
-
streamDetails[streamKey] = -1;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
return {
|
|
507
|
-
pendingMessages,
|
|
508
|
-
activeStreams: this._streams.size,
|
|
509
|
-
streamDetails
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
// ─── C1: Batch Presence Pipeline ────────────────────────────────────
|
|
513
|
-
/**
|
|
514
|
-
* Set multiple presence entries in a single Redis pipeline.
|
|
515
|
-
* Reduces N network round-trips to 1 for bulk presence updates.
|
|
516
|
-
*/
|
|
517
|
-
async setPresenceBatch(entries) {
|
|
518
|
-
if (entries.length === 0) return;
|
|
519
|
-
const batchEntries = entries.map(({ identity, nodeId, ttl }) => {
|
|
520
|
-
const key = `${this._prefix}presence:${identity}`;
|
|
521
|
-
const ttlSeconds = ttl ?? this._presenceTtlSeconds;
|
|
522
|
-
this._presenceCache.set(identity, { nodeId, ttl: ttlSeconds });
|
|
523
|
-
return { key, value: nodeId, ttlSeconds };
|
|
524
|
-
});
|
|
525
|
-
await this._driver.setPresenceBatch(batchEntries);
|
|
526
|
-
}
|
|
527
|
-
// ─── C3: Redis Failure Rehydration ──────────────────────────────────
|
|
528
|
-
/**
|
|
529
|
-
* Re-syncs all cached presence entries to Redis after a reconnection.
|
|
530
|
-
* Called automatically when the Redis client reconnects.
|
|
531
|
-
*/
|
|
532
|
-
async _rehydratePresence() {
|
|
533
|
-
if (this._presenceCache.size === 0) return;
|
|
534
|
-
const entries = Array.from(this._presenceCache.entries()).map(
|
|
535
|
-
([identity, { nodeId, ttl }]) => ({
|
|
536
|
-
key: `${this._prefix}presence:${identity}`,
|
|
537
|
-
value: nodeId,
|
|
538
|
-
ttlSeconds: ttl
|
|
539
|
-
})
|
|
540
|
-
);
|
|
541
|
-
await this._driver.setPresenceBatch(entries);
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
545
|
-
0 && (module.exports = {
|
|
546
|
-
RedisAdapter
|
|
547
|
-
});
|
|
1
|
+
'use strict';var l=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;this.sub.on&&this.sub.on("message",(r,i)=>{let n=this._handlers.get(r);n&&n(i);});}_handlers=new Map;async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){this._handlers.set(e,s),await this.sub.subscribe(e);}async unsubscribe(e){await this.sub.unsubscribe(e),this._handlers.delete(e);}async set(e,s,t){t?await this.pub.set(e,s,"EX",t):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mget(...e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){let r=[];t&&r.push("MAXLEN","~",t.toString()),r.push("*");for(let[i,n]of Object.entries(s))r.push(i,n);return await this.pub.xadd(e,...r)}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.pipeline();for(let r of e){let i=[];s&&i.push("MAXLEN","~",s.toString()),i.push("*");for(let[n,a]of Object.entries(r.args))i.push(n,a);t.xadd(r.stream,...i);}await t.exec();}async xread(e,s,t){let r=[];s&&r.push("COUNT",s),typeof t=="number"&&r.push("BLOCK",t),r.push("STREAMS"),e.forEach(a=>{r.push(a.key);}),e.forEach(a=>{r.push(a.id);});let n=await(t&&this.blocking?this.blocking:this.pub).xread(...r);return n?n.map(([a,o])=>({stream:a,messages:o.map(([u,h])=>{let m={};for(let d=0;d<h.length;d+=2)m[h[d]]=h[d+1];return {id:u,data:m}})})):null}async xlen(e){return await this.pub.xlen(e)}async disconnect(){this._handlers.clear();let e=async s=>{s.quit?await s.quit():s.disconnect&&await s.disconnect();};await Promise.all([e(this.pub),e(this.sub)]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.pipeline();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,"EX",i);await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}},g=class{constructor(e,s,t){this.pub=e;this.sub=s;this.blocking=t;}async publish(e,s){await this.pub.publish(e,s);}async subscribe(e,s){await this.sub.subscribe(e,s);}async unsubscribe(e){await this.sub.unsubscribe(e);}async set(e,s,t){t?await this.pub.set(e,s,{EX:t}):await this.pub.set(e,s);}async get(e){return await this.pub.get(e)||null}async mget(e){return e.length===0?[]:await this.pub.mGet(e)}async del(e){await this.pub.del(e);}async xadd(e,s,t){return await this.pub.xAdd(e,"*",s,{TRIM:t?{strategy:"MAXLEN",strategyModifier:"~",threshold:t}:void 0})}async xaddBatch(e,s){if(e.length===0)return;let t=this.pub.multi();for(let r of e)t.xAdd(r.stream,"*",r.args,{TRIM:s?{strategy:"MAXLEN",strategyModifier:"~",threshold:s}:void 0});await t.exec();}async xread(e,s,t){let r={};s&&(r.COUNT=s),typeof t=="number"&&(r.BLOCK=t);let i=e.map(o=>({key:o.key,id:o.id})),a=await(t&&this.blocking?this.blocking:this.pub).xRead(i,r);return !a||a.length===0?null:a.map(o=>({stream:o.name,messages:o.messages.map(u=>({id:u.id,data:u.message}))}))}async xlen(e){return await this.pub.xLen(e)}async disconnect(){await Promise.all([this.pub.disconnect(),this.sub.disconnect()]);}async setPresenceBatch(e){if(e.length===0)return;let s=this.pub.multi();for(let{key:t,value:r,ttlSeconds:i}of e)s.set(t,r,{EX:i});await s.exec();}async expire(e,s){await this.pub.expire(e,s);}onError(e){return this.pub.on("error",e),()=>this.pub.removeListener("error",e)}onReconnect(e){return this.pub.on("connect",e),()=>this.pub.removeListener("connect",e)}};function p(c,e,s){return e.isOpen!==void 0&&typeof e.subscribe=="function"?new g(c,e,s):new l(c,e,s)}var b=class{_driver;_prefix;_streamMaxLen;_streamTtlSeconds;_presenceTtlSeconds;_handlers=new Map;_streamOffsets=new Map;_streams=new Set;_polling=false;_closed=false;_sequenceCounters=new Map;_unsubError;_unsubReconnect;_presenceCache=new Map;_driverPool;_nextPoolIndex;constructor(e){this._prefix=e.prefix??"ocpp-ws-io:",this._streamMaxLen=e.streamMaxLen??1e3,this._streamTtlSeconds=e.streamTtlSeconds??300,this._presenceTtlSeconds=e.presenceTtlSeconds??300,this._driver=p(e.pubClient,e.subClient,e.blockingClient);let s=e.poolSize??1;if(this._driverPool=[this._driver],this._nextPoolIndex=0,s>1&&e.driverFactory)for(let t=1;t<s;t++)this._driverPool.push(e.driverFactory());this._driver.onError&&(this._unsubError=this._driver.onError(t=>{console.error("[RedisAdapter] Redis error:",t.message);})),this._driver.onReconnect&&(this._unsubReconnect=this._driver.onReconnect(()=>{this._rehydratePresence().catch(()=>{});}));}_getPoolDriver(){if(this._driverPool.length===1)return this._driver;let e=this._driverPool[this._nextPoolIndex];return this._nextPoolIndex=(this._nextPoolIndex+1)%this._driverPool.length,e}async publish(e,s){let t=this._prefix+e,r=s;if(r&&typeof r=="object"&&e.startsWith("ocpp:node:")){let n=(this._sequenceCounters.get(e)??0)+1;this._sequenceCounters.set(e,n),r.__seq=n;}let i=JSON.stringify(s);if(e.startsWith("ocpp:node:")){let n=this._getPoolDriver();await n.xadd(t,{message:i},this._streamMaxLen),await n.expire(t,this._streamTtlSeconds).catch(()=>{});}else await this._getPoolDriver().publish(t,i);}async publishBatch(e){let s=[],t=[];for(let i of e){let n=this._prefix+i.channel,a=JSON.stringify(i.data);i.channel.startsWith("ocpp:node:")?s.push({stream:n,args:{message:a}}):t.push({channel:n,message:a});}let r=[];s.length>0&&r.push(this._getPoolDriver().xaddBatch(s,this._streamMaxLen)),t.length>0&&r.push(Promise.all(t.map(i=>this._getPoolDriver().publish(i.channel,i.message))).then(()=>{})),await Promise.all(r);}async subscribe(e,s){if(!this._handlers.has(e)){this._handlers.set(e,new Set);let t=this._prefix+e;e.startsWith("ocpp:node:")?this._streams.has(t)||(this._streams.add(t),this._streamOffsets.set(t,"0"),this._ensurePolling()):await this._driver.subscribe(t,r=>{this._handleMessage(e,r);});}this._handlers.get(e)?.add(s);}async unsubscribe(e){let s=this._prefix+e;this._streams.has(s)?(this._streams.delete(s),this._streamOffsets.delete(s)):await this._driver.unsubscribe(s),this._handlers.delete(e);}async disconnect(){this._closed=true,this._handlers.clear(),this._streams.clear(),this._presenceCache.clear(),this._sequenceCounters.clear(),this._unsubError&&this._unsubError(),this._unsubReconnect&&this._unsubReconnect(),await Promise.allSettled(this._driverPool.map(e=>e.disconnect()));}_handleMessage(e,s){let t=this._handlers.get(e);if(!t)return;let r;try{r=JSON.parse(s);}catch{r=s;}for(let i of t)try{i(r);}catch{}}_ensurePolling(){this._polling||this._closed||(this._polling=true,this._pollLoop().catch(()=>{this._polling=false;}));}async _pollLoop(){for(;!this._closed;){if(this._streams.size===0){await new Promise(s=>setTimeout(s,1e3));continue}let e=Array.from(this._streams).map(s=>({key:s,id:this._streamOffsets.get(s)||"$"}));try{let s=await this._driver.xread(e,void 0,1e3);if(s)for(let t of s){let r=t.stream.replace(this._prefix,"");for(let i of t.messages){this._streamOffsets.set(t.stream,i.id);let n=i.data.message;n&&this._handleMessage(r,n);}}}catch{await new Promise(t=>setTimeout(t,1e3));}}this._polling=false;}async setPresence(e,s,t){let r=`${this._prefix}presence:${e}`;this._presenceCache.set(e,{nodeId:s,ttl:t}),await this._driver.set(r,s,t);}async getPresence(e){let s=`${this._prefix}presence:${e}`;return await this._driver.get(s)}async getPresenceBatch(e){if(e.length===0)return [];let s=e.map(t=>`${this._prefix}presence:${t}`);return this._driver.mget?await this._driver.mget(s):await Promise.all(s.map(t=>this._driver.get(t)))}async removePresence(e){let s=`${this._prefix}presence:${e}`;await this._driver.del(s);}async metrics(){let e=0,s={};for(let t of this._streams)try{let r=await this._driver.xlen(t);e+=r,s[t]=r;}catch{s[t]=-1;}return {pendingMessages:e,activeStreams:this._streams.size,streamDetails:s}}async setPresenceBatch(e){if(e.length===0)return;let s=e.map(({identity:t,nodeId:r,ttl:i})=>{let n=`${this._prefix}presence:${t}`,a=i??this._presenceTtlSeconds;return this._presenceCache.set(t,{nodeId:r,ttl:a}),{key:n,value:r,ttlSeconds:a}});await this._driver.setPresenceBatch(s);}async _rehydratePresence(){if(this._presenceCache.size===0)return;let e=Array.from(this._presenceCache.entries()).map(([s,{nodeId:t,ttl:r}])=>({key:`${this._prefix}presence:${s}`,value:t,ttlSeconds:r}));await this._driver.setPresenceBatch(e);}};exports.RedisAdapter=b;//# sourceMappingURL=redis.js.map
|
|
548
2
|
//# sourceMappingURL=redis.js.map
|