@cleverence/edge-js-sdk 1.0.0
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 +296 -0
- package/dist/index.cjs +482 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +313 -0
- package/dist/index.d.ts +313 -0
- package/dist/index.js +480 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +654 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +456 -0
- package/dist/react/index.d.ts +456 -0
- package/dist/react/index.js +649 -0
- package/dist/react/index.js.map +1 -0
- package/dist/vue/index.cjs +702 -0
- package/dist/vue/index.cjs.map +1 -0
- package/dist/vue/index.d.cts +447 -0
- package/dist/vue/index.d.ts +447 -0
- package/dist/vue/index.js +698 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +94 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
// src/core/events.ts
|
|
2
|
+
var EventEmitter = class {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Subscribe to an event
|
|
8
|
+
*/
|
|
9
|
+
on(event, handler) {
|
|
10
|
+
if (!this.handlers.has(event)) {
|
|
11
|
+
this.handlers.set(event, /* @__PURE__ */ new Set());
|
|
12
|
+
}
|
|
13
|
+
this.handlers.get(event).add(handler);
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Subscribe to an event once (auto-unsubscribes after first call)
|
|
18
|
+
*/
|
|
19
|
+
once(event, handler) {
|
|
20
|
+
const onceHandler = (data) => {
|
|
21
|
+
this.off(event, onceHandler);
|
|
22
|
+
handler(data);
|
|
23
|
+
};
|
|
24
|
+
return this.on(event, onceHandler);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Unsubscribe from an event
|
|
28
|
+
*/
|
|
29
|
+
off(event, handler) {
|
|
30
|
+
const eventHandlers = this.handlers.get(event);
|
|
31
|
+
if (eventHandlers) {
|
|
32
|
+
eventHandlers.delete(handler);
|
|
33
|
+
}
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Emit an event to all subscribers
|
|
38
|
+
*/
|
|
39
|
+
emit(event, data) {
|
|
40
|
+
const eventHandlers = this.handlers.get(event);
|
|
41
|
+
if (eventHandlers) {
|
|
42
|
+
eventHandlers.forEach((handler) => {
|
|
43
|
+
try {
|
|
44
|
+
handler(data);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error(`Error in event handler for "${String(event)}":`, err);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Remove all listeners for an event, or all listeners if no event specified
|
|
53
|
+
*/
|
|
54
|
+
removeAllListeners(event) {
|
|
55
|
+
if (event) {
|
|
56
|
+
this.handlers.delete(event);
|
|
57
|
+
} else {
|
|
58
|
+
this.handlers.clear();
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get listener count for an event
|
|
64
|
+
*/
|
|
65
|
+
listenerCount(event) {
|
|
66
|
+
return this.handlers.get(event)?.size ?? 0;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/core/websocket.ts
|
|
71
|
+
var DEFAULT_WS_OPTIONS = {
|
|
72
|
+
reconnect: true,
|
|
73
|
+
reconnectDelay: 1e3,
|
|
74
|
+
maxReconnectDelay: 3e4,
|
|
75
|
+
pingInterval: 3e4
|
|
76
|
+
};
|
|
77
|
+
var WebSocketManager = class extends EventEmitter {
|
|
78
|
+
constructor(url, options = {}) {
|
|
79
|
+
super();
|
|
80
|
+
this.ws = null;
|
|
81
|
+
this._state = "disconnected";
|
|
82
|
+
this.reconnectAttempts = 0;
|
|
83
|
+
this.reconnectTimer = null;
|
|
84
|
+
this.pingTimer = null;
|
|
85
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
86
|
+
this.intentionalClose = false;
|
|
87
|
+
this.url = url;
|
|
88
|
+
this.options = { ...DEFAULT_WS_OPTIONS, ...options };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Current connection state
|
|
92
|
+
*/
|
|
93
|
+
get state() {
|
|
94
|
+
return this._state;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Whether currently connected
|
|
98
|
+
*/
|
|
99
|
+
get isConnected() {
|
|
100
|
+
return this._state === "connected";
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Connect to the WebSocket server
|
|
104
|
+
*/
|
|
105
|
+
connect() {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
if (this._state === "connected" || this._state === "connecting") {
|
|
108
|
+
resolve();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.intentionalClose = false;
|
|
112
|
+
this.setState("connecting");
|
|
113
|
+
try {
|
|
114
|
+
this.ws = new WebSocket(this.url);
|
|
115
|
+
const onOpen = () => {
|
|
116
|
+
this.ws?.removeEventListener("error", onError);
|
|
117
|
+
this.reconnectAttempts = 0;
|
|
118
|
+
this.setState("connected");
|
|
119
|
+
this.startPing();
|
|
120
|
+
this.emit("open", void 0);
|
|
121
|
+
resolve();
|
|
122
|
+
};
|
|
123
|
+
const onError = (event) => {
|
|
124
|
+
this.ws?.removeEventListener("open", onOpen);
|
|
125
|
+
const error = new Error("WebSocket connection failed");
|
|
126
|
+
reject(error);
|
|
127
|
+
};
|
|
128
|
+
this.ws.addEventListener("open", onOpen, { once: true });
|
|
129
|
+
this.ws.addEventListener("error", onError, { once: true });
|
|
130
|
+
this.ws.addEventListener("message", this.handleMessage.bind(this));
|
|
131
|
+
this.ws.addEventListener("close", this.handleClose.bind(this));
|
|
132
|
+
this.ws.addEventListener("error", this.handleError.bind(this));
|
|
133
|
+
} catch (err) {
|
|
134
|
+
this.setState("disconnected");
|
|
135
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Disconnect from the WebSocket server
|
|
141
|
+
*/
|
|
142
|
+
disconnect() {
|
|
143
|
+
this.intentionalClose = true;
|
|
144
|
+
this.cleanup();
|
|
145
|
+
this.setState("disconnected");
|
|
146
|
+
this.emit("close", void 0);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Send a message to the server
|
|
150
|
+
*/
|
|
151
|
+
send(message) {
|
|
152
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
153
|
+
throw new Error("WebSocket is not connected");
|
|
154
|
+
}
|
|
155
|
+
this.ws.send(JSON.stringify(message));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Send a query and wait for response (request/response pattern)
|
|
159
|
+
*/
|
|
160
|
+
request(query, timeoutMs = 1e4) {
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const id = this.generateId();
|
|
163
|
+
const timeout = setTimeout(() => {
|
|
164
|
+
this.pendingRequests.delete(id);
|
|
165
|
+
reject(new Error(`Request timeout: ${query}`));
|
|
166
|
+
}, timeoutMs);
|
|
167
|
+
this.pendingRequests.set(id, {
|
|
168
|
+
resolve,
|
|
169
|
+
reject,
|
|
170
|
+
timeout
|
|
171
|
+
});
|
|
172
|
+
try {
|
|
173
|
+
this.send({ type: "query", id, query });
|
|
174
|
+
} catch (err) {
|
|
175
|
+
clearTimeout(timeout);
|
|
176
|
+
this.pendingRequests.delete(id);
|
|
177
|
+
reject(err);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Send a command (fire and forget, but throws if not connected)
|
|
183
|
+
*/
|
|
184
|
+
command(message) {
|
|
185
|
+
this.send(message);
|
|
186
|
+
}
|
|
187
|
+
handleMessage(event) {
|
|
188
|
+
try {
|
|
189
|
+
const message = JSON.parse(event.data);
|
|
190
|
+
if (message.type === "response") {
|
|
191
|
+
const pending = this.pendingRequests.get(message.id);
|
|
192
|
+
if (pending) {
|
|
193
|
+
clearTimeout(pending.timeout);
|
|
194
|
+
this.pendingRequests.delete(message.id);
|
|
195
|
+
if (message.success) {
|
|
196
|
+
pending.resolve(message.data.result);
|
|
197
|
+
} else {
|
|
198
|
+
pending.reject(new Error(message.error));
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (message.type === "pong") {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.emit("message", message);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.error("Failed to parse WebSocket message:", err);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
handleClose() {
|
|
212
|
+
this.cleanup();
|
|
213
|
+
if (this.intentionalClose) {
|
|
214
|
+
this.setState("disconnected");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.emit("close", void 0);
|
|
218
|
+
if (this.options.reconnect) {
|
|
219
|
+
this.scheduleReconnect();
|
|
220
|
+
} else {
|
|
221
|
+
this.setState("disconnected");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
handleError(event) {
|
|
225
|
+
const error = new Error("WebSocket error");
|
|
226
|
+
this.emit("error", error);
|
|
227
|
+
}
|
|
228
|
+
scheduleReconnect() {
|
|
229
|
+
if (this.reconnectTimer) return;
|
|
230
|
+
this.setState("reconnecting");
|
|
231
|
+
const delay = Math.min(
|
|
232
|
+
this.options.reconnectDelay * Math.pow(2, this.reconnectAttempts),
|
|
233
|
+
this.options.maxReconnectDelay
|
|
234
|
+
);
|
|
235
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
236
|
+
this.reconnectTimer = null;
|
|
237
|
+
this.reconnectAttempts++;
|
|
238
|
+
try {
|
|
239
|
+
await this.connect();
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}, delay);
|
|
243
|
+
}
|
|
244
|
+
startPing() {
|
|
245
|
+
this.stopPing();
|
|
246
|
+
this.pingTimer = setInterval(() => {
|
|
247
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
248
|
+
try {
|
|
249
|
+
this.send({ type: "ping" });
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}, this.options.pingInterval);
|
|
254
|
+
}
|
|
255
|
+
stopPing() {
|
|
256
|
+
if (this.pingTimer) {
|
|
257
|
+
clearInterval(this.pingTimer);
|
|
258
|
+
this.pingTimer = null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
cleanup() {
|
|
262
|
+
this.stopPing();
|
|
263
|
+
if (this.reconnectTimer) {
|
|
264
|
+
clearTimeout(this.reconnectTimer);
|
|
265
|
+
this.reconnectTimer = null;
|
|
266
|
+
}
|
|
267
|
+
this.pendingRequests.forEach((pending) => {
|
|
268
|
+
clearTimeout(pending.timeout);
|
|
269
|
+
pending.reject(new Error("WebSocket disconnected"));
|
|
270
|
+
});
|
|
271
|
+
this.pendingRequests.clear();
|
|
272
|
+
if (this.ws) {
|
|
273
|
+
const ws = this.ws;
|
|
274
|
+
this.ws = null;
|
|
275
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
276
|
+
ws.close();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
setState(state) {
|
|
281
|
+
if (this._state !== state) {
|
|
282
|
+
this._state = state;
|
|
283
|
+
this.emit("statechange", state);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
generateId() {
|
|
287
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/types/messages.ts
|
|
292
|
+
var DEFAULT_OPTIONS = {
|
|
293
|
+
url: "ws://localhost:8585",
|
|
294
|
+
autoConnect: true,
|
|
295
|
+
reconnectDelay: 1e3,
|
|
296
|
+
maxReconnectDelay: 3e4,
|
|
297
|
+
pingInterval: 3e4
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// src/core/client.ts
|
|
301
|
+
var CleverenceEdge = class _CleverenceEdge extends EventEmitter {
|
|
302
|
+
constructor(options = {}) {
|
|
303
|
+
super();
|
|
304
|
+
this._capabilities = null;
|
|
305
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
306
|
+
this.ws = new WebSocketManager(this.options.url, {
|
|
307
|
+
reconnect: true,
|
|
308
|
+
reconnectDelay: this.options.reconnectDelay,
|
|
309
|
+
maxReconnectDelay: this.options.maxReconnectDelay,
|
|
310
|
+
pingInterval: this.options.pingInterval
|
|
311
|
+
});
|
|
312
|
+
this.setupWebSocketHandlers();
|
|
313
|
+
if (this.options.autoConnect) {
|
|
314
|
+
setTimeout(() => this.connect().catch(() => {
|
|
315
|
+
}), 0);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Current connection state
|
|
320
|
+
*/
|
|
321
|
+
get connectionState() {
|
|
322
|
+
return this.ws.state;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Whether currently connected to the Edge service
|
|
326
|
+
*/
|
|
327
|
+
get isConnected() {
|
|
328
|
+
return this.ws.isConnected;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Cached device capabilities (available after connect)
|
|
332
|
+
*/
|
|
333
|
+
get capabilities() {
|
|
334
|
+
return this._capabilities;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Connect to the Edge service
|
|
338
|
+
*/
|
|
339
|
+
async connect() {
|
|
340
|
+
await this.ws.connect();
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Disconnect from the Edge service
|
|
344
|
+
*/
|
|
345
|
+
disconnect() {
|
|
346
|
+
this.ws.disconnect();
|
|
347
|
+
}
|
|
348
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
349
|
+
// Commands
|
|
350
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
351
|
+
/**
|
|
352
|
+
* Trigger a barcode scan programmatically
|
|
353
|
+
*/
|
|
354
|
+
async triggerScan() {
|
|
355
|
+
this.ensureConnected();
|
|
356
|
+
this.ws.command({ type: "command", command: "trigger_scan" });
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Set enabled barcode symbologies
|
|
360
|
+
*/
|
|
361
|
+
async setSymbologies(symbologies) {
|
|
362
|
+
this.ensureConnected();
|
|
363
|
+
this.ws.command({ type: "command", command: "set_symbologies", symbologies });
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Start RFID inventory (continuous reading)
|
|
367
|
+
*/
|
|
368
|
+
async startRfidInventory(options) {
|
|
369
|
+
this.ensureConnected();
|
|
370
|
+
this.ws.command({ type: "command", command: "start_rfid_inventory", options });
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Stop RFID inventory
|
|
374
|
+
*/
|
|
375
|
+
async stopRfidInventory() {
|
|
376
|
+
this.ensureConnected();
|
|
377
|
+
this.ws.command({ type: "command", command: "stop_rfid_inventory" });
|
|
378
|
+
}
|
|
379
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
380
|
+
// Queries
|
|
381
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
382
|
+
/**
|
|
383
|
+
* Get current Edge service status
|
|
384
|
+
*/
|
|
385
|
+
async getStatus() {
|
|
386
|
+
this.ensureConnected();
|
|
387
|
+
return this.ws.request("status");
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Get device capabilities
|
|
391
|
+
*/
|
|
392
|
+
async getCapabilities() {
|
|
393
|
+
this.ensureConnected();
|
|
394
|
+
return this.ws.request("capabilities");
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get current configuration
|
|
398
|
+
*/
|
|
399
|
+
async getConfig() {
|
|
400
|
+
this.ensureConnected();
|
|
401
|
+
return this.ws.request("config");
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get RFID tags from current/last inventory
|
|
405
|
+
*/
|
|
406
|
+
async getRfidTags() {
|
|
407
|
+
this.ensureConnected();
|
|
408
|
+
return this.ws.request("rfid_tags");
|
|
409
|
+
}
|
|
410
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
411
|
+
// Static factory
|
|
412
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
413
|
+
/**
|
|
414
|
+
* Create a new CleverenceEdge instance
|
|
415
|
+
*/
|
|
416
|
+
static create(options) {
|
|
417
|
+
return new _CleverenceEdge(options);
|
|
418
|
+
}
|
|
419
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
420
|
+
// Private methods
|
|
421
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
422
|
+
setupWebSocketHandlers() {
|
|
423
|
+
this.ws.on("open", () => {
|
|
424
|
+
this.emit("connect", void 0);
|
|
425
|
+
this.getCapabilities().then((caps) => {
|
|
426
|
+
this._capabilities = caps;
|
|
427
|
+
this.emit("capabilities", caps);
|
|
428
|
+
}).catch(() => {
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
this.ws.on("close", () => {
|
|
432
|
+
this.emit("disconnect", void 0);
|
|
433
|
+
});
|
|
434
|
+
this.ws.on("error", (error) => {
|
|
435
|
+
this.emit("error", error);
|
|
436
|
+
});
|
|
437
|
+
this.ws.on("statechange", (state) => {
|
|
438
|
+
if (state === "reconnecting") {
|
|
439
|
+
this.emit("reconnecting", void 0);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
this.ws.on("message", (message) => {
|
|
443
|
+
this.handleServerMessage(message);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
handleServerMessage(message) {
|
|
447
|
+
switch (message.type) {
|
|
448
|
+
case "event":
|
|
449
|
+
this.handleEvent(message.event);
|
|
450
|
+
break;
|
|
451
|
+
case "capabilities":
|
|
452
|
+
this._capabilities = message.data;
|
|
453
|
+
this.emit("capabilities", message.data);
|
|
454
|
+
break;
|
|
455
|
+
case "error":
|
|
456
|
+
this.emit("error", new Error(message.message));
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
handleEvent(event) {
|
|
461
|
+
const parsedEvent = {
|
|
462
|
+
...event,
|
|
463
|
+
timestamp: typeof event.timestamp === "string" ? new Date(event.timestamp) : event.timestamp
|
|
464
|
+
};
|
|
465
|
+
if (event.type === "scan") {
|
|
466
|
+
this.emit("scan", parsedEvent);
|
|
467
|
+
} else if (event.type === "rfid") {
|
|
468
|
+
this.emit("rfid", parsedEvent);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
ensureConnected() {
|
|
472
|
+
if (!this.isConnected) {
|
|
473
|
+
throw new Error("Not connected to Edge service. Call connect() first.");
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
export { CleverenceEdge };
|
|
479
|
+
//# sourceMappingURL=index.js.map
|
|
480
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/events.ts","../src/core/websocket.ts","../src/types/messages.ts","../src/core/client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Minimal typed event emitter for browser environments\r\n */\r\n\r\ntype EventHandler<T = unknown> = (data: T) => void;\r\n\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport class EventEmitter<Events extends Record<string, any> = Record<string, unknown>> {\r\n private handlers: Map<keyof Events, Set<EventHandler<unknown>>> = new Map();\r\n\r\n /**\r\n * Subscribe to an event\r\n */\r\n on<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): this {\r\n if (!this.handlers.has(event)) {\r\n this.handlers.set(event, new Set());\r\n }\r\n this.handlers.get(event)!.add(handler as EventHandler<unknown>);\r\n return this;\r\n }\r\n\r\n /**\r\n * Subscribe to an event once (auto-unsubscribes after first call)\r\n */\r\n once<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): this {\r\n const onceHandler: EventHandler<Events[K]> = (data) => {\r\n this.off(event, onceHandler);\r\n handler(data);\r\n };\r\n return this.on(event, onceHandler);\r\n }\r\n\r\n /**\r\n * Unsubscribe from an event\r\n */\r\n off<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): this {\r\n const eventHandlers = this.handlers.get(event);\r\n if (eventHandlers) {\r\n eventHandlers.delete(handler as EventHandler<unknown>);\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Emit an event to all subscribers\r\n */\r\n protected emit<K extends keyof Events>(event: K, data: Events[K]): void {\r\n const eventHandlers = this.handlers.get(event);\r\n if (eventHandlers) {\r\n eventHandlers.forEach((handler) => {\r\n try {\r\n handler(data);\r\n } catch (err) {\r\n console.error(`Error in event handler for \"${String(event)}\":`, err);\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Remove all listeners for an event, or all listeners if no event specified\r\n */\r\n removeAllListeners(event?: keyof Events): this {\r\n if (event) {\r\n this.handlers.delete(event);\r\n } else {\r\n this.handlers.clear();\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Get listener count for an event\r\n */\r\n listenerCount(event: keyof Events): number {\r\n return this.handlers.get(event)?.size ?? 0;\r\n }\r\n}\r\n","import { EventEmitter } from './events';\r\nimport type { ClientMessage, ServerMessage, ConnectionState } from '../types';\r\n\r\ninterface WSManagerEvents {\r\n message: ServerMessage;\r\n open: void;\r\n close: void;\r\n error: Error;\r\n statechange: ConnectionState;\r\n}\r\n\r\ninterface WSManagerOptions {\r\n reconnect?: boolean;\r\n reconnectDelay?: number;\r\n maxReconnectDelay?: number;\r\n pingInterval?: number;\r\n}\r\n\r\nconst DEFAULT_WS_OPTIONS: Required<WSManagerOptions> = {\r\n reconnect: true,\r\n reconnectDelay: 1000,\r\n maxReconnectDelay: 30000,\r\n pingInterval: 30000,\r\n};\r\n\r\n/**\r\n * WebSocket connection manager with auto-reconnect and request/response correlation\r\n */\r\nexport class WebSocketManager extends EventEmitter<WSManagerEvents> {\r\n private ws: WebSocket | null = null;\r\n private url: string;\r\n private options: Required<WSManagerOptions>;\r\n private _state: ConnectionState = 'disconnected';\r\n private reconnectAttempts = 0;\r\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n private pingTimer: ReturnType<typeof setInterval> | null = null;\r\n private pendingRequests: Map<string, {\r\n resolve: (data: unknown) => void;\r\n reject: (error: Error) => void;\r\n timeout: ReturnType<typeof setTimeout>;\r\n }> = new Map();\r\n private intentionalClose = false;\r\n\r\n constructor(url: string, options: WSManagerOptions = {}) {\r\n super();\r\n this.url = url;\r\n this.options = { ...DEFAULT_WS_OPTIONS, ...options };\r\n }\r\n\r\n /**\r\n * Current connection state\r\n */\r\n get state(): ConnectionState {\r\n return this._state;\r\n }\r\n\r\n /**\r\n * Whether currently connected\r\n */\r\n get isConnected(): boolean {\r\n return this._state === 'connected';\r\n }\r\n\r\n /**\r\n * Connect to the WebSocket server\r\n */\r\n connect(): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n if (this._state === 'connected' || this._state === 'connecting') {\r\n resolve();\r\n return;\r\n }\r\n\r\n this.intentionalClose = false;\r\n this.setState('connecting');\r\n\r\n try {\r\n this.ws = new WebSocket(this.url);\r\n\r\n const onOpen = () => {\r\n this.ws?.removeEventListener('error', onError);\r\n this.reconnectAttempts = 0;\r\n this.setState('connected');\r\n this.startPing();\r\n this.emit('open', undefined);\r\n resolve();\r\n };\r\n\r\n const onError = (event: Event) => {\r\n this.ws?.removeEventListener('open', onOpen);\r\n const error = new Error('WebSocket connection failed');\r\n reject(error);\r\n };\r\n\r\n this.ws.addEventListener('open', onOpen, { once: true });\r\n this.ws.addEventListener('error', onError, { once: true });\r\n\r\n this.ws.addEventListener('message', this.handleMessage.bind(this));\r\n this.ws.addEventListener('close', this.handleClose.bind(this));\r\n this.ws.addEventListener('error', this.handleError.bind(this));\r\n } catch (err) {\r\n this.setState('disconnected');\r\n reject(err instanceof Error ? err : new Error(String(err)));\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Disconnect from the WebSocket server\r\n */\r\n disconnect(): void {\r\n this.intentionalClose = true;\r\n this.cleanup();\r\n this.setState('disconnected');\r\n this.emit('close', undefined);\r\n }\r\n\r\n /**\r\n * Send a message to the server\r\n */\r\n send(message: ClientMessage): void {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\r\n throw new Error('WebSocket is not connected');\r\n }\r\n this.ws.send(JSON.stringify(message));\r\n }\r\n\r\n /**\r\n * Send a query and wait for response (request/response pattern)\r\n */\r\n request<T>(query: 'status' | 'capabilities' | 'config' | 'rfid_tags', timeoutMs = 10000): Promise<T> {\r\n return new Promise((resolve, reject) => {\r\n const id = this.generateId();\r\n \r\n const timeout = setTimeout(() => {\r\n this.pendingRequests.delete(id);\r\n reject(new Error(`Request timeout: ${query}`));\r\n }, timeoutMs);\r\n\r\n this.pendingRequests.set(id, {\r\n resolve: resolve as (data: unknown) => void,\r\n reject,\r\n timeout,\r\n });\r\n\r\n try {\r\n this.send({ type: 'query', id, query });\r\n } catch (err) {\r\n clearTimeout(timeout);\r\n this.pendingRequests.delete(id);\r\n reject(err);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Send a command (fire and forget, but throws if not connected)\r\n */\r\n command(message: Exclude<ClientMessage, { type: 'query' } | { type: 'ping' }>): void {\r\n this.send(message);\r\n }\r\n\r\n private handleMessage(event: MessageEvent): void {\r\n try {\r\n const message = JSON.parse(event.data) as ServerMessage;\r\n\r\n // Handle response to pending request\r\n if (message.type === 'response') {\r\n const pending = this.pendingRequests.get(message.id);\r\n if (pending) {\r\n clearTimeout(pending.timeout);\r\n this.pendingRequests.delete(message.id);\r\n \r\n if (message.success) {\r\n pending.resolve(message.data.result);\r\n } else {\r\n pending.reject(new Error(message.error));\r\n }\r\n return;\r\n }\r\n }\r\n\r\n // Handle pong\r\n if (message.type === 'pong') {\r\n return; // Keepalive response, no action needed\r\n }\r\n\r\n // Emit for other handlers\r\n this.emit('message', message);\r\n } catch (err) {\r\n console.error('Failed to parse WebSocket message:', err);\r\n }\r\n }\r\n\r\n private handleClose(): void {\r\n this.cleanup();\r\n \r\n if (this.intentionalClose) {\r\n this.setState('disconnected');\r\n return;\r\n }\r\n\r\n this.emit('close', undefined);\r\n\r\n if (this.options.reconnect) {\r\n this.scheduleReconnect();\r\n } else {\r\n this.setState('disconnected');\r\n }\r\n }\r\n\r\n private handleError(event: Event): void {\r\n const error = new Error('WebSocket error');\r\n this.emit('error', error);\r\n }\r\n\r\n private scheduleReconnect(): void {\r\n if (this.reconnectTimer) return;\r\n\r\n this.setState('reconnecting');\r\n\r\n // Exponential backoff\r\n const delay = Math.min(\r\n this.options.reconnectDelay * Math.pow(2, this.reconnectAttempts),\r\n this.options.maxReconnectDelay\r\n );\r\n\r\n this.reconnectTimer = setTimeout(async () => {\r\n this.reconnectTimer = null;\r\n this.reconnectAttempts++;\r\n \r\n try {\r\n await this.connect();\r\n } catch {\r\n // connect() will trigger handleClose which schedules another reconnect\r\n }\r\n }, delay);\r\n }\r\n\r\n private startPing(): void {\r\n this.stopPing();\r\n this.pingTimer = setInterval(() => {\r\n if (this.ws?.readyState === WebSocket.OPEN) {\r\n try {\r\n this.send({ type: 'ping' });\r\n } catch {\r\n // Ignore ping errors\r\n }\r\n }\r\n }, this.options.pingInterval);\r\n }\r\n\r\n private stopPing(): void {\r\n if (this.pingTimer) {\r\n clearInterval(this.pingTimer);\r\n this.pingTimer = null;\r\n }\r\n }\r\n\r\n private cleanup(): void {\r\n this.stopPing();\r\n\r\n if (this.reconnectTimer) {\r\n clearTimeout(this.reconnectTimer);\r\n this.reconnectTimer = null;\r\n }\r\n\r\n // Reject all pending requests\r\n this.pendingRequests.forEach((pending) => {\r\n clearTimeout(pending.timeout);\r\n pending.reject(new Error('WebSocket disconnected'));\r\n });\r\n this.pendingRequests.clear();\r\n\r\n if (this.ws) {\r\n const ws = this.ws;\r\n this.ws = null;\r\n // Only close if not already closed/closing\r\n if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {\r\n ws.close();\r\n }\r\n }\r\n }\r\n\r\n private setState(state: ConnectionState): void {\r\n if (this._state !== state) {\r\n this._state = state;\r\n this.emit('statechange', state);\r\n }\r\n }\r\n\r\n private generateId(): string {\r\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\r\n }\r\n}\r\n","import type { ScanEvent, RfidEvent } from './events';\r\nimport type { DeviceCapabilities, EdgeStatus, EdgeConfig, RfidInventoryOptions, RfidTag } from './capabilities';\r\n\r\n/**\r\n * Messages sent from SDK (client) to Edge service (server)\r\n */\r\nexport type ClientMessage =\r\n | { type: 'command'; command: 'trigger_scan' }\r\n | { type: 'command'; command: 'set_symbologies'; symbologies: string[] }\r\n | { type: 'command'; command: 'start_rfid_inventory'; options?: RfidInventoryOptions }\r\n | { type: 'command'; command: 'stop_rfid_inventory' }\r\n | { type: 'query'; id: string; query: 'status' }\r\n | { type: 'query'; id: string; query: 'capabilities' }\r\n | { type: 'query'; id: string; query: 'config' }\r\n | { type: 'query'; id: string; query: 'rfid_tags' }\r\n | { type: 'ping' };\r\n\r\n/**\r\n * Messages received from Edge service (server) to SDK (client)\r\n */\r\nexport type ServerMessage =\r\n | { type: 'event'; event: ScanEvent | RfidEvent }\r\n | { type: 'capabilities'; data: DeviceCapabilities }\r\n | { type: 'response'; id: string; success: true; data: ResponseData }\r\n | { type: 'response'; id: string; success: false; error: string }\r\n | { type: 'error'; message: string; code?: string }\r\n | { type: 'pong' };\r\n\r\n/**\r\n * Response data types based on query type\r\n */\r\nexport type ResponseData =\r\n | { query: 'status'; result: EdgeStatus }\r\n | { query: 'capabilities'; result: DeviceCapabilities }\r\n | { query: 'config'; result: EdgeConfig }\r\n | { query: 'rfid_tags'; result: RfidTag[] };\r\n\r\n/**\r\n * Connection state for the WebSocket\r\n */\r\nexport type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';\r\n\r\n/**\r\n * Options for creating a CleverenceEdge instance\r\n */\r\nexport interface EdgeOptions {\r\n /** WebSocket URL. Default: 'ws://localhost:8585' */\r\n url?: string;\r\n /** Auto-connect on instantiation. Default: true */\r\n autoConnect?: boolean;\r\n /** Initial reconnect delay in ms. Default: 1000 */\r\n reconnectDelay?: number;\r\n /** Maximum reconnect delay in ms. Default: 30000 */\r\n maxReconnectDelay?: number;\r\n /** Ping interval for keepalive in ms. Default: 30000 */\r\n pingInterval?: number;\r\n}\r\n\r\n/**\r\n * Default options\r\n */\r\nexport const DEFAULT_OPTIONS: Required<EdgeOptions> = {\r\n url: 'ws://localhost:8585',\r\n autoConnect: true,\r\n reconnectDelay: 1000,\r\n maxReconnectDelay: 30000,\r\n pingInterval: 30000,\r\n};\r\n","import { EventEmitter } from './events';\r\nimport { WebSocketManager } from './websocket';\r\nimport type {\r\n ScanEvent,\r\n RfidEvent,\r\n DeviceCapabilities,\r\n EdgeStatus,\r\n EdgeConfig,\r\n RfidInventoryOptions,\r\n RfidTag,\r\n ConnectionState,\r\n EdgeOptions,\r\n ServerMessage,\r\n} from '../types';\r\nimport { DEFAULT_OPTIONS } from '../types';\r\n\r\n/**\r\n * Events emitted by CleverenceEdge\r\n */\r\ninterface CleverenceEdgeEvents {\r\n scan: ScanEvent;\r\n rfid: RfidEvent;\r\n connect: void;\r\n disconnect: void;\r\n reconnecting: void;\r\n error: Error;\r\n capabilities: DeviceCapabilities;\r\n}\r\n\r\n/**\r\n * CleverenceEdge SDK - Connect to barcode scanners and RFID readers\r\n *\r\n * @example\r\n * ```typescript\r\n * const edge = new CleverenceEdge();\r\n *\r\n * edge.on('scan', (event) => {\r\n * console.log(event.data); // \"012345678905\"\r\n * console.log(event.symbology); // \"ean13\"\r\n * });\r\n *\r\n * edge.on('rfid', (event) => {\r\n * console.log(event.epc); // \"3034257BF400B7800004CB2F\"\r\n * console.log(event.rssi); // -45\r\n * });\r\n *\r\n * await edge.connect();\r\n * ```\r\n */\r\nexport class CleverenceEdge extends EventEmitter<CleverenceEdgeEvents> {\r\n private ws: WebSocketManager;\r\n private options: Required<EdgeOptions>;\r\n private _capabilities: DeviceCapabilities | null = null;\r\n\r\n constructor(options: EdgeOptions = {}) {\r\n super();\r\n this.options = { ...DEFAULT_OPTIONS, ...options };\r\n this.ws = new WebSocketManager(this.options.url, {\r\n reconnect: true,\r\n reconnectDelay: this.options.reconnectDelay,\r\n maxReconnectDelay: this.options.maxReconnectDelay,\r\n pingInterval: this.options.pingInterval,\r\n });\r\n\r\n this.setupWebSocketHandlers();\r\n\r\n if (this.options.autoConnect) {\r\n // Auto-connect on next tick to allow event handlers to be set up\r\n setTimeout(() => this.connect().catch(() => {}), 0);\r\n }\r\n }\r\n\r\n /**\r\n * Current connection state\r\n */\r\n get connectionState(): ConnectionState {\r\n return this.ws.state;\r\n }\r\n\r\n /**\r\n * Whether currently connected to the Edge service\r\n */\r\n get isConnected(): boolean {\r\n return this.ws.isConnected;\r\n }\r\n\r\n /**\r\n * Cached device capabilities (available after connect)\r\n */\r\n get capabilities(): DeviceCapabilities | null {\r\n return this._capabilities;\r\n }\r\n\r\n /**\r\n * Connect to the Edge service\r\n */\r\n async connect(): Promise<void> {\r\n await this.ws.connect();\r\n }\r\n\r\n /**\r\n * Disconnect from the Edge service\r\n */\r\n disconnect(): void {\r\n this.ws.disconnect();\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────────────────────\r\n // Commands\r\n // ─────────────────────────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Trigger a barcode scan programmatically\r\n */\r\n async triggerScan(): Promise<void> {\r\n this.ensureConnected();\r\n this.ws.command({ type: 'command', command: 'trigger_scan' });\r\n }\r\n\r\n /**\r\n * Set enabled barcode symbologies\r\n */\r\n async setSymbologies(symbologies: string[]): Promise<void> {\r\n this.ensureConnected();\r\n this.ws.command({ type: 'command', command: 'set_symbologies', symbologies });\r\n }\r\n\r\n /**\r\n * Start RFID inventory (continuous reading)\r\n */\r\n async startRfidInventory(options?: RfidInventoryOptions): Promise<void> {\r\n this.ensureConnected();\r\n this.ws.command({ type: 'command', command: 'start_rfid_inventory', options });\r\n }\r\n\r\n /**\r\n * Stop RFID inventory\r\n */\r\n async stopRfidInventory(): Promise<void> {\r\n this.ensureConnected();\r\n this.ws.command({ type: 'command', command: 'stop_rfid_inventory' });\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────────────────────\r\n // Queries\r\n // ─────────────────────────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Get current Edge service status\r\n */\r\n async getStatus(): Promise<EdgeStatus> {\r\n this.ensureConnected();\r\n return this.ws.request<EdgeStatus>('status');\r\n }\r\n\r\n /**\r\n * Get device capabilities\r\n */\r\n async getCapabilities(): Promise<DeviceCapabilities> {\r\n this.ensureConnected();\r\n return this.ws.request<DeviceCapabilities>('capabilities');\r\n }\r\n\r\n /**\r\n * Get current configuration\r\n */\r\n async getConfig(): Promise<EdgeConfig> {\r\n this.ensureConnected();\r\n return this.ws.request<EdgeConfig>('config');\r\n }\r\n\r\n /**\r\n * Get RFID tags from current/last inventory\r\n */\r\n async getRfidTags(): Promise<RfidTag[]> {\r\n this.ensureConnected();\r\n return this.ws.request<RfidTag[]>('rfid_tags');\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────────────────────\r\n // Static factory\r\n // ─────────────────────────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Create a new CleverenceEdge instance\r\n */\r\n static create(options?: EdgeOptions): CleverenceEdge {\r\n return new CleverenceEdge(options);\r\n }\r\n\r\n // ─────────────────────────────────────────────────────────────────────────────\r\n // Private methods\r\n // ─────────────────────────────────────────────────────────────────────────────\r\n\r\n private setupWebSocketHandlers(): void {\r\n this.ws.on('open', () => {\r\n this.emit('connect', undefined);\r\n // Fetch capabilities on connect\r\n this.getCapabilities()\r\n .then((caps) => {\r\n this._capabilities = caps;\r\n this.emit('capabilities', caps);\r\n })\r\n .catch(() => {});\r\n });\r\n\r\n this.ws.on('close', () => {\r\n this.emit('disconnect', undefined);\r\n });\r\n\r\n this.ws.on('error', (error) => {\r\n this.emit('error', error);\r\n });\r\n\r\n this.ws.on('statechange', (state) => {\r\n if (state === 'reconnecting') {\r\n this.emit('reconnecting', undefined);\r\n }\r\n });\r\n\r\n this.ws.on('message', (message) => {\r\n this.handleServerMessage(message);\r\n });\r\n }\r\n\r\n private handleServerMessage(message: ServerMessage): void {\r\n switch (message.type) {\r\n case 'event':\r\n this.handleEvent(message.event);\r\n break;\r\n case 'capabilities':\r\n this._capabilities = message.data;\r\n this.emit('capabilities', message.data);\r\n break;\r\n case 'error':\r\n this.emit('error', new Error(message.message));\r\n break;\r\n }\r\n }\r\n\r\n private handleEvent(event: ScanEvent | RfidEvent): void {\r\n // Parse timestamp if it's a string\r\n const parsedEvent = {\r\n ...event,\r\n timestamp: typeof event.timestamp === 'string' \r\n ? new Date(event.timestamp) \r\n : event.timestamp,\r\n };\r\n\r\n if (event.type === 'scan') {\r\n this.emit('scan', parsedEvent as ScanEvent);\r\n } else if (event.type === 'rfid') {\r\n this.emit('rfid', parsedEvent as RfidEvent);\r\n }\r\n }\r\n\r\n private ensureConnected(): void {\r\n if (!this.isConnected) {\r\n throw new Error('Not connected to Edge service. Call connect() first.');\r\n }\r\n }\r\n}\r\n"]}
|