{"_id":"4b-react-print","_rev":"13-15f4c221a99276f55d9210f4a2fcb7f1","name":"4b-react-print","dist-tags":{"latest":"0.9.0"},"versions":{"0.1.0":{"name":"4b-react-print","version":"0.1.0","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.1.0","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"5ea246c77e625c48fc95d7cce22c56c9ef60e260","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.1.0.tgz","fileCount":14,"integrity":"sha512-bPEmZYYw0aL3wv/rhD4saM/XAYZvc0pEXJ9b6Oe6zkDfnLaTck/B50n8SNdmmI/c2zZeChGIOxwjpl+oqKjsYw==","signatures":[{"sig":"MEYCIQCQFSd4VSkAXYlFWgN58X4YoDQh9tRY11MbDEivjt6t5wIhAMWASRlTuDA0WJi54WzrkBbUary8q80dikhxTApq8fRO","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":78276},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js","./styles.css":"./src/styles.css"},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"11.19.0","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.1.0_1788345348502_0.39531271402148116","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"4b-react-print","version":"0.2.0","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.2.0","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"2f4f286859e90b846f678216ee89920fec86fb94","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.2.0.tgz","fileCount":12,"integrity":"sha512-xZhGzwQ007kpywslRJkNWCs/+Q6WdjE/HsV3wP04Go0SXB23RK44zdNCNx0MWdVDfthkr+x6IiAYXnnZyDvU/w==","signatures":[{"sig":"MEUCIQCqFcq4etZ81KNgc2oey54E4nFlSGX5eWSHb+p+VGbSTgIgEpHFiV7rAQ1QwSLCt4k9Qae9dpCthhmd4ZPqdt5Qtsc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":71456},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"11.19.0","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.2.0_1788416401174_0.23013137535052497","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"4b-react-print","version":"0.2.1","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.2.1","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"239707d641354e980e493d9f0782e90bb3fcb3ff","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.2.1.tgz","fileCount":12,"integrity":"sha512-zomB7LZBlozrt4eioTbiXIsiORF3m8/ERE63R1R/LLqRGyISA9446atMC6g73CrdS3xM1TfWjiOBG8R8z3f+XQ==","signatures":[{"sig":"MEUCIEgwRTSIZ01C0WXy5HYmlOxAlz8hRe5GHzgxiJle6PKYAiEAoJlXAaP7Hso2QSq8ARlMlooo0UnPbAmVV1aST3V0eMU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":73673},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"11.19.0","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.2.1_1788416767229_0.2405784929294872","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"4b-react-print","version":"0.3.0","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.3.0","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"ac4cc4063e8a1b8f02084a3b1c4878351dd7563a","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.3.0.tgz","fileCount":12,"integrity":"sha512-quQJc2q2dlZ2wfCITmeYgBCg5CsCPI9oXsCPVD1pb6EoPl/ZwIny2MsWl4/nVCF6s9MyEjIAzNPuk5Vai/JVxA==","signatures":[{"sig":"MEQCICCPcSvPMx8pSVaX6OJeFc9mbolo2QApcAHzIa9bT6bkAiA+lfu3yQnQnMpIgeZJdAlLiZuLL86ff/Ip0RGPNrgURA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":80211},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"11.19.0","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.3.0_1788419311116_0.3621660862298899","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"4b-react-print","version":"0.4.0","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.4.0","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"12c50cc6cef1cefd583a18738c2cd1b8a0b5aee5","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.4.0.tgz","fileCount":12,"integrity":"sha512-k4Hv4pQkuf+AkbwMjODK2WpajArXIVe6nmSf54cmvAnP0bPpJnchQ76mVQco7qYjCgxIZxHXCcpq1bG9P88kFg==","signatures":[{"sig":"MEUCIQDVEyHBZQHIx7/JiRKNa+AppXk2n4yHc/cMKX8FDp0VYwIgXhxf53VReR4sC9cCHNEYFms9ioVtLHCh9JNI76GzVKk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":84704},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"11.19.0","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.4.0_1788425730138_0.17388822330965858","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"4b-react-print","version":"0.5.0","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.5.0","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"7acec2a109dba4f1129aa1e305aedf4851de037a","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.5.0.tgz","fileCount":12,"integrity":"sha512-7ryRngToPePiyCScWJC3hqj5Rfly+2y9URO/bzLCcGqj+Ulw41G21j2CeaXZ0LWRJFA+tkDk+Ed3JAtwGKdHnA==","signatures":[{"sig":"MEUCIQCEz0XfV3Vxx16sbGegKh2NyLmpFWhlK0HWWCVOpG2t+gIgWCxZ+XJlZTj6oTQGi2OXvslzFLLf4OGxIcaEFPR6xcw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":86439},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"12.0.2","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.5.0_1788433919884_0.9908132184641711","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"4b-react-print","version":"0.5.1","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.5.1","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"92733fa5e5e85aab5419e05112b50280975c9c34","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.5.1.tgz","fileCount":13,"integrity":"sha512-OSBoWh3V6wCw36PvvIFHwuvJi9UbVWX4I5xBTvJn75jluhGVubVG9Jokc4y38GdPw/GwKYLPfGi2Q+jbreKnHw==","signatures":[{"sig":"MEQCIGACKoOcRV53qM7tc/b6gOjFvoF3iEFqFUqT40rZLSkXAiAczyQBSSe+J9m37O4viy5QCca3AP89Er5YCiHhIaameg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":90313},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"12.0.2","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.5.1_1788435044352_0.5317352039773615","host":"s3://npm-registry-packages-npm-production"}},"0.5.7":{"name":"4b-react-print","version":"0.5.7","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.5.7","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"524c412bd34398a49bece9937ece69e397ca7deb","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.5.7.tgz","fileCount":13,"integrity":"sha512-fCiZhrSUeHfaYom468pnIMJGVlvF2KD/Wh1rdBvya0gtvC3UN88Q+R2o1YIyqSxUivVoqVgSYePM0HF5fhTaHw==","signatures":[{"sig":"MEQCIDKnr3GVzbxy9B9jqsY5LBEK7k5AhlwqetuIHhCUQzOZAiAkXvFLBGQxpQw+W9Z3Dzu2s0tzRhtaV8JgiufK+aTWWQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":93590},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"12.0.2","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.5.7_1788435227490_0.12990465760302472","host":"s3://npm-registry-packages-npm-production"}},"0.6.1":{"name":"4b-react-print","version":"0.6.1","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.6.1","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"23cb66589aedef7943b8d09f4402b0dc15c30c3a","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.6.1.tgz","fileCount":13,"integrity":"sha512-HS/dal0Edga/tFG446Gql1Ewu3ABp/GpKwrMy2kKKQ+bBd0qQn3gkSm3e7hcIhywt4EfrF2z1sXtmQYwsSzYxg==","signatures":[{"sig":"MEUCIQC1AP+TZI01SHj6VVjx0NSmR3ZheNM2rroxPfJjF9RK0wIgOxKOniAPiR3Jc26wuuLl49GM5vazQSWJE+W6CcjTgdM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":93590},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"12.0.2","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.6.1_1788435293605_0.4464349127666043","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"4b-react-print","version":"0.7.0","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.7.0","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"5868b83081bbe625c7baccb881c3ab86950c74ea","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.7.0.tgz","fileCount":13,"integrity":"sha512-YKLVwai006oDF1giOD0nj6zyQMkYO7bqvXXkE/7hzLQ2jc8KDyE59iF1kzyyVmrBUPUyEplkPMAMEHP0s+SqUA==","signatures":[{"sig":"MEQCIDFLK+xJMN03Lor5CseROPP4jrKp0GPq0C6as8SEGTG1AiArAIVgNIh/zOE89UWlcT1NPS9aiJLq7kEo+I+FyYlIag==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":98274},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"12.0.2","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.7.0_1788437017749_0.5451906167001248","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"4b-react-print","version":"0.8.0","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.8.0","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"c1c45f4e7926484cc5856be9c84199534161cc03","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.8.0.tgz","fileCount":14,"integrity":"sha512-2VN5RjdJU9Bncp73AE2n+KPDzTOcAj6gI3u649lZGF1kcgH4u404lBtAC8IyXJON09HgCIs2vS0eyK7hU/2JUA==","signatures":[{"sig":"MEYCIQDn2ExWMfJxD/k8Q0JdQ8b08xScoWeDHQu7Iqm5Oyza3wIhAMq/XIByl/7Kvl7kTxfYQQSwCOJg0Z2+zUoYyeMI76Ws","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":119103},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./vite":"./vite/plugin.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"12.0.2","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.15.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.8.0_1788507063124_0.4721376167529676","host":"s3://npm-registry-packages-npm-production"}},"0.8.1":{"name":"4b-react-print","version":"0.8.1","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"license":"MIT","_id":"4b-react-print@0.8.1","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"6ff45b63fb6e0580940a2f19fd154f7195bc88e1","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.8.1.tgz","fileCount":14,"integrity":"sha512-IwQir9JgeiK2GRte8PBBkM8hc++zHGx3JLRx3NmXizdqowcGb1JxwEGBMJg+E/c39mReqH2wtWant5W72/bmng==","signatures":[{"sig":"MEYCIQCujPQMwejSk5Hb0m+2E4/vA83CWpYHkqkM6udz+yMmGgIhANUVpshilppJbIOkTOViwIVc1oh15rpB8dmYQ0womMld","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":121080},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./vite":"./vite/plugin.js","./agent":"./agent/server.js"},"scripts":{"lint":"eslint ."},"_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"_npmVersion":"12.0.2","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.20.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"tmp":"tmp/4b-react-print_0.8.1_1788519236692_0.27794600622972854","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"_id":"4b-react-print@0.9.0","bin":{"4b-react-print-agent":"agent/cli.js"},"dist":{"shasum":"a443c54cc2a57ec8e94e1a20e20de40a6f7e608a","tarball":"https://registry.npmjs.org/4b-react-print/-/4b-react-print-0.9.0.tgz","fileCount":15,"integrity":"sha512-h+PaVhhGMJKv/jgxldwj3Mt45ePvuv2dkVP7fKz6TguUtSALLkreMyAcbVX0k0Qrk68V9hySaf+krzKjRc0Prw==","signatures":[{"sig":"MEUCIEMOGcWSjkqPDlvNVTneih7U/tICqoyhwcG7bVxFyLw9AiEAi0yqvYip+dfPHnGHzrfrHV6hvgw8c51W+2bx40vADBM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICZ3BqlRxXtlZKpZ0mWUDoS7FUEFYb08khVnZtzDBhitAiEApR/AsjugjwGceR86oEf8cD4up2TdsSfwzoF66YptpqQ="}],"unpackedSize":137984},"main":"./src/index.js","name":"4b-react-print","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./vite":"./vite/plugin.js","./agent":"./agent/server.js"},"license":"MIT","scripts":{"lint":"eslint ."},"version":"0.9.0","_npmUser":{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"},"keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"_npmVersion":"12.0.2","description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","directories":{},"maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"sideEffects":["*.css"],"_nodeVersion":"26.7.0","dependencies":{"cors":"^2.8.5","pdfkit":"^0.20.2","express":"^4.21.2"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.1","globals":"^16.5.0","@eslint/js":"^9.39.1","eslint-plugin-react-hooks":"^7.0.1"},"peerDependencies":{"react":">=18","react-to-print":">=3"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/4b-react-print_0.9.0_1789119690908_0.6877267302243009"}}},"time":{"created":"2026-09-02T10:35:48.401Z","modified":"2026-09-11T09:41:31.166Z","0.1.0":"2026-09-02T10:35:48.641Z","0.2.0":"2026-09-03T06:20:01.315Z","0.2.1":"2026-09-03T06:26:07.364Z","0.3.0":"2026-09-03T07:08:31.258Z","0.4.0":"2026-09-03T08:55:30.383Z","0.5.0":"2026-09-03T11:12:00.010Z","0.5.1":"2026-09-03T11:30:44.492Z","0.5.7":"2026-09-03T11:33:47.619Z","0.6.1":"2026-09-03T11:34:53.858Z","0.7.0":"2026-09-03T12:03:37.906Z","0.8.0":"2026-09-04T07:31:03.263Z","0.8.1":"2026-09-04T10:53:56.908Z","0.9.0":"2026-09-11T09:41:30.993Z"},"license":"MIT","keywords":["react","print","silent-print","kiosk-printing","cups","print-agent","react-to-print"],"description":"Silent printing for React — an in-app toggle, a local print agent, and a browser-dialog fallback","maintainers":[{"name":"connect.console","email":"shaileshkumargautam7783@gmail.com"}],"readme":"# 4b-react-print\n\nSilent printing for React — decided **in your app**, not by browser launch flags.\n\n```bash\nnpm install 4b-react-print\n```\n\n---\n\n## Why this exists\n\nA web page cannot suppress the print dialog. `window.print()` always prompts,\nand there is no permission to request. The usual workaround is to launch the\nbrowser with a kiosk-printing flag:\n\n| Browser       | Flag                                              |\n| ------------- | ------------------------------------------------- |\n| Chrome / Edge | `--kiosk-printing`                              |\n| Firefox       | `print.always_print_silent` in `about:config` |\n\nThose work, but they are a poor fit for a staffed print station:\n\n- **Invisible.** Nothing in the UI says whether silent printing is on.\n- **Per-profile.** A new machine, a new browser, or a fresh profile loses it.\n- **All-or-nothing.** Every site in that browser prints without a dialog.\n- **Unreadable.** The page cannot query the flag, so it can never tell the\n  operator the truth about what is about to happen.\n\n`4b-react-print` moves the decision into the application: a toggle the operator can\nsee, stored with the app, honoured by a small local agent — and an honest\nfallback to the ordinary print dialog when that agent is not reachable.\n\n## How it works\n\n```\n  your React app                          the print station\n ┌───────────────────────┐               ┌──────────────────────┐\n │ useSilentPrint(…)     │  POST /print  │  4b-react-print-agent       │\n │   ├ silent? ──────────┼──────────────►│    → PDF → lp / CUPS │\n │   └ else react-to-print               │  GET  /jobs/:id      │\n │        └ print dialog │◄──────────────┤    → job state       │\n └───────────────────────┘               └──────────────────────┘\n```\n\nTwo backends, one interface. The app detects which is available at runtime and\nnever branches on it:\n\n| Backend        | Silent | Job status | Requires                               |\n| -------------- | ------ | ---------- | -------------------------------------- |\n| Print agent    | yes    | yes        | the agent running on the print machine |\n| Browser dialog | no     | no         | nothing — always available            |\n\nIf the agent is not reachable, the job still prints via the dialog and the\nresult says `degraded: true`. **A print is never lost because silent printing\nwas unavailable.**\n\n---\n\n## Quick start\n\n### 1. Run the agent on the machine with the printer\n\n```bash\nnpx 4b-react-print-agent\n```\n\nIn development, let the dev server run it for you instead — one command, and\nnothing to remember to start:\n\n```js\n// vite.config.js\nimport { printAgent } from \"4b-react-print/vite\";\n\nexport default defineConfig({ plugins: [react(), printAgent()] });\n```\n\nIt follows the dev server's own binding: on `--host` the agent goes onto the LAN\ntoo, so a phone opening the Network address can reach it; without it the agent\nstays on loopback. See [The Vite plugin](#the-vite-plugin).\n\n### 2. Wrap your app\n\n```jsx\nimport { SilentPrintProvider } from \"4b-react-print\";\n\nexport default function App() {\n  return (\n    <SilentPrintProvider>\n      <YourApp />\n    </SilentPrintProvider>\n  );\n}\n```\n\n`agentUrl` is not needed: the agent is found by probing. Set it only to pin a\nspecific address.\n\nThe package ships no UI and no stylesheet — it is hooks only, so the toggle and\nstatus display look like the rest of your app. See\n[Building the controls](#building-the-controls) for a worked example.\n\n### 3. Print\n\nA station prints one sheet after another, so `useSilentPrintQueue` is the usual\nentry point — it serialises jobs and releases each one when the *printer* says\nit is finished, not on a timer:\n\n```jsx\nimport { useRef } from \"react\";\nimport { useSilentPrintQueue } from \"4b-react-print\";\n\nfunction BadgeStation({ placeholder }) {\n  const printRef = useRef();\n\n  const { enqueue, queueLength, isPrinting, status, currentImageUrl } =\n    useSilentPrintQueue({\n      contentRef: printRef, // printed by the dialog; artwork read from its <img>\n      paperSize: \"4x6in\",   // \"A4\" | \"letter\" | { width, height, unit } | …\n      silent: true,      // force it here; omit to follow the operator's toggle\n    });\n\n  return (\n    <>\n      <button onClick={() => enqueue(nextBadgeUrl())}>Print badge</button>\n      <span>{queueLength} waiting</span>\n\n      {/* While a job is in flight `status` is never null, so no fallback text\n          is needed — see \"The label, ready to render\". */}\n      {isPrinting && <p>{status.message}</p>}\n\n      {/* Bind the sheet to `currentImageUrl`. The queue waits for *this* image\n          to render and decode before handing the job over, which is what stops\n          sheet 2 printing sheet 1's artwork. */}\n      <div ref={printRef}>\n        <img src={currentImageUrl ?? placeholder} alt=\"\" />\n      </div>\n    </>\n  );\n}\n```\n\n`enqueue` takes a URL, or an object for per-job overrides\n(`{ imageUrl, paperSize, silent, copies }`), and returns how many jobs are\nwaiting behind it. Jobs that arrive mid-print wait their turn.\n\n#### Without the queue\n\n`useSilentPrint` is the primitive underneath — no queue, one `print()` you call\nyourself. It takes the same options, which the queue forwards untouched:\n\n```jsx\nimport { useRef } from \"react\";\nimport { useSilentPrint } from \"4b-react-print\";\n\nfunction Badge({ artwork }) {\n  const ref = useRef();\n\n  const { print, status, isPrinting, paperSize } = useSilentPrint({\n    // ── what to print ──────────────────────────────────────────────\n    contentRef: ref,        // printed by the dialog; artwork read from its <img>\n    imageUrl: artwork,      // optional — name the artwork directly instead\n    // getImageUrl: async () => fetchNextUrl(),   // or resolve it at print time\n\n    // ── how to print it ────────────────────────────────────────────\n    paperSize: \"4x6in\",     // \"A4\" | \"letter\" | { width, height, unit } | …\n    singlePage: true,       // default — clamp output to exactly one sheet\n    silent: undefined,      // leave unset to follow the user's toggle;\n                            // true/false forces it for this call site\n\n    // ── anything else goes to react-to-print untouched ─────────────\n    documentTitle: \"Badge\",\n    onAfterPrint: () => console.log(\"dialog closed\"),\n  });\n\n  return (\n    <>\n      <button onClick={() => print()} disabled={isPrinting}>\n        {isPrinting ? \"Printing…\" : `Print ${paperSize.css}`}\n      </button>\n\n      {status && <p>{status.message}</p>}\n\n      <div ref={ref}>\n        <img src={artwork} alt=\"\" />\n      </div>\n    </>\n  );\n}\n```\n\nEvery option is optional except a source for the artwork — `contentRef`,\n`imageUrl` or `getImageUrl`. The minimum that works:\n\n```jsx\nconst { print } = useSilentPrint({ contentRef: ref });\n```\n\nCalling it:\n\n```js\nconst result = await print();                    // uses the options above\nawait print({ silent: false });                  // dialog, just this once\nawait print({ imageUrl: other, paperSize: \"A4\" });// different artwork/stock\n\nresult; // { ok, mode: \"silent\" | \"dialog\", backend?, jobId?, degraded?, error? }\n```\n\n`mode` tells you which path actually ran. `degraded: true` means silent was\nwanted but nothing could honour it — the job still printed, via the dialog.\n\nMigrating from `react-to-print` is one line — every unrecognised option is\nforwarded to it untouched, so `documentTitle`, `fonts` and `nonce` keep working:\n\n```diff\n-const print = useReactToPrint({ contentRef, onAfterPrint });\n+const { print, status } = useSilentPrint({ contentRef, onAfterPrint });\n```\n\nNothing changes until the toggle is on **and** the agent is reachable.\n\n---\n\n## API\n\n### `useSilentPrint(options)`\n\nOptions are read at hook time, as in `react-to-print`.\n\n| Option          | Type                               | Default            |                                        |\n| --------------- | ---------------------------------- | ------------------ | -------------------------------------- |\n| `silent`      | `boolean`                        | provider toggle    | force silent on/off at this call site  |\n| `paperSize`   | name or`{width,height,unit}`     | `\"4x6in\"`        | stock for both paths                   |\n| `singlePage`  | `boolean`                        | `true`           | never print more than one sheet        |\n| `contentRef`  | `RefObject`                      | —                 | what the dialog prints; artwork source |\n| `imageUrl`    | `string`                         | from`contentRef` | artwork, named directly                |\n| `getImageUrl` | `() => string \\| Promise<string>` | —                 | resolved at print time                 |\n\nReturns:\n\n|                       |                                                               |\n| --------------------- | ------------------------------------------------------------- |\n| `print(overrides?)` | async →`{ ok, mode, backend?, jobId?, degraded?, error? }` |\n| `status`            | `{ state, percent, message, isTerminal }`, or `null`      |\n| `isPrinting`        | `true` until the job reaches a terminal state               |\n| `paperSize`         | the resolved size                                             |\n\n```js\nawait print();                                   // uses the hook's options\nawait print({ silent: false });                  // dialog, just this once\nawait print({ imageUrl: url, paperSize: \"A4\" }); // per-call overrides\n```\n\n### `useSilentPrintQueue(options)`\n\nOne print at a time, released by the printer rather than a timer. Takes\neverything `useSilentPrint` takes, plus:\n\n| Option              | Default    |                                          |\n| ------------------- | ---------- | ---------------------------------------- |\n| `cooldownMs`      | `20000`  | only when a job cannot be tracked        |\n| `dialogTimeoutMs` | `300000` | dialog never reported back (tab closed)  |\n| `onJobDone`       | —         | `(job, result)` after each job settles |\n\n```jsx\nconst { enqueue, queueLength, isPrinting, status, currentImageUrl } =\n  useSilentPrintQueue({ contentRef, paperSize: \"4x6in\" });\n\nenqueue(url);                 // hand it an image URL\nenqueue({ silent: false });   // or an object, to override\n\n<div ref={contentRef}><img src={currentImageUrl ?? placeholder} /></div>\n```\n\nJobs arriving mid-print wait their turn; each starts as soon as the printer is\nfinished with the last. Bind your preview to `currentImageUrl` and the screen\ncannot disagree with the sheet.\n\nThe hook waits for that image to be **rendered and loaded** before printing.\nWithout it, the dialog path — which prints whatever is inside `contentRef` —\nreproduces the *previous* sheet.\n\n| Returns                              |                                                                                               |\n| ------------------------------------ | --------------------------------------------------------------------------------------------- |\n| `enqueue(url \\| job?)`              | add a job; returns how many are waiting behind it (`0` if it started at once)               |\n| `queueLength`                      | jobs still waiting                                                                            |\n| `isPrinting`                       | a job is in flight                                                                            |\n| `currentJob` / `currentImageUrl` | what is printing now                                                                          |\n| `status`                           | `{ state, percent, message, isTerminal }` — the queue's own wording while it holds a sheet |\n| `clearQueue()`                     | drop everything still waiting                                                                 |\n| `print(overrides?)`                | bypass the queue entirely                                                                     |\n\n### `useSilentPrintSettings()`\n\nEverything needed to drive the toggle and show what is happening:\n\n```js\nconst {\n  settings,          // { silent, backend, printerName, copies, agentUrl }\n  update,            // (patch) => void\n  refresh,           // re-probe backends\n  detecting,\n  available,         // reachable backends\n  activeBackend,\n  canPrintSilently,  // false → the toggle cannot be honoured right now\n  printers,\n  job,\n  lastError,\n} = useSilentPrintSettings();\n```\n\n> Never present the toggle alone as proof that printing is silent. Check\n> `canPrintSilently`, and say so when it is `false`.\n\n### `<SilentPrintProvider>`\n\n| Prop              | Default                   |                                      |\n| ----------------- | ------------------------- | ------------------------------------ |\n| `agentUrl`      | `\"auto\"`                | where the agent listens — probed when unset |\n| `defaultSilent` | `false`                 | initial toggle state, first run only |\n| `storageKey`    | `4b-react-print.v1`     | localStorage key                     |\n\nDetection runs at mount, re-probes every 5s while the toggle is on but\nunsatisfied, and re-probes immediately after any silent-path failure — so the\nUI never advertises a backend that has gone away.\n\n---\n\n## Concepts\n\n### Where the agent is found\n\nThe agent has to be on the machine holding the printer, and which machine that\nis depends on how the app is served. Two arrangements are both ordinary, and\nthey want opposite answers:\n\n- **A dev server, or a single-box station.** The machine serving the page *is*\n  the machine with the printer. A phone opening `http://192.168.0.224:5173` has\n  to reach the agent at `192.168.0.224` — its own `127.0.0.1` is itself.\n- **A server handing the app to stations.** Each station has its own printer and\n  its own agent on loopback. The serving machine has no printer at all.\n\nThe page cannot tell these apart from its address, so it does not try. With\n`agentUrl` unset it asks the page's own host first, then loopback, stopping at\nwhichever answers.\n\nThe order matters more than it looks. A page on a LAN address asking for\n`127.0.0.1` is a **Local Network Access** request — browsers gate it behind a\npermission, and when it is refused they report it as a missing CORS header:\n\n```\nCross-Origin Request Blocked … (Reason: CORS header\n'Access-Control-Allow-Origin' missing). Status code: 200.\n```\n\nThat message names the wrong cause. The agent sent the header; the browser\nnever let the response count. Trying the page's own host first crosses no\nboundary, so the case where the machine serving the app is the machine printing\nnever makes that request at all. Loopback still follows for the station\ndeployment, where the crossing is unavoidable and the agent grants the\nprivate-network preflight for it.\n\nA page served from a public origin has only one candidate, its own loopback, so\na deployed station behaves exactly as it always has.\n\nSet `agentUrl` to pin one address. It is then used literally and nothing else is\ntried — an address someone named was meant, and quietly printing somewhere else\nwould be worse than not printing.\n\n| Export                   |                                                     |\n| ------------------------ | --------------------------------------------------- |\n| `agentUrlCandidates()` | the addresses that would be probed, in order         |\n| `resolveAgentUrl()`    | the single best guess, without probing               |\n| `AUTO_AGENT_URL`       | the `\"auto\"` sentinel `agentUrl` defaults to       |\n| `DEFAULT_AGENT_PORT`   | `4321`                                             |\n\n### Silent is a request, not a guarantee\n\n`silent: true` asks for silent printing. Without a reachable backend the job\ngoes through the dialog and the result reports `mode: \"dialog\", degraded: true`.\nResolution is per job, most specific first:\n\n```\nenqueue({ silent })  →  hook option  →  provider toggle\n```\n\nLeave it unset everywhere and the operator's toggle governs — the right default\nfor a staffed station.\n\n### Artwork\n\nA backend talking to the OS cannot print a React tree, so the silent path needs\nsomething rasterisable. Pass `imageUrl`, or let the hook take the first `<img>`\ninside `contentRef`. The dialog path always prints `contentRef` with its own\n`@media print` CSS.\n\n**The page sends the bytes, not a URL.** The browser holds the session, the\norigin and any certificate exceptions, so it is the side that can actually read\nthe artwork; an agent re-fetching the same URL from Node has none of that. Bytes\nare passed through unchanged rather than re-encoded through a canvas, which\nwould recompress the image and drop its colour profile.\n\n### Paper size\n\nResolved once, then handed to each layer in the form it needs: `@page` for the\ndialog, PDF points for the agent, `media=` for CUPS. That is why the two paths\ncannot end up on different stock.\n\nBuilt-in names: `4x6in`, `6x4in`, `5x7in`, `A4`, `A5`, `A6`, `letter`, `legal`.\nExplicit sizes take `{ width, height, unit }` with `in`, `mm`, `cm` or `pt`. An\nunknown name throws at render rather than printing a wrong sheet.\n\n### One sheet per job\n\nThe browser's print dialog has a **Pages** control — `All`, `Current`, a custom\nrange — and **no web API can set it**. A page whose content runs past one sheet\nwill hand the operator an `All` they have to notice and change, and a silent job\nhas no operator to notice at all. Either way you get two sheets where you wanted\none.\n\n`singlePage` (default `true`) closes that off from both ends:\n\n| Path   | What it does                                                                                                                                    |\n| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |\n| Dialog | clamps`body` to exactly the page box with `overflow: hidden`, and forbids page breaks — so there is only ever one page for `All` to mean |\n| Agent  | adds`-o page-ranges=1` to `lp` (a page range for SumatraPDF on Windows) — the spooler refuses to emit a second sheet                       |\n\nThe dialog side is prevention, not enforcement: it works by making overflow\nimpossible rather than by driving a control the page cannot reach. Content taller\nthan the sheet is **clipped**, which is the right trade for badges and labels —\na card cut off is obvious, a surprise second sheet is not.\n\nTurn it off with `singlePage: false` when a job genuinely spans pages:\n\n```js\nawait print({ singlePage: false });      // this job only\nuseSilentPrint({ singlePage: false });   // this call site\n```\n\nPassing your own `pageStyle` overrides the generated CSS entirely, so the\ndialog-side clamp is yours to reproduce; the agent-side `page-ranges` still\napplies.\n\n### Job status\n\n`lp` returns as soon as a job is spooled, which is not the same as paper coming\nout — so a submitted job reports `submitted`, and the provider polls until the\nprinter is genuinely finished.\n\n| `status.state` |                                                            |\n| ---------------- | ---------------------------------------------------------- |\n| `submitted`    | accepted by the spooler                                    |\n| `queued`       | waiting at the printer                                     |\n| `printing`     | on the roller                                              |\n| `completed`    | finished                                                   |\n| `dialog`       | went through the print dialog; nothing further is knowable |\n| `failed`       | silent path broke, fell back to the dialog                 |\n| `timeout`      | the printer stopped reporting — go and look at it         |\n| `unknown`      | left the queue without confirmation                        |\n\nTwo limits are worth wording carefully in your own UI:\n\n- CUPS files a **cancelled** job under completed alongside a successful one, so\n  an unconfirmed job reads as \"No longer in the queue\", not \"Printed\".\n- A stalled job usually shows up as the *printer* going `stopped` — a jam or an\n  empty tray — while the job itself still looks fine. That is surfaced\n  separately, with the reason CUPS gives.\n\nJob tracking needs CUPS; on Windows the agent reports `unknown`.\n\n#### The label, ready to render\n\n`status.message` is the state in operator language, so a call site does not\ncarry its own state-to-text map — and cannot miss a state:\n\n```jsx\nconst { status, isPrinting } = useSilentPrintQueue({ contentRef });\n\n{isPrinting && <span>{status.message}</span>}\n```\n\n| `status.state` | `status.message`                        |\n| ---------------- | ----------------------------------------- |\n| `submitted`    | Sent to the printer…                     |\n| `queued`       | Queued at the printer…                   |\n| `printing`     | Printing…                                |\n| `completed`    | Printed                                   |\n| `dialog`       | Waiting for the print dialog…            |\n| `failed`       | Silent print failed — using the dialog… |\n| `timeout`      | No response from the printer — check it  |\n| `unknown`      | Left the print queue                      |\n\n`useSilentPrintQueue` words two moments the job itself cannot describe:\n\n- while it holds the station after a sheet — `completed` reads\n  **\"Printed — preparing the next sheet…\"**, because the operator should wait\n  rather than reach for the print;\n- between dequeuing a job and the backend's first report, where `status.state`\n  is `preparing` and the message is the fallback below. `isPrinting` is already\n  true there, so a status bar bound to it is never blank.\n\nA state this version has no wording for falls back to\n`DEFAULT_JOB_MESSAGE` (\"Printing — please wait…\") — an older app stays\nreadable against a newer agent. Both maps are exported if you want your own\nwording, or another language:\n\n```js\nimport { JOB_MESSAGE, DEFAULT_JOB_MESSAGE } from \"4b-react-print\";\n```\n\n#### Progress, sent → printed\n\n`status.percent` is a number 0-100 across the job's life:\n\n| `status.state`                              | `status.percent` |\n| --------------------------------------------- | ------------------ |\n| `submitted`                                 | 25                 |\n| `queued`                                    | 50                 |\n| `printing`                                  | 75                 |\n| `completed`                                 | 100                |\n| `dialog` `failed` `timeout` `unknown` | `null`           |\n\n```jsx\nconst { status } = useSilentPrintQueue({ contentRef });\n\n{status?.percent != null ? (\n  <progress value={status.percent} max={100} />\n) : (\n  <span>{status?.state}</span>\n)}\n```\n\n**This is stage progress, not a measurement.** Nothing in the stack reports a\nsheet as it prints — not the browser, not CUPS, not the printer; they report\nonly that a sheet finished. So `percent` moves in four steps and is never\ninterpolated to look smoother than the information behind it. Treat it as \"how\nfar through the pipeline\", which is what an operator actually needs to know.\n\n`null` means progress is genuinely unknowable: a job handed to the print dialog\nreports nothing back, and a failed or timed-out job has no position. Show the\nstate label there rather than a bar stuck at some arbitrary value.\n\nThe mapping is exported as `JOB_PROGRESS` if you want different weightings:\n\n```js\nimport { JOB_PROGRESS } from \"4b-react-print\";\n```\n\nGenuine per-sheet percentages are only possible for multi-copy jobs, where CUPS\nreports `job-media-sheets-completed` against a known total. For single-sheet\nbadges there is nothing finer to report.\n\n---\n\n## The agent\n\nShips with the package. Run it on the machine with the printer:\n\n```bash\nnpx 4b-react-print-agent\n```\n\n```\nOptions\n  -p, --port <n>            Port to listen on            (default 4321)\n      --host <addr>         Address to bind              (default 127.0.0.1)\n      --allow-origin <o>    Restrict CORS to this origin (repeatable).\n                            Wildcards one label deep: https://*.example.com\n      --allow-local         Also allow pages served from this machine\n                            (localhost, 127.0.0.1 and this host's own LAN\n                            addresses, any port)\n      --max-artwork <size>  Request body limit           (default 32mb)\n  -h, --help                Show this message\n```\n\n`PORT`, `HOST` and `ALLOWED_ORIGINS` work too; flags win over environment.\n\nIt binds to **loopback** by default — the agent drives a physical printer and\nshould not be reachable from the network unless you say so. Bind elsewhere and\nit warns when no origin is allow-listed.\n\nAn origin may be exact, or name a wildcard label — for a deployment that mints\na hostname per build, listing them one at a time is not possible:\n\n```bash\n4b-react-print-agent --allow-origin \"https://*.example.com\"\n```\n\nThe wildcard stands for exactly one label and never crosses a dot, so\n`https://*.example.com` matches `https://preview-42.example.com` but not\n`https://a.b.example.com`, and not a lookalike domain that merely contains the\nname. The scheme must match too.\n\n**An allow-list written for production locks out your own dev server**, and the\nfailure is silent from the page — the browser blocks the response, the app\nreports no backend, and the station prints through the dialog as if the agent\nwere not there. Add `--allow-local` so one agent serves both:\n\n```bash\n4b-react-print-agent --allow-origin \"https://*.example.com\" --allow-local\n```\n\nThat accepts `http://localhost:<any port>`, `http://127.0.0.1:<any port>`, and\nthis host's **own LAN addresses**. A dev server prints two URLs:\n\n```\n  ➜  Local:   http://localhost:5173/\n  ➜  Network: http://192.168.0.224:5173/\n```\n\nBoth are the same page on the same machine, so both are allowed — and the\naddress is discovered from the interfaces rather than written down, because it\nchanges with DHCP and with the venue. It is re-read periodically, so a station\nthat gets a new address keeps printing without a restart. The port is a\nwildcard; `https://localhost:5173` (wrong scheme) is still refused.\n\nThis covers the host's own addresses, never the subnet: `http://192.168.0.99`\nis refused even on the same LAN.\n\n> **A different device cannot use this agent.** Open the app on a tablet and its\n> `127.0.0.1` is the tablet — there is no agent there, so it prints through the\n> dialog. Serving one agent to several devices means binding it to the network\n> (`--host 0.0.0.0`), allow-listing the origins, and pointing `agentUrl` at the\n> print machine. That trades away the loopback guarantee that makes a permissive\n> default safe, so do it deliberately.\n\nThe agent prints what it will accept as it starts, and warns once per origin it\nturns away:\n\n```\n4b-react-print agent listening on http://127.0.0.1:4321\nOrigins allowed: https://*.example.com, http://localhost:*, http://127.0.0.1:*\nRefused origin https://elsewhere.example — not in the allow-list.\n```\n\nEmbed it in an existing Node process instead:\n\n```js\nimport { startAgent, createAgent } from \"4b-react-print/agent\";\n\nawait startAgent({ port: 4321, allowedOrigins: [\"http://localhost:5180\"] });\n\napp.use(\"/print-agent\", createAgent());   // or mount the Express app yourself\n```\n\n### Endpoints\n\n| Route                | Returns                                                                           |\n| -------------------- | --------------------------------------------------------------------------------- |\n| `GET /health`      | `{ ok, canPrint, tool, printers, reason }` — what backend detection probes      |\n| `GET /printers`    | `{ printers: [{ name, isDefault }] }`                                           |\n| `GET /status`      | every printer's state plus the current queue                                      |\n| `POST /print`      | `{ jobId, state }` — body `{ imageData, printerName?, copies?, paperSize? }` |\n| `GET /jobs/:jobId` | `{ state, printer, printerState, detail }`                                      |\n\n`paperSize` arrives pre-resolved as `{ points: [w, h], cupsMedia }`, so the\nagent never reimplements the size table.\n\n### Platform support\n\n|               | Printing                                                                     | Job status         |\n| ------------- | ---------------------------------------------------------------------------- | ------------------ |\n| macOS / Linux | `lp` (CUPS)                                                                | full               |\n| Windows       | SumatraPDF — set`SUMATRA_PATH` if not in `C:\\Program Files\\SumatraPDF\\` | reports`unknown` |\n\n### The Vite plugin\n\nIn development the machine serving the app is the machine with the printer, so\nthe dev server may as well run the agent:\n\n```js\nimport { printAgent } from \"4b-react-print/vite\";\n\nexport default defineConfig({ plugins: [react(), printAgent()] });\n```\n\n`npm run dev` is then the whole setup. Started separately the agent is a second\nthing to remember, and forgetting it is invisible from the page — the job simply\nfalls back to the print dialog with nothing to say why.\n\n| Option             | Default                    |                                             |\n| ------------------ | -------------------------- | ------------------------------------------- |\n| `port`           | `4321`                   | port to listen on                            |\n| `host`           | follows the dev server     | `0.0.0.0` under `--host`, else loopback  |\n| `allowedOrigins` | `[]`                     | extra origins beyond the local ones          |\n| `allowLocal`     | `true`                   | accept pages served from this machine        |\n| `enable`         | `true`                   | set `false` to skip starting it            |\n\nIt follows the dev server's binding rather than choosing separately, because\n`--host` — the flag that lets another device open the app — is exactly the flag\nthat has to put the agent within that device's reach. Without it the agent stays\noff the network, which is the safer default: CORS governs browsers, but a\nrequest carrying no `Origin` at all is not a cross-origin request to police, so\nan agent on `0.0.0.0` can be driven by anything on the LAN.\n\nAn agent already on the port is reused rather than treated as a failure, and the\nplugin closes the one it started when the dev server closes. Dev only — a\nproduction build is served elsewhere, and the agent belongs where the printer\nis.\n\n### Keeping it running\n\nThe agent is a foreground process and dies with its terminal. For a permanent\nstation, run it under `launchd` (macOS), `systemd` (Linux) or as a Windows\nservice so it survives reboots. On macOS that is a LaunchAgent at\n`~/Library/LaunchAgents/`, with `RunAtLoad` to start it at login and\n`KeepAlive` to bring it back if it exits:\n\n```xml\n<key>ProgramArguments</key>\n<array>\n  <string>/opt/homebrew/bin/4b-react-print-agent</string>\n  <string>--allow-origin</string>\n  <string>https://*.example.com</string>\n</array>\n<key>RunAtLoad</key><true/>\n<key>KeepAlive</key><true/>\n```\n\nUse the absolute path from `which 4b-react-print-agent` — launchd has no shell\nand no user `PATH` — and set `EnvironmentVariables.PATH` so the Node the script\nwas installed against is findable.\n\n### Serving the app from a deployed origin\n\nNothing about the agent is deployed: it runs on the machine holding the\nprinter, the browser on that machine talks to `127.0.0.1`, and the request never\nleaves the box. You do not have to configure that difference — with `agentUrl`\nleft unset the page probes loopback and, when it was served from a private\naddress, the machine that served it, using whichever answers. A deployed origin\nis never private, so a deployed station only ever probes its own loopback.\n\nWhat changes is that the page is now HTTPS while the agent is HTTP:\n\n| Browser       | Reaching`http://127.0.0.1` from an HTTPS page                                                           |\n| ------------- | --------------------------------------------------------------------------------------------------------- |\n| Chrome / Edge | works — loopback counts as a trustworthy origin, and the agent grants Chrome's private-network preflight |\n| Firefox       | works, same treatment of loopback                                                                         |\n| Safari        | blocked — the job falls back to the print dialog                                                         |\n\nPut stations on Chrome or Edge. Chrome has also been moving local-network\naccess toward a one-time permission prompt, so test on the build the station\nactually runs rather than assuming it stays silent.\n\nThen name the deployed origin, since the default allows any:\n\n```bash\n4b-react-print-agent --allow-origin \"https://*.example.com\" --allow-origin http://localhost:5173\n```\n\n---\n\n## Building the controls\n\nThe package is hooks only — no components, no stylesheet. A print station's\ncontrols belong in the host app's design, and a prebuilt bar would either clash\nwith it or need theming hooks for every part. Everything you need is on\n`useSilentPrintSettings()`.\n\nA complete toggle, in about twenty lines:\n\n```jsx\nimport { useSilentPrintSettings } from \"4b-react-print\";\n\nfunction PrintControls() {\n  const { settings, update, canPrintSilently, activeBackend, detecting, job } =\n    useSilentPrintSettings();\n\n  // The toggle is on but nothing can honour it — say so, rather than letting\n  // the switch imply something that will not happen.\n  const unsupported = settings.silent && !canPrintSilently;\n\n  return (\n    <div>\n      <label>\n        <input\n          type=\"checkbox\"\n          checked={settings.silent}\n          onChange={(e) => update({ silent: e.target.checked })}\n        />\n        Silent print\n      </label>\n\n      <span>\n        {detecting\n          ? \"Detecting…\"\n          : unsupported\n            ? \"No agent reachable — will use the print dialog\"\n            : `via ${activeBackend.label}`}\n      </span>\n\n      {job && <span>{job.state}</span>}\n    </div>\n  );\n}\n```\n\nAdd a printer picker and copies from the same hook:\n\n```jsx\nconst { settings, update, printers, refresh } = useSilentPrintSettings();\n\n<select\n  value={settings.printerName}\n  onChange={(e) => update({ printerName: e.target.value })}\n>\n  <option value=\"\">System default</option>\n  {printers.map((p) => (\n    <option key={p.name} value={p.name}>\n      {p.displayName || p.name}{p.isDefault ? \" (default)\" : \"\"}\n    </option>\n  ))}\n</select>\n\n<input\n  type=\"number\"\n  min=\"1\"\n  value={settings.copies}\n  onChange={(e) => update({ copies: Number(e.target.value) || 1 })}\n/>\n\n<button onClick={refresh}>Re-detect</button>\n```\n\n> The one rule: never let the toggle alone imply that printing is silent. Check\n> `canPrintSilently` and tell the operator when it is `false`. A switch that\n> lies about what the printer will do is worse than no switch.\n\nHide your controls when printing, so they never land on the sheet:\n\n```css\n@media print {\n  .print-controls { display: none !important; }\n}\n```\n\n---\n\n## Diagnosing a station you cannot reach\n\n`--doctor` is for whoever has a terminal on the machine. Once the app is\ndeployed that is nobody: the operator is standing at the station and you are\nnot. The same answer is on the context, so the app can show it on the device\nwhere it is failing.\n\n```jsx\nimport { useSilentPrintSettings, formatDiagnostics } from \"4b-react-print\";\n\nfunction PrintSupport() {\n  const { diagnostics } = useSilentPrintSettings();\n  if (!diagnostics) return null;\n\n  const report = formatDiagnostics(diagnostics);\n  return (\n    <details>\n      <summary>Printing: {diagnostics.ok ? \"ready\" : \"not available\"}</summary>\n      <pre>{report}</pre>\n      <button onClick={() => navigator.clipboard.writeText(report)}>\n        Copy report\n      </button>\n    </details>\n  );\n}\n```\n\n`diagnostics` is structured as well as printable — `ok`, `reason`, `remedy`,\n`agentUrl`, `tried`, `agent.{version,platform,tool,printers}` and `page.origin`\n— so a station can also show just the one sentence an operator needs:\n\n```jsx\n{!diagnostics.ok && <p>{diagnostics.reason}</p>}\n```\n\nThe report distinguishes the two failures that look identical from the page and\nhave opposite fixes:\n\n| What the operator sees | What it means |\n| ---------------------- | ------------- |\n| `agent  none reachable` | the agent is not installed or not running **on that device** |\n| `agent  http://… (answered, unusable)` | the agent is running; the machine cannot print — the reason says which of CUPS, a printer, or SumatraPDF is missing |\n\nIt is plain text on purpose: it survives being pasted into a chat, read out over\na phone, or photographed — which is how this actually travels from a station to\nwhoever can fix it.\n\n## Troubleshooting\n\n| Symptom                                | Cause                                                                                      |\n| -------------------------------------- | ------------------------------------------------------------------------------------------ |\n| `No silent backend — using dialog`  | the agent is not running, or`agentUrl` is wrong                                          |\n| `NetworkError` / `Failed to fetch` | nothing is listening on the agent port                                                     |\n| `Artwork URL returned text/html`     | the URL served a page, not an image — often a dev server's SPA fallback on the wrong port |\n| `Could not read artwork`             | cross-origin without CORS, or a dead URL                                                   |\n| Silent toggle on but still prompting   | no backend reachable — check the chip, which always tells the truth                       |\n| Prints here, dialog on another machine | that machine cannot print — run `4b-react-print-agent --doctor` on it. Usually no CUPS, no printer configured, or Windows without SumatraPDF |\n| Works locally, refused once deployed   | the deployed origin is not allow-listed — a wildcard needs the agent at >= 0.5.0          |\n| Refused at the apex, fine on subdomains | `https://*.example.com` covers one label and not `https://example.com` — allow-list the apex too; the agent says so when it refuses one |\n| Another device on the LAN prints to the dialog | the agent is bound to loopback, so only the machine running it can reach it — start it with `--host 0.0.0.0`, or let the Vite plugin do it under `--host` |\n| Refused only in Safari                 | Safari blocks HTTPS pages from reaching`http://localhost` — use Chrome or Edge          |\n\nThe agent must be running for silent printing. That is not a limitation of this\nlibrary; it is the reason it exists.\n\n---\n\n## Requirements\n\n- React >= 18\n- `react-to-print` >= 3 (peer dependency — install it yourself)\n- Node >= 18 for the agent\n- CUPS for job status\n\n## Licence\n\nMIT\n","readmeFilename":"README.md"}