How to Build a Web App to Scan NFC Tags and Barcodes

Near-field communication (NFC) is a set of communication protocols that enables communication between two electronic devices over a distance of 4 cm (11⁄2 in) or less.1 We can store the data in an NFC tag in the NDEF format and then put a phone with NFC support to read its data.

Another invention for storing and sharing information is barcode. It uses lines and patterns to represent data and can be read with an optical scanner or a camera.

NFC has advantages over barcode like low light functionality, better security and less possibility to be deactivated with a pen. Barcode has advantages over NFC like longer scanning range, less cost, no surroundings interference and more supported devices.

We can use NFC tags and barcodes together for sharing data, like storing a password or private key in an NFC tag and using it to decrypt the encrypted content stored in a QR code or storing the same data in both of them for better data intactness and ease of retrieval.

In this article, we are going to build a web app to scan both NFC tags and barcodes.

Chrome for Android has added the NFC capability since version 89.2 We can use the NDEFReader interface to read and write NFC tags in browsers. As for barcode scanning, we are going to use Dynamsoft Barcode Reader.

Getting started with Dynamsoft Barcode Reader

Build a Web App to Scan NFC Tags and Barcode

Let’s do this in steps.

Scan NFC Tags

  1. Create a new HTML file with the following content:

    <!DOCTYPE html>
    <html>
    <head>
      <title>NFC and Barcode Scanner</title>
      <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
    </head>
    <body>
      <div class="app">
        <h2>NFC and Barcode Scanner</h2>
        <div>
          <button id="NFC-btn" onclick="toggleNFCScanning();">Scan NFC Tags</button>
        </div>
        <div>
          NFC Results:
          <ol id="NFC-results"></ol>
        </div>
      </div>
      <script type="text/javascript">
      </script>
    </body>
    </html>
    

    It has a button to start scanning NFC tags and displays the records in an ordered list.

  2. Check if NFC is supported. If not, disable the button.

    checkIFNFCSupported();
    function checkIFNFCSupported(){
      if (!("NDEFReader" in window)) {
        alert("NFC is not supported.");
        document.getElementById("NFC-btn").disabled = true;
      }
    }
    
  3. Add a scanNFCTags function to start scanning NFC tags.

    let ndef;
    let abortController;
    let NFCResults = [];
    async function scanNFCTags(){
      if (!ndef) {
        abortController = new AbortController();
        abortController.signal.onabort = event => {
          // All NFC operations have been aborted.
          console.log(event);
        };
        ndef = new NDEFReader();
        ndef.onreadingerror = () => {
          console.log("Cannot read data from the NFC tag. Try another one?");
        };
        ndef.onreading = event => {
          console.log("NDEF message read.");
          console.log(event);
          NFCResults = [];
          NFCResults = NFCResults.concat(event.message.records);
          displayNFCResults();
        };
      }
      ndef.scan({ signal: abortController.signal }).then(() => {
        console.log("Scan started successfully.");
      }).catch(error => {
        console.log(`Error! Scan failed to start: ${error}.`);
      });
    }
    

    We can abort the scanning using the AbortController:

    abortController.abort();
    
  4. Define the toggleNFCScanning function to toggle the scanning.

    function toggleNFCScanning(){
      const btn = document.getElementById("NFC-btn");
      if (btn.innerText === "Scan NFC Tags") {
        btn.innerText = "Stop Scanning NFC Tags";
        scanNFCTags();
      }else{
        btn.innerText = "Scan NFC Tags";
        abortController.abort();
      }
    }
    
  5. Display the NFC records in a list.

    function displayNFCResults(){
      const ol = document.getElementById("NFC-results");
      ol.innerHTML = "";
      for (let index = 0; index < NFCResults.length; index++) {
        const record = NFCResults[index];
        const buf = record.data.buffer;
        const str = new TextDecoder().decode(buf);
        console.log(str);
        const li = document.createElement("li");
        li.innerText = str;
        ol.appendChild(li);
      }
    }
    

