ocpp-ws-io 1.0.0-alpha

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.

Potentially problematic release.


This version of ocpp-ws-io might be problematic. Click here for more details.

Files changed (45) hide show
  1. package/.github/workflows/publish.yml +52 -0
  2. package/LICENSE +21 -0
  3. package/README.md +773 -0
  4. package/dist/adapters/redis.d.mts +73 -0
  5. package/dist/adapters/redis.d.ts +73 -0
  6. package/dist/adapters/redis.js +96 -0
  7. package/dist/adapters/redis.js.map +1 -0
  8. package/dist/adapters/redis.mjs +71 -0
  9. package/dist/adapters/redis.mjs.map +1 -0
  10. package/dist/index.d.mts +268 -0
  11. package/dist/index.d.ts +268 -0
  12. package/dist/index.js +38919 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.mjs +38855 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/types-6LVUoXof.d.mts +284 -0
  17. package/dist/types-6LVUoXof.d.ts +284 -0
  18. package/package.json +59 -0
  19. package/src/adapters/adapter.ts +40 -0
  20. package/src/adapters/redis.ts +144 -0
  21. package/src/client.ts +882 -0
  22. package/src/errors.ts +183 -0
  23. package/src/event-buffer.ts +73 -0
  24. package/src/index.ts +68 -0
  25. package/src/queue.ts +65 -0
  26. package/src/schemas/ocpp1_6.json +2376 -0
  27. package/src/schemas/ocpp2_0_1.json +11878 -0
  28. package/src/schemas/ocpp2_1.json +23176 -0
  29. package/src/server-client.ts +65 -0
  30. package/src/server.ts +374 -0
  31. package/src/standard-validators.ts +18 -0
  32. package/src/types.ts +316 -0
  33. package/src/util.ts +119 -0
  34. package/src/validator.ts +148 -0
  35. package/src/ws-util.ts +186 -0
  36. package/test/adapter.test.ts +88 -0
  37. package/test/client.test.ts +297 -0
  38. package/test/errors.test.ts +132 -0
  39. package/test/queue.test.ts +133 -0
  40. package/test/server.test.ts +274 -0
  41. package/test/util.test.ts +103 -0
  42. package/test/ws-util.test.ts +93 -0
  43. package/tsconfig.json +25 -0
  44. package/tsup.config.ts +16 -0
  45. package/vitest.config.ts +10 -0
