npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

print-setu

v1.0.3

Published

WebSocket-based printer SDK for browser-to-printer communication without USB drivers

Readme

Print Setu

A lightweight, browser-compatible SDK for connecting to USB printers via WebSocket without requiring USB driver installation. Perfect for POS systems, thermal printers, and offline printing applications.

Links

Website: https://printsetu.dev
Demo: https://demo.printsetu.dev
Windows Build: https://drive.google.com/file/d/15b-45-tsKLBB1MFhREr8aoA0e7ylhFSK/view?usp=sharing

Features

No USB Drivers Required - Print directly from browser to USB printers
Offline Support - Works completely offline via local WebSocket
Auto-Reconnection - Handles disconnections gracefully
TypeScript Support - Full type definitions included
Framework Agnostic - Works with React, Vue, Angular, or vanilla JS
Event-Driven - Subscribe to printer events and status updates

Installation

npm install print-setu

Or with yarn:

yarn add print-setu

Quick Start

import { PrinterService } from 'print-setu';

// Initialize the service
const printer = new PrinterService({
  url: 'ws://127.0.0.1:8899',
});

// Connect to the service
await printer.connect();

// Scan for available printers
await printer.scanPrinters();

// Listen for printer discovery results
printer.on('USB_PRINTERS_RESULT', (data) => {
  if (data.payload?.success) {
    console.log('Available printers:', data.payload.data);
  }
});

// Connect to a specific printer
await printer.connectPrinter('printer-id');

// Listen for connection result
printer.on('PRINTER_CONNECTED', (data) => {
  if (data.payload?.success) {
    console.log('Connected to:', data.payload.data);
  }
});

// Print data (base64 encoded)
await printer.print('printer-id', base64Data);

// Listen for print result
printer.on('PRINT_RESULT', (data) => {
  if (data.payload?.success) {
    console.log('Print successful');
  }
});

API Reference

Initialization

import { PrinterService } from 'print-setu';

const printer = new PrinterService({
  url: string;                 // Required: WebSocket URL (e.g., 'ws://127.0.0.1:8899')
  maxReconnectAttempts?: number; // Optional: Max reconnection attempts (default: 5)
  reconnectDelay?: number;     // Optional: Delay between reconnects in ms (default: 2000)
  enableLogging?: boolean;     // Optional: Enable console logging (default: true)
});

Methods

connect(): Promise<void>

Establishes WebSocket connection to the printer service.

await printer.connect();

scanPrinters(): Promise<void>

Scans for available USB printers. Listen to USB_PRINTERS_RESULT event for results.

ping(): Promise<void>

Check if electron app is running or not.

await printer.scanPrinters();

connectPrinter(printerId: string): Promise<void>

Connects to a specific printer by its ID. Listen to PRINTER_CONNECTED event for the result.

await printer.connectPrinter('printer-id');

disconnectPrinter(printerId: string): Promise<void>

Disconnects from a specific printer. Listen to PRINTER_DISCONNECTED event for the result.

await printer.disconnectPrinter('printer-id');

print(printerId: string, base64Data: string): Promise<void>

Sends print data to the specified printer. Listen to PRINT_RESULT event for the result.

const base64Data = btoa('Hello, Printer!');
await printer.print('printer-id', base64Data);

on(eventType: string, handler: (data: any) => void): () => void

Subscribes to events. Returns an unsubscribe function.

const unsubscribe = printer.on('USB_PRINTERS_RESULT', (data) => {
  console.log(data);
});

// Later, to unsubscribe:
unsubscribe();

Events

The service emits the following events with consistent payload structure:

{
  payload?: {
    success: boolean;
    data?: any;
    error?: string;
  };
  error?: string;
}

Available Events:

USB_PRINTERS_RESULT

Emitted when printer scan completes.

printer.on('USB_PRINTERS_RESULT', (data) => {
  if (data.payload?.success) {
    const printers = data.payload.data; // Array of printer objects
  }
});

PRINTER_DISCOVERED

Emitted when an individual printer is discovered during scanning.

printer.on('PRINTER_DISCOVERED', (data) => {
  const printer = data.data; // Single printer object
});

PRINTER_CONNECTED

Emitted when a printer connection attempt completes.

printer.on('PRINTER_CONNECTED', (data) => {
  if (data.payload?.success) {
    console.log('Connected:', data.payload.data);
  } else {
    console.error('Connection failed:', data.payload?.error);
  }
});

PRINTER_DISCONNECTED

Emitted when a printer disconnection completes.

printer.on('PRINTER_DISCONNECTED', (data) => {
  if (data.payload?.success) {
    console.log('Disconnected successfully');
  }
});

PRINT_RESULT

Emitted when a print job completes.