Scan Barcodes

  1. Include Dynamsoft Barcode Reader and Dynamsoft Camera Enhancer in the head.

    <script src="https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.2/dist/dbr.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@3.2.0/dist/dce.js"></script>
    
  2. Add a button to start scanning barcodes and a list for displaying the barcode results.

    <div class="app">
      <h2>NFC and Barcode Scanner</h2>
      <div>
        <label>Status:</label>
        <span id="status"></span>
      </div>
      <div>
        <button id="NFC-btn" onclick="toggleNFCScanning();">Scan NFC Tags</button>
        <button id="barcode-btn" onclick="scanBarcodes();">Scan Barcodes</button>
      </div>
      <div>
        NFC Results:
        <ol id="NFC-results"></ol>
      </div>
      <div>
        Barcode Results:
        <ol id="barcode-results"></ol>
      </div>
    </div>
    
  3. Add a scanner element.

    <div class="scanner" style="display:none;">
      <svg class="dce-bg-loading" viewBox="0 0 1792 1792"><path d="M1760 896q0 176-68.5 336t-184 275.5-275.5 184-336 68.5-336-68.5-275.5-184-184-275.5-68.5-336q0-213 97-398.5t265-305.5 374-151v228q-221 45-366.5 221t-145.5 406q0 130 51 248.5t136.5 204 204 136.5 248.5 51 248.5-51 204-136.5 136.5-204 51-248.5q0-230-145.5-406t-366.5-221v-228q206 31 374 151t265 305.5 97 398.5z"/></svg>
      <svg class="dce-bg-camera" viewBox="0 0 2048 1792"><path d="M1024 672q119 0 203.5 84.5t84.5 203.5-84.5 203.5-203.5 84.5-203.5-84.5-84.5-203.5 84.5-203.5 203.5-84.5zm704-416q106 0 181 75t75 181v896q0 106-75 181t-181 75h-1408q-106 0-181-75t-75-181v-896q0-106 75-181t181-75h224l51-136q19-49 69.5-84.5t103.5-35.5h512q53 0 103.5 35.5t69.5 84.5l51 136h224zm-704 1152q185 0 316.5-131.5t131.5-316.5-131.5-316.5-316.5-131.5-316.5 131.5-131.5 316.5 131.5 316.5 316.5 131.5z"/></svg>
      <div class="dce-video-container"></div>
      <div class="dce-scanarea">
      </div>
      <div class="sel-container">
        <select class="dce-sel-camera"></select>
        <select class="dce-sel-resolution"></select>
      </div>
      <button class="close-btn" onclick="stopBarcodeScan();">Close</button>
    </div>
    

    Styles:

    @keyframes dce-rotate{from{transform:rotate(0turn);}to{transform:rotate(1turn);}}
    @keyframes dce-scanlight{from{top:0;}to{top:97%;}}
    .scanner{width:100%;height:100%;min-width:100px;min-height:100px;background:#eee;position:absolute;left:0;top:0;}
    .dce-bg-loading{animation:1s linear infinite dce-rotate;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;}
    .dce-bg-camera{display:none;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;}
    .dce-video-container{position:absolute;left:0;top:0;width:100%;height:100%;}
    .dce-scanarea{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;}
    .sel-container{position: absolute;left: 0;top: 0;}
    .sel-container .dce-sel-camera{display:block;}
    .sel-container .dce-sel-resolution{display:block;margin-top:5px;}
    .sel-container {display:block;margin-top:5px;}
    .close-btn{position: absolute;right: 0;top: 0;}
    
  4. Initialize Dynamsoft Barcode Reader and Dynamsoft Camera Enhancer. You may need to apply for a license to use the barcode reader.

    init();
    async function init() {
      Dynamsoft.DBR.BarcodeReader.license = 'DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=='; //one-day trial license
      reader = await Dynamsoft.DBR.BarcodeReader.createInstance();
      camera = await Dynamsoft.DCE.CameraEnhancer.createInstance();
      await camera.setUIElement(document.getElementsByClassName("scanner")[0]); //bind the scanner element we defined
      camera.setScanRegion({
        regionLeft:0,
        regionTop:35,
        regionRight:100,
        regionBottom:65,
        regionMeasuredByPercentage: 1
      });
    }
    
  5. Open the camera and start scanning barcodes when the scan button is clicked. Stop scanning if the close button is clicked.

    async function scanBarcodes(){
      await camera.open(true);
      startProcessingLoop();
    }
    
    function stopBarcodeScan(){
      stopProcessingLoop();
      camera.close(true);
    }
    
    function startProcessingLoop(){
      stopProcessingLoop();
      interval = setInterval(captureAndDecode,100); // set an interval to read barcodes
    }
    
    function stopProcessingLoop(){
      if (interval) {
        clearInterval(interval);
        interval = undefined;
      }
      processing = false;
    }
       
    async function captureAndDecode() {
      if (!camera || !reader) {
        return
      }
      if (camera.isOpen() === false) {
        return;
      }
      if (processing == true) {
        return;
      }
      processing = true; // set decoding to true so that the next frame will be skipped if the decoding has not completed.
      let frame = camera.getFrame();
      if (frame) {
        let results = await reader.decode(frame);
        if (results.length > 0) { //if barcodes are found, stop scanning and display the results.
          barcodeResults = results;
          displayBarcodeResults();
          stopBarcodeScan();
        }
        processing = false;
      }
    };
    
  6. The barcode results are displayed in a list.

    function displayBarcodeResults(){
      const ol = document.getElementById("barcode-results");
      ol.innerHTML = "";
      for (let index = 0; index < barcodeResults.length; index++) {
        const result = barcodeResults[index];
        const li = document.createElement("li");
        const container = document.createElement("div");
        const span = document.createElement("span");
        span.innerText = result.barcodeText;
        container.appendChild(span);
        li.appendChild(container);
        ol.appendChild(li);
      }
    }
    

All right, we’ve now finished writing the web app to scan both NFC tags and barcodes.

Next, we can add some extra features.

  1. Add a button to write the NFC record with the barcode result for every list item.

     function displayBarcodeResults(){
       const ol = document.getElementById("barcode-results");
       ol.innerHTML = "";
       for (let index = 0; index < barcodeResults.length; index++) {
         const result = barcodeResults[index];
         const li = document.createElement("li");
         const container = document.createElement("div");
         const span = document.createElement("span");
         span.innerText = result.barcodeText;
         container.appendChild(span);
    +    if ("NDEFReader" in window) {
    +      const btn = document.createElement("button");
    +      btn.innerText = "Write to NFC Tag"
    +      btn.addEventListener('click',function(){
    +        writeNFCTag(result.barcodeText);
    +      });
    +      container.appendChild(btn);
    +    }
         li.appendChild(container);
         ol.appendChild(li);
       }
     }
    

    The writeNFCTag function:

    function writeNFCTag(message){
      alert("Put the device close to the tag to write");
      const ndef = new NDEFReader();
      ndef.write(
        message
      ).then(() => {
        alert("Message written.");
      }).catch(error => {
        alert(`Write failed :-( try again: ${error}.`);
      });
    }
    
  2. Decrypt the barcode’s content if it is encrypted using AES. The key is the value we get from the NFC tag. CryptoJS is used for decryption.

    function decrypt(){
      if (NFCResults.length>0 && barcodeResults.length>0) {
        const message = barcodeResults[0].barcodeText;
        const record = NFCResults[0];
        const buf = record.data.buffer;
        const key = new TextDecoder().decode(buf);
        const bytes = CryptoJS.AES.decrypt(message,key);
        const originalText = bytes.toString(CryptoJS.enc.Utf8);
        document.getElementById("decrypted").innerText = originalText;
      }else{
        alert("Please scan the NFC tag and barcode first.");
      }
    }
    

Source Code

You can find the source code of the demo in the following repo: https://github.com/tony-xlh/NFC-Barcode-Scanner/

It has a scanner to scan NFC tags and barcodes as well as a generator to create an encrypted QR code.

References