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