@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.
@@ -0,0 +1,698 @@
1
+ import { shallowRef, ref, onMounted, onUnmounted, watch } from 'vue';
2
+
3
+ // src/vue/useEdge.ts
4
+
5
+ // src/core/events.ts
6
+ var EventEmitter = class {
7
+ constructor() {
8
+ this.handlers = /* @__PURE__ */ new Map();
9
+ }
10
+ /**
11
+ * Subscribe to an event
12
+ */
13
+ on(event, handler) {
14
+ if (!this.handlers.has(event)) {
15
+ this.handlers.set(event, /* @__PURE__ */ new Set());
16
+ }
17
+ this.handlers.get(event).add(handler);
18
+ return this;
19
+ }
20
+ /**
21
+ * Subscribe to an event once (auto-unsubscribes after first call)
22
+ */
23
+ once(event, handler) {
24
+ const onceHandler = (data) => {
25
+ this.off(event, onceHandler);
26
+ handler(data);
27
+ };
28
+ return this.on(event, onceHandler);
29
+ }
30
+ /**
31
+ * Unsubscribe from an event
32
+ */
33
+ off(event, handler) {
34
+ const eventHandlers = this.handlers.get(event);
35
+ if (eventHandlers) {
36
+ eventHandlers.delete(handler);
37
+ }
38
+ return this;
39
+ }
40
+ /**
41
+ * Emit an event to all subscribers
42
+ */
43
+ emit(event, data) {
44
+ const eventHandlers = this.handlers.get(event);
45
+ if (eventHandlers) {
46
+ eventHandlers.forEach((handler) => {
47
+ try {
48
+ handler(data);
49
+ } catch (err) {
50
+ console.error(`Error in event handler for "${String(event)}":`, err);
51
+ }
52
+ });
53
+ }
54
+ }
55
+ /**
56
+ * Remove all listeners for an event, or all listeners if no event specified
57
+ */
58
+ removeAllListeners(event) {
59
+ if (event) {
60
+ this.handlers.delete(event);
61
+ } else {
62
+ this.handlers.clear();
63
+ }
64
+ return this;
65
+ }
66
+ /**
67
+ * Get listener count for an event
68
+ */
69
+ listenerCount(event) {
70
+ return this.handlers.get(event)?.size ?? 0;
71
+ }
72
+ };
73
+
74
+ // src/core/websocket.ts
75
+ var DEFAULT_WS_OPTIONS = {
76
+ reconnect: true,
77
+ reconnectDelay: 1e3,
78
+ maxReconnectDelay: 3e4,
79
+ pingInterval: 3e4
80
+ };
81
+ var WebSocketManager = class extends EventEmitter {
82
+ constructor(url, options = {}) {
83
+ super();
84
+ this.ws = null;
85
+ this._state = "disconnected";
86
+ this.reconnectAttempts = 0;
87
+ this.reconnectTimer = null;
88
+ this.pingTimer = null;
89
+ this.pendingRequests = /* @__PURE__ */ new Map();
90
+ this.intentionalClose = false;
91
+ this.url = url;
92
+ this.options = { ...DEFAULT_WS_OPTIONS, ...options };
93
+ }
94
+ /**
95
+ * Current connection state
96
+ */
97
+ get state() {
98
+ return this._state;
99
+ }
100
+ /**
101
+ * Whether currently connected
102
+ */
103
+ get isConnected() {
104
+ return this._state === "connected";
105
+ }
106
+ /**
107
+ * Connect to the WebSocket server
108
+ */
109
+ connect() {
110
+ return new Promise((resolve, reject) => {
111
+ if (this._state === "connected" || this._state === "connecting") {
112
+ resolve();
113
+ return;
114
+ }
115
+ this.intentionalClose = false;
116
+ this.setState("connecting");
117
+ try {
118
+ this.ws = new WebSocket(this.url);
119
+ const onOpen = () => {
120
+ this.ws?.removeEventListener("error", onError);
121
+ this.reconnectAttempts = 0;
122
+ this.setState("connected");
123
+ this.startPing();
124
+ this.emit("open", void 0);
125
+ resolve();
126
+ };
127
+ const onError = (event) => {
128
+ this.ws?.removeEventListener("open", onOpen);
129
+ const error = new Error("WebSocket connection failed");
130
+ reject(error);
131
+ };
132
+ this.ws.addEventListener("open", onOpen, { once: true });
133
+ this.ws.addEventListener("error", onError, { once: true });
134
+ this.ws.addEventListener("message", this.handleMessage.bind(this));
135
+ this.ws.addEventListener("close", this.handleClose.bind(this));
136
+ this.ws.addEventListener("error", this.handleError.bind(this));
137
+ } catch (err) {
138
+ this.setState("disconnected");
139
+ reject(err instanceof Error ? err : new Error(String(err)));
140
+ }
141
+ });
142
+ }
143
+ /**
144
+ * Disconnect from the WebSocket server
145
+ */
146
+ disconnect() {
147
+ this.intentionalClose = true;
148
+ this.cleanup();
149
+ this.setState("disconnected");
150
+ this.emit("close", void 0);
151
+ }
152
+ /**
153
+ * Send a message to the server
154
+ */
155
+ send(message) {
156
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
157
+ throw new Error("WebSocket is not connected");
158
+ }
159
+ this.ws.send(JSON.stringify(message));
160
+ }
161
+ /**
162
+ * Send a query and wait for response (request/response pattern)
163
+ */
164
+ request(query, timeoutMs = 1e4) {
165
+ return new Promise((resolve, reject) => {
166
+ const id = this.generateId();
167
+ const timeout = setTimeout(() => {
168
+ this.pendingRequests.delete(id);
169
+ reject(new Error(`Request timeout: ${query}`));
170
+ }, timeoutMs);
171
+ this.pendingRequests.set(id, {
172
+ resolve,
173
+ reject,
174
+ timeout
175
+ });
176
+ try {
177
+ this.send({ type: "query", id, query });
178
+ } catch (err) {
179
+ clearTimeout(timeout);
180
+ this.pendingRequests.delete(id);
181
+ reject(err);
182
+ }
183
+ });
184
+ }
185
+ /**
186
+ * Send a command (fire and forget, but throws if not connected)
187
+ */
188
+ command(message) {
189
+ this.send(message);
190
+ }
191
+ handleMessage(event) {
192
+ try {
193
+ const message = JSON.parse(event.data);
194
+ if (message.type === "response") {
195
+ const pending = this.pendingRequests.get(message.id);
196
+ if (pending) {
197
+ clearTimeout(pending.timeout);
198
+ this.pendingRequests.delete(message.id);
199
+ if (message.success) {
200
+ pending.resolve(message.data.result);
201
+ } else {
202
+ pending.reject(new Error(message.error));
203
+ }
204
+ return;
205
+ }
206
+ }
207
+ if (message.type === "pong") {
208
+ return;
209
+ }
210
+ this.emit("message", message);
211
+ } catch (err) {
212
+ console.error("Failed to parse WebSocket message:", err);
213
+ }
214
+ }
215
+ handleClose() {
216
+ this.cleanup();
217
+ if (this.intentionalClose) {
218
+ this.setState("disconnected");
219
+ return;
220
+ }
221
+ this.emit("close", void 0);
222
+ if (this.options.reconnect) {
223
+ this.scheduleReconnect();
224
+ } else {
225
+ this.setState("disconnected");
226
+ }
227
+ }
228
+ handleError(event) {
229
+ const error = new Error("WebSocket error");
230
+ this.emit("error", error);
231
+ }
232
+ scheduleReconnect() {
233
+ if (this.reconnectTimer) return;
234
+ this.setState("reconnecting");
235
+ const delay = Math.min(
236
+ this.options.reconnectDelay * Math.pow(2, this.reconnectAttempts),
237
+ this.options.maxReconnectDelay
238
+ );
239
+ this.reconnectTimer = setTimeout(async () => {
240
+ this.reconnectTimer = null;
241
+ this.reconnectAttempts++;
242
+ try {
243
+ await this.connect();
244
+ } catch {
245
+ }
246
+ }, delay);
247
+ }
248
+ startPing() {
249
+ this.stopPing();
250
+ this.pingTimer = setInterval(() => {
251
+ if (this.ws?.readyState === WebSocket.OPEN) {
252
+ try {
253
+ this.send({ type: "ping" });
254
+ } catch {
255
+ }
256
+ }
257
+ }, this.options.pingInterval);
258
+ }
259
+ stopPing() {
260
+ if (this.pingTimer) {
261
+ clearInterval(this.pingTimer);
262
+ this.pingTimer = null;
263
+ }
264
+ }
265
+ cleanup() {
266
+ this.stopPing();
267
+ if (this.reconnectTimer) {
268
+ clearTimeout(this.reconnectTimer);
269
+ this.reconnectTimer = null;
270
+ }
271
+ this.pendingRequests.forEach((pending) => {
272
+ clearTimeout(pending.timeout);
273
+ pending.reject(new Error("WebSocket disconnected"));
274
+ });
275
+ this.pendingRequests.clear();
276
+ if (this.ws) {
277
+ const ws = this.ws;
278
+ this.ws = null;
279
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
280
+ ws.close();
281
+ }
282
+ }
283
+ }
284
+ setState(state) {
285
+ if (this._state !== state) {
286
+ this._state = state;
287
+ this.emit("statechange", state);
288
+ }
289
+ }
290
+ generateId() {
291
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
292
+ }
293
+ };
294
+
295
+ // src/types/messages.ts
296
+ var DEFAULT_OPTIONS = {
297
+ url: "ws://localhost:8585",
298
+ autoConnect: true,
299
+ reconnectDelay: 1e3,
300
+ maxReconnectDelay: 3e4,
301
+ pingInterval: 3e4
302
+ };
303
+
304
+ // src/core/client.ts
305
+ var CleverenceEdge = class _CleverenceEdge extends EventEmitter {
306
+ constructor(options = {}) {
307
+ super();
308
+ this._capabilities = null;
309
+ this.options = { ...DEFAULT_OPTIONS, ...options };
310
+ this.ws = new WebSocketManager(this.options.url, {
311
+ reconnect: true,
312
+ reconnectDelay: this.options.reconnectDelay,
313
+ maxReconnectDelay: this.options.maxReconnectDelay,
314
+ pingInterval: this.options.pingInterval
315
+ });
316
+ this.setupWebSocketHandlers();
317
+ if (this.options.autoConnect) {
318
+ setTimeout(() => this.connect().catch(() => {
319
+ }), 0);
320
+ }
321
+ }
322
+ /**
323
+ * Current connection state
324
+ */
325
+ get connectionState() {
326
+ return this.ws.state;
327
+ }
328
+ /**
329
+ * Whether currently connected to the Edge service
330
+ */
331
+ get isConnected() {
332
+ return this.ws.isConnected;
333
+ }
334
+ /**
335
+ * Cached device capabilities (available after connect)
336
+ */
337
+ get capabilities() {
338
+ return this._capabilities;
339
+ }
340
+ /**
341
+ * Connect to the Edge service
342
+ */
343
+ async connect() {
344
+ await this.ws.connect();
345
+ }
346
+ /**
347
+ * Disconnect from the Edge service
348
+ */
349
+ disconnect() {
350
+ this.ws.disconnect();
351
+ }
352
+ // ─────────────────────────────────────────────────────────────────────────────
353
+ // Commands
354
+ // ─────────────────────────────────────────────────────────────────────────────
355
+ /**
356
+ * Trigger a barcode scan programmatically
357
+ */
358
+ async triggerScan() {
359
+ this.ensureConnected();
360
+ this.ws.command({ type: "command", command: "trigger_scan" });
361
+ }
362
+ /**
363
+ * Set enabled barcode symbologies
364
+ */
365
+ async setSymbologies(symbologies) {
366
+ this.ensureConnected();
367
+ this.ws.command({ type: "command", command: "set_symbologies", symbologies });
368
+ }
369
+ /**
370
+ * Start RFID inventory (continuous reading)
371
+ */
372
+ async startRfidInventory(options) {
373
+ this.ensureConnected();
374
+ this.ws.command({ type: "command", command: "start_rfid_inventory", options });
375
+ }
376
+ /**
377
+ * Stop RFID inventory
378
+ */
379
+ async stopRfidInventory() {
380
+ this.ensureConnected();
381
+ this.ws.command({ type: "command", command: "stop_rfid_inventory" });
382
+ }
383
+ // ─────────────────────────────────────────────────────────────────────────────
384
+ // Queries
385
+ // ─────────────────────────────────────────────────────────────────────────────
386
+ /**
387
+ * Get current Edge service status
388
+ */
389
+ async getStatus() {
390
+ this.ensureConnected();
391
+ return this.ws.request("status");
392
+ }
393
+ /**
394
+ * Get device capabilities
395
+ */
396
+ async getCapabilities() {
397
+ this.ensureConnected();
398
+ return this.ws.request("capabilities");
399
+ }
400
+ /**
401
+ * Get current configuration
402
+ */
403
+ async getConfig() {
404
+ this.ensureConnected();
405
+ return this.ws.request("config");
406
+ }
407
+ /**
408
+ * Get RFID tags from current/last inventory
409
+ */
410
+ async getRfidTags() {
411
+ this.ensureConnected();
412
+ return this.ws.request("rfid_tags");
413
+ }
414
+ // ─────────────────────────────────────────────────────────────────────────────
415
+ // Static factory
416
+ // ─────────────────────────────────────────────────────────────────────────────
417
+ /**
418
+ * Create a new CleverenceEdge instance
419
+ */
420
+ static create(options) {
421
+ return new _CleverenceEdge(options);
422
+ }
423
+ // ─────────────────────────────────────────────────────────────────────────────
424
+ // Private methods
425
+ // ─────────────────────────────────────────────────────────────────────────────
426
+ setupWebSocketHandlers() {
427
+ this.ws.on("open", () => {
428
+ this.emit("connect", void 0);
429
+ this.getCapabilities().then((caps) => {
430
+ this._capabilities = caps;
431
+ this.emit("capabilities", caps);
432
+ }).catch(() => {
433
+ });
434
+ });
435
+ this.ws.on("close", () => {
436
+ this.emit("disconnect", void 0);
437
+ });
438
+ this.ws.on("error", (error) => {
439
+ this.emit("error", error);
440
+ });
441
+ this.ws.on("statechange", (state) => {
442
+ if (state === "reconnecting") {
443
+ this.emit("reconnecting", void 0);
444
+ }
445
+ });
446
+ this.ws.on("message", (message) => {
447
+ this.handleServerMessage(message);
448
+ });
449
+ }
450
+ handleServerMessage(message) {
451
+ switch (message.type) {
452
+ case "event":
453
+ this.handleEvent(message.event);
454
+ break;
455
+ case "capabilities":
456
+ this._capabilities = message.data;
457
+ this.emit("capabilities", message.data);
458
+ break;
459
+ case "error":
460
+ this.emit("error", new Error(message.message));
461
+ break;
462
+ }
463
+ }
464
+ handleEvent(event) {
465
+ const parsedEvent = {
466
+ ...event,
467
+ timestamp: typeof event.timestamp === "string" ? new Date(event.timestamp) : event.timestamp
468
+ };
469
+ if (event.type === "scan") {
470
+ this.emit("scan", parsedEvent);
471
+ } else if (event.type === "rfid") {
472
+ this.emit("rfid", parsedEvent);
473
+ }
474
+ }
475
+ ensureConnected() {
476
+ if (!this.isConnected) {
477
+ throw new Error("Not connected to Edge service. Call connect() first.");
478
+ }
479
+ }
480
+ };
481
+
482
+ // src/vue/useEdge.ts
483
+ var sharedEdge = null;
484
+ var refCount = 0;
485
+ function useEdge(options = {}) {
486
+ const { shared = true, ...edgeOptions } = options;
487
+ const edge = shallowRef(null);
488
+ const isConnected = ref(false);
489
+ const connectionState = ref("disconnected");
490
+ const capabilities = ref(null);
491
+ const error = ref(null);
492
+ const handleConnect = () => {
493
+ connectionState.value = "connected";
494
+ isConnected.value = true;
495
+ error.value = null;
496
+ };
497
+ const handleDisconnect = () => {
498
+ connectionState.value = "disconnected";
499
+ isConnected.value = false;
500
+ };
501
+ const handleReconnecting = () => {
502
+ connectionState.value = "reconnecting";
503
+ isConnected.value = false;
504
+ };
505
+ const handleError = (err) => {
506
+ error.value = err;
507
+ };
508
+ const handleCapabilities = (caps) => {
509
+ capabilities.value = caps;
510
+ };
511
+ const setupListeners = (instance) => {
512
+ instance.on("connect", handleConnect);
513
+ instance.on("disconnect", handleDisconnect);
514
+ instance.on("reconnecting", handleReconnecting);
515
+ instance.on("error", handleError);
516
+ instance.on("capabilities", handleCapabilities);
517
+ };
518
+ const removeListeners = (instance) => {
519
+ instance.off("connect", handleConnect);
520
+ instance.off("disconnect", handleDisconnect);
521
+ instance.off("reconnecting", handleReconnecting);
522
+ instance.off("error", handleError);
523
+ instance.off("capabilities", handleCapabilities);
524
+ };
525
+ onMounted(() => {
526
+ let instance;
527
+ if (shared) {
528
+ if (!sharedEdge) {
529
+ sharedEdge = new CleverenceEdge(edgeOptions);
530
+ }
531
+ instance = sharedEdge;
532
+ refCount++;
533
+ } else {
534
+ instance = new CleverenceEdge(edgeOptions);
535
+ }
536
+ edge.value = instance;
537
+ connectionState.value = instance.connectionState;
538
+ isConnected.value = instance.isConnected;
539
+ capabilities.value = instance.capabilities;
540
+ setupListeners(instance);
541
+ });
542
+ onUnmounted(() => {
543
+ const instance = edge.value;
544
+ if (!instance) return;
545
+ removeListeners(instance);
546
+ if (shared) {
547
+ refCount--;
548
+ if (refCount === 0 && sharedEdge) {
549
+ sharedEdge.disconnect();
550
+ sharedEdge = null;
551
+ }
552
+ } else {
553
+ instance.disconnect();
554
+ }
555
+ edge.value = null;
556
+ });
557
+ const connect = async () => {
558
+ if (edge.value) {
559
+ await edge.value.connect();
560
+ }
561
+ };
562
+ const disconnect = () => {
563
+ if (edge.value) {
564
+ edge.value.disconnect();
565
+ }
566
+ };
567
+ return {
568
+ edge,
569
+ isConnected,
570
+ connectionState,
571
+ capabilities,
572
+ error,
573
+ connect,
574
+ disconnect
575
+ };
576
+ }
577
+ function useBarcode(options) {
578
+ const { edge, maxHistory = 50, onScan } = options;
579
+ const lastScan = ref(null);
580
+ const scanHistory = ref([]);
581
+ let currentHandler = null;
582
+ const handleScan = (event) => {
583
+ lastScan.value = event;
584
+ scanHistory.value = [event, ...scanHistory.value].slice(0, maxHistory);
585
+ onScan?.(event);
586
+ };
587
+ const stopWatch = watch(
588
+ edge,
589
+ (newEdge, oldEdge) => {
590
+ if (oldEdge && currentHandler) {
591
+ oldEdge.off("scan", currentHandler);
592
+ }
593
+ if (newEdge) {
594
+ currentHandler = handleScan;
595
+ newEdge.on("scan", currentHandler);
596
+ }
597
+ },
598
+ { immediate: true }
599
+ );
600
+ onUnmounted(() => {
601
+ stopWatch();
602
+ if (edge.value && currentHandler) {
603
+ edge.value.off("scan", currentHandler);
604
+ }
605
+ });
606
+ const clearHistory = () => {
607
+ lastScan.value = null;
608
+ scanHistory.value = [];
609
+ };
610
+ const triggerScan = async () => {
611
+ if (!edge.value) throw new Error("Not connected");
612
+ await edge.value.triggerScan();
613
+ };
614
+ return {
615
+ lastScan,
616
+ scanHistory,
617
+ clearHistory,
618
+ triggerScan
619
+ };
620
+ }
621
+ function useRfid(options) {
622
+ const { edge, onRead } = options;
623
+ const lastRead = ref(null);
624
+ const tags = ref(/* @__PURE__ */ new Map());
625
+ const isInventoryActive = ref(false);
626
+ let currentHandler = null;
627
+ const handleRfid = (event) => {
628
+ lastRead.value = event;
629
+ const newTags = new Map(tags.value);
630
+ const existing = newTags.get(event.epc);
631
+ const now = /* @__PURE__ */ new Date();
632
+ if (existing) {
633
+ newTags.set(event.epc, {
634
+ ...existing,
635
+ rssi: event.rssi,
636
+ antenna: event.antenna,
637
+ readCount: existing.readCount + 1,
638
+ lastSeen: now
639
+ });
640
+ } else {
641
+ newTags.set(event.epc, {
642
+ epc: event.epc,
643
+ rssi: event.rssi,
644
+ antenna: event.antenna,
645
+ readCount: 1,
646
+ firstSeen: now,
647
+ lastSeen: now
648
+ });
649
+ }
650
+ tags.value = newTags;
651
+ onRead?.(event);
652
+ };
653
+ const stopWatch = watch(
654
+ edge,
655
+ (newEdge, oldEdge) => {
656
+ if (oldEdge && currentHandler) {
657
+ oldEdge.off("rfid", currentHandler);
658
+ }
659
+ if (newEdge) {
660
+ currentHandler = handleRfid;
661
+ newEdge.on("rfid", currentHandler);
662
+ }
663
+ },
664
+ { immediate: true }
665
+ );
666
+ onUnmounted(() => {
667
+ stopWatch();
668
+ if (edge.value && currentHandler) {
669
+ edge.value.off("rfid", currentHandler);
670
+ }
671
+ });
672
+ const startInventory = async (inventoryOptions) => {
673
+ if (!edge.value) throw new Error("Not connected");
674
+ await edge.value.startRfidInventory(inventoryOptions);
675
+ isInventoryActive.value = true;
676
+ };
677
+ const stopInventory = async () => {
678
+ if (!edge.value) throw new Error("Not connected");
679
+ await edge.value.stopRfidInventory();
680
+ isInventoryActive.value = false;
681
+ };
682
+ const clearTags = () => {
683
+ lastRead.value = null;
684
+ tags.value = /* @__PURE__ */ new Map();
685
+ };
686
+ return {
687
+ lastRead,
688
+ tags,
689
+ isInventoryActive,
690
+ startInventory,
691
+ stopInventory,
692
+ clearTags
693
+ };
694
+ }
695
+
696
+ export { useBarcode, useEdge, useRfid };
697
+ //# sourceMappingURL=index.js.map
698
+ //# sourceMappingURL=index.js.map