package/README.md ADDED
@@ -0,0 +1,773 @@
1
+ # ocpp-ws-io
2
+
3
+ Type-safe OCPP WebSocket RPC client & server for Node.js.
4
+
5
+ Built with TypeScript from the ground up — supports OCPP 1.6, 2.0.1, and 2.1 with full JSON schema validation, all three security profiles, and optional Redis-based clustering.
6
+
7
+ ## Features
8
+
9
+ - ⚡ **Full OCPP-J RPC** — Call, CallResult, CallError message framing per OCPP-J spec
10
+ - 🔒 **Security Profiles 0–3** — Plain WS, Basic Auth, TLS + Basic Auth, Mutual TLS
11
+ - 📐 **Strict Mode** — Optional schema validation using built-in OCPP JSON schemas
12
+ - 🔁 **Auto-Reconnect** — Exponential backoff with configurable limits
13
+ - 🧩 **Framework Agnostic** — Use standalone, or attach to Express, Fastify, NestJS, etc.
14
+ - 📡 **Clustering** — Optional Redis adapter for multi-instance deployments
15
+ - 🎯 **Type-Safe** — Full TypeScript types for events, handlers, options, and messages
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install ocpp-ws-io
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### Client (Charging Station Simulator)
26
+
27
+ ```typescript
28
+ import { OCPPClient, SecurityProfile } from "ocpp-ws-io";
29
+
30
+ const client = new OCPPClient({
31
+ endpoint: "ws://localhost:3000",
32
+ identity: "CP001",
33
+ protocols: ["ocpp1.6"],
34
+ securityProfile: SecurityProfile.NONE,
35
+ });
36
+
37
+ // Register a handler for incoming calls from the server
38
+ client.handle("Reset", ({ params }) => {
39
+ console.log("Reset requested:", params);
40
+ return { status: "Accepted" };
41
+ });
42
+
43
+ // Connect and send a BootNotification
44
+ await client.connect();
45
+
46
+ const response = await client.call("BootNotification", {
47
+ chargePointVendor: "VendorX",
48
+ chargePointModel: "ModelY",
49
+ });
50
+
51
+ console.log("BootNotification response:", response);
52
+ ```
53
+
54
+ ### Server (Central System)
55
+
56
+ ```typescript
57
+ import { OCPPServer } from "ocpp-ws-io";
58
+
59
+ const server = new OCPPServer({
60
+ protocols: ["ocpp1.6", "ocpp2.0.1"],
61
+ });
62
+
63
+ // Optional: Add authentication
64
+ server.auth((accept, reject, handshake) => {
65
+ console.log(
66
+ `Connection from ${handshake.identity} @ ${handshake.remoteAddress}`,
67
+ );
68
+
69
+ if (handshake.identity === "BLOCKED") {
70
+ return reject(401, "Not authorized");
71
+ }
72
+
73
+ accept({ session: { authorized: true } });
74
+ });
75
+
76
+ // Handle new client connections
77
+ server.on("client", (client) => {
78
+ console.log(`${client.identity} connected (protocol: ${client.protocol})`);
79
+
80
+ // Handle BootNotification from this client
81
+ client.handle("BootNotification", ({ params }) => {
82
+ console.log("BootNotification:", params);
83
+ return {
84
+ status: "Accepted",
85
+ currentTime: new Date().toISOString(),
86
+ interval: 300,
87
+ };
88
+ });
89
+
90
+ // Send a call TO the client
91
+ client
92
+ .call("GetConfiguration", { key: ["HeartbeatInterval"] })
93
+ .then((result) => console.log("GetConfiguration result:", result))
94
+ .catch((err) => console.error("GetConfiguration failed:", err));
95
+
96
+ client.on("close", () => {
97
+ console.log(`${client.identity} disconnected`);
98
+ });
99
+ });
100
+
101
+ await server.listen(3000);
102
+ console.log("OCPP Server listening on port 3000");
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Security Profiles
108
+
109
+ `ocpp-ws-io` supports all OCPP security profiles out of the box.
110
+
111
+ ### Profile 0 — No Security (Development)
112
+
113
+ ```typescript
114
+ const client = new OCPPClient({
115
+ endpoint: "ws://localhost:3000",
116
+ identity: "CP001",
117
+ protocols: ["ocpp1.6"],
118
+ securityProfile: SecurityProfile.NONE,
119
+ });
120
+ ```
121
+
122
+ ### Profile 1 — Basic Auth (Unsecured WS)
123
+
124
+ ```typescript
125
+ const client = new OCPPClient({
126
+ endpoint: "ws://localhost:3000",
127
+ identity: "CP001",
128
+ protocols: ["ocpp1.6"],
129
+ securityProfile: SecurityProfile.BASIC_AUTH,
130
+ password: "my-secret-password",
131
+ });
132
+ ```
133
+
134
+ Server-side password verification:
135
+
136
+ ```typescript
137
+ server.auth((accept, reject, handshake) => {
138
+ const expectedPassword = getPasswordForStation(handshake.identity);
139
+
140
+ if (
141
+ !handshake.password ||
142
+ !handshake.password.equals(Buffer.from(expectedPassword))
143
+ ) {
144
+ return reject(401, "Invalid credentials");
145
+ }
146
+
147
+ accept();
148
+ });
149
+ ```
150
+
151
+ ### Profile 2 — TLS + Basic Auth
152
+
153
+ ```typescript
154
+ import fs from "fs";
155
+
156
+ const client = new OCPPClient({
157
+ endpoint: "wss://csms.example.com",
158
+ identity: "CP001",
159
+ protocols: ["ocpp2.0.1"],
160
+ securityProfile: SecurityProfile.TLS_BASIC_AUTH,
161
+ password: "my-secret-password",
162
+ tls: {
163
+ ca: fs.readFileSync("./certs/ca.pem"),
164
+ rejectUnauthorized: true,
165
+ },
166
+ });
167
+ ```
168
+
169
+ Server:
170
+
171
+ ```typescript
172
+ const server = new OCPPServer({
173
+ protocols: ["ocpp2.0.1"],
174
+ securityProfile: SecurityProfile.TLS_BASIC_AUTH,
175
+ tls: {
176
+ cert: fs.readFileSync("./certs/server.crt"),
177
+ key: fs.readFileSync("./certs/server.key"),
178
+ },
179
+ });
180
+ ```
181
+
182
+ ### Profile 3 — Mutual TLS (Client Certificates)
183
+
184
+ ```typescript
185
+ const client = new OCPPClient({
186
+ endpoint: "wss://csms.example.com",
187
+ identity: "CP001",
188
+ protocols: ["ocpp2.0.1"],
189
+ securityProfile: SecurityProfile.TLS_CLIENT_CERT,
190
+ tls: {
191
+ cert: fs.readFileSync("./certs/client.crt"),
192
+ key: fs.readFileSync("./certs/client.key"),
193
+ ca: fs.readFileSync("./certs/ca.pem"),
194
+ },
195
+ });
196
+ ```
197
+
198
+ Server-side certificate verification:
199
+
200
+ ```typescript
201
+ const server = new OCPPServer({
202
+ protocols: ["ocpp2.0.1"],
203
+ securityProfile: SecurityProfile.TLS_CLIENT_CERT,
204
+ tls: {
205
+ cert: fs.readFileSync("./certs/server.crt"),
206
+ key: fs.readFileSync("./certs/server.key"),
207
+ ca: fs.readFileSync("./certs/ca.pem"),
208
+ },
209
+ });
210
+
211
+ server.auth((accept, reject, handshake) => {
212
+ const cert = handshake.clientCertificate;
213
+ if (!cert || cert.subject?.CN !== handshake.identity) {
214
+ return reject(401, "Certificate identity mismatch");
215
+ }
216
+ accept();
217
+ });
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Framework Integration
223
+
224
+ ### Standalone
225
+
226
+ ```typescript
227
+ const server = new OCPPServer({ protocols: ["ocpp1.6"] });
228
+ await server.listen(3000);
229
+ ```
230
+
231
+ ### With Express
232
+
233
+ ```typescript
234
+ import express from "express";
235
+ import { createServer } from "http";
236
+ import { OCPPServer } from "ocpp-ws-io";
237
+
238
+ const app = express();
239
+ const httpServer = createServer(app);
240
+
241
+ const ocppServer = new OCPPServer({ protocols: ["ocpp1.6"] });
242
+
243
+ await ocppServer.listen(0, undefined, { server: httpServer });
244
+
245
+ ocppServer.on("client", (client) => {
246
+ console.log(`${client.identity} connected`);
247
+ });
248
+
249
+ httpServer.listen(3000, () => {
250
+ console.log("Express + OCPP on port 3000");
251
+ });
252
+ ```
253
+
254
+ ### With Fastify
255
+
256
+ ```typescript
257
+ import Fastify from "fastify";
258
+ import { OCPPServer } from "ocpp-ws-io";
259
+
260
+ const app = Fastify();
261
+ const ocppServer = new OCPPServer({ protocols: ["ocpp2.0.1"] });
262
+
263
+ app.ready().then(async () => {
264
+ await ocppServer.listen(0, undefined, { server: app.server });
265
+ });
266
+
267
+ ocppServer.on("client", (client) => {
268
+ console.log(`${client.identity} connected`);
269
+ });
270
+
271
+ await app.listen({ port: 3000 });
272
+ ```
273
+
274
+ ### Manual `handleUpgrade`
275
+
276
+ For maximum control, use the `handleUpgrade` getter directly:
277
+
278
+ ```typescript
279
+ import { createServer } from "http";
280
+ import { OCPPServer } from "ocpp-ws-io";
281
+
282
+ const httpServer = createServer();
283
+ const ocppServer = new OCPPServer({ protocols: ["ocpp1.6"] });
284
+
285
+ httpServer.on("upgrade", ocppServer.handleUpgrade);
286
+
287
+ httpServer.listen(3000);
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Strict Mode (Schema Validation)
293
+
294
+ Enable strict mode to validate all inbound and outbound messages against the OCPP JSON schemas:
295
+
296
+ ```typescript
297
+ const client = new OCPPClient({
298
+ endpoint: "ws://localhost:3000",
299
+ identity: "CP001",
300
+ protocols: ["ocpp1.6"],
301
+ strictMode: true, // validates all calls against built-in schemas
302
+ });
303
+
304
+ // If validation fails, an RPCError is thrown automatically
305
+ client.on("strictValidationFailure", ({ message, error }) => {
306
+ console.error("Validation failed:", error.message);
307
+ });
308
+ ```
309
+
310
+ You can restrict strict mode to specific subprotocols:
311
+
312
+ ```typescript
313
+ const client = new OCPPClient({
314
+ protocols: ["ocpp1.6", "ocpp2.0.1"],
315
+ strictMode: ["ocpp2.0.1"], // only validate OCPP 2.0.1 messages
316
+ });
317
+ ```
318
+
319
+ Custom validators:
320
+
321
+ ```typescript
322
+ import { createValidator } from "ocpp-ws-io";
323
+ import myCustomSchemas from "./my-schemas.json";
324
+
325
+ const myValidator = createValidator("custom-protocol", myCustomSchemas);
326
+
327
+ const client = new OCPPClient({
328
+ protocols: ["custom-protocol"],
329
+ strictMode: true,
330
+ strictModeValidators: [myValidator],
331
+ });
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Clustering with Redis
337
+
338
+ For multi-instance deployments behind a load balancer, use the `RedisAdapter` to distribute events across processes:
339
+
340
+ ```typescript
341
+ import { OCPPServer } from "ocpp-ws-io";
342
+ import { RedisAdapter } from "ocpp-ws-io/adapters/redis";
343
+ import Redis from "ioredis";
344
+
345
+ const server = new OCPPServer({ protocols: ["ocpp2.0.1"] });
346
+
347
+ server.setAdapter(
348
+ new RedisAdapter({
349
+ pubClient: new Redis(),
350
+ subClient: new Redis(),
351
+ prefix: "ocpp:", // optional, default: 'ocpp-ws-io:'
352
+ }),
353
+ );
354
+
355
+ server.on("client", (client) => {
356
+ console.log(`${client.identity} connected`);
357
+ });
358
+
359
+ await server.listen(3000);
360
+ ```
361
+
362
+ The adapter is generic — it works with `ioredis`, `redis` (node-redis), or any client implementing the `RedisLikeClient` interface.
363
+
364
+ ```typescript
365
+ // With node-redis
366
+ import { createClient } from "redis";
367
+
368
+ const pub = createClient();
369
+ const sub = pub.duplicate();
370
+ await pub.connect();
371
+ await sub.connect();
372
+
373
+ server.setAdapter(new RedisAdapter({ pubClient: pub, subClient: sub }));
374
+ ```
375
+
376
+ ---
377
+
378
+ ## API Reference
379
+
380
+ ### `OCPPClient`
381
+
382
+ ```typescript
383
+ import { OCPPClient } from "ocpp-ws-io";
384
+ ```
385
+
386
+ #### Constructor
387
+
388
+ ```typescript
389
+ const client = new OCPPClient(options: ClientOptions);
390
+ ```
391
+
392
+ **`ClientOptions`**:
393
+
394
+ | Option | Type | Default | Description |
395
+ | --------------------------- | ------------------------ | ---------- | -------------------------------------------- |
396
+ | `identity` | `string` | _required_ | Charging station ID |
397
+ | `endpoint` | `string` | _required_ | WebSocket URL (`ws://` or `wss://`) |
398
+ | `protocols` | `string[]` | `[]` | OCPP subprotocols to negotiate |
399
+ | `securityProfile` | `SecurityProfile` | `NONE` | Security profile (0–3) |
400
+ | `password` | `string \| Buffer` | — | Password for Basic Auth (Profile 1 & 2) |
401
+ | `tls` | `TLSOptions` | — | TLS/SSL options (Profile 2 & 3) |
402
+ | `headers` | `Record<string, string>` | — | Additional WebSocket headers |
403
+ | `query` | `Record<string, string>` | — | Additional URL query parameters |
404
+ | `reconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
405
+ | `maxReconnects` | `number` | `Infinity` | Max reconnection attempts |
406
+ | `backoffMin` | `number` | `1000` | Initial reconnect delay (ms) |
407
+ | `backoffMax` | `number` | `30000` | Maximum reconnect delay (ms) |
408
+ | `callTimeoutMs` | `number` | `30000` | Default RPC call timeout (ms) |
409
+ | `pingIntervalMs` | `number` | `30000` | WebSocket ping interval (ms), `0` to disable |
410
+ | `deferPingsOnActivity` | `boolean` | `false` | Defer pings when messages received |
411
+ | `callConcurrency` | `number` | `1` | Max concurrent outbound calls |
412
+ | `strictMode` | `boolean \| string[]` | `false` | Enable/restrict schema validation |
413
+ | `strictModeValidators` | `Validator[]` | — | Custom validators for strict mode |
414
+ | `maxBadMessages` | `number` | `Infinity` | Close after N consecutive bad messages |
415
+ | `respondWithDetailedErrors` | `boolean` | `false` | Include error details in responses |
416
+
417
+ #### Properties
418
+
419
+ | Property | Type | Description |
420
+ | ------------------------ | --------------------- | ---------------------------------------------------- |
421
+ | `client.identity` | `string` | Charging station identity |
422
+ | `client.protocol` | `string \| undefined` | Negotiated subprotocol |
423
+ | `client.state` | `ConnectionState` | Connection state (CONNECTING, OPEN, CLOSING, CLOSED) |
424
+ | `client.securityProfile` | `SecurityProfile` | Active security profile |
425
+
426
+ #### Methods
427
+
428
+ ```typescript
429
+ // Connect to the OCPP server
430
+ await client.connect();
431
+
432
+ // Make an RPC call
433
+ const result = await client.call('BootNotification', { ... });
434
+
435
+ // Make a call with options
436
+ const result = await client.call('RemoteStartTransaction', params, {
437
+ timeoutMs: 5000,
438
+ signal: abortController.signal,
439
+ });
440
+
441
+ // Register a handler for a specific method
442
+ client.handle('Reset', ({ params, method, messageId, signal }) => {
443
+ return { status: 'Accepted' };
444
+ });
445
+
446
+ // Register a wildcard handler (handles all unmatched methods)
447
+ client.handle((method, { params }) => {
448
+ console.log(`Unhandled method: ${method}`);
449
+ throw new RPCNotImplementedError();
450
+ });
451
+
452
+ // Remove a handler
453
+ client.removeHandler('Reset');
454
+ client.removeAllHandlers();
455
+
456
+ // Close the connection
457
+ await client.close();
458
+ await client.close({ code: 1000, reason: 'Normal closure' });
459
+ await client.close({ awaitPending: true }); // wait for in-flight calls
460
+ await client.close({ force: true }); // immediate termination
461
+
462
+ // Reconfigure at runtime
463
+ client.reconfigure({ callTimeoutMs: 10000 });
464
+
465
+ // Send a raw message (advanced — use with caution)
466
+ client.sendRaw(JSON.stringify([2, 'uuid', 'Heartbeat', {}]));
467
+ ```
468
+
469
+ #### Events
470
+
471
+ ```typescript
472
+ client.on("open", ({ response }) => {
473
+ /* connected */
474
+ });
475
+ client.on("close", ({ code, reason }) => {
476
+ /* disconnected */
477
+ });
478
+ client.on("error", (error) => {
479
+ /* error occurred */
480
+ });
481
+ client.on("connecting", ({ url }) => {
482
+ /* attempting connection */
483
+ });
484
+ client.on("reconnect", ({ attempt, delay }) => {
485
+ /* reconnecting */
486
+ });
487
+ client.on("message", (message) => {
488
+ /* raw OCPP message */
489
+ });
490
+ client.on("call", (call) => {
491
+ /* outbound call sent */
492
+ });
493
+ client.on("callResult", (result) => {
494
+ /* call result received */
495
+ });
496
+ client.on("callError", (error) => {
497
+ /* call error received */
498
+ });
499
+ client.on("badMessage", ({ message, error }) => {
500
+ /* malformed message */
501
+ });
502
+ client.on("ping", () => {
503
+ /* ping sent */
504
+ });
505
+ client.on("pong", () => {
506
+ /* pong received */
507
+ });
508
+ client.on("strictValidationFailure", ({ message, error }) => {
509
+ /* schema validation failure */
510
+ });
511
+ ```
512
+
513
+ ---
514
+
515
+ ### `OCPPServer`
516
+
517
+ ```typescript
518
+ import { OCPPServer } from "ocpp-ws-io";
519
+ ```
520
+
521
+ #### Constructor
522
+
523
+ ```typescript
524
+ const server = new OCPPServer(options?: ServerOptions);
525
+ ```
526
+
527
+ **`ServerOptions`**:
528
+
529
+ | Option | Type | Default | Description |
530
+ | --------------------------- | --------------------- | ---------- | ----------------------------------------- |
531
+ | `protocols` | `string[]` | `[]` | Accepted OCPP subprotocols |
532
+ | `securityProfile` | `SecurityProfile` | `NONE` | Security profile for auto-created servers |
533
+ | `tls` | `TLSOptions` | — | TLS options (Profile 2 & 3) |
534
+ | `callTimeoutMs` | `number` | `30000` | Inherited by server clients |
535
+ | `pingIntervalMs` | `number` | `30000` | Inherited by server clients |
536
+ | `callConcurrency` | `number` | `1` | Inherited by server clients |
537
+ | `strictMode` | `boolean \| string[]` | `false` | Inherited by server clients |
538
+ | `maxBadMessages` | `number` | `Infinity` | Inherited by server clients |
539
+ | `respondWithDetailedErrors` | `boolean` | `false` | Inherited by server clients |
540
+
541
+ #### Properties
542
+
543
+ | Property | Type | Description |
544
+ | ---------------- | ------------------------------- | --------------------- |
545
+ | `server.clients` | `ReadonlySet<OCPPServerClient>` | All connected clients |
546
+
547
+ #### Methods
548
+
549
+ ```typescript
550
+ // Start listening on a port
551
+ const httpServer = await server.listen(port, host?, options?);
552
+
553
+ // Attach authentication handler
554
+ server.auth((accept, reject, handshake, signal) => {
555
+ // handshake contains: identity, remoteAddress, headers,
556
+ // protocols, endpoint, query, password, clientCertificate,
557
+ // securityProfile, request
558
+ accept({ session: { userId: 123 } });
559
+ // or: reject(401, 'Not authorized');
560
+ });
561
+
562
+ // Close the server
563
+ await server.close();
564
+ await server.close({ awaitPending: true });
565
+
566
+ // Set an event adapter for clustering
567
+ server.setAdapter(adapter);
568
+
569
+ // Publish events across instances
570
+ await server.publish('firmware-update', { stationId: 'CP001' });
571
+
572
+ // Reconfigure at runtime
573
+ server.reconfigure({ callTimeoutMs: 60000 });
574
+
575
+ // Get the handleUpgrade function for manual use
576
+ const upgrade = server.handleUpgrade;
577
+ httpServer.on('upgrade', upgrade);
578
+ ```
579
+
580
+ #### Events
581
+
582
+ ```typescript
583
+ server.on("client", (client: OCPPServerClient) => {
584
+ /* new client connected */
585
+ });
586
+ server.on("error", (error) => {
587
+ /* server error */
588
+ });
589
+ server.on("upgradeError", ({ error, socket }) => {
590
+ /* upgrade failed */
591
+ });
592
+ ```
593
+
594
+ ---
595
+
596
+ ### `OCPPServerClient`
597
+
598
+ Server-side client representation, created automatically when a charging station connects. Extends `OCPPClient` with additional properties:
599
+
600
+ ```typescript
601
+ server.on("client", (client) => {
602
+ client.identity; // string — station identity
603
+ client.protocol; // string — negotiated subprotocol
604
+ client.session; // Record<string, unknown> — session data from auth
605
+ client.handshake; // HandshakeInfo — connection handshake details
606
+
607
+ // All OCPPClient methods available:
608
+ client.handle("Heartbeat", () => ({ currentTime: new Date().toISOString() }));
609
+ const result = await client.call("GetConfiguration", { key: [] });
610
+ await client.close();
611
+ });
612
+ ```
613
+
614
+ ---
615
+
616
+ ### `NOREPLY`
617
+
618
+ Return `NOREPLY` from a handler to suppress the automatic response:
619
+
620
+ ```typescript
621
+ import { NOREPLY } from "ocpp-ws-io";
622
+
623
+ client.handle("StatusNotification", ({ params }) => {
624
+ // Process the notification but don't send a response
625
+ return NOREPLY;
626
+ });
627
+ ```
628
+
629
+ ---
630
+
631
+ ### Error Classes
632
+
633
+ All error classes are exported for `instanceof` checks:
634
+
635
+ ```typescript
636
+ import {
637
+ // Base errors
638
+ TimeoutError, // Call timeout
639
+ UnexpectedHttpResponse, // Non-101 upgrade response
640
+ WebsocketUpgradeError, // WebSocket upgrade failure
641
+
642
+ // RPC errors (OCPP-J spec Section 4.3)
643
+ RPCGenericError,
644
+ RPCNotImplementedError,
645
+ RPCNotSupportedError,
646
+ RPCInternalError,
647
+ RPCProtocolError,
648
+ RPCSecurityError,
649
+ RPCFormatViolationError,
650
+ RPCFormationViolationError,
651
+ RPCPropertyConstraintViolationError,
652
+ RPCOccurrenceConstraintViolationError,
653
+ RPCTypeConstraintViolationError,
654
+ RPCMessageTypeNotSupportedError,
655
+ RPCFrameworkError,
656
+ } from "ocpp-ws-io";
657
+ ```
658
+
659
+ Throw typed errors from handlers:
660
+
661
+ ```typescript
662
+ client.handle("Reset", ({ params }) => {
663
+ if (params.type === "Hard") {
664
+ throw new RPCNotSupportedError("Hard reset not supported");
665
+ }
666
+ return { status: "Accepted" };
667
+ });
668
+ ```
669
+
670
+ ---
671
+
672
+ ### Utility Functions
673
+
674
+ ```typescript
675
+ import {
676
+ createRPCError,
677
+ getErrorPlainObject,
678
+ getPackageIdent,
679
+ } from "ocpp-ws-io";
680
+
681
+ // Create an RPC error from a code string
682
+ const err = createRPCError("NotImplemented", "Not available");
683
+
684
+ // Serialize an error to a plain JSON-safe object
685
+ const plain = getErrorPlainObject(err);
686
+
687
+ // Get package identifier (for headers, logging)
688
+ const ident = getPackageIdent(); // "ocpp-ws-io/1.0.0"
689
+ ```
690
+
691
+ ---
692
+
693
+ ## Constants
694
+
695
+ ```typescript
696
+ import { ConnectionState, MessageType, SecurityProfile } from "ocpp-ws-io";
697
+
698
+ // Connection states
699
+ ConnectionState.CONNECTING; // 0
700
+ ConnectionState.OPEN; // 1
701
+ ConnectionState.CLOSING; // 2
702
+ ConnectionState.CLOSED; // 3
703
+
704
+ // Also available as static properties
705
+ OCPPClient.CONNECTING;
706
+ OCPPClient.OPEN;
707
+ OCPPClient.CLOSING;
708
+ OCPPClient.CLOSED;
709
+
710
+ // Message types
711
+ MessageType.CALL; // 2
712
+ MessageType.CALLRESULT; // 3
713
+ MessageType.CALLERROR; // 4
714
+
715
+ // Security profiles
716
+ SecurityProfile.NONE; // 0
717
+ SecurityProfile.BASIC_AUTH; // 1
718
+ SecurityProfile.TLS_BASIC_AUTH; // 2
719
+ SecurityProfile.TLS_CLIENT_CERT; // 3
720
+ ```
721
+
722
+ ---
723
+
724
+ ## TypeScript Types
725
+
726
+ All types are exported for use in your application:
727
+
728
+ ```typescript
729
+ import type {
730
+ // OCPP message types
731
+ OCPPCall,
732
+ OCPPCallResult,
733
+ OCPPCallError,
734
+ OCPPMessage,
735
+
736
+ // Handler types
737
+ HandlerContext,
738
+ CallHandler,
739
+ WildcardHandler,
740
+
741
+ // Option types
742
+ ClientOptions,
743
+ ServerOptions,
744
+ CallOptions,
745
+ CloseOptions,
746
+ ListenOptions,
747
+ TLSOptions,
748
+
749
+ // Auth types
750
+ AuthAccept,
751
+ AuthCallback,
752
+ HandshakeInfo,
753
+ SessionData,
754
+
755
+ // Event types
756
+ ClientEvents,
757
+ ServerEvents,
758
+
759
+ // Adapter interface (for custom adapters)
760
+ EventAdapterInterface,
761
+ } from "ocpp-ws-io";
762
+ ```
763
+
764
+ ---
765
+
766
+ ## Requirements
767
+
768
+ - **Node.js** ≥ 18.0.0
769
+ - **TypeScript** ≥ 5.0 (optional, but recommended)
770
+
771
+ ## License
772
+
773
+ [MIT](LICENSE)