@tbsoft-gmbh/signature-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/assets-embedded.d.ts +12 -0
- package/dist/assets-embedded.js +37 -0
- package/dist/assets-embedded.js.map +1 -0
- package/dist/driver-hid.d.ts +45 -0
- package/dist/driver-hid.js +3777 -0
- package/dist/driver-hid.js.map +1 -0
- package/dist/font-8x13.d.ts +3 -0
- package/dist/font-8x13.js +145 -0
- package/dist/font-8x13.js.map +1 -0
- package/dist/hid-crypto.d.ts +58 -0
- package/dist/hid-crypto.js +138 -0
- package/dist/hid-crypto.js.map +1 -0
- package/dist/hid-display.d.ts +78 -0
- package/dist/hid-display.js +531 -0
- package/dist/hid-display.js.map +1 -0
- package/dist/hid-protocol.d.ts +227 -0
- package/dist/hid-protocol.js +2540 -0
- package/dist/hid-protocol.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +3900 -0
- package/dist/index.js.map +1 -0
- package/dist/png-decode.d.ts +14 -0
- package/dist/png-decode.js +141 -0
- package/dist/png-decode.js.map +1 -0
- package/dist/signature-renderer.d.ts +34 -0
- package/dist/signature-renderer.js +176 -0
- package/dist/signature-renderer.js.map +1 -0
- package/dist/types.d.ts +226 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
- package/udev/99-signotec.rules +10 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3900 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
12
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
27
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
28
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
29
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
30
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
31
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
32
|
+
mod
|
|
33
|
+
));
|
|
34
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
|
+
|
|
36
|
+
// node_modules/node-gyp-build/node-gyp-build.js
|
|
37
|
+
var require_node_gyp_build = __commonJS({
|
|
38
|
+
"node_modules/node-gyp-build/node-gyp-build.js"(exports2, module2) {
|
|
39
|
+
"use strict";
|
|
40
|
+
var fs3 = require("fs");
|
|
41
|
+
var path2 = require("path");
|
|
42
|
+
var os = require("os");
|
|
43
|
+
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require;
|
|
44
|
+
var vars = process.config && process.config.variables || {};
|
|
45
|
+
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
46
|
+
var abi = process.versions.modules;
|
|
47
|
+
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
48
|
+
var arch = process.env.npm_config_arch || os.arch();
|
|
49
|
+
var platform = process.env.npm_config_platform || os.platform();
|
|
50
|
+
var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
|
|
51
|
+
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
52
|
+
var uv = (process.versions.uv || "").split(".")[0];
|
|
53
|
+
module2.exports = load;
|
|
54
|
+
function load(dir) {
|
|
55
|
+
return runtimeRequire(load.resolve(dir));
|
|
56
|
+
}
|
|
57
|
+
load.resolve = load.path = function(dir) {
|
|
58
|
+
dir = path2.resolve(dir || ".");
|
|
59
|
+
try {
|
|
60
|
+
var name = runtimeRequire(path2.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
|
|
61
|
+
if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
|
|
62
|
+
} catch (err) {
|
|
63
|
+
}
|
|
64
|
+
if (!prebuildsOnly) {
|
|
65
|
+
var release = getFirst(path2.join(dir, "build/Release"), matchBuild);
|
|
66
|
+
if (release) return release;
|
|
67
|
+
var debug = getFirst(path2.join(dir, "build/Debug"), matchBuild);
|
|
68
|
+
if (debug) return debug;
|
|
69
|
+
}
|
|
70
|
+
var prebuild = resolve(dir);
|
|
71
|
+
if (prebuild) return prebuild;
|
|
72
|
+
var nearby = resolve(path2.dirname(process.execPath));
|
|
73
|
+
if (nearby) return nearby;
|
|
74
|
+
var target = [
|
|
75
|
+
"platform=" + platform,
|
|
76
|
+
"arch=" + arch,
|
|
77
|
+
"runtime=" + runtime,
|
|
78
|
+
"abi=" + abi,
|
|
79
|
+
"uv=" + uv,
|
|
80
|
+
armv ? "armv=" + armv : "",
|
|
81
|
+
"libc=" + libc,
|
|
82
|
+
"node=" + process.versions.node,
|
|
83
|
+
process.versions.electron ? "electron=" + process.versions.electron : "",
|
|
84
|
+
typeof __webpack_require__ === "function" ? "webpack=true" : ""
|
|
85
|
+
// eslint-disable-line
|
|
86
|
+
].filter(Boolean).join(" ");
|
|
87
|
+
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
88
|
+
function resolve(dir2) {
|
|
89
|
+
var tuples = readdirSync(path2.join(dir2, "prebuilds")).map(parseTuple);
|
|
90
|
+
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
|
|
91
|
+
if (!tuple) return;
|
|
92
|
+
var prebuilds = path2.join(dir2, "prebuilds", tuple.name);
|
|
93
|
+
var parsed = readdirSync(prebuilds).map(parseTags);
|
|
94
|
+
var candidates = parsed.filter(matchTags(runtime, abi));
|
|
95
|
+
var winner = candidates.sort(compareTags(runtime))[0];
|
|
96
|
+
if (winner) return path2.join(prebuilds, winner.file);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function readdirSync(dir) {
|
|
100
|
+
try {
|
|
101
|
+
return fs3.readdirSync(dir);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function getFirst(dir, filter) {
|
|
107
|
+
var files = readdirSync(dir).filter(filter);
|
|
108
|
+
return files[0] && path2.join(dir, files[0]);
|
|
109
|
+
}
|
|
110
|
+
function matchBuild(name) {
|
|
111
|
+
return /\.node$/.test(name);
|
|
112
|
+
}
|
|
113
|
+
function parseTuple(name) {
|
|
114
|
+
var arr = name.split("-");
|
|
115
|
+
if (arr.length !== 2) return;
|
|
116
|
+
var platform2 = arr[0];
|
|
117
|
+
var architectures = arr[1].split("+");
|
|
118
|
+
if (!platform2) return;
|
|
119
|
+
if (!architectures.length) return;
|
|
120
|
+
if (!architectures.every(Boolean)) return;
|
|
121
|
+
return { name, platform: platform2, architectures };
|
|
122
|
+
}
|
|
123
|
+
function matchTuple(platform2, arch2) {
|
|
124
|
+
return function(tuple) {
|
|
125
|
+
if (tuple == null) return false;
|
|
126
|
+
if (tuple.platform !== platform2) return false;
|
|
127
|
+
return tuple.architectures.includes(arch2);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function compareTuples(a, b) {
|
|
131
|
+
return a.architectures.length - b.architectures.length;
|
|
132
|
+
}
|
|
133
|
+
function parseTags(file) {
|
|
134
|
+
var arr = file.split(".");
|
|
135
|
+
var extension = arr.pop();
|
|
136
|
+
var tags = { file, specificity: 0 };
|
|
137
|
+
if (extension !== "node") return;
|
|
138
|
+
for (var i = 0; i < arr.length; i++) {
|
|
139
|
+
var tag = arr[i];
|
|
140
|
+
if (tag === "node" || tag === "electron" || tag === "node-webkit") {
|
|
141
|
+
tags.runtime = tag;
|
|
142
|
+
} else if (tag === "napi") {
|
|
143
|
+
tags.napi = true;
|
|
144
|
+
} else if (tag.slice(0, 3) === "abi") {
|
|
145
|
+
tags.abi = tag.slice(3);
|
|
146
|
+
} else if (tag.slice(0, 2) === "uv") {
|
|
147
|
+
tags.uv = tag.slice(2);
|
|
148
|
+
} else if (tag.slice(0, 4) === "armv") {
|
|
149
|
+
tags.armv = tag.slice(4);
|
|
150
|
+
} else if (tag === "glibc" || tag === "musl") {
|
|
151
|
+
tags.libc = tag;
|
|
152
|
+
} else {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
tags.specificity++;
|
|
156
|
+
}
|
|
157
|
+
return tags;
|
|
158
|
+
}
|
|
159
|
+
function matchTags(runtime2, abi2) {
|
|
160
|
+
return function(tags) {
|
|
161
|
+
if (tags == null) return false;
|
|
162
|
+
if (tags.runtime && tags.runtime !== runtime2 && !runtimeAgnostic(tags)) return false;
|
|
163
|
+
if (tags.abi && tags.abi !== abi2 && !tags.napi) return false;
|
|
164
|
+
if (tags.uv && tags.uv !== uv) return false;
|
|
165
|
+
if (tags.armv && tags.armv !== armv) return false;
|
|
166
|
+
if (tags.libc && tags.libc !== libc) return false;
|
|
167
|
+
return true;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function runtimeAgnostic(tags) {
|
|
171
|
+
return tags.runtime === "node" && tags.napi;
|
|
172
|
+
}
|
|
173
|
+
function compareTags(runtime2) {
|
|
174
|
+
return function(a, b) {
|
|
175
|
+
if (a.runtime !== b.runtime) {
|
|
176
|
+
return a.runtime === runtime2 ? -1 : 1;
|
|
177
|
+
} else if (a.abi !== b.abi) {
|
|
178
|
+
return a.abi ? -1 : 1;
|
|
179
|
+
} else if (a.specificity !== b.specificity) {
|
|
180
|
+
return a.specificity > b.specificity ? -1 : 1;
|
|
181
|
+
} else {
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function isNwjs() {
|
|
187
|
+
return !!(process.versions && process.versions.nw);
|
|
188
|
+
}
|
|
189
|
+
function isElectron() {
|
|
190
|
+
if (process.versions && process.versions.electron) return true;
|
|
191
|
+
if (process.env.ELECTRON_RUN_AS_NODE) return true;
|
|
192
|
+
return typeof window !== "undefined" && window.process && window.process.type === "renderer";
|
|
193
|
+
}
|
|
194
|
+
function isAlpine(platform2) {
|
|
195
|
+
return platform2 === "linux" && fs3.existsSync("/etc/alpine-release");
|
|
196
|
+
}
|
|
197
|
+
load.parseTags = parseTags;
|
|
198
|
+
load.matchTags = matchTags;
|
|
199
|
+
load.compareTags = compareTags;
|
|
200
|
+
load.parseTuple = parseTuple;
|
|
201
|
+
load.matchTuple = matchTuple;
|
|
202
|
+
load.compareTuples = compareTuples;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// node_modules/node-gyp-build/index.js
|
|
207
|
+
var require_node_gyp_build2 = __commonJS({
|
|
208
|
+
"node_modules/node-gyp-build/index.js"(exports2, module2) {
|
|
209
|
+
"use strict";
|
|
210
|
+
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require;
|
|
211
|
+
if (typeof runtimeRequire.addon === "function") {
|
|
212
|
+
module2.exports = runtimeRequire.addon.bind(runtimeRequire);
|
|
213
|
+
} else {
|
|
214
|
+
module2.exports = require_node_gyp_build();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// node_modules/usb/dist/usb/bindings.js
|
|
220
|
+
var require_bindings = __commonJS({
|
|
221
|
+
"node_modules/usb/dist/usb/bindings.js"(exports2, module2) {
|
|
222
|
+
"use strict";
|
|
223
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
224
|
+
var path_1 = require("path");
|
|
225
|
+
var usb = require_node_gyp_build2()(process.env.NODE_USB_PATH || (0, path_1.join)(__dirname, "..", ".."));
|
|
226
|
+
module2.exports = usb;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// node_modules/usb/dist/usb/endpoint.js
|
|
231
|
+
var require_endpoint = __commonJS({
|
|
232
|
+
"node_modules/usb/dist/usb/endpoint.js"(exports2) {
|
|
233
|
+
"use strict";
|
|
234
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
235
|
+
exports2.OutEndpoint = exports2.InEndpoint = exports2.Endpoint = void 0;
|
|
236
|
+
var events_1 = require("events");
|
|
237
|
+
var bindings_1 = require_bindings();
|
|
238
|
+
var util_1 = require("util");
|
|
239
|
+
var isBuffer = (obj) => obj && obj instanceof Buffer;
|
|
240
|
+
var Endpoint = class extends events_1.EventEmitter {
|
|
241
|
+
constructor(device, descriptor) {
|
|
242
|
+
super();
|
|
243
|
+
this.device = device;
|
|
244
|
+
this.timeout = 0;
|
|
245
|
+
this.descriptor = descriptor;
|
|
246
|
+
this.address = descriptor.bEndpointAddress;
|
|
247
|
+
this.transferType = descriptor.bmAttributes & 3;
|
|
248
|
+
}
|
|
249
|
+
/** Clear the halt/stall condition for this endpoint. */
|
|
250
|
+
clearHalt(callback) {
|
|
251
|
+
return this.device.__clearHalt(this.address, callback);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Create a new `Transfer` object for this endpoint.
|
|
255
|
+
*
|
|
256
|
+
* The passed callback will be called when the transfer is submitted and finishes. Its arguments are the error (if any), the submitted buffer, and the amount of data actually written (for
|
|
257
|
+
* OUT transfers) or read (for IN transfers).
|
|
258
|
+
*
|
|
259
|
+
* @param timeout Timeout for the transfer (0 means unlimited).
|
|
260
|
+
* @param callback Transfer completion callback.
|
|
261
|
+
*/
|
|
262
|
+
makeTransfer(timeout, callback) {
|
|
263
|
+
return new bindings_1.Transfer(this.device, this.address, this.transferType, timeout, callback);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
exports2.Endpoint = Endpoint;
|
|
267
|
+
var InEndpoint = class extends Endpoint {
|
|
268
|
+
constructor(device, descriptor) {
|
|
269
|
+
super(device, descriptor);
|
|
270
|
+
this.direction = "in";
|
|
271
|
+
this.pollTransfers = [];
|
|
272
|
+
this.pollTransferSize = 0;
|
|
273
|
+
this.pollPending = 0;
|
|
274
|
+
this.pollActive = false;
|
|
275
|
+
this.transferAsync = (0, util_1.promisify)(this.transfer).bind(this);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Perform a transfer to read data from the endpoint.
|
|
279
|
+
*
|
|
280
|
+
* If length is greater than maxPacketSize, libusb will automatically split the transfer in multiple packets, and you will receive one callback with all data once all packets are complete.
|
|
281
|
+
*
|
|
282
|
+
* `this` in the callback is the InEndpoint object.
|
|
283
|
+
*
|
|
284
|
+
* The device must be open to use this method.
|
|
285
|
+
* @param length
|
|
286
|
+
* @param callback
|
|
287
|
+
*/
|
|
288
|
+
transfer(length, callback) {
|
|
289
|
+
const buffer = Buffer.alloc(length);
|
|
290
|
+
const cb = (error, _buffer, actualLength) => {
|
|
291
|
+
callback.call(this, error, buffer.slice(0, actualLength));
|
|
292
|
+
};
|
|
293
|
+
try {
|
|
294
|
+
this.makeTransfer(this.timeout, cb).submit(buffer);
|
|
295
|
+
} catch (e) {
|
|
296
|
+
process.nextTick(() => callback.call(this, e));
|
|
297
|
+
}
|
|
298
|
+
return this;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Start polling the endpoint.
|
|
302
|
+
*
|
|
303
|
+
* The library will keep `nTransfers` transfers of size `transferSize` pending in the kernel at all times to ensure continuous data flow.
|
|
304
|
+
* This is handled by the libusb event thread, so it continues even if the Node v8 thread is busy. The `data` and `error` events are emitted as transfers complete.
|
|
305
|
+
*
|
|
306
|
+
* The device must be open to use this method.
|
|
307
|
+
* @param nTransfers
|
|
308
|
+
* @param transferSize
|
|
309
|
+
* @param callback
|
|
310
|
+
*/
|
|
311
|
+
startPoll(nTransfers, transferSize, callback) {
|
|
312
|
+
const transferDone = (error, transfer, buffer, actualLength) => {
|
|
313
|
+
if (!error) {
|
|
314
|
+
this.emit("data", buffer.slice(0, actualLength));
|
|
315
|
+
} else if (error.errno !== bindings_1.LIBUSB_TRANSFER_CANCELLED) {
|
|
316
|
+
if (this.pollActive) {
|
|
317
|
+
this.emit("error", error);
|
|
318
|
+
this.stopPoll();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (this.pollActive) {
|
|
322
|
+
startTransfer(transfer);
|
|
323
|
+
} else {
|
|
324
|
+
this.pollPending--;
|
|
325
|
+
if (this.pollPending === 0) {
|
|
326
|
+
this.pollTransfers = [];
|
|
327
|
+
this.pollActive = false;
|
|
328
|
+
this.emit("end");
|
|
329
|
+
if (callback) {
|
|
330
|
+
const cancelled = (error === null || error === void 0 ? void 0 : error.errno) === bindings_1.LIBUSB_TRANSFER_CANCELLED;
|
|
331
|
+
callback(cancelled ? void 0 : error, buffer, actualLength, cancelled);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
const startTransfer = (transfer) => {
|
|
337
|
+
try {
|
|
338
|
+
transfer.submit(Buffer.alloc(this.pollTransferSize), (error, buffer, actualLength) => {
|
|
339
|
+
transferDone(error, transfer, buffer, actualLength);
|
|
340
|
+
});
|
|
341
|
+
} catch (e) {
|
|
342
|
+
this.emit("error", e);
|
|
343
|
+
this.stopPoll();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
this.pollTransfers = this.startPollTransfers(nTransfers, transferSize, function(error, buffer, actualLength) {
|
|
347
|
+
transferDone(error, this, buffer, actualLength);
|
|
348
|
+
});
|
|
349
|
+
this.pollTransfers.forEach(startTransfer);
|
|
350
|
+
this.pollPending = this.pollTransfers.length;
|
|
351
|
+
return this.pollTransfers;
|
|
352
|
+
}
|
|
353
|
+
startPollTransfers(nTransfers = 3, transferSize = this.descriptor.wMaxPacketSize, callback) {
|
|
354
|
+
if (this.pollActive) {
|
|
355
|
+
throw new Error("Polling already active");
|
|
356
|
+
}
|
|
357
|
+
this.pollTransferSize = transferSize;
|
|
358
|
+
this.pollActive = true;
|
|
359
|
+
this.pollPending = 0;
|
|
360
|
+
const transfers = [];
|
|
361
|
+
for (let i = 0; i < nTransfers; i++) {
|
|
362
|
+
const transfer = this.makeTransfer(0, callback);
|
|
363
|
+
transfers[i] = transfer;
|
|
364
|
+
}
|
|
365
|
+
return transfers;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Stop polling.
|
|
369
|
+
*
|
|
370
|
+
* Further data may still be received. The `end` event is emitted and the callback is called once all transfers have completed or canceled.
|
|
371
|
+
*
|
|
372
|
+
* The device must be open to use this method.
|
|
373
|
+
* @param callback
|
|
374
|
+
*/
|
|
375
|
+
stopPoll(callback) {
|
|
376
|
+
if (!this.pollActive) {
|
|
377
|
+
throw new Error("Polling is not active.");
|
|
378
|
+
}
|
|
379
|
+
for (let i = 0; i < this.pollTransfers.length; i++) {
|
|
380
|
+
try {
|
|
381
|
+
this.pollTransfers[i].cancel();
|
|
382
|
+
} catch (error) {
|
|
383
|
+
this.emit("error", error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
this.pollActive = false;
|
|
387
|
+
if (callback)
|
|
388
|
+
this.once("end", callback);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
exports2.InEndpoint = InEndpoint;
|
|
392
|
+
var OutEndpoint = class extends Endpoint {
|
|
393
|
+
constructor(device, descriptor) {
|
|
394
|
+
super(device, descriptor);
|
|
395
|
+
this.direction = "out";
|
|
396
|
+
this.transferAsync = (0, util_1.promisify)(this.transfer).bind(this);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Perform a transfer to write `data` to the endpoint.
|
|
400
|
+
*
|
|
401
|
+
* If length is greater than maxPacketSize, libusb will automatically split the transfer in multiple packets, and you will receive one callback once all packets are complete.
|
|
402
|
+
*
|
|
403
|
+
* `this` in the callback is the OutEndpoint object.
|
|
404
|
+
*
|
|
405
|
+
* The device must be open to use this method.
|
|
406
|
+
* @param buffer
|
|
407
|
+
* @param callback
|
|
408
|
+
*/
|
|
409
|
+
transfer(buffer, callback) {
|
|
410
|
+
if (!buffer) {
|
|
411
|
+
buffer = Buffer.alloc(0);
|
|
412
|
+
} else if (!isBuffer(buffer)) {
|
|
413
|
+
buffer = Buffer.from(buffer);
|
|
414
|
+
}
|
|
415
|
+
const cb = (error, _buffer, actual) => {
|
|
416
|
+
if (callback) {
|
|
417
|
+
callback.call(this, error, actual || 0);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
try {
|
|
421
|
+
this.makeTransfer(this.timeout, cb).submit(buffer);
|
|
422
|
+
} catch (e) {
|
|
423
|
+
process.nextTick(() => cb(e));
|
|
424
|
+
}
|
|
425
|
+
return this;
|
|
426
|
+
}
|
|
427
|
+
transferWithZLP(buffer, callback) {
|
|
428
|
+
if (buffer.length % this.descriptor.wMaxPacketSize === 0) {
|
|
429
|
+
this.transfer(buffer);
|
|
430
|
+
this.transfer(Buffer.alloc(0), callback);
|
|
431
|
+
} else {
|
|
432
|
+
this.transfer(buffer, callback);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
exports2.OutEndpoint = OutEndpoint;
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// node_modules/usb/dist/usb/interface.js
|
|
441
|
+
var require_interface = __commonJS({
|
|
442
|
+
"node_modules/usb/dist/usb/interface.js"(exports2) {
|
|
443
|
+
"use strict";
|
|
444
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
445
|
+
exports2.Interface = void 0;
|
|
446
|
+
var bindings_1 = require_bindings();
|
|
447
|
+
var endpoint_1 = require_endpoint();
|
|
448
|
+
var util_1 = require("util");
|
|
449
|
+
var Interface = class {
|
|
450
|
+
constructor(device, id) {
|
|
451
|
+
this.device = device;
|
|
452
|
+
this.id = id;
|
|
453
|
+
this.altSetting = 0;
|
|
454
|
+
this.refresh();
|
|
455
|
+
this.releaseAsync = (0, util_1.promisify)(this.release).bind(this);
|
|
456
|
+
this.setAltSettingAsync = (0, util_1.promisify)(this.setAltSetting).bind(this);
|
|
457
|
+
}
|
|
458
|
+
refresh() {
|
|
459
|
+
if (!this.device.configDescriptor) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
this.descriptor = this.device.configDescriptor.interfaces[this.id][this.altSetting];
|
|
463
|
+
this.interfaceNumber = this.descriptor.bInterfaceNumber;
|
|
464
|
+
this.endpoints = [];
|
|
465
|
+
const len = this.descriptor.endpoints.length;
|
|
466
|
+
for (let i = 0; i < len; i++) {
|
|
467
|
+
const desc = this.descriptor.endpoints[i];
|
|
468
|
+
const c = desc.bEndpointAddress & bindings_1.LIBUSB_ENDPOINT_IN ? endpoint_1.InEndpoint : endpoint_1.OutEndpoint;
|
|
469
|
+
this.endpoints[i] = new c(this.device, desc);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Claims the interface. This method must be called before using any endpoints of this interface.
|
|
474
|
+
*
|
|
475
|
+
* The device must be open to use this method.
|
|
476
|
+
*/
|
|
477
|
+
claim() {
|
|
478
|
+
this.device.__claimInterface(this.id);
|
|
479
|
+
}
|
|
480
|
+
release(closeEndpointsOrCallback, callback) {
|
|
481
|
+
let closeEndpoints = false;
|
|
482
|
+
if (typeof closeEndpointsOrCallback === "boolean") {
|
|
483
|
+
closeEndpoints = closeEndpointsOrCallback;
|
|
484
|
+
} else {
|
|
485
|
+
callback = closeEndpointsOrCallback;
|
|
486
|
+
}
|
|
487
|
+
const next = () => {
|
|
488
|
+
this.device.__releaseInterface(this.id, (error) => {
|
|
489
|
+
if (!error) {
|
|
490
|
+
this.altSetting = 0;
|
|
491
|
+
this.refresh();
|
|
492
|
+
}
|
|
493
|
+
if (callback) {
|
|
494
|
+
callback.call(this, error);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
};
|
|
498
|
+
if (!closeEndpoints || this.endpoints.length === 0) {
|
|
499
|
+
next();
|
|
500
|
+
} else {
|
|
501
|
+
let n = this.endpoints.length;
|
|
502
|
+
this.endpoints.forEach((ep) => {
|
|
503
|
+
if (ep.direction === "in" && ep.pollActive) {
|
|
504
|
+
ep.once("end", () => {
|
|
505
|
+
if (--n === 0) {
|
|
506
|
+
next();
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
ep.stopPoll();
|
|
510
|
+
} else {
|
|
511
|
+
if (--n === 0) {
|
|
512
|
+
next();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Returns `false` if a kernel driver is not active; `true` if active.
|
|
520
|
+
*
|
|
521
|
+
* The device must be open to use this method.
|
|
522
|
+
*/
|
|
523
|
+
isKernelDriverActive() {
|
|
524
|
+
return this.device.__isKernelDriverActive(this.id);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Detaches the kernel driver from the interface.
|
|
528
|
+
*
|
|
529
|
+
* The device must be open to use this method.
|
|
530
|
+
*/
|
|
531
|
+
detachKernelDriver() {
|
|
532
|
+
return this.device.__detachKernelDriver(this.id);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Re-attaches the kernel driver for the interface.
|
|
536
|
+
*
|
|
537
|
+
* The device must be open to use this method.
|
|
538
|
+
*/
|
|
539
|
+
attachKernelDriver() {
|
|
540
|
+
return this.device.__attachKernelDriver(this.id);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Sets the alternate setting. It updates the `interface.endpoints` array to reflect the endpoints found in the alternate setting.
|
|
544
|
+
*
|
|
545
|
+
* The device must be open to use this method.
|
|
546
|
+
* @param altSetting
|
|
547
|
+
* @param callback
|
|
548
|
+
*/
|
|
549
|
+
setAltSetting(altSetting, callback) {
|
|
550
|
+
this.device.__setInterface(this.id, altSetting, (error) => {
|
|
551
|
+
if (!error) {
|
|
552
|
+
this.altSetting = altSetting;
|
|
553
|
+
this.refresh();
|
|
554
|
+
}
|
|
555
|
+
if (callback) {
|
|
556
|
+
callback.call(this, error);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Return the InEndpoint or OutEndpoint with the specified address.
|
|
562
|
+
*
|
|
563
|
+
* The device must be open to use this method.
|
|
564
|
+
* @param addr
|
|
565
|
+
*/
|
|
566
|
+
endpoint(addr) {
|
|
567
|
+
return this.endpoints.find((item) => item.address === addr);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
exports2.Interface = Interface;
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// node_modules/usb/dist/usb/capability.js
|
|
575
|
+
var require_capability = __commonJS({
|
|
576
|
+
"node_modules/usb/dist/usb/capability.js"(exports2) {
|
|
577
|
+
"use strict";
|
|
578
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
579
|
+
exports2.Capability = void 0;
|
|
580
|
+
var Capability = class {
|
|
581
|
+
constructor(device, id) {
|
|
582
|
+
this.device = device;
|
|
583
|
+
this.id = id;
|
|
584
|
+
if (!device._bosDescriptor) {
|
|
585
|
+
throw new Error("bosDescriptor not found");
|
|
586
|
+
}
|
|
587
|
+
this.descriptor = device._bosDescriptor.capabilities[this.id];
|
|
588
|
+
this.type = this.descriptor.bDevCapabilityType;
|
|
589
|
+
this.data = this.descriptor.dev_capability_data;
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
exports2.Capability = Capability;
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// node_modules/usb/dist/usb/device.js
|
|
597
|
+
var require_device = __commonJS({
|
|
598
|
+
"node_modules/usb/dist/usb/device.js"(exports2) {
|
|
599
|
+
"use strict";
|
|
600
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
601
|
+
exports2.ExtendedDevice = void 0;
|
|
602
|
+
var usb = require_bindings();
|
|
603
|
+
var interface_1 = require_interface();
|
|
604
|
+
var capability_1 = require_capability();
|
|
605
|
+
var isBuffer = (obj) => !!obj && obj instanceof Uint8Array;
|
|
606
|
+
var DEFAULT_TIMEOUT = 1e3;
|
|
607
|
+
var ExtendedDevice = class {
|
|
608
|
+
constructor() {
|
|
609
|
+
this._timeout = DEFAULT_TIMEOUT;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Timeout in milliseconds to use for control transfers.
|
|
613
|
+
*/
|
|
614
|
+
get timeout() {
|
|
615
|
+
return this._timeout || DEFAULT_TIMEOUT;
|
|
616
|
+
}
|
|
617
|
+
set timeout(value) {
|
|
618
|
+
this._timeout = value;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Object with properties for the fields of the active configuration descriptor.
|
|
622
|
+
*/
|
|
623
|
+
get configDescriptor() {
|
|
624
|
+
try {
|
|
625
|
+
return this.__getConfigDescriptor();
|
|
626
|
+
} catch (e) {
|
|
627
|
+
const errno = e.errno;
|
|
628
|
+
if (errno === usb.LIBUSB_ERROR_NOT_FOUND || errno === usb.LIBUSB_ERROR_NO_DEVICE) {
|
|
629
|
+
return void 0;
|
|
630
|
+
}
|
|
631
|
+
throw e;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Contains all config descriptors of the device (same structure as .configDescriptor above)
|
|
636
|
+
*/
|
|
637
|
+
get allConfigDescriptors() {
|
|
638
|
+
try {
|
|
639
|
+
return this.__getAllConfigDescriptors();
|
|
640
|
+
} catch (e) {
|
|
641
|
+
const errno = e.errno;
|
|
642
|
+
if (errno === usb.LIBUSB_ERROR_NOT_FOUND || errno === usb.LIBUSB_ERROR_NO_DEVICE) {
|
|
643
|
+
return [];
|
|
644
|
+
}
|
|
645
|
+
throw e;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Contains the parent of the device, such as a hub. If there is no parent this property is set to `null`.
|
|
650
|
+
*/
|
|
651
|
+
get parent() {
|
|
652
|
+
return this.__getParent();
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Open the device.
|
|
656
|
+
* @param defaultConfig
|
|
657
|
+
*/
|
|
658
|
+
open(defaultConfig = true) {
|
|
659
|
+
this.__open();
|
|
660
|
+
this.interfaces = [];
|
|
661
|
+
if (defaultConfig === false) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const len = this.configDescriptor ? this.configDescriptor.interfaces.length : 0;
|
|
665
|
+
for (let i = 0; i < len; i++) {
|
|
666
|
+
this.interfaces[i] = new interface_1.Interface(this, i);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Close the device.
|
|
671
|
+
*
|
|
672
|
+
* The device must be open to use this method.
|
|
673
|
+
*/
|
|
674
|
+
close() {
|
|
675
|
+
this.__close();
|
|
676
|
+
this.interfaces = void 0;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Set the device configuration to something other than the default (0). To use this, first call `.open(false)` (which tells it not to auto configure),
|
|
680
|
+
* then before claiming an interface, call this method.
|
|
681
|
+
*
|
|
682
|
+
* The device must be open to use this method.
|
|
683
|
+
* @param desired
|
|
684
|
+
* @param callback
|
|
685
|
+
*/
|
|
686
|
+
setConfiguration(desired, callback) {
|
|
687
|
+
this.__setConfiguration(desired, (error) => {
|
|
688
|
+
if (!error) {
|
|
689
|
+
this.interfaces = [];
|
|
690
|
+
const len = this.configDescriptor ? this.configDescriptor.interfaces.length : 0;
|
|
691
|
+
for (let i = 0; i < len; i++) {
|
|
692
|
+
this.interfaces[i] = new interface_1.Interface(this, i);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (callback) {
|
|
696
|
+
callback.call(this, error);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Enable/disable libusb's automatic kernel driver detachment
|
|
702
|
+
* When this is enabled libusb will automatically detach the kernel driver on an interface when claiming the interface, and attach it when releasing the interface
|
|
703
|
+
*
|
|
704
|
+
* The device must be open to use this method.
|
|
705
|
+
*/
|
|
706
|
+
setAutoDetachKernelDriver(enable) {
|
|
707
|
+
return this.__setAutoDetachKernelDriver(enable ? 1 : 0);
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Perform a control transfer with `libusb_control_transfer`.
|
|
711
|
+
*
|
|
712
|
+
* Parameter `data_or_length` can be an integer length for an IN transfer, or a `Buffer` for an OUT transfer. The type must match the direction specified in the MSB of bmRequestType.
|
|
713
|
+
*
|
|
714
|
+
* The `data` parameter of the callback is actual transferred for OUT transfers, or will be passed a Buffer for IN transfers.
|
|
715
|
+
*
|
|
716
|
+
* The device must be open to use this method.
|
|
717
|
+
* @param bmRequestType
|
|
718
|
+
* @param bRequest
|
|
719
|
+
* @param wValue
|
|
720
|
+
* @param wIndex
|
|
721
|
+
* @param data_or_length
|
|
722
|
+
* @param callback
|
|
723
|
+
*/
|
|
724
|
+
controlTransfer(bmRequestType, bRequest, wValue, wIndex, data_or_length, callback) {
|
|
725
|
+
const isIn = !!(bmRequestType & usb.LIBUSB_ENDPOINT_IN);
|
|
726
|
+
const wLength = isIn ? data_or_length : data_or_length.length;
|
|
727
|
+
if (isIn) {
|
|
728
|
+
if (wLength < 0) {
|
|
729
|
+
throw new TypeError("Expected size number for IN transfer (based on bmRequestType)");
|
|
730
|
+
}
|
|
731
|
+
} else {
|
|
732
|
+
if (!isBuffer(data_or_length)) {
|
|
733
|
+
throw new TypeError("Expected buffer for OUT transfer (based on bmRequestType)");
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const buf = Buffer.alloc(wLength + usb.LIBUSB_CONTROL_SETUP_SIZE);
|
|
737
|
+
buf.writeUInt8(bmRequestType, 0);
|
|
738
|
+
buf.writeUInt8(bRequest, 1);
|
|
739
|
+
buf.writeUInt16LE(wValue, 2);
|
|
740
|
+
buf.writeUInt16LE(wIndex, 4);
|
|
741
|
+
buf.writeUInt16LE(wLength, 6);
|
|
742
|
+
if (!isIn) {
|
|
743
|
+
buf.set(data_or_length, usb.LIBUSB_CONTROL_SETUP_SIZE);
|
|
744
|
+
}
|
|
745
|
+
const transfer = new usb.Transfer(this, 0, usb.LIBUSB_TRANSFER_TYPE_CONTROL, this.timeout, (error, buf2, actual) => {
|
|
746
|
+
if (callback) {
|
|
747
|
+
if (isIn) {
|
|
748
|
+
callback.call(this, error, buf2.slice(usb.LIBUSB_CONTROL_SETUP_SIZE, usb.LIBUSB_CONTROL_SETUP_SIZE + actual));
|
|
749
|
+
} else {
|
|
750
|
+
callback.call(this, error, actual);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
try {
|
|
755
|
+
transfer.submit(buf);
|
|
756
|
+
} catch (e) {
|
|
757
|
+
if (callback) {
|
|
758
|
+
process.nextTick(() => callback.call(this, e, void 0));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return this;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Return the interface with the specified interface number.
|
|
765
|
+
*
|
|
766
|
+
* The device must be open to use this method.
|
|
767
|
+
* @param addr
|
|
768
|
+
*/
|
|
769
|
+
interface(addr) {
|
|
770
|
+
if (!this.interfaces) {
|
|
771
|
+
throw new Error("Device must be open before searching for interfaces");
|
|
772
|
+
}
|
|
773
|
+
addr = addr || 0;
|
|
774
|
+
for (let i = 0; i < this.interfaces.length; i++) {
|
|
775
|
+
if (this.interfaces[i].interfaceNumber === addr) {
|
|
776
|
+
return this.interfaces[i];
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
throw new Error(`Interface not found for address: ${addr}`);
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Perform a control transfer to retrieve a string descriptor
|
|
783
|
+
*
|
|
784
|
+
* The device must be open to use this method.
|
|
785
|
+
* @param desc_index
|
|
786
|
+
* @param callback
|
|
787
|
+
*/
|
|
788
|
+
getStringDescriptor(desc_index, callback) {
|
|
789
|
+
if (desc_index === 0) {
|
|
790
|
+
callback();
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const langid = 1033;
|
|
794
|
+
const length = 255;
|
|
795
|
+
this.controlTransfer(usb.LIBUSB_ENDPOINT_IN, usb.LIBUSB_REQUEST_GET_DESCRIPTOR, usb.LIBUSB_DT_STRING << 8 | desc_index, langid, length, (error, buffer) => {
|
|
796
|
+
if (error) {
|
|
797
|
+
return callback(error);
|
|
798
|
+
}
|
|
799
|
+
callback(void 0, isBuffer(buffer) ? buffer.toString("utf16le", 2) : void 0);
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Perform a control transfer to retrieve an object with properties for the fields of the Binary Object Store descriptor.
|
|
804
|
+
*
|
|
805
|
+
* The device must be open to use this method.
|
|
806
|
+
* @param callback
|
|
807
|
+
*/
|
|
808
|
+
getBosDescriptor(callback) {
|
|
809
|
+
if (this._bosDescriptor) {
|
|
810
|
+
return callback(void 0, this._bosDescriptor);
|
|
811
|
+
}
|
|
812
|
+
if (this.deviceDescriptor.bcdUSB < 513) {
|
|
813
|
+
return callback(void 0, void 0);
|
|
814
|
+
}
|
|
815
|
+
this.controlTransfer(usb.LIBUSB_ENDPOINT_IN, usb.LIBUSB_REQUEST_GET_DESCRIPTOR, usb.LIBUSB_DT_BOS << 8, 0, usb.LIBUSB_DT_BOS_SIZE, (error, buffer) => {
|
|
816
|
+
if (error) {
|
|
817
|
+
if (error.errno === usb.LIBUSB_TRANSFER_STALL)
|
|
818
|
+
return callback(void 0, void 0);
|
|
819
|
+
return callback(error, void 0);
|
|
820
|
+
}
|
|
821
|
+
if (!isBuffer(buffer)) {
|
|
822
|
+
return callback(void 0, void 0);
|
|
823
|
+
}
|
|
824
|
+
const totalLength = buffer.readUInt16LE(2);
|
|
825
|
+
this.controlTransfer(usb.LIBUSB_ENDPOINT_IN, usb.LIBUSB_REQUEST_GET_DESCRIPTOR, usb.LIBUSB_DT_BOS << 8, 0, totalLength, (error2, buffer2) => {
|
|
826
|
+
if (error2) {
|
|
827
|
+
if (error2.errno === usb.LIBUSB_TRANSFER_STALL)
|
|
828
|
+
return callback(void 0, void 0);
|
|
829
|
+
return callback(error2, void 0);
|
|
830
|
+
}
|
|
831
|
+
if (!isBuffer(buffer2)) {
|
|
832
|
+
return callback(void 0, void 0);
|
|
833
|
+
}
|
|
834
|
+
const descriptor = {
|
|
835
|
+
bLength: buffer2.readUInt8(0),
|
|
836
|
+
bDescriptorType: buffer2.readUInt8(1),
|
|
837
|
+
wTotalLength: buffer2.readUInt16LE(2),
|
|
838
|
+
bNumDeviceCaps: buffer2.readUInt8(4),
|
|
839
|
+
capabilities: []
|
|
840
|
+
};
|
|
841
|
+
let i = usb.LIBUSB_DT_BOS_SIZE;
|
|
842
|
+
while (i < descriptor.wTotalLength) {
|
|
843
|
+
const capability = {
|
|
844
|
+
bLength: buffer2.readUInt8(i + 0),
|
|
845
|
+
bDescriptorType: buffer2.readUInt8(i + 1),
|
|
846
|
+
bDevCapabilityType: buffer2.readUInt8(i + 2),
|
|
847
|
+
dev_capability_data: buffer2.slice(i + 3, i + buffer2.readUInt8(i + 0))
|
|
848
|
+
};
|
|
849
|
+
descriptor.capabilities.push(capability);
|
|
850
|
+
i += capability.bLength;
|
|
851
|
+
}
|
|
852
|
+
this._bosDescriptor = descriptor;
|
|
853
|
+
callback(void 0, this._bosDescriptor);
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Retrieve a list of Capability objects for the Binary Object Store capabilities of the device.
|
|
859
|
+
*
|
|
860
|
+
* The device must be open to use this method.
|
|
861
|
+
* @param callback
|
|
862
|
+
*/
|
|
863
|
+
getCapabilities(callback) {
|
|
864
|
+
const capabilities = [];
|
|
865
|
+
this.getBosDescriptor((error, descriptor) => {
|
|
866
|
+
if (error)
|
|
867
|
+
return callback(error, void 0);
|
|
868
|
+
const len = descriptor ? descriptor.capabilities.length : 0;
|
|
869
|
+
for (let i = 0; i < len; i++) {
|
|
870
|
+
capabilities.push(new capability_1.Capability(this, i));
|
|
871
|
+
}
|
|
872
|
+
callback(void 0, capabilities);
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
exports2.ExtendedDevice = ExtendedDevice;
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// node_modules/usb/dist/usb/index.js
|
|
881
|
+
var require_usb = __commonJS({
|
|
882
|
+
"node_modules/usb/dist/usb/index.js"(exports2, module2) {
|
|
883
|
+
"use strict";
|
|
884
|
+
var events_1 = require("events");
|
|
885
|
+
var device_1 = require_device();
|
|
886
|
+
var usb = require_bindings();
|
|
887
|
+
if (usb.INIT_ERROR) {
|
|
888
|
+
console.warn("Failed to initialize libusb.");
|
|
889
|
+
}
|
|
890
|
+
Object.setPrototypeOf(usb, events_1.EventEmitter.prototype);
|
|
891
|
+
Object.defineProperty(usb, "pollHotplug", {
|
|
892
|
+
value: false,
|
|
893
|
+
writable: true
|
|
894
|
+
});
|
|
895
|
+
Object.defineProperty(usb, "pollHotplugDelay", {
|
|
896
|
+
value: 500,
|
|
897
|
+
writable: true
|
|
898
|
+
});
|
|
899
|
+
if (usb.Device) {
|
|
900
|
+
Object.getOwnPropertyNames(device_1.ExtendedDevice.prototype).forEach((name) => {
|
|
901
|
+
Object.defineProperty(usb.Device.prototype, name, Object.getOwnPropertyDescriptor(device_1.ExtendedDevice.prototype, name) || /* @__PURE__ */ Object.create(null));
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
var hotPlugDevices = /* @__PURE__ */ new Set();
|
|
905
|
+
var emitHotplugEvents = () => {
|
|
906
|
+
const devices = new Set(usb.getDeviceList());
|
|
907
|
+
for (const device of devices) {
|
|
908
|
+
if (!hotPlugDevices.has(device)) {
|
|
909
|
+
usb.emit("attach", device);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
for (const device of hotPlugDevices) {
|
|
913
|
+
if (!devices.has(device)) {
|
|
914
|
+
usb.emit("detach", device);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
hotPlugDevices = devices;
|
|
918
|
+
};
|
|
919
|
+
var pollingHotplug = false;
|
|
920
|
+
var pollHotplug = (start = false) => {
|
|
921
|
+
if (start) {
|
|
922
|
+
pollingHotplug = true;
|
|
923
|
+
} else if (!pollingHotplug) {
|
|
924
|
+
return;
|
|
925
|
+
} else {
|
|
926
|
+
emitHotplugEvents();
|
|
927
|
+
}
|
|
928
|
+
setTimeout(() => pollHotplug(), usb.pollHotplugDelay);
|
|
929
|
+
};
|
|
930
|
+
var devicesChanged = () => setTimeout(() => emitHotplugEvents(), usb.pollHotplugDelay);
|
|
931
|
+
var hotplugSupported = 0;
|
|
932
|
+
var startHotplug = () => {
|
|
933
|
+
hotplugSupported = usb.pollHotplug ? 0 : usb._supportedHotplugEvents();
|
|
934
|
+
if (hotplugSupported !== 1) {
|
|
935
|
+
hotPlugDevices = new Set(usb.getDeviceList());
|
|
936
|
+
}
|
|
937
|
+
if (hotplugSupported) {
|
|
938
|
+
usb._enableHotplugEvents();
|
|
939
|
+
if (hotplugSupported === 2) {
|
|
940
|
+
usb.on("attachIds", devicesChanged);
|
|
941
|
+
usb.on("detachIds", devicesChanged);
|
|
942
|
+
}
|
|
943
|
+
} else {
|
|
944
|
+
pollHotplug(true);
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
var stopHotplug = () => {
|
|
948
|
+
if (hotplugSupported) {
|
|
949
|
+
usb._disableHotplugEvents();
|
|
950
|
+
if (hotplugSupported === 2) {
|
|
951
|
+
usb.off("attachIds", devicesChanged);
|
|
952
|
+
usb.off("detachIds", devicesChanged);
|
|
953
|
+
}
|
|
954
|
+
} else {
|
|
955
|
+
pollingHotplug = false;
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
usb.on("newListener", (event) => {
|
|
959
|
+
if (event !== "attach" && event !== "detach") {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const listenerCount = usb.listenerCount("attach") + usb.listenerCount("detach");
|
|
963
|
+
if (listenerCount === 0) {
|
|
964
|
+
startHotplug();
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
usb.on("removeListener", (event) => {
|
|
968
|
+
if (event !== "attach" && event !== "detach") {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const listenerCount = usb.listenerCount("attach") + usb.listenerCount("detach");
|
|
972
|
+
if (listenerCount === 0) {
|
|
973
|
+
stopHotplug();
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
module2.exports = usb;
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
// node_modules/usb/dist/webusb/webusb-device.js
|
|
981
|
+
var require_webusb_device = __commonJS({
|
|
982
|
+
"node_modules/usb/dist/webusb/webusb-device.js"(exports2) {
|
|
983
|
+
"use strict";
|
|
984
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
985
|
+
exports2.WebUSBDevice = void 0;
|
|
986
|
+
var usb = require_usb();
|
|
987
|
+
var util_1 = require("util");
|
|
988
|
+
var os_1 = require("os");
|
|
989
|
+
var LIBUSB_TRANSFER_TYPE_MASK = 3;
|
|
990
|
+
var ENDPOINT_NUMBER_MASK = 127;
|
|
991
|
+
var CLEAR_FEATURE = 1;
|
|
992
|
+
var ENDPOINT_HALT = 0;
|
|
993
|
+
var WebUSBDevice = class _WebUSBDevice {
|
|
994
|
+
static async createInstance(device, autoDetachKernelDriver = true) {
|
|
995
|
+
const instance = new _WebUSBDevice(device, autoDetachKernelDriver);
|
|
996
|
+
await instance.initialize();
|
|
997
|
+
return instance;
|
|
998
|
+
}
|
|
999
|
+
constructor(device, autoDetachKernelDriver) {
|
|
1000
|
+
this.device = device;
|
|
1001
|
+
this.autoDetachKernelDriver = autoDetachKernelDriver;
|
|
1002
|
+
this.manufacturerName = null;
|
|
1003
|
+
this.productName = null;
|
|
1004
|
+
this.serialNumber = null;
|
|
1005
|
+
this.configurations = [];
|
|
1006
|
+
const usbVersion = this.decodeVersion(device.deviceDescriptor.bcdUSB);
|
|
1007
|
+
this.usbVersionMajor = usbVersion.major;
|
|
1008
|
+
this.usbVersionMinor = usbVersion.minor;
|
|
1009
|
+
this.usbVersionSubminor = usbVersion.sub;
|
|
1010
|
+
this.deviceClass = device.deviceDescriptor.bDeviceClass;
|
|
1011
|
+
this.deviceSubclass = device.deviceDescriptor.bDeviceSubClass;
|
|
1012
|
+
this.deviceProtocol = device.deviceDescriptor.bDeviceProtocol;
|
|
1013
|
+
this.vendorId = device.deviceDescriptor.idVendor;
|
|
1014
|
+
this.productId = device.deviceDescriptor.idProduct;
|
|
1015
|
+
const deviceVersion = this.decodeVersion(device.deviceDescriptor.bcdDevice);
|
|
1016
|
+
this.deviceVersionMajor = deviceVersion.major;
|
|
1017
|
+
this.deviceVersionMinor = deviceVersion.minor;
|
|
1018
|
+
this.deviceVersionSubminor = deviceVersion.sub;
|
|
1019
|
+
this.controlTransferAsync = (0, util_1.promisify)(this.device.controlTransfer).bind(this.device);
|
|
1020
|
+
this.setConfigurationAsync = (0, util_1.promisify)(this.device.setConfiguration).bind(this.device);
|
|
1021
|
+
this.resetAsync = (0, util_1.promisify)(this.device.reset).bind(this.device);
|
|
1022
|
+
this.getStringDescriptorAsync = (0, util_1.promisify)(this.device.getStringDescriptor).bind(this.device);
|
|
1023
|
+
}
|
|
1024
|
+
get configuration() {
|
|
1025
|
+
if (!this.device.configDescriptor) {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
const currentConfiguration = this.device.configDescriptor.bConfigurationValue;
|
|
1029
|
+
return this.configurations.find((configuration) => configuration.configurationValue === currentConfiguration) || null;
|
|
1030
|
+
}
|
|
1031
|
+
get opened() {
|
|
1032
|
+
return !!this.device.interfaces;
|
|
1033
|
+
}
|
|
1034
|
+
async open() {
|
|
1035
|
+
try {
|
|
1036
|
+
if (this.opened) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
this.device.open();
|
|
1040
|
+
if ((0, os_1.platform)() !== "win32") {
|
|
1041
|
+
this.device.setAutoDetachKernelDriver(this.autoDetachKernelDriver);
|
|
1042
|
+
}
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
throw new Error(`open error: ${error}`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
async close() {
|
|
1048
|
+
try {
|
|
1049
|
+
if (!this.opened) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
try {
|
|
1053
|
+
if (this.configuration) {
|
|
1054
|
+
for (const iface of this.configuration.interfaces) {
|
|
1055
|
+
await this._releaseInterface(iface.interfaceNumber);
|
|
1056
|
+
this.configuration.interfaces[this.configuration.interfaces.indexOf(iface)] = {
|
|
1057
|
+
interfaceNumber: iface.interfaceNumber,
|
|
1058
|
+
alternate: iface.alternate,
|
|
1059
|
+
alternates: iface.alternates,
|
|
1060
|
+
claimed: false
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
} catch (_error) {
|
|
1065
|
+
}
|
|
1066
|
+
this.device.close();
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
throw new Error(`close error: ${error}`);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
async selectConfiguration(configurationValue) {
|
|
1072
|
+
if (!this.opened || !this.device.configDescriptor) {
|
|
1073
|
+
throw new Error("selectConfiguration error: invalid state");
|
|
1074
|
+
}
|
|
1075
|
+
if (this.device.configDescriptor.bConfigurationValue === configurationValue) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const config = this.configurations.find((configuration) => configuration.configurationValue === configurationValue);
|
|
1079
|
+
if (!config) {
|
|
1080
|
+
throw new Error("selectConfiguration error: configuration not found");
|
|
1081
|
+
}
|
|
1082
|
+
try {
|
|
1083
|
+
await this.setConfigurationAsync(configurationValue);
|
|
1084
|
+
} catch (error) {
|
|
1085
|
+
throw new Error(`selectConfiguration error: ${error}`);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
async claimInterface(interfaceNumber) {
|
|
1089
|
+
if (!this.opened) {
|
|
1090
|
+
throw new Error("claimInterface error: invalid state");
|
|
1091
|
+
}
|
|
1092
|
+
if (!this.configuration) {
|
|
1093
|
+
throw new Error("claimInterface error: interface not found");
|
|
1094
|
+
}
|
|
1095
|
+
const iface = this.configuration.interfaces.find((usbInterface) => usbInterface.interfaceNumber === interfaceNumber);
|
|
1096
|
+
if (!iface) {
|
|
1097
|
+
throw new Error("claimInterface error: interface not found");
|
|
1098
|
+
}
|
|
1099
|
+
if (iface.claimed) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
try {
|
|
1103
|
+
this.device.interface(interfaceNumber).claim();
|
|
1104
|
+
this.configuration.interfaces[this.configuration.interfaces.indexOf(iface)] = {
|
|
1105
|
+
interfaceNumber,
|
|
1106
|
+
alternate: iface.alternate,
|
|
1107
|
+
alternates: iface.alternates,
|
|
1108
|
+
claimed: true
|
|
1109
|
+
};
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
throw new Error(`claimInterface error: ${error}`);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
async releaseInterface(interfaceNumber) {
|
|
1115
|
+
await this._releaseInterface(interfaceNumber);
|
|
1116
|
+
if (this.configuration) {
|
|
1117
|
+
const iface = this.configuration.interfaces.find((usbInterface) => usbInterface.interfaceNumber === interfaceNumber);
|
|
1118
|
+
if (iface) {
|
|
1119
|
+
this.configuration.interfaces[this.configuration.interfaces.indexOf(iface)] = {
|
|
1120
|
+
interfaceNumber,
|
|
1121
|
+
alternate: iface.alternate,
|
|
1122
|
+
alternates: iface.alternates,
|
|
1123
|
+
claimed: false
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
async selectAlternateInterface(interfaceNumber, alternateSetting) {
|
|
1129
|
+
if (!this.opened) {
|
|
1130
|
+
throw new Error("selectAlternateInterface error: invalid state");
|
|
1131
|
+
}
|
|
1132
|
+
if (!this.configuration) {
|
|
1133
|
+
throw new Error("selectAlternateInterface error: interface not found");
|
|
1134
|
+
}
|
|
1135
|
+
const iface = this.configuration.interfaces.find((usbInterface) => usbInterface.interfaceNumber === interfaceNumber);
|
|
1136
|
+
if (!iface) {
|
|
1137
|
+
throw new Error("selectAlternateInterface error: interface not found");
|
|
1138
|
+
}
|
|
1139
|
+
if (!iface.claimed) {
|
|
1140
|
+
throw new Error("selectAlternateInterface error: invalid state");
|
|
1141
|
+
}
|
|
1142
|
+
try {
|
|
1143
|
+
const iface2 = this.device.interface(interfaceNumber);
|
|
1144
|
+
await iface2.setAltSettingAsync(alternateSetting);
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
throw new Error(`selectAlternateInterface error: ${error}`);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
async controlTransferIn(setup, length) {
|
|
1150
|
+
try {
|
|
1151
|
+
this.checkDeviceOpen();
|
|
1152
|
+
const type = this.controlTransferParamsToType(setup, usb.LIBUSB_ENDPOINT_IN);
|
|
1153
|
+
const result = await this.controlTransferAsync(type, setup.request, setup.value, setup.index, length);
|
|
1154
|
+
return {
|
|
1155
|
+
data: result ? new DataView(new Uint8Array(result).buffer) : void 0,
|
|
1156
|
+
status: "ok"
|
|
1157
|
+
};
|
|
1158
|
+
} catch (error) {
|
|
1159
|
+
if (error.errno === usb.LIBUSB_TRANSFER_STALL) {
|
|
1160
|
+
return {
|
|
1161
|
+
status: "stall"
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
if (error.errno === usb.LIBUSB_TRANSFER_OVERFLOW) {
|
|
1165
|
+
return {
|
|
1166
|
+
status: "babble"
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
throw new Error(`controlTransferIn error: ${error}`);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
async controlTransferOut(setup, data) {
|
|
1173
|
+
try {
|
|
1174
|
+
this.checkDeviceOpen();
|
|
1175
|
+
const type = this.controlTransferParamsToType(setup, usb.LIBUSB_ENDPOINT_OUT);
|
|
1176
|
+
const buffer = data ? Buffer.from(data) : Buffer.alloc(0);
|
|
1177
|
+
const bytesWritten = await this.controlTransferAsync(type, setup.request, setup.value, setup.index, buffer);
|
|
1178
|
+
return {
|
|
1179
|
+
bytesWritten,
|
|
1180
|
+
status: "ok"
|
|
1181
|
+
};
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
if (error.errno === usb.LIBUSB_TRANSFER_STALL) {
|
|
1184
|
+
return {
|
|
1185
|
+
bytesWritten: 0,
|
|
1186
|
+
status: "stall"
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
throw new Error(`controlTransferOut error: ${error}`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
async clearHalt(direction, endpointNumber) {
|
|
1193
|
+
try {
|
|
1194
|
+
const wIndex = endpointNumber | (direction === "in" ? usb.LIBUSB_ENDPOINT_IN : usb.LIBUSB_ENDPOINT_OUT);
|
|
1195
|
+
await this.controlTransferAsync(usb.LIBUSB_RECIPIENT_ENDPOINT, CLEAR_FEATURE, ENDPOINT_HALT, wIndex, Buffer.from(new Uint8Array()));
|
|
1196
|
+
} catch (error) {
|
|
1197
|
+
throw new Error(`clearHalt error: ${error}`);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
async transferIn(endpointNumber, length) {
|
|
1201
|
+
try {
|
|
1202
|
+
this.checkDeviceOpen();
|
|
1203
|
+
const endpoint = this.getEndpoint(endpointNumber | usb.LIBUSB_ENDPOINT_IN);
|
|
1204
|
+
const result = await endpoint.transferAsync(length);
|
|
1205
|
+
return {
|
|
1206
|
+
data: result ? new DataView(new Uint8Array(result).buffer) : void 0,
|
|
1207
|
+
status: "ok"
|
|
1208
|
+
};
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
if (error.errno === usb.LIBUSB_TRANSFER_STALL) {
|
|
1211
|
+
return {
|
|
1212
|
+
status: "stall"
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
if (error.errno === usb.LIBUSB_TRANSFER_OVERFLOW) {
|
|
1216
|
+
return {
|
|
1217
|
+
status: "babble"
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
throw new Error(`transferIn error: ${error}`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
async transferOut(endpointNumber, data) {
|
|
1224
|
+
try {
|
|
1225
|
+
this.checkDeviceOpen();
|
|
1226
|
+
const endpoint = this.getEndpoint(endpointNumber | usb.LIBUSB_ENDPOINT_OUT);
|
|
1227
|
+
const buffer = Buffer.from(data);
|
|
1228
|
+
const bytesWritten = await endpoint.transferAsync(buffer);
|
|
1229
|
+
return {
|
|
1230
|
+
bytesWritten,
|
|
1231
|
+
status: "ok"
|
|
1232
|
+
};
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
if (error.errno === usb.LIBUSB_TRANSFER_STALL) {
|
|
1235
|
+
return {
|
|
1236
|
+
bytesWritten: 0,
|
|
1237
|
+
status: "stall"
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
throw new Error(`transferOut error: ${error}`);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
async reset() {
|
|
1244
|
+
try {
|
|
1245
|
+
await this.resetAsync();
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
throw new Error(`reset error: ${error}`);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
async isochronousTransferIn(_endpointNumber, _packetLengths) {
|
|
1251
|
+
throw new Error("isochronousTransferIn error: method not implemented");
|
|
1252
|
+
}
|
|
1253
|
+
async isochronousTransferOut(_endpointNumber, _data, _packetLengths) {
|
|
1254
|
+
throw new Error("isochronousTransferOut error: method not implemented");
|
|
1255
|
+
}
|
|
1256
|
+
async forget() {
|
|
1257
|
+
throw new Error("forget error: method not implemented");
|
|
1258
|
+
}
|
|
1259
|
+
async initialize() {
|
|
1260
|
+
try {
|
|
1261
|
+
if (!this.opened) {
|
|
1262
|
+
this.device.open();
|
|
1263
|
+
if (this.deviceClass === 255 && (0, os_1.platform)() === "darwin") {
|
|
1264
|
+
await this.setConfigurationAsync(1);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
this.manufacturerName = await this.getStringDescriptor(this.device.deviceDescriptor.iManufacturer);
|
|
1268
|
+
this.productName = await this.getStringDescriptor(this.device.deviceDescriptor.iProduct);
|
|
1269
|
+
this.serialNumber = await this.getStringDescriptor(this.device.deviceDescriptor.iSerialNumber);
|
|
1270
|
+
this.configurations = await this.getConfigurations();
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
throw new Error(`initialize error: ${error}`);
|
|
1273
|
+
} finally {
|
|
1274
|
+
if (this.opened) {
|
|
1275
|
+
this.device.close();
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
decodeVersion(version) {
|
|
1280
|
+
const hex = `0000${version.toString(16)}`.slice(-4);
|
|
1281
|
+
return {
|
|
1282
|
+
major: parseInt(hex.substr(0, 2), void 0),
|
|
1283
|
+
minor: parseInt(hex.substr(2, 1), void 0),
|
|
1284
|
+
sub: parseInt(hex.substr(3, 1), void 0)
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
async getStringDescriptor(index) {
|
|
1288
|
+
try {
|
|
1289
|
+
const buffer = await this.getStringDescriptorAsync(index);
|
|
1290
|
+
return buffer ? buffer.toString() : "";
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
return "";
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
async getConfigurations() {
|
|
1296
|
+
const configs = [];
|
|
1297
|
+
for (const config of this.device.allConfigDescriptors) {
|
|
1298
|
+
const interfaces = [];
|
|
1299
|
+
for (const iface of config.interfaces) {
|
|
1300
|
+
const alternates = [];
|
|
1301
|
+
for (const alternate2 of iface) {
|
|
1302
|
+
const endpoints = [];
|
|
1303
|
+
for (const endpoint of alternate2.endpoints) {
|
|
1304
|
+
endpoints.push({
|
|
1305
|
+
endpointNumber: endpoint.bEndpointAddress & ENDPOINT_NUMBER_MASK,
|
|
1306
|
+
direction: endpoint.bEndpointAddress & usb.LIBUSB_ENDPOINT_IN ? "in" : "out",
|
|
1307
|
+
type: (endpoint.bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) === usb.LIBUSB_TRANSFER_TYPE_BULK ? "bulk" : (endpoint.bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) === usb.LIBUSB_TRANSFER_TYPE_INTERRUPT ? "interrupt" : "isochronous",
|
|
1308
|
+
packetSize: endpoint.wMaxPacketSize
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
alternates.push({
|
|
1312
|
+
alternateSetting: alternate2.bAlternateSetting,
|
|
1313
|
+
interfaceClass: alternate2.bInterfaceClass,
|
|
1314
|
+
interfaceSubclass: alternate2.bInterfaceSubClass,
|
|
1315
|
+
interfaceProtocol: alternate2.bInterfaceProtocol,
|
|
1316
|
+
interfaceName: await this.getStringDescriptor(alternate2.iInterface),
|
|
1317
|
+
endpoints
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
const interfaceNumber = iface[0].bInterfaceNumber;
|
|
1321
|
+
const alternate = alternates.find((alt) => alt.alternateSetting === this.device.interface(interfaceNumber).altSetting);
|
|
1322
|
+
if (alternate) {
|
|
1323
|
+
interfaces.push({
|
|
1324
|
+
interfaceNumber,
|
|
1325
|
+
alternate,
|
|
1326
|
+
alternates,
|
|
1327
|
+
claimed: false
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
configs.push({
|
|
1332
|
+
configurationValue: config.bConfigurationValue,
|
|
1333
|
+
configurationName: await this.getStringDescriptor(config.iConfiguration),
|
|
1334
|
+
interfaces
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
return configs;
|
|
1338
|
+
}
|
|
1339
|
+
getEndpoint(address) {
|
|
1340
|
+
if (!this.device.interfaces) {
|
|
1341
|
+
return void 0;
|
|
1342
|
+
}
|
|
1343
|
+
for (const iface of this.device.interfaces) {
|
|
1344
|
+
const endpoint = iface.endpoint(address);
|
|
1345
|
+
if (endpoint) {
|
|
1346
|
+
return endpoint;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return void 0;
|
|
1350
|
+
}
|
|
1351
|
+
controlTransferParamsToType(setup, direction) {
|
|
1352
|
+
const recipient = setup.recipient === "device" ? usb.LIBUSB_RECIPIENT_DEVICE : setup.recipient === "interface" ? usb.LIBUSB_RECIPIENT_INTERFACE : setup.recipient === "endpoint" ? usb.LIBUSB_RECIPIENT_ENDPOINT : usb.LIBUSB_RECIPIENT_OTHER;
|
|
1353
|
+
const requestType = setup.requestType === "standard" ? usb.LIBUSB_REQUEST_TYPE_STANDARD : setup.requestType === "class" ? usb.LIBUSB_REQUEST_TYPE_CLASS : usb.LIBUSB_REQUEST_TYPE_VENDOR;
|
|
1354
|
+
return recipient | requestType | direction;
|
|
1355
|
+
}
|
|
1356
|
+
async _releaseInterface(interfaceNumber) {
|
|
1357
|
+
if (!this.opened) {
|
|
1358
|
+
throw new Error("releaseInterface error: invalid state");
|
|
1359
|
+
}
|
|
1360
|
+
if (!this.configuration) {
|
|
1361
|
+
throw new Error("releaseInterface error: interface not found");
|
|
1362
|
+
}
|
|
1363
|
+
const iface = this.configuration.interfaces.find((usbInterface) => usbInterface.interfaceNumber === interfaceNumber);
|
|
1364
|
+
if (!iface) {
|
|
1365
|
+
throw new Error("releaseInterface error: interface not found");
|
|
1366
|
+
}
|
|
1367
|
+
if (!iface.claimed) {
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
try {
|
|
1371
|
+
const iface2 = this.device.interface(interfaceNumber);
|
|
1372
|
+
await iface2.releaseAsync();
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
throw new Error(`releaseInterface error: ${error}`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
checkDeviceOpen() {
|
|
1378
|
+
if (!this.opened) {
|
|
1379
|
+
throw new Error("The device must be opened first");
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
exports2.WebUSBDevice = WebUSBDevice;
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
// node_modules/usb/dist/webusb/index.js
|
|
1388
|
+
var require_webusb = __commonJS({
|
|
1389
|
+
"node_modules/usb/dist/webusb/index.js"(exports2) {
|
|
1390
|
+
"use strict";
|
|
1391
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
1392
|
+
exports2.WebUSB = exports2.getWebUsb = void 0;
|
|
1393
|
+
var usb = require_usb();
|
|
1394
|
+
var events_1 = require("events");
|
|
1395
|
+
var webusb_device_1 = require_webusb_device();
|
|
1396
|
+
var getWebUsb = () => {
|
|
1397
|
+
if (navigator && navigator.usb) {
|
|
1398
|
+
return navigator.usb;
|
|
1399
|
+
}
|
|
1400
|
+
return new WebUSB();
|
|
1401
|
+
};
|
|
1402
|
+
exports2.getWebUsb = getWebUsb;
|
|
1403
|
+
var NamedError = class extends Error {
|
|
1404
|
+
constructor(message, name) {
|
|
1405
|
+
super(message);
|
|
1406
|
+
this.name = name;
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
var WebUSB = class {
|
|
1410
|
+
constructor(options = {}) {
|
|
1411
|
+
this.options = options;
|
|
1412
|
+
this.emitter = new events_1.EventEmitter();
|
|
1413
|
+
this.knownDevices = /* @__PURE__ */ new Map();
|
|
1414
|
+
this.authorisedDevices = /* @__PURE__ */ new Set();
|
|
1415
|
+
const deviceConnectCallback = async (device) => {
|
|
1416
|
+
const webDevice = await this.getWebDevice(device);
|
|
1417
|
+
if (webDevice && this.isAuthorisedDevice(webDevice)) {
|
|
1418
|
+
const event = {
|
|
1419
|
+
type: "connect",
|
|
1420
|
+
device: webDevice
|
|
1421
|
+
};
|
|
1422
|
+
this.emitter.emit("connect", event);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
const deviceDisconnectCallback = async (device) => {
|
|
1426
|
+
if (this.knownDevices.has(device)) {
|
|
1427
|
+
const webDevice = this.knownDevices.get(device);
|
|
1428
|
+
if (webDevice && this.isAuthorisedDevice(webDevice)) {
|
|
1429
|
+
const event = {
|
|
1430
|
+
type: "disconnect",
|
|
1431
|
+
device: webDevice
|
|
1432
|
+
};
|
|
1433
|
+
this.emitter.emit("disconnect", event);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
this.emitter.on("newListener", (event) => {
|
|
1438
|
+
const listenerCount = this.emitter.listenerCount(event);
|
|
1439
|
+
if (listenerCount !== 0) {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
if (event === "connect") {
|
|
1443
|
+
usb.addListener("attach", deviceConnectCallback);
|
|
1444
|
+
} else if (event === "disconnect") {
|
|
1445
|
+
usb.addListener("detach", deviceDisconnectCallback);
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
this.emitter.on("removeListener", (event) => {
|
|
1449
|
+
const listenerCount = this.emitter.listenerCount(event);
|
|
1450
|
+
if (listenerCount !== 0) {
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
if (event === "connect") {
|
|
1454
|
+
usb.removeListener("attach", deviceConnectCallback);
|
|
1455
|
+
} else if (event === "disconnect") {
|
|
1456
|
+
usb.removeListener("detach", deviceDisconnectCallback);
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
set onconnect(fn) {
|
|
1461
|
+
if (this._onconnect) {
|
|
1462
|
+
this.removeEventListener("connect", this._onconnect);
|
|
1463
|
+
this._onconnect = void 0;
|
|
1464
|
+
}
|
|
1465
|
+
if (fn) {
|
|
1466
|
+
this._onconnect = fn;
|
|
1467
|
+
this.addEventListener("connect", this._onconnect);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
set ondisconnect(fn) {
|
|
1471
|
+
if (this._ondisconnect) {
|
|
1472
|
+
this.removeEventListener("disconnect", this._ondisconnect);
|
|
1473
|
+
this._ondisconnect = void 0;
|
|
1474
|
+
}
|
|
1475
|
+
if (fn) {
|
|
1476
|
+
this._ondisconnect = fn;
|
|
1477
|
+
this.addEventListener("disconnect", this._ondisconnect);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
addEventListener(type, listener) {
|
|
1481
|
+
this.emitter.addListener(type, listener);
|
|
1482
|
+
}
|
|
1483
|
+
removeEventListener(type, callback) {
|
|
1484
|
+
this.emitter.removeListener(type, callback);
|
|
1485
|
+
}
|
|
1486
|
+
dispatchEvent(_event) {
|
|
1487
|
+
return false;
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Requests a single Web USB device
|
|
1491
|
+
* @param options The options to use when scanning
|
|
1492
|
+
* @returns Promise containing the selected device
|
|
1493
|
+
*/
|
|
1494
|
+
async requestDevice(options) {
|
|
1495
|
+
if (!options) {
|
|
1496
|
+
throw new TypeError("requestDevice error: 1 argument required, but only 0 present");
|
|
1497
|
+
}
|
|
1498
|
+
if (options.constructor !== {}.constructor) {
|
|
1499
|
+
throw new TypeError("requestDevice error: parameter 1 (options) is not an object");
|
|
1500
|
+
}
|
|
1501
|
+
if (!options.filters) {
|
|
1502
|
+
throw new TypeError("requestDevice error: required member filters is undefined");
|
|
1503
|
+
}
|
|
1504
|
+
if (options.filters.constructor !== [].constructor) {
|
|
1505
|
+
throw new TypeError("requestDevice error: the provided value cannot be converted to a sequence");
|
|
1506
|
+
}
|
|
1507
|
+
options.filters.forEach((filter) => {
|
|
1508
|
+
if (filter.protocolCode && !filter.subclassCode) {
|
|
1509
|
+
throw new TypeError("requestDevice error: subclass code is required");
|
|
1510
|
+
}
|
|
1511
|
+
if (filter.subclassCode && !filter.classCode) {
|
|
1512
|
+
throw new TypeError("requestDevice error: class code is required");
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
let devices = await this.loadDevices(options.filters);
|
|
1516
|
+
devices = devices.filter((device) => this.filterDevice(device, options.filters));
|
|
1517
|
+
if (devices.length === 0) {
|
|
1518
|
+
throw new NamedError("Failed to execute 'requestDevice' on 'USB': No device selected.", "NotFoundError");
|
|
1519
|
+
}
|
|
1520
|
+
try {
|
|
1521
|
+
const device = this.options.devicesFound ? await this.options.devicesFound(devices) : devices[0];
|
|
1522
|
+
if (!device) {
|
|
1523
|
+
throw new NamedError("Failed to execute 'requestDevice' on 'USB': No device selected.", "NotFoundError");
|
|
1524
|
+
}
|
|
1525
|
+
this.authorisedDevices.add({
|
|
1526
|
+
vendorId: device.vendorId,
|
|
1527
|
+
productId: device.productId,
|
|
1528
|
+
classCode: device.deviceClass,
|
|
1529
|
+
subclassCode: device.deviceSubclass,
|
|
1530
|
+
protocolCode: device.deviceProtocol,
|
|
1531
|
+
serialNumber: device.serialNumber || void 0
|
|
1532
|
+
});
|
|
1533
|
+
return device;
|
|
1534
|
+
} catch (error) {
|
|
1535
|
+
throw new NamedError("Failed to execute 'requestDevice' on 'USB': No device selected.", "NotFoundError");
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Gets all allowed Web USB devices which are connected
|
|
1540
|
+
* @returns Promise containing an array of devices
|
|
1541
|
+
*/
|
|
1542
|
+
async getDevices() {
|
|
1543
|
+
const preFilters = this.options.allowAllDevices ? void 0 : this.options.allowedDevices;
|
|
1544
|
+
const devices = await this.loadDevices(preFilters);
|
|
1545
|
+
return devices.filter((device) => this.isAuthorisedDevice(device));
|
|
1546
|
+
}
|
|
1547
|
+
async loadDevices(preFilters) {
|
|
1548
|
+
let devices = usb.getDeviceList();
|
|
1549
|
+
devices = this.quickFilter(devices, preFilters);
|
|
1550
|
+
const refreshedKnownDevices = /* @__PURE__ */ new Map();
|
|
1551
|
+
for (const device of devices) {
|
|
1552
|
+
const webDevice = await this.getWebDevice(device);
|
|
1553
|
+
if (webDevice) {
|
|
1554
|
+
refreshedKnownDevices.set(device, webDevice);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
this.knownDevices = refreshedKnownDevices;
|
|
1558
|
+
return [...this.knownDevices.values()];
|
|
1559
|
+
}
|
|
1560
|
+
// Get a WebUSBDevice corresponding to underlying device.
|
|
1561
|
+
// Returns undefined the device was not found and could not be created.
|
|
1562
|
+
async getWebDevice(device) {
|
|
1563
|
+
if (!this.knownDevices.has(device)) {
|
|
1564
|
+
if (this.options.deviceTimeout) {
|
|
1565
|
+
device.timeout = this.options.deviceTimeout;
|
|
1566
|
+
}
|
|
1567
|
+
try {
|
|
1568
|
+
const webDevice = await webusb_device_1.WebUSBDevice.createInstance(device, this.options.autoDetachKernelDriver);
|
|
1569
|
+
this.knownDevices.set(device, webDevice);
|
|
1570
|
+
} catch {
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
return this.knownDevices.get(device);
|
|
1574
|
+
}
|
|
1575
|
+
// Undertake quick filter on devices before creating WebUSB devices if possible
|
|
1576
|
+
quickFilter(devices, preFilters) {
|
|
1577
|
+
if (!preFilters || !preFilters.length) {
|
|
1578
|
+
return devices;
|
|
1579
|
+
}
|
|
1580
|
+
return devices.filter((device) => preFilters.some((filter) => {
|
|
1581
|
+
if (filter.vendorId && filter.vendorId !== device.deviceDescriptor.idVendor)
|
|
1582
|
+
return false;
|
|
1583
|
+
if (filter.productId && filter.productId !== device.deviceDescriptor.idProduct)
|
|
1584
|
+
return false;
|
|
1585
|
+
return true;
|
|
1586
|
+
}));
|
|
1587
|
+
}
|
|
1588
|
+
// Filter WebUSB devices
|
|
1589
|
+
filterDevice(device, filters) {
|
|
1590
|
+
if (!filters || !filters.length) {
|
|
1591
|
+
return true;
|
|
1592
|
+
}
|
|
1593
|
+
return filters.some((filter) => {
|
|
1594
|
+
if (filter.vendorId && filter.vendorId !== device.vendorId)
|
|
1595
|
+
return false;
|
|
1596
|
+
if (filter.productId && filter.productId !== device.productId)
|
|
1597
|
+
return false;
|
|
1598
|
+
if (filter.classCode) {
|
|
1599
|
+
if (!device.configuration) {
|
|
1600
|
+
return false;
|
|
1601
|
+
}
|
|
1602
|
+
const match = device.configuration.interfaces.some((iface) => {
|
|
1603
|
+
if (filter.classCode && filter.classCode !== iface.alternate.interfaceClass)
|
|
1604
|
+
return false;
|
|
1605
|
+
if (filter.subclassCode && filter.subclassCode !== iface.alternate.interfaceSubclass)
|
|
1606
|
+
return false;
|
|
1607
|
+
if (filter.protocolCode && filter.protocolCode !== iface.alternate.interfaceProtocol)
|
|
1608
|
+
return false;
|
|
1609
|
+
return true;
|
|
1610
|
+
});
|
|
1611
|
+
if (match) {
|
|
1612
|
+
return true;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
if (filter.classCode && filter.classCode !== device.deviceClass)
|
|
1616
|
+
return false;
|
|
1617
|
+
if (filter.subclassCode && filter.subclassCode !== device.deviceSubclass)
|
|
1618
|
+
return false;
|
|
1619
|
+
if (filter.protocolCode && filter.protocolCode !== device.deviceProtocol)
|
|
1620
|
+
return false;
|
|
1621
|
+
if (filter.serialNumber && filter.serialNumber !== device.serialNumber)
|
|
1622
|
+
return false;
|
|
1623
|
+
return true;
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
// Check whether a device is authorised
|
|
1627
|
+
isAuthorisedDevice(device) {
|
|
1628
|
+
if (this.options.allowAllDevices) {
|
|
1629
|
+
return true;
|
|
1630
|
+
}
|
|
1631
|
+
if (this.options.allowedDevices && this.filterDevice(device, this.options.allowedDevices)) {
|
|
1632
|
+
return true;
|
|
1633
|
+
}
|
|
1634
|
+
return [...this.authorisedDevices.values()].some((authorised) => authorised.vendorId === device.vendorId && authorised.productId === device.productId && authorised.classCode === device.deviceClass && authorised.subclassCode === device.deviceSubclass && authorised.protocolCode === device.deviceProtocol && authorised.serialNumber === device.serialNumber);
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
exports2.WebUSB = WebUSB;
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
|
|
1641
|
+
// node_modules/usb/dist/usb/descriptors.js
|
|
1642
|
+
var require_descriptors = __commonJS({
|
|
1643
|
+
"node_modules/usb/dist/usb/descriptors.js"(exports2) {
|
|
1644
|
+
"use strict";
|
|
1645
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
|
|
1649
|
+
// node_modules/usb/dist/index.js
|
|
1650
|
+
var require_dist = __commonJS({
|
|
1651
|
+
"node_modules/usb/dist/index.js"(exports2) {
|
|
1652
|
+
"use strict";
|
|
1653
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
1654
|
+
if (k2 === void 0) k2 = k;
|
|
1655
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
1656
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
1657
|
+
desc = { enumerable: true, get: function() {
|
|
1658
|
+
return m[k];
|
|
1659
|
+
} };
|
|
1660
|
+
}
|
|
1661
|
+
Object.defineProperty(o, k2, desc);
|
|
1662
|
+
} : function(o, m, k, k2) {
|
|
1663
|
+
if (k2 === void 0) k2 = k;
|
|
1664
|
+
o[k2] = m[k];
|
|
1665
|
+
});
|
|
1666
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
1667
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
1668
|
+
};
|
|
1669
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
1670
|
+
exports2.LibUSBException = exports2.useUsbDkBackend = exports2.getDeviceList = exports2.Transfer = exports2.Device = exports2.webusb = exports2.findBySerialNumber = exports2.findByIds = exports2.usb = void 0;
|
|
1671
|
+
var util_1 = require("util");
|
|
1672
|
+
var webusb_1 = require_webusb();
|
|
1673
|
+
var usb = require_usb();
|
|
1674
|
+
exports2.usb = usb;
|
|
1675
|
+
var findByIds = (vid, pid) => {
|
|
1676
|
+
const devices = usb.getDeviceList();
|
|
1677
|
+
return devices.find((item) => item.deviceDescriptor.idVendor === vid && item.deviceDescriptor.idProduct === pid);
|
|
1678
|
+
};
|
|
1679
|
+
exports2.findByIds = findByIds;
|
|
1680
|
+
var findBySerialNumber = async (serialNumber) => {
|
|
1681
|
+
const devices = usb.getDeviceList();
|
|
1682
|
+
const opened = (device) => !!device.interfaces;
|
|
1683
|
+
for (const device of devices) {
|
|
1684
|
+
try {
|
|
1685
|
+
if (!opened(device)) {
|
|
1686
|
+
device.open();
|
|
1687
|
+
}
|
|
1688
|
+
const getStringDescriptor = (0, util_1.promisify)(device.getStringDescriptor).bind(device);
|
|
1689
|
+
const buffer = await getStringDescriptor(device.deviceDescriptor.iSerialNumber);
|
|
1690
|
+
if (buffer && buffer.toString() === serialNumber) {
|
|
1691
|
+
return device;
|
|
1692
|
+
}
|
|
1693
|
+
} catch {
|
|
1694
|
+
} finally {
|
|
1695
|
+
try {
|
|
1696
|
+
if (opened(device)) {
|
|
1697
|
+
device.close();
|
|
1698
|
+
}
|
|
1699
|
+
} catch {
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
return void 0;
|
|
1704
|
+
};
|
|
1705
|
+
exports2.findBySerialNumber = findBySerialNumber;
|
|
1706
|
+
var webusb = new webusb_1.WebUSB();
|
|
1707
|
+
exports2.webusb = webusb;
|
|
1708
|
+
var usb_1 = require_usb();
|
|
1709
|
+
Object.defineProperty(exports2, "Device", { enumerable: true, get: function() {
|
|
1710
|
+
return usb_1.Device;
|
|
1711
|
+
} });
|
|
1712
|
+
Object.defineProperty(exports2, "Transfer", { enumerable: true, get: function() {
|
|
1713
|
+
return usb_1.Transfer;
|
|
1714
|
+
} });
|
|
1715
|
+
Object.defineProperty(exports2, "getDeviceList", { enumerable: true, get: function() {
|
|
1716
|
+
return usb_1.getDeviceList;
|
|
1717
|
+
} });
|
|
1718
|
+
Object.defineProperty(exports2, "useUsbDkBackend", { enumerable: true, get: function() {
|
|
1719
|
+
return usb_1.useUsbDkBackend;
|
|
1720
|
+
} });
|
|
1721
|
+
Object.defineProperty(exports2, "LibUSBException", { enumerable: true, get: function() {
|
|
1722
|
+
return usb_1.LibUSBException;
|
|
1723
|
+
} });
|
|
1724
|
+
__exportStar(require_capability(), exports2);
|
|
1725
|
+
__exportStar(require_descriptors(), exports2);
|
|
1726
|
+
__exportStar(require_endpoint(), exports2);
|
|
1727
|
+
__exportStar(require_interface(), exports2);
|
|
1728
|
+
__exportStar(require_webusb(), exports2);
|
|
1729
|
+
__exportStar(require_webusb_device(), exports2);
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
// src/usb-transport.ts
|
|
1734
|
+
function listUsbDevices(log) {
|
|
1735
|
+
let usb;
|
|
1736
|
+
try {
|
|
1737
|
+
usb = require_dist();
|
|
1738
|
+
} catch {
|
|
1739
|
+
log.debug?.("usb package not available, skipping raw USB discovery");
|
|
1740
|
+
return [];
|
|
1741
|
+
}
|
|
1742
|
+
try {
|
|
1743
|
+
const deviceList = usb.getDeviceList();
|
|
1744
|
+
const results = [];
|
|
1745
|
+
for (const device of deviceList) {
|
|
1746
|
+
const desc = device.deviceDescriptor;
|
|
1747
|
+
if (desc.idVendor === SIGNOTEC_VID && desc.idProduct === SIGNOTEC_PID) {
|
|
1748
|
+
results.push({
|
|
1749
|
+
path: `usb:${desc.idVendor}:${desc.idProduct}:${device.busNumber}:${device.deviceAddress}`,
|
|
1750
|
+
serialNumber: "",
|
|
1751
|
+
product: "Signotec Pad",
|
|
1752
|
+
_usbDevice: device
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return results;
|
|
1757
|
+
} catch (err) {
|
|
1758
|
+
log.error?.("Failed to enumerate USB devices:", err.message);
|
|
1759
|
+
return [];
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
function openUsbDevice(descriptor, log) {
|
|
1763
|
+
return new UsbTransportDevice(descriptor._usbDevice, log);
|
|
1764
|
+
}
|
|
1765
|
+
var import_events, SIGNOTEC_VID, SIGNOTEC_PID, REPORT_SIZE, UsbTransportDevice;
|
|
1766
|
+
var init_usb_transport = __esm({
|
|
1767
|
+
"src/usb-transport.ts"() {
|
|
1768
|
+
"use strict";
|
|
1769
|
+
import_events = require("events");
|
|
1770
|
+
SIGNOTEC_VID = 8499;
|
|
1771
|
+
SIGNOTEC_PID = 1;
|
|
1772
|
+
REPORT_SIZE = 63;
|
|
1773
|
+
UsbTransportDevice = class extends import_events.EventEmitter {
|
|
1774
|
+
constructor(usbDevice, log) {
|
|
1775
|
+
super();
|
|
1776
|
+
this.usbDevice = null;
|
|
1777
|
+
this.iface = null;
|
|
1778
|
+
this.inEndpoint = null;
|
|
1779
|
+
this.outEndpoint = null;
|
|
1780
|
+
this.closed = false;
|
|
1781
|
+
this.polling = false;
|
|
1782
|
+
this.transferSize = REPORT_SIZE;
|
|
1783
|
+
/**
|
|
1784
|
+
* Write queue: each entry is a callback that starts the OUT transfer and
|
|
1785
|
+
* calls `next()` when the transfer completes. This ensures the previous
|
|
1786
|
+
* USB transfer is fully on the wire before the next one starts — matching
|
|
1787
|
+
* node-hid's synchronous write() behavior.
|
|
1788
|
+
*/
|
|
1789
|
+
this.writeQueue = [];
|
|
1790
|
+
this.writeBusy = false;
|
|
1791
|
+
/** Resolve callback for close() waiting on the write queue to drain. */
|
|
1792
|
+
this.drainResolve = null;
|
|
1793
|
+
this.usbDevice = usbDevice;
|
|
1794
|
+
this.log = log;
|
|
1795
|
+
try {
|
|
1796
|
+
this.usbDevice.open();
|
|
1797
|
+
} catch (err) {
|
|
1798
|
+
throw new Error(`Cannot open USB device: ${err.message}`);
|
|
1799
|
+
}
|
|
1800
|
+
const { iface, inEp, outEp } = this.findEndpoints();
|
|
1801
|
+
this.iface = iface;
|
|
1802
|
+
this.inEndpoint = inEp;
|
|
1803
|
+
this.outEndpoint = outEp;
|
|
1804
|
+
this.transferSize = this.inEndpoint.descriptor?.wMaxPacketSize || REPORT_SIZE;
|
|
1805
|
+
this.log.info?.(`USB endpoint max packet size: ${this.transferSize}`);
|
|
1806
|
+
try {
|
|
1807
|
+
if (this.iface.isKernelDriverActive?.()) {
|
|
1808
|
+
this.iface.detachKernelDriver();
|
|
1809
|
+
}
|
|
1810
|
+
} catch {
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
this.iface.claim();
|
|
1814
|
+
} catch (err) {
|
|
1815
|
+
this.usbDevice.close();
|
|
1816
|
+
throw new Error(
|
|
1817
|
+
`Cannot claim USB interface: ${err.message}. On Windows, you may need to install WinUSB driver using Zadig.`
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
this.startPolling();
|
|
1821
|
+
}
|
|
1822
|
+
findEndpoints() {
|
|
1823
|
+
const interfaces = this.usbDevice.interfaces;
|
|
1824
|
+
for (const iface of interfaces) {
|
|
1825
|
+
let inEp = null;
|
|
1826
|
+
let outEp = null;
|
|
1827
|
+
for (const ep of iface.endpoints) {
|
|
1828
|
+
if (ep.transferType === 3) {
|
|
1829
|
+
if (ep.direction === "in") inEp = ep;
|
|
1830
|
+
else if (ep.direction === "out") outEp = ep;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
if (inEp && outEp) {
|
|
1834
|
+
return { iface, inEp, outEp };
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
throw new Error(
|
|
1838
|
+
"No suitable USB interface found with interrupt IN+OUT endpoints. The device may require a different driver."
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
startPolling() {
|
|
1842
|
+
if (this.closed || !this.inEndpoint || this.polling) return;
|
|
1843
|
+
this.polling = true;
|
|
1844
|
+
this.inEndpoint.startPoll(3, this.transferSize);
|
|
1845
|
+
this.inEndpoint.on("data", (data) => {
|
|
1846
|
+
if (this.closed) return;
|
|
1847
|
+
if (!data || data.length === 0) return;
|
|
1848
|
+
this.emit("data", Buffer.from(data));
|
|
1849
|
+
});
|
|
1850
|
+
this.inEndpoint.on("error", (err) => {
|
|
1851
|
+
if (this.closed) return;
|
|
1852
|
+
if (err.message?.includes("LIBUSB_TRANSFER_CANCELLED")) return;
|
|
1853
|
+
if (err.message?.includes("LIBUSB_TRANSFER_NO_DEVICE")) {
|
|
1854
|
+
this.emit("error", new Error("Device disconnected"));
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
this.log.error?.("USB IN error:", err.message);
|
|
1858
|
+
this.emit("error", err);
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Queue an OUT transfer. Transfers are serialized: each one completes
|
|
1863
|
+
* before the next starts. This matches node-hid's synchronous write().
|
|
1864
|
+
*/
|
|
1865
|
+
write(data) {
|
|
1866
|
+
if (this.closed || !this.outEndpoint) {
|
|
1867
|
+
throw new Error("Device not connected");
|
|
1868
|
+
}
|
|
1869
|
+
const buf = Buffer.alloc(REPORT_SIZE, 0);
|
|
1870
|
+
for (let i = 0; i < Math.min(data.length, REPORT_SIZE); i++) {
|
|
1871
|
+
buf[i] = data[i];
|
|
1872
|
+
}
|
|
1873
|
+
this.writeQueue.push(() => {
|
|
1874
|
+
if (this.closed || !this.outEndpoint) {
|
|
1875
|
+
queueMicrotask(() => this.drainNext());
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
this.outEndpoint.transfer(buf, (err) => {
|
|
1879
|
+
if (err && !this.closed) {
|
|
1880
|
+
this.log.error("USB write error:", err.message);
|
|
1881
|
+
this.emit("error", err);
|
|
1882
|
+
}
|
|
1883
|
+
this.drainNext();
|
|
1884
|
+
});
|
|
1885
|
+
});
|
|
1886
|
+
if (!this.writeBusy) {
|
|
1887
|
+
this.drainNext();
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
drainNext() {
|
|
1891
|
+
const next = this.writeQueue.shift();
|
|
1892
|
+
if (next) {
|
|
1893
|
+
this.writeBusy = true;
|
|
1894
|
+
next();
|
|
1895
|
+
} else {
|
|
1896
|
+
this.writeBusy = false;
|
|
1897
|
+
this.drainResolve?.();
|
|
1898
|
+
this.drainResolve = null;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Close the device. Waits up to 1s for any in-flight transfer to complete
|
|
1903
|
+
* before releasing the USB interface.
|
|
1904
|
+
*/
|
|
1905
|
+
close() {
|
|
1906
|
+
if (this.closed) return;
|
|
1907
|
+
this.closed = true;
|
|
1908
|
+
this.writeQueue.length = 0;
|
|
1909
|
+
const finish = () => {
|
|
1910
|
+
if (this.polling && this.inEndpoint) {
|
|
1911
|
+
try {
|
|
1912
|
+
this.inEndpoint.removeAllListeners("data");
|
|
1913
|
+
this.inEndpoint.removeAllListeners("error");
|
|
1914
|
+
this.inEndpoint.stopPoll(() => this.releaseAndClose());
|
|
1915
|
+
} catch {
|
|
1916
|
+
this.releaseAndClose();
|
|
1917
|
+
}
|
|
1918
|
+
} else {
|
|
1919
|
+
this.releaseAndClose();
|
|
1920
|
+
}
|
|
1921
|
+
this.polling = false;
|
|
1922
|
+
this.removeAllListeners();
|
|
1923
|
+
};
|
|
1924
|
+
if (this.writeBusy) {
|
|
1925
|
+
const timeout = setTimeout(() => {
|
|
1926
|
+
this.log.warn("USB close: in-flight transfer did not complete in 1s, forcing close");
|
|
1927
|
+
this.writeBusy = false;
|
|
1928
|
+
finish();
|
|
1929
|
+
}, 1e3);
|
|
1930
|
+
new Promise((resolve) => {
|
|
1931
|
+
this.drainResolve = resolve;
|
|
1932
|
+
}).then(() => {
|
|
1933
|
+
clearTimeout(timeout);
|
|
1934
|
+
finish();
|
|
1935
|
+
});
|
|
1936
|
+
} else {
|
|
1937
|
+
finish();
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
releaseAndClose() {
|
|
1941
|
+
if (this.iface) {
|
|
1942
|
+
try {
|
|
1943
|
+
this.iface.release(true, () => {
|
|
1944
|
+
try {
|
|
1945
|
+
this.usbDevice?.close();
|
|
1946
|
+
} catch {
|
|
1947
|
+
}
|
|
1948
|
+
this.usbDevice = null;
|
|
1949
|
+
});
|
|
1950
|
+
} catch {
|
|
1951
|
+
try {
|
|
1952
|
+
this.usbDevice?.close();
|
|
1953
|
+
} catch {
|
|
1954
|
+
}
|
|
1955
|
+
this.usbDevice = null;
|
|
1956
|
+
}
|
|
1957
|
+
} else {
|
|
1958
|
+
try {
|
|
1959
|
+
this.usbDevice?.close();
|
|
1960
|
+
} catch {
|
|
1961
|
+
}
|
|
1962
|
+
this.usbDevice = null;
|
|
1963
|
+
}
|
|
1964
|
+
this.inEndpoint = null;
|
|
1965
|
+
this.outEndpoint = null;
|
|
1966
|
+
this.iface = null;
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1972
|
+
// src/hid-protocol.ts
|
|
1973
|
+
var SIGNOTEC_VID2, SIGNOTEC_PID2, HID_REPORT_SIZE, DEFAULTS, CMD, BUTTON_CMD, RESP, REG, SIG_EVENT, HidProtocol;
|
|
1974
|
+
var init_hid_protocol = __esm({
|
|
1975
|
+
"src/hid-protocol.ts"() {
|
|
1976
|
+
"use strict";
|
|
1977
|
+
init_usb_transport();
|
|
1978
|
+
SIGNOTEC_VID2 = 8499;
|
|
1979
|
+
SIGNOTEC_PID2 = 1;
|
|
1980
|
+
HID_REPORT_SIZE = 63;
|
|
1981
|
+
DEFAULTS = {
|
|
1982
|
+
commandTimeout: 3e3,
|
|
1983
|
+
imageTransferTimeout: 15e3,
|
|
1984
|
+
flashStoreTimeout: 25e3,
|
|
1985
|
+
chunkDelayMs: 2,
|
|
1986
|
+
maxRetries: 2,
|
|
1987
|
+
reconnectAttempts: 10,
|
|
1988
|
+
reconnectIntervalMs: 2e3,
|
|
1989
|
+
logger: console
|
|
1990
|
+
};
|
|
1991
|
+
CMD = {
|
|
1992
|
+
READ_REG: 1,
|
|
1993
|
+
WRITE_REG: 16,
|
|
1994
|
+
START_SIGN: 130,
|
|
1995
|
+
STOP_SIGN: 131,
|
|
1996
|
+
DRAW_IMAGE: 132,
|
|
1997
|
+
IMAGE_DONE: 133,
|
|
1998
|
+
ERASE_DISPLAY: 134,
|
|
1999
|
+
STORE_IMAGE: 137,
|
|
2000
|
+
SHOW_STORED: 138,
|
|
2001
|
+
DATA_CHUNK: 96,
|
|
2002
|
+
STORE_CHUNK: 112,
|
|
2003
|
+
SLIDESHOW_CONFIG: 164,
|
|
2004
|
+
EXTENDED: 168,
|
|
2005
|
+
ADVANCED_BUTTON: 173
|
|
2006
|
+
};
|
|
2007
|
+
BUTTON_CMD = {
|
|
2008
|
+
CLEAR_ALL: 1,
|
|
2009
|
+
SET_RECT: 4,
|
|
2010
|
+
ENABLE_MONITORING: 10
|
|
2011
|
+
};
|
|
2012
|
+
RESP = {
|
|
2013
|
+
READ: 32,
|
|
2014
|
+
ACK: 144,
|
|
2015
|
+
START_ACK: 146,
|
|
2016
|
+
STOP_ACK: 147,
|
|
2017
|
+
IMAGE_ACK: 148,
|
|
2018
|
+
ERROR: 224,
|
|
2019
|
+
IDLE: 80
|
|
2020
|
+
};
|
|
2021
|
+
REG = {
|
|
2022
|
+
DEVICE_ID: 0,
|
|
2023
|
+
FW_VERSION1: 1,
|
|
2024
|
+
FW_VERSION2: 2,
|
|
2025
|
+
STATE: 16,
|
|
2026
|
+
CAPABILITIES: 17,
|
|
2027
|
+
SERIAL: 20,
|
|
2028
|
+
DISPLAY_WIDTH: 21,
|
|
2029
|
+
DISPLAY_HEIGHT: 22,
|
|
2030
|
+
SIGN_LEFT: 50,
|
|
2031
|
+
SIGN_TOP: 51,
|
|
2032
|
+
SIGN_RIGHT: 52,
|
|
2033
|
+
SIGN_BOTTOM: 53
|
|
2034
|
+
};
|
|
2035
|
+
SIG_EVENT = {
|
|
2036
|
+
PEN_UP: 64,
|
|
2037
|
+
PEN_DATA: 65,
|
|
2038
|
+
PEN_DATA_EXT: 66,
|
|
2039
|
+
HOTSPOT: 67
|
|
2040
|
+
};
|
|
2041
|
+
HidProtocol = class {
|
|
2042
|
+
constructor(config) {
|
|
2043
|
+
this.device = null;
|
|
2044
|
+
this.pending = null;
|
|
2045
|
+
this.signatureCallback = null;
|
|
2046
|
+
this.disconnectCallback = null;
|
|
2047
|
+
this.connected = false;
|
|
2048
|
+
this.closing = false;
|
|
2049
|
+
/** Cached USB device descriptors from last listDevices() call (for USB fallback). */
|
|
2050
|
+
this.usbDeviceMap = /* @__PURE__ */ new Map();
|
|
2051
|
+
this.config = {
|
|
2052
|
+
commandTimeout: config?.commandTimeout ?? DEFAULTS.commandTimeout,
|
|
2053
|
+
imageTransferTimeout: config?.imageTransferTimeout ?? DEFAULTS.imageTransferTimeout,
|
|
2054
|
+
flashStoreTimeout: config?.flashStoreTimeout ?? DEFAULTS.flashStoreTimeout,
|
|
2055
|
+
chunkDelayMs: config?.chunkDelayMs ?? DEFAULTS.chunkDelayMs,
|
|
2056
|
+
maxRetries: config?.maxRetries ?? DEFAULTS.maxRetries,
|
|
2057
|
+
reconnectAttempts: config?.reconnectAttempts ?? DEFAULTS.reconnectAttempts,
|
|
2058
|
+
reconnectIntervalMs: config?.reconnectIntervalMs ?? DEFAULTS.reconnectIntervalMs
|
|
2059
|
+
};
|
|
2060
|
+
this.log = config?.logger === null ? { info() {
|
|
2061
|
+
}, warn() {
|
|
2062
|
+
}, error() {
|
|
2063
|
+
}, debug() {
|
|
2064
|
+
} } : config?.logger ?? DEFAULTS.logger;
|
|
2065
|
+
try {
|
|
2066
|
+
this.HID = require("node-hid");
|
|
2067
|
+
} catch {
|
|
2068
|
+
throw new Error(
|
|
2069
|
+
"node-hid is required for Signotec HID support. Install with: npm install node-hid"
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
// ─── Device Management ──────────────────────────────────────────────────────
|
|
2074
|
+
/**
|
|
2075
|
+
* Discover all connected Signotec pads.
|
|
2076
|
+
* Tries node-hid first; falls back to raw USB enumeration (libusb) if no
|
|
2077
|
+
* HID devices are found (e.g., Windows without HID class driver).
|
|
2078
|
+
* @returns Array of device descriptors (never throws).
|
|
2079
|
+
*/
|
|
2080
|
+
listDevices() {
|
|
2081
|
+
this.usbDeviceMap.clear();
|
|
2082
|
+
try {
|
|
2083
|
+
const hidDevices = this.HID.devices().filter((d) => d.vendorId === SIGNOTEC_VID2 && d.productId === SIGNOTEC_PID2).map((d) => ({
|
|
2084
|
+
path: d.path,
|
|
2085
|
+
serialNumber: d.serialNumber || "",
|
|
2086
|
+
product: d.product || "Signotec Pad"
|
|
2087
|
+
}));
|
|
2088
|
+
if (hidDevices.length > 0) {
|
|
2089
|
+
this.log.info(`Found ${hidDevices.length} Signotec pad(s) via HID`);
|
|
2090
|
+
return hidDevices;
|
|
2091
|
+
}
|
|
2092
|
+
} catch (err) {
|
|
2093
|
+
this.log.warn("HID enumeration failed:", err.message);
|
|
2094
|
+
}
|
|
2095
|
+
this.log.info("No Signotec pads found via HID, trying raw USB...");
|
|
2096
|
+
try {
|
|
2097
|
+
const usbDevices = listUsbDevices(this.log);
|
|
2098
|
+
if (usbDevices.length > 0) {
|
|
2099
|
+
this.log.info(`Found ${usbDevices.length} Signotec pad(s) via raw USB`);
|
|
2100
|
+
for (const d of usbDevices) {
|
|
2101
|
+
this.usbDeviceMap.set(d.path, d);
|
|
2102
|
+
}
|
|
2103
|
+
return usbDevices.map((d) => ({
|
|
2104
|
+
path: d.path,
|
|
2105
|
+
serialNumber: d.serialNumber || "",
|
|
2106
|
+
product: d.product || "Signotec Pad"
|
|
2107
|
+
}));
|
|
2108
|
+
}
|
|
2109
|
+
} catch (err) {
|
|
2110
|
+
this.log.warn("USB enumeration failed:", err.message);
|
|
2111
|
+
}
|
|
2112
|
+
this.logDiscoveryDiagnostics();
|
|
2113
|
+
return [];
|
|
2114
|
+
}
|
|
2115
|
+
/** Log diagnostic info when no devices are found. */
|
|
2116
|
+
logDiscoveryDiagnostics() {
|
|
2117
|
+
try {
|
|
2118
|
+
const allHid = this.HID.devices();
|
|
2119
|
+
const signotecHid = allHid.filter((d) => d.vendorId === SIGNOTEC_VID2);
|
|
2120
|
+
this.log.warn(
|
|
2121
|
+
`No Signotec pads found. HID bus: ${allHid.length} devices total, ${signotecHid.length} matching VID=0x${SIGNOTEC_VID2.toString(16)}.`
|
|
2122
|
+
);
|
|
2123
|
+
if (signotecHid.length > 0) {
|
|
2124
|
+
this.log.warn("Signotec HID entries (wrong PID?):", JSON.stringify(
|
|
2125
|
+
signotecHid.map((d) => ({ pid: d.productId, usagePage: d.usagePage, product: d.product }))
|
|
2126
|
+
));
|
|
2127
|
+
}
|
|
2128
|
+
} catch {
|
|
2129
|
+
}
|
|
2130
|
+
const platform = process.platform;
|
|
2131
|
+
if (platform === "linux") {
|
|
2132
|
+
this.log.warn(
|
|
2133
|
+
"Linux: ensure udev rules are installed. Copy 99-signotec.rules to /etc/udev/rules.d/ and run: sudo udevadm control --reload-rules && sudo udevadm trigger"
|
|
2134
|
+
);
|
|
2135
|
+
} else if (platform === "darwin") {
|
|
2136
|
+
this.log.warn(
|
|
2137
|
+
"macOS: check System Preferences > Security & Privacy > Input Monitoring permissions."
|
|
2138
|
+
);
|
|
2139
|
+
} else if (platform === "win32") {
|
|
2140
|
+
this.log.warn(
|
|
2141
|
+
"Windows: the device may need the WinUSB driver. Install it using Zadig (https://zadig.akeo.ie/) by selecting the Signotec device and choosing WinUSB."
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Open a device by its path (HID path or USB path).
|
|
2147
|
+
* USB paths start with "usb:" and use the raw USB transport fallback.
|
|
2148
|
+
* @throws Error if the device cannot be opened.
|
|
2149
|
+
*/
|
|
2150
|
+
open(devicePath) {
|
|
2151
|
+
if (this.device) this.close();
|
|
2152
|
+
this.closing = false;
|
|
2153
|
+
const usbDescriptor = this.usbDeviceMap.get(devicePath);
|
|
2154
|
+
if (usbDescriptor) {
|
|
2155
|
+
this.log.info("Opening Signotec pad via raw USB transport");
|
|
2156
|
+
this.device = openUsbDevice(usbDescriptor, this.log);
|
|
2157
|
+
} else {
|
|
2158
|
+
this.device = new this.HID.HID(devicePath);
|
|
2159
|
+
}
|
|
2160
|
+
this.connected = true;
|
|
2161
|
+
this.device.on("data", (data) => this.handleData(Buffer.from(data)));
|
|
2162
|
+
this.device.on("error", (err) => {
|
|
2163
|
+
if (this.closing) return;
|
|
2164
|
+
this.log.error("Signotec HID error:", err.message);
|
|
2165
|
+
this.connected = false;
|
|
2166
|
+
this.disconnectCallback?.();
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
/** Close the device connection and release resources. */
|
|
2170
|
+
close() {
|
|
2171
|
+
this.closing = true;
|
|
2172
|
+
this.connected = false;
|
|
2173
|
+
if (this.pending) {
|
|
2174
|
+
clearTimeout(this.pending.timer);
|
|
2175
|
+
this.pending.resolve(null);
|
|
2176
|
+
this.pending = null;
|
|
2177
|
+
}
|
|
2178
|
+
if (this.device) {
|
|
2179
|
+
try {
|
|
2180
|
+
this.device.removeAllListeners?.();
|
|
2181
|
+
} catch {
|
|
2182
|
+
}
|
|
2183
|
+
try {
|
|
2184
|
+
this.device.close();
|
|
2185
|
+
} catch (err) {
|
|
2186
|
+
this.log.warn("Error closing HID device:", err.message);
|
|
2187
|
+
}
|
|
2188
|
+
this.device = null;
|
|
2189
|
+
}
|
|
2190
|
+
this.signatureCallback = null;
|
|
2191
|
+
}
|
|
2192
|
+
/** Check if the device connection is alive. */
|
|
2193
|
+
isConnected() {
|
|
2194
|
+
return this.connected && this.device !== null && !this.closing;
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Verify the device is actually responsive (sends a register read and checks response).
|
|
2198
|
+
* Useful after reconnection or before starting a session.
|
|
2199
|
+
*/
|
|
2200
|
+
async isResponsive() {
|
|
2201
|
+
if (!this.isConnected()) return false;
|
|
2202
|
+
const resp = await this.readRegister(REG.STATE);
|
|
2203
|
+
return resp !== null;
|
|
2204
|
+
}
|
|
2205
|
+
/** Register a callback for device disconnect events. */
|
|
2206
|
+
onDisconnect(cb) {
|
|
2207
|
+
this.disconnectCallback = cb;
|
|
2208
|
+
}
|
|
2209
|
+
/** Register a callback for signature/hotspot events from the device. */
|
|
2210
|
+
onSignatureEvent(cb) {
|
|
2211
|
+
this.signatureCallback = cb;
|
|
2212
|
+
}
|
|
2213
|
+
// ─── Core I/O ─────────────────────────────────────────────────────────────────
|
|
2214
|
+
send(bytes) {
|
|
2215
|
+
if (!this.device || !this.connected) throw new Error("Device not connected");
|
|
2216
|
+
const buf = Buffer.alloc(HID_REPORT_SIZE, 0);
|
|
2217
|
+
for (let i = 0; i < Math.min(bytes.length, HID_REPORT_SIZE); i++) {
|
|
2218
|
+
buf[i] = bytes[i];
|
|
2219
|
+
}
|
|
2220
|
+
this.device.write([...buf]);
|
|
2221
|
+
}
|
|
2222
|
+
/**
|
|
2223
|
+
* Send a command and wait for the response.
|
|
2224
|
+
* @returns Response buffer, or null on timeout/error.
|
|
2225
|
+
*/
|
|
2226
|
+
sendCommand(bytes, timeout) {
|
|
2227
|
+
const t = timeout ?? this.config.commandTimeout;
|
|
2228
|
+
if (!this.device || !this.connected) return Promise.resolve(null);
|
|
2229
|
+
if (this.pending) {
|
|
2230
|
+
clearTimeout(this.pending.timer);
|
|
2231
|
+
this.pending.resolve(null);
|
|
2232
|
+
this.pending = null;
|
|
2233
|
+
}
|
|
2234
|
+
return new Promise((resolve) => {
|
|
2235
|
+
const timer = setTimeout(() => {
|
|
2236
|
+
if (this.pending?.resolve === resolve) {
|
|
2237
|
+
this.pending = null;
|
|
2238
|
+
resolve(null);
|
|
2239
|
+
}
|
|
2240
|
+
}, t);
|
|
2241
|
+
this.pending = { resolve, timer };
|
|
2242
|
+
try {
|
|
2243
|
+
this.send(bytes);
|
|
2244
|
+
} catch {
|
|
2245
|
+
clearTimeout(timer);
|
|
2246
|
+
this.pending = null;
|
|
2247
|
+
this.connected = false;
|
|
2248
|
+
resolve(null);
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Send a command with automatic retry on failure.
|
|
2254
|
+
* @param bytes Command bytes to send
|
|
2255
|
+
* @param validateFn Function to check if response is valid (defaults to non-null + non-error)
|
|
2256
|
+
* @param timeout Timeout per attempt
|
|
2257
|
+
* @param maxRetries Max retry count (from config if not specified)
|
|
2258
|
+
*/
|
|
2259
|
+
async sendCommandWithRetry(bytes, validateFn, timeout, maxRetries) {
|
|
2260
|
+
const retries = maxRetries ?? this.config.maxRetries;
|
|
2261
|
+
const validate = validateFn ?? ((r) => r[0] !== RESP.ERROR);
|
|
2262
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
2263
|
+
const resp = await this.sendCommand(bytes, timeout);
|
|
2264
|
+
if (resp && validate(resp)) return resp;
|
|
2265
|
+
if (attempt < retries) {
|
|
2266
|
+
this.log.warn(`HID command 0x${bytes[0]?.toString(16)} failed (attempt ${attempt + 1}/${retries + 1}), retrying...`);
|
|
2267
|
+
await new Promise((r) => setTimeout(r, 100 * (attempt + 1)));
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
return null;
|
|
2271
|
+
}
|
|
2272
|
+
/** Send raw bytes without waiting for a response (fire-and-forget). */
|
|
2273
|
+
sendRaw(bytes) {
|
|
2274
|
+
try {
|
|
2275
|
+
this.send(bytes);
|
|
2276
|
+
} catch {
|
|
2277
|
+
this.connected = false;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Send a bulk data chunk for image transfers.
|
|
2282
|
+
* @param cmdByte Command byte (0x60 for display, 0x70 for flash store)
|
|
2283
|
+
* @param data Source buffer
|
|
2284
|
+
* @param offset Starting offset in data
|
|
2285
|
+
* @param maxPayload Max bytes per chunk (default 62)
|
|
2286
|
+
*/
|
|
2287
|
+
sendChunk(cmdByte, data, offset, maxPayload = 62) {
|
|
2288
|
+
const chunk = Buffer.alloc(HID_REPORT_SIZE, 0);
|
|
2289
|
+
chunk[0] = cmdByte;
|
|
2290
|
+
const toCopy = Math.min(maxPayload, data.length - offset);
|
|
2291
|
+
data.copy(chunk, 1, offset, offset + toCopy);
|
|
2292
|
+
try {
|
|
2293
|
+
this.device?.write([...chunk]);
|
|
2294
|
+
} catch {
|
|
2295
|
+
this.connected = false;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
handleData(buf) {
|
|
2299
|
+
const type = buf[0];
|
|
2300
|
+
if (type === RESP.IDLE || type === 81) return;
|
|
2301
|
+
if (type === SIG_EVENT.PEN_DATA || type === SIG_EVENT.PEN_UP || type === SIG_EVENT.PEN_DATA_EXT || type === SIG_EVENT.HOTSPOT) {
|
|
2302
|
+
this.signatureCallback?.(type, buf);
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
if (this.pending) {
|
|
2306
|
+
clearTimeout(this.pending.timer);
|
|
2307
|
+
const p = this.pending;
|
|
2308
|
+
this.pending = null;
|
|
2309
|
+
p.resolve(buf);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
// ─── Register Operations ──────────────────────────────────────────────────────
|
|
2313
|
+
/** Read a device register. Returns the data portion or null on error/timeout. */
|
|
2314
|
+
async readRegister(reg) {
|
|
2315
|
+
const resp = await this.sendCommand([CMD.READ_REG, reg]);
|
|
2316
|
+
if (resp && (resp[0] & 240) === 32) {
|
|
2317
|
+
const len = resp[2];
|
|
2318
|
+
return Buffer.from(resp.slice(3, 3 + len));
|
|
2319
|
+
}
|
|
2320
|
+
return null;
|
|
2321
|
+
}
|
|
2322
|
+
/** Write a 4-byte little-endian value to a register. */
|
|
2323
|
+
async writeRegister(reg, value) {
|
|
2324
|
+
const resp = await this.sendCommand([
|
|
2325
|
+
CMD.WRITE_REG,
|
|
2326
|
+
reg,
|
|
2327
|
+
4,
|
|
2328
|
+
value & 255,
|
|
2329
|
+
value >> 8 & 255,
|
|
2330
|
+
value >> 16 & 255,
|
|
2331
|
+
value >> 24 & 255
|
|
2332
|
+
]);
|
|
2333
|
+
if (!resp) return false;
|
|
2334
|
+
return (resp[0] & 240) === 32 || (resp[0] & 240) === 144;
|
|
2335
|
+
}
|
|
2336
|
+
/** Read full device info from registers. */
|
|
2337
|
+
async getDeviceInfo() {
|
|
2338
|
+
const info = {
|
|
2339
|
+
serialNo: "unknown",
|
|
2340
|
+
displayWidth: 320,
|
|
2341
|
+
displayHeight: 160,
|
|
2342
|
+
sensorWidth: 320,
|
|
2343
|
+
sensorHeight: 160,
|
|
2344
|
+
deviceId: 0,
|
|
2345
|
+
fwMajor: 0,
|
|
2346
|
+
fwMinor: 0,
|
|
2347
|
+
capabilities: 0
|
|
2348
|
+
};
|
|
2349
|
+
const serial = await this.readRegister(REG.SERIAL);
|
|
2350
|
+
if (serial) info.serialNo = serial.toString("utf16le").replace(/[\0#]/g, "");
|
|
2351
|
+
const devId = await this.readRegister(REG.DEVICE_ID);
|
|
2352
|
+
if (devId?.length && devId.length >= 4) info.deviceId = devId.readUInt32LE(0);
|
|
2353
|
+
const fw1 = await this.readRegister(REG.FW_VERSION1);
|
|
2354
|
+
if (fw1?.length && fw1.length >= 4) {
|
|
2355
|
+
info.fwMajor = fw1.readUInt16LE(0);
|
|
2356
|
+
info.fwMinor = fw1.readUInt16LE(2);
|
|
2357
|
+
}
|
|
2358
|
+
const caps = await this.readRegister(REG.CAPABILITIES);
|
|
2359
|
+
if (caps?.length && caps.length >= 4) info.capabilities = caps.readUInt32LE(0);
|
|
2360
|
+
const dw = await this.readRegister(REG.DISPLAY_WIDTH);
|
|
2361
|
+
if (dw?.length && dw.length >= 4) info.displayWidth = dw.readUInt32LE(0);
|
|
2362
|
+
const dh = await this.readRegister(REG.DISPLAY_HEIGHT);
|
|
2363
|
+
if (dh?.length && dh.length >= 4) info.displayHeight = dh.readUInt32LE(0);
|
|
2364
|
+
return info;
|
|
2365
|
+
}
|
|
2366
|
+
// ─── Display Commands ─────────────────────────────────────────────────────────
|
|
2367
|
+
/** Erase the pad display (clear to white). */
|
|
2368
|
+
async eraseDisplay() {
|
|
2369
|
+
const resp = await this.sendCommandWithRetry(
|
|
2370
|
+
[CMD.ERASE_DISPLAY],
|
|
2371
|
+
(r) => r[0] === RESP.ACK
|
|
2372
|
+
);
|
|
2373
|
+
return resp !== null;
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Send monochrome 1bpp bitmap data to the pad display.
|
|
2377
|
+
* Protocol: 0x84 header → 0x60 data chunks → 0x85 done.
|
|
2378
|
+
* Automatically inverts polarity (pad uses 1=black, framebuffer uses 0=black).
|
|
2379
|
+
*/
|
|
2380
|
+
async drawImage(x, y, width, height, bitmapData) {
|
|
2381
|
+
const imgHeader = Buffer.alloc(25, 0);
|
|
2382
|
+
imgHeader[0] = CMD.DRAW_IMAGE;
|
|
2383
|
+
imgHeader.writeInt32LE(x, 1);
|
|
2384
|
+
imgHeader.writeInt32LE(y, 5);
|
|
2385
|
+
imgHeader.writeInt32LE(width, 9);
|
|
2386
|
+
imgHeader.writeInt32LE(height, 13);
|
|
2387
|
+
const resp = await this.sendCommandWithRetry(
|
|
2388
|
+
[...imgHeader],
|
|
2389
|
+
(r) => r[0] === RESP.ACK,
|
|
2390
|
+
this.config.imageTransferTimeout
|
|
2391
|
+
);
|
|
2392
|
+
if (!resp) return false;
|
|
2393
|
+
const invertedData = Buffer.alloc(bitmapData.length);
|
|
2394
|
+
for (let i = 0; i < bitmapData.length; i++) {
|
|
2395
|
+
invertedData[i] = bitmapData[i] ^ 255;
|
|
2396
|
+
}
|
|
2397
|
+
const chunkPayload = 62;
|
|
2398
|
+
for (let offset = 0; offset < invertedData.length; offset += chunkPayload) {
|
|
2399
|
+
this.sendChunk(CMD.DATA_CHUNK, invertedData, offset, chunkPayload);
|
|
2400
|
+
if (offset % (chunkPayload * 10) === 0 && offset > 0) {
|
|
2401
|
+
await new Promise((r) => setTimeout(r, this.config.chunkDelayMs));
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
const doneResp = await this.sendCommand([CMD.IMAGE_DONE], this.config.imageTransferTimeout);
|
|
2405
|
+
return doneResp !== null && (doneResp[0] === RESP.IMAGE_ACK || doneResp[0] === RESP.ACK);
|
|
2406
|
+
}
|
|
2407
|
+
// ─── Flash Storage ────────────────────────────────────────────────────────────
|
|
2408
|
+
/**
|
|
2409
|
+
* Store a 1bpp bitmap to the pad's non-volatile flash as standby image.
|
|
2410
|
+
* Persists across power cycles and USB disconnects.
|
|
2411
|
+
*
|
|
2412
|
+
* Protocol: 0x89 header → 0x70 data chunks → 0xA4 standby config.
|
|
2413
|
+
* Flash uses direct polarity (opposite of display), auto-detected.
|
|
2414
|
+
*/
|
|
2415
|
+
async storeStandbyImage(width, height, bitmapData) {
|
|
2416
|
+
let ones = 0;
|
|
2417
|
+
for (let i = 0; i < bitmapData.length; i++) {
|
|
2418
|
+
for (let b = 0; b < 8; b++) {
|
|
2419
|
+
if (bitmapData[i] & 1 << b) ones++;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
const needsInvert = ones > bitmapData.length * 4;
|
|
2423
|
+
const storeData = Buffer.alloc(bitmapData.length);
|
|
2424
|
+
for (let i = 0; i < bitmapData.length; i++) {
|
|
2425
|
+
storeData[i] = needsInvert ? bitmapData[i] ^ 255 : bitmapData[i];
|
|
2426
|
+
}
|
|
2427
|
+
const storeHeader = Buffer.alloc(33, 0);
|
|
2428
|
+
storeHeader[0] = CMD.STORE_IMAGE;
|
|
2429
|
+
storeHeader.writeInt32LE(0, 1);
|
|
2430
|
+
storeHeader.writeInt32LE(0, 5);
|
|
2431
|
+
storeHeader.writeInt32LE(width, 9);
|
|
2432
|
+
storeHeader.writeInt32LE(height, 13);
|
|
2433
|
+
storeHeader.writeInt32LE(0, 17);
|
|
2434
|
+
storeHeader.writeInt32LE(1, 21);
|
|
2435
|
+
storeHeader.writeInt32LE(1, 25);
|
|
2436
|
+
storeHeader.writeInt32LE(0, 29);
|
|
2437
|
+
const resp1 = await this.sendCommand([...storeHeader], this.config.flashStoreTimeout);
|
|
2438
|
+
if (!resp1 || resp1[0] !== RESP.ACK) {
|
|
2439
|
+
this.log.error("Flash store header rejected:", resp1 ? `0x${resp1[0].toString(16)}` : "timeout");
|
|
2440
|
+
return false;
|
|
2441
|
+
}
|
|
2442
|
+
const chunkPayload = 62;
|
|
2443
|
+
for (let offset = 0; offset < storeData.length; offset += chunkPayload) {
|
|
2444
|
+
this.sendChunk(CMD.STORE_CHUNK, storeData, offset, chunkPayload);
|
|
2445
|
+
if (offset % (chunkPayload * 20) === 0 && offset > 0) {
|
|
2446
|
+
await new Promise((r) => setTimeout(r, this.config.chunkDelayMs * 3));
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2450
|
+
return this.sendSlideshowConfig(2, 3, 100, 1);
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Show the stored standby image from flash on the display.
|
|
2454
|
+
* Command 0x8A = "draw stored image" (from APIClosePad → APIDrawStoredImage).
|
|
2455
|
+
*
|
|
2456
|
+
* Single HID command - no image data transfer. Works regardless of who
|
|
2457
|
+
* stored the image (our library, Signotec SlideShow tool, etc.)
|
|
2458
|
+
*/
|
|
2459
|
+
async showStandbyFromFlash() {
|
|
2460
|
+
const cmd = Buffer.alloc(33, 0);
|
|
2461
|
+
cmd[0] = CMD.SHOW_STORED;
|
|
2462
|
+
const resp = await this.sendCommand([...cmd], this.config.imageTransferTimeout);
|
|
2463
|
+
return resp !== null && (resp[0] === RESP.ACK || resp[0] === 152);
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Send a slideshow/standby configuration command (0xA4).
|
|
2467
|
+
* Automatically wraps in 0xA8 for Sigma pads (which don't support direct 0xA4).
|
|
2468
|
+
*/
|
|
2469
|
+
async sendSlideshowConfig(action, mode, count, flag) {
|
|
2470
|
+
const configCmd = Buffer.alloc(49, 0);
|
|
2471
|
+
configCmd[0] = CMD.SLIDESHOW_CONFIG;
|
|
2472
|
+
configCmd.writeInt32LE(action, 1);
|
|
2473
|
+
configCmd.writeInt32LE(mode, 5);
|
|
2474
|
+
configCmd.writeInt32LE(count, 9);
|
|
2475
|
+
configCmd.writeInt32LE(flag, 13);
|
|
2476
|
+
let resp = await this.sendCommand([...configCmd], 5e3);
|
|
2477
|
+
if (!resp || resp[0] === RESP.ERROR) {
|
|
2478
|
+
const a8cmd = Buffer.alloc(50, 0);
|
|
2479
|
+
a8cmd[0] = CMD.EXTENDED;
|
|
2480
|
+
configCmd.copy(a8cmd, 1, 0, 49);
|
|
2481
|
+
resp = await this.sendCommand([...a8cmd], 5e3);
|
|
2482
|
+
}
|
|
2483
|
+
return resp !== null && (resp[0] === RESP.ACK || (resp[0] & 240) === 144);
|
|
2484
|
+
}
|
|
2485
|
+
// ─── Sensor / Hotspot Commands ────────────────────────────────────────────────
|
|
2486
|
+
/** Set the signature capture rectangle (left, top, width, height → registers 0x32-0x35). */
|
|
2487
|
+
async setSignRect(left, top, width, height) {
|
|
2488
|
+
const r1 = await this.writeRegister(REG.SIGN_LEFT, Math.round(left));
|
|
2489
|
+
const r2 = await this.writeRegister(REG.SIGN_TOP, Math.round(top));
|
|
2490
|
+
const r3 = await this.writeRegister(REG.SIGN_RIGHT, Math.round(left + width));
|
|
2491
|
+
const r4 = await this.writeRegister(REG.SIGN_BOTTOM, Math.round(top + height));
|
|
2492
|
+
return r1 && r2 && r3 && r4;
|
|
2493
|
+
}
|
|
2494
|
+
/** Clear all hotspot areas. */
|
|
2495
|
+
async clearHotSpots() {
|
|
2496
|
+
const resp = await this.sendCommand([CMD.ADVANCED_BUTTON, BUTTON_CMD.CLEAR_ALL]);
|
|
2497
|
+
return resp !== null && (resp[0] & 240) === 144;
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Add a hotspot (touch-sensitive button area).
|
|
2501
|
+
* @param index Hotspot index (0-based)
|
|
2502
|
+
* @param left X position
|
|
2503
|
+
* @param top Y position
|
|
2504
|
+
* @param width Width in pixels
|
|
2505
|
+
* @param height Height in pixels
|
|
2506
|
+
*/
|
|
2507
|
+
async addHotSpot(index, left, top, width, height) {
|
|
2508
|
+
const resp = await this.sendCommand([
|
|
2509
|
+
CMD.ADVANCED_BUTTON,
|
|
2510
|
+
BUTTON_CMD.SET_RECT,
|
|
2511
|
+
1,
|
|
2512
|
+
Math.round(index) & 127 | 128,
|
|
2513
|
+
Math.round(left) & 255,
|
|
2514
|
+
Math.round(left) >> 8 & 255,
|
|
2515
|
+
Math.round(top) & 255,
|
|
2516
|
+
Math.round(top) >> 8 & 255,
|
|
2517
|
+
0,
|
|
2518
|
+
Math.round(width) & 255,
|
|
2519
|
+
Math.round(width) >> 8 & 255,
|
|
2520
|
+
Math.round(height) & 255,
|
|
2521
|
+
Math.round(height) >> 8 & 255
|
|
2522
|
+
]);
|
|
2523
|
+
return resp !== null && (resp[0] & 240) === 144;
|
|
2524
|
+
}
|
|
2525
|
+
/** Enable hotspot monitoring (call after addHotSpot). */
|
|
2526
|
+
async enableHotspotMonitoring() {
|
|
2527
|
+
const resp = await this.sendCommand([CMD.ADVANCED_BUTTON, BUTTON_CMD.ENABLE_MONITORING]);
|
|
2528
|
+
return resp !== null && (resp[0] & 240) === 144;
|
|
2529
|
+
}
|
|
2530
|
+
// ─── Signature Session ────────────────────────────────────────────────────────
|
|
2531
|
+
/** Start signature capture. Protocol: [0x82, 0x00, 0x00, 0x00, 0x00]. */
|
|
2532
|
+
async startSignature() {
|
|
2533
|
+
const resp = await this.sendCommandWithRetry(
|
|
2534
|
+
[CMD.START_SIGN, 0, 0, 0, 0],
|
|
2535
|
+
(r) => r[0] === RESP.START_ACK
|
|
2536
|
+
);
|
|
2537
|
+
return resp !== null;
|
|
2538
|
+
}
|
|
2539
|
+
/** Stop signature capture. */
|
|
2540
|
+
async stopSignature() {
|
|
2541
|
+
const resp = await this.sendCommand([CMD.STOP_SIGN]);
|
|
2542
|
+
return resp !== null && resp[0] === RESP.STOP_ACK;
|
|
2543
|
+
}
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
|
|
2548
|
+
// src/font-8x13.ts
|
|
2549
|
+
var FONT_8X13;
|
|
2550
|
+
var init_font_8x13 = __esm({
|
|
2551
|
+
"src/font-8x13.ts"() {
|
|
2552
|
+
"use strict";
|
|
2553
|
+
FONT_8X13 = {
|
|
2554
|
+
// ─── Space ───────────────────────────────────────────────────────────────
|
|
2555
|
+
" ": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2556
|
+
// ─── Digits ──────────────────────────────────────────────────────────────
|
|
2557
|
+
"0": [0, 0, 60, 102, 110, 118, 102, 102, 102, 60, 0, 0, 0],
|
|
2558
|
+
"1": [0, 0, 24, 56, 24, 24, 24, 24, 24, 126, 0, 0, 0],
|
|
2559
|
+
"2": [0, 0, 60, 102, 6, 12, 24, 48, 96, 126, 0, 0, 0],
|
|
2560
|
+
"3": [0, 0, 60, 102, 6, 28, 6, 6, 102, 60, 0, 0, 0],
|
|
2561
|
+
"4": [0, 0, 12, 28, 44, 76, 126, 12, 12, 12, 0, 0, 0],
|
|
2562
|
+
"5": [0, 0, 126, 96, 96, 124, 6, 6, 102, 60, 0, 0, 0],
|
|
2563
|
+
"6": [0, 0, 28, 48, 96, 124, 102, 102, 102, 60, 0, 0, 0],
|
|
2564
|
+
"7": [0, 0, 126, 6, 12, 24, 24, 48, 48, 48, 0, 0, 0],
|
|
2565
|
+
"8": [0, 0, 60, 102, 102, 60, 102, 102, 102, 60, 0, 0, 0],
|
|
2566
|
+
"9": [0, 0, 60, 102, 102, 102, 62, 6, 12, 56, 0, 0, 0],
|
|
2567
|
+
// ─── Uppercase A–Z ───────────────────────────────────────────────────────
|
|
2568
|
+
"A": [0, 0, 24, 60, 102, 102, 126, 102, 102, 102, 0, 0, 0],
|
|
2569
|
+
"B": [0, 0, 124, 102, 102, 124, 102, 102, 102, 124, 0, 0, 0],
|
|
2570
|
+
"C": [0, 0, 60, 102, 96, 96, 96, 96, 102, 60, 0, 0, 0],
|
|
2571
|
+
"D": [0, 0, 120, 108, 102, 102, 102, 102, 108, 120, 0, 0, 0],
|
|
2572
|
+
"E": [0, 0, 126, 96, 96, 124, 96, 96, 96, 126, 0, 0, 0],
|
|
2573
|
+
"F": [0, 0, 126, 96, 96, 124, 96, 96, 96, 96, 0, 0, 0],
|
|
2574
|
+
"G": [0, 0, 60, 102, 96, 96, 110, 102, 102, 62, 0, 0, 0],
|
|
2575
|
+
"H": [0, 0, 102, 102, 102, 126, 102, 102, 102, 102, 0, 0, 0],
|
|
2576
|
+
"I": [0, 0, 60, 24, 24, 24, 24, 24, 24, 60, 0, 0, 0],
|
|
2577
|
+
"J": [0, 0, 30, 6, 6, 6, 6, 6, 102, 60, 0, 0, 0],
|
|
2578
|
+
"K": [0, 0, 102, 108, 120, 112, 120, 108, 102, 102, 0, 0, 0],
|
|
2579
|
+
"L": [0, 0, 96, 96, 96, 96, 96, 96, 96, 126, 0, 0, 0],
|
|
2580
|
+
"M": [0, 0, 66, 102, 126, 126, 90, 66, 66, 66, 0, 0, 0],
|
|
2581
|
+
"N": [0, 0, 98, 114, 122, 110, 102, 102, 102, 102, 0, 0, 0],
|
|
2582
|
+
"O": [0, 0, 60, 102, 102, 102, 102, 102, 102, 60, 0, 0, 0],
|
|
2583
|
+
"P": [0, 0, 124, 102, 102, 102, 124, 96, 96, 96, 0, 0, 0],
|
|
2584
|
+
"Q": [0, 0, 60, 102, 102, 102, 102, 118, 108, 54, 0, 0, 0],
|
|
2585
|
+
"R": [0, 0, 124, 102, 102, 124, 120, 108, 102, 102, 0, 0, 0],
|
|
2586
|
+
"S": [0, 0, 60, 102, 96, 56, 12, 6, 102, 60, 0, 0, 0],
|
|
2587
|
+
"T": [0, 0, 126, 24, 24, 24, 24, 24, 24, 24, 0, 0, 0],
|
|
2588
|
+
"U": [0, 0, 102, 102, 102, 102, 102, 102, 102, 60, 0, 0, 0],
|
|
2589
|
+
"V": [0, 0, 102, 102, 102, 102, 102, 60, 24, 24, 0, 0, 0],
|
|
2590
|
+
"W": [0, 0, 66, 66, 66, 90, 90, 126, 102, 66, 0, 0, 0],
|
|
2591
|
+
"X": [0, 0, 102, 102, 60, 24, 60, 102, 102, 102, 0, 0, 0],
|
|
2592
|
+
"Y": [0, 0, 102, 102, 102, 60, 24, 24, 24, 24, 0, 0, 0],
|
|
2593
|
+
"Z": [0, 0, 126, 6, 12, 24, 48, 96, 96, 126, 0, 0, 0],
|
|
2594
|
+
// ─── Lowercase a–z ───────────────────────────────────────────────────────
|
|
2595
|
+
// Baseline row = index 9 (row 10). Descenders use indices 10-11.
|
|
2596
|
+
"a": [0, 0, 0, 0, 60, 6, 62, 102, 102, 62, 0, 0, 0],
|
|
2597
|
+
"b": [0, 0, 96, 96, 124, 102, 102, 102, 102, 124, 0, 0, 0],
|
|
2598
|
+
"c": [0, 0, 0, 0, 60, 102, 96, 96, 102, 60, 0, 0, 0],
|
|
2599
|
+
"d": [0, 0, 6, 6, 62, 102, 102, 102, 102, 62, 0, 0, 0],
|
|
2600
|
+
"e": [0, 0, 0, 0, 60, 102, 126, 96, 102, 60, 0, 0, 0],
|
|
2601
|
+
"f": [0, 0, 14, 24, 24, 126, 24, 24, 24, 24, 0, 0, 0],
|
|
2602
|
+
"g": [0, 0, 0, 0, 62, 102, 102, 102, 62, 6, 6, 60, 0],
|
|
2603
|
+
"h": [0, 0, 96, 96, 124, 102, 102, 102, 102, 102, 0, 0, 0],
|
|
2604
|
+
"i": [0, 0, 24, 0, 56, 24, 24, 24, 24, 60, 0, 0, 0],
|
|
2605
|
+
"j": [0, 0, 6, 0, 14, 6, 6, 6, 6, 6, 70, 60, 0],
|
|
2606
|
+
"k": [0, 0, 96, 96, 102, 108, 120, 124, 108, 102, 0, 0, 0],
|
|
2607
|
+
"l": [0, 0, 56, 24, 24, 24, 24, 24, 24, 14, 0, 0, 0],
|
|
2608
|
+
"m": [0, 0, 0, 0, 108, 126, 126, 106, 98, 98, 0, 0, 0],
|
|
2609
|
+
"n": [0, 0, 0, 0, 124, 102, 102, 102, 102, 102, 0, 0, 0],
|
|
2610
|
+
"o": [0, 0, 0, 0, 60, 102, 102, 102, 102, 60, 0, 0, 0],
|
|
2611
|
+
"p": [0, 0, 0, 0, 124, 102, 102, 102, 124, 96, 96, 96, 0],
|
|
2612
|
+
"q": [0, 0, 0, 0, 62, 102, 102, 102, 62, 6, 6, 6, 0],
|
|
2613
|
+
"r": [0, 0, 0, 0, 108, 118, 96, 96, 96, 96, 0, 0, 0],
|
|
2614
|
+
"s": [0, 0, 0, 0, 60, 102, 56, 12, 102, 60, 0, 0, 0],
|
|
2615
|
+
"t": [0, 0, 24, 24, 126, 24, 24, 24, 24, 14, 0, 0, 0],
|
|
2616
|
+
"u": [0, 0, 0, 0, 102, 102, 102, 102, 102, 62, 0, 0, 0],
|
|
2617
|
+
"v": [0, 0, 0, 0, 102, 102, 102, 60, 60, 24, 0, 0, 0],
|
|
2618
|
+
"w": [0, 0, 0, 0, 66, 66, 90, 90, 126, 36, 0, 0, 0],
|
|
2619
|
+
"x": [0, 0, 0, 0, 102, 102, 60, 60, 102, 102, 0, 0, 0],
|
|
2620
|
+
"y": [0, 0, 0, 0, 102, 102, 102, 62, 6, 6, 70, 60, 0],
|
|
2621
|
+
"z": [0, 0, 0, 0, 126, 12, 24, 48, 96, 126, 0, 0, 0],
|
|
2622
|
+
// ─── Punctuation ─────────────────────────────────────────────────────────
|
|
2623
|
+
".": [0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 0],
|
|
2624
|
+
",": [0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 8, 16, 0],
|
|
2625
|
+
":": [0, 0, 0, 0, 24, 24, 0, 0, 24, 24, 0, 0, 0],
|
|
2626
|
+
";": [0, 0, 0, 0, 24, 24, 0, 0, 24, 24, 8, 16, 0],
|
|
2627
|
+
"!": [0, 0, 24, 24, 24, 24, 24, 24, 0, 24, 0, 0, 0],
|
|
2628
|
+
"?": [0, 0, 60, 102, 6, 12, 24, 24, 0, 24, 0, 0, 0],
|
|
2629
|
+
"-": [0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 0, 0, 0],
|
|
2630
|
+
"+": [0, 0, 0, 0, 24, 24, 126, 24, 24, 0, 0, 0, 0],
|
|
2631
|
+
"=": [0, 0, 0, 0, 0, 126, 0, 126, 0, 0, 0, 0, 0],
|
|
2632
|
+
"(": [0, 0, 12, 24, 48, 48, 48, 48, 24, 12, 0, 0, 0],
|
|
2633
|
+
")": [0, 0, 48, 24, 12, 12, 12, 12, 24, 48, 0, 0, 0],
|
|
2634
|
+
"/": [0, 0, 2, 6, 12, 24, 48, 96, 64, 0, 0, 0, 0],
|
|
2635
|
+
"\\": [0, 0, 64, 96, 48, 24, 12, 6, 2, 0, 0, 0, 0],
|
|
2636
|
+
"'": [0, 24, 24, 16, 32, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2637
|
+
'"': [0, 102, 102, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2638
|
+
"_": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 0, 0],
|
|
2639
|
+
"@": [0, 0, 60, 102, 110, 106, 110, 96, 98, 60, 0, 0, 0],
|
|
2640
|
+
"#": [0, 0, 36, 36, 126, 36, 36, 126, 36, 36, 0, 0, 0],
|
|
2641
|
+
"&": [0, 0, 56, 108, 108, 56, 118, 206, 204, 118, 0, 0, 0],
|
|
2642
|
+
"%": [0, 0, 98, 102, 12, 24, 48, 102, 70, 0, 0, 0, 0],
|
|
2643
|
+
"*": [0, 0, 0, 102, 60, 255, 60, 102, 0, 0, 0, 0, 0],
|
|
2644
|
+
"<": [0, 0, 0, 6, 28, 112, 112, 28, 6, 0, 0, 0, 0],
|
|
2645
|
+
">": [0, 0, 0, 96, 56, 14, 14, 56, 96, 0, 0, 0, 0],
|
|
2646
|
+
"[": [0, 0, 60, 48, 48, 48, 48, 48, 48, 60, 0, 0, 0],
|
|
2647
|
+
"]": [0, 0, 60, 12, 12, 12, 12, 12, 12, 60, 0, 0, 0],
|
|
2648
|
+
"{": [0, 0, 14, 24, 24, 48, 24, 24, 24, 14, 0, 0, 0],
|
|
2649
|
+
"}": [0, 0, 112, 24, 24, 12, 24, 24, 24, 112, 0, 0, 0],
|
|
2650
|
+
"|": [0, 0, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 0],
|
|
2651
|
+
"~": [0, 0, 0, 0, 0, 114, 156, 0, 0, 0, 0, 0, 0],
|
|
2652
|
+
"^": [0, 24, 60, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2653
|
+
"`": [0, 24, 12, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
2654
|
+
// ─── German characters ────────────────────────────────────────────────────
|
|
2655
|
+
// Umlauts: dots sit at rows 0-1, letter body follows below
|
|
2656
|
+
"\xE4": [0, 36, 0, 0, 60, 6, 62, 102, 102, 62, 0, 0, 0],
|
|
2657
|
+
"\xF6": [0, 36, 0, 0, 60, 102, 102, 102, 102, 60, 0, 0, 0],
|
|
2658
|
+
"\xFC": [0, 36, 0, 0, 102, 102, 102, 102, 102, 62, 0, 0, 0],
|
|
2659
|
+
"\xC4": [36, 0, 24, 60, 102, 102, 126, 102, 102, 102, 0, 0, 0],
|
|
2660
|
+
"\xD6": [36, 0, 60, 102, 102, 102, 102, 102, 102, 60, 0, 0, 0],
|
|
2661
|
+
"\xDC": [36, 0, 102, 102, 102, 102, 102, 102, 102, 60, 0, 0, 0],
|
|
2662
|
+
// ß: sharp-s / eszett
|
|
2663
|
+
"\xDF": [0, 0, 60, 102, 102, 124, 102, 102, 102, 124, 96, 96, 0],
|
|
2664
|
+
// ─── Currency / special symbols ──────────────────────────────────────────
|
|
2665
|
+
// € (Euro): slightly narrower than full width, classic double-bar C shape
|
|
2666
|
+
"\u20AC": [0, 0, 30, 48, 124, 48, 124, 48, 48, 30, 0, 0, 0]
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
});
|
|
2670
|
+
|
|
2671
|
+
// src/png-decode.ts
|
|
2672
|
+
var png_decode_exports = {};
|
|
2673
|
+
__export(png_decode_exports, {
|
|
2674
|
+
decodePng: () => decodePng
|
|
2675
|
+
});
|
|
2676
|
+
function decodePng(buf) {
|
|
2677
|
+
if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71) {
|
|
2678
|
+
return null;
|
|
2679
|
+
}
|
|
2680
|
+
let offset = 8;
|
|
2681
|
+
let width = 0, height = 0, bitDepth = 0, colorType = 0;
|
|
2682
|
+
const idatChunks = [];
|
|
2683
|
+
while (offset < buf.length) {
|
|
2684
|
+
const chunkLen = buf.readUInt32BE(offset);
|
|
2685
|
+
const chunkType = buf.toString("ascii", offset + 4, offset + 8);
|
|
2686
|
+
if (chunkType === "IHDR") {
|
|
2687
|
+
width = buf.readUInt32BE(offset + 8);
|
|
2688
|
+
height = buf.readUInt32BE(offset + 12);
|
|
2689
|
+
bitDepth = buf[offset + 16];
|
|
2690
|
+
colorType = buf[offset + 17];
|
|
2691
|
+
} else if (chunkType === "IDAT") {
|
|
2692
|
+
idatChunks.push(buf.slice(offset + 8, offset + 8 + chunkLen));
|
|
2693
|
+
} else if (chunkType === "IEND") {
|
|
2694
|
+
break;
|
|
2695
|
+
}
|
|
2696
|
+
offset += 12 + chunkLen;
|
|
2697
|
+
}
|
|
2698
|
+
if (width === 0 || height === 0 || idatChunks.length === 0) return null;
|
|
2699
|
+
let channels;
|
|
2700
|
+
switch (colorType) {
|
|
2701
|
+
case 0:
|
|
2702
|
+
channels = 1;
|
|
2703
|
+
break;
|
|
2704
|
+
// Grayscale
|
|
2705
|
+
case 2:
|
|
2706
|
+
channels = 3;
|
|
2707
|
+
break;
|
|
2708
|
+
// RGB
|
|
2709
|
+
case 4:
|
|
2710
|
+
channels = 2;
|
|
2711
|
+
break;
|
|
2712
|
+
// Grayscale + Alpha
|
|
2713
|
+
case 6:
|
|
2714
|
+
channels = 4;
|
|
2715
|
+
break;
|
|
2716
|
+
// RGBA
|
|
2717
|
+
default:
|
|
2718
|
+
return null;
|
|
2719
|
+
}
|
|
2720
|
+
const compressed = Buffer.concat(idatChunks);
|
|
2721
|
+
let decompressed;
|
|
2722
|
+
try {
|
|
2723
|
+
decompressed = import_zlib.default.inflateSync(compressed);
|
|
2724
|
+
} catch {
|
|
2725
|
+
return null;
|
|
2726
|
+
}
|
|
2727
|
+
const bytesPerPixel = channels * (bitDepth / 8);
|
|
2728
|
+
const stride = width * bytesPerPixel;
|
|
2729
|
+
const pixels = Buffer.alloc(height * stride);
|
|
2730
|
+
let srcOffset = 0;
|
|
2731
|
+
for (let y = 0; y < height; y++) {
|
|
2732
|
+
const filterType = decompressed[srcOffset++];
|
|
2733
|
+
const rowStart = y * stride;
|
|
2734
|
+
const prevRowStart = (y - 1) * stride;
|
|
2735
|
+
for (let x = 0; x < stride; x++) {
|
|
2736
|
+
const raw = decompressed[srcOffset++];
|
|
2737
|
+
let value;
|
|
2738
|
+
switch (filterType) {
|
|
2739
|
+
case 0:
|
|
2740
|
+
value = raw;
|
|
2741
|
+
break;
|
|
2742
|
+
case 1:
|
|
2743
|
+
value = raw + (x >= bytesPerPixel ? pixels[rowStart + x - bytesPerPixel] : 0);
|
|
2744
|
+
break;
|
|
2745
|
+
case 2:
|
|
2746
|
+
value = raw + (y > 0 ? pixels[prevRowStart + x] : 0);
|
|
2747
|
+
break;
|
|
2748
|
+
case 3:
|
|
2749
|
+
const left = x >= bytesPerPixel ? pixels[rowStart + x - bytesPerPixel] : 0;
|
|
2750
|
+
const up = y > 0 ? pixels[prevRowStart + x] : 0;
|
|
2751
|
+
value = raw + Math.floor((left + up) / 2);
|
|
2752
|
+
break;
|
|
2753
|
+
case 4:
|
|
2754
|
+
const a = x >= bytesPerPixel ? pixels[rowStart + x - bytesPerPixel] : 0;
|
|
2755
|
+
const b = y > 0 ? pixels[prevRowStart + x] : 0;
|
|
2756
|
+
const c = x >= bytesPerPixel && y > 0 ? pixels[prevRowStart + x - bytesPerPixel] : 0;
|
|
2757
|
+
value = raw + paethPredictor(a, b, c);
|
|
2758
|
+
break;
|
|
2759
|
+
default:
|
|
2760
|
+
value = raw;
|
|
2761
|
+
}
|
|
2762
|
+
pixels[rowStart + x] = value & 255;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
return { width, height, channels, data: pixels };
|
|
2766
|
+
}
|
|
2767
|
+
function paethPredictor(a, b, c) {
|
|
2768
|
+
const p = a + b - c;
|
|
2769
|
+
const pa = Math.abs(p - a);
|
|
2770
|
+
const pb = Math.abs(p - b);
|
|
2771
|
+
const pc = Math.abs(p - c);
|
|
2772
|
+
if (pa <= pb && pa <= pc) return a;
|
|
2773
|
+
if (pb <= pc) return b;
|
|
2774
|
+
return c;
|
|
2775
|
+
}
|
|
2776
|
+
var import_zlib;
|
|
2777
|
+
var init_png_decode = __esm({
|
|
2778
|
+
"src/png-decode.ts"() {
|
|
2779
|
+
"use strict";
|
|
2780
|
+
import_zlib = __toESM(require("zlib"));
|
|
2781
|
+
}
|
|
2782
|
+
});
|
|
2783
|
+
|
|
2784
|
+
// src/hid-display.ts
|
|
2785
|
+
function fontScale(fontSize) {
|
|
2786
|
+
return Math.max(1, Math.min(3, Math.round(fontSize)));
|
|
2787
|
+
}
|
|
2788
|
+
function fontLineHeight(fontSize) {
|
|
2789
|
+
return 13 * fontScale(fontSize) + 2;
|
|
2790
|
+
}
|
|
2791
|
+
function fontCharWidth(fontSize) {
|
|
2792
|
+
return 9 * fontScale(fontSize);
|
|
2793
|
+
}
|
|
2794
|
+
var import_fs, DisplayRenderer;
|
|
2795
|
+
var init_hid_display = __esm({
|
|
2796
|
+
"src/hid-display.ts"() {
|
|
2797
|
+
"use strict";
|
|
2798
|
+
import_fs = __toESM(require("fs"));
|
|
2799
|
+
init_font_8x13();
|
|
2800
|
+
DisplayRenderer = class {
|
|
2801
|
+
// 1bpp, row-aligned to bytes
|
|
2802
|
+
constructor(width, height) {
|
|
2803
|
+
this.width = width;
|
|
2804
|
+
this.height = height;
|
|
2805
|
+
const rowBytes = Math.ceil(width / 8);
|
|
2806
|
+
this.framebuffer = Buffer.alloc(rowBytes * height, 255);
|
|
2807
|
+
}
|
|
2808
|
+
get buffer() {
|
|
2809
|
+
return this.framebuffer;
|
|
2810
|
+
}
|
|
2811
|
+
clear() {
|
|
2812
|
+
this.framebuffer.fill(255);
|
|
2813
|
+
}
|
|
2814
|
+
/**
|
|
2815
|
+
* Set a pixel (0=black, 1=white) in the framebuffer.
|
|
2816
|
+
*/
|
|
2817
|
+
setPixel(x, y, black) {
|
|
2818
|
+
if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;
|
|
2819
|
+
const rowBytes = Math.ceil(this.width / 8);
|
|
2820
|
+
const byteIndex = y * rowBytes + Math.floor(x / 8);
|
|
2821
|
+
const bitIndex = 7 - x % 8;
|
|
2822
|
+
if (black) {
|
|
2823
|
+
this.framebuffer[byteIndex] &= ~(1 << bitIndex);
|
|
2824
|
+
} else {
|
|
2825
|
+
this.framebuffer[byteIndex] |= 1 << bitIndex;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Draw a filled rectangle.
|
|
2830
|
+
*/
|
|
2831
|
+
fillRect(x, y, w, h, black) {
|
|
2832
|
+
for (let dy = 0; dy < h; dy++) {
|
|
2833
|
+
for (let dx = 0; dx < w; dx++) {
|
|
2834
|
+
this.setPixel(x + dx, y + dy, black);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
/**
|
|
2839
|
+
* Draw a horizontal line.
|
|
2840
|
+
*/
|
|
2841
|
+
hLine(x, y, w) {
|
|
2842
|
+
for (let dx = 0; dx < w; dx++) {
|
|
2843
|
+
this.setPixel(x + dx, y, true);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Draw text using the 8x13 bitmap font.
|
|
2848
|
+
* fontSize: 1=normal(8x13), 2=large(16x26), 3=headline(24x39)
|
|
2849
|
+
* bold: draws text twice with 1px right shift for thicker strokes
|
|
2850
|
+
*/
|
|
2851
|
+
drawText(x, y, text, fontSize = 1, bold = false) {
|
|
2852
|
+
const w = this._renderText(x, y, text, fontSize, true);
|
|
2853
|
+
if (bold) this._renderText(x + 1, y, text, fontSize, true);
|
|
2854
|
+
return w;
|
|
2855
|
+
}
|
|
2856
|
+
/** Draw inverted text (white on black) for button labels. */
|
|
2857
|
+
drawTextInverted(x, y, text, fontSize = 1, bold = false) {
|
|
2858
|
+
const w = this._renderText(x, y, text, fontSize, false);
|
|
2859
|
+
if (bold) this._renderText(x + 1, y, text, fontSize, false);
|
|
2860
|
+
return w;
|
|
2861
|
+
}
|
|
2862
|
+
_renderText(x, y, text, fontSize, black) {
|
|
2863
|
+
const cleanText = text.replace(/[\u200B-\u200F\u2028-\u202F\u2060-\u206F\uFEFF]/g, "");
|
|
2864
|
+
const scale = fontScale(fontSize);
|
|
2865
|
+
const charW = 9 * scale;
|
|
2866
|
+
let curX = x;
|
|
2867
|
+
for (const char of cleanText) {
|
|
2868
|
+
const glyph = FONT_8X13[char] || FONT_8X13["?"];
|
|
2869
|
+
if (!glyph) continue;
|
|
2870
|
+
for (let row = 0; row < 13; row++) {
|
|
2871
|
+
for (let col = 0; col < 8; col++) {
|
|
2872
|
+
if (glyph[row] & 1 << 7 - col) {
|
|
2873
|
+
for (let sy = 0; sy < scale; sy++) {
|
|
2874
|
+
for (let sx = 0; sx < scale; sx++) {
|
|
2875
|
+
this.setPixel(curX + col * scale + sx, y + row * scale + sy, black);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
curX += charW;
|
|
2882
|
+
}
|
|
2883
|
+
return curX - x;
|
|
2884
|
+
}
|
|
2885
|
+
/**
|
|
2886
|
+
* Draw text fitted within a rectangle. Wraps words and scales to fit.
|
|
2887
|
+
*/
|
|
2888
|
+
drawTextInRect(left, top, width, height, text, fontSize = 1) {
|
|
2889
|
+
const charW = fontCharWidth(fontSize);
|
|
2890
|
+
const charH = fontLineHeight(fontSize);
|
|
2891
|
+
const maxCols = Math.floor(width / charW);
|
|
2892
|
+
const maxRows = Math.floor(height / charH);
|
|
2893
|
+
const words = text.split(" ");
|
|
2894
|
+
const lines = [];
|
|
2895
|
+
let currentLine = "";
|
|
2896
|
+
for (const word of words) {
|
|
2897
|
+
if (currentLine.length + word.length + 1 <= maxCols) {
|
|
2898
|
+
currentLine += (currentLine ? " " : "") + word;
|
|
2899
|
+
} else {
|
|
2900
|
+
if (currentLine) lines.push(currentLine);
|
|
2901
|
+
currentLine = word;
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
if (currentLine) lines.push(currentLine);
|
|
2905
|
+
for (let i = 0; i < Math.min(lines.length, maxRows); i++) {
|
|
2906
|
+
this.drawText(left, top + i * charH, lines[i], fontSize);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
/**
|
|
2910
|
+
* Load a BMP file and draw it at the specified position.
|
|
2911
|
+
* Supports 1-bit, 8-bit, and 24-bit BMPs.
|
|
2912
|
+
*/
|
|
2913
|
+
drawBmpFile(x, y, filePath) {
|
|
2914
|
+
try {
|
|
2915
|
+
const data = import_fs.default.readFileSync(filePath);
|
|
2916
|
+
return this.drawBmpData(x, y, data);
|
|
2917
|
+
} catch {
|
|
2918
|
+
return false;
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
/**
|
|
2922
|
+
* Load a PNG file and draw it at the specified position.
|
|
2923
|
+
* Uses a minimal PNG decoder (no external dependencies).
|
|
2924
|
+
*/
|
|
2925
|
+
drawPngFile(x, y, filePath) {
|
|
2926
|
+
try {
|
|
2927
|
+
const data = import_fs.default.readFileSync(filePath);
|
|
2928
|
+
return this.drawImageData(x, y, data, filePath);
|
|
2929
|
+
} catch {
|
|
2930
|
+
return false;
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Draw image data. Detects format from header.
|
|
2935
|
+
*/
|
|
2936
|
+
drawImageData(x, y, data, hint = "") {
|
|
2937
|
+
if (data[0] === 66 && data[1] === 77) {
|
|
2938
|
+
return this.drawBmpData(x, y, data);
|
|
2939
|
+
}
|
|
2940
|
+
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) {
|
|
2941
|
+
return this.drawPngData(x, y, data);
|
|
2942
|
+
}
|
|
2943
|
+
return false;
|
|
2944
|
+
}
|
|
2945
|
+
drawBmpData(x, y, data) {
|
|
2946
|
+
if (data.length < 54) return false;
|
|
2947
|
+
const dataOffset = data.readUInt32LE(10);
|
|
2948
|
+
const bmpWidth = data.readInt32LE(18);
|
|
2949
|
+
const bmpHeight = data.readInt32LE(22);
|
|
2950
|
+
const bitsPerPixel = data.readUInt16LE(28);
|
|
2951
|
+
const isBottomUp = bmpHeight > 0;
|
|
2952
|
+
const absHeight = Math.abs(bmpHeight);
|
|
2953
|
+
for (let row = 0; row < absHeight; row++) {
|
|
2954
|
+
const srcRow = isBottomUp ? absHeight - 1 - row : row;
|
|
2955
|
+
const dstY = y + row;
|
|
2956
|
+
if (bitsPerPixel === 1) {
|
|
2957
|
+
const rowBytes = Math.ceil(bmpWidth / 8);
|
|
2958
|
+
const paddedRowBytes = Math.ceil(rowBytes / 4) * 4;
|
|
2959
|
+
const rowOffset = dataOffset + srcRow * paddedRowBytes;
|
|
2960
|
+
for (let col = 0; col < bmpWidth; col++) {
|
|
2961
|
+
const byteIdx = rowOffset + Math.floor(col / 8);
|
|
2962
|
+
const bitIdx = 7 - col % 8;
|
|
2963
|
+
const pixel = data[byteIdx] >> bitIdx & 1;
|
|
2964
|
+
this.setPixel(x + col, dstY, pixel === 0);
|
|
2965
|
+
}
|
|
2966
|
+
} else if (bitsPerPixel === 24) {
|
|
2967
|
+
const paddedRowBytes = Math.ceil(bmpWidth * 3 / 4) * 4;
|
|
2968
|
+
const rowOffset = dataOffset + srcRow * paddedRowBytes;
|
|
2969
|
+
for (let col = 0; col < bmpWidth; col++) {
|
|
2970
|
+
const idx = rowOffset + col * 3;
|
|
2971
|
+
const b = data[idx];
|
|
2972
|
+
const g = data[idx + 1];
|
|
2973
|
+
const r = data[idx + 2];
|
|
2974
|
+
const brightness = (r * 299 + g * 587 + b * 114) / 1e3;
|
|
2975
|
+
this.setPixel(x + col, dstY, brightness < 128);
|
|
2976
|
+
}
|
|
2977
|
+
} else if (bitsPerPixel === 8) {
|
|
2978
|
+
const paddedRowBytes = Math.ceil(bmpWidth / 4) * 4;
|
|
2979
|
+
const rowOffset = dataOffset + srcRow * paddedRowBytes;
|
|
2980
|
+
for (let col = 0; col < bmpWidth; col++) {
|
|
2981
|
+
const pixel = data[rowOffset + col];
|
|
2982
|
+
this.setPixel(x + col, dstY, pixel < 128);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
return true;
|
|
2987
|
+
}
|
|
2988
|
+
drawPngData(x, y, data) {
|
|
2989
|
+
try {
|
|
2990
|
+
const { decodePng: decodePng2 } = (init_png_decode(), __toCommonJS(png_decode_exports));
|
|
2991
|
+
const pixels = decodePng2(data);
|
|
2992
|
+
if (!pixels) return false;
|
|
2993
|
+
for (let row = 0; row < pixels.height; row++) {
|
|
2994
|
+
for (let col = 0; col < pixels.width; col++) {
|
|
2995
|
+
const idx = (row * pixels.width + col) * pixels.channels;
|
|
2996
|
+
let brightness;
|
|
2997
|
+
if (pixels.channels >= 3) {
|
|
2998
|
+
brightness = (pixels.data[idx] * 299 + pixels.data[idx + 1] * 587 + pixels.data[idx + 2] * 114) / 1e3;
|
|
2999
|
+
} else {
|
|
3000
|
+
brightness = pixels.data[idx];
|
|
3001
|
+
}
|
|
3002
|
+
if (pixels.channels === 4 || pixels.channels === 2) {
|
|
3003
|
+
const alpha = pixels.data[idx + pixels.channels - 1];
|
|
3004
|
+
if (alpha < 128) continue;
|
|
3005
|
+
}
|
|
3006
|
+
this.setPixel(x + col, y + row, brightness < 128);
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
return true;
|
|
3010
|
+
} catch {
|
|
3011
|
+
return false;
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Send the entire framebuffer to the pad.
|
|
3016
|
+
*/
|
|
3017
|
+
async sendToDevice(protocol) {
|
|
3018
|
+
return protocol.drawImage(0, 0, this.width, this.height, this.framebuffer);
|
|
3019
|
+
}
|
|
3020
|
+
/**
|
|
3021
|
+
* Send a region of the framebuffer to the pad.
|
|
3022
|
+
*/
|
|
3023
|
+
async sendRegionToDevice(protocol, x, y, w, h) {
|
|
3024
|
+
const rowBytes = Math.ceil(this.width / 8);
|
|
3025
|
+
const regionRowBytes = Math.ceil(w / 8);
|
|
3026
|
+
const regionBuf = Buffer.alloc(regionRowBytes * h, 255);
|
|
3027
|
+
for (let row = 0; row < h; row++) {
|
|
3028
|
+
for (let col = 0; col < w; col++) {
|
|
3029
|
+
const srcByteIdx = (y + row) * rowBytes + Math.floor((x + col) / 8);
|
|
3030
|
+
const srcBitIdx = 7 - (x + col) % 8;
|
|
3031
|
+
const pixel = this.framebuffer[srcByteIdx] >> srcBitIdx & 1;
|
|
3032
|
+
const dstByteIdx = row * regionRowBytes + Math.floor(col / 8);
|
|
3033
|
+
const dstBitIdx = 7 - col % 8;
|
|
3034
|
+
if (pixel === 0) {
|
|
3035
|
+
regionBuf[dstByteIdx] &= ~(1 << dstBitIdx);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
return protocol.drawImage(x, y, w, h, regionBuf);
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
});
|
|
3044
|
+
|
|
3045
|
+
// src/hid-crypto.ts
|
|
3046
|
+
var import_crypto, KAESDEFKEY, PEN_UP_PRESSURE, SignatureDecryptor;
|
|
3047
|
+
var init_hid_crypto = __esm({
|
|
3048
|
+
"src/hid-crypto.ts"() {
|
|
3049
|
+
"use strict";
|
|
3050
|
+
import_crypto = __toESM(require("crypto"));
|
|
3051
|
+
KAESDEFKEY = Buffer.from([
|
|
3052
|
+
38,
|
|
3053
|
+
212,
|
|
3054
|
+
165,
|
|
3055
|
+
168,
|
|
3056
|
+
144,
|
|
3057
|
+
1,
|
|
3058
|
+
171,
|
|
3059
|
+
19,
|
|
3060
|
+
127,
|
|
3061
|
+
113,
|
|
3062
|
+
49,
|
|
3063
|
+
81,
|
|
3064
|
+
217,
|
|
3065
|
+
47,
|
|
3066
|
+
207,
|
|
3067
|
+
31,
|
|
3068
|
+
17,
|
|
3069
|
+
135,
|
|
3070
|
+
134,
|
|
3071
|
+
22,
|
|
3072
|
+
78,
|
|
3073
|
+
129,
|
|
3074
|
+
183,
|
|
3075
|
+
148,
|
|
3076
|
+
45,
|
|
3077
|
+
142,
|
|
3078
|
+
223,
|
|
3079
|
+
210,
|
|
3080
|
+
218,
|
|
3081
|
+
6,
|
|
3082
|
+
36,
|
|
3083
|
+
245
|
|
3084
|
+
]);
|
|
3085
|
+
PEN_UP_PRESSURE = 65535;
|
|
3086
|
+
SignatureDecryptor = class _SignatureDecryptor {
|
|
3087
|
+
constructor(key) {
|
|
3088
|
+
this.key = Buffer.from(key || KAESDEFKEY);
|
|
3089
|
+
this.iv = Buffer.alloc(16, 0);
|
|
3090
|
+
}
|
|
3091
|
+
/**
|
|
3092
|
+
* Create a decryptor using the static default key.
|
|
3093
|
+
* This works for Sigma USB pads (verified).
|
|
3094
|
+
*/
|
|
3095
|
+
static withDefaultKey() {
|
|
3096
|
+
return new _SignatureDecryptor();
|
|
3097
|
+
}
|
|
3098
|
+
/**
|
|
3099
|
+
* Reset the CBC IV to all zeros (for retry/new signature session).
|
|
3100
|
+
*/
|
|
3101
|
+
resetIv() {
|
|
3102
|
+
this.iv = Buffer.alloc(16, 0);
|
|
3103
|
+
}
|
|
3104
|
+
/**
|
|
3105
|
+
* Decrypt a single 16-byte AES block from a 0x41 HID event.
|
|
3106
|
+
* The ciphertext is at bytes 4-19 of the HID report.
|
|
3107
|
+
*
|
|
3108
|
+
* Implements AES-256-CBC manually:
|
|
3109
|
+
* 1. ECB decrypt the block
|
|
3110
|
+
* 2. XOR with previous IV
|
|
3111
|
+
* 3. Update IV to current ciphertext
|
|
3112
|
+
*/
|
|
3113
|
+
decryptBlock(ciphertext) {
|
|
3114
|
+
if (ciphertext.length < 16) return null;
|
|
3115
|
+
try {
|
|
3116
|
+
const block = ciphertext.slice(0, 16);
|
|
3117
|
+
const decipher = import_crypto.default.createDecipheriv("aes-256-ecb", this.key, null);
|
|
3118
|
+
decipher.setAutoPadding(false);
|
|
3119
|
+
const decrypted = Buffer.concat([decipher.update(block), decipher.final()]);
|
|
3120
|
+
for (let i = 0; i < 16; i++) {
|
|
3121
|
+
decrypted[i] ^= this.iv[i];
|
|
3122
|
+
}
|
|
3123
|
+
block.copy(this.iv, 0, 0, 16);
|
|
3124
|
+
const timestamp = decrypted.readUInt32LE(0);
|
|
3125
|
+
const x = decrypted.readUInt16LE(4);
|
|
3126
|
+
const y = decrypted.readUInt16LE(6);
|
|
3127
|
+
const pressure = decrypted.readUInt16LE(8);
|
|
3128
|
+
return {
|
|
3129
|
+
timestamp,
|
|
3130
|
+
x,
|
|
3131
|
+
y,
|
|
3132
|
+
pressure,
|
|
3133
|
+
penUp: pressure === PEN_UP_PRESSURE
|
|
3134
|
+
};
|
|
3135
|
+
} catch {
|
|
3136
|
+
return null;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
/**
|
|
3140
|
+
* Decrypt a point from a raw 0x41 HID event buffer.
|
|
3141
|
+
* Extracts the 16-byte ciphertext from bytes 4-19.
|
|
3142
|
+
*/
|
|
3143
|
+
decryptEvent(eventData) {
|
|
3144
|
+
if (eventData.length < 20) return null;
|
|
3145
|
+
return this.decryptBlock(eventData.slice(4, 20));
|
|
3146
|
+
}
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
});
|
|
3150
|
+
|
|
3151
|
+
// src/signature-renderer.ts
|
|
3152
|
+
function renderSignaturePng(strokes, sensorWidth, sensorHeight, options = {}) {
|
|
3153
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
3154
|
+
const { width, height, strokeWidth, strokeColor, backgroundColor, padding } = opts;
|
|
3155
|
+
const pixels = Buffer.alloc(width * height * 4);
|
|
3156
|
+
for (let i = 0; i < width * height; i++) {
|
|
3157
|
+
pixels[i * 4] = backgroundColor.r;
|
|
3158
|
+
pixels[i * 4 + 1] = backgroundColor.g;
|
|
3159
|
+
pixels[i * 4 + 2] = backgroundColor.b;
|
|
3160
|
+
pixels[i * 4 + 3] = 255;
|
|
3161
|
+
}
|
|
3162
|
+
if (strokes.length === 0) {
|
|
3163
|
+
return encodePng(pixels, width, height);
|
|
3164
|
+
}
|
|
3165
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3166
|
+
for (const stroke of strokes) {
|
|
3167
|
+
for (const pt of stroke) {
|
|
3168
|
+
if (pt.x < minX) minX = pt.x;
|
|
3169
|
+
if (pt.y < minY) minY = pt.y;
|
|
3170
|
+
if (pt.x > maxX) maxX = pt.x;
|
|
3171
|
+
if (pt.y > maxY) maxY = pt.y;
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
const dataWidth = maxX - minX || 1;
|
|
3175
|
+
const dataHeight = maxY - minY || 1;
|
|
3176
|
+
const scaleX = (width - padding * 2) / dataWidth;
|
|
3177
|
+
const scaleY = (height - padding * 2) / dataHeight;
|
|
3178
|
+
const scale = Math.min(scaleX, scaleY);
|
|
3179
|
+
const offsetX = padding + (width - padding * 2 - dataWidth * scale) / 2;
|
|
3180
|
+
const offsetY = padding + (height - padding * 2 - dataHeight * scale) / 2;
|
|
3181
|
+
for (const stroke of strokes) {
|
|
3182
|
+
if (stroke.length < 2) continue;
|
|
3183
|
+
for (let i = 1; i < stroke.length; i++) {
|
|
3184
|
+
const x0 = offsetX + (stroke[i - 1].x - minX) * scale;
|
|
3185
|
+
const y0 = offsetY + (stroke[i - 1].y - minY) * scale;
|
|
3186
|
+
const x1 = offsetX + (stroke[i].x - minX) * scale;
|
|
3187
|
+
const y1 = offsetY + (stroke[i].y - minY) * scale;
|
|
3188
|
+
drawLine(pixels, width, height, x0, y0, x1, y1, strokeWidth, strokeColor);
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
return encodePng(pixels, width, height);
|
|
3192
|
+
}
|
|
3193
|
+
function drawLine(pixels, imgWidth, imgHeight, x0, y0, x1, y1, thickness, color) {
|
|
3194
|
+
const dx = Math.abs(x1 - x0);
|
|
3195
|
+
const dy = Math.abs(y1 - y0);
|
|
3196
|
+
const sx = x0 < x1 ? 1 : -1;
|
|
3197
|
+
const sy = y0 < y1 ? 1 : -1;
|
|
3198
|
+
let err = dx - dy;
|
|
3199
|
+
const steps = Math.max(Math.ceil(Math.sqrt(dx * dx + dy * dy)), 1);
|
|
3200
|
+
const radius = thickness / 2;
|
|
3201
|
+
for (let step = 0; step <= steps; step++) {
|
|
3202
|
+
const t = steps > 0 ? step / steps : 0;
|
|
3203
|
+
const cx = x0 + (x1 - x0) * t;
|
|
3204
|
+
const cy = y0 + (y1 - y0) * t;
|
|
3205
|
+
const r = Math.ceil(radius);
|
|
3206
|
+
for (let dy2 = -r; dy2 <= r; dy2++) {
|
|
3207
|
+
for (let dx2 = -r; dx2 <= r; dx2++) {
|
|
3208
|
+
if (dx2 * dx2 + dy2 * dy2 <= radius * radius) {
|
|
3209
|
+
const px = Math.round(cx + dx2);
|
|
3210
|
+
const py = Math.round(cy + dy2);
|
|
3211
|
+
if (px >= 0 && px < imgWidth && py >= 0 && py < imgHeight) {
|
|
3212
|
+
const idx = (py * imgWidth + px) * 4;
|
|
3213
|
+
pixels[idx] = color.r;
|
|
3214
|
+
pixels[idx + 1] = color.g;
|
|
3215
|
+
pixels[idx + 2] = color.b;
|
|
3216
|
+
pixels[idx + 3] = 255;
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
function encodePng(pixels, width, height) {
|
|
3224
|
+
const rawData = Buffer.alloc(height * (1 + width * 4));
|
|
3225
|
+
for (let y = 0; y < height; y++) {
|
|
3226
|
+
rawData[y * (1 + width * 4)] = 0;
|
|
3227
|
+
pixels.copy(
|
|
3228
|
+
rawData,
|
|
3229
|
+
y * (1 + width * 4) + 1,
|
|
3230
|
+
y * width * 4,
|
|
3231
|
+
(y + 1) * width * 4
|
|
3232
|
+
);
|
|
3233
|
+
}
|
|
3234
|
+
const compressed = import_zlib2.default.deflateSync(rawData);
|
|
3235
|
+
const chunks = [];
|
|
3236
|
+
chunks.push(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
|
|
3237
|
+
const ihdr = Buffer.alloc(13);
|
|
3238
|
+
ihdr.writeUInt32BE(width, 0);
|
|
3239
|
+
ihdr.writeUInt32BE(height, 4);
|
|
3240
|
+
ihdr[8] = 8;
|
|
3241
|
+
ihdr[9] = 6;
|
|
3242
|
+
ihdr[10] = 0;
|
|
3243
|
+
ihdr[11] = 0;
|
|
3244
|
+
ihdr[12] = 0;
|
|
3245
|
+
chunks.push(pngChunk("IHDR", ihdr));
|
|
3246
|
+
chunks.push(pngChunk("IDAT", compressed));
|
|
3247
|
+
chunks.push(pngChunk("IEND", Buffer.alloc(0)));
|
|
3248
|
+
return Buffer.concat(chunks);
|
|
3249
|
+
}
|
|
3250
|
+
function pngChunk(type, data) {
|
|
3251
|
+
const length = Buffer.alloc(4);
|
|
3252
|
+
length.writeUInt32BE(data.length, 0);
|
|
3253
|
+
const typeAndData = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
|
3254
|
+
const crc = crc32(typeAndData);
|
|
3255
|
+
const crcBuf = Buffer.alloc(4);
|
|
3256
|
+
crcBuf.writeUInt32BE(crc >>> 0, 0);
|
|
3257
|
+
return Buffer.concat([length, typeAndData, crcBuf]);
|
|
3258
|
+
}
|
|
3259
|
+
function crc32(data) {
|
|
3260
|
+
let crc = 4294967295;
|
|
3261
|
+
for (let i = 0; i < data.length; i++) {
|
|
3262
|
+
crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
|
|
3263
|
+
}
|
|
3264
|
+
return (crc ^ 4294967295) >>> 0;
|
|
3265
|
+
}
|
|
3266
|
+
var import_zlib2, DEFAULT_OPTIONS, CRC_TABLE;
|
|
3267
|
+
var init_signature_renderer = __esm({
|
|
3268
|
+
"src/signature-renderer.ts"() {
|
|
3269
|
+
"use strict";
|
|
3270
|
+
import_zlib2 = __toESM(require("zlib"));
|
|
3271
|
+
DEFAULT_OPTIONS = {
|
|
3272
|
+
width: 400,
|
|
3273
|
+
height: 200,
|
|
3274
|
+
strokeWidth: 2,
|
|
3275
|
+
strokeColor: { r: 0, g: 0, b: 0 },
|
|
3276
|
+
backgroundColor: { r: 255, g: 255, b: 255 },
|
|
3277
|
+
padding: 10,
|
|
3278
|
+
smooth: true
|
|
3279
|
+
};
|
|
3280
|
+
CRC_TABLE = [];
|
|
3281
|
+
for (let n = 0; n < 256; n++) {
|
|
3282
|
+
let c = n;
|
|
3283
|
+
for (let k = 0; k < 8; k++) {
|
|
3284
|
+
if (c & 1) {
|
|
3285
|
+
c = 3988292384 ^ c >>> 1;
|
|
3286
|
+
} else {
|
|
3287
|
+
c = c >>> 1;
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
CRC_TABLE[n] = c;
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
});
|
|
3294
|
+
|
|
3295
|
+
// src/assets-embedded.ts
|
|
3296
|
+
var CANCEL_BMP, OK_BMP, RETRY_PNG;
|
|
3297
|
+
var init_assets_embedded = __esm({
|
|
3298
|
+
"src/assets-embedded.ts"() {
|
|
3299
|
+
"use strict";
|
|
3300
|
+
CANCEL_BMP = Buffer.from(`Qk02IQAAAAAAADYAAAAoAAAAVQAAACEAAAABABgAAAAAAAAAAAATCwAAEwsAAAAAAAAAAAAA////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////////8AAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAD/////////////AAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAA/////////wAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAP////8AAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAD///////////////////////////////////////////////////////////////8AAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAD/AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA////////////////////////////////////////////////////////AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAD/AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA////////////////////////////////////////////////////////AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA/////wAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAP///////////////////////////////////////////////////////////////wAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAP////////8AAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAD/////////////AAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAA/////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////`, "base64");
|
|
3301
|
+
OK_BMP = Buffer.from(`Qk02IQAAAAAAADYAAAAoAAAAVQAAACEAAAABABgAAAAAAAAAAAATCwAAEwsAAAAAAAAAAAAA////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////////8AAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAD/////////////AAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAA/////////wAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAP////8AAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAD/AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////wAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAA////////////////////AAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAP///////////////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAD///////////////////////////////8AAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAD/AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA/wAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAP8AAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAD/AAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAA/////wAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAP////////8AAAAAAAAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAAAAAAAAAAAAAAAAAD/////////////AAAAAAAAAAAAAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAA/////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////`, "base64");
|
|
3302
|
+
RETRY_PNG = Buffer.from(`iVBORw0KGgoAAAANSUhEUgAAAFUAAAAhCAYAAACoRueNAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAONSURBVHgB7Zo5ViMxEIbL/SZgXyKWCEIi1gPADeAEwA3gBMAN4ATADeAE+ADeUkd25CXyviXW1F+0euSxPdPGYHWg7z09tdUS6P1dKm1Fyqff76taraZKpZJaW1tTROTSfxJ0KpfLqtFoiH6amJ8Ti0nHx8fU6/WoUqnQ6uoqOcbDBig5dFpcXKREIkEbGxsUi8UoBoVZbTo8PBRhwdbWFqVSKZqbmyPHIJ7nUavVoqOjI8mbzSbBKKFZMpmkzc1NIpju9va2mDOrrLhQFYvFAXN2DKJdZaFQEL3IdwcsrJR7ULvdbovZwnzT6XRgxo7RQJuVlRWxSoxoWCmA6+x0OhSDs4V/WFpaomw2KxWdoOFhwyW4z729PfGz8LFetVqVlxAVDjcqgqJfT09PdHFxQbu7u58TAKf19XU6Ozuj19dXyufzQ+1QF2lWoE+Ye5AgsNZTlgbwD1Hwo7zyUPf396GXdTc3N9JGo8tnCXTDPGT0+VNUOFjb5HI5tbOzEwhzenqqnp+fpVzDPl/Kzs/Pg3poo+vYEBXgww6Jan5tG5iCIv/4+Ji4DX47UQ20OAcHBxP1BXUvLy8DYZ2oPvChWpSv9ANt8DFMXztrTFF/kWUwg7Oo8sxDnrhjodrc3t4GMy0wn21jXdR4PC45TzzElhqqDQR8e3sb+z7s3/kprIuK9SaAqGHhoS5W/a/3NonE8Af7+/s0CbzcoqjikWW0qLat6zuxLup38/DwINtb21hdUum1JVusmhbstshfms0ac0ll3VL1sNergGnIZDKS2579rYuqJ5yXlxealvf3d8mvrq7INlaHvzlswuz3x2Hu+80DmFkRqeGPHRQf38nz9fX1l3ZGaIMzVnB3d2d9+APre3/872kOVPS+38YEZfYj8kd/YYYwT24Tt/kpcB49JGoUblD/PqTGQTRPYIpn9aAOll4owwE2jTiktgF0w83JgKhIuGq1ba0a9ouhI0RQ13a/6/W6XE/jil/6ho7hR1SsVQPLw7UJLNK0XjzDgh8fHyNhBNpK+co60BFRKYpvKINgABdIER594YdRrgVFbJU3Pz8fhPdwBQlnQfhPo9GQK1fHMMq/isZ9v9YLLCwsSJJYKhTql7jHXl5elhgARF+gEtchxx+63a7EniGHuEPRPb4li6AnJycSAmRGtDlGo8XE5gWjHVF/QXSP6R8QrObiUyeLT4VeWKOa89Bv69GHm+SvZlsAAAAASUVORK5CYII=`, "base64");
|
|
3303
|
+
}
|
|
3304
|
+
});
|
|
3305
|
+
|
|
3306
|
+
// src/driver-hid.ts
|
|
3307
|
+
var driver_hid_exports = {};
|
|
3308
|
+
__export(driver_hid_exports, {
|
|
3309
|
+
createHidDriver: () => createHidDriver
|
|
3310
|
+
});
|
|
3311
|
+
function sensorToDisplay(sx, sy, dw, dh) {
|
|
3312
|
+
return {
|
|
3313
|
+
x: Math.round(sx * dw / SENSOR_RANGE),
|
|
3314
|
+
y: Math.round(sy * dh / SENSOR_RANGE)
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
function createHidDriver(config) {
|
|
3318
|
+
const log = config?.logger === null ? { info() {
|
|
3319
|
+
}, warn() {
|
|
3320
|
+
}, error() {
|
|
3321
|
+
} } : config?.logger ?? console;
|
|
3322
|
+
const onDriverEvent = config?.onDriverEvent;
|
|
3323
|
+
function emit(level, message, category, data) {
|
|
3324
|
+
if (level === "error") log.error(`[${category}] ${message}`, data ?? "");
|
|
3325
|
+
else if (level === "warn") log.warn(`[${category}] ${message}`, data ?? "");
|
|
3326
|
+
else log.info(`[${category}] ${message}`, data ?? "");
|
|
3327
|
+
onDriverEvent?.({
|
|
3328
|
+
level,
|
|
3329
|
+
message,
|
|
3330
|
+
category,
|
|
3331
|
+
data,
|
|
3332
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3333
|
+
});
|
|
3334
|
+
}
|
|
3335
|
+
let protocol = null;
|
|
3336
|
+
let padInfo = null;
|
|
3337
|
+
let currentHandlers = null;
|
|
3338
|
+
let currentSerialNo = null;
|
|
3339
|
+
let lastOptions = null;
|
|
3340
|
+
let lastDevicePath = null;
|
|
3341
|
+
let isCapturing = false;
|
|
3342
|
+
let sessionLock = Promise.resolve();
|
|
3343
|
+
let decryptor = null;
|
|
3344
|
+
let currentStroke = [];
|
|
3345
|
+
let allStrokes = [];
|
|
3346
|
+
let signatureEventCount = 0;
|
|
3347
|
+
let tapStartTime = 0;
|
|
3348
|
+
let tapStartPoint = null;
|
|
3349
|
+
let tapPointCount = 0;
|
|
3350
|
+
let tapMaxDrift = 0;
|
|
3351
|
+
let reconnectTimer = null;
|
|
3352
|
+
let cachedDeviceList = null;
|
|
3353
|
+
let cacheTimestamp = 0;
|
|
3354
|
+
const CACHE_TTL = 2e3;
|
|
3355
|
+
let cleanupPromise = Promise.resolve();
|
|
3356
|
+
function cleanup() {
|
|
3357
|
+
if (reconnectTimer) {
|
|
3358
|
+
clearTimeout(reconnectTimer);
|
|
3359
|
+
reconnectTimer = null;
|
|
3360
|
+
}
|
|
3361
|
+
isCapturing = false;
|
|
3362
|
+
const prot = protocol;
|
|
3363
|
+
protocol = null;
|
|
3364
|
+
currentHandlers = null;
|
|
3365
|
+
currentSerialNo = null;
|
|
3366
|
+
decryptor = null;
|
|
3367
|
+
currentStroke = [];
|
|
3368
|
+
allStrokes = [];
|
|
3369
|
+
signatureEventCount = 0;
|
|
3370
|
+
tapStartPoint = null;
|
|
3371
|
+
tapPointCount = 0;
|
|
3372
|
+
tapMaxDrift = 0;
|
|
3373
|
+
if (!prot) {
|
|
3374
|
+
cleanupPromise = Promise.resolve();
|
|
3375
|
+
return cleanupPromise;
|
|
3376
|
+
}
|
|
3377
|
+
prot.onSignatureEvent(null);
|
|
3378
|
+
prot.onDisconnect(null);
|
|
3379
|
+
cleanupPromise = (async () => {
|
|
3380
|
+
try {
|
|
3381
|
+
if (prot.isConnected()) {
|
|
3382
|
+
await prot.stopSignature().catch(() => {
|
|
3383
|
+
});
|
|
3384
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3385
|
+
await prot.eraseDisplay().catch(() => {
|
|
3386
|
+
});
|
|
3387
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
3388
|
+
await prot.showStandbyFromFlash().catch(() => {
|
|
3389
|
+
});
|
|
3390
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
3391
|
+
}
|
|
3392
|
+
} catch (err) {
|
|
3393
|
+
emit("warn", "Error during cleanup: " + err.message, "session");
|
|
3394
|
+
}
|
|
3395
|
+
try {
|
|
3396
|
+
prot.close();
|
|
3397
|
+
} catch (err) {
|
|
3398
|
+
emit("warn", "Error closing device: " + err.message, "device");
|
|
3399
|
+
}
|
|
3400
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
3401
|
+
})();
|
|
3402
|
+
return cleanupPromise;
|
|
3403
|
+
}
|
|
3404
|
+
function checkHotspot(x, y) {
|
|
3405
|
+
for (const hs of HOTSPOTS) {
|
|
3406
|
+
if (x >= hs.left && x <= hs.left + hs.width && y >= hs.top && y <= hs.top + hs.height) {
|
|
3407
|
+
return hs.id;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
return null;
|
|
3411
|
+
}
|
|
3412
|
+
function handleSignatureEvent(type, data) {
|
|
3413
|
+
if (!isCapturing || !currentHandlers || !currentSerialNo || !decryptor) return;
|
|
3414
|
+
const dw = padInfo?.displayWidth || 320;
|
|
3415
|
+
const dh = padInfo?.displayHeight || 160;
|
|
3416
|
+
if (type === SIG_EVENT.PEN_DATA || type === SIG_EVENT.PEN_DATA_EXT) {
|
|
3417
|
+
signatureEventCount++;
|
|
3418
|
+
const point = decryptor.decryptEvent(data);
|
|
3419
|
+
if (!point) return;
|
|
3420
|
+
const display = sensorToDisplay(point.x, point.y, dw, dh);
|
|
3421
|
+
if (tapPointCount === 0) {
|
|
3422
|
+
tapStartTime = Date.now();
|
|
3423
|
+
tapStartPoint = { x: display.x, y: display.y };
|
|
3424
|
+
tapMaxDrift = 0;
|
|
3425
|
+
} else if (tapStartPoint) {
|
|
3426
|
+
const drift = Math.abs(display.x - tapStartPoint.x) + Math.abs(display.y - tapStartPoint.y);
|
|
3427
|
+
if (drift > tapMaxDrift) tapMaxDrift = drift;
|
|
3428
|
+
}
|
|
3429
|
+
tapPointCount++;
|
|
3430
|
+
if (display.y > 39 && !point.penUp) {
|
|
3431
|
+
currentStroke.push({ x: point.x, y: point.y, pressure: point.pressure });
|
|
3432
|
+
const dllX = Math.round(180 + point.x / SENSOR_RANGE * 7620);
|
|
3433
|
+
const dllY = Math.round(1024 + point.y / SENSOR_RANGE * 2431);
|
|
3434
|
+
currentHandlers.onSignatureData("liveData", {
|
|
3435
|
+
point: { x: dllX, y: dllY, pressure: point.pressure },
|
|
3436
|
+
serialNo: currentSerialNo
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
} else if (type === SIG_EVENT.PEN_UP) {
|
|
3440
|
+
const tapDuration = Date.now() - tapStartTime;
|
|
3441
|
+
const isTap = tapPointCount > 0 && tapPointCount < TAP.MAX_POINTS && tapDuration < TAP.MAX_DURATION && tapMaxDrift < TAP.MAX_DRIFT;
|
|
3442
|
+
if (isTap && tapStartPoint) {
|
|
3443
|
+
const hotspotId = checkHotspot(tapStartPoint.x, tapStartPoint.y);
|
|
3444
|
+
if (hotspotId !== null) {
|
|
3445
|
+
handleHotspotPress(hotspotId);
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
if (currentStroke.length > 0) {
|
|
3449
|
+
allStrokes.push([...currentStroke]);
|
|
3450
|
+
currentStroke = [];
|
|
3451
|
+
}
|
|
3452
|
+
tapPointCount = 0;
|
|
3453
|
+
tapStartPoint = null;
|
|
3454
|
+
tapMaxDrift = 0;
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
function handleHotspotPress(id) {
|
|
3458
|
+
if (!currentHandlers || !currentSerialNo) return;
|
|
3459
|
+
switch (id) {
|
|
3460
|
+
case 0:
|
|
3461
|
+
emit("info", "Cancel button pressed", "session");
|
|
3462
|
+
isCapturing = false;
|
|
3463
|
+
currentHandlers.onUserEvent("cancel", { serialNo: currentSerialNo });
|
|
3464
|
+
cleanup();
|
|
3465
|
+
break;
|
|
3466
|
+
case 1:
|
|
3467
|
+
emit("info", "Retry button pressed", "session");
|
|
3468
|
+
currentStroke = [];
|
|
3469
|
+
allStrokes = [];
|
|
3470
|
+
signatureEventCount = 0;
|
|
3471
|
+
decryptor?.resetIv();
|
|
3472
|
+
tapPointCount = 0;
|
|
3473
|
+
tapStartPoint = null;
|
|
3474
|
+
currentHandlers.onSignatureData("clear", { serialNo: currentSerialNo });
|
|
3475
|
+
if (protocol?.isConnected() && lastOptions) {
|
|
3476
|
+
isCapturing = false;
|
|
3477
|
+
(async () => {
|
|
3478
|
+
try {
|
|
3479
|
+
await protocol.stopSignature();
|
|
3480
|
+
await protocol.eraseDisplay();
|
|
3481
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3482
|
+
await drawPadUI(lastOptions);
|
|
3483
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3484
|
+
await protocol.setSignRect(8, 40, 300, 95);
|
|
3485
|
+
isCapturing = true;
|
|
3486
|
+
await protocol.startSignature();
|
|
3487
|
+
} catch (err) {
|
|
3488
|
+
log.error("Error during retry:", err.message);
|
|
3489
|
+
}
|
|
3490
|
+
})();
|
|
3491
|
+
}
|
|
3492
|
+
break;
|
|
3493
|
+
case 2:
|
|
3494
|
+
emit("info", "Confirm button pressed", "session");
|
|
3495
|
+
driver2.confirmSignature();
|
|
3496
|
+
break;
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
function handleDisconnect() {
|
|
3500
|
+
emit("warn", "Pad disconnected during signing", "usb");
|
|
3501
|
+
isCapturing = false;
|
|
3502
|
+
if (lastDevicePath && currentHandlers) {
|
|
3503
|
+
attemptReconnect();
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
function attemptReconnect() {
|
|
3507
|
+
if (reconnectTimer) return;
|
|
3508
|
+
const maxAttempts = config?.reconnectAttempts ?? 10;
|
|
3509
|
+
const interval = config?.reconnectIntervalMs ?? 2e3;
|
|
3510
|
+
let attempts = 0;
|
|
3511
|
+
const tryReconnect = () => {
|
|
3512
|
+
attempts++;
|
|
3513
|
+
if (attempts > maxAttempts) {
|
|
3514
|
+
log.error(`Signotec pad reconnection failed after ${maxAttempts} attempts`);
|
|
3515
|
+
currentHandlers?.onUserEvent("cancel", { serialNo: currentSerialNo || "unknown" });
|
|
3516
|
+
cleanup();
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
log.info(`Signotec reconnect attempt ${attempts}/${maxAttempts}...`);
|
|
3520
|
+
try {
|
|
3521
|
+
const prot = new HidProtocol(config);
|
|
3522
|
+
const devices = prot.listDevices();
|
|
3523
|
+
if (devices.length > 0) {
|
|
3524
|
+
log.info("Signotec pad reconnected - cancelling session (state lost)");
|
|
3525
|
+
currentHandlers?.onUserEvent("cancel", { serialNo: currentSerialNo || "unknown" });
|
|
3526
|
+
cleanup();
|
|
3527
|
+
return;
|
|
3528
|
+
}
|
|
3529
|
+
} catch (err) {
|
|
3530
|
+
log.warn("Reconnect probe error:", err.message);
|
|
3531
|
+
}
|
|
3532
|
+
reconnectTimer = setTimeout(tryReconnect, interval);
|
|
3533
|
+
};
|
|
3534
|
+
reconnectTimer = setTimeout(tryReconnect, 1e3);
|
|
3535
|
+
}
|
|
3536
|
+
async function drawPadUI(options) {
|
|
3537
|
+
if (!protocol || !padInfo) return;
|
|
3538
|
+
const w = padInfo.displayWidth;
|
|
3539
|
+
const h = padInfo.displayHeight;
|
|
3540
|
+
const renderer = new DisplayRenderer(w, h);
|
|
3541
|
+
renderer.clear();
|
|
3542
|
+
for (const hs of HOTSPOTS) {
|
|
3543
|
+
renderer.fillRect(hs.left, hs.top, hs.width, hs.height, true);
|
|
3544
|
+
}
|
|
3545
|
+
renderer.drawTextInverted(16, 12, "Abbruch", 1);
|
|
3546
|
+
renderer.drawTextInverted(124, 12, "Neustart", 1);
|
|
3547
|
+
renderer.drawTextInverted(251, 12, "OK", 1);
|
|
3548
|
+
const dd = options?.displayData;
|
|
3549
|
+
let textY = 40;
|
|
3550
|
+
const bodySpacing = 15;
|
|
3551
|
+
const drawBodyLine = (line) => {
|
|
3552
|
+
const fs3 = Math.max(1, Math.min(3, line.fontSize ?? 1));
|
|
3553
|
+
renderer.drawText(8, textY, `${line.label} ${line.content}`, fs3);
|
|
3554
|
+
textY += bodySpacing;
|
|
3555
|
+
};
|
|
3556
|
+
if (dd?.body?.line1) drawBodyLine(dd.body.line1);
|
|
3557
|
+
if (dd?.body?.line2) drawBodyLine(dd.body.line2);
|
|
3558
|
+
if (dd?.body?.line3) drawBodyLine(dd.body.line3);
|
|
3559
|
+
if (dd?.body?.line4) drawBodyLine(dd.body.line4);
|
|
3560
|
+
renderer.drawText(8, 120, "Unterschrift:", 1, true);
|
|
3561
|
+
renderer.hLine(8, 135, w - 16);
|
|
3562
|
+
if (dd?.footer?.line1) {
|
|
3563
|
+
const fs3 = Math.max(1, Math.min(3, dd.footer.line1.fontSize ?? 1));
|
|
3564
|
+
renderer.drawText(8, 140, `${dd.footer.line1.label} ${dd.footer.line1.content}`, fs3);
|
|
3565
|
+
}
|
|
3566
|
+
if (dd?.footer?.line2) {
|
|
3567
|
+
const fs3 = Math.max(1, Math.min(3, dd.footer.line2.fontSize ?? 1));
|
|
3568
|
+
renderer.drawText(203, 140, `${dd.footer.line2.label} ${dd.footer.line2.content}`, fs3);
|
|
3569
|
+
}
|
|
3570
|
+
try {
|
|
3571
|
+
renderer.drawImageData(8, 5, CANCEL_BMP);
|
|
3572
|
+
renderer.drawImageData(223, 5, OK_BMP);
|
|
3573
|
+
renderer.drawImageData(115, 5, RETRY_PNG);
|
|
3574
|
+
} catch (err) {
|
|
3575
|
+
log.warn("Could not render button images:", err.message);
|
|
3576
|
+
}
|
|
3577
|
+
await renderer.sendToDevice(protocol);
|
|
3578
|
+
}
|
|
3579
|
+
async function doStartSession(serialNo, handlers, options) {
|
|
3580
|
+
await cleanupPromise;
|
|
3581
|
+
if (protocol?.isConnected()) {
|
|
3582
|
+
log.warn("Signotec: Previous session still active, cleaning up first");
|
|
3583
|
+
await cleanup();
|
|
3584
|
+
}
|
|
3585
|
+
protocol = new HidProtocol(config);
|
|
3586
|
+
const devices = protocol.listDevices();
|
|
3587
|
+
if (devices.length === 0) {
|
|
3588
|
+
protocol = null;
|
|
3589
|
+
throw new Error(`No device with serialNo.: ${serialNo} was found.`);
|
|
3590
|
+
}
|
|
3591
|
+
const targetDevice = devices.find((d) => d.serialNumber === serialNo) || devices[0];
|
|
3592
|
+
lastDevicePath = targetDevice.path;
|
|
3593
|
+
const maxOpenRetries = 3;
|
|
3594
|
+
for (let attempt = 1; attempt <= maxOpenRetries; attempt++) {
|
|
3595
|
+
try {
|
|
3596
|
+
protocol.open(targetDevice.path);
|
|
3597
|
+
break;
|
|
3598
|
+
} catch (err) {
|
|
3599
|
+
if (attempt < maxOpenRetries) {
|
|
3600
|
+
log.warn(`Device open attempt ${attempt}/${maxOpenRetries} failed, retrying in 500ms...`);
|
|
3601
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
3602
|
+
} else {
|
|
3603
|
+
protocol = null;
|
|
3604
|
+
throw new Error(`Failed to open device after ${maxOpenRetries} attempts: ${err.message}`);
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
const responsive = await protocol.isResponsive();
|
|
3609
|
+
if (!responsive) {
|
|
3610
|
+
protocol.close();
|
|
3611
|
+
protocol = null;
|
|
3612
|
+
throw new Error("Device is connected but not responding. Check USB connection.");
|
|
3613
|
+
}
|
|
3614
|
+
protocol.onDisconnect(handleDisconnect);
|
|
3615
|
+
protocol.onSignatureEvent(handleSignatureEvent);
|
|
3616
|
+
currentHandlers = handlers;
|
|
3617
|
+
currentSerialNo = serialNo;
|
|
3618
|
+
lastOptions = options;
|
|
3619
|
+
currentStroke = [];
|
|
3620
|
+
allStrokes = [];
|
|
3621
|
+
signatureEventCount = 0;
|
|
3622
|
+
tapPointCount = 0;
|
|
3623
|
+
tapStartPoint = null;
|
|
3624
|
+
tapMaxDrift = 0;
|
|
3625
|
+
decryptor = SignatureDecryptor.withDefaultKey();
|
|
3626
|
+
padInfo = await protocol.getDeviceInfo();
|
|
3627
|
+
log.info(`Signotec pad: ${padInfo.serialNo} (${padInfo.displayWidth}x${padInfo.displayHeight})`);
|
|
3628
|
+
const erased = await protocol.eraseDisplay();
|
|
3629
|
+
if (!erased) {
|
|
3630
|
+
await cleanup();
|
|
3631
|
+
throw new Error("Failed to erase display. Device may be unresponsive.");
|
|
3632
|
+
}
|
|
3633
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
3634
|
+
await drawPadUI(options);
|
|
3635
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3636
|
+
await protocol.setSignRect(8, 40, 300, 95);
|
|
3637
|
+
isCapturing = true;
|
|
3638
|
+
const started = await protocol.startSignature();
|
|
3639
|
+
if (!started) {
|
|
3640
|
+
await cleanup();
|
|
3641
|
+
throw new Error("Failed to start signature capture on device.");
|
|
3642
|
+
}
|
|
3643
|
+
emit("info", "Signing session started", "session", { serialNo: currentSerialNo });
|
|
3644
|
+
}
|
|
3645
|
+
const driver2 = {
|
|
3646
|
+
getSignotecPads() {
|
|
3647
|
+
if (cachedDeviceList && Date.now() - cacheTimestamp < CACHE_TTL) {
|
|
3648
|
+
return cachedDeviceList;
|
|
3649
|
+
}
|
|
3650
|
+
try {
|
|
3651
|
+
const prot = new HidProtocol(config);
|
|
3652
|
+
const devices = prot.listDevices();
|
|
3653
|
+
cachedDeviceList = devices.map((d, i) => ({
|
|
3654
|
+
serialNo: d.serialNumber || `signotec-${i}`,
|
|
3655
|
+
type: 1,
|
|
3656
|
+
name: d.product || "Sigma USB",
|
|
3657
|
+
vendor: "Signotec"
|
|
3658
|
+
}));
|
|
3659
|
+
cacheTimestamp = Date.now();
|
|
3660
|
+
return cachedDeviceList;
|
|
3661
|
+
} catch (err) {
|
|
3662
|
+
emit("error", "Device discovery failed: " + err.message, "device");
|
|
3663
|
+
return [];
|
|
3664
|
+
}
|
|
3665
|
+
},
|
|
3666
|
+
async startSigningProcess(serialNo, handlers, options) {
|
|
3667
|
+
let startError = null;
|
|
3668
|
+
await new Promise((resolve, reject) => {
|
|
3669
|
+
sessionLock = sessionLock.then(async () => {
|
|
3670
|
+
try {
|
|
3671
|
+
await doStartSession(serialNo, handlers, options);
|
|
3672
|
+
resolve();
|
|
3673
|
+
} catch (err) {
|
|
3674
|
+
startError = err;
|
|
3675
|
+
resolve();
|
|
3676
|
+
}
|
|
3677
|
+
});
|
|
3678
|
+
});
|
|
3679
|
+
if (startError) throw startError;
|
|
3680
|
+
},
|
|
3681
|
+
cancelSignature() {
|
|
3682
|
+
if (!currentSerialNo) return { status: false, message: "No signature process started" };
|
|
3683
|
+
log.info("Signotec: Signature cancelled (external)");
|
|
3684
|
+
const serial = currentSerialNo;
|
|
3685
|
+
currentHandlers?.onUserEvent("cancel", { serialNo: serial });
|
|
3686
|
+
cleanup();
|
|
3687
|
+
return { status: true, message: "Signature process stopped" };
|
|
3688
|
+
},
|
|
3689
|
+
confirmSignature() {
|
|
3690
|
+
if (!currentSerialNo || !currentHandlers) {
|
|
3691
|
+
throw new Error("No active signature process.");
|
|
3692
|
+
}
|
|
3693
|
+
const serialNo = currentSerialNo;
|
|
3694
|
+
const handlers = currentHandlers;
|
|
3695
|
+
if (currentStroke.length > 0) {
|
|
3696
|
+
allStrokes.push([...currentStroke]);
|
|
3697
|
+
currentStroke = [];
|
|
3698
|
+
}
|
|
3699
|
+
let base64Img = "";
|
|
3700
|
+
if (allStrokes.length > 0 && allStrokes.some((s) => s.length >= 2)) {
|
|
3701
|
+
try {
|
|
3702
|
+
const pngBuffer = renderSignaturePng(
|
|
3703
|
+
allStrokes,
|
|
3704
|
+
SENSOR_RANGE,
|
|
3705
|
+
SENSOR_RANGE,
|
|
3706
|
+
{
|
|
3707
|
+
width: lastOptions?.outputOptions?.imageSize?.width || 400,
|
|
3708
|
+
height: lastOptions?.outputOptions?.imageSize?.height || 200,
|
|
3709
|
+
strokeWidth: lastOptions?.outputOptions?.strokeWidth || 2
|
|
3710
|
+
}
|
|
3711
|
+
);
|
|
3712
|
+
if (pngBuffer.length >= MIN_SIGNATURE_SIZE) {
|
|
3713
|
+
base64Img = "data:image/png;base64," + pngBuffer.toString("base64");
|
|
3714
|
+
} else {
|
|
3715
|
+
log.warn("Signature too small, may be invalid");
|
|
3716
|
+
}
|
|
3717
|
+
} catch (err) {
|
|
3718
|
+
log.error("Signature image rendering error:", err.message);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
log.info(`Signotec: Signature confirmed (${allStrokes.length} strokes, ${base64Img.length} chars)`);
|
|
3722
|
+
handlers.onUserEvent("confirm", { base64Img, serialNo });
|
|
3723
|
+
cleanup();
|
|
3724
|
+
},
|
|
3725
|
+
async continueWithoutSignature() {
|
|
3726
|
+
if (!currentSerialNo) return { status: false, message: "No matching device found" };
|
|
3727
|
+
log.info("Signotec: Continue without signature");
|
|
3728
|
+
try {
|
|
3729
|
+
currentHandlers?.onUserEvent("confirm", { serialNo: currentSerialNo, base64Img: "" });
|
|
3730
|
+
cleanup();
|
|
3731
|
+
return { status: true, message: "User prompt cleared" };
|
|
3732
|
+
} catch (err) {
|
|
3733
|
+
log.error("continueWithoutSignature error:", err.message);
|
|
3734
|
+
cleanup();
|
|
3735
|
+
return { status: false, message: "User prompt could not be cleared" };
|
|
3736
|
+
}
|
|
3737
|
+
},
|
|
3738
|
+
async setStandbyImage(imagePath) {
|
|
3739
|
+
if (!imagePath) return true;
|
|
3740
|
+
if (!import_fs2.default.existsSync(imagePath)) {
|
|
3741
|
+
log.error(`Standby image not found: ${imagePath}`);
|
|
3742
|
+
return false;
|
|
3743
|
+
}
|
|
3744
|
+
const ext = import_path.default.extname(imagePath).toLowerCase();
|
|
3745
|
+
if (ext !== ".png" && ext !== ".bmp") {
|
|
3746
|
+
log.error(`Unsupported standby image format: ${ext}. Use .png or .bmp`);
|
|
3747
|
+
return false;
|
|
3748
|
+
}
|
|
3749
|
+
try {
|
|
3750
|
+
const prot = new HidProtocol(config);
|
|
3751
|
+
const devices = prot.listDevices();
|
|
3752
|
+
if (devices.length === 0) {
|
|
3753
|
+
log.error("No Signotec device connected for standby image upload");
|
|
3754
|
+
return false;
|
|
3755
|
+
}
|
|
3756
|
+
prot.open(devices[0].path);
|
|
3757
|
+
const info = await prot.getDeviceInfo();
|
|
3758
|
+
const w = info.displayWidth || 320;
|
|
3759
|
+
const h = info.displayHeight || 160;
|
|
3760
|
+
const renderer = new DisplayRenderer(w, h);
|
|
3761
|
+
renderer.clear();
|
|
3762
|
+
if (ext === ".png") renderer.drawPngFile(0, 0, imagePath);
|
|
3763
|
+
else renderer.drawBmpFile(0, 0, imagePath);
|
|
3764
|
+
log.info(`Storing standby image to flash: ${imagePath} (${w}x${h})`);
|
|
3765
|
+
const ok = await prot.storeStandbyImage(w, h, renderer.buffer);
|
|
3766
|
+
if (ok) {
|
|
3767
|
+
await prot.eraseDisplay();
|
|
3768
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3769
|
+
await prot.drawImage(0, 0, w, h, renderer.buffer);
|
|
3770
|
+
log.info("Standby image stored to flash successfully");
|
|
3771
|
+
} else {
|
|
3772
|
+
log.error("Failed to store standby image to flash");
|
|
3773
|
+
}
|
|
3774
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
3775
|
+
prot.close();
|
|
3776
|
+
return ok;
|
|
3777
|
+
} catch (err) {
|
|
3778
|
+
log.error("setStandbyImage error:", err.message);
|
|
3779
|
+
return false;
|
|
3780
|
+
}
|
|
3781
|
+
},
|
|
3782
|
+
async waitForDevice(timeoutMs = 3e4) {
|
|
3783
|
+
const start = Date.now();
|
|
3784
|
+
const interval = 1e3;
|
|
3785
|
+
while (Date.now() - start < timeoutMs) {
|
|
3786
|
+
const pads = driver2.getSignotecPads();
|
|
3787
|
+
if (pads.length > 0) return pads[0];
|
|
3788
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
3789
|
+
}
|
|
3790
|
+
return null;
|
|
3791
|
+
}
|
|
3792
|
+
};
|
|
3793
|
+
return driver2;
|
|
3794
|
+
}
|
|
3795
|
+
var import_path, import_fs2, HOTSPOTS, SENSOR_RANGE, TAP, MIN_SIGNATURE_SIZE;
|
|
3796
|
+
var init_driver_hid = __esm({
|
|
3797
|
+
"src/driver-hid.ts"() {
|
|
3798
|
+
"use strict";
|
|
3799
|
+
import_path = __toESM(require("path"));
|
|
3800
|
+
import_fs2 = __toESM(require("fs"));
|
|
3801
|
+
init_hid_protocol();
|
|
3802
|
+
init_hid_display();
|
|
3803
|
+
init_hid_crypto();
|
|
3804
|
+
init_signature_renderer();
|
|
3805
|
+
init_assets_embedded();
|
|
3806
|
+
HOTSPOTS = [
|
|
3807
|
+
{ id: 0, label: "Cancel", left: 8, top: 5, width: 85, height: 33 },
|
|
3808
|
+
{ id: 1, label: "Retry", left: 115, top: 5, width: 85, height: 33 },
|
|
3809
|
+
{ id: 2, label: "Confirm", left: 223, top: 5, width: 85, height: 33 }
|
|
3810
|
+
];
|
|
3811
|
+
SENSOR_RANGE = 4096;
|
|
3812
|
+
TAP = {
|
|
3813
|
+
MAX_POINTS: 200,
|
|
3814
|
+
// max pen-data events for a tap (~800ms at 250Hz)
|
|
3815
|
+
MAX_DURATION: 800,
|
|
3816
|
+
// max tap duration in ms
|
|
3817
|
+
MAX_DRIFT: 30
|
|
3818
|
+
// max pen movement in display pixels
|
|
3819
|
+
};
|
|
3820
|
+
MIN_SIGNATURE_SIZE = 500;
|
|
3821
|
+
}
|
|
3822
|
+
});
|
|
3823
|
+
|
|
3824
|
+
// src/index.ts
|
|
3825
|
+
var index_exports = {};
|
|
3826
|
+
__export(index_exports, {
|
|
3827
|
+
DEVICE_TYPE_MAPPINGS: () => DEVICE_TYPE_MAPPINGS,
|
|
3828
|
+
cancelSignature: () => cancelSignature,
|
|
3829
|
+
configure: () => configure,
|
|
3830
|
+
confirmSignature: () => confirmSignature,
|
|
3831
|
+
continueWithoutSignature: () => continueWithoutSignature,
|
|
3832
|
+
createHidDriver: () => createHidDriver,
|
|
3833
|
+
getSignotecPads: () => getSignotecPads,
|
|
3834
|
+
setStandbyImage: () => setStandbyImage,
|
|
3835
|
+
startSigningProcess: () => startSigningProcess,
|
|
3836
|
+
waitForDevice: () => waitForDevice
|
|
3837
|
+
});
|
|
3838
|
+
module.exports = __toCommonJS(index_exports);
|
|
3839
|
+
|
|
3840
|
+
// src/types.ts
|
|
3841
|
+
var DEVICE_TYPE_MAPPINGS = {
|
|
3842
|
+
1: "Sigma USB",
|
|
3843
|
+
2: "Sigma seriell",
|
|
3844
|
+
5: "Zeta USB",
|
|
3845
|
+
6: "Zeta seriell",
|
|
3846
|
+
11: "Omega USB",
|
|
3847
|
+
12: "Omega seriell",
|
|
3848
|
+
15: "Gamma USB",
|
|
3849
|
+
16: "Gamma seriell",
|
|
3850
|
+
21: "Delta USB",
|
|
3851
|
+
22: "Delta seriell",
|
|
3852
|
+
23: "Delta IP",
|
|
3853
|
+
31: "Alpha USB",
|
|
3854
|
+
32: "Alpha seriell",
|
|
3855
|
+
33: "Alpha IP"
|
|
3856
|
+
};
|
|
3857
|
+
|
|
3858
|
+
// src/index.ts
|
|
3859
|
+
init_driver_hid();
|
|
3860
|
+
var driver = null;
|
|
3861
|
+
var driverError = null;
|
|
3862
|
+
var driverConfig;
|
|
3863
|
+
function configure(config) {
|
|
3864
|
+
driverConfig = config;
|
|
3865
|
+
driver = null;
|
|
3866
|
+
driverError = null;
|
|
3867
|
+
}
|
|
3868
|
+
function getDriver() {
|
|
3869
|
+
if (driver) return driver;
|
|
3870
|
+
if (driverError) throw driverError;
|
|
3871
|
+
try {
|
|
3872
|
+
const { createHidDriver: createHidDriver2 } = (init_driver_hid(), __toCommonJS(driver_hid_exports));
|
|
3873
|
+
driver = createHidDriver2(driverConfig);
|
|
3874
|
+
} catch (err) {
|
|
3875
|
+
driverError = err;
|
|
3876
|
+
throw err;
|
|
3877
|
+
}
|
|
3878
|
+
return driver;
|
|
3879
|
+
}
|
|
3880
|
+
var getSignotecPads = () => getDriver().getSignotecPads();
|
|
3881
|
+
var startSigningProcess = async (serialNo, handlers, options) => getDriver().startSigningProcess(serialNo, handlers, options);
|
|
3882
|
+
var cancelSignature = () => getDriver().cancelSignature();
|
|
3883
|
+
var confirmSignature = () => getDriver().confirmSignature();
|
|
3884
|
+
var continueWithoutSignature = async () => getDriver().continueWithoutSignature();
|
|
3885
|
+
var setStandbyImage = async (imagePath) => getDriver().setStandbyImage?.(imagePath) ?? false;
|
|
3886
|
+
var waitForDevice = async (timeoutMs) => getDriver().waitForDevice?.(timeoutMs) ?? null;
|
|
3887
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3888
|
+
0 && (module.exports = {
|
|
3889
|
+
DEVICE_TYPE_MAPPINGS,
|
|
3890
|
+
cancelSignature,
|
|
3891
|
+
configure,
|
|
3892
|
+
confirmSignature,
|
|
3893
|
+
continueWithoutSignature,
|
|
3894
|
+
createHidDriver,
|
|
3895
|
+
getSignotecPads,
|
|
3896
|
+
setStandbyImage,
|
|
3897
|
+
startSigningProcess,
|
|
3898
|
+
waitForDevice
|
|
3899
|
+
});
|
|
3900
|
+
//# sourceMappingURL=index.js.map
|