const EventEmitter = require("events").EventEmitter; const util = require("util"); let driverType = null; function setDriverType(type) { driverType = type; } // lazy load the C++ binding let binding = null; function loadBinding() { if (!binding) { const options = require('./binding-options'); if (process.platform === "linux" && (!driverType || driverType === "hidraw")) { options.name = 'HID_hidraw'; } binding = require("pkg-prebuilds/bindings")(__dirname, options); } } //This class is a wrapper for `binding.HID` class function HID() { // see issue #150 (enhancement, solves issue #149) // throw an error for those who forget to instantiate, i.e. by "*new* HID.HID()" // and who would otherwise be left trying to figure out why "self.on is not a function" if (!new.target) { throw new Error('HID() must be called with \'new\' operator'); } //Inherit from EventEmitter EventEmitter.call(this); loadBinding(); /* We also want to inherit from `binding.HID`, but unfortunately, it's not so easy for native Objects. For example, the following won't work since `new` keyword isn't used: `binding.HID.apply(this, arguments);` So... we do this craziness instead... */ var thisPlusArgs = new Array(arguments.length + 1); thisPlusArgs[0] = null; for(var i = 0; i < arguments.length; i++) thisPlusArgs[i + 1] = arguments[i]; this._raw = new (Function.prototype.bind.apply(binding.HID, thisPlusArgs) )(); /* Now we have `this._raw` Object from which we need to inherit. So, one solution is to simply copy all prototype methods over to `this` and binding them to `this._raw` */ for(var i in binding.HID.prototype) this[i] = binding.HID.prototype[i].bind(this._raw); /* We are now done inheriting from `binding.HID` and EventEmitter. Now upon adding a new listener for "data" events, we start polling the HID device using `read(...)` See `resume()` for more details. */ this._paused = true; var self = this; self.on("newListener", function(eventName, listener) { if(eventName == "data") process.nextTick(self.resume.bind(self) ); }); } //Inherit prototype methods util.inherits(HID, EventEmitter); //Don't inherit from `binding.HID`; that's done above already! HID.prototype.close = function close() { this._closing = true; this.removeAllListeners(); if (this._paused) { // Don't exit if a read is currently running this._raw.close(); this._closed = true; } else { // Make sure the read is stopped ASAP this._raw.readInterrupt(); } }; //Pauses the reader, which stops "data" events from being emitted HID.prototype.pause = function pause() { this._paused = true; this._raw.readInterrupt(); }; HID.prototype.read = function read(callback) { if (this._closed) { throw new Error('Unable to read from a closed HID device'); } else { return this._raw.read(callback); } }; HID.prototype.resume = function resume() { var self = this; if(self._paused && self.listeners("data").length > 0) { //Start polling & reading loop self._paused = false; self.read(function readFunc(err, data) { try { if (self._closing) { // Discard any data if we're closing self._paused = true; self._raw.close(); self._closed = true; return } if(err) { //Emit error and pause reading self._paused = true; if(!self._closing) self.emit("error", err); //else ignore any errors if I'm closing the device } else { //If there are no "data" listeners, we pause if(self.listeners("data").length <= 0) self._paused = true; //Keep reading if we aren't paused if(!self._paused) self.read(readFunc); //Now emit the event self.emit("data", data); } } catch (e) { // Emit an error on the device instead of propagating to a c++ exception setImmediate(() => { if (!self._closing) self.emit("error", e); }); } }); } }; class HIDAsync extends EventEmitter { constructor(raw) { super() if (!(raw instanceof binding.HIDAsync)) { throw new Error(`HIDAsync cannot be constructed directly. Use HIDAsync.open() instead`) } this._raw = raw /* Now we have `this._raw` Object from which we need to inherit. So, one solution is to simply copy all prototype methods over to `this` and binding them to `this._raw`. We explicitly wrap them in an async method, to ensure that any thrown errors are promise rejections */ for (let i in this._raw) { this[i] = async (...args) => this._raw[i](...args); } /* Now upon adding a new listener for "data" events, we start the read thread executing. See `resume()` for more details. */ this.on("newListener", (eventName, listener) =>{ if(eventName == "data") process.nextTick(this.resume.bind(this) ); }); this.on("removeListener", (eventName, listener) => { if(eventName == "data" && this.listenerCount("data") == 0) process.nextTick(this.pause.bind(this) ); }) } static async open(...args) { loadBinding(); const native = await binding.openAsyncHIDDevice(...args); return new HIDAsync(native) } async close() { this._closing = true; await this._raw.close(); this.removeAllListeners(); this._closed = true; } //Pauses the reader, which stops "data" events from being emitted pause() { this._raw.readStop(); } resume() { if(this.listenerCount("data") > 0) { //Start polling & reading loop this._raw.readStart((err, data) => { try { if (err) { if(!this._closing) this.emit("error", err); //else ignore any errors if I'm closing the device } else { this.emit("data", data); } } catch (e) { // Emit an error on the device instead of propagating to a c++ exception setImmediate(() => { if (!this._closing) this.emit("error", e); }); } }) } } } function showdevices() { loadBinding(); return binding.devices.apply(HID,arguments); } function showdevicesAsync(...args) { loadBinding(); return binding.devicesAsync(...args); } function getHidapiVersion() { loadBinding(); return binding.hidapiVersion; } //Expose API exports.HID = HID; exports.HIDAsync = HIDAsync; exports.devices = showdevices; exports.devicesAsync = showdevicesAsync; exports.setDriverType = setDriverType; exports.getHidapiVersion = getHidapiVersion;