403Webshell
Server IP : 173.209.174.21  /  Your IP : 216.73.216.89
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux wcfs-server 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64
User : nodor ( 1000)
PHP Version : 8.3.6
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/wcfs/NAOPOS/src/lib/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/wcfs/NAOPOS/src/lib/printer.ts
/**
 * ESC/POS printer utility for Epson TM series via WebSerial API.
 * Handles receipt printing and cash drawer kick.
 */

const ESC = 0x1b;
const GS = 0x1d;
const LF = 0x0a;

// ESC/POS commands
const CMD = {
  INIT: [ESC, 0x40], // Initialize printer
  BOLD_ON: [ESC, 0x45, 0x01],
  BOLD_OFF: [ESC, 0x45, 0x00],
  ALIGN_CENTER: [ESC, 0x61, 0x01],
  ALIGN_LEFT: [ESC, 0x61, 0x00],
  ALIGN_RIGHT: [ESC, 0x61, 0x02],
  DOUBLE_HEIGHT_ON: [GS, 0x21, 0x11], // Double width + height
  DOUBLE_HEIGHT_OFF: [GS, 0x21, 0x00],
  CUT_PAPER: [GS, 0x56, 0x42, 0x03], // Partial cut with feed
  DRAWER_KICK_PIN2: [ESC, 0x70, 0x00, 0x19, 0x78], // Kick drawer pin 2
  DRAWER_KICK_PIN5: [ESC, 0x70, 0x01, 0x19, 0x78], // Kick drawer pin 5
  FEED_LINES: (n: number) => [ESC, 0x64, n],
};

const encoder = new TextEncoder();

function textBytes(text: string): number[] {
  return Array.from(encoder.encode(text));
}

function padLine(left: string, right: string, width = 42): string {
  const space = width - left.length - right.length;
  return left + " ".repeat(Math.max(1, space)) + right;
}

function dashLine(width = 42): string {
  return "-".repeat(width);
}

export interface ReceiptData {
  storeName: string;
  items: {
    name: string;
    quantity: number;
    price: number;
    taxRate?: number;
    taxAmount?: number;
  }[];
  subtotal: number;
  tax: number;
  total: number;
  paymentMethod: "cash" | "credit";
  amountTendered?: number;
  changeDue?: number;
  timestamp: Date;
}

function buildReceiptBytes(data: ReceiptData): number[] {
  const bytes: number[] = [];
  const push = (...b: number[]) => bytes.push(...b);

  // Initialize
  push(...CMD.INIT);

  // Store name (centered, bold, double)
  push(...CMD.ALIGN_CENTER);
  push(...CMD.BOLD_ON);
  push(...CMD.DOUBLE_HEIGHT_ON);
  push(...textBytes(data.storeName));
  push(LF);
  push(...CMD.DOUBLE_HEIGHT_OFF);
  push(...CMD.BOLD_OFF);

  // Date/time
  push(...textBytes(data.timestamp.toLocaleDateString() + " " + data.timestamp.toLocaleTimeString()));
  push(LF);
  push(...CMD.ALIGN_LEFT);
  push(...textBytes(dashLine()));
  push(LF);

  // Items
  for (const item of data.items) {
    const lineTotal = item.price * item.quantity;
    const name = item.quantity > 1 ? `${item.name} x${item.quantity}` : item.name;
    push(...textBytes(padLine(name, `$${lineTotal.toFixed(2)}`)));
    push(LF);
    if (item.taxRate && item.taxAmount && item.taxAmount > 0) {
      push(...textBytes(padLine(`  Tax ${item.taxRate}%`, `$${item.taxAmount.toFixed(2)}`)));
      push(LF);
    }
  }

  push(...textBytes(dashLine()));
  push(LF);

  // Totals
  push(...textBytes(padLine("Subtotal", `$${data.subtotal.toFixed(2)}`)));
  push(LF);
  if (data.tax > 0) {
    push(...textBytes(padLine("Tax", `$${data.tax.toFixed(2)}`)));
    push(LF);
  }
  push(...CMD.BOLD_ON);
  push(...CMD.DOUBLE_HEIGHT_ON);
  push(...textBytes(padLine("TOTAL", `$${data.total.toFixed(2)}`)));
  push(LF);
  push(...CMD.DOUBLE_HEIGHT_OFF);
  push(...CMD.BOLD_OFF);

  push(...textBytes(dashLine()));
  push(LF);

  // Payment info
  const methodLabel = data.paymentMethod === "cash" ? "Cash" : "Credit Card";
  push(...textBytes(padLine("Payment", methodLabel)));
  push(LF);
  if (data.paymentMethod === "cash" && data.amountTendered !== undefined) {
    push(...textBytes(padLine("Tendered", `$${data.amountTendered.toFixed(2)}`)));
    push(LF);
    if (data.changeDue !== undefined && data.changeDue > 0) {
      push(...CMD.BOLD_ON);
      push(...textBytes(padLine("Change", `$${data.changeDue.toFixed(2)}`)));
      push(LF);
      push(...CMD.BOLD_OFF);
    }
  }

  // Footer
  push(LF);
  push(...CMD.ALIGN_CENTER);
  push(...textBytes("Thank you!"));
  push(LF);
  push(...CMD.FEED_LINES(4));
  push(...CMD.CUT_PAPER);

  return bytes;
}

// ---------- Serial port management ----------

let serialPort: any = null;
let writer: any = null;

export function isWebSerialSupported(): boolean {
  return "serial" in (navigator as any);
}

export async function connectPrinter(): Promise<boolean> {
  if (!isWebSerialSupported()) {
    throw new Error("WebSerial API is not supported in this browser. Please use Chrome or Edge.");
  }

  try {
    // Prompt user to select the printer's serial port
    serialPort = await (navigator as any).serial.requestPort();
    await serialPort.open({ baudRate: 9600 });
    writer = serialPort.writable!.getWriter();
    return true;
  } catch (err) {
    console.error("Failed to connect to printer:", err);
    serialPort = null;
    writer = null;
    return false;
  }
}

export async function disconnectPrinter(): Promise<void> {
  try {
    if (writer) {
      writer.releaseLock();
      writer = null;
    }
    if (serialPort) {
      await serialPort.close();
      serialPort = null;
    }
  } catch (err) {
    console.error("Error disconnecting printer:", err);
  }
}

export function isPrinterConnected(): boolean {
  return serialPort !== null && writer !== null;
}

async function sendBytes(data: Uint8Array): Promise<void> {
  if (!writer) throw new Error("Printer not connected");
  await writer.write(data);
}

export async function kickDrawer(): Promise<void> {
  await sendBytes(new Uint8Array(CMD.DRAWER_KICK_PIN2));
}

export async function printReceipt(data: ReceiptData): Promise<void> {
  const bytes = buildReceiptBytes(data);
  await sendBytes(new Uint8Array(bytes));
}

export async function printReceiptAndKickDrawer(data: ReceiptData): Promise<void> {
  // Kick drawer first, then print
  await kickDrawer();
  await printReceipt(data);
}

Youez - 2016 - github.com/yon3zu
LinuXploit