# Universal NFC A framework-agnostic NFC package for reading and writing NFC tags in web applications and Progressive Web Apps (PWAs). This library provides a consistent API for working with NFC across different environments, with a focus on ease of use and developer experience. [![npm version](https://img.shields.io/npm/v/universal-nfc.svg)](https://www.npmjs.com/package/universal-nfc) [![license](https://img.shields.io/npm/l/universal-nfc.svg)](https://github.com/yourusername/universal-nfc/blob/main/LICENSE) ## Features - 📱 Read NFC tags in web apps and PWAs - ✍️ Write data to NFC tags - 🌐 Framework-agnostic - works with React, Vue, Angular, vanilla JS, etc. - 📦 Lightweight, with no external dependencies - 📊 TypeScript support with comprehensive type definitions - 🔄 Simple and advanced APIs for different use cases - 🔌 Based on the Web NFC API standard ## Installation ```bash npm install universal-nfc ``` or ```bash yarn add universal-nfc ``` ## Platform Compatibility | Feature | Chrome for Android (89+) | Chrome for Desktop | Safari iOS | Firefox | Edge | | ----------- | ------------------------ | ------------------ | ---------- | ------- | ---- | | Reading NFC | ✅ | ❌ | ❌ | ❌ | ❌ | | Writing NFC | ✅ | ❌ | ❌ | ❌ | ❌ | **Requirements for Web NFC:** - Chrome 89+ on Android - HTTPS connection (or localhost for development) - Device with NFC hardware - NFC enabled in device settings ## Basic Usage ### Reading NFC Tags (Simple API) The simplest way to read NFC tags: ```javascript import { SimpleNfc } from "universal-nfc"; const nfcReader = new SimpleNfc(); async function startReading() { // First check if NFC is available const available = await nfcReader.isAvailable(); if (!available) { console.log("NFC is not available on this device/browser"); return; } try { // Start reading NFC tags with a callback await nfcReader.startReading((content, type) => { console.log(`Read ${type} content:`, content); // 'type' will be 'text', 'url', or 'other' if (type === "url") { // Handle URL window.open(content, "_blank"); } else { // Handle text or other content document.getElementById("result").textContent = content; } }); console.log("Scan started - tap an NFC tag"); } catch (error) { console.error("Error starting NFC scan:", error); } } function stopReading() { nfcReader.stopReading(); console.log("NFC reading stopped"); } // Call startReading() when your app is ready to scan // Call stopReading() when you want to stop scanning ``` ### Writing to NFC Tags ```javascript import { SimpleNfc } from "universal-nfc"; const nfcWriter = new SimpleNfc(); async function writeTextToTag() { try { await nfcWriter.writeText("Hello from Universal NFC!"); console.log("Tag written successfully! Tap a tag to write."); } catch (error) { console.error("Error writing to tag:", error); } } async function writeUrlToTag() { try { await nfcWriter.writeUrl("https://example.com"); console.log("URL written successfully! Tap a tag to write."); } catch (error) { console.error("Error writing URL to tag:", error); } } ``` ## API Reference ### Core API (Nfc class) The `Nfc` class provides comprehensive access to NFC functionality. ```javascript import { Nfc } from "universal-nfc"; const nfc = new Nfc(); ``` #### Methods - **isEnabled()**: `Promise<{enabled: boolean}>` - Check if NFC is available - **openSettings()**: `Promise` - Open NFC settings or provide guidance - **startScanSession(options?)**: `Promise` - Start scanning for NFC tags - `options.once`: Stop after first tag (default: false) - `options.scanSoundEnabled`: Play sound on detection (iOS only, default: false) - `options.alertMessageEnabled`: Show alert on start (iOS only, default: false) - **stopScanSession()**: `Promise` - Stop scanning for NFC tags - **write(options)**: `Promise` - Write NDEF data to a tag - **format()**: `Promise` - Format a tag - **erase()**: `Promise` - Erase a tag's content - **makeReadOnly()**: `Promise` - Make a tag read-only (permanent) - **addListener(eventName, callback)**: `Promise<{remove: () => Promise}>` - Register event listener - **removeAllListeners()**: `Promise` - Remove all event listeners #### Events - **tagDetected**: Fires when an NFC tag is detected - **nfcStatusChanged**: Fires when NFC availability changes ### Simple API (SimpleNfc class) The `SimpleNfc` class provides a simplified interface for common NFC operations. ```javascript import { SimpleNfc } from "universal-nfc"; const nfc = new SimpleNfc(); ``` #### Methods - **isAvailable()**: `Promise` - Check if NFC is available - **startReading(callback)**: `Promise` - Start reading tags with a simplified callback - `callback(content, type)`: Called with the tag's content and type ('text', 'url', or 'other') - **stopReading()**: `Promise` - Stop reading tags - **writeText(text)**: `Promise` - Write text to a tag - **writeUrl(url)**: `Promise` - Write URL to a tag ### Utilities (NfcUtils) ```javascript import { NfcUtils } from "universal-nfc"; ``` #### Methods - **createTextRecord(text, languageCode?)**: Create a text NDEF record - **createUriRecord(uri)**: Create a URI NDEF record - **createMessage(records)**: Create an NDEF message from records - **getTextFromTag(tag)**: Extract text content from a tag - **getUrlFromTag(tag)**: Extract URL content from a tag - **isWebNfcSupported()**: Check if Web NFC API is supported - **isNfcLikelyAvailable()**: Check if device likely has NFC hardware ## Advanced Usage ### Using the Core API for Tag Reading ```javascript import { Nfc } from "universal-nfc"; const nfc = new Nfc(); async function setupNfcReader() { try { const { enabled } = await nfc.isEnabled(); if (!enabled) { console.log("NFC is not enabled or supported"); return; } // Register tag detection handler await nfc.addListener("tagDetected", (tag) => { console.log("Tag ID:", tag.id); console.log("Technology types:", tag.techTypes); // Process NDEF messages if (tag.messages && tag.messages.length > 0) { for (const message of tag.messages) { for (const record of message.records) { console.log("Record type:", record.type); console.log("Record payload:", record.payload); // Handle different record types if (record.type === "T") { console.log("Text:", record.text || record.payload); } else if (record.type === "U") { console.log("URL:", record.uri || record.payload); } } } } }); // Start scanning await nfc.startScanSession(); console.log("NFC scanning started"); } catch (error) { console.error("NFC setup error:", error); } } function cleanupNfcReader() { nfc .stopScanSession() .then(() => nfc.removeAllListeners()) .then(() => { console.log("NFC reader cleaned up"); }) .catch(console.error); } ``` ### Writing Custom Record Types ```javascript import { Nfc, NfcTnf, NfcUtils } from "universal-nfc"; const nfc = new Nfc(); async function writeCustomData() { // Create a custom record const customRecord = { id: "", tnf: NfcTnf.MIME_MEDIA, type: "application/json", payload: JSON.stringify({ id: 123, name: "Custom Data" }), }; // Create a message with the custom record and a text record const textRecord = NfcUtils.createTextRecord("This tag contains custom data"); const message = NfcUtils.createMessage([textRecord, customRecord]); try { // Write the message to a tag await nfc.write({ message }); console.log("Tag written successfully! Tap a tag to write."); } catch (error) { console.error("Error writing tag:", error); } } ``` ## Handling NFC in a React Application ```jsx import React, { useState, useEffect } from "react"; import { SimpleNfc } from "universal-nfc"; function NfcReader() { const [isReading, setIsReading] = useState(false); const [isAvailable, setIsAvailable] = useState(false); const [tagContent, setTagContent] = useState(""); const [error, setError] = useState(""); const nfcReader = React.useMemo(() => new SimpleNfc(), []); useEffect(() => { // Check if NFC is available when component mounts nfcReader .isAvailable() .then((available) => { setIsAvailable(available); if (!available) { setError("NFC is not available on this device or browser"); } }) .catch((err) => { setError("Error checking NFC availability: " + err.message); }); // Cleanup when component unmounts return () => { if (isReading) { nfcReader.stopReading().catch(console.error); } }; }, [nfcReader]); const startReading = async () => { try { setError(""); setIsReading(true); await nfcReader.startReading((content, type) => { setTagContent(`${type}: ${content}`); }); } catch (err) { setError("Error starting NFC: " + err.message); setIsReading(false); } }; const stopReading = async () => { try { await nfcReader.stopReading(); setIsReading(false); } catch (err) { setError("Error stopping NFC: " + err.message); } }; return (

