@ukeyfe/hardware-transport-web-device 1.1.13
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 +29 -0
- package/dist/electron-ble-transport.d.ts +44 -0
- package/dist/electron-ble-transport.d.ts.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +552 -0
- package/dist/webusb.d.ts +33 -0
- package/dist/webusb.d.ts.map +1 -0
- package/package.json +31 -0
- package/src/electron-ble-transport.ts +413 -0
- package/src/index.ts +4 -0
- package/src/webusb.ts +298 -0
- package/tsconfig.json +11 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var transport = require('@ukeyfe/hardware-transport');
|
|
6
|
+
var hardwareShared = require('@ukeyfe/hardware-shared');
|
|
7
|
+
var ByteBuffer = require('bytebuffer');
|
|
8
|
+
|
|
9
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
|
+
|
|
11
|
+
var transport__default = /*#__PURE__*/_interopDefaultLegacy(transport);
|
|
12
|
+
var ByteBuffer__default = /*#__PURE__*/_interopDefaultLegacy(ByteBuffer);
|
|
13
|
+
|
|
14
|
+
/******************************************************************************
|
|
15
|
+
Copyright (c) Microsoft Corporation.
|
|
16
|
+
|
|
17
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
18
|
+
purpose with or without fee is hereby granted.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
21
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
22
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
23
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
24
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
25
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
26
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
27
|
+
***************************************************************************** */
|
|
28
|
+
|
|
29
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
30
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
31
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
32
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
33
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
34
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
35
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
40
|
+
var e = new Error(message);
|
|
41
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const { parseConfigure: parseConfigure$1, buildEncodeBuffers, decodeProtocol, receiveOne: receiveOne$1, check: check$1 } = transport__default["default"];
|
|
45
|
+
const CONFIGURATION_ID = 1;
|
|
46
|
+
const INTERFACE_ID = 0;
|
|
47
|
+
const ENDPOINT_ID = 1;
|
|
48
|
+
const PACKET_SIZE = 64;
|
|
49
|
+
const HEADER_LENGTH = 6;
|
|
50
|
+
class WebUsbTransport {
|
|
51
|
+
constructor() {
|
|
52
|
+
this.name = 'WebUsbTransport';
|
|
53
|
+
this.stopped = false;
|
|
54
|
+
this.configured = false;
|
|
55
|
+
this.deviceList = [];
|
|
56
|
+
this.configurationId = CONFIGURATION_ID;
|
|
57
|
+
this.endpointId = ENDPOINT_ID;
|
|
58
|
+
this.interfaceId = INTERFACE_ID;
|
|
59
|
+
}
|
|
60
|
+
init(logger) {
|
|
61
|
+
this.Log = logger;
|
|
62
|
+
const { usb } = navigator;
|
|
63
|
+
if (!usb) {
|
|
64
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.RuntimeError, 'WebUSB is not supported by current browsers');
|
|
65
|
+
}
|
|
66
|
+
this.usb = usb;
|
|
67
|
+
}
|
|
68
|
+
configure(signedData) {
|
|
69
|
+
const messages = parseConfigure$1(signedData);
|
|
70
|
+
this.configured = true;
|
|
71
|
+
this.messages = messages;
|
|
72
|
+
}
|
|
73
|
+
promptDeviceAccess() {
|
|
74
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
if (!this.usb)
|
|
76
|
+
return null;
|
|
77
|
+
try {
|
|
78
|
+
const device = yield this.usb.requestDevice({ filters: hardwareShared.UKEY_WEBUSB_FILTER });
|
|
79
|
+
return device;
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
this.Log.debug('requestDevice error: ', e);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
enumerate() {
|
|
88
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
+
yield this.getConnectedDevices();
|
|
90
|
+
return this.deviceList;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
getConnectedDevices() {
|
|
94
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
95
|
+
if (!this.usb)
|
|
96
|
+
return [];
|
|
97
|
+
const devices = yield this.usb.getDevices();
|
|
98
|
+
const ukeyDevices = devices.filter(dev => {
|
|
99
|
+
const isUKey = hardwareShared.UKEY_WEBUSB_FILTER.some(desc => dev.vendorId === desc.vendorId && dev.productId === desc.productId);
|
|
100
|
+
const hasSerialNumber = typeof dev.serialNumber === 'string' && dev.serialNumber.length > 0;
|
|
101
|
+
return isUKey && hasSerialNumber;
|
|
102
|
+
});
|
|
103
|
+
this.deviceList = ukeyDevices.map(device => ({
|
|
104
|
+
path: device.serialNumber,
|
|
105
|
+
device,
|
|
106
|
+
}));
|
|
107
|
+
return this.deviceList;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
acquire(input) {
|
|
111
|
+
var _a;
|
|
112
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
113
|
+
if (!input.path)
|
|
114
|
+
return;
|
|
115
|
+
try {
|
|
116
|
+
yield this.connect((_a = input.path) !== null && _a !== void 0 ? _a : '', true);
|
|
117
|
+
return yield Promise.resolve(input.path);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
this.Log.debug('acquire error: ', e);
|
|
121
|
+
throw e;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
findDevice(path) {
|
|
126
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
127
|
+
if (this.deviceList.length === 0) {
|
|
128
|
+
yield this.getConnectedDevices();
|
|
129
|
+
}
|
|
130
|
+
let device = this.deviceList.find(d => d.path === path);
|
|
131
|
+
if (device == null) {
|
|
132
|
+
yield this.getConnectedDevices();
|
|
133
|
+
device = this.deviceList.find(d => d.path === path);
|
|
134
|
+
if (device == null) {
|
|
135
|
+
throw new Error('Action was interrupted.');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return device.device;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
connect(path, first) {
|
|
142
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
143
|
+
const maxRetries = 5;
|
|
144
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
145
|
+
try {
|
|
146
|
+
return yield this.connectToDevice(path, first);
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
if (i === maxRetries - 1) {
|
|
150
|
+
throw e;
|
|
151
|
+
}
|
|
152
|
+
yield hardwareShared.wait(i * 200);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
connectToDevice(path, first) {
|
|
158
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
159
|
+
const device = yield this.findDevice(path);
|
|
160
|
+
yield device.open();
|
|
161
|
+
if (first) {
|
|
162
|
+
yield device.selectConfiguration(this.configurationId);
|
|
163
|
+
try {
|
|
164
|
+
yield device.reset();
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
yield device.claimInterface(this.interfaceId);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
post(session, name, data) {
|
|
173
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
174
|
+
yield this.call(session, name, data);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
call(path, name, data) {
|
|
178
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
179
|
+
if (this.messages == null) {
|
|
180
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.TransportNotConfigured);
|
|
181
|
+
}
|
|
182
|
+
const device = yield this.findDevice(path);
|
|
183
|
+
if (!device) {
|
|
184
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.DeviceNotFound);
|
|
185
|
+
}
|
|
186
|
+
const { messages } = this;
|
|
187
|
+
if (transport.LogBlockCommand.has(name)) {
|
|
188
|
+
this.Log.debug('call-', ' name: ', name);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.Log.debug('call-', ' name: ', name, ' data: ', data);
|
|
192
|
+
}
|
|
193
|
+
const encodeBuffers = buildEncodeBuffers(messages, name, data);
|
|
194
|
+
for (const buffer of encodeBuffers) {
|
|
195
|
+
const newArray = new Uint8Array(PACKET_SIZE);
|
|
196
|
+
newArray[0] = 63;
|
|
197
|
+
newArray.set(new Uint8Array(buffer), 1);
|
|
198
|
+
if (!device.opened) {
|
|
199
|
+
yield this.connect(path, false);
|
|
200
|
+
}
|
|
201
|
+
yield device.transferOut(this.endpointId, newArray);
|
|
202
|
+
}
|
|
203
|
+
const resData = yield this.receiveData(path);
|
|
204
|
+
if (typeof resData !== 'string') {
|
|
205
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.NetworkError, 'Returning data is not string.');
|
|
206
|
+
}
|
|
207
|
+
const jsonData = receiveOne$1(messages, resData);
|
|
208
|
+
return check$1.call(jsonData);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
receiveData(path) {
|
|
212
|
+
var _a;
|
|
213
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
214
|
+
const device = yield this.findDevice(path);
|
|
215
|
+
if (!device.opened) {
|
|
216
|
+
yield this.connect(path, false);
|
|
217
|
+
}
|
|
218
|
+
const firstPacket = yield device.transferIn(this.endpointId, PACKET_SIZE);
|
|
219
|
+
const firstData = (_a = firstPacket.data) === null || _a === void 0 ? void 0 : _a.buffer.slice(1);
|
|
220
|
+
console.log('receive first packet: ', firstPacket);
|
|
221
|
+
const { length, typeId, restBuffer } = decodeProtocol.decodeChunked(firstData);
|
|
222
|
+
console.log('chunk length: ', length);
|
|
223
|
+
const lengthWithHeader = Number(length + HEADER_LENGTH);
|
|
224
|
+
const decoded = new ByteBuffer__default["default"](lengthWithHeader);
|
|
225
|
+
decoded.writeUint16(typeId);
|
|
226
|
+
decoded.writeUint32(length);
|
|
227
|
+
if (length) {
|
|
228
|
+
decoded.append(restBuffer);
|
|
229
|
+
}
|
|
230
|
+
console.log('first decoded: ', decoded);
|
|
231
|
+
while (decoded.offset < lengthWithHeader) {
|
|
232
|
+
const res = yield device.transferIn(this.endpointId, PACKET_SIZE);
|
|
233
|
+
if (!res.data) {
|
|
234
|
+
throw new Error('no data');
|
|
235
|
+
}
|
|
236
|
+
if (res.data.byteLength === 0) {
|
|
237
|
+
console.warn('empty data');
|
|
238
|
+
}
|
|
239
|
+
const buffer = res.data.buffer.slice(1);
|
|
240
|
+
if (lengthWithHeader - decoded.offset >= PACKET_SIZE) {
|
|
241
|
+
decoded.append(buffer);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
decoded.append(buffer.slice(0, lengthWithHeader - decoded.offset));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
decoded.reset();
|
|
248
|
+
const result = decoded.toBuffer();
|
|
249
|
+
return Buffer.from(result).toString('hex');
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
release(path) {
|
|
253
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
254
|
+
const device = yield this.findDevice(path);
|
|
255
|
+
yield device.releaseInterface(this.interfaceId);
|
|
256
|
+
yield device.close();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const { parseConfigure, buildBuffers, receiveOne, check } = transport__default["default"];
|
|
262
|
+
class ElectronBleTransport {
|
|
263
|
+
constructor() {
|
|
264
|
+
this.name = 'ElectronBleTransport';
|
|
265
|
+
this.configured = false;
|
|
266
|
+
this.runPromise = null;
|
|
267
|
+
this.connectedDevices = new Set();
|
|
268
|
+
this.dataBuffers = new Map();
|
|
269
|
+
this.notificationCleanups = new Map();
|
|
270
|
+
this.disconnectCleanups = new Map();
|
|
271
|
+
}
|
|
272
|
+
handleBluetoothError(error) {
|
|
273
|
+
if (error && typeof error === 'object') {
|
|
274
|
+
if ('code' in error) {
|
|
275
|
+
if (error.code === hardwareShared.HardwareErrorCode.BlePoweredOff) {
|
|
276
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BlePoweredOff);
|
|
277
|
+
}
|
|
278
|
+
if (error.code === hardwareShared.HardwareErrorCode.BleUnsupported) {
|
|
279
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleUnsupported);
|
|
280
|
+
}
|
|
281
|
+
if (error.code === hardwareShared.HardwareErrorCode.BlePermissionError) {
|
|
282
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BlePermissionError);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const errorMessage = error.message || String(error);
|
|
286
|
+
const poweredOffMessage = hardwareShared.HardwareErrorCodeMessage[hardwareShared.HardwareErrorCode.BlePoweredOff];
|
|
287
|
+
const unsupportedMessage = hardwareShared.HardwareErrorCodeMessage[hardwareShared.HardwareErrorCode.BleUnsupported];
|
|
288
|
+
const permissionMessage = hardwareShared.HardwareErrorCodeMessage[hardwareShared.HardwareErrorCode.BlePermissionError];
|
|
289
|
+
if (errorMessage.includes(poweredOffMessage) || errorMessage.includes('poweredOff')) {
|
|
290
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BlePoweredOff);
|
|
291
|
+
}
|
|
292
|
+
if (errorMessage.includes(unsupportedMessage) || errorMessage.includes('unsupported')) {
|
|
293
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleUnsupported);
|
|
294
|
+
}
|
|
295
|
+
if (errorMessage.includes(permissionMessage) || errorMessage.includes('unauthorized')) {
|
|
296
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BlePermissionError);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
cleanupDeviceState(deviceId) {
|
|
302
|
+
this.connectedDevices.delete(deviceId);
|
|
303
|
+
this.dataBuffers.delete(deviceId);
|
|
304
|
+
const notifyCleanup = this.notificationCleanups.get(deviceId);
|
|
305
|
+
if (notifyCleanup) {
|
|
306
|
+
notifyCleanup();
|
|
307
|
+
this.notificationCleanups.delete(deviceId);
|
|
308
|
+
}
|
|
309
|
+
const disconnectCleanup = this.disconnectCleanups.get(deviceId);
|
|
310
|
+
if (disconnectCleanup) {
|
|
311
|
+
disconnectCleanup();
|
|
312
|
+
this.disconnectCleanups.delete(deviceId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
init(logger, emitter) {
|
|
316
|
+
var _a, _b;
|
|
317
|
+
this.Log = logger;
|
|
318
|
+
this.emitter = emitter;
|
|
319
|
+
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
320
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.RuntimeError, 'Noble BLE API is not available. Please ensure you are running in Electron with Noble support.');
|
|
321
|
+
}
|
|
322
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Transport] Noble BLE Transport initialized');
|
|
323
|
+
}
|
|
324
|
+
configure(signedData) {
|
|
325
|
+
const messages = parseConfigure(signedData);
|
|
326
|
+
this.configured = true;
|
|
327
|
+
this._messages = messages;
|
|
328
|
+
}
|
|
329
|
+
listen() { }
|
|
330
|
+
enumerate() {
|
|
331
|
+
var _a, _b;
|
|
332
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
333
|
+
try {
|
|
334
|
+
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
335
|
+
throw new Error('Noble BLE API not available');
|
|
336
|
+
}
|
|
337
|
+
const devices = yield window.desktopApi.nobleBle.enumerate();
|
|
338
|
+
return devices;
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Transport] Noble BLE enumerate failed:', error);
|
|
342
|
+
this.handleBluetoothError(error);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
acquire(input) {
|
|
347
|
+
var _a, _b, _c;
|
|
348
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
349
|
+
const { uuid, forceCleanRunPromise } = input;
|
|
350
|
+
if (!uuid) {
|
|
351
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleRequiredUUID);
|
|
352
|
+
}
|
|
353
|
+
if (forceCleanRunPromise && this.runPromise) {
|
|
354
|
+
this.runPromise.reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleForceCleanRunPromise));
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
if (!((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle)) {
|
|
358
|
+
throw new Error('Noble BLE API not available');
|
|
359
|
+
}
|
|
360
|
+
const device = yield window.desktopApi.nobleBle.getDevice(uuid);
|
|
361
|
+
if (!device) {
|
|
362
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.DeviceNotFound, `Device ${uuid} not found`);
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
yield window.desktopApi.nobleBle.connect(uuid);
|
|
366
|
+
this.connectedDevices.add(uuid);
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
this.handleBluetoothError(error);
|
|
370
|
+
}
|
|
371
|
+
this.dataBuffers.set(uuid, { buffer: [], bufferLength: 0 });
|
|
372
|
+
yield window.desktopApi.nobleBle.subscribe(uuid);
|
|
373
|
+
const cleanup = window.desktopApi.nobleBle.onNotification((deviceId, data) => {
|
|
374
|
+
if (deviceId === uuid) {
|
|
375
|
+
this.handleNotificationData(uuid, data);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
this.notificationCleanups.set(uuid, cleanup);
|
|
379
|
+
const disconnectCleanup = window.desktopApi.nobleBle.onDeviceDisconnected((disconnectedDevice) => {
|
|
380
|
+
var _a;
|
|
381
|
+
if (disconnectedDevice.id === uuid) {
|
|
382
|
+
this.cleanupDeviceState(uuid);
|
|
383
|
+
(_a = this.emitter) === null || _a === void 0 ? void 0 : _a.emit('device-disconnect', {
|
|
384
|
+
name: disconnectedDevice.name,
|
|
385
|
+
id: disconnectedDevice.id,
|
|
386
|
+
connectId: disconnectedDevice.id,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
this.disconnectCleanups.set(uuid, disconnectCleanup);
|
|
391
|
+
(_b = this.emitter) === null || _b === void 0 ? void 0 : _b.emit('device-connect', {
|
|
392
|
+
name: device.name,
|
|
393
|
+
id: device.id,
|
|
394
|
+
connectId: device.id,
|
|
395
|
+
});
|
|
396
|
+
return { uuid, path: uuid };
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Transport] Noble BLE acquire failed:', error);
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
release(id) {
|
|
405
|
+
var _a, _b, _c;
|
|
406
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
407
|
+
try {
|
|
408
|
+
if (this.connectedDevices.has(id)) {
|
|
409
|
+
if ((_a = window.desktopApi) === null || _a === void 0 ? void 0 : _a.nobleBle) {
|
|
410
|
+
yield window.desktopApi.nobleBle.unsubscribe(id);
|
|
411
|
+
}
|
|
412
|
+
if ((_b = window.desktopApi) === null || _b === void 0 ? void 0 : _b.nobleBle) {
|
|
413
|
+
yield window.desktopApi.nobleBle.disconnect(id);
|
|
414
|
+
}
|
|
415
|
+
this.cleanupDeviceState(id);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.error('[Transport] Noble BLE release failed:', error);
|
|
420
|
+
this.cleanupDeviceState(id);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
handleNotificationData(deviceId, hexData) {
|
|
425
|
+
var _a, _b;
|
|
426
|
+
if (hexData === 'PAIRING_REJECTED') {
|
|
427
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Transport] Pairing rejection detected for device:', deviceId);
|
|
428
|
+
if (this.runPromise) {
|
|
429
|
+
this.runPromise.reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleDeviceBondedCanceled));
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const result = this.processNotificationPacket(deviceId, hexData);
|
|
434
|
+
if (result.error) {
|
|
435
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.error('[Transport] Packet processing error:', result.error);
|
|
436
|
+
if (this.runPromise) {
|
|
437
|
+
this.runPromise.reject(hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.BleWriteCharacteristicError));
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (result.isComplete && result.completePacket) {
|
|
442
|
+
if (this.runPromise) {
|
|
443
|
+
this.runPromise.resolve(result.completePacket);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
call(uuid, name, data) {
|
|
448
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
449
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
450
|
+
if (this._messages == null) {
|
|
451
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.TransportNotConfigured);
|
|
452
|
+
}
|
|
453
|
+
const forceRun = name === 'Initialize' || name === 'Cancel';
|
|
454
|
+
if (this.runPromise && !forceRun) {
|
|
455
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.TransportCallInProgress);
|
|
456
|
+
}
|
|
457
|
+
if (!this.connectedDevices.has(uuid)) {
|
|
458
|
+
throw hardwareShared.ERRORS.TypedError(hardwareShared.HardwareErrorCode.TransportNotFound, `Device ${uuid} not connected`);
|
|
459
|
+
}
|
|
460
|
+
this.runPromise = hardwareShared.createDeferred();
|
|
461
|
+
const messages = this._messages;
|
|
462
|
+
if (name === 'ResourceUpdate' || name === 'ResourceAck') {
|
|
463
|
+
(_a = this.Log) === null || _a === void 0 ? void 0 : _a.debug('[Transport] Noble BLE call', 'name:', name, 'data:', {
|
|
464
|
+
file_name: data === null || data === void 0 ? void 0 : data.file_name,
|
|
465
|
+
hash: data === null || data === void 0 ? void 0 : data.hash,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
else if (transport.LogBlockCommand.has(name)) {
|
|
469
|
+
(_b = this.Log) === null || _b === void 0 ? void 0 : _b.debug('[Transport] Noble BLE call', 'name:', name);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
(_c = this.Log) === null || _c === void 0 ? void 0 : _c.debug('[Transport] Noble BLE call', 'name:', name, 'data:', data);
|
|
473
|
+
}
|
|
474
|
+
const buffers = buildBuffers(messages, name, data);
|
|
475
|
+
try {
|
|
476
|
+
if (!((_d = window.desktopApi) === null || _d === void 0 ? void 0 : _d.nobleBle)) {
|
|
477
|
+
throw new Error('Noble BLE write API not available');
|
|
478
|
+
}
|
|
479
|
+
for (let i = 0; i < buffers.length; i++) {
|
|
480
|
+
const buffer = buffers[i];
|
|
481
|
+
if (!buffer || typeof buffer.toString !== 'function') {
|
|
482
|
+
(_e = this.Log) === null || _e === void 0 ? void 0 : _e.error(`[Transport] Noble BLE buffer ${i + 1} is invalid:`, buffer);
|
|
483
|
+
throw new Error(`Buffer ${i + 1} is invalid`);
|
|
484
|
+
}
|
|
485
|
+
const hexString = buffer.toString('hex');
|
|
486
|
+
if (hexString.length === 0) {
|
|
487
|
+
(_f = this.Log) === null || _f === void 0 ? void 0 : _f.error(`[Transport] Noble BLE buffer ${i + 1} generated empty hex string`);
|
|
488
|
+
throw new Error(`Buffer ${i + 1} is empty`);
|
|
489
|
+
}
|
|
490
|
+
yield window.desktopApi.nobleBle.write(uuid, hexString);
|
|
491
|
+
}
|
|
492
|
+
const response = yield this.runPromise.promise;
|
|
493
|
+
if (typeof response !== 'string') {
|
|
494
|
+
throw new Error('Returning data is not string.');
|
|
495
|
+
}
|
|
496
|
+
const jsonData = receiveOne(messages, response);
|
|
497
|
+
return check.call(jsonData);
|
|
498
|
+
}
|
|
499
|
+
catch (e) {
|
|
500
|
+
(_g = this.Log) === null || _g === void 0 ? void 0 : _g.error('[Transport] Noble BLE call error:', e);
|
|
501
|
+
throw e;
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
this.runPromise = null;
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
processNotificationPacket(deviceId, hexData) {
|
|
509
|
+
try {
|
|
510
|
+
if (typeof hexData !== 'string') {
|
|
511
|
+
return { isComplete: false, error: 'Invalid hexData type' };
|
|
512
|
+
}
|
|
513
|
+
const cleanHexData = hexData.replace(/\s+/g, '');
|
|
514
|
+
if (!/^[0-9A-Fa-f]*$/.test(cleanHexData)) {
|
|
515
|
+
return { isComplete: false, error: 'Invalid hex data format' };
|
|
516
|
+
}
|
|
517
|
+
const hexMatch = cleanHexData.match(/.{1,2}/g);
|
|
518
|
+
if (!hexMatch) {
|
|
519
|
+
return { isComplete: false, error: 'Failed to parse hex data' };
|
|
520
|
+
}
|
|
521
|
+
const data = new Uint8Array(hexMatch.map(byte => parseInt(byte, 16)));
|
|
522
|
+
const bufferState = this.dataBuffers.get(deviceId);
|
|
523
|
+
if (!bufferState) {
|
|
524
|
+
return { isComplete: false, error: 'No buffer state for device' };
|
|
525
|
+
}
|
|
526
|
+
if (hardwareShared.isHeaderChunk(data)) {
|
|
527
|
+
const dataView = new DataView(data.buffer);
|
|
528
|
+
bufferState.bufferLength = dataView.getInt32(5, false);
|
|
529
|
+
bufferState.buffer = [...data.subarray(3)];
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
bufferState.buffer = bufferState.buffer.concat([...data]);
|
|
533
|
+
}
|
|
534
|
+
if (bufferState.buffer.length - transport.COMMON_HEADER_SIZE >= bufferState.bufferLength) {
|
|
535
|
+
const completeBuffer = new Uint8Array(bufferState.buffer);
|
|
536
|
+
bufferState.bufferLength = 0;
|
|
537
|
+
bufferState.buffer = [];
|
|
538
|
+
const hexString = Array.from(completeBuffer)
|
|
539
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
540
|
+
.join('');
|
|
541
|
+
return { isComplete: true, completePacket: hexString };
|
|
542
|
+
}
|
|
543
|
+
return { isComplete: false };
|
|
544
|
+
}
|
|
545
|
+
catch (error) {
|
|
546
|
+
return { isComplete: false, error: `Packet processing error: ${error}` };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
exports.ElectronBleTransport = ElectronBleTransport;
|
|
552
|
+
exports.WebUsbTransport = WebUsbTransport;
|
package/dist/webusb.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/// <reference types="w3c-web-usb" />
|
|
2
|
+
import transport, { AcquireInput } from '@ukeyfe/hardware-transport';
|
|
3
|
+
interface DeviceInfo {
|
|
4
|
+
path: string;
|
|
5
|
+
device: USBDevice;
|
|
6
|
+
}
|
|
7
|
+
export default class WebUsbTransport {
|
|
8
|
+
messages: ReturnType<typeof transport.parseConfigure> | undefined;
|
|
9
|
+
name: string;
|
|
10
|
+
stopped: boolean;
|
|
11
|
+
configured: boolean;
|
|
12
|
+
Log?: any;
|
|
13
|
+
usb?: USB;
|
|
14
|
+
deviceList: Array<DeviceInfo>;
|
|
15
|
+
configurationId: number;
|
|
16
|
+
endpointId: number;
|
|
17
|
+
interfaceId: number;
|
|
18
|
+
init(logger: any): void;
|
|
19
|
+
configure(signedData: any): void;
|
|
20
|
+
promptDeviceAccess(): Promise<USBDevice | null>;
|
|
21
|
+
enumerate(): Promise<DeviceInfo[]>;
|
|
22
|
+
getConnectedDevices(): Promise<DeviceInfo[]>;
|
|
23
|
+
acquire(input: AcquireInput): Promise<string | undefined>;
|
|
24
|
+
findDevice(path: string): Promise<USBDevice>;
|
|
25
|
+
connect(path: string, first: boolean): Promise<void>;
|
|
26
|
+
connectToDevice(path: string, first: boolean): Promise<void>;
|
|
27
|
+
post(session: string, name: string, data: Record<string, unknown>): Promise<void>;
|
|
28
|
+
call(path: string, name: string, data: Record<string, unknown>): Promise<import("@ukeyfe/hardware-transport").MessageFromUKey>;
|
|
29
|
+
receiveData(path: string): Promise<string>;
|
|
30
|
+
release(path: string): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=webusb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webusb.d.ts","sourceRoot":"","sources":["../src/webusb.ts"],"names":[],"mappings":";AACA,OAAO,SAAS,EAAE,EAAE,YAAY,EAAmB,MAAM,4BAA4B,CAAC;AAetF,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,MAAM,CAAC,OAAO,OAAO,eAAe;IAClC,QAAQ,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAElE,IAAI,SAAqB;IAEzB,OAAO,UAAS;IAEhB,UAAU,UAAS;IAEnB,GAAG,CAAC,EAAE,GAAG,CAAC;IAEV,GAAG,CAAC,EAAE,GAAG,CAAC;IAMV,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAM;IAEnC,eAAe,SAAoB;IAEnC,UAAU,SAAe;IAEzB,WAAW,SAAgB;IAK3B,IAAI,CAAC,MAAM,EAAE,GAAG;IAgBhB,SAAS,CAAC,UAAU,EAAE,GAAG;IAWnB,kBAAkB;IAgBlB,SAAS;IAQT,mBAAmB;IAuBnB,OAAO,CAAC,KAAK,EAAE,YAAY;IAc3B,UAAU,CAAC,IAAI,EAAE,MAAM;IAwBvB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAiBpC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAgB5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAOjE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAyC9D,WAAW,CAAC,IAAI,EAAE,MAAM;IAkDxB,OAAO,CAAC,IAAI,EAAE,MAAM;CAK3B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ukeyfe/hardware-transport-web-device",
|
|
3
|
+
"version": "1.1.13",
|
|
4
|
+
"author": "UKey",
|
|
5
|
+
"homepage": "https://github.com/UKeyHQ/hardware-js-sdk#readme",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/bestyourwallet/hardware-js-sdk"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
|
|
18
|
+
"build": "rimraf dist && rollup -c ../../build/rollup.config.js",
|
|
19
|
+
"lint": "eslint .",
|
|
20
|
+
"lint:fix": "eslint . --fix"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@ukeyfe/hardware-shared": "1.1.13",
|
|
24
|
+
"@ukeyfe/hardware-transport": "1.1.13"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@ukeyfe/hardware-transport-electron": "1.1.13",
|
|
28
|
+
"@types/w3c-web-usb": "^1.0.6",
|
|
29
|
+
"@types/web-bluetooth": "^0.0.17"
|
|
30
|
+
}
|
|
31
|
+
}
|