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.
package/README.md ADDED
@@ -0,0 +1,397 @@
1
+ # ocpp-ws-io
2
+
3
+ > **Type-safe OCPP WebSocket RPC client & server for Node.js.**
4
+ >
5
+ > built with TypeScript — supports OCPP 1.6, 2.0.1, and 2.1 with full JSON schema validation, all security profiles, and clustering support.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/ocpp-ws-io.svg)](https://www.npmjs.com/package/ocpp-ws-io)
8
+ [![License](https://img.shields.io/npm/l/ocpp-ws-io.svg)](https://github.com/rohittiwari-dev/ocpp-ws-io/blob/main/LICENSE)
9
+
10
+ ## 📚 Documentation
11
+
12
+ For full API reference, advanced usage, and guides, visit the **[Official Documentation](https://ocpp-ws-io.rohittiwari.me)**.
13
+
14
+ ## ✨ Features
15
+
16
+ - ⚡ **Full OCPP-J RPC** — Compliant message framing
17
+ - 🔒 **Security Profiles 0–3** — Plain WS, Basic Auth, TLS, mTLS
18
+ - 🎯 **Type-Safe** — Auto-generated types for all OCPP messages
19
+ - 📐 **Strict Mode** — Optional JSON schema validation
20
+ - 📡 **Clustering** — Redis adapter support
21
+ - 🌐 **Browser Client** — Zero-dependency client for simulators
22
+
23
+ ## 📦 Installation
24
+
25
+ ```bash
26
+ npm install ocpp-ws-io
27
+ ```
28
+
29
+ ## 🚀 Quick Start
30
+
31
+ ### Client (Charging Station Simulator)
32
+
33
+ ```typescript
34
+ import { OCPPClient } from "ocpp-ws-io";
35
+
36
+ const client = new OCPPClient({
37
+ endpoint: "ws://localhost:3000",
38
+ identity: "CP001",
39
+ protocols: ["ocpp1.6"],
40
+ });
41
+
42
+ await client.connect();
43
+
44
+ // Fully typed call
45
+ const response = await client.call("ocpp1.6", "BootNotification", {
46
+ chargePointVendor: "VendorX",
47
+ chargePointModel: "ModelY",
48
+ });
49
+
50
+ console.log("Status:", response.status);
51
+ ```
52
+
53
+ ### Server (Central System)
54
+
55
+ ```typescript
56
+ import { OCPPServer } from "ocpp-ws-io";
57
+
58
+ const server = new OCPPServer({
59
+ protocols: ["ocpp1.6", "ocpp2.0.1"],
60
+ logging: { prettify: true, exchangeLog: true, level: "info" },
61
+ });
62
+
63
+ // Optional: Add authentication ringfence
64
+ server.auth((ctx) => {
65
+ console.log(
66
+ `Connection from ${ctx.handshake.identity} at path ${ctx.handshake.pathname}`,
67
+ );
68
+ ctx.accept({ session: { authorized: true } });
69
+ });
70
+
71
+ server.on("client", (client) => {
72
+ console.log(`${client.identity} connected`);
73
+
74
+ // Version-aware handler
75
+ client.handle("ocpp1.6", "BootNotification", ({ params }) => {
76
+ console.log("Boot from:", params.chargePointVendor);
77
+ return {
78
+ status: "Accepted",
79
+ currentTime: new Date().toISOString(),
80
+ interval: 300,
81
+ };
82
+ });
83
+ });
84
+
85
+ await server.listen(3000);
86
+ ```
87
+
88
+ ## ⚙️ Configuration
89
+
90
+ ### `OCPPClient` Options
91
+
92
+ | Option | Type | Default | Description |
93
+ | ------------------- | --------------------- | ---------- | --------------------------------------- |
94
+ | `identity` | `string` | _required_ | Charging station ID |
95
+ | `endpoint` | `string` | _required_ | WebSocket URL (`ws://` or `wss://`) |
96
+ | `protocols` | `OCPPProtocol[]` | `[]` | OCPP subprotocols to negotiate |
97
+ | `securityProfile` | `SecurityProfile` | `NONE` | Security profile (0–3) |
98
+ | `password` | `string \| Buffer` | — | Password for Basic Auth (Profile 1 & 2) |
99
+ | `tls` | `TLSOptions` | — | TLS/SSL options (Profile 2 & 3) |
100
+ | `reconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
101
+ | `pingIntervalMs` | `number` | `30000` | Includes ±25% randomized jitter |
102
+ | `strictMode` | `boolean \| string[]` | `false` | Enable/restrict schema validation |
103
+ | `strictModeMethods` | `string[]` | — | Restrict validation to specific methods |
104
+
105
+ ### `OCPPServer` Options
106
+
107
+ | Option | Type | Default | Description |
108
+ | -------------------- | ------------------ | --------- | ------------------------------------------ |
109
+ | `protocols` | `OCPPProtocol[]` | `[]` | Accepted OCPP subprotocols |
110
+ | `securityProfile` | `SecurityProfile` | `NONE` | Security profile for auto-created servers |
111
+ | `handshakeTimeoutMs` | `number` | `30000` | Timeout for WebSocket handshake (ms) |
112
+ | `tls` | `TLSOptions` | — | TLS options (Profile 2 & 3) |
113
+ | `logging` | `LoggingConfig` | `true` | Configure built-in logging |
114
+ | `sessionTtlMs` | `number` | `7200000` | Garbage collection inactivity timeout (ms) |
115
+ | `rateLimit` | `RateLimitOptions` | — | Token bucket socket & method rate-limiter |
116
+
117
+ ## 🛠️ Advanced Server Configuration
118
+
119
+ ### Handshake & Upgrades
120
+
121
+ You can fine-tune how the server handles the WebSocket upgrade process, including timeouts for custom auth logic.
122
+
123
+ ```typescript
124
+ const server = new OCPPServer({
125
+ // ...
126
+ handshakeTimeoutMs: 5000, // Timeout if auth callback takes too long (default 30s)
127
+ });
128
+
129
+ server.on("upgradeAborted", ({ identity, reason, socket }) => {
130
+ console.warn(`Handshake aborted for ${identity}: ${reason}`);
131
+ });
132
+
133
+ server.on("upgradeError", ({ error, socket }) => {
134
+ console.error("Upgrade failed:", error);
135
+ });
136
+ ```
137
+
138
+ ### Server & Router Execution Flow
139
+
140
+ The `OCPPServer` and its internal `OCPPRouter` handle connections and messages in a strict, two-phase execution hierarchy:
141
+
142
+ #### 1. Connection Phase (HTTP Upgrade)
143
+
144
+ Executes before the WebSocket connection is officially accepted.
145
+
146
+ 1. **Route Matching (`router.route`)**: The incoming URL is matched against defined patterns.
147
+ 2. **Connection Middleware (`router.use`)**: Runs sequentially. Used to extract tokens, inspect headers, or implement early rate-limiting logic.
148
+ 3. **Auth Callback (`router.auth`)**: Runs **last** in the HTTP upgrade chain. Used to validate credentials against a database and finally accept/reject the connection.
149
+
150
+ #### 2. Message Phase (WebSocket Open)
151
+
152
+ Executes after the connection is accepted and messages start flowing. 4. **Message Middleware (`client.use` / `server.use`)**: Intercepts every outgoing/incoming message for logging, schema validation, or metric tracking. 5. **Message Handlers (`client.handle` / `server.handle`)**: The **final piece of business logic** where the system reacts to a specific OCPP action (e.g., `BootNotification`).
153
+
154
+ ## 📝 Logging
155
+
156
+ ocpp-ws-io comes with **built-in structured logging** via [voltlog-io](https://www.npmjs.com/package/voltlog-io).
157
+
158
+ ### Default Behavior
159
+
160
+ By default (`logging: true`), logs are output as structured JSON to `stdout`.
161
+
162
+ ```json
163
+ {
164
+ "level": 30,
165
+ "time": 1678900000000,
166
+ "msg": "Client connected",
167
+ "component": "OCPPServer",
168
+ "identity": "CP001"
169
+ }
170
+ ```
171
+
172
+ ### Pretty Printing & Exchange Logs
173
+
174
+ Enable `prettify` for development to see colored output with icons.
175
+ Enable `exchangeLog` to log all OCPP messages with direction (`IN`/`OUT`) and metadata.
176
+
177
+ ```typescript
178
+ const client = new OCPPClient({
179
+ // ...
180
+ logging: {
181
+ enabled: true,
182
+ prettify: true, // 🌈 Colors & icons
183
+ exchangeLog: true, // ⚡ Log all OCPP messages
184
+ level: "debug", // Default: 'info'
185
+ },
186
+ });
187
+ ```
188
+
189
+ **Output:**
190
+
191
+ ```
192
+ ⚡ CP-101 → BootNotification [OUT]
193
+ ✅ CP-101 ← BootNotification [IN] { latencyMs: 45 }
194
+ ```
195
+
196
+ ### Custom Logger
197
+
198
+ You can bring your own logger (Pino, Winston, etc.) by implementing `LoggerLike`:
199
+
200
+ ````typescript
201
+ ```typescript
202
+ import pino from "pino";
203
+
204
+ const client = new OCPPClient({
205
+ logging: {
206
+ handler: pino(), // Use existing logger instance
207
+ },
208
+ });
209
+ ````
210
+
211
+ ## 🛡️ Safety & Reliability
212
+
213
+ ### Safe Calls (`safeCall`)
214
+
215
+ Perform RPC calls without `try/catch` blocks. Returns the response data on success, or `undefined` on failure while automatically logging the error. You can also pass per-call config options like timeouts.
216
+
217
+ ````typescript
218
+ const result = await client.safeCall(
219
+ "ocpp1.6",
220
+ "Heartbeat",
221
+ {},
222
+ {
223
+ timeoutMs: 15000, // Finely control the timeout specifically for this request
224
+ },
225
+ );
226
+
227
+ if (result) {
228
+ // Checked for undefined
229
+ console.log("Heartbeat accepted:", result.currentTime);
230
+ }
231
+
232
+ ### Unicast Routing (`sendToClient` / `safeSendToClient`) [Server]
233
+
234
+ Send a message to a specific client ID, even if they are connected to a different node in the cluster.
235
+
236
+ You have two options depending on your error-handling preference:
237
+
238
+ #### 1. Standard approach (`sendToClient`)
239
+ Throws an error if the client responds with a `CALLERROR` or if the timeout is reached.
240
+ ```typescript
241
+ try {
242
+ const result = await server.sendToClient(
243
+ "CP001",
244
+ "ocpp1.6",
245
+ "GetConfiguration",
246
+ { key: ["ClockAlignedDataInterval"] },
247
+ { timeoutMs: 10000 },
248
+ );
249
+ console.log("Configuration:", result);
250
+ } catch (error) {
251
+ console.error("Failed to get configuration:", error);
252
+ }
253
+ ````
254
+
255
+ #### 2. Safe approach (`safeSendToClient`)
256
+
257
+ Returns the response on success, or `undefined` on error, automatically logging the failure internally.
258
+
259
+ ```typescript
260
+ const result = await server.safeSendToClient(
261
+ "CP001",
262
+ "ocpp1.6",
263
+ "GetConfiguration",
264
+ { key: ["ClockAlignedDataInterval"] },
265
+ { timeoutMs: 10000 },
266
+ );
267
+
268
+ if (result) {
269
+ console.log("Configuration:", result);
270
+ }
271
+ ```
272
+
273
+ ### 2. Connection Middleware (Server)
274
+
275
+ For intercepting HTTP WebSocket Upgrade requests before they become an OCPP Client.
276
+
277
+ ```typescript
278
+ const rateLimiter = defineMiddleware(async (ctx) => {
279
+ const ip = ctx.handshake.remoteAddress;
280
+ if (isRateLimited(ip)) {
281
+ // Instantly aborts the WebSocket connection with an HTTP 429 status
282
+ ctx.reject(429, "Too Many Requests");
283
+ } else {
284
+ // Or proceed down the execution chain. You can optionally pass an object
285
+ // to next(), which will automatically be shallow-merged into `ctx.state`.
286
+ await ctx.next({ rateLimitRemaining: 99 });
287
+ }
288
+ });
289
+
290
+ server.use(rateLimiter);
291
+ ```
292
+
293
+ ### 3. Advanced Rate Limiting (Token Bucket)
294
+
295
+ To protect your CSMS from Noisy-Neighbor problems or firmware loops (e.g., a charger rapidly spamming `MeterValues`), you can enable the built-in Token Bucket Rate Limiter.
296
+
297
+ The Rate Limiter safely throttles connections without dropping the connection immediately. You can define global limits, method-specific limits, and provide a custom action callback when the limit is breached.
298
+
299
+ ```typescript
300
+ const server = new OCPPServer({
301
+ protocols: ["ocpp1.6"],
302
+ rateLimit: {
303
+ limit: 100, // Global limit
304
+ windowMs: 60000, // per 60 seconds
305
+ onLimitExceeded: "disconnect", // or "ignore", or a Custom Callback
306
+ methods: {
307
+ MeterValues: { limit: 10, windowMs: 60000 },
308
+ Heartbeat: { limit: 2, windowMs: 60000 },
309
+ },
310
+ },
311
+ });
312
+ ```
313
+
314
+ ## 🧩 Middleware
315
+
316
+ Intercept and modify OCPP messages using the middleware stack.
317
+
318
+ ```typescript
319
+ // Add logging middleware (enabled by default)
320
+ client.use(async (ctx, next) => {
321
+ console.log(`Processing ${ctx.method}`);
322
+ await next();
323
+ });
324
+ ```
325
+
326
+ ## 📡 Clustering (Redis)
327
+
328
+ Scale your OCPP server across multiple nodes using Redis.
329
+
330
+ 1. **Install Adapter**:
331
+
332
+ ```bash
333
+ npm install ioredis
334
+ ```
335
+
336
+ 2. **Configure Server**:
337
+
338
+ ```typescript
339
+ import { OCPPServer } from "ocpp-ws-io";
340
+ import { RedisAdapter } from "ocpp-ws-io/adapters/redis";
341
+ import Redis from "ioredis";
342
+
343
+ const redis = new Redis(process.env.REDIS_URL);
344
+ const server = new OCPPServer({
345
+ protocols: ["ocpp1.6"],
346
+ });
347
+
348
+ // Uses Redis Streams for clustering reliability
349
+ await server.setAdapter(new RedisAdapter(redis));
350
+ ```
351
+
352
+ **Features:**
353
+
354
+ - **Unicast Routing**: Send messages to any client on any node.
355
+ - **Presence**: Track connected clients across the cluster.
356
+ - **Reliability**: Zero message loss during node restarts (via Redis Streams).
357
+ - **Batch Processing**: Use `server.broadcastBatch` to combine multi-node calls effortlessly.
358
+
359
+ ### Custom Adapters (`EventAdapterInterface`)
360
+
361
+ You can build custom clustering adapters (e.g., RabbitMQ, Kafka, Postgres PUB/SUB) by implementing the exported `EventAdapterInterface`:
362
+
363
+ ```typescript
364
+ import type { EventAdapterInterface } from "ocpp-ws-io";
365
+
366
+ export class CustomAdapter implements EventAdapterInterface {
367
+ async publish(channel: string, data: unknown): Promise<void> {
368
+ /* ... */
369
+ }
370
+ async subscribe(
371
+ channel: string,
372
+ handler: (data: unknown) => void,
373
+ ): Promise<void> {
374
+ /* ... */
375
+ }
376
+ async unsubscribe(channel: string): Promise<void> {
377
+ /* ... */
378
+ }
379
+ async disconnect(): Promise<void> {
380
+ /* ... */
381
+ }
382
+
383
+ // Optional primitives for advanced routing:
384
+ async setPresence?(
385
+ identity: string,
386
+ nodeId: string,
387
+ ttl: number,
388
+ ): Promise<void>;
389
+ async getPresence?(identity: string): Promise<string | null>;
390
+ async removePresence?(identity: string): Promise<void>;
391
+ async getPresenceBatch?(identities: string[]): Promise<(string | null)[]>;
392
+ async publishBatch?(
393
+ messages: { channel: string; data: unknown }[],
394
+ ): Promise<void>;
395
+ async metrics?(): Promise<Record<string, unknown>>;
396
+ }
397
+ ```
@@ -0,0 +1,9 @@
1
+ export { W as RedisAdapter, a0 as RedisAdapterOptions } from '../index-BixJj_yJ.mjs';
2
+ import 'ws';
3
+ import 'node:https';
4
+ import 'node:http';
5
+ import 'node:events';
6
+ import 'node:stream';
7
+ import 'node:tls';
8
+ import 'voltlog-io';
9
+ import 'ajv';
@@ -0,0 +1,9 @@
1
+ export { W as RedisAdapter, a0 as RedisAdapterOptions } from '../index-BixJj_yJ.js';
2
+ import 'ws';
3
+ import 'node:https';
4
+ import 'node:http';
5
+ import 'node:events';
6
+ import 'node:stream';
7
+ import 'node:tls';
8
+ import 'voltlog-io';
9
+ import 'ajv';