Connect to USB Card Readers with Websockets
You can connect to any USB card reader from any web browser using Websockets and NodeJS. Which means you can do EMV transactions from a Virtual Terminal. Find out how, below!
Last time, I showed how to set up USB connectivity via JavaScript using NodeJS and a module called node-hid. The result was that we were able to get programmatic access to USB devices, from JavaScript, in only about 75 lines of code. Our script implemented automatic device detection and connection, and got us a device handle with which to do reading and writing of USB data.
All of which is fine, if the only thing you need is programmatic access to USB data from within a Node script.
But what if you want to communicate that data to another process? What if (for example) you want to send USB data to a web browser? Or push it out to a server?
No problem. You can do it. It’s easy!
WEBSOCKETS TO THE RESCUE
The Websocket Protocol (IETF spec here) is one of the most amazingly useful Web standards to come down the pike in a long, long time. And happily, it’s implemented by all modern browsers. Plus, it’s available to Node scripts. Which means your Node code can talk to your browser scripts quite easily, if you just create a socket connection that bridges the two worlds.
On the Node side, you’re going to need to open an OS console and run npm install socket.io -g (one time only) to install the very widely used and justifiably popular socket.io module. After you do this, your scripts can call require("socket.io")
to take advantage of that module’s incredible power. (More of which, in just a second.)
On the browser side, you need to point to the socket.io.slim.js script, so that your web page can slurp Websocket data. (Don’t try to use the standard browser Websocket API for this, because the aforementioned socket.io module uses its own keep-alive scheme that isn’t known to your browser. To avoid conflicts, use the socket.io.slim.js client-side script. As a side-benefit, the script comes with polyfills that give older browsers Websocket compatibility.) The easiest way to get the client-side script is to drag it down from an edge server (CDN) by putting the following snippet in your web page:
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.3/socket.io.slim.js"></script>
With that script in place, your web page is ready to connect to sockets served by your very own socket server.
SERVING WEBSOCKETS
Let’s talk about how to set up a socket server in loopback mode (which is to say, on ws://localhost). It’s unbelievably simple, and incredibly handy for inter-process communication.
You need Node, of course, and you need the socket.io module (as described above). Then you need to launch Node and have it run a script like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
// =========== socket-provider ============ // Dependency: This module uses the socket.io module. // There is a call to require("socket.io") further below. /* Usage: SocketProvider = require('socket-provider'); var sp = new SocketProvider(); sp.setPort( 9001 ); var server = sp.createServer(); (clients can now connect on port 9001) In WEB PAGE (with socket.io.slim.js loaded), do: var port = 9001; var socket = io.connect('http://localhost:'+port); if ( socket ) socket.on('message', function(msg) { ...do something with msg... }); // Or send messages: socket.emit( 'echo', something ); TO CREATE A NODE CLIENT (running headless in Node!): socket = require( 'socket.io-client' ); client = socket( "ws://localhost:9001" ); client.on('message', handlerFunction ); // handle events client.emit( 'message', someObject ); // send messages */ class SocketProvider { constructor() { var PORT = null; this.setPort = function( p ) { PORT = p; }; this.createServer = function( port ) { // Create do-nothing http server var server = require('http').createServer(); // require the socket.io module and attach to server: var io = require('socket.io').listen(server); // on client connection we don't do much io.sockets.on('connection', function (connection) { console.log('A client connected...'); connection.emit('message', 'Connected!'); // Set up disconnect handler connection.on('disconnect', ()=> { console.log('Client disconnected'); } ); // broadcast 'echo' messages connection.on('echo', (m)=>{ io.sockets.emit('message', m); }); }); // on connection // OK, now let's listen for socket requests server.listen( port || PORT ); return io.sockets; // return instance of socket server }; // createServer() } // constructor }; // class |
Disregarding comments, we’re only looking at about 30 lines of code here. Conceptually, what’s going on is that we require('http')
so that we can set up a dummy HTTP server instance. Then we require('socket.io').listen(server)
so that the socket.io module can upgrade incoming HTTP requests to Websocket connections.
By default, Websocket connections enable full-duplex, message-based, point-to-point communication between connected parties. But point-to-point doesn’t mean messages automatically get broadcast to other connections. To enable broadcasting, we register a custom event listener, the on.('echo')
listener at line 58, which re-emits incoming “echo” messages onto all socket connections, as a new message of type ‘message’. So the rule here is: To do point-to-point messaging, use the ‘message’ event type, but to make sure the socket server passes your message on to all listeners, use the ‘echo’ message type. (This is our own convention, btw, not something imposed by socket.io.)
The final thing to notice is that the above code, by itself, doesn’t do anything, because it’s a class definition. You have to instantiate the class at runtime in order to use it. But that’s super-easy. The comments at the top of the code explain how to use the class. Plus, we’ll be seeing how to use it in upcoming blog posts.
Don’t miss the next few posts in this series, because we’re going to be doing contactless transactions against a live gateway in no time — all from the browser, using JavaScript! Check back soon!