While integrating LAN + USB Epson TM printers (TM-m30III, TM-T20II/III) I hit three related problems around the adapter read() API. They share the same area, so I've grouped them.
Environment
@node-escpos/core@0.6.0
@node-escpos/network-adapter@0.0.1
@node-escpos/usb-adapter@0.3.1
- Node 24, hardware: Epson TM-m30III (LAN + USB), validated live
1. read() registers a listener that can never be removed (listener leak)
The network/serial adapters wrap the callback in an anonymous function and keep no reference to it, and there's no stopRead()/off():
// network-adapter
read(callback) {
this.device.on("data", (buf) => { if (callback) callback(buf); });
return this;
}
Because the registered listener is the anonymous wrapper (not callback), the caller cannot remove it — this.device.removeListener('data', callback) matches nothing. Every read() call leaks a 'data' listener.
Repro:
const printer = new Printer(new Network(host));
for (let i = 0; i < 20; i++) printer.adapter.read(() => {});
// -> MaxListenersExceededWarning: 11 data listeners added to [Socket]
Suggested fix: return a disposer (or add a counterpart method):
const stop = adapter.read(onData);
stop(); // removes exactly this listener
2. read() has inconsistent (and undocumented) semantics across adapters
Same method, opposite contract:
- network / serialport: persistent stream — fires on every
data event.
- usb: one-shot —
deviceToPcEndpoint.transfer(64, cb) fires once and does not re-arm.
Code that reads a multi-byte response works over LAN but silently returns nothing over USB (the first transfer completes before all bytes
arrive, and read() never re-fires). Callers currently have to sniff internals (e.g. 'deviceToPcEndpoint' in adapter) to branch.
Suggested fix: unify the contract (e.g. a continuous-read option for USB via node-usb endpoint.startPoll()), or document the
difference and expose a capability flag so callers can branch deterministically.
3. Debug console.log left in the network adapter
Network.open() registers a permanent debug listener:
.on("data", (buf) => { console.log("printer say:", buf); })
This prints on every response and is itself an unremovable 'data' listener (compounds #1).
Suggested fix: remove it, or gate it behind a debug flag / the debug package.
While integrating LAN + USB Epson TM printers (TM-m30III, TM-T20II/III) I hit three related problems around the adapter
read()API. They share the same area, so I've grouped them.Environment
@node-escpos/core@0.6.0@node-escpos/network-adapter@0.0.1@node-escpos/usb-adapter@0.3.11.
read()registers a listener that can never be removed (listener leak)The network/serial adapters wrap the callback in an anonymous function and keep no reference to it, and there's no
stopRead()/off():Because the registered listener is the anonymous wrapper (not
callback), the caller cannot remove it —this.device.removeListener('data', callback)matches nothing. Everyread()call leaks a'data'listener.Repro:
Suggested fix: return a disposer (or add a counterpart method):
2.
read()has inconsistent (and undocumented) semantics across adaptersSame method, opposite contract:
dataevent.deviceToPcEndpoint.transfer(64, cb)fires once and does not re-arm.Code that reads a multi-byte response works over LAN but silently returns nothing over USB (the first transfer completes before all bytes
arrive, and
read()never re-fires). Callers currently have to sniff internals (e.g.'deviceToPcEndpoint' in adapter) to branch.Suggested fix: unify the contract (e.g. a continuous-read option for USB via node-usb
endpoint.startPoll()), or document thedifference and expose a capability flag so callers can branch deterministically.
3. Debug
console.logleft in the network adapterNetwork.open()registers a permanent debug listener:This prints on every response and is itself an unremovable
'data'listener (compounds #1).Suggested fix: remove it, or gate it behind a debug flag / the
debugpackage.