label-printer 0.5.4 → 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
@@ -22,9 +22,9 @@ var __spreadValues = (a, b) => {
22
22
  return a;
23
23
  };
24
24
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
- var __export = (target, all) => {
25
+ var __export = (target2, all) => {
26
26
  for (var name in all)
27
- __defProp(target, name, { get: all[name], enumerable: true });
27
+ __defProp(target2, name, { get: all[name], enumerable: true });
28
28
  };
29
29
  var __copyProps = (to, from, except, desc) => {
30
30
  if (from && typeof from === "object" || typeof from === "function") {
@@ -34,15 +34,15 @@ var __copyProps = (to, from, except, desc) => {
34
34
  }
35
35
  return to;
36
36
  };
37
- var __toESM = (mod, isNodeMode, target) => (target = 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(target, "default", { value: mod, enumerable: true }) : target,
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
  }
@@ -424,14 +424,15 @@ var ImageProcessor = class {
424
424
  /**
425
425
  * Get pixel information about an image
426
426
  * @param image Image to process (local file path, remote URL, data URL, or Blob)
427
+ * @param target Optional target raster size. Useful for vector inputs (e.g. SVG) to rasterize at the final size.
427
428
  * @returns Promise with image data including width, height, pixel data, and bits per pixel
428
429
  */
429
- static getImageData(image2) {
430
+ static getImageData(image2, target2) {
430
431
  return __async(this, null, function* () {
431
432
  if (typeof window !== "undefined") {
432
- return this.getImageDataBrowser(image2);
433
+ return this.getImageDataBrowser(image2, target2);
433
434
  } else {
434
- return this.getImageDataNode(image2);
435
+ return this.getImageDataNode(image2, target2);
435
436
  }
436
437
  });
437
438
  }
@@ -439,74 +440,76 @@ var ImageProcessor = class {
439
440
  /**
440
441
  * Get pixel information about an image in browser environment
441
442
  * @param image Image to process
443
+ * @param target Optional target raster size.
442
444
  * @returns Promise with image data
443
445
  */
444
- static getImageDataBrowser(image2) {
446
+ static getImageDataBrowser(image2, target2) {
445
447
  return __async(this, null, function* () {
446
- return new Promise((resolve, reject) => {
447
- const img = new Image();
448
- img.crossOrigin = "anonymous";
449
- img.onload = () => {
450
- try {
451
- const canvas = document.createElement("canvas");
452
- const ctx = canvas.getContext("2d");
453
- if (!ctx) {
454
- reject(new Error("Could not get canvas context"));
455
- return;
456
- }
457
- canvas.width = img.width;
458
- canvas.height = img.height;
459
- ctx.drawImage(img, 0, 0);
460
- const imageData = ctx.getImageData(0, 0, img.width, img.height);
461
- resolve({
462
- data: new Uint8Array(imageData.data),
463
- width: img.width,
464
- height: img.height,
465
- bitsPerPixel: 4
466
- // RGBA
467
- });
468
- } catch (error) {
469
- reject(error);
470
- }
471
- };
472
- img.onerror = () => reject(new Error("Failed to load image"));
473
- if (typeof image2 === "string") {
474
- img.src = image2;
475
- } else {
476
- const url2 = URL.createObjectURL(image2);
477
- img.onload = () => {
478
- URL.revokeObjectURL(url2);
479
- resolve({
480
- data: new Uint8Array(0),
481
- // Will be set by the actual onload
482
- width: 0,
483
- height: 0,
484
- bitsPerPixel: 4
485
- });
486
- };
487
- img.src = url2;
448
+ var _a, _b;
449
+ const loadImage = (src2) => {
450
+ return new Promise((resolve, reject) => {
451
+ const img = new Image();
452
+ img.crossOrigin = "anonymous";
453
+ img.onload = () => resolve(img);
454
+ img.onerror = () => reject(new Error("Failed to load image"));
455
+ img.src = src2;
456
+ });
457
+ };
458
+ let src;
459
+ let revokeUrl;
460
+ if (typeof image2 === "string") {
461
+ src = this.normalizePotentialSVGSource(image2);
462
+ } else {
463
+ revokeUrl = URL.createObjectURL(image2);
464
+ src = revokeUrl;
465
+ }
466
+ try {
467
+ const img = yield loadImage(src);
468
+ const canvas = document.createElement("canvas");
469
+ const ctx = canvas.getContext("2d");
470
+ if (!ctx) {
471
+ throw new Error("Could not get canvas context");
488
472
  }
489
- });
473
+ const width = (_a = target2 == null ? void 0 : target2.width) != null ? _a : img.width;
474
+ const height = (_b = target2 == null ? void 0 : target2.height) != null ? _b : img.height;
475
+ canvas.width = width;
476
+ canvas.height = height;
477
+ ctx.drawImage(img, 0, 0, width, height);
478
+ const imageData2 = ctx.getImageData(0, 0, width, height);
479
+ return {
480
+ data: new Uint8Array(imageData2.data),
481
+ width,
482
+ height,
483
+ bitsPerPixel: 4
484
+ };
485
+ } finally {
486
+ if (revokeUrl) URL.revokeObjectURL(revokeUrl);
487
+ }
490
488
  });
491
489
  }
492
490
  /******** NODEJS ********/
493
491
  /**
494
492
  * Get pixel information about an image in Node.js environment
495
493
  * @param image Image to process
494
+ * @param target Optional target raster size.
496
495
  * @returns Promise with image data
497
496
  */
498
- static getImageDataNode(image2) {
497
+ static getImageDataNode(image2, _target) {
499
498
  return __async(this, null, function* () {
500
499
  console.log("Processing image in Node.js environment");
501
500
  if (image2 instanceof Blob) {
502
501
  throw new Error("Blob input not supported in Node.js environment. Use file path or data URL instead.");
503
502
  }
503
+ const trimmed = image2.trim();
504
+ if (trimmed.startsWith("<svg")) {
505
+ return this.rasterizeSVGNode(trimmed, _target);
506
+ }
504
507
  if (image2.startsWith("data:")) {
505
- return this.getImageFromData(image2);
508
+ return this.getImageFromData(image2, _target);
506
509
  } else if (image2.startsWith("http://") || image2.startsWith("https://")) {
507
- return this.getImageFromUrl(image2);
510
+ return this.getImageFromUrl(image2, _target);
508
511
  } else {
509
- return this.getImageFromFile(image2);
512
+ return this.getImageFromFile(image2, _target);
510
513
  }
511
514
  });
512
515
  }
@@ -515,7 +518,7 @@ var ImageProcessor = class {
515
518
  * @param dataURL Data URL string
516
519
  * @returns Promise with image data
517
520
  */
518
- static getImageFromData(dataURL) {
521
+ static getImageFromData(dataURL, target2) {
519
522
  return __async(this, null, function* () {
520
523
  var _a;
521
524
  const [header, data] = dataURL.split(",");
@@ -523,8 +526,13 @@ var ImageProcessor = class {
523
526
  if (!(mimeType == null ? void 0 : mimeType.startsWith("image/"))) {
524
527
  throw new Error("Invalid image data URL");
525
528
  }
526
- const buffer2 = Buffer.from(data, "base64");
527
529
  const extension = mimeType.split("/")[1].toLowerCase();
530
+ if (extension === "svg+xml" || mimeType === "image/svg+xml") {
531
+ const isBase64 = header.includes(";base64");
532
+ const svgText = isBase64 ? Buffer.from(data, "base64").toString("utf8") : decodeURIComponent(data);
533
+ return this.rasterizeSVGNode(svgText, target2);
534
+ }
535
+ const buffer2 = Buffer.from(data, "base64");
528
536
  return this.parse(buffer2, extension);
529
537
  });
530
538
  }
@@ -533,7 +541,7 @@ var ImageProcessor = class {
533
541
  * @param image
534
542
  * @returns
535
543
  */
536
- static getImageFromFile(image) {
544
+ static getImageFromFile(image, target) {
537
545
  return __async(this, null, function* () {
538
546
  const fs = yield eval("require")("fs");
539
547
  const path = yield eval("require")("path");
@@ -542,6 +550,10 @@ var ImageProcessor = class {
542
550
  }
543
551
  const buffer = fs.readFileSync(image);
544
552
  const ext = path.extname(image).toLowerCase();
553
+ if (ext === ".svg") {
554
+ const svgText = buffer.toString("utf8");
555
+ return this.rasterizeSVGNode(svgText, target);
556
+ }
545
557
  return this.parse(buffer, ext);
546
558
  });
547
559
  }
@@ -550,16 +562,16 @@ var ImageProcessor = class {
550
562
  * @param url Remote image URL
551
563
  * @returns Promise with image data
552
564
  */
553
- static getImageFromUrl(url2) {
565
+ static getImageFromUrl(url2, target2) {
554
566
  return __async(this, null, function* () {
555
567
  let fetch;
556
568
  try {
557
569
  fetch = globalThis.fetch;
558
570
  } catch (e) {
559
- return this.fetchWithHttps(url2);
571
+ return this.fetchWithHttps(url2, target2);
560
572
  }
561
573
  if (!fetch) {
562
- return this.fetchWithHttps(url2);
574
+ return this.fetchWithHttps(url2, target2);
563
575
  }
564
576
  const response = yield fetch(url2);
565
577
  if (!response.ok) {
@@ -569,6 +581,10 @@ var ImageProcessor = class {
569
581
  const buffer2 = Buffer.from(arrayBuffer);
570
582
  const contentType = response.headers.get("content-type");
571
583
  const imageType = this.getImageType(contentType || "", url2);
584
+ if (imageType === "svg") {
585
+ const svgText = buffer2.toString("utf8");
586
+ return this.rasterizeSVGNode(svgText, target2);
587
+ }
572
588
  return this.parse(buffer2, imageType);
573
589
  });
574
590
  }
@@ -577,7 +593,7 @@ var ImageProcessor = class {
577
593
  * @param url Remote image URL
578
594
  * @returns Promise with image data
579
595
  */
580
- static fetchWithHttps(url) {
596
+ static fetchWithHttps(url, target) {
581
597
  return __async(this, null, function* () {
582
598
  const https = yield eval("require")("https");
583
599
  const http = yield eval("require")("http");
@@ -597,8 +613,12 @@ var ImageProcessor = class {
597
613
  const buffer2 = Buffer.concat(chunks);
598
614
  const contentType = response.headers["content-type"] || "";
599
615
  const imageType = this.getImageType(contentType || "", url);
600
- const data = this.parse(buffer2, imageType);
601
- resolve(data);
616
+ if (imageType === "svg") {
617
+ const svgText = buffer2.toString("utf8");
618
+ resolve(this.rasterizeSVGNode(svgText, target));
619
+ return;
620
+ }
621
+ resolve(this.parse(buffer2, imageType));
602
622
  } catch (error) {
603
623
  reject(error);
604
624
  }
@@ -626,6 +646,8 @@ var ImageProcessor = class {
626
646
  return "png";
627
647
  } else if (contentType.includes("jpeg") || contentType.includes("jpg")) {
628
648
  return "jpeg";
649
+ } else if (contentType.includes("svg")) {
650
+ return "svg";
629
651
  }
630
652
  }
631
653
  const urlLower = url2.toLowerCase();
@@ -633,32 +655,57 @@ var ImageProcessor = class {
633
655
  return "png";
634
656
  } else if (urlLower.includes(".jpg") || urlLower.includes(".jpeg")) {
635
657
  return "jpeg";
658
+ } else if (urlLower.includes(".svg")) {
659
+ return "svg";
636
660
  }
637
661
  return "";
638
662
  }
639
663
  /**
640
- *
641
- * @param buffer
642
- * @param extension
643
- * @returns
644
- */
664
+ * Parse image data by extension
665
+ */
645
666
  static parse(buffer2, extension) {
646
- console.log(`Parsing image with extension: ${extension}`);
647
- if (extension === "png") {
667
+ const normalizedExtension = extension.startsWith(".") ? extension.slice(1) : extension;
668
+ console.log(`Parsing image with extension: ${normalizedExtension}`);
669
+ if (normalizedExtension === "png") {
648
670
  return parsePNG(buffer2);
649
- } else if (extension === "jpeg" || extension === "jpg") {
671
+ } else if (normalizedExtension === "jpeg" || normalizedExtension === "jpg") {
650
672
  return this.parseJPEG(buffer2);
673
+ } else if (normalizedExtension === "svg") {
674
+ throw new Error("svg-not-supported-in-node");
651
675
  } else {
652
- throw new Error(`Unsupported image format: ${extension}. Supported formats: PNG, JPEG`);
676
+ throw new Error(`Unsupported image format: ${normalizedExtension}. Supported formats: PNG, JPEG`);
653
677
  }
654
678
  }
655
- /**
656
- * JPEG parser that creates a meaningful placeholder image
657
- * Note: Full JPEG decoding requires complex DCT and Huffman decoding.
658
- * This creates a gradient pattern based on the image dimensions.
659
- * @param buffer JPEG file buffer
660
- * @returns Image data
661
- */
679
+ static rasterizeSVGNode(svg, target) {
680
+ let Resvg;
681
+ try {
682
+ Resvg = eval("require")("@resvg/resvg-js").Resvg;
683
+ } catch (_e) {
684
+ throw new Error("svg-rasterizer-missing");
685
+ }
686
+ const fitTo = target ? { mode: "width", value: target.width } : void 0;
687
+ const resvg = new Resvg(svg, {
688
+ fitTo
689
+ });
690
+ const pngData = resvg.render().asPng();
691
+ const pngBuffer = Buffer.from(pngData);
692
+ const imageData = parsePNG(pngBuffer);
693
+ if (target && (imageData.width !== target.width || imageData.height !== target.height)) {
694
+ return this.resize(imageData, target.width, target.height);
695
+ }
696
+ return imageData;
697
+ }
698
+ static normalizePotentialSVGSource(source) {
699
+ const trimmed = source.trim();
700
+ const isInlineSvg = trimmed.startsWith("<svg");
701
+ const isSvgDataUrl = trimmed.startsWith("data:image/svg+xml");
702
+ if (isInlineSvg) {
703
+ const encoded = encodeURIComponent(trimmed);
704
+ return `data:image/svg+xml;charset=utf-8,${encoded}`;
705
+ }
706
+ if (isSvgDataUrl) return source;
707
+ return source;
708
+ }
662
709
  static parseJPEG(buffer2) {
663
710
  if (buffer2[0] !== 255 || buffer2[1] !== 216) {
664
711
  throw new Error("Invalid JPEG file");
@@ -725,8 +772,8 @@ var ImageProcessor = class {
725
772
  * @param imageData Original image data
726
773
  * @returns Grayscale image data
727
774
  */
728
- static toGrayscale(imageData) {
729
- const { data, width, height } = imageData;
775
+ static toGrayscale(imageData2) {
776
+ const { data, width, height } = imageData2;
730
777
  const grayscaleData = new Uint8Array(data.length);
731
778
  for (let i = 0; i < data.length; i += 4) {
732
779
  const r = data[i];
@@ -743,7 +790,7 @@ var ImageProcessor = class {
743
790
  data: grayscaleData,
744
791
  width,
745
792
  height,
746
- bitsPerPixel: imageData.bitsPerPixel
793
+ bitsPerPixel: imageData2.bitsPerPixel
747
794
  };
748
795
  }
749
796
  /**
@@ -753,8 +800,8 @@ var ImageProcessor = class {
753
800
  * @param newHeight Target height
754
801
  * @returns Resized image data
755
802
  */
756
- static resize(imageData, newWidth, newHeight) {
757
- const { data, width, height, bitsPerPixel } = imageData;
803
+ static resize(imageData2, newWidth, newHeight) {
804
+ const { data, width, height, bitsPerPixel } = imageData2;
758
805
  const resizedData = new Uint8Array(newWidth * newHeight * bitsPerPixel);
759
806
  const xRatio = width / newWidth;
760
807
  const yRatio = height / newHeight;
@@ -782,15 +829,17 @@ var ImageProcessor_default = ImageProcessor;
782
829
  // src/helpers/ImageUtils.ts
783
830
  var BLACK_PIXEL = 0;
784
831
  var WHITE_PIXEL = 1;
832
+ var DEFAULT_THRESHOLD = 200;
785
833
  var ImageUtils = class {
786
834
  /**
787
835
  * Get pixel information about an image
788
836
  * @param image Image to process
837
+ * @param target Optional target raster size. Useful for vector inputs (e.g. SVG) to rasterize at the final size.
789
838
  * @returns
790
839
  */
791
- static getPixels(image2) {
840
+ static getPixels(image2, target2) {
792
841
  return __async(this, null, function* () {
793
- return yield ImageProcessor_default.getImageData(image2);
842
+ return yield ImageProcessor_default.getImageData(image2, target2);
794
843
  });
795
844
  }
796
845
  /**
@@ -810,7 +859,10 @@ var ImageUtils = class {
810
859
  width,
811
860
  height,
812
861
  bitsPerPixel
813
- } = yield this.getPixels(image2);
862
+ } = yield this.getPixels(
863
+ image2,
864
+ destinationWidth != null && destinationHeight != null ? { width: destinationWidth, height: destinationHeight } : void 0
865
+ );
814
866
  const dim = getSizePreserveAspect(width, height, destinationWidth, destinationHeight);
815
867
  const dWidth = dim.width;
816
868
  const dHeight = dim.height;
@@ -826,16 +878,16 @@ var ImageUtils = class {
826
878
  const r = data[baseIndex];
827
879
  const g = data[baseIndex + 1];
828
880
  const b = data[baseIndex + 2];
829
- const a = data[baseIndex + 3];
830
- if (a > 128) {
831
- const avg = (r + g + b) / 3;
832
- if (avg > 128) {
833
- bitmapData[destinationIndex] = WHITE_PIXEL;
834
- } else {
835
- bitmapData[destinationIndex] = BLACK_PIXEL;
836
- }
837
- } else {
881
+ const a = bitsPerPixel > 3 ? data[baseIndex + 3] : 255;
882
+ const alpha = a / 255;
883
+ const rC = r * alpha + 255 * (1 - alpha);
884
+ const gC = g * alpha + 255 * (1 - alpha);
885
+ const bC = b * alpha + 255 * (1 - alpha);
886
+ const luminance = 0.299 * rC + 0.587 * gC + 0.114 * bC;
887
+ if (luminance > DEFAULT_THRESHOLD) {
838
888
  bitmapData[destinationIndex] = WHITE_PIXEL;
889
+ } else {
890
+ bitmapData[destinationIndex] = BLACK_PIXEL;
839
891
  }
840
892
  destinationIndex += 1;
841
893
  }
@@ -916,7 +968,7 @@ var TSPLBitmapCommand = class _TSPLBitmapCommand extends TSPLVisualCommand {
916
968
  return 2;
917
969
  }
918
970
  }
919
- write(device) {
971
+ writeTo(device) {
920
972
  return __async(this, null, function* () {
921
973
  yield this.writeString(this.commandWithoutBytes, device);
922
974
  yield this.writeBytes(this.bitmap.bytes, device);
@@ -1097,7 +1149,7 @@ var TSPLDownload = class extends TSPLCommand {
1097
1149
  get commandString() {
1098
1150
  return `DOWNLOAD "${this.fileName}", ${this.data.byteLength},`;
1099
1151
  }
1100
- write(device) {
1152
+ writeTo(device) {
1101
1153
  return __async(this, null, function* () {
1102
1154
  yield this.writeString(this.commandString, device);
1103
1155
  yield this.writeBytes(this.data, device);
@@ -1281,14 +1333,14 @@ __export(printers_exports, {
1281
1333
  // src/printers/Printer.ts
1282
1334
  var Printer = class {
1283
1335
  constructor(device) {
1284
- this.usbDevice = device;
1336
+ this.device = device;
1285
1337
  }
1286
1338
  /**
1287
1339
  * Close the printer USB
1288
1340
  */
1289
1341
  close() {
1290
1342
  return __async(this, null, function* () {
1291
- yield this.usbDevice.close();
1343
+ yield this.device.close();
1292
1344
  });
1293
1345
  }
1294
1346
  /**
@@ -1317,8 +1369,8 @@ var Printer = class {
1317
1369
  */
1318
1370
  writeCommand(command) {
1319
1371
  return __async(this, null, function* () {
1320
- if (!this.usbDevice.opened) yield this.usbDevice.openAndConfigure();
1321
- yield command.write(this.usbDevice);
1372
+ if (!this.device.opened) yield this.device.openAndConfigure();
1373
+ yield command.writeTo(this.device);
1322
1374
  });
1323
1375
  }
1324
1376
  /**
@@ -1486,8 +1538,214 @@ var UsbDevice = class {
1486
1538
  }
1487
1539
  };
1488
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
+
1489
1747
  // src/printers/TSPLPrinter.ts
1490
- var TSPLPrinter = class extends Printer {
1748
+ var TSPLPrinter = class _TSPLPrinter extends Printer {
1491
1749
  get language() {
1492
1750
  return "tspl";
1493
1751
  }
@@ -1501,12 +1759,152 @@ var TSPLPrinter = class extends Printer {
1501
1759
  return __async(this, null, function* () {
1502
1760
  if (!device.opened) yield device.openAndConfigure();
1503
1761
  const testCommand = new TSPLRawCommand("~!I");
1504
- yield testCommand.write(device);
1762
+ yield testCommand.writeTo(device);
1505
1763
  const response = yield device.readString(64);
1506
1764
  yield device.close();
1507
1765
  return !!response;
1508
1766
  });
1509
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
+ }
1510
1908
  };
1511
1909
 
1512
1910
  // src/printers/PrinterService.ts
@@ -1531,12 +1929,40 @@ var PrinterService = class _PrinterService {
1531
1929
  return void 0;
1532
1930
  });
1533
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
+ }
1534
1958
  /**
1535
1959
  * @returns List of available printers
1536
1960
  */
1537
1961
  static getPrinters() {
1538
1962
  return __async(this, null, function* () {
1539
- const devices = yield getDevices();
1963
+ const usbDevices = yield getDevices();
1964
+ const discoveredDevices = yield _PrinterService.discoverDevices();
1965
+ const devices = [...usbDevices, ...discoveredDevices];
1540
1966
  const optionalPrinters = yield Promise.all(devices.map(_PrinterService.printerForDevice));
1541
1967
  return optionalPrinters.filter((printer) => !!printer);
1542
1968
  });