NFC Reader

{error &&
{error}
}
{isReading &&

Ready to scan: tap an NFC tag against your device

} {tagContent && (

Tag Content:

{tagContent}

)}
); } export default NfcReader; ``` ## Requirements for PWA Integration To use NFC in a Progressive Web App (PWA): 1. **HTTPS**: Your app must be served over HTTPS (except for localhost during development) 2. **Web App Manifest**: Include an appropriate manifest file: ```json { "name": "NFC Reader App", "short_name": "NFC App", "start_url": "/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#4285f4", "icons": [ { "src": "icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ``` 3. **Service Worker**: Register a service worker to make your app work offline ```javascript // register-sw.js if ("serviceWorker" in navigator) { window.addEventListener("load", () => { navigator.serviceWorker .register("/service-worker.js") .then((registration) => { console.log("Service Worker registered"); }) .catch((error) => { console.error("Service Worker registration failed:", error); }); }); } ``` 4. **Permission Policy**: You may need to include a permission policy header for NFC: ``` Permissions-Policy: nfc=self ``` ## Troubleshooting ### NFC Not Working 1. **Check Compatibility**: Ensure you're using Chrome 89+ on Android 2. **HTTPS Required**: Make sure your app is served over HTTPS (except on localhost) 3. **NFC Hardware**: Verify your device has NFC hardware 4. **NFC Enabled**: Ensure NFC is enabled in your device settings 5. **Permission**: The user must grant permission when prompted 6. **Tag Positioning**: Position the tag correctly against the NFC sensor ### Common Errors - **"NotSupportedError"**: Browser doesn't support Web NFC API - **"NotAllowedError"**: User denied permission or NFC is disabled - **"NetworkError"**: Problem communicating with the NFC tag - **"AbortError"**: The operation was cancelled - **"NotFoundError"**: No NFC tag in range or tag removed too quickly ## License MIT ## Credits This library is inspired by the API design of [@capawesome-team/capacitor-nfc](https://github.com/capawesome-team/capacitor-plugins/tree/main/packages/nfc), but implements a framework-agnostic solution based on the Web NFC API.