Communicate an NFC reader with a web application

Gautier Darchen

  • 23 🎂
  • ​Fullstack dev at Takima since 2018
    Working at Cartier in a lab
    • ​React
    • Node.js
    • Java / Spring
    • Devops
  • Easily know the remaining tasks to be performed on a piece of jewelry for a given people
     
  • Bar code / QR code
    • Innovation & a lot of data to hold
       
  • RFID
    • Short-range
       
  • NFC ✅

NFC today

  • Omnipresent in our daily life
     
  • Contactless payment, access badge, etc.
     
  • Cheap (NTAG 213 : about $ 0.08 per chip)
     
  • Different types: frequency, encryption, etc.

Target application 🎯 

Loading page

Result page

Local server NFC Reader and web-sockets

1st iteration
 

Summary

  • Buy a NFC Reader 💸
    • ACR 122U — USB NFC Reader
      ​Advanced Card System Ltd.
       
  • Start a Node.js server on the client side to have access to plugged devices 🔌
     
  • Use a PC/SC library 📕
     
  • Communicate the read payload to the web app via web-sockets 🧦 

Client - server

  • Need asynchronous communication
    • polling
    • server-sent events
    • websockets
  • Choice of a library: Socket.io

Connection workflow

NTAG 213

Memory organization

Local server

// src/index.js
// ...

io.on('connection', (socket) => {

  socket.on('require-scan', () => {
    const nfc = new NFC();
    nfc.on('reader', (reader) => {
      const pageNumber = 4;
      const bufferLength = 48;

      reader.on('card', async (card) => {
        try {
          const data = await reader.read(pageNumber, bufferLength);
          const extractedPayload = data.toString().split('/')[1];

          socket.emit('nfc-tag-scanned', extractedPayload);
        } catch (err) {
          // boilerplate code to handle the error
        }
      });
    });
  });
});
      

Client app

// src/pages/home/components/HomeContainer.jsx

// ...

const HomeContainer = () => {
  const [socket, setSocket] = useState();
  const [readTag, setReadTag] = useState();

  useEffect(() => {
    setSocket(SocketIO.connect(APILocalSocketUrl));
  }, [APILocalSocketUrl]);

  useEffect(() => {
    if (!socket) {
      return () => {};
    }

    socket.emit("require-scan");
    socket.on("nfc-tag-scanned", payload => {
      setReadTag(payload);
    });

    return () => socket.close();
  }, [socket]);

  return <Home readTag={readTag} />;
};

export default HomeContainer;

Demo 1

Cons

  • Requires Node.js on the client 🛠
  • Requires the user to start the web-socket server 👷
  • Need to update on each client if the code changes for the web-socket server 🔄
  • Works well on servers, but we are working here for desktop apps
    • Change our distribution mode

Pros

  • Users always have access to the last version of the web app ⚡️

Key learnings

  • Easy installation 🛠️
     
  • Easy distribution 🚚
     
  • Desktop app 🖥️

How to do it with web technologies?  🤔

Electron application using a NFC Reader

2nd iteration
 

Electron

Build cross platform desktop apps with JavaScript, HTML, and CSS

  • Have a desktop app developed using web frameworks/libraries (like an "embedded browser")
     
  • Easy installation on the client side
     
  • Node.js server on the client-side
    • Access to the file-system, hardware, etc.
       
  • Atom, VS Code, Slack, etc.

Summary

  • Use the USB NFC Reader bought for solution #1
    • ACR 122U — USB NFC Reader
      ​Advanced Card System Ltd.
       
  • Use Electron to have access to plugged devices 🔌
     
  • Use a PC/SC library that we already master 📕

 

Use the library

const HomeContainer = () => {
  const nfc = new NFC();
  
  const [readTag, setReadTag] = useState(undefined);

  nfc.on("reader", reader => {
    console.log(`${reader.reader.name}  device attached`);
    
    const pageNumber = 4;
    const bufferLength = 48;

    reader.on("card", card => {
      console.log(`${reader.reader.name}  card detected`, card);
      reader.read(pageNumber, bufferLength).then(data => {
        const extractedPayload = data.toString().split("/")[1];
        setReadTag(extractedPayload);
      });
      
    });
  });

  return <Home readTag={readTag} />;
};

Demo 2

Limits

  • Requires a build for each target platform (on each release)
     
  • Requires a reinstall on each device after updates
    • Manage the packaging and installation
    • Slow and heavy release cycles
      • Micro application with the release process of heavy industrial softwares
         
  • Lots of configuration to use native modules
     
  • Choose CI/CD providers that support all OS

Dream solution

  • Web app 🌐
     
  • Nothing installed client-side 🤤

Using WebUSB and an Arduino board

3rd iteration
 

WebUSB API

  • Use an API implemented in the browser 🌐
     
  • "Plug and play" devices for web applications 🔌
     
  • Security: devices declare some "web origins" they are allowed to communicate with (white list) 🛡
    • Equivalent to CORS for HTTP requests
       
  • Native code & SDKs  ➡️ cross-platform and "web-ready" libraries 📚

W3C spec draft: wicg.github.io/webusb

WebUSB API

Workflow

NFC Reader

1/2

NFC Reader

  • About $50 per device 💵
     
  • Handicraft and not yet industrialized 👨‍🔬
    • Will be subcontracted

2/2

Arduino programming

Import libraries and configure the WebUSB Serial

// NFC Reading libraries
#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>
#include <NfcAdapter.h>

// WebUSB library
#include <WebUSB.h>

// Web origin
#define PROTOCOL_HTTP 0 /* http:// */
WebUSB WebUSBSerial(PROTOCOL_HTTP, "localhost:3000");

// NFC Shield configuration
PN532_I2C pn532_i2c(Wire);
NfcAdapter nfc = NfcAdapter(pn532_i2c);

#define BAUD_RATE 9600

Arduino programming

Reading loop

void loop() {
  if (nfc.tagPresent()){
    NfcTag tag = nfc.read();
    if (tag.hasNdefMessage()) { // If your tag has a message
      NdefMessage message = tag.getNdefMessage();

      // If you have more than 1 record then it will cycle through them
      int recordCount = message.getRecordCount();
      for (int i = 0; i < recordCount; i++) {
        NdefRecord record = message.getRecord(i);

        int payloadLength = record.getPayloadLength();
        byte payload[payloadLength];
        record.getPayload(payload);

        String payloadAsString = ""; // Processes the message as a string vs as a HEX value
        for (int c = 0; c < payloadLength; c++) {
          payloadAsString += (char)payload[c];
        }
        WebUSBSerial.write(payloadAsString.c_str());
      }
    }
  };

  WebUSBSerial.flush();
  delay(250);
}

Client live coding
👨‍💻

Working zone 🚧

  • Draft Community status (since Aug. 2019)
    • Enabled in Chrome 61 (Sept. 2017)
    • Disabled for several months (security)
    • Only available in secured context (HTTPs)




       

Production 🏭

Thank you! 🙏