ocpp-ws-io 2.1.3

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.
@@ -0,0 +1,1303 @@
1
+ "use strict";
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/browser/index.ts
21
+ var browser_exports = {};
22
+ __export(browser_exports, {
23
+ BrowserOCPPClient: () => BrowserOCPPClient,
24
+ ConnectionState: () => ConnectionState,
25
+ MessageType: () => MessageType,
26
+ MiddlewareStack: () => MiddlewareStack,
27
+ NOREPLY: () => NOREPLY,
28
+ RPCFormatViolationError: () => RPCFormatViolationError,
29
+ RPCFormationViolationError: () => RPCFormationViolationError,
30
+ RPCFrameworkError: () => RPCFrameworkError,
31
+ RPCGenericError: () => RPCGenericError,
32
+ RPCInternalError: () => RPCInternalError,
33
+ RPCMessageTypeNotSupportedError: () => RPCMessageTypeNotSupportedError,
34
+ RPCNotImplementedError: () => RPCNotImplementedError,
35
+ RPCNotSupportedError: () => RPCNotSupportedError,
36
+ RPCOccurrenceConstraintViolationError: () => RPCOccurrenceConstraintViolationError,
37
+ RPCPropertyConstraintViolationError: () => RPCPropertyConstraintViolationError,
38
+ RPCProtocolError: () => RPCProtocolError,
39
+ RPCSecurityError: () => RPCSecurityError,
40
+ RPCTypeConstraintViolationError: () => RPCTypeConstraintViolationError,
41
+ TimeoutError: () => TimeoutError,
42
+ combineAuth: () => combineAuth,
43
+ createLoggingMiddleware: () => createLoggingMiddleware,
44
+ createRPCError: () => createRPCError,
45
+ defineAuth: () => defineAuth,
46
+ defineMiddleware: () => defineMiddleware,
47
+ defineRpcMiddleware: () => defineRpcMiddleware,
48
+ getErrorPlainObject: () => getErrorPlainObject
49
+ });
50
+ module.exports = __toCommonJS(browser_exports);
51
+
52
+ // src/helpers/index.ts
53
+ function defineMiddleware(mw) {
54
+ return mw;
55
+ }
56
+ function defineRpcMiddleware(mw) {
57
+ return mw;
58
+ }
59
+ function defineAuth(cb) {
60
+ return cb;
61
+ }
62
+ function combineAuth(...cbs) {
63
+ return async (ctx) => {
64
+ let accepted = false;
65
+ let rejected = false;
66
+ const trackedAccept = (opts) => {
67
+ accepted = true;
68
+ ctx.accept(opts);
69
+ };
70
+ const trackedReject = (code, message) => {
71
+ rejected = true;
72
+ return ctx.reject(code, message);
73
+ };
74
+ const trackedCtx = {
75
+ ...ctx,
76
+ accept: trackedAccept,
77
+ reject: trackedReject
78
+ };
79
+ try {
80
+ for (const cb of cbs) {
81
+ if (ctx.signal.aborted || accepted || rejected) break;
82
+ const p = cb(trackedCtx);
83
+ if (p instanceof Promise) {
84
+ await p;
85
+ }
86
+ if (accepted || rejected) break;
87
+ }
88
+ if (!accepted && !rejected) {
89
+ trackedReject(
90
+ 401,
91
+ "Unauthorized (All composeAuth handlers passed without accepting)"
92
+ );
93
+ }
94
+ } catch (_err) {
95
+ if (!rejected) {
96
+ trackedReject(
97
+ 500,
98
+ "Internal Server Error during auth compose execution"
99
+ );
100
+ }
101
+ }
102
+ };
103
+ }
104
+ function createLoggingMiddleware(logger, identity, config = {}) {
105
+ const options = typeof config === "object" ? config : {};
106
+ const { exchangeLog = false, prettify = false } = options;
107
+ return async (ctx, next) => {
108
+ const start = Date.now();
109
+ const method = ctx.method;
110
+ const level = exchangeLog ? "info" : "debug";
111
+ switch (ctx.type) {
112
+ case "incoming_call":
113
+ if (exchangeLog && prettify) {
114
+ logger[level]?.(`\u26A1 ${identity} \u2190 ${method} [IN]`, {
115
+ messageId: ctx.messageId,
116
+ method: ctx.method,
117
+ protocol: ctx.protocol,
118
+ payload: ctx.params,
119
+ direction: "IN"
120
+ });
121
+ } else {
122
+ logger[level]?.(`CALL \u2190`, {
123
+ messageId: ctx.messageId,
124
+ method: ctx.method,
125
+ protocol: ctx.protocol,
126
+ payload: ctx.params,
127
+ direction: "IN"
128
+ });
129
+ }
130
+ break;
131
+ case "outgoing_call":
132
+ if (exchangeLog && prettify) {
133
+ logger[level]?.(`\u26A1 ${identity} \u2192 ${method} [OUT]`, {
134
+ method: ctx.method,
135
+ params: ctx.params,
136
+ direction: "OUT"
137
+ });
138
+ } else {
139
+ logger[level]?.(`CALL \u2192`, {
140
+ method: ctx.method,
141
+ params: ctx.params,
142
+ direction: "OUT"
143
+ });
144
+ }
145
+ break;
146
+ }
147
+ try {
148
+ const result = await next();
149
+ const durationMs = Date.now() - start;
150
+ switch (ctx.type) {
151
+ case "incoming_call":
152
+ if (result !== void 0 && result !== null) {
153
+ if (exchangeLog && prettify) {
154
+ logger[level]?.(`\u2705 ${identity} \u2192 ${method} [RES]`, {
155
+ messageId: ctx.messageId,
156
+ method: ctx.method,
157
+ durationMs,
158
+ params: result,
159
+ direction: "OUT"
160
+ });
161
+ } else {
162
+ logger[level]?.(`CALLRESULT \u2192`, {
163
+ messageId: ctx.messageId,
164
+ method: ctx.method,
165
+ durationMs,
166
+ params: result,
167
+ direction: "OUT"
168
+ });
169
+ }
170
+ }
171
+ break;
172
+ case "outgoing_call":
173
+ if (exchangeLog && prettify) {
174
+ logger[level]?.(`\u2705 ${identity} \u2190 ${method} [RES]`, {
175
+ messageId: ctx.messageId,
176
+ method: ctx.method,
177
+ durationMs,
178
+ payload: result,
179
+ direction: "IN"
180
+ });
181
+ } else {
182
+ logger[level]?.(`CALLRESULT \u2190`, {
183
+ messageId: ctx.messageId,
184
+ method: ctx.method,
185
+ durationMs,
186
+ payload: result,
187
+ direction: "IN"
188
+ });
189
+ }
190
+ break;
191
+ }
192
+ return result;
193
+ } catch (err) {
194
+ const msg = err.message;
195
+ const durationMs = Date.now() - start;
196
+ if (ctx.type === "incoming_call") {
197
+ if (exchangeLog && prettify) {
198
+ logger.error?.(`\u{1F6A8} ${identity} \u2192 ${method} [ERR]`, {
199
+ messageId: ctx.messageId,
200
+ method: ctx.method,
201
+ durationMs,
202
+ error: msg,
203
+ direction: "OUT"
204
+ });
205
+ } else {
206
+ logger.error?.(`CALLERROR \u2192`, {
207
+ messageId: ctx.messageId,
208
+ method: ctx.method,
209
+ durationMs,
210
+ error: msg,
211
+ direction: "OUT"
212
+ });
213
+ }
214
+ } else if (ctx.type === "outgoing_call") {
215
+ if (exchangeLog && prettify) {
216
+ logger.warn?.(`\u{1F6A8} ${identity} \u2190 ${method} [ERR]`, {
217
+ messageId: ctx.messageId,
218
+ method: ctx.method,
219
+ durationMs,
220
+ error: msg,
221
+ direction: "IN"
222
+ });
223
+ } else {
224
+ logger.warn?.(`CALLERROR \u2190`, {
225
+ messageId: ctx.messageId,
226
+ method: ctx.method,
227
+ durationMs,
228
+ error: msg,
229
+ direction: "IN"
230
+ });
231
+ }
232
+ }
233
+ throw err;
234
+ }
235
+ };
236
+ }
237
+
238
+ // src/middleware.ts
239
+ var MiddlewareStack = class {
240
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
+ _stack = [];
242
+ /**
243
+ * Add a middleware function to the stack.
244
+ */
245
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
246
+ use(middleware) {
247
+ this._stack.push(middleware);
248
+ }
249
+ /**
250
+ * Execute the middleware stack composed with a runner function.
251
+ *
252
+ * @param context The context object to pass through middleware
253
+ * @param runner The final function to execute (the "core" logic)
254
+ */
255
+ async execute(context, runner) {
256
+ let index = -1;
257
+ const dispatch = async (i) => {
258
+ if (i <= index) {
259
+ throw new Error("next() called multiple times");
260
+ }
261
+ index = i;
262
+ const fn = this._stack[i];
263
+ if (i === this._stack.length) {
264
+ return runner(context);
265
+ }
266
+ if (!fn) {
267
+ return void 0;
268
+ }
269
+ return fn(context, () => dispatch(i + 1));
270
+ };
271
+ return dispatch(0);
272
+ }
273
+ };
274
+
275
+ // src/init-logger.ts
276
+ var import_voltlog_io = require("voltlog-io");
277
+ function hasDisplayCustomization(config) {
278
+ return config.showMetadata === false || config.showSourceMeta === false || config.prettifySource === true || config.prettifyMetadata === true;
279
+ }
280
+ function buildDisplayMiddleware(config) {
281
+ const showMeta = config.showMetadata ?? true;
282
+ const showSource = config.showSourceMeta ?? true;
283
+ const prettySrc = config.prettifySource ?? false;
284
+ const prettyMeta = config.prettifyMetadata ?? false;
285
+ const DIM = "\x1B[2;37m";
286
+ const RESET = "\x1B[0m";
287
+ const CYAN = "\x1B[36m";
288
+ return (entry, next) => {
289
+ if (!showSource) {
290
+ entry.context = void 0;
291
+ } else if (prettySrc && entry.context) {
292
+ const parts = [];
293
+ if (entry.context.component) parts.push(String(entry.context.component));
294
+ if (entry.context.identity) parts.push(String(entry.context.identity));
295
+ if (parts.length > 0) {
296
+ entry.message = `${DIM}[${parts.join("/")}]${RESET} ${entry.message}`;
297
+ entry.context = void 0;
298
+ }
299
+ }
300
+ const meta = entry.meta;
301
+ if (!showMeta) {
302
+ entry.meta = {};
303
+ } else if (prettyMeta && meta && Object.keys(meta).length > 0) {
304
+ const pairs = Object.entries(meta).filter(([, v]) => v !== void 0 && v !== null).map(([k, v]) => {
305
+ let valStr = typeof v === "object" ? JSON.stringify(v) : String(v);
306
+ if (typeof v === "string") {
307
+ valStr = `${DIM}${valStr}${RESET}`;
308
+ } else {
309
+ valStr = `${DIM}${valStr}${RESET}`;
310
+ }
311
+ return `${CYAN}${k}${RESET}=${valStr}`;
312
+ }).join(" ");
313
+ if (pairs) {
314
+ entry.message = `${entry.message} ${pairs}`;
315
+ }
316
+ entry.meta = {};
317
+ }
318
+ next(entry);
319
+ };
320
+ }
321
+ function initLogger(config, defaultContext) {
322
+ if (config === false) return null;
323
+ if (config?.enabled === false) return null;
324
+ if (config?.logger) {
325
+ if (defaultContext && config.logger.child) {
326
+ return config.logger.child(defaultContext);
327
+ }
328
+ return config.logger;
329
+ }
330
+ const level = config?.level ?? "INFO";
331
+ const usePrettify = config?.prettify ?? false;
332
+ const transports = usePrettify ? [(0, import_voltlog_io.prettyTransport)({ level })] : [(0, import_voltlog_io.consoleTransport)({ level })];
333
+ if (config?.handler) {
334
+ const customTransport = config.handler;
335
+ transports.push({
336
+ name: "customHandler",
337
+ write: (entry) => customTransport(entry)
338
+ });
339
+ }
340
+ const middleware = [];
341
+ if (config && hasDisplayCustomization(config)) {
342
+ middleware.push(buildDisplayMiddleware(config));
343
+ }
344
+ const logger = (0, import_voltlog_io.createLogger)({
345
+ level,
346
+ transports,
347
+ middleware: middleware.length > 0 ? middleware : void 0
348
+ });
349
+ if (defaultContext && Object.keys(defaultContext).length > 0) {
350
+ return logger.child(defaultContext);
351
+ }
352
+ return logger;
353
+ }
354
+
355
+ // src/browser/emitter.ts
356
+ var EventEmitter = class {
357
+ _listeners = /* @__PURE__ */ new Map();
358
+ on(event, listener) {
359
+ const arr = this._listeners.get(event);
360
+ if (arr) {
361
+ arr.push(listener);
362
+ } else {
363
+ this._listeners.set(event, [listener]);
364
+ }
365
+ return this;
366
+ }
367
+ once(event, listener) {
368
+ const wrapper = (...args) => {
369
+ this.off(event, wrapper);
370
+ listener(...args);
371
+ };
372
+ wrapper.__wrapped = listener;
373
+ return this.on(event, wrapper);
374
+ }
375
+ off(event, listener) {
376
+ const arr = this._listeners.get(event);
377
+ if (!arr) return this;
378
+ const idx = arr.findIndex(
379
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
380
+ (fn) => fn === listener || fn.__wrapped === listener
381
+ );
382
+ if (idx !== -1) arr.splice(idx, 1);
383
+ if (arr.length === 0) this._listeners.delete(event);
384
+ return this;
385
+ }
386
+ emit(event, ...args) {
387
+ const arr = this._listeners.get(event);
388
+ if (!arr || arr.length === 0) return false;
389
+ for (const fn of [...arr]) {
390
+ fn(...args);
391
+ }
392
+ return true;
393
+ }
394
+ addListener(event, listener) {
395
+ return this.on(event, listener);
396
+ }
397
+ removeListener(event, listener) {
398
+ return this.off(event, listener);
399
+ }
400
+ removeAllListeners(event) {
401
+ if (event) {
402
+ this._listeners.delete(event);
403
+ } else {
404
+ this._listeners.clear();
405
+ }
406
+ return this;
407
+ }
408
+ listenerCount(event) {
409
+ return this._listeners.get(event)?.length ?? 0;
410
+ }
411
+ };
412
+
413
+ // src/browser/errors.ts
414
+ var TimeoutError = class extends Error {
415
+ constructor(message = "Operation timed out") {
416
+ super(message);
417
+ this.name = "TimeoutError";
418
+ }
419
+ };
420
+ var RPCGenericError = class extends Error {
421
+ rpcErrorCode = "GenericError";
422
+ rpcErrorMessage = "";
423
+ details;
424
+ constructor(message, details = {}) {
425
+ super(message);
426
+ this.name = "RPCGenericError";
427
+ this.details = details;
428
+ }
429
+ };
430
+ var RPCNotImplementedError = class extends RPCGenericError {
431
+ rpcErrorCode = "NotImplemented";
432
+ rpcErrorMessage = "Requested method is not known";
433
+ constructor(message, details = {}) {
434
+ super(message, details);
435
+ this.name = "RPCNotImplementedError";
436
+ }
437
+ };
438
+ var RPCNotSupportedError = class extends RPCGenericError {
439
+ rpcErrorCode = "NotSupported";
440
+ rpcErrorMessage = "Requested method is recognised but not supported";
441
+ constructor(message, details = {}) {
442
+ super(message, details);
443
+ this.name = "RPCNotSupportedError";
444
+ }
445
+ };
446
+ var RPCInternalError = class extends RPCGenericError {
447
+ rpcErrorCode = "InternalError";
448
+ rpcErrorMessage = "An internal error occurred and the receiver was not able to process the requested action successfully";
449
+ constructor(message, details = {}) {
450
+ super(message, details);
451
+ this.name = "RPCInternalError";
452
+ }
453
+ };
454
+ var RPCProtocolError = class extends RPCGenericError {
455
+ rpcErrorCode = "ProtocolError";
456
+ rpcErrorMessage = "Payload for action is incomplete";
457
+ constructor(message, details = {}) {
458
+ super(message, details);
459
+ this.name = "RPCProtocolError";
460
+ }
461
+ };
462
+ var RPCSecurityError = class extends RPCGenericError {
463
+ rpcErrorCode = "SecurityError";
464
+ rpcErrorMessage = "During the processing of action a security issue occurred preventing receiver from completing the action successfully";
465
+ constructor(message, details = {}) {
466
+ super(message, details);
467
+ this.name = "RPCSecurityError";
468
+ }
469
+ };
470
+ var RPCFormationViolationError = class extends RPCGenericError {
471
+ rpcErrorCode = "FormationViolation";
472
+ rpcErrorMessage = "Payload for action is syntactically incorrect or not conform the PDU structure for action";
473
+ constructor(message, details = {}) {
474
+ super(message, details);
475
+ this.name = "RPCFormationViolationError";
476
+ }
477
+ };
478
+ var RPCFormatViolationError = class extends RPCGenericError {
479
+ rpcErrorCode = "FormatViolation";
480
+ rpcErrorMessage = "Payload is syntactically correct but at least one field contains an invalid value";
481
+ constructor(message, details = {}) {
482
+ super(message, details);
483
+ this.name = "RPCFormatViolationError";
484
+ }
485
+ };
486
+ var RPCPropertyConstraintViolationError = class extends RPCGenericError {
487
+ rpcErrorCode = "PropertyConstraintViolation";
488
+ rpcErrorMessage = "Payload is syntactically correct but at least one of the fields violates data type constraints";
489
+ constructor(message, details = {}) {
490
+ super(message, details);
491
+ this.name = "RPCPropertyConstraintViolationError";
492
+ }
493
+ };
494
+ var RPCOccurrenceConstraintViolationError = class extends RPCGenericError {
495
+ rpcErrorCode = "OccurrenceConstraintViolation";
496
+ rpcErrorMessage = "Payload for action is syntactically correct but at least one of the fields violates occurrence constraints";
497
+ constructor(message, details = {}) {
498
+ super(message, details);
499
+ this.name = "RPCOccurrenceConstraintViolationError";
500
+ }
501
+ };
502
+ var RPCTypeConstraintViolationError = class extends RPCGenericError {
503
+ rpcErrorCode = "TypeConstraintViolation";
504
+ rpcErrorMessage = "Payload for action is syntactically correct but at least one of the fields violates type constraints";
505
+ constructor(message, details = {}) {
506
+ super(message, details);
507
+ this.name = "RPCTypeConstraintViolationError";
508
+ }
509
+ };
510
+ var RPCMessageTypeNotSupportedError = class extends RPCGenericError {
511
+ rpcErrorCode = "MessageTypeNotSupported";
512
+ rpcErrorMessage = "A message with a Message Type Number received that is not supported by this implementation";
513
+ constructor(message, details = {}) {
514
+ super(message, details);
515
+ this.name = "RPCMessageTypeNotSupportedError";
516
+ }
517
+ };
518
+ var RPCFrameworkError = class extends RPCGenericError {
519
+ rpcErrorCode = "RpcFrameworkError";
520
+ rpcErrorMessage = "Content of the call is not a valid RPC request";
521
+ constructor(message, details = {}) {
522
+ super(message, details);
523
+ this.name = "RPCFrameworkError";
524
+ }
525
+ };
526
+
527
+ // src/browser/queue.ts
528
+ var Queue = class {
529
+ _concurrency;
530
+ _running = 0;
531
+ _queue = [];
532
+ constructor(concurrency = 1) {
533
+ this._concurrency = Math.max(1, concurrency);
534
+ }
535
+ get concurrency() {
536
+ return this._concurrency;
537
+ }
538
+ get pending() {
539
+ return this._queue.length;
540
+ }
541
+ get running() {
542
+ return this._running;
543
+ }
544
+ get size() {
545
+ return this._running + this._queue.length;
546
+ }
547
+ setConcurrency(concurrency) {
548
+ this._concurrency = Math.max(1, concurrency);
549
+ this._drain();
550
+ }
551
+ push(fn) {
552
+ return new Promise((resolve, reject) => {
553
+ this._queue.push({
554
+ fn,
555
+ resolve,
556
+ reject
557
+ });
558
+ this._drain();
559
+ });
560
+ }
561
+ _drain() {
562
+ while (this._running < this._concurrency && this._queue.length > 0) {
563
+ const item = this._queue.shift();
564
+ this._running++;
565
+ item?.fn().then(item.resolve).catch(item.reject).finally(() => {
566
+ this._running--;
567
+ this._drain();
568
+ });
569
+ }
570
+ }
571
+ };
572
+
573
+ // src/types.ts
574
+ var ConnectionState = {
575
+ CONNECTING: 0,
576
+ OPEN: 1,
577
+ CLOSING: 2,
578
+ CLOSED: 3
579
+ };
580
+ var MessageType = {
581
+ CALL: 2,
582
+ CALLRESULT: 3,
583
+ CALLERROR: 4
584
+ };
585
+ var NOREPLY = /* @__PURE__ */ Symbol("NOREPLY");
586
+
587
+ // src/util.ts
588
+ var NOOP_LOGGER = {
589
+ debug: () => {
590
+ },
591
+ info: () => {
592
+ },
593
+ warn: () => {
594
+ },
595
+ error: () => {
596
+ },
597
+ child: () => NOOP_LOGGER
598
+ };
599
+
600
+ // src/browser/util.ts
601
+ var RPC_ERROR_REGISTRY = /* @__PURE__ */ new Map([
602
+ ["GenericError", RPCGenericError],
603
+ ["RpcFrameworkError", RPCFrameworkError],
604
+ ["MessageTypeNotSupported", RPCMessageTypeNotSupportedError],
605
+ ["NotImplemented", RPCNotImplementedError],
606
+ ["NotSupported", RPCNotSupportedError],
607
+ ["InternalError", RPCInternalError],
608
+ ["ProtocolError", RPCProtocolError],
609
+ ["SecurityError", RPCSecurityError],
610
+ ["FormatViolation", RPCFormatViolationError],
611
+ ["FormationViolation", RPCFormationViolationError],
612
+ ["PropertyConstraintViolation", RPCPropertyConstraintViolationError],
613
+ [
614
+ "OccurrenceConstraintViolation",
615
+ RPCOccurrenceConstraintViolationError
616
+ ],
617
+ ["TypeConstraintViolation", RPCTypeConstraintViolationError]
618
+ ]);
619
+ function createRPCError(code, message, details = {}) {
620
+ const Ctor = RPC_ERROR_REGISTRY.get(code) ?? RPCGenericError;
621
+ return new Ctor(message, details);
622
+ }
623
+ var ERROR_PROPERTIES = [
624
+ "name",
625
+ "message",
626
+ "stack",
627
+ "code",
628
+ "rpcErrorCode",
629
+ "rpcErrorMessage",
630
+ "details"
631
+ ];
632
+ function getErrorPlainObject(err) {
633
+ const result = {};
634
+ for (const prop of ERROR_PROPERTIES) {
635
+ const value = err[prop];
636
+ if (value !== void 0) {
637
+ if (typeof value === "function" || typeof value === "symbol") continue;
638
+ if (typeof value === "object" && value !== null) {
639
+ try {
640
+ JSON.stringify(value);
641
+ result[prop] = value;
642
+ } catch {
643
+ }
644
+ } else {
645
+ result[prop] = value;
646
+ }
647
+ }
648
+ }
649
+ if (!result.name) result.name = err.name;
650
+ if (!result.message) result.message = err.message;
651
+ return result;
652
+ }
653
+
654
+ // src/browser/client.ts
655
+ var { CONNECTING, OPEN, CLOSING, CLOSED } = ConnectionState;
656
+ var BrowserOCPPClient = class _BrowserOCPPClient extends EventEmitter {
657
+ // Static connection states
658
+ static CONNECTING = CONNECTING;
659
+ static OPEN = OPEN;
660
+ static CLOSING = CLOSING;
661
+ static CLOSED = CLOSED;
662
+ _options;
663
+ _state = CLOSED;
664
+ _ws = null;
665
+ _protocol;
666
+ _identity;
667
+ _handlers = /* @__PURE__ */ new Map();
668
+ _wildcardHandler = null;
669
+ _pendingCalls = /* @__PURE__ */ new Map();
670
+ _pendingResponses = /* @__PURE__ */ new Set();
671
+ _callQueue;
672
+ _closePromise = null;
673
+ _reconnectAttempt = 0;
674
+ _reconnectTimer = null;
675
+ _badMessageCount = 0;
676
+ _outboundBuffer = [];
677
+ _logger;
678
+ _middleware;
679
+ constructor(options) {
680
+ super();
681
+ if (!options.identity) {
682
+ throw new Error("identity is required");
683
+ }
684
+ this._identity = options.identity;
685
+ this._options = {
686
+ reconnect: true,
687
+ maxReconnects: Infinity,
688
+ backoffMin: 1e3,
689
+ backoffMax: 3e4,
690
+ callTimeoutMs: 3e4,
691
+ callConcurrency: 1,
692
+ maxBadMessages: Infinity,
693
+ respondWithDetailedErrors: false,
694
+ ...options
695
+ };
696
+ this._callQueue = new Queue(this._options.callConcurrency);
697
+ this._middleware = new MiddlewareStack();
698
+ const loggerInstance = initLogger(this._options.logging, {
699
+ component: "BrowserOCPPClient",
700
+ identity: this._identity
701
+ });
702
+ this._logger = loggerInstance || NOOP_LOGGER;
703
+ }
704
+ // ─── Getters ─────────────────────────────────────────────────
705
+ get log() {
706
+ return this._logger;
707
+ }
708
+ get identity() {
709
+ return this._identity;
710
+ }
711
+ get protocol() {
712
+ return this._protocol;
713
+ }
714
+ get state() {
715
+ return this._state;
716
+ }
717
+ // ─── Connect ─────────────────────────────────────────────────
718
+ async connect() {
719
+ if (this._state !== CLOSED) {
720
+ throw new Error(`Cannot connect: client is in state ${this._state}`);
721
+ }
722
+ this._state = CONNECTING;
723
+ this._reconnectAttempt = 0;
724
+ return this._connectInternal();
725
+ }
726
+ async _connectInternal() {
727
+ return new Promise((resolve, reject) => {
728
+ const endpoint = this._buildEndpoint();
729
+ this._logger.debug?.("Connecting", { url: endpoint });
730
+ this.emit("connecting", { url: endpoint });
731
+ let ws;
732
+ try {
733
+ ws = this._options.protocols?.length ? new WebSocket(endpoint, this._options.protocols) : new WebSocket(endpoint);
734
+ } catch (err) {
735
+ this._state = CLOSED;
736
+ reject(err);
737
+ return;
738
+ }
739
+ this._ws = ws;
740
+ const onOpen = (event) => {
741
+ cleanup();
742
+ this._state = OPEN;
743
+ this._protocol = ws.protocol || void 0;
744
+ this._badMessageCount = 0;
745
+ if (ws.protocol && this._reconnectAttempt === 0) {
746
+ this._options.protocols = [ws.protocol];
747
+ }
748
+ this._attachWebsocket(ws);
749
+ if (this._outboundBuffer.length > 0) {
750
+ const buffer = this._outboundBuffer;
751
+ this._outboundBuffer = [];
752
+ for (const msg of buffer) this._ws?.send(msg);
753
+ }
754
+ this._logger.info?.("Connected", {
755
+ protocol: ws.protocol || void 0
756
+ });
757
+ this.emit("open", event);
758
+ resolve();
759
+ };
760
+ const onError = (event) => {
761
+ cleanup();
762
+ this._state = CLOSED;
763
+ this._logger.error?.("Connection error");
764
+ this.emit("error", event);
765
+ reject(event);
766
+ };
767
+ const onClose = () => {
768
+ cleanup();
769
+ if (this._state === CONNECTING) {
770
+ this._state = CLOSED;
771
+ reject(new Error("WebSocket closed during connection"));
772
+ }
773
+ };
774
+ const cleanup = () => {
775
+ ws.removeEventListener("open", onOpen);
776
+ ws.removeEventListener("error", onError);
777
+ ws.removeEventListener("close", onClose);
778
+ };
779
+ ws.addEventListener("open", onOpen);
780
+ ws.addEventListener("error", onError);
781
+ ws.addEventListener("close", onClose);
782
+ });
783
+ }
784
+ // ─── Close ───────────────────────────────────────────────────
785
+ async close(options = {}) {
786
+ const {
787
+ code = 1e3,
788
+ reason = "",
789
+ awaitPending = true,
790
+ force = false
791
+ } = options;
792
+ if (this._closePromise) return this._closePromise;
793
+ if (this._state === CLOSED) {
794
+ return { code: 1e3, reason: "" };
795
+ }
796
+ if (this._reconnectTimer) {
797
+ clearTimeout(this._reconnectTimer);
798
+ this._reconnectTimer = null;
799
+ }
800
+ this._closePromise = this._closeInternal(code, reason, awaitPending, force);
801
+ return this._closePromise;
802
+ }
803
+ async _closeInternal(code, reason, awaitPending, force) {
804
+ this._state = CLOSING;
805
+ if (!force && awaitPending) {
806
+ const pendingPromises = Array.from(this._pendingCalls.values()).map(
807
+ (p) => new Promise((resolve) => {
808
+ const origResolve = p.resolve;
809
+ const origReject = p.reject;
810
+ p.resolve = (v) => {
811
+ origResolve(v);
812
+ resolve();
813
+ };
814
+ p.reject = (r) => {
815
+ origReject(r);
816
+ resolve();
817
+ };
818
+ })
819
+ );
820
+ if (pendingPromises.length > 0) {
821
+ await Promise.allSettled(pendingPromises);
822
+ }
823
+ }
824
+ return new Promise((resolve) => {
825
+ if (!this._ws || this._ws.readyState === WebSocket.CLOSED) {
826
+ this._state = CLOSED;
827
+ this._cleanup();
828
+ const result = { code, reason };
829
+ this.emit("close", result);
830
+ resolve(result);
831
+ return;
832
+ }
833
+ const onClose = (event) => {
834
+ this._ws?.removeEventListener("close", onClose);
835
+ this._state = CLOSED;
836
+ this._cleanup();
837
+ const result = { code: event.code, reason: event.reason };
838
+ this.emit("close", result);
839
+ resolve(result);
840
+ };
841
+ this._ws.addEventListener("close", onClose);
842
+ if (force) {
843
+ this._ws.close();
844
+ } else {
845
+ const validCode = code >= 1e3 && code <= 4999 && ![1004, 1005, 1006].includes(code);
846
+ this._ws.close(validCode ? code : 1e3, reason);
847
+ }
848
+ });
849
+ }
850
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
851
+ handle(...args) {
852
+ if (args.length === 1 && typeof args[0] === "function") {
853
+ this._wildcardHandler = args[0];
854
+ } else if (args.length === 2 && typeof args[0] === "string" && typeof args[1] === "function") {
855
+ this._handlers.set(args[0], args[1]);
856
+ } else if (args.length === 3 && typeof args[0] === "string" && typeof args[1] === "string" && typeof args[2] === "function") {
857
+ this._handlers.set(`${args[0]}:${args[1]}`, args[2]);
858
+ } else {
859
+ throw new Error(
860
+ "Invalid arguments: provide (version, method, handler), (method, handler), or (wildcardHandler)"
861
+ );
862
+ }
863
+ }
864
+ removeHandler(versionOrMethod, method) {
865
+ if (versionOrMethod && method) {
866
+ this._handlers.delete(`${versionOrMethod}:${method}`);
867
+ } else if (versionOrMethod) {
868
+ this._handlers.delete(versionOrMethod);
869
+ } else {
870
+ this._wildcardHandler = null;
871
+ }
872
+ }
873
+ removeAllHandlers() {
874
+ this._handlers.clear();
875
+ this._wildcardHandler = null;
876
+ }
877
+ // ─── Middleware ──────────────────────────────────────────────
878
+ /**
879
+ * Register a middleware function to intercept calls and results.
880
+ * Middleware executes in the order registered.
881
+ */
882
+ use(middleware) {
883
+ this._middleware.use(middleware);
884
+ }
885
+ async call(...args) {
886
+ let method;
887
+ let params;
888
+ let options;
889
+ if (args.length >= 3 && typeof args[0] === "string" && typeof args[1] === "string") {
890
+ method = args[1];
891
+ params = args[2] ?? {};
892
+ options = args[3] ?? {};
893
+ } else {
894
+ method = args[0];
895
+ params = args[1] ?? {};
896
+ options = args[2] ?? {};
897
+ }
898
+ if (this._state !== OPEN) {
899
+ throw new Error(`Cannot call: client is in state ${this._state}`);
900
+ }
901
+ return this._callQueue.push(() => this._sendCall(method, params, options));
902
+ }
903
+ async _sendCall(method, params, options) {
904
+ const msgId = this._generateMessageId();
905
+ const timeoutMs = options.timeoutMs ?? this._options.callTimeoutMs;
906
+ const ctx = {
907
+ type: "outgoing_call",
908
+ messageId: msgId,
909
+ method,
910
+ params,
911
+ options
912
+ };
913
+ let callResult;
914
+ await this._middleware.execute(ctx, async (c) => {
915
+ const ctxvals = c;
916
+ const message = [
917
+ MessageType.CALL,
918
+ msgId,
919
+ ctxvals.method,
920
+ ctxvals.params
921
+ ];
922
+ const messageStr = JSON.stringify(message);
923
+ callResult = await new Promise((resolve, reject) => {
924
+ const timeoutHandle = setTimeout(() => {
925
+ this._pendingCalls.delete(msgId);
926
+ this._logger.warn?.("Call timed out", {
927
+ messageId: msgId,
928
+ method: ctxvals.method,
929
+ timeoutMs
930
+ });
931
+ reject(
932
+ new TimeoutError(
933
+ `Call to "${ctxvals.method}" timed out after ${timeoutMs}ms`
934
+ )
935
+ );
936
+ }, timeoutMs);
937
+ const pending = {
938
+ resolve,
939
+ reject,
940
+ timeoutHandle,
941
+ method: ctxvals.method,
942
+ sentAt: Date.now()
943
+ };
944
+ if (options.signal) {
945
+ if (options.signal.aborted) {
946
+ clearTimeout(timeoutHandle);
947
+ reject(options.signal.reason ?? new Error("Aborted"));
948
+ return;
949
+ }
950
+ const abortHandler = () => {
951
+ clearTimeout(timeoutHandle);
952
+ this._pendingCalls.delete(msgId);
953
+ reject(options.signal?.reason ?? new Error("Aborted"));
954
+ };
955
+ options.signal.addEventListener("abort", abortHandler, {
956
+ once: true
957
+ });
958
+ pending.abortHandler = abortHandler;
959
+ }
960
+ this._pendingCalls.set(msgId, pending);
961
+ this._ws?.send(messageStr);
962
+ this.emit("message", message);
963
+ this.emit("call", message);
964
+ });
965
+ });
966
+ return callResult;
967
+ }
968
+ /**
969
+ * Send a raw string message over the WebSocket (use with caution).
970
+ * Messages sent while CONNECTING are buffered and flushed on open.
971
+ */
972
+ sendRaw(message) {
973
+ if (this._state === OPEN && this._ws) {
974
+ this._ws.send(message);
975
+ } else if (this._state === CONNECTING) {
976
+ this._outboundBuffer.push(message);
977
+ } else {
978
+ throw new Error("Cannot send: client is not connected");
979
+ }
980
+ }
981
+ // ─── Reconfigure ─────────────────────────────────────────────
982
+ reconfigure(options) {
983
+ Object.assign(this._options, options);
984
+ if (options.callConcurrency !== void 0) {
985
+ this._callQueue.setConcurrency(options.callConcurrency);
986
+ }
987
+ }
988
+ // ─── Internal: WebSocket attachment ──────────────────────────
989
+ _attachWebsocket(ws) {
990
+ ws.addEventListener(
991
+ "message",
992
+ (event) => this._onMessage(event.data)
993
+ );
994
+ ws.addEventListener(
995
+ "close",
996
+ (event) => this._onClose(event.code, event.reason)
997
+ );
998
+ ws.addEventListener("error", (event) => this.emit("error", event));
999
+ }
1000
+ // ─── Internal: Message handling ──────────────────────────────
1001
+ _onMessage(data) {
1002
+ const raw = typeof data === "string" ? data : String(data);
1003
+ let message;
1004
+ try {
1005
+ message = JSON.parse(raw);
1006
+ if (!Array.isArray(message)) throw new Error("Message is not an array");
1007
+ } catch (err) {
1008
+ this._onBadMessage(raw, err);
1009
+ return;
1010
+ }
1011
+ const messageType = message[0];
1012
+ switch (messageType) {
1013
+ case MessageType.CALL:
1014
+ this._handleIncomingCall(message);
1015
+ break;
1016
+ case MessageType.CALLRESULT:
1017
+ this._handleCallResult(message);
1018
+ break;
1019
+ case MessageType.CALLERROR:
1020
+ this._handleCallError(message);
1021
+ break;
1022
+ default:
1023
+ this._onBadMessage(
1024
+ JSON.stringify(message),
1025
+ new RPCMessageTypeNotSupportedError(
1026
+ `Unknown message type: ${messageType}`
1027
+ )
1028
+ );
1029
+ }
1030
+ }
1031
+ async _handleIncomingCall(message) {
1032
+ const [, msgId, method, params] = message;
1033
+ const ctx = {
1034
+ type: "incoming_call",
1035
+ messageId: msgId,
1036
+ method,
1037
+ params,
1038
+ protocol: this._protocol
1039
+ };
1040
+ await this._middleware.execute(ctx, async (c) => {
1041
+ const ctxvals = c;
1042
+ const modifiedMessage = [
1043
+ MessageType.CALL,
1044
+ ctxvals.messageId,
1045
+ ctxvals.method,
1046
+ ctxvals.params
1047
+ ];
1048
+ this.emit("call", modifiedMessage);
1049
+ if (this._state !== OPEN) {
1050
+ return;
1051
+ }
1052
+ try {
1053
+ if (this._pendingResponses.has(ctxvals.messageId)) {
1054
+ throw createRPCError(
1055
+ "RpcFrameworkError",
1056
+ `Already processing call with ID: ${ctxvals.messageId}`
1057
+ );
1058
+ }
1059
+ const specificHandler = (this._protocol ? this._handlers.get(`${this._protocol}:${ctxvals.method}`) : void 0) ?? this._handlers.get(ctxvals.method);
1060
+ if (!specificHandler && !this._wildcardHandler) {
1061
+ throw createRPCError(
1062
+ "NotImplemented",
1063
+ `No handler for method: ${ctxvals.method}`
1064
+ );
1065
+ }
1066
+ this._pendingResponses.add(ctxvals.messageId);
1067
+ const ac = new AbortController();
1068
+ const context = {
1069
+ messageId: ctxvals.messageId,
1070
+ method: ctxvals.method,
1071
+ protocol: this._protocol,
1072
+ params: ctxvals.params,
1073
+ signal: ac.signal
1074
+ };
1075
+ let result;
1076
+ if (specificHandler) {
1077
+ result = await specificHandler(context);
1078
+ } else if (this._wildcardHandler) {
1079
+ result = await this._wildcardHandler(ctxvals.method, context);
1080
+ }
1081
+ this._pendingResponses.delete(ctxvals.messageId);
1082
+ if (result === NOREPLY) return;
1083
+ const response = [
1084
+ MessageType.CALLRESULT,
1085
+ ctxvals.messageId,
1086
+ result
1087
+ ];
1088
+ this._ws?.send(JSON.stringify(response));
1089
+ this.emit("callResult", response);
1090
+ } catch (err) {
1091
+ this._pendingResponses.delete(ctxvals.messageId);
1092
+ this._logger.error?.("Handler error", {
1093
+ messageId: ctxvals.messageId,
1094
+ method: ctxvals.method,
1095
+ error: err.message
1096
+ });
1097
+ const rpcErr = err instanceof RPCGenericError || err.rpcErrorCode ? err : createRPCError("InternalError", err.message);
1098
+ const details = this._options.respondWithDetailedErrors ? getErrorPlainObject(err) : {};
1099
+ const errorResponse = [
1100
+ MessageType.CALLERROR,
1101
+ ctxvals.messageId,
1102
+ rpcErr.rpcErrorCode,
1103
+ rpcErr.rpcErrorMessage || err.message || "",
1104
+ details
1105
+ ];
1106
+ this._ws?.send(JSON.stringify(errorResponse));
1107
+ this.emit("callError", errorResponse);
1108
+ }
1109
+ });
1110
+ }
1111
+ async _handleCallResult(message) {
1112
+ const [, msgId, result] = message;
1113
+ if (!this._pendingCalls.has(msgId)) {
1114
+ this._logger.warn?.("Received CallResult for unknown messageId", {
1115
+ messageId: msgId
1116
+ });
1117
+ return;
1118
+ }
1119
+ const pending = this._pendingCalls.get(msgId);
1120
+ const ctx = {
1121
+ type: "incoming_result",
1122
+ messageId: msgId,
1123
+ method: pending.method,
1124
+ payload: result
1125
+ };
1126
+ await this._middleware.execute(ctx, async (c) => {
1127
+ const ctxvals = c;
1128
+ const modifiedMessage = [
1129
+ MessageType.CALLRESULT,
1130
+ msgId,
1131
+ ctxvals.payload
1132
+ ];
1133
+ this.emit("callResult", modifiedMessage);
1134
+ clearTimeout(pending.timeoutHandle);
1135
+ this._pendingCalls.delete(msgId);
1136
+ pending.resolve(ctxvals.payload);
1137
+ });
1138
+ }
1139
+ async _handleCallError(message) {
1140
+ const [, msgId, errorCode, errorMessage, errorDetails] = message;
1141
+ if (!this._pendingCalls.has(msgId)) {
1142
+ this._logger.warn?.("Received CallError for unknown messageId", {
1143
+ messageId: msgId
1144
+ });
1145
+ return;
1146
+ }
1147
+ const pending = this._pendingCalls.get(msgId);
1148
+ const error = createRPCError(errorCode, errorMessage, errorDetails);
1149
+ const ctx = {
1150
+ type: "incoming_error",
1151
+ messageId: msgId,
1152
+ method: pending.method,
1153
+ error
1154
+ // Map to types.ts expected `error` shape which takes OCPPCallError specifically here, though we pass it via RPCError
1155
+ };
1156
+ await this._middleware.execute(ctx, async (c) => {
1157
+ const ctxvals = c;
1158
+ const resolvedRpcErr = ctxvals.error;
1159
+ const modifiedMessage = [
1160
+ MessageType.CALLERROR,
1161
+ msgId,
1162
+ resolvedRpcErr.rpcErrorCode,
1163
+ resolvedRpcErr.message,
1164
+ resolvedRpcErr.rpcErrorDetails ?? {}
1165
+ ];
1166
+ this.emit("callError", modifiedMessage);
1167
+ clearTimeout(pending.timeoutHandle);
1168
+ this._pendingCalls.delete(msgId);
1169
+ pending.reject(resolvedRpcErr);
1170
+ });
1171
+ }
1172
+ // ─── Internal: Bad message handling ──────────────────────────
1173
+ _onBadMessage(rawMessage, error) {
1174
+ this._badMessageCount++;
1175
+ this._logger?.warn?.("Bad message", {
1176
+ error: error.message,
1177
+ count: this._badMessageCount
1178
+ });
1179
+ this.emit("badMessage", { message: rawMessage, error });
1180
+ const match = rawMessage.match(/^\s*\[\s*2\s*,\s*"([^"]+)"/);
1181
+ if (match?.[1] && this._ws) {
1182
+ const errorResponse = [
1183
+ MessageType.CALLERROR,
1184
+ match[1],
1185
+ "FormatViolation",
1186
+ error.message || "Invalid message format",
1187
+ {}
1188
+ ];
1189
+ this._ws.send(JSON.stringify(errorResponse));
1190
+ this.emit("callError", errorResponse);
1191
+ }
1192
+ if (this._badMessageCount >= this._options.maxBadMessages) {
1193
+ this.close({ code: 1002, reason: "Too many bad messages" }).catch(
1194
+ () => {
1195
+ }
1196
+ );
1197
+ }
1198
+ }
1199
+ // ─── Internal: Close handling ────────────────────────────────
1200
+ /**
1201
+ * Reject all in-flight calls and clear pending state.
1202
+ */
1203
+ _rejectPendingCalls(reason) {
1204
+ for (const [, pending] of this._pendingCalls) {
1205
+ clearTimeout(pending.timeoutHandle);
1206
+ pending.reject(new Error(reason));
1207
+ }
1208
+ this._pendingCalls.clear();
1209
+ this._pendingResponses.clear();
1210
+ }
1211
+ _onClose(code, reason) {
1212
+ this._rejectPendingCalls(`Connection closed (${code}: ${reason})`);
1213
+ if (this._state !== CLOSING) {
1214
+ this._logger?.info?.("Disconnected", { code, reason });
1215
+ this.emit("disconnect", { code, reason });
1216
+ if (this._options.reconnect && this._reconnectAttempt < this._options.maxReconnects) {
1217
+ this._scheduleReconnect();
1218
+ } else {
1219
+ this._state = CLOSED;
1220
+ this.emit("close", { code, reason });
1221
+ }
1222
+ } else {
1223
+ this._state = CLOSED;
1224
+ }
1225
+ }
1226
+ // ─── Internal: Reconnection ──────────────────────────────────
1227
+ /** Errors that should stop reconnection immediately */
1228
+ static _INTOLERABLE_ERRORS = /* @__PURE__ */ new Set([
1229
+ "Maximum redirects exceeded",
1230
+ "Server sent no subprotocol",
1231
+ "Server sent an invalid subprotocol",
1232
+ "Server sent a subprotocol but none was requested",
1233
+ "Invalid Sec-WebSocket-Accept header"
1234
+ ]);
1235
+ _scheduleReconnect() {
1236
+ this._reconnectAttempt++;
1237
+ this._state = CONNECTING;
1238
+ const base = this._options.backoffMin;
1239
+ const max = this._options.backoffMax;
1240
+ const delayMs = Math.min(
1241
+ max,
1242
+ base * 2 ** (this._reconnectAttempt - 1) * (0.5 + Math.random() * 0.5)
1243
+ );
1244
+ this._logger?.warn?.("Reconnecting", {
1245
+ attempt: this._reconnectAttempt,
1246
+ delayMs: Math.round(delayMs)
1247
+ });
1248
+ this.emit("reconnect", { attempt: this._reconnectAttempt, delay: delayMs });
1249
+ this._reconnectTimer = setTimeout(async () => {
1250
+ this._reconnectTimer = null;
1251
+ try {
1252
+ await this._connectInternal();
1253
+ } catch (err) {
1254
+ const msg = err instanceof Error ? err.message : "";
1255
+ if (_BrowserOCPPClient._INTOLERABLE_ERRORS.has(msg)) {
1256
+ this._logger?.error?.("Intolerable error \u2014 stopping reconnection", {
1257
+ error: msg
1258
+ });
1259
+ this._state = CLOSED;
1260
+ this.emit("close", { code: 1001, reason: msg });
1261
+ return;
1262
+ }
1263
+ if (this._reconnectAttempt < this._options.maxReconnects && this._options.reconnect) {
1264
+ this._scheduleReconnect();
1265
+ } else {
1266
+ this._state = CLOSED;
1267
+ this.emit("close", {
1268
+ code: 1001,
1269
+ reason: "Max reconnection attempts exhausted"
1270
+ });
1271
+ }
1272
+ }
1273
+ }, delayMs);
1274
+ }
1275
+ // ─── Internal: Endpoint building ─────────────────────────────
1276
+ _buildEndpoint() {
1277
+ let url = this._options.endpoint;
1278
+ if (!url.endsWith("/")) url += "/";
1279
+ url += encodeURIComponent(this._identity);
1280
+ if (this._options.query) {
1281
+ const params = new URLSearchParams(this._options.query);
1282
+ url += (url.includes("?") ? "&" : "?") + params.toString();
1283
+ }
1284
+ return url;
1285
+ }
1286
+ // ─── Internal: Cleanup ───────────────────────────────────────
1287
+ _cleanup() {
1288
+ this._closePromise = null;
1289
+ this._ws = null;
1290
+ }
1291
+ // ─── Internal: ID Generation ─────────────────────────────────
1292
+ _generateMessageId() {
1293
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1294
+ return crypto.randomUUID();
1295
+ }
1296
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
1297
+ const r = Math.random() * 16 | 0;
1298
+ const v = c === "x" ? r : r & 3 | 8;
1299
+ return v.toString(16);
1300
+ });
1301
+ }
1302
+ };
1303
+ //# sourceMappingURL=browser.js.map