@hardwarebridge/client 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/dist/index.d.ts +349 -0
- package/dist/index.esm.js +438 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +442 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +448 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +78 -0
- package/src/__tests__/hardware-bridge-client.test.ts +248 -0
- package/src/__tests__/types.test.ts +153 -0
- package/src/__tests__/websocket-client.test.ts +198 -0
- package/src/core/hardware-bridge-client.ts +306 -0
- package/src/core/websocket-client.ts +337 -0
- package/src/index.ts +37 -0
- package/src/types.ts +187 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { WebSocketClient } from './websocket-client';
|
|
2
|
+
import {
|
|
3
|
+
DeviceInfo,
|
|
4
|
+
PrintResult,
|
|
5
|
+
PrintFormat,
|
|
6
|
+
SerialPortConfig,
|
|
7
|
+
QueueJob,
|
|
8
|
+
QueueStatus,
|
|
9
|
+
SystemHealth,
|
|
10
|
+
DeviceEvent,
|
|
11
|
+
ConnectionConfig,
|
|
12
|
+
ClientOptions
|
|
13
|
+
} from '../types';
|
|
14
|
+
|
|
15
|
+
export class HardwareBridgeClient {
|
|
16
|
+
private wsClient: WebSocketClient;
|
|
17
|
+
private options: ClientOptions;
|
|
18
|
+
|
|
19
|
+
constructor(config: ConnectionConfig, options: ClientOptions = {}) {
|
|
20
|
+
this.options = {
|
|
21
|
+
autoReconnect: true,
|
|
22
|
+
reconnectInterval: 5000,
|
|
23
|
+
maxReconnectAttempts: 10,
|
|
24
|
+
timeout: 30000,
|
|
25
|
+
debug: false,
|
|
26
|
+
...options
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
this.wsClient = new WebSocketClient(config, this.options);
|
|
30
|
+
this.setupEventHandlers();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private setupEventHandlers(): void {
|
|
34
|
+
// Forward device events
|
|
35
|
+
this.wsClient.onDeviceEvent((event: DeviceEvent) => {
|
|
36
|
+
this.onDeviceEvent(event);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Connection Management
|
|
41
|
+
async connect(): Promise<void> {
|
|
42
|
+
return this.wsClient.connect();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
disconnect(): void {
|
|
46
|
+
this.wsClient.disconnect();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get isConnected(): boolean {
|
|
50
|
+
return this.wsClient.isConnected;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get isConnecting(): boolean {
|
|
54
|
+
return this.wsClient.isConnecting;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getConnectionStatus(): string {
|
|
58
|
+
return this.wsClient.getConnectionStatus();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onConnectionStateChange(listener: (connected: boolean) => void): void {
|
|
62
|
+
this.wsClient.onConnectionStateChange((state) => {
|
|
63
|
+
listener(state.connected);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Device Discovery
|
|
68
|
+
async enumerateDevices(): Promise<DeviceInfo[]> {
|
|
69
|
+
const result = await this.wsClient.sendRequest<{ devices: DeviceInfo[] }>('devices.enumerate');
|
|
70
|
+
return result.devices;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async getDevice(deviceId: string): Promise<DeviceInfo> {
|
|
74
|
+
return this.wsClient.sendRequest<DeviceInfo>('devices.get', { deviceId });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async watchDevices(): Promise<void> {
|
|
78
|
+
return this.wsClient.sendRequest('devices.watch');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async unwatchDevices(): Promise<void> {
|
|
82
|
+
return this.wsClient.sendRequest('devices.unwatch');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Printer Operations
|
|
86
|
+
async print(deviceId: string, data: string, format: PrintFormat = 'raw'): Promise<PrintResult> {
|
|
87
|
+
return this.wsClient.sendRequest<PrintResult>('printer.print', {
|
|
88
|
+
deviceId,
|
|
89
|
+
data,
|
|
90
|
+
format
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async getPrinterStatus(deviceId: string): Promise<{
|
|
95
|
+
isConnected: boolean;
|
|
96
|
+
status: string;
|
|
97
|
+
isReady: boolean;
|
|
98
|
+
isBusy: boolean;
|
|
99
|
+
isPaused: boolean;
|
|
100
|
+
jobsInQueue: number;
|
|
101
|
+
error?: string;
|
|
102
|
+
timestamp: Date;
|
|
103
|
+
}> {
|
|
104
|
+
return this.wsClient.sendRequest('printer.getStatus', { deviceId });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async getPrinterCapabilities(deviceId: string): Promise<{
|
|
108
|
+
supportedProtocols: string[];
|
|
109
|
+
maxPrintWidth: number;
|
|
110
|
+
supportsColor: boolean;
|
|
111
|
+
supportsDuplex: boolean;
|
|
112
|
+
maxResolution: number;
|
|
113
|
+
maxJobSize: number;
|
|
114
|
+
error?: string;
|
|
115
|
+
}> {
|
|
116
|
+
return this.wsClient.sendRequest('printer.getCapabilities', { deviceId });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Serial Port Operations
|
|
120
|
+
async openSerialPort(deviceId: string, config: SerialPortConfig): Promise<{
|
|
121
|
+
success: boolean;
|
|
122
|
+
portName?: string;
|
|
123
|
+
config?: SerialPortConfig;
|
|
124
|
+
openedAt?: Date;
|
|
125
|
+
error?: string;
|
|
126
|
+
}> {
|
|
127
|
+
return this.wsClient.sendRequest('serial.open', {
|
|
128
|
+
deviceId,
|
|
129
|
+
...config
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async closeSerialPort(deviceId: string): Promise<{
|
|
134
|
+
success: boolean;
|
|
135
|
+
portName?: string;
|
|
136
|
+
closedAt?: Date;
|
|
137
|
+
error?: string;
|
|
138
|
+
}> {
|
|
139
|
+
return this.wsClient.sendRequest('serial.close', { deviceId });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async sendSerialData(deviceId: string, data: string): Promise<{
|
|
143
|
+
success: boolean;
|
|
144
|
+
bytesTransferred: number;
|
|
145
|
+
data: string;
|
|
146
|
+
error?: string;
|
|
147
|
+
timestamp: Date;
|
|
148
|
+
}> {
|
|
149
|
+
return this.wsClient.sendRequest('serial.send', { deviceId, data });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async receiveSerialData(deviceId: string, maxBytes: number = 1024, timeout: number = 10000): Promise<{
|
|
153
|
+
success: boolean;
|
|
154
|
+
bytesTransferred: number;
|
|
155
|
+
data: string;
|
|
156
|
+
error?: string;
|
|
157
|
+
timestamp: Date;
|
|
158
|
+
}> {
|
|
159
|
+
return this.wsClient.sendRequest('serial.receive', { deviceId, maxBytes, timeout });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async getSerialPortStatus(deviceId: string): Promise<{
|
|
163
|
+
isConnected: boolean;
|
|
164
|
+
status: string;
|
|
165
|
+
portName?: string;
|
|
166
|
+
baudRate?: number;
|
|
167
|
+
parity?: string;
|
|
168
|
+
dataBits?: number;
|
|
169
|
+
stopBits?: string;
|
|
170
|
+
flowControl?: string;
|
|
171
|
+
bytesToRead?: number;
|
|
172
|
+
bytesToWrite?: number;
|
|
173
|
+
isOpen?: boolean;
|
|
174
|
+
cdHolding?: boolean;
|
|
175
|
+
ctsHolding?: boolean;
|
|
176
|
+
dsrHolding?: boolean;
|
|
177
|
+
connectedAt?: Date;
|
|
178
|
+
lastActivity?: Date;
|
|
179
|
+
error?: string;
|
|
180
|
+
}> {
|
|
181
|
+
return this.wsClient.sendRequest('serial.getStatus', { deviceId });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// USB HID Operations
|
|
185
|
+
async openUsbDevice(deviceId: string): Promise<{
|
|
186
|
+
success: boolean;
|
|
187
|
+
deviceId: string;
|
|
188
|
+
vendorId?: number;
|
|
189
|
+
productId?: number;
|
|
190
|
+
openedAt?: Date;
|
|
191
|
+
error?: string;
|
|
192
|
+
}> {
|
|
193
|
+
return this.wsClient.sendRequest('usb.open', { deviceId });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async closeUsbDevice(deviceId: string): Promise<{
|
|
197
|
+
success: boolean;
|
|
198
|
+
deviceId: string;
|
|
199
|
+
closedAt?: Date;
|
|
200
|
+
error?: string;
|
|
201
|
+
}> {
|
|
202
|
+
return this.wsClient.sendRequest('usb.close', { deviceId });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async sendUsbReport(deviceId: string, reportId: number, data: string): Promise<{
|
|
206
|
+
success: boolean;
|
|
207
|
+
reportId: number;
|
|
208
|
+
bytesTransferred: number;
|
|
209
|
+
data: string;
|
|
210
|
+
error?: string;
|
|
211
|
+
timestamp: Date;
|
|
212
|
+
}> {
|
|
213
|
+
return this.wsClient.sendRequest('usb.sendReport', { deviceId, reportId, data });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async receiveUsbReport(deviceId: string, reportId: number, timeout: number = 5000): Promise<{
|
|
217
|
+
success: boolean;
|
|
218
|
+
reportId: number;
|
|
219
|
+
bytesTransferred: number;
|
|
220
|
+
data: string;
|
|
221
|
+
error?: string;
|
|
222
|
+
timestamp: Date;
|
|
223
|
+
}> {
|
|
224
|
+
return this.wsClient.sendRequest('usb.receiveReport', { deviceId, reportId, timeout });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async getUsbDeviceStatus(deviceId: string): Promise<{
|
|
228
|
+
isConnected: boolean;
|
|
229
|
+
status: string;
|
|
230
|
+
deviceId: string;
|
|
231
|
+
vendorId?: number;
|
|
232
|
+
productId?: number;
|
|
233
|
+
version?: number;
|
|
234
|
+
isOpen?: boolean;
|
|
235
|
+
inputReportLength?: number;
|
|
236
|
+
outputReportLength?: number;
|
|
237
|
+
featureReportLength?: number;
|
|
238
|
+
connectedAt?: Date;
|
|
239
|
+
lastActivity?: Date;
|
|
240
|
+
error?: string;
|
|
241
|
+
}> {
|
|
242
|
+
return this.wsClient.sendRequest('usb.getStatus', { deviceId });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Queue Management
|
|
246
|
+
async getQueueStatus(): Promise<QueueStatus> {
|
|
247
|
+
return this.wsClient.sendRequest('queue.getStatus');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async getQueueJobs(deviceId?: string, status?: string, limit: number = 100): Promise<QueueJob[]> {
|
|
251
|
+
return this.wsClient.sendRequest('queue.getJobs', { deviceId, status, limit });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async cancelQueueJob(jobId: string): Promise<boolean> {
|
|
255
|
+
return this.wsClient.sendRequest('queue.cancelJob', { jobId });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// System Information
|
|
259
|
+
async getSystemInfo(): Promise<{
|
|
260
|
+
version: string;
|
|
261
|
+
platform: string;
|
|
262
|
+
timestamp: Date;
|
|
263
|
+
uptime: number;
|
|
264
|
+
}> {
|
|
265
|
+
return this.wsClient.sendRequest('system.getInfo');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async getSystemHealth(): Promise<SystemHealth> {
|
|
269
|
+
return this.wsClient.sendRequest('system.getHealth');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Event Handlers
|
|
273
|
+
onDeviceEvent(event: DeviceEvent): void {
|
|
274
|
+
// This can be overridden by subclasses or used to emit events
|
|
275
|
+
console.log('Device event:', event);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Utility Methods
|
|
279
|
+
async waitForConnection(timeout: number = 30000): Promise<void> {
|
|
280
|
+
return new Promise((resolve, reject) => {
|
|
281
|
+
if (this.isConnected) {
|
|
282
|
+
resolve();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const timeoutId = setTimeout(() => {
|
|
287
|
+
reject(new Error('Connection timeout'));
|
|
288
|
+
}, timeout);
|
|
289
|
+
|
|
290
|
+
const checkConnection = () => {
|
|
291
|
+
if (this.isConnected) {
|
|
292
|
+
clearTimeout(timeoutId);
|
|
293
|
+
resolve();
|
|
294
|
+
} else {
|
|
295
|
+
setTimeout(checkConnection, 100);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
checkConnection();
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
dispose(): void {
|
|
304
|
+
this.wsClient.dispose();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConnectionConfig,
|
|
3
|
+
ConnectionState,
|
|
4
|
+
ConnectionStatus,
|
|
5
|
+
JsonRpcRequest,
|
|
6
|
+
JsonRpcResponse,
|
|
7
|
+
ClientOptions,
|
|
8
|
+
DeviceEvent
|
|
9
|
+
} from '../types';
|
|
10
|
+
|
|
11
|
+
export class WebSocketClient {
|
|
12
|
+
private ws: WebSocket | null = null;
|
|
13
|
+
private config: ConnectionConfig;
|
|
14
|
+
private options: ClientOptions;
|
|
15
|
+
private connectionState: ConnectionState;
|
|
16
|
+
private connectionListeners: Array<(state: ConnectionState) => void> = [];
|
|
17
|
+
private messageListeners: Array<(message: JsonRpcResponse) => void> = [];
|
|
18
|
+
private deviceEventListeners: Array<(event: DeviceEvent) => void> = [];
|
|
19
|
+
private reconnectTimer: any = null;
|
|
20
|
+
private pingTimer: any = null;
|
|
21
|
+
private messageId = 0;
|
|
22
|
+
private pendingRequests = new Map<string | number, { resolve: Function; reject: Function; timeout: any }>();
|
|
23
|
+
|
|
24
|
+
constructor(config: ConnectionConfig, options: ClientOptions = {}) {
|
|
25
|
+
this.config = config;
|
|
26
|
+
this.options = {
|
|
27
|
+
autoReconnect: true,
|
|
28
|
+
reconnectInterval: 5000,
|
|
29
|
+
maxReconnectAttempts: 10,
|
|
30
|
+
timeout: 30000,
|
|
31
|
+
debug: false,
|
|
32
|
+
protocols: ['jsonrpc-2.0'],
|
|
33
|
+
...options
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
this.connectionState = {
|
|
37
|
+
connected: false,
|
|
38
|
+
connecting: false,
|
|
39
|
+
reconnectAttempts: 0
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onConnectionStateChange(listener: (state: ConnectionState) => void): void {
|
|
44
|
+
this.connectionListeners.push(listener);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onMessage(listener: (message: JsonRpcResponse) => void): void {
|
|
48
|
+
this.messageListeners.push(listener);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onDeviceEvent(listener: (event: DeviceEvent) => void): void {
|
|
52
|
+
this.deviceEventListeners.push(listener);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get isConnected(): boolean {
|
|
56
|
+
return this.connectionState.connected;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get isConnecting(): boolean {
|
|
60
|
+
return this.connectionState.connecting;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getConnectionStatus(): ConnectionStatus {
|
|
64
|
+
if (this.connectionState.connecting) return 'connecting';
|
|
65
|
+
if (this.connectionState.connected) return 'connected';
|
|
66
|
+
if (this.connectionState.reconnectAttempts > 0) return 'reconnecting';
|
|
67
|
+
return 'disconnected';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async connect(): Promise<void> {
|
|
71
|
+
if (this.isConnected || this.isConnecting) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.updateConnectionState({ connecting: true });
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await this.createWebSocket();
|
|
79
|
+
this.updateConnectionState({
|
|
80
|
+
connected: true,
|
|
81
|
+
connecting: false,
|
|
82
|
+
reconnectAttempts: 0,
|
|
83
|
+
lastConnectionTime: new Date()
|
|
84
|
+
});
|
|
85
|
+
this.startPingTimer();
|
|
86
|
+
this.log('Connected to WebSocket server');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this.updateConnectionState({ connecting: false, error: (error as Error).message });
|
|
89
|
+
this.log('Failed to connect to WebSocket server', error);
|
|
90
|
+
|
|
91
|
+
if (this.options.autoReconnect) {
|
|
92
|
+
this.scheduleReconnect();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
disconnect(): void {
|
|
100
|
+
this.log('Disconnecting from WebSocket server');
|
|
101
|
+
|
|
102
|
+
this.cleanup();
|
|
103
|
+
|
|
104
|
+
if (this.ws) {
|
|
105
|
+
this.ws.close(1000, 'Client disconnect');
|
|
106
|
+
this.ws = null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.updateConnectionState({
|
|
110
|
+
connected: false,
|
|
111
|
+
connecting: false,
|
|
112
|
+
error: undefined
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async sendRequest<T = any>(method: string, params?: any): Promise<T> {
|
|
117
|
+
if (!this.isConnected) {
|
|
118
|
+
throw new Error('WebSocket client is not connected');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const id = ++this.messageId;
|
|
122
|
+
const request: JsonRpcRequest = {
|
|
123
|
+
jsonrpc: '2.0',
|
|
124
|
+
method,
|
|
125
|
+
params,
|
|
126
|
+
id
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const timeout = setTimeout(() => {
|
|
131
|
+
this.pendingRequests.delete(id);
|
|
132
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
133
|
+
}, this.options.timeout);
|
|
134
|
+
|
|
135
|
+
this.pendingRequests.set(id, { resolve, reject, timeout });
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
this.sendMessage(request);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
this.pendingRequests.delete(id);
|
|
142
|
+
reject(error);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
sendNotification(method: string, params?: any): void {
|
|
148
|
+
if (!this.isConnected) {
|
|
149
|
+
throw new Error('WebSocket client is not connected');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const notification: JsonRpcRequest = {
|
|
153
|
+
jsonrpc: '2.0',
|
|
154
|
+
method,
|
|
155
|
+
params
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
this.sendMessage(notification);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private async createWebSocket(): Promise<void> {
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
try {
|
|
164
|
+
const protocols = this.options.protocols || [];
|
|
165
|
+
const ws = new WebSocket(this.config.url, protocols);
|
|
166
|
+
|
|
167
|
+
ws.onopen = () => {
|
|
168
|
+
this.log('WebSocket connection opened');
|
|
169
|
+
resolve();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
ws.onclose = (event) => {
|
|
173
|
+
this.log(`WebSocket connection closed: ${event.code} ${event.reason}`);
|
|
174
|
+
this.handleDisconnect();
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
ws.onerror = (error) => {
|
|
178
|
+
this.log('WebSocket error', error);
|
|
179
|
+
reject(new Error('WebSocket connection failed'));
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
ws.onmessage = (event) => {
|
|
183
|
+
this.handleMessage(event.data);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
this.ws = ws;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
reject(error);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private handleMessage(data: string): void {
|
|
194
|
+
try {
|
|
195
|
+
const message: JsonRpcResponse = JSON.parse(data);
|
|
196
|
+
|
|
197
|
+
if (message.jsonrpc !== '2.0') {
|
|
198
|
+
this.log('Invalid JSON-RPC message received', message);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Handle responses to requests
|
|
203
|
+
if (message.id !== undefined && message.id !== null) {
|
|
204
|
+
const pendingRequest = this.pendingRequests.get(message.id);
|
|
205
|
+
if (pendingRequest) {
|
|
206
|
+
clearTimeout(pendingRequest.timeout);
|
|
207
|
+
this.pendingRequests.delete(message.id);
|
|
208
|
+
|
|
209
|
+
if (message.error) {
|
|
210
|
+
pendingRequest.reject(new Error(`JSON-RPC Error ${message.error.code}: ${message.error.message}`));
|
|
211
|
+
} else {
|
|
212
|
+
pendingRequest.resolve(message.result);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Handle notifications and broadcast messages
|
|
218
|
+
if (message.id === undefined || message.id === null) {
|
|
219
|
+
// Notify message listeners
|
|
220
|
+
this.messageListeners.forEach(listener => listener(message));
|
|
221
|
+
|
|
222
|
+
// Handle device events (check if it's a notification with method and params)
|
|
223
|
+
const notification = message as any;
|
|
224
|
+
if (notification.method === 'device.event' && notification.params) {
|
|
225
|
+
const deviceEvent = notification.params as DeviceEvent;
|
|
226
|
+
this.deviceEventListeners.forEach(listener => listener(deviceEvent));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
} catch (error) {
|
|
231
|
+
this.log('Error parsing WebSocket message', error);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private handleDisconnect(): void {
|
|
236
|
+
this.cleanup();
|
|
237
|
+
|
|
238
|
+
this.updateConnectionState({
|
|
239
|
+
connected: false,
|
|
240
|
+
connecting: false
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (this.options.autoReconnect) {
|
|
244
|
+
this.scheduleReconnect();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private scheduleReconnect(): void {
|
|
249
|
+
if (this.reconnectTimer) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const currentState = this.connectionState;
|
|
254
|
+
if (currentState.reconnectAttempts >= (this.options.maxReconnectAttempts || 10)) {
|
|
255
|
+
this.log('Max reconnection attempts reached');
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const nextAttempt = currentState.reconnectAttempts + 1;
|
|
260
|
+
this.log(`Scheduling reconnection attempt ${nextAttempt}`);
|
|
261
|
+
|
|
262
|
+
this.reconnectTimer = setTimeout(() => {
|
|
263
|
+
this.reconnectTimer = null;
|
|
264
|
+
this.updateConnectionState({ reconnectAttempts: nextAttempt });
|
|
265
|
+
this.connect().catch(() => {
|
|
266
|
+
// Reconnection failed, will be handled by scheduleReconnect
|
|
267
|
+
});
|
|
268
|
+
}, this.options.reconnectInterval);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private startPingTimer(): void {
|
|
272
|
+
if (this.pingTimer) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Send ping every 30 seconds to keep connection alive
|
|
277
|
+
this.pingTimer = setInterval(() => {
|
|
278
|
+
if (this.isConnected) {
|
|
279
|
+
try {
|
|
280
|
+
this.sendNotification('ping');
|
|
281
|
+
} catch (error) {
|
|
282
|
+
this.log('Ping failed', error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}, 30000);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private cleanup(): void {
|
|
289
|
+
// Clear timers
|
|
290
|
+
if (this.reconnectTimer) {
|
|
291
|
+
clearTimeout(this.reconnectTimer);
|
|
292
|
+
this.reconnectTimer = null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (this.pingTimer) {
|
|
296
|
+
clearInterval(this.pingTimer);
|
|
297
|
+
this.pingTimer = null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Clear pending requests
|
|
301
|
+
for (const request of this.pendingRequests.values()) {
|
|
302
|
+
clearTimeout(request.timeout);
|
|
303
|
+
request.reject(new Error('Connection closed'));
|
|
304
|
+
}
|
|
305
|
+
this.pendingRequests.clear();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private sendMessage(message: JsonRpcRequest): void {
|
|
309
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
310
|
+
throw new Error('WebSocket is not open');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const data = JSON.stringify(message);
|
|
314
|
+
this.ws.send(data);
|
|
315
|
+
this.log(`Sent message: ${message.method}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private updateConnectionState(updates: Partial<ConnectionState>): void {
|
|
319
|
+
this.connectionState = { ...this.connectionState, ...updates };
|
|
320
|
+
this.connectionListeners.forEach(listener => listener(this.connectionState));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private log(message: string, data?: any): void {
|
|
324
|
+
if (this.options.debug) {
|
|
325
|
+
console.log(`[HardwareBridge] ${message}`, data);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
dispose(): void {
|
|
330
|
+
this.log('Disposing WebSocket client');
|
|
331
|
+
this.disconnect();
|
|
332
|
+
|
|
333
|
+
this.connectionListeners = [];
|
|
334
|
+
this.messageListeners = [];
|
|
335
|
+
this.deviceEventListeners = [];
|
|
336
|
+
}
|
|
337
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Core exports
|
|
2
|
+
export { HardwareBridgeClient } from './core/hardware-bridge-client';
|
|
3
|
+
export { WebSocketClient } from './core/websocket-client';
|
|
4
|
+
|
|
5
|
+
// Type exports
|
|
6
|
+
export type {
|
|
7
|
+
DeviceInfo,
|
|
8
|
+
PrinterDevice,
|
|
9
|
+
SerialPortDevice,
|
|
10
|
+
UsbHidDevice,
|
|
11
|
+
ConnectionConfig,
|
|
12
|
+
ConnectionState,
|
|
13
|
+
ConnectionStatus,
|
|
14
|
+
ClientOptions,
|
|
15
|
+
JsonRpcRequest,
|
|
16
|
+
JsonRpcResponse,
|
|
17
|
+
JsonRpcError,
|
|
18
|
+
PrintJob,
|
|
19
|
+
PrintResult,
|
|
20
|
+
PrintFormat,
|
|
21
|
+
SerialPortConfig,
|
|
22
|
+
SerialData,
|
|
23
|
+
UsbHidReport,
|
|
24
|
+
QueueJob,
|
|
25
|
+
QueueStatus,
|
|
26
|
+
SystemHealth,
|
|
27
|
+
DeviceEvent,
|
|
28
|
+
DeviceType,
|
|
29
|
+
JobStatus,
|
|
30
|
+
ConnectionStatus as ConnectionStatusType
|
|
31
|
+
} from './types';
|
|
32
|
+
|
|
33
|
+
// Version
|
|
34
|
+
export const VERSION = '1.0.0';
|
|
35
|
+
|
|
36
|
+
// Default export
|
|
37
|
+
// Default export removed due to TypeScript compilation issues
|