printer.on('PRINT_RESULT', (data) => {
  if (data.payload?.success) {
    console.log('Print successful');
  } else {
    console.error('Print failed:', data.payload?.error);
  }
});

ERROR

Emitted when a general error occurs.

printer.on('ERROR', (data) => {
  console.error('Error:', data.error);
});

Examples

React Hook (Recommended)

Create a custom hook for easy integration:

// usePrinter.js
import { useState, useEffect, useCallback } from "react";
import { PrinterService } from 'print-setu';

const printer = new PrinterService({ 
  url: 'ws://127.0.0.1:8899', 
});

export function usePrinter() {
  const [printers, setPrinters] = useState([]);
  const [isScanning, setIsScanning] = useState(false);
  const [isConnected, setIsConnected] = useState(false);
  const [error, setError] = useState(null);
  const [connectedPrinter, setConnectedPrinter] = useState(null);
  const [isConnecting, setIsConnecting] = useState(false);

  useEffect(() => {
    // Connect to WebSocket
    printer
      .connect()
      .then(() => setIsConnected(true))
      .catch((err) => {
        setError(err.message);
        setIsConnected(false);
      });

    // Subscribe to events
    const unsubscribePrintersResult = printer.on("USB_PRINTERS_RESULT", (data) => {
      if (data.payload?.success) {
        setPrinters(data.payload.data || []);
        setIsScanning(false);
      } else {
        setError(data.payload?.error || "Failed to get printers");
        setIsScanning(false);
      }
    });

    const unsubscribePrinterDiscovered = printer.on("PRINTER_DISCOVERED", (data) => {
      if (data.data) {
        setPrinters((prev) => {
          const exists = prev.find((p) => p.id === data.data.id);
          return exists ? prev : [...prev, data.data];
        });
      }
    });

    const unsubscribeConnected = printer.on("PRINTER_CONNECTED", (data) => {
      setIsConnecting(false);
      if (data.payload?.success) {
        setConnectedPrinter(data.payload.data);
        setError(null);
      } else {
        setError(data.payload?.error || "Failed to connect");
      }
    });

    const unsubscribePrintResult = printer.on("PRINT_RESULT", (data) => {
      if (!data.payload?.success) {
        setError(data.payload?.error || "Failed to print");
      } else {
        setError(null);
      }
    });

    const unsubscribeError = printer.on("ERROR", (data) => {
      setError(data.error || "An error occurred");
    });

    // Cleanup subscriptions
    return () => {
      unsubscribePrintersResult();
      unsubscribePrinterDiscovered();
      unsubscribeConnected();
      unsubscribePrintResult();
      unsubscribeError();
    };
  }, []);

  const scanPrinters = useCallback(async () => {
    try {
      setIsScanning(true);
      setError(null);

      return new Promise((resolve, reject) => {
        const unsubscribe = printer.on("USB_PRINTERS_RESULT", (data) => {
          unsubscribe();
          setIsScanning(false);
          
          if (data.payload?.success) {
            setPrinters(data.payload.data || []);
            resolve(data.payload.data);
          } else {
            setError(data.payload?.error || "Failed to scan printers");
            reject(data);
          }
        });

        printer.scanPrinters().catch((err) => {
          setIsScanning(false);
          setError(err.message);
          reject(err);
        });
      });
    } catch (err) {
      setError(err.message);
      setIsScanning(false);
      throw err;
    }
  }, []);

  const connectPrinter = useCallback(async (printerId) => {
    setIsConnecting(true);
    try {
      setError(null);

      return new Promise((resolve, reject) => {
        const unsubscribe = printer.on("PRINTER_CONNECTED", (data) => {
          unsubscribe();
          setIsConnecting(false);
          
          if (data.payload?.success) {
            setConnectedPrinter(data.payload.data);
            resolve(data.payload.data);
          } else {
            setError(data.payload?.error || "Failed to connect");
            reject(data);
          }
        });

        printer.connectPrinter(printerId).catch((err) => {
          setIsConnecting(false);
          setError(err.message);
          reject(err);
        });
      });
    } catch (err) {
      setIsConnecting(false);
      setError(err.message);
      throw err;
    }
  }, []);

  const disconnectPrinter = useCallback(async () => {
    setConnectedPrinter(null);
    setPrinters([]);
    setError(null);
  }, []);

  const print = useCallback(async (printerId, base64Data) => {
    try {
      setError(null);

      return new Promise((resolve, reject) => {
        const unsubscribe = printer.on("PRINT_RESULT", (data) => {
          unsubscribe();
          
          if (data.payload?.success) {
            resolve(data);
          } else {
            setError(data.payload?.error || "Failed to print");
            reject(data);
          }
        });

        printer.print(printerId, base64Data).catch((err) => {
          setError(err.message);
          reject(err);
        });
      });
    } catch (err) {
      setError(err.message);
      throw err;
    }
  }, []);

  return {
    printers,
    isScanning,
    isConnected,
    isConnecting,
    error,
    connectedPrinter,
    scanPrinters,
    connectPrinter,
    disconnectPrinter,
    print
  };
}

