
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
WebSocket-based printer SDK for browser-to-printer communication without USB drivers
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.
Website: https://printsetu.dev
Demo: https://demo.printsetu.dev
Windows Build: https://drive.google.com/file/d/15b-45-tsKLBB1MFhREr8aoA0e7ylhFSK/view?usp=sharing
✅ 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
npm install print-setu
Or with yarn:
yarn add print-setu
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');
}
});
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)
});
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): () => voidSubscribes to events. Returns an unsubscribe function.
const unsubscribe = printer.on('USB_PRINTERS_RESULT', (data) => {
console.log(data);
});
// Later, to unsubscribe:
unsubscribe();
The service emits the following events with consistent payload structure:
{
payload?: {
success: boolean;
data?: any;
error?: string;
};
error?: string;
}
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);
});
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
};
}
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>
);
}
<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>
ws://127.0.0.1:8899All 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);
}
});
For issues and questions:
Contributions are welcome!
FAQs
WebSocket-based printer SDK for browser-to-printer communication without USB drivers
We found that print-setu demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.