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.
- package/.github/workflows/publish.yml +52 -0
- package/LICENSE +21 -0
- package/README.md +773 -0
- package/dist/adapters/redis.d.mts +73 -0
- package/dist/adapters/redis.d.ts +73 -0
- package/dist/adapters/redis.js +96 -0
- package/dist/adapters/redis.js.map +1 -0
- package/dist/adapters/redis.mjs +71 -0
- package/dist/adapters/redis.mjs.map +1 -0
- package/dist/index.d.mts +268 -0
- package/dist/index.d.ts +268 -0
- package/dist/index.js +38919 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +38855 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types-6LVUoXof.d.mts +284 -0
- package/dist/types-6LVUoXof.d.ts +284 -0
- package/package.json +59 -0
- package/src/adapters/adapter.ts +40 -0
- package/src/adapters/redis.ts +144 -0
- package/src/client.ts +882 -0
- package/src/errors.ts +183 -0
- package/src/event-buffer.ts +73 -0
- package/src/index.ts +68 -0
- package/src/queue.ts +65 -0
- package/src/schemas/ocpp1_6.json +2376 -0
- package/src/schemas/ocpp2_0_1.json +11878 -0
- package/src/schemas/ocpp2_1.json +23176 -0
- package/src/server-client.ts +65 -0
- package/src/server.ts +374 -0
- package/src/standard-validators.ts +18 -0
- package/src/types.ts +316 -0
- package/src/util.ts +119 -0
- package/src/validator.ts +148 -0
- package/src/ws-util.ts +186 -0
- package/test/adapter.test.ts +88 -0
- package/test/client.test.ts +297 -0
- package/test/errors.test.ts +132 -0
- package/test/queue.test.ts +133 -0
- package/test/server.test.ts +274 -0
- package/test/util.test.ts +103 -0
- package/test/ws-util.test.ts +93 -0
- package/tsconfig.json +25 -0
- package/tsup.config.ts +16 -0
- package/vitest.config.ts +10 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// ─── Base Errors ─────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
export class TimeoutError extends Error {
|
|
4
|
+
constructor(message = "Operation timed out") {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "TimeoutError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class UnexpectedHttpResponse extends Error {
|
|
11
|
+
public readonly statusCode: number;
|
|
12
|
+
public readonly headers: Record<string, string | string[] | undefined>;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
message: string,
|
|
16
|
+
statusCode: number,
|
|
17
|
+
headers: Record<string, string | string[] | undefined> = {},
|
|
18
|
+
) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "UnexpectedHttpResponse";
|
|
21
|
+
this.statusCode = statusCode;
|
|
22
|
+
this.headers = headers;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class WebsocketUpgradeError extends Error {
|
|
27
|
+
constructor(message = "WebSocket upgrade failed") {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "WebsocketUpgradeError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── RPC Error Base ──────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
export interface RPCError extends Error {
|
|
36
|
+
readonly rpcErrorCode: string;
|
|
37
|
+
readonly rpcErrorMessage: string;
|
|
38
|
+
readonly details: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class RPCGenericError extends Error implements RPCError {
|
|
42
|
+
readonly rpcErrorCode: string = "GenericError";
|
|
43
|
+
readonly rpcErrorMessage: string = "";
|
|
44
|
+
readonly details: Record<string, unknown>;
|
|
45
|
+
|
|
46
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "RPCGenericError";
|
|
49
|
+
this.details = details;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Specific RPC Errors ─────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
export class RPCNotImplementedError extends RPCGenericError {
|
|
56
|
+
override readonly rpcErrorCode = "NotImplemented";
|
|
57
|
+
override readonly rpcErrorMessage = "Requested method is not known";
|
|
58
|
+
|
|
59
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
60
|
+
super(message, details);
|
|
61
|
+
this.name = "RPCNotImplementedError";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class RPCNotSupportedError extends RPCGenericError {
|
|
66
|
+
override readonly rpcErrorCode = "NotSupported";
|
|
67
|
+
override readonly rpcErrorMessage =
|
|
68
|
+
"Requested method is recognised but not supported";
|
|
69
|
+
|
|
70
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
71
|
+
super(message, details);
|
|
72
|
+
this.name = "RPCNotSupportedError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class RPCInternalError extends RPCGenericError {
|
|
77
|
+
override readonly rpcErrorCode = "InternalError";
|
|
78
|
+
override readonly rpcErrorMessage =
|
|
79
|
+
"An internal error occurred and the receiver was not able to process the requested action successfully";
|
|
80
|
+
|
|
81
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
82
|
+
super(message, details);
|
|
83
|
+
this.name = "RPCInternalError";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class RPCProtocolError extends RPCGenericError {
|
|
88
|
+
override readonly rpcErrorCode = "ProtocolError";
|
|
89
|
+
override readonly rpcErrorMessage = "Payload for action is incomplete";
|
|
90
|
+
|
|
91
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
92
|
+
super(message, details);
|
|
93
|
+
this.name = "RPCProtocolError";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class RPCSecurityError extends RPCGenericError {
|
|
98
|
+
override readonly rpcErrorCode = "SecurityError";
|
|
99
|
+
override readonly rpcErrorMessage =
|
|
100
|
+
"During the processing of action a security issue occurred preventing receiver from completing the action successfully";
|
|
101
|
+
|
|
102
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
103
|
+
super(message, details);
|
|
104
|
+
this.name = "RPCSecurityError";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export class RPCFormationViolationError extends RPCGenericError {
|
|
109
|
+
override readonly rpcErrorCode = "FormationViolation";
|
|
110
|
+
override readonly rpcErrorMessage =
|
|
111
|
+
"Payload for action is syntactically incorrect or not conform the PDU structure for action";
|
|
112
|
+
|
|
113
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
114
|
+
super(message, details);
|
|
115
|
+
this.name = "RPCFormationViolationError";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class RPCFormatViolationError extends RPCGenericError {
|
|
120
|
+
override readonly rpcErrorCode = "FormatViolation";
|
|
121
|
+
override readonly rpcErrorMessage =
|
|
122
|
+
"Payload is syntactically correct but at least one field contains an invalid value";
|
|
123
|
+
|
|
124
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
125
|
+
super(message, details);
|
|
126
|
+
this.name = "RPCFormatViolationError";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export class RPCPropertyConstraintViolationError extends RPCGenericError {
|
|
131
|
+
override readonly rpcErrorCode = "PropertyConstraintViolation";
|
|
132
|
+
override readonly rpcErrorMessage =
|
|
133
|
+
"Payload is syntactically correct but at least one of the fields violates data type constraints";
|
|
134
|
+
|
|
135
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
136
|
+
super(message, details);
|
|
137
|
+
this.name = "RPCPropertyConstraintViolationError";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export class RPCOccurrenceConstraintViolationError extends RPCGenericError {
|
|
142
|
+
override readonly rpcErrorCode = "OccurrenceConstraintViolation";
|
|
143
|
+
override readonly rpcErrorMessage =
|
|
144
|
+
"Payload for action is syntactically correct but at least one of the fields violates occurrence constraints";
|
|
145
|
+
|
|
146
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
147
|
+
super(message, details);
|
|
148
|
+
this.name = "RPCOccurrenceConstraintViolationError";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export class RPCTypeConstraintViolationError extends RPCGenericError {
|
|
153
|
+
override readonly rpcErrorCode = "TypeConstraintViolation";
|
|
154
|
+
override readonly rpcErrorMessage =
|
|
155
|
+
"Payload for action is syntactically correct but at least one of the fields violates type constraints";
|
|
156
|
+
|
|
157
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
158
|
+
super(message, details);
|
|
159
|
+
this.name = "RPCTypeConstraintViolationError";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export class RPCMessageTypeNotSupportedError extends RPCGenericError {
|
|
164
|
+
override readonly rpcErrorCode = "MessageTypeNotSupported";
|
|
165
|
+
override readonly rpcErrorMessage =
|
|
166
|
+
"A message with a Message Type Number received that is not supported by this implementation";
|
|
167
|
+
|
|
168
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
169
|
+
super(message, details);
|
|
170
|
+
this.name = "RPCMessageTypeNotSupportedError";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class RPCFrameworkError extends RPCGenericError {
|
|
175
|
+
override readonly rpcErrorCode = "RpcFrameworkError";
|
|
176
|
+
override readonly rpcErrorMessage =
|
|
177
|
+
"Content of the call is not a valid RPC request";
|
|
178
|
+
|
|
179
|
+
constructor(message?: string, details: Record<string, unknown> = {}) {
|
|
180
|
+
super(message, details);
|
|
181
|
+
this.name = "RPCFrameworkError";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An event buffer that captures events emitted during a critical transition period
|
|
5
|
+
* (e.g., during WebSocket connection setup) and replays them in order once the
|
|
6
|
+
* consumer is ready.
|
|
7
|
+
*/
|
|
8
|
+
export class EventBuffer {
|
|
9
|
+
private _target: EventEmitter;
|
|
10
|
+
private _events: string[];
|
|
11
|
+
private _buffer: Array<{ event: string; args: unknown[] }> = [];
|
|
12
|
+
private _listeners: Map<string, (...args: unknown[]) => void> = new Map();
|
|
13
|
+
private _active = false;
|
|
14
|
+
|
|
15
|
+
constructor(target: EventEmitter, events: string[]) {
|
|
16
|
+
this._target = target;
|
|
17
|
+
this._events = events;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Start buffering events. Any matching events emitted on the target
|
|
22
|
+
* will be captured instead of being processed immediately.
|
|
23
|
+
*/
|
|
24
|
+
start(): void {
|
|
25
|
+
if (this._active) return;
|
|
26
|
+
this._active = true;
|
|
27
|
+
|
|
28
|
+
for (const event of this._events) {
|
|
29
|
+
const listener = (...args: unknown[]) => {
|
|
30
|
+
this._buffer.push({ event, args });
|
|
31
|
+
};
|
|
32
|
+
this._listeners.set(event, listener);
|
|
33
|
+
this._target.on(event, listener);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Stop buffering and replay all captured events on the target
|
|
39
|
+
* in the order they were received.
|
|
40
|
+
*/
|
|
41
|
+
condense(): void {
|
|
42
|
+
if (!this._active) return;
|
|
43
|
+
this._active = false;
|
|
44
|
+
|
|
45
|
+
// Remove buffer listeners
|
|
46
|
+
for (const [event, listener] of this._listeners) {
|
|
47
|
+
this._target.removeListener(event, listener);
|
|
48
|
+
}
|
|
49
|
+
this._listeners.clear();
|
|
50
|
+
|
|
51
|
+
// Replay buffered events
|
|
52
|
+
const events = this._buffer.slice();
|
|
53
|
+
this._buffer = [];
|
|
54
|
+
|
|
55
|
+
for (const { event, args } of events) {
|
|
56
|
+
this._target.emit(event, ...args);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Stop buffering and discard all captured events.
|
|
62
|
+
*/
|
|
63
|
+
discard(): void {
|
|
64
|
+
if (!this._active) return;
|
|
65
|
+
this._active = false;
|
|
66
|
+
|
|
67
|
+
for (const [event, listener] of this._listeners) {
|
|
68
|
+
this._target.removeListener(event, listener);
|
|
69
|
+
}
|
|
70
|
+
this._listeners.clear();
|
|
71
|
+
this._buffer = [];
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// ─── Core ────────────────────────────────────────────────────────
|
|
2
|
+
export { OCPPClient } from "./client.js";
|
|
3
|
+
export { OCPPServer } from "./server.js";
|
|
4
|
+
export { OCPPServerClient } from "./server-client.js";
|
|
5
|
+
|
|
6
|
+
// ─── Validation ──────────────────────────────────────────────────
|
|
7
|
+
export { Validator, createValidator } from "./validator.js";
|
|
8
|
+
export { standardValidators } from "./standard-validators.js";
|
|
9
|
+
|
|
10
|
+
// ─── Utilities ───────────────────────────────────────────────────
|
|
11
|
+
export {
|
|
12
|
+
createRPCError,
|
|
13
|
+
getErrorPlainObject,
|
|
14
|
+
getPackageIdent,
|
|
15
|
+
} from "./util.js";
|
|
16
|
+
|
|
17
|
+
// ─── Adapters ────────────────────────────────────────────────────
|
|
18
|
+
export { InMemoryAdapter } from "./adapters/adapter.js";
|
|
19
|
+
|
|
20
|
+
// ─── Errors ──────────────────────────────────────────────────────
|
|
21
|
+
export {
|
|
22
|
+
TimeoutError,
|
|
23
|
+
UnexpectedHttpResponse,
|
|
24
|
+
WebsocketUpgradeError,
|
|
25
|
+
type RPCError,
|
|
26
|
+
RPCGenericError,
|
|
27
|
+
RPCNotImplementedError,
|
|
28
|
+
RPCNotSupportedError,
|
|
29
|
+
RPCInternalError,
|
|
30
|
+
RPCProtocolError,
|
|
31
|
+
RPCSecurityError,
|
|
32
|
+
RPCFormationViolationError,
|
|
33
|
+
RPCFormatViolationError,
|
|
34
|
+
RPCPropertyConstraintViolationError,
|
|
35
|
+
RPCOccurrenceConstraintViolationError,
|
|
36
|
+
RPCTypeConstraintViolationError,
|
|
37
|
+
RPCMessageTypeNotSupportedError,
|
|
38
|
+
RPCFrameworkError,
|
|
39
|
+
} from "./errors.js";
|
|
40
|
+
|
|
41
|
+
// ─── Types ───────────────────────────────────────────────────────
|
|
42
|
+
export {
|
|
43
|
+
ConnectionState,
|
|
44
|
+
SecurityProfile,
|
|
45
|
+
MessageType,
|
|
46
|
+
NOREPLY,
|
|
47
|
+
type OCPPProtocol,
|
|
48
|
+
type OCPPCall,
|
|
49
|
+
type OCPPCallResult,
|
|
50
|
+
type OCPPCallError,
|
|
51
|
+
type OCPPMessage,
|
|
52
|
+
type TLSOptions,
|
|
53
|
+
type HandlerContext,
|
|
54
|
+
type CallHandler,
|
|
55
|
+
type WildcardHandler,
|
|
56
|
+
type CallOptions,
|
|
57
|
+
type CloseOptions,
|
|
58
|
+
type HandshakeInfo,
|
|
59
|
+
type SessionData,
|
|
60
|
+
type ClientOptions,
|
|
61
|
+
type ServerOptions,
|
|
62
|
+
type ListenOptions,
|
|
63
|
+
type AuthAccept,
|
|
64
|
+
type AuthCallback,
|
|
65
|
+
type ClientEvents,
|
|
66
|
+
type ServerEvents,
|
|
67
|
+
type EventAdapterInterface,
|
|
68
|
+
} from "./types.js";
|
package/src/queue.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A concurrency-limited async queue.
|
|
3
|
+
* Enqueues async functions and executes them with a configurable concurrency limit.
|
|
4
|
+
*/
|
|
5
|
+
export class Queue {
|
|
6
|
+
private _concurrency: number;
|
|
7
|
+
private _running = 0;
|
|
8
|
+
private _queue: Array<{
|
|
9
|
+
fn: () => Promise<unknown>;
|
|
10
|
+
resolve: (value: unknown) => void;
|
|
11
|
+
reject: (reason: unknown) => void;
|
|
12
|
+
}> = [];
|
|
13
|
+
|
|
14
|
+
constructor(concurrency = 1) {
|
|
15
|
+
this._concurrency = Math.max(1, concurrency);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get concurrency(): number {
|
|
19
|
+
return this._concurrency;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get pending(): number {
|
|
23
|
+
return this._queue.length;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get running(): number {
|
|
27
|
+
return this._running;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get size(): number {
|
|
31
|
+
return this._running + this._queue.length;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setConcurrency(concurrency: number): void {
|
|
35
|
+
this._concurrency = Math.max(1, concurrency);
|
|
36
|
+
this._drain();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
push<T>(fn: () => Promise<T>): Promise<T> {
|
|
40
|
+
return new Promise<T>((resolve, reject) => {
|
|
41
|
+
this._queue.push({
|
|
42
|
+
fn: fn as () => Promise<unknown>,
|
|
43
|
+
resolve: resolve as (value: unknown) => void,
|
|
44
|
+
reject,
|
|
45
|
+
});
|
|
46
|
+
this._drain();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private _drain(): void {
|
|
51
|
+
while (this._running < this._concurrency && this._queue.length > 0) {
|
|
52
|
+
const item = this._queue.shift()!;
|
|
53
|
+
this._running++;
|
|
54
|
+
|
|
55
|
+
item
|
|
56
|
+
.fn()
|
|
57
|
+
.then(item.resolve)
|
|
58
|
+
.catch(item.reject)
|
|
59
|
+
.finally(() => {
|
|
60
|
+
this._running--;
|
|
61
|
+
this._drain();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|