Electron is a great framework to build cross platform desktop applications. We chose it to build a Point of Sale system for one of our clients. We were able to create a great User Interface in React.js, amazing hardware integration using the Node.js pre-built in. We were communicating with serial ports, working on top of ModBus protocol very comfortably. However, we ran into an issue when we battle tested our application in a harsher environment. Here, our basic printer was failing in this environment.
We were just using the in-built mainwindow.WebContents.print()
method in electron. We were facing a random crash of our application due to Crashing due to FD ownership violation
. It was a much deeper Kernel level issue and when we dug deeper into it there was no way to wrap this crash and do a soft retry as well.
This is the point where we decided to use the power of node in electron for printing.
Now, there are a couple of challenges here.1. Which module to use for printing?
2. How we are going to do print a nice UI (We had QR code)
1. What module to use for printing?We turned towards a module called node-printer.
It is reliable and is a native method wrapper for windows and POSIX(CUPS). We were using the CUPS driver for printing in our linux PoS systems.
2. How we are going to do print a nice UI (We had QR code)We understand that this should be extensible and at the same time easy to use for our developer.
a. Pass on the details from renderer to main process
b. Generate the QR code.
c. Generate the UI (pdf)
d. Print the file.
a. Pass on the details from renderer to main process
The classic way of passing the message from renderer to main process is via the electron pre-built IPC (Inter Process Communication).
In Main Process
main.js
electron.ipcMain.on("nodePrint", (event, arg) => {
nativePrint(arg);
});
const nativePrint = (data) => {
...
}
Preload.js
electron.contextBridge.exposeInMainWorld("mainAPI", {
send: (channel, data) => {
let validChannels = ["nodePrint"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
});
In Renderer Process
window.mainAPI.send('nodePrint', data);
This provides us the basic way for communicating from renderer to main process.
b. Generate the QR code
We used qrcode module for generating the QR code
qr.toFile(
'./qrcode.png',
qrCode, { type: "png" },
async function (err, url) {
if(err) return console.log("error occurred");
}
)
c. Generate the UI (pdf)We used puppeteer based HTML to Image generator node-html-to-image
const QrImage = fs.readFileSync('./qrcode.png');
const base64Image = new Buffer.from(image).toString('base64');
const dataURI = 'data:image/png;base64,' + base64Image
await nodeHtmlToImage({
output: './image.png',
html: `<html>
<body style="width: 650px; height: 2700px">
<img src="{{imageSource}}" align="center" style="width: 100%" />
</body>
</html>`,
content: { ...data, imageSource: dataURI }
});
d. Print the fileNow, it is very straight forward to print the image generated using the node-printer
var fileBuffer = fs.readFileSync('./test.png');
var jobFromBuffer = printer.printBuffer(fileBuffer);
In this method we added various levels of catch systems to identify the errors and re-print the information. This is slower than electron print
but this is reliable and we also battle tested it with successful results.
What are your thoughts on this method? Let us know in the comments.