Skip to content

Adapter read() lifecycle: unremovable listener, inconsistent contract, and stray debug log #109

Description

@Schlumpf9

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions