import TcpSocket from "react-native-tcp-socket"; // const STX = "\x02"; // const ETX = "\x03"; type TcpResponse = { success: boolean; message: string; data: ScannerEvent; }; type ScannerEvent = { scannerId?: string; commandDescription?: string; prompt?: string; message?: string; status: "success" | "error" | "location" | "unknown" | "scan"; lines?: string[]; }; // const ERROR_MESSAGES = [ // "Invalid barcode", // "Already scanned", // "Not on stock", // "Article tolerance for consolidation not satisfied.", // ]; const ERROR_KEYWORDS = [ "invalid barcode", "invalid command", "already", "not on stock", "article tolerance", "unloaded", "delivered", "blocked", "not possible", ]; // function parseErpResponse(buffer: Buffer) { // const text = buffer // .toString("utf8") // .replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|#[0-9A-Za-z])/g, "") // .replace(/\x02/g, "") // .replace(/\x03/g, "") // .trim(); // const noHeader = text.replace(/^\d+@/, ""); // console.log(text); // if (!noHeader.includes("Scan:")) { // return { // raw: text, // type: "error", // message: noHeader.trim(), // lines: [noHeader.trim()], // }; // } // const [actionPart, scanPart = ""] = noHeader.split("Scan:"); // const action = actionPart.trim(); // const scanClean = scanPart.trim(); // const successMatch = scanClean.match(/^(.*?)\s+V$/); // if (successMatch) { // const prompt = successMatch[1].trim(); // return { // raw: text, // type: "success", // action, // prompt, // status: "V", // lines: [action, prompt, "V"], // }; // } // // // Handles: "Production lotInvalid barcode" // // const knownErrors = [ // // "Invalid barcode", // // "Invalid machine", // // "Not on stock", // // "Article tolerance for consolidation not satisfied", // // ].sort((a, b) => b.length - a.length); // // const foundError = knownErrors.find((err) => scanClean.includes(err)); // // if (foundError) { // // const prompt = scanClean.replace(foundError, "").trim(); // // return { // // raw: text, // // type: "error", // // action, // // prompt, // // message: foundError, // // lines: [action, prompt, foundError].filter(Boolean), // // }; // // } // // return { // // raw: text, // // type: "pending", // // action, // // prompt: scanClean, // // lines: [action, scanClean].filter(Boolean), // // }; // const unitMatch = scanClean.match(/^(Unit\s+\d+\/\d+)(.*)$/); // if (unitMatch) { // const prompt = unitMatch[1].trim(); // "Unit 1/4" // const remainder = unitMatch[2].trim(); // everything after // // SUCCESS // if (remainder === "V") { // return { // raw: text, // type: "success", // action, // prompt, // status: "V", // lines: [action, prompt, "V"], // }; // } // // Known ERP errors // const knownErrors = [ // "Invalid barcode", // "Invalid machine", // "Not on stock", // "Article tolerance for consolidation not satisfied", // ]; // const foundError = knownErrors.find((err) => // remainder.toLowerCase().includes(err.toLowerCase()), // ); // if (foundError) { // return { // raw: text, // type: "error", // action, // prompt, // message: foundError, // lines: [action, prompt, foundError], // }; // } // if (remainder) { // return { // raw: text, // type: "prompt", // action, // prompt, // message: remainder, // lines: [action, prompt, remainder], // }; // } // return { // raw: text, // type: "pending", // action, // prompt, // lines: [action, prompt], // }; // } // } const parseScannerText = (buffer: Buffer) => { const text = buffer.toString("utf8"); return ( text // remove cursor movement like ESC[122C, ESC[2;1H, ESC[8q .replace(/\x1B\[[0-9;]*[A-Za-z]/g, "\n") // remove other ANSI sequences like ESC#5 .replace(/\x1B#[0-9]/g, "\n") // normalize carriage returns .replace(/\r/g, "\n") // split into clean lines .split(/\n+/) // clean each line .map((line) => line.trim()) // remove blanks .filter(Boolean) ); }; const parseScannerEvent = (lines: string[]): ScannerEvent => { const scannerId = lines[0]; const messageLines = lines.slice(1); const message = messageLines.at(-1); const commandDescription = messageLines.find((x) => /^\d+\s+/.test(x)); const prompt = messageLines.find((x) => /^Scan:/i.test(x)); let status: ScannerEvent["status"] = "unknown"; const msg = message?.toLowerCase() ?? ""; if (msg === "v") status = "success"; else if (msg && ERROR_KEYWORDS.some((keyword) => msg.includes(keyword))) status = "error"; else if (msg?.includes("scan")) status = "success"; // everything else will just be a location else if (commandDescription?.includes("Relocate")) status = "location"; // TODO: split command description and use the command id next to description for sorting. return { scannerId, commandDescription, prompt, message, status, lines, }; }; /** * Sends a Zebra-style TCP message: * 98@{scanned} */ export async function sendTcpMessage( command: string, host: string, port: number, timeoutMs = 5000, ): Promise { return new Promise((resolve) => { //const responses: any = []; const client = TcpSocket.createConnection({ host, port }, () => { //console.log("Sending TCP (visible):", `${command}`); client.write(command); }); let settled = false; const done = (response: TcpResponse) => { if (settled) return; settled = true; clearTimeout(timeout); try { client.destroy(); } catch {} resolve(response); }; // const timeout = setTimeout(() => { // client.destroy(); // resolve({ // success: false, // message: "TCP timeout", // data: responses, // }); // }, timeoutMs); const timeout = setTimeout(() => { done({ success: false, message: "No response from scanner server", data: { scannerId: "999", commandDescription: "TCP Command", prompt: command, message: "Invalid command", status: "error", lines: [ "SYSTEM", "TCP Command", `Scan: ${command}`, "Invalid command", ], }, }); }, timeoutMs); client.on("data", (data: any) => { //console.log("TCP received:", text); const parsed = parseScannerText(data); //console.log("scanned:", parsed); //responses.push(parsed); const cleaned = parseScannerEvent(parsed); //console.log(responses); // clearTimeout(timeout); // resolve({ // success: true, // message: "TCP Response", // data: cleaned as any, // }); done({ success: cleaned.status !== "error", message: cleaned.status === "error" ? (cleaned.message ?? "TCP Error") : "TCP Response", data: cleaned, }); }); client.on("error", (err) => { // resolve({ // success: false, // message: err.message, // data: ["Error", "Please try again"] as any, // }); done({ success: false, message: err.message, data: { scannerId: "SYSTEM", commandDescription: "TCP Error", prompt: command, message: err.message, status: "error", lines: ["SYSTEM", "TCP Error", `Scan: ${command}`, err.message], }, }); }); client.on("close", () => { if (settled) return; // resolve({ // success: true, // message: "TCP complete", // data: ["Error", "Please try again"] as any, // }); done({ success: false, message: "TCP connection closed", data: { scannerId: "SYSTEM", commandDescription: "TCP Closed", prompt: command, message: "Connection closed before response", status: "error", lines: [ "SYSTEM", "TCP Closed", `Scan: ${command}`, "Connection closed before response", ], }, }); }); }); }