label-printer 0.6.0 → 0.7.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/dist/index.js CHANGED
@@ -34,15 +34,15 @@ var __copyProps = (to, from, except, desc) => {
34
34
  }
35
35
  return to;
36
36
  };
37
- var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
37
+ var __toESM = (mod2, isNodeMode, target2) => (target2 = mod2 != null ? __create(__getProtoOf(mod2)) : {}, __copyProps(
38
38
  // If the importer is in node compatibility mode or this is not an ESM
39
39
  // file that has been converted to a CommonJS file using a Babel-
40
40
  // compatible transform (i.e. "__esModule" has not been set), then set
41
41
  // "default" to the CommonJS "module.exports" for node compatibility.
42
- isNodeMode || !mod || !mod.__esModule ? __defProp(target2, "default", { value: mod, enumerable: true }) : target2,
43
- mod
42
+ isNodeMode || !mod2 || !mod2.__esModule ? __defProp(target2, "default", { value: mod2, enumerable: true }) : target2,
43
+ mod2
44
44
  ));
45
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
45
+ var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
46
46
  var __async = (__this, __arguments, generator) => {
47
47
  return new Promise((resolve, reject) => {
48
48
  var fulfilled = (value) => {
@@ -87,10 +87,10 @@ var Command = class {
87
87
  fn(this.commandString);
88
88
  }
89
89
  /**
90
- * Write the command data to a USB device
90
+ * Write the command data to a device
91
91
  * @param device Device to write to
92
92
  */
93
- write(device) {
93
+ writeTo(device) {
94
94
  return __async(this, null, function* () {
95
95
  yield this.writeString(this.commandString, device);
96
96
  yield this.terminateCommand(device);
@@ -144,10 +144,10 @@ var CommandGroup = class extends Command {
144
144
  this.commands[commandIndex].print(fn);
145
145
  }
146
146
  }
147
- write(device) {
147
+ writeTo(device) {
148
148
  return __async(this, null, function* () {
149
149
  for (let commandIndex in this.commands) {
150
- yield this.commands[commandIndex].write(device);
150
+ yield this.commands[commandIndex].writeTo(device);
151
151
  }
152
152
  });
153
153
  }
@@ -968,7 +968,7 @@ var TSPLBitmapCommand = class _TSPLBitmapCommand extends TSPLVisualCommand {
968
968
  return 2;
969
969
  }
970
970
  }
971
- write(device) {
971
+ writeTo(device) {
972
972
  return __async(this, null, function* () {
973
973
  yield this.writeString(this.commandWithoutBytes, device);
974
974
  yield this.writeBytes(this.bitmap.bytes, device);
@@ -1149,7 +1149,7 @@ var TSPLDownload = class extends TSPLCommand {
1149
1149
  get commandString() {
1150
1150
  return `DOWNLOAD "${this.fileName}", ${this.data.byteLength},`;
1151
1151
  }
1152
- write(device) {
1152
+ writeTo(device) {
1153
1153
  return __async(this, null, function* () {
1154
1154
  yield this.writeString(this.commandString, device);
1155
1155
  yield this.writeBytes(this.data, device);
@@ -1333,14 +1333,14 @@ __export(printers_exports, {
1333
1333
  // src/printers/Printer.ts
1334
1334
  var Printer = class {
1335
1335
  constructor(device) {
1336
- this.usbDevice = device;
1336
+ this.device = device;
1337
1337
  }
1338
1338
  /**
1339
1339
  * Close the printer USB
1340
1340
  */
1341
1341
  close() {
1342
1342
  return __async(this, null, function* () {
1343
- yield this.usbDevice.close();
1343
+ yield this.device.close();
1344
1344
  });
1345
1345
  }
1346
1346
  /**
@@ -1369,8 +1369,8 @@ var Printer = class {
1369
1369
  */
1370
1370
  writeCommand(command) {
1371
1371
  return __async(this, null, function* () {
1372
- if (!this.usbDevice.opened) yield this.usbDevice.openAndConfigure();
1373
- yield command.write(this.usbDevice);
1372
+ if (!this.device.opened) yield this.device.openAndConfigure();
1373
+ yield command.writeTo(this.device);
1374
1374
  });
1375
1375
  }
1376
1376
  /**
@@ -1538,8 +1538,214 @@ var UsbDevice = class {
1538
1538
  }
1539
1539
  };
1540
1540
 
1541
+ // src/helpers/NetworkDevice.ts
1542
+ var unsupportedNetworkError = "network-unsupported";
1543
+ var stringHelper2 = new StringUtils();
1544
+ var getNet = () => {
1545
+ if (typeof window !== "undefined") {
1546
+ throw unsupportedNetworkError;
1547
+ }
1548
+ return eval("require")("net");
1549
+ };
1550
+ var NetworkDevice = class {
1551
+ /**
1552
+ * Create a TCP-based device.
1553
+ *
1554
+ * This is intended for raw printing ports (typically 9100). It is Node-only.
1555
+ *
1556
+ * @param host Hostname or IP
1557
+ * @param port TCP port (defaults to 9100)
1558
+ * @param connectTimeoutMs Connection timeout
1559
+ * @param readTimeoutMs Read timeout used by `readData`/`readString`
1560
+ */
1561
+ constructor(host, port = 9100, connectTimeoutMs = 2e3, readTimeoutMs = 500) {
1562
+ this.host = host;
1563
+ this.port = port;
1564
+ this.connectTimeoutMs = connectTimeoutMs;
1565
+ this.readTimeoutMs = readTimeoutMs;
1566
+ }
1567
+ get opened() {
1568
+ return !!this.socket;
1569
+ }
1570
+ openAndConfigure() {
1571
+ return __async(this, null, function* () {
1572
+ if (this.socket) return;
1573
+ const net = getNet();
1574
+ yield new Promise((resolve, reject) => {
1575
+ let settled = false;
1576
+ const timeout = setTimeout(() => {
1577
+ var _a;
1578
+ if (settled) return;
1579
+ settled = true;
1580
+ try {
1581
+ (_a = this.socket) == null ? void 0 : _a.destroy();
1582
+ } catch (_e) {
1583
+ }
1584
+ this.socket = void 0;
1585
+ reject(new Error("network-connect-timeout"));
1586
+ }, this.connectTimeoutMs);
1587
+ try {
1588
+ const socket = net.createConnection({ host: this.host, port: this.port }, () => {
1589
+ if (settled) return;
1590
+ settled = true;
1591
+ clearTimeout(timeout);
1592
+ resolve();
1593
+ });
1594
+ socket.once("error", (err) => {
1595
+ if (settled) return;
1596
+ settled = true;
1597
+ clearTimeout(timeout);
1598
+ this.socket = void 0;
1599
+ reject(err);
1600
+ });
1601
+ this.socket = socket;
1602
+ } catch (e) {
1603
+ if (settled) return;
1604
+ settled = true;
1605
+ clearTimeout(timeout);
1606
+ this.socket = void 0;
1607
+ reject(e);
1608
+ }
1609
+ });
1610
+ });
1611
+ }
1612
+ close() {
1613
+ return __async(this, null, function* () {
1614
+ if (!this.socket) return;
1615
+ const socket = this.socket;
1616
+ this.socket = void 0;
1617
+ try {
1618
+ socket.end();
1619
+ } catch (_e) {
1620
+ try {
1621
+ socket.destroy();
1622
+ } catch (_e2) {
1623
+ }
1624
+ }
1625
+ });
1626
+ }
1627
+ writeData(data) {
1628
+ return __async(this, null, function* () {
1629
+ if (!this.socket) {
1630
+ throw new Error("network-not-open");
1631
+ }
1632
+ yield new Promise((resolve, reject) => {
1633
+ try {
1634
+ this.socket.write(data, (err) => {
1635
+ if (err) reject(err);
1636
+ else resolve();
1637
+ });
1638
+ } catch (e) {
1639
+ reject(e);
1640
+ }
1641
+ });
1642
+ });
1643
+ }
1644
+ writeString(text) {
1645
+ return __async(this, null, function* () {
1646
+ const bytes = stringHelper2.toUTF8Array(text);
1647
+ yield this.writeData(bytes);
1648
+ });
1649
+ }
1650
+ readData(length) {
1651
+ return __async(this, null, function* () {
1652
+ if (!this.socket) {
1653
+ throw new Error("network-not-open");
1654
+ }
1655
+ const socket = this.socket;
1656
+ const buffer2 = yield new Promise((resolve, reject) => {
1657
+ let settled = false;
1658
+ const onData = (data) => {
1659
+ if (settled) return;
1660
+ settled = true;
1661
+ cleanup();
1662
+ resolve(data);
1663
+ };
1664
+ const onError = (err) => {
1665
+ if (settled) return;
1666
+ settled = true;
1667
+ cleanup();
1668
+ reject(err);
1669
+ };
1670
+ const cleanup = () => {
1671
+ clearTimeout(timeout);
1672
+ socket.removeListener("data", onData);
1673
+ socket.removeListener("error", onError);
1674
+ };
1675
+ const timeout = setTimeout(() => {
1676
+ if (settled) return;
1677
+ settled = true;
1678
+ cleanup();
1679
+ resolve(void 0);
1680
+ }, this.readTimeoutMs);
1681
+ socket.once("data", onData);
1682
+ socket.once("error", onError);
1683
+ });
1684
+ if (!buffer2) return void 0;
1685
+ if (buffer2.byteLength > length) return void 0;
1686
+ return new DataView(buffer2.buffer.slice(buffer2.byteOffset, buffer2.byteOffset + buffer2.byteLength));
1687
+ });
1688
+ }
1689
+ readString(length) {
1690
+ return __async(this, null, function* () {
1691
+ const bytes = yield this.readData(length);
1692
+ if (bytes) return stringHelper2.toString(bytes);
1693
+ return void 0;
1694
+ });
1695
+ }
1696
+ };
1697
+
1698
+ // src/helpers/BonjourUtils.ts
1699
+ var unsupportedBonjourError = "bonjour-unsupported";
1700
+ var getBonjour = () => {
1701
+ var _a;
1702
+ if (typeof window !== "undefined") {
1703
+ throw unsupportedBonjourError;
1704
+ }
1705
+ const mod = eval("require")("bonjour");
1706
+ const factory = mod && ((_a = mod.default) != null ? _a : mod);
1707
+ if (typeof factory !== "function") {
1708
+ throw new Error("bonjour-invalid-module");
1709
+ }
1710
+ return factory;
1711
+ };
1712
+ var discoverBonjourServices = (types, timeoutMs = 1500) => __async(null, null, function* () {
1713
+ if (typeof window !== "undefined") return [];
1714
+ const factory2 = getBonjour();
1715
+ const bonjour = factory2();
1716
+ try {
1717
+ const results = [];
1718
+ const seen = /* @__PURE__ */ new Set();
1719
+ const browsers = types.map((type) => {
1720
+ return bonjour.find({ type }, (service) => {
1721
+ var _a;
1722
+ const host = ((_a = service == null ? void 0 : service.referer) == null ? void 0 : _a.address) || (service == null ? void 0 : service.host);
1723
+ const port = service == null ? void 0 : service.port;
1724
+ if (!host || !port) return;
1725
+ const key = `${host}:${port}`;
1726
+ if (seen.has(key)) return;
1727
+ seen.add(key);
1728
+ results.push({ host, port, name: service == null ? void 0 : service.name, type });
1729
+ });
1730
+ });
1731
+ yield new Promise((resolve) => setTimeout(resolve, timeoutMs));
1732
+ for (const browser of browsers) {
1733
+ try {
1734
+ browser.stop();
1735
+ } catch (_e) {
1736
+ }
1737
+ }
1738
+ return results;
1739
+ } finally {
1740
+ try {
1741
+ bonjour.destroy();
1742
+ } catch (_e) {
1743
+ }
1744
+ }
1745
+ });
1746
+
1541
1747
  // src/printers/TSPLPrinter.ts
1542
- var TSPLPrinter = class extends Printer {
1748
+ var TSPLPrinter = class _TSPLPrinter extends Printer {
1543
1749
  get language() {
1544
1750
  return "tspl";
1545
1751
  }
@@ -1553,12 +1759,152 @@ var TSPLPrinter = class extends Printer {
1553
1759
  return __async(this, null, function* () {
1554
1760
  if (!device.opened) yield device.openAndConfigure();
1555
1761
  const testCommand = new TSPLRawCommand("~!I");
1556
- yield testCommand.write(device);
1762
+ yield testCommand.writeTo(device);
1557
1763
  const response = yield device.readString(64);
1558
1764
  yield device.close();
1559
1765
  return !!response;
1560
1766
  });
1561
1767
  }
1768
+ /**
1769
+ * Discover TSPL-capable printers on the local network.
1770
+ *
1771
+ * Strategy:
1772
+ * - Use Bonjour/mDNS to discover "printer-ish" services to obtain a set of candidate hosts.
1773
+ * - For each unique host, probe TCP/9100 by sending the TSPL identify command (~!I).
1774
+ * - Only return devices that respond to the TSPL probe.
1775
+ * - If Bonjour yields no candidates (e.g. mDNS is blocked), fall back to a conservative
1776
+ * subnet scan on local private /24 networks (still verified by the same TSPL probe).
1777
+ */
1778
+ static discoverDevices() {
1779
+ return __async(this, null, function* () {
1780
+ if (typeof window !== "undefined") return [];
1781
+ const services = yield discoverBonjourServices([
1782
+ "pdl-datastream",
1783
+ "printer",
1784
+ "ipp",
1785
+ "ipps"
1786
+ ]);
1787
+ let uniqueHosts = Array.from(new Set(services.map((s) => s.host).filter(Boolean)));
1788
+ if (uniqueHosts.length === 0) {
1789
+ uniqueHosts = yield _TSPLPrinter.discoverHostsBySubnetScan();
1790
+ }
1791
+ const candidates = uniqueHosts.map((host) => ({ host, port: 9100 }));
1792
+ const concurrency = 5;
1793
+ const verified = [];
1794
+ for (let i = 0; i < candidates.length; i += concurrency) {
1795
+ const batch = candidates.slice(i, i + concurrency);
1796
+ const results = yield Promise.all(batch.map((c) => __async(null, null, function* () {
1797
+ const device = new NetworkDevice(c.host, c.port, 4e3, 1e3);
1798
+ try {
1799
+ const ok = yield _TSPLPrinter.try(device);
1800
+ return ok ? device : void 0;
1801
+ } catch (_e) {
1802
+ try {
1803
+ yield device.close();
1804
+ } catch (_e2) {
1805
+ }
1806
+ return void 0;
1807
+ }
1808
+ })));
1809
+ verified.push(...results.filter(Boolean));
1810
+ }
1811
+ return verified;
1812
+ });
1813
+ }
1814
+ /**
1815
+ * Fallback discovery mechanism used when Bonjour/mDNS returns no printer candidates.
1816
+ *
1817
+ * It derives local private IPv4 /24 prefixes from the current machine's network interfaces
1818
+ * and probes TCP/9100 using the TSPL identify command.
1819
+ *
1820
+ * The scan is intentionally conservative:
1821
+ * - Limited number of prefixes
1822
+ * - Concurrency limits
1823
+ * - Total time cap
1824
+ * - Early-exit when at least one printer is found
1825
+ */
1826
+ static discoverHostsBySubnetScan() {
1827
+ return __async(this, null, function* () {
1828
+ var _a, _b, _c;
1829
+ if (typeof window !== "undefined") return [];
1830
+ const req = _TSPLPrinter.getNodeRequire();
1831
+ if (!req) return [];
1832
+ const os = req("os");
1833
+ const networkInterfaces = (_b = (_a = os.networkInterfaces) == null ? void 0 : _a.call(os)) != null ? _b : {};
1834
+ const privatePrefixes = /* @__PURE__ */ new Set();
1835
+ const isPrivateIpv4 = (ip) => {
1836
+ if (ip.startsWith("10.")) return true;
1837
+ if (ip.startsWith("192.168.")) return true;
1838
+ const m = ip.match(/^172\.(\d+)\./);
1839
+ if (m) {
1840
+ const n = Number(m[1]);
1841
+ return n >= 16 && n <= 31;
1842
+ }
1843
+ return false;
1844
+ };
1845
+ for (const key of Object.keys(networkInterfaces)) {
1846
+ const infos = (_c = networkInterfaces[key]) != null ? _c : [];
1847
+ for (const info of infos) {
1848
+ const family = info == null ? void 0 : info.family;
1849
+ const address = info == null ? void 0 : info.address;
1850
+ const internal = info == null ? void 0 : info.internal;
1851
+ if (internal) continue;
1852
+ if (family !== "IPv4") continue;
1853
+ if (typeof address !== "string") continue;
1854
+ if (!isPrivateIpv4(address)) continue;
1855
+ const parts = address.split(".");
1856
+ if (parts.length !== 4) continue;
1857
+ privatePrefixes.add(`${parts[0]}.${parts[1]}.${parts[2]}`);
1858
+ }
1859
+ }
1860
+ const prefixes = Array.from(privatePrefixes).slice(0, 2);
1861
+ if (prefixes.length === 0) return [];
1862
+ const hosts = [];
1863
+ for (const prefix of prefixes) {
1864
+ for (let i = 1; i <= 254; i++) {
1865
+ hosts.push(`${prefix}.${i}`);
1866
+ }
1867
+ }
1868
+ const concurrency = 30;
1869
+ const verifiedHosts = [];
1870
+ const startedAt = Date.now();
1871
+ const maxDurationMs = 15e3;
1872
+ for (let i = 0; i < hosts.length; i += concurrency) {
1873
+ if (Date.now() - startedAt > maxDurationMs) break;
1874
+ if (verifiedHosts.length > 0) break;
1875
+ const batch = hosts.slice(i, i + concurrency);
1876
+ const results = yield Promise.all(batch.map((host) => __async(null, null, function* () {
1877
+ const device = new NetworkDevice(host, 9100, 800, 800);
1878
+ try {
1879
+ const ok = yield _TSPLPrinter.try(device);
1880
+ return ok ? host : void 0;
1881
+ } catch (_e) {
1882
+ try {
1883
+ yield device.close();
1884
+ } catch (_e2) {
1885
+ }
1886
+ return void 0;
1887
+ }
1888
+ })));
1889
+ verifiedHosts.push(...results.filter(Boolean));
1890
+ }
1891
+ return verifiedHosts;
1892
+ });
1893
+ }
1894
+ /**
1895
+ * Returns a Node-style `require` function.
1896
+ *
1897
+ * This is used to keep runtime dependencies Node-only while still allowing the library to
1898
+ * be imported/bundled in browser contexts.
1899
+ *
1900
+ * Tests may inject a custom require implementation via `globalThis.__label_printer_require`.
1901
+ */
1902
+ static getNodeRequire() {
1903
+ if (typeof window !== "undefined") return void 0;
1904
+ const override = globalThis.__label_printer_require;
1905
+ if (typeof override === "function") return override;
1906
+ return eval("require");
1907
+ }
1562
1908
  };
1563
1909
 
1564
1910
  // src/printers/PrinterService.ts
@@ -1583,12 +1929,40 @@ var PrinterService = class _PrinterService {
1583
1929
  return void 0;
1584
1930
  });
1585
1931
  }
1932
+ /**
1933
+ * Discover devices using printer-specific discovery hooks.
1934
+ *
1935
+ * Each printer class may optionally implement `static discoverDevices(): Promise<Device[]>`
1936
+ * to find candidates over non-USB transports.
1937
+ *
1938
+ * Candidates returned here are still verified by `printerForDevice` via the printer
1939
+ * class' `try(device)` method.
1940
+ */
1941
+ static discoverDevices() {
1942
+ return __async(this, null, function* () {
1943
+ const classes = [TSPLPrinter];
1944
+ const discoveryResults = yield Promise.all(classes.map((cls) => __async(null, null, function* () {
1945
+ const discoverer = cls.discoverDevices;
1946
+ if (typeof discoverer === "function") {
1947
+ try {
1948
+ return yield discoverer.call(cls);
1949
+ } catch (_e) {
1950
+ return [];
1951
+ }
1952
+ }
1953
+ return [];
1954
+ })));
1955
+ return discoveryResults.flat();
1956
+ });
1957
+ }
1586
1958
  /**
1587
1959
  * @returns List of available printers
1588
1960
  */
1589
1961
  static getPrinters() {
1590
1962
  return __async(this, null, function* () {
1591
- const devices = yield getDevices();
1963
+ const usbDevices = yield getDevices();
1964
+ const discoveredDevices = yield _PrinterService.discoverDevices();
1965
+ const devices = [...usbDevices, ...discoveredDevices];
1592
1966
  const optionalPrinters = yield Promise.all(devices.map(_PrinterService.printerForDevice));
1593
1967
  return optionalPrinters.filter((printer) => !!printer);
1594
1968
  });