Using the Hook in a Component

import { usePrinter } from './usePrinter';

function PrinterComponent() {
  const {
    printers,
    isScanning,
    isConnected,
    isConnecting,
    error,
    connectedPrinter,
    scanPrinters,
    connectPrinter,
    print
  } = usePrinter();

  const handlePrint = async () => {
    if (connectedPrinter) {
      const printData = btoa('Hello, Printer!');
      await print(connectedPrinter.id, printData);
    }
  };

  return (
    <div>
      {error && <div className="error">{error}</div>}
      
      <button onClick={scanPrinters} disabled={isScanning || !isConnected}>
        {isScanning ? 'Scanning...' : 'Scan Printers'}
      </button>

      <ul>
        {printers.map((printer) => (
          <li key={printer.id}>
            {printer.name}
            <button 
              onClick={() => connectPrinter(printer.id)}
              disabled={isConnecting}
            >
              Connect
            </button>
          </li>
        ))}
      </ul>

      {connectedPrinter && (
        <div>
          <p>Connected to: {connectedPrinter.name}</p>
          <button onClick={handlePrint}>Print Test</button>
        </div>
      )}
    </div>
  );
}

Vue Integration

<template>
  <div>
    <div v-if="error" class="error">{{ error }}</div>
    
    <button @click="scanPrinters" :disabled="isScanning || !isConnected">
      {{ isScanning ? 'Scanning...' : 'Scan Printers' }}
    </button>

    <ul>
      <li v-for="printer in printers" :key="printer.id">
        {{ printer.name }}
        <button @click="connectToPrinter(printer.id)" :disabled="isConnecting">
          Connect
        </button>
      </li>
    </ul>

    <div v-if="connectedPrinter">
      <p>Connected to: {{ connectedPrinter.name }}</p>
      <button @click="handlePrint">Print Test</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { PrinterService } from 'print-setu';

const printers = ref([]);
const isScanning = ref(false);
const isConnected = ref(false);
const isConnecting = ref(false);
const error = ref(null);
const connectedPrinter = ref(null);

const printer = new PrinterService({
  url: 'ws://127.0.0.1:8899',
});

onMounted(async () => {
  try {
    await printer.connect();
    isConnected.value = true;
  } catch (err) {
    error.value = err.message;
  }

  printer.on('USB_PRINTERS_RESULT', (data) => {
    isScanning.value = false;
    if (data.payload?.success) {
      printers.value = data.payload.data || [];
    } else {
      error.value = data.payload?.error || 'Failed to get printers';
    }
  });

  printer.on('PRINTER_CONNECTED', (data) => {
    isConnecting.value = false;
    if (data.payload?.success) {
      connectedPrinter.value = data.payload.data;
    } else {
      error.value = data.payload?.error || 'Failed to connect';
    }
  });

  printer.on('PRINT_RESULT', (data) => {
    if (!data.payload?.success) {
      error.value = data.payload?.error || 'Failed to print';
    }
  });
});

onUnmounted(() => {
  printer.disconnect();
});

const scanPrinters = async () => {
  isScanning.value = true;
  error.value = null;
  await printer.scanPrinters();
};

const connectToPrinter = async (printerId) => {
  isConnecting.value = true;
  error.value = null;
  await printer.connectPrinter(printerId);
};

const handlePrint = async () => {
  if (connectedPrinter.value) {
    const data = btoa('Hello from Vue!');
    await printer.print(connectedPrinter.value.id, data);
  }
};
</script>

Requirements

  • A running WebSocket server (Electron app or standalone service) on ws://127.0.0.1:8899
  • USB printers connected to the machine running the WebSocket server
  • Valid API key for authentication

Browser Compatibility

  • Chrome/Edge 88+
  • Firefox 85+
  • Safari 14+
  • Opera 74+

Error Handling

All events follow a consistent payload structure. Always check data.payload?.success before accessing the data:

printer.on('EVENT_NAME', (data) => {
  if (data.payload?.success) {
    // Handle success
    console.log(data.payload.data);
  } else {
    // Handle error
    console.error(data.payload?.error || data.error);
  }
});

Support

For issues and questions:

  • GitHub Issues: https://github.com/mishrabhavesh/print-setu/issues
  • Documentation: [Coming Soon]

Contributing

Contributions are welcome!