Browser Client
Use BrowserOCPPClient in browser environments — React, Vue, Next.js, and more.
BrowserOCPPClient is a lightweight OCPP WebSocket RPC client designed for browser environments. It enables building charge point simulators, testing dashboards, and debugging tools directly in the browser — using the same typed API as OCPPClient.
import { BrowserOCPPClient } from 'ocpp-ws-io/browser';[!IMPORTANT] The browser client is designed for testing and simulating charge points — it does not support all OCPP features. Missing: Security Profiles (0–3), Strict Mode (schema validation), TLS/mTLS configuration, WebSocket Ping/Pong, and custom HTTP headers. For production charge point communication, use
OCPPClientin a Node.js environment.
Limitations vs OCPPClient
OCPPClient depends on Node.js modules (ws, node:crypto, node:events, node:net) — it can't run in the browser. BrowserOCPPClient replaces these with browser-native APIs, but this comes with trade-offs:
| Feature | OCPPClient | BrowserOCPPClient |
|---|---|---|
| WebSocket | ws (Node.js) | Native browser WebSocket |
| Events | node:events | Custom lightweight EventEmitter |
| ID Generation | @paralleldrive/cuid2 | @paralleldrive/cuid2 |
| Security Profiles | 0–3 (TLS, mTLS, certs) | N/A (handled by browser/TLS) |
| Custom Headers | ✅ via ws | ❌ browser limitation |
| Ping/Pong | ✅ | ❌ browser limitation |
| Type Safety | ✅ Full | ✅ Full (same generated types) |
| Reconnection | ✅ | ✅ |
| Strict Mode | ✅ | ❌ |
Quick Start
import { BrowserOCPPClient } from 'ocpp-ws-io/browser';
const client = new BrowserOCPPClient({
endpoint: 'wss://csms.example.com/ocpp',
identity: 'CP001',
protocols: ['ocpp1.6'],
});
// Register a handler — params are auto-typed for OCPP 1.6 Reset
client.handle('Reset', ({ params }) => {
console.log('Reset type:', params.type);
return { status: 'Accepted' };
});
// Connect and send a BootNotification
await client.connect();
const response = await client.call('ocpp1.6', 'BootNotification', {
chargePointVendor: 'VendorX',
chargePointModel: 'ModelY',
});
console.log('Status:', response.status); // typed: "Accepted" | "Pending" | "Rejected"Configuration
const client = new BrowserOCPPClient(options: BrowserClientOptions);BrowserClientOptions
| Option | Type | Default | Description |
|---|---|---|---|
identity | string | required | Charging station ID |
endpoint | string | required | WebSocket URL (ws:// or wss://) |
protocols | string[] | [] | OCPP subprotocols to negotiate |
query | Record<string, string> | string | — | URL query parameters, as an object or a query string ("a=1&b=2", leading ? optional); added after any query in endpoint |
reconnect | boolean | true | Auto-reconnect on disconnect |
maxReconnects | number | Infinity | Max reconnection attempts |
backoffMin | number | 1000 | First reconnect delay (ms), doubled after each failed attempt; every wait adds up to 25% at random |
backoffMax | number | 30000 | Where the doubling stops (ms); the random 25% still applies on top |
callTimeoutMs | number | 30000 | Default RPC call timeout (ms) |
handlerTimeoutMs | number | 0 (off) | How long a handler may take to answer an incoming call. Past it, the call is answered InternalError, the handler's signal aborts with a TimeoutError, and the error is logged |
callConcurrency | number | 1 | Outbound CALLs in flight at once. 1 follows OCPP-J §4.1.1 (Synchronicity): a new CALL waits until the previous one is answered or has timed out. Above 1 only for a CSMS known to cope |
maxBadMessages | number | 50 | Close on the Nth bad message in a row; a valid message resets the count and empty frames are ignored. 0 = zero tolerance, Infinity = no limit. |
badMessageWindowMs | number | — | Also reset a run of bad messages once it is older than this many ms. |
respondWithDetailedErrors | boolean | false | Send a handler error's properties (not its stack) in CALLERROR details. Logs a warning when on; keep it off in production |
idGenerator | () => string | — | Creates the message ID of each outgoing CALL and SEND. A per-call idempotencyKey wins; without either, a random UUID |
idValidator | (id: string) => boolean | — | Checks each incoming CALL / SEND message ID; without it any string is accepted |
WebSocket compression is not an option here: the browser negotiates it itself, and all major browsers offer it. It is used only when the server accepts it (compression on OCPPServer, off by default).
Middleware
The BrowserOCPPClient supports the exact same MiddlewareStack execution pattern as the Node.js client. This allows you to attach custom interceptors via client.use() directly in the browser:
import {
BrowserOCPPClient,
defineRpcMiddleware,
createLoggingMiddleware,
} from 'ocpp-ws-io/browser';
const client = new BrowserOCPPClient({
identity: 'CS001',
endpoint: 'wss://csms.example.com/ocpp',
protocols: ['ocpp1.6'],
});
// 1. Attach the built-in structured logger (outputs to browser console)
client.use(createLoggingMiddleware(console, client.identity));
// 2. Attach your own middleware: here, how long each message takes.
// Leave the payload as it is: a key its schema does not define fails strict mode.
const browserInterceptor = defineRpcMiddleware(async (ctx, next) => {
const started = performance.now();
await next();
console.log(
`[Browser] ${ctx.method}: ${Math.round(performance.now() - started)} ms`
);
});
client.use(browserInterceptor);Connection Lifecycle
Connection States
BrowserOCPPClient.CONNECTING; // 0
BrowserOCPPClient.OPEN; // 1
BrowserOCPPClient.CLOSING; // 2
BrowserOCPPClient.CLOSED; // 3
// Check current state
if (client.state === BrowserOCPPClient.OPEN) {
await client.call('Heartbeat', {});
}Properties
| Property | Type | Description |
|---|---|---|
client.identity | string | Charging station identity |
client.protocol | string | undefined | Negotiated OCPP subprotocol |
client.state | ConnectionState | Current connection state |
Making Calls
// Version-aware call — fully typed params and response
const result = await client.call('ocpp1.6', 'BootNotification', {
chargePointVendor: 'VendorX',
chargePointModel: 'ModelY',
});
result.status; // typed: "Accepted" | "Pending" | "Rejected"
// OCPP 2.0.1 — different shape, still fully typed
const result201 = await client.call('ocpp2.0.1', 'BootNotification', {
chargingStation: { model: 'ModelX', vendorName: 'VendorY' },
reason: 'PowerUp',
});
// Short form: typed for the client's `protocols`
const res = await client.call('Heartbeat', {});
// A client configured for several versions: narrow to the negotiated one
const v201 = client.forProtocol('ocpp2.0.1'); // BrowserOCPPClient<"ocpp2.0.1"> | undefined
// An action the types do not know: unchecked(), with the response type you expect
import { unchecked } from 'ocpp-ws-io/browser';
const custom = await client.call<{ result: string }>(
unchecked('VendorAction'),
{
data: 'hello',
}
);Call Options
// Timeout
const res = await client.call(
'Reset',
{ type: 'Soft' },
{
timeoutMs: 5000,
}
);
// AbortSignal
const controller = new AbortController();
const res2 = await client.call(
'Heartbeat',
{},
{
signal: controller.signal,
}
);
// Cancel the call
controller.abort();Handling Incoming Calls
Register handlers for incoming server-to-client calls. The API is identical to OCPPClient.handle().
// Version-specific handler — only triggers for ocpp1.6
client.handle('ocpp1.6', 'Reset', ({ params }) => {
params.type; // typed: "Hard" | "Soft" (OCPP 1.6 shape)
return { status: 'Accepted' };
});
// Generic handler — triggers for any negotiated protocol
client.handle('Reset', ({ params, protocol }) => {
console.log(`Reset via ${protocol}:`, params.type);
return { status: 'Accepted' };
});
// Wildcard handler — catches all unhandled methods
client.handle(({ params, method }) => {
console.log(`Unhandled method: ${method}`);
return {};
});Handler Priority
When a call arrives, handlers are checked in this order:
- Version-specific handler (e.g.,
"ocpp1.6:Reset") - Generic handler (e.g.,
"Reset") - Wildcard handler
NOREPLY
Return NOREPLY from a handler to suppress the automatic response:
import { NOREPLY } from 'ocpp-ws-io/browser';
client.handle('StatusNotification', ({ params }) => {
// Process but don't send a response
return NOREPLY;
});An async handler can return it too, after its work.
Removing Handlers
client.removeHandler('Reset'); // remove generic
client.removeHandler('ocpp1.6', 'Reset'); // remove version-specific
client.removeHandler(); // remove wildcard
client.removeAllHandlers(); // remove allEvents
client.on('open', event => {
/* WebSocket connected */
});
client.on('close', ({ code, reason }) => {
/* WebSocket disconnected */
});
client.on('closing', () => {
/* close() started: no reconnect follows; close fires once the socket has closed */
});
client.on('error', error => {
/* Connection or WebSocket error */
});
client.on('connecting', ({ url }) => {
/* Attempting connection */
});
client.on('reconnect', ({ attempt, delay }) => {
/* Scheduled reconnection */
});
client.on('message', ({ message, direction, ctx }) => {
/* OCPP message */
/* message direction */
/* ctx context form client */
});
client.on('call', call => {
/* Incoming OCPP Call */
});
client.on('callResult', result => {
/* Received CallResult */
});
client.on('callError', error => {
/* Received CallError */
});
client.on('badMessage', ({ message, error }) => {
/* Malformed message received */
});Reconnection
BrowserOCPPClient supports automatic reconnection with exponential backoff, identical to OCPPClient. The first wait is backoffMin, it doubles after every failed attempt up to backoffMax, and every wait adds a fresh random amount of up to 25%. A wait is never shorter than backoffMin, and clients that reached backoffMax still spread out after a CSMS restart (OCPP-J 2.0.1 / 2.1 §5.3):
const client = new BrowserOCPPClient({
identity: 'CP001',
endpoint: 'wss://csms.example.com/ocpp',
protocols: ['ocpp1.6'],
reconnect: true, // default: true
maxReconnects: 10, // default: Infinity
backoffMin: 1000, // default: 1000ms
backoffMax: 30000, // default: 30000ms
});
client.on('reconnect', ({ attempt, delay }) => {
console.log(`Reconnect attempt ${attempt} in ${delay}ms`);
});
client.on('close', ({ code, reason }) => {
console.log(`Disconnected: ${code} ${reason}`);
});Reconfigurable Options
Unlike the constructor which creates a new connection, reconfigure() allows you to change the behavior of the existing client instance. Some options take effect immediately, while others only apply on the next connection attempt.
| Option | Effect on Reconfigure |
|---|---|
identity | Applies on next connect() or reconnect |
endpoint | Applies on next connect() or reconnect |
protocols | Applies on next connect() or reconnect |
query | Applies on next connect() or reconnect |
reconnect | Immediate — Cancels pending reconnects if false |
maxReconnects | Immediate — Evaluated on next disconnect |
backoffMin | Immediate — Used for next reconnect calculation |
backoffMax | Immediate — Used for next reconnect calculation |
callTimeoutMs | Immediate — Applies to all future .call()s |
handlerTimeoutMs | Immediate — Applies to handlers started after it |
callConcurrency | Immediate — Dynamically resizes the processing queue |
maxBadMessages | Immediate — Applies to future malformed messages |
badMessageWindowMs | Immediate — Applies to future bad-message windows |
respondWithDetailedErrors | Immediate — Applies to future thrown RPC errors |
Closing the Connection
// Graceful close (waits for pending calls)
await client.close();
// Custom close code and reason
await client.close({ code: 1000, reason: 'User navigated away' });
// Force close (immediately terminates)
await client.close({ force: true });
// Wait for in-flight calls to complete first
await client.close({ awaitPending: true });Framework Integration
React
import { useEffect, useRef, useState } from "react";
import { BrowserOCPPClient } from "ocpp-ws-io/browser";
function useOCPP(identity: string, endpoint: string) {
const clientRef = useRef<BrowserOCPPClient | null>(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
const client = new BrowserOCPPClient({
identity,
endpoint,
protocols: ["ocpp1.6"],
});
client.on("open", () => setConnected(true));
client.on("close", () => setConnected(false));
client.connect();
clientRef.current = client;
return () => {
client.close();
};
}, [identity, endpoint]);
return { client: clientRef.current, connected };
}
// Usage
function ChargingStation() {
const { client, connected } = useOCPP(
"CP001",
"wss://csms.example.com/ocpp",
);
const sendBoot = async () => {
if (!client) return;
const res = await client.call("BootNotification", {
chargePointVendor: "VendorX",
chargePointModel: "ModelY",
});
console.log("Boot:", res.status);
};
return (
<div>
<p>Status: {connected ? "Connected" : "Disconnected"}</p>
<button onClick={sendBoot} disabled={!connected}>
Send Boot
</button>
</div>
);
}Next.js (Client Component)
'use client';
import { BrowserOCPPClient } from 'ocpp-ws-io/browser';
// ✅ Works in client components — no Node.js dependencies
const client = new BrowserOCPPClient({
identity: 'CP001',
endpoint: 'wss://csms.example.com/ocpp',
protocols: ['ocpp1.6'],
});Note:
OCPPClientfrom"ocpp-ws-io"cannot be imported in client components — it requires Node.js modules. UseBrowserOCPPClientfrom"ocpp-ws-io/browser"instead.
Vue
import { ref, onMounted, onUnmounted } from 'vue';
import { BrowserOCPPClient } from 'ocpp-ws-io/browser';
export function useOCPP(identity: string, endpoint: string) {
const client = ref<BrowserOCPPClient | null>(null);
const connected = ref(false);
onMounted(() => {
const c = new BrowserOCPPClient({
identity,
endpoint,
protocols: ['ocpp1.6'],
});
c.on('open', () => (connected.value = true));
c.on('close', () => (connected.value = false));
c.connect();
client.value = c;
});
onUnmounted(() => {
client.value?.close();
});
return { client, connected };
}Browser Exports
All exports available from the ocpp-ws-io/browser subpath:
import {
// Client
BrowserOCPPClient,
// Error classes
RPCGenericError,
RPCNotImplementedError,
RPCNotSupportedError,
RPCInternalError,
RPCProtocolError,
RPCSecurityError,
RPCFormatViolationError,
RPCFormationViolationError,
RPCPropertyConstraintViolationError,
RPCOccurrenceConstraintViolationError,
RPCOccurenceConstraintViolationError, // 1.6 spelling, one `r`
RPCTypeConstraintViolationError,
RPCMessageTypeNotSupportedError,
RPCFrameworkError,
TimeoutError,
// Utilities
createRPCError,
getErrorPlainObject,
// Middleware Helpers
defineRpcMiddleware,
createLoggingMiddleware,
// Constants
ConnectionState,
MessageType,
NOREPLY,
} from 'ocpp-ws-io/browser';All types from the main package (OCPPProtocol, AllMethodNames, OCPPRequestType, OCPPResponseType, etc.) are re-exported for full type safety.