From 51e38d058b8bcf479356e5ae7460530ac3240760 Mon Sep 17 00:00:00 2001 From: u8array Date: Sun, 10 May 2026 12:48:47 +0200 Subject: [PATCH 1/2] fix(zebra-print): differentiate timeout from success on network send MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously sendViaNetwork swallowed every non-refused exception and returned void, so the dialog always painted a green 'ZPL sent successfully' for both real prints AND wrong-IP timeouts — a false positive that hid failed sends. Replace the void / throw mix with a NetworkPrintResult discriminated union (responded | no_response | refused). Raw-socket printers normally never reply with HTTP, so 'no_response' is the typical success path — but the same exception is raised when the host is unreachable, and the browser cannot tell those apart. The dialog now reports 'Sent. Verify on the printer.' for that case instead of an unverified green-success. Adds the sentNoResponse locale key across all 32 languages. --- src/components/Output/PrintToZebraDialog.tsx | 23 ++++++++----- src/lib/zebraPrint.ts | 36 +++++++++++++++++--- src/locales/ar.ts | 1 + src/locales/bg.ts | 1 + src/locales/cs.ts | 1 + src/locales/da.ts | 1 + src/locales/de.ts | 1 + src/locales/el.ts | 1 + src/locales/en.ts | 1 + src/locales/es.ts | 1 + src/locales/et.ts | 1 + src/locales/fa.ts | 1 + src/locales/fi.ts | 1 + src/locales/fr.ts | 1 + src/locales/he.ts | 1 + src/locales/hr.ts | 1 + src/locales/hu.ts | 1 + src/locales/it.ts | 1 + src/locales/ja.ts | 1 + src/locales/ko.ts | 1 + src/locales/lt.ts | 1 + src/locales/lv.ts | 1 + src/locales/nl.ts | 1 + src/locales/no.ts | 1 + src/locales/pl.ts | 1 + src/locales/pt.ts | 1 + src/locales/ro.ts | 1 + src/locales/sk.ts | 1 + src/locales/sl.ts | 1 + src/locales/sr.ts | 1 + src/locales/sv.ts | 1 + src/locales/tr.ts | 1 + src/locales/zh-hans.ts | 1 + src/locales/zh-hant.ts | 1 + src/test/zebraPrint.test.ts | 27 +++++++++------ 35 files changed, 94 insertions(+), 24 deletions(-) diff --git a/src/components/Output/PrintToZebraDialog.tsx b/src/components/Output/PrintToZebraDialog.tsx index f08489d1..d7a6b963 100644 --- a/src/components/Output/PrintToZebraDialog.tsx +++ b/src/components/Output/PrintToZebraDialog.tsx @@ -4,7 +4,6 @@ import { useT } from "../../lib/useT"; import { DialogShell } from "../ui/DialogShell"; import { discoverBrowserPrintDevices, - isConnectionRefused, sendViaBrowserPrint, sendViaNetwork, type BrowserPrintDevice, @@ -56,14 +55,20 @@ export function PrintToZebraDialog({ zpl, onClose }: Props) { async function handleNetworkSend() { persistNetwork(); setNetStatus({ type: "sending" }); - try { - await sendViaNetwork(ip.trim(), Number(port) || 9100, zpl); - setNetStatus({ type: "success", message: t.zebraPrint.success }); - } catch (e) { - const msg = isConnectionRefused(e) - ? t.zebraPrint.errorRefused - : t.zebraPrint.errorGeneric; - setNetStatus({ type: "error", message: msg }); + const result = await sendViaNetwork(ip.trim(), Number(port) || 9100, zpl); + switch (result.kind) { + case "responded": + setNetStatus({ type: "success", message: t.zebraPrint.success }); + return; + case "no_response": + // Raw-socket printers (port 9100) never reply with HTTP, so a + // timeout is the typical success case — but it is indistinguishable + // from an unreachable host. Report honestly instead of green-success. + setNetStatus({ type: "success", message: t.zebraPrint.sentNoResponse }); + return; + case "refused": + setNetStatus({ type: "error", message: t.zebraPrint.errorRefused }); + return; } } diff --git a/src/lib/zebraPrint.ts b/src/lib/zebraPrint.ts index d530813c..593a5aaf 100644 --- a/src/lib/zebraPrint.ts +++ b/src/lib/zebraPrint.ts @@ -45,21 +45,47 @@ export function isConnectionRefused(e: unknown): boolean { return e instanceof TypeError && /refused/i.test(e.message); } -// Chrome shows a Private Network Access permission prompt on first use. +/** + * Outcome of a direct network-print attempt. + * + * - `responded`: fetch completed with an HTTP response. Rare for raw-socket + * printers (port 9100), more typical for print servers / web frontends. + * - `no_response`: fetch threw without a connection-refused signal — most + * commonly a timeout. For raw-socket Zebra printers this is the *normal* + * success case (they read the bytes and never reply with HTTP), but the + * same exception is also raised when the host is unreachable. The browser + * cannot tell those apart, so the UI must surface this honestly rather + * than reporting an unverified success. + * - `refused`: TCP RST — host reachable but nothing listening on the port. + */ +export type NetworkPrintResult = + | { kind: "responded"; status: number } + | { kind: "no_response" } + | { kind: "refused" }; + +/** + * Direct raw-socket print attempt. Returns a Result rather than throwing + * because the success and unreachable-host paths raise the same exception + * (the printer never speaks HTTP back). The Browser-Print helpers above + * throw because they speak HTTP end-to-end and have no such ambiguity. + * + * Chrome shows a Private Network Access permission prompt on first use. + */ export async function sendViaNetwork( ip: string, port: number, zpl: string, -): Promise { +): Promise { try { - await fetch(`http://${ip}:${port}`, { + const res = await fetch(`http://${ip}:${port}`, { method: "POST", body: zpl, headers: { "Content-Type": "text/plain" }, signal: AbortSignal.timeout(4000), }); + return { kind: "responded", status: res.status }; } catch (e) { - // Timeout/abort is expected — printer never sends a valid HTTP response. - if (isConnectionRefused(e)) throw e; + if (isConnectionRefused(e)) return { kind: "refused" }; + return { kind: "no_response" }; } } diff --git a/src/locales/ar.ts b/src/locales/ar.ts index 41aca664..37173095 100644 --- a/src/locales/ar.ts +++ b/src/locales/ar.ts @@ -423,6 +423,7 @@ const ar = { noPrinters: 'لم يتم العثور على طابعات', agentNotFound: 'عميل Zebra Browser Print غير نشط. قم بتنزيله من zebra.com.', success: 'تم إرسال ZPL بنجاح', + sentNoResponse: 'تم الإرسال. تحقق من الطابعة.', errorRefused: 'الاتصال مرفوض — تحقق من IP والمنفذ', errorGeneric: 'فشل إرسال ZPL', httpsWarning: 'الصفحة على HTTPS — قد يحظر المتصفح الطباعة المباشرة عبر IP (محتوى مختلط).', diff --git a/src/locales/bg.ts b/src/locales/bg.ts index aa0c5f81..e1ebdf56 100644 --- a/src/locales/bg.ts +++ b/src/locales/bg.ts @@ -423,6 +423,7 @@ const bg = { noPrinters: 'Не са намерени принтери', agentNotFound: 'Агентът Zebra Browser Print не работи. Изтеглете от zebra.com.', success: 'ZPL изпратен успешно', + sentNoResponse: 'Изпратено. Проверете принтера.', errorRefused: 'Връзката е отказана — проверете IP и порт', errorGeneric: 'Неуспешно изпращане на ZPL', httpsWarning: 'Страницата е на HTTPS — директният печат по IP може да бъде блокиран от браузъра (смесено съдържание).', diff --git a/src/locales/cs.ts b/src/locales/cs.ts index 99ada681..d93effe9 100644 --- a/src/locales/cs.ts +++ b/src/locales/cs.ts @@ -423,6 +423,7 @@ const cs = { noPrinters: 'Nenalezeny žádné tiskárny', agentNotFound: 'Agent Zebra Browser Print není spuštěn. Stáhněte z zebra.com.', success: 'ZPL úspěšně odesláno', + sentNoResponse: 'Odesláno. Zkontrolujte tiskárnu.', errorRefused: 'Připojení odmítnuto — zkontrolujte IP a port', errorGeneric: 'Odeslání ZPL selhalo', httpsWarning: 'Stránka používá HTTPS — přímý tisk přes IP může být prohlížečem zablokován (smíšený obsah).', diff --git a/src/locales/da.ts b/src/locales/da.ts index ceba196e..2308af2d 100644 --- a/src/locales/da.ts +++ b/src/locales/da.ts @@ -423,6 +423,7 @@ const da = { noPrinters: 'Ingen printere fundet', agentNotFound: 'Zebra Browser Print-agenten kører ikke. Download fra zebra.com.', success: 'ZPL sendt', + sentNoResponse: 'Sendt. Kontrollér printeren.', errorRefused: 'Forbindelse nægtet — kontroller IP og port', errorGeneric: 'Afsendelse af ZPL mislykkedes', httpsWarning: 'Siden bruger HTTPS — direkte udskrivning via IP kan blive blokeret af browseren (blandet indhold).', diff --git a/src/locales/de.ts b/src/locales/de.ts index b278d645..8a3ddf05 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -135,6 +135,7 @@ const de = { noPrinters: 'Keine Drucker gefunden', agentNotFound: 'Zebra Browser Print Agent nicht gefunden. Download auf zebra.com.', success: 'ZPL erfolgreich gesendet', + sentNoResponse: 'Gesendet. Bitte am Drucker prüfen.', errorRefused: 'Verbindung abgelehnt — IP und Port prüfen', errorGeneric: 'Fehler beim Senden', httpsWarning: 'Seite läuft über HTTPS — direktes IP-Drucken kann vom Browser blockiert werden (Mixed Content).', diff --git a/src/locales/el.ts b/src/locales/el.ts index eaf6779c..b030d52c 100644 --- a/src/locales/el.ts +++ b/src/locales/el.ts @@ -423,6 +423,7 @@ const el = { noPrinters: 'Δεν βρέθηκαν εκτυπωτές', agentNotFound: 'Ο πράκτορας Zebra Browser Print δεν εκτελείται. Κατεβάστε από το zebra.com.', success: 'Το ZPL στάλθηκε επιτυχώς', + sentNoResponse: 'Στάλθηκε. Ελέγξτε τον εκτυπωτή.', errorRefused: 'Η σύνδεση απορρίφθηκε — ελέγξτε IP και θύρα', errorGeneric: 'Αποτυχία αποστολής ZPL', httpsWarning: 'Η σελίδα χρησιμοποιεί HTTPS — η άμεση εκτύπωση μέσω IP ενδέχεται να αποκλειστεί από τον φυλλομετρητή (μεικτό περιεχόμενο).', diff --git a/src/locales/en.ts b/src/locales/en.ts index 50f66c42..2caa32c2 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -135,6 +135,7 @@ const en = { noPrinters: 'No printers found', agentNotFound: 'Zebra Browser Print agent not running. Download it from zebra.com.', success: 'ZPL sent successfully', + sentNoResponse: 'Sent. Verify on the printer.', errorRefused: 'Connection refused — check IP and port', errorGeneric: 'Failed to send ZPL', httpsWarning: 'Page is on HTTPS — direct IP printing may be blocked by the browser (Mixed Content).', diff --git a/src/locales/es.ts b/src/locales/es.ts index bd3e57b2..89a51ecc 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -423,6 +423,7 @@ const es = { noPrinters: 'No se encontraron impresoras', agentNotFound: 'Agente Zebra Browser Print no activo. Descárgalo en zebra.com.', success: 'ZPL enviado correctamente', + sentNoResponse: 'Enviado. Verifica en la impresora.', errorRefused: 'Conexión rechazada — verifica IP y puerto', errorGeneric: 'Error al enviar ZPL', httpsWarning: 'La página está en HTTPS — la impresión directa por IP puede ser bloqueada por el navegador (contenido mixto).', diff --git a/src/locales/et.ts b/src/locales/et.ts index 6e76eff6..6d4915fc 100644 --- a/src/locales/et.ts +++ b/src/locales/et.ts @@ -423,6 +423,7 @@ const et = { noPrinters: 'Printereid ei leitud', agentNotFound: 'Zebra Browser Print agent ei tööta. Laadige alla aadressilt zebra.com.', success: 'ZPL edukalt saadetud', + sentNoResponse: 'Saadetud. Kontrolli printerit.', errorRefused: 'Ühendus keeldutud — kontrollige IP-d ja porti', errorGeneric: 'ZPL saatmine ebaõnnestus', httpsWarning: 'Leht kasutab HTTPS-i — otsene IP-printimistöö võib olla brauseri poolt blokeeritud (segasisaldus).', diff --git a/src/locales/fa.ts b/src/locales/fa.ts index 382c30ff..ea3f6843 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -423,6 +423,7 @@ const fa = { noPrinters: 'چاپگری یافت نشد', agentNotFound: 'عامل Zebra Browser Print در حال اجرا نیست. از zebra.com دانلود کنید.', success: 'ZPL با موفقیت ارسال شد', + sentNoResponse: 'ارسال شد. روی چاپگر بررسی کنید.', errorRefused: 'اتصال رد شد — IP و پورت را بررسی کنید', errorGeneric: 'ارسال ZPL ناموفق بود', httpsWarning: 'صفحه روی HTTPS است — چاپ مستقیم از طریق IP ممکن است توسط مرورگر مسدود شود (محتوای مختلط).', diff --git a/src/locales/fi.ts b/src/locales/fi.ts index 43c24da9..ac5acb81 100644 --- a/src/locales/fi.ts +++ b/src/locales/fi.ts @@ -423,6 +423,7 @@ const fi = { noPrinters: 'Tulostimia ei löydy', agentNotFound: 'Zebra Browser Print -agentti ei ole käynnissä. Lataa osoitteesta zebra.com.', success: 'ZPL lähetetty onnistuneesti', + sentNoResponse: 'Lähetetty. Tarkista tulostimelta.', errorRefused: 'Yhteys evätty — tarkista IP ja portti', errorGeneric: 'ZPL:n lähetys epäonnistui', httpsWarning: 'Sivu on HTTPS:llä — suora IP-tulostus saattaa olla selaimen estämä (sekasisältö).', diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 736cefde..c31dcd95 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -423,6 +423,7 @@ const fr = { noPrinters: 'Aucune imprimante trouvée', agentNotFound: 'Agent Zebra Browser Print non détecté. Téléchargez-le sur zebra.com.', success: 'ZPL envoyé avec succès', + sentNoResponse: 'Envoyé. Vérifie sur l\'imprimante.', errorRefused: 'Connexion refusée — vérifiez IP et port', errorGeneric: 'Échec de l’envoi ZPL', httpsWarning: 'La page est en HTTPS — l\'impression directe par IP peut être bloquée par le navigateur (contenu mixte).', diff --git a/src/locales/he.ts b/src/locales/he.ts index 4102d696..7b2df6b8 100644 --- a/src/locales/he.ts +++ b/src/locales/he.ts @@ -423,6 +423,7 @@ const he = { noPrinters: 'לא נמצאו מדפסות', agentNotFound: 'סוכן Zebra Browser Print אינו פועל. הורד מזברא.com.', success: 'ZPL נשלח בהצלחה', + sentNoResponse: 'נשלח. בדוק במדפסת.', errorRefused: 'החיבור סורב — בדוק IP ופורט', errorGeneric: 'שליחת ZPL נכשלה', httpsWarning: 'הדף נמצא על HTTPS — הדפסה ישירה דרך IP עלולה להיחסם על ידי הדפדפן (תוכן מעורב).', diff --git a/src/locales/hr.ts b/src/locales/hr.ts index 3a50ce9a..ee5ba20d 100644 --- a/src/locales/hr.ts +++ b/src/locales/hr.ts @@ -423,6 +423,7 @@ const hr = { noPrinters: 'Nisu pronađeni pisači', agentNotFound: 'Agent Zebra Browser Print nije pokrenut. Preuzmite sa zebra.com.', success: 'ZPL uspješno poslan', + sentNoResponse: 'Poslano. Provjerite na pisaču.', errorRefused: 'Veza odbijena — provjerite IP i port', errorGeneric: 'Slanje ZPL-a nije uspjelo', httpsWarning: 'Stranica koristi HTTPS — izravno ispisivanje putem IP-a može biti blokirano od preglednika (mješoviti sadržaj).', diff --git a/src/locales/hu.ts b/src/locales/hu.ts index 12b5d8bf..8022762c 100644 --- a/src/locales/hu.ts +++ b/src/locales/hu.ts @@ -423,6 +423,7 @@ const hu = { noPrinters: 'Nem találhatók nyomtatók', agentNotFound: 'A Zebra Browser Print ügynök nem fut. Töltse le a zebra.com oldalról.', success: 'ZPL sikeresen elküldve', + sentNoResponse: 'Elküldve. Ellenőrizze a nyomtatón.', errorRefused: 'Kapcsolat megtagadva — ellenőrizze az IP-t és a portot', errorGeneric: 'ZPL küldése sikertelen', httpsWarning: 'Az oldal HTTPS-en fut — az IP-n keresztüli közvetlen nyomtatást a böngésző blokkolhatja (vegyes tartalom).', diff --git a/src/locales/it.ts b/src/locales/it.ts index b34c4b3b..c9a127eb 100644 --- a/src/locales/it.ts +++ b/src/locales/it.ts @@ -423,6 +423,7 @@ const it = { noPrinters: 'Nessuna stampante trovata', agentNotFound: 'Agente Zebra Browser Print non attivo. Scaricalo su zebra.com.', success: 'ZPL inviato con successo', + sentNoResponse: 'Inviato. Verifica sulla stampante.', errorRefused: 'Connessione rifiutata — verifica IP e porta', errorGeneric: 'Invio ZPL non riuscito', httpsWarning: 'La pagina è in HTTPS — la stampa diretta tramite IP potrebbe essere bloccata dal browser (contenuto misto).', diff --git a/src/locales/ja.ts b/src/locales/ja.ts index 711a3ecb..af4e9f50 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -423,6 +423,7 @@ const ja = { noPrinters: 'プリンターが見つかりません', agentNotFound: 'Zebra Browser Print エージェントが起動していません。zebra.com からダウンロードしてください。', success: 'ZPL を送信しました', + sentNoResponse: '送信しました。プリンターで確認してください。', errorRefused: '接続が拒否されました — IP とポートを確認してください', errorGeneric: 'ZPL の送信に失敗しました', httpsWarning: 'ページが HTTPS 上にあります — ブラウザが直接 IP 印刷をブロックする場合があります(混在コンテンツ)。', diff --git a/src/locales/ko.ts b/src/locales/ko.ts index b6f454a6..5ed4edf0 100644 --- a/src/locales/ko.ts +++ b/src/locales/ko.ts @@ -423,6 +423,7 @@ const ko = { noPrinters: '프린터를 찾을 수 없습니다', agentNotFound: 'Zebra Browser Print 에이전트가 실행 중이 아닙니다. zebra.com에서 다운로드하세요.', success: 'ZPL이 전송되었습니다', + sentNoResponse: '전송됨. 프린터에서 확인하세요.', errorRefused: '연결이 거부되었습니다 — IP와 포트를 확인하세요', errorGeneric: 'ZPL 전송에 실패했습니다', httpsWarning: '페이지가 HTTPS 상에 있습니다 — 브라우저가 직접 IP 인쇄를 차단할 수 있습니다(혼합 콘텐츠).', diff --git a/src/locales/lt.ts b/src/locales/lt.ts index ab89239f..b6d3d3b2 100644 --- a/src/locales/lt.ts +++ b/src/locales/lt.ts @@ -423,6 +423,7 @@ const lt = { noPrinters: 'Spausdintuvų nerasta', agentNotFound: 'Zebra Browser Print agentas neveikia. Atsisiųskite iš zebra.com.', success: 'ZPL sėkmingai išsiųsta', + sentNoResponse: 'Išsiųsta. Patikrinkite spausdintuvą.', errorRefused: 'Ryšys atmestas — patikrinkite IP ir prievadą', errorGeneric: 'ZPL siuntimas nepavyko', httpsWarning: 'Puslapis naudoja HTTPS — tiesioginis spausdinimas per IP gali būti užblokuotas naršyklės (mišrus turinys).', diff --git a/src/locales/lv.ts b/src/locales/lv.ts index 7e9c013f..c18c7d0e 100644 --- a/src/locales/lv.ts +++ b/src/locales/lv.ts @@ -423,6 +423,7 @@ const lv = { noPrinters: 'Printeri nav atrasti', agentNotFound: 'Zebra Browser Print aģents nedarbojas. Lejupielādējiet no zebra.com.', success: 'ZPL veikmīgi nosūtīts', + sentNoResponse: 'Nosūtīts. Pārbaudiet printerī.', errorRefused: 'Savienojums atteikts — pārbaudiet IP un portu', errorGeneric: 'ZPL nosūtīšana neizdevās', httpsWarning: 'Lapa izmanto HTTPS — tiešā IP drukāšana var tikt bloķēta pārlūkprogrammā (jaukts saturs).', diff --git a/src/locales/nl.ts b/src/locales/nl.ts index 1a2a5b99..e89847a0 100644 --- a/src/locales/nl.ts +++ b/src/locales/nl.ts @@ -423,6 +423,7 @@ const nl = { noPrinters: 'Geen printers gevonden', agentNotFound: 'Zebra Browser Print-agent niet actief. Download op zebra.com.', success: 'ZPL succesvol verzonden', + sentNoResponse: 'Verzonden. Controleer op de printer.', errorRefused: 'Verbinding geweigerd — controleer IP en poort', errorGeneric: 'ZPL verzenden mislukt', httpsWarning: 'Pagina gebruikt HTTPS — direct afdrukken via IP kan worden geblokkeerd door de browser (gemengde inhoud).', diff --git a/src/locales/no.ts b/src/locales/no.ts index 3291ca55..c754541c 100644 --- a/src/locales/no.ts +++ b/src/locales/no.ts @@ -423,6 +423,7 @@ const no = { noPrinters: 'Ingen skrivere funnet', agentNotFound: 'Zebra Browser Print-agenten kjører ikke. Last ned fra zebra.com.', success: 'ZPL sendt', + sentNoResponse: 'Sendt. Sjekk på skriveren.', errorRefused: 'Tilkobling nektet — sjekk IP og port', errorGeneric: 'Sending av ZPL mislyktes', httpsWarning: 'Siden bruker HTTPS — direkte utskrift via IP kan bli blokkert av nettleseren (blandet innhold).', diff --git a/src/locales/pl.ts b/src/locales/pl.ts index 770f8731..cad41431 100644 --- a/src/locales/pl.ts +++ b/src/locales/pl.ts @@ -423,6 +423,7 @@ const pl = { noPrinters: 'Nie znaleziono drukarek', agentNotFound: 'Agent Zebra Browser Print nie działa. Pobierz ze strony zebra.com.', success: 'ZPL wysłany pomyślnie', + sentNoResponse: 'Wysłano. Sprawdź na drukarce.', errorRefused: 'Odmowa połączenia — sprawdź IP i port', errorGeneric: 'Błąd wysyłania ZPL', httpsWarning: 'Strona używa HTTPS — bezpośrednie drukowanie przez IP może zostać zablokowane przez przeglądarkę (mieszana treść).', diff --git a/src/locales/pt.ts b/src/locales/pt.ts index 4d51a728..d272e54a 100644 --- a/src/locales/pt.ts +++ b/src/locales/pt.ts @@ -423,6 +423,7 @@ const pt = { noPrinters: 'Nenhuma impressora encontrada', agentNotFound: 'Agente Zebra Browser Print não está em execução. Baixe em zebra.com.', success: 'ZPL enviado com sucesso', + sentNoResponse: 'Enviado. Verifique na impressora.', errorRefused: 'Conexão recusada — verifique IP e porta', errorGeneric: 'Falha ao enviar ZPL', httpsWarning: 'A página está em HTTPS — a impressão direta por IP pode ser bloqueada pelo navegador (conteúdo misto).', diff --git a/src/locales/ro.ts b/src/locales/ro.ts index 93ffa9b8..4404a6e1 100644 --- a/src/locales/ro.ts +++ b/src/locales/ro.ts @@ -423,6 +423,7 @@ const ro = { noPrinters: 'Nu s-au găsit imprimante', agentNotFound: 'Agentul Zebra Browser Print nu rulează. Descărcați de pe zebra.com.', success: 'ZPL trimis cu succes', + sentNoResponse: 'Trimis. Verifică pe imprimantă.', errorRefused: 'Conexiune refuzată — verificați IP şi portul', errorGeneric: 'Trimiterea ZPL a eşuat', httpsWarning: 'Pagina este pe HTTPS — imprimarea directă prin IP poate fi blocată de browser (conținut mixt).', diff --git a/src/locales/sk.ts b/src/locales/sk.ts index a097910e..f39a2dbb 100644 --- a/src/locales/sk.ts +++ b/src/locales/sk.ts @@ -423,6 +423,7 @@ const sk = { noPrinters: 'Neboli nájdené žiadne tlačiarne', agentNotFound: 'Agent Zebra Browser Print nie je spustený. Stiahnite zo zebra.com.', success: 'ZPL úspšne odoslané', + sentNoResponse: 'Odoslané. Skontrolujte na tlačiarni.', errorRefused: 'Pripojenie odmietnuté — skontrolujte IP a port', errorGeneric: 'Odoslanie ZPL zlyhalo', httpsWarning: 'Stránka beží cez HTTPS — priama tlač cez IP môže byť prehliadačom zablokovaná (zmiešaný obsah).', diff --git a/src/locales/sl.ts b/src/locales/sl.ts index 1038be7e..fee60b09 100644 --- a/src/locales/sl.ts +++ b/src/locales/sl.ts @@ -423,6 +423,7 @@ const sl = { noPrinters: 'Ni najdenih tiskalnikov', agentNotFound: 'Agent Zebra Browser Print ne deluje. Prenesite s zebra.com.', success: 'ZPL uspešno poslan', + sentNoResponse: 'Poslano. Preverite na tiskalniku.', errorRefused: 'Povezava zavrnjena — preverite IP in vrata', errorGeneric: 'Pošiljanje ZPL ni uspelo', httpsWarning: 'Stran uporablja HTTPS — neposredno tiskanje prek IP-ja je morda blokirano s strani brskalnika (mešana vsebina).', diff --git a/src/locales/sr.ts b/src/locales/sr.ts index 1a0774e8..ed634361 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -423,6 +423,7 @@ const sr = { noPrinters: 'Nisu pronađeni štampači', agentNotFound: 'Agent Zebra Browser Print nije pokrenut. Preuzmite sa zebra.com.', success: 'ZPL uspešno poslat', + sentNoResponse: 'Послато. Проверите на штампачу.', errorRefused: 'Veza odbijena — proverite IP i port', errorGeneric: 'Slanje ZPL-a nije uspelo', httpsWarning: 'Stranica koristi HTTPS — direktno štampanje putem IP može biti blokirano od strane pregledača (mešoviti sadržaj).', diff --git a/src/locales/sv.ts b/src/locales/sv.ts index db3cf45b..f88859ab 100644 --- a/src/locales/sv.ts +++ b/src/locales/sv.ts @@ -423,6 +423,7 @@ const sv = { noPrinters: 'Inga skrivare hittades', agentNotFound: 'Zebra Browser Print-agenten körs inte. Ladda ner från zebra.com.', success: 'ZPL skickades', + sentNoResponse: 'Skickat. Verifiera på skrivaren.', errorRefused: 'Anslutning nekad — kontrollera IP och port', errorGeneric: 'Det gick inte att skicka ZPL', httpsWarning: 'Sidan använder HTTPS — direktutskrift via IP kan blockeras av webbläsaren (blandat innehåll).', diff --git a/src/locales/tr.ts b/src/locales/tr.ts index b07ac490..b375eb63 100644 --- a/src/locales/tr.ts +++ b/src/locales/tr.ts @@ -423,6 +423,7 @@ const tr = { noPrinters: 'Yazıcı bulunamadı', agentNotFound: 'Zebra Browser Print aracısı çalışmıyor. zebra.com adresinden indirin.', success: 'ZPL başarıyla gönderildi', + sentNoResponse: 'Gönderildi. Yazıcıdan doğrulayın.', errorRefused: 'Bağlantı reddedildi — IP ve portu kontrol edin', errorGeneric: 'ZPL gönderilemedi', httpsWarning: 'Sayfa HTTPS üzerinde — doğrudan IP yazdırma tarayıcı tarafından engellenebilir (karma içerik).', diff --git a/src/locales/zh-hans.ts b/src/locales/zh-hans.ts index 781826f7..8ed5a2be 100644 --- a/src/locales/zh-hans.ts +++ b/src/locales/zh-hans.ts @@ -423,6 +423,7 @@ const zhHans = { noPrinters: '未找到打印机', agentNotFound: 'Zebra Browser Print 代理未运行。请从 zebra.com 下载。', success: 'ZPL 发送成功', + sentNoResponse: '已发送。请在打印机上确认。', errorRefused: '连接被拒绝 — 检查 IP 和端口', errorGeneric: '发送 ZPL 失败', httpsWarning: '页面在 HTTPS 上 — 浏览器可能阻止直接 IP 打印(混合内容)。', diff --git a/src/locales/zh-hant.ts b/src/locales/zh-hant.ts index 3d4ffe2d..ea7acfc3 100644 --- a/src/locales/zh-hant.ts +++ b/src/locales/zh-hant.ts @@ -423,6 +423,7 @@ const zhHant = { noPrinters: '未找到印表機', agentNotFound: 'Zebra Browser Print 代理程式未執行。請從 zebra.com 下載。', success: 'ZPL 傳送成功', + sentNoResponse: '已傳送。請在印表機上確認。', errorRefused: '連線遭拒 — 請確認 IP 和連接埠', errorGeneric: '傳送 ZPL 失敗', httpsWarning: '頁面在 HTTPS 上 — 瀏覽器可能封鎖直接 IP 列印(混合內容)。', diff --git a/src/test/zebraPrint.test.ts b/src/test/zebraPrint.test.ts index 47d24130..3008e55b 100644 --- a/src/test/zebraPrint.test.ts +++ b/src/test/zebraPrint.test.ts @@ -133,30 +133,37 @@ describe("sendViaNetwork", () => { expect(init.body).toBe("^XA^XZ"); }); - it("swallows a generic TypeError (printer gives no HTTP response)", async () => { + it("returns no_response on a generic TypeError (raw-socket printer's typical case)", async () => { vi.stubGlobal("fetch", mockFetchThrow(new TypeError("network error"))); - await expect(sendViaNetwork("192.168.1.50", 9100, "^XA^XZ")).resolves.toBeUndefined(); + await expect(sendViaNetwork("192.168.1.50", 9100, "^XA^XZ")).resolves.toEqual({ + kind: "no_response", + }); }); - it("swallows an AbortError (timeout — printer never responds)", async () => { + it("returns no_response on AbortError (timeout — host unreachable or printer silent)", async () => { const abort = new DOMException("signal timed out", "AbortError"); vi.stubGlobal("fetch", mockFetchThrow(abort)); - await expect(sendViaNetwork("192.168.1.50", 9100, "^XA^XZ")).resolves.toBeUndefined(); + await expect(sendViaNetwork("192.168.1.50", 9100, "^XA^XZ")).resolves.toEqual({ + kind: "no_response", + }); }); - it("re-throws TypeError with 'refused' in message", async () => { + it("returns refused on a TypeError with 'refused' in the message", async () => { vi.stubGlobal( "fetch", mockFetchThrow(new TypeError("Connection refused")), ); - await expect(sendViaNetwork("192.168.1.50", 9100, "^XA^XZ")).rejects.toThrow( - "Connection refused", - ); + await expect(sendViaNetwork("192.168.1.50", 9100, "^XA^XZ")).resolves.toEqual({ + kind: "refused", + }); }); - it("swallows a successful response (printer accepts data)", async () => { + it("returns responded with status when fetch resolves (e.g. print server)", async () => { vi.stubGlobal("fetch", mockFetch({})); - await expect(sendViaNetwork("192.168.1.50", 9100, "^XA^XZ")).resolves.toBeUndefined(); + await expect(sendViaNetwork("192.168.1.50", 9100, "^XA^XZ")).resolves.toEqual({ + kind: "responded", + status: 200, + }); }); }); From 6d466bfcc56a12fb82bcd7a098ab25531f0e1f89 Mon Sep 17 00:00:00 2001 From: u8array Date: Sun, 10 May 2026 14:00:27 +0200 Subject: [PATCH 2/2] fix(zebra-print): treat non-2xx responses as errors, fix sr Latin script Reviewer-spotted (gemini-code-assist on PR #47): - A print server / proxy that responds with 4xx or 5xx was previously shown as green-success because the dialog flagged any `responded` result as success. Gate the success path on 2xx; non-2xx surfaces errorGeneric. - The sentNoResponse translation in sr.ts was emitted in Cyrillic, but the rest of the zebraPrint block in that file is Latin script. Switch to Latin for in-block consistency. --- src/components/Output/PrintToZebraDialog.tsx | 8 +++++++- src/locales/sr.ts | 2 +- src/test/zebraPrint.test.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/Output/PrintToZebraDialog.tsx b/src/components/Output/PrintToZebraDialog.tsx index d7a6b963..6424e5fa 100644 --- a/src/components/Output/PrintToZebraDialog.tsx +++ b/src/components/Output/PrintToZebraDialog.tsx @@ -58,7 +58,13 @@ export function PrintToZebraDialog({ zpl, onClose }: Props) { const result = await sendViaNetwork(ip.trim(), Number(port) || 9100, zpl); switch (result.kind) { case "responded": - setNetStatus({ type: "success", message: t.zebraPrint.success }); + // 2xx only counts as success; print servers / proxies that respond + // with 4xx or 5xx must surface as an error rather than green-success. + if (result.status >= 200 && result.status < 300) { + setNetStatus({ type: "success", message: t.zebraPrint.success }); + } else { + setNetStatus({ type: "error", message: t.zebraPrint.errorGeneric }); + } return; case "no_response": // Raw-socket printers (port 9100) never reply with HTTP, so a diff --git a/src/locales/sr.ts b/src/locales/sr.ts index ed634361..88078a15 100644 --- a/src/locales/sr.ts +++ b/src/locales/sr.ts @@ -423,7 +423,7 @@ const sr = { noPrinters: 'Nisu pronađeni štampači', agentNotFound: 'Agent Zebra Browser Print nije pokrenut. Preuzmite sa zebra.com.', success: 'ZPL uspešno poslat', - sentNoResponse: 'Послато. Проверите на штампачу.', + sentNoResponse: 'Poslato. Proverite na štampaču.', errorRefused: 'Veza odbijena — proverite IP i port', errorGeneric: 'Slanje ZPL-a nije uspelo', httpsWarning: 'Stranica koristi HTTPS — direktno štampanje putem IP može biti blokirano od strane pregledača (mešoviti sadržaj).', diff --git a/src/test/zebraPrint.test.ts b/src/test/zebraPrint.test.ts index 3008e55b..5e656af4 100644 --- a/src/test/zebraPrint.test.ts +++ b/src/test/zebraPrint.test.ts @@ -165,6 +165,14 @@ describe("sendViaNetwork", () => { status: 200, }); }); + + it("preserves a non-2xx status so the caller can flag it as an error", async () => { + vi.stubGlobal("fetch", mockFetch({}, false)); + await expect(sendViaNetwork("192.168.1.50", 9100, "^XA^XZ")).resolves.toEqual({ + kind: "responded", + status: 500, + }); + }); }); // ── printPreview pure functions ───────────────────────────────────────────────