OCPP WS IOocpp-ws-io
Core WebSocket RPC

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 OCPPClient in 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:

FeatureOCPPClientBrowserOCPPClient
WebSocketws (Node.js)Native browser WebSocket
Eventsnode:eventsCustom lightweight EventEmitter
ID Generation@paralleldrive/cuid2@paralleldrive/cuid2
Security Profiles0–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

OptionTypeDefaultDescription
identitystringrequiredCharging station ID
endpointstringrequiredWebSocket URL (ws:// or wss://)
protocolsstring[][]OCPP subprotocols to negotiate
queryRecord<string, string>Additional URL query parameters
reconnectbooleantrueAuto-reconnect on disconnect
maxReconnectsnumberInfinityMax reconnection attempts
backoffMinnumber1000Initial reconnect delay (ms)
backoffMaxnumber30000Maximum reconnect delay (ms)
callTimeoutMsnumber30000Default RPC call timeout (ms)
callConcurrencynumber1Max concurrent outbound calls
maxBadMessagesnumberInfinityClose after N consecutive bad messages
respondWithDetailedErrorsbooleanfalseInclude error details in RPC error responses
pingIntervalMsnumber30000Includes ±25% randomized jitter
strictModeboolean | string[]falseEnable/restrict schema validation
strictModeMethodsstring[]Restrict validation to specific methods

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({ ... });

// 1. Attach the built-in structured logger (outputs to browser console)
client.use(createLoggingMiddleware(console, client.identity));

// 2. Attach your custom type-safe middleware
const browserInterceptor = defineRpcMiddleware(async (ctx, next) => {
  console.log(`[Browser] Intercepting ${ctx.method}`);

  if (ctx.type === "outgoing_call") {
    ctx.params.timestamp = new Date().toISOString(); // Auto-inject timestamps
  }

  await next();
});

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

PropertyTypeDescription
client.identitystringCharging station identity
client.protocolstring | undefinedNegotiated OCPP subprotocol
client.stateConnectionStateCurrent 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",
});

// Default protocol call (uses the client's type parameter P)
const res = await client.call("Heartbeat", {});

// Explicit response type (for custom/vendor methods)
const custom = await client.call<{ result: string }>("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((method, { params }) => {
  console.log(`Unhandled method: ${method}`);
  return {};
});

Handler Priority

When a call arrives, handlers are checked in this order:

  1. Version-specific handler (e.g., "ocpp1.6:Reset")
  2. Generic handler (e.g., "Reset")
  3. 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;
});

Removing Handlers

client.removeHandler("Reset"); // remove generic
client.removeHandler("ocpp1.6", "Reset"); // remove version-specific
client.removeHandler(); // remove wildcard
client.removeAllHandlers(); // remove all

Events

client.on("open", (event) => {
  /* WebSocket connected */
});

client.on("close", ({ code, reason }) => {
  /* WebSocket disconnected */
});

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) => {
  /* Any parsed OCPP message */
});

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:

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.

OptionEffect on Reconfigure
identityApplies on next connect() or reconnect
endpointApplies on next connect() or reconnect
protocolsApplies on next connect() or reconnect
queryApplies on next connect() or reconnect
reconnectImmediate — Cancels pending reconnects if false
maxReconnectsImmediate — Evaluated on next disconnect
backoffMinImmediate — Used for next reconnect calculation
backoffMaxImmediate — Used for next reconnect calculation
callTimeoutMsImmediate — Applies to all future .call()s
callConcurrencyImmediate — Dynamically resizes the processing queue
maxBadMessagesImmediate — Applies to future malformed messages
respondWithDetailedErrorsImmediate — 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: OCPPClient from "ocpp-ws-io" cannot be imported in client components — it requires Node.js modules. Use BrowserOCPPClient from "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,
  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.

On this page